@lankajs/svelte 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ import "./chunk-5WRI5ZAA.js";
2
+
3
+ // ../../../node_modules/.pnpm/@testing-library+svelte-cor_4dd48ea7741982ca29c6e300d35ac45b/node_modules/@testing-library/svelte-core/src/wrapper-scaffold.svelte
4
+ import "svelte/internal/disclose-version";
5
+ import * as $ from "svelte/internal/client";
6
+ function Wrapper_scaffold($$anchor, $$props) {
7
+ $.push($$props, true);
8
+ let wrapperInstance = $.state(void 0);
9
+ let componentInstance = $.state(void 0);
10
+ const getWrapper = () => $.get(wrapperInstance);
11
+ const getComponent = () => $.get(componentInstance);
12
+ const getComponentProps = () => $$props.componentProps;
13
+ var $$exports = { getWrapper, getComponent, getComponentProps };
14
+ var fragment = $.comment();
15
+ var node = $.first_child(fragment);
16
+ $.component(node, () => $$props.wrapper, ($$anchor2, Wrapper_1) => {
17
+ $.bind_this(
18
+ Wrapper_1($$anchor2, $.spread_props(() => $$props.wrapperProps, {
19
+ children: ($$anchor3, $$slotProps) => {
20
+ var fragment_1 = $.comment();
21
+ var node_1 = $.first_child(fragment_1);
22
+ $.component(node_1, () => $$props.component, ($$anchor4, Component_1) => {
23
+ $.bind_this(Component_1($$anchor4, $.spread_props(() => $$props.componentProps)), ($$value) => $.set(componentInstance, $$value, true), () => $.get(componentInstance));
24
+ });
25
+ $.append($$anchor3, fragment_1);
26
+ },
27
+ $$slots: { default: true }
28
+ })),
29
+ ($$value) => $.set(wrapperInstance, $$value, true),
30
+ () => $.get(wrapperInstance)
31
+ );
32
+ });
33
+ $.append($$anchor, fragment);
34
+ return $.pop($$exports);
35
+ }
36
+ export {
37
+ Wrapper_scaffold as default
38
+ };
39
+ //# sourceMappingURL=wrapper-scaffold-3RO7M4FF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../node_modules/.pnpm/@testing-library+svelte-cor_4dd48ea7741982ca29c6e300d35ac45b/node_modules/@testing-library/svelte-core/src/wrapper-scaffold.svelte"],"sourcesContent":["import 'svelte/internal/disclose-version';\nimport * as $ from 'svelte/internal/client';\n\nexport default function Wrapper_scaffold($$anchor, $$props) {\n\t$.push($$props, true);\n\n\tlet wrapperInstance = $.state(void 0);\n\tlet componentInstance = $.state(void 0);\n\tconst getWrapper = () => $.get(wrapperInstance);\n\tconst getComponent = () => $.get(componentInstance);\n\tconst getComponentProps = () => $$props.componentProps;\n\tvar $$exports = { getWrapper, getComponent, getComponentProps };\n\tvar fragment = $.comment();\n\tvar node = $.first_child(fragment);\n\n\t$.component(node, () => $$props.wrapper, ($$anchor, Wrapper_1) => {\n\t\t$.bind_this(\n\t\t\tWrapper_1($$anchor, $.spread_props(() => $$props.wrapperProps, {\n\t\t\t\tchildren: ($$anchor, $$slotProps) => {\n\t\t\t\t\tvar fragment_1 = $.comment();\n\t\t\t\t\tvar node_1 = $.first_child(fragment_1);\n\n\t\t\t\t\t$.component(node_1, () => $$props.component, ($$anchor, Component_1) => {\n\t\t\t\t\t\t$.bind_this(Component_1($$anchor, $.spread_props(() => $$props.componentProps)), ($$value) => $.set(componentInstance, $$value, true), () => $.get(componentInstance));\n\t\t\t\t\t});\n\n\t\t\t\t\t$.append($$anchor, fragment_1);\n\t\t\t\t},\n\t\t\t\t$$slots: { default: true }\n\t\t\t})),\n\t\t\t($$value) => $.set(wrapperInstance, $$value, true),\n\t\t\t() => $.get(wrapperInstance)\n\t\t);\n\t});\n\n\t$.append($$anchor, fragment);\n\n\treturn $.pop($$exports);\n}"],"mappings":";;;AAAA,OAAO;AACP,YAAY,OAAO;AAEJ,SAAR,iBAAkC,UAAU,SAAS;AAC3D,EAAE,OAAK,SAAS,IAAI;AAEpB,MAAI,kBAAoB,QAAM,MAAM;AACpC,MAAI,oBAAsB,QAAM,MAAM;AACtC,QAAM,aAAa,MAAQ,MAAI,eAAe;AAC9C,QAAM,eAAe,MAAQ,MAAI,iBAAiB;AAClD,QAAM,oBAAoB,MAAM,QAAQ;AACxC,MAAI,YAAY,EAAE,YAAY,cAAc,kBAAkB;AAC9D,MAAI,WAAa,UAAQ;AACzB,MAAI,OAAS,cAAY,QAAQ;AAEjC,EAAE,YAAU,MAAM,MAAM,QAAQ,SAAS,CAACA,WAAU,cAAc;AACjE,IAAE;AAAA,MACD,UAAUA,WAAY,eAAa,MAAM,QAAQ,cAAc;AAAA,QAC9D,UAAU,CAACA,WAAU,gBAAgB;AACpC,cAAI,aAAe,UAAQ;AAC3B,cAAI,SAAW,cAAY,UAAU;AAErC,UAAE,YAAU,QAAQ,MAAM,QAAQ,WAAW,CAACA,WAAU,gBAAgB;AACvE,YAAE,YAAU,YAAYA,WAAY,eAAa,MAAM,QAAQ,cAAc,CAAC,GAAG,CAAC,YAAc,MAAI,mBAAmB,SAAS,IAAI,GAAG,MAAQ,MAAI,iBAAiB,CAAC;AAAA,UACtK,CAAC;AAED,UAAE,SAAOA,WAAU,UAAU;AAAA,QAC9B;AAAA,QACA,SAAS,EAAE,SAAS,KAAK;AAAA,MAC1B,CAAC,CAAC;AAAA,MACF,CAAC,YAAc,MAAI,iBAAiB,SAAS,IAAI;AAAA,MACjD,MAAQ,MAAI,eAAe;AAAA,IAC5B;AAAA,EACD,CAAC;AAED,EAAE,SAAO,UAAU,QAAQ;AAE3B,SAAS,MAAI,SAAS;AACvB;","names":["$$anchor"]}
@@ -0,0 +1,48 @@
1
+ import "./chunk-5WRI5ZAA.js";
2
+
3
+ // ../../../node_modules/.pnpm/@testing-library+svelte-cor_4dd48ea7741982ca29c6e300d35ac45b/node_modules/@testing-library/svelte-core/src/wrapper-scaffold-legacy.svelte
4
+ import "svelte/internal/disclose-version";
5
+ import "svelte/internal/flags/legacy";
6
+ import * as $ from "svelte/internal/client";
7
+ function Wrapper_scaffold_legacy($$anchor, $$props) {
8
+ $.push($$props, false);
9
+ let wrapper = $.prop($$props, "wrapper", 8);
10
+ let wrapperProps = $.prop($$props, "wrapperProps", 8);
11
+ let component2 = $.prop($$props, "component", 8);
12
+ let componentProps = $.prop($$props, "componentProps", 8);
13
+ let wrapperInstance = $.mutable_source();
14
+ let componentInstance = $.mutable_source();
15
+ const getWrapper = () => $.get(wrapperInstance);
16
+ const getComponent = () => $.get(componentInstance);
17
+ const getComponentProps = () => componentProps();
18
+ var $$exports = { getWrapper, getComponent, getComponentProps };
19
+ var fragment = $.comment();
20
+ var node = $.first_child(fragment);
21
+ $.component(node, wrapper, ($$anchor2, $$component) => {
22
+ $.bind_this(
23
+ $$component($$anchor2, $.spread_props(wrapperProps, {
24
+ children: ($$anchor3, $$slotProps) => {
25
+ var fragment_1 = $.comment();
26
+ var node_1 = $.first_child(fragment_1);
27
+ $.component(node_1, component2, ($$anchor4, $$component2) => {
28
+ $.bind_this($$component2($$anchor4, $.spread_props(componentProps, { $$legacy: true })), ($$value) => $.set(componentInstance, $$value), () => $.get(componentInstance));
29
+ });
30
+ $.append($$anchor3, fragment_1);
31
+ },
32
+ $$slots: { default: true },
33
+ $$legacy: true
34
+ })),
35
+ ($$value) => $.set(wrapperInstance, $$value),
36
+ () => $.get(wrapperInstance)
37
+ );
38
+ });
39
+ $.append($$anchor, fragment);
40
+ $.bind_prop($$props, "getWrapper", getWrapper);
41
+ $.bind_prop($$props, "getComponent", getComponent);
42
+ $.bind_prop($$props, "getComponentProps", getComponentProps);
43
+ return $.pop($$exports);
44
+ }
45
+ export {
46
+ Wrapper_scaffold_legacy as default
47
+ };
48
+ //# sourceMappingURL=wrapper-scaffold-legacy-UQDPD4HL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../node_modules/.pnpm/@testing-library+svelte-cor_4dd48ea7741982ca29c6e300d35ac45b/node_modules/@testing-library/svelte-core/src/wrapper-scaffold-legacy.svelte"],"sourcesContent":["import 'svelte/internal/disclose-version';\nimport 'svelte/internal/flags/legacy';\nimport * as $ from 'svelte/internal/client';\n\nexport default function Wrapper_scaffold_legacy($$anchor, $$props) {\n\t$.push($$props, false);\n\n\tlet wrapper = $.prop($$props, 'wrapper', 8);\n\tlet wrapperProps = $.prop($$props, 'wrapperProps', 8);\n\tlet component = $.prop($$props, 'component', 8);\n\tlet componentProps = $.prop($$props, 'componentProps', 8);\n\tlet wrapperInstance = $.mutable_source();\n\tlet componentInstance = $.mutable_source();\n\tconst getWrapper = () => $.get(wrapperInstance);\n\tconst getComponent = () => $.get(componentInstance);\n\tconst getComponentProps = () => componentProps();\n\tvar $$exports = { getWrapper, getComponent, getComponentProps };\n\tvar fragment = $.comment();\n\tvar node = $.first_child(fragment);\n\n\t$.component(node, wrapper, ($$anchor, $$component) => {\n\t\t$.bind_this(\n\t\t\t$$component($$anchor, $.spread_props(wrapperProps, {\n\t\t\t\tchildren: ($$anchor, $$slotProps) => {\n\t\t\t\t\tvar fragment_1 = $.comment();\n\t\t\t\t\tvar node_1 = $.first_child(fragment_1);\n\n\t\t\t\t\t$.component(node_1, component, ($$anchor, $$component) => {\n\t\t\t\t\t\t$.bind_this($$component($$anchor, $.spread_props(componentProps, { $$legacy: true })), ($$value) => $.set(componentInstance, $$value), () => $.get(componentInstance));\n\t\t\t\t\t});\n\n\t\t\t\t\t$.append($$anchor, fragment_1);\n\t\t\t\t},\n\t\t\t\t$$slots: { default: true },\n\t\t\t\t$$legacy: true\n\t\t\t})),\n\t\t\t($$value) => $.set(wrapperInstance, $$value),\n\t\t\t() => $.get(wrapperInstance)\n\t\t);\n\t});\n\n\t$.append($$anchor, fragment);\n\t$.bind_prop($$props, 'getWrapper', getWrapper);\n\t$.bind_prop($$props, 'getComponent', getComponent);\n\t$.bind_prop($$props, 'getComponentProps', getComponentProps);\n\n\treturn $.pop($$exports);\n}"],"mappings":";;;AAAA,OAAO;AACP,OAAO;AACP,YAAY,OAAO;AAEJ,SAAR,wBAAyC,UAAU,SAAS;AAClE,EAAE,OAAK,SAAS,KAAK;AAErB,MAAI,UAAY,OAAK,SAAS,WAAW,CAAC;AAC1C,MAAI,eAAiB,OAAK,SAAS,gBAAgB,CAAC;AACpD,MAAIA,aAAc,OAAK,SAAS,aAAa,CAAC;AAC9C,MAAI,iBAAmB,OAAK,SAAS,kBAAkB,CAAC;AACxD,MAAI,kBAAoB,iBAAe;AACvC,MAAI,oBAAsB,iBAAe;AACzC,QAAM,aAAa,MAAQ,MAAI,eAAe;AAC9C,QAAM,eAAe,MAAQ,MAAI,iBAAiB;AAClD,QAAM,oBAAoB,MAAM,eAAe;AAC/C,MAAI,YAAY,EAAE,YAAY,cAAc,kBAAkB;AAC9D,MAAI,WAAa,UAAQ;AACzB,MAAI,OAAS,cAAY,QAAQ;AAEjC,EAAE,YAAU,MAAM,SAAS,CAACC,WAAU,gBAAgB;AACrD,IAAE;AAAA,MACD,YAAYA,WAAY,eAAa,cAAc;AAAA,QAClD,UAAU,CAACA,WAAU,gBAAgB;AACpC,cAAI,aAAe,UAAQ;AAC3B,cAAI,SAAW,cAAY,UAAU;AAErC,UAAE,YAAU,QAAQD,YAAW,CAACC,WAAUC,iBAAgB;AACzD,YAAE,YAAUA,aAAYD,WAAY,eAAa,gBAAgB,EAAE,UAAU,KAAK,CAAC,CAAC,GAAG,CAAC,YAAc,MAAI,mBAAmB,OAAO,GAAG,MAAQ,MAAI,iBAAiB,CAAC;AAAA,UACtK,CAAC;AAED,UAAE,SAAOA,WAAU,UAAU;AAAA,QAC9B;AAAA,QACA,SAAS,EAAE,SAAS,KAAK;AAAA,QACzB,UAAU;AAAA,MACX,CAAC,CAAC;AAAA,MACF,CAAC,YAAc,MAAI,iBAAiB,OAAO;AAAA,MAC3C,MAAQ,MAAI,eAAe;AAAA,IAC5B;AAAA,EACD,CAAC;AAED,EAAE,SAAO,UAAU,QAAQ;AAC3B,EAAE,YAAU,SAAS,cAAc,UAAU;AAC7C,EAAE,YAAU,SAAS,gBAAgB,YAAY;AACjD,EAAE,YAAU,SAAS,qBAAqB,iBAAiB;AAE3D,SAAS,MAAI,SAAS;AACvB;","names":["component","$$anchor","$$component"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@lankajs/svelte",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "module: One function — `useLankaVM` — over `createSubscriber`, and the access tracking core already does.",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/lankajs/lanka.git",
10
+ "directory": "modules/bindings/svelte"
11
+ },
12
+ "homepage": "https://github.com/lankajs/lanka/tree/main/modules/bindings/svelte#readme",
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./testing": {
21
+ "types": "./dist/testing.d.ts",
22
+ "default": "./dist/testing.js"
23
+ }
24
+ },
25
+ "sideEffects": false,
26
+ "files": [
27
+ "dist",
28
+ "LICENSE",
29
+ "README.md",
30
+ "skills"
31
+ ],
32
+ "dependencies": {
33
+ "lanka": "^2.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@testing-library/svelte": "^5.4.2",
37
+ "@lankajs/tool-testing": "^2.0.0"
38
+ },
39
+ "peerDependencies": {
40
+ "svelte": "^5.7.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "@testing-library/svelte": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "lint": "eslint src _playground --max-warnings=0",
50
+ "test": "vitest run",
51
+ "test:coverage": "vitest run --coverage",
52
+ "test:watch": "vitest",
53
+ "bench": "vitest bench --run",
54
+ "typecheck": "tsc -p tsconfig.json --noEmit"
55
+ }
56
+ }
@@ -0,0 +1,175 @@
1
+ ---
2
+ name: lanka-svelte
3
+ description: Read a lanka ViewModel from a Svelte 5 component with useLankaVM, select one value through `.current`, or satisfy the `svelte/store` contract with toLankaSvelteVM so `$todos` works. Use when writing or reviewing a Svelte or SvelteKit screen in a lanka application, when markup does not update after state changed, when a selector wakes on every change, when a store helper refuses a ViewModel, or when reviewing code that imports `@lankajs/svelte`.
4
+ license: MIT
5
+ metadata:
6
+ author: lankajs
7
+ package: @lankajs/svelte
8
+ version: "0.1.0"
9
+ ---
10
+
11
+ # @lankajs/svelte
12
+
13
+ One call to read a ViewModel, and the store contract for the half of Svelte that
14
+ still speaks it. `reference.md` beside this file is the full guide.
15
+
16
+ > [!NOTE]
17
+ > Only what the framework or a gate refuses is binding. Everything else here is a
18
+ > recommendation you can adapt.
19
+
20
+ ## Pick the call
21
+
22
+ | The situation | Use |
23
+ | ------------------------------------------------- | ------------------------------------------------------- |
24
+ | a component reads a ViewModel | `useLankaVM(todoVM)` — an object of getters |
25
+ | it needs one derived value | `useLankaVM(todoVM, (s) => s.rows.length)` → `.current` |
26
+ | `derived`, `get`, a `$` prefix, a SvelteKit store | `toLankaSvelteVM(todoVM)` |
27
+ | outside a component — a handler, a module | `todoVM.getState()` |
28
+ | a component test | `renderWithLanka` from `@lankajs/svelte/testing` |
29
+
30
+ ```svelte
31
+ <script lang="ts">
32
+ import { useLankaVM } from "@lankajs/svelte";
33
+ import { todoVM } from "./todoVM";
34
+
35
+ const state = useLankaVM(todoVM);
36
+ </script>
37
+
38
+ {#if state.isLoading}
39
+ <p>loading</p>
40
+ {:else}
41
+ <ul onclick={() => state.load()}>
42
+ {#each state.todos as todo (todo.id)}
43
+ <li>{todo.title}</li>
44
+ {/each}
45
+ </ul>
46
+ {/if}
47
+ ```
48
+
49
+ It answers **an object whose properties are getters** — Svelte 5's own shape, and
50
+ the one thing the shelf does not make uniform. A read registers with the
51
+ reactivity graph and with the access tracker in ONE access, so nothing needs a
52
+ `$`. `const` is right: the object never changes, its getters do.
53
+
54
+ **No compiler and no extra plugin.** `createSubscriber` from `svelte/reactivity`
55
+ is a runtime function, so this package is plain TypeScript.
56
+
57
+ ## Selecting one value
58
+
59
+ ```svelte
60
+ <script lang="ts">
61
+ const count = useLankaVM(todoVM, (state) => state.rows.length);
62
+ </script>
63
+
64
+ <p>{count.current}</p>
65
+ ```
66
+
67
+ `current` is Svelte's own convention for a reactive value, the way `MediaQuery`
68
+ and the rest of `svelte/reactivity` read. Any selector, including one answering a
69
+ number.
70
+
71
+ The reader wakes when the **selection** moves, compared with `Object.is` — so a
72
+ selector building a fresh object every call is a reader saying it depends on
73
+ everything. Pick the leaves instead.
74
+
75
+ ## The store contract, when you want `$`
76
+
77
+ ```svelte
78
+ <script lang="ts">
79
+ import { toLankaSvelteVM } from "@lankajs/svelte";
80
+ const todos = toLankaSvelteVM(todoVM);
81
+ </script>
82
+
83
+ {#each $todos.rows as row}<li>{row}</li>{/each}
84
+ ```
85
+
86
+ `run` is called immediately and synchronously, so `$todos` is never `undefined`
87
+ on the first render, and `derived`, `get` and every other `svelte/store` helper
88
+ accept it. Each subscriber gets its own recording.
89
+
90
+ Reach for it when something else demands the contract — a `derived`, a `get`, a
91
+ codebase that has not moved to runes. `useLankaVM` is the default, because a
92
+ plain read is what every other binding on the shelf writes.
93
+
94
+ ## What re-renders
95
+
96
+ Without a selector the view RECORDS which keys you read, and the next change
97
+ repaints only if one of those moved. With a selector, the selector decides and
98
+ tracking is bypassed.
99
+
100
+ > [!WARNING]
101
+ > **The blind spot.** Tracking sees keys you read DIRECTLY. A key reached only
102
+ > inside a derived getter is invisible to it, so a change to that key repaints
103
+ > nothing and the screen freezes with no error. Set
104
+ > `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT read the
105
+ > underlying keys in the markup "for the side effect": that is dead code, and a
106
+ > refactor or a lint autofix removes it. In development the framework announces
107
+ > the mismatch by ViewModel and key name.
108
+
109
+ ## Releasing the subscription
110
+
111
+ `createSubscriber` releases it when the last effect reading the view is
112
+ destroyed, which is every case inside a component. A read where there is no
113
+ effect at all — a module-level snapshot, a script, a test — has none, so the view
114
+ carries `stop()` and you own it.
115
+
116
+ ## Testing
117
+
118
+ ```ts
119
+ import { renderWithLanka } from "@lankajs/svelte/testing";
120
+
121
+ renderWithLanka(TodoScreen, {
122
+ fakes: { gateways: { TodoGateway: { list: () => Promise.resolve([]) } } },
123
+ });
124
+ ```
125
+
126
+ Every call gets a fresh instance and disposes the previous one.
127
+
128
+ ## A selector that builds an object
129
+
130
+ A selector answering a fresh object is never identical to its own last answer,
131
+ so the reader wakes for EVERY change in the ViewModel — including the keys the
132
+ selector exists to ignore. Hold it:
133
+
134
+ ```ts
135
+ import { createLankaShallowHold } from "lanka/viewmodel";
136
+
137
+ const hold = createLankaShallowHold<{ title: string }>();
138
+ const mission = useLankaVM(missionVM, (s) => hold({ title: s.title }));
139
+ ```
140
+
141
+ One hold per reader, declared in the component script — never at module level and never
142
+ shared between two components. A selector answering a **primitive** needs none
143
+ of this. The comparison is one level deep: own keys, same count, `Object.is` on
144
+ each value, arrays included.
145
+
146
+ ## Never do these
147
+
148
+ - **Never pass an object-building selector without a hold.** The reader then
149
+ wakes for every change in the ViewModel, selector or no selector.
150
+ - **Never destructure the view.** `const { rows } = state` reads the getter once
151
+ and the value stops tracking; keep reading through `state.rows`.
152
+ - **Never look for `.current` without a selector.** The plain call answers the
153
+ getters directly; `current` is the selected shape only.
154
+ - **Never build a fresh object in a selector.** `Object.is` then says it changed
155
+ every time, and the reader wakes on everything.
156
+ - **Never leave a read outside an effect unstopped.** A module-level or script
157
+ read has no effect to hang on, so nothing releases the subscription but you.
158
+ - **Never reach for `$` by default.** `toLankaSvelteVM` is for the store
159
+ contract; a plain read is the spelling the rest of the shelf shares.
160
+
161
+ ## Symptom → cause
162
+
163
+ | What you see | What it is |
164
+ | ------------------------------------------------- | ----------------------------------------------- |
165
+ | a screen repainting for changes it never selected | an object selector with no hold |
166
+ | the first paint is right, nothing updates | the view was destructured |
167
+ | `undefined` from a selected read | `.current` missing |
168
+ | a reader waking on every change | a selector returning a fresh object |
169
+ | `derived` or `get` refuses the ViewModel | it wants the store contract — `toLankaSvelteVM` |
170
+ | a subscription that outlives the test | a read with no effect, `stop()` never called |
171
+
172
+ ## More
173
+
174
+ `reference.md` — the full guide: the tracking rules, the store contract in
175
+ detail, and what this package deliberately is not.
@@ -0,0 +1,247 @@
1
+ <!-- Generated from modules/bindings/svelte/GUIDE.md by scripts/skills.mjs. Edit the guide. -->
2
+
3
+ > **`@lankajs/svelte@0.1.0`** — this document describes that version.
4
+ >
5
+ > Install: `npm install @lankajs/svelte svelte zustand` (the peers are not optional; only npm adds a missing one for you).
6
+ >
7
+ > Complete code, compiled and run in CI: [modules/bindings/svelte/_playground/playground.test.ts](https://github.com/lankajs/lanka/blob/main/modules/bindings/svelte/_playground/playground.test.ts)
8
+
9
+ # @lankajs/svelte — user guide
10
+
11
+ How a Svelte component reads a lanka ViewModel.
12
+
13
+ ## You will learn
14
+
15
+ - the one call this package publishes, and what it answers
16
+ - when a component re-renders and when it deliberately does not
17
+ - why a selector that builds an object needs a hold, and when it needs nothing
18
+ - what to do about a ViewModel that derives what the screen shows
19
+ - how to test a Svelte component with a live framework behind it
20
+
21
+ ## When to reach for this
22
+
23
+ Reach for it the moment a Svelte component has to read a lanka ViewModel — that
24
+ is the whole job, and there is no other supported way to do it. Install this one
25
+ package and no other binding: the five are alternatives, not layers.
26
+
27
+ You do NOT need it to reach the rest of the framework. Gateways, scenarios and
28
+ the locator are plain calls with no view in them, and `viewModel.getState()`
29
+ works anywhere, including on a server.
30
+
31
+ > [!NOTE]
32
+ > Everything below is how this package is _meant_ to be used, not how it must
33
+ > be. The framework bends at the seams it publishes — see
34
+ > [ARCHITECTURE.md](https://github.com/lankajs/lanka/blob/main/ARCHITECTURE.md) for what is checked and what is
35
+ > merely advice.
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ npm install @lankajs/svelte svelte zustand
41
+ ```
42
+
43
+ > [!IMPORTANT]
44
+ > `svelte` is already in your project; `zustand` is `lanka`'s own peer. No
45
+ > compiler plugin is needed — this package is plain TypeScript. npm adds a
46
+ > missing peer for you and pnpm does not, so the line names all of them.
47
+
48
+ ## The one call
49
+
50
+ `useLankaVM` is a function. Every member of `modules/bindings/` publishes that same
51
+ name, so moving a screen from one framework to another rewrites the view and not
52
+ the vocabulary.
53
+
54
+ ```svelte
55
+ <script lang="ts">
56
+ import { useLankaVM } from "@lankajs/svelte";
57
+ import { todoVM } from "./todoVM";
58
+
59
+ const state = useLankaVM(todoVM);
60
+ </script>
61
+
62
+ {#if state.isLoading}
63
+ <p>loading</p>
64
+ {:else}
65
+ <ul onclick={() => state.load()}>
66
+ {#each state.todos as todo (todo.id)}
67
+ <li>{todo.title}</li>
68
+ {/each}
69
+ </ul>
70
+ {/if}
71
+ ```
72
+
73
+ It answers **an object whose properties are getters** — the one thing this shelf does not make uniform,
74
+ because that is Svelte's own idea of reactivity and a binding that hid it
75
+ would be a second reactivity system fighting the first.
76
+
77
+ ## Svelte's store contract, when you want `$`
78
+
79
+ `useLankaVM` answers an object of getters, which is Svelte 5's own shape: a read
80
+ registers with the reactivity graph and with the access tracker in one access,
81
+ and nothing needs a `$`.
82
+
83
+ The store contract is the other half of Svelte and has not gone anywhere —
84
+ `$page`, `derived`, `get`, and every codebase that has not moved to runes — so
85
+ this package publishes it:
86
+
87
+ ```svelte
88
+ <script lang="ts">
89
+ import { toLankaSvelteVM } from "@lankajs/svelte";
90
+ const todos = toLankaSvelteVM(todosVM);
91
+ </script>
92
+
93
+ {#each $todos.rows as row}<li>{row}</li>{/each}
94
+ ```
95
+
96
+ It satisfies the contract properly: `run` is called immediately and
97
+ synchronously, so `$todos` is never `undefined` on the first render, and
98
+ `derived`, `get` and every other `svelte/store` helper accept it.
99
+
100
+ Each Svelte subscriber gets its own recording, because two readers of one
101
+ ViewModel read different keys and must be woken for different changes — the same
102
+ rule every binding on the shelf follows.
103
+
104
+ ## Selecting one value
105
+
106
+ With a selector the call answers ONE value, under `current` — Svelte's own
107
+ convention for a reactive value, the way `MediaQuery` and the rest of
108
+ `svelte/reactivity` read:
109
+
110
+ ```svelte
111
+ <script lang="ts">
112
+ const count = useLankaVM(todosVM, (state) => state.rows.length);
113
+ </script>
114
+
115
+ <p>{count.current}</p>
116
+ ```
117
+
118
+ Any selector, including one answering a number: the shape that carried the
119
+ selection's own keys could not, and a member of this shelf narrowing the shared
120
+ name is what the conformance suite's selector scenes now refuse.
121
+
122
+ The reader wakes when the SELECTION moves, not when the state does — compared
123
+ with `Object.is`.
124
+
125
+ ### A selector that builds its answer
126
+
127
+ That comparison is why a selector answering a fresh object wakes the reader for
128
+ **every** change in the ViewModel, including the keys it exists to ignore: the
129
+ object is new on every call, so it is never identical to the previous one.
130
+ `createLankaShallowHold` is the comparison that fixes it — it answers the
131
+ PREVIOUS object while nothing in the selection moved, one level deep over own
132
+ keys:
133
+
134
+ ```svelte
135
+ <script lang="ts">
136
+ import { createLankaShallowHold } from "lanka/viewmodel";
137
+ import { useLankaVM } from "@lankajs/svelte";
138
+
139
+ const hold = createLankaShallowHold<{ title: string; status: string }>();
140
+ const mission = useLankaVM(missionVM, (state) =>
141
+ hold({ title: state.title, status: state.status }),
142
+ );
143
+ </script>
144
+
145
+ <h1>{mission.current.title} — {mission.current.status}</h1>
146
+ ```
147
+
148
+ One hold per reader, created beside the read in the component — never at module
149
+ level and never shared between two of them, because the answer it holds belongs
150
+ to whoever selected it.
151
+
152
+ A selector answering a **primitive** needs none of this and was always free. A
153
+ selection with a **nested** object wants a selector that picks the leaves —
154
+ comparing deeper would mean walking a state of unknown size on every read, which
155
+ is the cost a selector was taken to avoid.
156
+
157
+ ## What re-renders, and what does not
158
+
159
+ Without a selector you get a value that RECORDS which keys you read. The next
160
+ change re-renders only if one of those moved:
161
+
162
+ ```ts
163
+ // reads `todos`; a change to `isLoading` alone repaints nothing
164
+ ```
165
+
166
+ With a selector, the selector decides and tracking is bypassed:
167
+
168
+ ```ts
169
+ const count = useLankaVM(todoVM, (state) => state.todos.length);
170
+ ```
171
+
172
+ > [!WARNING]
173
+ > **The blind spot.** Tracking sees keys you read DIRECTLY. A key reached only
174
+ > inside a derived getter — an action calling `get()` — is invisible to it, so a
175
+ > change to that key re-renders nothing and the screen freezes with no error.
176
+ >
177
+ > Set `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT patch
178
+ > it in the view by reading the underlying keys "for the side effect": that is
179
+ > dead code, and a refactor or a lint autofix removes it.
180
+ >
181
+ > In development the framework announces the mismatch by ViewModel and key name.
182
+
183
+ ## Releasing the subscription
184
+
185
+ `createSubscriber` releases the subscription when the last effect reading this
186
+ view is destroyed, which is every case inside a component. A read where there is
187
+ no effect at all — a module-level snapshot, a script — has none, so the view
188
+ carries `stop()` and you own it.
189
+
190
+ ## No compiler
191
+
192
+ This package is plain TypeScript: `createSubscriber` from `svelte/reactivity` is
193
+ a runtime function, so nothing here needs the Svelte compiler and your build
194
+ needs no extra plugin.
195
+
196
+ ## Which spelling, and when
197
+
198
+ `useLankaVM` is the default. A read registers with the reactivity graph and with
199
+ the access tracker in one access, it composes with `$state` and `$derived`, and
200
+ it is the plain read every other binding on the shelf writes — so a screen moves
201
+ between frameworks with the view rewritten and the vocabulary kept.
202
+
203
+ `toLankaSvelteVM` is for when something else demands the store contract: a
204
+ `derived`, a `get`, a helper from `svelte/store`, or a codebase that has not
205
+ moved to runes. Reaching for `$todoVM` by preference is the one case to weigh,
206
+ because it is the spelling no other framework has.
207
+
208
+ ## Testing
209
+
210
+ `@lankajs/svelte/testing` renders a component with a bootstrapped framework, so a component
211
+ test needs no bootstrap preamble of its own:
212
+
213
+ ```ts
214
+ import { renderWithLanka } from "@lankajs/svelte/testing";
215
+
216
+ renderWithLanka(TodoScreen, {
217
+ fakes: { gateways: { TodoGateway: { list: () => Promise.resolve([]) } } },
218
+ });
219
+ ```
220
+
221
+ Every call gets a FRESH instance and disposes the previous one, so a test never
222
+ inherits its neighbour's subscriptions.
223
+
224
+ ## What this package is not
225
+
226
+ It is a subscription and a render trigger, and nothing else. The recording of
227
+ which keys you read, the comparison that decides whether a change is worth a
228
+ render, and the blind-spot warning are all in `lanka` itself — which is why the
229
+ behaviour you see is the framework's rather than this package's reading of it,
230
+ and why `lankaViewBindingConformance` can hold every binding to one list.
231
+
232
+ If this package ever needs more than the ViewModel port gives it, the port has
233
+ the defect and the fix belongs in `lanka`, for every framework at once.
234
+
235
+ ## Recap
236
+
237
+ - `useLankaVM(todoVM)` is the one call, and every binding publishes that name.
238
+ - It answers an object of getters, so reading `state.todos` registers with Svelte's graph and records the key in one access.
239
+ - A selected read answers one value under `.current` — Svelte's own convention, and the only shape that can carry a selection which is not an object.
240
+ - A key reached only through a derived getter is invisible to tracking: set `enableAccessTrackingOptimization: false` on that ViewModel.
241
+ - `toLankaSvelteVM` is there for the `$` spelling; the plain read is what the other four frameworks write.
242
+ - Inside a component the subscription is released for you; outside one, `stop()` is yours to call.
243
+
244
+ ---
245
+
246
+ Maintaining this package: [SKILL.md](https://github.com/lankajs/lanka/blob/main/modules/bindings/svelte/SKILL.md) · What it is:
247
+ [README.md](https://github.com/lankajs/lanka/blob/main/modules/bindings/svelte/README.md) · Repository map: [../../../README.md](https://github.com/lankajs/lanka/blob/main/README.md)