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.
- package/README.md +1 -1
- package/dist/{LankaSharedStoreLocator-zS2kLu-S.d.ts → ALankaLocator-BUCCz0Q5.d.ts} +1 -47
- package/dist/ILankaReadableVM-BoPzHEPV.d.ts +72 -0
- package/dist/{ILankaScenarioVM-DpKFL3iE.d.ts → ILankaScenario-DQd9ZfUw.d.ts} +1 -30
- package/dist/{ILankaScenarioMetadata-Dj4GCqmX.d.ts → ILankaScenarioMetadata-BsDp0Bzm.d.ts} +1 -1
- package/dist/ILankaScenarioVM-DKwjbIRq.d.ts +30 -0
- package/dist/{LankaScenarioLocator-CAENLhDO.d.ts → LankaScenarioVMRegistry-DySAOaj2.d.ts} +4 -37
- package/dist/_extend/index.d.ts +235 -7
- package/dist/_extend/index.js +152 -12
- package/dist/_extend/index.js.map +1 -1
- package/dist/_internal/index.d.ts +13 -8
- package/dist/_internal/index.js +9 -3
- package/dist/_internal/index.js.map +1 -1
- package/dist/{activeRuntime-BYd2D6Lx.d.ts → activeRuntime-ByucLPhj.d.ts} +55 -4
- package/dist/bootstrap/index.d.ts +15 -10
- package/dist/bootstrap/index.js +14 -9
- package/dist/chunk-24MUFFHF.js +43 -0
- package/dist/chunk-24MUFFHF.js.map +1 -0
- package/dist/chunk-3R2NO47A.js +35 -0
- package/dist/chunk-3R2NO47A.js.map +1 -0
- package/dist/{chunk-5MAQVBI2.js → chunk-42CWYVZJ.js} +44 -61
- package/dist/chunk-42CWYVZJ.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-BMF4TM2Z.js +22 -0
- package/dist/chunk-BMF4TM2Z.js.map +1 -0
- package/dist/{chunk-C2HP7CRD.js → chunk-FII3PW2G.js} +2 -2
- package/dist/{chunk-DTO27QFR.js → chunk-H5TIUKRK.js} +2 -2
- package/dist/chunk-JZJ6GXX3.js +12 -0
- package/dist/chunk-JZJ6GXX3.js.map +1 -0
- package/dist/{chunk-B7EYIAW7.js → chunk-MKKMTLOY.js} +47 -13
- package/dist/chunk-MKKMTLOY.js.map +1 -0
- package/dist/{chunk-UE2C76OR.js → chunk-N3275IPH.js} +2 -69
- package/dist/chunk-N3275IPH.js.map +1 -0
- package/dist/{chunk-O5ROO7QF.js → chunk-NWEHOMTS.js} +3 -3
- package/dist/{chunk-NDCJBO47.js → chunk-O5EUTNR6.js} +2 -2
- package/dist/{chunk-RKYKK6MN.js → chunk-RUMUFHSL.js} +2 -2
- package/dist/chunk-SBITVBF7.js +49 -0
- package/dist/chunk-SBITVBF7.js.map +1 -0
- package/dist/{chunk-UBGXDTXC.js → chunk-SO7D5E7O.js} +22 -16
- package/dist/chunk-SO7D5E7O.js.map +1 -0
- package/dist/chunk-UDP6IXDS.js +17 -0
- package/dist/chunk-UDP6IXDS.js.map +1 -0
- 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-NrlvN-WQ.d.ts → createLanka-DvUGu9Hl.d.ts} +2 -2
- package/dist/{createLankaScope-BiFxNQgl.d.ts → createLankaScope-CVoV0EwO.d.ts} +1 -1
- package/dist/errors/index.js +3 -3
- package/dist/gateway/index.js +6 -6
- package/dist/index.d.ts +14 -9
- package/dist/index.js +15 -10
- package/dist/locator/gateway/lanka-gateway-locator/LankaGatewayLocator.d.ts +18 -0
- package/dist/locator/gateway/lanka-gateway-locator/LankaGatewayLocator.js +9 -0
- package/dist/locator/gateway/lanka-gateway-locator/LankaGatewayLocator.js.map +1 -0
- package/dist/locator/index.d.ts +4 -2
- package/dist/locator/index.js +6 -3
- package/dist/locator/index.js.map +1 -1
- package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.d.ts +60 -0
- package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.js +12 -0
- package/dist/locator/scenario/lanka-scenario-locator/LankaScenarioLocator.js.map +1 -0
- package/dist/locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.d.ts +22 -0
- package/dist/locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.js +9 -0
- package/dist/locator/shared-store/lanka-shared-store-locator/LankaSharedStoreLocator.js.map +1 -0
- package/dist/locator/singleton/lanka-singleton-locator/LankaSingletonLocator.d.ts +30 -0
- package/dist/locator/singleton/lanka-singleton-locator/LankaSingletonLocator.js +9 -0
- package/dist/locator/singleton/lanka-singleton-locator/LankaSingletonLocator.js.map +1 -0
- package/dist/logger/index.js +3 -3
- package/dist/mock/index.js +3 -3
- package/dist/scenario/index.d.ts +29 -3
- package/dist/scenario/index.js +5 -5
- package/dist/stream/index.d.ts +17 -12
- package/dist/viewmodel/index.d.ts +139 -28
- package/dist/viewmodel/index.js +95 -126
- package/dist/viewmodel/index.js.map +1 -1
- package/package.json +3 -4
- package/skills/lanka-core/SKILL.md +18 -16
- package/skills/lanka-core/reference.md +135 -58
- package/skills/lanka-packages/SKILL.md +94 -22
- package/dist/chunk-5MAQVBI2.js.map +0 -1
- package/dist/chunk-B7EYIAW7.js.map +0 -1
- package/dist/chunk-BGVDPDX4.js.map +0 -1
- package/dist/chunk-UBGXDTXC.js.map +0 -1
- package/dist/chunk-UE2C76OR.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-O5ROO7QF.js.map → chunk-NWEHOMTS.js.map} +0 -0
- /package/dist/{chunk-NDCJBO47.js.map → chunk-O5EUTNR6.js.map} +0 -0
- /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@
|
|
3
|
+
> **`lanka@2.0.1`** — this document describes that version.
|
|
4
4
|
>
|
|
5
|
-
> Install: `npm install lanka
|
|
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.
|
|
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
|
|
64
|
-
pnpm add lanka
|
|
65
|
-
yarn add lanka
|
|
66
|
-
bun add lanka
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
|
842
|
-
|
|
|
843
|
-
| a field ARRAY — rows the user adds and removes
|
|
844
|
-
| validation on every keystroke
|
|
845
|
-
| one field's validity depends on another's value
|
|
846
|
-
| `touched` / `dirty` / blur as behaviour, not decoration
|
|
847
|
-
| the screen is server-rendered WITH values
|
|
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
|
-
|
|
|
855
|
-
|
|
|
856
|
-
| values, `touched`/`dirty`, focus, per-input messages
|
|
857
|
-
| `isSubmitting`
|
|
858
|
-
| `defaultValues`
|
|
859
|
-
| calling the gateway, `trigger`, clearing a draft, navigating | the **ViewModel**
|
|
860
|
-
| an input's ASYNCHRONOUS check
|
|
861
|
-
| failures with an address
|
|
862
|
-
| failures without one
|
|
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
|
|
878
|
-
|
|
|
879
|
-
| `orderWire`
|
|
880
|
-
| `order`
|
|
881
|
-
| `orderInput`
|
|
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 });
|
|
941
|
-
services.cache?.write(["order", id], order);
|
|
942
|
-
return { ok: true, data: order };
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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.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
|
|
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/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":"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","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":[]}
|