lanka 1.3.0 → 2.0.1

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.
Files changed (91) hide show
  1. package/README.md +1 -1
  2. package/dist/{LankaSharedStoreLocator-zS2kLu-S.d.ts → ALankaLocator-BUCCz0Q5.d.ts} +1 -47
  3. package/dist/ILankaReadableVM-BoPzHEPV.d.ts +72 -0
  4. package/dist/{ILankaScenarioVM-DpKFL3iE.d.ts → ILankaScenario-DQd9ZfUw.d.ts} +1 -30
  5. package/dist/{ILankaScenarioMetadata-Dj4GCqmX.d.ts → ILankaScenarioMetadata-BsDp0Bzm.d.ts} +1 -1
  6. package/dist/ILankaScenarioVM-DKwjbIRq.d.ts +30 -0
  7. package/dist/{LankaScenarioLocator-CAENLhDO.d.ts → LankaScenarioVMRegistry-DySAOaj2.d.ts} +4 -37
  8. package/dist/_extend/index.d.ts +235 -7
  9. package/dist/_extend/index.js +152 -12
  10. package/dist/_extend/index.js.map +1 -1
  11. package/dist/_internal/index.d.ts +13 -8
  12. package/dist/_internal/index.js +9 -3
  13. package/dist/_internal/index.js.map +1 -1
  14. package/dist/{activeRuntime-BYd2D6Lx.d.ts → activeRuntime-ByucLPhj.d.ts} +55 -4
  15. package/dist/bootstrap/index.d.ts +15 -10
  16. package/dist/bootstrap/index.js +14 -9
  17. package/dist/chunk-24MUFFHF.js +43 -0
  18. package/dist/chunk-24MUFFHF.js.map +1 -0
  19. package/dist/chunk-3R2NO47A.js +35 -0
  20. package/dist/chunk-3R2NO47A.js.map +1 -0
  21. package/dist/{chunk-5MAQVBI2.js → chunk-42CWYVZJ.js} +44 -61
  22. package/dist/chunk-42CWYVZJ.js.map +1 -0
  23. package/dist/{chunk-D27MREPB.js → chunk-7DQUF2QR.js} +2 -2
  24. package/dist/{chunk-UJEC7H6K.js → chunk-BHQ2SQ7P.js} +2 -2
  25. package/dist/chunk-BMF4TM2Z.js +22 -0
  26. package/dist/chunk-BMF4TM2Z.js.map +1 -0
  27. package/dist/{chunk-C2HP7CRD.js → chunk-FII3PW2G.js} +2 -2
  28. package/dist/{chunk-DTO27QFR.js → chunk-H5TIUKRK.js} +2 -2
  29. package/dist/chunk-JZJ6GXX3.js +12 -0
  30. package/dist/chunk-JZJ6GXX3.js.map +1 -0
  31. package/dist/{chunk-B7EYIAW7.js → chunk-MKKMTLOY.js} +47 -13
  32. package/dist/chunk-MKKMTLOY.js.map +1 -0
  33. package/dist/{chunk-UE2C76OR.js → chunk-N3275IPH.js} +2 -69
  34. package/dist/chunk-N3275IPH.js.map +1 -0
  35. package/dist/{chunk-O5ROO7QF.js → chunk-NWEHOMTS.js} +3 -3
  36. package/dist/{chunk-NDCJBO47.js → chunk-O5EUTNR6.js} +2 -2
  37. package/dist/{chunk-RKYKK6MN.js → chunk-RUMUFHSL.js} +2 -2
  38. package/dist/chunk-SBITVBF7.js +49 -0
  39. package/dist/chunk-SBITVBF7.js.map +1 -0
  40. package/dist/{chunk-UBGXDTXC.js → chunk-SO7D5E7O.js} +22 -16
  41. package/dist/chunk-SO7D5E7O.js.map +1 -0
  42. package/dist/chunk-UDP6IXDS.js +17 -0
  43. package/dist/chunk-UDP6IXDS.js.map +1 -0
  44. package/dist/{chunk-BGVDPDX4.js → chunk-XGMXT4XZ.js} +20 -3
  45. package/dist/chunk-XGMXT4XZ.js.map +1 -0
  46. package/dist/config/index.js +3 -3
  47. package/dist/{createLanka-NrlvN-WQ.d.ts → createLanka-DvUGu9Hl.d.ts} +2 -2
  48. package/dist/{createLankaScope-BiFxNQgl.d.ts → createLankaScope-CVoV0EwO.d.ts} +1 -1
  49. package/dist/errors/index.js +3 -3
  50. package/dist/gateway/index.js +6 -6
  51. package/dist/index.d.ts +14 -9
  52. package/dist/index.js +15 -10
  53. package/dist/locator/gateway/lanka-gateway-locator/LankaGatewayLocator.d.ts +18 -0
  54. package/dist/locator/gateway/lanka-gateway-locator/LankaGatewayLocator.js +9 -0
  55. package/dist/locator/gateway/lanka-gateway-locator/LankaGatewayLocator.js.map +1 -0
  56. package/dist/locator/index.d.ts +4 -2
  57. package/dist/locator/index.js +6 -3
  58. package/dist/locator/index.js.map +1 -1
  59. package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.d.ts +60 -0
  60. package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.js +12 -0
  61. package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.js.map +1 -0
  62. package/dist/locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.d.ts +22 -0
  63. package/dist/locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.js +9 -0
  64. package/dist/locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.js.map +1 -0
  65. package/dist/locator/singleton/lanka-singleton-locator/LankaSingletonLocator.d.ts +30 -0
  66. package/dist/locator/singleton/lanka-singleton-locator/LankaSingletonLocator.js +9 -0
  67. package/dist/locator/singleton/lanka-singleton-locator/LankaSingletonLocator.js.map +1 -0
  68. package/dist/logger/index.js +3 -3
  69. package/dist/mock/index.js +3 -3
  70. package/dist/scenario/index.d.ts +29 -3
  71. package/dist/scenario/index.js +5 -5
  72. package/dist/stream/index.d.ts +17 -12
  73. package/dist/viewmodel/index.d.ts +139 -28
  74. package/dist/viewmodel/index.js +95 -126
  75. package/dist/viewmodel/index.js.map +1 -1
  76. package/package.json +3 -4
  77. package/skills/lanka-core/SKILL.md +18 -16
  78. package/skills/lanka-core/reference.md +135 -58
  79. package/skills/lanka-packages/SKILL.md +94 -22
  80. package/dist/chunk-5MAQVBI2.js.map +0 -1
  81. package/dist/chunk-B7EYIAW7.js.map +0 -1
  82. package/dist/chunk-BGVDPDX4.js.map +0 -1
  83. package/dist/chunk-UBGXDTXC.js.map +0 -1
  84. package/dist/chunk-UE2C76OR.js.map +0 -1
  85. /package/dist/{chunk-D27MREPB.js.map → chunk-7DQUF2QR.js.map} +0 -0
  86. /package/dist/{chunk-UJEC7H6K.js.map → chunk-BHQ2SQ7P.js.map} +0 -0
  87. /package/dist/{chunk-C2HP7CRD.js.map → chunk-FII3PW2G.js.map} +0 -0
  88. /package/dist/{chunk-DTO27QFR.js.map → chunk-H5TIUKRK.js.map} +0 -0
  89. /package/dist/{chunk-O5ROO7QF.js.map → chunk-NWEHOMTS.js.map} +0 -0
  90. /package/dist/{chunk-NDCJBO47.js.map → chunk-O5EUTNR6.js.map} +0 -0
  91. /package/dist/{chunk-RKYKK6MN.js.map → chunk-RUMUFHSL.js.map} +0 -0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **◆ core** · Core
4
4
 
5
- > Fourteen subsystems, two peer dependencies, five extension points.
5
+ > Fourteen subsystems, one peer dependency, six extension points.
6
6
 
7
7
  One, unscoped. Everything depends on it; it depends on nothing.
8
8
 
@@ -1,5 +1,3 @@
1
- import { A as ALankaSharedStore } from './ALankaSharedStore-B7uepuuk.js';
2
-
3
1
  /**
4
2
  * Resolves instances by camelCase property name, mapping it to the PascalCase
5
3
  * class name.
@@ -100,48 +98,4 @@ declare abstract class ALankaLocator<TInstance> implements ILankaLocator<TInstan
100
98
  isRegistered(className: string): boolean;
101
99
  }
102
100
 
103
- /**
104
- * LankaSingletonLocator configuration.
105
- */
106
- interface ILankaSingletonLocatorConfig {
107
- /** The module holding singleton classes — the consumer's barrel. */
108
- singletonIndexModule?: Record<string, unknown>;
109
- }
110
- /**
111
- * Resolves singletons by property name (camelCase) or class name (PascalCase),
112
- * constructing them on first use and caching them.
113
- *
114
- * A class arrives either from the consumer's barrel or registered by hand — the
115
- * latter for tests and for objects that arrive already built.
116
- */
117
- declare class LankaSingletonLocator extends ALankaLocator<unknown> {
118
- private readonly singletonIndexModule?;
119
- constructor(config?: ILankaSingletonLocatorConfig);
120
- /**
121
- * Creates a NEW service object, bypassing the locator's cache.
122
- *
123
- * For scopes: they take the class from here and set the lifetime themselves.
124
- * The root cache is untouched — otherwise the first resolution inside a scope
125
- * would replace the application's root object.
126
- */
127
- createScopedInstance(className: string, propertyName: string): unknown;
128
- }
129
-
130
- type TSharedStoreState = object;
131
- /**
132
- * LankaSharedStoreLocator configuration.
133
- */
134
- interface ILankaSharedStoreLocatorConfig {
135
- /** The module holding shared store classes — the consumer's barrel. */
136
- sharedStoreIndexModule?: Record<string, unknown>;
137
- }
138
- /**
139
- * Resolves shared stores by property name (camelCase) or class name
140
- * (PascalCase), constructing them on first use and caching them.
141
- */
142
- declare class LankaSharedStoreLocator extends ALankaLocator<ALankaSharedStore<TSharedStoreState>> {
143
- private readonly sharedStoreIndexModule?;
144
- constructor(config?: ILankaSharedStoreLocatorConfig);
145
- }
146
-
147
- export { ALankaLocator as A, type ILankaLocatorConfig as I, LankaSharedStoreLocator as L, type ILankaLocator as a, LankaSingletonLocator as b, type ILankaSharedStoreLocatorConfig as c, type ILankaSingletonLocatorConfig as d };
101
+ export { ALankaLocator as A, type ILankaLocatorConfig as I, type ILankaLocator as a };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * What a view binding needs of a ViewModel, and nothing else.
3
+ *
4
+ * The reading half: a name, the current state, a way to hear about the next one,
5
+ * and whether this ViewModel wants access tracking. Writing is deliberately
6
+ * absent — a binding renders, and a binding that could write would be a second
7
+ * place actions live.
8
+ *
9
+ * ## Why this is the port and not `StoreApi`
10
+ *
11
+ * All three ViewModel shapes answer it, and they are built out of different
12
+ * things: the stateful one over a store of its own, the shared-store one over a
13
+ * slice of somebody else's, the stateless one over a plain object with no store
14
+ * at all. A binding written against a store type would work for one of the three.
15
+ *
16
+ * It is also what keeps zustand inside core. `@lankajs/react`, `@lankajs/vue`
17
+ * and every later member of the shelf see this interface and never `StoreApi`,
18
+ * so the store underneath can change without a binding hearing about it — and if
19
+ * a binding ever needs something zustand-shaped, the abstraction leaked and the
20
+ * fix belongs here rather than there. Canon: `skills/hosts/SKILL.md` §1a.
21
+ *
22
+ * @see ILankaVM for the half that also writes, which `hydrateLankaVM` needs.
23
+ */
24
+ interface ILankaReadableVM<TState extends object> {
25
+ /**
26
+ * Names the ViewModel in logs, the scenario registry and the blind-spot
27
+ * warning — and in whatever a binding's devtools shows.
28
+ */
29
+ readonly name: string;
30
+ /**
31
+ * The current state, in full — and the SAME object until something changes.
32
+ *
33
+ * The identity is part of the contract, not an accident of the six factories
34
+ * that keep it. Every binding on the shelf holds something against it: the
35
+ * access tracker caches its recording proxy by the identity of the state it
36
+ * wrapped, and `@lankajs/react` holds a selector's answer the same way. An
37
+ * implementation that composes a fresh object on every call hands React a
38
+ * snapshot that never agrees with itself between the render read and the
39
+ * post-commit one, and the component renders until React stops it — a crash
40
+ * whose stack names React and not the ViewModel.
41
+ *
42
+ * It costs an implementer nothing to keep: answer a held object, and build a
43
+ * new one when you write. A ViewModel that composes its state from somewhere
44
+ * else memoises the composition against what it composed from —
45
+ * `createSharedStoreLankaVM` is the worked example.
46
+ */
47
+ getState(): TState;
48
+ /**
49
+ * Hears about every change, with both states in FULL shape.
50
+ *
51
+ * A shared-store ViewModel composes its full state from a store slice, and
52
+ * does so HERE rather than in the binding: a binding compares two states, it
53
+ * does not know how one is assembled.
54
+ *
55
+ * A stateless ViewModel has nothing that changes, so its implementation
56
+ * returns an unsubscribe and never calls the listener. That is not a gap —
57
+ * it is what makes the same binding work for all three without asking which
58
+ * it was handed.
59
+ */
60
+ subscribe(listener: (next: TState, prev: TState) => void): () => void;
61
+ /**
62
+ * Whether this ViewModel wants a reader to track which keys it read.
63
+ *
64
+ * `enableAccessTrackingOptimization`, as a binding sees it. A binding that
65
+ * ignores it is not merely slower: the blind spot documented on `ALankaVM`
66
+ * becomes a frozen screen, because the ViewModel turned tracking OFF for
67
+ * exactly the reason that it derives what the view shows.
68
+ */
69
+ readonly isAccessTracked: boolean;
70
+ }
71
+
72
+ export type { ILankaReadableVM as I };
@@ -342,33 +342,4 @@ interface ILankaScenario<TData = void> {
342
342
  cleanup?(): void;
343
343
  }
344
344
 
345
- /**
346
- * A ViewModel that participates in scenarios.
347
- *
348
- * Not every ViewModel does: one with no `scenarioHandlers` and neither
349
- * lifecycle hook has nothing for bootstrap to do and is never registered. This
350
- * is the pair of methods `LankaScenarioBootstrap` calls on those that are, which
351
- * is why the name says scenario rather than ViewModel — `onInit` and `onReset`
352
- * ride on the same two calls.
353
- *
354
- * Both are idempotent by contract: bootstrap may run after a ViewModel has
355
- * already initialised itself, and a reset may arrive for one that never did.
356
- */
357
- interface ILankaScenarioVM {
358
- /**
359
- * Subscribes this ViewModel's scenario handlers.
360
- *
361
- * Called once the scenario registry is populated — a ViewModel declared at
362
- * module level exists before any scenario does.
363
- */
364
- initializeScenario(): void;
365
- /**
366
- * Releases every subscription, leaving the ViewModel ready to initialise again.
367
- *
368
- * Called when the framework instance is disposed, and by `resetLanka()`
369
- * between tests.
370
- */
371
- resetScenario(): void;
372
- }
373
-
374
- export { type ILankaScenarioVM as I, LankaEventBusInstance as L, type TLankaReplayRequest as T, type ILankaScenario as a, type ILankaEventMetadata as b, type TLankaEventBusMiddleware as c, type TLankaEventBusObserver as d, type ILankaEventLog as e, type ILankaEventBusOutcome as f, type TLankaEventBusDecision as g };
345
+ export { type ILankaScenario as I, LankaEventBusInstance as L, type TLankaReplayRequest as T, type ILankaEventMetadata as a, type TLankaEventBusMiddleware as b, type TLankaEventBusObserver as c, type ILankaEventLog as d, type ILankaEventBusOutcome as e, type TLankaEventBusDecision as f };
@@ -1,4 +1,4 @@
1
- import { a as ILankaScenario } from './ILankaScenarioVM-DpKFL3iE.js';
1
+ import { I as ILankaScenario } from './ILankaScenario-DQd9ZfUw.js';
2
2
 
3
3
  /**
4
4
  * Metadata about a registered scenario
@@ -0,0 +1,30 @@
1
+ /**
2
+ * A ViewModel that participates in scenarios.
3
+ *
4
+ * Not every ViewModel does: one with no `scenarioHandlers` and neither
5
+ * lifecycle hook has nothing for bootstrap to do and is never registered. This
6
+ * is the pair of methods `LankaScenarioBootstrap` calls on those that are, which
7
+ * is why the name says scenario rather than ViewModel — `onInit` and `onReset`
8
+ * ride on the same two calls.
9
+ *
10
+ * Both are idempotent by contract: bootstrap may run after a ViewModel has
11
+ * already initialised itself, and a reset may arrive for one that never did.
12
+ */
13
+ interface ILankaScenarioVM {
14
+ /**
15
+ * Subscribes this ViewModel's scenario handlers.
16
+ *
17
+ * Called once the scenario registry is populated — a ViewModel declared at
18
+ * module level exists before any scenario does.
19
+ */
20
+ initializeScenario(): void;
21
+ /**
22
+ * Releases every subscription, leaving the ViewModel ready to initialise again.
23
+ *
24
+ * Called when the framework instance is disposed, and by `resetLanka()`
25
+ * between tests.
26
+ */
27
+ resetScenario(): void;
28
+ }
29
+
30
+ export type { ILankaScenarioVM as I };
@@ -1,7 +1,6 @@
1
- import { a as ILankaScenario, I as ILankaScenarioVM } from './ILankaScenarioVM-DpKFL3iE.js';
2
- import { I as ILankaScenarioMetadata } from './ILankaScenarioMetadata-Dj4GCqmX.js';
3
- import { A as ALankaGateway } from './ALankaGateway-BrVPaZN5.js';
4
- import { A as ALankaLocator } from './LankaSharedStoreLocator-zS2kLu-S.js';
1
+ import { I as ILankaScenario } from './ILankaScenario-DQd9ZfUw.js';
2
+ import { I as ILankaScenarioVM } from './ILankaScenarioVM-DKwjbIRq.js';
3
+ import { I as ILankaScenarioMetadata } from './ILankaScenarioMetadata-BsDp0Bzm.js';
5
4
 
6
5
  /**
7
6
  * The scenario registry: both those that registered themselves and those
@@ -114,36 +113,4 @@ declare class LankaScenarioVMRegistry {
114
113
  resetAll(): void;
115
114
  }
116
115
 
117
- /**
118
- * Resolves a gateway by property name (camelCase) or class name (PascalCase),
119
- * constructing it lazily and caching the instance.
120
- *
121
- * Adding a gateway takes one export line in `@lanka_di/Gateways`: types are
122
- * inferred and nothing is registered.
123
- */
124
- declare class LankaGatewayLocator extends ALankaLocator<ALankaGateway<unknown>> {
125
- constructor();
126
- }
127
-
128
- /**
129
- * Resolves scenarios by property name (camelCase) or scenario name
130
- * (PascalCase).
131
- *
132
- * Looks in the registry, then in the self-registration pool, and failing both
133
- * constructs the scenario from the consumer's barrel on first use.
134
- */
135
- declare class LankaScenarioLocator extends ALankaLocator<ILankaScenario<unknown>> {
136
- /**
137
- * The registry is read LAZILY rather than in a field initialiser.
138
- *
139
- * The locator is constructed INSIDE `createLanka`, before the instance becomes
140
- * active. A field initialiser would ask for the active instance at that moment
141
- * and fail with "no instance yet" while the instance being created is on the
142
- * stack. Deferred reading resolves it: by the first scenario resolution an
143
- * active instance exists.
144
- */
145
- private get registry();
146
- constructor();
147
- }
148
-
149
- export { LankaGatewayLocator as L, LankaScenarioLocator as a, LankaScenarioVMRegistry as b, LankaScenariosRegistry as c };
116
+ export { LankaScenariosRegistry as L, LankaScenarioVMRegistry as a };
@@ -1,10 +1,16 @@
1
- export { L as LankaGatewayLocator, a as LankaScenarioLocator, b as LankaScenarioVMRegistry, c as LankaScenariosRegistry } from '../LankaScenarioLocator-CAENLhDO.js';
2
- import { a as ILankaLocator } from '../LankaSharedStoreLocator-zS2kLu-S.js';
3
- export { A as ALankaLocator, L as LankaSharedStoreLocator, b as LankaSingletonLocator } from '../LankaSharedStoreLocator-zS2kLu-S.js';
4
- export { c as createLankaScope } from '../createLankaScope-BiFxNQgl.js';
1
+ export { a as LankaScenarioVMRegistry, L as LankaScenariosRegistry } from '../LankaScenarioVMRegistry-DySAOaj2.js';
2
+ import { a as ILankaLocator } from '../ALankaLocator-BUCCz0Q5.js';
3
+ export { A as ALankaLocator } from '../ALankaLocator-BUCCz0Q5.js';
4
+ export { c as createLankaScope } from '../createLankaScope-CVoV0EwO.js';
5
+ export { LankaGatewayLocator } from '../locator/gateway/lanka-gateway-locator/LankaGatewayLocator.js';
6
+ export { LankaScenarioLocator } from '../locator/scenario/lanka-scenario-locator/LankaScenarioLocator.js';
7
+ export { LankaSingletonLocator } from '../locator/singleton/lanka-singleton-locator/LankaSingletonLocator.js';
8
+ export { LankaSharedStoreLocator } from '../locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.js';
5
9
  export { c as composeLankaRequestMiddleware } from '../lankaRequestMiddleware-DAC5kCb7.js';
6
- import '../ILankaScenarioVM-DpKFL3iE.js';
7
- import '../ILankaScenarioMetadata-Dj4GCqmX.js';
10
+ import { I as ILankaReadableVM } from '../ILankaReadableVM-BoPzHEPV.js';
11
+ import '../ILankaScenario-DQd9ZfUw.js';
12
+ import '../ILankaScenarioVM-DKwjbIRq.js';
13
+ import '../ILankaScenarioMetadata-BsDp0Bzm.js';
8
14
  import '../ALankaGateway-BrVPaZN5.js';
9
15
  import '../lankaStandardValidator-BUFnysK0.js';
10
16
  import '@standard-schema/spec';
@@ -32,4 +38,226 @@ interface ILankaLocatorProxyConfig<TInstance> {
32
38
  */
33
39
  declare function createLankaLocatorProxy<TInstance, TType extends Record<string, TInstance>>(config: ILankaLocatorProxyConfig<TInstance>): TType;
34
40
 
35
- export { type ILankaLocatorProxyConfig, createLankaLocatorProxy };
41
+ interface ILankaAccessTracker<TState extends object> {
42
+ /**
43
+ * The state as a recording Proxy: every key read off it is remembered.
44
+ *
45
+ * Cached by the IDENTITY of the state it wrapped, so a second read while
46
+ * nothing has changed hands back the same proxy — and therefore the same
47
+ * recorded keys — rather than starting the recording over.
48
+ *
49
+ * A ViewModel that turned tracking off gets the state itself, and every
50
+ * change then notifies. That is its decision, not a fallback: it turned
51
+ * tracking off because it DERIVES what the screen shows, and a recording that
52
+ * cannot see those reads would skip renders the screen needs.
53
+ */
54
+ read(): TState;
55
+ /**
56
+ * Whether a change touches anything this reader actually looked at.
57
+ *
58
+ * A reader that has looked at NOTHING yet is notified of everything: it has
59
+ * not had the chance to record a key, and staying silent would mean its first
60
+ * render never arrives.
61
+ */
62
+ shouldNotify(next: TState, prev: TState): boolean;
63
+ /**
64
+ * Says, in development, that a change was skipped — so the framework can warn
65
+ * if the screen reads the changed key through a getter.
66
+ *
67
+ * Called by a binding exactly when `shouldNotify` answered `false`. In
68
+ * production, and for a ViewModel with no trap, it does nothing.
69
+ */
70
+ reportSkipped(next: TState, prev: TState): void;
71
+ /**
72
+ * The state itself, never the proxy.
73
+ *
74
+ * For a render that happens once and is thrown away — a server snapshot. There
75
+ * is nothing to skip on a second render that will not happen, so recording
76
+ * reads would be work whose result nothing consults.
77
+ */
78
+ readPlain(): TState;
79
+ /** The keys read so far. Handed to the blind-spot diagnostic, which names them. */
80
+ readonly trackedKeys: ReadonlySet<string>;
81
+ }
82
+ /**
83
+ * Which state keys one reader looked at, and whether a change touched them.
84
+ *
85
+ * This is the whole of access tracking, and it is deliberately ignorant of how
86
+ * anybody subscribes. A component re-renders only for keys it READ off the proxy
87
+ * this returns; every framework asks that question the same way and answers it
88
+ * with a different mechanism — `useSyncExternalStore`, a `shallowRef`, a signal —
89
+ * so the question lives here and the mechanism lives in the binding.
90
+ *
91
+ * **This is the one piece of core a binding author needs.** It is published
92
+ * through `lanka/extend` for exactly that: a binding is then a subscription, a
93
+ * render trigger and these four calls, and the behaviour a consumer sees is the
94
+ * framework's rather than each binding's re-reading of it. Canon:
95
+ * `skills/parity/SKILL.md`.
96
+ *
97
+ * ## One tracker per reader, not per store
98
+ *
99
+ * The recorded keys are the property of whoever did the reading. Two components
100
+ * over one ViewModel read different keys and must re-render for different
101
+ * changes, so each holds its own tracker — which is also why this is a factory
102
+ * with closed-over state rather than a set of pure functions over a shared map:
103
+ * the lifetime of the recording is exactly the lifetime of the reader.
104
+ *
105
+ * ## The blind spot this cannot see, and reports instead
106
+ *
107
+ * Tracking sees reads made DIRECTLY off the proxy. A key reached only inside a
108
+ * derived getter — an action calling `get()` — is invisible here, so a change to
109
+ * it answers `shouldNotify` with `false` and the screen does not move. That is
110
+ * what `reportSkipped` is for: core kept a trap for this ViewModel, and in
111
+ * development it names the ViewModel and the key rather than leaving a frozen
112
+ * screen with no error anywhere.
113
+ */
114
+ declare const createLankaAccessTracker: <TState extends object>(viewModel: ILankaReadableVM<TState>) => ILankaAccessTracker<TState>;
115
+
116
+ /** One reader's live view of a ViewModel. */
117
+ interface ILankaViewSubscription<TState extends object> {
118
+ /**
119
+ * The current state, RECORDED.
120
+ *
121
+ * Every key read off it is remembered, which is what lets the next change be
122
+ * skipped when it touched none of them. Call it again on every read: it asks
123
+ * the ViewModel for its state each time, so it is never a snapshot.
124
+ */
125
+ read: () => TState;
126
+ /** Releases the subscription. */
127
+ stop: () => void;
128
+ }
129
+ /**
130
+ * The TRACKED half of a view binding, minus the framework.
131
+ *
132
+ * The tracked half, and not the whole: all five shipped members also publish a
133
+ * selector arm, which this deliberately does not serve — the reason is below,
134
+ * under "Why it does not take a selector".
135
+ *
136
+ * ```ts
137
+ * // a binding for a framework this repository has never heard of
138
+ * export const useMyFrameworkVM = (viewModel) => {
139
+ * const view = createLankaViewSubscription(viewModel, () => invalidate());
140
+ * onTeardown(view.stop);
141
+ *
142
+ * return view.read;
143
+ * };
144
+ * ```
145
+ *
146
+ * Subscribe, ask whether the change touched anything this reader looked at,
147
+ * report the skip so the blind-spot diagnostic can fire, and hand back a read
148
+ * that records. Five packages wrote those four steps out by hand, identically —
149
+ * and five copies of a decision diverge on the day one of them gains a line.
150
+ *
151
+ * ## What a caller still owns
152
+ *
153
+ * `onChange` and the teardown, which are the only framework-shaped things left.
154
+ * That is the seam: a binding says how its framework is WOKEN and when a reader
155
+ * has gone, and everything about which changes are worth waking for is here.
156
+ *
157
+ * ## Why it does not take a selector
158
+ *
159
+ * A selector BYPASSES tracking — the selector decides, and there is nothing to
160
+ * record — so a subscription that took one would be two mechanisms behind one
161
+ * name, each right half the time. A binding with a selector arm calls
162
+ * `createLankaAccessTracker` directly, which is what the shelf's members do, and
163
+ * the four steps are worth writing out where they genuinely differ.
164
+ *
165
+ * ## What it is not
166
+ *
167
+ * Not a store, not a cache, not a second place state lives. It holds a tracker
168
+ * and an unsubscribe, and everything it answers comes from the ViewModel on the
169
+ * call.
170
+ */
171
+ declare const createLankaViewSubscription: <TState extends object>(viewModel: ILankaReadableVM<TState>, onChange: () => void) => ILankaViewSubscription<TState>;
172
+
173
+ /**
174
+ * The brand that makes a definition impossible to write by hand.
175
+ *
176
+ * Not exported, and that is the whole mechanism. A structural interface with
177
+ * `name` and `build` on it would let a consumer author one, and the moment one
178
+ * does, `ILankaVMDefinition` stops being a type the framework implements and
179
+ * becomes a PORT — after which every member added to it is a compile error in
180
+ * code nobody touched. Publishing an opaque type costs one line and keeps the
181
+ * option space: a `dispose`, a `scope` field, even a call signature so that
182
+ * `missionsVM()` could mean `resolveLankaVM(missionsVM)`, all stay additive.
183
+ */
184
+ declare const LANKA_VM_DEFINITION: unique symbol;
185
+ /**
186
+ * A ViewModel that has not been built yet.
187
+ *
188
+ * Hold it, pass it, type against it. There is nothing on it to call, because
189
+ * calling it is `resolveLankaVM`'s job and a definition built by hand would be
190
+ * an instance outside every scope's map.
191
+ */
192
+ interface ILankaVMDefinition<TViewModel extends ILankaReadableVM<object>> {
193
+ readonly [LANKA_VM_DEFINITION]: TViewModel;
194
+ }
195
+ /**
196
+ * Declares a ViewModel WITHOUT building it.
197
+ *
198
+ * ## When NOT to use this
199
+ *
200
+ * A browser-only application needs none of it. One module is one instance per
201
+ * TAB there, so a module-level `createLankaVM` is still the shape, and wrapping
202
+ * the nine factories in this one buys nothing but a lookup per read. Reach for
203
+ * it when the same ViewModel has to exist on a SERVER, where one module is one
204
+ * instance per PROCESS — shared by every user connected to it, so the first
205
+ * request to write a draft into it serves that draft to the next stranger.
206
+ *
207
+ * ```ts
208
+ * export const missionsVM = defineLankaVM({
209
+ * name: "MissionsVM",
210
+ * build: () => createLankaVM({ … }),
211
+ * });
212
+ * ```
213
+ *
214
+ * ## What it is not
215
+ *
216
+ * Not a tenth way to write a ViewModel. `build` returns whatever the nine
217
+ * existing factories return and nothing here reaches inside it — a definition
218
+ * adds a lifetime and takes nothing away.
219
+ *
220
+ * Not lazy in the sense `createLazyLankaVM` is. That one defers the STORE until
221
+ * first read and still has one per module; this defers WHICH INSTANCE, and the
222
+ * two compose.
223
+ */
224
+ declare const defineLankaVM: <TViewModel extends ILankaReadableVM<object>>(config: {
225
+ name: string;
226
+ build: () => TViewModel;
227
+ }) => ILankaVMDefinition<TViewModel>;
228
+
229
+ /**
230
+ * The instance of a definition that belongs to the CURRENT scope.
231
+ *
232
+ * Called twice in one scope it answers the same instance; called in two scopes
233
+ * it answers two, and neither can see the other's state. In a browser there is
234
+ * one scope for the life of the tab, so this is the module-level ViewModel a
235
+ * consumer already knows — written once and correct on a server as well.
236
+ *
237
+ * ## It throws outside a scope, and that is the feature
238
+ *
239
+ * On a server the answer comes from the SCOPE seam rather than from the active
240
+ * runtime, and the difference is not academic. `runInLankaServerScope` creates
241
+ * its instance inside the scope and `createLanka` activates every instance it
242
+ * builds, so during a request the process pointer and the scope's runtime are
243
+ * the same object — keying on the runtime would make "inside a request" and
244
+ * "after one ended" indistinguishable, and a call made after would be handed the
245
+ * last stranger's ViewModel.
246
+ *
247
+ * So: a scope resolver installed and answering `null` means this ran outside
248
+ * every request, and that fails loudly. No fallback, for the same reason
249
+ * `requireActiveRuntime` has none — a wrong answer here is one user's data in
250
+ * another user's page, and it would surface three layers from the call.
251
+ *
252
+ * ## Why the build is wrapped
253
+ *
254
+ * `ALankaVM.build()` declares a ViewModel that has scenario handlers into a
255
+ * PROCESS-wide list, so that every instance ever created adopts it. That is
256
+ * right for a module-level ViewModel and catastrophic for a scoped one: the list
257
+ * would grow per request forever, and request N+1 would adopt request N's
258
+ * ViewModel and run N's handlers against N's gateways. `buildScoped` is the seam
259
+ * that keeps a scoped declaration out of it.
260
+ */
261
+ declare const resolveLankaVM: <TViewModel extends ILankaReadableVM<object>>(definition: ILankaVMDefinition<TViewModel>) => TViewModel;
262
+
263
+ export { type ILankaAccessTracker, type ILankaLocatorProxyConfig, type ILankaVMDefinition, type ILankaViewSubscription, createLankaAccessTracker, createLankaLocatorProxy, createLankaViewSubscription, defineLankaVM, resolveLankaVM };