@storve/core 1.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/CHANGELOG.md +151 -0
- package/benchmarks/run.ts +102 -0
- package/benchmarks/week2.md +9 -0
- package/benchmarks/week2.ts +64 -0
- package/benchmarks/week4.md +13 -0
- package/benchmarks/week4.ts +178 -0
- package/benchmarks/week5.md +15 -0
- package/benchmarks/week5.ts +184 -0
- package/coverage/coverage-summary.json +31 -0
- package/dist/adapters/indexedDB.cjs +2 -0
- package/dist/adapters/indexedDB.cjs.map +1 -0
- package/dist/adapters/indexedDB.mjs +2 -0
- package/dist/adapters/indexedDB.mjs.map +1 -0
- package/dist/adapters/localStorage.cjs +2 -0
- package/dist/adapters/localStorage.cjs.map +1 -0
- package/dist/adapters/localStorage.mjs +2 -0
- package/dist/adapters/localStorage.mjs.map +1 -0
- package/dist/adapters/memory.cjs +2 -0
- package/dist/adapters/memory.cjs.map +1 -0
- package/dist/adapters/memory.mjs +2 -0
- package/dist/adapters/memory.mjs.map +1 -0
- package/dist/adapters/sessionStorage.cjs +2 -0
- package/dist/adapters/sessionStorage.cjs.map +1 -0
- package/dist/adapters/sessionStorage.mjs +2 -0
- package/dist/adapters/sessionStorage.mjs.map +1 -0
- package/dist/async-entry.d.ts +7 -0
- package/dist/async-entry.d.ts.map +1 -0
- package/dist/async.cjs +2 -0
- package/dist/async.cjs.map +1 -0
- package/dist/async.d.ts +52 -0
- package/dist/async.d.ts.map +1 -0
- package/dist/async.mjs +2 -0
- package/dist/async.mjs.map +1 -0
- package/dist/batch.d.ts +12 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/compose.d.ts +7 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/computed-entry.d.ts +7 -0
- package/dist/computed-entry.d.ts.map +1 -0
- package/dist/computed.cjs +2 -0
- package/dist/computed.cjs.map +1 -0
- package/dist/computed.d.ts +56 -0
- package/dist/computed.d.ts.map +1 -0
- package/dist/computed.mjs +2 -0
- package/dist/computed.mjs.map +1 -0
- package/dist/devtools/history.d.ts +51 -0
- package/dist/devtools/history.d.ts.map +1 -0
- package/dist/devtools/index.d.ts +5 -0
- package/dist/devtools/index.d.ts.map +1 -0
- package/dist/devtools/redux-bridge.d.ts +21 -0
- package/dist/devtools/redux-bridge.d.ts.map +1 -0
- package/dist/devtools/snapshots.d.ts +32 -0
- package/dist/devtools/snapshots.d.ts.map +1 -0
- package/dist/devtools/withDevtools.d.ts +17 -0
- package/dist/devtools/withDevtools.d.ts.map +1 -0
- package/dist/devtools.cjs +2 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.mjs +2 -0
- package/dist/devtools.mjs.map +1 -0
- package/dist/extensions/noop.d.ts +2 -0
- package/dist/extensions/noop.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.js +118 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +116 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/persist/adapters/indexedDB.d.ts +12 -0
- package/dist/persist/adapters/indexedDB.d.ts.map +1 -0
- package/dist/persist/adapters/localStorage.d.ts +11 -0
- package/dist/persist/adapters/localStorage.d.ts.map +1 -0
- package/dist/persist/adapters/memory.d.ts +11 -0
- package/dist/persist/adapters/memory.d.ts.map +1 -0
- package/dist/persist/adapters/sessionStorage.d.ts +11 -0
- package/dist/persist/adapters/sessionStorage.d.ts.map +1 -0
- package/dist/persist/debounce.d.ts +12 -0
- package/dist/persist/debounce.d.ts.map +1 -0
- package/dist/persist/hydrate.d.ts +15 -0
- package/dist/persist/hydrate.d.ts.map +1 -0
- package/dist/persist/index.d.ts +34 -0
- package/dist/persist/index.d.ts.map +1 -0
- package/dist/persist/serialize.d.ts +28 -0
- package/dist/persist/serialize.d.ts.map +1 -0
- package/dist/persist.cjs +2 -0
- package/dist/persist.cjs.map +1 -0
- package/dist/persist.mjs +2 -0
- package/dist/persist.mjs.map +1 -0
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/registry-D3X0HSbl.js +26 -0
- package/dist/registry-D3X0HSbl.js.map +1 -0
- package/dist/registry-RDjbeJdx.js +29 -0
- package/dist/registry-RDjbeJdx.js.map +1 -0
- package/dist/registry-qtr1UpFU.js +2 -0
- package/dist/registry-qtr1UpFU.js.map +1 -0
- package/dist/registry-zaKZ1P-s.js +2 -0
- package/dist/registry-zaKZ1P-s.js.map +1 -0
- package/dist/registry.d.ts +54 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/signals/createSignal.d.ts +19 -0
- package/dist/signals/createSignal.d.ts.map +1 -0
- package/dist/signals/index.d.ts +20 -0
- package/dist/signals/index.d.ts.map +1 -0
- package/dist/signals/useSignal.d.ts +11 -0
- package/dist/signals/useSignal.d.ts.map +1 -0
- package/dist/signals.cjs +2 -0
- package/dist/signals.cjs.map +1 -0
- package/dist/signals.mjs +2 -0
- package/dist/signals.mjs.map +1 -0
- package/dist/stats.html +4949 -0
- package/dist/store.d.ts +12 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/sync/channel.d.ts +7 -0
- package/dist/sync/channel.d.ts.map +1 -0
- package/dist/sync/index.d.ts +3 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/protocol.d.ts +22 -0
- package/dist/sync/protocol.d.ts.map +1 -0
- package/dist/sync/withSync.d.ts +17 -0
- package/dist/sync/withSync.d.ts.map +1 -0
- package/dist/sync.cjs +2 -0
- package/dist/sync.cjs.map +1 -0
- package/dist/sync.mjs +2 -0
- package/dist/sync.mjs.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +91 -0
- package/rollup.config.mjs +44 -0
- package/src/async-entry.ts +6 -0
- package/src/async.ts +240 -0
- package/src/batch.ts +33 -0
- package/src/compose.ts +50 -0
- package/src/computed-entry.ts +6 -0
- package/src/computed.ts +187 -0
- package/src/devtools/history.ts +103 -0
- package/src/devtools/index.ts +5 -0
- package/src/devtools/redux-bridge.ts +70 -0
- package/src/devtools/snapshots.ts +54 -0
- package/src/devtools/withDevtools.ts +196 -0
- package/src/extensions/noop.ts +12 -0
- package/src/index.ts +4 -0
- package/src/persist/adapters/indexedDB.ts +114 -0
- package/src/persist/adapters/localStorage.ts +28 -0
- package/src/persist/adapters/memory.ts +26 -0
- package/src/persist/adapters/sessionStorage.ts +28 -0
- package/src/persist/debounce.ts +28 -0
- package/src/persist/hydrate.ts +60 -0
- package/src/persist/index.ts +141 -0
- package/src/persist/serialize.ts +60 -0
- package/src/proxy.ts +87 -0
- package/src/registry.ts +67 -0
- package/src/signals/createSignal.ts +81 -0
- package/src/signals/index.ts +20 -0
- package/src/signals/useSignal.ts +18 -0
- package/src/store.ts +250 -0
- package/src/sync/channel.ts +15 -0
- package/src/sync/index.ts +3 -0
- package/src/sync/protocol.ts +18 -0
- package/src/sync/withSync.ts +147 -0
- package/src/types.ts +159 -0
- package/tests/async.test.ts +1100 -0
- package/tests/batch.test.ts +41 -0
- package/tests/compose.test.ts +209 -0
- package/tests/computed.test.ts +867 -0
- package/tests/devtools.test.ts +1039 -0
- package/tests/integration/persist.integration.test.ts +258 -0
- package/tests/integration/signals.integration.test.ts +309 -0
- package/tests/integration.test.ts +278 -0
- package/tests/persist/adapters/indexedDB.adapter.test.ts +185 -0
- package/tests/persist/adapters/localStorage.adapter.test.ts +105 -0
- package/tests/persist/adapters/memory.adapter.test.ts +112 -0
- package/tests/persist/adapters/sessionStorage.adapter.test.ts +128 -0
- package/tests/persist/debounce.test.ts +121 -0
- package/tests/persist/hydrate.test.ts +120 -0
- package/tests/persist/migrate.test.ts +208 -0
- package/tests/persist/persist.test.ts +357 -0
- package/tests/persist/serialize.test.ts +128 -0
- package/tests/proxy.test.ts +473 -0
- package/tests/registry.test.ts +67 -0
- package/tests/signals/derived.test.ts +244 -0
- package/tests/signals/inference.test.ts +108 -0
- package/tests/signals/signal.test.ts +348 -0
- package/tests/signals/useSignal.test.tsx +275 -0
- package/tests/store.test.ts +482 -0
- package/tests/stress.test.ts +268 -0
- package/tests/sync.test.ts +576 -0
- package/tests/types.test.ts +32 -0
- package/tests/v0.3.test.ts +813 -0
- package/tree-shake-test.js +1 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +22 -0
- package/vitest_play.ts +7 -0
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Store, StoreDefinition, StoreOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a reactive store with auto-tracking features via Proxies.
|
|
4
|
+
* Any mutations to the state via setState or directly to deep objects will notify subscribers.
|
|
5
|
+
* Extensions (async, computed) register when their modules are imported and extend the store.
|
|
6
|
+
*
|
|
7
|
+
* @param definition - The initial state object including optional actions.
|
|
8
|
+
* @param options - Configuration options for the store (e.g., immer).
|
|
9
|
+
* @returns A generic store instance with getState, setState, subscribe, batch, and actions.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createStore<D extends object>(definition: StoreDefinition<D>, options?: StoreOptions): Store<D>;
|
|
12
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,eAAe,EAAY,YAAY,EAA4B,MAAM,SAAS,CAAA;AAMlG;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EACxC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,EAC9B,OAAO,GAAE,YAAiB,GAC3B,KAAK,CAAC,CAAC,CAAC,CAuOV"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/sync/channel.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CASjE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Random tab ID generated once per tab session.
|
|
3
|
+
*/
|
|
4
|
+
export declare const tabId: string;
|
|
5
|
+
/**
|
|
6
|
+
* Message types for cross-tab synchronization.
|
|
7
|
+
* @template S - The state type
|
|
8
|
+
*/
|
|
9
|
+
export type SyncMessage<S> = {
|
|
10
|
+
type: 'STATE_UPDATE';
|
|
11
|
+
payload: Partial<S>;
|
|
12
|
+
tabId: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'REQUEST_STATE';
|
|
15
|
+
tabId: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'PROVIDE_STATE';
|
|
18
|
+
payload: S;
|
|
19
|
+
targetTabId: string;
|
|
20
|
+
tabId: string;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/sync/protocol.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,KAAK,QAKd,CAAC;AAEL;;;GAGG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,IACnB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for configuring cross-tab synchronization.
|
|
3
|
+
*/
|
|
4
|
+
export interface SyncOptions {
|
|
5
|
+
/** Unique name for the BroadcastChannel */
|
|
6
|
+
channel: string;
|
|
7
|
+
/** Optional list of keys to sync. If omitted, all keys are synced. */
|
|
8
|
+
keys?: string[];
|
|
9
|
+
/** Whether sync is enabled (default true) */
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Wraps a store definition with cross-tab synchronization.
|
|
14
|
+
* Updates to the store will be broadcast to other tabs via BroadcastChannel.
|
|
15
|
+
*/
|
|
16
|
+
export declare function withSync<D extends object>(definition: D, options: SyncOptions): D;
|
|
17
|
+
//# sourceMappingURL=withSync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withSync.d.ts","sourceRoot":"","sources":["../../src/sync/withSync.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAKD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EACrC,UAAU,EAAE,CAAC,EACb,OAAO,EAAE,WAAW,GACrB,CAAC,CAOH"}
|
package/dist/sync.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var t=require("./registry-qtr1UpFU.js");const e="undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID?crypto.randomUUID():Math.random().toString(36).substring(2,11),n=Symbol("storve_sync_options");t.registerExtension({key:"sync",extendStore:t=>{const{store:o,definition:a}=t,s=a[n];if(!s||!1===s.enabled)return{};const r=function(t){if("undefined"==typeof window)return null;if("undefined"==typeof BroadcastChannel)return null;try{return new BroadcastChannel(t)}catch{return null}}(s.channel);if(!r)return{};let c=!1,y=!1;const i=o.setState.bind(o);return o.setState=t=>{const n={...o.getState()};if(i(t),!c){const t=((t,e)=>{const n=s.keys||Object.keys(t),o={};let a=!1;for(const s of n)"symbol"!=typeof s&&t[s]!==e[s]&&(o[s]=t[s],a=!0);return a?o:null})(o.getState(),n);t&&r.postMessage({type:"STATE_UPDATE",payload:t,tabId:e})}},r.onmessage=t=>{const n=t.data;if(n.tabId!==e)switch(n.type){case"STATE_UPDATE":c=!0,o.setState(n.payload),c=!1;break;case"REQUEST_STATE":{const t=o.getState(),a={},c=s.keys||Object.keys(t);for(const e of c)"symbol"!=typeof e&&e in t&&(a[e]=t[e]);r.postMessage({type:"PROVIDE_STATE",payload:a,targetTabId:n.tabId,tabId:e});break}case"PROVIDE_STATE":n.targetTabId!==e||y||(y=!0,c=!0,o.setState(n.payload),c=!1)}},r.postMessage({type:"REQUEST_STATE",tabId:e}),{__sync_channel:r}}}),exports.withSync=function(t,e){return Object.defineProperty(t,n,{value:e,enumerable:!1,configurable:!0}),t};
|
|
2
|
+
//# sourceMappingURL=sync.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.cjs","sources":["../src/sync/protocol.ts","../src/sync/withSync.ts","../src/sync/channel.ts"],"sourcesContent":[null,null,null],"names":["tabId","crypto","randomUUID","Math","random","toString","substring","SYNC_OPTIONS","Symbol","registerExtension","key","extendStore","context","store","definition","options","enabled","channel","name","window","BroadcastChannel","openChannel","isSyncUpdate","rehydrated","originalSetState","setState","bind","updater","prevState","getState","payload","nextState","keysToSync","keys","Object","hasChanges","buildPayload","postMessage","type","onmessage","event","data","currentState","targetTabId","__sync_channel","defineProperty","value","enumerable","configurable"],"mappings":"qDAGO,MAAMA,EACa,oBAAXC,QAAuD,mBAAtBA,OAAOC,WACxCD,OAAOC,aAEXC,KAAKC,SAASC,SAAS,IAAIC,UAAU,EAAG,ICW7CC,EAAeC,OAAO,uBAkB5BC,EAAAA,kBAAkB,CACdC,IAAK,OACLC,YAAcC,IACV,MAAMC,MAAEA,EAAKC,WAAEA,GAAeF,EACxBG,EAAWD,EAAuCP,GAExD,IAAKQ,IAA+B,IAApBA,EAAQC,QAAmB,MAAO,CAAA,EAElD,MAAMC,ECvCR,SAAsBC,GACxB,GAAsB,oBAAXC,OAAwB,OAAO,KAC1C,GAAgC,oBAArBC,iBAAkC,OAAO,KACpD,IACI,OAAO,IAAIA,iBAAiBF,EAChC,CAAE,MAEE,OAAO,IACX,CACJ,CD8BwBG,CAAYN,EAAQE,SACpC,IAAKA,EAAS,MAAO,CAAA,EAErB,IAAIK,GAAe,EACfC,GAAa,EAGjB,MAkBMC,EAAmBX,EAAMY,SAASC,KAAKb,GAyE7C,OAxEAA,EAAMY,SAAYE,IACd,MAAMC,EAAY,IAAKf,EAAMgB,YAE7B,GADAL,EAAiBG,IACZL,EAAc,CACf,MACMQ,EAxBO,EAACC,EAAoCH,KACtD,MAAMI,EAAajB,EAAQkB,MAAQC,OAAOD,KAAKF,GACzCD,EAAmC,CAAA,EACzC,IAAIK,GAAa,EAEjB,IAAK,MAAMzB,KAAOsB,EAEK,iBAARtB,GAEPqB,EAAUrB,KAASkB,EAAUlB,KAC7BoB,EAAQpB,GAAOqB,EAAUrB,GACzByB,GAAa,GAGrB,OAAOA,EAAaL,EAAU,MAUVM,CADEvB,EAAMgB,WACgBD,GACpCE,GACAb,EAAQoB,YAAY,CAChBC,KAAM,eACNR,UACA9B,SAGZ,GAIJiB,EAAQsB,UAAaC,IACjB,MAAMC,EAAOD,EAAMC,KAGnB,GAAIA,EAAKzC,QAAUA,EAEnB,OAAQyC,EAAKH,MACT,IAAK,eACDhB,GAAe,EACfT,EAAMY,SAASgB,EAAKX,SACpBR,GAAe,EACf,MAEJ,IAAK,gBAAiB,CAElB,MAAMoB,EAAe7B,EAAMgB,WACrBC,EAAmC,CAAA,EACnCE,EAAajB,EAAQkB,MAAQC,OAAOD,KAAKS,GAE/C,IAAK,MAAMhC,KAAOsB,EACK,iBAARtB,GACPA,KAAOgC,IACPZ,EAAQpB,GAAOgC,EAAahC,IAIpCO,EAAQoB,YAAY,CAChBC,KAAM,gBACNR,UACAa,YAAaF,EAAKzC,MAClBA,UAEJ,KACJ,CACA,IAAK,gBAEGyC,EAAKE,cAAgB3C,GAAUuB,IAC/BA,GAAa,EACbD,GAAe,EACfT,EAAMY,SAASgB,EAAKX,SACpBR,GAAe,KAQ/BL,EAAQoB,YAAY,CAAEC,KAAM,gBAAiBtC,UAOtC,CACH4C,eAAgB3B,uBAvHtB,SACFH,EACAC,GAOA,OALAmB,OAAOW,eAAe/B,EAAYP,EAAc,CAC5CuC,MAAO/B,EACPgC,YAAY,EACZC,cAAc,IAEXlC,CACX"}
|
package/dist/sync.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{r as t}from"./registry-zaKZ1P-s.js";const e="undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID?crypto.randomUUID():Math.random().toString(36).substring(2,11),n=Symbol("storve_sync_options");function o(t,e){return Object.defineProperty(t,n,{value:e,enumerable:!1,configurable:!0}),t}t({key:"sync",extendStore:t=>{const{store:o,definition:a}=t,s=a[n];if(!s||!1===s.enabled)return{};const r=function(t){if("undefined"==typeof window)return null;if("undefined"==typeof BroadcastChannel)return null;try{return new BroadcastChannel(t)}catch{return null}}(s.channel);if(!r)return{};let c=!1,y=!1;const d=o.setState.bind(o);return o.setState=t=>{const n={...o.getState()};if(d(t),!c){const t=((t,e)=>{const n=s.keys||Object.keys(t),o={};let a=!1;for(const s of n)"symbol"!=typeof s&&t[s]!==e[s]&&(o[s]=t[s],a=!0);return a?o:null})(o.getState(),n);t&&r.postMessage({type:"STATE_UPDATE",payload:t,tabId:e})}},r.onmessage=t=>{const n=t.data;if(n.tabId!==e)switch(n.type){case"STATE_UPDATE":c=!0,o.setState(n.payload),c=!1;break;case"REQUEST_STATE":{const t=o.getState(),a={},c=s.keys||Object.keys(t);for(const e of c)"symbol"!=typeof e&&e in t&&(a[e]=t[e]);r.postMessage({type:"PROVIDE_STATE",payload:a,targetTabId:n.tabId,tabId:e});break}case"PROVIDE_STATE":n.targetTabId!==e||y||(y=!0,c=!0,o.setState(n.payload),c=!1)}},r.postMessage({type:"REQUEST_STATE",tabId:e}),{__sync_channel:r}}});export{o as withSync};
|
|
2
|
+
//# sourceMappingURL=sync.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.mjs","sources":["../src/sync/protocol.ts","../src/sync/withSync.ts","../src/sync/channel.ts"],"sourcesContent":[null,null,null],"names":["tabId","crypto","randomUUID","Math","random","toString","substring","SYNC_OPTIONS","Symbol","withSync","definition","options","Object","defineProperty","value","enumerable","configurable","registerExtension","key","extendStore","context","store","enabled","channel","name","window","BroadcastChannel","openChannel","isSyncUpdate","rehydrated","originalSetState","setState","bind","updater","prevState","getState","payload","nextState","keysToSync","keys","hasChanges","buildPayload","postMessage","type","onmessage","event","data","currentState","targetTabId","__sync_channel"],"mappings":"2CAGO,MAAMA,EACa,oBAAXC,QAAuD,mBAAtBA,OAAOC,WACxCD,OAAOC,aAEXC,KAAKC,SAASC,SAAS,IAAIC,UAAU,EAAG,ICW7CC,EAAeC,OAAO,uBAMtB,SAAUC,EACZC,EACAC,GAOA,OALAC,OAAOC,eAAeH,EAAYH,EAAc,CAC5CO,MAAOH,EACPI,YAAY,EACZC,cAAc,IAEXN,CACX,CAEAO,EAAkB,CACdC,IAAK,OACLC,YAAcC,IACV,MAAMC,MAAEA,EAAKX,WAAEA,GAAeU,EACxBT,EAAWD,EAAuCH,GAExD,IAAKI,IAA+B,IAApBA,EAAQW,QAAmB,MAAO,CAAA,EAElD,MAAMC,ECvCR,SAAsBC,GACxB,GAAsB,oBAAXC,OAAwB,OAAO,KAC1C,GAAgC,oBAArBC,iBAAkC,OAAO,KACpD,IACI,OAAO,IAAIA,iBAAiBF,EAChC,CAAE,MAEE,OAAO,IACX,CACJ,CD8BwBG,CAAYhB,EAAQY,SACpC,IAAKA,EAAS,MAAO,CAAA,EAErB,IAAIK,GAAe,EACfC,GAAa,EAGjB,MAkBMC,EAAmBT,EAAMU,SAASC,KAAKX,GAyE7C,OAxEAA,EAAMU,SAAYE,IACd,MAAMC,EAAY,IAAKb,EAAMc,YAE7B,GADAL,EAAiBG,IACZL,EAAc,CACf,MACMQ,EAxBO,EAACC,EAAoCH,KACtD,MAAMI,EAAa3B,EAAQ4B,MAAQ3B,OAAO2B,KAAKF,GACzCD,EAAmC,CAAA,EACzC,IAAII,GAAa,EAEjB,IAAK,MAAMtB,KAAOoB,EAEK,iBAARpB,GAEPmB,EAAUnB,KAASgB,EAAUhB,KAC7BkB,EAAQlB,GAAOmB,EAAUnB,GACzBsB,GAAa,GAGrB,OAAOA,EAAaJ,EAAU,MAUVK,CADEpB,EAAMc,WACgBD,GACpCE,GACAb,EAAQmB,YAAY,CAChBC,KAAM,eACNP,UACApC,SAGZ,GAIJuB,EAAQqB,UAAaC,IACjB,MAAMC,EAAOD,EAAMC,KAGnB,GAAIA,EAAK9C,QAAUA,EAEnB,OAAQ8C,EAAKH,MACT,IAAK,eACDf,GAAe,EACfP,EAAMU,SAASe,EAAKV,SACpBR,GAAe,EACf,MAEJ,IAAK,gBAAiB,CAElB,MAAMmB,EAAe1B,EAAMc,WACrBC,EAAmC,CAAA,EACnCE,EAAa3B,EAAQ4B,MAAQ3B,OAAO2B,KAAKQ,GAE/C,IAAK,MAAM7B,KAAOoB,EACK,iBAARpB,GACPA,KAAO6B,IACPX,EAAQlB,GAAO6B,EAAa7B,IAIpCK,EAAQmB,YAAY,CAChBC,KAAM,gBACNP,UACAY,YAAaF,EAAK9C,MAClBA,UAEJ,KACJ,CACA,IAAK,gBAEG8C,EAAKE,cAAgBhD,GAAU6B,IAC/BA,GAAa,EACbD,GAAe,EACfP,EAAMU,SAASe,EAAKV,SACpBR,GAAe,KAQ/BL,EAAQmB,YAAY,CAAEC,KAAM,gBAAiB3C,UAOtC,CACHiD,eAAgB1B"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ComputedValue } from './computed';
|
|
2
|
+
/** @internal */
|
|
3
|
+
export declare const ASYNC_VALUE_MARKER = "__rf_async";
|
|
4
|
+
/** Re-export for consumers. Defined in computed.ts. */
|
|
5
|
+
export type { ComputedValue } from './computed';
|
|
6
|
+
/**
|
|
7
|
+
* Status of an async operation.
|
|
8
|
+
*/
|
|
9
|
+
export type AsyncStatus = 'idle' | 'loading' | 'success' | 'error';
|
|
10
|
+
/**
|
|
11
|
+
* The shape of an async state value in the store.
|
|
12
|
+
*/
|
|
13
|
+
export interface AsyncState<T> {
|
|
14
|
+
/** The resolved data or null */
|
|
15
|
+
data: T | null;
|
|
16
|
+
/** Error message if the operation failed */
|
|
17
|
+
error: string | null;
|
|
18
|
+
/** Current status of the operation */
|
|
19
|
+
status: AsyncStatus;
|
|
20
|
+
/** Derived from status === 'loading' */
|
|
21
|
+
loading: boolean;
|
|
22
|
+
/** Re-runs the async function with the last used arguments */
|
|
23
|
+
refetch: () => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Configuration options for an async value.
|
|
27
|
+
*/
|
|
28
|
+
export interface AsyncOptions {
|
|
29
|
+
/** Cache TTL in milliseconds. Default: 0 (no cache) */
|
|
30
|
+
ttl?: number;
|
|
31
|
+
/** Enable stale-while-revalidate behavior. Default: false */
|
|
32
|
+
staleWhileRevalidate?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Internal interface for the async engine.
|
|
36
|
+
*/
|
|
37
|
+
export interface IAsyncEngine<T> {
|
|
38
|
+
getState: () => AsyncState<T>;
|
|
39
|
+
fetch: (...args: unknown[]) => Promise<void>;
|
|
40
|
+
refetch: () => Promise<void>;
|
|
41
|
+
invalidate: () => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Internal marker for values created via createAsync().
|
|
45
|
+
* Use this to distinguish async definitions from sync state.
|
|
46
|
+
*/
|
|
47
|
+
export interface AsyncValue<T> {
|
|
48
|
+
readonly [ASYNC_VALUE_MARKER]: true;
|
|
49
|
+
init: (onUpdate: (s: AsyncState<T>) => void) => IAsyncEngine<T>;
|
|
50
|
+
}
|
|
51
|
+
/** Keys of the definition that are computed values (read-only in setState). */
|
|
52
|
+
export type ComputedKeys<D> = {
|
|
53
|
+
[K in keyof Omit<D, 'actions'>]: Omit<D, 'actions'>[K] extends ComputedValue<unknown> ? K : never;
|
|
54
|
+
}[keyof Omit<D, 'actions'>];
|
|
55
|
+
/**
|
|
56
|
+
* State shape with computed keys omitted. Use for setState payloads so TS flags setting computed keys.
|
|
57
|
+
*/
|
|
58
|
+
export type WritableStoreState<D> = Omit<StoreState<D>, ComputedKeys<D>>;
|
|
59
|
+
/**
|
|
60
|
+
* Utility to extract state, omitting the actions key and unwrapping AsyncValues and ComputedValues.
|
|
61
|
+
*/
|
|
62
|
+
export type StoreState<D> = {
|
|
63
|
+
[K in keyof Omit<D, 'actions'>]: Omit<D, 'actions'>[K] extends AsyncValue<infer T> ? AsyncState<T> : Omit<D, 'actions'>[K] extends ComputedValue<infer T> ? T : Omit<D, 'actions'>[K];
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Utility to extract actions from the definition.
|
|
67
|
+
*/
|
|
68
|
+
export type StoreActions<D> = D extends {
|
|
69
|
+
actions: infer A;
|
|
70
|
+
} ? A : Record<string, never>;
|
|
71
|
+
/**
|
|
72
|
+
* The shape of initial state passed to createStore.
|
|
73
|
+
* D represents the full definition including optional actions.
|
|
74
|
+
*/
|
|
75
|
+
export type StoreDefinition<D extends object> = D;
|
|
76
|
+
/**
|
|
77
|
+
* Configuration options for the store.
|
|
78
|
+
*/
|
|
79
|
+
export interface StoreOptions {
|
|
80
|
+
/**
|
|
81
|
+
* Enable Immer for mutation-style state updates.
|
|
82
|
+
* @default false
|
|
83
|
+
*/
|
|
84
|
+
immer?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export type Listener<T> = (state: T) => void;
|
|
87
|
+
export type Unsubscribe = () => void;
|
|
88
|
+
/**
|
|
89
|
+
* The store instance returned by createStore().
|
|
90
|
+
*/
|
|
91
|
+
export type Store<D extends object> = {
|
|
92
|
+
/**
|
|
93
|
+
* Returns the current state snapshot.
|
|
94
|
+
*/
|
|
95
|
+
getState: () => StoreState<D>;
|
|
96
|
+
/**
|
|
97
|
+
* Updates the state. Supports partial objects, updaters, and Immer mutators.
|
|
98
|
+
* Computed keys are read-only; passing them in the payload is a type error and is ignored at runtime.
|
|
99
|
+
*/
|
|
100
|
+
setState: (updater: Partial<WritableStoreState<D>> | ((state: StoreState<D>) => Partial<WritableStoreState<D>>) | ((draft: StoreState<D>) => void)) => void;
|
|
101
|
+
/**
|
|
102
|
+
* Subscribes to state changes.
|
|
103
|
+
*/
|
|
104
|
+
subscribe: (listener: Listener<StoreState<D>>) => Unsubscribe;
|
|
105
|
+
/**
|
|
106
|
+
* Batches multiple state updates to trigger only one notification.
|
|
107
|
+
*/
|
|
108
|
+
batch: (fn: () => void) => void;
|
|
109
|
+
/**
|
|
110
|
+
* Stable reference to the store's actions.
|
|
111
|
+
*/
|
|
112
|
+
actions: StoreActions<D>;
|
|
113
|
+
/**
|
|
114
|
+
* Triggers an async fetch for a specific key.
|
|
115
|
+
*/
|
|
116
|
+
fetch: (key: keyof StoreState<D>, ...args: unknown[]) => Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Re-runs the async operation for a key with the last used arguments.
|
|
119
|
+
*/
|
|
120
|
+
refetch: (key: keyof StoreState<D>) => Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Invalidates the cache for a specific async key.
|
|
123
|
+
*/
|
|
124
|
+
invalidate: (key: keyof StoreState<D>) => void;
|
|
125
|
+
/**
|
|
126
|
+
* Invalidates all async caches in the store.
|
|
127
|
+
*/
|
|
128
|
+
invalidateAll: () => void;
|
|
129
|
+
/**
|
|
130
|
+
* Gets the raw async state for a key (internal use).
|
|
131
|
+
*/
|
|
132
|
+
getAsyncState: (key: keyof StoreState<D>) => AsyncState<unknown> | undefined;
|
|
133
|
+
} & StoreActions<D>;
|
|
134
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@storve/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.cjs",
|
|
5
|
+
"module": "dist/index.mjs",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
},
|
|
13
|
+
"./async": {
|
|
14
|
+
"types": "./dist/async-entry.d.ts",
|
|
15
|
+
"import": "./dist/async.mjs",
|
|
16
|
+
"require": "./dist/async.cjs"
|
|
17
|
+
},
|
|
18
|
+
"./computed": {
|
|
19
|
+
"types": "./dist/computed-entry.d.ts",
|
|
20
|
+
"import": "./dist/computed.mjs",
|
|
21
|
+
"require": "./dist/computed.cjs"
|
|
22
|
+
},
|
|
23
|
+
"./persist": {
|
|
24
|
+
"types": "./dist/persist/index.d.ts",
|
|
25
|
+
"import": "./dist/persist.mjs",
|
|
26
|
+
"require": "./dist/persist.cjs"
|
|
27
|
+
},
|
|
28
|
+
"./persist/adapters/localStorage": {
|
|
29
|
+
"types": "./dist/persist/adapters/localStorage.d.ts",
|
|
30
|
+
"import": "./dist/adapters/localStorage.mjs",
|
|
31
|
+
"require": "./dist/adapters/localStorage.cjs"
|
|
32
|
+
},
|
|
33
|
+
"./persist/adapters/sessionStorage": {
|
|
34
|
+
"types": "./dist/persist/adapters/sessionStorage.d.ts",
|
|
35
|
+
"import": "./dist/adapters/sessionStorage.mjs",
|
|
36
|
+
"require": "./dist/adapters/sessionStorage.cjs"
|
|
37
|
+
},
|
|
38
|
+
"./persist/adapters/memory": {
|
|
39
|
+
"types": "./dist/persist/adapters/memory.d.ts",
|
|
40
|
+
"import": "./dist/adapters/memory.mjs",
|
|
41
|
+
"require": "./dist/adapters/memory.cjs"
|
|
42
|
+
},
|
|
43
|
+
"./persist/adapters/indexedDB": {
|
|
44
|
+
"types": "./dist/persist/adapters/indexedDB.d.ts",
|
|
45
|
+
"import": "./dist/adapters/indexedDB.mjs",
|
|
46
|
+
"require": "./dist/adapters/indexedDB.cjs"
|
|
47
|
+
},
|
|
48
|
+
"./signals": {
|
|
49
|
+
"types": "./dist/signals.d.ts",
|
|
50
|
+
"import": "./dist/signals.mjs",
|
|
51
|
+
"require": "./dist/signals.cjs"
|
|
52
|
+
},
|
|
53
|
+
"./devtools": {
|
|
54
|
+
"types": "./dist/devtools.d.ts",
|
|
55
|
+
"import": "./dist/devtools.mjs",
|
|
56
|
+
"require": "./dist/devtools.cjs"
|
|
57
|
+
},
|
|
58
|
+
"./sync": {
|
|
59
|
+
"types": "./dist/sync/index.d.ts",
|
|
60
|
+
"import": "./dist/sync.mjs",
|
|
61
|
+
"require": "./dist/sync.cjs"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"test": "vitest run --coverage && npx tsx benchmarks/run.ts && npx tsx benchmarks/week4.ts && npx tsx benchmarks/week5.ts",
|
|
66
|
+
"test:watch": "vitest --coverage",
|
|
67
|
+
"test:coverage": "vitest run --coverage",
|
|
68
|
+
"test:bench": "npx tsx benchmarks/run.ts",
|
|
69
|
+
"bench:week4": "npx tsx benchmarks/week4.ts",
|
|
70
|
+
"bench:week5": "npx tsx benchmarks/week5.ts",
|
|
71
|
+
"lint": "eslint src --ext .ts",
|
|
72
|
+
"build": "rollup -c",
|
|
73
|
+
"build:analyze": "rollup -c --environment ANALYZE:true"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
77
|
+
"@testing-library/react": "^16.3.2",
|
|
78
|
+
"@types/react": "^18.3.3",
|
|
79
|
+
"@types/react-dom": "^18.3.0",
|
|
80
|
+
"@vitest/coverage-v8": "^1.6.1",
|
|
81
|
+
"jsdom": "^24.1.3",
|
|
82
|
+
"react": "^18.3.1",
|
|
83
|
+
"react-dom": "^18.3.1",
|
|
84
|
+
"rollup-plugin-visualizer": "^5.12.0",
|
|
85
|
+
"tsx": "^4.21.0"
|
|
86
|
+
},
|
|
87
|
+
"peerDependencies": {
|
|
88
|
+
"immer": ">=10.0.0",
|
|
89
|
+
"react": ">=18.0.0"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import typescript from '@rollup/plugin-typescript';
|
|
2
|
+
import terser from '@rollup/plugin-terser';
|
|
3
|
+
import { visualizer } from 'rollup-plugin-visualizer';
|
|
4
|
+
|
|
5
|
+
const analyze = process.env.ANALYZE === 'true';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
input: {
|
|
9
|
+
index: 'src/index.ts',
|
|
10
|
+
async: 'src/async-entry.ts',
|
|
11
|
+
computed: 'src/computed-entry.ts',
|
|
12
|
+
persist: 'src/persist/index.ts',
|
|
13
|
+
'adapters/localStorage': 'src/persist/adapters/localStorage.ts',
|
|
14
|
+
'adapters/sessionStorage': 'src/persist/adapters/sessionStorage.ts',
|
|
15
|
+
'adapters/memory': 'src/persist/adapters/memory.ts',
|
|
16
|
+
'adapters/indexedDB': 'src/persist/adapters/indexedDB.ts',
|
|
17
|
+
signals: 'src/signals/index.ts',
|
|
18
|
+
devtools: 'src/devtools/index.ts',
|
|
19
|
+
sync: 'src/sync/index.ts',
|
|
20
|
+
},
|
|
21
|
+
output: [
|
|
22
|
+
{
|
|
23
|
+
dir: 'dist',
|
|
24
|
+
format: 'es',
|
|
25
|
+
entryFileNames: '[name].mjs',
|
|
26
|
+
sourcemap: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
dir: 'dist',
|
|
30
|
+
format: 'cjs',
|
|
31
|
+
entryFileNames: '[name].cjs',
|
|
32
|
+
sourcemap: true,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
plugins: [
|
|
36
|
+
typescript({
|
|
37
|
+
tsconfig: './tsconfig.json',
|
|
38
|
+
declaration: true,
|
|
39
|
+
declarationDir: 'dist',
|
|
40
|
+
}),
|
|
41
|
+
terser(),
|
|
42
|
+
analyze && visualizer({ filename: 'dist/stats.html', gzipSize: true }),
|
|
43
|
+
].filter(Boolean),
|
|
44
|
+
};
|
package/src/async.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { AsyncValue, AsyncOptions, AsyncState, AsyncStatus, ASYNC_VALUE_MARKER, IAsyncEngine } from './types';
|
|
2
|
+
import { registerExtension } from './registry';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates an async value definition for use in createStore.
|
|
6
|
+
*
|
|
7
|
+
* @param fn - The async function to execute.
|
|
8
|
+
* @param options - Optional configuration (ttl, staleWhileRevalidate).
|
|
9
|
+
*/
|
|
10
|
+
export function createAsync<T, Args extends unknown[] = unknown[]>(
|
|
11
|
+
fn: (...args: Args) => Promise<T>,
|
|
12
|
+
options: AsyncOptions = {}
|
|
13
|
+
): AsyncValue<T> {
|
|
14
|
+
return {
|
|
15
|
+
[ASYNC_VALUE_MARKER]: true,
|
|
16
|
+
init: (onUpdate: (s: AsyncState<T>) => void) =>
|
|
17
|
+
new AsyncEngine(fn as unknown as (...args: unknown[]) => Promise<T>, options, onUpdate)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Internal engine that manages the state of a single async key.
|
|
23
|
+
* Handles fetching, caching, and race condition protection.
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export class AsyncEngine<T> implements IAsyncEngine<T> {
|
|
27
|
+
private lastRequestId = 0;
|
|
28
|
+
private lastArgs: unknown[] = [];
|
|
29
|
+
private cache = new Map<string, { data: T; expiresAt: number }>();
|
|
30
|
+
private status: AsyncStatus = 'idle';
|
|
31
|
+
private data: T | null = null;
|
|
32
|
+
private error: string | null = null;
|
|
33
|
+
|
|
34
|
+
// Rollback state for optimistic updates
|
|
35
|
+
private previousData: T | null = null;
|
|
36
|
+
private previousStatus: AsyncStatus = 'idle';
|
|
37
|
+
private hasPrevious = false;
|
|
38
|
+
|
|
39
|
+
// Stable state result to maintain identity
|
|
40
|
+
private lastState: AsyncState<T> | null = null;
|
|
41
|
+
private stableRefetch = () => this.refetch();
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
private fn: (...args: unknown[]) => Promise<T>,
|
|
45
|
+
private options: AsyncOptions,
|
|
46
|
+
private onUpdate: (state: AsyncState<T>) => void
|
|
47
|
+
) { }
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns the current public state shape for this async key.
|
|
51
|
+
*/
|
|
52
|
+
getState(): AsyncState<T> {
|
|
53
|
+
if (!this.lastState) {
|
|
54
|
+
this.lastState = {
|
|
55
|
+
data: this.data,
|
|
56
|
+
error: this.error,
|
|
57
|
+
status: this.status,
|
|
58
|
+
loading: this.status === 'loading',
|
|
59
|
+
refetch: this.stableRefetch,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return this.lastState;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Triggers a fetch. Handles TTL caching and SWR.
|
|
67
|
+
*/
|
|
68
|
+
async fetch(...args: unknown[]): Promise<void> {
|
|
69
|
+
// Handle optional options as last argument if it looks like { optimistic: ... }
|
|
70
|
+
let fetchOptions: { optimistic?: { data: T, status?: AsyncStatus } } | undefined;
|
|
71
|
+
let actualArgs = args;
|
|
72
|
+
|
|
73
|
+
if (args.length > 0) {
|
|
74
|
+
const lastArg = args[args.length - 1];
|
|
75
|
+
if (lastArg && typeof lastArg === 'object' && 'optimistic' in lastArg) {
|
|
76
|
+
fetchOptions = lastArg as { optimistic?: { data: T, status?: AsyncStatus } };
|
|
77
|
+
actualArgs = args.slice(0, -1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const { ttl = 0, staleWhileRevalidate = false } = this.options;
|
|
83
|
+
|
|
84
|
+
// Argument-based cache key. v0.5+: replace JSON.stringify with a more robust stable hash
|
|
85
|
+
const cacheKey = JSON.stringify(actualArgs);
|
|
86
|
+
const cached = this.cache.get(cacheKey);
|
|
87
|
+
|
|
88
|
+
let fromOptimistic = false;
|
|
89
|
+
if (fetchOptions?.optimistic) {
|
|
90
|
+
this.previousData = this.data;
|
|
91
|
+
this.previousStatus = this.status;
|
|
92
|
+
this.hasPrevious = true;
|
|
93
|
+
this.data = fetchOptions.optimistic.data;
|
|
94
|
+
this.status = fetchOptions.optimistic.status || 'success';
|
|
95
|
+
this.lastArgs = actualArgs;
|
|
96
|
+
this.notify();
|
|
97
|
+
fromOptimistic = true;
|
|
98
|
+
// Fall through to runFetch which will handle result or rollback
|
|
99
|
+
} else {
|
|
100
|
+
// Cache hit logic (only if NOT optimistic)
|
|
101
|
+
if (ttl > 0 && cached && now < cached.expiresAt && this.status === 'success') {
|
|
102
|
+
if (this.data !== cached.data) {
|
|
103
|
+
this.data = cached.data;
|
|
104
|
+
this.lastArgs = actualArgs;
|
|
105
|
+
this.notify();
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Stale-While-Revalidate logic
|
|
111
|
+
const isStale = ttl > 0 && cached && now >= cached.expiresAt && this.status === 'success';
|
|
112
|
+
|
|
113
|
+
if (isStale && staleWhileRevalidate) {
|
|
114
|
+
this.data = cached.data;
|
|
115
|
+
this.lastArgs = actualArgs;
|
|
116
|
+
return this.runFetch(actualArgs, true);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.lastArgs = actualArgs;
|
|
121
|
+
return this.runFetch(actualArgs, fromOptimistic);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Re-runs the operation with last used arguments. Bypasses TTL.
|
|
126
|
+
*/
|
|
127
|
+
async refetch(): Promise<void> {
|
|
128
|
+
return this.runFetch(this.lastArgs, false);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Clears the cache for this key.
|
|
133
|
+
*/
|
|
134
|
+
invalidate(): void {
|
|
135
|
+
this.cache.clear();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Internal runner for the async function with race protection.
|
|
140
|
+
*/
|
|
141
|
+
private async runFetch(args: unknown[], background: boolean): Promise<void> {
|
|
142
|
+
const requestId = ++this.lastRequestId;
|
|
143
|
+
|
|
144
|
+
// If not a background SWR fetch and NOT already optimistic, show loading
|
|
145
|
+
if (!background) {
|
|
146
|
+
this.status = 'loading';
|
|
147
|
+
this.notify();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const result = await this.fn(...args);
|
|
152
|
+
|
|
153
|
+
// Race condition protection: only the latest request wins
|
|
154
|
+
if (requestId !== this.lastRequestId) return;
|
|
155
|
+
|
|
156
|
+
this.data = result;
|
|
157
|
+
this.error = null;
|
|
158
|
+
this.status = 'success';
|
|
159
|
+
this.hasPrevious = false; // Resolved, no need to rollback anymore
|
|
160
|
+
|
|
161
|
+
const ttl = this.options.ttl || 0;
|
|
162
|
+
if (ttl > 0) {
|
|
163
|
+
const cacheKey = JSON.stringify(args);
|
|
164
|
+
this.cache.set(cacheKey, { data: result, expiresAt: Date.now() + ttl });
|
|
165
|
+
}
|
|
166
|
+
} catch (err: unknown) {
|
|
167
|
+
if (requestId !== this.lastRequestId) return;
|
|
168
|
+
|
|
169
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
170
|
+
|
|
171
|
+
// Rollback if we had previous data (likely from optimistic update)
|
|
172
|
+
if (this.hasPrevious) {
|
|
173
|
+
this.data = this.previousData;
|
|
174
|
+
this.status = this.previousStatus;
|
|
175
|
+
this.hasPrevious = false;
|
|
176
|
+
} else if (background) {
|
|
177
|
+
// Keep data but set error status
|
|
178
|
+
this.status = 'error';
|
|
179
|
+
} else {
|
|
180
|
+
this.data = null;
|
|
181
|
+
this.status = 'error';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.notify();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private notify() {
|
|
189
|
+
this.lastState = null; // Invalidate cached state object
|
|
190
|
+
this.onUpdate(this.getState());
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Register async extension when module is imported (order 0 = runs before computed)
|
|
195
|
+
registerExtension({
|
|
196
|
+
key: 'async',
|
|
197
|
+
order: 0,
|
|
198
|
+
processDefinition: (definition) => {
|
|
199
|
+
const state: Record<string, unknown> = {};
|
|
200
|
+
const asyncInits: Array<{ key: string; init: (onUpdate: (state: unknown) => void) => unknown }> = [];
|
|
201
|
+
for (const key of Object.keys(definition)) {
|
|
202
|
+
const val = definition[key];
|
|
203
|
+
if (val && typeof val === 'object' && ASYNC_VALUE_MARKER in val) {
|
|
204
|
+
const asyncVal = val as unknown as AsyncValue<unknown>;
|
|
205
|
+
asyncInits.push({ key, init: asyncVal.init });
|
|
206
|
+
} else {
|
|
207
|
+
state[key] = val;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { state, asyncInits };
|
|
211
|
+
},
|
|
212
|
+
extendStore: (ctx) => {
|
|
213
|
+
const engines = ctx.engines as Map<string, IAsyncEngine<unknown>>;
|
|
214
|
+
return {
|
|
215
|
+
fetch: async (key: string, ...args: unknown[]) => {
|
|
216
|
+
if (!engines.has(key)) {
|
|
217
|
+
throw new Error(`Storve: no async key "${key}" found in store`);
|
|
218
|
+
}
|
|
219
|
+
const engine = engines.get(key);
|
|
220
|
+
if (engine) await engine.fetch(...args);
|
|
221
|
+
},
|
|
222
|
+
refetch: async (key: string) => {
|
|
223
|
+
const engine = engines.get(key);
|
|
224
|
+
if (engine) await engine.refetch();
|
|
225
|
+
},
|
|
226
|
+
invalidate: (key: string) => {
|
|
227
|
+
const engine = engines.get(key);
|
|
228
|
+
if (engine) engine.invalidate();
|
|
229
|
+
},
|
|
230
|
+
invalidateAll: () => {
|
|
231
|
+
engines.forEach((engine) => engine.invalidate());
|
|
232
|
+
},
|
|
233
|
+
getAsyncState: (key: string) => {
|
|
234
|
+
const engine = engines.get(key);
|
|
235
|
+
return engine ? engine.getState() : undefined;
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|