lanka 1.2.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.
- package/README.md +3 -1
- package/dist/{ALankaGateway-CkW1LbKE.d.ts → ALankaGateway-BrVPaZN5.d.ts} +1 -1
- package/dist/ILankaFieldError-D5931-vT.d.ts +34 -0
- package/dist/ILankaReadableVM-BoPzHEPV.d.ts +72 -0
- package/dist/{ILankaScenarioMetadata-GoWWNEQL.d.ts → ILankaScenarioMetadata-Dj4GCqmX.d.ts} +1 -1
- package/dist/{ILankaScenarioVM-DUsI-fSc.d.ts → ILankaScenarioVM-DpKFL3iE.d.ts} +5 -4
- package/dist/{LankaError-xpI-qj35.d.ts → LankaError-D6RWNyLM.d.ts} +18 -0
- package/dist/{LankaScenarioLocator-D86TIwiu.d.ts → LankaScenarioLocator-Dki2Es3O.d.ts} +5 -5
- package/dist/{LankaSharedStoreLocator-zS2kLu-S.d.ts → LankaSharedStoreLocator-jsz_gnUm.d.ts} +1 -1
- package/dist/_extend/index.d.ts +233 -9
- package/dist/_extend/index.js +137 -8
- package/dist/_extend/index.js.map +1 -1
- package/dist/_internal/index.d.ts +62 -9
- package/dist/_internal/index.js +34 -3
- package/dist/_internal/index.js.map +1 -1
- package/dist/{activeRuntime-B336NU5I.d.ts → activeRuntime-BO-qY0yY.d.ts} +51 -4
- package/dist/bootstrap/index.d.ts +11 -10
- package/dist/bootstrap/index.js +7 -8
- package/dist/cache/index.d.ts +99 -0
- package/dist/cache/index.js +1 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/{chunk-D27MREPB.js → chunk-7DQUF2QR.js} +2 -2
- package/dist/{chunk-UJEC7H6K.js → chunk-BHQ2SQ7P.js} +2 -2
- package/dist/chunk-CRIRTOLB.js +27 -0
- package/dist/chunk-CRIRTOLB.js.map +1 -0
- package/dist/chunk-EYDMBI5T.js +550 -0
- package/dist/chunk-EYDMBI5T.js.map +1 -0
- package/dist/{chunk-C2HP7CRD.js → chunk-FII3PW2G.js} +2 -2
- package/dist/{chunk-Q7QESSYF.js → chunk-G32H73QY.js} +10 -1
- package/dist/chunk-G32H73QY.js.map +1 -0
- package/dist/{chunk-DTO27QFR.js → chunk-H5TIUKRK.js} +2 -2
- package/dist/chunk-HVSQOMRE.js +69 -0
- package/dist/chunk-HVSQOMRE.js.map +1 -0
- package/dist/{chunk-73IVH3C6.js → chunk-O5EUTNR6.js} +3 -3
- package/dist/{chunk-RKYKK6MN.js → chunk-RUMUFHSL.js} +2 -2
- package/dist/{chunk-G3I7QIZR.js → chunk-SZVNPK24.js} +21 -14
- package/dist/chunk-SZVNPK24.js.map +1 -0
- package/dist/chunk-UDP6IXDS.js +17 -0
- package/dist/chunk-UDP6IXDS.js.map +1 -0
- package/dist/{chunk-5MAQVBI2.js → chunk-UMPSR4US.js} +3 -3
- package/dist/{chunk-BGVDPDX4.js → chunk-XGMXT4XZ.js} +20 -3
- package/dist/chunk-XGMXT4XZ.js.map +1 -0
- package/dist/config/index.js +3 -3
- package/dist/{createLanka-DI1CSy2Q.d.ts → createLanka-x4oSMs7P.d.ts} +2 -2
- package/dist/{createLankaScope-BiFxNQgl.d.ts → createLankaScope-DomNbEvb.d.ts} +1 -1
- package/dist/errors/index.d.ts +22 -3
- package/dist/errors/index.js +10 -5
- package/dist/errors/index.js.map +1 -1
- package/dist/gateway/index.d.ts +4 -3
- package/dist/gateway/index.js +9 -8
- package/dist/gateway/index.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.js +9 -10
- package/dist/{lankaStandardValidator-CL-r-zEV.d.ts → lankaStandardValidator-BUFnysK0.d.ts} +10 -0
- package/dist/locator/index.d.ts +5 -4
- package/dist/locator/index.js +2 -2
- package/dist/logger/index.js +3 -3
- package/dist/mock/index.js +3 -3
- package/dist/scenario/index.d.ts +81 -6
- package/dist/scenario/index.js +6 -8
- package/dist/scenario/index.js.map +1 -1
- package/dist/storage/index.d.ts +120 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/stream/index.d.ts +10 -9
- package/dist/validation/index.d.ts +7 -4
- package/dist/validation/index.js +5 -3
- package/dist/viewmodel/index.d.ts +188 -32
- package/dist/viewmodel/index.js +167 -171
- package/dist/viewmodel/index.js.map +1 -1
- package/package.json +11 -4
- package/skills/lanka-core/SKILL.md +70 -10
- package/skills/lanka-core/reference.md +417 -34
- package/skills/lanka-packages/SKILL.md +94 -22
- package/dist/chunk-BGVDPDX4.js.map +0 -1
- package/dist/chunk-G3I7QIZR.js.map +0 -1
- package/dist/chunk-GV5DUYST.js +0 -64
- package/dist/chunk-GV5DUYST.js.map +0 -1
- package/dist/chunk-O5ROO7QF.js +0 -315
- package/dist/chunk-O5ROO7QF.js.map +0 -1
- package/dist/chunk-Q7QESSYF.js.map +0 -1
- package/dist/chunk-UGXSGQPW.js +0 -180
- package/dist/chunk-UGXSGQPW.js.map +0 -1
- /package/dist/{chunk-D27MREPB.js.map → chunk-7DQUF2QR.js.map} +0 -0
- /package/dist/{chunk-UJEC7H6K.js.map → chunk-BHQ2SQ7P.js.map} +0 -0
- /package/dist/{chunk-C2HP7CRD.js.map → chunk-FII3PW2G.js.map} +0 -0
- /package/dist/{chunk-DTO27QFR.js.map → chunk-H5TIUKRK.js.map} +0 -0
- /package/dist/{chunk-73IVH3C6.js.map → chunk-O5EUTNR6.js.map} +0 -0
- /package/dist/{chunk-RKYKK6MN.js.map → chunk-RUMUFHSL.js.map} +0 -0
- /package/dist/{chunk-5MAQVBI2.js.map → chunk-UMPSR4US.js.map} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lanka-packages
|
|
3
|
-
description: Choose which lanka package solves a problem, and find its skill. Use when a lanka application needs a capability it does not have yet — storage, realtime,
|
|
3
|
+
description: Choose which lanka package solves a problem, and find its skill. Use when a lanka application needs a capability it does not have yet — reading a ViewModel from a React, Vue, Svelte, Solid or Angular screen, storage and its engine, realtime over SSE or WebSocket, GraphQL or gRPC-Web, a read cache, response validation, prefetching, optimistic updates, list handling, cookies, start-up stages, a devtool, or living inside Next, Nuxt, SvelteKit or Expo — or when deciding whether something belongs in application code at all.
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
6
|
author: lankajs
|
|
7
7
|
package: lanka
|
|
8
|
-
version: "
|
|
8
|
+
version: "2.0.0"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# lanka — which package, and what it costs
|
|
@@ -19,24 +19,91 @@ has a skill of its own with the detail.
|
|
|
19
19
|
|
|
20
20
|
## By the problem
|
|
21
21
|
|
|
22
|
-
| The problem | The package
|
|
23
|
-
| ------------------------------------------------------- |
|
|
24
|
-
| screens, data, state, events | `lanka`
|
|
25
|
-
| stale data after several refreshes; bursts; polling | `@lankajs/async`
|
|
26
|
-
| a button that must not double-fire; instant feedback | `@lankajs/optimistic`
|
|
27
|
-
| a table: sort, filter, paginate, stop re-rendering rows | `@lankajs/collection`
|
|
28
|
-
| localStorage, IndexedDB, encryption, persisted state | `@lankajs/storage`
|
|
29
|
-
| avatars and thumbnails that reload or flicker | `@lankajs/blob-cache`
|
|
30
|
-
| cookies; stale caches after a new build shipped | `@lankajs/browser`
|
|
31
|
-
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
|
22
|
+
| The problem | The package | Its skill |
|
|
23
|
+
| ------------------------------------------------------- | --------------------- | ------------------ |
|
|
24
|
+
| screens, data, state, events | `lanka` | `lanka-core` |
|
|
25
|
+
| stale data after several refreshes; bursts; polling | `@lankajs/async` | `lanka-async` |
|
|
26
|
+
| a button that must not double-fire; instant feedback | `@lankajs/optimistic` | `lanka-optimistic` |
|
|
27
|
+
| a table: sort, filter, paginate, stop re-rendering rows | `@lankajs/collection` | `lanka-collection` |
|
|
28
|
+
| localStorage, IndexedDB, encryption, persisted state | `@lankajs/storage` | `lanka-storage` |
|
|
29
|
+
| avatars and thumbnails that reload or flicker | `@lankajs/blob-cache` | `lanka-blob-cache` |
|
|
30
|
+
| cookies; stale caches after a new build shipped | `@lankajs/browser` | `lanka-browser` |
|
|
31
|
+
| Next, React Router, TanStack Start or Expo around lanka | `@lankajs/host` | `lanka-host` |
|
|
32
|
+
|
|
33
|
+
## Reading a ViewModel from a screen
|
|
34
|
+
|
|
35
|
+
Core imports no UI library, so this is the one package a rendering application
|
|
36
|
+
always adds. Install the one for its framework and no other — they are
|
|
37
|
+
alternatives, not layers:
|
|
38
|
+
|
|
39
|
+
| The framework | The package | Its skill |
|
|
40
|
+
| ------------------- | ------------------ | --------------- |
|
|
41
|
+
| React, React Native | `@lankajs/react` | `lanka-react` |
|
|
42
|
+
| Vue, Nuxt | `@lankajs/vue` | `lanka-vue` |
|
|
43
|
+
| Svelte, SvelteKit | `@lankajs/svelte` | `lanka-svelte` |
|
|
44
|
+
| Solid | `@lankajs/solid` | `lanka-solid` |
|
|
45
|
+
| Angular | `@lankajs/angular` | `lanka-angular` |
|
|
46
|
+
|
|
47
|
+
All five publish `useLankaVM`, and the access tracking is core's rather than
|
|
48
|
+
each binding's — so what a screen re-renders for is the same answer in every
|
|
49
|
+
framework. What differs is what the call ANSWERS: the state itself in React, a
|
|
50
|
+
`ShallowRef` in Vue, getters in Svelte, an `Accessor` in Solid, a `Signal` in
|
|
51
|
+
Angular.
|
|
52
|
+
|
|
53
|
+
Nothing else needs one. Gateways, scenarios, the locator and
|
|
54
|
+
`viewModel.getState()` are plain calls with no view in them, and they run on a
|
|
55
|
+
server unchanged.
|
|
56
|
+
|
|
57
|
+
## What goes on the wire
|
|
58
|
+
|
|
59
|
+
Everything here is a plugin: core declares the extension point, and the plugin is
|
|
60
|
+
registered with `lanka.use(...)`.
|
|
61
|
+
|
|
62
|
+
| The problem | The package | Its skill |
|
|
63
|
+
| ------------------------------------------------- | --------------------------------- | ----------------------- |
|
|
64
|
+
| retry, idempotency, timeouts, auth refresh, CSRF | `@lankajs/plugin-http` | `lanka-http` |
|
|
65
|
+
| the server pushes updates, one way | `@lankajs/plugin-sse` | `lanka-sse` |
|
|
66
|
+
| the wire has to carry traffic BOTH ways | `@lankajs/plugin-websocket` | `lanka-websocket` |
|
|
67
|
+
| the API is GraphQL, with or without subscriptions | `@lankajs/plugin-graphql` | `lanka-graphql` |
|
|
68
|
+
| the API is gRPC and the client is a browser | `@lankajs/plugin-grpc` | `lanka-grpc` |
|
|
69
|
+
| warming data or route code before the user asks | `@lankajs/plugin-prefetch` | `lanka-prefetch` |
|
|
70
|
+
| ordered start-up stages that can redirect | `@lankajs/plugin-bootstrap-steps` | `lanka-bootstrap-steps` |
|
|
71
|
+
| seeing what the bus and the logger did | `@lankajs/plugin-devtools` | `lanka-devtools` |
|
|
72
|
+
|
|
73
|
+
SSE and WebSocket sit behind the SAME port, so the choice is one line in bootstrap
|
|
74
|
+
and not a rewrite. Take SSE unless the client has to SEND on the same connection.
|
|
75
|
+
|
|
76
|
+
## Pick exactly one from a family
|
|
77
|
+
|
|
78
|
+
Four decisions where the framework refuses to choose for you, because the right
|
|
79
|
+
answer is whichever library your application already has. Each family binds one
|
|
80
|
+
port, so every member publishes the same surface under a different vendor name.
|
|
81
|
+
|
|
82
|
+
| The decision | The members | Its skill |
|
|
83
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
84
|
+
| which schema library validates responses | `@lankajs/zod`, `@lankajs/valibot`, `@lankajs/arktype`, `@lankajs/yup`, `@lankajs/typebox`, `@lankajs/effect` | `lanka-zod`, `lanka-valibot`, … |
|
|
85
|
+
| …and the app ended up with two of them | `@lankajs/any-schema` | `lanka-any-schema` |
|
|
86
|
+
| which read cache sits under the ViewModels | `@lankajs/tanstack-query`, `@lankajs/nanostores-query` | `lanka-tanstack-query`, `lanka-nanostores-query` |
|
|
87
|
+
| where `@lankajs/storage` writes off the web | `@lankajs/mmkv`, `@lankajs/react-native-async-storage`, `@lankajs/secure-store`, `@lankajs/unstorage` | `lanka-mmkv`, `lanka-secure-store`, … |
|
|
88
|
+
| which UI framework reads the ViewModels | `@lankajs/react`, `@lankajs/vue`, `@lankajs/svelte`, `@lankajs/solid`, `@lankajs/angular` | `lanka-react`, `lanka-vue`, … |
|
|
89
|
+
|
|
90
|
+
The recommended member, when there is one: `@lankajs/zod` for schemas, and
|
|
91
|
+
`@lankajs/tanstack-query` for the read cache. Take another only for a reason you
|
|
92
|
+
can name — usually that the library is already in the application.
|
|
93
|
+
|
|
94
|
+
A storage engine is the one family you can skip entirely: in a browser
|
|
95
|
+
`@lankajs/storage` brings its own adapters. Install one when the app runs on a
|
|
96
|
+
device (`@lankajs/mmkv`, or `@lankajs/secure-store` for a token) or on a server
|
|
97
|
+
(`@lankajs/unstorage`).
|
|
98
|
+
|
|
99
|
+
## Build-time and test-time
|
|
100
|
+
|
|
101
|
+
| The problem | The package | Its skill |
|
|
102
|
+
| ------------------------------------------------------ | ----------------------- | --------------- |
|
|
103
|
+
| the `@lanka_di` wiring and its build-time check | `@lankajs/tool-di` | `lanka-di` |
|
|
104
|
+
| architectural boundaries as lint rules | `@lankajs/tool-eslint` | `lanka-eslint` |
|
|
105
|
+
| testing, and benchmarking | `@lankajs/tool-testing` | `lanka-testing` |
|
|
106
|
+
| keeping these skills matched to the installed versions | `@lankajs/tool-skills` | `lanka-skills` |
|
|
40
107
|
|
|
41
108
|
## Module or plugin, and why you should care
|
|
42
109
|
|
|
@@ -49,11 +116,16 @@ has a skill of its own with the detail.
|
|
|
49
116
|
|
|
50
117
|
## What NOT to install
|
|
51
118
|
|
|
52
|
-
-
|
|
53
|
-
is
|
|
119
|
+
- Two members of one family. Pick one; the point is that the choice is visible in
|
|
120
|
+
your dependency list. `@lankajs/any-schema` is the exception, and only for an
|
|
121
|
+
application that already carries two schema libraries.
|
|
122
|
+
- A second view binding. One application renders with one UI framework, and
|
|
123
|
+
installing two puts two copies of `useLankaVM` in the import list.
|
|
54
124
|
- `@lankajs/plugin-prefetch` before the app is measurably slow somewhere. It is
|
|
55
125
|
three tiers of machinery for a problem you may not have.
|
|
56
126
|
- `@lankajs/blob-cache` for images whose URL can change. It never checks freshness.
|
|
127
|
+
- A read cache before two screens read the same resource. One screen with its own
|
|
128
|
+
ViewModel already has everywhere it needs to keep that data.
|
|
57
129
|
|
|
58
130
|
## Getting the skills
|
|
59
131
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_internal/active-runtime/activeRuntime.ts"],"sourcesContent":["import type { LankaEventBusInstance } from \"../../scenario/event-bus/lanka-event-bus-instance/LankaEventBusInstance\";\nimport type { LankaScenariosRegistry } from \"../../scenario/_registries/lanka-scenarios-registry/LankaScenariosRegistry\";\nimport type { LankaScenarioVMRegistry } from \"../../scenario/_registries/lanka-scenario-vm-registry/LankaScenarioVMRegistry\";\nimport type { ILankaRuntimeConfig } from \"../../config/_interfaces/ILankaRuntimeConfig\";\nimport type { ILankaInFlightCounter } from \"../../gateway/inflight/lankaHttpInFlight\";\nimport type { ILankaScenarioVM } from \"../../scenario/_interfaces/ILankaScenarioVM\";\nimport type { TLankaRequestMiddleware } from \"../../gateway/request/lankaRequestMiddleware\";\nimport type { LankaGatewayLocator } from \"../../locator/gateway/lanka-gateway-locator/LankaGatewayLocator\";\nimport type { LankaScenarioLocator } from \"../../locator/scenario/lanka-scenario-locator/LankaScenarioLocator\";\nimport type { LankaSingletonLocator } from \"../../locator/singleton/lanka-singleton-locator/LankaSingletonLocator\";\nimport type { LankaSharedStoreLocator } from \"../../locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator\";\n\n/**\n * The pointer to the framework instance that ambient facades resolve to.\n *\n * ## Why this module is separate and imports no value\n *\n * Framework state belongs to an instance (`createLanka`), but the callers of\n * `lankaEventBus.dispatch`, `getLankaFlags()`, `lankaSingletons.foo`, `lankaHttpInFlight` —\n * the ambient facades cannot hold one — a user-extended `ALankaScenario`, the\n * static `LankaScenarioBootstrap` — so they ask which instance is active.\n *\n * The module is dependency-free DELIBERATELY: every import here is `import type`\n * and disappears at compile time. Otherwise there would be a cycle\n * `createLanka → locator → gateway → config → createLanka`, and module\n * evaluation order would start deciding what ends up `undefined`.\n *\n * This is still a global pointer. Real isolation belongs to whoever holds an\n * instance: their own bus, registries and counter. The facades serve ONE active\n * instance — exactly what an application with one needs.\n */\nexport interface ILankaRuntime {\n\treadonly eventBus: LankaEventBusInstance;\n\treadonly scenarios: LankaScenariosRegistry;\n\treadonly viewModels: LankaScenarioVMRegistry;\n\treadonly inFlight: ILankaInFlightCounter;\n\t/**\n\t * Request wrappers, in registration order: the first wraps them all.\n\t *\n\t * An array, not a set: order carries meaning — retry must sit outside request\n\t * signing, or the second attempt goes out unsigned.\n\t */\n\treadonly requestMiddleware: readonly TLankaRequestMiddleware[];\n\t/** Default response deadline. `undefined` means no limit. */\n\treadonly requestTimeoutMs: number | undefined;\n\treadonly locators: {\n\t\treadonly gateways: LankaGatewayLocator;\n\t\treadonly scenarios: LankaScenarioLocator;\n\t\treadonly singletons: LankaSingletonLocator;\n\t\treadonly sharedStores: LankaSharedStoreLocator;\n\t};\n\treadonly config: ILankaRuntimeConfig;\n\t/**\n\t * Bootstrap state of the scenario layer.\n\t *\n\t * A mutable object rather than contract fields: `LankaScenarioBootstrap` is an\n\t * ambient facade and must write here without holding an instance.\n\t */\n\treadonly scenarioState: {\n\t\tbootstrapped: boolean;\n\t\tinitialized: WeakSet<ILankaScenarioVM>;\n\t};\n\tsetConfig(patch: ILankaRuntimeConfig): void;\n\tgetFlags(): NonNullable<ILankaRuntimeConfig[\"flags\"]>;\n}\n\n/**\n * How \"which instance is active\" is answered.\n *\n * A strategy, because the answer depends on where the code runs. In a browser\n * there is one instance per document and a module-level pointer IS the answer. In\n * a server process there is one instance per REQUEST, and a module-level pointer\n * is the bug: the last request to start would answer for all of them.\n */\nexport type TLankaRuntimeResolver = () => ILankaRuntime | null;\n\nlet active: ILankaRuntime | null = null;\nlet resolveRuntime: TLankaRuntimeResolver | null = null;\n\nexport function setActiveRuntime(runtime: ILankaRuntime | null): void {\n\tactive = runtime;\n}\n\n/**\n * Replaces the strategy, or restores the default one with `null`.\n *\n * The installed resolver is the WHOLE answer — the module-level pointer is not\n * consulted behind it. That is deliberate: a fallback would turn \"this ran\n * outside a request\" from a loud error into one request quietly reading another\n * request's instance, which is the failure this seam exists to make impossible.\n *\n * Core ships no resolver and knows of none. What installs one is a package that\n * knows the shape of a request; core knows only that the question has more than\n * one answer.\n */\nexport function setLankaRuntimeResolver(resolver: TLankaRuntimeResolver | null): void {\n\tresolveRuntime = resolver;\n}\n\nexport function getActiveRuntime(): ILankaRuntime | null {\n\treturn resolveRuntime ? resolveRuntime() : active;\n}\n\n/**\n * The active instance, or a loud failure.\n *\n * No fallback, deliberately, for the same reason `getLankaHost` has none: an\n * instance created silently on the fly would mean subscriptions went to one bus\n * and events to another, and the divergence would surface three layers away.\n */\nexport function requireActiveRuntime(): ILankaRuntime {\n\t// The strategy is read here rather than through `getActiveRuntime()`: this is\n\t// the hottest path in the framework — every ambient facade, every request,\n\t// every dispatch — and a call that only forwards is a call that shows up in\n\t// `perf/`.\n\tconst runtime = resolveRuntime ? resolveRuntime() : active;\n\n\tif (!runtime) {\n\t\tthrow new Error(\n\t\t\tresolveRuntime\n\t\t\t\t? \"lanka has no instance for this call. A runtime resolver is installed and \" +\n\t\t\t\t\t\t\"answered with none, which on a server means the code ran outside a \" +\n\t\t\t\t\t\t\"request scope — start one, or do this work inside it.\"\n\t\t\t\t: \"lanka used before an instance existed. Call createLanka({ host }) and \" +\n\t\t\t\t\t\t\"activate it.\",\n\t\t);\n\t}\n\n\treturn runtime;\n}\n"],"mappings":";AA4EA,IAAI,SAA+B;AACnC,IAAI,iBAA+C;AAE5C,SAAS,iBAAiB,SAAqC;AACrE,WAAS;AACV;AAcO,SAAS,wBAAwB,UAA8C;AACrF,mBAAiB;AAClB;AAEO,SAAS,mBAAyC;AACxD,SAAO,iBAAiB,eAAe,IAAI;AAC5C;AASO,SAAS,uBAAsC;AAKrD,QAAM,UAAU,iBAAiB,eAAe,IAAI;AAEpD,MAAI,CAAC,SAAS;AACb,UAAM,IAAI;AAAA,MACT,iBACG,2MAGA;AAAA,IAEJ;AAAA,EACD;AAEA,SAAO;AACR;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/scenario/event-bus/lanka-event-bus-instance/LankaEventBusInstance.ts","../src/bootstrap/_internal/create-lanka-plugin-registry/createLankaPluginRegistry.ts","../src/bootstrap/_factories/create-lanka/createLanka.ts","../src/bootstrap/start-lanka/startLanka.ts","../src/bootstrap/reset-active-lanka/resetActiveLanka.ts","../src/bootstrap/_abstractions/lanka-plugin/ALankaPlugin.ts"],"sourcesContent":["import { ILankaEventMetadata } from \"../../_interfaces/ILankaEventMetadata\";\nimport { ILankaEventLog } from \"../../_interfaces/ILankaEventLog\";\nimport {\n\tTLankaEventBusDecision,\n\tTLankaEventBusMiddleware,\n} from \"../../_types/TLankaEventBusMiddleware\";\nimport { TLankaEventBusObserver } from \"../../_types/TLankaEventBusObserver\";\nimport { ILankaEventBusOutcome } from \"../../_interfaces/ILankaEventBusOutcome\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\n\n/** One subscription: the callback plus everything the bus knows about it. */\ntype TSubscriptionEntry<T = unknown> = {\n\tcallback: (data: T) => void;\n\tpriority: number;\n\t/**\n\t * How many recent values this subscriber asks to be replayed on subscribe.\n\t *\n\t * A number rather than a flag: \"the whole buffer\" can be a hundred events in a\n\t * row, which is almost never what \"give me the current state\" means.\n\t */\n\treplayCount: number;\n};\n\n/** State of one registered event type. */\ntype TEventState = {\n\tmeta: ILankaEventMetadata;\n\tlogs: ILankaEventLog[];\n\tbuffer: unknown[];\n\tsubs: TSubscriptionEntry[];\n};\n\n/**\n * How many recent values to replay to a new subscriber.\n *\n * `true` is kept and means \"the last one\".\n */\nexport type TLankaReplayRequest = boolean | \"last\" | number;\n\nfunction resolveReplayCount(request: TLankaReplayRequest | undefined): number {\n\tif (request === undefined || request === false) return 0;\n\tif (request === true || request === \"last\") return 1;\n\treturn Math.max(0, Math.trunc(request));\n}\n\nfunction maxReplayDepth(subs: readonly TSubscriptionEntry[]): number {\n\tlet depth = 0;\n\tfor (const entry of subs) if (entry.replayCount > depth) depth = entry.replayCount;\n\treturn depth;\n}\n\n// -----------------------------------------------------------------------------\n// Bus implementation\n// -----------------------------------------------------------------------------\n\nexport class LankaEventBusInstance {\n\t/** Event registry and per-event state — private to each instance. */\n\tprivate registry: Map<string, TEventState> = new Map();\n\n\t/** Middleware registered on this instance. */\n\tprivate middlewares: TLankaEventBusMiddleware<unknown>[] = [];\n\n\t/**\n\t * What is watching the OUTCOME of each dispatch — see `TLankaEventBusObserver`.\n\t *\n\t * An array rather than a set for one reason: `dispatch` reads `.length` on\n\t * every event, and that check is what keeps an unobserved bus paying nothing.\n\t */\n\tprivate observers: TLankaEventBusObserver[] = [];\n\n\t/** Default maximum number of logs kept per event type. */\n\tprivate defaultMaxLogs = 100;\n\n\t/** Internal timers for replay batching (avoid multiple replays in the same tick). */\n\tprivate pendingReplayTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();\n\n\t/** Whether event logging is enabled. */\n\tprivate isEnableLogs = false;\n\n\t/**\n\t * How many events this bus has logged, ever — the order records are read in.\n\t *\n\t * Incremented only while logging is on, which is off by default, so a bus\n\t * nobody is inspecting pays nothing for it.\n\t */\n\tprivate dispatchCount = 0;\n\n\t// -------------------------------------------------------------------------\n\t// Configuration\n\t// -------------------------------------------------------------------------\n\n\t/** Configures the bus, e.g. logging. Called once at application start. */\n\tpublic enableLogs(): void {\n\t\tthis.isEnableLogs = true;\n\t}\n\tpublic disableLogs(): void {\n\t\tthis.isEnableLogs = false;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Internal\n\t// -------------------------------------------------------------------------\n\n\t/**\n\t * Ensures the event is registered, registering it if not.\n\t *\n\t * @param eventType Event type\n\t * @param usedBy Who uses it — for the inspector and for finding an owner\n\t */\n\tprivate ensureEvent(eventType: string, usedBy?: string): TEventState {\n\t\tlet state = this.registry.get(eventType);\n\t\tif (!state) {\n\t\t\tconst meta: ILankaEventMetadata = {\n\t\t\t\tdataType: \"any\",\n\t\t\t\tdescription: `Auto-registered event: ${eventType}`,\n\t\t\t\tusedBy: usedBy ? [usedBy] : [],\n\t\t\t\tpriority: 0,\n\t\t\t\tmaxLogs: this.defaultMaxLogs,\n\t\t\t};\n\t\t\tstate = {\n\t\t\t\tmeta,\n\t\t\t\tlogs: [],\n\t\t\t\tbuffer: [],\n\t\t\t\tsubs: [],\n\t\t\t};\n\t\t\tthis.registry.set(eventType, state);\n\t\t} else if (usedBy) {\n\t\t\tconst meta = state.meta;\n\t\t\tmeta.usedBy = [...new Set([...(meta.usedBy ?? []), usedBy])];\n\t\t}\n\t\treturn state;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Public surface\n\t// -------------------------------------------------------------------------\n\n\t/**\n\t * Declares an event explicitly, together with its metadata.\n\t *\n\t * @param eventType Event type\n\t * @param metadata What is known about the event and what it is allowed\n\t */\n\tpublic registerEvent(eventType: string, metadata: ILankaEventMetadata): void {\n\t\t// MERGE, never recreate the state.\n\t\t//\n\t\t// Writing `subs: []` here means a call after anyone subscribed silently\n\t\t// drops every subscriber. Bootstrap order (register before ViewModels\n\t\t// initialise) hides it, but that is a coincidence, not an invariant: any\n\t\t// lazy event registration breaks it, and the only symptom is that nothing\n\t\t// works.\n\t\tconst existing = this.registry.get(eventType);\n\t\tconst meta: ILankaEventMetadata = {\n\t\t\tdataType: metadata.dataType ?? existing?.meta.dataType ?? \"any\",\n\t\t\tdescription:\n\t\t\t\tmetadata.description ??\n\t\t\t\texisting?.meta.description ??\n\t\t\t\t`Registered event: ${eventType}`,\n\t\t\t// `usedBy` lists everyone using the event and exists precisely to be\n\t\t\t// complete. Replacing it would erase half.\n\t\t\tusedBy: [...new Set([...(existing?.meta.usedBy ?? []), ...(metadata.usedBy ?? [])])],\n\t\t\tschema: metadata.schema ?? existing?.meta.schema,\n\t\t\tpriority: metadata.priority ?? existing?.meta.priority ?? 0,\n\t\t\tmaxLogs: metadata.maxLogs ?? existing?.meta.maxLogs ?? this.defaultMaxLogs,\n\t\t\treplay: metadata.replay ?? existing?.meta.replay,\n\t\t};\n\n\t\tif (existing) {\n\t\t\texisting.meta = meta;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.registry.set(eventType, { meta, logs: [], buffer: [], subs: [] });\n\t}\n\n\t/** Every registered event with its metadata. */\n\tpublic getRegisteredEvents() {\n\t\treturn Array.from(this.registry.entries())\n\t\t\t.map(([eventType, state]) => ({\n\t\t\t\teventType,\n\t\t\t\tmetadata: state.meta,\n\t\t\t}))\n\t\t\t.sort((a, b) => a.eventType.localeCompare(b.eventType));\n\t}\n\n\t/** Metadata of one event type. */\n\tpublic getEventInfo(eventType: string): ILankaEventMetadata | null {\n\t\treturn this.registry.get(eventType)?.meta ?? null;\n\t}\n\n\t/** How many live subscriptions this event has. */\n\t/**\n\t * How many values sit in the event's buffer.\n\t *\n\t * Exists for the test: \"the buffer stays empty until someone asks for replay\"\n\t * is a claim about MEMORY, and nothing but asking can check it.\n\t */\n\tpublic getBufferedCount(eventType: string): number {\n\t\treturn this.registry.get(eventType)?.buffer.length ?? 0;\n\t}\n\n\tpublic getSubscriptions(eventType: string): number {\n\t\treturn this.registry.get(eventType)?.subs.length ?? 0;\n\t}\n\n\t/**\n\t * Subscribes a handler to an event type.\n\t *\n\t * @param eventType Event type\n\t * @param callback What to run on dispatch\n\t * @param options Subscription options:\n\t * - `priority` — execution order, higher runs earlier;\n\t * - `replay` — receive what was dispatched before subscribing;\n\t * - `usedBy` — who subscribed, for the inspector.\n\t */\n\tpublic subscribe<T>(\n\t\teventType: string,\n\t\tcallback: (data: T) => void,\n\t\toptions?: {\n\t\t\tpriority?: number;\n\t\t\treplay?: TLankaReplayRequest;\n\t\t\tusedBy?: string;\n\t\t},\n\t): () => void {\n\t\tconst state = this.ensureEvent(eventType, options?.usedBy);\n\t\tconst prio = options?.priority ?? state.meta.priority ?? 0;\n\t\tconst entry: TSubscriptionEntry<unknown> = {\n\t\t\tcallback: callback as unknown as (data: unknown) => void,\n\t\t\tpriority: prio,\n\t\t\treplayCount: resolveReplayCount(options?.replay),\n\t\t};\n\n\t\tconst subs = state.subs;\n\t\tlet low = 0,\n\t\t\thigh = subs.length;\n\t\twhile (low < high) {\n\t\t\tconst mid = (low + high) >>> 1;\n\t\t\tif (subs[mid].priority < prio) high = mid;\n\t\t\telse low = mid + 1;\n\t\t}\n\t\tsubs.splice(low, 0, entry);\n\n\t\tif (options?.replay) this.scheduleBatchedReplay(eventType, state);\n\n\t\t// The returned function removes EXACTLY this record, not the first one with\n\t\t// the same callback: by callback, two subscriptions of one function are\n\t\t// indistinguishable.\n\t\treturn () => {\n\t\t\tconst at = state.subs.indexOf(entry);\n\t\t\tif (at !== -1) state.subs.splice(at, 1);\n\t\t};\n\t}\n\n\t/**\n\t * Removes a handler from an event type.\n\t *\n\t * @param eventType Event type\n\t * @param callback A previously subscribed handler\n\t */\n\tpublic unsubscribe<T>(eventType: string, callback: (data: T) => void): void {\n\t\tconst state = this.registry.get(eventType);\n\t\tif (!state) return;\n\t\tconst subs = state.subs;\n\t\tfor (let i = 0; i < subs.length; i++) {\n\t\t\tif (subs[i].callback === callback) {\n\t\t\t\tsubs.splice(i, 1);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Dispatches an event: the middleware chain first, then subscribers.\n\t *\n\t * @param eventType Event type\n\t * @param data Payload\n\t * @param usedBy Who dispatched — recorded in the metadata\n\t */\n\tpublic dispatch<T>(eventType: string, data?: T, usedBy?: string): void {\n\t\tconst state = this.ensureEvent(eventType, usedBy);\n\t\tconst { meta, subs, buffer } = state;\n\n\t\t// Payload shape check\n\t\tif (meta.schema && data !== undefined && !meta.schema(data)) {\n\t\t\tlankaLogger.printScenarioLog(`Invalid data for event \"${eventType}\":`, data);\n\t\t\tthis.report({ eventType, outcome: \"invalid\", subscribers: subs.length });\n\t\t\treturn;\n\t\t}\n\n\t\tthis.logEvent(state, eventType, data);\n\n\t\tconst deliver = (): void => {\n\t\t\t// Iterate a COPY. A subscriber may unsubscribe inside its own handler —\n\t\t\t// \"waited for the session, then unsubscribed\" is a routine pattern — and\n\t\t\t// `unsubscribe` splices this same array, so iterating the original skips\n\t\t\t// a neighbour silently.\n\t\t\t//\n\t\t\t// The deliberate side effect: subscribing DURING delivery waits for the\n\t\t\t// next event. Otherwise delivery order would depend on subscription\n\t\t\t// timing, which is undefined.\n\t\t\tfor (const entry of [...subs]) {\n\t\t\t\ttry {\n\t\t\t\t\tentry.callback(data);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlankaLogger.printScenarioLog(`Error dispatching event \"${eventType}\":`, err);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\t// Every middleware RETURNS a decision. A `next()`-based shape means not\n\t\t// calling it makes the event vanish silently.\n\t\t//\n\t\t// An exception is the same case as \"did not call next\": it also stops\n\t\t// delivery, so it is handled the same way and names itself in the log,\n\t\t// instead of surfacing and breaking whoever dispatched.\n\t\tfor (const middleware of [...this.middlewares]) {\n\t\t\tlet decision: TLankaEventBusDecision;\n\t\t\ttry {\n\t\t\t\tdecision = middleware(eventType, data);\n\t\t\t} catch (err) {\n\t\t\t\tdecision = { stop: err instanceof Error ? err.message : String(err) };\n\t\t\t}\n\n\t\t\tif (decision !== \"pass\") {\n\t\t\t\tthis.markStopped(state, decision.stop);\n\t\t\t\tlankaLogger.printScenarioLog(\n\t\t\t\t\t`Event \"${eventType}\" stopped by middleware:`,\n\t\t\t\t\tdecision.stop,\n\t\t\t\t);\n\t\t\t\t// The one place that knows WHICH middleware stopped it. A middleware\n\t\t\t\t// cannot learn this about a middleware after itself, which is why an\n\t\t\t\t// inspector needed an observer rather than a sixth middleware.\n\t\t\t\tthis.report({\n\t\t\t\t\teventType,\n\t\t\t\t\toutcome: \"stopped\",\n\t\t\t\t\tsubscribers: subs.length,\n\t\t\t\t\tstoppedBy: decision.stop,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// AFTER the chain, and that ordering is the guarantee.\n\t\t//\n\t\t// The buffer used to be filled before the middlewares ran, so an event the\n\t\t// chain STOPPED still sat in it — and the next subscriber asking for replay\n\t\t// received a payload no subscriber had been allowed to see, with nothing in\n\t\t// the bus log recording that delivery. Middleware is how an application\n\t\t// gates an event (an authorisation check, a privacy filter, a feature\n\t\t// flag); a gate the framework routes around is not a gate.\n\t\t//\n\t\t// The buffer exists ONLY on demand and holds exactly what the greediest\n\t\t// subscriber asked for. Buffering every payload unconditionally holds up to\n\t\t// a hundred payloads per event type until the end of the session — in an app\n\t\t// where user data arrives over SSE, that is personal data kept in memory\n\t\t// with no consumer.\n\t\tconst depth = Math.max(resolveReplayCount(meta.replay), maxReplayDepth(subs));\n\t\tif (depth > 0) {\n\t\t\tbuffer.push(data);\n\t\t\twhile (buffer.length > depth) buffer.shift();\n\t\t} else if (buffer.length > 0) {\n\t\t\t// The last replay asker unsubscribed — nothing left to hold.\n\t\t\tbuffer.length = 0;\n\t\t}\n\n\t\tdeliver();\n\t\tthis.report({ eventType, outcome: \"delivered\", subscribers: subs.length });\n\t}\n\n\t/** Adds middleware to the shared chain. */\n\tpublic addMiddleware<T>(mw: TLankaEventBusMiddleware<T>): void {\n\t\tthis.middlewares.push(mw as unknown as TLankaEventBusMiddleware<unknown>);\n\t}\n\n\t/** Removes previously added middleware. */\n\tpublic removeMiddleware<T>(mw: TLankaEventBusMiddleware<T>): void {\n\t\tconst i = this.middlewares.indexOf(mw as unknown as TLankaEventBusMiddleware<unknown>);\n\t\tif (i !== -1) this.middlewares.splice(i, 1);\n\t}\n\n\t/**\n\t * Watches what became of each dispatch. An extension point — the sixth.\n\t *\n\t * An observer never decides: its return value is ignored, and a throw from it\n\t * is logged rather than surfaced. A diagnostic tool that could stop an event\n\t * would make \"I disabled the inspector and it started working\" a sentence\n\t * somebody says.\n\t */\n\tpublic addObserver(observer: TLankaEventBusObserver): void {\n\t\tthis.observers.push(observer);\n\t}\n\n\t/** Removes a previously added observer. */\n\tpublic removeObserver(observer: TLankaEventBusObserver): void {\n\t\tconst i = this.observers.indexOf(observer);\n\t\tif (i !== -1) this.observers.splice(i, 1);\n\t}\n\n\t/**\n\t * Log of dispatched events, oldest first, `limit` most recent kept.\n\t *\n\t * ## Why the records are sorted rather than concatenated\n\t *\n\t * They are held per event TYPE, so that each type keeps its own `maxLogs`\n\t * and a chatty event cannot push a quiet one's history out. Concatenating\n\t * those lists puts every record of one type before every record of the next,\n\t * and `slice(-limit)` then answers the tail of whichever type the registry\n\t * happened to hold last — not the most recent events at all. On a bus with\n\t * one event type nothing looked wrong; on a real one the log was a\n\t * chronology that had never been in chronological order.\n\t *\n\t * @param eventType Limit to one type\n\t * @param limit How many recent records to return\n\t */\n\tpublic getEventLogs(eventType?: string, limit = 100): ILankaEventLog[] {\n\t\tif (eventType) {\n\t\t\treturn this.registry.get(eventType)?.logs.slice(-limit) ?? [];\n\t\t}\n\t\tconst all: ILankaEventLog[] = [];\n\t\tfor (const { logs } of this.registry.values()) all.push(...logs);\n\t\t// By `sequence`, not by `timestamp`: a timestamp has millisecond\n\t\t// resolution and a burst — an SSE storm, bootstrap — dispatches many\n\t\t// events inside one, so sorting by it would leave those in the order the\n\t\t// concatenation happened to produce, which is the defect itself.\n\t\tall.sort((left, right) => (left.sequence ?? 0) - (right.sequence ?? 0));\n\t\treturn all.slice(-limit);\n\t}\n\n\t/** Removes an event together with its subscriptions, log and buffer. */\n\tpublic clearEvent(eventType: string): void {\n\t\tthis.registry.delete(eventType);\n\t\tconst t = this.pendingReplayTimers.get(eventType);\n\t\tif (t) {\n\t\t\tclearTimeout(t);\n\t\t\tthis.pendingReplayTimers.delete(eventType);\n\t\t}\n\t}\n\n\t/** Removes every event and any scheduled replays. */\n\tpublic clearAllEvents(): void {\n\t\tthis.registry.clear();\n\t\tfor (const t of this.pendingReplayTimers.values()) clearTimeout(t);\n\t\tthis.pendingReplayTimers.clear();\n\t}\n\n\t/** Full bus reset: events, middleware and observers. */\n\tpublic reset(): void {\n\t\tthis.clearAllEvents();\n\t\tthis.middlewares = [];\n\t\tthis.observers = [];\n\t\tthis.isEnableLogs = false;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Internal helpers\n\t// -------------------------------------------------------------------------\n\n\t/**\n\t * Tells the observers what became of a dispatch.\n\t *\n\t * The empty-list check is the whole reason a bus nobody watches pays nothing\n\t * for this: `dispatch` is a hot path, and an iteration over an empty array on\n\t * every event would be a cost paid by every application for a feature almost\n\t * none of them installs. Measured in `LankaEventBusInstance.bench.ts`.\n\t *\n\t * An observer that throws is logged and the next one still runs — the same\n\t * treatment a subscriber gets, and for the same reason: a diagnostic tool must\n\t * not be able to break the application it is diagnosing.\n\t */\n\tprivate report(outcome: ILankaEventBusOutcome): void {\n\t\tif (this.observers.length === 0) return;\n\n\t\tfor (const observer of [...this.observers]) {\n\t\t\ttry {\n\t\t\t\tobserver(outcome);\n\t\t\t} catch (err) {\n\t\t\t\tlankaLogger.printScenarioLog(\n\t\t\t\t\t`Event bus observer failed for \"${outcome.eventType}\":`,\n\t\t\t\t\terr,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Marks the last log record with the reason it was stopped.\n\t *\n\t * Writes where `logEvent` just put the event: a stop is not a separate\n\t * occurrence but this one's outcome.\n\t */\n\tprivate markStopped(state: TEventState, reason: string): void {\n\t\tconst last = state.logs.at(-1);\n\t\tif (last) last.stoppedBy = reason;\n\t}\n\n\t/** Add an event log entry for debugging/auditing. */\n\tprivate logEvent(state: TEventState, eventType: string, data: unknown): void {\n\t\tif (this.isEnableLogs) {\n\t\t\tconst cap = state.meta.maxLogs ?? this.defaultMaxLogs;\n\t\t\tconst logs = state.logs;\n\t\t\tthis.dispatchCount += 1;\n\t\t\tconst log: ILankaEventLog = {\n\t\t\t\teventType,\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\tdata,\n\t\t\t\tsequence: this.dispatchCount,\n\t\t\t};\n\t\t\tlog.stackTrace = new Error().stack;\n\t\t\tlogs.push(log);\n\t\t\tif (logs.length > cap) logs.shift();\n\t\t}\n\t}\n\n\t/**\n\t * Schedules replay for late subscribers that asked for it.\n\t *\n\t * Replays are batched into one tick: otherwise ten subscriptions to one event\n\t * would make ten separate passes over the same buffer.\n\t */\n\tprivate scheduleBatchedReplay(eventType: string, state: TEventState): void {\n\t\tif (this.pendingReplayTimers.has(eventType)) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.pendingReplayTimers.delete(eventType);\n\t\t\tconst { buffer, subs } = state;\n\t\t\tif (!buffer.length) return;\n\n\t\t\tconst needReplay = subs.filter((s) => s.replayCount > 0);\n\t\t\tif (!needReplay.length) return;\n\n\t\t\tfor (const entry of needReplay) {\n\t\t\t\t// Each gets what IT asked for, not what the buffer happens to hold.\n\t\t\t\tfor (const payload of buffer.slice(-entry.replayCount)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tentry.callback(payload);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tlankaLogger.printScenarioLog(\n\t\t\t\t\t\t\t`Error dispatching event \"${eventType}\" (replay):`,\n\t\t\t\t\t\t\terr,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Replay is an event of subscribing, not a property of the subscriber: it\n\t\t\t// does not happen a second time.\n\t\t\tfor (const entry of needReplay) entry.replayCount = 0;\n\t\t}, 0);\n\n\t\tthis.pendingReplayTimers.set(eventType, timer);\n\t}\n}\n","import type { ILankaPlugin } from \"../../ILankaPlugin\";\nimport type { ILankaInstance } from \"../../_factories/create-lanka/createLanka\";\n\nexport interface ILankaPluginRegistry {\n\t/** Installs a plugin and returns the function that removes it. */\n\tuse: (plugin: ILankaPlugin) => () => void;\n\t/** Removes every plugin, newest first is NOT guaranteed — see `removeAll`. */\n\tremoveAll: () => void;\n}\n\n/**\n * The plugins installed on one framework instance.\n *\n * Owns three decisions that are easy to get subtly wrong and impossible to see\n * from the outside: a duplicate is refused, removal is idempotent, and removing\n * all of them iterates a COPY of the values.\n */\nexport const createLankaPluginRegistry = (instance: ILankaInstance): ILankaPluginRegistry => {\n\tconst removers = new Map<string, () => void>();\n\n\treturn {\n\t\tuse(plugin: ILankaPlugin): () => void {\n\t\t\tif (removers.has(plugin.name)) {\n\t\t\t\t// Rejected LOUDLY: two copies of a retry policy double the request\n\t\t\t\t// count, and that shows up only as backend load — much later, and to\n\t\t\t\t// somebody else.\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Plugin \"${plugin.name}\" is already registered on this lanka instance. ` +\n\t\t\t\t\t\t\"Registering twice almost always means two copies of one policy.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst uninstall = plugin.install(instance);\n\n\t\t\tconst remove = (): void => {\n\t\t\t\t// Idempotent: a caller may hold the remover and also dispose the\n\t\t\t\t// instance, and uninstalling twice is how a plugin's own teardown ends\n\t\t\t\t// up running against a framework that is already gone.\n\t\t\t\tif (!removers.has(plugin.name)) return;\n\t\t\t\tremovers.delete(plugin.name);\n\t\t\t\tuninstall?.();\n\t\t\t};\n\n\t\t\tremovers.set(plugin.name, remove);\n\t\t\treturn remove;\n\t\t},\n\n\t\tremoveAll(): void {\n\t\t\t// A COPY: each remover deletes its own entry, and mutating the map being\n\t\t\t// iterated skips every second plugin.\n\t\t\tfor (const remove of [...removers.values()]) remove();\n\t\t\tremovers.clear();\n\t\t},\n\t};\n};\n","import { LankaEventBusInstance } from \"../../../scenario/event-bus/lanka-event-bus-instance/LankaEventBusInstance\";\nimport { createLankaPluginRegistry } from \"../../_internal/create-lanka-plugin-registry/createLankaPluginRegistry\";\nimport { LankaScenariosRegistry } from \"../../../scenario/_registries/lanka-scenarios-registry/LankaScenariosRegistry\";\nimport { LankaScenarioVMRegistry } from \"../../../scenario/_registries/lanka-scenario-vm-registry/LankaScenarioVMRegistry\";\nimport { lankaScenarioBootstrap } from \"../../../scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap\";\nimport { LankaGatewayLocator } from \"../../../locator/gateway/lanka-gateway-locator/LankaGatewayLocator\";\nimport { LankaScenarioLocator } from \"../../../locator/scenario/lanka-scenario-locator/LankaScenarioLocator\";\nimport { LankaSingletonLocator } from \"../../../locator/singleton/lanka-singleton-locator/LankaSingletonLocator\";\nimport { LankaSharedStoreLocator } from \"../../../locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator\";\nimport { createInFlightCounter } from \"../../../gateway/inflight/lankaHttpInFlight\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\nimport {\n\tgetActiveRuntime,\n\tsetActiveRuntime,\n} from \"../../../_internal/active-runtime/activeRuntime\";\nimport { createLankaScope } from \"../../../locator/_factories/create-lanka-scope/createLankaScope\";\nimport type { ILankaScope } from \"../../../locator/_factories/create-lanka-scope/createLankaScope\";\nimport type { ILankaPlugin } from \"../../ILankaPlugin\";\nimport type { ILankaRuntime } from \"../../../_internal/active-runtime/activeRuntime\";\nimport type { ILankaFlags } from \"../../../config/_interfaces/ILankaFlags\";\nimport type { ILankaHost } from \"../../../config/_interfaces/ILankaHost\";\nimport type { ILankaRuntimeConfig } from \"../../../config/_interfaces/ILankaRuntimeConfig\";\nimport type { ILankaScenarioVM } from \"../../../scenario/_interfaces/ILankaScenarioVM\";\nimport type { TLankaRequestMiddleware } from \"../../../gateway/request/lankaRequestMiddleware\";\n\n/**\n * A framework instance: all framework state in one object.\n *\n * What instance-scoped state buys: two apps in one process (micro-frontends,\n * Storybook beside the app) no longer share a bus, locator caches and mock mode;\n * SSR does not reuse state between different users' requests; and test isolation\n * rests on construction rather than on a global `beforeEach` reaching into\n * internal registries.\n *\n * **Ambient facades** — `lankaEventBus.dispatch`, `lankaSingletons.foo`,\n * `getLankaFlags()`, `lankaHttpInFlight` — resolve THE ONE active instance\n * (`internal/activeRuntime.ts`). Isolation belongs to whoever holds an instance;\n * a facade cannot offer it.\n *\n * One thing stays at module level deliberately: `ALankaScenario` collects\n * constructed scenarios into a static pool. That is a REGISTRY OF DEFINITIONS,\n * not runtime state — the classes come from one `@lanka_di/Scenarios` barrel and\n * both instances must see the same list. Splitting it would be divergence.\n */\nexport interface ILankaInstance extends ILankaRuntime {\n\t/**\n\t * Adds a wrapper around every request. Returns a function that removes it.\n\t *\n\t * This is how `@lankajs/plugin-http` installs retry, the idempotency key, the\n\t * CSRF header and auth refresh.\n\t */\n\tuseRequestMiddleware(middleware: TLankaRequestMiddleware): () => void;\n\t/** Default timeout for every request of this instance. */\n\tsetRequestTimeout(timeoutMs: number | undefined): void;\n\t/** Resolves a service in the root scope — for the instance's whole lifetime. */\n\tresolve<TInstance>(propertyName: string): TInstance;\n\t/**\n\t * Creates a scope: a lifetime shorter than the application's.\n\t *\n\t * An object created in a scope goes away with it.\n\t */\n\tcreateScope(): ILankaScope;\n\t/**\n\t * Registers a plugin. Returns a function that removes it.\n\t *\n\t * The fifth and last extension point. An extension point declared before\n\t * anything plugs into it describes an imagined need while costing real\n\t * support, so this one arrived with the FIRST plugin.\n\t */\n\tuse(plugin: ILankaPlugin): () => void;\n\t/** Runs services and the scenario layer. Idempotent. */\n\tbootstrap(config?: ILankaBootstrapConfig): Promise<void>;\n\t/** Has bootstrap already run? */\n\tisBootstrapped(): boolean;\n\t/** Makes this instance the one ambient facades resolve to. */\n\tactivate(): void;\n\t/**\n\t * Removes subscriptions, clears registries and, if this instance was active,\n\t * clears the pointer. Returns the framework to its pre-bootstrap state.\n\t */\n\tdispose(): void;\n}\n\nexport interface ILankaInstanceConfig {\n\thost: ILankaHost;\n\tflags?: ILankaFlags;\n}\n\nexport interface ILankaServiceConfig {\n\tname?: string;\n\tinit: () => void | Promise<void>;\n\tsync?: boolean;\n\tpriority?: number;\n\t/**\n\t * A failure of this service does not abort bootstrap.\n\t *\n\t * Without the flag one failed service takes the WHOLE phase with it: the async\n\t * phase because of `Promise.all`, the sync phase because later tasks never run.\n\t * Wrapping the failure in a swallowing `try/catch` is worse still — the app\n\t * starts with a partially executed plan and does not know it.\n\t */\n\toptional?: boolean;\n\t/**\n\t * How long to wait for the service. Overrunning counts as a failure.\n\t *\n\t * Without a deadline a service that never settles holds bootstrap forever and\n\t * the app never paints its first screen. Failing is more honest than waiting:\n\t * an optional service is skipped, a required one names itself.\n\t */\n\ttimeoutMs?: number;\n}\n\nexport interface ILankaScenarioBootstrapConfig {\n\tsync?: boolean;\n\tpriority?: number;\n}\n\nexport interface ILankaBootstrapConfig {\n\tservices?: ILankaServiceConfig[];\n\tscenarios?: ILankaScenarioBootstrapConfig;\n}\n\n/**\n * Config merge: patch over previous, flags over flags.\n *\n * Lives HERE rather than in `config/lankaConfig` because the rule belongs to\n * whoever holds the config — the instance. Importing it from the config module\n * would pull that module into EVERY test's graph through the test kit, and any\n * test that partially mocks the config module would break the suite with \"No\n * export is defined on the mock\".\n */\nfunction mergeLankaConfig(\n\tprevious: ILankaRuntimeConfig,\n\tpatch: ILankaRuntimeConfig,\n): ILankaRuntimeConfig {\n\treturn {\n\t\t// `host` is read field by field: `setConfig({ host: undefined })` through a\n\t\t// spread would leave the instance with no host, and every request URL would\n\t\t// then carry the string \"undefined\". Flags stay a spread — there both\n\t\t// absent and undefined mean \"not set\", so nothing can be lost.\n\t\thost: patch.host ?? previous.host,\n\t\tflags: { ...previous.flags, ...patch.flags },\n\t};\n}\n\ntype TExecutionTask = {\n\tname: string;\n\tpriority: number;\n\toptional: boolean;\n\ttimeoutMs?: number;\n\texecute: () => void | Promise<void>;\n};\n\n/**\n * Creates an instance and makes it active.\n *\n * Activating on creation is the deliberate default: there is almost always one\n * app and the facades must work immediately. A second instance takes the\n * pointer — the last one created serves the facades. Callers needing another\n * order call `activate()` explicitly.\n */\nexport function createLanka(config: ILankaInstanceConfig): ILankaInstance {\n\tlet runtimeConfig: ILankaRuntimeConfig = mergeLankaConfig(\n\t\t{},\n\t\t{ host: config.host, flags: config.flags },\n\t);\n\tlet bootstrapped = false;\n\tlet bootstrapping: Promise<void> | null = null; // the plan in flight, joined by later callers\n\n\tconst eventBus = new LankaEventBusInstance();\n\tconst scenarios = new LankaScenariosRegistry();\n\tconst viewModels = new LankaScenarioVMRegistry();\n\tconst inFlight = createInFlightCounter();\n\tconst scenarioState = {\n\t\tbootstrapped: false,\n\t\tinitialized: new WeakSet<ILankaScenarioVM>(),\n\t};\n\tconst scopes = new Set<ILankaScope>();\n\tconst requestMiddleware: TLankaRequestMiddleware[] = [];\n\tlet requestTimeoutMs: number | undefined;\n\n\tconst locators = {\n\t\tgateways: new LankaGatewayLocator(),\n\t\tscenarios: new LankaScenarioLocator(),\n\t\tsingletons: new LankaSingletonLocator(),\n\t\tsharedStores: new LankaSharedStoreLocator(),\n\t};\n\n\tconst instance: ILankaInstance = {\n\t\teventBus,\n\t\tscenarios,\n\t\tviewModels,\n\t\tinFlight,\n\t\tlocators,\n\t\tscenarioState,\n\t\trequestMiddleware,\n\t\tget requestTimeoutMs() {\n\t\t\treturn requestTimeoutMs;\n\t\t},\n\t\tuseRequestMiddleware(middleware: TLankaRequestMiddleware): () => void {\n\t\t\trequestMiddleware.push(middleware);\n\t\t\treturn () => {\n\t\t\t\tconst at = requestMiddleware.indexOf(middleware);\n\t\t\t\tif (at !== -1) requestMiddleware.splice(at, 1);\n\t\t\t};\n\t\t},\n\t\tsetRequestTimeout(timeoutMs: number | undefined): void {\n\t\t\trequestTimeoutMs = timeoutMs;\n\t\t},\n\t\tresolve<TInstance>(propertyName: string): TInstance {\n\t\t\treturn locators.singletons.get(propertyName) as TInstance;\n\t\t},\n\t\tuse(plugin: ILankaPlugin): () => void {\n\t\t\treturn plugins.use(plugin);\n\t\t},\n\t\tcreateScope(): ILankaScope {\n\t\t\tconst scope = createLankaScope(locators.singletons);\n\t\t\tscopes.add(scope);\n\t\t\treturn scope;\n\t\t},\n\t\tget config() {\n\t\t\treturn runtimeConfig;\n\t\t},\n\t\tsetConfig(patch: ILankaRuntimeConfig): void {\n\t\t\truntimeConfig = mergeLankaConfig(runtimeConfig, patch);\n\t\t},\n\t\tgetFlags(): ILankaFlags {\n\t\t\treturn runtimeConfig.flags ?? {};\n\t\t},\n\t\tisBootstrapped(): boolean {\n\t\t\treturn bootstrapped;\n\t\t},\n\t\tactivate(): void {\n\t\t\tsetActiveRuntime(instance);\n\t\t},\n\t\tdispose(): void {\n\t\t\tteardown();\n\t\t\trequestMiddleware.length = 0;\n\t\t\trequestTimeoutMs = undefined;\n\t\t\tbootstrapped = false;\n\t\t},\n\t\tbootstrap(bootstrapConfig: ILankaBootstrapConfig = {}): Promise<void> {\n\t\t\tif (bootstrapped) {\n\t\t\t\tlankaLogger.printBootstrapLog(\"LANKA ALREADY BOOTSTRAPPED\");\n\t\t\t\treturn Promise.resolve();\n\t\t\t}\n\t\t\t// One run, shared: the flag flips when the plan has FINISHED, so two\n\t\t\t// callers arriving before that — two routes, StrictMode mounting twice —\n\t\t\t// both read \"not bootstrapped\" and every service ran twice.\n\t\t\tbootstrapping ??= runBootstrap(instance, bootstrapConfig, () => {\n\t\t\t\tbootstrapped = true;\n\t\t\t}).finally(() => {\n\t\t\t\tbootstrapping = null;\n\t\t\t});\n\t\t\treturn bootstrapping;\n\t\t},\n\t};\n\n\t/**\n\t * Tears the instance down, in the ONE order that works.\n\t *\n\t * Scopes close FIRST: they hold objects built from this instance's classes, so\n\t * disposing without them would leave alive exactly what a scope exists to\n\t * remove. Plugins go AFTER scopes and BEFORE the registries: removing them\n\t * earlier would leave the objects being disposed with half a framework — no\n\t * middleware, but still expecting it.\n\t */\n\tfunction teardown(): void {\n\t\tfor (const scope of scopes) scope.dispose();\n\t\tscopes.clear();\n\n\t\tplugins.removeAll();\n\n\t\tviewModels.resetAll();\n\t\tscenarios.clear();\n\t\teventBus.reset();\n\n\t\tlocators.gateways.clearCache();\n\t\tlocators.scenarios.clearCache();\n\t\tlocators.singletons.clearCache();\n\t\tlocators.sharedStores.clearCache();\n\n\t\tscenarioState.bootstrapped = false;\n\t\tscenarioState.initialized = new WeakSet<ILankaScenarioVM>();\n\n\t\tif (getActiveRuntime() === instance) setActiveRuntime(null);\n\t}\n\n\tconst plugins = createLankaPluginRegistry(instance);\n\n\tinstance.activate();\n\t// EVERY new instance adopts the declared ViewModels: a screen file creates\n\t// them at module level, and neither import order nor test number may decide\n\t// whether the framework knows about them.\n\tlankaScenarioBootstrap.adoptDeclaredViewModels();\n\n\treturn instance;\n}\n\nfunction createExecutionPlan(config: ILankaBootstrapConfig) {\n\tconst syncTasks: TExecutionTask[] = [];\n\tconst asyncTasks: TExecutionTask[] = [];\n\tconst postAsyncTasks: TExecutionTask[] = [];\n\n\tconfig.services?.forEach((service, index) => {\n\t\tconst task: TExecutionTask = {\n\t\t\tname: service.name || `Service-${index + 1}`,\n\t\t\tpriority: service.priority ?? 0,\n\t\t\toptional: service.optional ?? false,\n\t\t\ttimeoutMs: service.timeoutMs,\n\t\t\texecute: service.init,\n\t\t};\n\n\t\tif (service.sync) syncTasks.push(task);\n\t\telse asyncTasks.push(task);\n\t});\n\n\t// Field by field rather than a spread over defaults: a caller forwarding its\n\t// own optional config passes `{ sync: undefined }`, and a spread would put that\n\t// over `sync: true` — moving the whole scenario layer out of the sync phase\n\t// because somebody did not set a variable.\n\tconst scenarioSync = config.scenarios?.sync ?? true;\n\tconst scenarioTask: TExecutionTask = {\n\t\tname: \"LankaScenarioBootstrap\",\n\t\tpriority: config.scenarios?.priority ?? 0,\n\t\t// The scenario layer is required: without it nothing subscribed to it\n\t\t// works, and continuing silently would start an app where half the screens\n\t\t// never update.\n\t\toptional: false,\n\t\texecute: () => {\n\t\t\tlankaScenarioBootstrap.bootstrap();\n\t\t},\n\t};\n\n\tif (scenarioSync) syncTasks.push(scenarioTask);\n\telse postAsyncTasks.push(scenarioTask);\n\n\tsyncTasks.sort((a, b) => b.priority - a.priority);\n\n\treturn { syncTasks, asyncTasks, postAsyncTasks };\n}\n\n/**\n * One bootstrap, start to finish: the three phases in the one order that works,\n * then `markDone` — which the instance uses to flip its flag, and only then.\n *\n * Outside `createLanka` because it closes over nothing the instance holds: the\n * flag it must flip arrives as a callback, and everything else it needs it is\n * handed. That keeps the factory readable as the object it builds.\n */\nasync function runBootstrap(\n\tinstance: ILankaInstance,\n\tconfig: ILankaBootstrapConfig,\n\tmarkDone: () => void,\n): Promise<void> {\n\tinstance.activate();\n\tlankaLogger.printBootstrapLog(\"LANKA BOOTSTRAP START\");\n\n\tconst plan = createExecutionPlan(config);\n\tawait runSequential(plan.syncTasks, \"SYNC TASKS\");\n\tawait runParallel(plan.asyncTasks, \"ASYNC TASKS\");\n\tawait runSequential(plan.postAsyncTasks, \"POST-ASYNC TASKS (lankaScenarios)\");\n\n\tmarkDone();\n\tlankaLogger.printBootstrapLog(\"LANKA BOOTSTRAP FINISH\");\n}\n\n/**\n * Runs a task under its deadline.\n *\n * A race against a timer rather than an `AbortSignal`: a service is an arbitrary\n * application function and cannot be cancelled. The deadline bounds the WAIT,\n * not the work — hung work continues in the background, bootstrap stops waiting.\n */\nasync function runWithDeadline(task: TExecutionTask): Promise<void> {\n\tif (task.timeoutMs === undefined) {\n\t\tawait task.execute();\n\t\treturn;\n\t}\n\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\ttry {\n\t\tawait Promise.race([\n\t\t\tPromise.resolve(task.execute()),\n\t\t\tnew Promise<never>((_resolve, reject) => {\n\t\t\t\ttimer = setTimeout(\n\t\t\t\t\t() =>\n\t\t\t\t\t\treject(\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t`Service \"${task.name}\" did not finish within ${String(task.timeoutMs)}ms`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\ttask.timeoutMs,\n\t\t\t\t);\n\t\t\t}),\n\t\t]);\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t}\n}\n\n/**\n * One task's failure: swallow and log, or abort the plan.\n *\n * The TASK's flag decides, not a blanket tolerance in the runner — otherwise\n * `optional` would mean nothing.\n */\nfunction handleTaskFailure(task: TExecutionTask, phaseName: string, error: unknown): void {\n\tlankaLogger.printBootstrapLog(`[${phaseName}] ERROR IN ${task.name}`, formatError(error));\n\tif (!task.optional) throw error;\n\tlankaLogger.printBootstrapLog(`[${phaseName}] SKIPPED (optional): ${task.name}`);\n}\n\nasync function runSequential(tasks: TExecutionTask[], phaseName: string): Promise<void> {\n\tif (tasks.length === 0) return;\n\n\tfor (const task of tasks) {\n\t\ttry {\n\t\t\tlankaLogger.printBootstrapLog(\n\t\t\t\t`[${phaseName}] EXECUTING: ${task.name} (Priority: ${String(task.priority)})`,\n\t\t\t);\n\t\t\tawait runWithDeadline(task);\n\t\t} catch (error) {\n\t\t\thandleTaskFailure(task, phaseName, error);\n\t\t}\n\t}\n}\n\nasync function runParallel(tasks: TExecutionTask[], phaseName: string): Promise<void> {\n\tif (tasks.length === 0) return;\n\n\tlankaLogger.printBootstrapLog(\n\t\t`[${phaseName}] EXECUTING ${String(tasks.length)} TASKS IN PARALLEL`,\n\t);\n\n\t// `allSettled`, not `all`: `all` rejects on the first failure, so an optional\n\t// service would take down required ones that were still running. Outcomes are\n\t// examined after everything has finished.\n\tconst outcomes = await Promise.allSettled(\n\t\ttasks.map(async (task) => {\n\t\t\tlankaLogger.printBootstrapLog(`[${phaseName}] EXECUTING: ${task.name}`);\n\t\t\tawait runWithDeadline(task);\n\t\t}),\n\t);\n\n\tfor (const [index, outcome] of outcomes.entries()) {\n\t\tif (outcome.status === \"rejected\") {\n\t\t\thandleTaskFailure(tasks[index], phaseName, outcome.reason);\n\t\t}\n\t}\n}\n\nfunction formatError(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n","import { createLanka } from \"../_factories/create-lanka/createLanka\";\nimport { createLankaHost } from \"../../config/_factories/create-lanka-host/createLankaHost\";\nimport type { ILankaHost } from \"../../config/_interfaces/ILankaHost\";\nimport type { ILankaInstance } from \"../_factories/create-lanka/createLanka\";\nimport type { TLankaStartConfig } from \"../_types/TLankaStartConfig\";\n\n/** The host as given, or one built from the base URL. */\nconst hostOf = (config: TLankaStartConfig): ILankaHost =>\n\tconfig.host ?? createLankaHost({ apiBaseUrl: config.apiBaseUrl, ...config.messages });\n\n/**\n * A started framework, in one call.\n *\n * ```ts\n * const lanka = await startLanka({ apiBaseUrl: \"https://api.example.com\" });\n * const sameOrigin = await startLanka(); // nothing to configure yet\n * ```\n *\n * ## What it saves, and what it does not hide\n *\n * `createLanka` already activates the instance it returns, and `bootstrap` already\n * brings up the scenario layer — so an application's whole start-up was two calls\n * and a `for` loop over its plugins. This is those, in the order that works, and\n * nothing else: what comes back is the same `ILankaInstance`, with every method\n * it always had.\n *\n * The order matters and is the reason this exists rather than a paragraph in a\n * guide. Plugins install BEFORE bootstrap, because a plugin that adds a\n * bootstrap service after the plan is built adds it to nothing; and bootstrap is\n * awaited, because a screen rendered against a half-started framework fails in\n * the layer it reaches first rather than where the mistake was.\n *\n * An application that needs something between those steps — registering a\n * singleton whose construction reads a service's result, say — writes the two\n * calls out and keeps this one for the next project.\n */\nexport const startLanka = async (config: TLankaStartConfig = {}): Promise<ILankaInstance> => {\n\tconst lanka = createLanka({ host: hostOf(config), flags: config.flags });\n\n\tfor (const plugin of config.plugins ?? []) lanka.use(plugin);\n\n\tawait lanka.bootstrap({ services: config.services, scenarios: config.scenarios });\n\n\treturn lanka;\n};\n","import { getActiveRuntime } from \"../../_internal/active-runtime/activeRuntime\";\nimport type { ILankaScenarioVM } from \"../../scenario/_interfaces/ILankaScenarioVM\";\n\n/**\n * Resets the active instance, if there is one.\n *\n * What the test kit needs between cases. Silent when there is no instance:\n * \"reset nothing\" is not an error, and the setup file runs before EVERY test,\n * including those that never bootstrap the framework.\n */\nexport function resetActiveLanka(): void {\n\tconst active = getActiveRuntime();\n\tif (!active) return;\n\n\tactive.viewModels.resetAll();\n\tactive.scenarios.clear();\n\tactive.eventBus.clearAllEvents();\n\tactive.scenarioState.bootstrapped = false;\n\tactive.scenarioState.initialized = new WeakSet<ILankaScenarioVM>();\n}\n","import type { ILankaInstance } from \"../../_factories/create-lanka/createLanka\";\nimport type { ILankaPlugin } from \"../../ILankaPlugin\";\n\n/**\n * A plugin, written as a class.\n *\n * The functional style is a function returning `{ name, install }`, and it is\n * what the framework's own five plugins use — a plugin is a value handed to\n * `use()`, and the ecosystem writes those as `react()`, `pinia()`,\n * `tsconfigPaths()`.\n *\n * This is the same contract for an application that keeps its plugins as\n * classes: `name` is a field, `install` is a method, and `uninstall` is the\n * teardown the framework calls on removal and on dispose. Nothing above can\n * tell which style wrote it — `use()` sees `ILankaPlugin` either way.\n */\nexport abstract class ALankaPlugin implements ILankaPlugin {\n\tpublic abstract readonly name: string;\n\n\t/** Everything this plugin puts in place. Return nothing if there is nothing to undo. */\n\tprotected abstract onInstall(lanka: ILankaInstance): void;\n\n\t/**\n\t * Removes what `onInstall` put in place.\n\t *\n\t * A default that does nothing rather than an abstract member: an abstract one\n\t * would break every existing subclass the day it was added, and most plugins\n\t * genuinely have nothing to undo.\n\t */\n\tprotected onUninstall(): void {\n\t\t/* Nothing to undo unless a subclass says otherwise. */\n\t}\n\n\tpublic install(lanka: ILankaInstance): () => void {\n\t\tthis.onInstall(lanka);\n\n\t\treturn () => {\n\t\t\tthis.onUninstall();\n\t\t};\n\t}\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-GV5DUYST.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LankaError
|
|
3
|
-
} from "./chunk-Q7QESSYF.js";
|
|
4
|
-
|
|
5
|
-
// src/validation/lanka-validation-error/LankaValidationError.ts
|
|
6
|
-
var LankaValidationError = class _LankaValidationError extends LankaError {
|
|
7
|
-
constructor(message, errors = []) {
|
|
8
|
-
super({
|
|
9
|
-
kind: "schema",
|
|
10
|
-
message,
|
|
11
|
-
status: 422,
|
|
12
|
-
issues: errors.length > 0 ? errors : [message]
|
|
13
|
-
});
|
|
14
|
-
this.name = "LankaValidationError";
|
|
15
|
-
Object.setPrototypeOf(this, _LankaValidationError.prototype);
|
|
16
|
-
}
|
|
17
|
-
/** Always present here, unlike the base's optional list. */
|
|
18
|
-
get errors() {
|
|
19
|
-
return [...this.issues ?? []];
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// src/validation/lanka-standard-validator/lankaStandardValidator.ts
|
|
24
|
-
function describeIssue(issue) {
|
|
25
|
-
const path = (issue.path ?? []).map(
|
|
26
|
-
(segment) => typeof segment === "object" && segment !== null && "key" in segment ? String(segment.key) : String(segment)
|
|
27
|
-
).join(".");
|
|
28
|
-
return path ? `${path}: ${issue.message}` : issue.message;
|
|
29
|
-
}
|
|
30
|
-
function runSync(schema, data) {
|
|
31
|
-
const result = schema["~standard"].validate(data);
|
|
32
|
-
if (result instanceof Promise) {
|
|
33
|
-
throw new LankaValidationError(
|
|
34
|
-
"The schema is asynchronous and the validation port is synchronous. Parse such data by hand: passing it silently would be worse than failing.",
|
|
35
|
-
[]
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
var lankaStandardValidator = Object.freeze({
|
|
41
|
-
validate(schema, data, context) {
|
|
42
|
-
const result = runSync(schema, data);
|
|
43
|
-
if (result.issues) {
|
|
44
|
-
throw new LankaValidationError(
|
|
45
|
-
`Validation failed for ${context}`,
|
|
46
|
-
result.issues.map(describeIssue)
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
return result.value;
|
|
50
|
-
},
|
|
51
|
-
validateSafe(schema, data) {
|
|
52
|
-
const result = runSync(schema, data);
|
|
53
|
-
if (result.issues) {
|
|
54
|
-
return { success: false, errors: result.issues.map(describeIssue) };
|
|
55
|
-
}
|
|
56
|
-
return { success: true, data: result.value };
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
export {
|
|
61
|
-
LankaValidationError,
|
|
62
|
-
lankaStandardValidator
|
|
63
|
-
};
|
|
64
|
-
//# sourceMappingURL=chunk-GV5DUYST.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/validation/lanka-validation-error/LankaValidationError.ts","../src/validation/lanka-standard-validator/lankaStandardValidator.ts"],"sourcesContent":["import { LankaError } from \"../../errors/lanka-error/LankaError\";\n\n/**\n * The validation port's failure: a body the schema refused.\n *\n * A `LankaError` of kind `schema`, so that `LankaError.is` and everything built\n * on it — the HTTP policy's error middleware, an application branching on\n * `kind` — see a refused body the way they see the request layer's own\n * \"200 that was not JSON\". It used to extend `Error` directly, and a consumer\n * that told a contract drift from a network failure by `kind` never saw it.\n *\n * `name` stays `LankaValidationError` and `status` stays 422: both are what a\n * consumer's existing `catch` reads, and the message list is `errors`.\n */\nexport class LankaValidationError extends LankaError {\n\tconstructor(message: string, errors: string[] = []) {\n\t\tsuper({\n\t\t\tkind: \"schema\",\n\t\t\tmessage,\n\t\t\tstatus: 422,\n\t\t\tissues: errors.length > 0 ? errors : [message],\n\t\t});\n\t\tthis.name = \"LankaValidationError\";\n\t\tObject.setPrototypeOf(this, LankaValidationError.prototype);\n\t}\n\n\t/** Always present here, unlike the base's optional list. */\n\toverride get errors(): string[] {\n\t\treturn [...(this.issues ?? [])];\n\t}\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { LankaValidationError } from \"../lanka-validation-error/LankaValidationError\";\nimport type { TLankaValidationResult } from \"../_types/TLankaValidationResult\";\n\n/**\n * Response body validation with any Standard Schema implementation.\n *\n * An abstraction typed by one library cannot be implemented by another, which is\n * the whole reason a validation port exists. Standard Schema is the shared\n * interface implemented by zod 4, valibot, arktype and others.\n *\n * `@standard-schema/spec` contains TYPES ONLY and adds no bytes to a build, so it\n * is a regular dependency rather than another peer.\n *\n * No adapter classes: a schema describes itself, and\n * `schema[\"~standard\"].validate(data)` is the whole protocol.\n */\n\n/** A schema the port understands: any Standard Schema implementation. */\nexport type TLankaSchema<TOutput = unknown> = StandardSchemaV1<unknown, TOutput>;\n\nexport interface ILankaValidator {\n\t/** Validates and returns the parsed value, or throws. */\n\tvalidate<TOutput>(schema: TLankaSchema<TOutput>, data: unknown, context: string): TOutput;\n\t/** Validates and returns an outcome, throwing nothing. */\n\tvalidateSafe<TOutput>(\n\t\tschema: TLankaSchema<TOutput>,\n\t\tdata: unknown,\n\t): TLankaValidationResult<TOutput>;\n}\n\n/**\n * A field path plus a message.\n *\n * The path is assembled WHOLE — `items.0.id`, not `id`. Without the index and the\n * parent the message points nowhere when the list has twenty items.\n */\nfunction describeIssue(issue: StandardSchemaV1.Issue): string {\n\tconst path = (issue.path ?? [])\n\t\t.map((segment) =>\n\t\t\ttypeof segment === \"object\" && segment !== null && \"key\" in segment\n\t\t\t\t? String(segment.key)\n\t\t\t\t: String(segment),\n\t\t)\n\t\t.join(\".\");\n\n\treturn path ? `${path}: ${issue.message}` : issue.message;\n}\n\nfunction runSync<TOutput>(\n\tschema: TLankaSchema<TOutput>,\n\tdata: unknown,\n): StandardSchemaV1.Result<TOutput> {\n\tconst result = schema[\"~standard\"].validate(data);\n\n\t// Standard Schema allows returning a promise. A synchronous port cannot await\n\t// it, and answering \"fine\" would let UNVALIDATED data through — a check that\n\t// cannot fail reporting success.\n\tif (result instanceof Promise) {\n\t\tthrow new LankaValidationError(\n\t\t\t\"The schema is asynchronous and the validation port is synchronous. Parse \" +\n\t\t\t\t\"such data by hand: passing it silently would be worse than failing.\",\n\t\t\t[],\n\t\t);\n\t}\n\n\treturn result;\n}\n\nexport const lankaStandardValidator: ILankaValidator = Object.freeze<ILankaValidator>({\n\tvalidate<TOutput>(schema: TLankaSchema<TOutput>, data: unknown, context: string): TOutput {\n\t\tconst result = runSync(schema, data);\n\n\t\tif (result.issues) {\n\t\t\tthrow new LankaValidationError(\n\t\t\t\t`Validation failed for ${context}`,\n\t\t\t\tresult.issues.map(describeIssue),\n\t\t\t);\n\t\t}\n\n\t\treturn result.value;\n\t},\n\n\tvalidateSafe<TOutput>(\n\t\tschema: TLankaSchema<TOutput>,\n\t\tdata: unknown,\n\t): TLankaValidationResult<TOutput> {\n\t\tconst result = runSync(schema, data);\n\n\t\tif (result.issues) {\n\t\t\treturn { success: false, errors: result.issues.map(describeIssue) };\n\t\t}\n\n\t\treturn { success: true, data: result.value };\n\t},\n});\n"],"mappings":";;;;;AAcO,IAAM,uBAAN,MAAM,8BAA6B,WAAW;AAAA,EACpD,YAAY,SAAiB,SAAmB,CAAC,GAAG;AACnD,UAAM;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ,OAAO,SAAS,IAAI,SAAS,CAAC,OAAO;AAAA,IAC9C,CAAC;AACD,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,sBAAqB,SAAS;AAAA,EAC3D;AAAA;AAAA,EAGA,IAAa,SAAmB;AAC/B,WAAO,CAAC,GAAI,KAAK,UAAU,CAAC,CAAE;AAAA,EAC/B;AACD;;;ACOA,SAAS,cAAc,OAAuC;AAC7D,QAAM,QAAQ,MAAM,QAAQ,CAAC,GAC3B;AAAA,IAAI,CAAC,YACL,OAAO,YAAY,YAAY,YAAY,QAAQ,SAAS,UACzD,OAAO,QAAQ,GAAG,IAClB,OAAO,OAAO;AAAA,EAClB,EACC,KAAK,GAAG;AAEV,SAAO,OAAO,GAAG,IAAI,KAAK,MAAM,OAAO,KAAK,MAAM;AACnD;AAEA,SAAS,QACR,QACA,MACmC;AACnC,QAAM,SAAS,OAAO,WAAW,EAAE,SAAS,IAAI;AAKhD,MAAI,kBAAkB,SAAS;AAC9B,UAAM,IAAI;AAAA,MACT;AAAA,MAEA,CAAC;AAAA,IACF;AAAA,EACD;AAEA,SAAO;AACR;AAEO,IAAM,yBAA0C,OAAO,OAAwB;AAAA,EACrF,SAAkB,QAA+B,MAAe,SAA0B;AACzF,UAAM,SAAS,QAAQ,QAAQ,IAAI;AAEnC,QAAI,OAAO,QAAQ;AAClB,YAAM,IAAI;AAAA,QACT,yBAAyB,OAAO;AAAA,QAChC,OAAO,OAAO,IAAI,aAAa;AAAA,MAChC;AAAA,IACD;AAEA,WAAO,OAAO;AAAA,EACf;AAAA,EAEA,aACC,QACA,MACkC;AAClC,UAAM,SAAS,QAAQ,QAAQ,IAAI;AAEnC,QAAI,OAAO,QAAQ;AAClB,aAAO,EAAE,SAAS,OAAO,QAAQ,OAAO,OAAO,IAAI,aAAa,EAAE;AAAA,IACnE;AAEA,WAAO,EAAE,SAAS,MAAM,MAAM,OAAO,MAAM;AAAA,EAC5C;AACD,CAAC;","names":[]}
|