seitu 0.1.0 → 0.2.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 CHANGED
@@ -15,10 +15,10 @@ To quick start you only need to write the following code:
15
15
 
16
16
  ```tsx
17
17
  import { useSubscription } from 'seitu/react'
18
- import { sessionStorageValue } from 'seitu/web'
18
+ import { createSessionStorageValue } from 'seitu/web'
19
19
  import * as z from 'zod'
20
20
 
21
- const value = sessionStorageValue({
21
+ const value = createSessionStorageValue({
22
22
  key: 'test',
23
23
  defaultValue: 0,
24
24
  schema: z.number(),
@@ -30,7 +30,7 @@ value.remove()
30
30
  value.subscribe(v => console.log(v))
31
31
 
32
32
  function Counter() {
33
- const count = useSubscription(() => value)
33
+ const count = useSubscription(value)
34
34
 
35
35
  return (
36
36
  <div>
@@ -1,2 +1,2 @@
1
- export * from './store';
1
+ export * from './schema-store';
2
2
  export * from './subscription';
@@ -0,0 +1,19 @@
1
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
+ import type { Simplify } from '../utils';
3
+ import type { Readable, Subscribable, Writable } from './index';
4
+ export type SchemaStoreSchema = Record<string, StandardSchemaV1<unknown, unknown>>;
5
+ export type SchemaStoreOutput<S extends SchemaStoreSchema> = Simplify<{
6
+ [K in keyof S]: StandardSchemaV1.InferOutput<S[K]>;
7
+ }>;
8
+ export interface SchemaStore<O extends Record<string, unknown>> extends Subscribable<O>, Readable<O>, Writable<Partial<O>> {
9
+ getDefaultValue: <K extends keyof O>(key: K) => O[K];
10
+ }
11
+ export interface SchemaStoreOptions<S extends Record<string, StandardSchemaV1>> {
12
+ schemas: S;
13
+ defaultValues: SchemaStoreOutput<S>;
14
+ provider: {
15
+ get: () => SchemaStoreOutput<S>;
16
+ set: (value: Partial<SchemaStoreOutput<S>>) => void;
17
+ };
18
+ }
19
+ export declare function createSchemaStore<S extends Record<string, StandardSchemaV1>>(options: SchemaStoreOptions<S>): SchemaStore<SchemaStoreOutput<S>>;
@@ -1,7 +1,13 @@
1
1
  export interface Subscribable<V> {
2
2
  'subscribe': (callback: (value: V) => any) => () => void;
3
3
  '~': {
4
+ /**
5
+ * Type type with returned value of the subscription.
6
+ */
4
7
  output: V;
8
+ /**
9
+ * A function that notifies all subscribers that the value has changed.
10
+ */
5
11
  notify: () => void;
6
12
  };
7
13
  }
package/dist/core.js CHANGED
@@ -1,2 +1,2 @@
1
1
  import { n as e, t } from "./core-CuCUF5Aj.js";
2
- export { e as createStore, t as createSubscription };
2
+ export { e as createSchemaStore, t as createSubscription };
@@ -1,20 +1,25 @@
1
1
  import type { Readable, Subscribable } from '../core/index';
2
+ import * as React from 'react';
3
+ export type UseSubscriptionSource<S extends Subscribable<any> & Readable<any>> = S | (() => S);
4
+ export interface UseSubscriptionOptions<S extends Subscribable<any> & Readable<any>, R = S['~']['output']> {
5
+ selector?: (value: S['~']['output']) => R;
6
+ deps?: React.DependencyList;
7
+ }
2
8
  /**
3
- * Use this hook to subscribe to a reactive value. The factory function is called only
4
- * once on first render subsequent renders reuse the cached subscription.
9
+ * Use this hook to subscribe to a reactive value. Accepts a subscription object
10
+ * directly, or a factory function that is called only once on first render —
11
+ * subsequent renders reuse the cached subscription unless dependency array changes.
5
12
  *
6
- * @kind hook
7
- *
8
- * @example
13
+ * @example Inline subscription
9
14
  * ```tsx twoslash title="/app/page.tsx"
10
15
  * 'use client'
11
16
  *
12
- * import { sessionStorageValue } from 'seitu/web'
17
+ * import { createSessionStorageValue } from 'seitu/web'
13
18
  * import { useSubscription } from 'seitu/react'
14
19
  * import * as z from 'zod'
15
20
  *
16
21
  * export default function Page() {
17
- * const value = useSubscription(() => sessionStorageValue({
22
+ * const value = useSubscription(() => createSessionStorageValue({
18
23
  * key: 'test',
19
24
  * defaultValue: 0,
20
25
  * schema: z.number(),
@@ -24,7 +29,26 @@ import type { Readable, Subscribable } from '../core/index';
24
29
  * }
25
30
  * ```
26
31
  *
27
- * @example
32
+ * @example Instance outside of component
33
+ * ```tsx twoslash title="/app/page.tsx"
34
+ * 'use client'
35
+ *
36
+ * import { createSessionStorage } from 'seitu/web'
37
+ * import { useSubscription } from 'seitu/react'
38
+ * import * as z from 'zod'
39
+ *
40
+ * const sessionStorage = createSessionStorage({
41
+ * schemas: { count: z.number(), name: z.string() },
42
+ * defaultValues: { count: 0, name: '' },
43
+ * })
44
+ *
45
+ * export default function Page() {
46
+ * const value = useSubscription(sessionStorage)
47
+ * return <div>{value.count}</div>
48
+ * }
49
+ * ```
50
+ *
51
+ * @example Subscription with selector
28
52
  * ```tsx twoslash title="/app/page.tsx"
29
53
  * 'use client'
30
54
  *
@@ -42,10 +66,30 @@ import type { Readable, Subscribable } from '../core/index';
42
66
  *
43
67
  * export default function Page() {
44
68
  * // Usage with selector, re-renders only when count changes
45
- * const count = useSubscription(() => sessionStorage, value => value.count)
69
+ * const count = useSubscription(sessionStorage, { selector: value => value.count })
46
70
  *
47
71
  * return <div>{count}</div>
48
72
  * }
49
73
  * ```
74
+ *
75
+ * @example Ref example
76
+ * ```tsx twoslash title="/app/page.tsx"
77
+ * 'use client'
78
+ *
79
+ * import * as React from 'react'
80
+ * import { scrollState } from 'seitu/web'
81
+ * import { useSubscription } from 'seitu/react'
82
+ *
83
+ * export default function Page() {
84
+ * const [ref, setRef] = React.useState<HTMLDivElement | null>(null)
85
+ * const state = useSubscription(() => scrollState({ element: ref, direction: 'vertical' }), { deps: [ref] })
86
+ *
87
+ * return (
88
+ * <div ref={setRef}>
89
+ * {String(state.top.value)}
90
+ * </div>
91
+ * )
92
+ * }
93
+ * ```
50
94
  */
51
- export declare function useSubscription<S extends Subscribable<any> & Readable<any>, R = S['~']['output']>(factory: () => S, selector?: (value: S['~']['output']) => R): R;
95
+ export declare function useSubscription<S extends Subscribable<any> & Readable<any> = Subscribable<any> & Readable<any>, R = S['~']['output']>(source: UseSubscriptionSource<S>, options?: UseSubscriptionOptions<S, R>): R;
package/dist/react.js CHANGED
@@ -1778,14 +1778,17 @@ var t = Object.create, n = Object.defineProperty, r = Object.getOwnPropertyDescr
1778
1778
  };
1779
1779
  }))(), 1);
1780
1780
  function Ze(t, n) {
1781
- let r = e.useRef(void 0);
1782
- r.current === void 0 && (r.current = t());
1783
- let i = r.current;
1784
- if (!i.get || !i.subscribe) throw Error("Subscription is not valid. It must have a get and subscribe method.");
1785
- let [a, o] = e.useState(() => n ? n(i.get()) : i.get()), s = e.useEffectEvent(() => a);
1786
- return e.useEffect(() => i.subscribe((e) => {
1787
- let t = n ? n(e) : e;
1788
- (0, Xe.default)(t, s()) || o(t);
1789
- }), [i, n]), a;
1781
+ let { selector: r, deps: i = [] } = n ?? {}, a = typeof t == "function", o = e.useRef(t);
1782
+ if (!a && t !== o.current) throw Error("useSubscription detected a new object on re-render. Either create the subscription outside the component or wrap it in a factory: useSubscription(() => yourSubscription(...))");
1783
+ o.current = t;
1784
+ let s = a ? t : () => t, c = e.useRef(s);
1785
+ c.current = s;
1786
+ let l = e.useMemo(() => c.current(), i);
1787
+ if (!l.get || !l.subscribe) throw Error("Subscription is not valid. It must have a get and subscribe method.");
1788
+ let [u, d] = e.useState(() => r ? r(l.get()) : l.get()), f = e.useEffectEvent(() => u);
1789
+ return e.useEffect(() => l.subscribe((e) => {
1790
+ let t = r ? r(e) : e;
1791
+ (0, Xe.default)(t, f()) || d(t);
1792
+ }), [l, r]), u;
1790
1793
  }
1791
1794
  export { Ze as useSubscription };
@@ -1,3 +1,5 @@
1
+ export * from './local-storage';
2
+ export * from './local-storage-value';
1
3
  export * from './media-query';
2
4
  export * from './scroll-state';
3
5
  export * from './session-storage';
@@ -0,0 +1,68 @@
1
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
+ import type { LocalStorage } from './local-storage';
3
+ import type { WebStorageValue as LocalStorageValue, WebStorageValueOptionsWithSchema as LocalStorageValueOptionsWithSchema, WebStorageValueOptionsWithStorage as LocalStorageValueOptionsWithStorage } from './web-storage-value';
4
+ export type { LocalStorageValue, LocalStorageValueOptionsWithSchema, LocalStorageValueOptionsWithStorage, };
5
+ /**
6
+ * Creates a reactive handle for a single localStorage value.
7
+ *
8
+ * Two modes:
9
+ * - **Schema mode** (`schema`, `key`, `defaultValue`): standalone key with validation. Missing or
10
+ * invalid stored data returns `defaultValue`.
11
+ * - **Storage mode** (`storage`, `key`): binds to one key of a `createLocalStorage` instance.
12
+ * Type and default come from that storage.
13
+ *
14
+ * @example Vanilla
15
+ * ```ts twoslash title="local-storage-value.ts"
16
+ * import { createLocalStorageValue } from 'seitu/web'
17
+ * import * as z from 'zod'
18
+ *
19
+ * const value = createLocalStorageValue({
20
+ * key: 'count',
21
+ * defaultValue: 0,
22
+ * schema: z.number(),
23
+ * })
24
+ * value.get() // 0
25
+ * value.set(1)
26
+ * value.set(v => v + 1)
27
+ * value.subscribe(v => console.log(v))
28
+ * ```
29
+ *
30
+ * @example Storage mode
31
+ * ```ts twoslash title="local-storage-value.ts"
32
+ * import { createLocalStorage, createLocalStorageValue } from 'seitu/web'
33
+ * import * as z from 'zod'
34
+ *
35
+ * const storage = createLocalStorage({
36
+ * schemas: { count: z.number(), name: z.string() },
37
+ * defaultValues: { count: 0, name: '' },
38
+ * })
39
+ * const count = createLocalStorageValue({ storage, key: 'count' })
40
+ * count.set(5)
41
+ * storage.get().count === 5 // true
42
+ * ```
43
+ *
44
+ * @example React
45
+ * ```tsx twoslash title="page.tsx"
46
+ * 'use client'
47
+ *
48
+ * import { createLocalStorageValue } from 'seitu/web'
49
+ * import { useSubscription } from 'seitu/react'
50
+ * import * as z from 'zod'
51
+ *
52
+ * export default function Page() {
53
+ * const { value: count } = useSubscription(() => createLocalStorageValue({
54
+ * key: 'count',
55
+ * defaultValue: 0,
56
+ * schema: z.number(),
57
+ * }))
58
+ *
59
+ * return (
60
+ * <div>
61
+ * <span>{count}</span>
62
+ * </div>
63
+ * )
64
+ * }
65
+ * ```
66
+ */
67
+ export declare function createLocalStorageValue<Storage extends LocalStorage<any>, K extends keyof Storage['~']['output']>(options: LocalStorageValueOptionsWithStorage<Storage, K>): LocalStorageValue<Storage['~']['output'][K]>;
68
+ export declare function createLocalStorageValue<S extends StandardSchemaV1<unknown>>(options: LocalStorageValueOptionsWithSchema<S>): LocalStorageValue<StandardSchemaV1.InferOutput<S>>;
@@ -0,0 +1,50 @@
1
+ import type { SchemaStoreOutput, SchemaStoreSchema } from '../core/index';
2
+ import type { WebStorage as LocalStorage, WebStorageOptions as LocalStorageOptions } from './web-storage';
3
+ export type { LocalStorage, LocalStorageOptions };
4
+ /**
5
+ * Creates a reactive handle for a localStorage instance.
6
+ *
7
+ * @example Vanilla
8
+ * ```ts twoslash title="local-storage.ts"
9
+ * import { createLocalStorage } from 'seitu/web'
10
+ * import * as z from 'zod'
11
+ *
12
+ * const localStorage = createLocalStorage({
13
+ * schemas: {
14
+ * token: z.string().nullable(),
15
+ * preferences: z.object({ theme: z.enum(['light', 'dark']) }),
16
+ * },
17
+ * defaultValues: { token: null, preferences: { theme: 'light' } },
18
+ * })
19
+ *
20
+ * localStorage.get() // { token: null, preferences: { theme: 'light' } }
21
+ * localStorage.set({ token: 'abc' })
22
+ * localStorage.get() // { token: 'abc', preferences: { theme: 'light' } }
23
+ * localStorage.subscribe(console.log)
24
+ * ```
25
+ *
26
+ * @example React
27
+ * ```tsx twoslash title="page.tsx"
28
+ * 'use client'
29
+ *
30
+ * import { createLocalStorage } from 'seitu/web'
31
+ * import { useSubscription } from 'seitu/react'
32
+ * import * as z from 'zod'
33
+ *
34
+ * const localStorage = createLocalStorage({
35
+ * schemas: { count: z.number(), name: z.string() },
36
+ * defaultValues: { count: 0, name: '' },
37
+ * })
38
+ *
39
+ * export default function Page() {
40
+ * const { value } = useSubscription(localStorage)
41
+ * return (
42
+ * <div>
43
+ * <span>{value.count}</span>
44
+ * <span>{value.name}</span>
45
+ * </div>
46
+ * )
47
+ * }
48
+ * ```
49
+ */
50
+ export declare function createLocalStorage<S extends SchemaStoreSchema>(options: LocalStorageOptions<S>): LocalStorage<SchemaStoreOutput<S>>;
@@ -31,7 +31,7 @@ export interface MediaQueryOptions<T extends string> {
31
31
  /**
32
32
  * Creates a handle for a media query.
33
33
  *
34
- * @example
34
+ * @example Vanilla
35
35
  * ```ts twoslash title="media-query.ts"
36
36
  * import { mediaQuery } from 'seitu/web'
37
37
  * import { useSubscription } from 'seitu/react'
@@ -47,6 +47,7 @@ export interface MediaQueryOptions<T extends string> {
47
47
  * console.log(state)
48
48
  * ```
49
49
  *
50
+ * @example React
50
51
  * ```tsx twoslash title="page.tsx"
51
52
  * import { mediaQuery } from 'seitu/web'
52
53
  * import { useSubscription } from 'seitu/react'
@@ -55,11 +56,12 @@ export interface MediaQueryOptions<T extends string> {
55
56
  *
56
57
  * // Usage with some function component
57
58
  * function Layout() {
58
- * const matches = useSubscription(() => isDesktop)
59
+ * const { value: matches } = useSubscription(isDesktop)
59
60
  * return matches ? 'i am desktop' : 'i am mobile'
60
61
  * }
61
62
  * ```
62
63
  *
64
+ * @example Errors
63
65
  * ```tsx twoslash
64
66
  * import { mediaQuery } from 'seitu/web'
65
67
  * import { useSubscription } from 'seitu/react'
@@ -31,7 +31,7 @@ export interface ScrollStateOptions {
31
31
  /**
32
32
  * Creates a reactive handle that tracks scroll position of an element relative to each edge.
33
33
  *
34
- * @example
34
+ * @example Vanilla
35
35
  * ```ts twoslash
36
36
  * import { scrollState } from 'seitu/web'
37
37
  *
@@ -42,17 +42,18 @@ export interface ScrollStateOptions {
42
42
  * })
43
43
  *
44
44
  * scroll.subscribe(state => {
45
- * console.log(state.top.value) // scrolled away from top
46
- * console.log(state.top.remaining) // px left to scroll back to top
47
- * console.log(state.bottom.value) // reached the bottom
48
- * console.log(state.bottom.remaining) // px left to reach bottom
45
+ * console.log(state.top.value)
46
+ * console.log(state.top.remaining)
47
+ * console.log(state.bottom.value)
48
+ * console.log(state.bottom.remaining)
49
49
  * })
50
50
  *
51
51
  * const state = scroll.get()
52
52
  * console.log(state)
53
53
  * ```
54
54
  *
55
- * ```tsx twoslash
55
+ * @example React
56
+ * ```tsx twoslash title="page.tsx"
56
57
  * import { scrollState } from 'seitu/web'
57
58
  * import { useSubscription } from 'seitu/react'
58
59
  *
@@ -62,8 +63,24 @@ export interface ScrollStateOptions {
62
63
  * })
63
64
  *
64
65
  * function Layout() {
65
- * const state = useSubscription(() => scroll)
66
- * return state.top.value ? 'scrolled' : 'at the top'
66
+ * const { value: state, ref } = useSubscription(scroll)
67
+ *
68
+ * return (
69
+ * <div ref={ref}>
70
+ * <div>
71
+ * {state.top.value ? 'scrolled' : 'at the top'}
72
+ * </div>
73
+ * <div>
74
+ * {state.bottom.value ? 'scrolled' : 'at the bottom'}
75
+ * </div>
76
+ * <div>
77
+ * {state.left.value ? 'scrolled' : 'at the left'}
78
+ * </div>
79
+ * <div>
80
+ * {state.right.value ? 'scrolled' : 'at the right'}
81
+ * </div>
82
+ * </div>
83
+ * )
67
84
  * }
68
85
  * ```
69
86
  */
@@ -1,18 +1,7 @@
1
1
  import type { StandardSchemaV1 } from '@standard-schema/spec';
2
- import type { Readable, Subscribable, Writable } from '../core/index';
3
2
  import type { SessionStorage } from './session-storage';
4
- export interface SessionStorageValue<V> extends Subscribable<V>, Readable<V>, Writable<V> {
5
- }
6
- export interface SessionStorageValueOptionsWithStorage<Storage extends SessionStorage<any>, K extends keyof Storage['~']['output']> {
7
- storage: Storage;
8
- key: K;
9
- }
10
- export interface SessionStorageValueOptionsWithSchema<S extends StandardSchemaV1<unknown>> {
11
- schema: S;
12
- key: string;
13
- defaultValue: StandardSchemaV1.InferOutput<S>;
14
- }
15
- export type SessionStorageValueOptions = SessionStorageValueOptionsWithStorage<SessionStorage<any>, string> | SessionStorageValueOptionsWithSchema<StandardSchemaV1<unknown>>;
3
+ import type { WebStorageValue as SessionStorageValue, WebStorageValueOptionsWithSchema as SessionStorageValueOptionsWithSchema, WebStorageValueOptionsWithStorage as SessionStorageValueOptionsWithStorage } from './web-storage-value';
4
+ export type { SessionStorageValue, SessionStorageValueOptionsWithSchema, SessionStorageValueOptionsWithStorage, };
16
5
  /**
17
6
  * Creates a reactive handle for a single sessionStorage value.
18
7
  *
@@ -22,12 +11,12 @@ export type SessionStorageValueOptions = SessionStorageValueOptionsWithStorage<S
22
11
  * - **Storage mode** (`storage`, `key`): binds to one key of a `createSessionStorage` instance.
23
12
  * Type and default come from that storage.
24
13
  *
25
- * @example
26
- * ```ts twoslash
27
- * import { sessionStorageValue } from 'seitu/web'
14
+ * @example Vanilla
15
+ * ```ts twoslash title="session-storage-value.ts"
16
+ * import { createSessionStorageValue } from 'seitu/web'
28
17
  * import * as z from 'zod'
29
18
  *
30
- * const value = sessionStorageValue({
19
+ * const value = createSessionStorageValue({
31
20
  * key: 'count',
32
21
  * defaultValue: 0,
33
22
  * schema: z.number(),
@@ -38,19 +27,42 @@ export type SessionStorageValueOptions = SessionStorageValueOptionsWithStorage<S
38
27
  * value.subscribe(v => console.log(v))
39
28
  * ```
40
29
  *
41
- * @example
42
- * ```ts twoslash
43
- * import { createSessionStorage, sessionStorageValue } from 'seitu/web'
30
+ * @example Storage mode
31
+ * ```ts twoslash title="session-storage-value.ts"
32
+ * import { createSessionStorage, createSessionStorageValue } from 'seitu/web'
44
33
  * import * as z from 'zod'
45
34
  *
46
35
  * const storage = createSessionStorage({
47
36
  * schemas: { count: z.number(), name: z.string() },
48
37
  * defaultValues: { count: 0, name: '' },
49
38
  * })
50
- * const count = sessionStorageValue({ storage, key: 'count' })
39
+ * const count = createSessionStorageValue({ storage, key: 'count' })
51
40
  * count.set(5)
52
41
  * storage.get().count === 5 // true
53
42
  * ```
43
+ *
44
+ * @example React
45
+ * ```tsx twoslash title="page.tsx"
46
+ * 'use client'
47
+ *
48
+ * import { createSessionStorageValue } from 'seitu/web'
49
+ * import { useSubscription } from 'seitu/react'
50
+ * import * as z from 'zod'
51
+ *
52
+ * export default function Page() {
53
+ * const { value: count } = useSubscription(() => createSessionStorageValue({
54
+ * key: 'count',
55
+ * defaultValue: 0,
56
+ * schema: z.number(),
57
+ * }))
58
+ *
59
+ * return (
60
+ * <div>
61
+ * <span>{count}</span>
62
+ * </div>
63
+ * )
64
+ * }
65
+ * ```
54
66
  */
55
- export declare function sessionStorageValue<Storage extends SessionStorage<any>, K extends keyof Storage['~']['output']>(options: SessionStorageValueOptionsWithStorage<Storage, K>): SessionStorageValue<Storage['~']['output'][K]>;
56
- export declare function sessionStorageValue<S extends StandardSchemaV1<unknown>>(options: SessionStorageValueOptionsWithSchema<S>): SessionStorageValue<StandardSchemaV1.InferOutput<S>>;
67
+ export declare function createSessionStorageValue<Storage extends SessionStorage<any>, K extends keyof Storage['~']['output']>(options: SessionStorageValueOptionsWithStorage<Storage, K>): SessionStorageValue<Storage['~']['output'][K]>;
68
+ export declare function createSessionStorageValue<S extends StandardSchemaV1<unknown>>(options: SessionStorageValueOptionsWithSchema<S>): SessionStorageValue<StandardSchemaV1.InferOutput<S>>;
@@ -1,10 +1,11 @@
1
- import type { Store, StoreOptions, StoreOutput, StoreSchema } from '../core/index';
2
- export type SessionStorage<O extends Record<string, unknown>> = Store<O>;
1
+ import type { SchemaStoreOutput, SchemaStoreSchema } from '../core/index';
2
+ import type { WebStorage as SessionStorage, WebStorageOptions as SessionStorageOptions } from './web-storage';
3
+ export type { SessionStorage, SessionStorageOptions };
3
4
  /**
4
5
  * Creates a reactive handle for a sessionStorage instance.
5
6
  *
6
- * @example
7
- * ```ts twoslash
7
+ * @example Vanilla
8
+ * ```ts twoslash title="session-storage.ts"
8
9
  * import { createSessionStorage } from 'seitu/web'
9
10
  * import * as z from 'zod'
10
11
  *
@@ -16,14 +17,34 @@ export type SessionStorage<O extends Record<string, unknown>> = Store<O>;
16
17
  * defaultValues: { token: null, preferences: { theme: 'light' } },
17
18
  * })
18
19
  *
19
- * sessionStorage.get().token // null
20
- * sessionStorage.get().preferences // { theme: 'light' }
21
- * sessionStorage.set({ token: '123', preferences: { theme: 'dark' } })
22
- * sessionStorage.get().token // '123'
23
- * sessionStorage.get().preferences // { theme: 'dark' }
24
- * sessionStorage.subscribe(value => console.log(value))
25
- * sessionStorage.set({ token: '456', preferences: { theme: 'light' } })
26
- * // { token: '456', preferences: { theme: 'light' } }
20
+ * sessionStorage.get() // { token: null, preferences: { theme: 'light' } }
21
+ * sessionStorage.set({ token: 'abc' })
22
+ * sessionStorage.get() // { token: 'abc', preferences: { theme: 'light' } }
23
+ * sessionStorage.subscribe(console.log)
24
+ * ```
25
+ *
26
+ * @example React
27
+ * ```tsx twoslash title="page.tsx"
28
+ * 'use client'
29
+ *
30
+ * import { createSessionStorage } from 'seitu/web'
31
+ * import { useSubscription } from 'seitu/react'
32
+ * import * as z from 'zod'
33
+ *
34
+ * const sessionStorage = createSessionStorage({
35
+ * schemas: { count: z.number(), name: z.string() },
36
+ * defaultValues: { count: 0, name: '' },
37
+ * })
38
+ *
39
+ * export default function Page() {
40
+ * const { value } = useSubscription(sessionStorage)
41
+ * return (
42
+ * <div>
43
+ * <span>{value.count}</span>
44
+ * <span>{value.name}</span>
45
+ * </div>
46
+ * )
47
+ * }
27
48
  * ```
28
49
  */
29
- export declare function createSessionStorage<S extends StoreSchema>(options: Omit<StoreOptions<S>, 'provider'>): SessionStorage<StoreOutput<S>>;
50
+ export declare function createSessionStorage<S extends SchemaStoreSchema>(options: SessionStorageOptions<S>): SessionStorage<SchemaStoreOutput<S>>;
@@ -0,0 +1,18 @@
1
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
+ import type { Readable, Subscribable, Writable } from '../core/index';
3
+ import type { WebStorage } from './web-storage';
4
+ export interface WebStorageValue<V> extends Subscribable<V>, Readable<V>, Writable<V> {
5
+ }
6
+ export interface WebStorageValueOptionsWithStorage<Storage extends WebStorage<any>, K extends keyof Storage['~']['output']> {
7
+ storage: Storage;
8
+ key: K;
9
+ }
10
+ export interface WebStorageValueOptionsWithSchema<S extends StandardSchemaV1<unknown>> {
11
+ schema: S;
12
+ key: string;
13
+ defaultValue: StandardSchemaV1.InferOutput<S>;
14
+ }
15
+ export declare function createWebStorageValue<Storage extends WebStorage<any>, K extends keyof Storage['~']['output']>(options: WebStorageValueOptionsWithStorage<Storage, K>): WebStorageValue<Storage['~']['output'][K]>;
16
+ export declare function createWebStorageValue<S extends StandardSchemaV1<unknown>>(options: WebStorageValueOptionsWithSchema<S> & {
17
+ kind: 'sessionStorage' | 'localStorage';
18
+ }): WebStorageValue<StandardSchemaV1.InferOutput<S>>;
@@ -0,0 +1,11 @@
1
+ import type { SchemaStore, SchemaStoreOptions, SchemaStoreOutput, SchemaStoreSchema } from '../core/index';
2
+ export interface WebStorageOptions<S extends SchemaStoreSchema> extends Omit<SchemaStoreOptions<S>, 'provider'> {
3
+ }
4
+ export interface WebStorage<O extends Record<string, unknown>> extends SchemaStore<O> {
5
+ '~': {
6
+ kind: 'sessionStorage' | 'localStorage';
7
+ } & SchemaStore<O>['~'];
8
+ }
9
+ export declare function createWebStorage<S extends SchemaStoreSchema>(options: WebStorageOptions<S> & {
10
+ kind: 'sessionStorage' | 'localStorage';
11
+ }): WebStorage<SchemaStoreOutput<S>>;
package/dist/web.js CHANGED
@@ -1,5 +1,91 @@
1
1
  import { n as e, r as t, t as n } from "./core-CuCUF5Aj.js";
2
- function r(e) {
2
+ function r(n) {
3
+ let { kind: r, ...i } = n, a = e({
4
+ ...i,
5
+ provider: {
6
+ get: () => {
7
+ if (typeof window > "u") return i.defaultValues;
8
+ let e = window[r], n = { ...i.defaultValues };
9
+ for (let r in n) {
10
+ let a = t(e.getItem(r)), o = i.schemas[r]["~standard"].validate(a);
11
+ if (o instanceof Promise) throw TypeError("[createWebSchemaStore] Validation schema should not return a Promise.");
12
+ o.issues && console.warn(JSON.stringify(o.issues, null, 2), { cause: o.issues }), n[r] = o.issues ? i.defaultValues[r] : o.value;
13
+ }
14
+ return n;
15
+ },
16
+ set: (e) => {
17
+ if (typeof window > "u") return;
18
+ let t = window[r];
19
+ Object.entries(e).forEach(([e, n]) => {
20
+ t.setItem(e, typeof n == "string" ? n : JSON.stringify(n));
21
+ });
22
+ }
23
+ }
24
+ });
25
+ return typeof window < "u" && window.addEventListener("storage", () => {
26
+ a["~"].notify();
27
+ }), {
28
+ ...a,
29
+ "~": {
30
+ kind: r,
31
+ ...a["~"]
32
+ }
33
+ };
34
+ }
35
+ function i(e) {
36
+ return r({
37
+ kind: "localStorage",
38
+ ...e
39
+ });
40
+ }
41
+ function a(e) {
42
+ let r = "storage" in e ? e.storage["~"].kind : e.kind, i = `${r}Value`;
43
+ if ("schema" in e && e.defaultValue === void 0) throw Error(`[${i}] Default value is required`);
44
+ if (e.key === void 0) throw Error(`[${i}] Key is required`);
45
+ if (!("schema" in e || "storage" in e)) throw Error(`[${i}] Either schema or storage must be provided`);
46
+ let a = ("schema" in e ? e.defaultValue : e.storage.getDefaultValue(e.key)) ?? null, { subscribe: o, notify: s } = n(), c = () => {
47
+ if (typeof window > "u") return a;
48
+ let n = window[r].getItem(e.key);
49
+ if (n === null) return a;
50
+ let i = t(n);
51
+ try {
52
+ if ("schema" in e) {
53
+ let t = e.schema["~standard"].validate(i);
54
+ if (t instanceof Promise) throw TypeError("Validation schema should not return a Promise.");
55
+ return t.issues ? (console.error(JSON.stringify(t.issues, null, 2), { cause: t.issues }), a) : t.value;
56
+ } else return i;
57
+ } catch {
58
+ return a !== void 0 && typeof a != "string" ? a : i;
59
+ }
60
+ };
61
+ return {
62
+ get: c,
63
+ set: (t) => {
64
+ if (typeof window > "u") return;
65
+ let n = window[r], i = typeof t == "function" ? t(c()) : t;
66
+ n.setItem(e.key, typeof i == "string" ? i : JSON.stringify(i)), window.dispatchEvent(new Event("storage")), s();
67
+ },
68
+ subscribe: (t) => {
69
+ let n = o(() => t(c())), r = (n) => {
70
+ n.key === e.key && t(c());
71
+ };
72
+ return typeof window < "u" && window.addEventListener("storage", r), () => {
73
+ n(), typeof window < "u" && window.removeEventListener("storage", r);
74
+ };
75
+ },
76
+ "~": {
77
+ output: null,
78
+ notify: s
79
+ }
80
+ };
81
+ }
82
+ function o(e) {
83
+ return "storage" in e ? a(e) : a({
84
+ ...e,
85
+ kind: "localStorage"
86
+ });
87
+ }
88
+ function s(e) {
3
89
  let { subscribe: t, notify: r } = n(), i = () => {
4
90
  if (typeof window > "u") return e.defaultMatches ?? !1;
5
91
  try {
@@ -23,109 +109,56 @@ function r(e) {
23
109
  }
24
110
  };
25
111
  }
26
- var i = {
112
+ var c = {
27
113
  value: !1,
28
114
  remaining: 0
29
115
  };
30
- function a(e) {
31
- let { direction: t = "both", threshold: r = 0 } = e, { subscribe: a, notify: o } = n(), s = () => {
32
- let n = e.element, a = (e, t) => ({
116
+ function l(e) {
117
+ let { direction: t = "both", threshold: r = 0 } = e, { subscribe: i, notify: a } = n(), o = () => {
118
+ let n = e.element, i = (e, t) => ({
33
119
  value: e,
34
120
  remaining: Math.max(0, t)
35
121
  });
36
122
  if (!n) return {
37
- top: i,
38
- bottom: i,
39
- left: i,
40
- right: i
123
+ top: c,
124
+ bottom: c,
125
+ left: c,
126
+ right: c
41
127
  };
42
- let o = n.scrollTop, s = n.scrollHeight - n.scrollTop - n.clientHeight, c = n.scrollLeft, l = n.scrollWidth - n.scrollLeft - n.clientWidth;
128
+ let a = n.scrollTop, o = n.scrollHeight - n.scrollTop - n.clientHeight, s = n.scrollLeft, l = n.scrollWidth - n.scrollLeft - n.clientWidth;
43
129
  return {
44
- top: t === "horizontal" ? i : a(o > r, o),
45
- bottom: t === "horizontal" ? i : a(s <= r, s),
46
- left: t === "vertical" ? i : a(c > r, c),
47
- right: t === "vertical" ? i : a(l <= r, l)
130
+ top: t === "horizontal" ? c : i(a > r, a),
131
+ bottom: t === "horizontal" ? c : i(o <= r, o),
132
+ left: t === "vertical" ? c : i(s > r, s),
133
+ right: t === "vertical" ? c : i(l <= r, l)
48
134
  };
49
135
  };
50
136
  return {
51
- get: s,
137
+ get: o,
52
138
  subscribe: (t) => {
53
139
  let n = e.element;
54
- if (!n) return t(s()), () => {};
55
- let r = a(() => t(s())), i = () => t(s());
56
- return n.addEventListener("scroll", i, { passive: !0 }), () => {
57
- r(), n.removeEventListener("scroll", i);
140
+ if (!n) return t(o()), () => {};
141
+ let r = i(() => t(o())), a = () => t(o());
142
+ return n.addEventListener("scroll", a, { passive: !0 }), () => {
143
+ r(), n.removeEventListener("scroll", a);
58
144
  };
59
145
  },
60
146
  "~": {
61
147
  output: null,
62
- notify: o
148
+ notify: a
63
149
  }
64
150
  };
65
151
  }
66
- function o(n) {
67
- let r = e({
68
- ...n,
69
- provider: {
70
- get: () => {
71
- if (typeof window > "u") return n.defaultValues;
72
- let e = { ...n.defaultValues };
73
- for (let r in e) {
74
- let i = t(window.sessionStorage.getItem(r)), a = n.schemas[r]["~standard"].validate(i);
75
- if (a instanceof Promise) throw TypeError("[createSessionStorage] Validation schema should not return a Promise.");
76
- a.issues && console.warn(JSON.stringify(a.issues, null, 2), { cause: a.issues }), e[r] = a.issues ? n.defaultValues[r] : a.value;
77
- }
78
- return e;
79
- },
80
- set: (e) => {
81
- typeof window > "u" || Object.entries(e).forEach(([e, t]) => {
82
- window.sessionStorage.setItem(e, typeof t == "string" ? t : JSON.stringify(t));
83
- });
84
- }
85
- }
152
+ function u(e) {
153
+ return r({
154
+ kind: "sessionStorage",
155
+ ...e
86
156
  });
87
- return typeof window < "u" && window.addEventListener("storage", () => {
88
- r["~"].notify();
89
- }), r;
90
157
  }
91
- function s(e) {
92
- if ("schema" in e && e.defaultValue === void 0) throw Error("[sessionStorageValue] Default value is required");
93
- if (e.key === void 0) throw Error("[sessionStorageValue] Key is required");
94
- if (!("schema" in e || "storage" in e)) throw Error("[sessionStorageValue] Either schema or storage must be provided");
95
- let r = ("schema" in e ? e.defaultValue : e.storage.getDefaultValue(e.key)) ?? null, { subscribe: i, notify: a } = n(), o = () => {
96
- if (typeof window > "u") return r;
97
- let n = window.sessionStorage.getItem(e.key);
98
- if (n === null) return r;
99
- let i = t(n);
100
- try {
101
- if ("schema" in e) {
102
- let t = e.schema["~standard"].validate(i);
103
- if (t instanceof Promise) throw TypeError("Validation schema should not return a Promise.");
104
- return t.issues ? (console.error(JSON.stringify(t.issues, null, 2), { cause: t.issues }), r) : t.value;
105
- } else return i;
106
- } catch {
107
- return r !== void 0 && typeof r != "string" ? r : i;
108
- }
109
- };
110
- return {
111
- get: o,
112
- set: (t) => {
113
- if (typeof window > "u") return;
114
- let n = typeof t == "function" ? t(o()) : t;
115
- window.sessionStorage.setItem(e.key, typeof n == "string" ? n : JSON.stringify(n)), window.dispatchEvent(new Event("storage")), a();
116
- },
117
- subscribe: (t) => {
118
- let n = i(() => t(o())), r = (n) => {
119
- n.key === e.key && t(o());
120
- };
121
- return typeof window < "u" && window.addEventListener("storage", r), () => {
122
- n(), typeof window < "u" && window.removeEventListener("storage", r);
123
- };
124
- },
125
- "~": {
126
- output: null,
127
- notify: a
128
- }
129
- };
158
+ function d(e) {
159
+ return "storage" in e ? a(e) : a({
160
+ ...e,
161
+ kind: "sessionStorage"
162
+ });
130
163
  }
131
- export { o as createSessionStorage, r as mediaQuery, a as scrollState, s as sessionStorageValue };
164
+ export { i as createLocalStorage, o as createLocalStorageValue, u as createSessionStorage, d as createSessionStorageValue, s as mediaQuery, l as scrollState };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "seitu",
3
3
  "displayName": "Seitu",
4
4
  "type": "module",
5
- "version": "0.1.0",
5
+ "version": "0.2.0",
6
6
  "private": false,
7
7
  "author": "Valerii Strilets",
8
8
  "license": "MIT",
@@ -1,107 +0,0 @@
1
- import type { StandardSchemaV1 } from '@standard-schema/spec';
2
- import type { Simplify } from '../utils';
3
- import type { Readable, Subscribable, Writable } from './index';
4
- export type StoreSchema = Record<string, StandardSchemaV1<unknown, unknown>>;
5
- export type StoreOutput<S extends StoreSchema> = Simplify<{
6
- [K in keyof S]: StandardSchemaV1.InferOutput<S[K]>;
7
- }>;
8
- export interface Store<O extends Record<string, unknown>> extends Subscribable<O>, Readable<O>, Writable<O> {
9
- getDefaultValue: <K extends keyof O>(key: K) => O[K];
10
- }
11
- export interface StoreOptions<S extends Record<string, StandardSchemaV1>> {
12
- /**
13
- * Schemas for each storage key (one validator per key).
14
- * Use this when each key has its own type; for a single compound schema use a wrapper with one key.
15
- *
16
- * @example
17
- * ```ts
18
- * const store = createStore({
19
- * schemas: {
20
- * token: z.string().nullable(),
21
- * preferences: z.object({ theme: z.enum(['light', 'dark']) }),
22
- * },
23
- * defaultValues: { token: null, preferences: { theme: 'light' } },
24
- * provider: {
25
- * get: () => ({
26
- * token: window.localStorage.getItem('token'),
27
- * preferences: window.localStorage.getItem('preferences'),
28
- * }),
29
- * set: (value) => {
30
- * window.localStorage.setItem('token', value.token ?? '')
31
- * window.localStorage.setItem('preferences', JSON.stringify(value.preferences))
32
- * },
33
- * },
34
- * })
35
- * ```
36
- */
37
- schemas: S;
38
- /**
39
- * The default values to use for the storage.
40
- *
41
- * @example
42
- * ```ts
43
- * const store = createStore({
44
- * schemas: {
45
- * token: z.string().nullable(),
46
- * preferences: z.object({ theme: z.enum(['light', 'dark']) }),
47
- * },
48
- * defaultValues: {
49
- * token: null,
50
- * preferences: { theme: 'light' },
51
- * },
52
- * provider: {
53
- * get: () => ({
54
- * token: window.localStorage.getItem('token'),
55
- * preferences: window.localStorage.getItem('preferences'),
56
- * }),
57
- * set: (value) => {
58
- * window.localStorage.setItem('token', value.token ?? '')
59
- * window.localStorage.setItem('preferences', JSON.stringify(value.preferences))
60
- * },
61
- * },
62
- * })
63
- * ```
64
- */
65
- defaultValues: StoreOutput<S>;
66
- provider: {
67
- get: () => StoreOutput<S>;
68
- set: (value: StoreOutput<S>) => void;
69
- };
70
- }
71
- /**
72
- * Creates a reactive handle for a storage instance.
73
- *
74
- * @example
75
- * ```ts twoslash
76
- * import { createStore } from 'seitu'
77
- * import * as z from 'zod'
78
- *
79
- * const store = createStore({
80
- * schemas: {
81
- * token: z.string().nullable(),
82
- * preferences: z.object({ theme: z.enum(['light', 'dark']) }),
83
- * },
84
- * defaultValues: { token: null, preferences: { theme: 'light' } },
85
- * provider: {
86
- * get: () => ({
87
- * token: window.localStorage.getItem('token'),
88
- * preferences: JSON.parse(window.localStorage.getItem('preferences') ?? '{"theme":"light"}'),
89
- * }),
90
- * set: (value) => {
91
- * window.localStorage.setItem('token', value.token ?? '')
92
- * window.localStorage.setItem('preferences', JSON.stringify(value.preferences))
93
- * },
94
- * },
95
- * })
96
- *
97
- * store.get().token // null
98
- * store.get().preferences // { theme: 'light' }
99
- * store.set({ token: '123', preferences: { theme: 'dark' } })
100
- * store.get().token // '123'
101
- * store.get().preferences // { theme: 'dark' }
102
- * store.subscribe(value => console.log(value))
103
- * store.set({ token: '456', preferences: { theme: 'light' } })
104
- * // { token: '456', preferences: { theme: 'light' } }
105
- * ```
106
- */
107
- export declare function createStore<S extends Record<string, StandardSchemaV1>>(options: StoreOptions<S>): Store<StoreOutput<S>>;