lanka 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/ILankaReadableVM-BoPzHEPV.d.ts +72 -0
- package/dist/{LankaScenarioLocator-CAENLhDO.d.ts → LankaScenarioLocator-Dki2Es3O.d.ts} +2 -2
- package/dist/{LankaSharedStoreLocator-zS2kLu-S.d.ts → LankaSharedStoreLocator-jsz_gnUm.d.ts} +1 -1
- package/dist/_extend/index.d.ts +228 -5
- package/dist/_extend/index.js +137 -8
- package/dist/_extend/index.js.map +1 -1
- package/dist/_internal/index.d.ts +3 -3
- package/dist/_internal/index.js +9 -3
- package/dist/_internal/index.js.map +1 -1
- package/dist/{activeRuntime-BYd2D6Lx.d.ts → activeRuntime-BO-qY0yY.d.ts} +50 -3
- package/dist/bootstrap/index.d.ts +6 -6
- package/dist/bootstrap/index.js +7 -8
- package/dist/{chunk-D27MREPB.js → chunk-7DQUF2QR.js} +2 -2
- package/dist/{chunk-UJEC7H6K.js → chunk-BHQ2SQ7P.js} +2 -2
- package/dist/chunk-EYDMBI5T.js +550 -0
- package/dist/chunk-EYDMBI5T.js.map +1 -0
- package/dist/{chunk-C2HP7CRD.js → chunk-FII3PW2G.js} +2 -2
- package/dist/{chunk-DTO27QFR.js → chunk-H5TIUKRK.js} +2 -2
- package/dist/{chunk-NDCJBO47.js → chunk-O5EUTNR6.js} +2 -2
- package/dist/{chunk-RKYKK6MN.js → chunk-RUMUFHSL.js} +2 -2
- package/dist/{chunk-UBGXDTXC.js → chunk-SZVNPK24.js} +11 -13
- package/dist/chunk-SZVNPK24.js.map +1 -0
- package/dist/chunk-UDP6IXDS.js +17 -0
- package/dist/chunk-UDP6IXDS.js.map +1 -0
- package/dist/{chunk-5MAQVBI2.js → chunk-UMPSR4US.js} +3 -3
- package/dist/{chunk-BGVDPDX4.js → chunk-XGMXT4XZ.js} +20 -3
- package/dist/chunk-XGMXT4XZ.js.map +1 -0
- package/dist/config/index.js +3 -3
- package/dist/{createLanka-NrlvN-WQ.d.ts → createLanka-x4oSMs7P.d.ts} +2 -2
- package/dist/{createLankaScope-BiFxNQgl.d.ts → createLankaScope-DomNbEvb.d.ts} +1 -1
- package/dist/errors/index.js +3 -3
- package/dist/gateway/index.js +9 -9
- package/dist/index.d.ts +5 -5
- package/dist/index.js +8 -9
- package/dist/locator/index.d.ts +2 -2
- package/dist/locator/index.js +2 -2
- package/dist/logger/index.js +3 -3
- package/dist/mock/index.js +3 -3
- package/dist/scenario/index.d.ts +25 -0
- package/dist/scenario/index.js +6 -8
- package/dist/scenario/index.js.map +1 -1
- package/dist/stream/index.d.ts +5 -5
- package/dist/viewmodel/index.d.ts +137 -27
- package/dist/viewmodel/index.js +94 -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-B7EYIAW7.js +0 -216
- package/dist/chunk-B7EYIAW7.js.map +0 -1
- package/dist/chunk-BGVDPDX4.js.map +0 -1
- package/dist/chunk-O5ROO7QF.js +0 -315
- package/dist/chunk-O5ROO7QF.js.map +0 -1
- package/dist/chunk-UBGXDTXC.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-NDCJBO47.js.map → chunk-O5EUTNR6.js.map} +0 -0
- /package/dist/{chunk-RKYKK6MN.js.map → chunk-RUMUFHSL.js.map} +0 -0
- /package/dist/{chunk-5MAQVBI2.js.map → chunk-UMPSR4US.js.map} +0 -0
|
@@ -1,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.0`** — 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.0"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# lanka — which package, and what it costs
|
|
@@ -19,24 +19,91 @@ has a skill of its own with the detail.
|
|
|
19
19
|
|
|
20
20
|
## By the problem
|
|
21
21
|
|
|
22
|
-
| The problem | The package
|
|
23
|
-
| ------------------------------------------------------- |
|
|
24
|
-
| screens, data, state, events | `lanka`
|
|
25
|
-
| stale data after several refreshes; bursts; polling | `@lankajs/async`
|
|
26
|
-
| a button that must not double-fire; instant feedback | `@lankajs/optimistic`
|
|
27
|
-
| a table: sort, filter, paginate, stop re-rendering rows | `@lankajs/collection`
|
|
28
|
-
| localStorage, IndexedDB, encryption, persisted state | `@lankajs/storage`
|
|
29
|
-
| avatars and thumbnails that reload or flicker | `@lankajs/blob-cache`
|
|
30
|
-
| cookies; stale caches after a new build shipped | `@lankajs/browser`
|
|
31
|
-
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
|
22
|
+
| The problem | The package | Its skill |
|
|
23
|
+
| ------------------------------------------------------- | --------------------- | ------------------ |
|
|
24
|
+
| screens, data, state, events | `lanka` | `lanka-core` |
|
|
25
|
+
| stale data after several refreshes; bursts; polling | `@lankajs/async` | `lanka-async` |
|
|
26
|
+
| a button that must not double-fire; instant feedback | `@lankajs/optimistic` | `lanka-optimistic` |
|
|
27
|
+
| a table: sort, filter, paginate, stop re-rendering rows | `@lankajs/collection` | `lanka-collection` |
|
|
28
|
+
| localStorage, IndexedDB, encryption, persisted state | `@lankajs/storage` | `lanka-storage` |
|
|
29
|
+
| avatars and thumbnails that reload or flicker | `@lankajs/blob-cache` | `lanka-blob-cache` |
|
|
30
|
+
| cookies; stale caches after a new build shipped | `@lankajs/browser` | `lanka-browser` |
|
|
31
|
+
| Next, React Router, TanStack Start or Expo around lanka | `@lankajs/host` | `lanka-host` |
|
|
32
|
+
|
|
33
|
+
## Reading a ViewModel from a screen
|
|
34
|
+
|
|
35
|
+
Core imports no UI library, so this is the one package a rendering application
|
|
36
|
+
always adds. Install the one for its framework and no other — they are
|
|
37
|
+
alternatives, not layers:
|
|
38
|
+
|
|
39
|
+
| The framework | The package | Its skill |
|
|
40
|
+
| ------------------- | ------------------ | --------------- |
|
|
41
|
+
| React, React Native | `@lankajs/react` | `lanka-react` |
|
|
42
|
+
| Vue, Nuxt | `@lankajs/vue` | `lanka-vue` |
|
|
43
|
+
| Svelte, SvelteKit | `@lankajs/svelte` | `lanka-svelte` |
|
|
44
|
+
| Solid | `@lankajs/solid` | `lanka-solid` |
|
|
45
|
+
| Angular | `@lankajs/angular` | `lanka-angular` |
|
|
46
|
+
|
|
47
|
+
All five publish `useLankaVM`, and the access tracking is core's rather than
|
|
48
|
+
each binding's — so what a screen re-renders for is the same answer in every
|
|
49
|
+
framework. What differs is what the call ANSWERS: the state itself in React, a
|
|
50
|
+
`ShallowRef` in Vue, getters in Svelte, an `Accessor` in Solid, a `Signal` in
|
|
51
|
+
Angular.
|
|
52
|
+
|
|
53
|
+
Nothing else needs one. Gateways, scenarios, the locator and
|
|
54
|
+
`viewModel.getState()` are plain calls with no view in them, and they run on a
|
|
55
|
+
server unchanged.
|
|
56
|
+
|
|
57
|
+
## What goes on the wire
|
|
58
|
+
|
|
59
|
+
Everything here is a plugin: core declares the extension point, and the plugin is
|
|
60
|
+
registered with `lanka.use(...)`.
|
|
61
|
+
|
|
62
|
+
| The problem | The package | Its skill |
|
|
63
|
+
| ------------------------------------------------- | --------------------------------- | ----------------------- |
|
|
64
|
+
| retry, idempotency, timeouts, auth refresh, CSRF | `@lankajs/plugin-http` | `lanka-http` |
|
|
65
|
+
| the server pushes updates, one way | `@lankajs/plugin-sse` | `lanka-sse` |
|
|
66
|
+
| the wire has to carry traffic BOTH ways | `@lankajs/plugin-websocket` | `lanka-websocket` |
|
|
67
|
+
| the API is GraphQL, with or without subscriptions | `@lankajs/plugin-graphql` | `lanka-graphql` |
|
|
68
|
+
| the API is gRPC and the client is a browser | `@lankajs/plugin-grpc` | `lanka-grpc` |
|
|
69
|
+
| warming data or route code before the user asks | `@lankajs/plugin-prefetch` | `lanka-prefetch` |
|
|
70
|
+
| ordered start-up stages that can redirect | `@lankajs/plugin-bootstrap-steps` | `lanka-bootstrap-steps` |
|
|
71
|
+
| seeing what the bus and the logger did | `@lankajs/plugin-devtools` | `lanka-devtools` |
|
|
72
|
+
|
|
73
|
+
SSE and WebSocket sit behind the SAME port, so the choice is one line in bootstrap
|
|
74
|
+
and not a rewrite. Take SSE unless the client has to SEND on the same connection.
|
|
75
|
+
|
|
76
|
+
## Pick exactly one from a family
|
|
77
|
+
|
|
78
|
+
Four decisions where the framework refuses to choose for you, because the right
|
|
79
|
+
answer is whichever library your application already has. Each family binds one
|
|
80
|
+
port, so every member publishes the same surface under a different vendor name.
|
|
81
|
+
|
|
82
|
+
| The decision | The members | Its skill |
|
|
83
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
84
|
+
| which schema library validates responses | `@lankajs/zod`, `@lankajs/valibot`, `@lankajs/arktype`, `@lankajs/yup`, `@lankajs/typebox`, `@lankajs/effect` | `lanka-zod`, `lanka-valibot`, … |
|
|
85
|
+
| …and the app ended up with two of them | `@lankajs/any-schema` | `lanka-any-schema` |
|
|
86
|
+
| which read cache sits under the ViewModels | `@lankajs/tanstack-query`, `@lankajs/nanostores-query` | `lanka-tanstack-query`, `lanka-nanostores-query` |
|
|
87
|
+
| where `@lankajs/storage` writes off the web | `@lankajs/mmkv`, `@lankajs/react-native-async-storage`, `@lankajs/secure-store`, `@lankajs/unstorage` | `lanka-mmkv`, `lanka-secure-store`, … |
|
|
88
|
+
| which UI framework reads the ViewModels | `@lankajs/react`, `@lankajs/vue`, `@lankajs/svelte`, `@lankajs/solid`, `@lankajs/angular` | `lanka-react`, `lanka-vue`, … |
|
|
89
|
+
|
|
90
|
+
The recommended member, when there is one: `@lankajs/zod` for schemas, and
|
|
91
|
+
`@lankajs/tanstack-query` for the read cache. Take another only for a reason you
|
|
92
|
+
can name — usually that the library is already in the application.
|
|
93
|
+
|
|
94
|
+
A storage engine is the one family you can skip entirely: in a browser
|
|
95
|
+
`@lankajs/storage` brings its own adapters. Install one when the app runs on a
|
|
96
|
+
device (`@lankajs/mmkv`, or `@lankajs/secure-store` for a token) or on a server
|
|
97
|
+
(`@lankajs/unstorage`).
|
|
98
|
+
|
|
99
|
+
## Build-time and test-time
|
|
100
|
+
|
|
101
|
+
| The problem | The package | Its skill |
|
|
102
|
+
| ------------------------------------------------------ | ----------------------- | --------------- |
|
|
103
|
+
| the `@lanka_di` wiring and its build-time check | `@lankajs/tool-di` | `lanka-di` |
|
|
104
|
+
| architectural boundaries as lint rules | `@lankajs/tool-eslint` | `lanka-eslint` |
|
|
105
|
+
| testing, and benchmarking | `@lankajs/tool-testing` | `lanka-testing` |
|
|
106
|
+
| keeping these skills matched to the installed versions | `@lankajs/tool-skills` | `lanka-skills` |
|
|
40
107
|
|
|
41
108
|
## Module or plugin, and why you should care
|
|
42
109
|
|
|
@@ -49,11 +116,16 @@ has a skill of its own with the detail.
|
|
|
49
116
|
|
|
50
117
|
## What NOT to install
|
|
51
118
|
|
|
52
|
-
-
|
|
53
|
-
is
|
|
119
|
+
- Two members of one family. Pick one; the point is that the choice is visible in
|
|
120
|
+
your dependency list. `@lankajs/any-schema` is the exception, and only for an
|
|
121
|
+
application that already carries two schema libraries.
|
|
122
|
+
- A second view binding. One application renders with one UI framework, and
|
|
123
|
+
installing two puts two copies of `useLankaVM` in the import list.
|
|
54
124
|
- `@lankajs/plugin-prefetch` before the app is measurably slow somewhere. It is
|
|
55
125
|
three tiers of machinery for a problem you may not have.
|
|
56
126
|
- `@lankajs/blob-cache` for images whose URL can change. It never checks freshness.
|
|
127
|
+
- A read cache before two screens read the same resource. One screen with its own
|
|
128
|
+
ViewModel already has everywhere it needs to keep that data.
|
|
57
129
|
|
|
58
130
|
## Getting the skills
|
|
59
131
|
|