lanka 1.3.0 → 2.0.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 (63) hide show
  1. package/README.md +1 -1
  2. package/dist/ILankaReadableVM-BoPzHEPV.d.ts +72 -0
  3. package/dist/{LankaScenarioLocator-CAENLhDO.d.ts → LankaScenarioLocator-Dki2Es3O.d.ts} +2 -2
  4. package/dist/{LankaSharedStoreLocator-zS2kLu-S.d.ts → LankaSharedStoreLocator-jsz_gnUm.d.ts} +1 -1
  5. package/dist/_extend/index.d.ts +228 -5
  6. package/dist/_extend/index.js +137 -8
  7. package/dist/_extend/index.js.map +1 -1
  8. package/dist/_internal/index.d.ts +3 -3
  9. package/dist/_internal/index.js +9 -3
  10. package/dist/_internal/index.js.map +1 -1
  11. package/dist/{activeRuntime-BYd2D6Lx.d.ts → activeRuntime-BO-qY0yY.d.ts} +50 -3
  12. package/dist/bootstrap/index.d.ts +6 -6
  13. package/dist/bootstrap/index.js +7 -8
  14. package/dist/{chunk-D27MREPB.js → chunk-7DQUF2QR.js} +2 -2
  15. package/dist/{chunk-UJEC7H6K.js → chunk-BHQ2SQ7P.js} +2 -2
  16. package/dist/chunk-EYDMBI5T.js +550 -0
  17. package/dist/chunk-EYDMBI5T.js.map +1 -0
  18. package/dist/{chunk-C2HP7CRD.js → chunk-FII3PW2G.js} +2 -2
  19. package/dist/{chunk-DTO27QFR.js → chunk-H5TIUKRK.js} +2 -2
  20. package/dist/{chunk-NDCJBO47.js → chunk-O5EUTNR6.js} +2 -2
  21. package/dist/{chunk-RKYKK6MN.js → chunk-RUMUFHSL.js} +2 -2
  22. package/dist/{chunk-UBGXDTXC.js → chunk-SZVNPK24.js} +11 -13
  23. package/dist/chunk-SZVNPK24.js.map +1 -0
  24. package/dist/chunk-UDP6IXDS.js +17 -0
  25. package/dist/chunk-UDP6IXDS.js.map +1 -0
  26. package/dist/{chunk-5MAQVBI2.js → chunk-UMPSR4US.js} +3 -3
  27. package/dist/{chunk-BGVDPDX4.js → chunk-XGMXT4XZ.js} +20 -3
  28. package/dist/chunk-XGMXT4XZ.js.map +1 -0
  29. package/dist/config/index.js +3 -3
  30. package/dist/{createLanka-NrlvN-WQ.d.ts → createLanka-x4oSMs7P.d.ts} +2 -2
  31. package/dist/{createLankaScope-BiFxNQgl.d.ts → createLankaScope-DomNbEvb.d.ts} +1 -1
  32. package/dist/errors/index.js +3 -3
  33. package/dist/gateway/index.js +9 -9
  34. package/dist/index.d.ts +5 -5
  35. package/dist/index.js +8 -9
  36. package/dist/locator/index.d.ts +2 -2
  37. package/dist/locator/index.js +2 -2
  38. package/dist/logger/index.js +3 -3
  39. package/dist/mock/index.js +3 -3
  40. package/dist/scenario/index.d.ts +25 -0
  41. package/dist/scenario/index.js +6 -8
  42. package/dist/scenario/index.js.map +1 -1
  43. package/dist/stream/index.d.ts +5 -5
  44. package/dist/viewmodel/index.d.ts +137 -27
  45. package/dist/viewmodel/index.js +94 -126
  46. package/dist/viewmodel/index.js.map +1 -1
  47. package/package.json +3 -4
  48. package/skills/lanka-core/SKILL.md +18 -16
  49. package/skills/lanka-core/reference.md +135 -58
  50. package/skills/lanka-packages/SKILL.md +94 -22
  51. package/dist/chunk-B7EYIAW7.js +0 -216
  52. package/dist/chunk-B7EYIAW7.js.map +0 -1
  53. package/dist/chunk-BGVDPDX4.js.map +0 -1
  54. package/dist/chunk-O5ROO7QF.js +0 -315
  55. package/dist/chunk-O5ROO7QF.js.map +0 -1
  56. package/dist/chunk-UBGXDTXC.js.map +0 -1
  57. /package/dist/{chunk-D27MREPB.js.map → chunk-7DQUF2QR.js.map} +0 -0
  58. /package/dist/{chunk-UJEC7H6K.js.map → chunk-BHQ2SQ7P.js.map} +0 -0
  59. /package/dist/{chunk-C2HP7CRD.js.map → chunk-FII3PW2G.js.map} +0 -0
  60. /package/dist/{chunk-DTO27QFR.js.map → chunk-H5TIUKRK.js.map} +0 -0
  61. /package/dist/{chunk-NDCJBO47.js.map → chunk-O5EUTNR6.js.map} +0 -0
  62. /package/dist/{chunk-RKYKK6MN.js.map → chunk-RUMUFHSL.js.map} +0 -0
  63. /package/dist/{chunk-5MAQVBI2.js.map → chunk-UMPSR4US.js.map} +0 -0
@@ -1,9 +1,9 @@
1
1
  import { L as LankaEventBusInstance, I as ILankaScenarioVM } from './ILankaScenarioVM-DpKFL3iE.js';
2
- import { c as LankaScenariosRegistry, b as LankaScenarioVMRegistry, L as LankaGatewayLocator, a as LankaScenarioLocator } from './LankaScenarioLocator-CAENLhDO.js';
2
+ import { L as LankaScenariosRegistry, a as LankaScenarioVMRegistry, b as LankaGatewayLocator, c as LankaScenarioLocator } from './LankaScenarioLocator-Dki2Es3O.js';
3
3
  import { b as ILankaRuntimeConfig } from './ILankaRuntimeConfig-Vl436GWK.js';
4
4
  import { I as ILankaInFlightCounter } from './lankaHttpInFlight-Bk1eIuSx.js';
5
5
  import { T as TLankaRequestMiddleware } from './lankaRequestMiddleware-DAC5kCb7.js';
6
- import { b as LankaSingletonLocator, L as LankaSharedStoreLocator } from './LankaSharedStoreLocator-zS2kLu-S.js';
6
+ import { L as LankaSingletonLocator, a as LankaSharedStoreLocator } from './LankaSharedStoreLocator-jsz_gnUm.js';
7
7
 
8
8
  /**
9
9
  * The pointer to the framework instance that ambient facades resolve to.
@@ -67,6 +67,26 @@ interface ILankaRuntime {
67
67
  * is the bug: the last request to start would answer for all of them.
68
68
  */
69
69
  type TLankaRuntimeResolver = () => ILankaRuntime | null;
70
+ /**
71
+ * How "which SCOPE is this" is answered, which is a different question.
72
+ *
73
+ * `TLankaRuntimeResolver` answers which framework INSTANCE serves a call, and on
74
+ * a server that is one per request. This answers which unit of work the call
75
+ * belongs to, and the two are not interchangeable: `runInLankaServerScope`
76
+ * creates its instance INSIDE the scope, and `createLanka` activates every
77
+ * instance it builds — so the process pointer and the scope's runtime are the
78
+ * same object during a request, and nothing downstream can tell "inside a
79
+ * scope" from "after one ended".
80
+ *
81
+ * That distinction is what a per-scope lifetime needs. Without it a call made
82
+ * outside every request resolves against the LAST request's runtime and is
83
+ * handed the last stranger's state, which is the failure a scope exists to
84
+ * abolish.
85
+ *
86
+ * Core ships no resolver here either, and knows only that the question exists.
87
+ */
88
+ type TLankaScopeResolver = () => object | null;
89
+ declare function setActiveLankaRuntime(runtime: ILankaRuntime | null): void;
70
90
  /**
71
91
  * Replaces the strategy, or restores the default one with `null`.
72
92
  *
@@ -80,5 +100,32 @@ type TLankaRuntimeResolver = () => ILankaRuntime | null;
80
100
  * one answer.
81
101
  */
82
102
  declare function setLankaRuntimeResolver(resolver: TLankaRuntimeResolver | null): void;
103
+ /**
104
+ * The PROCESS's own instance, read without asking the strategy.
105
+ *
106
+ * `getActiveRuntime` consults the installed resolver and is the answer for
107
+ * almost everything. This is the one question it cannot answer: what a call
108
+ * would have resolved to if nobody had installed a resolver at all.
109
+ *
110
+ * It exists for a resolver that wants to DEFER. A server's resolver answers from
111
+ * its request scope, and a process that also holds an ambient instance — a
112
+ * worker with a cache, a dev server between reloads, a suite between cases —
113
+ * has a right answer outside every scope that the resolver cannot see. Without
114
+ * this, the first request in such a process makes every later ambient call fail
115
+ * for the life of it.
116
+ *
117
+ * Not a fallback inside `getActiveRuntime`, deliberately: a resolver that wants
118
+ * to defer says so, and one that wants a call outside its scope to fail loudly
119
+ * goes on failing loudly. The choice belongs to whoever knows what a scope is.
120
+ */
121
+ declare function getLankaProcessRuntime(): ILankaRuntime | null;
122
+ /**
123
+ * Installs the strategy that says which unit of work a call belongs to.
124
+ *
125
+ * Installed by whoever knows what a scope IS — `@lankajs/host` wraps a request
126
+ * in `AsyncLocalStorage` and answers from its store. A browser installs none,
127
+ * and that is the right answer there: a tab is one scope for its whole life.
128
+ */
129
+ declare function setLankaScopeResolver(resolver: TLankaScopeResolver | null): void;
83
130
 
84
- export { type ILankaRuntime as I, type TLankaRuntimeResolver as T, setLankaRuntimeResolver as s };
131
+ export { type ILankaRuntime as I, type TLankaRuntimeResolver as T, type TLankaScopeResolver as a, setLankaRuntimeResolver as b, setLankaScopeResolver as c, getLankaProcessRuntime as g, setActiveLankaRuntime as s };
@@ -1,16 +1,16 @@
1
- import { c as ILankaPlugin, e as ILankaServiceConfig, I as ILankaBootstrapConfig, a as ILankaInstance } from '../createLanka-NrlvN-WQ.js';
2
- export { b as ILankaInstanceConfig, d as ILankaScenarioBootstrapConfig, f as createLanka } from '../createLanka-NrlvN-WQ.js';
1
+ import { c as ILankaPlugin, e as ILankaServiceConfig, I as ILankaBootstrapConfig, a as ILankaInstance } from '../createLanka-x4oSMs7P.js';
2
+ export { b as ILankaInstanceConfig, d as ILankaScenarioBootstrapConfig, f as createLanka } from '../createLanka-x4oSMs7P.js';
3
3
  import { I as ILankaFlags, a as ILankaHost } from '../ILankaRuntimeConfig-Vl436GWK.js';
4
- export { I as ILankaScope } from '../createLankaScope-BiFxNQgl.js';
5
- import '../activeRuntime-BYd2D6Lx.js';
4
+ export { I as ILankaScope } from '../createLankaScope-DomNbEvb.js';
5
+ import '../activeRuntime-BO-qY0yY.js';
6
6
  import '../ILankaScenarioVM-DpKFL3iE.js';
7
- import '../LankaScenarioLocator-CAENLhDO.js';
7
+ import '../LankaScenarioLocator-Dki2Es3O.js';
8
8
  import '../ILankaScenarioMetadata-Dj4GCqmX.js';
9
9
  import '../ALankaGateway-BrVPaZN5.js';
10
10
  import '../lankaStandardValidator-BUFnysK0.js';
11
11
  import '@standard-schema/spec';
12
12
  import '../ILankaFieldError-D5931-vT.js';
13
- import '../LankaSharedStoreLocator-zS2kLu-S.js';
13
+ import '../LankaSharedStoreLocator-jsz_gnUm.js';
14
14
  import '../ALankaSharedStore-B7uepuuk.js';
15
15
  import 'zustand/vanilla';
16
16
  import '../lankaHttpInFlight-Bk1eIuSx.js';
@@ -3,16 +3,15 @@ import {
3
3
  createLanka,
4
4
  resetActiveLanka,
5
5
  startLanka
6
- } from "../chunk-UBGXDTXC.js";
7
- import "../chunk-B7EYIAW7.js";
8
- import "../chunk-5MAQVBI2.js";
9
- import "../chunk-O5ROO7QF.js";
6
+ } from "../chunk-SZVNPK24.js";
7
+ import "../chunk-UMPSR4US.js";
8
+ import "../chunk-EYDMBI5T.js";
10
9
  import "../chunk-XESL274R.js";
11
- import "../chunk-UJEC7H6K.js";
10
+ import "../chunk-BHQ2SQ7P.js";
12
11
  import "../chunk-UE2C76OR.js";
13
- import "../chunk-C2HP7CRD.js";
14
- import "../chunk-D27MREPB.js";
15
- import "../chunk-BGVDPDX4.js";
12
+ import "../chunk-FII3PW2G.js";
13
+ import "../chunk-7DQUF2QR.js";
14
+ import "../chunk-XGMXT4XZ.js";
16
15
  export {
17
16
  ALankaPlugin,
18
17
  createLanka,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getActiveRuntime
3
- } from "./chunk-BGVDPDX4.js";
3
+ } from "./chunk-XGMXT4XZ.js";
4
4
 
5
5
  // src/config/get-lanka-flags/getLankaFlags.ts
6
6
  function getLankaFlags() {
@@ -10,4 +10,4 @@ function getLankaFlags() {
10
10
  export {
11
11
  getLankaFlags
12
12
  };
13
- //# sourceMappingURL=chunk-D27MREPB.js.map
13
+ //# sourceMappingURL=chunk-7DQUF2QR.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  requireActiveRuntime
3
- } from "./chunk-BGVDPDX4.js";
3
+ } from "./chunk-XGMXT4XZ.js";
4
4
 
5
5
  // src/gateway/inflight/lankaHttpInFlight.ts
6
6
  function createInFlightCounter() {
@@ -36,4 +36,4 @@ export {
36
36
  createInFlightCounter,
37
37
  lankaHttpInFlight
38
38
  };
39
- //# sourceMappingURL=chunk-UJEC7H6K.js.map
39
+ //# sourceMappingURL=chunk-BHQ2SQ7P.js.map
@@ -0,0 +1,550 @@
1
+ import {
2
+ lankaLogger
3
+ } from "./chunk-FII3PW2G.js";
4
+ import {
5
+ getActiveRuntime,
6
+ requireActiveRuntime
7
+ } from "./chunk-XGMXT4XZ.js";
8
+
9
+ // src/scenario/event-bus/_facades/lanka-event-bus/lankaEventBus.ts
10
+ var bus = () => requireActiveRuntime().eventBus;
11
+ var lankaEventBus = Object.freeze({
12
+ enableLogs: () => {
13
+ bus().enableLogs();
14
+ },
15
+ disableLogs: () => {
16
+ bus().disableLogs();
17
+ },
18
+ registerEvent: (eventType, metadata) => {
19
+ bus().registerEvent(eventType, metadata);
20
+ },
21
+ getRegisteredEvents: () => bus().getRegisteredEvents(),
22
+ getEventInfo: (eventType) => bus().getEventInfo(eventType),
23
+ getSubscriptions: (eventType) => bus().getSubscriptions(eventType),
24
+ subscribe: (eventType, callback, options) => bus().subscribe(eventType, callback, options),
25
+ unsubscribe: (eventType, callback) => {
26
+ bus().unsubscribe(eventType, callback);
27
+ },
28
+ dispatch: (eventType, data, usedBy) => {
29
+ bus().dispatch(eventType, data, usedBy);
30
+ },
31
+ addMiddleware: (middleware) => {
32
+ bus().addMiddleware(middleware);
33
+ },
34
+ removeMiddleware: (middleware) => {
35
+ bus().removeMiddleware(middleware);
36
+ },
37
+ addObserver: (observer) => {
38
+ bus().addObserver(observer);
39
+ },
40
+ removeObserver: (observer) => {
41
+ bus().removeObserver(observer);
42
+ },
43
+ getEventLogs: (eventType, limit = 100) => bus().getEventLogs(eventType, limit),
44
+ clearEvent: (eventType) => {
45
+ bus().clearEvent(eventType);
46
+ },
47
+ clearAllEvents: () => {
48
+ bus().clearAllEvents();
49
+ },
50
+ reset: () => {
51
+ bus().reset();
52
+ }
53
+ });
54
+
55
+ // src/scenario/_registries/lanka-scenario-vm-registry/LankaScenarioVMRegistry.ts
56
+ var LankaScenarioVMRegistry = class {
57
+ registeredViewModels = /* @__PURE__ */ new Set();
58
+ /**
59
+ * Public: the registry belongs to a framework instance rather than to the
60
+ * module.
61
+ */
62
+ constructor() {
63
+ }
64
+ /**
65
+ * The active instance's registry, for callers that cannot hold one — the
66
+ * static `LankaScenarioBootstrap`. Instance holders read `lanka.viewModels`.
67
+ */
68
+ static getInstance() {
69
+ return requireActiveRuntime().viewModels;
70
+ }
71
+ /** Whether this ViewModel is already registered. */
72
+ isRegistered(viewModel) {
73
+ return this.registeredViewModels.has(viewModel);
74
+ }
75
+ /**
76
+ * Registers a ViewModel.
77
+ *
78
+ * @returns `false` when it was already registered
79
+ */
80
+ register(viewModel) {
81
+ if (this.registeredViewModels.has(viewModel)) {
82
+ return false;
83
+ }
84
+ this.registeredViewModels.add(viewModel);
85
+ return true;
86
+ }
87
+ /** Removes a ViewModel from the registry. */
88
+ unregister(viewModel) {
89
+ this.registeredViewModels.delete(viewModel);
90
+ }
91
+ /** Every registered ViewModel. */
92
+ getAllViewModels() {
93
+ return Array.from(this.registeredViewModels);
94
+ }
95
+ /**
96
+ * Clears every registered ViewModel. Required by tests.
97
+ */
98
+ clear() {
99
+ this.registeredViewModels.clear();
100
+ }
101
+ /**
102
+ * Unsubscribes every registered ViewModel from its scenarios and clears the
103
+ * registry.
104
+ *
105
+ * Without it tests are not isolated: a subscription leaks from test to test.
106
+ */
107
+ resetAll() {
108
+ this.registeredViewModels.forEach((vm) => {
109
+ try {
110
+ vm.resetScenario();
111
+ } catch {
112
+ }
113
+ });
114
+ this.clear();
115
+ }
116
+ };
117
+
118
+ // src/scenario/_registries/lanka-scenarios-registry/LankaScenariosRegistry.ts
119
+ var release = (scenario) => {
120
+ if (!scenario.cleanup) return;
121
+ try {
122
+ scenario.cleanup();
123
+ } catch (error) {
124
+ lankaLogger.printScenarioLog(`Error cleaning up scenario "${scenario.name}":`, error);
125
+ }
126
+ };
127
+ var LankaScenariosRegistry = class {
128
+ registeredScenarios = /* @__PURE__ */ new Map();
129
+ scenarioMetadata = /* @__PURE__ */ new Map();
130
+ /**
131
+ * Public: the registry belongs to a framework instance rather than to the
132
+ * module.
133
+ */
134
+ constructor() {
135
+ }
136
+ /**
137
+ * The active instance's registry, for callers that cannot hold one:
138
+ * `ALankaScenario` (a base class the consumer extends) and the static
139
+ * `LankaScenarioBootstrap`. Instance holders read `lanka.scenarios`.
140
+ */
141
+ static getInstance() {
142
+ return requireActiveRuntime().scenarios;
143
+ }
144
+ /**
145
+ * Registers a scenario by hand.
146
+ *
147
+ * @param scenario What to register
148
+ * @returns `false` when it was already registered
149
+ */
150
+ register(scenario) {
151
+ if (this.registeredScenarios.has(scenario.name)) {
152
+ return false;
153
+ }
154
+ this.registeredScenarios.set(scenario.name, scenario);
155
+ this.scenarioMetadata.set(scenario.name, {
156
+ scenario,
157
+ name: scenario.name,
158
+ eventType: scenario.eventType,
159
+ dataTypeName: scenario.dataTypeName,
160
+ isRegistered: true
161
+ });
162
+ return true;
163
+ }
164
+ /**
165
+ * Removes a scenario, calling its `cleanup` if it declared one.
166
+ *
167
+ * @param scenarioName Name of the scenario to unregister
168
+ * @returns true if unregistered successfully, false if not found
169
+ */
170
+ unregister(scenarioName) {
171
+ const scenario = this.registeredScenarios.get(scenarioName);
172
+ if (!scenario) {
173
+ return false;
174
+ }
175
+ const metadata = this.scenarioMetadata.get(scenarioName);
176
+ if (metadata) {
177
+ metadata.isRegistered = false;
178
+ }
179
+ this.registeredScenarios.delete(scenarioName);
180
+ release(scenario);
181
+ return true;
182
+ }
183
+ /**
184
+ * Get metadata for a specific scenario
185
+ * @param scenarioName Name of the scenario
186
+ * @returns Scenario metadata or undefined if not found
187
+ */
188
+ getMetadata(scenarioName) {
189
+ return this.scenarioMetadata.get(scenarioName);
190
+ }
191
+ /** Every registered scenario. */
192
+ getAllScenarios() {
193
+ return Array.from(this.registeredScenarios.values());
194
+ }
195
+ /** Metadata of every scenario. */
196
+ getAllMetadata() {
197
+ return Array.from(this.scenarioMetadata.values());
198
+ }
199
+ /** Whether a scenario with this name is registered. */
200
+ isRegistered(scenarioName) {
201
+ return this.registeredScenarios.has(scenarioName);
202
+ }
203
+ /**
204
+ * Collects every scenario that registered itself at construction.
205
+ */
206
+ collectAutoRegisteredScenarios() {
207
+ const autoRegistered = ALankaScenario.getAutoRegisteredScenarios();
208
+ autoRegistered.forEach((scenario) => {
209
+ if (!this.registeredScenarios.has(scenario.name)) {
210
+ this.register(scenario);
211
+ }
212
+ });
213
+ }
214
+ /**
215
+ * A scenario by name.
216
+ *
217
+ * @returns `undefined` when there is none
218
+ */
219
+ getScenarioByName(scenarioName) {
220
+ return this.registeredScenarios.get(scenarioName);
221
+ }
222
+ /** Every registered ViewModel. */
223
+ getAllViewModels() {
224
+ return LankaScenarioVMRegistry.getInstance().getAllViewModels();
225
+ }
226
+ /**
227
+ * Removes every scenario, calling each one's `cleanup`.
228
+ *
229
+ * What `dispose()` and the between-tests reset go through: `initialize` ran
230
+ * for every scenario at bootstrap, and this is the one moment its mirror
231
+ * image can run for all of them.
232
+ */
233
+ clear() {
234
+ const scenarios = [...this.registeredScenarios.values()];
235
+ this.registeredScenarios.clear();
236
+ this.scenarioMetadata.clear();
237
+ for (const scenario of scenarios) release(scenario);
238
+ }
239
+ };
240
+
241
+ // src/scenario/_abstractions/lanka-scenario/ALankaScenario.ts
242
+ var ALankaScenario = class _ALankaScenario {
243
+ /** A subclass sets this to opt out of self-registration. */
244
+ static skipAutoRegistration;
245
+ /**
246
+ * The self-registration pool.
247
+ *
248
+ * Class-level deliberately: a registry of DEFINITIONS, not runtime state. The
249
+ * classes come from one barrel and both framework instances must see the same
250
+ * list — splitting it would be divergence, not isolation.
251
+ */
252
+ static autoRegisteredScenarios = /* @__PURE__ */ new Set();
253
+ /** Registers the scenario itself unless the subclass opted out. */
254
+ constructor() {
255
+ const constructor = this.constructor;
256
+ if (!constructor.skipAutoRegistration) {
257
+ _ALankaScenario.autoRegisteredScenarios.add(this);
258
+ }
259
+ }
260
+ /**
261
+ * Every scenario that registered itself.
262
+ *
263
+ * @internal
264
+ */
265
+ static getAutoRegisteredScenarios() {
266
+ return Array.from(_ALankaScenario.autoRegisteredScenarios);
267
+ }
268
+ /**
269
+ * Clears the auto-registration pool. Required by tests.
270
+ * @internal
271
+ */
272
+ static clearAutoRegisteredScenarios() {
273
+ _ALankaScenario.autoRegisteredScenarios.clear();
274
+ }
275
+ /**
276
+ * Registers the scenario by hand.
277
+ *
278
+ * @returns `false` when it was already registered
279
+ */
280
+ register() {
281
+ return LankaScenariosRegistry.getInstance().register(this);
282
+ }
283
+ /**
284
+ * Removes the scenario from the registry.
285
+ *
286
+ * @returns `false` when it was not there
287
+ */
288
+ unregister() {
289
+ return LankaScenariosRegistry.getInstance().unregister(this.name);
290
+ }
291
+ trigger(data) {
292
+ lankaLogger.printScenarioLog("TRIGGER Scenario", this.name, data);
293
+ lankaEventBus.dispatch(this.eventType, data, this.name);
294
+ }
295
+ /**
296
+ * Subscribes a handler and returns an unsubscribe function.
297
+ *
298
+ * Without the return, unsubscribing is possible only by callback identity, and
299
+ * every ViewModel factory has to keep a map of references for it.
300
+ */
301
+ subscribe(callback, options) {
302
+ lankaLogger.printScenarioLog("SUBSCRIBE Scenario", this.name, this.dataTypeName);
303
+ return lankaEventBus.subscribe(this.eventType, callback, {
304
+ ...options,
305
+ usedBy: options?.usedBy ?? this.name
306
+ });
307
+ }
308
+ };
309
+
310
+ // src/scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap.ts
311
+ import * as ScenariosModule from "@lanka_di/Scenarios";
312
+ var LankaScenarioBootstrap = class {
313
+ /**
314
+ * The bootstrapped flag and the set of initialised VMs live on the INSTANCE.
315
+ *
316
+ * As static fields, a second framework in the same process would exit
317
+ * `bootstrap()` on someone else's flag — never starting at all, with no symptom
318
+ * except that no scenario works.
319
+ */
320
+ get state() {
321
+ return requireActiveRuntime().scenarioState;
322
+ }
323
+ isScenarioLayerBootstrapped() {
324
+ return this.state.bootstrapped;
325
+ }
326
+ forceInstantiateAllScenarios() {
327
+ const pooled = new Set(
328
+ ALankaScenario.getAutoRegisteredScenarios().map((scenario) => scenario.constructor)
329
+ );
330
+ for (const exported of Object.values(ScenariosModule)) {
331
+ if (typeof exported !== "function") continue;
332
+ if (pooled.has(exported)) continue;
333
+ try {
334
+ new exported();
335
+ } catch {
336
+ }
337
+ }
338
+ }
339
+ registerScenarioEvents(scenarios) {
340
+ const registeredEventTypes = /* @__PURE__ */ new Set();
341
+ scenarios.forEach((scenario) => {
342
+ if (!registeredEventTypes.has(scenario.eventType)) {
343
+ registeredEventTypes.add(scenario.eventType);
344
+ lankaLogger.printScenarioLog("REGISTER SCENARIO EVENT", scenario.name);
345
+ lankaEventBus.registerEvent(scenario.eventType, {
346
+ dataType: scenario.dataTypeName,
347
+ description: `Scenario: ${scenario.name}`,
348
+ usedBy: [scenario.name],
349
+ priority: 0
350
+ });
351
+ }
352
+ if (scenario.initialize) {
353
+ lankaLogger.printScenarioLog("INITIALIZE SCENARIO", scenario.name);
354
+ scenario.initialize();
355
+ }
356
+ });
357
+ }
358
+ initializeAlreadyCreatedViewModels() {
359
+ const viewModels = LankaScenarioVMRegistry.getInstance().getAllViewModels();
360
+ viewModels.forEach((vm) => {
361
+ if (!this.state.initialized.has(vm)) {
362
+ this.state.initialized.add(vm);
363
+ vm.initializeScenario();
364
+ }
365
+ });
366
+ }
367
+ /**
368
+ * Brings up the scenario layer.
369
+ *
370
+ * Constructs the scenario classes so they can be collected, declares their
371
+ * events on the bus, calls `initialize()` where present, and initialises
372
+ * ViewModels that already exist — which happens in tests and under unusual
373
+ * import order.
374
+ *
375
+ * Idempotent.
376
+ */
377
+ bootstrap() {
378
+ if (this.state.bootstrapped) return;
379
+ lankaLogger.printScenarioLog("BOOTSTRAP SCENARIOS START");
380
+ this.forceInstantiateAllScenarios();
381
+ const registry = LankaScenariosRegistry.getInstance();
382
+ registry.collectAutoRegisteredScenarios();
383
+ const scenarios = registry.getAllScenarios();
384
+ this.registerScenarioEvents(scenarios);
385
+ this.state.bootstrapped = true;
386
+ this.adoptDeclaredViewModels();
387
+ this.initializeAlreadyCreatedViewModels();
388
+ lankaLogger.printScenarioLog("BOOTSTRAP SCENARIOS FINISH");
389
+ }
390
+ /**
391
+ * Called by a ViewModel factory when the ViewModel declares scenario handlers.
392
+ *
393
+ * Registers the ViewModel and, if the layer is already up, initialises it
394
+ * immediately. Duplicate registration and duplicate initialisation are
395
+ * impossible.
396
+ *
397
+ * @param viewModel What to register
398
+ * @param name Name for the log
399
+ */
400
+ /**
401
+ * ViewModels DECLARED in this process.
402
+ *
403
+ * Module-level deliberately, the same case as the scenario pool: a registry of
404
+ * DEFINITIONS, not runtime state. Splitting it between instances would be
405
+ * divergence, not isolation — the classes come from the same modules and both
406
+ * instances must see one list.
407
+ *
408
+ * Two reasons it exists:
409
+ *
410
+ * 1. Declaring a ViewModel is a declaration, not work. A screen file creates it
411
+ * at module level, and import order decides whether that happens before or
412
+ * after `createLanka()`. Requiring a live instance at declaration time
413
+ * requires an import order the consumer does not control.
414
+ * 2. Instances come one after another: every test creates its own. The list is
415
+ * NOT drained by the first — otherwise the second instance would know no
416
+ * module-level ViewModel and its `dispose()` would not remove their
417
+ * subscriptions, so a test would receive events the previous one subscribed
418
+ * to.
419
+ */
420
+ declaredViewModels = [];
421
+ /**
422
+ * Registers everything declared into a NEW instance.
423
+ *
424
+ * Called twice, and the second call is the one that makes this work on a
425
+ * server: `createLanka` calls it, and so does `bootstrap()`.
426
+ *
427
+ * The reason is an order nobody can change. Where "which instance is active"
428
+ * is answered by a RESOLVER — one instance per request, the shape
429
+ * `@lankajs/host` installs — an instance becomes findable only once the CALLER
430
+ * has put it in its store, and a caller can only do that after `createLanka`
431
+ * has returned. So during creation the answer is honestly "none", and asking
432
+ * for it threw: every server render of an application with a module-level
433
+ * ViewModel failed inside the call that was creating the scope, with a message
434
+ * about running outside one.
435
+ *
436
+ * Skipping is safe precisely because bootstrap adopts again. It is idempotent
437
+ * at the other end too: `attachViewModel` returns early for a ViewModel the
438
+ * registry already holds.
439
+ */
440
+ adoptDeclaredViewModels() {
441
+ if (!getActiveRuntime()) return;
442
+ for (const { viewModel, name } of [...this.declaredViewModels]) {
443
+ this.attachViewModel(viewModel, name);
444
+ }
445
+ }
446
+ /**
447
+ * Whether the ViewModel being built belongs to ONE scope.
448
+ *
449
+ * A module-level ViewModel is declared once and adopted by every instance
450
+ * ever created, which is what `declaredViewModels` is for and why it is never
451
+ * drained. A SCOPED one is the opposite: it belongs to the request that built
452
+ * it and to nothing after.
453
+ *
454
+ * Declaring one would be the defect this flag exists to prevent, and it is
455
+ * worth spelling out because nothing would look wrong. The array grows by an
456
+ * entry per request for the life of the process, and request N+1 ADOPTS
457
+ * request N's ViewModel — subscribing it to N+1's scenarios, so a dispatch
458
+ * runs N's handlers against N's gateways and writes into N's store. Every
459
+ * suite stays green and the leak is one user seeing another's board.
460
+ */
461
+ isBuildingScoped = false;
462
+ /**
463
+ * Runs `build` with its declarations kept OUT of the process-wide list.
464
+ *
465
+ * Called by `resolveLankaVM` and by nothing else. A narrow seam rather than a
466
+ * parameter on every factory: the nine ways to build a ViewModel would each
467
+ * have to carry a flag through to `registerViewModel`, and eight of them have
468
+ * no business knowing what a scope is.
469
+ */
470
+ buildScoped(build) {
471
+ const before = this.isBuildingScoped;
472
+ this.isBuildingScoped = true;
473
+ try {
474
+ return build();
475
+ } finally {
476
+ this.isBuildingScoped = before;
477
+ }
478
+ }
479
+ registerViewModel(viewModel, name) {
480
+ if (!this.isBuildingScoped) {
481
+ const isKnown = this.declaredViewModels.some(
482
+ (declared) => declared.viewModel === viewModel
483
+ );
484
+ if (!isKnown) this.declaredViewModels.push({ viewModel, name });
485
+ }
486
+ if (!getActiveRuntime()) return;
487
+ this.attachViewModel(viewModel, name);
488
+ }
489
+ attachViewModel(viewModel, name) {
490
+ const registry = LankaScenarioVMRegistry.getInstance();
491
+ if (registry.isRegistered(viewModel)) {
492
+ if (name) {
493
+ lankaLogger.printViewModelLog("VM Already Registered (skipped)", name);
494
+ }
495
+ return;
496
+ }
497
+ registry.register(viewModel);
498
+ if (name) {
499
+ lankaLogger.printViewModelLog("Register Scenario VM", name);
500
+ }
501
+ if (this.state.bootstrapped) {
502
+ if (!this.state.initialized.has(viewModel)) {
503
+ this.state.initialized.add(viewModel);
504
+ viewModel.initializeScenario();
505
+ }
506
+ }
507
+ }
508
+ /**
509
+ * Resets the whole scenario layer. Without it tests are not isolated.
510
+ *
511
+ * Clears the self-registration list, the scenario registry with its metadata,
512
+ * the ViewModel registry together with all their subscriptions, and the bus —
513
+ * events and
514
+ * middleware.
515
+ *
516
+ * What it does NOT clear by default is which ViewModels were DECLARED, and
517
+ * that default is load-bearing: a module-level ViewModel is built once per
518
+ * process, so the declaration is the only thing that lets a second instance
519
+ * find it. Drop it and its handlers bind to nothing, silently, for the rest of
520
+ * the process — see `declaredViewModels`.
521
+ *
522
+ * `withDeclarations` is for the case that default gets wrong: a suite that
523
+ * builds ViewModels inside test BODIES. Those are declared like any other and
524
+ * nothing un-declares them, so the next bootstrap re-adopts every one ever
525
+ * built and a finished test's handlers run again — against the gateway that
526
+ * test built, which is somebody else's double. Forgetting is not a tombstone:
527
+ * a ViewModel declared again afterwards is adopted again.
528
+ *
529
+ * @param config `withDeclarations` also forgets which ViewModels exist
530
+ */
531
+ reset(config = {}) {
532
+ ALankaScenario.clearAutoRegisteredScenarios();
533
+ LankaScenariosRegistry.getInstance().clear();
534
+ LankaScenarioVMRegistry.getInstance().resetAll();
535
+ lankaEventBus.clearAllEvents();
536
+ this.state.bootstrapped = false;
537
+ this.state.initialized = /* @__PURE__ */ new WeakSet();
538
+ if (config.withDeclarations) this.declaredViewModels.length = 0;
539
+ }
540
+ };
541
+ var lankaScenarioBootstrap = new LankaScenarioBootstrap();
542
+
543
+ export {
544
+ lankaEventBus,
545
+ ALankaScenario,
546
+ LankaScenarioVMRegistry,
547
+ LankaScenariosRegistry,
548
+ lankaScenarioBootstrap
549
+ };
550
+ //# sourceMappingURL=chunk-EYDMBI5T.js.map