@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 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 react server components (RSC) wrap your app with `StateVocabProvider` at the root and call `serverify` to attach RSC-methods. Use state anywhere in the tree
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 a client context, then create server and client storage handles:**
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
- export const layoutServerStorage = serverify(layoutStorage, {
470
- clientContext: LayoutClientContext
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
- export const layoutClientStorage = clientify(layoutStorage, {
475
- clientContext: LayoutClientContext
476
- })
451
+ "use client"
452
+ export const layoutClientStorage = clientify(layoutStorage, { clientContext: LayoutClientContext })
477
453
 
478
454
  // page.storage.server.ts
479
- export const pageServerStorage = serverify(pageStorage, {
480
- clientContext: PageClientContext
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
- export const pageClientStorage = clientify(pageStorage, {
485
- clientContext: PageClientContext
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 seeded into the nearest `StateVocabProvider`. Each namespace node (including the root) gains a `.seed()` method that returns the input wrapped under its full ancestor path. This method is optional for using. The result also exposes `StateVocabProvider` — a server-aware provider that accepts a plain object `value` prop to pre-seed the store.
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 from the surrounding `StateVocabProvider`. Throws if called outside one.
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>` | **Required.** A React context created with `createContext({})` in a `"use client"` file. Must match the `clientContext` passed to `clientify` for the same storage tree. |
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
- Each leaf gains `.getState()` reads the value from the nearest `StateVocabProvider`. Each namespace node gains `.seed()`, which returns the input wrapped under its full ancestor path. The result also includes `StateVocabProvider` — use it instead of importing from `@yakocloud/state-vocab/server`.
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 // server-aware provider
778
+ const { StateVocabProvider } = serverStorage
765
779
 
766
- serverStorage.user.name.getState() // reads "user.name" from context
767
- serverStorage.user.seed({ name: 'Alice' }) // { user: { name: 'Alice' } }
768
- serverStorage.person.address.seed({ city: 'NY' }) // → { person: { address: { city: 'NY' } } }
769
- serverStorage.seed({ user: { name: 'Alice' } }) // → { user: { name: 'Alice' } } (identity)
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 created with `createContext({})` for this storage tree. Must match the `clientContext` passed to `serverify`. Required when using RSC; omit for SPA-only setups. |
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
 
@@ -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-CAHe8VnM.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))},R=typeof window>"u",m=R?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 I(t){const n=R?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),z=f.useRef(!1);if(!z.current){z.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 p(t){const n=R?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 k(t){return typeof t=="object"&&t!==null&&"clientSlot"in t}function O(t,n={}){const s={};for(const o in t){const e=t[o];k(e)?(s[o]=e.clientSlot({useState(l){return I.apply(this,[{clientContext:n.clientContext,...l}])},useInitialState(l){p.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;
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 y, useSyncExternalStore as B, useEffectEvent as D, useEffect as P, useCallback as p, useLayoutEffect as H } from "react";
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 C, u as _ } from "./provider-client-D0BmMWcY.mjs";
6
- import { S as ne } from "./provider-client-D0BmMWcY.mjs";
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
- }, z = typeof window > "u", k = z ? P : H, J = new C(), L = "Make sure your component is wrapped in StateVocabProvider (RSC) or disable ssr option in setupStorage for SPA (RCC only)";
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 C)) {
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 = y(void 0), w = y(!1);
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 U(t) {
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 C)) {
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 = y(!1);
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
- W(e) ? (i[o] = e.clientSlot({
142
+ Q(e) ? (i[o] = e.clientSlot({
142
143
  useState(a) {
143
- return Q.apply(this, [{
144
+ return U.apply(this, [{
144
145
  clientContext: s.clientContext,
145
146
  ...a
146
147
  }]);
147
148
  },
148
149
  useInitialState(a) {
149
- U.apply(this, [{
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;
@@ -8,6 +8,7 @@ function m(e) {
8
8
  return e.verbose && console.log(`[Store uid]: ${t.uid}`), t;
9
9
  }
10
10
  class d {
11
+ uid;
11
12
  #t;
12
13
  #e;
13
14
  constructor(t) {
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),S=require("./constants-CbsduCZ7.js"),c=require("react"),d=require("./provider-client-CAHe8VnM.js"),y=require("./utils-0CTNJ4ZE.js"),i=e=>{if(c.useState)throw new Error(`${e} only intended for Server Components`)},a=c.cache(()=>new Map),x=({children:e})=>e,C=async e=>{i("StateVocabServerContext.Provider");const{serverContextKey:n,value:t,children:r}=e;return a().set(n,t),r},f=e=>(i("getStateVocab"),a().get(e)),b={Provider:c.useState?x:C},p=e=>{const{serverContextKey:n,value:t,children:r}=e;return s.jsx(b.Provider,{serverContextKey:n,value:t,children:r})};function P(e){const{children:n,serverContextKey:t,clientContext:r,value:o={}}=e;return s.jsx(p,{serverContextKey:t,value:o,children:s.jsx(d.StateVocabClientProvider,{clientContext:r,value:o,children:n})})}const h="Make sure your component is wrapped in StateVocabProvider";function w(e){const n=this[S.STATE_PATH],t=f(e.serverContextKey);if(!t)throw new Error(h);return y.get(t,n)}function g(e){return typeof e=="object"&&e!==null&&"serverSlot"in e}function l(e,n){const t={};for(const r in e){const o=e[r];if(g(o))t[r]=o.serverSlot({getState(){return w.apply(this,[{serverContextKey:n.serverContextKey}])}}),delete t[r].serverSlot,delete t[r].clientSlot;else if(o!==null&&typeof o=="object"){const u=v=>n.wrap({[r]:v});t[r]=l(o,{serverContextKey:n.serverContextKey,wrap:u})}else t[r]=o}return t.seed=r=>n.wrap(r),t}function K(e,n){const t=Symbol();return{...l(e,{serverContextKey:t,wrap:r=>r}),StateVocabProvider({children:r,value:o}){return s.jsx(P,{clientContext:n.clientContext,serverContextKey:t,value:o,children:r})}}}exports.serverify=K;
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 c } from "react/jsx-runtime";
2
- import { S as u } from "./constants-BB1YAX6c.mjs";
3
- import s, { cache as d } from "react";
4
- import { S as f } from "./provider-client-D0BmMWcY.mjs";
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 a = (e) => {
7
- if (s.useState)
8
- throw new Error(`${e} only intended for Server Components`);
9
- }, i = d(() => /* @__PURE__ */ new Map()), p = ({ children: e }) => e, C = async (e) => {
10
- a("StateVocabServerContext.Provider");
11
- const { serverContextKey: o, value: t, children: r } = e;
12
- return i().set(o, t), r;
13
- }, x = (e) => (a("getStateVocab"), i().get(e)), b = {
14
- Provider: s.useState ? p : C
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 K(e) {
16
+ function m(e) {
57
17
  return typeof e == "object" && e !== null && "serverSlot" in e;
58
18
  }
59
- function v(e, o) {
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 (K(n))
64
- t[r] = n.serverSlot({
33
+ if (m(n))
34
+ o[r] = n.serverSlot({
65
35
  getState() {
66
- return w.apply(this, [{
67
- serverContextKey: o.serverContextKey
36
+ return b.apply(this, [{
37
+ serverContextKey: t.serverContextKey
68
38
  }]);
69
39
  }
70
- }), delete t[r].serverSlot, delete t[r].clientSlot;
40
+ }), delete o[r].serverSlot, delete o[r].clientSlot;
71
41
  else if (n !== null && typeof n == "object") {
72
- const l = (S) => o.wrap({ [r]: S });
73
- t[r] = v(n, {
74
- serverContextKey: o.serverContextKey,
75
- wrap: l
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
- t[r] = n;
48
+ o[r] = n;
79
49
  }
80
- return t.seed = (r) => o.wrap(r), t;
50
+ return o.seed = (r) => t.wrap(r), o;
81
51
  }
82
- function k(e, o) {
83
- const t = /* @__PURE__ */ Symbol();
52
+ function K(e, t) {
53
+ t ??= {};
54
+ const o = Object.keys(e).slice(0, 3).join("-"), r = Symbol(o);
84
55
  return {
85
- ...v(e, {
86
- serverContextKey: t,
87
- wrap: (r) => r
56
+ ...i(e, {
57
+ serverContextKey: r,
58
+ wrap: (n) => n
88
59
  }),
89
- StateVocabProvider({ children: r, value: n }) {
90
- return /* @__PURE__ */ c(
91
- h,
60
+ StateVocabProvider({ children: n, value: c }) {
61
+ return c ??= {}, x(r, c), /* @__PURE__ */ l(
62
+ u,
92
63
  {
93
- clientContext: o.clientContext,
94
- serverContextKey: t,
95
- value: n,
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
- k as serverify
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 StateVocabServerContext: {
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 { ValueOrTransformer, VocabThis } from "./state.types";
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
- }): Clientified<R>;
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>;
@@ -1,14 +1,2 @@
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
- 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, PropsWithChildren, ReactNode } from "react";
2
- import type { Vocab } from "./state.types";
3
- type Placeholder<V> = {
4
- getState(): V;
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 {};
@@ -0,0 +1,3 @@
1
+ import type { ServerSlot } from "./setup.server.types";
2
+ export declare function healtcheck(prefix?: string): void;
3
+ export declare function isServerSlot(value: unknown): value is ServerSlot<unknown>;
@@ -1,16 +1,13 @@
1
- import type { Deserialize, Serialize, ValueOrFactory } 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
- export type UseInitialStateOptions<V> = {
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 {};
@@ -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;
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@yakocloud/state-vocab",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "main": "dist/index.cjs.js",
5
5
  "module": "dist/index.es.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -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;
@@ -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;
@@ -1,6 +0,0 @@
1
- import type { Vocab } from "./state.types";
2
- import type { PropsWithChildren } from "react";
3
- export declare const StateVocabServerProvider: React.FC<PropsWithChildren<{
4
- value: Vocab;
5
- serverContextKey: symbol;
6
- }>>;