lanka 1.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.
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/ALankaGateway-ExlRGT3D.d.ts +122 -0
- package/dist/ALankaSharedStore-B7uepuuk.d.ts +29 -0
- package/dist/ILankaApiError-zI_2bNs0.d.ts +16 -0
- package/dist/ILankaRuntimeConfig-Vl436GWK.d.ts +62 -0
- package/dist/ILankaScenarioMetadata-Bu-yggTZ.d.ts +14 -0
- package/dist/ILankaScenarioVM-DuCyPoyT.d.ts +260 -0
- package/dist/LankaError-B1HtuIkw.d.ts +86 -0
- package/dist/LankaScenarioLocator-BGQHwf3n.d.ts +144 -0
- package/dist/LankaSharedStoreLocator-MvCpav5F.d.ts +126 -0
- package/dist/TLankaErrorHandler-Yfqtdh1M.d.ts +3 -0
- package/dist/_extend/index.d.ts +34 -0
- package/dist/_extend/index.js +36 -0
- package/dist/_extend/index.js.map +1 -0
- package/dist/_internal/index.d.ts +43 -0
- package/dist/_internal/index.js +34 -0
- package/dist/_internal/index.js.map +1 -0
- package/dist/activeRuntime-FcsSJvUg.d.ts +84 -0
- package/dist/bootstrap/index.d.ts +242 -0
- package/dist/bootstrap/index.js +22 -0
- package/dist/bootstrap/index.js.map +1 -0
- package/dist/chunk-73IVH3C6.js +52 -0
- package/dist/chunk-73IVH3C6.js.map +1 -0
- package/dist/chunk-BGVDPDX4.js +29 -0
- package/dist/chunk-BGVDPDX4.js.map +1 -0
- package/dist/chunk-C2HP7CRD.js +224 -0
- package/dist/chunk-C2HP7CRD.js.map +1 -0
- package/dist/chunk-D27MREPB.js +13 -0
- package/dist/chunk-D27MREPB.js.map +1 -0
- package/dist/chunk-DTO27QFR.js +59 -0
- package/dist/chunk-DTO27QFR.js.map +1 -0
- package/dist/chunk-EWVDJYCC.js +296 -0
- package/dist/chunk-EWVDJYCC.js.map +1 -0
- package/dist/chunk-FIR4XTBL.js +136 -0
- package/dist/chunk-FIR4XTBL.js.map +1 -0
- package/dist/chunk-ILQNYQY5.js +200 -0
- package/dist/chunk-ILQNYQY5.js.map +1 -0
- package/dist/chunk-J5LA3Q6D.js +14 -0
- package/dist/chunk-J5LA3Q6D.js.map +1 -0
- package/dist/chunk-MDZ42XRH.js +10 -0
- package/dist/chunk-MDZ42XRH.js.map +1 -0
- package/dist/chunk-Q3SOVBIJ.js +176 -0
- package/dist/chunk-Q3SOVBIJ.js.map +1 -0
- package/dist/chunk-Q7QESSYF.js +49 -0
- package/dist/chunk-Q7QESSYF.js.map +1 -0
- package/dist/chunk-RKYKK6MN.js +19 -0
- package/dist/chunk-RKYKK6MN.js.map +1 -0
- package/dist/chunk-RYFZCAQ3.js +637 -0
- package/dist/chunk-RYFZCAQ3.js.map +1 -0
- package/dist/chunk-UJEC7H6K.js +39 -0
- package/dist/chunk-UJEC7H6K.js.map +1 -0
- package/dist/chunk-XESL274R.js +23 -0
- package/dist/chunk-XESL274R.js.map +1 -0
- package/dist/chunk-YR4MZXMU.js +13 -0
- package/dist/chunk-YR4MZXMU.js.map +1 -0
- package/dist/config/index.d.ts +63 -0
- package/dist/config/index.js +16 -0
- package/dist/config/index.js.map +1 -0
- package/dist/createLankaScope-Bc_vChRs.d.ts +30 -0
- package/dist/errors/index.d.ts +37 -0
- package/dist/errors/index.js +16 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/gateway/index.d.ts +258 -0
- package/dist/gateway/index.js +438 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/lankaHttpInFlight-Bk1eIuSx.d.ts +43 -0
- package/dist/lankaRequestMiddleware-DAC5kCb7.d.ts +62 -0
- package/dist/lankaStandardValidator-CL-r-zEV.d.ts +34 -0
- package/dist/locator/index.d.ts +282 -0
- package/dist/locator/index.js +67 -0
- package/dist/locator/index.js.map +1 -0
- package/dist/logger/index.d.ts +146 -0
- package/dist/logger/index.js +11 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/mock/index.d.ts +16 -0
- package/dist/mock/index.js +58 -0
- package/dist/mock/index.js.map +1 -0
- package/dist/role/index.d.ts +48 -0
- package/dist/role/index.js +7 -0
- package/dist/role/index.js.map +1 -0
- package/dist/scenario/index.d.ts +204 -0
- package/dist/scenario/index.js +28 -0
- package/dist/scenario/index.js.map +1 -0
- package/dist/validation/index.d.ts +18 -0
- package/dist/validation/index.js +53 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/viewmodel/index.d.ts +478 -0
- package/dist/viewmodel/index.js +701 -0
- package/dist/viewmodel/index.js.map +1 -0
- package/package.json +99 -0
- package/skills/lanka-core/SKILL.md +241 -0
- package/skills/lanka-core/reference.md +1090 -0
- package/skills/lanka-packages/SKILL.md +70 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import {
|
|
2
|
+
lankaScenarioBootstrap
|
|
3
|
+
} from "./chunk-Q3SOVBIJ.js";
|
|
4
|
+
import {
|
|
5
|
+
LankaGatewayLocator,
|
|
6
|
+
LankaScenarioLocator,
|
|
7
|
+
createLankaScope
|
|
8
|
+
} from "./chunk-FIR4XTBL.js";
|
|
9
|
+
import {
|
|
10
|
+
LankaScenarioVMRegistry,
|
|
11
|
+
LankaScenariosRegistry
|
|
12
|
+
} from "./chunk-EWVDJYCC.js";
|
|
13
|
+
import {
|
|
14
|
+
createLankaHost
|
|
15
|
+
} from "./chunk-XESL274R.js";
|
|
16
|
+
import {
|
|
17
|
+
createInFlightCounter
|
|
18
|
+
} from "./chunk-UJEC7H6K.js";
|
|
19
|
+
import {
|
|
20
|
+
LankaSharedStoreLocator,
|
|
21
|
+
LankaSingletonLocator
|
|
22
|
+
} from "./chunk-ILQNYQY5.js";
|
|
23
|
+
import {
|
|
24
|
+
lankaLogger
|
|
25
|
+
} from "./chunk-C2HP7CRD.js";
|
|
26
|
+
import {
|
|
27
|
+
getActiveRuntime,
|
|
28
|
+
setActiveRuntime
|
|
29
|
+
} from "./chunk-BGVDPDX4.js";
|
|
30
|
+
|
|
31
|
+
// src/scenario/event-bus/lanka-event-bus-instance/LankaEventBusInstance.ts
|
|
32
|
+
function resolveReplayCount(request) {
|
|
33
|
+
if (request === void 0 || request === false) return 0;
|
|
34
|
+
if (request === true || request === "last") return 1;
|
|
35
|
+
return Math.max(0, Math.trunc(request));
|
|
36
|
+
}
|
|
37
|
+
function maxReplayDepth(subs) {
|
|
38
|
+
let depth = 0;
|
|
39
|
+
for (const entry of subs) if (entry.replayCount > depth) depth = entry.replayCount;
|
|
40
|
+
return depth;
|
|
41
|
+
}
|
|
42
|
+
var LankaEventBusInstance = class {
|
|
43
|
+
/** Event registry and per-event state — private to each instance. */
|
|
44
|
+
registry = /* @__PURE__ */ new Map();
|
|
45
|
+
/** Middleware registered on this instance. */
|
|
46
|
+
middlewares = [];
|
|
47
|
+
/** Default maximum number of logs kept per event type. */
|
|
48
|
+
defaultMaxLogs = 100;
|
|
49
|
+
/** Internal timers for replay batching (avoid multiple replays in the same tick). */
|
|
50
|
+
pendingReplayTimers = /* @__PURE__ */ new Map();
|
|
51
|
+
/** Whether event logging is enabled. */
|
|
52
|
+
isEnableLogs = false;
|
|
53
|
+
// -------------------------------------------------------------------------
|
|
54
|
+
// Configuration
|
|
55
|
+
// -------------------------------------------------------------------------
|
|
56
|
+
/** Configures the bus, e.g. logging. Called once at application start. */
|
|
57
|
+
enableLogs() {
|
|
58
|
+
this.isEnableLogs = true;
|
|
59
|
+
}
|
|
60
|
+
disableLogs() {
|
|
61
|
+
this.isEnableLogs = false;
|
|
62
|
+
}
|
|
63
|
+
// -------------------------------------------------------------------------
|
|
64
|
+
// Internal
|
|
65
|
+
// -------------------------------------------------------------------------
|
|
66
|
+
/**
|
|
67
|
+
* Ensures the event is registered, registering it if not.
|
|
68
|
+
*
|
|
69
|
+
* @param eventType Event type
|
|
70
|
+
* @param usedBy Who uses it — for the inspector and for finding an owner
|
|
71
|
+
*/
|
|
72
|
+
ensureEvent(eventType, usedBy) {
|
|
73
|
+
let state = this.registry.get(eventType);
|
|
74
|
+
if (!state) {
|
|
75
|
+
const meta = {
|
|
76
|
+
dataType: "any",
|
|
77
|
+
description: `Auto-registered event: ${eventType}`,
|
|
78
|
+
usedBy: usedBy ? [usedBy] : [],
|
|
79
|
+
priority: 0,
|
|
80
|
+
maxLogs: this.defaultMaxLogs
|
|
81
|
+
};
|
|
82
|
+
state = {
|
|
83
|
+
meta,
|
|
84
|
+
logs: [],
|
|
85
|
+
buffer: [],
|
|
86
|
+
subs: []
|
|
87
|
+
};
|
|
88
|
+
this.registry.set(eventType, state);
|
|
89
|
+
} else if (usedBy) {
|
|
90
|
+
const meta = state.meta;
|
|
91
|
+
meta.usedBy = [.../* @__PURE__ */ new Set([...meta.usedBy ?? [], usedBy])];
|
|
92
|
+
}
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
// -------------------------------------------------------------------------
|
|
96
|
+
// Public surface
|
|
97
|
+
// -------------------------------------------------------------------------
|
|
98
|
+
/**
|
|
99
|
+
* Declares an event explicitly, together with its metadata.
|
|
100
|
+
*
|
|
101
|
+
* @param eventType Event type
|
|
102
|
+
* @param metadata What is known about the event and what it is allowed
|
|
103
|
+
*/
|
|
104
|
+
registerEvent(eventType, metadata) {
|
|
105
|
+
const existing = this.registry.get(eventType);
|
|
106
|
+
const meta = {
|
|
107
|
+
dataType: metadata.dataType ?? existing?.meta.dataType ?? "any",
|
|
108
|
+
description: metadata.description ?? existing?.meta.description ?? `Registered event: ${eventType}`,
|
|
109
|
+
// `usedBy` lists everyone using the event and exists precisely to be
|
|
110
|
+
// complete. Replacing it would erase half.
|
|
111
|
+
usedBy: [.../* @__PURE__ */ new Set([...existing?.meta.usedBy ?? [], ...metadata.usedBy ?? []])],
|
|
112
|
+
schema: metadata.schema ?? existing?.meta.schema,
|
|
113
|
+
priority: metadata.priority ?? existing?.meta.priority ?? 0,
|
|
114
|
+
maxLogs: metadata.maxLogs ?? existing?.meta.maxLogs ?? this.defaultMaxLogs,
|
|
115
|
+
replay: metadata.replay ?? existing?.meta.replay
|
|
116
|
+
};
|
|
117
|
+
if (existing) {
|
|
118
|
+
existing.meta = meta;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.registry.set(eventType, { meta, logs: [], buffer: [], subs: [] });
|
|
122
|
+
}
|
|
123
|
+
/** Every registered event with its metadata. */
|
|
124
|
+
getRegisteredEvents() {
|
|
125
|
+
return Array.from(this.registry.entries()).map(([eventType, state]) => ({
|
|
126
|
+
eventType,
|
|
127
|
+
metadata: state.meta
|
|
128
|
+
})).sort((a, b) => a.eventType.localeCompare(b.eventType));
|
|
129
|
+
}
|
|
130
|
+
/** Metadata of one event type. */
|
|
131
|
+
getEventInfo(eventType) {
|
|
132
|
+
return this.registry.get(eventType)?.meta ?? null;
|
|
133
|
+
}
|
|
134
|
+
/** How many live subscriptions this event has. */
|
|
135
|
+
/**
|
|
136
|
+
* How many values sit in the event's buffer.
|
|
137
|
+
*
|
|
138
|
+
* Exists for the test: "the buffer stays empty until someone asks for replay"
|
|
139
|
+
* is a claim about MEMORY, and nothing but asking can check it.
|
|
140
|
+
*/
|
|
141
|
+
getBufferedCount(eventType) {
|
|
142
|
+
return this.registry.get(eventType)?.buffer.length ?? 0;
|
|
143
|
+
}
|
|
144
|
+
getSubscriptions(eventType) {
|
|
145
|
+
return this.registry.get(eventType)?.subs.length ?? 0;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Subscribes a handler to an event type.
|
|
149
|
+
*
|
|
150
|
+
* @param eventType Event type
|
|
151
|
+
* @param callback What to run on dispatch
|
|
152
|
+
* @param options Subscription options:
|
|
153
|
+
* - `priority` — execution order, higher runs earlier;
|
|
154
|
+
* - `replay` — receive what was dispatched before subscribing;
|
|
155
|
+
* - `usedBy` — who subscribed, for the inspector.
|
|
156
|
+
*/
|
|
157
|
+
subscribe(eventType, callback, options) {
|
|
158
|
+
const state = this.ensureEvent(eventType, options?.usedBy);
|
|
159
|
+
const prio = options?.priority ?? state.meta.priority ?? 0;
|
|
160
|
+
const entry = {
|
|
161
|
+
callback,
|
|
162
|
+
priority: prio,
|
|
163
|
+
replayCount: resolveReplayCount(options?.replay)
|
|
164
|
+
};
|
|
165
|
+
const subs = state.subs;
|
|
166
|
+
let low = 0, high = subs.length;
|
|
167
|
+
while (low < high) {
|
|
168
|
+
const mid = low + high >>> 1;
|
|
169
|
+
if (subs[mid].priority < prio) high = mid;
|
|
170
|
+
else low = mid + 1;
|
|
171
|
+
}
|
|
172
|
+
subs.splice(low, 0, entry);
|
|
173
|
+
if (options?.replay) this.scheduleBatchedReplay(eventType, state);
|
|
174
|
+
return () => {
|
|
175
|
+
const at = state.subs.indexOf(entry);
|
|
176
|
+
if (at !== -1) state.subs.splice(at, 1);
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Removes a handler from an event type.
|
|
181
|
+
*
|
|
182
|
+
* @param eventType Event type
|
|
183
|
+
* @param callback A previously subscribed handler
|
|
184
|
+
*/
|
|
185
|
+
unsubscribe(eventType, callback) {
|
|
186
|
+
const state = this.registry.get(eventType);
|
|
187
|
+
if (!state) return;
|
|
188
|
+
const subs = state.subs;
|
|
189
|
+
for (let i = 0; i < subs.length; i++) {
|
|
190
|
+
if (subs[i].callback === callback) {
|
|
191
|
+
subs.splice(i, 1);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Dispatches an event: the middleware chain first, then subscribers.
|
|
198
|
+
*
|
|
199
|
+
* @param eventType Event type
|
|
200
|
+
* @param data Payload
|
|
201
|
+
* @param usedBy Who dispatched — recorded in the metadata
|
|
202
|
+
*/
|
|
203
|
+
dispatch(eventType, data, usedBy) {
|
|
204
|
+
const state = this.ensureEvent(eventType, usedBy);
|
|
205
|
+
const { meta, subs, buffer } = state;
|
|
206
|
+
if (meta.schema && data !== void 0 && !meta.schema(data)) {
|
|
207
|
+
lankaLogger.printScenarioLog(`Invalid data for event "${eventType}":`, data);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
this.logEvent(state, eventType, data);
|
|
211
|
+
const depth = Math.max(resolveReplayCount(meta.replay), maxReplayDepth(subs));
|
|
212
|
+
if (depth > 0) {
|
|
213
|
+
buffer.push(data);
|
|
214
|
+
while (buffer.length > depth) buffer.shift();
|
|
215
|
+
} else if (buffer.length > 0) {
|
|
216
|
+
buffer.length = 0;
|
|
217
|
+
}
|
|
218
|
+
const deliver = () => {
|
|
219
|
+
for (const entry of [...subs]) {
|
|
220
|
+
try {
|
|
221
|
+
entry.callback(data);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
lankaLogger.printScenarioLog(`Error dispatching event "${eventType}":`, err);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
for (const middleware of [...this.middlewares]) {
|
|
228
|
+
let decision;
|
|
229
|
+
try {
|
|
230
|
+
decision = middleware(eventType, data);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
decision = { stop: err instanceof Error ? err.message : String(err) };
|
|
233
|
+
}
|
|
234
|
+
if (decision !== "pass") {
|
|
235
|
+
this.markStopped(state, decision.stop);
|
|
236
|
+
lankaLogger.printScenarioLog(
|
|
237
|
+
`Event "${eventType}" stopped by middleware:`,
|
|
238
|
+
decision.stop
|
|
239
|
+
);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
deliver();
|
|
244
|
+
}
|
|
245
|
+
/** Adds middleware to the shared chain. */
|
|
246
|
+
addMiddleware(mw) {
|
|
247
|
+
this.middlewares.push(mw);
|
|
248
|
+
}
|
|
249
|
+
/** Removes previously added middleware. */
|
|
250
|
+
removeMiddleware(mw) {
|
|
251
|
+
const i = this.middlewares.indexOf(mw);
|
|
252
|
+
if (i !== -1) this.middlewares.splice(i, 1);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Log of dispatched events.
|
|
256
|
+
*
|
|
257
|
+
* @param eventType Limit to one type
|
|
258
|
+
* @param limit How many recent records to return
|
|
259
|
+
*/
|
|
260
|
+
getEventLogs(eventType, limit = 100) {
|
|
261
|
+
if (eventType) {
|
|
262
|
+
return this.registry.get(eventType)?.logs.slice(-limit) ?? [];
|
|
263
|
+
}
|
|
264
|
+
const all = [];
|
|
265
|
+
for (const { logs } of this.registry.values()) all.push(...logs);
|
|
266
|
+
return all.slice(-limit);
|
|
267
|
+
}
|
|
268
|
+
/** Removes an event together with its subscriptions, log and buffer. */
|
|
269
|
+
clearEvent(eventType) {
|
|
270
|
+
this.registry.delete(eventType);
|
|
271
|
+
const t = this.pendingReplayTimers.get(eventType);
|
|
272
|
+
if (t) {
|
|
273
|
+
clearTimeout(t);
|
|
274
|
+
this.pendingReplayTimers.delete(eventType);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/** Removes every event and any scheduled replays. */
|
|
278
|
+
clearAllEvents() {
|
|
279
|
+
this.registry.clear();
|
|
280
|
+
for (const t of this.pendingReplayTimers.values()) clearTimeout(t);
|
|
281
|
+
this.pendingReplayTimers.clear();
|
|
282
|
+
}
|
|
283
|
+
/** Full bus reset: events and middleware. */
|
|
284
|
+
reset() {
|
|
285
|
+
this.clearAllEvents();
|
|
286
|
+
this.middlewares = [];
|
|
287
|
+
this.isEnableLogs = false;
|
|
288
|
+
}
|
|
289
|
+
// -------------------------------------------------------------------------
|
|
290
|
+
// Internal helpers
|
|
291
|
+
// -------------------------------------------------------------------------
|
|
292
|
+
/**
|
|
293
|
+
* Marks the last log record with the reason it was stopped.
|
|
294
|
+
*
|
|
295
|
+
* Writes where `logEvent` just put the event: a stop is not a separate
|
|
296
|
+
* occurrence but this one's outcome.
|
|
297
|
+
*/
|
|
298
|
+
markStopped(state, reason) {
|
|
299
|
+
const last = state.logs.at(-1);
|
|
300
|
+
if (last) last.stoppedBy = reason;
|
|
301
|
+
}
|
|
302
|
+
/** Add an event log entry for debugging/auditing. */
|
|
303
|
+
logEvent(state, eventType, data) {
|
|
304
|
+
if (this.isEnableLogs) {
|
|
305
|
+
const cap = state.meta.maxLogs ?? this.defaultMaxLogs;
|
|
306
|
+
const logs = state.logs;
|
|
307
|
+
const log = {
|
|
308
|
+
eventType,
|
|
309
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
310
|
+
data
|
|
311
|
+
};
|
|
312
|
+
log.stackTrace = new Error().stack;
|
|
313
|
+
logs.push(log);
|
|
314
|
+
if (logs.length > cap) logs.shift();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Schedules replay for late subscribers that asked for it.
|
|
319
|
+
*
|
|
320
|
+
* Replays are batched into one tick: otherwise ten subscriptions to one event
|
|
321
|
+
* would make ten separate passes over the same buffer.
|
|
322
|
+
*/
|
|
323
|
+
scheduleBatchedReplay(eventType, state) {
|
|
324
|
+
if (this.pendingReplayTimers.has(eventType)) return;
|
|
325
|
+
const timer = setTimeout(() => {
|
|
326
|
+
this.pendingReplayTimers.delete(eventType);
|
|
327
|
+
const { buffer, subs } = state;
|
|
328
|
+
if (!buffer.length) return;
|
|
329
|
+
const needReplay = subs.filter((s) => s.replayCount > 0);
|
|
330
|
+
if (!needReplay.length) return;
|
|
331
|
+
for (const entry of needReplay) {
|
|
332
|
+
for (const payload of buffer.slice(-entry.replayCount)) {
|
|
333
|
+
try {
|
|
334
|
+
entry.callback(payload);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
lankaLogger.printScenarioLog(
|
|
337
|
+
`Error dispatching event "${eventType}" (replay):`,
|
|
338
|
+
err
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
for (const entry of needReplay) entry.replayCount = 0;
|
|
344
|
+
}, 0);
|
|
345
|
+
this.pendingReplayTimers.set(eventType, timer);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// src/bootstrap/_internal/create-lanka-plugin-registry/createLankaPluginRegistry.ts
|
|
350
|
+
var createLankaPluginRegistry = (instance) => {
|
|
351
|
+
const removers = /* @__PURE__ */ new Map();
|
|
352
|
+
return {
|
|
353
|
+
use(plugin) {
|
|
354
|
+
if (removers.has(plugin.name)) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
`Plugin "${plugin.name}" is already registered on this lanka instance. Registering twice almost always means two copies of one policy.`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
const uninstall = plugin.install(instance);
|
|
360
|
+
const remove = () => {
|
|
361
|
+
if (!removers.has(plugin.name)) return;
|
|
362
|
+
removers.delete(plugin.name);
|
|
363
|
+
uninstall?.();
|
|
364
|
+
};
|
|
365
|
+
removers.set(plugin.name, remove);
|
|
366
|
+
return remove;
|
|
367
|
+
},
|
|
368
|
+
removeAll() {
|
|
369
|
+
for (const remove of [...removers.values()]) remove();
|
|
370
|
+
removers.clear();
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// src/bootstrap/_factories/create-lanka/createLanka.ts
|
|
376
|
+
function mergeLankaConfig(previous, patch) {
|
|
377
|
+
return {
|
|
378
|
+
// `host` is read field by field: `setConfig({ host: undefined })` through a
|
|
379
|
+
// spread would leave the instance with no host, and every request URL would
|
|
380
|
+
// then carry the string "undefined". Flags stay a spread — there both
|
|
381
|
+
// absent and undefined mean "not set", so nothing can be lost.
|
|
382
|
+
host: patch.host ?? previous.host,
|
|
383
|
+
flags: { ...previous.flags, ...patch.flags }
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function createLanka(config) {
|
|
387
|
+
let runtimeConfig = mergeLankaConfig(
|
|
388
|
+
{},
|
|
389
|
+
{ host: config.host, flags: config.flags }
|
|
390
|
+
);
|
|
391
|
+
let bootstrapped = false;
|
|
392
|
+
const eventBus = new LankaEventBusInstance();
|
|
393
|
+
const scenarios = new LankaScenariosRegistry();
|
|
394
|
+
const viewModels = new LankaScenarioVMRegistry();
|
|
395
|
+
const inFlight = createInFlightCounter();
|
|
396
|
+
const scenarioState = {
|
|
397
|
+
bootstrapped: false,
|
|
398
|
+
initialized: /* @__PURE__ */ new WeakSet()
|
|
399
|
+
};
|
|
400
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
401
|
+
const requestMiddleware = [];
|
|
402
|
+
let requestTimeoutMs;
|
|
403
|
+
const locators = {
|
|
404
|
+
gateways: new LankaGatewayLocator(),
|
|
405
|
+
scenarios: new LankaScenarioLocator(),
|
|
406
|
+
singletons: new LankaSingletonLocator(),
|
|
407
|
+
sharedStores: new LankaSharedStoreLocator()
|
|
408
|
+
};
|
|
409
|
+
const instance = {
|
|
410
|
+
eventBus,
|
|
411
|
+
scenarios,
|
|
412
|
+
viewModels,
|
|
413
|
+
inFlight,
|
|
414
|
+
locators,
|
|
415
|
+
scenarioState,
|
|
416
|
+
requestMiddleware,
|
|
417
|
+
get requestTimeoutMs() {
|
|
418
|
+
return requestTimeoutMs;
|
|
419
|
+
},
|
|
420
|
+
useRequestMiddleware(middleware) {
|
|
421
|
+
requestMiddleware.push(middleware);
|
|
422
|
+
return () => {
|
|
423
|
+
const at = requestMiddleware.indexOf(middleware);
|
|
424
|
+
if (at !== -1) requestMiddleware.splice(at, 1);
|
|
425
|
+
};
|
|
426
|
+
},
|
|
427
|
+
setRequestTimeout(timeoutMs) {
|
|
428
|
+
requestTimeoutMs = timeoutMs;
|
|
429
|
+
},
|
|
430
|
+
resolve(propertyName) {
|
|
431
|
+
return locators.singletons.get(propertyName);
|
|
432
|
+
},
|
|
433
|
+
use(plugin) {
|
|
434
|
+
return plugins.use(plugin);
|
|
435
|
+
},
|
|
436
|
+
createScope() {
|
|
437
|
+
const scope = createLankaScope(locators.singletons);
|
|
438
|
+
scopes.add(scope);
|
|
439
|
+
return scope;
|
|
440
|
+
},
|
|
441
|
+
get config() {
|
|
442
|
+
return runtimeConfig;
|
|
443
|
+
},
|
|
444
|
+
setConfig(patch) {
|
|
445
|
+
runtimeConfig = mergeLankaConfig(runtimeConfig, patch);
|
|
446
|
+
},
|
|
447
|
+
getFlags() {
|
|
448
|
+
return runtimeConfig.flags ?? {};
|
|
449
|
+
},
|
|
450
|
+
isBootstrapped() {
|
|
451
|
+
return bootstrapped;
|
|
452
|
+
},
|
|
453
|
+
activate() {
|
|
454
|
+
setActiveRuntime(instance);
|
|
455
|
+
},
|
|
456
|
+
dispose() {
|
|
457
|
+
teardown();
|
|
458
|
+
requestMiddleware.length = 0;
|
|
459
|
+
requestTimeoutMs = void 0;
|
|
460
|
+
bootstrapped = false;
|
|
461
|
+
},
|
|
462
|
+
async bootstrap(bootstrapConfig = {}) {
|
|
463
|
+
if (bootstrapped) {
|
|
464
|
+
lankaLogger.printBootstrapLog("LANKA ALREADY BOOTSTRAPPED");
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
instance.activate();
|
|
468
|
+
lankaLogger.printBootstrapLog("LANKA BOOTSTRAP START");
|
|
469
|
+
const plan = createExecutionPlan(bootstrapConfig);
|
|
470
|
+
await runSequential(plan.syncTasks, "SYNC TASKS");
|
|
471
|
+
await runParallel(plan.asyncTasks, "ASYNC TASKS");
|
|
472
|
+
await runSequential(plan.postAsyncTasks, "POST-ASYNC TASKS (lankaScenarios)");
|
|
473
|
+
bootstrapped = true;
|
|
474
|
+
lankaLogger.printBootstrapLog("LANKA BOOTSTRAP FINISH");
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
function teardown() {
|
|
478
|
+
for (const scope of scopes) scope.dispose();
|
|
479
|
+
scopes.clear();
|
|
480
|
+
plugins.removeAll();
|
|
481
|
+
viewModels.resetAll();
|
|
482
|
+
scenarios.clear();
|
|
483
|
+
eventBus.reset();
|
|
484
|
+
locators.gateways.clearCache();
|
|
485
|
+
locators.scenarios.clearCache();
|
|
486
|
+
locators.singletons.clearCache();
|
|
487
|
+
locators.sharedStores.clearCache();
|
|
488
|
+
scenarioState.bootstrapped = false;
|
|
489
|
+
scenarioState.initialized = /* @__PURE__ */ new WeakSet();
|
|
490
|
+
if (getActiveRuntime() === instance) setActiveRuntime(null);
|
|
491
|
+
}
|
|
492
|
+
const plugins = createLankaPluginRegistry(instance);
|
|
493
|
+
instance.activate();
|
|
494
|
+
lankaScenarioBootstrap.adoptDeclaredViewModels();
|
|
495
|
+
return instance;
|
|
496
|
+
}
|
|
497
|
+
function createExecutionPlan(config) {
|
|
498
|
+
const syncTasks = [];
|
|
499
|
+
const asyncTasks = [];
|
|
500
|
+
const postAsyncTasks = [];
|
|
501
|
+
config.services?.forEach((service, index) => {
|
|
502
|
+
const task = {
|
|
503
|
+
name: service.name || `Service-${index + 1}`,
|
|
504
|
+
priority: service.priority ?? 0,
|
|
505
|
+
optional: service.optional ?? false,
|
|
506
|
+
timeoutMs: service.timeoutMs,
|
|
507
|
+
execute: service.init
|
|
508
|
+
};
|
|
509
|
+
if (service.sync) syncTasks.push(task);
|
|
510
|
+
else asyncTasks.push(task);
|
|
511
|
+
});
|
|
512
|
+
const scenarioSync = config.scenarios?.sync ?? true;
|
|
513
|
+
const scenarioTask = {
|
|
514
|
+
name: "LankaScenarioBootstrap",
|
|
515
|
+
priority: config.scenarios?.priority ?? 0,
|
|
516
|
+
// The scenario layer is required: without it nothing subscribed to it
|
|
517
|
+
// works, and continuing silently would start an app where half the screens
|
|
518
|
+
// never update.
|
|
519
|
+
optional: false,
|
|
520
|
+
execute: () => {
|
|
521
|
+
lankaScenarioBootstrap.bootstrap();
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
if (scenarioSync) syncTasks.push(scenarioTask);
|
|
525
|
+
else postAsyncTasks.push(scenarioTask);
|
|
526
|
+
syncTasks.sort((a, b) => b.priority - a.priority);
|
|
527
|
+
return { syncTasks, asyncTasks, postAsyncTasks };
|
|
528
|
+
}
|
|
529
|
+
async function runWithDeadline(task) {
|
|
530
|
+
if (task.timeoutMs === void 0) {
|
|
531
|
+
await task.execute();
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
let timer;
|
|
535
|
+
try {
|
|
536
|
+
await Promise.race([
|
|
537
|
+
Promise.resolve(task.execute()),
|
|
538
|
+
new Promise((_resolve, reject) => {
|
|
539
|
+
timer = setTimeout(
|
|
540
|
+
() => reject(
|
|
541
|
+
new Error(
|
|
542
|
+
`Service "${task.name}" did not finish within ${String(task.timeoutMs)}ms`
|
|
543
|
+
)
|
|
544
|
+
),
|
|
545
|
+
task.timeoutMs
|
|
546
|
+
);
|
|
547
|
+
})
|
|
548
|
+
]);
|
|
549
|
+
} finally {
|
|
550
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function handleTaskFailure(task, phaseName, error) {
|
|
554
|
+
lankaLogger.printBootstrapLog(`[${phaseName}] ERROR IN ${task.name}`, formatError(error));
|
|
555
|
+
if (!task.optional) throw error;
|
|
556
|
+
lankaLogger.printBootstrapLog(`[${phaseName}] SKIPPED (optional): ${task.name}`);
|
|
557
|
+
}
|
|
558
|
+
async function runSequential(tasks, phaseName) {
|
|
559
|
+
if (tasks.length === 0) return;
|
|
560
|
+
for (const task of tasks) {
|
|
561
|
+
try {
|
|
562
|
+
lankaLogger.printBootstrapLog(
|
|
563
|
+
`[${phaseName}] EXECUTING: ${task.name} (Priority: ${String(task.priority)})`
|
|
564
|
+
);
|
|
565
|
+
await runWithDeadline(task);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
handleTaskFailure(task, phaseName, error);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async function runParallel(tasks, phaseName) {
|
|
572
|
+
if (tasks.length === 0) return;
|
|
573
|
+
lankaLogger.printBootstrapLog(
|
|
574
|
+
`[${phaseName}] EXECUTING ${String(tasks.length)} TASKS IN PARALLEL`
|
|
575
|
+
);
|
|
576
|
+
const outcomes = await Promise.allSettled(
|
|
577
|
+
tasks.map(async (task) => {
|
|
578
|
+
lankaLogger.printBootstrapLog(`[${phaseName}] EXECUTING: ${task.name}`);
|
|
579
|
+
await runWithDeadline(task);
|
|
580
|
+
})
|
|
581
|
+
);
|
|
582
|
+
for (const [index, outcome] of outcomes.entries()) {
|
|
583
|
+
if (outcome.status === "rejected") {
|
|
584
|
+
handleTaskFailure(tasks[index], phaseName, outcome.reason);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function formatError(error) {
|
|
589
|
+
return error instanceof Error ? error.message : String(error);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/bootstrap/start-lanka/startLanka.ts
|
|
593
|
+
var hostOf = (config) => config.host ?? createLankaHost({ apiBaseUrl: config.apiBaseUrl, ...config.messages });
|
|
594
|
+
var startLanka = async (config = {}) => {
|
|
595
|
+
const lanka = createLanka({ host: hostOf(config), flags: config.flags });
|
|
596
|
+
for (const plugin of config.plugins ?? []) lanka.use(plugin);
|
|
597
|
+
await lanka.bootstrap({ services: config.services, scenarios: config.scenarios });
|
|
598
|
+
return lanka;
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
// src/bootstrap/reset-active-lanka/resetActiveLanka.ts
|
|
602
|
+
function resetActiveLanka() {
|
|
603
|
+
const active = getActiveRuntime();
|
|
604
|
+
if (!active) return;
|
|
605
|
+
active.viewModels.resetAll();
|
|
606
|
+
active.scenarios.clear();
|
|
607
|
+
active.eventBus.clearAllEvents();
|
|
608
|
+
active.scenarioState.bootstrapped = false;
|
|
609
|
+
active.scenarioState.initialized = /* @__PURE__ */ new WeakSet();
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// src/bootstrap/_abstractions/lanka-plugin/ALankaPlugin.ts
|
|
613
|
+
var ALankaPlugin = class {
|
|
614
|
+
/**
|
|
615
|
+
* Removes what `onInstall` put in place.
|
|
616
|
+
*
|
|
617
|
+
* A default that does nothing rather than an abstract member: an abstract one
|
|
618
|
+
* would break every existing subclass the day it was added, and most plugins
|
|
619
|
+
* genuinely have nothing to undo.
|
|
620
|
+
*/
|
|
621
|
+
onUninstall() {
|
|
622
|
+
}
|
|
623
|
+
install(lanka) {
|
|
624
|
+
this.onInstall(lanka);
|
|
625
|
+
return () => {
|
|
626
|
+
this.onUninstall();
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
export {
|
|
632
|
+
createLanka,
|
|
633
|
+
startLanka,
|
|
634
|
+
resetActiveLanka,
|
|
635
|
+
ALankaPlugin
|
|
636
|
+
};
|
|
637
|
+
//# sourceMappingURL=chunk-RYFZCAQ3.js.map
|