seitu 0.14.1 → 0.15.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/core.d.mts +1 -1
- package/dist/core.mjs +1 -1
- package/dist/{index-Dzu3WPwr.d.mts → index-DwN8Up-P.d.mts} +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/utils.d.mts +1 -24
- package/dist/utils.mjs +1 -1
- package/dist/validation-BRHMV4Ky.mjs +9 -0
- package/dist/vue.d.mts +1 -1
- package/dist/web.d.mts +181 -72
- package/dist/web.mjs +227 -3
- package/package.json +14 -11
- package/skills/Subscription-react/SKILL.md +34 -0
- package/skills/createComputed/SKILL.md +50 -0
- package/skills/createDebounced/SKILL.md +37 -0
- package/skills/createDebouncedFn/SKILL.md +39 -0
- package/skills/createIndexedDbStorage/SKILL.md +63 -0
- package/skills/createIsOnline/SKILL.md +30 -0
- package/skills/createMediaQuery/SKILL.md +43 -0
- package/skills/createReadableSubscription/SKILL.md +44 -0
- package/skills/createSchemaStore/SKILL.md +42 -0
- package/skills/createScrollState/SKILL.md +63 -0
- package/skills/createStore/SKILL.md +48 -0
- package/skills/createSubscription/SKILL.md +47 -0
- package/skills/createThrottled/SKILL.md +37 -0
- package/skills/createThrottledFn/SKILL.md +40 -0
- package/skills/createWebStorage/SKILL.md +64 -0
- package/skills/createWebStorageValue/SKILL.md +60 -0
- package/skills/seitu-overview/SKILL.md +148 -0
- package/skills/useSubscription-react/SKILL.md +71 -0
- package/skills/useSubscription-vue/SKILL.md +75 -0
- package/dist/validation-B8XxyKZ0.mjs +0 -32
- /package/dist/{core-CwV8yAwb.mjs → core-Vbl0ORaj.mjs} +0 -0
- /package/dist/{validate-IWiN_jFZ.d.mts → validate-BYHKInzu.d.mts} +0 -0
package/dist/core.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { S as createSubscription, _ as Readable, a as Store, b as Writable, c as SchemaStoreOptions, d as createDebouncedFn, f as Debounced, g as Clearable, h as createComputed, i as createThrottled, l as createSchemaStore, m as Computed, n as createThrottledFn, o as createStore, p as createDebounced, r as Throttled, s as SchemaStore, t as ThrottledFn, u as DebouncedFn, v as Subscribable, x as createReadableSubscription, y as SubscribeOptions } from "./index-
|
|
1
|
+
import { S as createSubscription, _ as Readable, a as Store, b as Writable, c as SchemaStoreOptions, d as createDebouncedFn, f as Debounced, g as Clearable, h as createComputed, i as createThrottled, l as createSchemaStore, m as Computed, n as createThrottledFn, o as createStore, p as createDebounced, r as Throttled, s as SchemaStore, t as ThrottledFn, u as DebouncedFn, v as Subscribable, x as createReadableSubscription, y as SubscribeOptions } from "./index-DwN8Up-P.mjs";
|
|
2
2
|
export { Clearable, Computed, Debounced, DebouncedFn, Readable, SchemaStore, SchemaStoreOptions, Store, Subscribable, SubscribeOptions, Throttled, ThrottledFn, Writable, createComputed, createDebounced, createDebouncedFn, createReadableSubscription, createSchemaStore, createStore, createSubscription, createThrottled, createThrottledFn };
|
package/dist/core.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { c as createDebounced, d as createSubscription, i as createSchemaStore, l as createComputed, n as createThrottled, r as createStore, s as createDebouncedFn, t as createThrottledFn, u as createReadableSubscription } from "./core-
|
|
1
|
+
import { c as createDebounced, d as createSubscription, i as createSchemaStore, l as createComputed, n as createThrottled, r as createStore, s as createDebouncedFn, t as createThrottledFn, u as createReadableSubscription } from "./core-Vbl0ORaj.mjs";
|
|
2
2
|
export { createComputed, createDebounced, createDebouncedFn, createReadableSubscription, createSchemaStore, createStore, createSubscription, createThrottled, createThrottledFn };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as StandardSchemaV1, r as ValidationSchemaOutput, t as ValidationSchemaErrorProps } from "./validate-
|
|
1
|
+
import { i as StandardSchemaV1, r as ValidationSchemaOutput, t as ValidationSchemaErrorProps } from "./validate-BYHKInzu.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/core/subscription.d.ts
|
|
4
4
|
interface SubscribeOptions {
|
package/dist/react.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as Readable, v as Subscribable } from "./index-
|
|
1
|
+
import { _ as Readable, v as Subscribable } from "./index-DwN8Up-P.mjs";
|
|
2
2
|
import * as React$1 from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/react/hooks.d.ts
|
|
@@ -159,6 +159,6 @@ declare function Subscription<S extends Subscribable<any> & Readable<any>, R = S
|
|
|
159
159
|
value,
|
|
160
160
|
selector,
|
|
161
161
|
children
|
|
162
|
-
}: SubscriptionProps<S, R>):
|
|
162
|
+
}: SubscriptionProps<S, R>): import("react").ReactNode;
|
|
163
163
|
//#endregion
|
|
164
164
|
export { Subscription, SubscriptionProps, UseSubscriptionOptions, useSubscription };
|
package/dist/utils.d.mts
CHANGED
|
@@ -1,29 +1,6 @@
|
|
|
1
|
-
import { t as ValidationSchemaErrorProps } from "./validate-
|
|
1
|
+
import { t as ValidationSchemaErrorProps } from "./validate-BYHKInzu.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/utils/validation.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Repair broken web storage value object if value was object but not all keys are in the default value object.
|
|
6
|
-
*
|
|
7
|
-
* @returns A new object with the existing keys in the value object that are not in the default value object.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* import { createWebStorageValue } from 'seitu/web'
|
|
12
|
-
* import { repairValueObjectWithDefault } from 'seitu/utils'
|
|
13
|
-
* import * as z from 'zod'
|
|
14
|
-
*
|
|
15
|
-
* const value = createWebStorageValue({
|
|
16
|
-
* type: 'localStorage',
|
|
17
|
-
* schema: z.object({ a: z.number(), b: z.string() }),
|
|
18
|
-
* key: 'storage-key',
|
|
19
|
-
* defaultValue: { a: 0, b: 'default' },
|
|
20
|
-
* onValidationError: repairValueObjectWithDefault,
|
|
21
|
-
* })
|
|
22
|
-
* value.get() // { a: 0, b: 'default' }
|
|
23
|
-
* window.localStorage.setItem('storage-key', JSON.stringify({ a: 1 }))
|
|
24
|
-
* value.get() // { a: 1, b: 'default' }
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
4
|
declare function repairValueObjectWithDefault<O extends Record<string, unknown>>(props: ValidationSchemaErrorProps<O>): O & {
|
|
28
5
|
[k: string]: any;
|
|
29
6
|
};
|
package/dist/utils.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as repairValueObjectWithDefault } from "./validation-
|
|
1
|
+
import { t as repairValueObjectWithDefault } from "./validation-BRHMV4Ky.mjs";
|
|
2
2
|
export { repairValueObjectWithDefault };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/utils/validation.ts
|
|
2
|
+
function repairValueObjectWithDefault(props) {
|
|
3
|
+
return {
|
|
4
|
+
...props.defaultValue,
|
|
5
|
+
...typeof props.value === "object" && props.value !== null ? Object.fromEntries(Object.entries(props.value).filter(([key, val]) => key in props.defaultValue && typeof val === typeof props.defaultValue[key])) : {}
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
export { repairValueObjectWithDefault as t };
|
package/dist/vue.d.mts
CHANGED
package/dist/web.d.mts
CHANGED
|
@@ -1,6 +1,184 @@
|
|
|
1
|
-
import { _ as Readable, b as Writable, g as Clearable, v as Subscribable } from "./index-
|
|
2
|
-
import { i as StandardSchemaV1, n as ValidationSchemaObjectErrorProps, t as ValidationSchemaErrorProps } from "./validate-
|
|
1
|
+
import { _ as Readable, b as Writable, g as Clearable, v as Subscribable } from "./index-DwN8Up-P.mjs";
|
|
2
|
+
import { i as StandardSchemaV1, n as ValidationSchemaObjectErrorProps, t as ValidationSchemaErrorProps } from "./validate-BYHKInzu.mjs";
|
|
3
3
|
|
|
4
|
+
//#region src/utils.d.ts
|
|
5
|
+
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/web/web-storage.d.ts
|
|
8
|
+
type WebStorageInput = Record<string, StandardSchemaV1<unknown, unknown>>;
|
|
9
|
+
type WebStorageOutput<S extends WebStorageInput> = Simplify<{ [K in keyof S]: StandardSchemaV1.InferOutput<S[K]> }>;
|
|
10
|
+
interface WebStorageOptions<S extends WebStorageInput> {
|
|
11
|
+
schemas: S;
|
|
12
|
+
defaultValues: WebStorageOutput<S>;
|
|
13
|
+
type: 'localStorage' | 'sessionStorage';
|
|
14
|
+
keyTransform?: (key: keyof S) => string;
|
|
15
|
+
onValidationError?: (props: ValidationSchemaObjectErrorProps<WebStorageOutput<S>>) => void | StandardSchemaV1.InferOutput<S[keyof S]>;
|
|
16
|
+
}
|
|
17
|
+
interface WebStorage<O extends Record<string, unknown>> extends Subscribable<O>, Readable<O>, Writable<Partial<O>, O>, Clearable {
|
|
18
|
+
'~': {
|
|
19
|
+
getDefaultValue: <K extends keyof O>(key: K) => O[K];
|
|
20
|
+
type: 'localStorage' | 'sessionStorage';
|
|
21
|
+
} & Subscribable<O>['~'];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a reactive handle for a localStorage or sessionStorage instance.
|
|
25
|
+
*
|
|
26
|
+
* @example Vanilla
|
|
27
|
+
* ```ts twoslash title="session-storage.ts"
|
|
28
|
+
* import { createWebStorage } from 'seitu/web'
|
|
29
|
+
* import * as z from 'zod'
|
|
30
|
+
*
|
|
31
|
+
* const sessionStorage = createWebStorage({
|
|
32
|
+
* type: 'sessionStorage',
|
|
33
|
+
* schemas: {
|
|
34
|
+
* token: z.string().nullable(),
|
|
35
|
+
* preferences: z.object({ theme: z.enum(['light', 'dark']) }),
|
|
36
|
+
* },
|
|
37
|
+
* defaultValues: { token: null, preferences: { theme: 'light' } },
|
|
38
|
+
* })
|
|
39
|
+
*
|
|
40
|
+
* sessionStorage.get() // { token: null, preferences: { theme: 'light' } }
|
|
41
|
+
* sessionStorage.set({ token: 'abc' })
|
|
42
|
+
* sessionStorage.get() // { token: 'abc', preferences: { theme: 'light' } }
|
|
43
|
+
* sessionStorage.subscribe(console.log)
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example React
|
|
47
|
+
* ```tsx twoslash title="page.tsx"
|
|
48
|
+
* 'use client'
|
|
49
|
+
*
|
|
50
|
+
* import { createWebStorage } from 'seitu/web'
|
|
51
|
+
* import { useSubscription } from 'seitu/react'
|
|
52
|
+
* import * as z from 'zod'
|
|
53
|
+
*
|
|
54
|
+
* const sessionStorage = createWebStorage({
|
|
55
|
+
* type: 'sessionStorage',
|
|
56
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
57
|
+
* defaultValues: { count: 0, name: '' },
|
|
58
|
+
* })
|
|
59
|
+
*
|
|
60
|
+
* export default function Page() {
|
|
61
|
+
* const value = useSubscription(sessionStorage)
|
|
62
|
+
* return (
|
|
63
|
+
* <div>
|
|
64
|
+
* <span>{value.count}</span>
|
|
65
|
+
* <span>{value.name}</span>
|
|
66
|
+
* </div>
|
|
67
|
+
* )
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
declare function createWebStorage<S extends WebStorageInput>(options: WebStorageOptions<S>): WebStorage<WebStorageOutput<S>>;
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/web/indexed-db-storage.d.ts
|
|
74
|
+
interface IndexedDbStorageOptions<S extends WebStorageInput> {
|
|
75
|
+
schemas: S;
|
|
76
|
+
defaultValues: WebStorageOutput<S>;
|
|
77
|
+
/**
|
|
78
|
+
* Name of the IndexedDB database to open.
|
|
79
|
+
*/
|
|
80
|
+
databaseName: string;
|
|
81
|
+
/**
|
|
82
|
+
* Name of the object store used to persist values.
|
|
83
|
+
*
|
|
84
|
+
* @default 'seitu'
|
|
85
|
+
*/
|
|
86
|
+
storeName?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Version of the IndexedDB database. Leave unset to let the storage manage
|
|
89
|
+
* the version automatically (it bumps the version when it needs to create
|
|
90
|
+
* its object store).
|
|
91
|
+
*/
|
|
92
|
+
version?: number;
|
|
93
|
+
keyTransform?: (key: keyof S) => string;
|
|
94
|
+
onValidationError?: (props: ValidationSchemaObjectErrorProps<WebStorageOutput<S>>) => void | StandardSchemaV1.InferOutput<S[keyof S]>;
|
|
95
|
+
}
|
|
96
|
+
interface IndexedDbStorage<O extends Record<string, unknown>> extends Subscribable<O>, Readable<O>, Writable<Partial<O>, O>, Clearable {
|
|
97
|
+
/**
|
|
98
|
+
* Resolves once the initial value has been read from IndexedDB.
|
|
99
|
+
*
|
|
100
|
+
* Because IndexedDB is asynchronous, `get()` returns the in-memory cache
|
|
101
|
+
* (the default values until hydration completes). Await `ready` to read the
|
|
102
|
+
* persisted value. This promise never rejects — read failures fall back to
|
|
103
|
+
* the default values and are logged.
|
|
104
|
+
*/
|
|
105
|
+
'ready': Promise<O>;
|
|
106
|
+
/**
|
|
107
|
+
* Updates the in-memory cache synchronously (so `get()` reflects the change
|
|
108
|
+
* immediately) and persists to IndexedDB asynchronously. The returned promise
|
|
109
|
+
* resolves once persistence settles and never rejects.
|
|
110
|
+
*/
|
|
111
|
+
'set': (value: Partial<O> | ((prev: O) => Partial<O>)) => Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Resets the cache to the default values synchronously and removes the managed
|
|
114
|
+
* keys from IndexedDB asynchronously. The returned promise resolves once
|
|
115
|
+
* persistence settles and never rejects.
|
|
116
|
+
*/
|
|
117
|
+
'clear': () => Promise<void>;
|
|
118
|
+
'~': {
|
|
119
|
+
getDefaultValue: <K extends keyof O>(key: K) => O[K];
|
|
120
|
+
databaseName: string;
|
|
121
|
+
storeName: string;
|
|
122
|
+
} & Subscribable<O>['~'];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Creates a reactive handle for an IndexedDB object store.
|
|
126
|
+
*
|
|
127
|
+
* IndexedDB is asynchronous, so the handle keeps an in-memory cache that
|
|
128
|
+
* `get()` reads synchronously. The cache is hydrated from IndexedDB on
|
|
129
|
+
* creation (await `ready` to know when), and changes made via `set()` /
|
|
130
|
+
* `clear()` are persisted asynchronously. When `BroadcastChannel` is
|
|
131
|
+
* available, changes are synced across tabs while there is at least one
|
|
132
|
+
* subscriber.
|
|
133
|
+
*
|
|
134
|
+
* @example Vanilla
|
|
135
|
+
* ```ts twoslash title="settings-storage.ts"
|
|
136
|
+
* import { createIndexedDbStorage } from 'seitu/web'
|
|
137
|
+
* import * as z from 'zod'
|
|
138
|
+
*
|
|
139
|
+
* const settingsStorage = createIndexedDbStorage({
|
|
140
|
+
* databaseName: 'app',
|
|
141
|
+
* schemas: {
|
|
142
|
+
* token: z.string().nullable(),
|
|
143
|
+
* preferences: z.object({ theme: z.enum(['light', 'dark']) }),
|
|
144
|
+
* },
|
|
145
|
+
* defaultValues: { token: null, preferences: { theme: 'light' } },
|
|
146
|
+
* })
|
|
147
|
+
*
|
|
148
|
+
* settingsStorage.get() // { token: null, preferences: { theme: 'light' } }
|
|
149
|
+
* await settingsStorage.ready // value hydrated from IndexedDB
|
|
150
|
+
* await settingsStorage.set({ token: 'abc' })
|
|
151
|
+
* settingsStorage.get() // { token: 'abc', preferences: { theme: 'light' } }
|
|
152
|
+
* settingsStorage.subscribe(console.log)
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @example React
|
|
156
|
+
* ```tsx twoslash title="page.tsx"
|
|
157
|
+
* 'use client'
|
|
158
|
+
*
|
|
159
|
+
* import { createIndexedDbStorage } from 'seitu/web'
|
|
160
|
+
* import { useSubscription } from 'seitu/react'
|
|
161
|
+
* import * as z from 'zod'
|
|
162
|
+
*
|
|
163
|
+
* const settingsStorage = createIndexedDbStorage({
|
|
164
|
+
* databaseName: 'app',
|
|
165
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
166
|
+
* defaultValues: { count: 0, name: '' },
|
|
167
|
+
* })
|
|
168
|
+
*
|
|
169
|
+
* export default function Page() {
|
|
170
|
+
* const value = useSubscription(settingsStorage)
|
|
171
|
+
* return (
|
|
172
|
+
* <div>
|
|
173
|
+
* <span>{value.count}</span>
|
|
174
|
+
* <span>{value.name}</span>
|
|
175
|
+
* </div>
|
|
176
|
+
* )
|
|
177
|
+
* }
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
declare function createIndexedDbStorage<S extends WebStorageInput>(options: IndexedDbStorageOptions<S>): IndexedDbStorage<WebStorageOutput<S>>;
|
|
181
|
+
//#endregion
|
|
4
182
|
//#region src/web/is-online.d.ts
|
|
5
183
|
interface IsOnline extends Subscribable<boolean>, Readable<boolean> {}
|
|
6
184
|
/**
|
|
@@ -221,75 +399,6 @@ interface ScrollStateOptions {
|
|
|
221
399
|
*/
|
|
222
400
|
declare function createScrollState(options: ScrollStateOptions): ScrollState;
|
|
223
401
|
//#endregion
|
|
224
|
-
//#region src/utils.d.ts
|
|
225
|
-
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
|
|
226
|
-
//#endregion
|
|
227
|
-
//#region src/web/web-storage.d.ts
|
|
228
|
-
type WebStorageInput = Record<string, StandardSchemaV1<unknown, unknown>>;
|
|
229
|
-
type WebStorageOutput<S extends WebStorageInput> = Simplify<{ [K in keyof S]: StandardSchemaV1.InferOutput<S[K]> }>;
|
|
230
|
-
interface WebStorageOptions<S extends WebStorageInput> {
|
|
231
|
-
schemas: S;
|
|
232
|
-
defaultValues: WebStorageOutput<S>;
|
|
233
|
-
type: 'localStorage' | 'sessionStorage';
|
|
234
|
-
keyTransform?: (key: keyof S) => string;
|
|
235
|
-
onValidationError?: (props: ValidationSchemaObjectErrorProps<WebStorageOutput<S>>) => void | StandardSchemaV1.InferOutput<S[keyof S]>;
|
|
236
|
-
}
|
|
237
|
-
interface WebStorage<O extends Record<string, unknown>> extends Subscribable<O>, Readable<O>, Writable<Partial<O>, O>, Clearable {
|
|
238
|
-
'~': {
|
|
239
|
-
getDefaultValue: <K extends keyof O>(key: K) => O[K];
|
|
240
|
-
type: 'localStorage' | 'sessionStorage';
|
|
241
|
-
} & Subscribable<O>['~'];
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Creates a reactive handle for a localStorage or sessionStorage instance.
|
|
245
|
-
*
|
|
246
|
-
* @example Vanilla
|
|
247
|
-
* ```ts twoslash title="session-storage.ts"
|
|
248
|
-
* import { createWebStorage } from 'seitu/web'
|
|
249
|
-
* import * as z from 'zod'
|
|
250
|
-
*
|
|
251
|
-
* const sessionStorage = createWebStorage({
|
|
252
|
-
* type: 'sessionStorage',
|
|
253
|
-
* schemas: {
|
|
254
|
-
* token: z.string().nullable(),
|
|
255
|
-
* preferences: z.object({ theme: z.enum(['light', 'dark']) }),
|
|
256
|
-
* },
|
|
257
|
-
* defaultValues: { token: null, preferences: { theme: 'light' } },
|
|
258
|
-
* })
|
|
259
|
-
*
|
|
260
|
-
* sessionStorage.get() // { token: null, preferences: { theme: 'light' } }
|
|
261
|
-
* sessionStorage.set({ token: 'abc' })
|
|
262
|
-
* sessionStorage.get() // { token: 'abc', preferences: { theme: 'light' } }
|
|
263
|
-
* sessionStorage.subscribe(console.log)
|
|
264
|
-
* ```
|
|
265
|
-
*
|
|
266
|
-
* @example React
|
|
267
|
-
* ```tsx twoslash title="page.tsx"
|
|
268
|
-
* 'use client'
|
|
269
|
-
*
|
|
270
|
-
* import { createWebStorage } from 'seitu/web'
|
|
271
|
-
* import { useSubscription } from 'seitu/react'
|
|
272
|
-
* import * as z from 'zod'
|
|
273
|
-
*
|
|
274
|
-
* const sessionStorage = createWebStorage({
|
|
275
|
-
* type: 'sessionStorage',
|
|
276
|
-
* schemas: { count: z.number(), name: z.string() },
|
|
277
|
-
* defaultValues: { count: 0, name: '' },
|
|
278
|
-
* })
|
|
279
|
-
*
|
|
280
|
-
* export default function Page() {
|
|
281
|
-
* const value = useSubscription(sessionStorage)
|
|
282
|
-
* return (
|
|
283
|
-
* <div>
|
|
284
|
-
* <span>{value.count}</span>
|
|
285
|
-
* <span>{value.name}</span>
|
|
286
|
-
* </div>
|
|
287
|
-
* )
|
|
288
|
-
* }
|
|
289
|
-
* ```
|
|
290
|
-
*/
|
|
291
|
-
declare function createWebStorage<S extends WebStorageInput>(options: WebStorageOptions<S>): WebStorage<WebStorageOutput<S>>;
|
|
292
|
-
//#endregion
|
|
293
402
|
//#region src/web/web-storage-value.d.ts
|
|
294
403
|
interface WebStorageValue<V> extends Subscribable<V>, Readable<V>, Writable<V>, Clearable {}
|
|
295
404
|
interface WebStorageValueOptionsWithStorage<Storage extends WebStorage<any>, K extends keyof Storage['~']['output']> {
|
|
@@ -380,4 +489,4 @@ interface WebStorageValueOptionsWithSchema<S extends StandardSchemaV1<unknown>>
|
|
|
380
489
|
declare function createWebStorageValue<Storage extends WebStorage<any>, K extends keyof Storage['~']['output']>(options: WebStorageValueOptionsWithStorage<Storage, K>): WebStorageValue<Storage['~']['output'][K]>;
|
|
381
490
|
declare function createWebStorageValue<S extends StandardSchemaV1<unknown>>(options: WebStorageValueOptionsWithSchema<S>): WebStorageValue<StandardSchemaV1.InferOutput<S>>;
|
|
382
491
|
//#endregion
|
|
383
|
-
export { IsOnline, MediaQuery, MediaQueryOptions, MediaQueryType, ScrollDirection, ScrollState, ScrollStateEdge, ScrollStateOptions, ScrollStateThreshold, ScrollStateValue, WebStorage, WebStorageInput, WebStorageOptions, WebStorageOutput, WebStorageValue, WebStorageValueOptionsWithSchema, WebStorageValueOptionsWithStorage, createIsOnline, createMediaQuery, createScrollState, createWebStorage, createWebStorageValue };
|
|
492
|
+
export { IndexedDbStorage, IndexedDbStorageOptions, IsOnline, MediaQuery, MediaQueryOptions, MediaQueryType, ScrollDirection, ScrollState, ScrollStateEdge, ScrollStateOptions, ScrollStateThreshold, ScrollStateValue, WebStorage, WebStorageInput, WebStorageOptions, WebStorageOutput, WebStorageValue, WebStorageValueOptionsWithSchema, WebStorageValueOptionsWithStorage, createIndexedDbStorage, createIsOnline, createMediaQuery, createScrollState, createWebStorage, createWebStorageValue };
|
package/dist/web.mjs
CHANGED
|
@@ -1,5 +1,229 @@
|
|
|
1
|
-
import { a as validateSchema, d as createSubscription, o as tryParseJson, u as createReadableSubscription } from "./core-
|
|
2
|
-
import { t as repairValueObjectWithDefault } from "./validation-
|
|
1
|
+
import { a as validateSchema, d as createSubscription, o as tryParseJson, u as createReadableSubscription } from "./core-Vbl0ORaj.mjs";
|
|
2
|
+
import { t as repairValueObjectWithDefault } from "./validation-BRHMV4Ky.mjs";
|
|
3
|
+
import { deepEqual } from "fast-equals";
|
|
4
|
+
//#region src/web/indexed-db-storage.ts
|
|
5
|
+
function requestToPromise(request) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
request.onsuccess = () => resolve(request.result);
|
|
8
|
+
request.onerror = () => reject(request.error);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function transactionDone(transaction) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
transaction.oncomplete = () => resolve();
|
|
14
|
+
transaction.onerror = () => reject(transaction.error);
|
|
15
|
+
transaction.onabort = () => reject(transaction.error);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function openRequest(name, version, storeName) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const request = version === void 0 ? indexedDB.open(name) : indexedDB.open(name, version);
|
|
21
|
+
request.onupgradeneeded = () => {
|
|
22
|
+
const db = request.result;
|
|
23
|
+
if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName);
|
|
24
|
+
};
|
|
25
|
+
request.onsuccess = () => resolve(request.result);
|
|
26
|
+
request.onerror = () => reject(request.error);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function openDatabase(name, storeName, version) {
|
|
30
|
+
const db = await openRequest(name, version, storeName);
|
|
31
|
+
if (db.objectStoreNames.contains(storeName)) return db;
|
|
32
|
+
if (version !== void 0) return db;
|
|
33
|
+
const nextVersion = db.version + 1;
|
|
34
|
+
db.close();
|
|
35
|
+
return openRequest(name, nextVersion, storeName);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates a reactive handle for an IndexedDB object store.
|
|
39
|
+
*
|
|
40
|
+
* IndexedDB is asynchronous, so the handle keeps an in-memory cache that
|
|
41
|
+
* `get()` reads synchronously. The cache is hydrated from IndexedDB on
|
|
42
|
+
* creation (await `ready` to know when), and changes made via `set()` /
|
|
43
|
+
* `clear()` are persisted asynchronously. When `BroadcastChannel` is
|
|
44
|
+
* available, changes are synced across tabs while there is at least one
|
|
45
|
+
* subscriber.
|
|
46
|
+
*
|
|
47
|
+
* @example Vanilla
|
|
48
|
+
* ```ts twoslash title="settings-storage.ts"
|
|
49
|
+
* import { createIndexedDbStorage } from 'seitu/web'
|
|
50
|
+
* import * as z from 'zod'
|
|
51
|
+
*
|
|
52
|
+
* const settingsStorage = createIndexedDbStorage({
|
|
53
|
+
* databaseName: 'app',
|
|
54
|
+
* schemas: {
|
|
55
|
+
* token: z.string().nullable(),
|
|
56
|
+
* preferences: z.object({ theme: z.enum(['light', 'dark']) }),
|
|
57
|
+
* },
|
|
58
|
+
* defaultValues: { token: null, preferences: { theme: 'light' } },
|
|
59
|
+
* })
|
|
60
|
+
*
|
|
61
|
+
* settingsStorage.get() // { token: null, preferences: { theme: 'light' } }
|
|
62
|
+
* await settingsStorage.ready // value hydrated from IndexedDB
|
|
63
|
+
* await settingsStorage.set({ token: 'abc' })
|
|
64
|
+
* settingsStorage.get() // { token: 'abc', preferences: { theme: 'light' } }
|
|
65
|
+
* settingsStorage.subscribe(console.log)
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example React
|
|
69
|
+
* ```tsx twoslash title="page.tsx"
|
|
70
|
+
* 'use client'
|
|
71
|
+
*
|
|
72
|
+
* import { createIndexedDbStorage } from 'seitu/web'
|
|
73
|
+
* import { useSubscription } from 'seitu/react'
|
|
74
|
+
* import * as z from 'zod'
|
|
75
|
+
*
|
|
76
|
+
* const settingsStorage = createIndexedDbStorage({
|
|
77
|
+
* databaseName: 'app',
|
|
78
|
+
* schemas: { count: z.number(), name: z.string() },
|
|
79
|
+
* defaultValues: { count: 0, name: '' },
|
|
80
|
+
* })
|
|
81
|
+
*
|
|
82
|
+
* export default function Page() {
|
|
83
|
+
* const value = useSubscription(settingsStorage)
|
|
84
|
+
* return (
|
|
85
|
+
* <div>
|
|
86
|
+
* <span>{value.count}</span>
|
|
87
|
+
* <span>{value.name}</span>
|
|
88
|
+
* </div>
|
|
89
|
+
* )
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
function createIndexedDbStorage(options) {
|
|
94
|
+
const defaultValues = { ...options.defaultValues };
|
|
95
|
+
const storeName = options.storeName ?? "seitu";
|
|
96
|
+
const keys = Object.keys(options.defaultValues);
|
|
97
|
+
const isSupported = typeof indexedDB !== "undefined";
|
|
98
|
+
const channelName = `seitu:indexed-db:${options.databaseName}:${storeName}`;
|
|
99
|
+
const label = `createIndexedDbStorage:${options.databaseName}/${storeName}`;
|
|
100
|
+
const resolveKey = (key) => String(options.keyTransform ? options.keyTransform(key) : key);
|
|
101
|
+
let cache = { ...options.defaultValues };
|
|
102
|
+
let writeVersion = 0;
|
|
103
|
+
const keyWriteVersion = /* @__PURE__ */ new Map();
|
|
104
|
+
let hydrateSeq = 0;
|
|
105
|
+
const markWritten = (written) => {
|
|
106
|
+
writeVersion++;
|
|
107
|
+
for (const key of written) keyWriteVersion.set(key, writeVersion);
|
|
108
|
+
};
|
|
109
|
+
let channel;
|
|
110
|
+
let triggerHydrate = () => {};
|
|
111
|
+
const broadcast = () => {
|
|
112
|
+
if (typeof BroadcastChannel === "undefined") return;
|
|
113
|
+
if (channel) {
|
|
114
|
+
channel.postMessage(null);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const ephemeral = new BroadcastChannel(channelName);
|
|
118
|
+
ephemeral.postMessage(null);
|
|
119
|
+
ephemeral.close();
|
|
120
|
+
};
|
|
121
|
+
let dbPromise;
|
|
122
|
+
const getDatabase = () => {
|
|
123
|
+
if (!dbPromise) dbPromise = openDatabase(options.databaseName, storeName, options.version).then((db) => {
|
|
124
|
+
db.onversionchange = () => {
|
|
125
|
+
db.close();
|
|
126
|
+
dbPromise = void 0;
|
|
127
|
+
};
|
|
128
|
+
return db;
|
|
129
|
+
}).catch((error) => {
|
|
130
|
+
dbPromise = void 0;
|
|
131
|
+
throw error;
|
|
132
|
+
});
|
|
133
|
+
return dbPromise;
|
|
134
|
+
};
|
|
135
|
+
const { subscribe, notify } = createSubscription({ onFirstSubscribe: () => {
|
|
136
|
+
triggerHydrate();
|
|
137
|
+
if (typeof BroadcastChannel === "undefined") return;
|
|
138
|
+
channel = new BroadcastChannel(channelName);
|
|
139
|
+
channel.onmessage = () => triggerHydrate();
|
|
140
|
+
return () => {
|
|
141
|
+
channel?.close();
|
|
142
|
+
channel = void 0;
|
|
143
|
+
};
|
|
144
|
+
} });
|
|
145
|
+
const validateValue = (key, raw) => {
|
|
146
|
+
if (raw === void 0) return options.defaultValues[key];
|
|
147
|
+
return validateSchema(options.schemas[key], raw, {
|
|
148
|
+
defaultValue: options.defaultValues[key],
|
|
149
|
+
label: `createIndexedDbStorage:${String(key)}`,
|
|
150
|
+
onError: options.onValidationError ? (issues, parsed) => options.onValidationError({
|
|
151
|
+
issues: [...issues],
|
|
152
|
+
key,
|
|
153
|
+
value: parsed,
|
|
154
|
+
defaultValue: options.defaultValues[key]
|
|
155
|
+
}) : void 0
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
const hydrate = async () => {
|
|
159
|
+
if (!isSupported) return cache;
|
|
160
|
+
const seq = ++hydrateSeq;
|
|
161
|
+
const startVersion = writeVersion;
|
|
162
|
+
const store = (await getDatabase()).transaction(storeName, "readonly").objectStore(storeName);
|
|
163
|
+
const persisted = {};
|
|
164
|
+
await Promise.all(keys.map(async (key) => {
|
|
165
|
+
persisted[key] = validateValue(key, await requestToPromise(store.get(resolveKey(key))));
|
|
166
|
+
}));
|
|
167
|
+
if (seq !== hydrateSeq) return cache;
|
|
168
|
+
const next = {};
|
|
169
|
+
for (const key of keys) next[key] = (keyWriteVersion.get(key) ?? 0) > startVersion ? cache[key] : persisted[key];
|
|
170
|
+
if (!deepEqual(cache, next)) {
|
|
171
|
+
cache = next;
|
|
172
|
+
notify();
|
|
173
|
+
}
|
|
174
|
+
return cache;
|
|
175
|
+
};
|
|
176
|
+
const safeHydrate = () => hydrate().catch((error) => {
|
|
177
|
+
console.warn(`[${label}] Failed to read from IndexedDB, using cached values.`, error);
|
|
178
|
+
return cache;
|
|
179
|
+
});
|
|
180
|
+
triggerHydrate = () => void safeHydrate();
|
|
181
|
+
const ready = safeHydrate();
|
|
182
|
+
const get = () => cache;
|
|
183
|
+
const readable = createReadableSubscription(get, subscribe, notify);
|
|
184
|
+
const persist = async (mutate) => {
|
|
185
|
+
if (!isSupported) return;
|
|
186
|
+
try {
|
|
187
|
+
const transaction = (await getDatabase()).transaction(storeName, "readwrite");
|
|
188
|
+
mutate(transaction.objectStore(storeName));
|
|
189
|
+
await transactionDone(transaction);
|
|
190
|
+
broadcast();
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.warn(`[${label}] Failed to write to IndexedDB.`, error);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
return {
|
|
196
|
+
...readable,
|
|
197
|
+
ready,
|
|
198
|
+
"set": (value) => {
|
|
199
|
+
const resolved = typeof value === "function" ? value(cache) : value;
|
|
200
|
+
markWritten(Object.keys(resolved));
|
|
201
|
+
cache = {
|
|
202
|
+
...cache,
|
|
203
|
+
...resolved
|
|
204
|
+
};
|
|
205
|
+
notify();
|
|
206
|
+
return persist((store) => {
|
|
207
|
+
for (const [key, entry] of Object.entries(resolved)) store.put(entry, resolveKey(key));
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
"clear": () => {
|
|
211
|
+
markWritten(keys);
|
|
212
|
+
cache = { ...options.defaultValues };
|
|
213
|
+
notify();
|
|
214
|
+
return persist((store) => {
|
|
215
|
+
for (const key of keys) store.delete(resolveKey(key));
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
"~": {
|
|
219
|
+
...readable["~"],
|
|
220
|
+
getDefaultValue: (key) => defaultValues[key],
|
|
221
|
+
databaseName: options.databaseName,
|
|
222
|
+
storeName
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
3
227
|
//#region src/web/is-online.ts
|
|
4
228
|
/**
|
|
5
229
|
* Creates a reactive handle for browser online status.
|
|
@@ -458,4 +682,4 @@ function createWebStorageValue(options) {
|
|
|
458
682
|
};
|
|
459
683
|
}
|
|
460
684
|
//#endregion
|
|
461
|
-
export { createIsOnline, createMediaQuery, createScrollState, createWebStorage, createWebStorageValue };
|
|
685
|
+
export { createIndexedDbStorage, createIsOnline, createMediaQuery, createScrollState, createWebStorage, createWebStorageValue };
|
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.15.0",
|
|
6
6
|
"private": false,
|
|
7
7
|
"author": "Valerii Strilets",
|
|
8
8
|
"license": "MIT",
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"main": "./dist/core.mjs",
|
|
49
49
|
"types": "./dist/core/index.d.mts",
|
|
50
50
|
"files": [
|
|
51
|
-
"dist"
|
|
51
|
+
"dist",
|
|
52
|
+
"skills"
|
|
52
53
|
],
|
|
53
54
|
"engines": {
|
|
54
55
|
"node": ">=22"
|
|
@@ -76,21 +77,23 @@
|
|
|
76
77
|
"@standard-schema/spec": "^1.1.0",
|
|
77
78
|
"@testing-library/jest-dom": "^6.9.1",
|
|
78
79
|
"@testing-library/react": "^16.3.2",
|
|
79
|
-
"@types/react": "^19.2.
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
80
|
+
"@types/react": "^19.2.17",
|
|
81
|
+
"fake-indexeddb": "^6.2.5",
|
|
82
|
+
"happy-dom": "^20.10.2",
|
|
83
|
+
"react-dom": "^19.2.7",
|
|
84
|
+
"tsdown": "^0.22.2",
|
|
85
|
+
"type-fest": "^5.7.0",
|
|
84
86
|
"typescript": "^6.0.3",
|
|
85
|
-
"vite": "^
|
|
86
|
-
"vitest": "^
|
|
87
|
-
"vue": "^3.5.
|
|
87
|
+
"vite": "^6.4.3",
|
|
88
|
+
"vitest": "^3.2.6",
|
|
89
|
+
"vue": "^3.5.35",
|
|
88
90
|
"zod": "^4.4.3"
|
|
89
91
|
},
|
|
90
92
|
"scripts": {
|
|
91
93
|
"build": "tsdown",
|
|
92
94
|
"test": "vitest run",
|
|
93
95
|
"check-types": "tsc",
|
|
94
|
-
"scripts:copy-readme": "node ./scripts/copy-readme.ts"
|
|
96
|
+
"scripts:copy-readme": "node ./scripts/copy-readme.ts",
|
|
97
|
+
"scripts:copy-skills": "node ./scripts/copy-skills.ts"
|
|
95
98
|
}
|
|
96
99
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Subscription-react
|
|
3
|
+
description: >-
|
|
4
|
+
Declarative React component for subscribing to Seitu reactive values via
|
|
5
|
+
render props. Use when you prefer a component API over the useSubscription hook.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Subscription (React component)
|
|
9
|
+
|
|
10
|
+
Declarative render-prop component from `seitu/react`. Isolates re-renders to the children function.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Subscription } from 'seitu/react'
|
|
14
|
+
|
|
15
|
+
<Subscription value={storage}>
|
|
16
|
+
{(value) => <div>{value.count}</div>}
|
|
17
|
+
</Subscription>
|
|
18
|
+
|
|
19
|
+
<Subscription value={storage} selector={v => v.count}>
|
|
20
|
+
{(count) => <div>{count}</div>}
|
|
21
|
+
</Subscription>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Props
|
|
25
|
+
|
|
26
|
+
| Prop | Type | Description |
|
|
27
|
+
|------|------|-------------|
|
|
28
|
+
| `value` | `Subscribable & Readable` | The reactive source |
|
|
29
|
+
| `selector?` | `(value) => R` | Optional selector for granular updates |
|
|
30
|
+
| `children` | `(value) => ReactNode` | Render function |
|
|
31
|
+
|
|
32
|
+
## Source
|
|
33
|
+
|
|
34
|
+
`seitu/src/react/components.tsx`
|