lanka 2.0.1 → 2.2.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.
Files changed (65) hide show
  1. package/dist/{ILankaScenario-DQd9ZfUw.d.ts → ILankaScenario-7J3MoKiY.d.ts} +9 -0
  2. package/dist/{ILankaScenarioMetadata-BsDp0Bzm.d.ts → ILankaScenarioMetadata-DgOxvpwx.d.ts} +1 -1
  3. package/dist/LankaScenarioVMRegistry-CFzCpIFd.d.ts +46 -0
  4. package/dist/{LankaScenarioVMRegistry-DySAOaj2.d.ts → LankaScenariosRegistry-DiLN9xwy.d.ts} +3 -46
  5. package/dist/_extend/index.d.ts +79 -6
  6. package/dist/_extend/index.js +74 -29
  7. package/dist/_extend/index.js.map +1 -1
  8. package/dist/_internal/index.d.ts +5 -4
  9. package/dist/_internal/index.js +3 -1
  10. package/dist/_internal/index.js.map +1 -1
  11. package/dist/{activeRuntime-ByucLPhj.d.ts → activeRuntime-DxB62KN4.d.ts} +8 -4
  12. package/dist/bootstrap/index.d.ts +8 -7
  13. package/dist/bootstrap/index.js +9 -9
  14. package/dist/chunk-3OQ3JZGT.js +90 -0
  15. package/dist/chunk-3OQ3JZGT.js.map +1 -0
  16. package/dist/{chunk-FII3PW2G.js → chunk-6TNWBT6T.js} +2 -2
  17. package/dist/{chunk-RUMUFHSL.js → chunk-BZNDUUTA.js} +2 -2
  18. package/dist/{chunk-7DQUF2QR.js → chunk-FR2FRJME.js} +2 -2
  19. package/dist/{chunk-O5EUTNR6.js → chunk-HYT5NSIB.js} +2 -2
  20. package/dist/{chunk-42CWYVZJ.js → chunk-IB2LRYDK.js} +2 -2
  21. package/dist/{chunk-MKKMTLOY.js → chunk-JX47U5DC.js} +38 -5
  22. package/dist/{chunk-MKKMTLOY.js.map → chunk-JX47U5DC.js.map} +1 -1
  23. package/dist/chunk-MLKTN4OV.js +76 -0
  24. package/dist/chunk-MLKTN4OV.js.map +1 -0
  25. package/dist/{chunk-BHQ2SQ7P.js → chunk-NPJSZLTB.js} +2 -2
  26. package/dist/{chunk-H5TIUKRK.js → chunk-R365QEN2.js} +2 -2
  27. package/dist/{chunk-NWEHOMTS.js → chunk-RZGBLXXL.js} +3 -3
  28. package/dist/{chunk-SO7D5E7O.js → chunk-UX4A7TBG.js} +10 -10
  29. package/dist/chunk-UX4A7TBG.js.map +1 -0
  30. package/dist/config/index.js +3 -3
  31. package/dist/{createLanka-DvUGu9Hl.d.ts → createLanka-DxKZkmWe.d.ts} +2 -2
  32. package/dist/{createLankaScope-CVoV0EwO.d.ts → createLankaScope-DeElRH-E.d.ts} +7 -1
  33. package/dist/errors/index.js +3 -3
  34. package/dist/gateway/index.js +7 -7
  35. package/dist/index.d.ts +8 -7
  36. package/dist/index.js +10 -10
  37. package/dist/locator/index.d.ts +3 -1
  38. package/dist/locator/index.js +2 -2
  39. package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.d.ts +1 -1
  40. package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.js +5 -5
  41. package/dist/logger/index.js +3 -3
  42. package/dist/mock/index.js +3 -3
  43. package/dist/scenario/index.d.ts +18 -4
  44. package/dist/scenario/index.js +5 -5
  45. package/dist/stream/index.d.ts +8 -7
  46. package/dist/viewmodel/index.d.ts +41 -12
  47. package/dist/viewmodel/index.js +7 -6
  48. package/dist/viewmodel/index.js.map +1 -1
  49. package/package.json +2 -2
  50. package/skills/lanka-core/SKILL.md +1 -1
  51. package/skills/lanka-core/reference.md +21 -1
  52. package/skills/lanka-packages/SKILL.md +1 -1
  53. package/dist/chunk-SBITVBF7.js +0 -49
  54. package/dist/chunk-SBITVBF7.js.map +0 -1
  55. package/dist/chunk-SO7D5E7O.js.map +0 -1
  56. package/dist/chunk-XGMXT4XZ.js +0 -46
  57. package/dist/chunk-XGMXT4XZ.js.map +0 -1
  58. /package/dist/{chunk-FII3PW2G.js.map → chunk-6TNWBT6T.js.map} +0 -0
  59. /package/dist/{chunk-RUMUFHSL.js.map → chunk-BZNDUUTA.js.map} +0 -0
  60. /package/dist/{chunk-7DQUF2QR.js.map → chunk-FR2FRJME.js.map} +0 -0
  61. /package/dist/{chunk-O5EUTNR6.js.map → chunk-HYT5NSIB.js.map} +0 -0
  62. /package/dist/{chunk-42CWYVZJ.js.map → chunk-IB2LRYDK.js.map} +0 -0
  63. /package/dist/{chunk-BHQ2SQ7P.js.map → chunk-NPJSZLTB.js.map} +0 -0
  64. /package/dist/{chunk-H5TIUKRK.js.map → chunk-R365QEN2.js.map} +0 -0
  65. /package/dist/{chunk-NWEHOMTS.js.map → chunk-RZGBLXXL.js.map} +0 -0
@@ -115,6 +115,15 @@ interface ILankaEventBusOutcome {
115
115
  subscribers: number;
116
116
  /** The reason a middleware gave, when one stopped it. */
117
117
  stoppedBy?: string;
118
+ /**
119
+ * The payload, on a DELIVERED outcome only.
120
+ *
121
+ * Only there, because only there did the chain let a subscriber see it: a
122
+ * payload handed out on "stopped" would route around the gate that stopped
123
+ * it, and on "invalid" it is what the schema refused. An observer that
124
+ * repeats a delivery elsewhere needs exactly this and nothing more.
125
+ */
126
+ data?: unknown;
118
127
  }
119
128
 
120
129
  /**
@@ -1,4 +1,4 @@
1
- import { I as ILankaScenario } from './ILankaScenario-DQd9ZfUw.js';
1
+ import { I as ILankaScenario } from './ILankaScenario-7J3MoKiY.js';
2
2
 
3
3
  /**
4
4
  * Metadata about a registered scenario
@@ -0,0 +1,46 @@
1
+ import { I as ILankaScenarioVM } from './ILankaScenarioVM-DKwjbIRq.js';
2
+
3
+ /**
4
+ * The registry of ViewModels that use scenarios.
5
+ *
6
+ * They arrive by themselves: the factory registers a ViewModel when it declares
7
+ * scenario handlers.
8
+ */
9
+ declare class LankaScenarioVMRegistry {
10
+ private registeredViewModels;
11
+ /**
12
+ * Public: the registry belongs to a framework instance rather than to the
13
+ * module.
14
+ */
15
+ constructor();
16
+ /**
17
+ * The active instance's registry, for callers that cannot hold one — the
18
+ * static `LankaScenarioBootstrap`. Instance holders read `lanka.viewModels`.
19
+ */
20
+ static getInstance(): LankaScenarioVMRegistry;
21
+ /** Whether this ViewModel is already registered. */
22
+ isRegistered(viewModel: ILankaScenarioVM): boolean;
23
+ /**
24
+ * Registers a ViewModel.
25
+ *
26
+ * @returns `false` when it was already registered
27
+ */
28
+ register(viewModel: ILankaScenarioVM): boolean;
29
+ /** Removes a ViewModel from the registry. */
30
+ unregister(viewModel: ILankaScenarioVM): void;
31
+ /** Every registered ViewModel. */
32
+ getAllViewModels(): ILankaScenarioVM[];
33
+ /**
34
+ * Clears every registered ViewModel. Required by tests.
35
+ */
36
+ clear(): void;
37
+ /**
38
+ * Unsubscribes every registered ViewModel from its scenarios and clears the
39
+ * registry.
40
+ *
41
+ * Without it tests are not isolated: a subscription leaks from test to test.
42
+ */
43
+ resetAll(): void;
44
+ }
45
+
46
+ export { LankaScenarioVMRegistry as L };
@@ -1,6 +1,6 @@
1
- import { I as ILankaScenario } from './ILankaScenario-DQd9ZfUw.js';
1
+ import { I as ILankaScenario } from './ILankaScenario-7J3MoKiY.js';
2
2
  import { I as ILankaScenarioVM } from './ILankaScenarioVM-DKwjbIRq.js';
3
- import { I as ILankaScenarioMetadata } from './ILankaScenarioMetadata-BsDp0Bzm.js';
3
+ import { I as ILankaScenarioMetadata } from './ILankaScenarioMetadata-DgOxvpwx.js';
4
4
 
5
5
  /**
6
6
  * The scenario registry: both those that registered themselves and those
@@ -70,47 +70,4 @@ declare class LankaScenariosRegistry {
70
70
  clear(): void;
71
71
  }
72
72
 
73
- /**
74
- * The registry of ViewModels that use scenarios.
75
- *
76
- * They arrive by themselves: the factory registers a ViewModel when it declares
77
- * scenario handlers.
78
- */
79
- declare class LankaScenarioVMRegistry {
80
- private registeredViewModels;
81
- /**
82
- * Public: the registry belongs to a framework instance rather than to the
83
- * module.
84
- */
85
- constructor();
86
- /**
87
- * The active instance's registry, for callers that cannot hold one — the
88
- * static `LankaScenarioBootstrap`. Instance holders read `lanka.viewModels`.
89
- */
90
- static getInstance(): LankaScenarioVMRegistry;
91
- /** Whether this ViewModel is already registered. */
92
- isRegistered(viewModel: ILankaScenarioVM): boolean;
93
- /**
94
- * Registers a ViewModel.
95
- *
96
- * @returns `false` when it was already registered
97
- */
98
- register(viewModel: ILankaScenarioVM): boolean;
99
- /** Removes a ViewModel from the registry. */
100
- unregister(viewModel: ILankaScenarioVM): void;
101
- /** Every registered ViewModel. */
102
- getAllViewModels(): ILankaScenarioVM[];
103
- /**
104
- * Clears every registered ViewModel. Required by tests.
105
- */
106
- clear(): void;
107
- /**
108
- * Unsubscribes every registered ViewModel from its scenarios and clears the
109
- * registry.
110
- *
111
- * Without it tests are not isolated: a subscription leaks from test to test.
112
- */
113
- resetAll(): void;
114
- }
115
-
116
- export { LankaScenariosRegistry as L, LankaScenarioVMRegistry as a };
73
+ export { LankaScenariosRegistry as L };
@@ -1,16 +1,18 @@
1
- export { a as LankaScenarioVMRegistry, L as LankaScenariosRegistry } from '../LankaScenarioVMRegistry-DySAOaj2.js';
1
+ export { L as LankaScenariosRegistry } from '../LankaScenariosRegistry-DiLN9xwy.js';
2
+ export { L as LankaScenarioVMRegistry } from '../LankaScenarioVMRegistry-CFzCpIFd.js';
2
3
  import { a as ILankaLocator } from '../ALankaLocator-BUCCz0Q5.js';
3
4
  export { A as ALankaLocator } from '../ALankaLocator-BUCCz0Q5.js';
4
- export { c as createLankaScope } from '../createLankaScope-CVoV0EwO.js';
5
+ import { I as ILankaScope } from '../createLankaScope-DeElRH-E.js';
6
+ export { c as createLankaScope } from '../createLankaScope-DeElRH-E.js';
5
7
  export { LankaGatewayLocator } from '../locator/gateway/lanka-gateway-locator/LankaGatewayLocator.js';
6
8
  export { LankaScenarioLocator } from '../locator/scenario/lanka-scenario-locator/LankaScenarioLocator.js';
7
9
  export { LankaSingletonLocator } from '../locator/singleton/lanka-singleton-locator/LankaSingletonLocator.js';
8
10
  export { LankaSharedStoreLocator } from '../locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.js';
9
11
  export { c as composeLankaRequestMiddleware } from '../lankaRequestMiddleware-DAC5kCb7.js';
10
12
  import { I as ILankaReadableVM } from '../ILankaReadableVM-BoPzHEPV.js';
11
- import '../ILankaScenario-DQd9ZfUw.js';
13
+ import '../ILankaScenario-7J3MoKiY.js';
12
14
  import '../ILankaScenarioVM-DKwjbIRq.js';
13
- import '../ILankaScenarioMetadata-BsDp0Bzm.js';
15
+ import '../ILankaScenarioMetadata-DgOxvpwx.js';
14
16
  import '../ALankaGateway-BrVPaZN5.js';
15
17
  import '../lankaStandardValidator-BUFnysK0.js';
16
18
  import '@standard-schema/spec';
@@ -113,6 +115,58 @@ interface ILankaAccessTracker<TState extends object> {
113
115
  */
114
116
  declare const createLankaAccessTracker: <TState extends object>(viewModel: ILankaReadableVM<TState>) => ILankaAccessTracker<TState>;
115
117
 
118
+ /**
119
+ * A ViewModel that is also the call its binding reads it with.
120
+ *
121
+ * ```ts
122
+ * // inside a binding: the framework's own read, pre-applied
123
+ * const useTodoVM = createLankaCallableVM<typeof todoVM, TMyCall>(todoVM, (selector) =>
124
+ * useLankaVM(todoVM, selector),
125
+ * );
126
+ * ```
127
+ *
128
+ * ## Why this is in core and not in a binding
129
+ *
130
+ * Five bindings publish core's six ViewModel factories under core's own names,
131
+ * each one already wearing that framework's read — so a consumer moves a
132
+ * declaration by changing the import line. What every one of them needs to do
133
+ * that is the same object: a function that is ALSO the ViewModel, so
134
+ * `useTodoVM()` reads and `useTodoVM.getState()` does what it always did.
135
+ *
136
+ * The forwarding is the part that is easy to get subtly wrong — which members
137
+ * belong to the function, what `in` must answer, what a lazy ViewModel does with
138
+ * a symbol — and five copies of it would be five packages diverging on a
139
+ * question that has one answer. It lives here for the reason
140
+ * `createLankaAccessTracker` does: what a binding sees is then lanka's behaviour
141
+ * rather than that binding author's reading of it.
142
+ *
143
+ * ## What it does NOT do
144
+ *
145
+ * It adds no state and no second store. The Proxy forwards rather than copying,
146
+ * so a LAZY ViewModel still builds on first access: reading `useTodoVM.name`
147
+ * answers from the config and constructs nothing. `TCall` is the binding's own
148
+ * call signature, because what the call answers is the framework's idea of
149
+ * reactivity and the one thing no binding can hide — a plain state in React, a
150
+ * `ShallowRef` in Vue, an `Accessor` in Solid.
151
+ *
152
+ * **`TCall` is an ASSERTION, not a checked parameter.** The function passed as
153
+ * `call` is typed as the single forwarding signature a binding can actually
154
+ * write, and the overloaded shape a caller wants is not assignable from it — so
155
+ * nothing compares the two, and a `TCall` that stops describing what `call`
156
+ * answers compiles. Declare the call type once, use it at both sites, and keep
157
+ * them within sight of each other; every member of `modules/bindings/` does.
158
+ *
159
+ * **Nine names come from the FUNCTION and cannot be forwarded**: `prototype`,
160
+ * `length`, `arguments`, `caller`, `constructor`, `call`, `apply`, `bind` and
161
+ * `toString`, plus every symbol. The return type says the whole ViewModel is
162
+ * readable, and for a ViewModel built from core's factories it is — none of them
163
+ * collides. A ViewModel written by hand with a public `apply`, `bind` or
164
+ * `length` is inside the constraint and would read the function's instead, with
165
+ * no error anywhere. That is the price of the object being both things at once,
166
+ * and the list is short enough to check a ViewModel against.
167
+ */
168
+ declare const createLankaCallableVM: <TViewModel extends ILankaReadableVM<object>, TCall extends (...args: never[]) => unknown>(viewModel: TViewModel, call: (...args: never[]) => unknown) => TViewModel & TCall;
169
+
116
170
  /** One reader's live view of a ViewModel. */
117
171
  interface ILankaViewSubscription<TState extends object> {
118
172
  /**
@@ -226,6 +280,17 @@ declare const defineLankaVM: <TViewModel extends ILankaReadableVM<object>>(confi
226
280
  build: () => TViewModel;
227
281
  }) => ILankaVMDefinition<TViewModel>;
228
282
 
283
+ /**
284
+ * Where the instance should live, when not in the current scope.
285
+ *
286
+ * `scope` is a lifetime shorter than the page's: a module that mounts and
287
+ * later leaves, a modal, a route. The ViewModel resolved with it is that
288
+ * scope's own — one per definition in it, a different one from the page's —
289
+ * and `scope.dispose()` takes it off the bus and out of the registry.
290
+ */
291
+ interface ILankaResolveVMOptions {
292
+ scope?: ILankaScope;
293
+ }
229
294
  /**
230
295
  * The instance of a definition that belongs to the CURRENT scope.
231
296
  *
@@ -257,7 +322,15 @@ declare const defineLankaVM: <TViewModel extends ILankaReadableVM<object>>(confi
257
322
  * would grow per request forever, and request N+1 would adopt request N's
258
323
  * ViewModel and run N's handlers against N's gateways. `buildScoped` is the seam
259
324
  * that keeps a scoped declaration out of it.
325
+ *
326
+ * ## An explicit scope
327
+ *
328
+ * A scope handed in `options` is the key instead, and the same seam keeps its
329
+ * ViewModels out of the process-wide list. The scope is told what it built, so
330
+ * that closing it can take them off the bus — which is the whole reason a
331
+ * module would hand one in. A closed scope is refused like `scope.resolve`
332
+ * refuses: an object handed out of it would outlive what it belongs to.
260
333
  */
261
- declare const resolveLankaVM: <TViewModel extends ILankaReadableVM<object>>(definition: ILankaVMDefinition<TViewModel>) => TViewModel;
334
+ declare const resolveLankaVM: <TViewModel extends ILankaReadableVM<object>>(definition: ILankaVMDefinition<TViewModel>, options?: ILankaResolveVMOptions) => TViewModel;
262
335
 
263
- export { type ILankaAccessTracker, type ILankaLocatorProxyConfig, type ILankaVMDefinition, type ILankaViewSubscription, createLankaAccessTracker, createLankaLocatorProxy, createLankaViewSubscription, defineLankaVM, resolveLankaVM };
336
+ export { type ILankaAccessTracker, type ILankaLocatorProxyConfig, type ILankaResolveVMOptions, type ILankaVMDefinition, type ILankaViewSubscription, createLankaAccessTracker, createLankaCallableVM, createLankaLocatorProxy, createLankaViewSubscription, defineLankaVM, resolveLankaVM };
@@ -2,27 +2,28 @@ import {
2
2
  lankaBlindSpotRegistry
3
3
  } from "../chunk-UDP6IXDS.js";
4
4
  import {
5
- createLankaScope
6
- } from "../chunk-SBITVBF7.js";
5
+ createLankaScope,
6
+ lankaVMRecipes
7
+ } from "../chunk-3OQ3JZGT.js";
7
8
  import {
8
9
  LankaGatewayLocator
9
10
  } from "../chunk-BMF4TM2Z.js";
10
11
  import {
11
12
  LankaScenarioLocator
12
- } from "../chunk-42CWYVZJ.js";
13
+ } from "../chunk-IB2LRYDK.js";
13
14
  import {
14
15
  lankaScenarioBootstrap
15
- } from "../chunk-MKKMTLOY.js";
16
+ } from "../chunk-JX47U5DC.js";
16
17
  import {
17
18
  LankaScenarioVMRegistry,
18
19
  LankaScenariosRegistry
19
- } from "../chunk-NWEHOMTS.js";
20
+ } from "../chunk-RZGBLXXL.js";
20
21
  import {
21
22
  composeLankaRequestMiddleware
22
23
  } from "../chunk-YR4MZXMU.js";
23
24
  import {
24
25
  createLankaLocatorProxy
25
- } from "../chunk-H5TIUKRK.js";
26
+ } from "../chunk-R365QEN2.js";
26
27
  import {
27
28
  LankaSharedStoreLocator
28
29
  } from "../chunk-3R2NO47A.js";
@@ -33,13 +34,13 @@ import "../chunk-JZJ6GXX3.js";
33
34
  import {
34
35
  ALankaLocator
35
36
  } from "../chunk-N3275IPH.js";
36
- import "../chunk-FII3PW2G.js";
37
- import "../chunk-7DQUF2QR.js";
37
+ import "../chunk-6TNWBT6T.js";
38
+ import "../chunk-FR2FRJME.js";
38
39
  import {
39
40
  getActiveLankaScope,
40
41
  hasLankaScopeResolver,
41
42
  requireActiveRuntime
42
- } from "../chunk-XGMXT4XZ.js";
43
+ } from "../chunk-MLKTN4OV.js";
43
44
 
44
45
  // src/viewmodel/_internal/create-lanka-access-tracker/createLankaAccessTracker.ts
45
46
  var asRecord = (state) => state;
@@ -101,6 +102,44 @@ var createLankaAccessTracker = (viewModel) => {
101
102
  };
102
103
  };
103
104
 
105
+ // src/viewmodel/_factories/create-lanka-callable-vm/createLankaCallableVM.ts
106
+ var FUNCTION_MEMBERS = /* @__PURE__ */ new Set([
107
+ "prototype",
108
+ "length",
109
+ "arguments",
110
+ "caller",
111
+ "constructor",
112
+ "call",
113
+ "apply",
114
+ "bind",
115
+ "toString"
116
+ ]);
117
+ var forwardToViewModel = (viewModel) => {
118
+ const members = viewModel;
119
+ return {
120
+ get: (target, property, receiver) => typeof property === "symbol" || FUNCTION_MEMBERS.has(property) ? Reflect.get(target, property, receiver) : members[property],
121
+ /**
122
+ * `in` answers for the ViewModel too.
123
+ *
124
+ * Without this the callable would report that it has no `getState`, while
125
+ * reading `getState` hands one back — and `"getState" in useTodoVM` is how a
126
+ * devtool, a serialiser and a duck-typed helper ask. The ViewModel behind
127
+ * this may be a lazy proxy with no `has` trap of its own, so the question is
128
+ * answered by READING the property, which for a lazy ViewModel builds
129
+ * nothing.
130
+ *
131
+ * The price, measured: over an EAGER ViewModel `"whatever" in callable` is
132
+ * false, and over a LAZY one it is true, because the lazy proxy answers any
133
+ * unknown key with a wrapper. `in` is therefore a reliable yes and an
134
+ * unreliable no, and a caller that needs a real answer asks the state.
135
+ * Narrowing it would mean asking a lazy ViewModel to enumerate itself,
136
+ * which is the one thing it exists not to do.
137
+ */
138
+ has: (target, property) => Reflect.has(target, property) || typeof property === "string" && members[property] !== void 0
139
+ };
140
+ };
141
+ var createLankaCallableVM = (viewModel, call) => new Proxy(call, forwardToViewModel(viewModel));
142
+
104
143
  // src/viewmodel/_factories/create-lanka-view-subscription/createLankaViewSubscription.ts
105
144
  var createLankaViewSubscription = (viewModel, onChange) => {
106
145
  const tracker = createLankaAccessTracker(viewModel);
@@ -114,20 +153,6 @@ var createLankaViewSubscription = (viewModel, onChange) => {
114
153
  return { read: () => tracker.read(), stop };
115
154
  };
116
155
 
117
- // src/viewmodel/_internal/lanka-vm-recipes/lankaVMRecipes.ts
118
- var lankaVMRecipes = {
119
- recipes: /* @__PURE__ */ new WeakMap(),
120
- instances: /* @__PURE__ */ new WeakMap(),
121
- /** The instances belonging to one scope, created on first use. */
122
- forScope(scope) {
123
- const existing = this.instances.get(scope);
124
- if (existing) return existing;
125
- const created = /* @__PURE__ */ new WeakMap();
126
- this.instances.set(scope, created);
127
- return created;
128
- }
129
- };
130
-
131
156
  // src/viewmodel/_factories/define-lanka-vm/defineLankaVM.ts
132
157
  var defineLankaVM = (config) => {
133
158
  const definition = Object.freeze({});
@@ -136,24 +161,43 @@ var defineLankaVM = (config) => {
136
161
  };
137
162
 
138
163
  // src/viewmodel/_factories/resolve-lanka-vm/resolveLankaVM.ts
139
- var resolveLankaVM = (definition) => {
164
+ var closedScope = (name) => new Error(
165
+ `The scope is closed: "${name}" can no longer be resolved in it. This is usually a scope reference that outlived the screen that created it.`
166
+ );
167
+ var recipeOf = (definition) => {
140
168
  const recipe = lankaVMRecipes.recipes.get(definition);
141
169
  if (!recipe) {
142
170
  throw new Error(
143
171
  "resolveLankaVM was given something defineLankaVM did not make. A definition is opaque on purpose: build it with defineLankaVM rather than by hand."
144
172
  );
145
173
  }
174
+ return recipe;
175
+ };
176
+ var scopeKeyOf = (name, own) => {
146
177
  const runtime = requireActiveRuntime();
147
- const scope = getActiveLankaScope();
148
- if (hasLankaScopeResolver() && !scope) {
178
+ if (own?.isDisposed()) throw closedScope(name);
179
+ const scope = own ?? getActiveLankaScope();
180
+ if (!own && hasLankaScopeResolver() && !scope) {
149
181
  throw new Error(
150
- `resolveLankaVM("${recipe.name}") ran outside every scope. A scope resolver is installed, which on a server means this code ran outside a request \u2014 and a ViewModel resolved there would be the previous request's. Do this work inside the scope, or start one.`
182
+ `resolveLankaVM("${name}") ran outside every scope. A scope resolver is installed, which on a server means this code ran outside a request \u2014 and a ViewModel resolved there would be the previous request's. Do this work inside the scope, or start one.`
151
183
  );
152
184
  }
153
- const instances = lankaVMRecipes.forScope(scope ?? runtime);
185
+ return scope ?? runtime;
186
+ };
187
+ var reportTo = (own, name) => (viewModel) => {
188
+ if (own.isDisposed()) throw closedScope(name);
189
+ lankaVMRecipes.own(own, viewModel);
190
+ };
191
+ var resolveLankaVM = (definition, options) => {
192
+ const recipe = recipeOf(definition);
193
+ const own = options?.scope;
194
+ const instances = lankaVMRecipes.forScope(scopeKeyOf(recipe.name, own));
154
195
  const held = instances.get(definition);
155
196
  if (held) return held;
156
- const built = lankaScenarioBootstrap.buildScoped(() => recipe.build());
197
+ const built = lankaScenarioBootstrap.buildScoped(
198
+ () => recipe.build(),
199
+ own ? reportTo(own, recipe.name) : void 0
200
+ );
157
201
  instances.set(definition, built);
158
202
  return built;
159
203
  };
@@ -167,6 +211,7 @@ export {
167
211
  LankaSingletonLocator,
168
212
  composeLankaRequestMiddleware,
169
213
  createLankaAccessTracker,
214
+ createLankaCallableVM,
170
215
  createLankaLocatorProxy,
171
216
  createLankaScope,
172
217
  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/_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 { 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\";\nimport type { ILankaScope } from \"../../../locator/_factories/create-lanka-scope/createLankaScope\";\nimport type { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport type { ILankaVMRecipe } from \"../../_internal/lanka-vm-recipes/lankaVMRecipes\";\n\n/** A closed scope hands nothing out — at resolve time, or at a lazy first read. */\nconst closedScope = (name: string): Error =>\n\tnew Error(\n\t\t`The scope is closed: \"${name}\" can no longer be resolved in it. ` +\n\t\t\t`This is usually a scope reference that outlived the screen that created it.`,\n\t);\n\n/**\n * Where the instance should live, when not in the current scope.\n *\n * `scope` is a lifetime shorter than the page's: a module that mounts and\n * later leaves, a modal, a route. The ViewModel resolved with it is that\n * scope's own — one per definition in it, a different one from the page's —\n * and `scope.dispose()` takes it off the bus and out of the registry.\n */\nexport interface ILankaResolveVMOptions {\n\tscope?: ILankaScope;\n}\n\n/** The recipe a definition holds, or a refusal: a definition is opaque on purpose. */\nconst recipeOf = (definition: object): ILankaVMRecipe => {\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\treturn recipe;\n};\n\n/**\n * What the instance is keyed on: the scope handed in, else the current one, else\n * the runtime — a browser tab is one scope for its whole life, and the instance\n * is what identifies it. A closed scope, or no scope where a resolver says there\n * must be one, is refused.\n */\nconst scopeKeyOf = (name: string, own: ILankaScope | undefined): object => {\n\tconst runtime = requireActiveRuntime();\n\n\tif (own?.isDisposed()) throw closedScope(name);\n\n\tconst scope = own ?? getActiveLankaScope();\n\n\tif (!own && hasLankaScopeResolver() && !scope) {\n\t\tthrow new Error(\n\t\t\t`resolveLankaVM(\"${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\treturn scope ?? runtime;\n};\n\n/**\n * Tells an explicit scope what it built, so closing it can take it off the bus.\n *\n * A LAZY ViewModel registers on its first read, which may come after the scope\n * closed; building it then would subscribe a ViewModel whose scope is gone.\n */\nconst reportTo =\n\t(own: ILankaScope, name: string) =>\n\t(viewModel: ILankaScenarioVM): void => {\n\t\tif (own.isDisposed()) throw closedScope(name);\n\t\tlankaVMRecipes.own(own, viewModel);\n\t};\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 *\n * ## An explicit scope\n *\n * A scope handed in `options` is the key instead, and the same seam keeps its\n * ViewModels out of the process-wide list. The scope is told what it built, so\n * that closing it can take them off the bus — which is the whole reason a\n * module would hand one in. A closed scope is refused like `scope.resolve`\n * refuses: an object handed out of it would outlive what it belongs to.\n */\nexport const resolveLankaVM = <TViewModel extends ILankaReadableVM<object>>(\n\tdefinition: ILankaVMDefinition<TViewModel>,\n\toptions?: ILankaResolveVMOptions,\n): TViewModel => {\n\tconst recipe = recipeOf(definition);\n\tconst own = options?.scope;\n\tconst instances = lankaVMRecipes.forScope(scopeKeyOf(recipe.name, own));\n\tconst held = instances.get(definition);\n\n\tif (held) return held as TViewModel;\n\n\tconst built = lankaScenarioBootstrap.buildScoped(\n\t\t() => recipe.build(),\n\t\town ? reportTo(own, recipe.name) : undefined,\n\t);\n\n\tinstances.set(definition, built);\n\n\treturn built as TViewModel;\n};\n"],"mappings":"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","names":[]}
@@ -1,9 +1,10 @@
1
- export { I as ILankaRuntime, T as TLankaRuntimeResolver, a as TLankaScopeResolver, g as getLankaProcessRuntime, s as setActiveLankaRuntime, b as setLankaRuntimeResolver, c as setLankaScopeResolver } from '../activeRuntime-ByucLPhj.js';
1
+ export { I as ILankaRuntime, T as TLankaRuntimeResolver, a as TLankaScopeResolver, g as getLankaProcessRuntime, h as hasLankaScopeResolver, s as setActiveLankaRuntime, b as setLankaRuntimeResolver, c as setLankaScopeResolver } from '../activeRuntime-DxB62KN4.js';
2
2
  import { a as TLankaValidationResult } from '../lankaStandardValidator-BUFnysK0.js';
3
- import '../ILankaScenario-DQd9ZfUw.js';
4
- import '../LankaScenarioVMRegistry-DySAOaj2.js';
3
+ import '../ILankaScenario-7J3MoKiY.js';
4
+ import '../LankaScenariosRegistry-DiLN9xwy.js';
5
5
  import '../ILankaScenarioVM-DKwjbIRq.js';
6
- import '../ILankaScenarioMetadata-BsDp0Bzm.js';
6
+ import '../ILankaScenarioMetadata-DgOxvpwx.js';
7
+ import '../LankaScenarioVMRegistry-CFzCpIFd.js';
7
8
  import '../ILankaRuntimeConfig-Vl436GWK.js';
8
9
  import '../lankaHttpInFlight-Bk1eIuSx.js';
9
10
  import '../lankaRequestMiddleware-DAC5kCb7.js';
@@ -8,10 +8,11 @@ import {
8
8
  import "../chunk-G32H73QY.js";
9
9
  import {
10
10
  getLankaProcessRuntime,
11
+ hasLankaScopeResolver,
11
12
  setActiveLankaRuntime,
12
13
  setLankaRuntimeResolver,
13
14
  setLankaScopeResolver
14
- } from "../chunk-XGMXT4XZ.js";
15
+ } from "../chunk-MLKTN4OV.js";
15
16
 
16
17
  // src/_internal/generate-uuid/generateUuid.ts
17
18
  function generateUuid() {
@@ -55,6 +56,7 @@ export {
55
56
  generateUuid,
56
57
  getLankaProcessRuntime,
57
58
  getStringField,
59
+ hasLankaScopeResolver,
58
60
  isRecord,
59
61
  lankaForeignSchemaMessage,
60
62
  lankaValueOrThrow,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/_internal/generate-uuid/generateUuid.ts","../../src/_internal/lanka-foreign-schema-message/lankaForeignSchemaMessage.ts","../../src/_internal/lanka-value-or-throw/lankaValueOrThrow.ts"],"sourcesContent":["export function generateUuid() {\n\tif (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n\t\treturn crypto.randomUUID();\n\t}\n\n\tlet d = new Date().getTime();\n\n\tlet d2 =\n\t\t(typeof performance !== \"undefined\" && performance.now && performance.now() * 1000) || 0;\n\n\treturn \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n\t\tlet r = Math.random() * 16;\n\n\t\tif (d > 0) {\n\t\t\tr = ((d + r) % 16) | 0;\n\t\t\td = Math.floor(d / 16);\n\t\t} else {\n\t\t\tr = ((d2 + r) % 16) | 0;\n\t\t\td2 = Math.floor(d2 / 16);\n\t\t}\n\n\t\treturn (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n\t});\n}\n","/**\n * What to say when a validator is handed a schema from another library.\n *\n * ## Why this is not three sentences in three packages\n *\n * `@lankajs/yup`, `@lankajs/typebox` and `@lankajs/effect` each refuse a schema\n * they do not own, and each has to answer the same follow-up: is this a schema at\n * all, and if so whose? The answer is identical in all three because the\n * SITUATION is identical — only the lead differs, because only the marker each\n * looked for differs.\n *\n * Written three times it was three copies, and `check:composition` said so. Made\n * deliberately different to satisfy the gate it would have been worse: three\n * wordings of one fact, and a consumer who met two of them would be entitled to\n * think they meant different things.\n *\n * ## Why `lanka/internal` and not the facade\n *\n * This tier exists for exactly this: \"a sibling package needs these and must not\n * reach into another package's `src/`\". It promises nothing beyond a patch, which\n * is the right promise for a sentence.\n *\n * A facade `isStandardSchema` was proposed first and refused, for a reason worth\n * keeping: it would answer `true` for every yup schema, and `lankaStandardValidator`\n * throws on every yup schema. A consumer writing `if (isStandardSchema(s))\n * validate(s, …)` would have written the exact bug the family works to prevent,\n * and the name would have told them it was safe.\n */\nexport const lankaForeignSchemaMessage = (schema: unknown, { lead }: { lead: string }): string => {\n\t// Object OR function: arktype's schema is callable, with `~standard` on its\n\t// prototype, and a check reading only objects would tell an arktype user\n\t// their schema is not a schema.\n\tconst indexable =\n\t\tschema !== null && (typeof schema === \"object\" || typeof schema === \"function\");\n\n\tif (indexable && \"~standard\" in schema) {\n\t\treturn (\n\t\t\t`${lead} It does carry \\`~standard\\`, so it belongs to another library in ` +\n\t\t\t\"`modules/validators/` — validate it with that package's validator, or with \" +\n\t\t\t\"`lankaStandardValidator`.\"\n\t\t);\n\t}\n\n\treturn (\n\t\t`${lead} Either it is not a schema at all, or it belongs to a library with its ` +\n\t\t\"own package in `modules/validators/`.\"\n\t);\n};\n","import { LankaValidationError } from \"../../validation/lanka-validation-error/LankaValidationError\";\nimport type { TLankaValidationResult } from \"../../validation/_types/TLankaValidationResult\";\n\n/**\n * The strict path, built from the safe one.\n *\n * `ILankaValidator` publishes two methods over one answer: `validateSafe`\n * returns an outcome, and `validate` is that outcome with the failure raised.\n * Every implementation of the port therefore writes the same five lines, and\n * three of them in this repository did — `check:composition` counted them.\n *\n * It is a GENERIC over the outcome rather than a helper returning `unknown`,\n * which is the whole reason it can be shared: `@lankajs/typebox` returns\n * `Static<TSchema>` and `@lankajs/effect` returns `Schema.Schema.Type<TAlias>`,\n * and a helper that erased those would have cost each package its inference —\n * a worse trade than the duplication it removed.\n *\n * `lanka/internal` because a sibling package needs it and must not reach into\n * core's `src/`. It promises nothing beyond a patch, which is right for five\n * lines that only restate what the port already says.\n */\nexport const lankaValueOrThrow = <TOutput>(\n\tresult: TLankaValidationResult<TOutput>,\n\tcontext: string,\n): TOutput => {\n\tif (result.success) return result.data;\n\n\tthrow new LankaValidationError(\n\t\t`Validation failed for ${context}`,\n\t\tresult.errors,\n\t\tresult.fields,\n\t);\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAAO,SAAS,eAAe;AAC9B,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC7E,WAAO,OAAO,WAAW;AAAA,EAC1B;AAEA,MAAI,KAAI,oBAAI,KAAK,GAAE,QAAQ;AAE3B,MAAI,KACF,OAAO,gBAAgB,eAAe,YAAY,OAAO,YAAY,IAAI,IAAI,OAAS;AAExF,SAAO,uCAAuC,QAAQ,SAAS,SAAU,GAAG;AAC3E,QAAI,IAAI,KAAK,OAAO,IAAI;AAExB,QAAI,IAAI,GAAG;AACV,WAAM,IAAI,KAAK,KAAM;AACrB,UAAI,KAAK,MAAM,IAAI,EAAE;AAAA,IACtB,OAAO;AACN,WAAM,KAAK,KAAK,KAAM;AACtB,WAAK,KAAK,MAAM,KAAK,EAAE;AAAA,IACxB;AAEA,YAAQ,MAAM,MAAM,IAAK,IAAI,IAAO,GAAK,SAAS,EAAE;AAAA,EACrD,CAAC;AACF;;;ACKO,IAAM,4BAA4B,CAAC,QAAiB,EAAE,KAAK,MAAgC;AAIjG,QAAM,YACL,WAAW,SAAS,OAAO,WAAW,YAAY,OAAO,WAAW;AAErE,MAAI,aAAa,eAAe,QAAQ;AACvC,WACC,GAAG,IAAI;AAAA,EAIT;AAEA,SACC,GAAG,IAAI;AAGT;;;AC1BO,IAAM,oBAAoB,CAChC,QACA,YACa;AACb,MAAI,OAAO,QAAS,QAAO,OAAO;AAElC,QAAM,IAAI;AAAA,IACT,yBAAyB,OAAO;AAAA,IAChC,OAAO;AAAA,IACP,OAAO;AAAA,EACR;AACD;","names":[]}
1
+ {"version":3,"sources":["../../src/_internal/generate-uuid/generateUuid.ts","../../src/_internal/lanka-foreign-schema-message/lankaForeignSchemaMessage.ts","../../src/_internal/lanka-value-or-throw/lankaValueOrThrow.ts"],"sourcesContent":["export function generateUuid() {\n\tif (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n\t\treturn crypto.randomUUID();\n\t}\n\n\tlet d = new Date().getTime();\n\n\tlet d2 =\n\t\t(typeof performance !== \"undefined\" && performance.now && performance.now() * 1000) || 0;\n\n\treturn \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n\t\tlet r = Math.random() * 16;\n\n\t\tif (d > 0) {\n\t\t\tr = ((d + r) % 16) | 0;\n\t\t\td = Math.floor(d / 16);\n\t\t} else {\n\t\t\tr = ((d2 + r) % 16) | 0;\n\t\t\td2 = Math.floor(d2 / 16);\n\t\t}\n\n\t\treturn (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n\t});\n}\n","/**\n * What to say when a validator is handed a schema from another library.\n *\n * ## Why this is not three sentences in three packages\n *\n * `@lankajs/yup`, `@lankajs/typebox` and `@lankajs/effect` each refuse a schema\n * they do not own, and each has to answer the same follow-up: is this a schema at\n * all, and if so whose? The answer is identical in all three because the\n * SITUATION is identical — only the lead differs, because only the marker each\n * looked for differs.\n *\n * Written three times it was three copies, and `check:composition` said so. Made\n * deliberately different to satisfy the gate it would have been worse: three\n * wordings of one fact, and a consumer who met two of them would be entitled to\n * think they meant different things.\n *\n * ## Why `lanka/internal` and not the facade\n *\n * This tier exists for exactly this: \"a sibling package needs these and must not\n * reach into another package's `src/`\". It promises nothing beyond a patch, which\n * is the right promise for a sentence.\n *\n * A facade `isStandardSchema` was proposed first and refused, for a reason worth\n * keeping: it would answer `true` for every yup schema, and `lankaStandardValidator`\n * throws on every yup schema. A consumer writing `if (isStandardSchema(s))\n * validate(s, …)` would have written the exact bug the family works to prevent,\n * and the name would have told them it was safe.\n */\nexport const lankaForeignSchemaMessage = (schema: unknown, { lead }: { lead: string }): string => {\n\t// Object OR function: arktype's schema is callable, with `~standard` on its\n\t// prototype, and a check reading only objects would tell an arktype user\n\t// their schema is not a schema.\n\tconst indexable =\n\t\tschema !== null && (typeof schema === \"object\" || typeof schema === \"function\");\n\n\tif (indexable && \"~standard\" in schema) {\n\t\treturn (\n\t\t\t`${lead} It does carry \\`~standard\\`, so it belongs to another library in ` +\n\t\t\t\"`modules/validators/` — validate it with that package's validator, or with \" +\n\t\t\t\"`lankaStandardValidator`.\"\n\t\t);\n\t}\n\n\treturn (\n\t\t`${lead} Either it is not a schema at all, or it belongs to a library with its ` +\n\t\t\"own package in `modules/validators/`.\"\n\t);\n};\n","import { LankaValidationError } from \"../../validation/lanka-validation-error/LankaValidationError\";\nimport type { TLankaValidationResult } from \"../../validation/_types/TLankaValidationResult\";\n\n/**\n * The strict path, built from the safe one.\n *\n * `ILankaValidator` publishes two methods over one answer: `validateSafe`\n * returns an outcome, and `validate` is that outcome with the failure raised.\n * Every implementation of the port therefore writes the same five lines, and\n * three of them in this repository did — `check:composition` counted them.\n *\n * It is a GENERIC over the outcome rather than a helper returning `unknown`,\n * which is the whole reason it can be shared: `@lankajs/typebox` returns\n * `Static<TSchema>` and `@lankajs/effect` returns `Schema.Schema.Type<TAlias>`,\n * and a helper that erased those would have cost each package its inference —\n * a worse trade than the duplication it removed.\n *\n * `lanka/internal` because a sibling package needs it and must not reach into\n * core's `src/`. It promises nothing beyond a patch, which is right for five\n * lines that only restate what the port already says.\n */\nexport const lankaValueOrThrow = <TOutput>(\n\tresult: TLankaValidationResult<TOutput>,\n\tcontext: string,\n): TOutput => {\n\tif (result.success) return result.data;\n\n\tthrow new LankaValidationError(\n\t\t`Validation failed for ${context}`,\n\t\tresult.errors,\n\t\tresult.fields,\n\t);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAAO,SAAS,eAAe;AAC9B,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC7E,WAAO,OAAO,WAAW;AAAA,EAC1B;AAEA,MAAI,KAAI,oBAAI,KAAK,GAAE,QAAQ;AAE3B,MAAI,KACF,OAAO,gBAAgB,eAAe,YAAY,OAAO,YAAY,IAAI,IAAI,OAAS;AAExF,SAAO,uCAAuC,QAAQ,SAAS,SAAU,GAAG;AAC3E,QAAI,IAAI,KAAK,OAAO,IAAI;AAExB,QAAI,IAAI,GAAG;AACV,WAAM,IAAI,KAAK,KAAM;AACrB,UAAI,KAAK,MAAM,IAAI,EAAE;AAAA,IACtB,OAAO;AACN,WAAM,KAAK,KAAK,KAAM;AACtB,WAAK,KAAK,MAAM,KAAK,EAAE;AAAA,IACxB;AAEA,YAAQ,MAAM,MAAM,IAAK,IAAI,IAAO,GAAK,SAAS,EAAE;AAAA,EACrD,CAAC;AACF;;;ACKO,IAAM,4BAA4B,CAAC,QAAiB,EAAE,KAAK,MAAgC;AAIjG,QAAM,YACL,WAAW,SAAS,OAAO,WAAW,YAAY,OAAO,WAAW;AAErE,MAAI,aAAa,eAAe,QAAQ;AACvC,WACC,GAAG,IAAI;AAAA,EAIT;AAEA,SACC,GAAG,IAAI;AAGT;;;AC1BO,IAAM,oBAAoB,CAChC,QACA,YACa;AACb,MAAI,OAAO,QAAS,QAAO,OAAO;AAElC,QAAM,IAAI;AAAA,IACT,yBAAyB,OAAO;AAAA,IAChC,OAAO;AAAA,IACP,OAAO;AAAA,EACR;AACD;","names":[]}