@yakocloud/state-vocab 4.1.0 → 4.3.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 +67 -52
- package/dist/client.cjs.js +1 -1
- package/dist/client.es.js +16 -15
- package/dist/provider-client-DA53zfcT.js +1 -0
- package/dist/{provider-client-D0BmMWcY.mjs → provider-client-DLa3_Ms9.mjs} +1 -0
- package/dist/server.cjs.js +1 -1
- package/dist/server.es.js +50 -80
- package/dist/types/context.server.d.ts +2 -10
- package/dist/types/setup.client.d.ts +2 -18
- package/dist/types/setup.client.types.d.ts +29 -0
- package/dist/types/setup.client.utils.d.ts +3 -1
- package/dist/types/setup.d.ts +1 -13
- package/dist/types/setup.server.d.ts +4 -27
- package/dist/types/setup.server.types.d.ts +25 -0
- package/dist/types/setup.server.utils.d.ts +3 -0
- package/dist/types/setup.types.d.ts +12 -15
- package/dist/types/state.d.ts +1 -1
- package/dist/types/utils.d.ts +1 -0
- package/package.json +1 -1
- package/dist/provider-client-CAHe8VnM.js +0 -1
- package/dist/types/provider.d.ts +0 -8
- package/dist/types/provider.server.d.ts +0 -6
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ npm install @yakocloud/state-vocab react react-dom
|
|
|
117
117
|
|
|
118
118
|
## Quick Start
|
|
119
119
|
|
|
120
|
-
Define your storage tree once (`setupStorage`/`defineState`), call `clientify` to attach React hooks. If you use
|
|
120
|
+
Define your storage tree once (`setupStorage`/`defineState`), call `clientify` to attach React hooks. If you use React Server Components (RSC), wrap the relevant subtree with `StateVocabProvider` and call `serverify` to attach server-side `.getState()` methods. Use state anywhere in the tree.
|
|
121
121
|
|
|
122
122
|
Library SSR-requirements:
|
|
123
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.
|
|
@@ -366,28 +366,14 @@ export const storage = setupStorage({
|
|
|
366
366
|
})
|
|
367
367
|
```
|
|
368
368
|
|
|
369
|
-
**2. Create
|
|
370
|
-
|
|
371
|
-
Each storage tree needs its own React context so that multiple independent providers can coexist on the same page without their state bleeding into each other.
|
|
372
|
-
|
|
373
|
-
```ts
|
|
374
|
-
// storage.context.client.ts ("use client")
|
|
375
|
-
"use client"
|
|
376
|
-
|
|
377
|
-
import { createContext } from 'react'
|
|
378
|
-
|
|
379
|
-
export const StorageClientContext = createContext({})
|
|
380
|
-
```
|
|
369
|
+
**2. Create server and client storage handles:**
|
|
381
370
|
|
|
382
371
|
```ts
|
|
383
372
|
// storage.server.ts
|
|
384
373
|
import { storage } from '@/storage'
|
|
385
374
|
import { serverify } from '@yakocloud/state-vocab/server'
|
|
386
|
-
import { StorageClientContext } from '@/storage.context.client'
|
|
387
375
|
|
|
388
|
-
export const serverStorage = serverify(storage
|
|
389
|
-
clientContext: StorageClientContext,
|
|
390
|
-
})
|
|
376
|
+
export const serverStorage = serverify(storage)
|
|
391
377
|
```
|
|
392
378
|
|
|
393
379
|
```ts
|
|
@@ -396,11 +382,8 @@ export const serverStorage = serverify(storage, {
|
|
|
396
382
|
|
|
397
383
|
import { storage } from '@/storage'
|
|
398
384
|
import { clientify } from '@yakocloud/state-vocab/client'
|
|
399
|
-
import { StorageClientContext } from '@/storage.context.client'
|
|
400
385
|
|
|
401
|
-
export const clientStorage = clientify(storage
|
|
402
|
-
clientContext: StorageClientContext,
|
|
403
|
-
})
|
|
386
|
+
export const clientStorage = clientify(storage)
|
|
404
387
|
```
|
|
405
388
|
|
|
406
389
|
**3. Seed initial state in the server component and read it in Server and Client children:**
|
|
@@ -456,41 +439,67 @@ export default function UserInfoClient() {
|
|
|
456
439
|
|
|
457
440
|
#### Multiple independent storage trees
|
|
458
441
|
|
|
459
|
-
Because each `serverify`/`clientify` pair is bound to its own context, you can have multiple independent providers active at the same time — for example, a layout-level store and a page-level store — without them interfering with each other:
|
|
442
|
+
Because each `serverify`/`clientify` pair is bound to its own React context, you can have multiple independent providers active at the same time — for example, a layout-level store and a page-level store — without them interfering with each other. Pass a `clientContext` to link the server and client sides of each tree:
|
|
460
443
|
|
|
461
444
|
```ts
|
|
462
|
-
// layout.context.client.ts ("use client")
|
|
463
|
-
export const LayoutClientContext = createContext({})
|
|
464
|
-
|
|
465
|
-
// page.context.client.ts ("use client")
|
|
466
|
-
export const PageClientContext = createContext({})
|
|
467
|
-
|
|
468
445
|
// layout.storage.server.ts
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
})
|
|
446
|
+
import { createContext } from 'react'
|
|
447
|
+
export const LayoutClientContext = createContext({})
|
|
448
|
+
export const layoutServerStorage = serverify(layoutStorage, { clientContext: LayoutClientContext })
|
|
472
449
|
|
|
473
450
|
// layout.storage.client.ts ("use client")
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
})
|
|
451
|
+
"use client"
|
|
452
|
+
export const layoutClientStorage = clientify(layoutStorage, { clientContext: LayoutClientContext })
|
|
477
453
|
|
|
478
454
|
// page.storage.server.ts
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
})
|
|
455
|
+
import { createContext } from 'react'
|
|
456
|
+
export const PageClientContext = createContext({})
|
|
457
|
+
export const pageServerStorage = serverify(pageStorage, { clientContext: PageClientContext })
|
|
482
458
|
|
|
483
459
|
// page.storage.client.ts ("use client")
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
})
|
|
460
|
+
"use client"
|
|
461
|
+
export const pageClientStorage = clientify(pageStorage, { clientContext: PageClientContext })
|
|
487
462
|
```
|
|
488
463
|
|
|
489
464
|
Each `StateVocabProvider` wraps its own subtree; a component that calls `pageClientStorage.user.name.useState()` reads only from the nearest `pageServerStorage.StateVocabProvider`, not from the layout provider.
|
|
490
465
|
|
|
466
|
+
#### Next.js App Router: layout and page are isolated contexts
|
|
467
|
+
|
|
468
|
+
In Next.js App Router, **layouts do not re-render during client-side navigation between pages**. Layout and page run as independent React render passes — the layout renders once on entry and stays mounted; the page renders fresh on every navigation.
|
|
469
|
+
|
|
470
|
+
Because `React.cache()` (which backs the per-request server store) is scoped to a single render pass, layout and page never share the same cache scope:
|
|
471
|
+
|
|
472
|
+
- A page server component **cannot** call `layoutServerStorage.someField.getState()` — the layout's `StateVocabProvider` ran in a separate pass, so its vocab is not in the page's cache scope.
|
|
473
|
+
- A layout server component **cannot** call `pageServerStorage.someField.getState()` for the same reason.
|
|
474
|
+
- **Each storage tree must be both seeded and consumed within the same route segment.**
|
|
475
|
+
|
|
476
|
+
```tsx
|
|
477
|
+
// ✅ Correct — layout seeds and reads from its own storage
|
|
478
|
+
// app/(dashboard)/layout.tsx
|
|
479
|
+
const LayoutProvider = layoutServerStorage.StateVocabProvider
|
|
480
|
+
export default async function Layout({ children }) {
|
|
481
|
+
return (
|
|
482
|
+
<LayoutProvider value={{ session: { id: 123 } }}>
|
|
483
|
+
<LayoutHeader /> {/* ← can call layoutServerStorage.session.getState() */}
|
|
484
|
+
{children}
|
|
485
|
+
</LayoutProvider>
|
|
486
|
+
)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ❌ Wrong — page cannot reach layout's storage
|
|
490
|
+
// app/(dashboard)/page.tsx
|
|
491
|
+
export default async function Page() {
|
|
492
|
+
const session = layoutServerStorage.session.getState() // throws — not in layout's render pass
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
If a page needs data that the layout already fetches (e.g., the current user session), fetch it independently in the page, pass it via URL params, or use a separate mechanism — do not attempt to share it through other state-vocab server storage across route segments.
|
|
497
|
+
|
|
498
|
+
This constraint is **server-only**. On the client, there is no such limitation — client components can freely call `.useState()` from any storage (`layoutClientStorage`, `pageClientStorage`, or any other) regardless of which route segment they live in.
|
|
499
|
+
|
|
491
500
|
#### `serverify(storage)`
|
|
492
501
|
|
|
493
|
-
Converts a storage tree into its server-side counterpart. Each leaf gains a `.getState()` method that reads the value
|
|
502
|
+
Converts a storage tree into its server-side counterpart. Each leaf gains a synchronous `.getState()` method that reads the value injected by the nearest `StateVocabProvider`. Each namespace node (including the root) gains a `.seed()` method that returns the input wrapped under its full ancestor path. The result also exposes `StateVocabProvider`.
|
|
494
503
|
|
|
495
504
|
**`.seed()` syntax:**
|
|
496
505
|
|
|
@@ -508,7 +517,7 @@ serverStorage.person.address.seed({ city: 'NY' })
|
|
|
508
517
|
// → { person: { address: { city: 'NY' } } }
|
|
509
518
|
```
|
|
510
519
|
|
|
511
|
-
**`node.getState()`** reads the value for that leaf
|
|
520
|
+
**`node.getState()`** synchronously reads the value for that leaf. Must be called within a React render context (i.e., inside a component that is a descendant of `StateVocabProvider`). Throws if called outside a provider scope or outside a React render context.
|
|
512
521
|
|
|
513
522
|
#### `clientify(storage)`
|
|
514
523
|
|
|
@@ -745,28 +754,34 @@ import { StateVocabClientProvider } from '@yakocloud/state-vocab/client'
|
|
|
745
754
|
</StateVocabClientProvider>
|
|
746
755
|
```
|
|
747
756
|
|
|
748
|
-
### `serverify<T>(storage: T, options)`
|
|
757
|
+
### `serverify<T>(storage: T, options?)`
|
|
749
758
|
|
|
750
759
|
Converts a storage tree to its server-side counterpart. Available from `@yakocloud/state-vocab/server`.
|
|
751
760
|
|
|
752
761
|
| Option | Type | Description |
|
|
753
762
|
|---|---|---|
|
|
754
|
-
| `clientContext` | `Context<object
|
|
763
|
+
| `clientContext` | `Context<object> \| undefined` | A React context created with `createContext({})`. Pass the same context to `clientify` when multiple independent storage trees coexist on the same page. Omit for single-tree setups. |
|
|
764
|
+
|
|
765
|
+
The result exposes:
|
|
755
766
|
|
|
756
|
-
|
|
767
|
+
- **`StateVocabProvider`** — server component that accepts a `value` prop and seeds the per-request store via `React.cache()`.
|
|
768
|
+
- **`node.getState()`** — synchronously reads the value for that leaf. Must be called within a React render context (descendant of `StateVocabProvider`). Throws if called outside a provider scope or outside a render context.
|
|
769
|
+
- **`node.seed(input)`** — wraps `input` under the node's ancestor path, returning a plain object ready to pass as the `value` prop.
|
|
757
770
|
|
|
758
771
|
```ts
|
|
759
772
|
import { serverify } from '@yakocloud/state-vocab/server'
|
|
760
|
-
import { MyClientContext } from '@/storage.context.client'
|
|
761
773
|
|
|
774
|
+
const serverStorage = serverify(storage)
|
|
775
|
+
// or, for multiple independent trees:
|
|
762
776
|
const serverStorage = serverify(storage, { clientContext: MyClientContext })
|
|
763
777
|
|
|
764
|
-
const { StateVocabProvider } = serverStorage
|
|
778
|
+
const { StateVocabProvider } = serverStorage
|
|
765
779
|
|
|
766
|
-
|
|
767
|
-
serverStorage.user.
|
|
768
|
-
serverStorage.
|
|
769
|
-
serverStorage.seed({
|
|
780
|
+
// In a Server Component:
|
|
781
|
+
serverStorage.user.name.getState() // reads "user.name" (sync)
|
|
782
|
+
serverStorage.user.seed({ name: 'Alice' }) // → { user: { name: 'Alice' } }
|
|
783
|
+
serverStorage.person.address.seed({ city: 'NY' }) // → { person: { address: { city: 'NY' } } }
|
|
784
|
+
serverStorage.seed({ user: { name: 'Alice' } }) // → { user: { name: 'Alice' } } (identity)
|
|
770
785
|
```
|
|
771
786
|
|
|
772
787
|
### `clientify<T>(storage: T, options?)`
|
|
@@ -775,7 +790,7 @@ Converts a storage tree to its client-side counterpart. Available from `@yakoclo
|
|
|
775
790
|
|
|
776
791
|
| Option | Type | Description |
|
|
777
792
|
|---|---|---|
|
|
778
|
-
| `clientContext` | `Context<object> \| undefined` | The React context
|
|
793
|
+
| `clientContext` | `Context<object> \| undefined` | The React context used to isolate this storage tree. Must match the `clientContext` passed to `serverify` for the same tree. Omit for single-tree setups. |
|
|
779
794
|
|
|
780
795
|
Each leaf gains `.useState()` and `.useInitialState()`. The tree structure mirrors the original.
|
|
781
796
|
|
package/dist/client.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("react"),v=require("./constants-CbsduCZ7.js"),a=require("./utils-0CTNJ4ZE.js"),V=require("./provider-client-
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("react"),v=require("./constants-CbsduCZ7.js"),a=require("./utils-0CTNJ4ZE.js"),V=require("./provider-client-DA53zfcT.js");function D(t,n,s=[]){return f.useMemo(()=>a.debounce(t,n),s)}const y=t=>{const{vocabStore:n,storage:s,statePath:o,value:e,deserialize:l,serialize:c}=t,d=s.getItem(o);d===null?a.isValueDefined(e)&&s.setItem(o,c(e)):n.set(o,l(d))};function I(t){return typeof t=="object"&&t!==null&&"clientSlot"in t}const z=typeof window>"u",m=z?f.useEffect:f.useLayoutEffect,A=new V.VocabStore,P="Make sure your component is wrapped in StateVocabProvider (RSC) or disable ssr option in setupStorage for SPA (RCC only)";function p(t){const n=z?void 0:a.valueOrFactory(t.storage),s=a.valueOrFactory(t.defaultValue),o=t.bidirectional,e=this[v.STATE_PATH],l=this[v.STATE_VERBOSE],c=this[v.STATE_VERBOSE_PATH],d=this[v.STATE_SSR];let i=V.useStateVocabClientContext({clientContext:t.clientContext,verbose:l});if(!(i instanceof V.VocabStore)){if(d)throw new Error(P);i=A}const S=t.serialize??JSON.stringify,u=t.deserialize??JSON.parse,E=D(t.onSet??(()=>{}),t.delayedSet,[]),g=f.useRef(void 0),R=f.useRef(!1);if(!R.current){R.current=!0;let r=i.get(e);a.isValueDefined(r)||(r=s,a.isValueDefined(r)&&i.set(e,r)),!d&&n&&y({vocabStore:i,storage:n,statePath:e,value:r,serialize:S,deserialize:u})}const C=f.useSyncExternalStore(i.subscribe.bind(i),i.getClientSnapshot.bind(i),i.getServerSnapshot.bind(i));if(l)if(c){const r=a.get(C,c);r&&a.logStyled(r)}else a.logStyled(C);const h=a.get(C,e,s);g.current=h,m(()=>{!d||!n||y({vocabStore:i,storage:n,statePath:e,value:h,serialize:S,deserialize:u})},[]);const w=f.useEffectEvent(r=>{if(r.key!==e)return;const b=r.newValue,T=(b===null?null:u(b))??s;a.isValueDefined(T)&&(i.set(e,T),E(T,g.current))});f.useEffect(()=>{if(o)return window.addEventListener("storage",w),()=>window.removeEventListener("storage",w)},[o]);const x=f.useCallback(r=>{const b=a.isTransformer(r)?r(g.current):r;i.set(e,b),E(b,g.current),n&&n.setItem(e,S(b))},[i,e,E,n,S]),_=f.useCallback(()=>{const r=s;if(!a.isValueDefined(r)){n?.removeItem(e);return}i.set(e,r),E(r,g.current),n&&n.setItem(e,S(r))},[s,i,e,E,n,S]);return[h,x,_]}function k(t){const n=z?void 0:a.valueOrFactory(t.storage),s=a.valueOrFactory(t.defaultValue),o=this[v.STATE_PATH],e=this[v.STATE_VERBOSE],l=this[v.STATE_SSR];let c=V.useStateVocabClientContext({clientContext:t.clientContext,verbose:e});if(!(c instanceof V.VocabStore)){if(l)throw new Error(P);c=A}const d=t.serialize??JSON.stringify,i=t.deserialize??JSON.parse,S=f.useRef(!1);let u;S.current||(S.current=!0,u=c.get(o),a.isValueDefined(u)||(u=s,a.isValueDefined(u)&&c.set(o,u)),!l&&n&&y({vocabStore:c,storage:n,statePath:o,value:u,serialize:d,deserialize:i})),m(()=>{!l||!n||y({vocabStore:c,storage:n,statePath:o,value:u,serialize:d,deserialize:i})},[])}function O(t,n={}){const s={};for(const o in t){const e=t[o];I(e)?(s[o]=e.clientSlot({useState(l){return p.apply(this,[{clientContext:n.clientContext,...l}])},useInitialState(l){k.apply(this,[{clientContext:n.clientContext,...l}])}}),delete s[o].serverSlot,delete s[o].clientSlot):e!==null&&typeof e=="object"?s[o]=O(e,n):s[o]=e}return s}exports.StateVocabClientProvider=V.StateVocabClientProvider;exports.clientify=O;
|
package/dist/client.es.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useMemo as j, useRef as
|
|
2
|
+
import { useMemo as j, useRef as C, useSyncExternalStore as B, useEffectEvent as D, useEffect as P, useCallback as p, useLayoutEffect as H } from "react";
|
|
3
3
|
import { S as A, a as I, b as O, c as F } from "./constants-BB1YAX6c.mjs";
|
|
4
4
|
import { a as d, d as G, v as g, g as T, l as x, i as q } from "./utils-xV3x3fTc.mjs";
|
|
5
|
-
import { V as
|
|
6
|
-
import { S as ne } from "./provider-client-
|
|
5
|
+
import { V as y, u as _ } from "./provider-client-DLa3_Ms9.mjs";
|
|
6
|
+
import { S as ne } from "./provider-client-DLa3_Ms9.mjs";
|
|
7
7
|
function K(t, s, i = []) {
|
|
8
8
|
return j(
|
|
9
9
|
() => G(t, s),
|
|
@@ -21,14 +21,18 @@ const E = (t) => {
|
|
|
21
21
|
serialize: l
|
|
22
22
|
} = t, u = i.getItem(o);
|
|
23
23
|
u === null ? d(e) && i.setItem(o, l(e)) : s.set(o, a(u));
|
|
24
|
-
}
|
|
24
|
+
};
|
|
25
25
|
function Q(t) {
|
|
26
|
+
return typeof t == "object" && t !== null && "clientSlot" in t;
|
|
27
|
+
}
|
|
28
|
+
const z = typeof window > "u", k = z ? P : H, J = new y(), L = "Make sure your component is wrapped in StateVocabProvider (RSC) or disable ssr option in setupStorage for SPA (RCC only)";
|
|
29
|
+
function U(t) {
|
|
26
30
|
const s = z ? void 0 : g(t.storage), i = g(t.defaultValue), o = t.bidirectional, e = this[A], a = this[I], l = this[F], u = this[O];
|
|
27
31
|
let r = _({
|
|
28
32
|
clientContext: t.clientContext,
|
|
29
33
|
verbose: a
|
|
30
34
|
});
|
|
31
|
-
if (!(r instanceof
|
|
35
|
+
if (!(r instanceof y)) {
|
|
32
36
|
if (u)
|
|
33
37
|
throw new Error(L);
|
|
34
38
|
r = J;
|
|
@@ -38,7 +42,7 @@ function Q(t) {
|
|
|
38
42
|
}),
|
|
39
43
|
t.delayedSet,
|
|
40
44
|
[]
|
|
41
|
-
), b =
|
|
45
|
+
), b = C(void 0), w = C(!1);
|
|
42
46
|
if (!w.current) {
|
|
43
47
|
w.current = !0;
|
|
44
48
|
let n = r.get(e);
|
|
@@ -100,18 +104,18 @@ function Q(t) {
|
|
|
100
104
|
M
|
|
101
105
|
];
|
|
102
106
|
}
|
|
103
|
-
function
|
|
107
|
+
function W(t) {
|
|
104
108
|
const s = z ? void 0 : g(t.storage), i = g(t.defaultValue), o = this[A], e = this[I], a = this[O];
|
|
105
109
|
let l = _({
|
|
106
110
|
clientContext: t.clientContext,
|
|
107
111
|
verbose: e
|
|
108
112
|
});
|
|
109
|
-
if (!(l instanceof
|
|
113
|
+
if (!(l instanceof y)) {
|
|
110
114
|
if (a)
|
|
111
115
|
throw new Error(L);
|
|
112
116
|
l = J;
|
|
113
117
|
}
|
|
114
|
-
const u = t.serialize ?? JSON.stringify, r = t.deserialize ?? JSON.parse, f =
|
|
118
|
+
const u = t.serialize ?? JSON.stringify, r = t.deserialize ?? JSON.parse, f = C(!1);
|
|
115
119
|
let c;
|
|
116
120
|
f.current || (f.current = !0, c = l.get(o), d(c) || (c = i, d(c) && l.set(o, c)), !a && s && E({
|
|
117
121
|
vocabStore: l,
|
|
@@ -131,22 +135,19 @@ function U(t) {
|
|
|
131
135
|
});
|
|
132
136
|
}, []);
|
|
133
137
|
}
|
|
134
|
-
function W(t) {
|
|
135
|
-
return typeof t == "object" && t !== null && "clientSlot" in t;
|
|
136
|
-
}
|
|
137
138
|
function X(t, s = {}) {
|
|
138
139
|
const i = {};
|
|
139
140
|
for (const o in t) {
|
|
140
141
|
const e = t[o];
|
|
141
|
-
|
|
142
|
+
Q(e) ? (i[o] = e.clientSlot({
|
|
142
143
|
useState(a) {
|
|
143
|
-
return
|
|
144
|
+
return U.apply(this, [{
|
|
144
145
|
clientContext: s.clientContext,
|
|
145
146
|
...a
|
|
146
147
|
}]);
|
|
147
148
|
},
|
|
148
149
|
useInitialState(a) {
|
|
149
|
-
|
|
150
|
+
W.apply(this, [{
|
|
150
151
|
clientContext: s.clientContext,
|
|
151
152
|
...a
|
|
152
153
|
}]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";const h=require("react/jsx-runtime"),c=require("react"),r=require("./utils-0CTNJ4ZE.js"),a=c.createContext({});function S(e){const t=c.useContext(e.clientContext??a);return e.verbose&&console.log(`[Store uid]: ${t.uid}`),t}class u{uid;#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 r.get(this.#t,t)}set(t,n){const s=r.get(this.#t,t),i=r.isTransformer(n)?n(s):n,o={...this.#t};r.set(o,t,i),this.#t=o,this.#e.forEach(l=>l())}}function b(e){const{clientContext:t,value:n,children:s}=e,[i]=c.useState(()=>new u(n)),o=t??a;return h.jsx(o.Provider,{value:i,children:s})}exports.StateVocabClientProvider=b;exports.VocabStore=u;exports.useStateVocabClientContext=S;
|
package/dist/server.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react/jsx-runtime"),u=require("./constants-CbsduCZ7.js"),y=require("react"),S=require("./provider-client-DA53zfcT.js"),d=require("./utils-0CTNJ4ZE.js"),s=y.cache(()=>new Map),f=e=>s().get(e),v=(e,t)=>s().set(e,t);function b(e=""){const t=Symbol("test"),{size:n}=s();if(s().set(t,{}),s().size===n)throw new Error([e,"Start execution only within a React render context (per-request)."].join(" "));s().delete(t)}function x(e){return typeof e=="object"&&e!==null&&"serverSlot"in e}function p(e){const t=this[u.STATE_PATH],n=`Failed access to "${t}".`;b(n);const r=f(e.serverContextKey);if(!r)throw new Error(`${n} Reason: no data. Make sure your component is wrapped in StateVocabProvider.`);return d.get(r,t)}function i(e,t){const n={};for(const r in e){const o=e[r];if(x(o))n[r]=o.serverSlot({getState(){return p.apply(this,[{serverContextKey:t.serverContextKey}])}}),delete n[r].serverSlot,delete n[r].clientSlot;else if(o!==null&&typeof o=="object"){const c=a=>t.wrap({[r]:a});n[r]=i(o,{serverContextKey:t.serverContextKey,wrap:c})}else n[r]=o}return n.seed=r=>t.wrap(r),n}function w(e,t){t??={};const n=Object.keys(e).slice(0,3).join("-"),r=Symbol(n);return{...i(e,{serverContextKey:r,wrap:o=>o}),StateVocabProvider({children:o,value:c}){return c??={},v(r,c),l.jsx(S.StateVocabClientProvider,{clientContext:t.clientContext,value:c,children:o})}}}exports.serverify=w;
|
package/dist/server.es.js
CHANGED
|
@@ -1,104 +1,74 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { S
|
|
3
|
-
import
|
|
4
|
-
import { S as
|
|
1
|
+
import { jsx as l } from "react/jsx-runtime";
|
|
2
|
+
import { S } from "./constants-BB1YAX6c.mjs";
|
|
3
|
+
import { cache as f } from "react";
|
|
4
|
+
import { S as u } from "./provider-client-DLa3_Ms9.mjs";
|
|
5
5
|
import { g as y } from "./utils-xV3x3fTc.mjs";
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}, m = (e) => {
|
|
16
|
-
const { serverContextKey: o, value: t, children: r } = e;
|
|
17
|
-
return /* @__PURE__ */ c(
|
|
18
|
-
b.Provider,
|
|
19
|
-
{
|
|
20
|
-
serverContextKey: o,
|
|
21
|
-
value: t,
|
|
22
|
-
children: r
|
|
23
|
-
}
|
|
24
|
-
);
|
|
25
|
-
};
|
|
26
|
-
function h(e) {
|
|
27
|
-
const {
|
|
28
|
-
children: o,
|
|
29
|
-
serverContextKey: t,
|
|
30
|
-
clientContext: r,
|
|
31
|
-
value: n = {}
|
|
32
|
-
} = e;
|
|
33
|
-
return /* @__PURE__ */ c(
|
|
34
|
-
m,
|
|
35
|
-
{
|
|
36
|
-
serverContextKey: t,
|
|
37
|
-
value: n,
|
|
38
|
-
children: /* @__PURE__ */ c(
|
|
39
|
-
f,
|
|
40
|
-
{
|
|
41
|
-
clientContext: r,
|
|
42
|
-
value: n,
|
|
43
|
-
children: o
|
|
44
|
-
}
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
const P = "Make sure your component is wrapped in StateVocabProvider";
|
|
50
|
-
function w(e) {
|
|
51
|
-
const o = this[u], t = x(e.serverContextKey);
|
|
52
|
-
if (!t)
|
|
53
|
-
throw new Error(P);
|
|
54
|
-
return y(t, o);
|
|
6
|
+
const s = f(() => /* @__PURE__ */ new Map()), p = (e) => s().get(e), x = (e, t) => s().set(e, t);
|
|
7
|
+
function d(e = "") {
|
|
8
|
+
const t = /* @__PURE__ */ Symbol("test"), { size: o } = s();
|
|
9
|
+
if (s().set(t, {}), s().size === o)
|
|
10
|
+
throw new Error([
|
|
11
|
+
e,
|
|
12
|
+
"Start execution only within a React render context (per-request)."
|
|
13
|
+
].join(" "));
|
|
14
|
+
s().delete(t);
|
|
55
15
|
}
|
|
56
|
-
function
|
|
16
|
+
function m(e) {
|
|
57
17
|
return typeof e == "object" && e !== null && "serverSlot" in e;
|
|
58
18
|
}
|
|
59
|
-
function
|
|
60
|
-
const t = {}
|
|
19
|
+
function b(e) {
|
|
20
|
+
const t = this[S], o = `Failed access to "${t}".`;
|
|
21
|
+
d(o);
|
|
22
|
+
const r = p(e.serverContextKey);
|
|
23
|
+
if (!r)
|
|
24
|
+
throw new Error(
|
|
25
|
+
`${o} Reason: no data. Make sure your component is wrapped in StateVocabProvider.`
|
|
26
|
+
);
|
|
27
|
+
return y(r, t);
|
|
28
|
+
}
|
|
29
|
+
function i(e, t) {
|
|
30
|
+
const o = {};
|
|
61
31
|
for (const r in e) {
|
|
62
32
|
const n = e[r];
|
|
63
|
-
if (
|
|
64
|
-
|
|
33
|
+
if (m(n))
|
|
34
|
+
o[r] = n.serverSlot({
|
|
65
35
|
getState() {
|
|
66
|
-
return
|
|
67
|
-
serverContextKey:
|
|
36
|
+
return b.apply(this, [{
|
|
37
|
+
serverContextKey: t.serverContextKey
|
|
68
38
|
}]);
|
|
69
39
|
}
|
|
70
|
-
}), delete
|
|
40
|
+
}), delete o[r].serverSlot, delete o[r].clientSlot;
|
|
71
41
|
else if (n !== null && typeof n == "object") {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
serverContextKey:
|
|
75
|
-
wrap:
|
|
42
|
+
const c = (a) => t.wrap({ [r]: a });
|
|
43
|
+
o[r] = i(n, {
|
|
44
|
+
serverContextKey: t.serverContextKey,
|
|
45
|
+
wrap: c
|
|
76
46
|
});
|
|
77
47
|
} else
|
|
78
|
-
|
|
48
|
+
o[r] = n;
|
|
79
49
|
}
|
|
80
|
-
return
|
|
50
|
+
return o.seed = (r) => t.wrap(r), o;
|
|
81
51
|
}
|
|
82
|
-
function
|
|
83
|
-
|
|
52
|
+
function K(e, t) {
|
|
53
|
+
t ??= {};
|
|
54
|
+
const o = Object.keys(e).slice(0, 3).join("-"), r = Symbol(o);
|
|
84
55
|
return {
|
|
85
|
-
...
|
|
86
|
-
serverContextKey:
|
|
87
|
-
wrap: (
|
|
56
|
+
...i(e, {
|
|
57
|
+
serverContextKey: r,
|
|
58
|
+
wrap: (n) => n
|
|
88
59
|
}),
|
|
89
|
-
StateVocabProvider({ children:
|
|
90
|
-
return /* @__PURE__ */
|
|
91
|
-
|
|
60
|
+
StateVocabProvider({ children: n, value: c }) {
|
|
61
|
+
return c ??= {}, x(r, c), /* @__PURE__ */ l(
|
|
62
|
+
u,
|
|
92
63
|
{
|
|
93
|
-
clientContext:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
children: r
|
|
64
|
+
clientContext: t.clientContext,
|
|
65
|
+
value: c,
|
|
66
|
+
children: n
|
|
97
67
|
}
|
|
98
68
|
);
|
|
99
69
|
}
|
|
100
70
|
};
|
|
101
71
|
}
|
|
102
72
|
export {
|
|
103
|
-
|
|
73
|
+
K as serverify
|
|
104
74
|
};
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { PropsWithChildren, ReactNode } from "react";
|
|
3
1
|
import type { Vocab } from "./state.types";
|
|
2
|
+
export declare const getRequestStore: () => Map<symbol, Vocab>;
|
|
4
3
|
export declare const getStateVocab: (serverContextKey: symbol) => Vocab | undefined;
|
|
5
|
-
export declare const
|
|
6
|
-
Provider: (({ children }: PropsWithChildren<{
|
|
7
|
-
value?: Vocab;
|
|
8
|
-
}>) => ReactNode) | ((props: PropsWithChildren<{
|
|
9
|
-
serverContextKey: symbol;
|
|
10
|
-
value: Vocab;
|
|
11
|
-
}>) => Promise<React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | (string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined)>);
|
|
12
|
-
};
|
|
4
|
+
export declare const setStateVocab: (serverContextKey: symbol, value: Vocab) => Map<symbol, Vocab>;
|
|
@@ -1,21 +1,5 @@
|
|
|
1
1
|
import { type Context } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
import VocabStore from "./store";
|
|
4
|
-
import type { UseInitialStateOptions, UseStateOptions } from "./setup.types";
|
|
5
|
-
declare function useState<V>(this: VocabThis, options: UseStateOptions<V> & {
|
|
6
|
-
clientContext: Context<VocabStore> | undefined;
|
|
7
|
-
}): readonly [V, (nextValue: ValueOrTransformer<V>) => void, () => void];
|
|
8
|
-
type Placeholder<V> = {
|
|
9
|
-
useState(options?: UseStateOptions<V>): ReturnType<typeof useState<V>>;
|
|
10
|
-
useInitialState(options?: UseInitialStateOptions<V>): void;
|
|
11
|
-
};
|
|
12
|
-
type Slot<V> = {
|
|
13
|
-
clientSlot(input: Placeholder<V>): Placeholder<V>;
|
|
14
|
-
};
|
|
15
|
-
type Clientified<R> = R extends Slot<infer TValue> ? Placeholder<TValue> : R extends object ? {
|
|
16
|
-
[K in keyof R]: Clientified<R[K]>;
|
|
17
|
-
} : R;
|
|
2
|
+
import type { ClientifyResult } from "./setup.client.types";
|
|
18
3
|
export declare function clientify<R extends object>(tree: R, clientifyOptions?: {
|
|
19
4
|
clientContext?: Context<object>;
|
|
20
|
-
}):
|
|
21
|
-
export {};
|
|
5
|
+
}): ClientifyResult<R>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Deserialize, Serialize, ValueOrFactory, ValueOrTransformer } from "./state.types";
|
|
2
|
+
export type UseStateOptions<V> = {
|
|
3
|
+
defaultValue?: ValueOrFactory<V>;
|
|
4
|
+
delayedSet?: number;
|
|
5
|
+
bidirectional?: true;
|
|
6
|
+
onSet?(nextValue: NoInfer<V>, prevValue: NoInfer<V>): void;
|
|
7
|
+
storage?: unknown;
|
|
8
|
+
serialize?: Serialize<V>;
|
|
9
|
+
deserialize?: Deserialize<V>;
|
|
10
|
+
};
|
|
11
|
+
type UseStateResult<V> = readonly [V, (nextValue: ValueOrTransformer<V>) => void, () => void];
|
|
12
|
+
export type UseInitialStateOptions<V> = {
|
|
13
|
+
defaultValue?: ValueOrFactory<V>;
|
|
14
|
+
storage?: unknown;
|
|
15
|
+
serialize?: Serialize<V>;
|
|
16
|
+
deserialize?: Deserialize<V>;
|
|
17
|
+
};
|
|
18
|
+
type Placeholder<V> = {
|
|
19
|
+
useState(options?: UseStateOptions<V>): UseStateResult<V>;
|
|
20
|
+
useInitialState(options?: UseInitialStateOptions<V>): void;
|
|
21
|
+
};
|
|
22
|
+
export type ClientSlot<V> = {
|
|
23
|
+
clientSlot(input: Placeholder<V>): Placeholder<V>;
|
|
24
|
+
};
|
|
25
|
+
type Clientified<R> = R extends ClientSlot<infer V> ? Placeholder<V> : R extends object ? {
|
|
26
|
+
[K in keyof R]: Clientified<R[K]>;
|
|
27
|
+
} : R;
|
|
28
|
+
export type ClientifyResult<R> = Clientified<R>;
|
|
29
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { ClientSlot } from "./setup.client.types";
|
|
2
|
+
import type { Deserialize, Serialize } from "./state.types";
|
|
1
3
|
import { type DependencyList } from "react";
|
|
2
4
|
import VocabStore from "./store";
|
|
3
|
-
import type { Deserialize, Serialize } from "./state.types";
|
|
4
5
|
export declare function useDebounce<T extends (...args: never[]) => unknown>(effect: T, wait: number | undefined, deps?: DependencyList): (...args: Parameters<T>) => void;
|
|
5
6
|
export declare const sync: <V>(options: {
|
|
6
7
|
vocabStore: VocabStore;
|
|
@@ -10,3 +11,4 @@ export declare const sync: <V>(options: {
|
|
|
10
11
|
deserialize: Deserialize<V>;
|
|
11
12
|
value: V | undefined;
|
|
12
13
|
}) => void;
|
|
14
|
+
export declare function isClientSlot(value: unknown): value is ClientSlot<unknown>;
|
package/dist/types/setup.d.ts
CHANGED
|
@@ -1,14 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Path<T, Prefix extends string = ""> = {
|
|
3
|
-
[K in keyof T & string]: T[K] extends object ? T[K] extends {
|
|
4
|
-
[STATE_DEFINITION]: unknown;
|
|
5
|
-
} ? `${Prefix}${K}` : `${Prefix}${K}` | Path<T[K], `${Prefix}${K}.`> : `${Prefix}${K}`;
|
|
6
|
-
}[keyof T & string];
|
|
7
|
-
type InjectPathsOptions<T extends object> = {
|
|
8
|
-
path: string;
|
|
9
|
-
verbose: boolean;
|
|
10
|
-
verbosePath: Path<T>;
|
|
11
|
-
ssr: boolean;
|
|
12
|
-
};
|
|
1
|
+
import type { InjectPathsOptions } from "./setup.types";
|
|
13
2
|
export declare function setupStorage<T extends object>(tree: T, options?: Partial<Omit<InjectPathsOptions<T>, "path">>): T;
|
|
14
|
-
export {};
|
|
@@ -1,28 +1,5 @@
|
|
|
1
|
-
import type { Context
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
type Slot<V> = {
|
|
7
|
-
serverSlot(input: Placeholder<V>): Placeholder<V>;
|
|
8
|
-
};
|
|
9
|
-
type ServerifiedValue<R> = {
|
|
10
|
-
[K in keyof R]?: R[K] extends Slot<infer V> ? V : R[K] extends object ? ServerifiedValue<R[K]> : R[K];
|
|
11
|
-
};
|
|
12
|
-
type Serverified<R> = R extends Slot<infer TValue> ? Placeholder<TValue> : R extends object ? {
|
|
13
|
-
seed(input: ServerifiedValue<R>): Vocab;
|
|
14
|
-
} & {
|
|
15
|
-
[K in keyof R]: Serverified<R[K]>;
|
|
16
|
-
} : R;
|
|
17
|
-
type ServerifyResult<R extends object> = {
|
|
18
|
-
seed(input: ServerifiedValue<R>): Vocab;
|
|
19
|
-
StateVocabProvider(props: PropsWithChildren<{
|
|
20
|
-
value?: ServerifiedValue<R>;
|
|
21
|
-
}>): Promise<ReactNode>;
|
|
22
|
-
} & {
|
|
23
|
-
[K in keyof R]: Serverified<R[K]>;
|
|
24
|
-
};
|
|
25
|
-
export declare function serverify<R extends object>(tree: R, serverifyOptions: {
|
|
26
|
-
clientContext: Context<object>;
|
|
1
|
+
import type { Context } from "react";
|
|
2
|
+
import type { ServerifyResult } from "./setup.server.types";
|
|
3
|
+
export declare function serverify<R extends object>(tree: R, serverifyOptions?: {
|
|
4
|
+
clientContext?: Context<object>;
|
|
27
5
|
}): ServerifyResult<R>;
|
|
28
|
-
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PropsWithChildren, ReactNode } from "react";
|
|
2
|
+
import type { Vocab } from "./state.types";
|
|
3
|
+
type Placeholder<V> = {
|
|
4
|
+
getState(): V;
|
|
5
|
+
};
|
|
6
|
+
export type ServerSlot<V> = {
|
|
7
|
+
serverSlot(input: Placeholder<V>): Placeholder<V>;
|
|
8
|
+
};
|
|
9
|
+
type ServerifiedValue<R> = {
|
|
10
|
+
[K in keyof R]?: R[K] extends ServerSlot<infer V> ? V : R[K] extends object ? ServerifiedValue<R[K]> : R[K];
|
|
11
|
+
};
|
|
12
|
+
type Serverified<R> = R extends ServerSlot<infer V> ? Placeholder<V> : R extends object ? {
|
|
13
|
+
seed(input: ServerifiedValue<R>): Vocab;
|
|
14
|
+
} & {
|
|
15
|
+
[K in keyof R]: Serverified<R[K]>;
|
|
16
|
+
} : R;
|
|
17
|
+
export type ServerifyResult<R extends object> = {
|
|
18
|
+
seed(input: ServerifiedValue<R>): Vocab;
|
|
19
|
+
StateVocabProvider(props: PropsWithChildren<{
|
|
20
|
+
value?: ServerifiedValue<R>;
|
|
21
|
+
}>): ReactNode;
|
|
22
|
+
} & {
|
|
23
|
+
[K in keyof R]: Serverified<R[K]>;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
defaultValue?: ValueOrFactory<V>;
|
|
13
|
-
storage?: unknown;
|
|
14
|
-
serialize?: Serialize<V>;
|
|
15
|
-
deserialize?: Deserialize<V>;
|
|
1
|
+
import { STATE_DEFINITION } from "./constants";
|
|
2
|
+
type Path<T, Prefix extends string = ""> = {
|
|
3
|
+
[K in keyof T & string]: T[K] extends object ? T[K] extends {
|
|
4
|
+
[STATE_DEFINITION]: unknown;
|
|
5
|
+
} ? `${Prefix}${K}` : `${Prefix}${K}` | Path<T[K], `${Prefix}${K}.`> : `${Prefix}${K}`;
|
|
6
|
+
}[keyof T & string];
|
|
7
|
+
export type InjectPathsOptions<T extends object> = {
|
|
8
|
+
path: string;
|
|
9
|
+
verbose: boolean;
|
|
10
|
+
verbosePath: Path<T>;
|
|
11
|
+
ssr: boolean;
|
|
16
12
|
};
|
|
13
|
+
export {};
|
package/dist/types/state.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { STATE_DEFINITION, STATE_PATH, STATE_SSR, STATE_VERBOSE, STATE_VERBOSE_PATH } from "./constants";
|
|
2
1
|
import type { Deserialize, Serialize, ValueOrFactory, ValueOrTransformer, VocabThis } from "./state.types";
|
|
2
|
+
import { STATE_DEFINITION, STATE_PATH, STATE_SSR, STATE_VERBOSE, STATE_VERBOSE_PATH } from "./constants";
|
|
3
3
|
export declare function defineState<V>(superOptions?: {
|
|
4
4
|
defaultValue?: ValueOrFactory<V>;
|
|
5
5
|
bidirectional?: true;
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -7,3 +7,4 @@ export declare function logStyled(obj: unknown): void;
|
|
|
7
7
|
export declare const isTransformer: <V>(v: ValueOrTransformer<V>) => v is Transformer<V>;
|
|
8
8
|
export declare const isValueDefined: <V>(v: V | undefined) => v is V;
|
|
9
9
|
export declare const valueOrFactory: <V>(input: ValueOrFactory<V>) => V;
|
|
10
|
+
export declare function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T>;
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use client";"use strict";const h=require("react/jsx-runtime"),c=require("react"),r=require("./utils-0CTNJ4ZE.js"),a=c.createContext({});function S(e){const t=c.useContext(e.clientContext??a);return e.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 r.get(this.#t,t)}set(t,n){const s=r.get(this.#t,t),i=r.isTransformer(n)?n(s):n,o={...this.#t};r.set(o,t,i),this.#t=o,this.#e.forEach(l=>l())}}function b(e){const{clientContext:t,value:n,children:s}=e,[i]=c.useState(()=>new u(n)),o=t??a;return h.jsx(o.Provider,{value:i,children:s})}exports.StateVocabClientProvider=b;exports.VocabStore=u;exports.useStateVocabClientContext=S;
|
package/dist/types/provider.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Context, PropsWithChildren } from "react";
|
|
2
|
-
import type { Vocab } from "./state.types";
|
|
3
|
-
import VocabStore from "./store";
|
|
4
|
-
export declare function StateVocabProvider(props: PropsWithChildren<{
|
|
5
|
-
value?: Vocab;
|
|
6
|
-
serverContextKey: symbol;
|
|
7
|
-
clientContext: Context<VocabStore>;
|
|
8
|
-
}>): import("react/jsx-runtime").JSX.Element;
|