seitu 0.15.1 → 0.16.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/dist/solid.d.mts +170 -0
- package/dist/solid.mjs +180 -0
- package/dist/svelte.d.mts +77 -0
- package/dist/svelte.mjs +91 -0
- package/package.json +29 -5
- package/skills/README.md +13 -0
- package/skills/create-computed/SKILL.md +1 -1
- package/skills/create-debounced/SKILL.md +1 -1
- package/skills/create-debounced-fn/SKILL.md +1 -1
- package/skills/create-indexed-db-storage/SKILL.md +1 -1
- package/skills/create-is-online/SKILL.md +1 -1
- package/skills/create-media-query/SKILL.md +1 -1
- package/skills/create-readable-subscription/SKILL.md +1 -1
- package/skills/create-schema-store/SKILL.md +1 -1
- package/skills/create-scroll-state/SKILL.md +1 -1
- package/skills/create-store/SKILL.md +1 -1
- package/skills/create-subscription/SKILL.md +1 -1
- package/skills/create-throttled/SKILL.md +1 -1
- package/skills/create-throttled-fn/SKILL.md +1 -1
- package/skills/create-web-storage/SKILL.md +1 -1
- package/skills/create-web-storage-value/SKILL.md +1 -1
- package/skills/seitu-overview/SKILL.md +23 -4
- package/skills/subscription-react/SKILL.md +1 -1
- package/skills/subscription-solid/SKILL.md +97 -0
- package/skills/use-subscription-react/SKILL.md +1 -1
- package/skills/use-subscription-solid/SKILL.md +144 -0
- package/skills/use-subscription-svelte/SKILL.md +134 -0
- package/skills/use-subscription-vue/SKILL.md +1 -1
package/dist/solid.d.mts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { _ as Readable, v as Subscribable } from "./index-DwN8Up-P.mjs";
|
|
2
|
+
import { Accessor, JSX } from "solid-js";
|
|
3
|
+
|
|
4
|
+
//#region src/solid/hooks.d.ts
|
|
5
|
+
interface UseSubscriptionOptions<S extends Subscribable<any> & Readable<any>, R = S['~']['output']> {
|
|
6
|
+
selector?: (value: S['~']['output']) => R;
|
|
7
|
+
isEqual?: (prev: R, next: R) => boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Use this primitive to subscribe to a reactive value. Accepts a subscription object
|
|
11
|
+
* directly, or an accessor/getter that returns one. The getter is reactive — when it
|
|
12
|
+
* reads a signal, the subscription is recreated and re-subscribed automatically.
|
|
13
|
+
*
|
|
14
|
+
* Returns a Solid `Accessor<R>`: call it (`value()`) to read the current value inside JSX
|
|
15
|
+
* or another reactive scope.
|
|
16
|
+
*
|
|
17
|
+
* @example Inline subscription
|
|
18
|
+
* ```tsx
|
|
19
|
+
* import { createWebStorageValue } from 'seitu/web'
|
|
20
|
+
* import { useSubscription } from 'seitu/solid'
|
|
21
|
+
* import * as z from 'zod'
|
|
22
|
+
*
|
|
23
|
+
* function Counter() {
|
|
24
|
+
* const value = useSubscription(() => createWebStorageValue({
|
|
25
|
+
* type: 'sessionStorage',
|
|
26
|
+
* key: 'test',
|
|
27
|
+
* defaultValue: 0,
|
|
28
|
+
* schema: z.number(),
|
|
29
|
+
* }))
|
|
30
|
+
*
|
|
31
|
+
* return <div>{value()}</div>
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example Instance outside of component
|
|
36
|
+
* ```tsx
|
|
37
|
+
* import { createWebStorage } from 'seitu/web'
|
|
38
|
+
* import { useSubscription } from 'seitu/solid'
|
|
39
|
+
* import * as z from 'zod'
|
|
40
|
+
*
|
|
41
|
+
* const sessionStorage = createWebStorage({
|
|
42
|
+
* type: 'sessionStorage',
|
|
43
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
44
|
+
* defaultValues: { count: 0, name: '' },
|
|
45
|
+
* })
|
|
46
|
+
*
|
|
47
|
+
* function Counter() {
|
|
48
|
+
* const value = useSubscription(sessionStorage)
|
|
49
|
+
* return <div>{value().count}</div>
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example Subscription with selector
|
|
54
|
+
* ```tsx
|
|
55
|
+
* import { createWebStorage } from 'seitu/web'
|
|
56
|
+
* import { useSubscription } from 'seitu/solid'
|
|
57
|
+
* import * as z from 'zod'
|
|
58
|
+
*
|
|
59
|
+
* const sessionStorage = createWebStorage({
|
|
60
|
+
* type: 'sessionStorage',
|
|
61
|
+
* schemas: {
|
|
62
|
+
* count: z.number(),
|
|
63
|
+
* name: z.string(),
|
|
64
|
+
* },
|
|
65
|
+
* defaultValues: { count: 0, name: '' },
|
|
66
|
+
* })
|
|
67
|
+
*
|
|
68
|
+
* function Counter() {
|
|
69
|
+
* // Usage with selector, the accessor only updates when count changes
|
|
70
|
+
* const count = useSubscription(sessionStorage, { selector: value => value.count })
|
|
71
|
+
*
|
|
72
|
+
* return <div>{count()}</div>
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example Reactive source (re-subscribes when a signal changes)
|
|
77
|
+
* ```tsx
|
|
78
|
+
* import { createSignal } from 'solid-js'
|
|
79
|
+
* import { createWebStorageValue } from 'seitu/web'
|
|
80
|
+
* import { useSubscription } from 'seitu/solid'
|
|
81
|
+
* import * as z from 'zod'
|
|
82
|
+
*
|
|
83
|
+
* function User() {
|
|
84
|
+
* const [userId, setUserId] = createSignal('user-1')
|
|
85
|
+
* const data = useSubscription(() => createWebStorageValue({
|
|
86
|
+
* type: 'localStorage',
|
|
87
|
+
* key: `user:${userId()}`,
|
|
88
|
+
* schema: z.object({ name: z.string() }),
|
|
89
|
+
* defaultValue: { name: '' },
|
|
90
|
+
* }))
|
|
91
|
+
*
|
|
92
|
+
* return <div>{data().name}</div>
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @example Ref example
|
|
97
|
+
* ```tsx
|
|
98
|
+
* import { createScrollState } from 'seitu/web'
|
|
99
|
+
* import { useSubscription } from 'seitu/solid'
|
|
100
|
+
*
|
|
101
|
+
* function Page() {
|
|
102
|
+
* let ref: HTMLDivElement | undefined
|
|
103
|
+
* const state = useSubscription(() => createScrollState({ element: () => ref, direction: 'vertical' }))
|
|
104
|
+
*
|
|
105
|
+
* return (
|
|
106
|
+
* <div ref={ref}>
|
|
107
|
+
* {String(state().top.reached)}
|
|
108
|
+
* </div>
|
|
109
|
+
* )
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
declare function useSubscription<S extends Subscribable<any> & Readable<any>, R = S['~']['output']>(source: S | Accessor<S>, options?: UseSubscriptionOptions<S, R>): Accessor<R>;
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/solid/components.d.ts
|
|
116
|
+
interface SubscriptionProps<S extends Subscribable<any> & Readable<any>, R = S['~']['output']> extends Pick<UseSubscriptionOptions<S, R>, 'selector'> {
|
|
117
|
+
value: S;
|
|
118
|
+
children: (value: Accessor<R>) => JSX.Element;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Declarative component that subscribes to a reactive value and passes an accessor to a render function.
|
|
122
|
+
* Unlike React, the children function runs once and receives a Solid `Accessor<R>` — call it (`value()`)
|
|
123
|
+
* inside the returned JSX so updates stay fine-grained. Use when you prefer a component API over the
|
|
124
|
+
* useSubscription primitive.
|
|
125
|
+
*
|
|
126
|
+
* @example Basic usage
|
|
127
|
+
* ```tsx
|
|
128
|
+
* import { createWebStorage } from 'seitu/web'
|
|
129
|
+
* import { Subscription } from 'seitu/solid'
|
|
130
|
+
* import * as z from 'zod'
|
|
131
|
+
*
|
|
132
|
+
* const sessionStorage = createWebStorage({
|
|
133
|
+
* type: 'sessionStorage',
|
|
134
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
135
|
+
* defaultValues: { count: 0, name: '' },
|
|
136
|
+
* })
|
|
137
|
+
*
|
|
138
|
+
* function Page() {
|
|
139
|
+
* return (
|
|
140
|
+
* <Subscription value={sessionStorage}>
|
|
141
|
+
* {value => <div>{value().count}</div>}
|
|
142
|
+
* </Subscription>
|
|
143
|
+
* )
|
|
144
|
+
* }
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* @example With selector
|
|
148
|
+
* ```tsx
|
|
149
|
+
* import { createWebStorage } from 'seitu/web'
|
|
150
|
+
* import { Subscription } from 'seitu/solid'
|
|
151
|
+
* import * as z from 'zod'
|
|
152
|
+
*
|
|
153
|
+
* const sessionStorage = createWebStorage({
|
|
154
|
+
* type: 'sessionStorage',
|
|
155
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
156
|
+
* defaultValues: { count: 0, name: '' },
|
|
157
|
+
* })
|
|
158
|
+
*
|
|
159
|
+
* function Page() {
|
|
160
|
+
* return (
|
|
161
|
+
* <Subscription value={sessionStorage} selector={v => v.count}>
|
|
162
|
+
* {count => <div>{count()}</div>}
|
|
163
|
+
* </Subscription>
|
|
164
|
+
* )
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare function Subscription<S extends Subscribable<any> & Readable<any>, R = S['~']['output']>(props: SubscriptionProps<S, R>): JSX.Element;
|
|
169
|
+
//#endregion
|
|
170
|
+
export { Subscription, SubscriptionProps, UseSubscriptionOptions, useSubscription };
|
package/dist/solid.mjs
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { deepEqual } from "fast-equals";
|
|
2
|
+
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js";
|
|
3
|
+
//#region src/solid/hooks.ts
|
|
4
|
+
/**
|
|
5
|
+
* Use this primitive to subscribe to a reactive value. Accepts a subscription object
|
|
6
|
+
* directly, or an accessor/getter that returns one. The getter is reactive — when it
|
|
7
|
+
* reads a signal, the subscription is recreated and re-subscribed automatically.
|
|
8
|
+
*
|
|
9
|
+
* Returns a Solid `Accessor<R>`: call it (`value()`) to read the current value inside JSX
|
|
10
|
+
* or another reactive scope.
|
|
11
|
+
*
|
|
12
|
+
* @example Inline subscription
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { createWebStorageValue } from 'seitu/web'
|
|
15
|
+
* import { useSubscription } from 'seitu/solid'
|
|
16
|
+
* import * as z from 'zod'
|
|
17
|
+
*
|
|
18
|
+
* function Counter() {
|
|
19
|
+
* const value = useSubscription(() => createWebStorageValue({
|
|
20
|
+
* type: 'sessionStorage',
|
|
21
|
+
* key: 'test',
|
|
22
|
+
* defaultValue: 0,
|
|
23
|
+
* schema: z.number(),
|
|
24
|
+
* }))
|
|
25
|
+
*
|
|
26
|
+
* return <div>{value()}</div>
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Instance outside of component
|
|
31
|
+
* ```tsx
|
|
32
|
+
* import { createWebStorage } from 'seitu/web'
|
|
33
|
+
* import { useSubscription } from 'seitu/solid'
|
|
34
|
+
* import * as z from 'zod'
|
|
35
|
+
*
|
|
36
|
+
* const sessionStorage = createWebStorage({
|
|
37
|
+
* type: 'sessionStorage',
|
|
38
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
39
|
+
* defaultValues: { count: 0, name: '' },
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* function Counter() {
|
|
43
|
+
* const value = useSubscription(sessionStorage)
|
|
44
|
+
* return <div>{value().count}</div>
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example Subscription with selector
|
|
49
|
+
* ```tsx
|
|
50
|
+
* import { createWebStorage } from 'seitu/web'
|
|
51
|
+
* import { useSubscription } from 'seitu/solid'
|
|
52
|
+
* import * as z from 'zod'
|
|
53
|
+
*
|
|
54
|
+
* const sessionStorage = createWebStorage({
|
|
55
|
+
* type: 'sessionStorage',
|
|
56
|
+
* schemas: {
|
|
57
|
+
* count: z.number(),
|
|
58
|
+
* name: z.string(),
|
|
59
|
+
* },
|
|
60
|
+
* defaultValues: { count: 0, name: '' },
|
|
61
|
+
* })
|
|
62
|
+
*
|
|
63
|
+
* function Counter() {
|
|
64
|
+
* // Usage with selector, the accessor only updates when count changes
|
|
65
|
+
* const count = useSubscription(sessionStorage, { selector: value => value.count })
|
|
66
|
+
*
|
|
67
|
+
* return <div>{count()}</div>
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example Reactive source (re-subscribes when a signal changes)
|
|
72
|
+
* ```tsx
|
|
73
|
+
* import { createSignal } from 'solid-js'
|
|
74
|
+
* import { createWebStorageValue } from 'seitu/web'
|
|
75
|
+
* import { useSubscription } from 'seitu/solid'
|
|
76
|
+
* import * as z from 'zod'
|
|
77
|
+
*
|
|
78
|
+
* function User() {
|
|
79
|
+
* const [userId, setUserId] = createSignal('user-1')
|
|
80
|
+
* const data = useSubscription(() => createWebStorageValue({
|
|
81
|
+
* type: 'localStorage',
|
|
82
|
+
* key: `user:${userId()}`,
|
|
83
|
+
* schema: z.object({ name: z.string() }),
|
|
84
|
+
* defaultValue: { name: '' },
|
|
85
|
+
* }))
|
|
86
|
+
*
|
|
87
|
+
* return <div>{data().name}</div>
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @example Ref example
|
|
92
|
+
* ```tsx
|
|
93
|
+
* import { createScrollState } from 'seitu/web'
|
|
94
|
+
* import { useSubscription } from 'seitu/solid'
|
|
95
|
+
*
|
|
96
|
+
* function Page() {
|
|
97
|
+
* let ref: HTMLDivElement | undefined
|
|
98
|
+
* const state = useSubscription(() => createScrollState({ element: () => ref, direction: 'vertical' }))
|
|
99
|
+
*
|
|
100
|
+
* return (
|
|
101
|
+
* <div ref={ref}>
|
|
102
|
+
* {String(state().top.reached)}
|
|
103
|
+
* </div>
|
|
104
|
+
* )
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
function useSubscription(source, options) {
|
|
109
|
+
const { selector, isEqual = deepEqual } = options ?? {};
|
|
110
|
+
const getSource = typeof source === "function" ? source : () => source;
|
|
111
|
+
function getSnapshot(sub) {
|
|
112
|
+
return selector ? selector(sub.get()) : sub.get();
|
|
113
|
+
}
|
|
114
|
+
const sub = createMemo(() => getSource());
|
|
115
|
+
const [state, setState] = createSignal(getSnapshot(sub()), { equals: isEqual });
|
|
116
|
+
createEffect(() => {
|
|
117
|
+
const current = sub();
|
|
118
|
+
setState(() => getSnapshot(current));
|
|
119
|
+
onCleanup(current.subscribe(() => {
|
|
120
|
+
setState(() => getSnapshot(current));
|
|
121
|
+
}));
|
|
122
|
+
});
|
|
123
|
+
return state;
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/solid/components.ts
|
|
127
|
+
/**
|
|
128
|
+
* Declarative component that subscribes to a reactive value and passes an accessor to a render function.
|
|
129
|
+
* Unlike React, the children function runs once and receives a Solid `Accessor<R>` — call it (`value()`)
|
|
130
|
+
* inside the returned JSX so updates stay fine-grained. Use when you prefer a component API over the
|
|
131
|
+
* useSubscription primitive.
|
|
132
|
+
*
|
|
133
|
+
* @example Basic usage
|
|
134
|
+
* ```tsx
|
|
135
|
+
* import { createWebStorage } from 'seitu/web'
|
|
136
|
+
* import { Subscription } from 'seitu/solid'
|
|
137
|
+
* import * as z from 'zod'
|
|
138
|
+
*
|
|
139
|
+
* const sessionStorage = createWebStorage({
|
|
140
|
+
* type: 'sessionStorage',
|
|
141
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
142
|
+
* defaultValues: { count: 0, name: '' },
|
|
143
|
+
* })
|
|
144
|
+
*
|
|
145
|
+
* function Page() {
|
|
146
|
+
* return (
|
|
147
|
+
* <Subscription value={sessionStorage}>
|
|
148
|
+
* {value => <div>{value().count}</div>}
|
|
149
|
+
* </Subscription>
|
|
150
|
+
* )
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* @example With selector
|
|
155
|
+
* ```tsx
|
|
156
|
+
* import { createWebStorage } from 'seitu/web'
|
|
157
|
+
* import { Subscription } from 'seitu/solid'
|
|
158
|
+
* import * as z from 'zod'
|
|
159
|
+
*
|
|
160
|
+
* const sessionStorage = createWebStorage({
|
|
161
|
+
* type: 'sessionStorage',
|
|
162
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
163
|
+
* defaultValues: { count: 0, name: '' },
|
|
164
|
+
* })
|
|
165
|
+
*
|
|
166
|
+
* function Page() {
|
|
167
|
+
* return (
|
|
168
|
+
* <Subscription value={sessionStorage} selector={v => v.count}>
|
|
169
|
+
* {count => <div>{count()}</div>}
|
|
170
|
+
* </Subscription>
|
|
171
|
+
* )
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
function Subscription(props) {
|
|
176
|
+
const value = useSubscription(() => props.value, { selector: props.selector });
|
|
177
|
+
return props.children(value);
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
export { Subscription, useSubscription };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { _ as Readable$1, v as Subscribable } from "./index-DwN8Up-P.mjs";
|
|
2
|
+
import { Readable } from "svelte/store";
|
|
3
|
+
|
|
4
|
+
//#region src/svelte/hooks.d.ts
|
|
5
|
+
interface UseSubscriptionOptions<S extends Subscribable<any> & Readable$1<any>, R = S['~']['output']> {
|
|
6
|
+
selector?: (value: S['~']['output']) => R;
|
|
7
|
+
isEqual?: (prev: R, next: R) => boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Use this function to subscribe to a reactive value from a Svelte component.
|
|
11
|
+
* Accepts a subscription object directly, or a factory function that is called
|
|
12
|
+
* once to create one.
|
|
13
|
+
*
|
|
14
|
+
* Returns a Svelte `Readable` store — read it with the `$` auto-subscription
|
|
15
|
+
* (`$value`) in markup. The underlying subscription is created lazily on the
|
|
16
|
+
* first subscriber and torn down when the last one leaves.
|
|
17
|
+
*
|
|
18
|
+
* @example Inline subscription
|
|
19
|
+
* ```svelte
|
|
20
|
+
* <script lang="ts">
|
|
21
|
+
* import { createWebStorageValue } from 'seitu/web'
|
|
22
|
+
* import { useSubscription } from 'seitu/svelte'
|
|
23
|
+
* import * as z from 'zod'
|
|
24
|
+
*
|
|
25
|
+
* const value = useSubscription(() => createWebStorageValue({
|
|
26
|
+
* type: 'sessionStorage',
|
|
27
|
+
* key: 'test',
|
|
28
|
+
* defaultValue: 0,
|
|
29
|
+
* schema: z.number(),
|
|
30
|
+
* }))
|
|
31
|
+
* </script>
|
|
32
|
+
*
|
|
33
|
+
* <div>{$value}</div>
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example Instance outside of component
|
|
37
|
+
* ```svelte
|
|
38
|
+
* <script lang="ts">
|
|
39
|
+
* import { createWebStorage } from 'seitu/web'
|
|
40
|
+
* import { useSubscription } from 'seitu/svelte'
|
|
41
|
+
* import * as z from 'zod'
|
|
42
|
+
*
|
|
43
|
+
* const sessionStorage = createWebStorage({
|
|
44
|
+
* type: 'sessionStorage',
|
|
45
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
46
|
+
* defaultValues: { count: 0, name: '' },
|
|
47
|
+
* })
|
|
48
|
+
*
|
|
49
|
+
* const value = useSubscription(sessionStorage)
|
|
50
|
+
* </script>
|
|
51
|
+
*
|
|
52
|
+
* <div>{$value.count}</div>
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @example With selector
|
|
56
|
+
* ```svelte
|
|
57
|
+
* <script lang="ts">
|
|
58
|
+
* import { createWebStorage } from 'seitu/web'
|
|
59
|
+
* import { useSubscription } from 'seitu/svelte'
|
|
60
|
+
* import * as z from 'zod'
|
|
61
|
+
*
|
|
62
|
+
* const sessionStorage = createWebStorage({
|
|
63
|
+
* type: 'sessionStorage',
|
|
64
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
65
|
+
* defaultValues: { count: 0, name: '' },
|
|
66
|
+
* })
|
|
67
|
+
*
|
|
68
|
+
* // Updates only when count changes
|
|
69
|
+
* const count = useSubscription(sessionStorage, { selector: v => v.count })
|
|
70
|
+
* </script>
|
|
71
|
+
*
|
|
72
|
+
* <div>{$count}</div>
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
declare function useSubscription<S extends Subscribable<any> & Readable$1<any>, R = S['~']['output']>(source: S | (() => S), options?: UseSubscriptionOptions<S, R>): Readable<R>;
|
|
76
|
+
//#endregion
|
|
77
|
+
export { UseSubscriptionOptions, useSubscription };
|
package/dist/svelte.mjs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { deepEqual } from "fast-equals";
|
|
2
|
+
import { readable } from "svelte/store";
|
|
3
|
+
//#region src/svelte/hooks.ts
|
|
4
|
+
/**
|
|
5
|
+
* Use this function to subscribe to a reactive value from a Svelte component.
|
|
6
|
+
* Accepts a subscription object directly, or a factory function that is called
|
|
7
|
+
* once to create one.
|
|
8
|
+
*
|
|
9
|
+
* Returns a Svelte `Readable` store — read it with the `$` auto-subscription
|
|
10
|
+
* (`$value`) in markup. The underlying subscription is created lazily on the
|
|
11
|
+
* first subscriber and torn down when the last one leaves.
|
|
12
|
+
*
|
|
13
|
+
* @example Inline subscription
|
|
14
|
+
* ```svelte
|
|
15
|
+
* <script lang="ts">
|
|
16
|
+
* import { createWebStorageValue } from 'seitu/web'
|
|
17
|
+
* import { useSubscription } from 'seitu/svelte'
|
|
18
|
+
* import * as z from 'zod'
|
|
19
|
+
*
|
|
20
|
+
* const value = useSubscription(() => createWebStorageValue({
|
|
21
|
+
* type: 'sessionStorage',
|
|
22
|
+
* key: 'test',
|
|
23
|
+
* defaultValue: 0,
|
|
24
|
+
* schema: z.number(),
|
|
25
|
+
* }))
|
|
26
|
+
* <\/script>
|
|
27
|
+
*
|
|
28
|
+
* <div>{$value}</div>
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example Instance outside of component
|
|
32
|
+
* ```svelte
|
|
33
|
+
* <script lang="ts">
|
|
34
|
+
* import { createWebStorage } from 'seitu/web'
|
|
35
|
+
* import { useSubscription } from 'seitu/svelte'
|
|
36
|
+
* import * as z from 'zod'
|
|
37
|
+
*
|
|
38
|
+
* const sessionStorage = createWebStorage({
|
|
39
|
+
* type: 'sessionStorage',
|
|
40
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
41
|
+
* defaultValues: { count: 0, name: '' },
|
|
42
|
+
* })
|
|
43
|
+
*
|
|
44
|
+
* const value = useSubscription(sessionStorage)
|
|
45
|
+
* <\/script>
|
|
46
|
+
*
|
|
47
|
+
* <div>{$value.count}</div>
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example With selector
|
|
51
|
+
* ```svelte
|
|
52
|
+
* <script lang="ts">
|
|
53
|
+
* import { createWebStorage } from 'seitu/web'
|
|
54
|
+
* import { useSubscription } from 'seitu/svelte'
|
|
55
|
+
* import * as z from 'zod'
|
|
56
|
+
*
|
|
57
|
+
* const sessionStorage = createWebStorage({
|
|
58
|
+
* type: 'sessionStorage',
|
|
59
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
60
|
+
* defaultValues: { count: 0, name: '' },
|
|
61
|
+
* })
|
|
62
|
+
*
|
|
63
|
+
* // Updates only when count changes
|
|
64
|
+
* const count = useSubscription(sessionStorage, { selector: v => v.count })
|
|
65
|
+
* <\/script>
|
|
66
|
+
*
|
|
67
|
+
* <div>{$count}</div>
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
function useSubscription(source, options) {
|
|
71
|
+
const { selector, isEqual = deepEqual } = options ?? {};
|
|
72
|
+
const sub = typeof source === "function" ? source() : source;
|
|
73
|
+
function getSnapshot() {
|
|
74
|
+
return selector ? selector(sub.get()) : sub.get();
|
|
75
|
+
}
|
|
76
|
+
const initial = getSnapshot();
|
|
77
|
+
return readable(initial, (set) => {
|
|
78
|
+
let current = initial;
|
|
79
|
+
function refresh() {
|
|
80
|
+
const next = getSnapshot();
|
|
81
|
+
if (!isEqual(current, next)) {
|
|
82
|
+
current = next;
|
|
83
|
+
set(next);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
refresh();
|
|
87
|
+
return sub.subscribe(refresh);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { useSubscription };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "seitu",
|
|
3
3
|
"displayName": "Seitu",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.16.0",
|
|
6
6
|
"private": false,
|
|
7
7
|
"author": "Valerii Strilets",
|
|
8
8
|
"license": "MIT",
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"typescript",
|
|
21
21
|
"react",
|
|
22
22
|
"vue",
|
|
23
|
+
"solid",
|
|
24
|
+
"solid-js",
|
|
25
|
+
"svelte",
|
|
23
26
|
"utils",
|
|
24
27
|
"type-safe",
|
|
25
28
|
"tanstack-intent"
|
|
@@ -45,6 +48,14 @@
|
|
|
45
48
|
"types": "./dist/vue/index.d.mts",
|
|
46
49
|
"import": "./dist/vue.mjs"
|
|
47
50
|
},
|
|
51
|
+
"./solid": {
|
|
52
|
+
"types": "./dist/solid/index.d.mts",
|
|
53
|
+
"import": "./dist/solid.mjs"
|
|
54
|
+
},
|
|
55
|
+
"./svelte": {
|
|
56
|
+
"types": "./dist/svelte/index.d.mts",
|
|
57
|
+
"import": "./dist/svelte.mjs"
|
|
58
|
+
},
|
|
48
59
|
"./utils": {
|
|
49
60
|
"types": "./dist/utils/index.d.mts",
|
|
50
61
|
"import": "./dist/utils.mjs"
|
|
@@ -62,6 +73,8 @@
|
|
|
62
73
|
"peerDependencies": {
|
|
63
74
|
"react": ">=19",
|
|
64
75
|
"react-dom": ">=19",
|
|
76
|
+
"solid-js": ">=1.9",
|
|
77
|
+
"svelte": ">=5",
|
|
65
78
|
"vue": ">=3.5"
|
|
66
79
|
},
|
|
67
80
|
"peerDependenciesMeta": {
|
|
@@ -71,6 +84,12 @@
|
|
|
71
84
|
"react-dom": {
|
|
72
85
|
"optional": true
|
|
73
86
|
},
|
|
87
|
+
"solid-js": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
90
|
+
"svelte": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
74
93
|
"vue": {
|
|
75
94
|
"optional": true
|
|
76
95
|
}
|
|
@@ -79,21 +98,26 @@
|
|
|
79
98
|
"fast-equals": "^6.0.0"
|
|
80
99
|
},
|
|
81
100
|
"devDependencies": {
|
|
101
|
+
"@solidjs/testing-library": "^0.8.10",
|
|
82
102
|
"@standard-schema/spec": "^1.1.0",
|
|
83
103
|
"@tanstack/intent": "^0.0.42",
|
|
84
104
|
"@testing-library/jest-dom": "^6.9.1",
|
|
85
105
|
"@testing-library/react": "^16.3.2",
|
|
86
106
|
"@types/react": "^19.2.17",
|
|
107
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
87
108
|
"fake-indexeddb": "^6.2.5",
|
|
88
109
|
"happy-dom": "^20.10.2",
|
|
89
110
|
"react-dom": "^19.2.7",
|
|
111
|
+
"solid-js": "^1.9.13",
|
|
112
|
+
"svelte": "^5.56.3",
|
|
90
113
|
"tsdown": "^0.22.2",
|
|
91
114
|
"type-fest": "^5.7.0",
|
|
92
115
|
"typescript": "^6.0.3",
|
|
93
|
-
"vite": "^
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
116
|
+
"vite": "^8.0.16",
|
|
117
|
+
"vite-plugin-solid": "^2.11.12",
|
|
118
|
+
"vitest": "^4.1.8",
|
|
119
|
+
"vue": "^3.5.38",
|
|
120
|
+
"yaml": "^2.9.0",
|
|
97
121
|
"zod": "^4.4.3"
|
|
98
122
|
},
|
|
99
123
|
"scripts": {
|
package/skills/README.md
CHANGED
|
@@ -78,6 +78,19 @@ Restart Cursor or start a new agent chat so skills are picked up.
|
|
|
78
78
|
|-------|-----------|-------------|
|
|
79
79
|
| [use-subscription-vue](./use-subscription-vue/SKILL.md) | `seitu#use-subscription-vue` | Composable for any Seitu primitive |
|
|
80
80
|
|
|
81
|
+
### Solid (`seitu/solid`)
|
|
82
|
+
|
|
83
|
+
| Skill | Intent id | When to use |
|
|
84
|
+
|-------|-----------|-------------|
|
|
85
|
+
| [use-subscription-solid](./use-subscription-solid/SKILL.md) | `seitu#use-subscription-solid` | Primitive returning an accessor for any Seitu primitive |
|
|
86
|
+
| [subscription-solid](./subscription-solid/SKILL.md) | `seitu#subscription-solid` | Render-prop component |
|
|
87
|
+
|
|
88
|
+
### Svelte (`seitu/svelte`)
|
|
89
|
+
|
|
90
|
+
| Skill | Intent id | When to use |
|
|
91
|
+
|-------|-----------|-------------|
|
|
92
|
+
| [use-subscription-svelte](./use-subscription-svelte/SKILL.md) | `seitu#use-subscription-svelte` | Binding returning a Svelte `Readable` store for any Seitu primitive |
|
|
93
|
+
|
|
81
94
|
## Registry and version history
|
|
82
95
|
|
|
83
96
|
The package includes the `tanstack-intent` npm keyword. Published versions are indexed on the [Agent Skills Registry](https://tanstack.com/intent/registry) with skill history per release.
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Module map, mental model, decision tree, SSR — read before other Seitu skills.
|
|
5
5
|
type: lifecycle
|
|
6
6
|
library: seitu
|
|
7
|
-
library_version: "0.
|
|
7
|
+
library_version: "0.16.0"
|
|
8
8
|
sources:
|
|
9
9
|
- letstri/seitu:docs/content/docs/index.mdx
|
|
10
10
|
- letstri/seitu:seitu/src/core/index.ts
|
|
@@ -13,7 +13,7 @@ sources:
|
|
|
13
13
|
# Seitu Overview
|
|
14
14
|
|
|
15
15
|
Seitu is a type-safe reactive primitives library. Framework-agnostic core with
|
|
16
|
-
React and
|
|
16
|
+
React, Vue, Solid, and Svelte bindings. Every primitive shares the same `get()` / `subscribe()`
|
|
17
17
|
API — no actions, no reducers, no context providers.
|
|
18
18
|
|
|
19
19
|
## Module map
|
|
@@ -24,9 +24,11 @@ API — no actions, no reducers, no context providers.
|
|
|
24
24
|
| `seitu/web` | Browser persistence (localStorage, sessionStorage, IndexedDB) and DOM state |
|
|
25
25
|
| `seitu/react` | `useSubscription` hook + `Subscription` component |
|
|
26
26
|
| `seitu/vue` | `useSubscription` composable |
|
|
27
|
+
| `seitu/solid` | `useSubscription` primitive (returns `Accessor`) + `Subscription` component |
|
|
28
|
+
| `seitu/svelte` | `useSubscription` (returns a Svelte `Readable` store) |
|
|
27
29
|
| `seitu/utils` | Helpers (`repairValueObjectWithDefault`) |
|
|
28
30
|
|
|
29
|
-
ESM-only. Node >= 22. React >= 19
|
|
31
|
+
ESM-only. Node >= 22. React >= 19, Vue >= 3.5, Solid >= 1.9, and Svelte >= 5 are optional peer deps.
|
|
30
32
|
|
|
31
33
|
## Mental model
|
|
32
34
|
|
|
@@ -101,7 +103,9 @@ What do you need?
|
|
|
101
103
|
│
|
|
102
104
|
└─ Framework integration
|
|
103
105
|
├─ React → useSubscription (hook) or Subscription (component)
|
|
104
|
-
|
|
106
|
+
├─ Vue → useSubscription (composable)
|
|
107
|
+
├─ Solid → useSubscription (returns Accessor) or Subscription (component)
|
|
108
|
+
└─ Svelte → useSubscription (returns a Readable store, read with $value)
|
|
105
109
|
```
|
|
106
110
|
|
|
107
111
|
## Framework binding
|
|
@@ -122,6 +126,21 @@ const value = useSubscription(store)
|
|
|
122
126
|
const data = useSubscription(computed(() => createWebStorageValue({ ... })))
|
|
123
127
|
```
|
|
124
128
|
|
|
129
|
+
```tsx
|
|
130
|
+
// Solid — instance or reactive getter; returns an Accessor, read with value()
|
|
131
|
+
const value = useSubscription(store)
|
|
132
|
+
const data = useSubscription(() => createWebStorageValue({ ... }))
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```svelte
|
|
136
|
+
<!-- Svelte — instance or factory; returns a Readable store, read with $value -->
|
|
137
|
+
<script lang="ts">
|
|
138
|
+
const value = useSubscription(store)
|
|
139
|
+
const data = useSubscription(() => createWebStorageValue({ ... }))
|
|
140
|
+
</script>
|
|
141
|
+
<div>{$value}</div>
|
|
142
|
+
```
|
|
143
|
+
|
|
125
144
|
## SSR
|
|
126
145
|
|
|
127
146
|
All `seitu/web` primitives return defaults when `window` / `navigator` is
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: subscription-solid
|
|
3
|
+
description: >-
|
|
4
|
+
Render-prop Subscription component for Solid.
|
|
5
|
+
type: framework
|
|
6
|
+
library: seitu
|
|
7
|
+
library_version: "0.16.0"
|
|
8
|
+
requires:
|
|
9
|
+
- seitu-overview
|
|
10
|
+
- use-subscription-solid
|
|
11
|
+
sources:
|
|
12
|
+
- letstri/seitu:docs/content/docs/solid/components.mdx
|
|
13
|
+
- letstri/seitu:seitu/src/solid/components.ts
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Subscription (Solid component)
|
|
17
|
+
|
|
18
|
+
Declarative render-prop component from `seitu/solid`. The children function runs once and
|
|
19
|
+
receives a Solid `Accessor<R>` — read it with `value()` so updates stay fine-grained.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { Subscription } from 'seitu/solid'
|
|
23
|
+
|
|
24
|
+
<Subscription value={storage}>
|
|
25
|
+
{value => <div>{value().count}</div>}
|
|
26
|
+
</Subscription>
|
|
27
|
+
|
|
28
|
+
<Subscription value={storage} selector={v => v.count}>
|
|
29
|
+
{count => <div>{count()}</div>}
|
|
30
|
+
</Subscription>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Props
|
|
34
|
+
|
|
35
|
+
| Prop | Type | Description |
|
|
36
|
+
|------|------|-------------|
|
|
37
|
+
| `value` | `Subscribable & Readable` | The reactive source |
|
|
38
|
+
| `selector?` | `(value) => R` | Optional selector for granular updates |
|
|
39
|
+
| `children` | `(value: Accessor<R>) => JSX.Element` | Render function receiving an accessor |
|
|
40
|
+
|
|
41
|
+
## Common Mistakes
|
|
42
|
+
|
|
43
|
+
### [CRITICAL] Treating children's argument as a value, not an accessor
|
|
44
|
+
|
|
45
|
+
Wrong:
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<Subscription value={store}>
|
|
49
|
+
{value => <div>{value.count}</div>}
|
|
50
|
+
</Subscription>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Correct:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<Subscription value={store}>
|
|
57
|
+
{value => <div>{value().count}</div>}
|
|
58
|
+
</Subscription>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Unlike React, the children function runs once and receives a Solid `Accessor`; call it (`value()`) inside JSX.
|
|
62
|
+
|
|
63
|
+
### [LOW] Preferring the component over the primitive without reason
|
|
64
|
+
|
|
65
|
+
Wrong:
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
<Subscription value={s}>{v => <Child value={v()} />}</Subscription>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Correct:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
const v = useSubscription(s); return <Child value={v()} />
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`useSubscription` is simpler for most cases; `Subscription` is for render-prop composition.
|
|
78
|
+
|
|
79
|
+
### [CRITICAL] Importing from seitu/react in Solid
|
|
80
|
+
|
|
81
|
+
Wrong:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { Subscription } from 'seitu/react'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Correct:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { Subscription } from 'seitu/solid'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The React component relies on React internals; Solid apps must use `seitu/solid`.
|
|
94
|
+
|
|
95
|
+
## Source
|
|
96
|
+
|
|
97
|
+
`src/solid/components.ts`
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: use-subscription-solid
|
|
3
|
+
description: >-
|
|
4
|
+
Solid primitive returning an Accessor for any Seitu primitive.
|
|
5
|
+
type: framework
|
|
6
|
+
library: seitu
|
|
7
|
+
library_version: "0.16.0"
|
|
8
|
+
requires:
|
|
9
|
+
- seitu-overview
|
|
10
|
+
sources:
|
|
11
|
+
- letstri/seitu:docs/content/docs/solid/hooks.mdx
|
|
12
|
+
- letstri/seitu:seitu/src/solid/hooks.ts
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# useSubscription (Solid)
|
|
16
|
+
|
|
17
|
+
Core Solid primitive from `seitu/solid`. Returns a Solid `Accessor<T>` (`() => T`) that stays in sync with the source. Works with any `Subscribable<T> & Readable<T>`. Accepts a raw instance or a reactive getter.
|
|
18
|
+
|
|
19
|
+
## Basic usage (module-level instance)
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { useSubscription } from 'seitu/solid'
|
|
23
|
+
import { createStore } from 'seitu'
|
|
24
|
+
|
|
25
|
+
const count = createStore(0)
|
|
26
|
+
|
|
27
|
+
function Counter() {
|
|
28
|
+
const value = useSubscription(count)
|
|
29
|
+
return <button onClick={() => count.set(v => v + 1)}>{value()}</button>
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Inline subscription (getter form)
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { useSubscription } from 'seitu/solid'
|
|
37
|
+
import { createScrollState } from 'seitu/web'
|
|
38
|
+
|
|
39
|
+
function ScrollTracker() {
|
|
40
|
+
let ref: HTMLDivElement | undefined
|
|
41
|
+
const state = useSubscription(() =>
|
|
42
|
+
createScrollState({ element: () => ref, direction: 'vertical' })
|
|
43
|
+
)
|
|
44
|
+
return <div ref={ref}>{state().top.reached ? 'at top' : 'scrolled'}</div>
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Reactive source (re-subscribes when a signal changes)
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { createSignal } from 'solid-js'
|
|
52
|
+
import { useSubscription } from 'seitu/solid'
|
|
53
|
+
import { createWebStorageValue } from 'seitu/web'
|
|
54
|
+
|
|
55
|
+
function UserStorage(props: { userId: string }) {
|
|
56
|
+
const data = useSubscription(() =>
|
|
57
|
+
createWebStorageValue({ type: 'localStorage', key: `user:${props.userId}`, schema, defaultValue })
|
|
58
|
+
)
|
|
59
|
+
return <div>{data().name}</div>
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## With selector (granular updates)
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
const count = useSubscription(storage, { selector: v => v.count })
|
|
67
|
+
// count() only changes when the selected value changes
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Options
|
|
71
|
+
|
|
72
|
+
| Option | Type | Description |
|
|
73
|
+
|--------|------|-------------|
|
|
74
|
+
| `selector?` | `(value) => R` | Derive subset; the accessor updates only when it changes |
|
|
75
|
+
| `isEqual?` | `(prev, next) => boolean` | Custom equality (default: `deepEqual`) |
|
|
76
|
+
|
|
77
|
+
## Returns
|
|
78
|
+
|
|
79
|
+
`Accessor<R>` — a getter function. Read it with `value()` inside JSX or any tracked scope.
|
|
80
|
+
|
|
81
|
+
## Common Mistakes
|
|
82
|
+
|
|
83
|
+
### [CRITICAL] Reading the accessor without calling it
|
|
84
|
+
|
|
85
|
+
Wrong:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
const value = useSubscription(store)
|
|
89
|
+
return <div>{value}</div>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Correct:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
const value = useSubscription(store)
|
|
96
|
+
return <div>{value()}</div>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`useSubscription` returns a Solid `Accessor`; you must call it (`value()`) to read and track the value.
|
|
100
|
+
|
|
101
|
+
### [HIGH] Passing reactive props directly instead of a getter
|
|
102
|
+
|
|
103
|
+
Wrong:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
function Item(props: { store: Store<number> }) {
|
|
107
|
+
const value = useSubscription(props.store)
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Correct:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
function Item(props: { store: Store<number> }) {
|
|
115
|
+
const value = useSubscription(() => props.store)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Reading `props.store` outside a tracked scope loses reactivity; the getter form re-subscribes when the prop changes.
|
|
120
|
+
|
|
121
|
+
### [CRITICAL] Importing from seitu/react in Solid
|
|
122
|
+
|
|
123
|
+
Wrong:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { useSubscription } from 'seitu/react'
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Correct:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { useSubscription } from 'seitu/solid'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Solid apps must use the `seitu/solid` primitive — the React hook relies on `useSyncExternalStore`.
|
|
136
|
+
|
|
137
|
+
## See also
|
|
138
|
+
|
|
139
|
+
- [`subscription-solid`](../subscription-solid/SKILL.md) — Render-prop component alternative.
|
|
140
|
+
- [`create-scroll-state`](../create-scroll-state/SKILL.md) — Scroll tracking uses the getter + ref pattern in Solid.
|
|
141
|
+
|
|
142
|
+
## Source
|
|
143
|
+
|
|
144
|
+
`src/solid/hooks.ts`
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: use-subscription-svelte
|
|
3
|
+
description: >-
|
|
4
|
+
Svelte binding returning a Readable store for any Seitu primitive.
|
|
5
|
+
type: framework
|
|
6
|
+
library: seitu
|
|
7
|
+
library_version: "0.16.0"
|
|
8
|
+
requires:
|
|
9
|
+
- seitu-overview
|
|
10
|
+
sources:
|
|
11
|
+
- letstri/seitu:docs/content/docs/svelte/hooks.mdx
|
|
12
|
+
- letstri/seitu:seitu/src/svelte/hooks.ts
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# useSubscription (Svelte)
|
|
16
|
+
|
|
17
|
+
Binding from `seitu/svelte`. Returns a Svelte [`Readable`](https://svelte.dev/docs/svelte-store#readable)
|
|
18
|
+
store that stays in sync with any `Subscribable<T> & Readable<T>`. Read it with the `$`
|
|
19
|
+
auto-subscription (`$value`) in markup. The source subscription is created lazily on the
|
|
20
|
+
first subscriber and torn down when the last one leaves (SSR-safe, auto-cleanup).
|
|
21
|
+
|
|
22
|
+
## Basic usage (module-level instance)
|
|
23
|
+
|
|
24
|
+
```svelte
|
|
25
|
+
<script lang="ts">
|
|
26
|
+
import { useSubscription } from 'seitu/svelte'
|
|
27
|
+
import { createStore } from 'seitu'
|
|
28
|
+
|
|
29
|
+
const count = createStore(0)
|
|
30
|
+
const value = useSubscription(count)
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<button onclick={() => count.set(v => v + 1)}>{$value}</button>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Inline subscription (factory form)
|
|
37
|
+
|
|
38
|
+
```svelte
|
|
39
|
+
<script lang="ts">
|
|
40
|
+
import { useSubscription } from 'seitu/svelte'
|
|
41
|
+
import { createWebStorageValue } from 'seitu/web'
|
|
42
|
+
import * as z from 'zod'
|
|
43
|
+
|
|
44
|
+
const value = useSubscription(() => createWebStorageValue({
|
|
45
|
+
type: 'sessionStorage',
|
|
46
|
+
key: 'test',
|
|
47
|
+
defaultValue: 0,
|
|
48
|
+
schema: z.number(),
|
|
49
|
+
}))
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div>{$value}</div>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## With selector (granular updates)
|
|
56
|
+
|
|
57
|
+
```svelte
|
|
58
|
+
<script lang="ts">
|
|
59
|
+
const count = useSubscription(storage, { selector: v => v.count })
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<div>{$count}</div>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Options
|
|
66
|
+
|
|
67
|
+
| Option | Type | Description |
|
|
68
|
+
|--------|------|-------------|
|
|
69
|
+
| `selector?` | `(value) => R` | Derive a subset; the store updates only when it changes |
|
|
70
|
+
| `isEqual?` | `(prev, next) => boolean` | Custom equality (default: `deepEqual`) |
|
|
71
|
+
|
|
72
|
+
## Returns
|
|
73
|
+
|
|
74
|
+
`Readable<R>` (from `svelte/store`) — read it as `$value` in markup, or with `get(value)` in scripts.
|
|
75
|
+
|
|
76
|
+
## Common Mistakes
|
|
77
|
+
|
|
78
|
+
### [CRITICAL] Reading the store without the `$` prefix
|
|
79
|
+
|
|
80
|
+
Wrong:
|
|
81
|
+
|
|
82
|
+
```svelte
|
|
83
|
+
<div>{value}</div>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Correct:
|
|
87
|
+
|
|
88
|
+
```svelte
|
|
89
|
+
<div>{$value}</div>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`useSubscription` returns a Svelte store; the `$` prefix auto-subscribes and reads the current value.
|
|
93
|
+
|
|
94
|
+
### [MEDIUM] Passing a factory expecting reactivity to a changing source
|
|
95
|
+
|
|
96
|
+
Wrong:
|
|
97
|
+
|
|
98
|
+
```svelte
|
|
99
|
+
<script lang="ts">
|
|
100
|
+
// The factory runs once; it does not re-run when `id` changes.
|
|
101
|
+
const value = useSubscription(() => createWebStorageValue({ key: `user:${id}`, ... }))
|
|
102
|
+
</script>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Correct:
|
|
106
|
+
|
|
107
|
+
```svelte
|
|
108
|
+
<script lang="ts">
|
|
109
|
+
// Recreate via a keyed block or derive the source explicitly when it must change.
|
|
110
|
+
const value = useSubscription(createWebStorageValue({ key: `user:${id}`, ... }))
|
|
111
|
+
</script>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The factory is a one-time initializer; for a source that changes over time, recreate the binding (e.g. in a `{#key}` block).
|
|
115
|
+
|
|
116
|
+
### [CRITICAL] Importing from seitu/react in Svelte
|
|
117
|
+
|
|
118
|
+
Wrong:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { useSubscription } from 'seitu/react'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Correct:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { useSubscription } from 'seitu/svelte'
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The React hook relies on `useSyncExternalStore`; Svelte apps must use the `seitu/svelte` binding.
|
|
131
|
+
|
|
132
|
+
## Source
|
|
133
|
+
|
|
134
|
+
`src/svelte/hooks.ts`
|