lanka 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_extend/index.d.ts +53 -1
- package/dist/_extend/index.js +39 -0
- package/dist/_extend/index.js.map +1 -1
- package/dist/viewmodel/index.d.ts +40 -11
- package/dist/viewmodel/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/lanka-core/SKILL.md +1 -1
- package/skills/lanka-core/reference.md +1 -1
- package/skills/lanka-packages/SKILL.md +1 -1
package/dist/_extend/index.d.ts
CHANGED
|
@@ -113,6 +113,58 @@ interface ILankaAccessTracker<TState extends object> {
|
|
|
113
113
|
*/
|
|
114
114
|
declare const createLankaAccessTracker: <TState extends object>(viewModel: ILankaReadableVM<TState>) => ILankaAccessTracker<TState>;
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* A ViewModel that is also the call its binding reads it with.
|
|
118
|
+
*
|
|
119
|
+
* ```ts
|
|
120
|
+
* // inside a binding: the framework's own read, pre-applied
|
|
121
|
+
* const useTodoVM = createLankaCallableVM<typeof todoVM, TMyCall>(todoVM, (selector) =>
|
|
122
|
+
* useLankaVM(todoVM, selector),
|
|
123
|
+
* );
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* ## Why this is in core and not in a binding
|
|
127
|
+
*
|
|
128
|
+
* Five bindings publish core's six ViewModel factories under core's own names,
|
|
129
|
+
* each one already wearing that framework's read — so a consumer moves a
|
|
130
|
+
* declaration by changing the import line. What every one of them needs to do
|
|
131
|
+
* that is the same object: a function that is ALSO the ViewModel, so
|
|
132
|
+
* `useTodoVM()` reads and `useTodoVM.getState()` does what it always did.
|
|
133
|
+
*
|
|
134
|
+
* The forwarding is the part that is easy to get subtly wrong — which members
|
|
135
|
+
* belong to the function, what `in` must answer, what a lazy ViewModel does with
|
|
136
|
+
* a symbol — and five copies of it would be five packages diverging on a
|
|
137
|
+
* question that has one answer. It lives here for the reason
|
|
138
|
+
* `createLankaAccessTracker` does: what a binding sees is then lanka's behaviour
|
|
139
|
+
* rather than that binding author's reading of it.
|
|
140
|
+
*
|
|
141
|
+
* ## What it does NOT do
|
|
142
|
+
*
|
|
143
|
+
* It adds no state and no second store. The Proxy forwards rather than copying,
|
|
144
|
+
* so a LAZY ViewModel still builds on first access: reading `useTodoVM.name`
|
|
145
|
+
* answers from the config and constructs nothing. `TCall` is the binding's own
|
|
146
|
+
* call signature, because what the call answers is the framework's idea of
|
|
147
|
+
* reactivity and the one thing no binding can hide — a plain state in React, a
|
|
148
|
+
* `ShallowRef` in Vue, an `Accessor` in Solid.
|
|
149
|
+
*
|
|
150
|
+
* **`TCall` is an ASSERTION, not a checked parameter.** The function passed as
|
|
151
|
+
* `call` is typed as the single forwarding signature a binding can actually
|
|
152
|
+
* write, and the overloaded shape a caller wants is not assignable from it — so
|
|
153
|
+
* nothing compares the two, and a `TCall` that stops describing what `call`
|
|
154
|
+
* answers compiles. Declare the call type once, use it at both sites, and keep
|
|
155
|
+
* them within sight of each other; every member of `modules/bindings/` does.
|
|
156
|
+
*
|
|
157
|
+
* **Nine names come from the FUNCTION and cannot be forwarded**: `prototype`,
|
|
158
|
+
* `length`, `arguments`, `caller`, `constructor`, `call`, `apply`, `bind` and
|
|
159
|
+
* `toString`, plus every symbol. The return type says the whole ViewModel is
|
|
160
|
+
* readable, and for a ViewModel built from core's factories it is — none of them
|
|
161
|
+
* collides. A ViewModel written by hand with a public `apply`, `bind` or
|
|
162
|
+
* `length` is inside the constraint and would read the function's instead, with
|
|
163
|
+
* no error anywhere. That is the price of the object being both things at once,
|
|
164
|
+
* and the list is short enough to check a ViewModel against.
|
|
165
|
+
*/
|
|
166
|
+
declare const createLankaCallableVM: <TViewModel extends ILankaReadableVM<object>, TCall extends (...args: never[]) => unknown>(viewModel: TViewModel, call: (...args: never[]) => unknown) => TViewModel & TCall;
|
|
167
|
+
|
|
116
168
|
/** One reader's live view of a ViewModel. */
|
|
117
169
|
interface ILankaViewSubscription<TState extends object> {
|
|
118
170
|
/**
|
|
@@ -260,4 +312,4 @@ declare const defineLankaVM: <TViewModel extends ILankaReadableVM<object>>(confi
|
|
|
260
312
|
*/
|
|
261
313
|
declare const resolveLankaVM: <TViewModel extends ILankaReadableVM<object>>(definition: ILankaVMDefinition<TViewModel>) => TViewModel;
|
|
262
314
|
|
|
263
|
-
export { type ILankaAccessTracker, type ILankaLocatorProxyConfig, type ILankaVMDefinition, type ILankaViewSubscription, createLankaAccessTracker, createLankaLocatorProxy, createLankaViewSubscription, defineLankaVM, resolveLankaVM };
|
|
315
|
+
export { type ILankaAccessTracker, type ILankaLocatorProxyConfig, type ILankaVMDefinition, type ILankaViewSubscription, createLankaAccessTracker, createLankaCallableVM, createLankaLocatorProxy, createLankaViewSubscription, defineLankaVM, resolveLankaVM };
|
package/dist/_extend/index.js
CHANGED
|
@@ -101,6 +101,44 @@ var createLankaAccessTracker = (viewModel) => {
|
|
|
101
101
|
};
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
// src/viewmodel/_factories/create-lanka-callable-vm/createLankaCallableVM.ts
|
|
105
|
+
var FUNCTION_MEMBERS = /* @__PURE__ */ new Set([
|
|
106
|
+
"prototype",
|
|
107
|
+
"length",
|
|
108
|
+
"arguments",
|
|
109
|
+
"caller",
|
|
110
|
+
"constructor",
|
|
111
|
+
"call",
|
|
112
|
+
"apply",
|
|
113
|
+
"bind",
|
|
114
|
+
"toString"
|
|
115
|
+
]);
|
|
116
|
+
var forwardToViewModel = (viewModel) => {
|
|
117
|
+
const members = viewModel;
|
|
118
|
+
return {
|
|
119
|
+
get: (target, property, receiver) => typeof property === "symbol" || FUNCTION_MEMBERS.has(property) ? Reflect.get(target, property, receiver) : members[property],
|
|
120
|
+
/**
|
|
121
|
+
* `in` answers for the ViewModel too.
|
|
122
|
+
*
|
|
123
|
+
* Without this the callable would report that it has no `getState`, while
|
|
124
|
+
* reading `getState` hands one back — and `"getState" in useTodoVM` is how a
|
|
125
|
+
* devtool, a serialiser and a duck-typed helper ask. The ViewModel behind
|
|
126
|
+
* this may be a lazy proxy with no `has` trap of its own, so the question is
|
|
127
|
+
* answered by READING the property, which for a lazy ViewModel builds
|
|
128
|
+
* nothing.
|
|
129
|
+
*
|
|
130
|
+
* The price, measured: over an EAGER ViewModel `"whatever" in callable` is
|
|
131
|
+
* false, and over a LAZY one it is true, because the lazy proxy answers any
|
|
132
|
+
* unknown key with a wrapper. `in` is therefore a reliable yes and an
|
|
133
|
+
* unreliable no, and a caller that needs a real answer asks the state.
|
|
134
|
+
* Narrowing it would mean asking a lazy ViewModel to enumerate itself,
|
|
135
|
+
* which is the one thing it exists not to do.
|
|
136
|
+
*/
|
|
137
|
+
has: (target, property) => Reflect.has(target, property) || typeof property === "string" && members[property] !== void 0
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
var createLankaCallableVM = (viewModel, call) => new Proxy(call, forwardToViewModel(viewModel));
|
|
141
|
+
|
|
104
142
|
// src/viewmodel/_factories/create-lanka-view-subscription/createLankaViewSubscription.ts
|
|
105
143
|
var createLankaViewSubscription = (viewModel, onChange) => {
|
|
106
144
|
const tracker = createLankaAccessTracker(viewModel);
|
|
@@ -167,6 +205,7 @@ export {
|
|
|
167
205
|
LankaSingletonLocator,
|
|
168
206
|
composeLankaRequestMiddleware,
|
|
169
207
|
createLankaAccessTracker,
|
|
208
|
+
createLankaCallableVM,
|
|
170
209
|
createLankaLocatorProxy,
|
|
171
210
|
createLankaScope,
|
|
172
211
|
createLankaViewSubscription,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/viewmodel/_internal/create-lanka-access-tracker/createLankaAccessTracker.ts","../../src/viewmodel/_factories/create-lanka-view-subscription/createLankaViewSubscription.ts","../../src/viewmodel/_internal/lanka-vm-recipes/lankaVMRecipes.ts","../../src/viewmodel/_factories/define-lanka-vm/defineLankaVM.ts","../../src/viewmodel/_factories/resolve-lanka-vm/resolveLankaVM.ts"],"sourcesContent":["import { lankaBlindSpotRegistry } from \"../lanka-blind-spot-registry/lankaBlindSpotRegistry\";\nimport type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\nexport interface ILankaAccessTracker<TState extends object> {\n\t/**\n\t * The state as a recording Proxy: every key read off it is remembered.\n\t *\n\t * Cached by the IDENTITY of the state it wrapped, so a second read while\n\t * nothing has changed hands back the same proxy — and therefore the same\n\t * recorded keys — rather than starting the recording over.\n\t *\n\t * A ViewModel that turned tracking off gets the state itself, and every\n\t * change then notifies. That is its decision, not a fallback: it turned\n\t * tracking off because it DERIVES what the screen shows, and a recording that\n\t * cannot see those reads would skip renders the screen needs.\n\t */\n\tread(): TState;\n\t/**\n\t * Whether a change touches anything this reader actually looked at.\n\t *\n\t * A reader that has looked at NOTHING yet is notified of everything: it has\n\t * not had the chance to record a key, and staying silent would mean its first\n\t * render never arrives.\n\t */\n\tshouldNotify(next: TState, prev: TState): boolean;\n\t/**\n\t * Says, in development, that a change was skipped — so the framework can warn\n\t * if the screen reads the changed key through a getter.\n\t *\n\t * Called by a binding exactly when `shouldNotify` answered `false`. In\n\t * production, and for a ViewModel with no trap, it does nothing.\n\t */\n\treportSkipped(next: TState, prev: TState): void;\n\t/**\n\t * The state itself, never the proxy.\n\t *\n\t * For a render that happens once and is thrown away — a server snapshot. There\n\t * is nothing to skip on a second render that will not happen, so recording\n\t * reads would be work whose result nothing consults.\n\t */\n\treadPlain(): TState;\n\t/** The keys read so far. Handed to the blind-spot diagnostic, which names them. */\n\treadonly trackedKeys: ReadonlySet<string>;\n}\n\nconst asRecord = (state: object): Record<string, unknown> => state as Record<string, unknown>;\n\n/**\n * A view of `state` that records the keys a READER depends on.\n *\n * Three things are excluded, and each was found by a reader going deaf rather\n * than by review. What they have in common is that none of them can ever make\n * `shouldNotify` answer true — so recording one cannot cause a render, and can\n * only switch off the rule that a reader who has read NOTHING hears about\n * everything.\n *\n * **Symbols.** `shouldNotify` compares keys by name, so a recorded symbol could\n * never be consulted, and every symbol a runtime asks for while inspecting an\n * object would join the set.\n *\n * **Keys the state does not have.** A framework probes an unfamiliar object\n * before it will hold it: Vue's `shallowRef` reads `__v_isRef`, a promise\n * resolution reads `then`, React reads `$$typeof`. Each went through this proxy\n * and was recorded, and `undefined === undefined` on every later comparison.\n * That is what made `defineLankaStore(vm)` deaf to its own first change — the\n * ONE key it had recorded was `__v_isRef`, and a template reading a real key\n * during its first render is what hid it everywhere else.\n *\n * **Functions.** An action is one object for the life of the store, so a\n * recorded action can never differ. `await store.load()` before any other read\n * is the shape that found it. A state key holding a function that genuinely\n * changes is the case this gives up, and it is the right one: a callback living\n * in state is state two readers cannot agree about, and every ViewModel here and\n * in the applications keeps its functions in actions.\n */\nconst recordReadsInto = <TState extends object>(state: TState, keys: Set<string>): TState =>\n\tnew Proxy(state, {\n\t\tget(target, prop, receiver) {\n\t\t\tconst value = Reflect.get(target, prop, receiver) as unknown;\n\n\t\t\tif (\n\t\t\t\ttypeof prop === \"string\" &&\n\t\t\t\ttypeof value !== \"function\" &&\n\t\t\t\tObject.hasOwn(target, prop)\n\t\t\t) {\n\t\t\t\tkeys.add(prop);\n\t\t\t}\n\n\t\t\treturn value;\n\t\t},\n\t});\n\n/**\n * The recording one reader currently holds, rebuilt when the state moves.\n *\n * Its own unit because it is the stateful half: a proxy, the keys it has\n * collected, and the identity of the state it was made for. What is left around\n * it is a comparison and a report, neither of which remembers anything.\n */\nconst trackedReadOf = <TState extends object>(read: () => TState) => {\n\tlet keys = new Set<string>();\n\tlet state: TState | null = null;\n\tlet proxy: TState | null = null;\n\n\treturn {\n\t\tkeys: (): ReadonlySet<string> => keys,\n\n\t\tcurrent: (): TState => {\n\t\t\tconst next = read();\n\t\t\tif (state === next && proxy) return proxy;\n\n\t\t\t// A FRESH set per state: the keys a reader looks at can change between\n\t\t\t// renders — a branch stops being taken, a list empties — and keeping the\n\t\t\t// old ones would re-render for a key nobody reads any more, forever.\n\t\t\tkeys = new Set<string>();\n\t\t\tstate = next;\n\t\t\tproxy = recordReadsInto(next, keys);\n\n\t\t\treturn proxy;\n\t\t},\n\t};\n};\n\n/** Whether the two states disagree on any of the keys a reader looked at. */\nconst anyKeyMoved = (keys: ReadonlySet<string>, next: object, prev: object): boolean => {\n\tconst nextRecord = asRecord(next);\n\tconst prevRecord = asRecord(prev);\n\n\tfor (const key of keys) {\n\t\tif (!Object.is(nextRecord[key], prevRecord[key])) return true;\n\t}\n\n\treturn false;\n};\n\n/**\n * Which state keys one reader looked at, and whether a change touched them.\n *\n * This is the whole of access tracking, and it is deliberately ignorant of how\n * anybody subscribes. A component re-renders only for keys it READ off the proxy\n * this returns; every framework asks that question the same way and answers it\n * with a different mechanism — `useSyncExternalStore`, a `shallowRef`, a signal —\n * so the question lives here and the mechanism lives in the binding.\n *\n * **This is the one piece of core a binding author needs.** It is published\n * through `lanka/extend` for exactly that: a binding is then a subscription, a\n * render trigger and these four calls, and the behaviour a consumer sees is the\n * framework's rather than each binding's re-reading of it. Canon:\n * `skills/parity/SKILL.md`.\n *\n * ## One tracker per reader, not per store\n *\n * The recorded keys are the property of whoever did the reading. Two components\n * over one ViewModel read different keys and must re-render for different\n * changes, so each holds its own tracker — which is also why this is a factory\n * with closed-over state rather than a set of pure functions over a shared map:\n * the lifetime of the recording is exactly the lifetime of the reader.\n *\n * ## The blind spot this cannot see, and reports instead\n *\n * Tracking sees reads made DIRECTLY off the proxy. A key reached only inside a\n * derived getter — an action calling `get()` — is invisible here, so a change to\n * it answers `shouldNotify` with `false` and the screen does not move. That is\n * what `reportSkipped` is for: core kept a trap for this ViewModel, and in\n * development it names the ViewModel and the key rather than leaving a frozen\n * screen with no error anywhere.\n */\nexport const createLankaAccessTracker = <TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n): ILankaAccessTracker<TState> => {\n\tconst trap = lankaBlindSpotRegistry.of(viewModel);\n\tconst isTracked = viewModel.isAccessTracked;\n\tconst tracked = trackedReadOf(() => viewModel.getState());\n\n\treturn {\n\t\tget trackedKeys(): ReadonlySet<string> {\n\t\t\treturn isTracked ? tracked.keys() : new Set<string>();\n\t\t},\n\n\t\tread(): TState {\n\t\t\treturn isTracked ? tracked.current() : viewModel.getState();\n\t\t},\n\n\t\tshouldNotify(next: TState, prev: TState): boolean {\n\t\t\tif (!isTracked) return true;\n\n\t\t\tconst keys = tracked.keys();\n\n\t\t\treturn keys.size === 0 || anyKeyMoved(keys, next, prev);\n\t\t},\n\n\t\treportSkipped(next: TState, prev: TState): void {\n\t\t\ttrap?.report(new Set(tracked.keys()), asRecord(next), asRecord(prev));\n\t\t},\n\n\t\treadPlain(): TState {\n\t\t\treturn viewModel.getState();\n\t\t},\n\t};\n};\n","import { createLankaAccessTracker } from \"../../_internal/create-lanka-access-tracker/createLankaAccessTracker\";\nimport type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/** One reader's live view of a ViewModel. */\nexport interface ILankaViewSubscription<TState extends object> {\n\t/**\n\t * The current state, RECORDED.\n\t *\n\t * Every key read off it is remembered, which is what lets the next change be\n\t * skipped when it touched none of them. Call it again on every read: it asks\n\t * the ViewModel for its state each time, so it is never a snapshot.\n\t */\n\tread: () => TState;\n\t/** Releases the subscription. */\n\tstop: () => void;\n}\n\n/**\n * The TRACKED half of a view binding, minus the framework.\n *\n * The tracked half, and not the whole: all five shipped members also publish a\n * selector arm, which this deliberately does not serve — the reason is below,\n * under \"Why it does not take a selector\".\n *\n * ```ts\n * // a binding for a framework this repository has never heard of\n * export const useMyFrameworkVM = (viewModel) => {\n * \tconst view = createLankaViewSubscription(viewModel, () => invalidate());\n * \tonTeardown(view.stop);\n *\n * \treturn view.read;\n * };\n * ```\n *\n * Subscribe, ask whether the change touched anything this reader looked at,\n * report the skip so the blind-spot diagnostic can fire, and hand back a read\n * that records. Five packages wrote those four steps out by hand, identically —\n * and five copies of a decision diverge on the day one of them gains a line.\n *\n * ## What a caller still owns\n *\n * `onChange` and the teardown, which are the only framework-shaped things left.\n * That is the seam: a binding says how its framework is WOKEN and when a reader\n * has gone, and everything about which changes are worth waking for is here.\n *\n * ## Why it does not take a selector\n *\n * A selector BYPASSES tracking — the selector decides, and there is nothing to\n * record — so a subscription that took one would be two mechanisms behind one\n * name, each right half the time. A binding with a selector arm calls\n * `createLankaAccessTracker` directly, which is what the shelf's members do, and\n * the four steps are worth writing out where they genuinely differ.\n *\n * ## What it is not\n *\n * Not a store, not a cache, not a second place state lives. It holds a tracker\n * and an unsubscribe, and everything it answers comes from the ViewModel on the\n * call.\n */\nexport const createLankaViewSubscription = <TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n\tonChange: () => void,\n): ILankaViewSubscription<TState> => {\n\tconst tracker = createLankaAccessTracker(viewModel);\n\n\tconst stop = viewModel.subscribe((next, prev) => {\n\t\tif (!tracker.shouldNotify(next, prev)) {\n\t\t\t// No update will follow. If the changed key is linked to this reader\n\t\t\t// through a getter it read, the screen froze — and in development core\n\t\t\t// names the ViewModel and the key rather than leaving it silent.\n\t\t\ttracker.reportSkipped(next, prev);\n\t\t\treturn;\n\t\t}\n\n\t\tonChange();\n\t});\n\n\treturn { read: () => tracker.read(), stop };\n};\n","import type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/** What a definition actually holds, kept where a consumer cannot reach it. */\nexport interface ILankaVMRecipe {\n\treadonly name: string;\n\treadonly build: () => ILankaReadableVM<object>;\n}\n\n/**\n * What `defineLankaVM` writes and `resolveLankaVM` reads.\n *\n * One object rather than two exported maps, because a file here holds one\n * runtime identity — and because the two halves are one mechanism: a recipe\n * nobody can resolve and an instance map keyed by nothing are each meaningless\n * alone.\n *\n * ## Weak at both ends, and both ends matter\n *\n * `recipes` is weak so a definition dropped by its module takes its recipe with\n * it. `instances` is weak in the SCOPE so a finished request's ViewModels go\n * when its scope does, and weak in the DEFINITION because the published type\n * invites `defineLankaVM({...})` written inline in a component or a loop — in a\n * browser the scope key is the framework instance and lives as long as the tab,\n * so a strong inner map would hold every inline definition and its ViewModel\n * for the life of the page.\n */\nexport const lankaVMRecipes = {\n\trecipes: new WeakMap<object, ILankaVMRecipe>(),\n\tinstances: new WeakMap<object, WeakMap<object, ILankaReadableVM<object>>>(),\n\n\t/** The instances belonging to one scope, created on first use. */\n\tforScope(scope: object): WeakMap<object, ILankaReadableVM<object>> {\n\t\tconst existing = this.instances.get(scope);\n\n\t\tif (existing) return existing;\n\n\t\tconst created = new WeakMap<object, ILankaReadableVM<object>>();\n\n\t\tthis.instances.set(scope, created);\n\n\t\treturn created;\n\t},\n};\n","import { lankaVMRecipes } from \"../../_internal/lanka-vm-recipes/lankaVMRecipes\";\nimport type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/**\n * The brand that makes a definition impossible to write by hand.\n *\n * Not exported, and that is the whole mechanism. A structural interface with\n * `name` and `build` on it would let a consumer author one, and the moment one\n * does, `ILankaVMDefinition` stops being a type the framework implements and\n * becomes a PORT — after which every member added to it is a compile error in\n * code nobody touched. Publishing an opaque type costs one line and keeps the\n * option space: a `dispose`, a `scope` field, even a call signature so that\n * `missionsVM()` could mean `resolveLankaVM(missionsVM)`, all stay additive.\n */\ndeclare const LANKA_VM_DEFINITION: unique symbol;\n\n/**\n * A ViewModel that has not been built yet.\n *\n * Hold it, pass it, type against it. There is nothing on it to call, because\n * calling it is `resolveLankaVM`'s job and a definition built by hand would be\n * an instance outside every scope's map.\n */\nexport interface ILankaVMDefinition<TViewModel extends ILankaReadableVM<object>> {\n\treadonly [LANKA_VM_DEFINITION]: TViewModel;\n}\n\n/**\n * Declares a ViewModel WITHOUT building it.\n *\n * ## When NOT to use this\n *\n * A browser-only application needs none of it. One module is one instance per\n * TAB there, so a module-level `createLankaVM` is still the shape, and wrapping\n * the nine factories in this one buys nothing but a lookup per read. Reach for\n * it when the same ViewModel has to exist on a SERVER, where one module is one\n * instance per PROCESS — shared by every user connected to it, so the first\n * request to write a draft into it serves that draft to the next stranger.\n *\n * ```ts\n * export const missionsVM = defineLankaVM({\n * \tname: \"MissionsVM\",\n * \tbuild: () => createLankaVM({ … }),\n * });\n * ```\n *\n * ## What it is not\n *\n * Not a tenth way to write a ViewModel. `build` returns whatever the nine\n * existing factories return and nothing here reaches inside it — a definition\n * adds a lifetime and takes nothing away.\n *\n * Not lazy in the sense `createLazyLankaVM` is. That one defers the STORE until\n * first read and still has one per module; this defers WHICH INSTANCE, and the\n * two compose.\n */\nexport const defineLankaVM = <TViewModel extends ILankaReadableVM<object>>(config: {\n\tname: string;\n\tbuild: () => TViewModel;\n}): ILankaVMDefinition<TViewModel> => {\n\tconst definition = Object.freeze({});\n\n\tlankaVMRecipes.recipes.set(definition, config);\n\n\treturn definition as ILankaVMDefinition<TViewModel>;\n};\n","import {\n\tgetActiveLankaScope,\n\thasLankaScopeResolver,\n\trequireActiveRuntime,\n} from \"../../../_internal/active-runtime/activeRuntime\";\nimport { lankaScenarioBootstrap } from \"../../../scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap\";\nimport { lankaVMRecipes } from \"../../_internal/lanka-vm-recipes/lankaVMRecipes\";\nimport type { ILankaVMDefinition } from \"../define-lanka-vm/defineLankaVM\";\nimport type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/**\n * The instance of a definition that belongs to the CURRENT scope.\n *\n * Called twice in one scope it answers the same instance; called in two scopes\n * it answers two, and neither can see the other's state. In a browser there is\n * one scope for the life of the tab, so this is the module-level ViewModel a\n * consumer already knows — written once and correct on a server as well.\n *\n * ## It throws outside a scope, and that is the feature\n *\n * On a server the answer comes from the SCOPE seam rather than from the active\n * runtime, and the difference is not academic. `runInLankaServerScope` creates\n * its instance inside the scope and `createLanka` activates every instance it\n * builds, so during a request the process pointer and the scope's runtime are\n * the same object — keying on the runtime would make \"inside a request\" and\n * \"after one ended\" indistinguishable, and a call made after would be handed the\n * last stranger's ViewModel.\n *\n * So: a scope resolver installed and answering `null` means this ran outside\n * every request, and that fails loudly. No fallback, for the same reason\n * `requireActiveRuntime` has none — a wrong answer here is one user's data in\n * another user's page, and it would surface three layers from the call.\n *\n * ## Why the build is wrapped\n *\n * `ALankaVM.build()` declares a ViewModel that has scenario handlers into a\n * PROCESS-wide list, so that every instance ever created adopts it. That is\n * right for a module-level ViewModel and catastrophic for a scoped one: the list\n * would grow per request forever, and request N+1 would adopt request N's\n * ViewModel and run N's handlers against N's gateways. `buildScoped` is the seam\n * that keeps a scoped declaration out of it.\n */\nexport const resolveLankaVM = <TViewModel extends ILankaReadableVM<object>>(\n\tdefinition: ILankaVMDefinition<TViewModel>,\n): TViewModel => {\n\tconst recipe = lankaVMRecipes.recipes.get(definition);\n\n\tif (!recipe) {\n\t\tthrow new Error(\n\t\t\t\"resolveLankaVM was given something defineLankaVM did not make. A definition \" +\n\t\t\t\t\"is opaque on purpose: build it with defineLankaVM rather than by hand.\",\n\t\t);\n\t}\n\n\tconst runtime = requireActiveRuntime();\n\tconst scope = getActiveLankaScope();\n\n\tif (hasLankaScopeResolver() && !scope) {\n\t\tthrow new Error(\n\t\t\t`resolveLankaVM(\"${recipe.name}\") ran outside every scope. A scope resolver is ` +\n\t\t\t\t\"installed, which on a server means this code ran outside a request — and a \" +\n\t\t\t\t\"ViewModel resolved there would be the previous request's. Do this work inside \" +\n\t\t\t\t\"the scope, or start one.\",\n\t\t);\n\t}\n\n\t// The runtime when nothing knows about scopes: a browser tab is one scope for\n\t// its whole life, and the instance is what identifies it.\n\tconst instances = lankaVMRecipes.forScope(scope ?? runtime);\n\tconst held = instances.get(definition);\n\n\tif (held) return held as TViewModel;\n\n\tconst built = lankaScenarioBootstrap.buildScoped(() => recipe.build());\n\n\tinstances.set(definition, built);\n\n\treturn built as TViewModel;\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/viewmodel/_internal/create-lanka-access-tracker/createLankaAccessTracker.ts","../../src/viewmodel/_factories/create-lanka-callable-vm/createLankaCallableVM.ts","../../src/viewmodel/_factories/create-lanka-view-subscription/createLankaViewSubscription.ts","../../src/viewmodel/_internal/lanka-vm-recipes/lankaVMRecipes.ts","../../src/viewmodel/_factories/define-lanka-vm/defineLankaVM.ts","../../src/viewmodel/_factories/resolve-lanka-vm/resolveLankaVM.ts"],"sourcesContent":["import { lankaBlindSpotRegistry } from \"../lanka-blind-spot-registry/lankaBlindSpotRegistry\";\nimport type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\nexport interface ILankaAccessTracker<TState extends object> {\n\t/**\n\t * The state as a recording Proxy: every key read off it is remembered.\n\t *\n\t * Cached by the IDENTITY of the state it wrapped, so a second read while\n\t * nothing has changed hands back the same proxy — and therefore the same\n\t * recorded keys — rather than starting the recording over.\n\t *\n\t * A ViewModel that turned tracking off gets the state itself, and every\n\t * change then notifies. That is its decision, not a fallback: it turned\n\t * tracking off because it DERIVES what the screen shows, and a recording that\n\t * cannot see those reads would skip renders the screen needs.\n\t */\n\tread(): TState;\n\t/**\n\t * Whether a change touches anything this reader actually looked at.\n\t *\n\t * A reader that has looked at NOTHING yet is notified of everything: it has\n\t * not had the chance to record a key, and staying silent would mean its first\n\t * render never arrives.\n\t */\n\tshouldNotify(next: TState, prev: TState): boolean;\n\t/**\n\t * Says, in development, that a change was skipped — so the framework can warn\n\t * if the screen reads the changed key through a getter.\n\t *\n\t * Called by a binding exactly when `shouldNotify` answered `false`. In\n\t * production, and for a ViewModel with no trap, it does nothing.\n\t */\n\treportSkipped(next: TState, prev: TState): void;\n\t/**\n\t * The state itself, never the proxy.\n\t *\n\t * For a render that happens once and is thrown away — a server snapshot. There\n\t * is nothing to skip on a second render that will not happen, so recording\n\t * reads would be work whose result nothing consults.\n\t */\n\treadPlain(): TState;\n\t/** The keys read so far. Handed to the blind-spot diagnostic, which names them. */\n\treadonly trackedKeys: ReadonlySet<string>;\n}\n\nconst asRecord = (state: object): Record<string, unknown> => state as Record<string, unknown>;\n\n/**\n * A view of `state` that records the keys a READER depends on.\n *\n * Three things are excluded, and each was found by a reader going deaf rather\n * than by review. What they have in common is that none of them can ever make\n * `shouldNotify` answer true — so recording one cannot cause a render, and can\n * only switch off the rule that a reader who has read NOTHING hears about\n * everything.\n *\n * **Symbols.** `shouldNotify` compares keys by name, so a recorded symbol could\n * never be consulted, and every symbol a runtime asks for while inspecting an\n * object would join the set.\n *\n * **Keys the state does not have.** A framework probes an unfamiliar object\n * before it will hold it: Vue's `shallowRef` reads `__v_isRef`, a promise\n * resolution reads `then`, React reads `$$typeof`. Each went through this proxy\n * and was recorded, and `undefined === undefined` on every later comparison.\n * That is what made `defineLankaStore(vm)` deaf to its own first change — the\n * ONE key it had recorded was `__v_isRef`, and a template reading a real key\n * during its first render is what hid it everywhere else.\n *\n * **Functions.** An action is one object for the life of the store, so a\n * recorded action can never differ. `await store.load()` before any other read\n * is the shape that found it. A state key holding a function that genuinely\n * changes is the case this gives up, and it is the right one: a callback living\n * in state is state two readers cannot agree about, and every ViewModel here and\n * in the applications keeps its functions in actions.\n */\nconst recordReadsInto = <TState extends object>(state: TState, keys: Set<string>): TState =>\n\tnew Proxy(state, {\n\t\tget(target, prop, receiver) {\n\t\t\tconst value = Reflect.get(target, prop, receiver) as unknown;\n\n\t\t\tif (\n\t\t\t\ttypeof prop === \"string\" &&\n\t\t\t\ttypeof value !== \"function\" &&\n\t\t\t\tObject.hasOwn(target, prop)\n\t\t\t) {\n\t\t\t\tkeys.add(prop);\n\t\t\t}\n\n\t\t\treturn value;\n\t\t},\n\t});\n\n/**\n * The recording one reader currently holds, rebuilt when the state moves.\n *\n * Its own unit because it is the stateful half: a proxy, the keys it has\n * collected, and the identity of the state it was made for. What is left around\n * it is a comparison and a report, neither of which remembers anything.\n */\nconst trackedReadOf = <TState extends object>(read: () => TState) => {\n\tlet keys = new Set<string>();\n\tlet state: TState | null = null;\n\tlet proxy: TState | null = null;\n\n\treturn {\n\t\tkeys: (): ReadonlySet<string> => keys,\n\n\t\tcurrent: (): TState => {\n\t\t\tconst next = read();\n\t\t\tif (state === next && proxy) return proxy;\n\n\t\t\t// A FRESH set per state: the keys a reader looks at can change between\n\t\t\t// renders — a branch stops being taken, a list empties — and keeping the\n\t\t\t// old ones would re-render for a key nobody reads any more, forever.\n\t\t\tkeys = new Set<string>();\n\t\t\tstate = next;\n\t\t\tproxy = recordReadsInto(next, keys);\n\n\t\t\treturn proxy;\n\t\t},\n\t};\n};\n\n/** Whether the two states disagree on any of the keys a reader looked at. */\nconst anyKeyMoved = (keys: ReadonlySet<string>, next: object, prev: object): boolean => {\n\tconst nextRecord = asRecord(next);\n\tconst prevRecord = asRecord(prev);\n\n\tfor (const key of keys) {\n\t\tif (!Object.is(nextRecord[key], prevRecord[key])) return true;\n\t}\n\n\treturn false;\n};\n\n/**\n * Which state keys one reader looked at, and whether a change touched them.\n *\n * This is the whole of access tracking, and it is deliberately ignorant of how\n * anybody subscribes. A component re-renders only for keys it READ off the proxy\n * this returns; every framework asks that question the same way and answers it\n * with a different mechanism — `useSyncExternalStore`, a `shallowRef`, a signal —\n * so the question lives here and the mechanism lives in the binding.\n *\n * **This is the one piece of core a binding author needs.** It is published\n * through `lanka/extend` for exactly that: a binding is then a subscription, a\n * render trigger and these four calls, and the behaviour a consumer sees is the\n * framework's rather than each binding's re-reading of it. Canon:\n * `skills/parity/SKILL.md`.\n *\n * ## One tracker per reader, not per store\n *\n * The recorded keys are the property of whoever did the reading. Two components\n * over one ViewModel read different keys and must re-render for different\n * changes, so each holds its own tracker — which is also why this is a factory\n * with closed-over state rather than a set of pure functions over a shared map:\n * the lifetime of the recording is exactly the lifetime of the reader.\n *\n * ## The blind spot this cannot see, and reports instead\n *\n * Tracking sees reads made DIRECTLY off the proxy. A key reached only inside a\n * derived getter — an action calling `get()` — is invisible here, so a change to\n * it answers `shouldNotify` with `false` and the screen does not move. That is\n * what `reportSkipped` is for: core kept a trap for this ViewModel, and in\n * development it names the ViewModel and the key rather than leaving a frozen\n * screen with no error anywhere.\n */\nexport const createLankaAccessTracker = <TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n): ILankaAccessTracker<TState> => {\n\tconst trap = lankaBlindSpotRegistry.of(viewModel);\n\tconst isTracked = viewModel.isAccessTracked;\n\tconst tracked = trackedReadOf(() => viewModel.getState());\n\n\treturn {\n\t\tget trackedKeys(): ReadonlySet<string> {\n\t\t\treturn isTracked ? tracked.keys() : new Set<string>();\n\t\t},\n\n\t\tread(): TState {\n\t\t\treturn isTracked ? tracked.current() : viewModel.getState();\n\t\t},\n\n\t\tshouldNotify(next: TState, prev: TState): boolean {\n\t\t\tif (!isTracked) return true;\n\n\t\t\tconst keys = tracked.keys();\n\n\t\t\treturn keys.size === 0 || anyKeyMoved(keys, next, prev);\n\t\t},\n\n\t\treportSkipped(next: TState, prev: TState): void {\n\t\t\ttrap?.report(new Set(tracked.keys()), asRecord(next), asRecord(prev));\n\t\t},\n\n\t\treadPlain(): TState {\n\t\t\treturn viewModel.getState();\n\t\t},\n\t};\n};\n","import type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/**\n * What must keep coming from the FUNCTION rather than from the ViewModel.\n *\n * Everything else a caller reads by name is the ViewModel's — including `name`,\n * which is the ViewModel's name and was the ViewModel's name before any of this\n * existed, because `build()` defines it over the store.\n *\n * Symbols are excluded wholesale, and that is not tidiness. The ViewModel behind\n * this may be a LAZY proxy, which answers an unknown property with a wrapper\n * function; a wrapper handed back for `Symbol.iterator` makes the callable look\n * iterable, and one for `Symbol.toPrimitive` breaks every string coercion of it.\n * Neither is a member of any ViewModel, so neither may be forwarded.\n *\n * What the exclusion does NOT cover, and what a reader should know before\n * trusting this list: `$$typeof` is a STRING key, so over a lazy ViewModel it is\n * answered with one of those wrappers — measured, and it comes back a function.\n * React compares `$$typeof` against a symbol, so a function is not equal to it\n * and nothing treats the callable as an element; Vue's `isRef` compares\n * `__v_isRef` against `true` for the same reason and is likewise safe. The list\n * above cannot be extended to cover this, because an unknown string key is\n * exactly what a ViewModel's own members look like.\n */\nconst FUNCTION_MEMBERS: ReadonlySet<string> = new Set([\n\t\"prototype\",\n\t\"length\",\n\t\"arguments\",\n\t\"caller\",\n\t\"constructor\",\n\t\"call\",\n\t\"apply\",\n\t\"bind\",\n\t\"toString\",\n]);\n\n/**\n * How the callable answers for the ViewModel behind it.\n *\n * Its own function, because the two traps are the whole mechanism and the\n * factory below is then one Proxy and one cast.\n */\nconst forwardToViewModel = (\n\tviewModel: ILankaReadableVM<object>,\n): ProxyHandler<(...args: never[]) => unknown> => {\n\tconst members = viewModel as unknown as Record<string, unknown>;\n\n\treturn {\n\t\tget: (target, property, receiver): unknown =>\n\t\t\ttypeof property === \"symbol\" || FUNCTION_MEMBERS.has(property)\n\t\t\t\t? Reflect.get(target, property, receiver)\n\t\t\t\t: members[property],\n\n\t\t/**\n\t\t * `in` answers for the ViewModel too.\n\t\t *\n\t\t * Without this the callable would report that it has no `getState`, while\n\t\t * reading `getState` hands one back — and `\"getState\" in useTodoVM` is how a\n\t\t * devtool, a serialiser and a duck-typed helper ask. The ViewModel behind\n\t\t * this may be a lazy proxy with no `has` trap of its own, so the question is\n\t\t * answered by READING the property, which for a lazy ViewModel builds\n\t\t * nothing.\n\t\t *\n\t\t * The price, measured: over an EAGER ViewModel `\"whatever\" in callable` is\n\t\t * false, and over a LAZY one it is true, because the lazy proxy answers any\n\t\t * unknown key with a wrapper. `in` is therefore a reliable yes and an\n\t\t * unreliable no, and a caller that needs a real answer asks the state.\n\t\t * Narrowing it would mean asking a lazy ViewModel to enumerate itself,\n\t\t * which is the one thing it exists not to do.\n\t\t */\n\t\thas: (target, property) =>\n\t\t\tReflect.has(target, property) ||\n\t\t\t(typeof property === \"string\" && members[property] !== undefined),\n\t};\n};\n\n/**\n * A ViewModel that is also the call its binding reads it with.\n *\n * ```ts\n * // inside a binding: the framework's own read, pre-applied\n * const useTodoVM = createLankaCallableVM<typeof todoVM, TMyCall>(todoVM, (selector) =>\n * \tuseLankaVM(todoVM, selector),\n * );\n * ```\n *\n * ## Why this is in core and not in a binding\n *\n * Five bindings publish core's six ViewModel factories under core's own names,\n * each one already wearing that framework's read — so a consumer moves a\n * declaration by changing the import line. What every one of them needs to do\n * that is the same object: a function that is ALSO the ViewModel, so\n * `useTodoVM()` reads and `useTodoVM.getState()` does what it always did.\n *\n * The forwarding is the part that is easy to get subtly wrong — which members\n * belong to the function, what `in` must answer, what a lazy ViewModel does with\n * a symbol — and five copies of it would be five packages diverging on a\n * question that has one answer. It lives here for the reason\n * `createLankaAccessTracker` does: what a binding sees is then lanka's behaviour\n * rather than that binding author's reading of it.\n *\n * ## What it does NOT do\n *\n * It adds no state and no second store. The Proxy forwards rather than copying,\n * so a LAZY ViewModel still builds on first access: reading `useTodoVM.name`\n * answers from the config and constructs nothing. `TCall` is the binding's own\n * call signature, because what the call answers is the framework's idea of\n * reactivity and the one thing no binding can hide — a plain state in React, a\n * `ShallowRef` in Vue, an `Accessor` in Solid.\n *\n * **`TCall` is an ASSERTION, not a checked parameter.** The function passed as\n * `call` is typed as the single forwarding signature a binding can actually\n * write, and the overloaded shape a caller wants is not assignable from it — so\n * nothing compares the two, and a `TCall` that stops describing what `call`\n * answers compiles. Declare the call type once, use it at both sites, and keep\n * them within sight of each other; every member of `modules/bindings/` does.\n *\n * **Nine names come from the FUNCTION and cannot be forwarded**: `prototype`,\n * `length`, `arguments`, `caller`, `constructor`, `call`, `apply`, `bind` and\n * `toString`, plus every symbol. The return type says the whole ViewModel is\n * readable, and for a ViewModel built from core's factories it is — none of them\n * collides. A ViewModel written by hand with a public `apply`, `bind` or\n * `length` is inside the constraint and would read the function's instead, with\n * no error anywhere. That is the price of the object being both things at once,\n * and the list is short enough to check a ViewModel against.\n */\nexport const createLankaCallableVM = <\n\tTViewModel extends ILankaReadableVM<object>,\n\tTCall extends (...args: never[]) => unknown,\n>(\n\tviewModel: TViewModel,\n\tcall: (...args: never[]) => unknown,\n): TViewModel & TCall => new Proxy(call, forwardToViewModel(viewModel)) as TViewModel & TCall;\n","import { createLankaAccessTracker } from \"../../_internal/create-lanka-access-tracker/createLankaAccessTracker\";\nimport type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/** One reader's live view of a ViewModel. */\nexport interface ILankaViewSubscription<TState extends object> {\n\t/**\n\t * The current state, RECORDED.\n\t *\n\t * Every key read off it is remembered, which is what lets the next change be\n\t * skipped when it touched none of them. Call it again on every read: it asks\n\t * the ViewModel for its state each time, so it is never a snapshot.\n\t */\n\tread: () => TState;\n\t/** Releases the subscription. */\n\tstop: () => void;\n}\n\n/**\n * The TRACKED half of a view binding, minus the framework.\n *\n * The tracked half, and not the whole: all five shipped members also publish a\n * selector arm, which this deliberately does not serve — the reason is below,\n * under \"Why it does not take a selector\".\n *\n * ```ts\n * // a binding for a framework this repository has never heard of\n * export const useMyFrameworkVM = (viewModel) => {\n * \tconst view = createLankaViewSubscription(viewModel, () => invalidate());\n * \tonTeardown(view.stop);\n *\n * \treturn view.read;\n * };\n * ```\n *\n * Subscribe, ask whether the change touched anything this reader looked at,\n * report the skip so the blind-spot diagnostic can fire, and hand back a read\n * that records. Five packages wrote those four steps out by hand, identically —\n * and five copies of a decision diverge on the day one of them gains a line.\n *\n * ## What a caller still owns\n *\n * `onChange` and the teardown, which are the only framework-shaped things left.\n * That is the seam: a binding says how its framework is WOKEN and when a reader\n * has gone, and everything about which changes are worth waking for is here.\n *\n * ## Why it does not take a selector\n *\n * A selector BYPASSES tracking — the selector decides, and there is nothing to\n * record — so a subscription that took one would be two mechanisms behind one\n * name, each right half the time. A binding with a selector arm calls\n * `createLankaAccessTracker` directly, which is what the shelf's members do, and\n * the four steps are worth writing out where they genuinely differ.\n *\n * ## What it is not\n *\n * Not a store, not a cache, not a second place state lives. It holds a tracker\n * and an unsubscribe, and everything it answers comes from the ViewModel on the\n * call.\n */\nexport const createLankaViewSubscription = <TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n\tonChange: () => void,\n): ILankaViewSubscription<TState> => {\n\tconst tracker = createLankaAccessTracker(viewModel);\n\n\tconst stop = viewModel.subscribe((next, prev) => {\n\t\tif (!tracker.shouldNotify(next, prev)) {\n\t\t\t// No update will follow. If the changed key is linked to this reader\n\t\t\t// through a getter it read, the screen froze — and in development core\n\t\t\t// names the ViewModel and the key rather than leaving it silent.\n\t\t\ttracker.reportSkipped(next, prev);\n\t\t\treturn;\n\t\t}\n\n\t\tonChange();\n\t});\n\n\treturn { read: () => tracker.read(), stop };\n};\n","import type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/** What a definition actually holds, kept where a consumer cannot reach it. */\nexport interface ILankaVMRecipe {\n\treadonly name: string;\n\treadonly build: () => ILankaReadableVM<object>;\n}\n\n/**\n * What `defineLankaVM` writes and `resolveLankaVM` reads.\n *\n * One object rather than two exported maps, because a file here holds one\n * runtime identity — and because the two halves are one mechanism: a recipe\n * nobody can resolve and an instance map keyed by nothing are each meaningless\n * alone.\n *\n * ## Weak at both ends, and both ends matter\n *\n * `recipes` is weak so a definition dropped by its module takes its recipe with\n * it. `instances` is weak in the SCOPE so a finished request's ViewModels go\n * when its scope does, and weak in the DEFINITION because the published type\n * invites `defineLankaVM({...})` written inline in a component or a loop — in a\n * browser the scope key is the framework instance and lives as long as the tab,\n * so a strong inner map would hold every inline definition and its ViewModel\n * for the life of the page.\n */\nexport const lankaVMRecipes = {\n\trecipes: new WeakMap<object, ILankaVMRecipe>(),\n\tinstances: new WeakMap<object, WeakMap<object, ILankaReadableVM<object>>>(),\n\n\t/** The instances belonging to one scope, created on first use. */\n\tforScope(scope: object): WeakMap<object, ILankaReadableVM<object>> {\n\t\tconst existing = this.instances.get(scope);\n\n\t\tif (existing) return existing;\n\n\t\tconst created = new WeakMap<object, ILankaReadableVM<object>>();\n\n\t\tthis.instances.set(scope, created);\n\n\t\treturn created;\n\t},\n};\n","import { lankaVMRecipes } from \"../../_internal/lanka-vm-recipes/lankaVMRecipes\";\nimport type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/**\n * The brand that makes a definition impossible to write by hand.\n *\n * Not exported, and that is the whole mechanism. A structural interface with\n * `name` and `build` on it would let a consumer author one, and the moment one\n * does, `ILankaVMDefinition` stops being a type the framework implements and\n * becomes a PORT — after which every member added to it is a compile error in\n * code nobody touched. Publishing an opaque type costs one line and keeps the\n * option space: a `dispose`, a `scope` field, even a call signature so that\n * `missionsVM()` could mean `resolveLankaVM(missionsVM)`, all stay additive.\n */\ndeclare const LANKA_VM_DEFINITION: unique symbol;\n\n/**\n * A ViewModel that has not been built yet.\n *\n * Hold it, pass it, type against it. There is nothing on it to call, because\n * calling it is `resolveLankaVM`'s job and a definition built by hand would be\n * an instance outside every scope's map.\n */\nexport interface ILankaVMDefinition<TViewModel extends ILankaReadableVM<object>> {\n\treadonly [LANKA_VM_DEFINITION]: TViewModel;\n}\n\n/**\n * Declares a ViewModel WITHOUT building it.\n *\n * ## When NOT to use this\n *\n * A browser-only application needs none of it. One module is one instance per\n * TAB there, so a module-level `createLankaVM` is still the shape, and wrapping\n * the nine factories in this one buys nothing but a lookup per read. Reach for\n * it when the same ViewModel has to exist on a SERVER, where one module is one\n * instance per PROCESS — shared by every user connected to it, so the first\n * request to write a draft into it serves that draft to the next stranger.\n *\n * ```ts\n * export const missionsVM = defineLankaVM({\n * \tname: \"MissionsVM\",\n * \tbuild: () => createLankaVM({ … }),\n * });\n * ```\n *\n * ## What it is not\n *\n * Not a tenth way to write a ViewModel. `build` returns whatever the nine\n * existing factories return and nothing here reaches inside it — a definition\n * adds a lifetime and takes nothing away.\n *\n * Not lazy in the sense `createLazyLankaVM` is. That one defers the STORE until\n * first read and still has one per module; this defers WHICH INSTANCE, and the\n * two compose.\n */\nexport const defineLankaVM = <TViewModel extends ILankaReadableVM<object>>(config: {\n\tname: string;\n\tbuild: () => TViewModel;\n}): ILankaVMDefinition<TViewModel> => {\n\tconst definition = Object.freeze({});\n\n\tlankaVMRecipes.recipes.set(definition, config);\n\n\treturn definition as ILankaVMDefinition<TViewModel>;\n};\n","import {\n\tgetActiveLankaScope,\n\thasLankaScopeResolver,\n\trequireActiveRuntime,\n} from \"../../../_internal/active-runtime/activeRuntime\";\nimport { lankaScenarioBootstrap } from \"../../../scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap\";\nimport { lankaVMRecipes } from \"../../_internal/lanka-vm-recipes/lankaVMRecipes\";\nimport type { ILankaVMDefinition } from \"../define-lanka-vm/defineLankaVM\";\nimport type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/**\n * The instance of a definition that belongs to the CURRENT scope.\n *\n * Called twice in one scope it answers the same instance; called in two scopes\n * it answers two, and neither can see the other's state. In a browser there is\n * one scope for the life of the tab, so this is the module-level ViewModel a\n * consumer already knows — written once and correct on a server as well.\n *\n * ## It throws outside a scope, and that is the feature\n *\n * On a server the answer comes from the SCOPE seam rather than from the active\n * runtime, and the difference is not academic. `runInLankaServerScope` creates\n * its instance inside the scope and `createLanka` activates every instance it\n * builds, so during a request the process pointer and the scope's runtime are\n * the same object — keying on the runtime would make \"inside a request\" and\n * \"after one ended\" indistinguishable, and a call made after would be handed the\n * last stranger's ViewModel.\n *\n * So: a scope resolver installed and answering `null` means this ran outside\n * every request, and that fails loudly. No fallback, for the same reason\n * `requireActiveRuntime` has none — a wrong answer here is one user's data in\n * another user's page, and it would surface three layers from the call.\n *\n * ## Why the build is wrapped\n *\n * `ALankaVM.build()` declares a ViewModel that has scenario handlers into a\n * PROCESS-wide list, so that every instance ever created adopts it. That is\n * right for a module-level ViewModel and catastrophic for a scoped one: the list\n * would grow per request forever, and request N+1 would adopt request N's\n * ViewModel and run N's handlers against N's gateways. `buildScoped` is the seam\n * that keeps a scoped declaration out of it.\n */\nexport const resolveLankaVM = <TViewModel extends ILankaReadableVM<object>>(\n\tdefinition: ILankaVMDefinition<TViewModel>,\n): TViewModel => {\n\tconst recipe = lankaVMRecipes.recipes.get(definition);\n\n\tif (!recipe) {\n\t\tthrow new Error(\n\t\t\t\"resolveLankaVM was given something defineLankaVM did not make. A definition \" +\n\t\t\t\t\"is opaque on purpose: build it with defineLankaVM rather than by hand.\",\n\t\t);\n\t}\n\n\tconst runtime = requireActiveRuntime();\n\tconst scope = getActiveLankaScope();\n\n\tif (hasLankaScopeResolver() && !scope) {\n\t\tthrow new Error(\n\t\t\t`resolveLankaVM(\"${recipe.name}\") ran outside every scope. A scope resolver is ` +\n\t\t\t\t\"installed, which on a server means this code ran outside a request — and a \" +\n\t\t\t\t\"ViewModel resolved there would be the previous request's. Do this work inside \" +\n\t\t\t\t\"the scope, or start one.\",\n\t\t);\n\t}\n\n\t// The runtime when nothing knows about scopes: a browser tab is one scope for\n\t// its whole life, and the instance is what identifies it.\n\tconst instances = lankaVMRecipes.forScope(scope ?? runtime);\n\tconst held = instances.get(definition);\n\n\tif (held) return held as TViewModel;\n\n\tconst built = lankaScenarioBootstrap.buildScoped(() => recipe.build());\n\n\tinstances.set(definition, built);\n\n\treturn built as TViewModel;\n};\n"],"mappings":"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","names":[]}
|
|
@@ -176,7 +176,7 @@ interface ILankaStatelessScenarioBinding<TData, TState extends object, TGateways
|
|
|
176
176
|
usedBy?: string;
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
|
-
type TLankaStatelessVMConfig
|
|
179
|
+
type TLankaStatelessVMConfig<Actions extends object, TGateways extends object = Record<string, never>, Services extends object = Record<string, never>> = {
|
|
180
180
|
name: string;
|
|
181
181
|
createActions: (ctx: ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>) => Actions;
|
|
182
182
|
/** A FACTORY is read at bind time — see `TLankaScenarioBindingsDeclaration`. */
|
|
@@ -194,22 +194,51 @@ type TLankaStatelessVMConfig$1<Actions extends object, TGateways extends object
|
|
|
194
194
|
* resolves to exactly the type the factory returns.
|
|
195
195
|
*/
|
|
196
196
|
type TLankaStatelessVMHook<Actions extends object> = ILankaReadableVM<Actions & ILankaScenarioVM>;
|
|
197
|
-
declare function createStatelessLankaVM<Actions extends object>(config: TLankaStatelessVMConfig
|
|
198
|
-
declare function createStatelessLankaVM<Actions extends object, Services extends object>(config: TLankaStatelessVMConfig
|
|
199
|
-
declare function createStatelessLankaVM<Actions extends object, TGateways extends object>(config: TLankaStatelessVMConfig
|
|
200
|
-
declare function createStatelessLankaVM<Actions extends object, TGateways extends object, Services extends object>(config: TLankaStatelessVMConfig
|
|
197
|
+
declare function createStatelessLankaVM<Actions extends object>(config: TLankaStatelessVMConfig<Actions, Record<string, never>, Record<string, never>>): ILankaReadableVM<Actions & ILankaScenarioVM>;
|
|
198
|
+
declare function createStatelessLankaVM<Actions extends object, Services extends object>(config: TLankaStatelessVMConfig<Actions, Record<string, never>, Services>): ILankaReadableVM<Actions & ILankaScenarioVM>;
|
|
199
|
+
declare function createStatelessLankaVM<Actions extends object, TGateways extends object>(config: TLankaStatelessVMConfig<Actions, TGateways, Record<string, never>>): ILankaReadableVM<Actions & ILankaScenarioVM>;
|
|
200
|
+
declare function createStatelessLankaVM<Actions extends object, TGateways extends object, Services extends object>(config: TLankaStatelessVMConfig<Actions, TGateways, Services>): ILankaReadableVM<Actions & ILankaScenarioVM>;
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
/**
|
|
203
|
+
* What the LAZY stateless factory takes, which is not what the eager one takes.
|
|
204
|
+
*
|
|
205
|
+
* This type was called `TLankaStatelessVMConfig` — the same name the eager
|
|
206
|
+
* factory declares and `lanka/viewmodel` publishes — and the two are not the
|
|
207
|
+
* same shape. One name over two types is a surface that cannot be read: a
|
|
208
|
+
* consumer annotating a shared config with the published name and passing it
|
|
209
|
+
* here got a compile error nothing in the published surface explained, and five
|
|
210
|
+
* binding mirrors had to derive their parameter type off this function rather
|
|
211
|
+
* than name it. Renaming costs nothing, because no barrel ever re-exported the
|
|
212
|
+
* duplicate.
|
|
213
|
+
*
|
|
214
|
+
* **Where it genuinely differs, and it is not cosmetic.** The eager config hands
|
|
215
|
+
* `createActions`, `onInit` and `onReset` an `ILankaStatelessVMContext`, whose
|
|
216
|
+
* `set` is `TLankaSetState`. This one is `ILankaVMConfig` with `states` removed,
|
|
217
|
+
* so it hands them the STATEFUL `ILankaVMContext`, whose `set` is zustand's
|
|
218
|
+
* `setState` — including the `replace` argument, on a ViewModel that holds
|
|
219
|
+
* nothing to replace.
|
|
220
|
+
*
|
|
221
|
+
* It also accepts `enhancers` and `enableAccessTrackingOptimization`, which
|
|
222
|
+
* `createStatelessLankaVM` does not read: a stateless ViewModel has no store to
|
|
223
|
+
* enhance and nothing whose reads could be tracked. They are accepted and
|
|
224
|
+
* ignored, and that is worth knowing before writing one.
|
|
225
|
+
*
|
|
226
|
+
* Both are the eager factory's shape leaking through `Omit`, and narrowing this
|
|
227
|
+
* to the eager config would REFUSE configs that compile today — the unsafe move
|
|
228
|
+
* `skills/surface/SKILL.md` §6c names. So the difference is published under its
|
|
229
|
+
* own name instead, where a reader meets it, and closing it belongs to a major.
|
|
230
|
+
*/
|
|
231
|
+
type TLankaLazyStatelessVMConfig<Actions extends object, TGateways extends object = Record<string, never>, Services extends object = Record<string, never>> = Omit<ILankaVMConfig<object, Actions, TGateways, Services>, "states"> & {
|
|
203
232
|
states?: never;
|
|
204
233
|
};
|
|
205
234
|
type TLazyStatelessReturn<Actions extends object, TGateways extends object, Services extends object> = ReturnType<typeof createStatelessLankaVM<Actions, TGateways, Services>> & {
|
|
206
235
|
getState: () => Actions & ILankaScenarioVM;
|
|
207
236
|
dispose: () => void;
|
|
208
237
|
};
|
|
209
|
-
declare function createLazyStatelessLankaVM<Actions extends object>(config:
|
|
210
|
-
declare function createLazyStatelessLankaVM<Actions extends object, Services extends object>(config:
|
|
211
|
-
declare function createLazyStatelessLankaVM<Actions extends object, TGateways extends object>(config:
|
|
212
|
-
declare function createLazyStatelessLankaVM<Actions extends object, TGateways extends object, Services extends object>(config:
|
|
238
|
+
declare function createLazyStatelessLankaVM<Actions extends object>(config: TLankaLazyStatelessVMConfig<Actions, Record<string, never>, Record<string, never>>): TLazyStatelessReturn<Actions, Record<string, never>, Record<string, never>>;
|
|
239
|
+
declare function createLazyStatelessLankaVM<Actions extends object, Services extends object>(config: TLankaLazyStatelessVMConfig<Actions, Record<string, never>, Services>): TLazyStatelessReturn<Actions, Record<string, never>, Services>;
|
|
240
|
+
declare function createLazyStatelessLankaVM<Actions extends object, TGateways extends object>(config: TLankaLazyStatelessVMConfig<Actions, TGateways, Record<string, never>>): TLazyStatelessReturn<Actions, TGateways, Record<string, never>>;
|
|
241
|
+
declare function createLazyStatelessLankaVM<Actions extends object, TGateways extends object, Services extends object>(config: TLankaLazyStatelessVMConfig<Actions, TGateways, Services>): TLazyStatelessReturn<Actions, TGateways, Services>;
|
|
213
242
|
|
|
214
243
|
interface ILankaSharedStoreVMContext<TStoreState extends object, TFullState extends object, TStore extends ALankaSharedStore<TStoreState>, TGateways extends object, TServices extends object> {
|
|
215
244
|
set: StoreApi$1<TStoreState>["setState"];
|
|
@@ -674,4 +703,4 @@ type TLankaVMStateCreator<TFullState> = StateCreator<TFullState, TLankaAnyMutato
|
|
|
674
703
|
|
|
675
704
|
type TLankaSharedStoreSetState<TState extends object> = (partial: TState | Partial<TState> | ((state: TState) => TState | Partial<TState>), replace?: boolean) => void;
|
|
676
705
|
|
|
677
|
-
export { ALankaSharedStore, ALankaSharedStoreVM, ALankaStatelessVM, ALankaVM, ILankaReadableVM, type ILankaScenarioBinding, type ILankaSharedStoreScenarioBinding, type ILankaSharedStoreVM, type ILankaSharedStoreVMConfig, type ILankaSharedStoreVMContext, type ILankaStatelessScenarioBinding, type ILankaStatelessVMContext, type ILankaVM, type ILankaVMConfig, type ILankaVMContext, type TLankaAnyMutators, type TLankaSetState, type TLankaSharedStoreSetState, type TLankaSharedStoreVMHook, type TLankaStatelessVMConfig
|
|
706
|
+
export { ALankaSharedStore, ALankaSharedStoreVM, ALankaStatelessVM, ALankaVM, ILankaReadableVM, type ILankaScenarioBinding, type ILankaSharedStoreScenarioBinding, type ILankaSharedStoreVM, type ILankaSharedStoreVMConfig, type ILankaSharedStoreVMContext, type ILankaStatelessScenarioBinding, type ILankaStatelessVMContext, type ILankaVM, type ILankaVMConfig, type ILankaVMContext, type TLankaAnyMutators, type TLankaLazyStatelessVMConfig, type TLankaSetState, type TLankaSharedStoreSetState, type TLankaSharedStoreVMHook, type TLankaStatelessVMConfig, type TLankaStatelessVMHook, type TLankaVMEnhancer, type TLankaVMStateCreator, type TLazyLankaVM, type TUnknownLankaScenarioBinding, createLankaShallowHold, createLankaSharedStore, createLankaVM, createLazyLankaVM, createLazySharedStoreLankaVM, createLazyStatelessLankaVM, createSharedStoreLankaVM, createStatelessLankaVM };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/viewmodel/_abstractions/lanka-vm-environment/ALankaVMEnvironment.ts","../../src/viewmodel/_abstractions/lanka-vm/ALankaVM.ts","../../src/viewmodel/_internal/create-lanka-blind-spot-trap/createLankaBlindSpotTrap.ts","../../src/viewmodel/_internal/create-lanka-scenario-binder/createLankaScenarioBinder.ts","../../src/viewmodel/_utils/resolve-lanka-dependency/resolveLankaDependency.ts","../../src/viewmodel/_factories/create-lanka-vm/createLankaVM.ts","../../src/viewmodel/_internal/create-lazy-lanka-vm-proxy/createLazyLankaVMProxy.ts","../../src/viewmodel/_factories/create-lazy-lanka-vm/createLazyLankaVM.ts","../../src/viewmodel/_abstractions/lanka-stateless-vm/ALankaStatelessVM.ts","../../src/viewmodel/_factories/create-stateless-lanka-vm/createStatelessLankaVM.ts","../../src/viewmodel/_factories/create-lazy-stateless-lanka-vm/createLazyStatelessLankaVM.ts","../../src/viewmodel/_factories/create-shared-store-lanka-vm/createSharedStoreLankaVM.ts","../../src/viewmodel/_factories/create-lazy-shared-store-lanka-vm/createLazySharedStoreLankaVM.ts","../../src/viewmodel/_abstractions/lanka-shared-store-vm/ALankaSharedStoreVM.ts","../../src/viewmodel/_abstractions/lanka-shared-store/ALankaSharedStore.ts","../../src/viewmodel/_factories/create-lanka-shared-store/createLankaSharedStore.ts","../../src/viewmodel/_factories/create-lanka-shallow-hold/createLankaShallowHold.ts"],"sourcesContent":["import type { ILankaVMLifecycleHooks } from \"../../_interfaces/ILankaVMLifecycleHooks\";\n\n/**\n * What every ViewModel is given, and the two moments it is told about.\n *\n * The three ViewModel shapes — stateful, stateless, over a shared store — differ\n * in where their state lives and in nothing else about this: each is handed a\n * data layer and a set of collaborators, and each is told when its scenarios are\n * bound and when they are about to be unbound.\n *\n * Stated once because a fifth hook added to two of the three is exactly the\n * divergence `skills/parity/SKILL.md` is written against, and three copies of a\n * default is how that starts.\n *\n * It is not a role and nothing extends it directly: the three bases do, and a\n * consumer extends one of them.\n */\nexport abstract class ALankaVMEnvironment<\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> {\n\t/** What `createGateways` answered: the data layer, kept apart from services. */\n\tprotected gateways!: TGateways;\n\n\t/** What `createServices` answered: everything that is not a gateway. */\n\tprotected services!: Services;\n\n\t/** The data layer, built once per ViewModel. */\n\tprotected createGateways(): TGateways {\n\t\treturn {} as TGateways;\n\t}\n\n\t/** Non-gateway collaborators, built once per ViewModel. */\n\tprotected createServices(): Services {\n\t\treturn {} as Services;\n\t}\n\n\t/**\n\t * Runs once the scenarios are bound: inside `startLanka()` for a ViewModel built\n\t * at module level, inside `build()` for one built after bootstrap — in both\n\t * cases before any screen has read the hook the build returns.\n\t */\n\tprotected onInit(): void {}\n\n\t/**\n\t * Runs when the ViewModel is released — the framework instance disposed, or a\n\t * lazy ViewModel's `dispose()` — after its scenario subscriptions are gone.\n\t */\n\tprotected onReset(): void {}\n\n\t/**\n\t * The two moments above as the scenario binder receives them: present only\n\t * where this ViewModel took them.\n\t *\n\t * Framework plumbing, not an extension point — a ViewModel overrides `onInit`\n\t * and `onReset`, never this. Protected because the three bases call it, and\n\t * named after `toStyleContext` for the same reason: a derived view of the\n\t * protected surface, assembled by the framework.\n\t *\n\t * Why \"took them\" decides anything: `onInit` runs inside `initializeScenario`,\n\t * which bootstrap alone calls, on the ViewModels registered with it.\n\t * Registration used to follow scenario bindings only, so a ViewModel that\n\t * overrode `onInit` and bound nothing was never initialised — silently. Now a\n\t * declared hook registers the ViewModel too, and a default no-op must not\n\t * count, or every ViewModel would sit in the scenario registry for nothing.\n\t * The functional bridges declare theirs as own properties over these methods,\n\t * which is what this comparison sees.\n\t */\n\tprotected toLifecycleHooks(): ILankaVMLifecycleHooks {\n\t\tconst defaults = ALankaVMEnvironment.prototype;\n\t\tconst hooks: ILankaVMLifecycleHooks = {};\n\n\t\tif (this.onInit !== defaults.onInit) hooks.onInit = () => this.onInit();\n\t\tif (this.onReset !== defaults.onReset) hooks.onReset = () => this.onReset();\n\n\t\treturn hooks;\n\t}\n}\n","import { ALankaVMEnvironment } from \"../lanka-vm-environment/ALankaVMEnvironment\";\nimport { createStore, StoreApi } from \"zustand/vanilla\";\nimport { getLankaFlags } from \"../../../config/get-lanka-flags/getLankaFlags\";\nimport { createLankaBlindSpotTrap } from \"../../_internal/create-lanka-blind-spot-trap/createLankaBlindSpotTrap\";\nimport { createLankaScenarioBinder } from \"../../_internal/create-lanka-scenario-binder/createLankaScenarioBinder\";\nimport { lankaBlindSpotRegistry } from \"../../_internal/lanka-blind-spot-registry/lankaBlindSpotRegistry\";\nimport { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { lankaScenarioBootstrap } from \"../../../scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\nimport type { ILankaScenario } from \"../../../scenario/_interfaces/ILankaScenario\";\nimport type { ILankaVM } from \"../../_interfaces/ILankaVM\";\nimport type { ILankaVMContext } from \"../../_interfaces/ILankaVMContext\";\nimport type { TLankaVMEnhancer } from \"../../_types/TLankaVMEnhancer\";\nimport type { TLankaVMStateCreator } from \"../../_types/TLankaVMStateCreator\";\nimport type { TLankaScenarioBindingsDeclaration } from \"../../_types/TLankaScenarioBindingsDeclaration\";\nimport type { TUnknownLankaScenarioBinding } from \"../../_types/TUnknownLankaScenarioBinding\";\n\n/**\n * A ViewModel written as a class: the state a screen reads, and the only place a\n * gateway is called from.\n *\n * This is the implementation of the role, and `createLankaVM` is the same thing\n * reached the other way — a subclass built from an options object. Neither style\n * can do what the other cannot, because there is nothing here to diverge from.\n *\n * The protected surface IS the functional context, member for member:\n * `set`, `get`, `gateways`, `services`, `trigger`. What the config supplies as a\n * value or a thunk, the class supplies by overriding a method of the same name —\n * `states`, `scenarioHandlers`, `enhancers`, `onInit`, `onReset` — with the two\n * dependency suppliers named `createGateways` and `createServices`, because\n * `gateways` and `services` already name what they answer. `toLifecycleHooks`\n * is protected too and is not one of these: it is how the framework reads the\n * two hooks, and a ViewModel overrides the hooks, never it.\n *\n * ```ts\n * class TodoVM extends ALankaVM<ITodoState, ITodoActions, ITodoGateways> {\n * \tprotected readonly name = \"TodoVM\";\n *\n * \tprotected states(): ITodoState {\n * \t\treturn { todos: [], isLoading: false };\n * \t}\n *\n * \tprotected createGateways(): ITodoGateways {\n * \t\treturn { todo: new TodoGateway() };\n * \t}\n *\n * \tprotected createActions(): ITodoActions {\n * \t\treturn {\n * \t\t\tload: async () => {\n * \t\t\t\tthis.set({ isLoading: true });\n * \t\t\t\tthis.set({ todos: await this.gateways.todo.list(), isLoading: false });\n * \t\t\t},\n * \t\t};\n * \t}\n * }\n *\n * export const useTodoVM = new TodoVM().build();\n * ```\n *\n * The access-tracking blind spot the functional style documents is the same one\n * here, and `enableAccessTrackingOptimization` is the same switch. A consumer\n * re-renders only for state keys it READ off the returned proxy; an action that\n * DERIVES a value reads the store through `get`, which the proxy never sees, so a\n * component whose only link to a key is such a getter never re-renders for it. In\n * development the mismatch announces itself by name rather than by a frozen\n * screen. Canon: `skills/parity/SKILL.md`.\n */\nexport abstract class ALankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> extends ALankaVMEnvironment<TGateways, Services> {\n\t/** Names the ViewModel in the logs, the scenario registry and the blind-spot warning. */\n\tprotected abstract readonly name: string;\n\n\t/**\n\t * Turn off when one broad consumer reads most fields, or when an action derives\n\t * what the screen shows — proxy tracking then costs more than it saves, and in\n\t * the second case it cannot see the read at all.\n\t */\n\tprotected readonly enableAccessTrackingOptimization: boolean = true;\n\n\t/** Writes state. Available from `createActions` onwards, never before. */\n\tprotected set!: StoreApi<State & Actions & ILankaScenarioVM>[\"setState\"];\n\n\t/** Reads state. The read a tracked hook cannot see — hence the switch above. */\n\tprotected get!: () => State & Actions & ILankaScenarioVM;\n\n\t/** Fires a scenario, which every ViewModel bound to it then hears. */\n\tprotected trigger = <TData>(scenario: ILankaScenario<TData>, data?: TData): void => {\n\t\tscenario.trigger(data);\n\t};\n\n\t/** The reactive fields the screen reads. */\n\tprotected states(): State {\n\t\treturn {} as State;\n\t}\n\n\t/**\n\t * The scenarios this ViewModel listens to, unsubscribed for it on reset.\n\t *\n\t * Returning a FACTORY postpones building the list until bind time, which is\n\t * what a ViewModel declared at module level needs when its entries name their\n\t * scenarios through the locator. See `TLankaScenarioBindingsDeclaration`.\n\t */\n\tprotected scenarioHandlers(): TLankaScenarioBindingsDeclaration<\n\t\tTUnknownLankaScenarioBinding<State & Actions, TGateways, Services>\n\t> {\n\t\treturn [];\n\t}\n\n\t/** Store middleware — `persist`, `lankaDevtools` — the last one applied outermost. */\n\tprotected enhancers(): TLankaVMEnhancer<State & Actions & ILankaScenarioVM>[] {\n\t\treturn [];\n\t}\n\n\t/** The actions the screen calls. Written against `this.set` and `this.get`. */\n\tprotected abstract createActions(): Actions;\n\n\t/**\n\t * The protected surface as an object, for the functional style.\n\t *\n\t * Assembled INSIDE the class because that is the only place `protected` can be\n\t * read — a context built from outside could carry only the public half, which is\n\t * the wrong half. Canon: `skills/parity/SKILL.md` section 3a.\n\t */\n\tprotected toStyleContext(): ILankaVMContext<\n\t\tState & Actions & ILankaScenarioVM,\n\t\tTGateways,\n\t\tServices\n\t> {\n\t\treturn {\n\t\t\tset: this.set,\n\t\t\tget: this.get,\n\t\t\tgateways: this.gateways,\n\t\t\tservices: this.services,\n\t\t\ttrigger: this.trigger,\n\t\t};\n\t}\n\n\t/**\n\t * Builds the ViewModel a screen reads. One store per call.\n\t *\n\t * What comes back is a STORE, not a hook: `getState`, `subscribe`, `setState`,\n\t * plus the name and the tracking flag. A screen reaches it through its\n\t * framework's binding — `useLankaVM(todoVM)` from `@lankajs/react`,\n\t * `@lankajs/vue` and the rest of the shelf — and a program with no framework\n\t * at all reads `getState()` and `subscribe()` directly.\n\t */\n\tpublic build(): ILankaVM<State & Actions & ILankaScenarioVM> {\n\t\ttype TFullState = State & Actions & ILankaScenarioVM;\n\n\t\tlankaLogger.printViewModelLog(\"START Create VM\", this.name);\n\n\t\tconst blindSpot = createLankaBlindSpotTrap(\n\t\t\tthis.name,\n\t\t\tgetLankaFlags().isDevelopment === true,\n\t\t);\n\n\t\t// One binder per ViewModel, built OUTSIDE the state creator: a middleware may\n\t\t// run the creator more than once, and the binder's verdict on bootstrap is\n\t\t// read after the store exists. The bindings are passed through unopened — a\n\t\t// factory is called at bind time, which is the whole point of that form.\n\t\tconst hooks = this.toLifecycleHooks();\n\t\tconst { initializeScenario, resetScenario, needsBootstrap } = createLankaScenarioBinder({\n\t\t\tname: this.name,\n\t\t\tbindings: this.scenarioHandlers(),\n\t\t\tcontext: () => this.toStyleContext(),\n\t\t\tonInit: hooks.onInit,\n\t\t\tonReset: hooks.onReset,\n\t\t});\n\n\t\tconst stateCreator: TLankaVMStateCreator<TFullState> = (set, get) => {\n\t\t\tthis.set = set;\n\t\t\tthis.get = blindSpot.observeGet(get);\n\t\t\tthis.gateways = this.createGateways();\n\t\t\tthis.services = this.createServices();\n\n\t\t\tconst actions = blindSpot.observeActions(this.createActions());\n\n\t\t\treturn {\n\t\t\t\t...this.states(),\n\t\t\t\t...actions,\n\t\t\t\tinitializeScenario,\n\t\t\t\tresetScenario,\n\t\t\t};\n\t\t};\n\n\t\tconst enhancedCreator = this.enhancers().reduce<TLankaVMStateCreator<TFullState>>(\n\t\t\t(creator, enhance) => enhance(creator),\n\t\t\tstateCreator,\n\t\t);\n\n\t\tconst store = createStore<TFullState>()(enhancedCreator);\n\n\t\t/**\n\t\t * The two members the port adds to what a store already answers.\n\t\t *\n\t\t * A vanilla store is most of `ILankaVM` already — `getState`, `subscribe`,\n\t\t * `getInitialState`, `setState` — and what it cannot know is who it belongs\n\t\t * to and what this ViewModel decided about tracking. Attached rather than\n\t\t * wrapped, so the object a screen holds IS the store: no extra hop, and\n\t\t * every zustand middleware a consumer applied still reaches it.\n\t\t *\n\t\t * Non-enumerable, because they are a description of the ViewModel rather\n\t\t * than part of its state, and a spread of the store should not pick them up.\n\t\t */\n\t\tconst viewModel: ILankaVM<TFullState> = Object.defineProperties(store, {\n\t\t\tname: { value: this.name, enumerable: false, configurable: true },\n\t\t\tisAccessTracked: {\n\t\t\t\tvalue: this.enableAccessTrackingOptimization,\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: true,\n\t\t\t},\n\t\t}) as unknown as ILankaVM<TFullState>;\n\n\t\tlankaBlindSpotRegistry.remember(viewModel, blindSpot);\n\n\t\tif (needsBootstrap) {\n\t\t\tlankaScenarioBootstrap.registerViewModel(store.getState(), this.name);\n\t\t}\n\n\t\tlankaLogger.printViewModelLog(\"FINISH Create VM\", this.name);\n\n\t\treturn viewModel;\n\t}\n}\n","export interface ILankaBlindSpotTrap {\n\t/** Whether the trap is armed. Everything below is inert when it is not. */\n\treadonly isArmed: boolean;\n\t/** Wraps `get` so reads made inside an action are attributed to it. */\n\tobserveGet: <TState>(get: () => TState) => () => TState;\n\t/** Wraps the actions so the trap knows which one is running. */\n\tobserveActions: <TActions extends object>(declared: TActions) => TActions;\n\t/** Reports a change that will not re-render, when the component reads the key. */\n\treport: (\n\t\ttrackedKeys: Set<string>,\n\t\tnext: Record<string, unknown>,\n\t\tprev: Record<string, unknown>,\n\t) => void;\n}\n\n/**\n * The blind spot: a key the component reads through a GETTER.\n *\n * Access tracking sees direct reads off the proxy. A key reached only inside a\n * derived getter is invisible to it, so a change to that key re-renders nothing\n * and the screen freezes with no error anywhere.\n *\n * The trap watches which keys each action reads through `get()`, and warns when\n * one of those changes without a tracked key changing with it.\n *\n * Development only: in production this is work on a hot path plus console noise\n * for somebody who does not write the code. Disarmed, every method is identity.\n */\nexport const createLankaBlindSpotTrap = (\n\tviewModelName: string,\n\tisArmed: boolean,\n): ILankaBlindSpotTrap => {\n\tif (!isArmed) {\n\t\treturn {\n\t\t\tisArmed: false,\n\t\t\tobserveGet: (get) => get,\n\t\t\tobserveActions: (declared) => declared,\n\t\t\treport: () => undefined,\n\t\t};\n\t}\n\n\t/** Action name → state keys it read through `get()`, past the proxy. */\n\tconst indirectReads = new Map<string, Set<string>>();\n\t/** Keys already warned about: a second warning adds nothing. */\n\tconst warnedKeys = new Set<string>();\n\tlet activeActionName: string | null = null;\n\n\treturn {\n\t\tisArmed: true,\n\n\t\tobserveGet<TState>(get: () => TState): () => TState {\n\t\t\treturn () => {\n\t\t\t\tconst state = get();\n\t\t\t\tconst actionName = activeActionName;\n\t\t\t\tif (actionName === null) return state;\n\n\t\t\t\treturn new Proxy(state as object, {\n\t\t\t\t\tget(target, prop, receiver) {\n\t\t\t\t\t\tif (typeof prop === \"string\") {\n\t\t\t\t\t\t\tlet keys = indirectReads.get(actionName);\n\t\t\t\t\t\t\tif (!keys) {\n\t\t\t\t\t\t\t\tkeys = new Set<string>();\n\t\t\t\t\t\t\t\tindirectReads.set(actionName, keys);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tkeys.add(prop);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn Reflect.get(target, prop, receiver) as unknown;\n\t\t\t\t\t},\n\t\t\t\t}) as TState;\n\t\t\t};\n\t\t},\n\n\t\tobserveActions<TActions extends object>(declared: TActions): TActions {\n\t\t\tconst observed = { ...declared } as Record<string, unknown>;\n\n\t\t\tfor (const [name, value] of Object.entries(declared)) {\n\t\t\t\tif (typeof value !== \"function\") continue;\n\n\t\t\t\tobserved[name] = (...args: unknown[]) => {\n\t\t\t\t\t// Restored rather than cleared: actions call each other, and clearing\n\t\t\t\t\t// would attribute the caller's later reads to nobody.\n\t\t\t\t\tconst previous = activeActionName;\n\t\t\t\t\tactiveActionName = name;\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn (value as (...a: unknown[]) => unknown)(...args);\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tactiveActionName = previous;\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn observed as TActions;\n\t\t},\n\n\t\treport(trackedKeys, next, prev): void {\n\t\t\tfor (const [changedKey, value] of Object.entries(next)) {\n\t\t\t\tif (Object.is(value, prev[changedKey])) continue;\n\t\t\t\tif (trackedKeys.has(changedKey) || warnedKeys.has(changedKey)) continue;\n\n\t\t\t\t// Not every untracked key is a fault: not reading what you do not need\n\t\t\t\t// is exactly what tracking exists for. The fault is a key the component\n\t\t\t\t// DOES read — through a getter taken off the proxy.\n\t\t\t\tconst viaGetter = [...trackedKeys].some((key) =>\n\t\t\t\t\tindirectReads.get(key)?.has(changedKey),\n\t\t\t\t);\n\t\t\t\tif (!viaGetter) continue;\n\n\t\t\t\twarnedKeys.add(changedKey);\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[lanka] ${viewModelName}: key \"${changedKey}\" changed but no re-render will follow. ` +\n\t\t\t\t\t\t`The component reads it only through a getter, and tracking sees direct proxy reads only. ` +\n\t\t\t\t\t\t`Set enableAccessTrackingOptimization: false on this ViewModel — ` +\n\t\t\t\t\t\t`destructuring the key in the view \"for the side effect\" reads as dead code and will not survive a refactor.`,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t};\n};\n","import type { TLankaScenarioBindingsDeclaration } from \"../../_types/TLankaScenarioBindingsDeclaration\";\n\n/**\n * A subscription option bag, as wide as the bus accepts.\n *\n * The binder passes it through and reads only `usedBy`; naming the rest here\n * would make this file a second declaration of the bus's options.\n */\nexport type TLankaScenarioBindingOptions = Record<string, unknown> & { usedBy?: string };\n\n/**\n * What the binder needs of a binding, and nothing more.\n *\n * Structural rather than the concrete `ILankaScenarioBinding`: three ViewModel\n * families declare their own binding type over their own context, and the binder\n * only ever calls `subscribe` and `handler`.\n */\nexport interface ILankaScenarioBindingLike<TContext, TData = unknown> {\n\tscenario: {\n\t\teventType: string;\n\t\tsubscribe: (\n\t\t\tcallback: (data?: TData) => void,\n\t\t\toptions?: TLankaScenarioBindingOptions,\n\t\t) => () => void;\n\t};\n\thandler: (context: TContext) => (data?: TData) => void;\n\toptions?: TLankaScenarioBindingOptions;\n}\n\nexport interface ILankaScenarioBinderConfig<TContext> {\n\t/** The ViewModel's name, reported to the bus as the subscriber. */\n\tname: string;\n\t/**\n\t * What to bind. A ViewModel with none still gets a working binder.\n\t *\n\t * A factory is read at BIND time — like `context` below, and for the same\n\t * reason: an entry that names its scenario through the locator must not be\n\t * built while the declaring module is evaluated. See\n\t * `TLankaScenarioBindingsDeclaration`.\n\t */\n\tbindings?: TLankaScenarioBindingsDeclaration<ILankaScenarioBindingLike<TContext>>;\n\t/** The context handlers are built with, read at bind time. */\n\tcontext: () => TContext;\n\tonInit?: (context: TContext) => void;\n\tonReset?: (context: TContext) => void;\n}\n\nexport interface ILankaScenarioBinder {\n\t/** Whether the bindings are live. */\n\treadonly isInitialized: boolean;\n\t/**\n\t * Whether bootstrap must know this ViewModel: it has scenarios to bind, or a\n\t * lifecycle moment to be told about. `onInit` runs inside `initializeScenario`,\n\t * which only bootstrap calls, so a ViewModel that is not registered never hears\n\t * either hook.\n\t */\n\treadonly needsBootstrap: boolean;\n\t/** Subscribes every binding once. A second call does nothing. */\n\tinitializeScenario: () => void;\n\t/** Releases every subscription and allows a later re-initialisation. */\n\tresetScenario: () => void;\n}\n\n/**\n * The bindings as declared, opened at the moment of binding.\n *\n * Its own function because WHEN this runs is the point: a factory form exists so\n * that the locator lookups inside the entries happen after the framework\n * instance does, and the only call site is inside `initializeScenario`.\n */\nconst readBindings = <TContext>(\n\tdeclared: TLankaScenarioBindingsDeclaration<ILankaScenarioBindingLike<TContext>> | undefined,\n): readonly ILankaScenarioBindingLike<TContext>[] =>\n\ttypeof declared === \"function\" ? declared() : (declared ?? []);\n\n/**\n * Whether bootstrap must be told about the ViewModel: something to bind, or a\n * lifecycle moment to be told about.\n *\n * Decided here, once, for all three ViewModel families: each wrote the bindings\n * half of this and none wrote the lifecycle half, so a ViewModel with a hook and\n * no scenarios was never initialised. A factory counts as bindings WITHOUT being\n * opened — opening it here would be the module-scope locator read the form\n * exists to postpone. See `TLankaScenarioBindingsDeclaration`.\n */\nconst needsBootstrap = <TContext>(config: ILankaScenarioBinderConfig<TContext>): boolean =>\n\ttypeof config.bindings === \"function\" ||\n\t(config.bindings !== undefined && config.bindings.length > 0) ||\n\tconfig.onInit !== undefined ||\n\tconfig.onReset !== undefined;\n\n/**\n * The scenario lifetime of a ViewModel: bind once, release on reset.\n *\n * All three ViewModel families need exactly this, and all three had written it\n * out — two with an array plus a Set of seen event types, one with a Map. The\n * Map is the shape kept, because it does both jobs with one structure: the key\n * prevents a second subscription to the same event, and the value is the\n * function that releases THIS subscription.\n *\n * Releasing by callback identity, which an array invites, removes the first\n * entry carrying that callback rather than the caller's own — two handlers whose\n * closures compare equal cancel each other.\n */\nexport const createLankaScenarioBinder = <TContext>(\n\tconfig: ILankaScenarioBinderConfig<TContext>,\n): ILankaScenarioBinder => {\n\tconst subscriptions = new Map<string, () => void>();\n\tlet isInitialized = false;\n\n\treturn {\n\t\tneedsBootstrap: needsBootstrap(config),\n\t\tget isInitialized(): boolean {\n\t\t\treturn isInitialized;\n\t\t},\n\n\t\tinitializeScenario(): void {\n\t\t\tif (isInitialized) return;\n\n\t\t\tconst context = config.context();\n\n\t\t\tfor (const binding of readBindings(config.bindings)) {\n\t\t\t\tconst key = binding.scenario.eventType;\n\t\t\t\tif (subscriptions.has(key)) continue;\n\n\t\t\t\tsubscriptions.set(\n\t\t\t\t\tkey,\n\t\t\t\t\tbinding.scenario.subscribe(binding.handler(context), {\n\t\t\t\t\t\tusedBy: config.name,\n\t\t\t\t\t\t...binding.options,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconfig.onInit?.(context);\n\t\t\tisInitialized = true;\n\t\t},\n\n\t\tresetScenario(): void {\n\t\t\tfor (const release of subscriptions.values()) release();\n\t\t\tsubscriptions.clear();\n\t\t\tisInitialized = false;\n\n\t\t\tconfig.onReset?.(config.context());\n\t\t},\n\t};\n};\n","/**\n * A dependency bag given either directly or as a factory.\n *\n * The factory form exists so a ViewModel declared at module level does not\n * resolve its gateways at import time: the locator needs an active framework\n * instance, and a module body runs before one exists.\n */\nexport type TLankaDependencyBag<TBag extends object> = TBag | (() => TBag) | undefined;\n\n/**\n * Reads a dependency bag, whichever form it was declared in.\n *\n * All three ViewModel factories wrote this out twice each — once for gateways,\n * once for services — which is six copies of one three-line decision.\n */\nexport const resolveLankaDependency = <TBag extends object>(\n\tdeclared: TLankaDependencyBag<TBag>,\n): TBag => {\n\tif (typeof declared === \"function\") return declared();\n\treturn declared ?? ({} as TBag);\n};\n","import type { ILankaVM } from \"../../_interfaces/ILankaVM\";\nimport { ALankaVM } from \"../../_abstractions/lanka-vm/ALankaVM\";\nimport { resolveLankaDependency } from \"../../_utils/resolve-lanka-dependency/resolveLankaDependency\";\nimport { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport type { ILankaVMConfig } from \"../../_interfaces/ILankaVMConfig\";\n\n/**\n * The functional style of `ALankaVM`: a ViewModel declared as an options object.\n *\n * Everything it can do, the class can do, because this IS the class — the hooks a\n * subclass overrides arrive here as config fields of the same names, and the\n * protected surface arrives as the `ctx` every hook is handed. What a ViewModel is\n * and how it binds scenarios is documented once, on `ALankaVM`.\n *\n * ⚠️ ACCESS-TRACKING BLIND SPOT. A consumer re-renders only for state keys it READ\n * off the returned proxy. An action that DERIVES a value (`getSomeView()`) reads the\n * store through `get()`, which the proxy never sees — so a component whose only link\n * to a state key is such a getter will never re-render when that key changes.\n *\n * Set `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT patch it\n * in the view by destructuring the underlying keys for their side effect only: that\n * reads as dead code, so a refactor, an unused-variable cleanup or a lint autofix\n * removes it and the screen silently freezes again. `MeetingReportViewModel` carries\n * the worked example (its report toggles froze exactly that way, twice).\n *\n * In development the mismatch ANNOUNCES ITSELF: the framework sees that a key\n * changed, that no re-render will follow, and that the component reads that key\n * through a getter — and warns with the ViewModel and key names. \"Remember to\n * set the flag\" is not a mechanism.\n */\nexport function createLankaVM<State extends object, Actions extends object>(\n\tconfig: ILankaVMConfig<State, Actions, Record<string, never>, Record<string, never>>,\n): ILankaVM<State & Actions & ILankaScenarioVM>;\n\nexport function createLankaVM<\n\tState extends object,\n\tActions extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, Record<string, never>, Services>,\n): ILankaVM<State & Actions & ILankaScenarioVM>;\n\nexport function createLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, TGateways, Record<string, never>>,\n): ILankaVM<State & Actions & ILankaScenarioVM>;\n\nexport function createLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, TGateways, Services>,\n): ILankaVM<State & Actions & ILankaScenarioVM>;\n\nexport function createLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(\n\tconfig: ILankaVMConfig<State, Actions, TGateways, Services>,\n): ILankaVM<State & Actions & ILankaScenarioVM> {\n\t/**\n\t * The bridge subclass lives here rather than in a shared helper because\n\t * `toStyleContext` is `protected`: only a class body deriving from `ALankaVM`\n\t * may read it. Canon: `skills/parity/SKILL.md` section 3a.\n\t */\n\tclass FunctionalVM extends ALankaVM<State, Actions, TGateways, Services> {\n\t\tprotected readonly name = config.name;\n\n\t\tprotected override readonly enableAccessTrackingOptimization =\n\t\t\tconfig.enableAccessTrackingOptimization ?? true;\n\n\t\tprotected override states(): State {\n\t\t\treturn config.states ?? ({} as State);\n\t\t}\n\n\t\tprotected override scenarioHandlers(): NonNullable<typeof config.scenarioHandlers> {\n\t\t\treturn config.scenarioHandlers ?? [];\n\t\t}\n\n\t\tprotected override enhancers(): NonNullable<typeof config.enhancers> {\n\t\t\treturn config.enhancers ?? [];\n\t\t}\n\n\t\tprotected override createGateways(): TGateways {\n\t\t\treturn resolveLankaDependency(config.gateways);\n\t\t}\n\n\t\tprotected override createServices(): Services {\n\t\t\treturn resolveLankaDependency(config.services);\n\t\t}\n\n\t\tprotected createActions(): Actions {\n\t\t\treturn config.createActions(this.toStyleContext());\n\t\t}\n\n\t\t// The config's hooks become this instance's OWN `onInit`/`onReset`, assigned\n\t\t// only when declared. An unconditional method override would make the base\n\t\t// read every functional ViewModel as taking both hooks and register it with\n\t\t// bootstrap for nothing; an own property is seen by the same test a class\n\t\t// override is, and `this.onInit()` means the same thing in both styles.\n\t\tpublic constructor() {\n\t\t\tsuper();\n\t\t\tconst { onInit, onReset } = config;\n\n\t\t\tif (onInit) this.onInit = () => onInit(this.toStyleContext());\n\t\t\tif (onReset) this.onReset = () => onReset(this.toStyleContext());\n\t\t}\n\t}\n\n\treturn new FunctionalVM().build();\n}\n","import { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\n\n/**\n * What a lazy ViewModel needs of the one behind it.\n *\n * Stated rather than cast: releasing a ViewModel means unsubscribing its\n * scenarios, so the mechanism does require this much — and every store it is\n * given has it, because `ILankaScenarioVM` is part of all three VM states.\n */\nexport interface ILankaReleasableStore {\n\tgetState: () => { resetScenario: () => void };\n}\n\n/**\n * What every lazy factory is handed.\n *\n * `name` and `isAccessTracked` are here rather than read off the built\n * ViewModel because they are the two members of the port that are VALUES, and a\n * property read may not build anything — see the trap below. Both are known\n * from the config the eager factory was given, so answering them costs nothing\n * and a devtool inspecting a lazy ViewModel does not construct it.\n */\nexport interface ILankaLazyVMConfig<TStore> {\n\t/** The ViewModel's name, as the log line and the port both say it. */\n\tname: string;\n\t/** What the log calls this kind of ViewModel — `VM`, `slVM`, `ssVM`. */\n\tkind: string;\n\t/** What this ViewModel decided about access tracking. */\n\tisAccessTracked: boolean;\n\t/** Builds the real ViewModel. Called at most once until `release`. */\n\tcreate: () => TStore;\n}\n\n/** A lazily built ViewModel: everything it is, plus `dispose`. */\nexport type TLazyLankaVMProxy<TStore> = TStore & { dispose: () => void };\n\n/** The one ViewModel a lazy proxy stands in front of, built at most once. */\ninterface ILankaLazySlot<TStore> {\n\tget: () => TStore;\n\trelease: () => void;\n}\n\n/**\n * Build once, release on demand: the state a lazy hook is made of.\n *\n * Separate from the forwarding below because they answer different questions —\n * WHEN the store exists, and WHERE a call goes. Read together they were one\n * function of fifty lines, which the composition canon calls what it is.\n */\nconst lazySlot = <TStore extends ILankaReleasableStore>(\n\tconfig: ILankaLazyVMConfig<TStore>,\n): ILankaLazySlot<TStore> => {\n\tlet store: TStore | null = null;\n\n\treturn {\n\t\tget: () => {\n\t\t\tif (!store) {\n\t\t\t\tlankaLogger.printViewModelLog(\n\t\t\t\t\t`LAZY: Creating ${config.kind} on first access`,\n\t\t\t\t\tconfig.name,\n\t\t\t\t);\n\t\t\t\tstore = config.create();\n\t\t\t}\n\t\t\treturn store;\n\t\t},\n\n\t\t/**\n\t\t * Releases the store. The next access builds a new one.\n\t\t *\n\t\t * Scenario subscriptions go first, through `resetScenario` — the same path\n\t\t * the between-tests reset uses. Dropping the reference without\n\t\t * unsubscribing would leave the bus holding handlers that write into a\n\t\t * discarded store.\n\t\t *\n\t\t * On a slot that never built, this does NOTHING: building a store in order\n\t\t * to destroy it is work with no result, and it would resurrect a ViewModel\n\t\t * a closed screen had just released.\n\t\t */\n\t\trelease: () => {\n\t\t\tif (!store) return;\n\t\t\tstore.getState().resetScenario();\n\t\t\tstore = null;\n\t\t},\n\t};\n};\n\n/**\n * How a member reaches the store: through a wrapper, always.\n *\n * The trap returns a FUNCTION for every member rather than the member itself, so\n * reading `useVM.setState` builds nothing and calling it builds the store. A trap\n * that resolved eagerly would end laziness the moment a devtool, a spread or a\n * debugger looked at the object — which is most of the ways an object is looked\n * at.\n *\n * `dispose` is the exception in both directions: it is not the store's, and it\n * must not build one.\n *\n * ## The thenable trap\n *\n * `then` is answered with `undefined`, never with a wrapper, and that line is\n * load-bearing. `await` and `Promise.resolve` decide whether a value is a\n * promise by READING `.then` and checking it is callable: a trap that returns a\n * function for every name says yes to that question for an object that is not a\n * promise. The runtime then calls it as `then(resolve, reject)`, the wrapper\n * forwards to a store member that does not exist, gets `undefined`, and neither\n * callback is ever invoked — so the `await` hangs FOREVER, with no error and no\n * stack.\n *\n * That makes `await someLazyVM` and every `async` function that RETURNS one a\n * silent deadlock, which is a shape a test harness reaches for constantly:\n * `const vm = await load()` resolves a promise with the proxy, and resolution\n * adopts a thenable. It cost an afternoon in a consumer's suite, where nineteen\n * tests timed out at 30s each and named their own first line.\n *\n * `catch` and `finally` are excluded with it. They are not part of the\n * thenable check, but an object answering `then` alone while a caller treats it\n * as a promise is the more confusing half of the same mistake — and no zustand\n * store has a member by either name.\n */\nconst PROMISE_MEMBERS: ReadonlySet<string | symbol> = new Set([\"then\", \"catch\", \"finally\"]);\n\nconst forwardEveryMember =\n\t<TStore>(slot: ILankaLazySlot<TStore>, config: ILankaLazyVMConfig<TStore>) =>\n\t(_target: object, property: string | symbol): unknown => {\n\t\tif (property === \"dispose\") return slot.release;\n\t\tif (PROMISE_MEMBERS.has(property)) return undefined;\n\n\t\t// The port's two VALUE members, answered from the config without building.\n\t\t// A binding reads both before it subscribes — `useLankaVM` asks\n\t\t// `isAccessTracked` on its first render — so forwarding them as functions\n\t\t// would have made every lazy ViewModel eager the moment a screen mounted,\n\t\t// which is the whole feature.\n\t\tif (property === \"name\") return config.name;\n\t\tif (property === \"isAccessTracked\") return config.isAccessTracked;\n\n\t\treturn (...args: unknown[]) => {\n\t\t\tconst built = slot.get();\n\t\t\tconst member = (built as Record<string | symbol, unknown>)[property];\n\n\t\t\t// Every remaining member of a ViewModel is a method — `getState`,\n\t\t\t// `subscribe`, `setState`, `getStoreState`. A value member cannot be\n\t\t\t// classified without building first, which is the one thing a property\n\t\t\t// READ may not do, so it is handed back from the call instead:\n\t\t\t// `lazyVM.whatever()` gives the value.\n\t\t\treturn typeof member === \"function\"\n\t\t\t\t? (member as (...rest: unknown[]) => unknown).apply(built, args)\n\t\t\t\t: member;\n\t\t};\n\t};\n\n/**\n * The lazy half of every ViewModel factory, written once.\n *\n * Three factories used to carry this mechanism: build on first access, keep the\n * store, hand every member through, release on `dispose`. They differed in the\n * one line that builds — and, as it turned out, in which members they had\n * remembered to hand through.\n *\n * ## Why a Proxy rather than a list of members\n *\n * The three copies attached members BY HAND, and the three lists differed:\n * `getState` + `getInitialState` + `setState` + `subscribe` for the plain one,\n * `getState` alone for the stateless, `getState` + `getStoreState` for the\n * shared-store. Each list matched its own type, so nothing was broken — but the\n * plain one MIRRORS a zustand store, and a mirror is a promise to keep in step\n * with something somebody else releases. The day zustand adds a member, three\n * files have to hear about it.\n *\n * A Proxy cannot be incomplete. What the built store has, the lazy hook has, and\n * the type each factory declares is what narrows it back down to that variant's\n * contract — a lazy variant promises exactly what its eager twin promises, plus\n * `dispose`.\n */\nexport const createLazyLankaVMProxy = <TStore extends ILankaReleasableStore>(\n\tconfig: ILankaLazyVMConfig<TStore>,\n): TLazyLankaVMProxy<TStore> => {\n\tconst slot = lazySlot(config);\n\n\t// An ordinary object is the target, where it used to be a function: a\n\t// ViewModel is read through a binding now, not called, so there is no call\n\t// signature left to stand in front of.\n\treturn new Proxy(\n\t\t{},\n\t\t{ get: forwardEveryMember(slot, config) },\n\t) as unknown as TLazyLankaVMProxy<TStore>;\n};\n","import { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { createLazyLankaVMProxy } from \"../../_internal/create-lazy-lanka-vm-proxy/createLazyLankaVMProxy\";\nimport { createLankaVM } from \"../create-lanka-vm/createLankaVM\";\nimport type { ILankaVMConfig } from \"../../_interfaces/ILankaVMConfig\";\n\nexport function createLazyLankaVM<State extends object, Actions extends object>(\n\tconfig: ILankaVMConfig<State, Actions, Record<string, never>, Record<string, never>>,\n): TLazyLankaVM<\n\tReturnType<typeof createLankaVM<State, Actions, Record<string, never>, Record<string, never>>>,\n\tState & Actions & ILankaScenarioVM\n>;\n\nexport function createLazyLankaVM<\n\tState extends object,\n\tActions extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, Record<string, never>, Services>,\n): TLazyLankaVM<\n\tReturnType<typeof createLankaVM<State, Actions, Record<string, never>, Services>>,\n\tState & Actions & ILankaScenarioVM\n>;\n\nexport function createLazyLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, TGateways, Record<string, never>>,\n): TLazyLankaVM<\n\tReturnType<typeof createLankaVM<State, Actions, TGateways, Record<string, never>>>,\n\tState & Actions & ILankaScenarioVM\n>;\n\nexport function createLazyLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, TGateways, Services>,\n): TLazyLankaVM<\n\tReturnType<typeof createLankaVM<State, Actions, TGateways, Services>>,\n\tState & Actions & ILankaScenarioVM\n>;\n\n/**\n * The lazy `createLankaVM`: nothing is built until a screen asks.\n *\n * For a ViewModel a session may never open — a settings screen, an admin panel,\n * a route behind a flag. The eager factory builds its store at module load, and\n * this one waits, which is the difference between paying for every screen the\n * application has and paying for the screens it shows.\n *\n * The mechanism is `createLazyLankaVMProxy`, shared with the stateless and\n * shared-store variants, so all three hand through every member the store has\n * and release the same way. This file is the one line that says what gets built.\n */\nexport function createLazyLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(config: ILankaVMConfig<State, Actions, TGateways, Services>) {\n\treturn createLazyLankaVMProxy({\n\t\tname: config.name,\n\t\tkind: \"VM\",\n\t\tisAccessTracked: config.enableAccessTrackingOptimization ?? true,\n\t\tcreate: () => createLankaVM(config),\n\t});\n}\n\n/**\n * A lazy ViewModel hook: everything an ordinary store does, plus `dispose`.\n */\nexport type TLazyLankaVM<TStore, TFullState> = TStore & {\n\tgetState: () => TFullState;\n\tdispose: () => void;\n};\n","import { ALankaVMEnvironment } from \"../lanka-vm-environment/ALankaVMEnvironment\";\nimport { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { lankaScenarioBootstrap } from \"../../../scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\nimport { createLankaScenarioBinder } from \"../../_internal/create-lanka-scenario-binder/createLankaScenarioBinder\";\nimport type { ILankaScenario } from \"../../../scenario/_interfaces/ILankaScenario\";\nimport type { TLankaScenarioBindingsDeclaration } from \"../../_types/TLankaScenarioBindingsDeclaration\";\nimport type {\n\tILankaStatelessScenarioBinding,\n\tILankaStatelessVMContext,\n\tTLankaSetState,\n} from \"../../_factories/create-stateless-lanka-vm/createStatelessLankaVM\";\nimport type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/**\n * A ViewModel that holds no reactive state: actions, and what they orchestrate.\n *\n * The second rung of the ladder in `core/README.md` written as a class. A screen\n * that reads nothing and only DOES things — a sign-out, a share sheet, a form\n * whose fields live in the form library — pays for a zustand store it never\n * reads; this is the same role without one.\n *\n * The protected surface IS the functional context, member for member: `set`,\n * `get`, `gateways`, `services`, `trigger`. What the config supplies as a value\n * or a thunk, the class supplies by overriding a method of the same name, with\n * the two dependency suppliers named `createGateways` and `createServices`\n * because `gateways` and `services` already name what they answer.\n * `toLifecycleHooks` is protected too and is not one of these: it is how the\n * framework reads `onInit` and `onReset`, and a ViewModel overrides those,\n * never it.\n *\n * ```ts\n * class SessionVM extends ALankaStatelessVM<ISessionActions, ISessionGateways> {\n * \tprotected readonly name = \"SessionVM\";\n *\n * \tprotected createGateways(): ISessionGateways {\n * \t\treturn { session: new SessionGateway() };\n * \t}\n *\n * \tprotected createActions(): ISessionActions {\n * \t\treturn {\n * \t\t\tsignOut: async () => {\n * \t\t\t\tawait this.gateways.session.signOut();\n * \t\t\t\tthis.trigger(sessionEnded);\n * \t\t\t},\n * \t\t};\n * \t}\n * }\n *\n * export const useSessionVM = new SessionVM().build();\n * ```\n *\n * Canon: `skills/parity/SKILL.md`.\n */\nexport abstract class ALankaStatelessVM<\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> extends ALankaVMEnvironment<TGateways, Services> {\n\t/** Names the ViewModel in the logs, the scenario registry and any warning. */\n\tprotected abstract readonly name: string;\n\n\t/** Writes state. Available from `createActions` onwards, never before. */\n\tprotected set!: TLankaSetState<Actions & ILankaScenarioVM>;\n\n\t/** Reads what the actions have written, including the actions themselves. */\n\tprotected get!: () => Actions & ILankaScenarioVM;\n\n\t/** Fires a scenario, which every ViewModel bound to it then hears. */\n\tprotected trigger = <TData>(scenario: ILankaScenario<TData>, data?: TData): void => {\n\t\tscenario.trigger(data);\n\t};\n\n\t/**\n\t * The scenarios this ViewModel listens to, unsubscribed for it on reset.\n\t *\n\t * Returning a FACTORY postpones building the list until bind time. See\n\t * `TLankaScenarioBindingsDeclaration`.\n\t */\n\tprotected scenarioHandlers(): TLankaScenarioBindingsDeclaration<\n\t\tILankaStatelessScenarioBinding<unknown, Actions & ILankaScenarioVM, TGateways, Services>\n\t> {\n\t\treturn [];\n\t}\n\n\t/** The actions the screen calls. Written against `this.set` and `this.get`. */\n\tprotected abstract createActions(): Actions;\n\n\t/**\n\t * The protected surface as an object, for the functional style.\n\t *\n\t * Assembled INSIDE the class because that is the only place `protected` can be\n\t * read. Canon: `skills/parity/SKILL.md` section 3a.\n\t */\n\tprotected toStyleContext(): ILankaStatelessVMContext<\n\t\tActions & ILankaScenarioVM,\n\t\tTGateways,\n\t\tServices\n\t> {\n\t\treturn {\n\t\t\tset: this.set,\n\t\t\tget: this.get,\n\t\t\tgateways: this.gateways,\n\t\t\tservices: this.services,\n\t\t\ttrigger: this.trigger,\n\t\t};\n\t}\n\n\t/**\n\t * Builds the ViewModel a screen reads. One per call.\n\t *\n\t * What comes back answers the read half of the port and nothing more: there\n\t * is no store to write to from outside, because there is no state to write.\n\t */\n\tpublic build(): ILankaReadableVM<Actions & ILankaScenarioVM> {\n\t\ttype TFullState = Actions & ILankaScenarioVM;\n\n\t\tlankaLogger.printViewModelLog(\"START Create slVM\", this.name);\n\n\t\tlet state = {} as TFullState;\n\n\t\tthis.get = () => state;\n\t\tthis.set = (partial, replace) => {\n\t\t\tconst next =\n\t\t\t\ttypeof partial === \"function\"\n\t\t\t\t\t? (partial as (current: TFullState) => TFullState)(state)\n\t\t\t\t\t: partial;\n\n\t\t\tstate = replace ? (next as TFullState) : { ...state, ...next };\n\t\t};\n\n\t\tthis.gateways = this.createGateways();\n\t\tthis.services = this.createServices();\n\n\t\tconst actions = this.createActions();\n\n\t\tconst hooks = this.toLifecycleHooks();\n\t\tconst { initializeScenario, resetScenario, needsBootstrap } = createLankaScenarioBinder({\n\t\t\tname: this.name,\n\t\t\tbindings: this.scenarioHandlers(),\n\t\t\tcontext: () => this.toStyleContext(),\n\t\t\tonInit: hooks.onInit,\n\t\t\tonReset: hooks.onReset,\n\t\t});\n\n\t\tstate = { ...state, ...actions, initializeScenario, resetScenario };\n\n\t\tif (needsBootstrap) {\n\t\t\tlankaScenarioBootstrap.registerViewModel(state, this.name);\n\t\t}\n\n\t\t/**\n\t\t * The port over a ViewModel with nothing that changes.\n\t\t *\n\t\t * `subscribe` returns an unsubscribe and never calls the listener, which is\n\t\t * the honest implementation rather than a stub: a stateless ViewModel holds\n\t\t * actions and no reactive fields, so there is no next state to report. That\n\t\t * is what lets one binding serve all three shapes without asking which it\n\t\t * was handed.\n\t\t *\n\t\t * `isAccessTracked` is false for the same reason — there are no keys whose\n\t\t * reads could be worth recording, and a binding that tried would pay for a\n\t\t * Proxy over an object that never moves.\n\t\t */\n\t\tconst statelessViewModel: ILankaReadableVM<TFullState> = {\n\t\t\tname: this.name,\n\t\t\tgetState: () => state,\n\t\t\tsubscribe: () => () => undefined,\n\t\t\tisAccessTracked: false,\n\t\t};\n\n\t\tlankaLogger.printViewModelLog(\"FINISH Create slVM\", this.name);\n\n\t\treturn statelessViewModel;\n\t}\n}\n","import type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\nimport type { TLankaReplayRequest } from \"../../../scenario/event-bus/lanka-event-bus-instance/LankaEventBusInstance\";\nimport type { TLankaScenarioHandler } from \"../../_types/TLankaScenarioHandler\";\nimport type { TLankaScenarioBindingsDeclaration } from \"../../_types/TLankaScenarioBindingsDeclaration\";\nimport { resolveLankaDependency } from \"../../_utils/resolve-lanka-dependency/resolveLankaDependency\";\nimport { ALankaStatelessVM } from \"../../_abstractions/lanka-stateless-vm/ALankaStatelessVM\";\nimport { ILankaScenario } from \"../../../scenario/_interfaces/ILankaScenario\";\nimport { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\n\nexport type TLankaSetState<TState> = (\n\tpartial: TState | Partial<TState> | ((state: TState) => TState | Partial<TState>),\n\treplace?: boolean,\n) => void;\n\nexport interface ILankaStatelessVMContext<\n\tTState,\n\tTGateways extends object,\n\tTServices extends object,\n> {\n\tset: TLankaSetState<TState>;\n\tget: () => TState;\n\tgateways: TGateways;\n\tservices: TServices;\n\ttrigger: <T>(scenario: ILankaScenario<T>, data?: T) => void;\n}\n\nexport interface ILankaStatelessScenarioBinding<\n\tTData,\n\tTState extends object,\n\tTGateways extends object,\n\tTServices extends object,\n> {\n\tscenario: ILankaScenario<TData>;\n\thandler: (\n\t\tctx: ILankaStatelessVMContext<TState, TGateways, TServices>,\n\t) => TLankaScenarioHandler<TData>;\n\toptions?: {\n\t\tpriority?: number;\n\t\treplay?: TLankaReplayRequest;\n\t\tusedBy?: string;\n\t};\n}\n\nexport type TLankaStatelessVMConfig<\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> = {\n\tname: string;\n\tcreateActions: (\n\t\tctx: ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>,\n\t) => Actions;\n\t/** A FACTORY is read at bind time — see `TLankaScenarioBindingsDeclaration`. */\n\tscenarioHandlers?: TLankaScenarioBindingsDeclaration<\n\t\tILankaStatelessScenarioBinding<unknown, Actions & ILankaScenarioVM, TGateways, Services>\n\t>;\n\tservices?: Services | (() => Services);\n\tgateways?: TGateways | (() => TGateways);\n\tonInit?: (\n\t\tctx: ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>,\n\t) => void;\n\tonReset?: (\n\t\tctx: ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>,\n\t) => void;\n};\n\n/**\n * @deprecated since 2.0.0 - use ILankaReadableVM, which is what a stateless\n * ViewModel now IS: `createStatelessLankaVM` answers a readable ViewModel rather\n * than a React hook, so the word \"Hook\" in this name no longer describes\n * anything. The alias is kept because a published name is never removed, and it\n * resolves to exactly the type the factory returns.\n */\nexport type TLankaStatelessVMHook<Actions extends object> = ILankaReadableVM<\n\tActions & ILankaScenarioVM\n>;\n\nexport function createStatelessLankaVM<Actions extends object>(\n\tconfig: TLankaStatelessVMConfig<Actions, Record<string, never>, Record<string, never>>,\n): ILankaReadableVM<Actions & ILankaScenarioVM>;\n\nexport function createStatelessLankaVM<Actions extends object, Services extends object>(\n\tconfig: TLankaStatelessVMConfig<Actions, Record<string, never>, Services>,\n): ILankaReadableVM<Actions & ILankaScenarioVM>;\n\nexport function createStatelessLankaVM<Actions extends object, TGateways extends object>(\n\tconfig: TLankaStatelessVMConfig<Actions, TGateways, Record<string, never>>,\n): ILankaReadableVM<Actions & ILankaScenarioVM>;\n\nexport function createStatelessLankaVM<\n\tActions extends object,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: TLankaStatelessVMConfig<Actions, TGateways, Services>,\n): ILankaReadableVM<Actions & ILankaScenarioVM>;\n\n/**\n * The functional style of `ALankaStatelessVM`: actions declared as an options object.\n *\n * Same outward shape as the stateful factory — a readable ViewModel — and no\n * zustand store under it, because a ViewModel that holds nothing has nothing to\n * subscribe to. What it is and how it binds scenarios is documented once, on the\n * class.\n */\nexport function createStatelessLankaVM<\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(config: TLankaStatelessVMConfig<Actions, TGateways, Services>): TLankaStatelessVMHook<Actions> {\n\t/**\n\t * The bridge subclass lives here rather than in a shared helper because\n\t * `toStyleContext` is `protected`: only a class body deriving from\n\t * `ALankaStatelessVM` may read it. Canon: `skills/parity/SKILL.md` section 3a.\n\t */\n\tclass FunctionalStatelessVM extends ALankaStatelessVM<Actions, TGateways, Services> {\n\t\tprotected readonly name = config.name;\n\n\t\tprotected override scenarioHandlers(): NonNullable<typeof config.scenarioHandlers> {\n\t\t\treturn config.scenarioHandlers ?? [];\n\t\t}\n\n\t\tprotected override createGateways(): TGateways {\n\t\t\treturn resolveLankaDependency(config.gateways);\n\t\t}\n\n\t\tprotected override createServices(): Services {\n\t\t\treturn resolveLankaDependency(config.services);\n\t\t}\n\n\t\tprotected createActions(): Actions {\n\t\t\treturn config.createActions(this.toStyleContext());\n\t\t}\n\n\t\t// Own properties, assigned only when declared — see the same constructor in\n\t\t// `createLankaVM` for why not a method override.\n\t\tpublic constructor() {\n\t\t\tsuper();\n\t\t\tconst { onInit, onReset } = config;\n\n\t\t\tif (onInit) this.onInit = () => onInit(this.toStyleContext());\n\t\t\tif (onReset) this.onReset = () => onReset(this.toStyleContext());\n\t\t}\n\t}\n\n\treturn new FunctionalStatelessVM().build();\n}\n","import { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { createStatelessLankaVM } from \"../create-stateless-lanka-vm/createStatelessLankaVM\";\nimport type { ILankaVMConfig } from \"../../_interfaces/ILankaVMConfig\";\nimport { createLazyLankaVMProxy } from \"../../_internal/create-lazy-lanka-vm-proxy/createLazyLankaVMProxy\";\n\nexport type TLankaStatelessVMConfig<\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> = Omit<ILankaVMConfig<object, Actions, TGateways, Services>, \"states\"> & {\n\tstates?: never;\n};\n\ntype TLazyStatelessReturn<\n\tActions extends object,\n\tTGateways extends object,\n\tServices extends object,\n> = ReturnType<typeof createStatelessLankaVM<Actions, TGateways, Services>> & {\n\tgetState: () => Actions & ILankaScenarioVM;\n\tdispose: () => void;\n};\n\nexport function createLazyStatelessLankaVM<Actions extends object>(\n\tconfig: TLankaStatelessVMConfig<Actions, Record<string, never>, Record<string, never>>,\n): TLazyStatelessReturn<Actions, Record<string, never>, Record<string, never>>;\n\nexport function createLazyStatelessLankaVM<Actions extends object, Services extends object>(\n\tconfig: TLankaStatelessVMConfig<Actions, Record<string, never>, Services>,\n): TLazyStatelessReturn<Actions, Record<string, never>, Services>;\n\nexport function createLazyStatelessLankaVM<Actions extends object, TGateways extends object>(\n\tconfig: TLankaStatelessVMConfig<Actions, TGateways, Record<string, never>>,\n): TLazyStatelessReturn<Actions, TGateways, Record<string, never>>;\n\nexport function createLazyStatelessLankaVM<\n\tActions extends object,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: TLankaStatelessVMConfig<Actions, TGateways, Services>,\n): TLazyStatelessReturn<Actions, TGateways, Services>;\n\n/**\n * The lazy `createStatelessLankaVM`: nothing is built until a screen asks.\n *\n * The mechanism is `createLazyLankaVMProxy` — build on first access, hand every\n * member through, release on `dispose` — and this file is the one line that says\n * WHICH ViewModel is built. It used to be a copy of that mechanism, and the copy\n * had forgotten `setState`, `subscribe` and `getInitialState`: typed as the whole\n * store, absent at runtime.\n */\nexport function createLazyStatelessLankaVM<\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(config: TLankaStatelessVMConfig<Actions, TGateways, Services>) {\n\treturn createLazyLankaVMProxy({\n\t\tname: config.name,\n\t\tkind: \"slVM\",\n\t\t// A stateless ViewModel has no reactive fields, so there is nothing whose\n\t\t// reads could be worth recording — the eager one answers false too.\n\t\tisAccessTracked: false,\n\t\tcreate: () => createStatelessLankaVM(config),\n\t});\n}\n","import type { ILankaSharedStoreVM } from \"../../_interfaces/ILankaSharedStoreVMConfig\";\nimport { resolveLankaDependency } from \"../../_utils/resolve-lanka-dependency/resolveLankaDependency\";\nimport { createLankaScenarioBinder } from \"../../_internal/create-lanka-scenario-binder/createLankaScenarioBinder\";\n\nimport { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { lankaScenarioBootstrap } from \"../../../scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\nimport type { ILankaSharedStoreVMConfig } from \"../../_interfaces/ILankaSharedStoreVMConfig\";\nimport type { ILankaSharedStoreVMContext } from \"../../_interfaces/ILankaSharedStoreVMContext\";\nimport { ALankaSharedStore } from \"../../_abstractions/lanka-shared-store/ALankaSharedStore\";\n\n/**\n * Factory for ViewModels backed by an external shared store instance.\n * Multiple ViewModels can be created on top of the same store instance.\n */\nexport function createSharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n>(\n\tconfig: ILankaSharedStoreVMConfig<\n\t\tStoreState,\n\t\tActions,\n\t\tStore,\n\t\tRecord<string, never>,\n\t\tRecord<string, never>\n\t>,\n): ILankaSharedStoreVM<StoreState, Actions>;\n\nexport function createSharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tServices extends object,\n>(\n\tconfig: ILankaSharedStoreVMConfig<StoreState, Actions, Store, Record<string, never>, Services>,\n): ILankaSharedStoreVM<StoreState, Actions>;\n\nexport function createSharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object,\n>(\n\tconfig: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Record<string, never>>,\n): ILankaSharedStoreVM<StoreState, Actions>;\n\nexport function createSharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Services>,\n): ILankaSharedStoreVM<StoreState, Actions>;\n\nexport function createSharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Services>) {\n\tlankaLogger.printViewModelLog(\"START Create ssVM\", config.name);\n\n\ttype TFullState = StoreState & Actions & ILankaScenarioVM;\n\n\t/**\n\t * Unsubscribe functions, plus the event types already subscribed to.\n\t *\n\t * Two separate jobs, kept separate: the array releases subscriptions, the set\n\t * prevents subscribing to one event type twice.\n\t */\n\tlet actions = {} as Actions;\n\n\tconst gateways = resolveLankaDependency(config.gateways);\n\tconst services = resolveLankaDependency(config.services);\n\n\tlet lastStoreStateRef: StoreState | null = null;\n\tlet lastFullStateRef: TFullState | null = null;\n\n\tconst buildFullState = (storeState: StoreState): TFullState => {\n\t\tif (lastStoreStateRef === storeState && lastFullStateRef) {\n\t\t\treturn lastFullStateRef;\n\t\t}\n\n\t\tlastStoreStateRef = storeState;\n\t\tlastFullStateRef = {\n\t\t\t...storeState,\n\t\t\t...actions,\n\t\t\tinitializeScenario,\n\t\t\tresetScenario,\n\t\t};\n\n\t\treturn lastFullStateRef;\n\t};\n\n\tconst getFullState = (): TFullState => buildFullState(config.store.getState());\n\n\tconst ctx: ILankaSharedStoreVMContext<StoreState, TFullState, Store, TGateways, Services> = {\n\t\tset: (partial, replace) => config.store.setState(partial, replace),\n\t\tgetStore: () => config.store.getState(),\n\t\tget: () => getFullState(),\n\t\tstore: config.store,\n\t\tgateways,\n\t\tservices,\n\t\ttrigger: (scenario, data) => scenario.trigger(data),\n\t};\n\n\tconst { initializeScenario, resetScenario, needsBootstrap } = createLankaScenarioBinder({\n\t\tname: config.name,\n\t\tbindings: config.scenarioHandlers,\n\t\tcontext: () => ctx,\n\t\tonInit: config.onInit,\n\t\tonReset: config.onReset,\n\t});\n\n\tactions = config.createActions(ctx);\n\tlastStoreStateRef = null;\n\tlastFullStateRef = null;\n\tconst isAccessTrackingEnabled = config.enableAccessTrackingOptimization ?? true;\n\n\t/**\n\t * The port over a slice of somebody else's store.\n\t *\n\t * `subscribe` is where the SHAPING happens — the store reports its own slice,\n\t * and both states are composed into full shape before a listener sees them.\n\t * Doing it here rather than in a binding is what lets one binding serve this\n\t * shape and the plain one without knowing which it was handed.\n\t *\n\t * Two ViewModels over one store therefore each hear every change to it, and\n\t * each decides for itself — through its reader's access tracking — whether the\n\t * change is worth a render.\n\t */\n\tconst sharedStoreViewModel: ILankaSharedStoreVM<StoreState, Actions> = {\n\t\tname: config.name,\n\t\tgetState: getFullState,\n\t\tgetStoreState: () => config.store.getState(),\n\t\tisAccessTracked: isAccessTrackingEnabled,\n\t\tsubscribe: (listener) =>\n\t\t\tconfig.store.subscribe((storeState, prevStoreState) => {\n\t\t\t\tlistener(buildFullState(storeState), buildFullState(prevStoreState));\n\t\t\t}),\n\t};\n\n\tif (needsBootstrap) {\n\t\tconst scenarioViewModel: ILankaScenarioVM = {\n\t\t\tinitializeScenario,\n\t\t\tresetScenario,\n\t\t};\n\t\tlankaScenarioBootstrap.registerViewModel(scenarioViewModel, config.name);\n\t}\n\n\tlankaLogger.printViewModelLog(\"FINISH Create ssVM\", config.name);\n\n\treturn sharedStoreViewModel;\n}\n","import { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { ALankaSharedStore } from \"../../_abstractions/lanka-shared-store/ALankaSharedStore\";\nimport { createLazyLankaVMProxy } from \"../../_internal/create-lazy-lanka-vm-proxy/createLazyLankaVMProxy\";\nimport { createSharedStoreLankaVM } from \"../create-shared-store-lanka-vm/createSharedStoreLankaVM\";\nimport type { ILankaSharedStoreVMConfig } from \"../../_interfaces/ILankaSharedStoreVMConfig\";\n\n/**\n * What a lazy shared-store ViewModel is, in the type as well as at runtime.\n *\n * The overloads used to promise the eager factory's return and nothing else, so\n * `dispose` and `getStoreState` existed on the object and not in the type: a\n * consumer releasing a closed screen's ViewModel got a compile error for calling\n * something that was there.\n */\ntype TLazySharedStoreReturn<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object,\n\tServices extends object,\n> = ReturnType<typeof createSharedStoreLankaVM<StoreState, Actions, Store, TGateways, Services>> & {\n\tgetState: () => StoreState & Actions & ILankaScenarioVM;\n\tgetStoreState: () => StoreState;\n\tdispose: () => void;\n};\n\nexport function createLazySharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n>(\n\tconfig: ILankaSharedStoreVMConfig<\n\t\tStoreState,\n\t\tActions,\n\t\tStore,\n\t\tRecord<string, never>,\n\t\tRecord<string, never>\n\t>,\n): TLazySharedStoreReturn<StoreState, Actions, Store, Record<string, never>, Record<string, never>>;\n\nexport function createLazySharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object,\n>(\n\tconfig: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Record<string, never>>,\n): TLazySharedStoreReturn<StoreState, Actions, Store, TGateways, Record<string, never>>;\n\nexport function createLazySharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Services>,\n): TLazySharedStoreReturn<StoreState, Actions, Store, TGateways, Services>;\n\n/**\n * The lazy `createSharedStoreLankaVM`: nothing is built until a screen asks.\n *\n * The mechanism is `createLazyLankaVMProxy`; this file is the one line that says\n * which ViewModel gets built. `dispose` releases THIS ViewModel's scenario\n * subscriptions and never the shared store — the store is shared, other\n * ViewModels stand on it, and taking its state away is not this one's decision.\n * That is the hook's behaviour, not a special case here: it resets the scenario\n * and drops its reference, and a shared store outlives both.\n */\nexport function createLazySharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Services>) {\n\treturn createLazyLankaVMProxy({\n\t\tname: config.name,\n\t\tkind: \"ssVM\",\n\t\tisAccessTracked: config.enableAccessTrackingOptimization ?? true,\n\t\tcreate: () => createSharedStoreLankaVM(config),\n\t});\n}\n","import { ALankaVMEnvironment } from \"../lanka-vm-environment/ALankaVMEnvironment\";\nimport { createSharedStoreLankaVM } from \"../../_factories/create-shared-store-lanka-vm/createSharedStoreLankaVM\";\nimport type { ALankaSharedStore } from \"../lanka-shared-store/ALankaSharedStore\";\nimport type { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport type { ILankaSharedStoreVMContext } from \"../../_interfaces/ILankaSharedStoreVMContext\";\nimport type { TLankaScenarioBindingsDeclaration } from \"../../_types/TLankaScenarioBindingsDeclaration\";\nimport type {\n\tILankaSharedStoreScenarioBinding,\n\tTLankaSharedStoreVMHook,\n} from \"../../_interfaces/ILankaSharedStoreVMConfig\";\n\n/**\n * A ViewModel over a store several ViewModels share, written as a class.\n *\n * The third rung of the ladder in `core/README.md`: reach for it only when two\n * ViewModels must CO-EDIT one state — a list and the badge that counts it, a\n * form and the header that says it is dirty. What the class adds over the\n * stateful base is where the state lives: in the store it is given, so `set`\n * writes there and `getStore` reads it.\n *\n * ```ts\n * class BadgeVM extends ALankaSharedStoreVM<ISelection, IBadgeActions, TodoStore> {\n * \tprotected readonly name = \"BadgeVM\";\n *\n * \tpublic constructor(store: TodoStore) {\n * \t\tsuper(store);\n * \t}\n *\n * \tprotected createActions(): IBadgeActions {\n * \t\treturn { clear: () => this.set({ selectedId: null }) };\n * \t}\n * }\n * ```\n *\n * Unlike its two siblings this one is a thin adapter rather than the\n * implementation: the store, the tracked hook and the two memoised state\n * references are the factory's, and duplicating them here would be the second\n * implementation the parity canon exists to prevent. What it gives a class-style\n * consumer is the same protected surface under the same names — plus\n * `toLifecycleHooks`, the framework's reading of `onInit`/`onReset`, which is\n * not an extension point.\n *\n * Canon: `skills/parity/SKILL.md`.\n */\nexport abstract class ALankaSharedStoreVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> extends ALankaVMEnvironment<TGateways, Services> {\n\t/** Names the ViewModel in the logs and in the scenario registry. */\n\tprotected abstract readonly name: string;\n\n\t/** The store this ViewModel and its siblings share. */\n\tprotected readonly store: Store;\n\n\t/** Writes into the shared store, which every reader of it hears about. */\n\tprotected set!: ILankaSharedStoreVMContext<\n\t\tStoreState,\n\t\tStoreState & Actions & ILankaScenarioVM,\n\t\tStore,\n\t\tTGateways,\n\t\tServices\n\t>[\"set\"];\n\n\t/** Reads the store's own state, without this ViewModel's actions on top. */\n\tprotected getStore!: () => StoreState;\n\n\t/** Reads the store's state WITH the actions, which is what a screen sees. */\n\tprotected get!: () => StoreState & Actions & ILankaScenarioVM;\n\n\t/** Fires a scenario, which every ViewModel bound to it then hears. */\n\tprotected trigger!: ILankaSharedStoreVMContext<\n\t\tStoreState,\n\t\tStoreState & Actions & ILankaScenarioVM,\n\t\tStore,\n\t\tTGateways,\n\t\tServices\n\t>[\"trigger\"];\n\n\tpublic constructor(store: Store) {\n\t\tsuper();\n\t\tthis.store = store;\n\t}\n\n\t/**\n\t * Turn off when one broad consumer reads most fields of the store — proxy\n\t * tracking then costs more than it saves.\n\t */\n\tprotected readonly enableAccessTrackingOptimization: boolean = true;\n\n\t/** The scenarios this ViewModel listens to, unsubscribed for it on reset. */\n\tprotected scenarioHandlers(): TLankaScenarioBindingsDeclaration<\n\t\tILankaSharedStoreScenarioBinding<unknown, StoreState, Actions, Store, TGateways, Services>\n\t> {\n\t\treturn [];\n\t}\n\n\t/** The actions the screen calls. Written against `this.set` and `this.get`. */\n\tprotected abstract createActions(): Actions;\n\n\t/** Builds the hook a screen calls. One ViewModel per call. */\n\tpublic build(): TLankaSharedStoreVMHook<StoreState, Actions> {\n\t\tconst hooks = this.toLifecycleHooks();\n\n\t\treturn createSharedStoreLankaVM<StoreState, Actions, Store, TGateways, Services>({\n\t\t\tname: this.name,\n\t\t\tstore: this.store,\n\t\t\tenableAccessTrackingOptimization: this.enableAccessTrackingOptimization,\n\t\t\tgateways: () => this.createGateways(),\n\t\t\tservices: () => this.createServices(),\n\t\t\tscenarioHandlers: this.scenarioHandlers(),\n\t\t\tcreateActions: (context) => {\n\t\t\t\t// The context arrives here and becomes the protected surface, under the\n\t\t\t\t// same names it carries: a consumer who switches styles moves the same\n\t\t\t\t// call from `set(...)` to `this.set(...)` and changes nothing else.\n\t\t\t\tthis.set = context.set;\n\t\t\t\tthis.getStore = context.getStore;\n\t\t\t\tthis.get = context.get;\n\t\t\t\tthis.gateways = context.gateways;\n\t\t\t\tthis.services = context.services;\n\t\t\t\tthis.trigger = context.trigger;\n\n\t\t\t\treturn this.createActions();\n\t\t\t},\n\t\t\tonInit: hooks.onInit,\n\t\t\tonReset: hooks.onReset,\n\t\t});\n\t}\n}\n","import { createStore, StoreApi } from \"zustand/vanilla\";\n\n/**\n * Base abstraction for shared feature stores.\n *\n * A store built on this class is resolved through the shared-store locator and\n * reused across several ViewModels.\n */\nexport abstract class ALankaSharedStore<TState extends object> {\n\tprivate readonly api: StoreApi<TState>;\n\tprivate readonly createInitialState: () => TState;\n\n\tprotected constructor(createInitialState: () => TState) {\n\t\tthis.createInitialState = createInitialState;\n\t\tthis.api = createStore<TState>()(() => this.createInitialState());\n\t}\n\n\tpublic getApi(): StoreApi<TState> {\n\t\treturn this.api;\n\t}\n\n\tpublic getState(): TState {\n\t\treturn this.api.getState();\n\t}\n\n\tpublic setState(\n\t\tpartial: TState | Partial<TState> | ((state: TState) => TState | Partial<TState>),\n\t\treplace?: boolean,\n\t): void {\n\t\tconst resolved = typeof partial === \"function\" ? partial(this.api.getState()) : partial;\n\n\t\tif (replace) {\n\t\t\tthis.api.setState(resolved as TState, true);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.api.setState(resolved);\n\t}\n\n\tpublic subscribe(listener: (state: TState, prevState: TState) => void) {\n\t\treturn this.api.subscribe(listener);\n\t}\n\n\t/**\n\t * Back to what the store was built with.\n\t *\n\t * Public, and it was not: while this was `protected` only a subclass could\n\t * reset, so the class style had a capability the functional one could not\n\t * reach — the asymmetry `skills/parity/SKILL.md` forbids. A shared store needs\n\t * explicit reset points (see the ladder in `core/README.md`), and the\n\t * application that owns them is outside the class either way.\n\t */\n\tpublic reset(): void {\n\t\tthis.api.setState(this.createInitialState(), true);\n\t}\n}\n","import { ALankaSharedStore } from \"../../_abstractions/lanka-shared-store/ALankaSharedStore\";\n\n/**\n * A shared store, without writing a class whose body is one function.\n *\n * A shared store is the third rung of the ladder in `core/README.md`: reach for\n * it only when several ViewModels must CO-EDIT one state. Most stores have no\n * behaviour of their own — the state and how to build it fresh is the whole\n * declaration — and this is the shape for those.\n *\n * One implementation: what comes back is an `ALankaSharedStore`, so a reset, a\n * subscription and the zustand api behave identically either way.\n */\nexport const createLankaSharedStore = <TState extends object>(\n\tcreateInitialState: () => TState,\n): ALankaSharedStore<TState> => {\n\tclass FunctionalSharedStore extends ALankaSharedStore<TState> {\n\t\tpublic constructor() {\n\t\t\tsuper(createInitialState);\n\t\t}\n\t}\n\n\treturn new FunctionalSharedStore();\n};\n","/**\n * Compares two selections one level deep.\n *\n * Own keys, same count, `Object.is` on each value. Arrays included, because an\n * array IS an object with numeric keys and a selection like `state.todos.map(…)`\n * is the commonest thing there is.\n *\n * Unbranded, because it is a pure function over plain values and holds nothing —\n * `skills/naming/SKILL.md`, the prefix table. Not exported: what a consumer\n * needs is the hold below, and a second name for the predicate would be a second\n * promise to keep.\n */\nconst isShallowEqual = (a: unknown, b: unknown): boolean => {\n\tif (Object.is(a, b)) return true;\n\tif (typeof a !== \"object\" || a === null || typeof b !== \"object\" || b === null) return false;\n\n\tconst left = Object.keys(a);\n\tconst right = Object.keys(b);\n\tif (left.length !== right.length) return false;\n\n\treturn left.every(\n\t\t(key) =>\n\t\t\tObject.hasOwn(b as Record<string, unknown>, key) &&\n\t\t\tObject.is((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n\t);\n};\n\n/**\n * Keeps a selection's IDENTITY while nothing in it moved.\n *\n * ```ts\n * const hold = createLankaShallowHold<{ title: string; status: string }>();\n *\n * // in any binding on the shelf\n * const view = useLankaVM(missionVM, (state) => hold({\n * \ttitle: state.title,\n * \tstatus: state.status,\n * }));\n * ```\n *\n * ## What it is for\n *\n * A selector narrows what a reader depends on, and a selector that BUILDS its\n * answer — `(state) => ({ a: state.a })`, `(state) => rows.filter(…)`, the shape\n * a consumer reaches for first — cannot say so. Every binding compares the new\n * selection with the last one by identity, and a fresh object is never identical\n * to anything, so the reader wakes for every change in the ViewModel including\n * the keys the selector exists to ignore. This is the comparison that makes the\n * selection mean something.\n *\n * A selector answering a PRIMITIVE never needed it, which is what makes the cost\n * quiet: the shape that is free and the shape that repaints on everything look\n * the same on the page.\n *\n * ## Why it is in core and not in a binding\n *\n * It was `useLankaShallow` in `@lankajs/react` and nowhere else, and that made it\n * a CAPABILITY one member of the shelf had and four did not. An idiom is a\n * spelling; this changes which notifications reach a reader, and it encodes a\n * policy — one level deep, own keys, `Object.is` — that five packages inventing\n * separately would answer five ways. `skills/parity/SKILL.md` 3c: a binding that\n * needs more than the port gives it has found something that belongs in core, for\n * everybody.\n *\n * `useLankaShallow` keeps working and is now React's spelling over this. It has\n * to exist there and cannot exist here: a React component re-runs the hook on\n * every render, so the holding has to survive a render while the SELECTOR stays\n * the current one — which is a `useRef`, and a ref is not something core can\n * have.\n *\n * ## Why it holds a value and not a selector\n *\n * `createLankaShallowSelector(selector)` was the other shape and it cannot serve\n * React: the wrapper would be rebuilt whenever the selector's identity moved, and\n * an inline arrow is a new function every render, so the holding would reset\n * before it ever held anything. Taking the VALUE puts the state in the only place\n * every framework can keep it, and the extra line at a call site is the price of\n * one name that works in all five rather than two that each work in some.\n *\n * ## One level, and it says so by failing\n *\n * Deeper would mean walking a state of unknown size on every read, which is the\n * cost a reader took a selector to avoid. A selection with a nested object wants\n * a selector that picks the leaves.\n */\nexport const createLankaShallowHold = <TValue>(): ((next: TValue) => TValue) => {\n\tlet held: TValue | undefined;\n\tlet holding = false;\n\n\treturn (next: TValue): TValue => {\n\t\t// `holding` rather than `held !== undefined`: a selection that legitimately\n\t\t// answers `undefined` is a selection, and a flag is the difference between\n\t\t// \"nothing yet\" and \"nothing, and that is the answer\".\n\t\tif (holding && isShallowEqual(held, next)) return held as TValue;\n\n\t\tholding = true;\n\t\theld = next;\n\n\t\treturn next;\n\t};\n};\n"],"mappings":"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","names":["needsBootstrap","needsBootstrap","needsBootstrap","createStore"]}
|
|
1
|
+
{"version":3,"sources":["../../src/viewmodel/_abstractions/lanka-vm-environment/ALankaVMEnvironment.ts","../../src/viewmodel/_abstractions/lanka-vm/ALankaVM.ts","../../src/viewmodel/_internal/create-lanka-blind-spot-trap/createLankaBlindSpotTrap.ts","../../src/viewmodel/_internal/create-lanka-scenario-binder/createLankaScenarioBinder.ts","../../src/viewmodel/_utils/resolve-lanka-dependency/resolveLankaDependency.ts","../../src/viewmodel/_factories/create-lanka-vm/createLankaVM.ts","../../src/viewmodel/_internal/create-lazy-lanka-vm-proxy/createLazyLankaVMProxy.ts","../../src/viewmodel/_factories/create-lazy-lanka-vm/createLazyLankaVM.ts","../../src/viewmodel/_abstractions/lanka-stateless-vm/ALankaStatelessVM.ts","../../src/viewmodel/_factories/create-stateless-lanka-vm/createStatelessLankaVM.ts","../../src/viewmodel/_factories/create-lazy-stateless-lanka-vm/createLazyStatelessLankaVM.ts","../../src/viewmodel/_factories/create-shared-store-lanka-vm/createSharedStoreLankaVM.ts","../../src/viewmodel/_factories/create-lazy-shared-store-lanka-vm/createLazySharedStoreLankaVM.ts","../../src/viewmodel/_abstractions/lanka-shared-store-vm/ALankaSharedStoreVM.ts","../../src/viewmodel/_abstractions/lanka-shared-store/ALankaSharedStore.ts","../../src/viewmodel/_factories/create-lanka-shared-store/createLankaSharedStore.ts","../../src/viewmodel/_factories/create-lanka-shallow-hold/createLankaShallowHold.ts"],"sourcesContent":["import type { ILankaVMLifecycleHooks } from \"../../_interfaces/ILankaVMLifecycleHooks\";\n\n/**\n * What every ViewModel is given, and the two moments it is told about.\n *\n * The three ViewModel shapes — stateful, stateless, over a shared store — differ\n * in where their state lives and in nothing else about this: each is handed a\n * data layer and a set of collaborators, and each is told when its scenarios are\n * bound and when they are about to be unbound.\n *\n * Stated once because a fifth hook added to two of the three is exactly the\n * divergence `skills/parity/SKILL.md` is written against, and three copies of a\n * default is how that starts.\n *\n * It is not a role and nothing extends it directly: the three bases do, and a\n * consumer extends one of them.\n */\nexport abstract class ALankaVMEnvironment<\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> {\n\t/** What `createGateways` answered: the data layer, kept apart from services. */\n\tprotected gateways!: TGateways;\n\n\t/** What `createServices` answered: everything that is not a gateway. */\n\tprotected services!: Services;\n\n\t/** The data layer, built once per ViewModel. */\n\tprotected createGateways(): TGateways {\n\t\treturn {} as TGateways;\n\t}\n\n\t/** Non-gateway collaborators, built once per ViewModel. */\n\tprotected createServices(): Services {\n\t\treturn {} as Services;\n\t}\n\n\t/**\n\t * Runs once the scenarios are bound: inside `startLanka()` for a ViewModel built\n\t * at module level, inside `build()` for one built after bootstrap — in both\n\t * cases before any screen has read the hook the build returns.\n\t */\n\tprotected onInit(): void {}\n\n\t/**\n\t * Runs when the ViewModel is released — the framework instance disposed, or a\n\t * lazy ViewModel's `dispose()` — after its scenario subscriptions are gone.\n\t */\n\tprotected onReset(): void {}\n\n\t/**\n\t * The two moments above as the scenario binder receives them: present only\n\t * where this ViewModel took them.\n\t *\n\t * Framework plumbing, not an extension point — a ViewModel overrides `onInit`\n\t * and `onReset`, never this. Protected because the three bases call it, and\n\t * named after `toStyleContext` for the same reason: a derived view of the\n\t * protected surface, assembled by the framework.\n\t *\n\t * Why \"took them\" decides anything: `onInit` runs inside `initializeScenario`,\n\t * which bootstrap alone calls, on the ViewModels registered with it.\n\t * Registration used to follow scenario bindings only, so a ViewModel that\n\t * overrode `onInit` and bound nothing was never initialised — silently. Now a\n\t * declared hook registers the ViewModel too, and a default no-op must not\n\t * count, or every ViewModel would sit in the scenario registry for nothing.\n\t * The functional bridges declare theirs as own properties over these methods,\n\t * which is what this comparison sees.\n\t */\n\tprotected toLifecycleHooks(): ILankaVMLifecycleHooks {\n\t\tconst defaults = ALankaVMEnvironment.prototype;\n\t\tconst hooks: ILankaVMLifecycleHooks = {};\n\n\t\tif (this.onInit !== defaults.onInit) hooks.onInit = () => this.onInit();\n\t\tif (this.onReset !== defaults.onReset) hooks.onReset = () => this.onReset();\n\n\t\treturn hooks;\n\t}\n}\n","import { ALankaVMEnvironment } from \"../lanka-vm-environment/ALankaVMEnvironment\";\nimport { createStore, StoreApi } from \"zustand/vanilla\";\nimport { getLankaFlags } from \"../../../config/get-lanka-flags/getLankaFlags\";\nimport { createLankaBlindSpotTrap } from \"../../_internal/create-lanka-blind-spot-trap/createLankaBlindSpotTrap\";\nimport { createLankaScenarioBinder } from \"../../_internal/create-lanka-scenario-binder/createLankaScenarioBinder\";\nimport { lankaBlindSpotRegistry } from \"../../_internal/lanka-blind-spot-registry/lankaBlindSpotRegistry\";\nimport { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { lankaScenarioBootstrap } from \"../../../scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\nimport type { ILankaScenario } from \"../../../scenario/_interfaces/ILankaScenario\";\nimport type { ILankaVM } from \"../../_interfaces/ILankaVM\";\nimport type { ILankaVMContext } from \"../../_interfaces/ILankaVMContext\";\nimport type { TLankaVMEnhancer } from \"../../_types/TLankaVMEnhancer\";\nimport type { TLankaVMStateCreator } from \"../../_types/TLankaVMStateCreator\";\nimport type { TLankaScenarioBindingsDeclaration } from \"../../_types/TLankaScenarioBindingsDeclaration\";\nimport type { TUnknownLankaScenarioBinding } from \"../../_types/TUnknownLankaScenarioBinding\";\n\n/**\n * A ViewModel written as a class: the state a screen reads, and the only place a\n * gateway is called from.\n *\n * This is the implementation of the role, and `createLankaVM` is the same thing\n * reached the other way — a subclass built from an options object. Neither style\n * can do what the other cannot, because there is nothing here to diverge from.\n *\n * The protected surface IS the functional context, member for member:\n * `set`, `get`, `gateways`, `services`, `trigger`. What the config supplies as a\n * value or a thunk, the class supplies by overriding a method of the same name —\n * `states`, `scenarioHandlers`, `enhancers`, `onInit`, `onReset` — with the two\n * dependency suppliers named `createGateways` and `createServices`, because\n * `gateways` and `services` already name what they answer. `toLifecycleHooks`\n * is protected too and is not one of these: it is how the framework reads the\n * two hooks, and a ViewModel overrides the hooks, never it.\n *\n * ```ts\n * class TodoVM extends ALankaVM<ITodoState, ITodoActions, ITodoGateways> {\n * \tprotected readonly name = \"TodoVM\";\n *\n * \tprotected states(): ITodoState {\n * \t\treturn { todos: [], isLoading: false };\n * \t}\n *\n * \tprotected createGateways(): ITodoGateways {\n * \t\treturn { todo: new TodoGateway() };\n * \t}\n *\n * \tprotected createActions(): ITodoActions {\n * \t\treturn {\n * \t\t\tload: async () => {\n * \t\t\t\tthis.set({ isLoading: true });\n * \t\t\t\tthis.set({ todos: await this.gateways.todo.list(), isLoading: false });\n * \t\t\t},\n * \t\t};\n * \t}\n * }\n *\n * export const useTodoVM = new TodoVM().build();\n * ```\n *\n * The access-tracking blind spot the functional style documents is the same one\n * here, and `enableAccessTrackingOptimization` is the same switch. A consumer\n * re-renders only for state keys it READ off the returned proxy; an action that\n * DERIVES a value reads the store through `get`, which the proxy never sees, so a\n * component whose only link to a key is such a getter never re-renders for it. In\n * development the mismatch announces itself by name rather than by a frozen\n * screen. Canon: `skills/parity/SKILL.md`.\n */\nexport abstract class ALankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> extends ALankaVMEnvironment<TGateways, Services> {\n\t/** Names the ViewModel in the logs, the scenario registry and the blind-spot warning. */\n\tprotected abstract readonly name: string;\n\n\t/**\n\t * Turn off when one broad consumer reads most fields, or when an action derives\n\t * what the screen shows — proxy tracking then costs more than it saves, and in\n\t * the second case it cannot see the read at all.\n\t */\n\tprotected readonly enableAccessTrackingOptimization: boolean = true;\n\n\t/** Writes state. Available from `createActions` onwards, never before. */\n\tprotected set!: StoreApi<State & Actions & ILankaScenarioVM>[\"setState\"];\n\n\t/** Reads state. The read a tracked hook cannot see — hence the switch above. */\n\tprotected get!: () => State & Actions & ILankaScenarioVM;\n\n\t/** Fires a scenario, which every ViewModel bound to it then hears. */\n\tprotected trigger = <TData>(scenario: ILankaScenario<TData>, data?: TData): void => {\n\t\tscenario.trigger(data);\n\t};\n\n\t/** The reactive fields the screen reads. */\n\tprotected states(): State {\n\t\treturn {} as State;\n\t}\n\n\t/**\n\t * The scenarios this ViewModel listens to, unsubscribed for it on reset.\n\t *\n\t * Returning a FACTORY postpones building the list until bind time, which is\n\t * what a ViewModel declared at module level needs when its entries name their\n\t * scenarios through the locator. See `TLankaScenarioBindingsDeclaration`.\n\t */\n\tprotected scenarioHandlers(): TLankaScenarioBindingsDeclaration<\n\t\tTUnknownLankaScenarioBinding<State & Actions, TGateways, Services>\n\t> {\n\t\treturn [];\n\t}\n\n\t/** Store middleware — `persist`, `lankaDevtools` — the last one applied outermost. */\n\tprotected enhancers(): TLankaVMEnhancer<State & Actions & ILankaScenarioVM>[] {\n\t\treturn [];\n\t}\n\n\t/** The actions the screen calls. Written against `this.set` and `this.get`. */\n\tprotected abstract createActions(): Actions;\n\n\t/**\n\t * The protected surface as an object, for the functional style.\n\t *\n\t * Assembled INSIDE the class because that is the only place `protected` can be\n\t * read — a context built from outside could carry only the public half, which is\n\t * the wrong half. Canon: `skills/parity/SKILL.md` section 3a.\n\t */\n\tprotected toStyleContext(): ILankaVMContext<\n\t\tState & Actions & ILankaScenarioVM,\n\t\tTGateways,\n\t\tServices\n\t> {\n\t\treturn {\n\t\t\tset: this.set,\n\t\t\tget: this.get,\n\t\t\tgateways: this.gateways,\n\t\t\tservices: this.services,\n\t\t\ttrigger: this.trigger,\n\t\t};\n\t}\n\n\t/**\n\t * Builds the ViewModel a screen reads. One store per call.\n\t *\n\t * What comes back is a STORE, not a hook: `getState`, `subscribe`, `setState`,\n\t * plus the name and the tracking flag. A screen reaches it through its\n\t * framework's binding — `useLankaVM(todoVM)` from `@lankajs/react`,\n\t * `@lankajs/vue` and the rest of the shelf — and a program with no framework\n\t * at all reads `getState()` and `subscribe()` directly.\n\t */\n\tpublic build(): ILankaVM<State & Actions & ILankaScenarioVM> {\n\t\ttype TFullState = State & Actions & ILankaScenarioVM;\n\n\t\tlankaLogger.printViewModelLog(\"START Create VM\", this.name);\n\n\t\tconst blindSpot = createLankaBlindSpotTrap(\n\t\t\tthis.name,\n\t\t\tgetLankaFlags().isDevelopment === true,\n\t\t);\n\n\t\t// One binder per ViewModel, built OUTSIDE the state creator: a middleware may\n\t\t// run the creator more than once, and the binder's verdict on bootstrap is\n\t\t// read after the store exists. The bindings are passed through unopened — a\n\t\t// factory is called at bind time, which is the whole point of that form.\n\t\tconst hooks = this.toLifecycleHooks();\n\t\tconst { initializeScenario, resetScenario, needsBootstrap } = createLankaScenarioBinder({\n\t\t\tname: this.name,\n\t\t\tbindings: this.scenarioHandlers(),\n\t\t\tcontext: () => this.toStyleContext(),\n\t\t\tonInit: hooks.onInit,\n\t\t\tonReset: hooks.onReset,\n\t\t});\n\n\t\tconst stateCreator: TLankaVMStateCreator<TFullState> = (set, get) => {\n\t\t\tthis.set = set;\n\t\t\tthis.get = blindSpot.observeGet(get);\n\t\t\tthis.gateways = this.createGateways();\n\t\t\tthis.services = this.createServices();\n\n\t\t\tconst actions = blindSpot.observeActions(this.createActions());\n\n\t\t\treturn {\n\t\t\t\t...this.states(),\n\t\t\t\t...actions,\n\t\t\t\tinitializeScenario,\n\t\t\t\tresetScenario,\n\t\t\t};\n\t\t};\n\n\t\tconst enhancedCreator = this.enhancers().reduce<TLankaVMStateCreator<TFullState>>(\n\t\t\t(creator, enhance) => enhance(creator),\n\t\t\tstateCreator,\n\t\t);\n\n\t\tconst store = createStore<TFullState>()(enhancedCreator);\n\n\t\t/**\n\t\t * The two members the port adds to what a store already answers.\n\t\t *\n\t\t * A vanilla store is most of `ILankaVM` already — `getState`, `subscribe`,\n\t\t * `getInitialState`, `setState` — and what it cannot know is who it belongs\n\t\t * to and what this ViewModel decided about tracking. Attached rather than\n\t\t * wrapped, so the object a screen holds IS the store: no extra hop, and\n\t\t * every zustand middleware a consumer applied still reaches it.\n\t\t *\n\t\t * Non-enumerable, because they are a description of the ViewModel rather\n\t\t * than part of its state, and a spread of the store should not pick them up.\n\t\t */\n\t\tconst viewModel: ILankaVM<TFullState> = Object.defineProperties(store, {\n\t\t\tname: { value: this.name, enumerable: false, configurable: true },\n\t\t\tisAccessTracked: {\n\t\t\t\tvalue: this.enableAccessTrackingOptimization,\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: true,\n\t\t\t},\n\t\t}) as unknown as ILankaVM<TFullState>;\n\n\t\tlankaBlindSpotRegistry.remember(viewModel, blindSpot);\n\n\t\tif (needsBootstrap) {\n\t\t\tlankaScenarioBootstrap.registerViewModel(store.getState(), this.name);\n\t\t}\n\n\t\tlankaLogger.printViewModelLog(\"FINISH Create VM\", this.name);\n\n\t\treturn viewModel;\n\t}\n}\n","export interface ILankaBlindSpotTrap {\n\t/** Whether the trap is armed. Everything below is inert when it is not. */\n\treadonly isArmed: boolean;\n\t/** Wraps `get` so reads made inside an action are attributed to it. */\n\tobserveGet: <TState>(get: () => TState) => () => TState;\n\t/** Wraps the actions so the trap knows which one is running. */\n\tobserveActions: <TActions extends object>(declared: TActions) => TActions;\n\t/** Reports a change that will not re-render, when the component reads the key. */\n\treport: (\n\t\ttrackedKeys: Set<string>,\n\t\tnext: Record<string, unknown>,\n\t\tprev: Record<string, unknown>,\n\t) => void;\n}\n\n/**\n * The blind spot: a key the component reads through a GETTER.\n *\n * Access tracking sees direct reads off the proxy. A key reached only inside a\n * derived getter is invisible to it, so a change to that key re-renders nothing\n * and the screen freezes with no error anywhere.\n *\n * The trap watches which keys each action reads through `get()`, and warns when\n * one of those changes without a tracked key changing with it.\n *\n * Development only: in production this is work on a hot path plus console noise\n * for somebody who does not write the code. Disarmed, every method is identity.\n */\nexport const createLankaBlindSpotTrap = (\n\tviewModelName: string,\n\tisArmed: boolean,\n): ILankaBlindSpotTrap => {\n\tif (!isArmed) {\n\t\treturn {\n\t\t\tisArmed: false,\n\t\t\tobserveGet: (get) => get,\n\t\t\tobserveActions: (declared) => declared,\n\t\t\treport: () => undefined,\n\t\t};\n\t}\n\n\t/** Action name → state keys it read through `get()`, past the proxy. */\n\tconst indirectReads = new Map<string, Set<string>>();\n\t/** Keys already warned about: a second warning adds nothing. */\n\tconst warnedKeys = new Set<string>();\n\tlet activeActionName: string | null = null;\n\n\treturn {\n\t\tisArmed: true,\n\n\t\tobserveGet<TState>(get: () => TState): () => TState {\n\t\t\treturn () => {\n\t\t\t\tconst state = get();\n\t\t\t\tconst actionName = activeActionName;\n\t\t\t\tif (actionName === null) return state;\n\n\t\t\t\treturn new Proxy(state as object, {\n\t\t\t\t\tget(target, prop, receiver) {\n\t\t\t\t\t\tif (typeof prop === \"string\") {\n\t\t\t\t\t\t\tlet keys = indirectReads.get(actionName);\n\t\t\t\t\t\t\tif (!keys) {\n\t\t\t\t\t\t\t\tkeys = new Set<string>();\n\t\t\t\t\t\t\t\tindirectReads.set(actionName, keys);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tkeys.add(prop);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn Reflect.get(target, prop, receiver) as unknown;\n\t\t\t\t\t},\n\t\t\t\t}) as TState;\n\t\t\t};\n\t\t},\n\n\t\tobserveActions<TActions extends object>(declared: TActions): TActions {\n\t\t\tconst observed = { ...declared } as Record<string, unknown>;\n\n\t\t\tfor (const [name, value] of Object.entries(declared)) {\n\t\t\t\tif (typeof value !== \"function\") continue;\n\n\t\t\t\tobserved[name] = (...args: unknown[]) => {\n\t\t\t\t\t// Restored rather than cleared: actions call each other, and clearing\n\t\t\t\t\t// would attribute the caller's later reads to nobody.\n\t\t\t\t\tconst previous = activeActionName;\n\t\t\t\t\tactiveActionName = name;\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn (value as (...a: unknown[]) => unknown)(...args);\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tactiveActionName = previous;\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn observed as TActions;\n\t\t},\n\n\t\treport(trackedKeys, next, prev): void {\n\t\t\tfor (const [changedKey, value] of Object.entries(next)) {\n\t\t\t\tif (Object.is(value, prev[changedKey])) continue;\n\t\t\t\tif (trackedKeys.has(changedKey) || warnedKeys.has(changedKey)) continue;\n\n\t\t\t\t// Not every untracked key is a fault: not reading what you do not need\n\t\t\t\t// is exactly what tracking exists for. The fault is a key the component\n\t\t\t\t// DOES read — through a getter taken off the proxy.\n\t\t\t\tconst viaGetter = [...trackedKeys].some((key) =>\n\t\t\t\t\tindirectReads.get(key)?.has(changedKey),\n\t\t\t\t);\n\t\t\t\tif (!viaGetter) continue;\n\n\t\t\t\twarnedKeys.add(changedKey);\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[lanka] ${viewModelName}: key \"${changedKey}\" changed but no re-render will follow. ` +\n\t\t\t\t\t\t`The component reads it only through a getter, and tracking sees direct proxy reads only. ` +\n\t\t\t\t\t\t`Set enableAccessTrackingOptimization: false on this ViewModel — ` +\n\t\t\t\t\t\t`destructuring the key in the view \"for the side effect\" reads as dead code and will not survive a refactor.`,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t};\n};\n","import type { TLankaScenarioBindingsDeclaration } from \"../../_types/TLankaScenarioBindingsDeclaration\";\n\n/**\n * A subscription option bag, as wide as the bus accepts.\n *\n * The binder passes it through and reads only `usedBy`; naming the rest here\n * would make this file a second declaration of the bus's options.\n */\nexport type TLankaScenarioBindingOptions = Record<string, unknown> & { usedBy?: string };\n\n/**\n * What the binder needs of a binding, and nothing more.\n *\n * Structural rather than the concrete `ILankaScenarioBinding`: three ViewModel\n * families declare their own binding type over their own context, and the binder\n * only ever calls `subscribe` and `handler`.\n */\nexport interface ILankaScenarioBindingLike<TContext, TData = unknown> {\n\tscenario: {\n\t\teventType: string;\n\t\tsubscribe: (\n\t\t\tcallback: (data?: TData) => void,\n\t\t\toptions?: TLankaScenarioBindingOptions,\n\t\t) => () => void;\n\t};\n\thandler: (context: TContext) => (data?: TData) => void;\n\toptions?: TLankaScenarioBindingOptions;\n}\n\nexport interface ILankaScenarioBinderConfig<TContext> {\n\t/** The ViewModel's name, reported to the bus as the subscriber. */\n\tname: string;\n\t/**\n\t * What to bind. A ViewModel with none still gets a working binder.\n\t *\n\t * A factory is read at BIND time — like `context` below, and for the same\n\t * reason: an entry that names its scenario through the locator must not be\n\t * built while the declaring module is evaluated. See\n\t * `TLankaScenarioBindingsDeclaration`.\n\t */\n\tbindings?: TLankaScenarioBindingsDeclaration<ILankaScenarioBindingLike<TContext>>;\n\t/** The context handlers are built with, read at bind time. */\n\tcontext: () => TContext;\n\tonInit?: (context: TContext) => void;\n\tonReset?: (context: TContext) => void;\n}\n\nexport interface ILankaScenarioBinder {\n\t/** Whether the bindings are live. */\n\treadonly isInitialized: boolean;\n\t/**\n\t * Whether bootstrap must know this ViewModel: it has scenarios to bind, or a\n\t * lifecycle moment to be told about. `onInit` runs inside `initializeScenario`,\n\t * which only bootstrap calls, so a ViewModel that is not registered never hears\n\t * either hook.\n\t */\n\treadonly needsBootstrap: boolean;\n\t/** Subscribes every binding once. A second call does nothing. */\n\tinitializeScenario: () => void;\n\t/** Releases every subscription and allows a later re-initialisation. */\n\tresetScenario: () => void;\n}\n\n/**\n * The bindings as declared, opened at the moment of binding.\n *\n * Its own function because WHEN this runs is the point: a factory form exists so\n * that the locator lookups inside the entries happen after the framework\n * instance does, and the only call site is inside `initializeScenario`.\n */\nconst readBindings = <TContext>(\n\tdeclared: TLankaScenarioBindingsDeclaration<ILankaScenarioBindingLike<TContext>> | undefined,\n): readonly ILankaScenarioBindingLike<TContext>[] =>\n\ttypeof declared === \"function\" ? declared() : (declared ?? []);\n\n/**\n * Whether bootstrap must be told about the ViewModel: something to bind, or a\n * lifecycle moment to be told about.\n *\n * Decided here, once, for all three ViewModel families: each wrote the bindings\n * half of this and none wrote the lifecycle half, so a ViewModel with a hook and\n * no scenarios was never initialised. A factory counts as bindings WITHOUT being\n * opened — opening it here would be the module-scope locator read the form\n * exists to postpone. See `TLankaScenarioBindingsDeclaration`.\n */\nconst needsBootstrap = <TContext>(config: ILankaScenarioBinderConfig<TContext>): boolean =>\n\ttypeof config.bindings === \"function\" ||\n\t(config.bindings !== undefined && config.bindings.length > 0) ||\n\tconfig.onInit !== undefined ||\n\tconfig.onReset !== undefined;\n\n/**\n * The scenario lifetime of a ViewModel: bind once, release on reset.\n *\n * All three ViewModel families need exactly this, and all three had written it\n * out — two with an array plus a Set of seen event types, one with a Map. The\n * Map is the shape kept, because it does both jobs with one structure: the key\n * prevents a second subscription to the same event, and the value is the\n * function that releases THIS subscription.\n *\n * Releasing by callback identity, which an array invites, removes the first\n * entry carrying that callback rather than the caller's own — two handlers whose\n * closures compare equal cancel each other.\n */\nexport const createLankaScenarioBinder = <TContext>(\n\tconfig: ILankaScenarioBinderConfig<TContext>,\n): ILankaScenarioBinder => {\n\tconst subscriptions = new Map<string, () => void>();\n\tlet isInitialized = false;\n\n\treturn {\n\t\tneedsBootstrap: needsBootstrap(config),\n\t\tget isInitialized(): boolean {\n\t\t\treturn isInitialized;\n\t\t},\n\n\t\tinitializeScenario(): void {\n\t\t\tif (isInitialized) return;\n\n\t\t\tconst context = config.context();\n\n\t\t\tfor (const binding of readBindings(config.bindings)) {\n\t\t\t\tconst key = binding.scenario.eventType;\n\t\t\t\tif (subscriptions.has(key)) continue;\n\n\t\t\t\tsubscriptions.set(\n\t\t\t\t\tkey,\n\t\t\t\t\tbinding.scenario.subscribe(binding.handler(context), {\n\t\t\t\t\t\tusedBy: config.name,\n\t\t\t\t\t\t...binding.options,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconfig.onInit?.(context);\n\t\t\tisInitialized = true;\n\t\t},\n\n\t\tresetScenario(): void {\n\t\t\tfor (const release of subscriptions.values()) release();\n\t\t\tsubscriptions.clear();\n\t\t\tisInitialized = false;\n\n\t\t\tconfig.onReset?.(config.context());\n\t\t},\n\t};\n};\n","/**\n * A dependency bag given either directly or as a factory.\n *\n * The factory form exists so a ViewModel declared at module level does not\n * resolve its gateways at import time: the locator needs an active framework\n * instance, and a module body runs before one exists.\n */\nexport type TLankaDependencyBag<TBag extends object> = TBag | (() => TBag) | undefined;\n\n/**\n * Reads a dependency bag, whichever form it was declared in.\n *\n * All three ViewModel factories wrote this out twice each — once for gateways,\n * once for services — which is six copies of one three-line decision.\n */\nexport const resolveLankaDependency = <TBag extends object>(\n\tdeclared: TLankaDependencyBag<TBag>,\n): TBag => {\n\tif (typeof declared === \"function\") return declared();\n\treturn declared ?? ({} as TBag);\n};\n","import type { ILankaVM } from \"../../_interfaces/ILankaVM\";\nimport { ALankaVM } from \"../../_abstractions/lanka-vm/ALankaVM\";\nimport { resolveLankaDependency } from \"../../_utils/resolve-lanka-dependency/resolveLankaDependency\";\nimport { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport type { ILankaVMConfig } from \"../../_interfaces/ILankaVMConfig\";\n\n/**\n * The functional style of `ALankaVM`: a ViewModel declared as an options object.\n *\n * Everything it can do, the class can do, because this IS the class — the hooks a\n * subclass overrides arrive here as config fields of the same names, and the\n * protected surface arrives as the `ctx` every hook is handed. What a ViewModel is\n * and how it binds scenarios is documented once, on `ALankaVM`.\n *\n * ⚠️ ACCESS-TRACKING BLIND SPOT. A consumer re-renders only for state keys it READ\n * off the returned proxy. An action that DERIVES a value (`getSomeView()`) reads the\n * store through `get()`, which the proxy never sees — so a component whose only link\n * to a state key is such a getter will never re-render when that key changes.\n *\n * Set `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT patch it\n * in the view by destructuring the underlying keys for their side effect only: that\n * reads as dead code, so a refactor, an unused-variable cleanup or a lint autofix\n * removes it and the screen silently freezes again. `MeetingReportViewModel` carries\n * the worked example (its report toggles froze exactly that way, twice).\n *\n * In development the mismatch ANNOUNCES ITSELF: the framework sees that a key\n * changed, that no re-render will follow, and that the component reads that key\n * through a getter — and warns with the ViewModel and key names. \"Remember to\n * set the flag\" is not a mechanism.\n */\nexport function createLankaVM<State extends object, Actions extends object>(\n\tconfig: ILankaVMConfig<State, Actions, Record<string, never>, Record<string, never>>,\n): ILankaVM<State & Actions & ILankaScenarioVM>;\n\nexport function createLankaVM<\n\tState extends object,\n\tActions extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, Record<string, never>, Services>,\n): ILankaVM<State & Actions & ILankaScenarioVM>;\n\nexport function createLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, TGateways, Record<string, never>>,\n): ILankaVM<State & Actions & ILankaScenarioVM>;\n\nexport function createLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, TGateways, Services>,\n): ILankaVM<State & Actions & ILankaScenarioVM>;\n\nexport function createLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(\n\tconfig: ILankaVMConfig<State, Actions, TGateways, Services>,\n): ILankaVM<State & Actions & ILankaScenarioVM> {\n\t/**\n\t * The bridge subclass lives here rather than in a shared helper because\n\t * `toStyleContext` is `protected`: only a class body deriving from `ALankaVM`\n\t * may read it. Canon: `skills/parity/SKILL.md` section 3a.\n\t */\n\tclass FunctionalVM extends ALankaVM<State, Actions, TGateways, Services> {\n\t\tprotected readonly name = config.name;\n\n\t\tprotected override readonly enableAccessTrackingOptimization =\n\t\t\tconfig.enableAccessTrackingOptimization ?? true;\n\n\t\tprotected override states(): State {\n\t\t\treturn config.states ?? ({} as State);\n\t\t}\n\n\t\tprotected override scenarioHandlers(): NonNullable<typeof config.scenarioHandlers> {\n\t\t\treturn config.scenarioHandlers ?? [];\n\t\t}\n\n\t\tprotected override enhancers(): NonNullable<typeof config.enhancers> {\n\t\t\treturn config.enhancers ?? [];\n\t\t}\n\n\t\tprotected override createGateways(): TGateways {\n\t\t\treturn resolveLankaDependency(config.gateways);\n\t\t}\n\n\t\tprotected override createServices(): Services {\n\t\t\treturn resolveLankaDependency(config.services);\n\t\t}\n\n\t\tprotected createActions(): Actions {\n\t\t\treturn config.createActions(this.toStyleContext());\n\t\t}\n\n\t\t// The config's hooks become this instance's OWN `onInit`/`onReset`, assigned\n\t\t// only when declared. An unconditional method override would make the base\n\t\t// read every functional ViewModel as taking both hooks and register it with\n\t\t// bootstrap for nothing; an own property is seen by the same test a class\n\t\t// override is, and `this.onInit()` means the same thing in both styles.\n\t\tpublic constructor() {\n\t\t\tsuper();\n\t\t\tconst { onInit, onReset } = config;\n\n\t\t\tif (onInit) this.onInit = () => onInit(this.toStyleContext());\n\t\t\tif (onReset) this.onReset = () => onReset(this.toStyleContext());\n\t\t}\n\t}\n\n\treturn new FunctionalVM().build();\n}\n","import { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\n\n/**\n * What a lazy ViewModel needs of the one behind it.\n *\n * Stated rather than cast: releasing a ViewModel means unsubscribing its\n * scenarios, so the mechanism does require this much — and every store it is\n * given has it, because `ILankaScenarioVM` is part of all three VM states.\n */\nexport interface ILankaReleasableStore {\n\tgetState: () => { resetScenario: () => void };\n}\n\n/**\n * What every lazy factory is handed.\n *\n * `name` and `isAccessTracked` are here rather than read off the built\n * ViewModel because they are the two members of the port that are VALUES, and a\n * property read may not build anything — see the trap below. Both are known\n * from the config the eager factory was given, so answering them costs nothing\n * and a devtool inspecting a lazy ViewModel does not construct it.\n */\nexport interface ILankaLazyVMConfig<TStore> {\n\t/** The ViewModel's name, as the log line and the port both say it. */\n\tname: string;\n\t/** What the log calls this kind of ViewModel — `VM`, `slVM`, `ssVM`. */\n\tkind: string;\n\t/** What this ViewModel decided about access tracking. */\n\tisAccessTracked: boolean;\n\t/** Builds the real ViewModel. Called at most once until `release`. */\n\tcreate: () => TStore;\n}\n\n/** A lazily built ViewModel: everything it is, plus `dispose`. */\nexport type TLazyLankaVMProxy<TStore> = TStore & { dispose: () => void };\n\n/** The one ViewModel a lazy proxy stands in front of, built at most once. */\ninterface ILankaLazySlot<TStore> {\n\tget: () => TStore;\n\trelease: () => void;\n}\n\n/**\n * Build once, release on demand: the state a lazy hook is made of.\n *\n * Separate from the forwarding below because they answer different questions —\n * WHEN the store exists, and WHERE a call goes. Read together they were one\n * function of fifty lines, which the composition canon calls what it is.\n */\nconst lazySlot = <TStore extends ILankaReleasableStore>(\n\tconfig: ILankaLazyVMConfig<TStore>,\n): ILankaLazySlot<TStore> => {\n\tlet store: TStore | null = null;\n\n\treturn {\n\t\tget: () => {\n\t\t\tif (!store) {\n\t\t\t\tlankaLogger.printViewModelLog(\n\t\t\t\t\t`LAZY: Creating ${config.kind} on first access`,\n\t\t\t\t\tconfig.name,\n\t\t\t\t);\n\t\t\t\tstore = config.create();\n\t\t\t}\n\t\t\treturn store;\n\t\t},\n\n\t\t/**\n\t\t * Releases the store. The next access builds a new one.\n\t\t *\n\t\t * Scenario subscriptions go first, through `resetScenario` — the same path\n\t\t * the between-tests reset uses. Dropping the reference without\n\t\t * unsubscribing would leave the bus holding handlers that write into a\n\t\t * discarded store.\n\t\t *\n\t\t * On a slot that never built, this does NOTHING: building a store in order\n\t\t * to destroy it is work with no result, and it would resurrect a ViewModel\n\t\t * a closed screen had just released.\n\t\t */\n\t\trelease: () => {\n\t\t\tif (!store) return;\n\t\t\tstore.getState().resetScenario();\n\t\t\tstore = null;\n\t\t},\n\t};\n};\n\n/**\n * How a member reaches the store: through a wrapper, always.\n *\n * The trap returns a FUNCTION for every member rather than the member itself, so\n * reading `useVM.setState` builds nothing and calling it builds the store. A trap\n * that resolved eagerly would end laziness the moment a devtool, a spread or a\n * debugger looked at the object — which is most of the ways an object is looked\n * at.\n *\n * `dispose` is the exception in both directions: it is not the store's, and it\n * must not build one.\n *\n * ## The thenable trap\n *\n * `then` is answered with `undefined`, never with a wrapper, and that line is\n * load-bearing. `await` and `Promise.resolve` decide whether a value is a\n * promise by READING `.then` and checking it is callable: a trap that returns a\n * function for every name says yes to that question for an object that is not a\n * promise. The runtime then calls it as `then(resolve, reject)`, the wrapper\n * forwards to a store member that does not exist, gets `undefined`, and neither\n * callback is ever invoked — so the `await` hangs FOREVER, with no error and no\n * stack.\n *\n * That makes `await someLazyVM` and every `async` function that RETURNS one a\n * silent deadlock, which is a shape a test harness reaches for constantly:\n * `const vm = await load()` resolves a promise with the proxy, and resolution\n * adopts a thenable. It cost an afternoon in a consumer's suite, where nineteen\n * tests timed out at 30s each and named their own first line.\n *\n * `catch` and `finally` are excluded with it. They are not part of the\n * thenable check, but an object answering `then` alone while a caller treats it\n * as a promise is the more confusing half of the same mistake — and no zustand\n * store has a member by either name.\n */\nconst PROMISE_MEMBERS: ReadonlySet<string | symbol> = new Set([\"then\", \"catch\", \"finally\"]);\n\nconst forwardEveryMember =\n\t<TStore>(slot: ILankaLazySlot<TStore>, config: ILankaLazyVMConfig<TStore>) =>\n\t(_target: object, property: string | symbol): unknown => {\n\t\tif (property === \"dispose\") return slot.release;\n\t\tif (PROMISE_MEMBERS.has(property)) return undefined;\n\n\t\t// The port's two VALUE members, answered from the config without building.\n\t\t// A binding reads both before it subscribes — `useLankaVM` asks\n\t\t// `isAccessTracked` on its first render — so forwarding them as functions\n\t\t// would have made every lazy ViewModel eager the moment a screen mounted,\n\t\t// which is the whole feature.\n\t\tif (property === \"name\") return config.name;\n\t\tif (property === \"isAccessTracked\") return config.isAccessTracked;\n\n\t\treturn (...args: unknown[]) => {\n\t\t\tconst built = slot.get();\n\t\t\tconst member = (built as Record<string | symbol, unknown>)[property];\n\n\t\t\t// Every remaining member of a ViewModel is a method — `getState`,\n\t\t\t// `subscribe`, `setState`, `getStoreState`. A value member cannot be\n\t\t\t// classified without building first, which is the one thing a property\n\t\t\t// READ may not do, so it is handed back from the call instead:\n\t\t\t// `lazyVM.whatever()` gives the value.\n\t\t\treturn typeof member === \"function\"\n\t\t\t\t? (member as (...rest: unknown[]) => unknown).apply(built, args)\n\t\t\t\t: member;\n\t\t};\n\t};\n\n/**\n * The lazy half of every ViewModel factory, written once.\n *\n * Three factories used to carry this mechanism: build on first access, keep the\n * store, hand every member through, release on `dispose`. They differed in the\n * one line that builds — and, as it turned out, in which members they had\n * remembered to hand through.\n *\n * ## Why a Proxy rather than a list of members\n *\n * The three copies attached members BY HAND, and the three lists differed:\n * `getState` + `getInitialState` + `setState` + `subscribe` for the plain one,\n * `getState` alone for the stateless, `getState` + `getStoreState` for the\n * shared-store. Each list matched its own type, so nothing was broken — but the\n * plain one MIRRORS a zustand store, and a mirror is a promise to keep in step\n * with something somebody else releases. The day zustand adds a member, three\n * files have to hear about it.\n *\n * A Proxy cannot be incomplete. What the built store has, the lazy hook has, and\n * the type each factory declares is what narrows it back down to that variant's\n * contract — a lazy variant promises exactly what its eager twin promises, plus\n * `dispose`.\n */\nexport const createLazyLankaVMProxy = <TStore extends ILankaReleasableStore>(\n\tconfig: ILankaLazyVMConfig<TStore>,\n): TLazyLankaVMProxy<TStore> => {\n\tconst slot = lazySlot(config);\n\n\t// An ordinary object is the target, where it used to be a function: a\n\t// ViewModel is read through a binding now, not called, so there is no call\n\t// signature left to stand in front of.\n\treturn new Proxy(\n\t\t{},\n\t\t{ get: forwardEveryMember(slot, config) },\n\t) as unknown as TLazyLankaVMProxy<TStore>;\n};\n","import { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { createLazyLankaVMProxy } from \"../../_internal/create-lazy-lanka-vm-proxy/createLazyLankaVMProxy\";\nimport { createLankaVM } from \"../create-lanka-vm/createLankaVM\";\nimport type { ILankaVMConfig } from \"../../_interfaces/ILankaVMConfig\";\n\nexport function createLazyLankaVM<State extends object, Actions extends object>(\n\tconfig: ILankaVMConfig<State, Actions, Record<string, never>, Record<string, never>>,\n): TLazyLankaVM<\n\tReturnType<typeof createLankaVM<State, Actions, Record<string, never>, Record<string, never>>>,\n\tState & Actions & ILankaScenarioVM\n>;\n\nexport function createLazyLankaVM<\n\tState extends object,\n\tActions extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, Record<string, never>, Services>,\n): TLazyLankaVM<\n\tReturnType<typeof createLankaVM<State, Actions, Record<string, never>, Services>>,\n\tState & Actions & ILankaScenarioVM\n>;\n\nexport function createLazyLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, TGateways, Record<string, never>>,\n): TLazyLankaVM<\n\tReturnType<typeof createLankaVM<State, Actions, TGateways, Record<string, never>>>,\n\tState & Actions & ILankaScenarioVM\n>;\n\nexport function createLazyLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaVMConfig<State, Actions, TGateways, Services>,\n): TLazyLankaVM<\n\tReturnType<typeof createLankaVM<State, Actions, TGateways, Services>>,\n\tState & Actions & ILankaScenarioVM\n>;\n\n/**\n * The lazy `createLankaVM`: nothing is built until a screen asks.\n *\n * For a ViewModel a session may never open — a settings screen, an admin panel,\n * a route behind a flag. The eager factory builds its store at module load, and\n * this one waits, which is the difference between paying for every screen the\n * application has and paying for the screens it shows.\n *\n * The mechanism is `createLazyLankaVMProxy`, shared with the stateless and\n * shared-store variants, so all three hand through every member the store has\n * and release the same way. This file is the one line that says what gets built.\n */\nexport function createLazyLankaVM<\n\tState extends object,\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(config: ILankaVMConfig<State, Actions, TGateways, Services>) {\n\treturn createLazyLankaVMProxy({\n\t\tname: config.name,\n\t\tkind: \"VM\",\n\t\tisAccessTracked: config.enableAccessTrackingOptimization ?? true,\n\t\tcreate: () => createLankaVM(config),\n\t});\n}\n\n/**\n * A lazy ViewModel hook: everything an ordinary store does, plus `dispose`.\n */\nexport type TLazyLankaVM<TStore, TFullState> = TStore & {\n\tgetState: () => TFullState;\n\tdispose: () => void;\n};\n","import { ALankaVMEnvironment } from \"../lanka-vm-environment/ALankaVMEnvironment\";\nimport { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { lankaScenarioBootstrap } from \"../../../scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\nimport { createLankaScenarioBinder } from \"../../_internal/create-lanka-scenario-binder/createLankaScenarioBinder\";\nimport type { ILankaScenario } from \"../../../scenario/_interfaces/ILankaScenario\";\nimport type { TLankaScenarioBindingsDeclaration } from \"../../_types/TLankaScenarioBindingsDeclaration\";\nimport type {\n\tILankaStatelessScenarioBinding,\n\tILankaStatelessVMContext,\n\tTLankaSetState,\n} from \"../../_factories/create-stateless-lanka-vm/createStatelessLankaVM\";\nimport type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\n\n/**\n * A ViewModel that holds no reactive state: actions, and what they orchestrate.\n *\n * The second rung of the ladder in `core/README.md` written as a class. A screen\n * that reads nothing and only DOES things — a sign-out, a share sheet, a form\n * whose fields live in the form library — pays for a zustand store it never\n * reads; this is the same role without one.\n *\n * The protected surface IS the functional context, member for member: `set`,\n * `get`, `gateways`, `services`, `trigger`. What the config supplies as a value\n * or a thunk, the class supplies by overriding a method of the same name, with\n * the two dependency suppliers named `createGateways` and `createServices`\n * because `gateways` and `services` already name what they answer.\n * `toLifecycleHooks` is protected too and is not one of these: it is how the\n * framework reads `onInit` and `onReset`, and a ViewModel overrides those,\n * never it.\n *\n * ```ts\n * class SessionVM extends ALankaStatelessVM<ISessionActions, ISessionGateways> {\n * \tprotected readonly name = \"SessionVM\";\n *\n * \tprotected createGateways(): ISessionGateways {\n * \t\treturn { session: new SessionGateway() };\n * \t}\n *\n * \tprotected createActions(): ISessionActions {\n * \t\treturn {\n * \t\t\tsignOut: async () => {\n * \t\t\t\tawait this.gateways.session.signOut();\n * \t\t\t\tthis.trigger(sessionEnded);\n * \t\t\t},\n * \t\t};\n * \t}\n * }\n *\n * export const useSessionVM = new SessionVM().build();\n * ```\n *\n * Canon: `skills/parity/SKILL.md`.\n */\nexport abstract class ALankaStatelessVM<\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> extends ALankaVMEnvironment<TGateways, Services> {\n\t/** Names the ViewModel in the logs, the scenario registry and any warning. */\n\tprotected abstract readonly name: string;\n\n\t/** Writes state. Available from `createActions` onwards, never before. */\n\tprotected set!: TLankaSetState<Actions & ILankaScenarioVM>;\n\n\t/** Reads what the actions have written, including the actions themselves. */\n\tprotected get!: () => Actions & ILankaScenarioVM;\n\n\t/** Fires a scenario, which every ViewModel bound to it then hears. */\n\tprotected trigger = <TData>(scenario: ILankaScenario<TData>, data?: TData): void => {\n\t\tscenario.trigger(data);\n\t};\n\n\t/**\n\t * The scenarios this ViewModel listens to, unsubscribed for it on reset.\n\t *\n\t * Returning a FACTORY postpones building the list until bind time. See\n\t * `TLankaScenarioBindingsDeclaration`.\n\t */\n\tprotected scenarioHandlers(): TLankaScenarioBindingsDeclaration<\n\t\tILankaStatelessScenarioBinding<unknown, Actions & ILankaScenarioVM, TGateways, Services>\n\t> {\n\t\treturn [];\n\t}\n\n\t/** The actions the screen calls. Written against `this.set` and `this.get`. */\n\tprotected abstract createActions(): Actions;\n\n\t/**\n\t * The protected surface as an object, for the functional style.\n\t *\n\t * Assembled INSIDE the class because that is the only place `protected` can be\n\t * read. Canon: `skills/parity/SKILL.md` section 3a.\n\t */\n\tprotected toStyleContext(): ILankaStatelessVMContext<\n\t\tActions & ILankaScenarioVM,\n\t\tTGateways,\n\t\tServices\n\t> {\n\t\treturn {\n\t\t\tset: this.set,\n\t\t\tget: this.get,\n\t\t\tgateways: this.gateways,\n\t\t\tservices: this.services,\n\t\t\ttrigger: this.trigger,\n\t\t};\n\t}\n\n\t/**\n\t * Builds the ViewModel a screen reads. One per call.\n\t *\n\t * What comes back answers the read half of the port and nothing more: there\n\t * is no store to write to from outside, because there is no state to write.\n\t */\n\tpublic build(): ILankaReadableVM<Actions & ILankaScenarioVM> {\n\t\ttype TFullState = Actions & ILankaScenarioVM;\n\n\t\tlankaLogger.printViewModelLog(\"START Create slVM\", this.name);\n\n\t\tlet state = {} as TFullState;\n\n\t\tthis.get = () => state;\n\t\tthis.set = (partial, replace) => {\n\t\t\tconst next =\n\t\t\t\ttypeof partial === \"function\"\n\t\t\t\t\t? (partial as (current: TFullState) => TFullState)(state)\n\t\t\t\t\t: partial;\n\n\t\t\tstate = replace ? (next as TFullState) : { ...state, ...next };\n\t\t};\n\n\t\tthis.gateways = this.createGateways();\n\t\tthis.services = this.createServices();\n\n\t\tconst actions = this.createActions();\n\n\t\tconst hooks = this.toLifecycleHooks();\n\t\tconst { initializeScenario, resetScenario, needsBootstrap } = createLankaScenarioBinder({\n\t\t\tname: this.name,\n\t\t\tbindings: this.scenarioHandlers(),\n\t\t\tcontext: () => this.toStyleContext(),\n\t\t\tonInit: hooks.onInit,\n\t\t\tonReset: hooks.onReset,\n\t\t});\n\n\t\tstate = { ...state, ...actions, initializeScenario, resetScenario };\n\n\t\tif (needsBootstrap) {\n\t\t\tlankaScenarioBootstrap.registerViewModel(state, this.name);\n\t\t}\n\n\t\t/**\n\t\t * The port over a ViewModel with nothing that changes.\n\t\t *\n\t\t * `subscribe` returns an unsubscribe and never calls the listener, which is\n\t\t * the honest implementation rather than a stub: a stateless ViewModel holds\n\t\t * actions and no reactive fields, so there is no next state to report. That\n\t\t * is what lets one binding serve all three shapes without asking which it\n\t\t * was handed.\n\t\t *\n\t\t * `isAccessTracked` is false for the same reason — there are no keys whose\n\t\t * reads could be worth recording, and a binding that tried would pay for a\n\t\t * Proxy over an object that never moves.\n\t\t */\n\t\tconst statelessViewModel: ILankaReadableVM<TFullState> = {\n\t\t\tname: this.name,\n\t\t\tgetState: () => state,\n\t\t\tsubscribe: () => () => undefined,\n\t\t\tisAccessTracked: false,\n\t\t};\n\n\t\tlankaLogger.printViewModelLog(\"FINISH Create slVM\", this.name);\n\n\t\treturn statelessViewModel;\n\t}\n}\n","import type { ILankaReadableVM } from \"../../_interfaces/ILankaReadableVM\";\nimport type { TLankaReplayRequest } from \"../../../scenario/event-bus/lanka-event-bus-instance/LankaEventBusInstance\";\nimport type { TLankaScenarioHandler } from \"../../_types/TLankaScenarioHandler\";\nimport type { TLankaScenarioBindingsDeclaration } from \"../../_types/TLankaScenarioBindingsDeclaration\";\nimport { resolveLankaDependency } from \"../../_utils/resolve-lanka-dependency/resolveLankaDependency\";\nimport { ALankaStatelessVM } from \"../../_abstractions/lanka-stateless-vm/ALankaStatelessVM\";\nimport { ILankaScenario } from \"../../../scenario/_interfaces/ILankaScenario\";\nimport { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\n\nexport type TLankaSetState<TState> = (\n\tpartial: TState | Partial<TState> | ((state: TState) => TState | Partial<TState>),\n\treplace?: boolean,\n) => void;\n\nexport interface ILankaStatelessVMContext<\n\tTState,\n\tTGateways extends object,\n\tTServices extends object,\n> {\n\tset: TLankaSetState<TState>;\n\tget: () => TState;\n\tgateways: TGateways;\n\tservices: TServices;\n\ttrigger: <T>(scenario: ILankaScenario<T>, data?: T) => void;\n}\n\nexport interface ILankaStatelessScenarioBinding<\n\tTData,\n\tTState extends object,\n\tTGateways extends object,\n\tTServices extends object,\n> {\n\tscenario: ILankaScenario<TData>;\n\thandler: (\n\t\tctx: ILankaStatelessVMContext<TState, TGateways, TServices>,\n\t) => TLankaScenarioHandler<TData>;\n\toptions?: {\n\t\tpriority?: number;\n\t\treplay?: TLankaReplayRequest;\n\t\tusedBy?: string;\n\t};\n}\n\nexport type TLankaStatelessVMConfig<\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> = {\n\tname: string;\n\tcreateActions: (\n\t\tctx: ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>,\n\t) => Actions;\n\t/** A FACTORY is read at bind time — see `TLankaScenarioBindingsDeclaration`. */\n\tscenarioHandlers?: TLankaScenarioBindingsDeclaration<\n\t\tILankaStatelessScenarioBinding<unknown, Actions & ILankaScenarioVM, TGateways, Services>\n\t>;\n\tservices?: Services | (() => Services);\n\tgateways?: TGateways | (() => TGateways);\n\tonInit?: (\n\t\tctx: ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>,\n\t) => void;\n\tonReset?: (\n\t\tctx: ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>,\n\t) => void;\n};\n\n/**\n * @deprecated since 2.0.0 - use ILankaReadableVM, which is what a stateless\n * ViewModel now IS: `createStatelessLankaVM` answers a readable ViewModel rather\n * than a React hook, so the word \"Hook\" in this name no longer describes\n * anything. The alias is kept because a published name is never removed, and it\n * resolves to exactly the type the factory returns.\n */\nexport type TLankaStatelessVMHook<Actions extends object> = ILankaReadableVM<\n\tActions & ILankaScenarioVM\n>;\n\nexport function createStatelessLankaVM<Actions extends object>(\n\tconfig: TLankaStatelessVMConfig<Actions, Record<string, never>, Record<string, never>>,\n): ILankaReadableVM<Actions & ILankaScenarioVM>;\n\nexport function createStatelessLankaVM<Actions extends object, Services extends object>(\n\tconfig: TLankaStatelessVMConfig<Actions, Record<string, never>, Services>,\n): ILankaReadableVM<Actions & ILankaScenarioVM>;\n\nexport function createStatelessLankaVM<Actions extends object, TGateways extends object>(\n\tconfig: TLankaStatelessVMConfig<Actions, TGateways, Record<string, never>>,\n): ILankaReadableVM<Actions & ILankaScenarioVM>;\n\nexport function createStatelessLankaVM<\n\tActions extends object,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: TLankaStatelessVMConfig<Actions, TGateways, Services>,\n): ILankaReadableVM<Actions & ILankaScenarioVM>;\n\n/**\n * The functional style of `ALankaStatelessVM`: actions declared as an options object.\n *\n * Same outward shape as the stateful factory — a readable ViewModel — and no\n * zustand store under it, because a ViewModel that holds nothing has nothing to\n * subscribe to. What it is and how it binds scenarios is documented once, on the\n * class.\n */\nexport function createStatelessLankaVM<\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(config: TLankaStatelessVMConfig<Actions, TGateways, Services>): TLankaStatelessVMHook<Actions> {\n\t/**\n\t * The bridge subclass lives here rather than in a shared helper because\n\t * `toStyleContext` is `protected`: only a class body deriving from\n\t * `ALankaStatelessVM` may read it. Canon: `skills/parity/SKILL.md` section 3a.\n\t */\n\tclass FunctionalStatelessVM extends ALankaStatelessVM<Actions, TGateways, Services> {\n\t\tprotected readonly name = config.name;\n\n\t\tprotected override scenarioHandlers(): NonNullable<typeof config.scenarioHandlers> {\n\t\t\treturn config.scenarioHandlers ?? [];\n\t\t}\n\n\t\tprotected override createGateways(): TGateways {\n\t\t\treturn resolveLankaDependency(config.gateways);\n\t\t}\n\n\t\tprotected override createServices(): Services {\n\t\t\treturn resolveLankaDependency(config.services);\n\t\t}\n\n\t\tprotected createActions(): Actions {\n\t\t\treturn config.createActions(this.toStyleContext());\n\t\t}\n\n\t\t// Own properties, assigned only when declared — see the same constructor in\n\t\t// `createLankaVM` for why not a method override.\n\t\tpublic constructor() {\n\t\t\tsuper();\n\t\t\tconst { onInit, onReset } = config;\n\n\t\t\tif (onInit) this.onInit = () => onInit(this.toStyleContext());\n\t\t\tif (onReset) this.onReset = () => onReset(this.toStyleContext());\n\t\t}\n\t}\n\n\treturn new FunctionalStatelessVM().build();\n}\n","import { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { createStatelessLankaVM } from \"../create-stateless-lanka-vm/createStatelessLankaVM\";\nimport type { ILankaVMConfig } from \"../../_interfaces/ILankaVMConfig\";\nimport { createLazyLankaVMProxy } from \"../../_internal/create-lazy-lanka-vm-proxy/createLazyLankaVMProxy\";\n\n/**\n * What the LAZY stateless factory takes, which is not what the eager one takes.\n *\n * This type was called `TLankaStatelessVMConfig` — the same name the eager\n * factory declares and `lanka/viewmodel` publishes — and the two are not the\n * same shape. One name over two types is a surface that cannot be read: a\n * consumer annotating a shared config with the published name and passing it\n * here got a compile error nothing in the published surface explained, and five\n * binding mirrors had to derive their parameter type off this function rather\n * than name it. Renaming costs nothing, because no barrel ever re-exported the\n * duplicate.\n *\n * **Where it genuinely differs, and it is not cosmetic.** The eager config hands\n * `createActions`, `onInit` and `onReset` an `ILankaStatelessVMContext`, whose\n * `set` is `TLankaSetState`. This one is `ILankaVMConfig` with `states` removed,\n * so it hands them the STATEFUL `ILankaVMContext`, whose `set` is zustand's\n * `setState` — including the `replace` argument, on a ViewModel that holds\n * nothing to replace.\n *\n * It also accepts `enhancers` and `enableAccessTrackingOptimization`, which\n * `createStatelessLankaVM` does not read: a stateless ViewModel has no store to\n * enhance and nothing whose reads could be tracked. They are accepted and\n * ignored, and that is worth knowing before writing one.\n *\n * Both are the eager factory's shape leaking through `Omit`, and narrowing this\n * to the eager config would REFUSE configs that compile today — the unsafe move\n * `skills/surface/SKILL.md` §6c names. So the difference is published under its\n * own name instead, where a reader meets it, and closing it belongs to a major.\n */\nexport type TLankaLazyStatelessVMConfig<\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> = Omit<ILankaVMConfig<object, Actions, TGateways, Services>, \"states\"> & {\n\tstates?: never;\n};\n\ntype TLazyStatelessReturn<\n\tActions extends object,\n\tTGateways extends object,\n\tServices extends object,\n> = ReturnType<typeof createStatelessLankaVM<Actions, TGateways, Services>> & {\n\tgetState: () => Actions & ILankaScenarioVM;\n\tdispose: () => void;\n};\n\nexport function createLazyStatelessLankaVM<Actions extends object>(\n\tconfig: TLankaLazyStatelessVMConfig<Actions, Record<string, never>, Record<string, never>>,\n): TLazyStatelessReturn<Actions, Record<string, never>, Record<string, never>>;\n\nexport function createLazyStatelessLankaVM<Actions extends object, Services extends object>(\n\tconfig: TLankaLazyStatelessVMConfig<Actions, Record<string, never>, Services>,\n): TLazyStatelessReturn<Actions, Record<string, never>, Services>;\n\nexport function createLazyStatelessLankaVM<Actions extends object, TGateways extends object>(\n\tconfig: TLankaLazyStatelessVMConfig<Actions, TGateways, Record<string, never>>,\n): TLazyStatelessReturn<Actions, TGateways, Record<string, never>>;\n\nexport function createLazyStatelessLankaVM<\n\tActions extends object,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: TLankaLazyStatelessVMConfig<Actions, TGateways, Services>,\n): TLazyStatelessReturn<Actions, TGateways, Services>;\n\n/**\n * The lazy `createStatelessLankaVM`: nothing is built until a screen asks.\n *\n * The mechanism is `createLazyLankaVMProxy` — build on first access, hand every\n * member through, release on `dispose` — and this file is the one line that says\n * WHICH ViewModel is built. It used to be a copy of that mechanism, and the copy\n * had forgotten `setState`, `subscribe` and `getInitialState`: typed as the whole\n * store, absent at runtime.\n */\nexport function createLazyStatelessLankaVM<\n\tActions extends object,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(config: TLankaLazyStatelessVMConfig<Actions, TGateways, Services>) {\n\treturn createLazyLankaVMProxy({\n\t\tname: config.name,\n\t\tkind: \"slVM\",\n\t\t// A stateless ViewModel has no reactive fields, so there is nothing whose\n\t\t// reads could be worth recording — the eager one answers false too.\n\t\tisAccessTracked: false,\n\t\tcreate: () => createStatelessLankaVM(config),\n\t});\n}\n","import type { ILankaSharedStoreVM } from \"../../_interfaces/ILankaSharedStoreVMConfig\";\nimport { resolveLankaDependency } from \"../../_utils/resolve-lanka-dependency/resolveLankaDependency\";\nimport { createLankaScenarioBinder } from \"../../_internal/create-lanka-scenario-binder/createLankaScenarioBinder\";\n\nimport { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { lankaScenarioBootstrap } from \"../../../scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\nimport type { ILankaSharedStoreVMConfig } from \"../../_interfaces/ILankaSharedStoreVMConfig\";\nimport type { ILankaSharedStoreVMContext } from \"../../_interfaces/ILankaSharedStoreVMContext\";\nimport { ALankaSharedStore } from \"../../_abstractions/lanka-shared-store/ALankaSharedStore\";\n\n/**\n * Factory for ViewModels backed by an external shared store instance.\n * Multiple ViewModels can be created on top of the same store instance.\n */\nexport function createSharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n>(\n\tconfig: ILankaSharedStoreVMConfig<\n\t\tStoreState,\n\t\tActions,\n\t\tStore,\n\t\tRecord<string, never>,\n\t\tRecord<string, never>\n\t>,\n): ILankaSharedStoreVM<StoreState, Actions>;\n\nexport function createSharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tServices extends object,\n>(\n\tconfig: ILankaSharedStoreVMConfig<StoreState, Actions, Store, Record<string, never>, Services>,\n): ILankaSharedStoreVM<StoreState, Actions>;\n\nexport function createSharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object,\n>(\n\tconfig: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Record<string, never>>,\n): ILankaSharedStoreVM<StoreState, Actions>;\n\nexport function createSharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Services>,\n): ILankaSharedStoreVM<StoreState, Actions>;\n\nexport function createSharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Services>) {\n\tlankaLogger.printViewModelLog(\"START Create ssVM\", config.name);\n\n\ttype TFullState = StoreState & Actions & ILankaScenarioVM;\n\n\t/**\n\t * Unsubscribe functions, plus the event types already subscribed to.\n\t *\n\t * Two separate jobs, kept separate: the array releases subscriptions, the set\n\t * prevents subscribing to one event type twice.\n\t */\n\tlet actions = {} as Actions;\n\n\tconst gateways = resolveLankaDependency(config.gateways);\n\tconst services = resolveLankaDependency(config.services);\n\n\tlet lastStoreStateRef: StoreState | null = null;\n\tlet lastFullStateRef: TFullState | null = null;\n\n\tconst buildFullState = (storeState: StoreState): TFullState => {\n\t\tif (lastStoreStateRef === storeState && lastFullStateRef) {\n\t\t\treturn lastFullStateRef;\n\t\t}\n\n\t\tlastStoreStateRef = storeState;\n\t\tlastFullStateRef = {\n\t\t\t...storeState,\n\t\t\t...actions,\n\t\t\tinitializeScenario,\n\t\t\tresetScenario,\n\t\t};\n\n\t\treturn lastFullStateRef;\n\t};\n\n\tconst getFullState = (): TFullState => buildFullState(config.store.getState());\n\n\tconst ctx: ILankaSharedStoreVMContext<StoreState, TFullState, Store, TGateways, Services> = {\n\t\tset: (partial, replace) => config.store.setState(partial, replace),\n\t\tgetStore: () => config.store.getState(),\n\t\tget: () => getFullState(),\n\t\tstore: config.store,\n\t\tgateways,\n\t\tservices,\n\t\ttrigger: (scenario, data) => scenario.trigger(data),\n\t};\n\n\tconst { initializeScenario, resetScenario, needsBootstrap } = createLankaScenarioBinder({\n\t\tname: config.name,\n\t\tbindings: config.scenarioHandlers,\n\t\tcontext: () => ctx,\n\t\tonInit: config.onInit,\n\t\tonReset: config.onReset,\n\t});\n\n\tactions = config.createActions(ctx);\n\tlastStoreStateRef = null;\n\tlastFullStateRef = null;\n\tconst isAccessTrackingEnabled = config.enableAccessTrackingOptimization ?? true;\n\n\t/**\n\t * The port over a slice of somebody else's store.\n\t *\n\t * `subscribe` is where the SHAPING happens — the store reports its own slice,\n\t * and both states are composed into full shape before a listener sees them.\n\t * Doing it here rather than in a binding is what lets one binding serve this\n\t * shape and the plain one without knowing which it was handed.\n\t *\n\t * Two ViewModels over one store therefore each hear every change to it, and\n\t * each decides for itself — through its reader's access tracking — whether the\n\t * change is worth a render.\n\t */\n\tconst sharedStoreViewModel: ILankaSharedStoreVM<StoreState, Actions> = {\n\t\tname: config.name,\n\t\tgetState: getFullState,\n\t\tgetStoreState: () => config.store.getState(),\n\t\tisAccessTracked: isAccessTrackingEnabled,\n\t\tsubscribe: (listener) =>\n\t\t\tconfig.store.subscribe((storeState, prevStoreState) => {\n\t\t\t\tlistener(buildFullState(storeState), buildFullState(prevStoreState));\n\t\t\t}),\n\t};\n\n\tif (needsBootstrap) {\n\t\tconst scenarioViewModel: ILankaScenarioVM = {\n\t\t\tinitializeScenario,\n\t\t\tresetScenario,\n\t\t};\n\t\tlankaScenarioBootstrap.registerViewModel(scenarioViewModel, config.name);\n\t}\n\n\tlankaLogger.printViewModelLog(\"FINISH Create ssVM\", config.name);\n\n\treturn sharedStoreViewModel;\n}\n","import { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport { ALankaSharedStore } from \"../../_abstractions/lanka-shared-store/ALankaSharedStore\";\nimport { createLazyLankaVMProxy } from \"../../_internal/create-lazy-lanka-vm-proxy/createLazyLankaVMProxy\";\nimport { createSharedStoreLankaVM } from \"../create-shared-store-lanka-vm/createSharedStoreLankaVM\";\nimport type { ILankaSharedStoreVMConfig } from \"../../_interfaces/ILankaSharedStoreVMConfig\";\n\n/**\n * What a lazy shared-store ViewModel is, in the type as well as at runtime.\n *\n * The overloads used to promise the eager factory's return and nothing else, so\n * `dispose` and `getStoreState` existed on the object and not in the type: a\n * consumer releasing a closed screen's ViewModel got a compile error for calling\n * something that was there.\n */\ntype TLazySharedStoreReturn<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object,\n\tServices extends object,\n> = ReturnType<typeof createSharedStoreLankaVM<StoreState, Actions, Store, TGateways, Services>> & {\n\tgetState: () => StoreState & Actions & ILankaScenarioVM;\n\tgetStoreState: () => StoreState;\n\tdispose: () => void;\n};\n\nexport function createLazySharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n>(\n\tconfig: ILankaSharedStoreVMConfig<\n\t\tStoreState,\n\t\tActions,\n\t\tStore,\n\t\tRecord<string, never>,\n\t\tRecord<string, never>\n\t>,\n): TLazySharedStoreReturn<StoreState, Actions, Store, Record<string, never>, Record<string, never>>;\n\nexport function createLazySharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object,\n>(\n\tconfig: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Record<string, never>>,\n): TLazySharedStoreReturn<StoreState, Actions, Store, TGateways, Record<string, never>>;\n\nexport function createLazySharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object,\n\tServices extends object,\n>(\n\tconfig: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Services>,\n): TLazySharedStoreReturn<StoreState, Actions, Store, TGateways, Services>;\n\n/**\n * The lazy `createSharedStoreLankaVM`: nothing is built until a screen asks.\n *\n * The mechanism is `createLazyLankaVMProxy`; this file is the one line that says\n * which ViewModel gets built. `dispose` releases THIS ViewModel's scenario\n * subscriptions and never the shared store — the store is shared, other\n * ViewModels stand on it, and taking its state away is not this one's decision.\n * That is the hook's behaviour, not a special case here: it resets the scenario\n * and drops its reference, and a shared store outlives both.\n */\nexport function createLazySharedStoreLankaVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Services>) {\n\treturn createLazyLankaVMProxy({\n\t\tname: config.name,\n\t\tkind: \"ssVM\",\n\t\tisAccessTracked: config.enableAccessTrackingOptimization ?? true,\n\t\tcreate: () => createSharedStoreLankaVM(config),\n\t});\n}\n","import { ALankaVMEnvironment } from \"../lanka-vm-environment/ALankaVMEnvironment\";\nimport { createSharedStoreLankaVM } from \"../../_factories/create-shared-store-lanka-vm/createSharedStoreLankaVM\";\nimport type { ALankaSharedStore } from \"../lanka-shared-store/ALankaSharedStore\";\nimport type { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport type { ILankaSharedStoreVMContext } from \"../../_interfaces/ILankaSharedStoreVMContext\";\nimport type { TLankaScenarioBindingsDeclaration } from \"../../_types/TLankaScenarioBindingsDeclaration\";\nimport type {\n\tILankaSharedStoreScenarioBinding,\n\tTLankaSharedStoreVMHook,\n} from \"../../_interfaces/ILankaSharedStoreVMConfig\";\n\n/**\n * A ViewModel over a store several ViewModels share, written as a class.\n *\n * The third rung of the ladder in `core/README.md`: reach for it only when two\n * ViewModels must CO-EDIT one state — a list and the badge that counts it, a\n * form and the header that says it is dirty. What the class adds over the\n * stateful base is where the state lives: in the store it is given, so `set`\n * writes there and `getStore` reads it.\n *\n * ```ts\n * class BadgeVM extends ALankaSharedStoreVM<ISelection, IBadgeActions, TodoStore> {\n * \tprotected readonly name = \"BadgeVM\";\n *\n * \tpublic constructor(store: TodoStore) {\n * \t\tsuper(store);\n * \t}\n *\n * \tprotected createActions(): IBadgeActions {\n * \t\treturn { clear: () => this.set({ selectedId: null }) };\n * \t}\n * }\n * ```\n *\n * Unlike its two siblings this one is a thin adapter rather than the\n * implementation: the store, the tracked hook and the two memoised state\n * references are the factory's, and duplicating them here would be the second\n * implementation the parity canon exists to prevent. What it gives a class-style\n * consumer is the same protected surface under the same names — plus\n * `toLifecycleHooks`, the framework's reading of `onInit`/`onReset`, which is\n * not an extension point.\n *\n * Canon: `skills/parity/SKILL.md`.\n */\nexport abstract class ALankaSharedStoreVM<\n\tStoreState extends object,\n\tActions extends object,\n\tStore extends ALankaSharedStore<StoreState>,\n\tTGateways extends object = Record<string, never>,\n\tServices extends object = Record<string, never>,\n> extends ALankaVMEnvironment<TGateways, Services> {\n\t/** Names the ViewModel in the logs and in the scenario registry. */\n\tprotected abstract readonly name: string;\n\n\t/** The store this ViewModel and its siblings share. */\n\tprotected readonly store: Store;\n\n\t/** Writes into the shared store, which every reader of it hears about. */\n\tprotected set!: ILankaSharedStoreVMContext<\n\t\tStoreState,\n\t\tStoreState & Actions & ILankaScenarioVM,\n\t\tStore,\n\t\tTGateways,\n\t\tServices\n\t>[\"set\"];\n\n\t/** Reads the store's own state, without this ViewModel's actions on top. */\n\tprotected getStore!: () => StoreState;\n\n\t/** Reads the store's state WITH the actions, which is what a screen sees. */\n\tprotected get!: () => StoreState & Actions & ILankaScenarioVM;\n\n\t/** Fires a scenario, which every ViewModel bound to it then hears. */\n\tprotected trigger!: ILankaSharedStoreVMContext<\n\t\tStoreState,\n\t\tStoreState & Actions & ILankaScenarioVM,\n\t\tStore,\n\t\tTGateways,\n\t\tServices\n\t>[\"trigger\"];\n\n\tpublic constructor(store: Store) {\n\t\tsuper();\n\t\tthis.store = store;\n\t}\n\n\t/**\n\t * Turn off when one broad consumer reads most fields of the store — proxy\n\t * tracking then costs more than it saves.\n\t */\n\tprotected readonly enableAccessTrackingOptimization: boolean = true;\n\n\t/** The scenarios this ViewModel listens to, unsubscribed for it on reset. */\n\tprotected scenarioHandlers(): TLankaScenarioBindingsDeclaration<\n\t\tILankaSharedStoreScenarioBinding<unknown, StoreState, Actions, Store, TGateways, Services>\n\t> {\n\t\treturn [];\n\t}\n\n\t/** The actions the screen calls. Written against `this.set` and `this.get`. */\n\tprotected abstract createActions(): Actions;\n\n\t/** Builds the hook a screen calls. One ViewModel per call. */\n\tpublic build(): TLankaSharedStoreVMHook<StoreState, Actions> {\n\t\tconst hooks = this.toLifecycleHooks();\n\n\t\treturn createSharedStoreLankaVM<StoreState, Actions, Store, TGateways, Services>({\n\t\t\tname: this.name,\n\t\t\tstore: this.store,\n\t\t\tenableAccessTrackingOptimization: this.enableAccessTrackingOptimization,\n\t\t\tgateways: () => this.createGateways(),\n\t\t\tservices: () => this.createServices(),\n\t\t\tscenarioHandlers: this.scenarioHandlers(),\n\t\t\tcreateActions: (context) => {\n\t\t\t\t// The context arrives here and becomes the protected surface, under the\n\t\t\t\t// same names it carries: a consumer who switches styles moves the same\n\t\t\t\t// call from `set(...)` to `this.set(...)` and changes nothing else.\n\t\t\t\tthis.set = context.set;\n\t\t\t\tthis.getStore = context.getStore;\n\t\t\t\tthis.get = context.get;\n\t\t\t\tthis.gateways = context.gateways;\n\t\t\t\tthis.services = context.services;\n\t\t\t\tthis.trigger = context.trigger;\n\n\t\t\t\treturn this.createActions();\n\t\t\t},\n\t\t\tonInit: hooks.onInit,\n\t\t\tonReset: hooks.onReset,\n\t\t});\n\t}\n}\n","import { createStore, StoreApi } from \"zustand/vanilla\";\n\n/**\n * Base abstraction for shared feature stores.\n *\n * A store built on this class is resolved through the shared-store locator and\n * reused across several ViewModels.\n */\nexport abstract class ALankaSharedStore<TState extends object> {\n\tprivate readonly api: StoreApi<TState>;\n\tprivate readonly createInitialState: () => TState;\n\n\tprotected constructor(createInitialState: () => TState) {\n\t\tthis.createInitialState = createInitialState;\n\t\tthis.api = createStore<TState>()(() => this.createInitialState());\n\t}\n\n\tpublic getApi(): StoreApi<TState> {\n\t\treturn this.api;\n\t}\n\n\tpublic getState(): TState {\n\t\treturn this.api.getState();\n\t}\n\n\tpublic setState(\n\t\tpartial: TState | Partial<TState> | ((state: TState) => TState | Partial<TState>),\n\t\treplace?: boolean,\n\t): void {\n\t\tconst resolved = typeof partial === \"function\" ? partial(this.api.getState()) : partial;\n\n\t\tif (replace) {\n\t\t\tthis.api.setState(resolved as TState, true);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.api.setState(resolved);\n\t}\n\n\tpublic subscribe(listener: (state: TState, prevState: TState) => void) {\n\t\treturn this.api.subscribe(listener);\n\t}\n\n\t/**\n\t * Back to what the store was built with.\n\t *\n\t * Public, and it was not: while this was `protected` only a subclass could\n\t * reset, so the class style had a capability the functional one could not\n\t * reach — the asymmetry `skills/parity/SKILL.md` forbids. A shared store needs\n\t * explicit reset points (see the ladder in `core/README.md`), and the\n\t * application that owns them is outside the class either way.\n\t */\n\tpublic reset(): void {\n\t\tthis.api.setState(this.createInitialState(), true);\n\t}\n}\n","import { ALankaSharedStore } from \"../../_abstractions/lanka-shared-store/ALankaSharedStore\";\n\n/**\n * A shared store, without writing a class whose body is one function.\n *\n * A shared store is the third rung of the ladder in `core/README.md`: reach for\n * it only when several ViewModels must CO-EDIT one state. Most stores have no\n * behaviour of their own — the state and how to build it fresh is the whole\n * declaration — and this is the shape for those.\n *\n * One implementation: what comes back is an `ALankaSharedStore`, so a reset, a\n * subscription and the zustand api behave identically either way.\n */\nexport const createLankaSharedStore = <TState extends object>(\n\tcreateInitialState: () => TState,\n): ALankaSharedStore<TState> => {\n\tclass FunctionalSharedStore extends ALankaSharedStore<TState> {\n\t\tpublic constructor() {\n\t\t\tsuper(createInitialState);\n\t\t}\n\t}\n\n\treturn new FunctionalSharedStore();\n};\n","/**\n * Compares two selections one level deep.\n *\n * Own keys, same count, `Object.is` on each value. Arrays included, because an\n * array IS an object with numeric keys and a selection like `state.todos.map(…)`\n * is the commonest thing there is.\n *\n * Unbranded, because it is a pure function over plain values and holds nothing —\n * `skills/naming/SKILL.md`, the prefix table. Not exported: what a consumer\n * needs is the hold below, and a second name for the predicate would be a second\n * promise to keep.\n */\nconst isShallowEqual = (a: unknown, b: unknown): boolean => {\n\tif (Object.is(a, b)) return true;\n\tif (typeof a !== \"object\" || a === null || typeof b !== \"object\" || b === null) return false;\n\n\tconst left = Object.keys(a);\n\tconst right = Object.keys(b);\n\tif (left.length !== right.length) return false;\n\n\treturn left.every(\n\t\t(key) =>\n\t\t\tObject.hasOwn(b as Record<string, unknown>, key) &&\n\t\t\tObject.is((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n\t);\n};\n\n/**\n * Keeps a selection's IDENTITY while nothing in it moved.\n *\n * ```ts\n * const hold = createLankaShallowHold<{ title: string; status: string }>();\n *\n * // in any binding on the shelf\n * const view = useLankaVM(missionVM, (state) => hold({\n * \ttitle: state.title,\n * \tstatus: state.status,\n * }));\n * ```\n *\n * ## What it is for\n *\n * A selector narrows what a reader depends on, and a selector that BUILDS its\n * answer — `(state) => ({ a: state.a })`, `(state) => rows.filter(…)`, the shape\n * a consumer reaches for first — cannot say so. Every binding compares the new\n * selection with the last one by identity, and a fresh object is never identical\n * to anything, so the reader wakes for every change in the ViewModel including\n * the keys the selector exists to ignore. This is the comparison that makes the\n * selection mean something.\n *\n * A selector answering a PRIMITIVE never needed it, which is what makes the cost\n * quiet: the shape that is free and the shape that repaints on everything look\n * the same on the page.\n *\n * ## Why it is in core and not in a binding\n *\n * It was `useLankaShallow` in `@lankajs/react` and nowhere else, and that made it\n * a CAPABILITY one member of the shelf had and four did not. An idiom is a\n * spelling; this changes which notifications reach a reader, and it encodes a\n * policy — one level deep, own keys, `Object.is` — that five packages inventing\n * separately would answer five ways. `skills/parity/SKILL.md` 3c: a binding that\n * needs more than the port gives it has found something that belongs in core, for\n * everybody.\n *\n * `useLankaShallow` keeps working and is now React's spelling over this. It has\n * to exist there and cannot exist here: a React component re-runs the hook on\n * every render, so the holding has to survive a render while the SELECTOR stays\n * the current one — which is a `useRef`, and a ref is not something core can\n * have.\n *\n * ## Why it holds a value and not a selector\n *\n * `createLankaShallowSelector(selector)` was the other shape and it cannot serve\n * React: the wrapper would be rebuilt whenever the selector's identity moved, and\n * an inline arrow is a new function every render, so the holding would reset\n * before it ever held anything. Taking the VALUE puts the state in the only place\n * every framework can keep it, and the extra line at a call site is the price of\n * one name that works in all five rather than two that each work in some.\n *\n * ## One level, and it says so by failing\n *\n * Deeper would mean walking a state of unknown size on every read, which is the\n * cost a reader took a selector to avoid. A selection with a nested object wants\n * a selector that picks the leaves.\n */\nexport const createLankaShallowHold = <TValue>(): ((next: TValue) => TValue) => {\n\tlet held: TValue | undefined;\n\tlet holding = false;\n\n\treturn (next: TValue): TValue => {\n\t\t// `holding` rather than `held !== undefined`: a selection that legitimately\n\t\t// answers `undefined` is a selection, and a flag is the difference between\n\t\t// \"nothing yet\" and \"nothing, and that is the answer\".\n\t\tif (holding && isShallowEqual(held, next)) return held as TValue;\n\n\t\tholding = true;\n\t\theld = next;\n\n\t\treturn next;\n\t};\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAiBO,IAAe,sBAAf,MAAe,qBAGpB;AAAA;AAAA,EAES;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA,iBAA4B;AACrC,WAAO,CAAC;AAAA,EACT;AAAA;AAAA,EAGU,iBAA2B;AACpC,WAAO,CAAC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,SAAe;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,UAAgB;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBjB,mBAA2C;AACpD,UAAM,WAAW,qBAAoB;AACrC,UAAM,QAAgC,CAAC;AAEvC,QAAI,KAAK,WAAW,SAAS,OAAQ,OAAM,SAAS,MAAM,KAAK,OAAO;AACtE,QAAI,KAAK,YAAY,SAAS,QAAS,OAAM,UAAU,MAAM,KAAK,QAAQ;AAE1E,WAAO;AAAA,EACR;AACD;;;AC5EA,SAAS,mBAA6B;;;AC2B/B,IAAM,2BAA2B,CACvC,eACA,YACyB;AACzB,MAAI,CAAC,SAAS;AACb,WAAO;AAAA,MACN,SAAS;AAAA,MACT,YAAY,CAAC,QAAQ;AAAA,MACrB,gBAAgB,CAAC,aAAa;AAAA,MAC9B,QAAQ,MAAM;AAAA,IACf;AAAA,EACD;AAGA,QAAM,gBAAgB,oBAAI,IAAyB;AAEnD,QAAM,aAAa,oBAAI,IAAY;AACnC,MAAI,mBAAkC;AAEtC,SAAO;AAAA,IACN,SAAS;AAAA,IAET,WAAmB,KAAiC;AACnD,aAAO,MAAM;AACZ,cAAM,QAAQ,IAAI;AAClB,cAAM,aAAa;AACnB,YAAI,eAAe,KAAM,QAAO;AAEhC,eAAO,IAAI,MAAM,OAAiB;AAAA,UACjC,IAAI,QAAQ,MAAM,UAAU;AAC3B,gBAAI,OAAO,SAAS,UAAU;AAC7B,kBAAI,OAAO,cAAc,IAAI,UAAU;AACvC,kBAAI,CAAC,MAAM;AACV,uBAAO,oBAAI,IAAY;AACvB,8BAAc,IAAI,YAAY,IAAI;AAAA,cACnC;AACA,mBAAK,IAAI,IAAI;AAAA,YACd;AACA,mBAAO,QAAQ,IAAI,QAAQ,MAAM,QAAQ;AAAA,UAC1C;AAAA,QACD,CAAC;AAAA,MACF;AAAA,IACD;AAAA,IAEA,eAAwC,UAA8B;AACrE,YAAM,WAAW,EAAE,GAAG,SAAS;AAE/B,iBAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACrD,YAAI,OAAO,UAAU,WAAY;AAEjC,iBAAS,IAAI,IAAI,IAAI,SAAoB;AAGxC,gBAAM,WAAW;AACjB,6BAAmB;AACnB,cAAI;AACH,mBAAQ,MAAuC,GAAG,IAAI;AAAA,UACvD,UAAE;AACD,+BAAmB;AAAA,UACpB;AAAA,QACD;AAAA,MACD;AAEA,aAAO;AAAA,IACR;AAAA,IAEA,OAAO,aAAa,MAAM,MAAY;AACrC,iBAAW,CAAC,YAAY,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AACvD,YAAI,OAAO,GAAG,OAAO,KAAK,UAAU,CAAC,EAAG;AACxC,YAAI,YAAY,IAAI,UAAU,KAAK,WAAW,IAAI,UAAU,EAAG;AAK/D,cAAM,YAAY,CAAC,GAAG,WAAW,EAAE;AAAA,UAAK,CAAC,QACxC,cAAc,IAAI,GAAG,GAAG,IAAI,UAAU;AAAA,QACvC;AACA,YAAI,CAAC,UAAW;AAEhB,mBAAW,IAAI,UAAU;AACzB,gBAAQ;AAAA,UACP,WAAW,aAAa,UAAU,UAAU;AAAA,QAI7C;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;;;AC/CA,IAAM,eAAe,CACpB,aAEA,OAAO,aAAa,aAAa,SAAS,IAAK,YAAY,CAAC;AAY7D,IAAM,iBAAiB,CAAW,WACjC,OAAO,OAAO,aAAa,cAC1B,OAAO,aAAa,UAAa,OAAO,SAAS,SAAS,KAC3D,OAAO,WAAW,UAClB,OAAO,YAAY;AAeb,IAAM,4BAA4B,CACxC,WAC0B;AAC1B,QAAM,gBAAgB,oBAAI,IAAwB;AAClD,MAAI,gBAAgB;AAEpB,SAAO;AAAA,IACN,gBAAgB,eAAe,MAAM;AAAA,IACrC,IAAI,gBAAyB;AAC5B,aAAO;AAAA,IACR;AAAA,IAEA,qBAA2B;AAC1B,UAAI,cAAe;AAEnB,YAAM,UAAU,OAAO,QAAQ;AAE/B,iBAAW,WAAW,aAAa,OAAO,QAAQ,GAAG;AACpD,cAAM,MAAM,QAAQ,SAAS;AAC7B,YAAI,cAAc,IAAI,GAAG,EAAG;AAE5B,sBAAc;AAAA,UACb;AAAA,UACA,QAAQ,SAAS,UAAU,QAAQ,QAAQ,OAAO,GAAG;AAAA,YACpD,QAAQ,OAAO;AAAA,YACf,GAAG,QAAQ;AAAA,UACZ,CAAC;AAAA,QACF;AAAA,MACD;AAEA,aAAO,SAAS,OAAO;AACvB,sBAAgB;AAAA,IACjB;AAAA,IAEA,gBAAsB;AACrB,iBAAW,WAAW,cAAc,OAAO,EAAG,SAAQ;AACtD,oBAAc,MAAM;AACpB,sBAAgB;AAEhB,aAAO,UAAU,OAAO,QAAQ,CAAC;AAAA,IAClC;AAAA,EACD;AACD;;;AF/EO,IAAe,WAAf,cAKG,oBAAyC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS/B,mCAA4C;AAAA;AAAA,EAGrD;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA,UAAU,CAAQ,UAAiC,SAAuB;AACnF,aAAS,QAAQ,IAAI;AAAA,EACtB;AAAA;AAAA,EAGU,SAAgB;AACzB,WAAO,CAAC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASU,mBAER;AACD,WAAO,CAAC;AAAA,EACT;AAAA;AAAA,EAGU,YAAoE;AAC7E,WAAO,CAAC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYU,iBAIR;AACD,WAAO;AAAA,MACN,KAAK,KAAK;AAAA,MACV,KAAK,KAAK;AAAA,MACV,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf,SAAS,KAAK;AAAA,IACf;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWO,QAAsD;AAG5D,gBAAY,kBAAkB,mBAAmB,KAAK,IAAI;AAE1D,UAAM,YAAY;AAAA,MACjB,KAAK;AAAA,MACL,cAAc,EAAE,kBAAkB;AAAA,IACnC;AAMA,UAAM,QAAQ,KAAK,iBAAiB;AACpC,UAAM,EAAE,oBAAoB,eAAe,gBAAAA,gBAAe,IAAI,0BAA0B;AAAA,MACvF,MAAM,KAAK;AAAA,MACX,UAAU,KAAK,iBAAiB;AAAA,MAChC,SAAS,MAAM,KAAK,eAAe;AAAA,MACnC,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,IAChB,CAAC;AAED,UAAM,eAAiD,CAAC,KAAK,QAAQ;AACpE,WAAK,MAAM;AACX,WAAK,MAAM,UAAU,WAAW,GAAG;AACnC,WAAK,WAAW,KAAK,eAAe;AACpC,WAAK,WAAW,KAAK,eAAe;AAEpC,YAAM,UAAU,UAAU,eAAe,KAAK,cAAc,CAAC;AAE7D,aAAO;AAAA,QACN,GAAG,KAAK,OAAO;AAAA,QACf,GAAG;AAAA,QACH;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAEA,UAAM,kBAAkB,KAAK,UAAU,EAAE;AAAA,MACxC,CAAC,SAAS,YAAY,QAAQ,OAAO;AAAA,MACrC;AAAA,IACD;AAEA,UAAM,QAAQ,YAAwB,EAAE,eAAe;AAcvD,UAAM,YAAkC,OAAO,iBAAiB,OAAO;AAAA,MACtE,MAAM,EAAE,OAAO,KAAK,MAAM,YAAY,OAAO,cAAc,KAAK;AAAA,MAChE,iBAAiB;AAAA,QAChB,OAAO,KAAK;AAAA,QACZ,YAAY;AAAA,QACZ,cAAc;AAAA,MACf;AAAA,IACD,CAAC;AAED,2BAAuB,SAAS,WAAW,SAAS;AAEpD,QAAIA,iBAAgB;AACnB,6BAAuB,kBAAkB,MAAM,SAAS,GAAG,KAAK,IAAI;AAAA,IACrE;AAEA,gBAAY,kBAAkB,oBAAoB,KAAK,IAAI;AAE3D,WAAO;AAAA,EACR;AACD;;;AGpNO,IAAM,yBAAyB,CACrC,aACU;AACV,MAAI,OAAO,aAAa,WAAY,QAAO,SAAS;AACpD,SAAO,YAAa,CAAC;AACtB;;;ACuCO,SAAS,cAMf,QAC+C;AAAA,EAM/C,MAAM,qBAAqB,SAA8C;AAAA,IACrD,OAAO,OAAO;AAAA,IAEL,mCAC3B,OAAO,oCAAoC;AAAA,IAEzB,SAAgB;AAClC,aAAO,OAAO,UAAW,CAAC;AAAA,IAC3B;AAAA,IAEmB,mBAAgE;AAClF,aAAO,OAAO,oBAAoB,CAAC;AAAA,IACpC;AAAA,IAEmB,YAAkD;AACpE,aAAO,OAAO,aAAa,CAAC;AAAA,IAC7B;AAAA,IAEmB,iBAA4B;AAC9C,aAAO,uBAAuB,OAAO,QAAQ;AAAA,IAC9C;AAAA,IAEmB,iBAA2B;AAC7C,aAAO,uBAAuB,OAAO,QAAQ;AAAA,IAC9C;AAAA,IAEU,gBAAyB;AAClC,aAAO,OAAO,cAAc,KAAK,eAAe,CAAC;AAAA,IAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,cAAc;AACpB,YAAM;AACN,YAAM,EAAE,QAAQ,QAAQ,IAAI;AAE5B,UAAI,OAAQ,MAAK,SAAS,MAAM,OAAO,KAAK,eAAe,CAAC;AAC5D,UAAI,QAAS,MAAK,UAAU,MAAM,QAAQ,KAAK,eAAe,CAAC;AAAA,IAChE;AAAA,EACD;AAEA,SAAO,IAAI,aAAa,EAAE,MAAM;AACjC;;;ACpEA,IAAM,WAAW,CAChB,WAC4B;AAC5B,MAAI,QAAuB;AAE3B,SAAO;AAAA,IACN,KAAK,MAAM;AACV,UAAI,CAAC,OAAO;AACX,oBAAY;AAAA,UACX,kBAAkB,OAAO,IAAI;AAAA,UAC7B,OAAO;AAAA,QACR;AACA,gBAAQ,OAAO,OAAO;AAAA,MACvB;AACA,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAcA,SAAS,MAAM;AACd,UAAI,CAAC,MAAO;AACZ,YAAM,SAAS,EAAE,cAAc;AAC/B,cAAQ;AAAA,IACT;AAAA,EACD;AACD;AAoCA,IAAM,kBAAgD,oBAAI,IAAI,CAAC,QAAQ,SAAS,SAAS,CAAC;AAE1F,IAAM,qBACL,CAAS,MAA8B,WACvC,CAAC,SAAiB,aAAuC;AACxD,MAAI,aAAa,UAAW,QAAO,KAAK;AACxC,MAAI,gBAAgB,IAAI,QAAQ,EAAG,QAAO;AAO1C,MAAI,aAAa,OAAQ,QAAO,OAAO;AACvC,MAAI,aAAa,kBAAmB,QAAO,OAAO;AAElD,SAAO,IAAI,SAAoB;AAC9B,UAAM,QAAQ,KAAK,IAAI;AACvB,UAAM,SAAU,MAA2C,QAAQ;AAOnE,WAAO,OAAO,WAAW,aACrB,OAA2C,MAAM,OAAO,IAAI,IAC7D;AAAA,EACJ;AACD;AAyBM,IAAM,yBAAyB,CACrC,WAC+B;AAC/B,QAAM,OAAO,SAAS,MAAM;AAK5B,SAAO,IAAI;AAAA,IACV,CAAC;AAAA,IACD,EAAE,KAAK,mBAAmB,MAAM,MAAM,EAAE;AAAA,EACzC;AACD;;;AChIO,SAAS,kBAKd,QAA6D;AAC9D,SAAO,uBAAuB;AAAA,IAC7B,MAAM,OAAO;AAAA,IACb,MAAM;AAAA,IACN,iBAAiB,OAAO,oCAAoC;AAAA,IAC5D,QAAQ,MAAM,cAAc,MAAM;AAAA,EACnC,CAAC;AACF;;;AChBO,IAAe,oBAAf,cAIG,oBAAyC;AAAA;AAAA,EAKxC;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA,UAAU,CAAQ,UAAiC,SAAuB;AACnF,aAAS,QAAQ,IAAI;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,mBAER;AACD,WAAO,CAAC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWU,iBAIR;AACD,WAAO;AAAA,MACN,KAAK,KAAK;AAAA,MACV,KAAK,KAAK;AAAA,MACV,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf,SAAS,KAAK;AAAA,IACf;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,QAAsD;AAG5D,gBAAY,kBAAkB,qBAAqB,KAAK,IAAI;AAE5D,QAAI,QAAQ,CAAC;AAEb,SAAK,MAAM,MAAM;AACjB,SAAK,MAAM,CAAC,SAAS,YAAY;AAChC,YAAM,OACL,OAAO,YAAY,aACf,QAAgD,KAAK,IACtD;AAEJ,cAAQ,UAAW,OAAsB,EAAE,GAAG,OAAO,GAAG,KAAK;AAAA,IAC9D;AAEA,SAAK,WAAW,KAAK,eAAe;AACpC,SAAK,WAAW,KAAK,eAAe;AAEpC,UAAM,UAAU,KAAK,cAAc;AAEnC,UAAM,QAAQ,KAAK,iBAAiB;AACpC,UAAM,EAAE,oBAAoB,eAAe,gBAAAC,gBAAe,IAAI,0BAA0B;AAAA,MACvF,MAAM,KAAK;AAAA,MACX,UAAU,KAAK,iBAAiB;AAAA,MAChC,SAAS,MAAM,KAAK,eAAe;AAAA,MACnC,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,IAChB,CAAC;AAED,YAAQ,EAAE,GAAG,OAAO,GAAG,SAAS,oBAAoB,cAAc;AAElE,QAAIA,iBAAgB;AACnB,6BAAuB,kBAAkB,OAAO,KAAK,IAAI;AAAA,IAC1D;AAeA,UAAM,qBAAmD;AAAA,MACxD,MAAM,KAAK;AAAA,MACX,UAAU,MAAM;AAAA,MAChB,WAAW,MAAM,MAAM;AAAA,MACvB,iBAAiB;AAAA,IAClB;AAEA,gBAAY,kBAAkB,sBAAsB,KAAK,IAAI;AAE7D,WAAO;AAAA,EACR;AACD;;;ACtEO,SAAS,uBAId,QAA+F;AAAA,EAMhG,MAAM,8BAA8B,kBAAgD;AAAA,IAChE,OAAO,OAAO;AAAA,IAEd,mBAAgE;AAClF,aAAO,OAAO,oBAAoB,CAAC;AAAA,IACpC;AAAA,IAEmB,iBAA4B;AAC9C,aAAO,uBAAuB,OAAO,QAAQ;AAAA,IAC9C;AAAA,IAEmB,iBAA2B;AAC7C,aAAO,uBAAuB,OAAO,QAAQ;AAAA,IAC9C;AAAA,IAEU,gBAAyB;AAClC,aAAO,OAAO,cAAc,KAAK,eAAe,CAAC;AAAA,IAClD;AAAA;AAAA;AAAA,IAIO,cAAc;AACpB,YAAM;AACN,YAAM,EAAE,QAAQ,QAAQ,IAAI;AAE5B,UAAI,OAAQ,MAAK,SAAS,MAAM,OAAO,KAAK,eAAe,CAAC;AAC5D,UAAI,QAAS,MAAK,UAAU,MAAM,QAAQ,KAAK,eAAe,CAAC;AAAA,IAChE;AAAA,EACD;AAEA,SAAO,IAAI,sBAAsB,EAAE,MAAM;AAC1C;;;AClEO,SAAS,2BAId,QAAmE;AACpE,SAAO,uBAAuB;AAAA,IAC7B,MAAM,OAAO;AAAA,IACb,MAAM;AAAA;AAAA;AAAA,IAGN,iBAAiB;AAAA,IACjB,QAAQ,MAAM,uBAAuB,MAAM;AAAA,EAC5C,CAAC;AACF;;;ACpCO,SAAS,yBAMd,QAAoF;AACrF,cAAY,kBAAkB,qBAAqB,OAAO,IAAI;AAU9D,MAAI,UAAU,CAAC;AAEf,QAAM,WAAW,uBAAuB,OAAO,QAAQ;AACvD,QAAM,WAAW,uBAAuB,OAAO,QAAQ;AAEvD,MAAI,oBAAuC;AAC3C,MAAI,mBAAsC;AAE1C,QAAM,iBAAiB,CAAC,eAAuC;AAC9D,QAAI,sBAAsB,cAAc,kBAAkB;AACzD,aAAO;AAAA,IACR;AAEA,wBAAoB;AACpB,uBAAmB;AAAA,MAClB,GAAG;AAAA,MACH,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAEA,QAAM,eAAe,MAAkB,eAAe,OAAO,MAAM,SAAS,CAAC;AAE7E,QAAM,MAAsF;AAAA,IAC3F,KAAK,CAAC,SAAS,YAAY,OAAO,MAAM,SAAS,SAAS,OAAO;AAAA,IACjE,UAAU,MAAM,OAAO,MAAM,SAAS;AAAA,IACtC,KAAK,MAAM,aAAa;AAAA,IACxB,OAAO,OAAO;AAAA,IACd;AAAA,IACA;AAAA,IACA,SAAS,CAAC,UAAU,SAAS,SAAS,QAAQ,IAAI;AAAA,EACnD;AAEA,QAAM,EAAE,oBAAoB,eAAe,gBAAAC,gBAAe,IAAI,0BAA0B;AAAA,IACvF,MAAM,OAAO;AAAA,IACb,UAAU,OAAO;AAAA,IACjB,SAAS,MAAM;AAAA,IACf,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,EACjB,CAAC;AAED,YAAU,OAAO,cAAc,GAAG;AAClC,sBAAoB;AACpB,qBAAmB;AACnB,QAAM,0BAA0B,OAAO,oCAAoC;AAc3E,QAAM,uBAAiE;AAAA,IACtE,MAAM,OAAO;AAAA,IACb,UAAU;AAAA,IACV,eAAe,MAAM,OAAO,MAAM,SAAS;AAAA,IAC3C,iBAAiB;AAAA,IACjB,WAAW,CAAC,aACX,OAAO,MAAM,UAAU,CAAC,YAAY,mBAAmB;AACtD,eAAS,eAAe,UAAU,GAAG,eAAe,cAAc,CAAC;AAAA,IACpE,CAAC;AAAA,EACH;AAEA,MAAIA,iBAAgB;AACnB,UAAM,oBAAsC;AAAA,MAC3C;AAAA,MACA;AAAA,IACD;AACA,2BAAuB,kBAAkB,mBAAmB,OAAO,IAAI;AAAA,EACxE;AAEA,cAAY,kBAAkB,sBAAsB,OAAO,IAAI;AAE/D,SAAO;AACR;;;ACxFO,SAAS,6BAMd,QAAoF;AACrF,SAAO,uBAAuB;AAAA,IAC7B,MAAM,OAAO;AAAA,IACb,MAAM;AAAA,IACN,iBAAiB,OAAO,oCAAoC;AAAA,IAC5D,QAAQ,MAAM,yBAAyB,MAAM;AAAA,EAC9C,CAAC;AACF;;;ACtCO,IAAe,sBAAf,cAMG,oBAAyC;AAAA;AAAA,EAK/B;AAAA;AAAA,EAGT;AAAA;AAAA,EASA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA,EAQH,YAAY,OAAc;AAChC,UAAM;AACN,SAAK,QAAQ;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMmB,mCAA4C;AAAA;AAAA,EAGrD,mBAER;AACD,WAAO,CAAC;AAAA,EACT;AAAA;AAAA,EAMO,QAAsD;AAC5D,UAAM,QAAQ,KAAK,iBAAiB;AAEpC,WAAO,yBAA0E;AAAA,MAChF,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,kCAAkC,KAAK;AAAA,MACvC,UAAU,MAAM,KAAK,eAAe;AAAA,MACpC,UAAU,MAAM,KAAK,eAAe;AAAA,MACpC,kBAAkB,KAAK,iBAAiB;AAAA,MACxC,eAAe,CAAC,YAAY;AAI3B,aAAK,MAAM,QAAQ;AACnB,aAAK,WAAW,QAAQ;AACxB,aAAK,MAAM,QAAQ;AACnB,aAAK,WAAW,QAAQ;AACxB,aAAK,WAAW,QAAQ;AACxB,aAAK,UAAU,QAAQ;AAEvB,eAAO,KAAK,cAAc;AAAA,MAC3B;AAAA,MACA,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,IAChB,CAAC;AAAA,EACF;AACD;;;AClIA,SAAS,eAAAC,oBAA6B;AAQ/B,IAAe,oBAAf,MAAwD;AAAA,EAC7C;AAAA,EACA;AAAA,EAEP,YAAY,oBAAkC;AACvD,SAAK,qBAAqB;AAC1B,SAAK,MAAMA,aAAoB,EAAE,MAAM,KAAK,mBAAmB,CAAC;AAAA,EACjE;AAAA,EAEO,SAA2B;AACjC,WAAO,KAAK;AAAA,EACb;AAAA,EAEO,WAAmB;AACzB,WAAO,KAAK,IAAI,SAAS;AAAA,EAC1B;AAAA,EAEO,SACN,SACA,SACO;AACP,UAAM,WAAW,OAAO,YAAY,aAAa,QAAQ,KAAK,IAAI,SAAS,CAAC,IAAI;AAEhF,QAAI,SAAS;AACZ,WAAK,IAAI,SAAS,UAAoB,IAAI;AAC1C;AAAA,IACD;AAEA,SAAK,IAAI,SAAS,QAAQ;AAAA,EAC3B;AAAA,EAEO,UAAU,UAAsD;AACtE,WAAO,KAAK,IAAI,UAAU,QAAQ;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWO,QAAc;AACpB,SAAK,IAAI,SAAS,KAAK,mBAAmB,GAAG,IAAI;AAAA,EAClD;AACD;;;AC1CO,IAAM,yBAAyB,CACrC,uBAC+B;AAAA,EAC/B,MAAM,8BAA8B,kBAA0B;AAAA,IACtD,cAAc;AACpB,YAAM,kBAAkB;AAAA,IACzB;AAAA,EACD;AAEA,SAAO,IAAI,sBAAsB;AAClC;;;ACXA,IAAM,iBAAiB,CAAC,GAAY,MAAwB;AAC3D,MAAI,OAAO,GAAG,GAAG,CAAC,EAAG,QAAO;AAC5B,MAAI,OAAO,MAAM,YAAY,MAAM,QAAQ,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;AAEvF,QAAM,OAAO,OAAO,KAAK,CAAC;AAC1B,QAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,MAAI,KAAK,WAAW,MAAM,OAAQ,QAAO;AAEzC,SAAO,KAAK;AAAA,IACX,CAAC,QACA,OAAO,OAAO,GAA8B,GAAG,KAC/C,OAAO,GAAI,EAA8B,GAAG,GAAI,EAA8B,GAAG,CAAC;AAAA,EACpF;AACD;AA4DO,IAAM,yBAAyB,MAA0C;AAC/E,MAAI;AACJ,MAAI,UAAU;AAEd,SAAO,CAAC,SAAyB;AAIhC,QAAI,WAAW,eAAe,MAAM,IAAI,EAAG,QAAO;AAElD,cAAU;AACV,WAAO;AAEP,WAAO;AAAA,EACR;AACD;","names":["needsBootstrap","needsBootstrap","needsBootstrap","createStore"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lanka",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "core: Fourteen subsystems, one peer dependency, six extension points.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"@standard-schema/spec": "^1.0.0"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
|
-
"@lankajs/tool-testing": "^2.
|
|
96
|
+
"@lankajs/tool-testing": "^2.1.0"
|
|
97
97
|
},
|
|
98
98
|
"peerDependencies": {
|
|
99
99
|
"zustand": "^5.0.10"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!-- Generated from core/GUIDE.md by scripts/skills.mjs. Edit the guide. -->
|
|
2
2
|
|
|
3
|
-
> **`lanka@2.0
|
|
3
|
+
> **`lanka@2.1.0`** — this document describes that version.
|
|
4
4
|
>
|
|
5
5
|
> Install: `npm install lanka zustand` (the peers are not optional; only npm adds a missing one for you).
|
|
6
6
|
>
|