lanka 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/{ALankaGateway-ExlRGT3D.d.ts → ALankaGateway-CkW1LbKE.d.ts} +50 -4
- package/dist/{ILankaScenarioMetadata-Bu-yggTZ.d.ts → ILankaScenarioMetadata-GoWWNEQL.d.ts} +1 -1
- package/dist/{ILankaScenarioVM-DuCyPoyT.d.ts → ILankaScenarioVM-DUsI-fSc.d.ts} +116 -3
- package/dist/{LankaScenarioLocator-BGQHwf3n.d.ts → LankaScenarioLocator-D86TIwiu.d.ts} +11 -6
- package/dist/{LankaSharedStoreLocator-MvCpav5F.d.ts → LankaSharedStoreLocator-zS2kLu-S.d.ts} +21 -0
- package/dist/_extend/index.d.ts +7 -7
- package/dist/_extend/index.js +3 -3
- package/dist/_internal/index.d.ts +6 -6
- package/dist/{activeRuntime-FcsSJvUg.d.ts → activeRuntime-B336NU5I.d.ts} +3 -3
- package/dist/bootstrap/index.d.ts +14 -145
- package/dist/bootstrap/index.js +5 -5
- package/dist/{chunk-FIR4XTBL.js → chunk-5MAQVBI2.js} +27 -24
- package/dist/chunk-5MAQVBI2.js.map +1 -0
- package/dist/chunk-D5WKKEIR.js +54 -0
- package/dist/chunk-D5WKKEIR.js.map +1 -0
- package/dist/{chunk-RYFZCAQ3.js → chunk-LMKLLEHA.js} +111 -25
- package/dist/chunk-LMKLLEHA.js.map +1 -0
- package/dist/{chunk-EWVDJYCC.js → chunk-O5ROO7QF.js} +28 -9
- package/dist/chunk-O5ROO7QF.js.map +1 -0
- package/dist/{chunk-ILQNYQY5.js → chunk-UE2C76OR.js} +32 -18
- package/dist/chunk-UE2C76OR.js.map +1 -0
- package/dist/{chunk-Q3SOVBIJ.js → chunk-UGXSGQPW.js} +8 -4
- package/dist/chunk-UGXSGQPW.js.map +1 -0
- package/dist/createLanka-DI1CSy2Q.d.ts +139 -0
- package/dist/{createLankaScope-Bc_vChRs.d.ts → createLankaScope-BiFxNQgl.d.ts} +1 -1
- package/dist/gateway/index.d.ts +77 -67
- package/dist/gateway/index.js +51 -56
- package/dist/gateway/index.js.map +1 -1
- package/dist/index.d.ts +9 -8
- package/dist/index.js +5 -5
- package/dist/locator/index.d.ts +7 -55
- package/dist/locator/index.js +1 -1
- package/dist/scenario/index.d.ts +6 -4
- package/dist/scenario/index.js +2 -2
- package/dist/stream/index.d.ts +386 -0
- package/dist/stream/index.js +287 -0
- package/dist/stream/index.js.map +1 -0
- package/dist/validation/index.js +4 -48
- package/dist/validation/index.js.map +1 -1
- package/dist/viewmodel/index.d.ts +1 -1
- package/dist/viewmodel/index.js +4 -2
- package/dist/viewmodel/index.js.map +1 -1
- package/package.json +7 -3
- package/skills/lanka-core/SKILL.md +1 -1
- package/skills/lanka-core/reference.md +110 -17
- package/skills/lanka-packages/SKILL.md +1 -1
- package/dist/chunk-EWVDJYCC.js.map +0 -1
- package/dist/chunk-FIR4XTBL.js.map +0 -1
- package/dist/chunk-ILQNYQY5.js.map +0 -1
- package/dist/chunk-Q3SOVBIJ.js.map +0 -1
- package/dist/chunk-RYFZCAQ3.js.map +0 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// src/stream/_factories/create-lanka-stream-trigger-context/createLankaStreamTriggerContext.ts
|
|
2
|
+
var createLankaStreamTriggerContext = () => {
|
|
3
|
+
let active = false;
|
|
4
|
+
return {
|
|
5
|
+
isActive: () => active,
|
|
6
|
+
run(fn) {
|
|
7
|
+
const previous = active;
|
|
8
|
+
active = true;
|
|
9
|
+
try {
|
|
10
|
+
return fn();
|
|
11
|
+
} finally {
|
|
12
|
+
active = previous;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/stream/lanka-stream/lankaStream.ts
|
|
19
|
+
var lankaStream = (config) => {
|
|
20
|
+
const stream = config.transport;
|
|
21
|
+
const trigger = createLankaStreamTriggerContext();
|
|
22
|
+
return {
|
|
23
|
+
name: config.name ?? "lanka/stream",
|
|
24
|
+
stream,
|
|
25
|
+
trigger,
|
|
26
|
+
install() {
|
|
27
|
+
const bridges = config.bridges?.({ stream, trigger }) ?? [];
|
|
28
|
+
for (const bridge of bridges) bridge.register();
|
|
29
|
+
if (config.connectOnInstall) stream.connect();
|
|
30
|
+
return () => {
|
|
31
|
+
for (const bridge of bridges) bridge.dispose();
|
|
32
|
+
stream.disconnect();
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/stream/_abstractions/lanka-stream-transport/ALankaStreamTransport.ts
|
|
39
|
+
var ALankaStreamTransport = class {
|
|
40
|
+
listeners = /* @__PURE__ */ new Map();
|
|
41
|
+
reconnectCallbacks = /* @__PURE__ */ new Set();
|
|
42
|
+
config;
|
|
43
|
+
handlers;
|
|
44
|
+
reconnectTimer = null;
|
|
45
|
+
reconnectAttempts = 0;
|
|
46
|
+
/** A connection exists as far as this class is concerned. */
|
|
47
|
+
live = false;
|
|
48
|
+
/**
|
|
49
|
+
* A connection has opened at least once.
|
|
50
|
+
*
|
|
51
|
+
* Separate from `hadError`, and both are needed. A first attempt that FAILS
|
|
52
|
+
* and a second that succeeds is not a reconnection — nothing was ever
|
|
53
|
+
* delivered, so nothing was missed — and announcing one there makes every
|
|
54
|
+
* screen refetch the data it has just loaded.
|
|
55
|
+
*/
|
|
56
|
+
everConnected = false;
|
|
57
|
+
/** The last connection ended badly, so the next `opened` is a RE-connection. */
|
|
58
|
+
hadError = false;
|
|
59
|
+
/** Disconnected EXPLICITLY: everything scheduled after this must die. */
|
|
60
|
+
stopped = false;
|
|
61
|
+
constructor(config = {}) {
|
|
62
|
+
this.config = config;
|
|
63
|
+
this.handlers = {
|
|
64
|
+
opened: () => {
|
|
65
|
+
this.acceptOpen();
|
|
66
|
+
},
|
|
67
|
+
received: (eventType, payload) => {
|
|
68
|
+
this.dispatch(eventType, payload);
|
|
69
|
+
},
|
|
70
|
+
lost: () => {
|
|
71
|
+
this.acceptLoss();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Whether this engine can carry the connection at all.
|
|
77
|
+
*
|
|
78
|
+
* `true` unless a subclass says otherwise, because most transports need
|
|
79
|
+
* nothing the platform might be missing. Where one does, the answer is a
|
|
80
|
+
* `typeof` guard: an engine without the global gets the plugin switched off
|
|
81
|
+
* for the session rather than a `ReferenceError` from inside the framework.
|
|
82
|
+
*/
|
|
83
|
+
isSupported() {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
connect() {
|
|
87
|
+
if (this.live) return;
|
|
88
|
+
if (!this.isSupported()) return;
|
|
89
|
+
this.stopped = false;
|
|
90
|
+
this.live = true;
|
|
91
|
+
try {
|
|
92
|
+
this.open(this.handlers);
|
|
93
|
+
} catch {
|
|
94
|
+
this.live = false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
disconnect() {
|
|
98
|
+
this.stopped = true;
|
|
99
|
+
this.clearReconnect();
|
|
100
|
+
if (!this.live) return;
|
|
101
|
+
this.live = false;
|
|
102
|
+
this.close();
|
|
103
|
+
}
|
|
104
|
+
/** Subscribes to an event type. Returns an unsubscribe function. */
|
|
105
|
+
on(eventType, callback) {
|
|
106
|
+
let callbacks = this.listeners.get(eventType);
|
|
107
|
+
if (!callbacks) {
|
|
108
|
+
callbacks = /* @__PURE__ */ new Set();
|
|
109
|
+
this.listeners.set(eventType, callbacks);
|
|
110
|
+
if (this.live) this.subscribeTo(eventType);
|
|
111
|
+
}
|
|
112
|
+
callbacks.add(callback);
|
|
113
|
+
return () => {
|
|
114
|
+
this.forget(eventType, callback);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Notification after EVERY reconnection. Returns an unsubscribe function. */
|
|
118
|
+
onReconnect(callback) {
|
|
119
|
+
this.reconnectCallbacks.add(callback);
|
|
120
|
+
return () => {
|
|
121
|
+
this.reconnectCallbacks.delete(callback);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Tells the wire that an event type is wanted, where the wire needs telling.
|
|
126
|
+
*
|
|
127
|
+
* Called for every already-known type as soon as a connection opens, and for
|
|
128
|
+
* each new one after that — so a subclass never has to remember what was
|
|
129
|
+
* subscribed before the link came back.
|
|
130
|
+
*/
|
|
131
|
+
subscribeTo(eventType) {
|
|
132
|
+
void eventType;
|
|
133
|
+
}
|
|
134
|
+
/** The reverse, when the last subscriber of a type goes away. */
|
|
135
|
+
unsubscribeFrom(eventType) {
|
|
136
|
+
void eventType;
|
|
137
|
+
}
|
|
138
|
+
/** Every event type somebody is waiting for right now. */
|
|
139
|
+
subscribedEventTypes() {
|
|
140
|
+
return [...this.listeners.keys()];
|
|
141
|
+
}
|
|
142
|
+
acceptOpen() {
|
|
143
|
+
for (const eventType of this.listeners.keys()) this.subscribeTo(eventType);
|
|
144
|
+
if (this.everConnected && this.hadError) {
|
|
145
|
+
for (const callback of [...this.reconnectCallbacks]) callback();
|
|
146
|
+
}
|
|
147
|
+
this.everConnected = true;
|
|
148
|
+
this.hadError = false;
|
|
149
|
+
this.reconnectAttempts = 0;
|
|
150
|
+
}
|
|
151
|
+
acceptLoss() {
|
|
152
|
+
if (!this.live) return;
|
|
153
|
+
this.hadError = true;
|
|
154
|
+
this.live = false;
|
|
155
|
+
this.close();
|
|
156
|
+
void this.scheduleReconnect();
|
|
157
|
+
}
|
|
158
|
+
forget(eventType, callback) {
|
|
159
|
+
const current = this.listeners.get(eventType);
|
|
160
|
+
if (!current) return;
|
|
161
|
+
current.delete(callback);
|
|
162
|
+
if (current.size > 0) return;
|
|
163
|
+
this.listeners.delete(eventType);
|
|
164
|
+
if (this.live) this.unsubscribeFrom(eventType);
|
|
165
|
+
}
|
|
166
|
+
dispatch(eventType, payload) {
|
|
167
|
+
const callbacks = this.listeners.get(eventType);
|
|
168
|
+
if (!callbacks) return;
|
|
169
|
+
for (const callback of [...callbacks]) callback(payload);
|
|
170
|
+
}
|
|
171
|
+
clearReconnect() {
|
|
172
|
+
if (!this.reconnectTimer) return;
|
|
173
|
+
clearTimeout(this.reconnectTimer);
|
|
174
|
+
this.reconnectTimer = null;
|
|
175
|
+
}
|
|
176
|
+
async scheduleReconnect() {
|
|
177
|
+
if (this.stopped) return;
|
|
178
|
+
if (this.reconnectAttempts >= (this.config.maxReconnectAttempts ?? 10)) {
|
|
179
|
+
await this.lastResort();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const first = this.config.reconnectDelayMs ?? 1e3;
|
|
183
|
+
const delay = Math.min(
|
|
184
|
+
first * 2 ** this.reconnectAttempts,
|
|
185
|
+
this.config.maxReconnectDelayMs ?? 3e4
|
|
186
|
+
);
|
|
187
|
+
this.reconnectAttempts += 1;
|
|
188
|
+
this.reconnectTimer = setTimeout(() => {
|
|
189
|
+
this.reconnectTimer = null;
|
|
190
|
+
if (!this.stopped) this.connect();
|
|
191
|
+
}, delay);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The attempts are spent: refresh the session once, or report it lost.
|
|
195
|
+
*
|
|
196
|
+
* `stopped` is re-read after the await. The refresh is a network call of
|
|
197
|
+
* unknown length, and an application that signed the user out while it was in
|
|
198
|
+
* flight must not be handed a connection afterwards.
|
|
199
|
+
*/
|
|
200
|
+
async lastResort() {
|
|
201
|
+
const refreshed = await this.config.refreshAuth?.() ?? false;
|
|
202
|
+
if (this.stopped) return;
|
|
203
|
+
if (!refreshed) {
|
|
204
|
+
this.config.onSessionLost?.();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.reconnectAttempts = 0;
|
|
208
|
+
this.connect();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// src/stream/_abstractions/lanka-stream-bridge/ALankaStreamBridge.ts
|
|
213
|
+
var ALankaStreamBridge = class {
|
|
214
|
+
unsubscribes = [];
|
|
215
|
+
stream;
|
|
216
|
+
trigger;
|
|
217
|
+
// Fields declared explicitly: `erasableSyntaxOnly` forbids parameter
|
|
218
|
+
// properties, the one TypeScript construct that emits code.
|
|
219
|
+
//
|
|
220
|
+
// The constructor is PUBLIC: the class is abstract and cannot be constructed
|
|
221
|
+
// on its own, while `protected` is inherited — and the application's subclass
|
|
222
|
+
// would then be unreachable to the code that creates it.
|
|
223
|
+
constructor(stream, trigger) {
|
|
224
|
+
this.stream = stream;
|
|
225
|
+
this.trigger = trigger;
|
|
226
|
+
}
|
|
227
|
+
/** Removes everything the bridge subscribed to. */
|
|
228
|
+
dispose() {
|
|
229
|
+
for (const unsubscribe of this.unsubscribes) unsubscribe();
|
|
230
|
+
this.unsubscribes.length = 0;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Subscribes to an event type; the handler runs with the "from outside"
|
|
234
|
+
* marker.
|
|
235
|
+
*
|
|
236
|
+
* The marker is set HERE rather than left to each bridge: a handler that
|
|
237
|
+
* forgot it is indistinguishable from a user action, and the difference is
|
|
238
|
+
* silent — the screen simply behaves oddly in a rare case.
|
|
239
|
+
*/
|
|
240
|
+
on(eventType, handler) {
|
|
241
|
+
this.unsubscribes.push(
|
|
242
|
+
this.stream.on(eventType, (payload) => {
|
|
243
|
+
this.trigger.run(() => handler(payload));
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
/** The same for an event with no payload. */
|
|
248
|
+
onSignal(eventType, handler) {
|
|
249
|
+
this.on(eventType, () => handler());
|
|
250
|
+
}
|
|
251
|
+
/** Catch-up for what was missed while disconnected. */
|
|
252
|
+
onReconnect(handler) {
|
|
253
|
+
this.unsubscribes.push(
|
|
254
|
+
this.stream.onReconnect(() => {
|
|
255
|
+
this.trigger.run(handler);
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/stream/_factories/create-lanka-stream-bridge/createLankaStreamBridge.ts
|
|
262
|
+
var createLankaStreamBridge = (register) => (stream, trigger) => {
|
|
263
|
+
class FunctionalBridge extends ALankaStreamBridge {
|
|
264
|
+
register() {
|
|
265
|
+
register({
|
|
266
|
+
on: (eventType, handler) => {
|
|
267
|
+
this.on(eventType, handler);
|
|
268
|
+
},
|
|
269
|
+
onSignal: (eventType, handler) => {
|
|
270
|
+
this.onSignal(eventType, handler);
|
|
271
|
+
},
|
|
272
|
+
onReconnect: (handler) => {
|
|
273
|
+
this.onReconnect(handler);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return new FunctionalBridge(stream, trigger);
|
|
279
|
+
};
|
|
280
|
+
export {
|
|
281
|
+
ALankaStreamBridge,
|
|
282
|
+
ALankaStreamTransport,
|
|
283
|
+
createLankaStreamBridge,
|
|
284
|
+
createLankaStreamTriggerContext,
|
|
285
|
+
lankaStream
|
|
286
|
+
};
|
|
287
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/stream/_factories/create-lanka-stream-trigger-context/createLankaStreamTriggerContext.ts","../../src/stream/lanka-stream/lankaStream.ts","../../src/stream/_abstractions/lanka-stream-transport/ALankaStreamTransport.ts","../../src/stream/_abstractions/lanka-stream-bridge/ALankaStreamBridge.ts","../../src/stream/_factories/create-lanka-stream-bridge/createLankaStreamBridge.ts"],"sourcesContent":["export interface ILankaStreamTriggerContext {\n\t/** Whether a SERVER event handler is running right now. */\n\tisActive: () => boolean;\n\t/** Runs a function marked as triggered by a server event. */\n\trun: <T>(fn: () => T) => T;\n}\n\n/**\n * The \"this change came from outside\" marker.\n *\n * A handler that updates state cannot tell its own change from someone else's:\n * the user pressed a button and then receives the server event about that\n * button. Without the marker the screen notifies the user about their own\n * action, and an optimistic update is rolled back by a \"foreign\" response that\n * in fact confirms it.\n *\n * ## An instance, not a module variable\n *\n * A module-level flag is one per process: two framework instances (a test beside\n * the app) would share it, and one instance's handler would see a marker set by\n * the other.\n *\n * ## Boundaries\n *\n * The marker is SYNCHRONOUS: it holds for the duration of the call and is\n * cleared in `finally`. An async continuation (an `await` inside the handler)\n * no longer sees it — honestly so: after an `await` control has been anywhere,\n * and claiming \"we are still inside a server event\" would be untrue.\n */\nexport const createLankaStreamTriggerContext = (): ILankaStreamTriggerContext => {\n\tlet active = false;\n\n\treturn {\n\t\tisActive: () => active,\n\t\trun<T>(fn: () => T): T {\n\t\t\t// The previous value is saved rather than reset to `false`: otherwise a\n\t\t\t// nested call would clear the outer marker, and the rest of the outer\n\t\t\t// handler would consider itself a user action.\n\t\t\tconst previous = active;\n\t\t\tactive = true;\n\t\t\ttry {\n\t\t\t\treturn fn();\n\t\t\t} finally {\n\t\t\t\tactive = previous;\n\t\t\t}\n\t\t},\n\t};\n};\n","import type { ILankaPlugin } from \"../../bootstrap/ILankaPlugin\";\nimport type { ILankaServerEventTransport } from \"../_interfaces/ILankaServerEventTransport\";\nimport type { ALankaStreamBridge } from \"../_abstractions/lanka-stream-bridge/ALankaStreamBridge\";\nimport {\n\tcreateLankaStreamTriggerContext,\n\ttype ILankaStreamTriggerContext,\n} from \"../_factories/create-lanka-stream-trigger-context/createLankaStreamTriggerContext\";\n\n/** What the bridges factory is handed: the connection, and the marker. */\nexport interface ILankaStreamPluginContext {\n\tstream: ILankaServerEventTransport;\n\ttrigger: ILankaStreamTriggerContext;\n}\n\nexport interface ILankaStreamPluginConfig {\n\t/** The connection this plugin owns for its lifetime. */\n\ttransport: ILankaServerEventTransport;\n\t/**\n\t * The application's bridges, created by it — the framework knows no event type.\n\t *\n\t * A factory rather than ready objects: a bridge needs the transport and the\n\t * \"from outside\" marker, and both belong to THIS plugin installation. The same\n\t * bridge declaration can therefore be handed to two instances in one process.\n\t */\n\tbridges?: (context: ILankaStreamPluginContext) => readonly ALankaStreamBridge[];\n\t/**\n\t * Connect as soon as the plugin is registered. Off by default.\n\t *\n\t * Deliberately off: the stream is opened for an AUTHENTICATED user, and when\n\t * that happens is the application's knowledge. A plugin that connected by\n\t * itself would open a connection on the sign-in screen.\n\t */\n\tconnectOnInstall?: boolean;\n\t/**\n\t * The name the plugin is registered under. Defaults to `lanka/stream`.\n\t *\n\t * A protocol package passes its own npm name, so a duplicate registration\n\t * names the package a reader can go and look at. Registering twice is refused\n\t * by the registry, and two copies of one connection is exactly the mistake\n\t * worth refusing.\n\t */\n\tname?: string;\n}\n\nexport interface ILankaStreamPlugin extends ILankaPlugin {\n\t/** The connection: subscribe, connect, disconnect. */\n\treadonly stream: ILankaServerEventTransport;\n\t/** The \"this change came from the server\" marker. */\n\treadonly trigger: ILankaStreamTriggerContext;\n}\n\n/**\n * A pushing connection wired into the framework's lifetime.\n *\n * Usable directly — hand it any `ILankaServerEventTransport` and your bridges —\n * and it is also what every protocol plugin in this repository is built on, so\n * the four of them cannot disagree about installation order.\n *\n * ## The order in teardown is the whole point\n *\n * Bridges are detached BEFORE the connection is closed. The other order leaves\n * them a window in which a dying connection delivers one last event into\n * scenarios belonging to an instance that is being disposed.\n */\nexport const lankaStream = (config: ILankaStreamPluginConfig): ILankaStreamPlugin => {\n\tconst stream = config.transport;\n\tconst trigger = createLankaStreamTriggerContext();\n\n\treturn {\n\t\tname: config.name ?? \"lanka/stream\",\n\t\tstream,\n\t\ttrigger,\n\t\tinstall() {\n\t\t\tconst bridges = config.bridges?.({ stream, trigger }) ?? [];\n\t\t\tfor (const bridge of bridges) bridge.register();\n\n\t\t\tif (config.connectOnInstall) stream.connect();\n\n\t\t\treturn () => {\n\t\t\t\tfor (const bridge of bridges) bridge.dispose();\n\t\t\t\tstream.disconnect();\n\t\t\t};\n\t\t},\n\t};\n};\n","import type { ILankaServerEventTransport } from \"../../_interfaces/ILankaServerEventTransport\";\nimport type { TLankaStreamEventCallback } from \"../../_types/TLankaStreamEventCallback\";\nimport type { TLankaStreamReconnectCallback } from \"../../_types/TLankaStreamReconnectCallback\";\n\n/** What every pushing connection is configured with, whatever carries it. */\nexport interface ILankaStreamConfig {\n\t/** How many reconnect attempts before giving up. Defaults to 10. */\n\tmaxReconnectAttempts?: number;\n\t/** The first pause between attempts; it doubles from there. Defaults to 1s. */\n\treconnectDelayMs?: number;\n\t/** Ceiling on the pause between attempts. Defaults to 30 seconds. */\n\tmaxReconnectDelayMs?: number;\n\t/**\n\t * Refreshes authorization when attempts are exhausted. `true` means try again.\n\t *\n\t * A function, not a URL: a framework that knew the endpoint would also know\n\t * the response shape and how the session is stored.\n\t */\n\trefreshAuth?: () => Promise<boolean>;\n\t/** The session is lost for good: the application signs the user out. */\n\tonSessionLost?: () => void;\n}\n\n/**\n * How a subclass reports what happened on the wire.\n *\n * Three facts and no more, because they are the three the ladder above branches\n * on. A subclass able to reach further into the base could reset the attempt\n * counter or fire a reconnect by hand, which is exactly the state this class\n * exists to own.\n */\nexport interface ILankaStreamTransportHandlers {\n\t/**\n\t * The connection is up and ready to carry subscriptions.\n\t *\n\t * Called by the subclass when the far end is actually usable — after a\n\t * handshake, not merely after a socket opened — because the base answers it by\n\t * subscribing to every event type somebody is waiting for.\n\t */\n\topened: () => void;\n\t/** A named event arrived. */\n\treceived: (eventType: string, payload: Record<string, unknown>) => void;\n\t/** The connection is gone: dropped, refused, or closed by the far end. */\n\tlost: () => void;\n}\n\n/**\n * The half of a pushing connection that is the same for every protocol.\n *\n * Four transports in this repository — server-sent events, a WebSocket, a\n * `graphql-ws` subscription and a gRPC server stream — differ in how a\n * connection is opened and in how bytes become a named event. They do not\n * differ in anything below:\n *\n * - who is listening to which event type, and dispatch to a COPY of that set;\n * - the reconnect ladder: growing backoff with a ceiling, an attempt count that\n * only resets on a connection that actually opened, one auth refresh at the\n * end of it, and an explicit disconnect that outranks everything scheduled;\n * - `onReconnect` meaning \"you missed something\", so it never fires the first\n * time.\n *\n * A subclass writes `open` and `close`, and `subscribeTo` / `unsubscribeFrom`\n * when the wire has to be told which events are wanted.\n *\n * ## A subclass never says \"connected\"\n *\n * Everything the ladder decides is decided here, off the three handlers. That is\n * what keeps a protocol package free of the bug this shape is prone to:\n * resetting the attempt counter where reconnection STARTS rather than where it\n * SUCCEEDS, after which a ten-attempt ceiling exists, reads as a guard, and can\n * never fire.\n */\nexport abstract class ALankaStreamTransport implements ILankaServerEventTransport {\n\tprivate readonly listeners = new Map<string, Set<TLankaStreamEventCallback>>();\n\tprivate readonly reconnectCallbacks = new Set<TLankaStreamReconnectCallback>();\n\tprivate readonly config: ILankaStreamConfig;\n\tprivate readonly handlers: ILankaStreamTransportHandlers;\n\n\tprivate reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate reconnectAttempts = 0;\n\t/** A connection exists as far as this class is concerned. */\n\tprivate live = false;\n\t/**\n\t * A connection has opened at least once.\n\t *\n\t * Separate from `hadError`, and both are needed. A first attempt that FAILS\n\t * and a second that succeeds is not a reconnection — nothing was ever\n\t * delivered, so nothing was missed — and announcing one there makes every\n\t * screen refetch the data it has just loaded.\n\t */\n\tprivate everConnected = false;\n\t/** The last connection ended badly, so the next `opened` is a RE-connection. */\n\tprivate hadError = false;\n\t/** Disconnected EXPLICITLY: everything scheduled after this must die. */\n\tprivate stopped = false;\n\n\tpublic constructor(config: ILankaStreamConfig = {}) {\n\t\tthis.config = config;\n\t\tthis.handlers = {\n\t\t\topened: () => {\n\t\t\t\tthis.acceptOpen();\n\t\t\t},\n\t\t\treceived: (eventType, payload) => {\n\t\t\t\tthis.dispatch(eventType, payload);\n\t\t\t},\n\t\t\tlost: () => {\n\t\t\t\tthis.acceptLoss();\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Whether this engine can carry the connection at all.\n\t *\n\t * `true` unless a subclass says otherwise, because most transports need\n\t * nothing the platform might be missing. Where one does, the answer is a\n\t * `typeof` guard: an engine without the global gets the plugin switched off\n\t * for the session rather than a `ReferenceError` from inside the framework.\n\t */\n\tpublic isSupported(): boolean {\n\t\treturn true;\n\t}\n\n\tpublic connect(): void {\n\t\tif (this.live) return;\n\t\tif (!this.isSupported()) return;\n\n\t\tthis.stopped = false;\n\t\tthis.live = true;\n\n\t\ttry {\n\t\t\tthis.open(this.handlers);\n\t\t} catch {\n\t\t\t// Some engines expose the API and refuse the connection: a scheme a proxy\n\t\t\t// will not upgrade, a strict CSP, a URL the platform rejects. Give up\n\t\t\t// quietly rather than enter a reconnect loop — retrying what cannot\n\t\t\t// succeed is a storm.\n\t\t\tthis.live = false;\n\t\t}\n\t}\n\n\tpublic disconnect(): void {\n\t\t// A flag rather than \"wind the counter to its limit\": a scheduled reconnect\n\t\t// may be waiting on an auth refresh, and by the time it returns the counter\n\t\t// means nothing — the connection would come up AFTER an explicit disconnect.\n\t\tthis.stopped = true;\n\t\tthis.clearReconnect();\n\t\tif (!this.live) return;\n\n\t\tthis.live = false;\n\t\tthis.close();\n\t}\n\n\t/** Subscribes to an event type. Returns an unsubscribe function. */\n\tpublic on(eventType: string, callback: TLankaStreamEventCallback): () => void {\n\t\tlet callbacks = this.listeners.get(eventType);\n\t\tif (!callbacks) {\n\t\t\tcallbacks = new Set();\n\t\t\tthis.listeners.set(eventType, callbacks);\n\t\t\tif (this.live) this.subscribeTo(eventType);\n\t\t}\n\t\tcallbacks.add(callback);\n\n\t\treturn () => {\n\t\t\tthis.forget(eventType, callback);\n\t\t};\n\t}\n\n\t/** Notification after EVERY reconnection. Returns an unsubscribe function. */\n\tpublic onReconnect(callback: TLankaStreamReconnectCallback): () => void {\n\t\tthis.reconnectCallbacks.add(callback);\n\n\t\treturn () => {\n\t\t\tthis.reconnectCallbacks.delete(callback);\n\t\t};\n\t}\n\n\t/**\n\t * Opens the connection and reports through the handlers.\n\t *\n\t * Called once per attempt. Throwing is allowed and means \"this engine cannot\n\t * do it\": the transport goes quiet instead of retrying.\n\t */\n\tprotected abstract open(handlers: ILankaStreamTransportHandlers): void;\n\n\t/**\n\t * Closes whatever `open` opened.\n\t *\n\t * Called at most ONCE per connection, and never for one `open` did not finish\n\t * — so a subclass may assume it holds what it created. The base owns that\n\t * guarantee (see `acceptLoss`) precisely so a subclass does not carry a flag\n\t * to make it true.\n\t */\n\tprotected abstract close(): void;\n\n\t/**\n\t * Tells the wire that an event type is wanted, where the wire needs telling.\n\t *\n\t * Called for every already-known type as soon as a connection opens, and for\n\t * each new one after that — so a subclass never has to remember what was\n\t * subscribed before the link came back.\n\t */\n\tprotected subscribeTo(eventType: string): void {\n\t\tvoid eventType;\n\t\t/* Most wires deliver everything and need no per-event registration. */\n\t}\n\n\t/** The reverse, when the last subscriber of a type goes away. */\n\tprotected unsubscribeFrom(eventType: string): void {\n\t\tvoid eventType;\n\t\t/* Nothing to take back unless a subclass registered something. */\n\t}\n\n\t/** Every event type somebody is waiting for right now. */\n\tprotected subscribedEventTypes(): readonly string[] {\n\t\treturn [...this.listeners.keys()];\n\t}\n\n\tprivate acceptOpen(): void {\n\t\tfor (const eventType of this.listeners.keys()) this.subscribeTo(eventType);\n\n\t\t// Both conditions, and each rules out a different false announcement. A\n\t\t// connection that never opened before means nothing was missed; no loss\n\t\t// since the last open means a subclass reported `opened` twice on one\n\t\t// connection, which a duplicate handshake acknowledgement does.\n\t\tif (this.everConnected && this.hadError) {\n\t\t\t// A copy: a handler may unsubscribe inside itself.\n\t\t\tfor (const callback of [...this.reconnectCallbacks]) callback();\n\t\t}\n\t\tthis.everConnected = true;\n\t\tthis.hadError = false;\n\n\t\t// The counter resets ONLY here, on a connection that actually opened.\n\t\t// Resetting it in `connect()` would return it to zero on every attempt,\n\t\t// because the reconnect timer calls `connect()`.\n\t\tthis.reconnectAttempts = 0;\n\t}\n\n\tprivate acceptLoss(): void {\n\t\t// A connection already known to be gone reports nothing more, and the guard\n\t\t// belongs HERE rather than in each subclass. Two reasons, and the second is\n\t\t// the one that matters: a browser fires `error` and then `close` on one\n\t\t// dropped socket, so two losses would spend two rungs of the backoff for one\n\t\t// failure — and a loss arriving after an explicit disconnect would reconnect\n\t\t// what the application just gave up. A transport a CONSUMER wrote gets both\n\t\t// for free, which is not true of a flag each subclass has to remember.\n\t\tif (!this.live) return;\n\n\t\tthis.hadError = true;\n\t\tthis.live = false;\n\t\tthis.close();\n\t\tvoid this.scheduleReconnect();\n\t}\n\n\tprivate forget(eventType: string, callback: TLankaStreamEventCallback): void {\n\t\tconst current = this.listeners.get(eventType);\n\t\tif (!current) return;\n\n\t\tcurrent.delete(callback);\n\t\tif (current.size > 0) return;\n\n\t\tthis.listeners.delete(eventType);\n\t\tif (this.live) this.unsubscribeFrom(eventType);\n\t}\n\n\tprivate dispatch(eventType: string, payload: Record<string, unknown>): void {\n\t\tconst callbacks = this.listeners.get(eventType);\n\t\tif (!callbacks) return;\n\n\t\t// A copy: a handler may unsubscribe inside itself, and iterating the live\n\t\t// set would skip a neighbour.\n\t\tfor (const callback of [...callbacks]) callback(payload);\n\t}\n\n\tprivate clearReconnect(): void {\n\t\tif (!this.reconnectTimer) return;\n\n\t\tclearTimeout(this.reconnectTimer);\n\t\tthis.reconnectTimer = null;\n\t}\n\n\tprivate async scheduleReconnect(): Promise<void> {\n\t\tif (this.stopped) return;\n\n\t\tif (this.reconnectAttempts >= (this.config.maxReconnectAttempts ?? 10)) {\n\t\t\tawait this.lastResort();\n\t\t\treturn;\n\t\t}\n\n\t\t// Growing backoff with a ceiling: a steady stream of attempts against a\n\t\t// server that drops the connection immediately is a storm that finishes it.\n\t\tconst first = this.config.reconnectDelayMs ?? 1000;\n\t\tconst delay = Math.min(\n\t\t\tfirst * 2 ** this.reconnectAttempts,\n\t\t\tthis.config.maxReconnectDelayMs ?? 30_000,\n\t\t);\n\t\tthis.reconnectAttempts += 1;\n\n\t\tthis.reconnectTimer = setTimeout(() => {\n\t\t\tthis.reconnectTimer = null;\n\t\t\tif (!this.stopped) this.connect();\n\t\t}, delay);\n\t}\n\n\t/**\n\t * The attempts are spent: refresh the session once, or report it lost.\n\t *\n\t * `stopped` is re-read after the await. The refresh is a network call of\n\t * unknown length, and an application that signed the user out while it was in\n\t * flight must not be handed a connection afterwards.\n\t */\n\tprivate async lastResort(): Promise<void> {\n\t\tconst refreshed = (await this.config.refreshAuth?.()) ?? false;\n\t\tif (this.stopped) return;\n\n\t\tif (!refreshed) {\n\t\t\tthis.config.onSessionLost?.();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts = 0;\n\t\tthis.connect();\n\t}\n}\n","import type { ILankaServerEventTransport } from \"../../_interfaces/ILankaServerEventTransport\";\nimport type { ILankaStreamTriggerContext } from \"../../_factories/create-lanka-stream-trigger-context/createLankaStreamTriggerContext\";\nimport type { TLankaStreamEventCallback } from \"../../_types/TLankaStreamEventCallback\";\n\n/**\n * A server-event -> application-scenario bridge.\n *\n * It KNOWS about the event stream and the \"came from outside\" marker, and knows\n * no concrete event type: which bridges exist is the application's domain.\n *\n * ## Inbound only, even where the wire is two-way\n *\n * A WebSocket can send and a bridge cannot, deliberately. A bridge translates\n * what arrived into a fact the rest of the application already understands, and\n * a bridge that also sends becomes the one object that both starts and finishes\n * a conversation — untestable without a socket, and the place every screen\n * eventually reaches into. Sending belongs to whatever already owns the action:\n * a gateway holding the channel, or a ViewModel calling it.\n *\n * ## Why subscriptions are collected rather than forgotten\n *\n * A bridge that subscribes in its constructor and never unsubscribes cannot\n * notice the problem while it lives exactly as long as the application. In a\n * package it is different — the plugin is removed, the instance is disposed, a\n * dev server reloads the module — and a leftover subscription keeps triggering a\n * dead instance's scenarios.\n */\nexport abstract class ALankaStreamBridge {\n\tprivate readonly unsubscribes: (() => void)[] = [];\n\n\tprotected readonly stream: ILankaServerEventTransport;\n\tprotected readonly trigger: ILankaStreamTriggerContext;\n\n\t// Fields declared explicitly: `erasableSyntaxOnly` forbids parameter\n\t// properties, the one TypeScript construct that emits code.\n\t//\n\t// The constructor is PUBLIC: the class is abstract and cannot be constructed\n\t// on its own, while `protected` is inherited — and the application's subclass\n\t// would then be unreachable to the code that creates it.\n\tpublic constructor(stream: ILankaServerEventTransport, trigger: ILankaStreamTriggerContext) {\n\t\tthis.stream = stream;\n\t\tthis.trigger = trigger;\n\t}\n\n\t/** Subscribes the bridge to its events. Called by the plugin on install. */\n\tpublic abstract register(): void;\n\n\t/** Removes everything the bridge subscribed to. */\n\tpublic dispose(): void {\n\t\tfor (const unsubscribe of this.unsubscribes) unsubscribe();\n\t\tthis.unsubscribes.length = 0;\n\t}\n\n\t/**\n\t * Subscribes to an event type; the handler runs with the \"from outside\"\n\t * marker.\n\t *\n\t * The marker is set HERE rather than left to each bridge: a handler that\n\t * forgot it is indistinguishable from a user action, and the difference is\n\t * silent — the screen simply behaves oddly in a rare case.\n\t */\n\tprotected on(eventType: string, handler: TLankaStreamEventCallback): void {\n\t\tthis.unsubscribes.push(\n\t\t\tthis.stream.on(eventType, (payload) => {\n\t\t\t\tthis.trigger.run(() => handler(payload));\n\t\t\t}),\n\t\t);\n\t}\n\n\t/** The same for an event with no payload. */\n\tprotected onSignal(eventType: string, handler: () => void): void {\n\t\tthis.on(eventType, () => handler());\n\t}\n\n\t/** Catch-up for what was missed while disconnected. */\n\tprotected onReconnect(handler: () => void): void {\n\t\tthis.unsubscribes.push(\n\t\t\tthis.stream.onReconnect(() => {\n\t\t\t\tthis.trigger.run(handler);\n\t\t\t}),\n\t\t);\n\t}\n}\n","import { ALankaStreamBridge } from \"../../_abstractions/lanka-stream-bridge/ALankaStreamBridge\";\nimport type { ILankaStreamBridgeContext } from \"../../_interfaces/ILankaStreamBridgeContext\";\nimport type { ILankaServerEventTransport } from \"../../_interfaces/ILankaServerEventTransport\";\nimport type { ILankaStreamTriggerContext } from \"../create-lanka-stream-trigger-context/createLankaStreamTriggerContext\";\n\n/**\n * A bridge, without writing a class.\n *\n * What a bridge does is one function — subscribe these events, trigger those\n * scenarios — and the class exists to carry the subscriptions and the \"from\n * outside\" marker, which this carries identically: it IS an `ALankaStreamBridge`.\n *\n * The plugin receives what a class-style bridge would give it, `dispose`\n * included, so nothing above knows which style wrote it.\n *\n * Two steps rather than one call: a bridge needs the transport and the marker,\n * and both belong to the plugin INSTALLATION rather than to the module that\n * declares the bridge. So the declaration is written once at module level and\n * the plugin applies it, which is what lets the same bridge be handed to two\n * instances in one process.\n */\nexport const createLankaStreamBridge =\n\t(register: (context: ILankaStreamBridgeContext) => void) =>\n\t(\n\t\tstream: ILankaServerEventTransport,\n\t\ttrigger: ILankaStreamTriggerContext,\n\t): ALankaStreamBridge => {\n\t\tclass FunctionalBridge extends ALankaStreamBridge {\n\t\t\tpublic register(): void {\n\t\t\t\tregister({\n\t\t\t\t\ton: (eventType, handler) => {\n\t\t\t\t\t\tthis.on(eventType, handler);\n\t\t\t\t\t},\n\t\t\t\t\tonSignal: (eventType, handler) => {\n\t\t\t\t\t\tthis.onSignal(eventType, handler);\n\t\t\t\t\t},\n\t\t\t\t\tonReconnect: (handler) => {\n\t\t\t\t\t\tthis.onReconnect(handler);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn new FunctionalBridge(stream, trigger);\n\t};\n"],"mappings":"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","names":[]}
|
package/dist/validation/index.js
CHANGED
|
@@ -1,51 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
constructor(message, errors = []) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.name = "LankaValidationError";
|
|
8
|
-
this.status = 422;
|
|
9
|
-
this.errors = errors.length > 0 ? errors : [message];
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
// src/validation/lanka-standard-validator/lankaStandardValidator.ts
|
|
14
|
-
function describeIssue(issue) {
|
|
15
|
-
const path = (issue.path ?? []).map(
|
|
16
|
-
(segment) => typeof segment === "object" && segment !== null && "key" in segment ? String(segment.key) : String(segment)
|
|
17
|
-
).join(".");
|
|
18
|
-
return path ? `${path}: ${issue.message}` : issue.message;
|
|
19
|
-
}
|
|
20
|
-
function runSync(schema, data) {
|
|
21
|
-
const result = schema["~standard"].validate(data);
|
|
22
|
-
if (result instanceof Promise) {
|
|
23
|
-
throw new LankaValidationError(
|
|
24
|
-
"The schema is asynchronous and the validation port is synchronous. Parse such data by hand: passing it silently would be worse than failing.",
|
|
25
|
-
[]
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
30
|
-
var lankaStandardValidator = Object.freeze({
|
|
31
|
-
validate(schema, data, context) {
|
|
32
|
-
const result = runSync(schema, data);
|
|
33
|
-
if (result.issues) {
|
|
34
|
-
throw new LankaValidationError(
|
|
35
|
-
`Validation failed for ${context}`,
|
|
36
|
-
result.issues.map(describeIssue)
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
return result.value;
|
|
40
|
-
},
|
|
41
|
-
validateSafe(schema, data) {
|
|
42
|
-
const result = runSync(schema, data);
|
|
43
|
-
if (result.issues) {
|
|
44
|
-
return { success: false, errors: result.issues.map(describeIssue) };
|
|
45
|
-
}
|
|
46
|
-
return { success: true, data: result.value };
|
|
47
|
-
}
|
|
48
|
-
});
|
|
1
|
+
import {
|
|
2
|
+
LankaValidationError,
|
|
3
|
+
lankaStandardValidator
|
|
4
|
+
} from "../chunk-D5WKKEIR.js";
|
|
49
5
|
export {
|
|
50
6
|
LankaValidationError,
|
|
51
7
|
lankaStandardValidator
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StoreApi, StateCreator, UseBoundStore } from 'zustand';
|
|
2
|
-
import { a as ILankaScenario, T as TLankaReplayRequest, I as ILankaScenarioVM } from '../ILankaScenarioVM-
|
|
2
|
+
import { a as ILankaScenario, T as TLankaReplayRequest, I as ILankaScenarioVM } from '../ILankaScenarioVM-DUsI-fSc.js';
|
|
3
3
|
import { A as ALankaSharedStore } from '../ALankaSharedStore-B7uepuuk.js';
|
|
4
4
|
import { StoreApi as StoreApi$1 } from 'zustand/vanilla';
|
|
5
5
|
|
package/dist/viewmodel/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
lankaScenarioBootstrap
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-UGXSGQPW.js";
|
|
5
|
+
import "../chunk-O5ROO7QF.js";
|
|
6
6
|
import {
|
|
7
7
|
lankaLogger
|
|
8
8
|
} from "../chunk-C2HP7CRD.js";
|
|
@@ -379,8 +379,10 @@ var lazySlot = (config) => {
|
|
|
379
379
|
}
|
|
380
380
|
};
|
|
381
381
|
};
|
|
382
|
+
var PROMISE_MEMBERS = /* @__PURE__ */ new Set(["then", "catch", "finally"]);
|
|
382
383
|
var forwardEveryMember = (slot) => (_target, property) => {
|
|
383
384
|
if (property === "dispose") return slot.release;
|
|
385
|
+
if (PROMISE_MEMBERS.has(property)) return void 0;
|
|
384
386
|
return (...args) => {
|
|
385
387
|
const built = slot.get();
|
|
386
388
|
const member = built[property];
|