lanka 1.3.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +1 -1
  2. package/dist/{LankaSharedStoreLocator-zS2kLu-S.d.ts → ALankaLocator-BUCCz0Q5.d.ts} +1 -47
  3. package/dist/ILankaReadableVM-BoPzHEPV.d.ts +72 -0
  4. package/dist/{ILankaScenarioVM-DpKFL3iE.d.ts → ILankaScenario-DQd9ZfUw.d.ts} +1 -30
  5. package/dist/{ILankaScenarioMetadata-Dj4GCqmX.d.ts → ILankaScenarioMetadata-BsDp0Bzm.d.ts} +1 -1
  6. package/dist/ILankaScenarioVM-DKwjbIRq.d.ts +30 -0
  7. package/dist/{LankaScenarioLocator-CAENLhDO.d.ts → LankaScenarioVMRegistry-DySAOaj2.d.ts} +4 -37
  8. package/dist/_extend/index.d.ts +235 -7
  9. package/dist/_extend/index.js +152 -12
  10. package/dist/_extend/index.js.map +1 -1
  11. package/dist/_internal/index.d.ts +13 -8
  12. package/dist/_internal/index.js +9 -3
  13. package/dist/_internal/index.js.map +1 -1
  14. package/dist/{activeRuntime-BYd2D6Lx.d.ts → activeRuntime-ByucLPhj.d.ts} +55 -4
  15. package/dist/bootstrap/index.d.ts +15 -10
  16. package/dist/bootstrap/index.js +14 -9
  17. package/dist/chunk-24MUFFHF.js +43 -0
  18. package/dist/chunk-24MUFFHF.js.map +1 -0
  19. package/dist/chunk-3R2NO47A.js +35 -0
  20. package/dist/chunk-3R2NO47A.js.map +1 -0
  21. package/dist/{chunk-5MAQVBI2.js → chunk-42CWYVZJ.js} +44 -61
  22. package/dist/chunk-42CWYVZJ.js.map +1 -0
  23. package/dist/{chunk-D27MREPB.js → chunk-7DQUF2QR.js} +2 -2
  24. package/dist/{chunk-UJEC7H6K.js → chunk-BHQ2SQ7P.js} +2 -2
  25. package/dist/chunk-BMF4TM2Z.js +22 -0
  26. package/dist/chunk-BMF4TM2Z.js.map +1 -0
  27. package/dist/{chunk-C2HP7CRD.js → chunk-FII3PW2G.js} +2 -2
  28. package/dist/{chunk-DTO27QFR.js → chunk-H5TIUKRK.js} +2 -2
  29. package/dist/chunk-JZJ6GXX3.js +12 -0
  30. package/dist/chunk-JZJ6GXX3.js.map +1 -0
  31. package/dist/{chunk-B7EYIAW7.js → chunk-MKKMTLOY.js} +47 -13
  32. package/dist/chunk-MKKMTLOY.js.map +1 -0
  33. package/dist/{chunk-UE2C76OR.js → chunk-N3275IPH.js} +2 -69
  34. package/dist/chunk-N3275IPH.js.map +1 -0
  35. package/dist/{chunk-O5ROO7QF.js → chunk-NWEHOMTS.js} +3 -3
  36. package/dist/{chunk-NDCJBO47.js → chunk-O5EUTNR6.js} +2 -2
  37. package/dist/{chunk-RKYKK6MN.js → chunk-RUMUFHSL.js} +2 -2
  38. package/dist/chunk-SBITVBF7.js +49 -0
  39. package/dist/chunk-SBITVBF7.js.map +1 -0
  40. package/dist/{chunk-UBGXDTXC.js → chunk-SO7D5E7O.js} +22 -16
  41. package/dist/chunk-SO7D5E7O.js.map +1 -0
  42. package/dist/chunk-UDP6IXDS.js +17 -0
  43. package/dist/chunk-UDP6IXDS.js.map +1 -0
  44. package/dist/{chunk-BGVDPDX4.js → chunk-XGMXT4XZ.js} +20 -3
  45. package/dist/chunk-XGMXT4XZ.js.map +1 -0
  46. package/dist/config/index.js +3 -3
  47. package/dist/{createLanka-NrlvN-WQ.d.ts → createLanka-DvUGu9Hl.d.ts} +2 -2
  48. package/dist/{createLankaScope-BiFxNQgl.d.ts → createLankaScope-CVoV0EwO.d.ts} +1 -1
  49. package/dist/errors/index.js +3 -3
  50. package/dist/gateway/index.js +6 -6
  51. package/dist/index.d.ts +14 -9
  52. package/dist/index.js +15 -10
  53. package/dist/locator/gateway/lanka-gateway-locator/LankaGatewayLocator.d.ts +18 -0
  54. package/dist/locator/gateway/lanka-gateway-locator/LankaGatewayLocator.js +9 -0
  55. package/dist/locator/gateway/lanka-gateway-locator/LankaGatewayLocator.js.map +1 -0
  56. package/dist/locator/index.d.ts +4 -2
  57. package/dist/locator/index.js +6 -3
  58. package/dist/locator/index.js.map +1 -1
  59. package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.d.ts +60 -0
  60. package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.js +12 -0
  61. package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.js.map +1 -0
  62. package/dist/locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.d.ts +22 -0
  63. package/dist/locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.js +9 -0
  64. package/dist/locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.js.map +1 -0
  65. package/dist/locator/singleton/lanka-singleton-locator/LankaSingletonLocator.d.ts +30 -0
  66. package/dist/locator/singleton/lanka-singleton-locator/LankaSingletonLocator.js +9 -0
  67. package/dist/locator/singleton/lanka-singleton-locator/LankaSingletonLocator.js.map +1 -0
  68. package/dist/logger/index.js +3 -3
  69. package/dist/mock/index.js +3 -3
  70. package/dist/scenario/index.d.ts +29 -3
  71. package/dist/scenario/index.js +5 -5
  72. package/dist/stream/index.d.ts +17 -12
  73. package/dist/viewmodel/index.d.ts +139 -28
  74. package/dist/viewmodel/index.js +95 -126
  75. package/dist/viewmodel/index.js.map +1 -1
  76. package/package.json +3 -4
  77. package/skills/lanka-core/SKILL.md +18 -16
  78. package/skills/lanka-core/reference.md +135 -58
  79. package/skills/lanka-packages/SKILL.md +94 -22
  80. package/dist/chunk-5MAQVBI2.js.map +0 -1
  81. package/dist/chunk-B7EYIAW7.js.map +0 -1
  82. package/dist/chunk-BGVDPDX4.js.map +0 -1
  83. package/dist/chunk-UBGXDTXC.js.map +0 -1
  84. package/dist/chunk-UE2C76OR.js.map +0 -1
  85. /package/dist/{chunk-D27MREPB.js.map → chunk-7DQUF2QR.js.map} +0 -0
  86. /package/dist/{chunk-UJEC7H6K.js.map → chunk-BHQ2SQ7P.js.map} +0 -0
  87. /package/dist/{chunk-C2HP7CRD.js.map → chunk-FII3PW2G.js.map} +0 -0
  88. /package/dist/{chunk-DTO27QFR.js.map → chunk-H5TIUKRK.js.map} +0 -0
  89. /package/dist/{chunk-O5ROO7QF.js.map → chunk-NWEHOMTS.js.map} +0 -0
  90. /package/dist/{chunk-NDCJBO47.js.map → chunk-O5EUTNR6.js.map} +0 -0
  91. /package/dist/{chunk-RKYKK6MN.js.map → chunk-RUMUFHSL.js.map} +0 -0
@@ -1,10 +1,10 @@
1
1
  <!-- Generated from core/GUIDE.md by scripts/skills.mjs. Edit the guide. -->
2
2
 
3
- > **`lanka@1.3.0`** — this document describes that version.
3
+ > **`lanka@2.0.1`** — this document describes that version.
4
4
  >
5
- > Install: `npm install lanka react zustand` (the peers are not optional; only npm adds a missing one for you).
5
+ > Install: `npm install lanka zustand` (the peers are not optional; only npm adds a missing one for you).
6
6
  >
7
- > Complete code, compiled and run in CI: [core/_playground/playground.test.tsx](https://github.com/lankajs/lanka/blob/main/core/_playground/playground.test.tsx)
7
+ > Complete code, compiled and run in CI: [core/_playground/playground.test.ts](https://github.com/lankajs/lanka/blob/main/core/_playground/playground.test.ts)
8
8
 
9
9
  # lanka — user guide
10
10
 
@@ -27,8 +27,31 @@ If you want to know _why_ the framework is shaped this way, read
27
27
  > required: [ARCHITECTURE.md](https://github.com/lankajs/lanka/blob/main/ARCHITECTURE.md) separates the five things the
28
28
  > machine checks from the many that are advice you can take or leave.
29
29
 
30
+ ## When to reach for this
31
+
32
+ Every other package here is adopted one at a time; this one is the framework, so
33
+ the question is the other way round. Reach for lanka when an application has
34
+ SERVER STATE that several screens care about — a list one screen changes and
35
+ another shows, a session two screens read, a fact that has to travel without one
36
+ screen importing another. That is what the gateway, the ViewModel and the
37
+ scenario bus are for, and it is the shape that gets expensive when it is left to
38
+ grow by itself.
39
+
40
+ Do not reach for it for a form, a landing page, or a screen that loads once and
41
+ sits still: a `useState` and a `fetch` are the right size for those, and this
42
+ framework would be four layers around nothing.
43
+
44
+ It is not an application shell either. Your host framework — Next, React Router,
45
+ TanStack Start, Astro, Expo — keeps routing, rendering and caching;
46
+ [ARCHITECTURE.md](https://github.com/lankajs/lanka/blob/main/ARCHITECTURE.md#inside-another-framework) draws the line.
47
+
48
+ It binds no UI framework of its own: core imports no UI library at all, and
49
+ reading a ViewModel from a screen is one of the five packages in
50
+ `modules/bindings/`.
51
+
30
52
  ## Contents
31
53
 
54
+ - [When to reach for this](#when-to-reach-for-this)
32
55
  - [Install](#install)
33
56
  - [Your first application](#your-first-application)
34
57
  - [The layers, and which way imports go](#the-layers-and-which-way-imports-go)
@@ -60,16 +83,28 @@ If you want to know _why_ the framework is shaped this way, read
60
83
  ## Install
61
84
 
62
85
  ```bash
63
- npm install lanka react react-dom zustand
64
- pnpm add lanka react react-dom zustand
65
- yarn add lanka react react-dom zustand
66
- bun add lanka react react-dom zustand
86
+ npm install lanka zustand
87
+ pnpm add lanka zustand
88
+ yarn add lanka zustand
89
+ bun add lanka zustand
67
90
  ```
68
91
 
69
- **The peers are listed on purpose.** `react` 19 and `zustand` 5 are peer
70
- dependencies, and only npm installs those for you. Under pnpm, yarn or bun a
71
- missing peer is a warning at install time and a resolution error at build time —
72
- name them once and the question never comes up.
92
+ **The peer is listed on purpose.** `zustand` 5 is core's one peer dependency,
93
+ and only npm installs a missing peer for you. Under pnpm, yarn or bun it is a
94
+ warning at install time and a resolution error at build time — name it once and
95
+ the question never comes up.
96
+
97
+ **No UI framework is in that line, and that is not an omission.** Core imports no
98
+ UI library at all: a ViewModel is a store, a gateway is a function, and neither
99
+ knows what renders. Reading a ViewModel from a screen is one package more, the
100
+ one for your framework:
101
+
102
+ ```bash
103
+ npm install @lankajs/react # or @lankajs/vue, @lankajs/svelte, @lankajs/solid, @lankajs/angular
104
+ ```
105
+
106
+ All five publish the same name — `useLankaVM` — so the guide you read for one
107
+ is the guide for the next.
73
108
 
74
109
  > [!NOTE]
75
110
  > Any package manager works, and nothing in the framework knows which one you
@@ -599,16 +634,16 @@ more protected member, `toLifecycleHooks`, is the framework's own reading of
599
634
 
600
635
  ### Config reference
601
636
 
602
- | Field | Meaning |
603
- | ---------------------------------- | --------------------------------------------------- |
604
- | `name` | Shown in logs and devtools. Required |
605
- | `states` | The initial state. Omit for a stateless ViewModel |
606
- | `createActions` | Receives the context, returns the actions |
607
- | `gateways` / `services` | An object or a factory; reachable as `gateways.x` |
608
- | `scenarioHandlers` | `{ scenario, handler }` pairs, bound at bootstrap |
609
- | `enhancers` | Store enhancers, zustand style |
637
+ | Field | Meaning |
638
+ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
639
+ | `name` | Shown in logs and devtools. Required |
640
+ | `states` | The initial state. Omit for a stateless ViewModel |
641
+ | `createActions` | Receives the context, returns the actions |
642
+ | `gateways` / `services` | An object or a factory; reachable as `gateways.x` |
643
+ | `scenarioHandlers` | `{ scenario, handler }` pairs, bound at bootstrap |
644
+ | `enhancers` | Store enhancers, zustand style |
610
645
  | `onInit` / `onReset` | Lifecycle hooks over the same context. Declaring either enrols the ViewModel with scenario bootstrap: `onInit` runs when bootstrap binds it, `onReset` when the instance — or a lazy ViewModel — is disposed |
611
- | `enableAccessTrackingOptimization` | Default on; see [common mistakes](#common-mistakes) |
646
+ | `enableAccessTrackingOptimization` | Default on; see [common mistakes](#common-mistakes) |
612
647
 
613
648
  ### Using one in a component
614
649
 
@@ -626,6 +661,48 @@ The hook re-renders a component only for the keys it actually **read**. That is
626
661
  usually free performance, and it has one blind spot, described at the end of this
627
662
  guide.
628
663
 
664
+ ### A selector that builds its answer
665
+
666
+ Every binding also takes a selector, and then the selector decides instead of
667
+ the tracking. A selector that **builds** what it answers has a trap in it:
668
+
669
+ ```ts
670
+ useLankaVM(missionVM, (state) => ({ title: state.title, status: state.status }));
671
+ ```
672
+
673
+ That object is new on every call, so it is never identical to the previous one,
674
+ and a binding compares selections by identity. The reader is therefore woken by
675
+ every change in the ViewModel — including the keys the selector exists to
676
+ ignore. A selector answering a **primitive** never has the problem, which is what
677
+ makes this quiet: the shape that is free and the shape that repaints on
678
+ everything look the same on the page.
679
+
680
+ `createLankaShallowHold` is the comparison that fixes it. It answers the
681
+ **previous** object while nothing in the selection moved, one level deep — own
682
+ keys, same count, `Object.is` on each value, arrays included:
683
+
684
+ ```ts
685
+ import { createLankaShallowHold } from "lanka/viewmodel";
686
+
687
+ const hold = createLankaShallowHold<{ title: string; status: string }>();
688
+
689
+ const view = useLankaVM(missionVM, (state) => hold({ title: state.title, status: state.status }));
690
+ ```
691
+
692
+ One hold per reader, created once beside the ViewModel read — never shared
693
+ between two components, because the answer it holds belongs to whoever selected
694
+ it.
695
+
696
+ > [!NOTE]
697
+ > In React, write `useLankaShallow` from `@lankajs/react` instead. It is the same
698
+ > comparison as a hook, and React needs one: a component re-runs the hook on
699
+ > every render, so the holding has to survive a render while the selector stays
700
+ > the current one.
701
+
702
+ A selection with a **nested** object wants a selector that picks the leaves.
703
+ Comparing deeper would mean walking a state of unknown size on every read, which
704
+ is the cost a selector was taken to avoid.
705
+
629
706
  ## Choosing a coordination tool
630
707
 
631
708
  When "A must affect B", one question decides it: **do A and B co-own a thing, or
@@ -788,15 +865,15 @@ Things worth knowing:
788
865
  - **A failing handler cannot take the dispatch down**, and that holds for an
789
866
  async one too. A handler is typed `(data) => void`, but TypeScript assigns a
790
867
  `Promise<void>` to a void return position — so `async () => { await refetch();
791
- }` compiles with nothing to warn about, and "refetch when the stream
868
+ }` compiles with nothing to warn about, and "refetch when the stream
792
869
  reconnects" is the ordinary shape rather than an exotic one. Its rejection is
793
870
  caught and written to the scenario log, not left to surface as an unhandled
794
871
  rejection in whatever ran next.
795
872
 
796
- What the framework cannot do is decide what the failure MEANT. A log line is a
797
- diagnostic, not a retry and not a message on a screen — so an action called
798
- from a handler should still own its own failure, because the handler returns
799
- `void` and has nowhere to put one.
873
+ What the framework cannot do is decide what the failure MEANT. A log line is a
874
+ diagnostic, not a retry and not a message on a screen — so an action called
875
+ from a handler should still own its own failure, because the handler returns
876
+ `void` and has nowhere to put one.
800
877
 
801
878
  - **Middleware returns a decision** — `"pass"` or `{ stop: reason }` — never
802
879
  `next()`. A middleware that forgot to call `next()` would make the event vanish
@@ -838,28 +915,28 @@ the whole cost a form library exists to remove — and here you get it for free.
838
915
 
839
916
  ### Add a form library when the form grows its own behaviour
840
917
 
841
- | Reach for one when | Why the ViewModel stops being enough |
842
- | -------------------------------------------------------- | ---------------------------------------------------- |
843
- | a field ARRAY — rows the user adds and removes | rows force nesting, and nesting is not tracked |
844
- | validation on every keystroke | the whole object re-checked per character |
845
- | one field's validity depends on another's value | written by hand, it drifts |
846
- | `touched` / `dirty` / blur as behaviour, not decoration | three booleans per input in a screen's state |
847
- | the screen is server-rendered WITH values | see the warning at the end of this section |
918
+ | Reach for one when | Why the ViewModel stops being enough |
919
+ | ------------------------------------------------------- | ---------------------------------------------- |
920
+ | a field ARRAY — rows the user adds and removes | rows force nesting, and nesting is not tracked |
921
+ | validation on every keystroke | the whole object re-checked per character |
922
+ | one field's validity depends on another's value | written by hand, it drifts |
923
+ | `touched` / `dirty` / blur as behaviour, not decoration | three booleans per input in a screen's state |
924
+ | the screen is server-rendered WITH values | see the warning at the end of this section |
848
925
 
849
926
  Not on the list: how many forms the application has. A big application of flat
850
927
  forms needs nothing; one wizard with dynamic rows does.
851
928
 
852
929
  ### The boundary
853
930
 
854
- | | Owner |
855
- | --- | --- |
856
- | values, `touched`/`dirty`, focus, per-input messages | the **form** |
857
- | `isSubmitting` | the form — unless it is visible outside the form (a global overlay) |
858
- | `defaultValues` | the ViewModel: the server's version, held as `server` |
859
- | calling the gateway, `trigger`, clearing a draft, navigating | the **ViewModel** |
860
- | an input's ASYNCHRONOUS check | a ViewModel action — a resolver may not call a gateway |
861
- | failures with an address | pass through the ViewModel to the form |
862
- | failures without one | the ViewModel's own state |
931
+ | | Owner |
932
+ | ------------------------------------------------------------ | ------------------------------------------------------------------- |
933
+ | values, `touched`/`dirty`, focus, per-input messages | the **form** |
934
+ | `isSubmitting` | the form — unless it is visible outside the form (a global overlay) |
935
+ | `defaultValues` | the ViewModel: the server's version, held as `server` |
936
+ | calling the gateway, `trigger`, clearing a draft, navigating | the **ViewModel** |
937
+ | an input's ASYNCHRONOUS check | a ViewModel action — a resolver may not call a gateway |
938
+ | failures with an address | pass through the ViewModel to the form |
939
+ | failures without one | the ViewModel's own state |
863
940
 
864
941
  The last row is the interesting one, and the two before it are why a form cannot
865
942
  be left to talk to the network itself.
@@ -874,11 +951,11 @@ no adapter to write and nothing declared twice.
874
951
  Keep the INPUT schema apart from the response ones. A form given the response
875
952
  schema asks for an `id` and an `updatedAt` the user does not have:
876
953
 
877
- | Schema | Read by | Changes when |
878
- | ------------- | -------------------------------- | -------------------- |
879
- | `orderWire` | the gateway, on the way in | the backend changes |
880
- | `order` | the gateway, the domain check | the application does |
881
- | `orderInput` | **the form's resolver AND the gateway's payload check** | what a person may type changes |
954
+ | Schema | Read by | Changes when |
955
+ | ------------ | ------------------------------------------------------- | ------------------------------ |
956
+ | `orderWire` | the gateway, on the way in | the backend changes |
957
+ | `order` | the gateway, the domain check | the application does |
958
+ | `orderInput` | **the form's resolver AND the gateway's payload check** | what a person may type changes |
882
959
 
883
960
  ### A failure that knows which input it belongs to
884
961
 
@@ -937,9 +1014,9 @@ your decision, not a framework's.
937
1014
  const order = await gateways.orderGateway.update(id, values);
938
1015
 
939
1016
  set({ server: order, serverChangedAt: null }); // 1. mark your own write
940
- trigger(orderUpdated, { order }); // 2. announce it, WITH the data
941
- services.cache?.write(["order", id], order); // 3. tell a cache, if you have one
942
- return { ok: true, data: order }; // 4. the form is still mounted here
1017
+ trigger(orderUpdated, { order }); // 2. announce it, WITH the data
1018
+ services.cache?.write(["order", id], order); // 3. tell a cache, if you have one
1019
+ return { ok: true, data: order }; // 4. the form is still mounted here
943
1020
  // 5. navigate — after this returns
944
1021
  ```
945
1022
 
@@ -1053,14 +1130,14 @@ lanka at all: a Standard Schema in, `ILankaFieldError[]` out.
1053
1130
  the wire is server-sent events, a WebSocket, a GraphQL subscription or a gRPC
1054
1131
  server stream.
1055
1132
 
1056
- | Name | What it is |
1057
- | --------------------------------- | ---------------------------------------------------------------- |
1133
+ | Name | What it is |
1134
+ | --------------------------------- | --------------------------------------------------------------------- |
1058
1135
  | `ILankaServerEventTransport` | the port: `isSupported`, `connect`, `disconnect`, `on`, `onReconnect` |
1059
- | `ALankaStreamBridge` | a wire event → one of your scenarios |
1060
- | `createLankaStreamBridge` | the same, written by calling |
1061
- | `createLankaStreamTriggerContext` | the "this came from outside" marker |
1062
- | `ALankaStreamTransport` | the base a transport extends: dispatch, and the reconnect ladder |
1063
- | `lankaStream` | the plugin: bridges attached, lifetime owned |
1136
+ | `ALankaStreamBridge` | a wire event → one of your scenarios |
1137
+ | `createLankaStreamBridge` | the same, written by calling |
1138
+ | `createLankaStreamTriggerContext` | the "this came from outside" marker |
1139
+ | `ALankaStreamTransport` | the base a transport extends: dispatch, and the reconnect ladder |
1140
+ | `lankaStream` | the plugin: bridges attached, lifetime owned |
1064
1141
 
1065
1142
  You normally reach these through a protocol package —
1066
1143
  [`@lankajs/plugin-sse`](https://github.com/lankajs/lanka/blob/main/plugins/sse/GUIDE.md),
@@ -1103,7 +1180,7 @@ the backoff ladder with its attempt ceiling and one auth refresh, and
1103
1180
  `onReconnect`. A subclass writes `open` and `close`, and reports through three
1104
1181
  handlers — `opened`, `received`, `lost`. Nothing else, and deliberately: the bug
1105
1182
  this shape is prone to is resetting the attempt counter where reconnection
1106
- *starts* rather than where it *succeeds*, after which the ceiling exists, reads
1183
+ _starts_ rather than where it _succeeds_, after which the ceiling exists, reads
1107
1184
  as a guard, and can never fire.
1108
1185
 
1109
1186
  ## The locator
@@ -1310,7 +1387,7 @@ npx lanka-skills sync
1310
1387
 
1311
1388
  ### The modules and plugins
1312
1389
 
1313
- Twenty-one more packages solve problems you may or may not have — four wire
1390
+ Thirty-seven more packages solve problems you may or may not have — four wire
1314
1391
  protocols, retry policy, optimistic updates, list handling, storage,
1315
1392
  prefetching. The table of
1316
1393
  "add it when" is in [ARCHITECTURE.md](https://github.com/lankajs/lanka/blob/main/ARCHITECTURE.md#adopting-the-packages),
@@ -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, caching, prefetching, optimistic updates, list handling, validation, cookies, start-up stages or a devtool — or when deciding whether something belongs in application code at all.
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: "1.3.0"
8
+ version: "2.0.1"
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 | 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
- | validating responses with zod / valibot | `@lankajs/zod`, `@lankajs/valibot` | `lanka-zod`, `lanka-valibot` |
32
- | retry, idempotency, timeouts, auth refresh, CSRF | `@lankajs/plugin-http` | `lanka-http` |
33
- | realtime updates pushed by the server | `@lankajs/plugin-sse` | `lanka-sse` |
34
- | warming data or route code before the user asks | `@lankajs/plugin-prefetch` | `lanka-prefetch` |
35
- | ordered start-up stages that can redirect | `@lankajs/plugin-bootstrap-steps` | `lanka-bootstrap-steps` |
36
- | seeing what the bus and the logger did | `@lankajs/plugin-devtools` | `lanka-devtools` |
37
- | the `@lanka_di` wiring and its build-time check | `@lankajs/tool-di` | `lanka-di` |
38
- | architectural boundaries as lint rules | `@lankajs/tool-eslint` | `lanka-eslint` |
39
- | testing, and benchmarking | `@lankajs/tool-testing` | `lanka-testing` |
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
- - Both `@lankajs/zod` and `@lankajs/valibot`. Pick one; the point is that the choice
53
- is visible in your dependency list.
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/locator/gateway/lanka-gateway-locator/LankaGatewayLocator.ts","../src/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.ts","../src/locator/_factories/create-lanka-scope/createLankaScope.ts"],"sourcesContent":["import * as GatewaysModule from \"@lanka_di/Gateways\";\nimport type { ALankaGateway } from \"../../../gateway/_abstractions/lanka-gateway/ALankaGateway\";\nimport { ALankaLocator } from \"../../_abstractions/lanka-locator/ALankaLocator\";\nimport { findExportedClass } from \"../../_internal/find-exported-class/findExportedClass\";\n\n/**\n * Resolves a gateway by property name (camelCase) or class name (PascalCase),\n * constructing it lazily and caching the instance.\n *\n * Adding a gateway takes one export line in `@lanka_di/Gateways`: types are\n * inferred and nothing is registered.\n */\nexport class LankaGatewayLocator extends ALankaLocator<ALankaGateway<unknown>> {\n\tconstructor() {\n\t\tsuper({\n\t\t\tfindClassByName: (gatewayName: string) =>\n\t\t\t\tfindExportedClass<ALankaGateway<unknown>>(GatewaysModule, gatewayName),\n\t\t\tnotFoundError: (gatewayName, propertyName) =>\n\t\t\t\t`Gateway \"${gatewayName}\" (accessed as \"${propertyName}\") not found. ` +\n\t\t\t\t`Make sure the gateway class extends ALankaGateway and is exported from lankaGateways.ts.`,\n\t\t});\n\t}\n}\n","import { LankaScenariosRegistry } from \"../../../scenario/_registries/lanka-scenarios-registry/LankaScenariosRegistry\";\nimport { ILankaScenario } from \"../../../scenario/_interfaces/ILankaScenario\";\nimport { ALankaScenario } from \"../../../scenario/_abstractions/lanka-scenario/ALankaScenario\";\nimport { ALankaLocator } from \"../../_abstractions/lanka-locator/ALankaLocator\";\n/**\n * Classes come from the consumer's barrel.\n *\n * Adding one is ONE export line: types are inferred, autocomplete works, and no\n * list has to be maintained.\n */\nimport * as ScenariosModule from \"@lanka_di/Scenarios\";\n\n/**\n * The barrel's class whose INSTANCE carries this name, when the export key does\n * not.\n *\n * A pooled instance answers for its class without constructing another: the\n * pool is never drained, so a lookup that constructed every class on every miss\n * grew it by the whole barrel each time a bad name was asked for. Only a class\n * the pool has never seen is constructed here — once, and it joins the pool.\n */\nconst findScenarioClassByInstanceName = (\n\tscenarioName: string,\n): (new () => ILankaScenario<unknown>) | undefined => {\n\tconst pooled = ALankaScenario.getAutoRegisteredScenarios();\n\n\tfor (const value of Object.values(ScenariosModule)) {\n\t\tif (typeof value !== \"function\" || !value.prototype) continue;\n\n\t\tconst Class = value as new () => ALankaScenario<unknown>;\n\t\tconst instance =\n\t\t\tpooled.find((scenario) => scenario.constructor === Class) ?? construct(Class);\n\t\tif (instance?.name === scenarioName) return Class;\n\t}\n\n\treturn undefined;\n};\n\n/** A new instance, or `undefined` for an export that is not constructible. */\nconst construct = (\n\tClass: new () => ALankaScenario<unknown>,\n): ALankaScenario<unknown> | undefined => {\n\ttry {\n\t\treturn new Class();\n\t} catch {\n\t\treturn undefined;\n\t}\n};\n\n/**\n * Resolves scenarios by property name (camelCase) or scenario name\n * (PascalCase).\n *\n * Looks in the registry, then in the self-registration pool, and failing both\n * constructs the scenario from the consumer's barrel on first use.\n */\nexport class LankaScenarioLocator extends ALankaLocator<ILankaScenario<unknown>> {\n\t/**\n\t * The registry is read LAZILY rather than in a field initialiser.\n\t *\n\t * The locator is constructed INSIDE `createLanka`, before the instance becomes\n\t * active. A field initialiser would ask for the active instance at that moment\n\t * and fail with \"no instance yet\" while the instance being created is on the\n\t * stack. Deferred reading resolves it: by the first scenario resolution an\n\t * active instance exists.\n\t */\n\tprivate get registry(): LankaScenariosRegistry {\n\t\treturn LankaScenariosRegistry.getInstance();\n\t}\n\n\tconstructor() {\n\t\tsuper({\n\t\t\tfindClassByName: (scenarioName: string) => {\n\t\t\t\t// By export key first: an instance name does not always survive minification.\n\t\t\t\tif (scenarioName in ScenariosModule) {\n\t\t\t\t\t// `as unknown` before the guards, exactly as in the singleton locator.\n\t\t\t\t\t// An EMPTY barrel — what the scaffolder writes, and what a new project\n\t\t\t\t\t// has until its first scenario — makes the module's keys `never`, so\n\t\t\t\t\t// an indexed access is `never` and reading `.prototype` off it does\n\t\t\t\t\t// not compile.\n\t\t\t\t\tconst ScenarioClass = ScenariosModule[\n\t\t\t\t\t\tscenarioName as keyof typeof ScenariosModule\n\t\t\t\t\t] as unknown;\n\t\t\t\t\tif (\n\t\t\t\t\t\ttypeof ScenarioClass === \"function\" &&\n\t\t\t\t\t\t\"prototype\" in ScenarioClass &&\n\t\t\t\t\t\tScenarioClass.prototype\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn ScenarioClass as new () => ILankaScenario<unknown>;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Fallback: look up by instance name.\n\t\t\t\treturn findScenarioClassByInstanceName(scenarioName);\n\t\t\t},\n\t\t\tgetInstanceByName: (scenarioName: string) => {\n\t\t\t\t// The registry first.\n\t\t\t\tconst registered = this.registry.getScenarioByName(scenarioName);\n\t\t\t\tif (registered) {\n\t\t\t\t\treturn registered;\n\t\t\t\t}\n\n\t\t\t\t// Then those that registered themselves.\n\t\t\t\tconst autoRegistered = ALankaScenario.getAutoRegisteredScenarios();\n\t\t\t\tconst scenario = autoRegistered.find((s) => s.name === scenarioName);\n\n\t\t\t\tif (scenario) {\n\t\t\t\t\tif (!this.registry.isRegistered(scenarioName)) {\n\t\t\t\t\t\tthis.registry.register(scenario);\n\t\t\t\t\t}\n\t\t\t\t\treturn scenario;\n\t\t\t\t}\n\n\t\t\t\t// Then finding the class by name and constructing it.\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t\tcreateInstance: (ScenarioClass) => {\n\t\t\t\tconst instance = new ScenarioClass();\n\t\t\t\tconst scenarioName = instance.name;\n\t\t\t\tif (!this.registry.isRegistered(scenarioName)) {\n\t\t\t\t\tthis.registry.register(instance);\n\t\t\t\t}\n\t\t\t\treturn instance;\n\t\t\t},\n\t\t\tnotFoundError: (scenarioName, propertyName) =>\n\t\t\t\t`Scenario \"${scenarioName}\" (accessed as \"${propertyName}\") not found. ` +\n\t\t\t\t`Make sure the scenario class extends ALankaScenario and has name=\"${scenarioName}\".`,\n\t\t});\n\t}\n}\n","import { caseConvert } from \"../../../_internal/case-convert/caseConvert\";\nimport { lankaLogger } from \"../../../logger/lanka-logger/LankaLogger\";\nimport type { LankaSingletonLocator } from \"../../singleton/lanka-singleton-locator/LankaSingletonLocator\";\n\n/**\n * A service lifetime other than \"as long as the application lives\".\n *\n * Everything resolved through `lankaSingletons.*` is a singleton for the life of\n * the app. Per-route, per-session and per-modal lifetimes otherwise rest on\n * discipline: every ViewModel calling `resetScenario()` and writing its own\n * `onReset`, in any of which it can be forgotten.\n *\n * A scope provides the same by construction: an object created in it goes away\n * with it, and nobody has to remember.\n *\n * ## What a scope does NOT do\n *\n * It does not shadow root objects and does not take them on close. It takes only\n * ITS OWN — the ones it created. A scope taking others' would be more dangerous\n * than no scopes at all: closing a screen would break the app.\n */\nexport interface ILankaScope {\n\t/** Resolves a service in this scope, creating it on first use. */\n\tresolve<TInstance>(propertyName: string): TInstance;\n\t/** Disposes everything this scope created and closes it. Idempotent. */\n\tdispose(): void;\n\t/** Whether the scope is closed. */\n\tisDisposed(): boolean;\n}\n\n/** A service may have a `dispose` — or may not. */\ninterface IMaybeDisposable {\n\tdispose?: () => void;\n}\n\nexport function createLankaScope(singletons: LankaSingletonLocator): ILankaScope {\n\tconst instances = new Map<string, unknown>();\n\tlet disposed = false;\n\n\treturn {\n\t\tresolve<TInstance>(propertyName: string): TInstance {\n\t\t\tif (disposed) {\n\t\t\t\t// Resolving from a closed scope is almost always a reference leaked from\n\t\t\t\t// an already-unmounted screen. Silently handing out an object would\n\t\t\t\t// extend the life of what was closed, invisibly.\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`The scope is closed: \"${propertyName}\" can no longer be resolved in it. ` +\n\t\t\t\t\t\t`This is usually a scope reference that outlived the screen that created it.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst className = caseConvert(propertyName, \"pascalCase\");\n\t\t\tif (instances.has(className)) return instances.get(className) as TInstance;\n\n\t\t\t// The class comes from the root locator while the OBJECT is created here:\n\t\t\t// a scope is a different lifetime, not a different set of services.\n\t\t\tconst instance = singletons.createScopedInstance(className, propertyName);\n\t\t\tinstances.set(className, instance);\n\t\t\treturn instance as TInstance;\n\t\t},\n\n\t\tdispose(): void {\n\t\t\tif (disposed) return;\n\t\t\tdisposed = true;\n\n\t\t\tfor (const [name, instance] of instances) {\n\t\t\t\tconst disposable = instance as IMaybeDisposable;\n\t\t\t\tif (typeof disposable.dispose !== \"function\") continue;\n\t\t\t\ttry {\n\t\t\t\t\tdisposable.dispose();\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// One failure must not leave the rest alive: that is exactly what a\n\t\t\t\t\t// scope exists to prevent.\n\t\t\t\t\tlankaLogger.printBootstrapLog(\n\t\t\t\t\t\t`Failed to dispose \"${name}\" in the scope`,\n\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tinstances.clear();\n\t\t},\n\n\t\tisDisposed: () => disposed,\n\t};\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap.ts"],"sourcesContent":["import type { ILankaScenario } from \"../_interfaces/ILankaScenario\";\nimport type { ILankaScenarioVM } from \"../_interfaces/ILankaScenarioVM\";\nimport type { ILankaScenarioResetConfig } from \"../_interfaces/ILankaScenarioResetConfig\";\nimport { LankaScenariosRegistry } from \"../_registries/lanka-scenarios-registry/LankaScenariosRegistry\";\nimport { LankaScenarioVMRegistry } from \"../_registries/lanka-scenario-vm-registry/LankaScenarioVMRegistry\";\nimport { ALankaScenario } from \"../_abstractions/lanka-scenario/ALankaScenario\";\nimport { lankaLogger } from \"../../logger/lanka-logger/LankaLogger\";\n\n/**\n * Brings up the scenario layer — deliberately React-free.\n *\n * Two jobs: declare every scenario's events on the bus as early as possible,\n * before the first ViewModel exists; and let a ViewModel raise its own\n * subscriptions at construction.\n *\n * Called from the application entry point before ViewModels are imported.\n */\n\n// The barrel of every scenario class: what keeps bootstrap in step with the list.\nimport * as ScenariosModule from \"@lanka_di/Scenarios\";\nimport { lankaEventBus } from \"../event-bus/_facades/lanka-event-bus/lankaEventBus\";\nimport {\n\tgetActiveRuntime,\n\trequireActiveRuntime,\n} from \"../../_internal/active-runtime/activeRuntime\";\n\n/**\n * Bootstrapping the scenario layer, for callers with no instance in hand.\n *\n * An ordinary class with an ordinary instance: the declared-ViewModel list below\n * is deliberately shared across instances — every test builds a new framework and\n * module-level ViewModels are declared once — so the single instance IS the\n * shared list, and a second one would be a second list nobody adopts.\n */\nexport class LankaScenarioBootstrap {\n\t/**\n\t * The bootstrapped flag and the set of initialised VMs live on the INSTANCE.\n\t *\n\t * As static fields, a second framework in the same process would exit\n\t * `bootstrap()` on someone else's flag — never starting at all, with no symptom\n\t * except that no scenario works.\n\t */\n\tprivate get state() {\n\t\treturn requireActiveRuntime().scenarioState;\n\t}\n\n\tpublic isScenarioLayerBootstrapped(): boolean {\n\t\treturn this.state.bootstrapped;\n\t}\n\n\tprivate forceInstantiateAllScenarios(): void {\n\t\t// Every class the pool already holds an instance of. Constructing it again\n\t\t// would add a second instance to a pool nothing ever drains: each bootstrap\n\t\t// — one per framework instance, one per test — grew it by the whole barrel,\n\t\t// and both `collectAutoRegisteredScenarios` and the locator's fallback walk\n\t\t// it in full.\n\t\tconst pooled = new Set(\n\t\t\tALankaScenario.getAutoRegisteredScenarios().map((scenario) => scenario.constructor),\n\t\t);\n\n\t\tfor (const exported of Object.values(ScenariosModule)) {\n\t\t\t// Only class constructors are relevant.\n\t\t\tif (typeof exported !== \"function\") continue;\n\t\t\tif (pooled.has(exported)) continue;\n\n\t\t\ttry {\n\t\t\t\t// Constructing an ALankaScenario subclass puts it into the\n\t\t\t\t// self-registration pool — see ALankaScenario.\n\t\t\t\tnew (exported as unknown as new () => unknown)();\n\t\t\t} catch {\n\t\t\t\t// Not every export is constructible; skip those.\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate registerScenarioEvents(scenarios: ILankaScenario<unknown>[]): void {\n\t\tconst registeredEventTypes = new Set<string>();\n\n\t\tscenarios.forEach((scenario) => {\n\t\t\tif (!registeredEventTypes.has(scenario.eventType)) {\n\t\t\t\tregisteredEventTypes.add(scenario.eventType);\n\n\t\t\t\tlankaLogger.printScenarioLog(\"REGISTER SCENARIO EVENT\", scenario.name);\n\t\t\t\tlankaEventBus.registerEvent(scenario.eventType, {\n\t\t\t\t\tdataType: scenario.dataTypeName,\n\t\t\t\t\tdescription: `Scenario: ${scenario.name}`,\n\t\t\t\t\tusedBy: [scenario.name],\n\t\t\t\t\tpriority: 0,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (scenario.initialize) {\n\t\t\t\tlankaLogger.printScenarioLog(\"INITIALIZE SCENARIO\", scenario.name);\n\t\t\t\tscenario.initialize();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate initializeAlreadyCreatedViewModels(): void {\n\t\tconst viewModels = LankaScenarioVMRegistry.getInstance().getAllViewModels();\n\t\tviewModels.forEach((vm) => {\n\t\t\tif (!this.state.initialized.has(vm)) {\n\t\t\t\tthis.state.initialized.add(vm);\n\t\t\t\tvm.initializeScenario();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Brings up the scenario layer.\n\t *\n\t * Constructs the scenario classes so they can be collected, declares their\n\t * events on the bus, calls `initialize()` where present, and initialises\n\t * ViewModels that already exist — which happens in tests and under unusual\n\t * import order.\n\t *\n\t * Idempotent.\n\t */\n\tpublic bootstrap(): void {\n\t\tif (this.state.bootstrapped) return;\n\n\t\tlankaLogger.printScenarioLog(\"BOOTSTRAP SCENARIOS START\");\n\n\t\t// Instances must exist, or the registry has nothing to collect.\n\t\tthis.forceInstantiateAllScenarios();\n\n\t\tconst registry = LankaScenariosRegistry.getInstance();\n\t\tregistry.collectAutoRegisteredScenarios();\n\n\t\tconst scenarios = registry.getAllScenarios();\n\t\tthis.registerScenarioEvents(scenarios);\n\n\t\tthis.state.bootstrapped = true;\n\n\t\t// The second adoption. On a server this is the FIRST one that can succeed:\n\t\t// the instance is in its caller's store by now, and creation's attempt was\n\t\t// made before it could be. In a browser it is a no-op — creation adopted\n\t\t// already, and attaching is idempotent.\n\t\tthis.adoptDeclaredViewModels();\n\n\t\t// Some ViewModels may predate bootstrap — in tests, or under unusual import\n\t\t// order; initialise them now.\n\t\tthis.initializeAlreadyCreatedViewModels();\n\n\t\tlankaLogger.printScenarioLog(\"BOOTSTRAP SCENARIOS FINISH\");\n\t}\n\n\t/**\n\t * Called by a ViewModel factory when the ViewModel declares scenario handlers.\n\t *\n\t * Registers the ViewModel and, if the layer is already up, initialises it\n\t * immediately. Duplicate registration and duplicate initialisation are\n\t * impossible.\n\t *\n\t * @param viewModel What to register\n\t * @param name Name for the log\n\t */\n\t/**\n\t * ViewModels DECLARED in this process.\n\t *\n\t * Module-level deliberately, the same case as the scenario pool: a registry of\n\t * DEFINITIONS, not runtime state. Splitting it between instances would be\n\t * divergence, not isolation — the classes come from the same modules and both\n\t * instances must see one list.\n\t *\n\t * Two reasons it exists:\n\t *\n\t * 1. Declaring a ViewModel is a declaration, not work. A screen file creates it\n\t * at module level, and import order decides whether that happens before or\n\t * after `createLanka()`. Requiring a live instance at declaration time\n\t * requires an import order the consumer does not control.\n\t * 2. Instances come one after another: every test creates its own. The list is\n\t * NOT drained by the first — otherwise the second instance would know no\n\t * module-level ViewModel and its `dispose()` would not remove their\n\t * subscriptions, so a test would receive events the previous one subscribed\n\t * to.\n\t */\n\tprivate readonly declaredViewModels: { viewModel: ILankaScenarioVM; name?: string }[] = [];\n\n\t/**\n\t * Registers everything declared into a NEW instance.\n\t *\n\t * Called twice, and the second call is the one that makes this work on a\n\t * server: `createLanka` calls it, and so does `bootstrap()`.\n\t *\n\t * The reason is an order nobody can change. Where \"which instance is active\"\n\t * is answered by a RESOLVER — one instance per request, the shape\n\t * `@lankajs/host` installs — an instance becomes findable only once the CALLER\n\t * has put it in its store, and a caller can only do that after `createLanka`\n\t * has returned. So during creation the answer is honestly \"none\", and asking\n\t * for it threw: every server render of an application with a module-level\n\t * ViewModel failed inside the call that was creating the scope, with a message\n\t * about running outside one.\n\t *\n\t * Skipping is safe precisely because bootstrap adopts again. It is idempotent\n\t * at the other end too: `attachViewModel` returns early for a ViewModel the\n\t * registry already holds.\n\t */\n\tpublic adoptDeclaredViewModels(): void {\n\t\tif (!getActiveRuntime()) return;\n\n\t\tfor (const { viewModel, name } of [...this.declaredViewModels]) {\n\t\t\tthis.attachViewModel(viewModel, name);\n\t\t}\n\t}\n\n\tpublic registerViewModel(viewModel: ILankaScenarioVM, name?: string): void {\n\t\tconst isKnown = this.declaredViewModels.some(\n\t\t\t(declared) => declared.viewModel === viewModel,\n\t\t);\n\t\tif (!isKnown) this.declaredViewModels.push({ viewModel, name });\n\n\t\t// No instance yet: the declaration came before bootstrap, and whoever\n\t\t// bootstraps will pick it up.\n\t\tif (!getActiveRuntime()) return;\n\n\t\tthis.attachViewModel(viewModel, name);\n\t}\n\n\tprivate attachViewModel(viewModel: ILankaScenarioVM, name?: string): void {\n\t\tconst registry = LankaScenarioVMRegistry.getInstance();\n\n\t\t// Already registered — nothing to do.\n\t\tif (registry.isRegistered(viewModel)) {\n\t\t\tif (name) {\n\t\t\t\tlankaLogger.printViewModelLog(\"VM Already Registered (skipped)\", name);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Register it.\n\t\tregistry.register(viewModel);\n\n\t\tif (name) {\n\t\t\tlankaLogger.printViewModelLog(\"Register Scenario VM\", name);\n\t\t}\n\n\t\t// The layer is already up — initialise it now rather than at the next\n\t\t// bootstrap.\n\t\tif (this.state.bootstrapped) {\n\t\t\tif (!this.state.initialized.has(viewModel)) {\n\t\t\t\tthis.state.initialized.add(viewModel);\n\t\t\t\tviewModel.initializeScenario();\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Resets the whole scenario layer. Without it tests are not isolated.\n\t *\n\t * Clears the self-registration list, the scenario registry with its metadata,\n\t * the ViewModel registry together with all their subscriptions, and the bus —\n\t * events and\n\t * middleware.\n\t *\n\t * What it does NOT clear by default is which ViewModels were DECLARED, and\n\t * that default is load-bearing: a module-level ViewModel is built once per\n\t * process, so the declaration is the only thing that lets a second instance\n\t * find it. Drop it and its handlers bind to nothing, silently, for the rest of\n\t * the process — see `declaredViewModels`.\n\t *\n\t * `withDeclarations` is for the case that default gets wrong: a suite that\n\t * builds ViewModels inside test BODIES. Those are declared like any other and\n\t * nothing un-declares them, so the next bootstrap re-adopts every one ever\n\t * built and a finished test's handlers run again — against the gateway that\n\t * test built, which is somebody else's double. Forgetting is not a tombstone:\n\t * a ViewModel declared again afterwards is adopted again.\n\t *\n\t * @param config `withDeclarations` also forgets which ViewModels exist\n\t */\n\tpublic reset(config: ILankaScenarioResetConfig = {}): void {\n\t\tALankaScenario.clearAutoRegisteredScenarios();\n\t\tLankaScenariosRegistry.getInstance().clear();\n\t\tLankaScenarioVMRegistry.getInstance().resetAll();\n\t\tlankaEventBus.clearAllEvents();\n\t\tthis.state.bootstrapped = false;\n\t\tthis.state.initialized = new WeakSet<ILankaScenarioVM>();\n\n\t\tif (config.withDeclarations) this.declaredViewModels.length = 0;\n\t}\n}\n\n/** The one every caller wants. */\nexport const lankaScenarioBootstrap = new LankaScenarioBootstrap();\n"],"mappings":";;;;;;;;;;;;;;;AAmBA,YAAY,qBAAqB;AAe1B,IAAM,yBAAN,MAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQnC,IAAY,QAAQ;AACnB,WAAO,qBAAqB,EAAE;AAAA,EAC/B;AAAA,EAEO,8BAAuC;AAC7C,WAAO,KAAK,MAAM;AAAA,EACnB;AAAA,EAEQ,+BAAqC;AAM5C,UAAM,SAAS,IAAI;AAAA,MAClB,eAAe,2BAA2B,EAAE,IAAI,CAAC,aAAa,SAAS,WAAW;AAAA,IACnF;AAEA,eAAW,YAAY,OAAO,OAAO,eAAe,GAAG;AAEtD,UAAI,OAAO,aAAa,WAAY;AACpC,UAAI,OAAO,IAAI,QAAQ,EAAG;AAE1B,UAAI;AAGH,YAAK,SAA0C;AAAA,MAChD,QAAQ;AAAA,MAER;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,uBAAuB,WAA4C;AAC1E,UAAM,uBAAuB,oBAAI,IAAY;AAE7C,cAAU,QAAQ,CAAC,aAAa;AAC/B,UAAI,CAAC,qBAAqB,IAAI,SAAS,SAAS,GAAG;AAClD,6BAAqB,IAAI,SAAS,SAAS;AAE3C,oBAAY,iBAAiB,2BAA2B,SAAS,IAAI;AACrE,sBAAc,cAAc,SAAS,WAAW;AAAA,UAC/C,UAAU,SAAS;AAAA,UACnB,aAAa,aAAa,SAAS,IAAI;AAAA,UACvC,QAAQ,CAAC,SAAS,IAAI;AAAA,UACtB,UAAU;AAAA,QACX,CAAC;AAAA,MACF;AAEA,UAAI,SAAS,YAAY;AACxB,oBAAY,iBAAiB,uBAAuB,SAAS,IAAI;AACjE,iBAAS,WAAW;AAAA,MACrB;AAAA,IACD,CAAC;AAAA,EACF;AAAA,EAEQ,qCAA2C;AAClD,UAAM,aAAa,wBAAwB,YAAY,EAAE,iBAAiB;AAC1E,eAAW,QAAQ,CAAC,OAAO;AAC1B,UAAI,CAAC,KAAK,MAAM,YAAY,IAAI,EAAE,GAAG;AACpC,aAAK,MAAM,YAAY,IAAI,EAAE;AAC7B,WAAG,mBAAmB;AAAA,MACvB;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYO,YAAkB;AACxB,QAAI,KAAK,MAAM,aAAc;AAE7B,gBAAY,iBAAiB,2BAA2B;AAGxD,SAAK,6BAA6B;AAElC,UAAM,WAAW,uBAAuB,YAAY;AACpD,aAAS,+BAA+B;AAExC,UAAM,YAAY,SAAS,gBAAgB;AAC3C,SAAK,uBAAuB,SAAS;AAErC,SAAK,MAAM,eAAe;AAM1B,SAAK,wBAAwB;AAI7B,SAAK,mCAAmC;AAExC,gBAAY,iBAAiB,4BAA4B;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgCiB,qBAAuE,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBlF,0BAAgC;AACtC,QAAI,CAAC,iBAAiB,EAAG;AAEzB,eAAW,EAAE,WAAW,KAAK,KAAK,CAAC,GAAG,KAAK,kBAAkB,GAAG;AAC/D,WAAK,gBAAgB,WAAW,IAAI;AAAA,IACrC;AAAA,EACD;AAAA,EAEO,kBAAkB,WAA6B,MAAqB;AAC1E,UAAM,UAAU,KAAK,mBAAmB;AAAA,MACvC,CAAC,aAAa,SAAS,cAAc;AAAA,IACtC;AACA,QAAI,CAAC,QAAS,MAAK,mBAAmB,KAAK,EAAE,WAAW,KAAK,CAAC;AAI9D,QAAI,CAAC,iBAAiB,EAAG;AAEzB,SAAK,gBAAgB,WAAW,IAAI;AAAA,EACrC;AAAA,EAEQ,gBAAgB,WAA6B,MAAqB;AACzE,UAAM,WAAW,wBAAwB,YAAY;AAGrD,QAAI,SAAS,aAAa,SAAS,GAAG;AACrC,UAAI,MAAM;AACT,oBAAY,kBAAkB,mCAAmC,IAAI;AAAA,MACtE;AACA;AAAA,IACD;AAGA,aAAS,SAAS,SAAS;AAE3B,QAAI,MAAM;AACT,kBAAY,kBAAkB,wBAAwB,IAAI;AAAA,IAC3D;AAIA,QAAI,KAAK,MAAM,cAAc;AAC5B,UAAI,CAAC,KAAK,MAAM,YAAY,IAAI,SAAS,GAAG;AAC3C,aAAK,MAAM,YAAY,IAAI,SAAS;AACpC,kBAAU,mBAAmB;AAAA,MAC9B;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBO,MAAM,SAAoC,CAAC,GAAS;AAC1D,mBAAe,6BAA6B;AAC5C,2BAAuB,YAAY,EAAE,MAAM;AAC3C,4BAAwB,YAAY,EAAE,SAAS;AAC/C,kBAAc,eAAe;AAC7B,SAAK,MAAM,eAAe;AAC1B,SAAK,MAAM,cAAc,oBAAI,QAA0B;AAEvD,QAAI,OAAO,iBAAkB,MAAK,mBAAmB,SAAS;AAAA,EAC/D;AACD;AAGO,IAAM,yBAAyB,IAAI,uBAAuB;","names":[]}
@@ -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":[]}