@picked/svelte 0.0.23 → 0.0.24-next.1

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.
@@ -0,0 +1,145 @@
1
+ import { Store } from 'effector';
2
+ interface QueryOptions {
3
+ refetch?: boolean;
4
+ staleTime?: number;
5
+ retry?: number;
6
+ retryDelay?: number;
7
+ }
8
+ export interface QueryResult<T> {
9
+ data: Store<T | null>;
10
+ error: Store<Error | null>;
11
+ isLoading: Store<boolean>;
12
+ refetch: () => Promise<T>;
13
+ invalidate: () => void;
14
+ }
15
+ export type QueryFactory<Params, Results> = (params: Params, queryOptions?: QueryOptions) => QueryResult<Results>;
16
+ /**
17
+ * Effector-powered data fetching helper inspired by [SWR](https://www.npmjs.com/package/swr).
18
+ *
19
+ * It provides:
20
+ * - Request de-duplication per key (in-flight promise reuse)
21
+ * - Stale-while-revalidate caching with per-key timestamps
22
+ * - Automatic retries with exponential backoff
23
+ * - Fine-grained invalidation (per key, per namespace, or global)
24
+ *
25
+ * Behavior overview:
26
+ * - Cache key: built from the `keyFn(params)` within a `namespace` to avoid collisions across domains.
27
+ * - Staleness: when a record is older than `staleTime`, the next read will revalidate in the background.
28
+ * - Deduplication: concurrent reads for the same key share the same promise.
29
+ * - Retries: failures are retried up to `retry` times using exponential backoff starting at `retryDelay` ms.
30
+ *
31
+ * Type parameters:
32
+ * - Params: the shape of parameters passed to the fetcher
33
+ * - Results: the shape of the resolved fetcher result
34
+ *
35
+ * @param fetcher A function that performs the async request and returns a Promise of the result.
36
+ * @param keyFn A function that builds a stable cache key from the provided params.
37
+ * Use all inputs that affect the result to prevent collisions.
38
+ * @param options Optional configuration applied to all queries created by this factory.
39
+ * @param options.namespace Logical grouping for cache separation. Defaults to "default".
40
+ * @param options.defaultStaleTime Default time in ms before data is considered stale. Defaults to 5 minutes.
41
+ * @param options.defaultRetry Default max retry attempts on failure. Defaults to 3.
42
+ * @param options.defaultRetryDelay Base delay in ms for retries; exponential backoff is applied. Defaults to 1000ms.
43
+ *
44
+ * @returns A QueryFactory(params, queryOptions) that produces QueryResult stores and controls for a specific key.
45
+ *
46
+ * Example: Basic usage with request de-duplication and caching
47
+ * ```typescript
48
+ * type User = { id: string; name: string }
49
+ *
50
+ * async function fetchUser({ id }: { id: string }): Promise<User> {
51
+ * const res = await fetch(`/api/users/${id}`)
52
+ * if (!res.ok) throw new Error('Failed to load user')
53
+ * return res.json()
54
+ * }
55
+ *
56
+ * const userQuery = createQuery(fetchUser, (p) => `user:${p.id}`, {
57
+ * namespace: 'users',
58
+ * defaultStaleTime: 60_000, // 1 minute
59
+ * defaultRetry: 2,
60
+ * defaultRetryDelay: 500,
61
+ * })
62
+ *
63
+ * // Later in your code (each call uses the same key -> dedup + cache)
64
+ * const { data, error, isLoading, refetch, invalidate } = userQuery({ id: '42' })
65
+ *
66
+ * // Subscribe reactively via Effector API:
67
+ * const stopDataWatch = data.watch((value) => {
68
+ * console.log('User data changed:', value)
69
+ * })
70
+ * const stopErrorWatch = error.watch((err) => {
71
+ * if (err) console.error('User query error:', err)
72
+ * })
73
+ * const stopLoadingWatch = isLoading.watch((pending) => {
74
+ * console.log('Loading:', pending)
75
+ * })
76
+ *
77
+ * // Manually refetch (ignores staleness)
78
+ * refetch()
79
+ *
80
+ * // Invalidate only this key (forces next read to refetch)
81
+ * invalidate()
82
+ *
83
+ * // Clean up watchers when leaving scope
84
+ * stopDataWatch()
85
+ * stopErrorWatch()
86
+ * stopLoadingWatch()
87
+ * ```
88
+ *
89
+ * Example: Per-call overrides (staleTime, retry)
90
+ * ```typescript
91
+ * const fastUserQuery = createQuery(fetchUser, (p) => `user:${p.id}`, {
92
+ * namespace: 'users',
93
+ * defaultStaleTime: 5 * 60_000, // 5 minutes
94
+ * })
95
+ *
96
+ * // For this call, we want fresher data and fewer retries:
97
+ * const fast = fastUserQuery({ id: '42' }, { staleTime: 5_000, retry: 1 })
98
+ *
99
+ * // For another call, rely on defaults:
100
+ * const normal = fastUserQuery({ id: '42' })
101
+ * ```
102
+ *
103
+ * Example: Using multiple params in the key
104
+ * ```typescript
105
+ * type ListParams = { page: number; q?: string }
106
+ * const listQuery = createQuery(
107
+ * async (p: ListParams) => {
108
+ * const u = new URL('/api/items', window.location.origin)
109
+ * u.searchParams.set('page', String(p.page))
110
+ * if (p.q) u.searchParams.set('q', p.q)
111
+ * const r = await fetch(u)
112
+ * if (!r.ok) throw new Error('Failed')
113
+ * return r.json() as Promise<{ items: unknown[]; total: number }>
114
+ * },
115
+ * // Include all inputs that affect response:
116
+ * (p) => `items:page=${p.page}:q=${p.q ?? ''}`,
117
+ * { namespace: 'items' },
118
+ * )
119
+ * ```
120
+ */
121
+ export declare function createQuery<Params, Results>(fetcher: (params: Params) => Promise<Results>, keyFn: (params: Params) => string, options?: {
122
+ namespace?: string;
123
+ defaultStaleTime?: number;
124
+ defaultRetry?: number;
125
+ defaultRetryDelay?: number;
126
+ }): QueryFactory<Params, Results>;
127
+ export declare const queryCache: {
128
+ invalidate: (namespace: string, key?: string) => void;
129
+ clear: () => void;
130
+ };
131
+ export declare const queryPresets: {
132
+ realtime: {
133
+ defaultStaleTime: number;
134
+ defaultRetry: number;
135
+ };
136
+ standard: {
137
+ defaultStaleTime: number;
138
+ defaultRetry: number;
139
+ };
140
+ static: {
141
+ defaultStaleTime: number;
142
+ defaultRetry: number;
143
+ };
144
+ };
145
+ export {};
@@ -0,0 +1 @@
1
+ import{createEffect as E,createStore as w,sample as S}from"effector";const y=new Map;function T(a){return y.has(a)||y.set(a,new Map),y.get(a)}function R(a,i){const o=T(a).get(i);o&&(o.promise=null,o.timestamp=0)}function v(a){T(a).forEach(c=>{c.promise=null,c.timestamp=0})}function L(){y.clear()}function P(a,i,c={}){const{namespace:o="default",defaultStaleTime:f=300*1e3,defaultRetry:m=3,defaultRetryDelay:g=1e3}=c,u=T(o),d=E(async p=>{var D;const{key:s,params:l,options:e}=p;let r=(D=u.get(s))==null?void 0:D.promise;const t=u.get(s),n=e.refetch||!r||t&&Date.now()-t.timestamp>(e.staleTime??t.staleTime);if(!r||n){r=_(()=>a(l),e.retry??m,e.retryDelay??g);const h=u.get(s);h&&(h.promise=r,h.timestamp=Date.now(),h.staleTime=e.staleTime??f)}return{key:s,promise:r,result:await r}});return(p,s={})=>{const l=i(p);let e=u.get(l);e||(e={promise:null,data:w(null),error:w(null),isLoading:w(!1),timestamp:0,staleTime:s.staleTime??f},u.set(l,e));const r=E(t=>t);return S({source:d.doneData,filter:t=>t.key===l,fn:t=>t.promise,target:r}),e.isLoading.on(d.pending,(t,n)=>n),e.isLoading.on(r.pending,(t,n)=>n),e.data.on(r.doneData,(t,n)=>n),e.error.on(r.doneData,()=>null),e.error.on(r.failData,(t,n)=>n),e.error.on(d.failData,(t,n)=>n),(!e.promise||Date.now()-e.timestamp>e.staleTime)&&d({key:l,params:p,options:s}),{data:e.data,error:e.error,isLoading:e.isLoading,refetch:()=>d({key:l,params:p,options:{...s,refetch:!0}}).then(({result:t})=>t),invalidate:()=>R(o,l)}}}async function _(a,i,c){let o;for(let f=0;f<=i;f++)try{return await a()}catch(m){o=m instanceof Error?m:new Error(String(m)),f<i&&await new Promise(g=>{setTimeout(g,c*Math.pow(2,f))})}throw o}const M={invalidate:(a,i)=>{i?R(a,i):v(a)},clear:()=>L()},b={realtime:{defaultStaleTime:5*1e3,defaultRetry:1},standard:{defaultStaleTime:300*1e3,defaultRetry:3},static:{defaultStaleTime:3600*1e3,defaultRetry:2}};export{P as createQuery,M as queryCache,b as queryPresets};
@@ -3,3 +3,4 @@ export { getStep, setStepContext } from './step';
3
3
  export { useUnit, getContextScope, setContextScope } from './stores';
4
4
  export { createSessionStorageStore, createLocalStorageStore } from './storage.js';
5
5
  export { getTrackRecommendationPresentEvent } from './events';
6
+ export { createQuery, queryCache, queryPresets, type QueryResult, type QueryFactory, } from './create-query';
@@ -1 +1 @@
1
- import{getExperience as o,getPicked as r}from"./client.js";import{getStep as p,setStepContext as c}from"./step.js";import{getContextScope as S,setContextScope as a,useUnit as g}from"./stores.js";import{createLocalStorageStore as s,createSessionStorageStore as f}from"./storage.js";import{getTrackRecommendationPresentEvent as C}from"./events.js";export{s as createLocalStorageStore,f as createSessionStorageStore,S as getContextScope,o as getExperience,r as getPicked,p as getStep,C as getTrackRecommendationPresentEvent,a as setContextScope,c as setStepContext,g as useUnit};
1
+ import{getExperience as r,getPicked as o}from"./client.js";import{getStep as p,setStepContext as n}from"./step.js";import{getContextScope as a,setContextScope as S,useUnit as m}from"./stores.js";import{createLocalStorageStore as g,createSessionStorageStore as f}from"./storage.js";import{getTrackRecommendationPresentEvent as u}from"./events.js";import{createQuery as y,queryCache as P,queryPresets as d}from"./create-query/index.js";export{g as createLocalStorageStore,y as createQuery,f as createSessionStorageStore,a as getContextScope,r as getExperience,o as getPicked,p as getStep,u as getTrackRecommendationPresentEvent,P as queryCache,d as queryPresets,S as setContextScope,n as setStepContext,m as useUnit};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picked/svelte",
3
- "version": "0.0.23",
3
+ "version": "0.0.24-next.1",
4
4
  "dependencies": {
5
5
  "d3": "^7.9.0"
6
6
  },
@@ -14,13 +14,17 @@
14
14
  "@tsconfig/svelte": "^5.0.4",
15
15
  "@types/d3": "^7.4.3",
16
16
  "@types/node": "^22",
17
+ "@vitest/coverage-v8": "^3.2.4",
17
18
  "effector": "^23.3.0",
18
19
  "fast-glob": "^3.3.3",
20
+ "happy-dom": "^20.0.2",
21
+ "svelte": "^5.34.3",
19
22
  "svelte-check": "^4.2.2",
20
23
  "typedoc": "^0.28.5",
21
24
  "typedoc-plugin-markdown": "^4.6.3",
22
25
  "vite": "^6.4.1",
23
- "vite-plugin-dts": "^4.5.4"
26
+ "vite-plugin-dts": "^4.5.4",
27
+ "vitest": "^3.2.4"
24
28
  },
25
29
  "exports": {
26
30
  ".": {
@@ -71,7 +75,7 @@
71
75
  ],
72
76
  "module": "./dist/index.js",
73
77
  "peerDependencies": {
74
- "@picked/client": "0.0.23",
78
+ "@picked/client": "0.0.24-next.1",
75
79
  "effector": "^23.3.0",
76
80
  "svelte": "^5.34.3"
77
81
  },
@@ -85,7 +89,10 @@
85
89
  "lint:format": "eslint . --fix",
86
90
  "lint:report": "eslint . -o './.reports/eslint.json' -f json || [ -f './.reports/eslint.json' ]",
87
91
  "svelte-check:report": "mkdir -p ./.reports && pnpm svelte-check --tsconfig ./tsconfig.json --output=machine > ./.reports/svelte-check.txt || [ -f './.reports/svelte-check.txt' ]",
92
+ "test": "vitest run",
93
+ "test:coverage": "vitest run --coverage",
88
94
  "test:packaged-types": "attw --pack",
95
+ "test:watch": "vitest",
89
96
  "type-check": "tsc -p . --noEmit"
90
97
  },
91
98
  "svelte": "./dist/index.js",