@yakocloud/state-vocab 3.1.6 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -29
- package/dist/client.cjs.js +1 -0
- package/dist/client.es.js +107 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.es.js +119 -0
- package/dist/provider.client--OPImdtY.js +1 -0
- package/dist/provider.client-CtAC9tPr.mjs +41 -0
- package/dist/server.cjs.js +1 -0
- package/dist/server.es.js +77 -0
- package/dist/types/client.d.ts +2 -0
- package/dist/types/context.client.d.ts +8 -0
- package/dist/types/context.server.d.ts +10 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/provider.client.d.ts +5 -0
- package/dist/types/provider.d.ts +5 -0
- package/dist/types/provider.server.d.ts +5 -0
- package/dist/types/server.d.ts +2 -0
- package/dist/types/setup.client.d.ts +15 -0
- package/dist/types/setup.d.ts +1 -1
- package/dist/types/setup.server.d.ts +18 -0
- package/dist/types/setup.types.d.ts +16 -0
- package/dist/types/state.client.utils.d.ts +2 -0
- package/dist/types/state.d.ts +45 -25
- package/dist/types/state.types.d.ts +7 -0
- package/dist/types/store.d.ts +1 -1
- package/dist/types/utils.d.ts +4 -2
- package/dist/utils-33NqsZoR.js +3 -0
- package/dist/utils-t8tYdd6B.mjs +63 -0
- package/package.json +21 -4
- package/dist/state-vocab.cjs.js +0 -8
- package/dist/state-vocab.es.js +0 -505
- package/dist/types/context.d.ts +0 -9
- package/dist/types/main.d.ts +0 -1
- package/dist/types/main.utils.d.ts +0 -2
- package/dist/types/state.utils.d.ts +0 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @yakocloud/state-vocab
|
|
2
2
|
|
|
3
|
-
A lightweight React state management library that synchronizes component state with any `Storage`-compatible backend (localStorage, sessionStorage, custom).
|
|
3
|
+
A lightweight React state management library that synchronizes component state with any `Storage`-compatible backend (localStorage, sessionStorage, custom). Supports React Server Components and Next.js SSR out of the box.
|
|
4
4
|
|
|
5
5
|
## Why use state-vocab?
|
|
6
6
|
|
|
@@ -82,6 +82,8 @@ function DeepChild() {
|
|
|
82
82
|
}
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
**React Server Components support.** Read state in async server components and seed it into client components — no extra API routes or serialization boilerplate.
|
|
86
|
+
|
|
85
87
|
**Dot-notation access with full TypeScript inference.** The state tree is navigated like a plain object — autocomplete guides you to the right node, and types flow from `defineState<T>` all the way to the hook return value without any manual annotations.
|
|
86
88
|
|
|
87
89
|
```ts
|
|
@@ -103,7 +105,7 @@ defineState({
|
|
|
103
105
|
})
|
|
104
106
|
```
|
|
105
107
|
|
|
106
|
-
**Minimal API surface.**
|
|
108
|
+
**Minimal API surface.** `defineState` and `setupStorage` define the state tree; `clientify` from `@yakocloud/state-vocab/client` wires up React hooks. No actions, reducers, selectors, or stores to configure.
|
|
107
109
|
|
|
108
110
|
## Installation
|
|
109
111
|
|
|
@@ -115,17 +117,19 @@ npm install @yakocloud/state-vocab react react-dom
|
|
|
115
117
|
|
|
116
118
|
## Quick Start
|
|
117
119
|
|
|
118
|
-
Wrap your app with `
|
|
120
|
+
Wrap your app with `StateVocabProvider` at the root — this initializes an isolated store for the component tree. Then define and use state anywhere inside it.
|
|
121
|
+
|
|
119
122
|
Library SSR-requirements:
|
|
120
123
|
- `Per-request store`: A Next.js server can handle multiple requests simultaneously. This means that the store should be created per request and should not be shared across requests.
|
|
121
124
|
- `SSR friendly`: Next.js applications are rendered twice, first on the server and again on the client. Having different outputs on both the client and the server will result in "hydration errors." The store will have to be initialized on the server and then re-initialized on the client with the same data in order to avoid that.
|
|
122
125
|
|
|
123
126
|
```tsx
|
|
124
|
-
import { setupStorage, defineState
|
|
127
|
+
import { setupStorage, defineState } from '@yakocloud/state-vocab'
|
|
128
|
+
import { clientify, StateVocabProvider } from '@yakocloud/state-vocab/client'
|
|
125
129
|
|
|
126
130
|
type Theme = 'Dark' | 'White' | 'System'
|
|
127
131
|
|
|
128
|
-
const storage = setupStorage({
|
|
132
|
+
const storage = clientify(setupStorage({
|
|
129
133
|
path: {
|
|
130
134
|
to: {
|
|
131
135
|
theme: defineState<Theme>({
|
|
@@ -134,13 +138,13 @@ const storage = setupStorage({
|
|
|
134
138
|
}),
|
|
135
139
|
},
|
|
136
140
|
},
|
|
137
|
-
})
|
|
141
|
+
}))
|
|
138
142
|
|
|
139
143
|
function App() {
|
|
140
144
|
return (
|
|
141
|
-
<
|
|
145
|
+
<StateVocabProvider>
|
|
142
146
|
<Settings />
|
|
143
|
-
</
|
|
147
|
+
</StateVocabProvider>
|
|
144
148
|
)
|
|
145
149
|
}
|
|
146
150
|
|
|
@@ -159,19 +163,18 @@ function Settings() {
|
|
|
159
163
|
|
|
160
164
|
## Setup
|
|
161
165
|
|
|
162
|
-
### `
|
|
166
|
+
### `StateVocabProvider`
|
|
163
167
|
|
|
164
|
-
All components that call `.useState()` must be descendants of `
|
|
168
|
+
All components that call `.useState()` must be descendants of `StateVocabProvider`. It creates an isolated `VocabStore` instance for its subtree — multiple providers can coexist in the same app without sharing state.
|
|
165
169
|
|
|
166
170
|
```tsx
|
|
167
|
-
import {
|
|
171
|
+
import { StateVocabProvider } from '@yakocloud/state-vocab/client'
|
|
168
172
|
|
|
169
|
-
// Wrap your app root
|
|
170
173
|
createRoot(document.getElementById('root')!).render(
|
|
171
174
|
<React.StrictMode>
|
|
172
|
-
<
|
|
175
|
+
<StateVocabProvider>
|
|
173
176
|
<App />
|
|
174
|
-
</
|
|
177
|
+
</StateVocabProvider>
|
|
175
178
|
</React.StrictMode>
|
|
176
179
|
)
|
|
177
180
|
```
|
|
@@ -180,13 +183,13 @@ You can mount multiple independent providers — each gets its own store:
|
|
|
180
183
|
|
|
181
184
|
```tsx
|
|
182
185
|
// Two isolated state trees — state does not bleed between them
|
|
183
|
-
<
|
|
186
|
+
<StateVocabProvider>
|
|
184
187
|
<WidgetA />
|
|
185
|
-
</
|
|
188
|
+
</StateVocabProvider>
|
|
186
189
|
|
|
187
|
-
<
|
|
190
|
+
<StateVocabProvider>
|
|
188
191
|
<WidgetB />
|
|
189
|
-
</
|
|
192
|
+
</StateVocabProvider>
|
|
190
193
|
```
|
|
191
194
|
|
|
192
195
|
## Core Concepts
|
|
@@ -273,7 +276,7 @@ const storage = setupStorage({
|
|
|
273
276
|
|
|
274
277
|
TypeScript will only accept paths that exist in your tree — `"user"`, `"user.profile"`, `"cart.items"`, etc. Invalid paths are caught at compile time.
|
|
275
278
|
|
|
276
|
-
### SSR / Next.js
|
|
279
|
+
### SSR / Next.js (client components)
|
|
277
280
|
|
|
278
281
|
When using localStorage or sessionStorage in a Next.js app, the server renders with `defaultValue` while the client reads the persisted value — causing a hydration mismatch. Pass `ssr: true` to fix this:
|
|
279
282
|
|
|
@@ -292,7 +295,150 @@ With `ssr: true`:
|
|
|
292
295
|
|
|
293
296
|
This guarantees the server and client produce identical markup, and the value from storage is applied without a visible flash.
|
|
294
297
|
|
|
295
|
-
|
|
298
|
+
### React Server Components (RSC)
|
|
299
|
+
|
|
300
|
+
For Next.js apps using the App Router, state-vocab provides dedicated server and client entry points that let you read state in async server components and pass it down to client components via `StateVocabProvider`.
|
|
301
|
+
|
|
302
|
+
**Package entry points:**
|
|
303
|
+
|
|
304
|
+
| Import path | Use in |
|
|
305
|
+
|---|---|
|
|
306
|
+
| `@yakocloud/state-vocab` | Shared files — `defineState`, `setupStorage` |
|
|
307
|
+
| `@yakocloud/state-vocab/server` | Server Components — `serverify`, `StateVocabProvider` |
|
|
308
|
+
| `@yakocloud/state-vocab/client` | Client Components — `clientify` |
|
|
309
|
+
|
|
310
|
+
**1. Define the shared storage schema** (used on both server and client):
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
// storage.ts
|
|
314
|
+
import { setupStorage, defineState } from '@yakocloud/state-vocab'
|
|
315
|
+
|
|
316
|
+
export const storage = setupStorage({
|
|
317
|
+
user: {
|
|
318
|
+
name: defineState<string>(),
|
|
319
|
+
role: defineState<string>(),
|
|
320
|
+
},
|
|
321
|
+
person: {
|
|
322
|
+
address: {
|
|
323
|
+
city: defineState<string>(),
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
}, { ssr: true })
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**2. Create server and client storage handles:**
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
// storage.server.ts
|
|
333
|
+
import { storage } from '@/storage'
|
|
334
|
+
import { serverify } from '@yakocloud/state-vocab/server'
|
|
335
|
+
|
|
336
|
+
export const serverStorage = serverify(storage)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
// storage.client.ts ("use client")
|
|
341
|
+
"use client"
|
|
342
|
+
|
|
343
|
+
import { storage } from '@/storage'
|
|
344
|
+
import { clientify } from '@yakocloud/state-vocab/client'
|
|
345
|
+
|
|
346
|
+
export const clientStorage = clientify(storage)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**3. Seed initial state in the server component and read it in Server and Client children:**
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
// app/page.tsx (Server Component)
|
|
353
|
+
import { serverStorage } from '@/storage.server'
|
|
354
|
+
import { StateVocabProvider } from '@yakocloud/state-vocab/server'
|
|
355
|
+
|
|
356
|
+
export default async function Page() {
|
|
357
|
+
// Fetch data from DB / API
|
|
358
|
+
const user = await db.getUser()
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<StateVocabProvider
|
|
362
|
+
value={serverStorage({
|
|
363
|
+
user: { name: user.name, role: user.role },
|
|
364
|
+
person: { address: { city: user.city } },
|
|
365
|
+
})}
|
|
366
|
+
>
|
|
367
|
+
<UserInfo />
|
|
368
|
+
</StateVocabProvider>
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
```tsx
|
|
374
|
+
// app/user-info.server.tsx (Server Component)
|
|
375
|
+
import { serverStorage } from '@/storage.server'
|
|
376
|
+
|
|
377
|
+
export default async function UserInfo() {
|
|
378
|
+
const name = serverStorage.user.name.getState()
|
|
379
|
+
const role = serverStorage.user.role.getState()
|
|
380
|
+
|
|
381
|
+
return <p>{name} — {role}</p>
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
// app/user-info.tsx ("use client")
|
|
387
|
+
"use client"
|
|
388
|
+
|
|
389
|
+
import { clientStorage } from '@/storage.client'
|
|
390
|
+
|
|
391
|
+
export default function UserInfoClient() {
|
|
392
|
+
const [name] = clientStorage.user.name.useState()
|
|
393
|
+
const [role] = clientStorage.user.role.useState()
|
|
394
|
+
|
|
395
|
+
return <p>{name} — {role}</p>
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
#### `serverify(storage)`
|
|
400
|
+
|
|
401
|
+
Converts a storage tree into its server-side counterpart. Each leaf gains a `.getState()` method that reads the value seeded into the nearest `StateVocabProvider`. All namespace nodes become callable functions that build the `value` shape expected by `StateVocabProvider`.
|
|
402
|
+
|
|
403
|
+
**Namespace callable syntax:**
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
// Full tree at once — root call is identity (returns input as-is)
|
|
407
|
+
serverStorage({ user: { name: 'Alice', role: 'Admin' } })
|
|
408
|
+
// → { user: { name: 'Alice', role: 'Admin' } }
|
|
409
|
+
|
|
410
|
+
// Single namespace — wraps input under its key
|
|
411
|
+
serverStorage.user({ name: 'Alice', role: 'Admin' })
|
|
412
|
+
// → { user: { name: 'Alice', role: 'Admin' } }
|
|
413
|
+
|
|
414
|
+
// Nested namespace — wraps up to the root
|
|
415
|
+
serverStorage.person.address({ city: 'NY' })
|
|
416
|
+
// → { person: { address: { city: 'NY' } } }
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
All three forms return a value ready to pass as `StateVocabProvider`'s `value` prop. They can be combined by spreading:
|
|
420
|
+
|
|
421
|
+
```tsx
|
|
422
|
+
<StateVocabProvider
|
|
423
|
+
value={{
|
|
424
|
+
...serverStorage.user({ name: 'Alice', role: 'Admin' }),
|
|
425
|
+
...serverStorage.person.address({ city: 'NY' }),
|
|
426
|
+
}}
|
|
427
|
+
>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**`node.getState()`** reads the value for that leaf from the surrounding `StateVocabProvider`. Throws if called outside one.
|
|
431
|
+
|
|
432
|
+
#### `clientify(storage)`
|
|
433
|
+
|
|
434
|
+
Converts a storage tree into its client-side counterpart. Each leaf gains `.useState()` and `.useInitialState()` hooks. The tree structure is identical to the original — same dot-notation access.
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
const clientStorage = clientify(storage)
|
|
438
|
+
|
|
439
|
+
// In a client component:
|
|
440
|
+
const [name] = clientStorage.user.name.useState()
|
|
441
|
+
```
|
|
296
442
|
|
|
297
443
|
## `useState` Hook
|
|
298
444
|
|
|
@@ -374,11 +520,12 @@ const storage = setupStorage({
|
|
|
374
520
|
```tsx
|
|
375
521
|
import React from 'react'
|
|
376
522
|
import { createRoot } from 'react-dom/client'
|
|
377
|
-
import { setupStorage, defineState
|
|
523
|
+
import { setupStorage, defineState } from '@yakocloud/state-vocab'
|
|
524
|
+
import { clientify, StateVocabProvider } from '@yakocloud/state-vocab/client'
|
|
378
525
|
|
|
379
526
|
type Theme = 'Dark' | 'White' | 'System'
|
|
380
527
|
|
|
381
|
-
const storage = setupStorage({
|
|
528
|
+
const storage = clientify(setupStorage({
|
|
382
529
|
preference: {
|
|
383
530
|
theme: defineState<Theme>({ storage: localStorage, defaultValue: 'Dark' }),
|
|
384
531
|
nightMode: defineState({ storage: sessionStorage, defaultValue: false }),
|
|
@@ -400,7 +547,7 @@ const storage = setupStorage({
|
|
|
400
547
|
storage: localStorage,
|
|
401
548
|
}),
|
|
402
549
|
},
|
|
403
|
-
})
|
|
550
|
+
}))
|
|
404
551
|
|
|
405
552
|
// Root — initializes shared state for the whole subtree
|
|
406
553
|
function Page() {
|
|
@@ -460,9 +607,9 @@ function Dashboard() {
|
|
|
460
607
|
|
|
461
608
|
createRoot(document.getElementById('root')!).render(
|
|
462
609
|
<React.StrictMode>
|
|
463
|
-
<
|
|
610
|
+
<StateVocabProvider>
|
|
464
611
|
<Page />
|
|
465
|
-
</
|
|
612
|
+
</StateVocabProvider>
|
|
466
613
|
</React.StrictMode>
|
|
467
614
|
)
|
|
468
615
|
```
|
|
@@ -489,14 +636,52 @@ createRoot(document.getElementById('root')!).render(
|
|
|
489
636
|
|
|
490
637
|
Returns a proxied copy of `tree` with paths injected into all leaf nodes.
|
|
491
638
|
|
|
492
|
-
### `
|
|
639
|
+
### `StateVocabProvider`
|
|
493
640
|
|
|
494
641
|
A React context provider that initializes a `VocabStore` for its subtree. Required — all components calling `.useState()` must be descendants of this provider.
|
|
495
642
|
|
|
496
643
|
```tsx
|
|
497
|
-
<
|
|
644
|
+
<StateVocabProvider>
|
|
498
645
|
<App />
|
|
499
|
-
</
|
|
646
|
+
</StateVocabProvider>
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Accepts an optional `value` prop (imported from `@yakocloud/state-vocab/server` in RSC contexts) to pre-seed the store with server-fetched data:
|
|
650
|
+
|
|
651
|
+
```tsx
|
|
652
|
+
<StateVocabProvider value={serverStorage({ user: { name: 'Alice' } })}>
|
|
653
|
+
<App />
|
|
654
|
+
</StateVocabProvider>
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### `serverify<T>(storage: T)`
|
|
658
|
+
|
|
659
|
+
Converts a storage tree to its server-side counterpart. Available from `@yakocloud/state-vocab/server`.
|
|
660
|
+
|
|
661
|
+
Each leaf gains `.getState()` — reads the value from the nearest `StateVocabProvider`. Each namespace node becomes callable, returning the input wrapped under its full ancestor path (ready for `StateVocabProvider`'s `value` prop).
|
|
662
|
+
|
|
663
|
+
```ts
|
|
664
|
+
import { serverify } from '@yakocloud/state-vocab/server'
|
|
665
|
+
const serverStorage = serverify(storage)
|
|
666
|
+
|
|
667
|
+
serverStorage.user.name.getState() // reads "user.name" from context
|
|
668
|
+
serverStorage.user({ name: 'Alice' }) // → { user: { name: 'Alice' } }
|
|
669
|
+
serverStorage.person.address({ city: 'NY' }) // → { person: { address: { city: 'NY' } } }
|
|
670
|
+
serverStorage({ user: { name: 'Alice' } }) // → { user: { name: 'Alice' } } (identity)
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
### `clientify<T>(storage: T)`
|
|
674
|
+
|
|
675
|
+
Converts a storage tree to its client-side counterpart. Available from `@yakocloud/state-vocab/client`.
|
|
676
|
+
|
|
677
|
+
Each leaf gains `.useState()` and `.useInitialState()`. The tree structure mirrors the original.
|
|
678
|
+
|
|
679
|
+
```ts
|
|
680
|
+
import { clientify } from '@yakocloud/state-vocab/client'
|
|
681
|
+
const clientStorage = clientify(storage)
|
|
682
|
+
|
|
683
|
+
// In a "use client" component:
|
|
684
|
+
const [name] = clientStorage.user.name.useState()
|
|
500
685
|
```
|
|
501
686
|
|
|
502
687
|
### `node.useState(options?)`
|
|
@@ -527,4 +712,4 @@ function Page() {
|
|
|
527
712
|
|
|
528
713
|
return <Dashboard />
|
|
529
714
|
}
|
|
530
|
-
```
|
|
715
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("react"),t=require("./utils-33NqsZoR.js"),z=require("./provider.client--OPImdtY.js");function D(s,e,o=[]){return c.useMemo(()=>t.debounce(s,e),o)}const O=typeof window>"u",w=O?c.useEffect:c.useLayoutEffect;function _(s){s??={};const e=O?void 0:t.valueOrFactory(s.storage),o=t.valueOrFactory(s.defaultValue),l=s.bidirectional,n=this[t.STATE_PATH],y=this[t.STATE_VERBOSE],V=this[t.STATE_VERBOSE_PATH],T=this[t.STATE_SSR],r=z.useStateVocabClientContext({verbose:y});if(!(r instanceof z.VocabStore))throw new Error("Make sure your component is wrapped in StateVocabProvider");const d=s.serialize??JSON.stringify,E=s.deserialize??JSON.parse,a=(i,f,m)=>{const v=f.getItem(i);v===null?t.isValueDefined(m)&&f.setItem(i,d(m)):r.set(i,E(v))},u=D(s.onSet??(()=>{}),s.delayedSet,[]),S=c.useRef(void 0),g=c.useRef(!1);if(!g.current){g.current=!0;let i=r.get(n);t.isValueDefined(i)||(i=o,t.isValueDefined(i)&&r.set(n,i)),!T&&e&&a(n,e,i)}const b=c.useSyncExternalStore(r.subscribe.bind(r),r.getClientSnapshot.bind(r),r.getServerSnapshot.bind(r));if(y)if(V){const i=t.get(b,V);i&&t.logStyled(i)}else t.logStyled(b);const h=t.get(b,n,o);S.current=h,w(()=>{!T||!e||a(n,e,h)},[]);const R=c.useEffectEvent(i=>{if(i.key!==n)return;const f=i.newValue,v=(f===null?null:E(f))??o;t.isValueDefined(v)&&(r.set(n,v),u(v,S.current))});c.useEffect(()=>{if(l)return window.addEventListener("storage",R),()=>window.removeEventListener("storage",R)},[l]);const I=c.useCallback(i=>{const f=t.isTransformer(i)?i(S.current):i;r.set(n,f),u(f,S.current),e&&e.setItem(n,d(f))},[r,n,u,e,d]),C=c.useCallback(()=>{const i=o;if(!t.isValueDefined(i)){e?.removeItem(n);return}r.set(n,i),u(i,S.current),e&&e.setItem(n,d(i))},[o,r,n,u,e,d]);return[h,I,C]}function p(s){s??={};const e=O?void 0:t.valueOrFactory(s.storage),o=t.valueOrFactory(s.defaultValue),l=this[t.STATE_PATH],n=this[t.STATE_VERBOSE],y=this[t.STATE_SSR],V=z.useStateVocabClientContext({verbose:n}),T=s.serialize??JSON.stringify,r=s.deserialize??JSON.parse,d=(u,S,g)=>{const b=S.getItem(u);b===null?t.isValueDefined(g)&&S.setItem(u,T(g)):V.set(u,r(b))},E=c.useRef(!1);let a;E.current||(E.current=!0,a=V.get(l),t.isValueDefined(a)||(a=o,t.isValueDefined(a)&&V.set(l,a)),!y&&e&&d(l,e,a)),w(()=>{!y||!e||d(l,e,a)},[])}function P(s){return typeof s=="object"&&s!==null&&"clientSlot"in s}function A(s){const e={};for(const o in s){const l=s[o];P(l)?(e[o]=l.clientSlot({useState(...n){return _.apply(this,n)},useInitialState(...n){p.apply(this,n)}}),delete e[o].serverSlot,delete e[o].clientSlot):l!==null&&typeof l=="object"?e[o]=A(l):e[o]=l}return e}exports.StateVocabProvider=z.StateVocabClientProvider;exports.clientify=A;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useMemo as j, useRef as T, useSyncExternalStore as x, useEffectEvent as B, useEffect as A, useCallback as I, useLayoutEffect as D } from "react";
|
|
3
|
+
import { d as H, v as E, S as C, a as _, b as k, i as d, c as M, g as O, l as R, e as F } from "./utils-t8tYdd6B.mjs";
|
|
4
|
+
import { u as J, V as q } from "./provider.client-CtAC9tPr.mjs";
|
|
5
|
+
import { S as te } from "./provider.client-CtAC9tPr.mjs";
|
|
6
|
+
function G(s, e, n = []) {
|
|
7
|
+
return j(
|
|
8
|
+
() => H(s, e),
|
|
9
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
10
|
+
n
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
const p = typeof window > "u", L = p ? A : D;
|
|
14
|
+
function K(s) {
|
|
15
|
+
s ??= {};
|
|
16
|
+
const e = p ? void 0 : E(s.storage), n = E(s.defaultValue), o = s.bidirectional, r = this[C], m = this[_], S = this[M], g = this[k], i = J({ verbose: m });
|
|
17
|
+
if (!(i instanceof q))
|
|
18
|
+
throw new Error("Make sure your component is wrapped in StateVocabProvider");
|
|
19
|
+
const u = s.serialize ?? JSON.stringify, y = s.deserialize ?? JSON.parse, a = (t, l, h) => {
|
|
20
|
+
const v = l.getItem(t);
|
|
21
|
+
v === null ? d(h) && l.setItem(t, u(h)) : i.set(t, y(v));
|
|
22
|
+
}, c = G(
|
|
23
|
+
s.onSet ?? (() => {
|
|
24
|
+
}),
|
|
25
|
+
s.delayedSet,
|
|
26
|
+
[]
|
|
27
|
+
), f = T(void 0), V = T(!1);
|
|
28
|
+
if (!V.current) {
|
|
29
|
+
V.current = !0;
|
|
30
|
+
let t = i.get(r);
|
|
31
|
+
d(t) || (t = n, d(t) && i.set(r, t)), !g && e && a(r, e, t);
|
|
32
|
+
}
|
|
33
|
+
const b = x(
|
|
34
|
+
i.subscribe.bind(i),
|
|
35
|
+
i.getClientSnapshot.bind(i),
|
|
36
|
+
i.getServerSnapshot.bind(i)
|
|
37
|
+
);
|
|
38
|
+
if (m)
|
|
39
|
+
if (S) {
|
|
40
|
+
const t = O(b, S);
|
|
41
|
+
t && R(t);
|
|
42
|
+
} else
|
|
43
|
+
R(b);
|
|
44
|
+
const z = O(b, r, n);
|
|
45
|
+
f.current = z, L(() => {
|
|
46
|
+
!g || !e || a(r, e, z);
|
|
47
|
+
}, []);
|
|
48
|
+
const w = B((t) => {
|
|
49
|
+
if (t.key !== r)
|
|
50
|
+
return;
|
|
51
|
+
const l = t.newValue, v = (l === null ? null : y(l)) ?? n;
|
|
52
|
+
d(v) && (i.set(r, v), c(v, f.current));
|
|
53
|
+
});
|
|
54
|
+
A(() => {
|
|
55
|
+
if (o)
|
|
56
|
+
return window.addEventListener("storage", w), () => window.removeEventListener("storage", w);
|
|
57
|
+
}, [o]);
|
|
58
|
+
const N = I((t) => {
|
|
59
|
+
const l = F(t) ? t(f.current) : t;
|
|
60
|
+
i.set(r, l), c(l, f.current), e && e.setItem(r, u(l));
|
|
61
|
+
}, [i, r, c, e, u]), P = I(() => {
|
|
62
|
+
const t = n;
|
|
63
|
+
if (!d(t)) {
|
|
64
|
+
e?.removeItem(r);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
i.set(r, t), c(t, f.current), e && e.setItem(r, u(t));
|
|
68
|
+
}, [n, i, r, c, e, u]);
|
|
69
|
+
return [
|
|
70
|
+
z,
|
|
71
|
+
N,
|
|
72
|
+
P
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
function Q(s) {
|
|
76
|
+
s ??= {};
|
|
77
|
+
const e = p ? void 0 : E(s.storage), n = E(s.defaultValue), o = this[C], r = this[_], m = this[k], S = J({ verbose: r }), g = s.serialize ?? JSON.stringify, i = s.deserialize ?? JSON.parse, u = (c, f, V) => {
|
|
78
|
+
const b = f.getItem(c);
|
|
79
|
+
b === null ? d(V) && f.setItem(c, g(V)) : S.set(c, i(b));
|
|
80
|
+
}, y = T(!1);
|
|
81
|
+
let a;
|
|
82
|
+
y.current || (y.current = !0, a = S.get(o), d(a) || (a = n, d(a) && S.set(o, a)), !m && e && u(o, e, a)), L(() => {
|
|
83
|
+
!m || !e || u(o, e, a);
|
|
84
|
+
}, []);
|
|
85
|
+
}
|
|
86
|
+
function U(s) {
|
|
87
|
+
return typeof s == "object" && s !== null && "clientSlot" in s;
|
|
88
|
+
}
|
|
89
|
+
function W(s) {
|
|
90
|
+
const e = {};
|
|
91
|
+
for (const n in s) {
|
|
92
|
+
const o = s[n];
|
|
93
|
+
U(o) ? (e[n] = o.clientSlot({
|
|
94
|
+
useState(...r) {
|
|
95
|
+
return K.apply(this, r);
|
|
96
|
+
},
|
|
97
|
+
useInitialState(...r) {
|
|
98
|
+
Q.apply(this, r);
|
|
99
|
+
}
|
|
100
|
+
}), delete e[n].serverSlot, delete e[n].clientSlot) : o !== null && typeof o == "object" ? e[n] = W(o) : e[n] = o;
|
|
101
|
+
}
|
|
102
|
+
return e;
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
te as StateVocabProvider,
|
|
106
|
+
W as clientify
|
|
107
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("./utils-33NqsZoR.js");function V(t={}){return{[a.STATE_DEFINITION]:!0,[a.STATE_PATH]:"",[a.STATE_VERBOSE]:!1,[a.STATE_VERBOSE_PATH]:"",[a.STATE_SSR]:!1,serverSlot(r){return Object.assign(this,{getState(...e){return r.getState.apply(this,e)}})},clientSlot(r){return Object.assign(this,{useState(e){return e??={},r.useState.apply(this,[{defaultValue:a.valueOrFactory(e.defaultValue)??a.valueOrFactory(t.defaultValue),bidirectional:e.bidirectional??t.bidirectional,storage:e.storage??t.storage,serialize:t.serialize??JSON.stringify,deserialize:t.deserialize??JSON.parse,delayedSet:e.delayedSet,onSet(...u){e.onSet&&e.onSet(...u)}}])},useInitialState(e){r.useInitialState.apply(this,[{defaultValue:e.defaultValue,storage:t.storage,serialize:t.serialize,deserialize:t.deserialize}])}})},toString(){return this[a.STATE_PATH]}}}function h(t,r){const{path:e="",verbose:u,verbosePath:y,ssr:A,cache:S}=r;let s=S.proxy.get(t);s||(s=new Map,S.proxy.set(t,s));const d=s.get(e);if(d)return d;const o=new Proxy(t,{get(b,T){const l=b[T],c=e?`${e}.${String(T)}`:String(T);if(l&&typeof l=="object"&&a.STATE_DEFINITION in l){const i=l;let n=S.leaf.get(i);n||(n=new Map,S.leaf.set(i,n));const g=n.get(c);if(g)return g;const P=Reflect.ownKeys(i).filter(f=>typeof i[f]=="function"),_=Object.fromEntries(P.map(f=>[f,(...v)=>i[f].call({...i,[a.STATE_PATH]:c,[a.STATE_VERBOSE]:u,[a.STATE_VERBOSE_PATH]:y,[a.STATE_SSR]:A},...v)])),E={...i,..._};return n.set(c,E),E}return l&&typeof l=="object"?h(l,{...r,path:c}):l}});return s.set(e,o),o}function z(t,r){return h(t,{...r,ssr:r?.ssr??!0,verbosePath:r?.verbosePath??"",cache:{proxy:new WeakMap,leaf:new WeakMap}})}exports.defineState=V;exports.setupStorage=z;
|
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { S as d, v as b, b as T, c as E, a as P, f as v } from "./utils-t8tYdd6B.mjs";
|
|
2
|
+
function m(t = {}) {
|
|
3
|
+
return {
|
|
4
|
+
[v]: !0,
|
|
5
|
+
// marks this object as a leaf in the router tree
|
|
6
|
+
[d]: "",
|
|
7
|
+
// placeholder; injected at runtime by injectPaths()
|
|
8
|
+
[P]: !1,
|
|
9
|
+
// placeholder
|
|
10
|
+
[E]: "",
|
|
11
|
+
// placeholder
|
|
12
|
+
[T]: !1,
|
|
13
|
+
// placeholder
|
|
14
|
+
serverSlot(a) {
|
|
15
|
+
return Object.assign(this, {
|
|
16
|
+
// Impl|RSC
|
|
17
|
+
getState(...e) {
|
|
18
|
+
return a.getState.apply(this, e);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
clientSlot(a) {
|
|
23
|
+
return Object.assign(this, {
|
|
24
|
+
// Impl|RCC
|
|
25
|
+
useState(e) {
|
|
26
|
+
return e ??= {}, a.useState.apply(this, [{
|
|
27
|
+
defaultValue: b(e.defaultValue) ?? b(t.defaultValue),
|
|
28
|
+
bidirectional: e.bidirectional ?? t.bidirectional,
|
|
29
|
+
storage: e.storage ?? t.storage,
|
|
30
|
+
serialize: t.serialize ?? JSON.stringify,
|
|
31
|
+
deserialize: t.deserialize ?? JSON.parse,
|
|
32
|
+
delayedSet: e.delayedSet,
|
|
33
|
+
onSet(...S) {
|
|
34
|
+
e.onSet && e.onSet(...S);
|
|
35
|
+
}
|
|
36
|
+
}]);
|
|
37
|
+
},
|
|
38
|
+
// Impl|RCC
|
|
39
|
+
useInitialState(e) {
|
|
40
|
+
a.useInitialState.apply(this, [{
|
|
41
|
+
defaultValue: e.defaultValue,
|
|
42
|
+
storage: t.storage,
|
|
43
|
+
serialize: t.serialize,
|
|
44
|
+
deserialize: t.deserialize
|
|
45
|
+
}]);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
/** Returns the fully qualified state name (dot-separated path). */
|
|
50
|
+
toString() {
|
|
51
|
+
return this[d];
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function z(t, a) {
|
|
56
|
+
const {
|
|
57
|
+
path: e = "",
|
|
58
|
+
verbose: S,
|
|
59
|
+
verbosePath: x,
|
|
60
|
+
ssr: A,
|
|
61
|
+
cache: n
|
|
62
|
+
} = a;
|
|
63
|
+
let s = n.proxy.get(t);
|
|
64
|
+
s || (s = /* @__PURE__ */ new Map(), n.proxy.set(t, s));
|
|
65
|
+
const h = s.get(e);
|
|
66
|
+
if (h)
|
|
67
|
+
return h;
|
|
68
|
+
const o = new Proxy(t, {
|
|
69
|
+
get(V, u) {
|
|
70
|
+
const r = V[u], c = e ? `${e}.${String(u)}` : String(u);
|
|
71
|
+
if (r && typeof r == "object" && v in r) {
|
|
72
|
+
const l = r;
|
|
73
|
+
let i = n.leaf.get(l);
|
|
74
|
+
i || (i = /* @__PURE__ */ new Map(), n.leaf.set(l, i));
|
|
75
|
+
const g = i.get(c);
|
|
76
|
+
if (g)
|
|
77
|
+
return g;
|
|
78
|
+
const j = Reflect.ownKeys(l).filter(
|
|
79
|
+
(f) => typeof l[f] == "function"
|
|
80
|
+
), w = Object.fromEntries(
|
|
81
|
+
j.map((f) => [
|
|
82
|
+
f,
|
|
83
|
+
(..._) => l[f].call(
|
|
84
|
+
{
|
|
85
|
+
...l,
|
|
86
|
+
[d]: c,
|
|
87
|
+
[P]: S,
|
|
88
|
+
[E]: x,
|
|
89
|
+
[T]: A
|
|
90
|
+
},
|
|
91
|
+
..._
|
|
92
|
+
)
|
|
93
|
+
])
|
|
94
|
+
), y = { ...l, ...w };
|
|
95
|
+
return i.set(c, y), y;
|
|
96
|
+
}
|
|
97
|
+
return r && typeof r == "object" ? z(r, {
|
|
98
|
+
...a,
|
|
99
|
+
path: c
|
|
100
|
+
}) : r;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return s.set(e, o), o;
|
|
104
|
+
}
|
|
105
|
+
function M(t, a) {
|
|
106
|
+
return z(t, {
|
|
107
|
+
...a,
|
|
108
|
+
ssr: a?.ssr ?? !0,
|
|
109
|
+
verbosePath: a?.verbosePath ?? "",
|
|
110
|
+
cache: {
|
|
111
|
+
proxy: /* @__PURE__ */ new WeakMap(),
|
|
112
|
+
leaf: /* @__PURE__ */ new WeakMap()
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
m as defineState,
|
|
118
|
+
M as setupStorage
|
|
119
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";const h=require("react/jsx-runtime"),r=require("react"),s=require("./utils-33NqsZoR.js"),c=r.createContext({});function S(o={}){const t=r.useContext(c);return o.verbose&&console.log(`[Store uid]: ${t.uid}`),t}class u{#t;#e;constructor(t){this.uid=Math.random().toString(36).slice(2),this.#t=t??{},this.#e=new Set}subscribe(t){return this.#e.add(t),()=>this.#e.delete(t)}getClientSnapshot(){return this.#t}getServerSnapshot(){return this.#t}get(t){return s.get(this.#t,t)}set(t,e){const n=s.get(this.#t,t),a=s.isTransformer(e)?e(n):e,i={...this.#t};s.set(i,t,a),this.#t=i,this.#e.forEach(l=>l())}}function b(o){const{children:t,value:e}=o,[n]=r.useState(()=>new u(e));return h.jsx(c.Provider,{value:n,children:t})}exports.StateVocabClientProvider=b;exports.VocabStore=u;exports.useStateVocabClientContext=S;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as u } from "react/jsx-runtime";
|
|
3
|
+
import { createContext as h, useContext as l, useState as S } from "react";
|
|
4
|
+
import { g as n, e as d, s as b } from "./utils-t8tYdd6B.mjs";
|
|
5
|
+
const i = h({});
|
|
6
|
+
function V(s = {}) {
|
|
7
|
+
const t = l(i);
|
|
8
|
+
return s.verbose && console.log(`[Store uid]: ${t.uid}`), t;
|
|
9
|
+
}
|
|
10
|
+
class f {
|
|
11
|
+
#t;
|
|
12
|
+
#e;
|
|
13
|
+
constructor(t) {
|
|
14
|
+
this.uid = Math.random().toString(36).slice(2), this.#t = t ?? {}, this.#e = /* @__PURE__ */ new Set();
|
|
15
|
+
}
|
|
16
|
+
subscribe(t) {
|
|
17
|
+
return this.#e.add(t), () => this.#e.delete(t);
|
|
18
|
+
}
|
|
19
|
+
getClientSnapshot() {
|
|
20
|
+
return this.#t;
|
|
21
|
+
}
|
|
22
|
+
getServerSnapshot() {
|
|
23
|
+
return this.#t;
|
|
24
|
+
}
|
|
25
|
+
get(t) {
|
|
26
|
+
return n(this.#t, t);
|
|
27
|
+
}
|
|
28
|
+
set(t, e) {
|
|
29
|
+
const o = n(this.#t, t), c = d(e) ? e(o) : e, r = { ...this.#t };
|
|
30
|
+
b(r, t, c), this.#t = r, this.#e.forEach((a) => a());
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function g(s) {
|
|
34
|
+
const { children: t, value: e } = s, [o] = S(() => new f(e));
|
|
35
|
+
return /* @__PURE__ */ u(i.Provider, { value: o, children: t });
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
g as S,
|
|
39
|
+
f as V,
|
|
40
|
+
V as u
|
|
41
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("react/jsx-runtime"),v=require("react"),b=require("node:async_hooks"),y=require("./provider.client--OPImdtY.js"),u=require("./utils-33NqsZoR.js");function g(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const r in e)if(r!=="default"){const n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:()=>e[r]})}}return t.default=e,Object.freeze(t)}const P=g(b),l=e=>{if(v.useState)throw new Error(`${e} only intended for Server Components`)};let i=null;try{l("StateVocabServerContext"),i=new P.AsyncLocalStorage}catch{i=null}const h=({value:e})=>(i?.enterWith({value:e}),null),j=({children:e})=>e,x=async e=>{l("StateVocabServerContext.Provider");const{value:t,children:r}=e;return c.jsxs(c.Fragment,{children:[c.jsx(h,{value:t}),r]})},V=()=>(l("getStateVocab"),i?.getStore()?.value),m={Provider:i?x:j},p=({value:e,children:t})=>c.jsx(m.Provider,{value:e,children:t});function w(e){const{children:t,value:r={}}=e;return c.jsx(p,{value:r,children:c.jsx(y.StateVocabClientProvider,{value:r,children:t})})}function O(){const e=this[u.STATE_PATH],t=V();if(!t)throw new Error("Make sure your component is wrapped in StateVocabProvider");return u.get(t,e)}function f(e,t){return new Proxy(e,{get(r,n,o){return n in t?Reflect.get(t,n,o):Reflect.get(r,n,o)},has(r,n){return n in t||n in r}})}function k(e){return typeof e=="object"&&e!==null&&"serverSlot"in e}function S(e,t){const r={};for(const n in e){const o=e[n];if(k(o))r[n]=o.serverSlot({getState(...s){return O.apply(this,s)}}),delete r[n].serverSlot,delete r[n].clientSlot;else if(o!==null&&typeof o=="object"){const s=a=>t({[n]:a}),d=S(o,s);r[n]=f(a=>s(a),d)}else r[n]=o}return f(n=>t(n),r)}function C(e){return S(e,t=>t)}exports.StateVocabProvider=w;exports.serverify=C;
|