Archived
init
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "bun:test";
|
||||
import { get } from "svelte/store";
|
||||
import { useSWR } from "../index";
|
||||
|
||||
// Mock fetcher
|
||||
const fetcher = vi.fn((key: string) => Promise.resolve(`data-${key}`));
|
||||
|
||||
describe("useSWR (Svelte)", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
fetcher.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("fetches data on mount", async () => {
|
||||
const { data, isLoading } = useSWR("test-key", fetcher);
|
||||
|
||||
// Initial state (synchronously set by my core change)
|
||||
expect(get(isLoading)).toBe(true);
|
||||
|
||||
// Wait for fetch
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(get(data)).toBe("data-test-key");
|
||||
expect(get(isLoading)).toBe(false);
|
||||
});
|
||||
|
||||
it("deduplicates requests", async () => {
|
||||
const { data: d1 } = useSWR("dedupe-key", fetcher);
|
||||
const { data: d2 } = useSWR("dedupe-key", fetcher);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(fetcher).toHaveBeenCalledTimes(1);
|
||||
expect(get(d1)).toBe("data-dedupe-key");
|
||||
expect(get(d2)).toBe("data-dedupe-key");
|
||||
});
|
||||
|
||||
it("mutates data manually", async () => {
|
||||
const { data, mutate } = useSWR("mutate-key", fetcher);
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(get(data)).toBe("data-mutate-key");
|
||||
|
||||
await mutate("new-data");
|
||||
|
||||
expect(get(data)).toBe("new-data");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
// Svelte SWR exports
|
||||
export { default as useSWR, useSWRPrefetch } from './use-swr'
|
||||
|
||||
// Re-export core types
|
||||
export type { Key, Fetcher } from '../../core'
|
||||
|
||||
// Main exports
|
||||
export { createSWRStores, createSWRStore } from './swr'
|
||||
export {
|
||||
swrDataStore,
|
||||
swrErrorStore,
|
||||
swrLoadingStore,
|
||||
swrValidatingStore,
|
||||
useSWRInstance
|
||||
} from './swr'
|
||||
|
||||
// Types
|
||||
export type { SWRStores, SWRStore, SWRConfig } from './types'
|
||||
|
||||
// Default export
|
||||
export { createSWRStores as default } from './swr'
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@supwr/svelte",
|
||||
"version": "0.0.1",
|
||||
"module": "src/index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "bun build ./src/index.ts --outdir ./dist --external react --target browser"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supwr/core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import { writable, derived } from 'svelte/store'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { createSWR } from '../../core'
|
||||
import type { SWRStores, SWRStore, Key, Fetcher, SWRFetcher } from './types'
|
||||
|
||||
// Global SWR instance
|
||||
const globalSWR = createSWR()
|
||||
|
||||
/**
|
||||
* Create SWR stores for Svelte
|
||||
*/
|
||||
export function createSWRStores<Data = unknown, Error = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: SWRFetcher<Data>,
|
||||
config?: Partial<import('./types').SWRConfig<Data, Error>>
|
||||
): SWRStores<Data, Error> {
|
||||
// Update config if provided
|
||||
if (config) {
|
||||
globalSWR.updateConfig(config as any)
|
||||
}
|
||||
|
||||
// Initial state logic
|
||||
let initialData: Data | undefined = undefined
|
||||
let initialError: Error | undefined = undefined
|
||||
let initialIsLoading = false
|
||||
let initialIsValidating = false
|
||||
|
||||
if (key) {
|
||||
const entry = globalSWR.get(key)
|
||||
if (entry) {
|
||||
initialData = entry.data as Data
|
||||
initialError = entry.error as Error
|
||||
initialIsLoading = entry.isLoading
|
||||
initialIsValidating = entry.isValidating
|
||||
}
|
||||
}
|
||||
|
||||
// Create base stores
|
||||
const dataStore = writable<Data | undefined>(initialData)
|
||||
const errorStore = writable<Error | undefined>(initialError)
|
||||
const isLoadingStore = writable<boolean>(initialIsLoading)
|
||||
const isValidatingStore = writable<boolean>(initialIsValidating)
|
||||
|
||||
// Subscribe to SWR changes
|
||||
let unsubscribe: (() => void) | undefined
|
||||
|
||||
const normalizedFetcher = fetcher ? (
|
||||
fetcher.length === 2 ? fetcher : (k: Key, options: any) => {
|
||||
if (Array.isArray(k)) {
|
||||
return (fetcher as any)(...k)
|
||||
}
|
||||
return (fetcher as any)(k)
|
||||
}
|
||||
) : null
|
||||
|
||||
if (key) {
|
||||
unsubscribe = globalSWR.subscribe(key, (newData, newError, newIsValidating, newIsLoading) => {
|
||||
dataStore.set(newData as Data)
|
||||
errorStore.set(newError as Error)
|
||||
isValidatingStore.set(newIsValidating ?? false)
|
||||
isLoadingStore.set(newIsLoading ?? false)
|
||||
}, normalizedFetcher as any)
|
||||
}
|
||||
|
||||
// Cleanup on destroy
|
||||
onDestroy(() => {
|
||||
unsubscribe?.()
|
||||
})
|
||||
|
||||
// Create mutate function
|
||||
const mutate = (mutateData?: Data | ((current: Data | undefined) => Data | Promise<Data>), options?: Partial<import('../../core').MutatorOptions<Data>>):Promise<Data | undefined> => {
|
||||
if (!key) return Promise.resolve(undefined)
|
||||
return globalSWR.mutate(key, mutateData, options) as Promise<Data>
|
||||
}
|
||||
|
||||
return {
|
||||
data: { subscribe: dataStore.subscribe },
|
||||
error: { subscribe: errorStore.subscribe },
|
||||
isLoading: { subscribe: isLoadingStore.subscribe },
|
||||
isValidating: { subscribe: isValidatingStore.subscribe },
|
||||
loading: { subscribe: isLoadingStore.subscribe },
|
||||
validating: { subscribe: isValidatingStore.subscribe },
|
||||
mutate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single SWR store containing all state
|
||||
*/
|
||||
export function createSWRStore<Data = unknown, Error = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<Data>,
|
||||
config?: Partial<import('./types').SWRConfig<Data, Error>>
|
||||
): SWRStore<Data, Error> {
|
||||
const stores = createSWRStores<Data, Error>(key, fetcher, config)
|
||||
|
||||
let initialData: Data | undefined;
|
||||
let initialError: Error | undefined;
|
||||
let initialIsLoading = false;
|
||||
let initialIsValidating = false;
|
||||
|
||||
stores.data.subscribe(v => initialData = v)();
|
||||
stores.error.subscribe(v => initialError = v)();
|
||||
stores.isLoading.subscribe(v => initialIsLoading = v)();
|
||||
stores.isValidating.subscribe(v => initialIsValidating = v)();
|
||||
|
||||
const combinedStore = writable<{
|
||||
data?: Data
|
||||
error?: Error
|
||||
isLoading: boolean
|
||||
isValidating: boolean
|
||||
loading: boolean
|
||||
validating: boolean
|
||||
}>({
|
||||
data: initialData,
|
||||
error: initialError,
|
||||
isLoading: initialIsLoading,
|
||||
isValidating: initialIsValidating,
|
||||
loading: initialIsLoading,
|
||||
validating: initialIsValidating
|
||||
})
|
||||
|
||||
// Update combined store when individual stores change
|
||||
const unsubscribeData = stores.data.subscribe(data => {
|
||||
combinedStore.update(state => ({ ...state, data }))
|
||||
})
|
||||
|
||||
const unsubscribeError = stores.error.subscribe(error => {
|
||||
combinedStore.update(state => ({ ...state, error }))
|
||||
})
|
||||
|
||||
const unsubscribeLoading = stores.isLoading.subscribe(isLoading => {
|
||||
combinedStore.update(state => ({ ...state, isLoading, loading: isLoading }))
|
||||
})
|
||||
|
||||
const unsubscribeValidating = stores.isValidating.subscribe(isValidating => {
|
||||
combinedStore.update(state => ({ ...state, isValidating, validating: isValidating }))
|
||||
})
|
||||
|
||||
// Cleanup on destroy
|
||||
onDestroy(() => {
|
||||
unsubscribeData()
|
||||
unsubscribeError()
|
||||
unsubscribeLoading()
|
||||
unsubscribeValidating()
|
||||
})
|
||||
|
||||
return {
|
||||
...combinedStore,
|
||||
mutate: stores.mutate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual store creators
|
||||
*/
|
||||
export function swrDataStore<Data = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<Data>,
|
||||
config?: Partial<import('./types').SWRConfig<Data, Error>>
|
||||
) {
|
||||
return createSWRStores<Data, unknown>(key, fetcher, config as any).data
|
||||
}
|
||||
|
||||
export function swrErrorStore<Error = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<any>,
|
||||
config?: Partial<import('./types').SWRConfig<any, Error>>
|
||||
) {
|
||||
return createSWRStores<any, Error>(key, fetcher, config).error
|
||||
}
|
||||
|
||||
export function swrLoadingStore(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<any>,
|
||||
config?: Partial<import('./types').SWRConfig<any, any>>
|
||||
) {
|
||||
return createSWRStores<any, any>(key, fetcher, config).isLoading
|
||||
}
|
||||
|
||||
export function swrValidatingStore(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<any>,
|
||||
config?: Partial<import('./types').SWRConfig<any, any>>
|
||||
) {
|
||||
return createSWRStores<any, any>(key, fetcher, config).isValidating
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for manual mutation
|
||||
*/
|
||||
export function useSWRMutate<Data = unknown, Error = unknown>() {
|
||||
return (key: Key, data?: Data | ((current: Data | undefined) => Data | Promise<Data>), options?: Partial<import('../../core').MutatorOptions<Data>>) => {
|
||||
return globalSWR.mutate(key, data, options)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for accessing global SWR instance
|
||||
*/
|
||||
export function useSWRInstance() {
|
||||
return globalSWR
|
||||
}
|
||||
|
||||
export default createSWRStores
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["DOM", "ES2022"],
|
||||
"target": "ES2018",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "preserve",
|
||||
"allowJs": true,
|
||||
"types": ["bun"],
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"reference/swr",
|
||||
"reference/swrev",
|
||||
"reference/swrv",
|
||||
"sswr",
|
||||
"solid-swr"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { SWRConfig as CoreSWRConfig, Key, Fetcher, MutatorOptions, BareFetcher } from '../../core'
|
||||
import type { Writable, Readable } from 'svelte/store'
|
||||
|
||||
export interface SWRConfig<Data = unknown, Error = unknown> extends CoreSWRConfig<Data, Error> {
|
||||
// Svelte-specific config
|
||||
store?: boolean
|
||||
suspense?: boolean
|
||||
fallbackData?: Data
|
||||
revalidateOnMount?: boolean
|
||||
revalidateOnFocus?: boolean
|
||||
revalidateOnReconnect?: boolean
|
||||
refreshWhenHidden?: boolean
|
||||
refreshWhenOffline?: boolean
|
||||
compare?: (a: Data | undefined, b: Data | undefined) => boolean
|
||||
}
|
||||
|
||||
export interface SWRStores<Data = unknown, Error = unknown> {
|
||||
data: Readable<Data | undefined>
|
||||
error: Readable<Error | undefined>
|
||||
isLoading: Readable<boolean>
|
||||
isValidating: Readable<boolean>
|
||||
loading: Readable<boolean>
|
||||
validating: Readable<boolean>
|
||||
mutate: (data?: Data | ((current: Data | undefined) => Data | Promise<Data>), options?: Partial<MutatorOptions<Data>>) => Promise<Data | undefined>
|
||||
}
|
||||
|
||||
export interface SWRStore<Data = unknown, Error = unknown> extends Writable<{
|
||||
data?: Data
|
||||
error?: Error
|
||||
isLoading: boolean
|
||||
isValidating: boolean
|
||||
loading: boolean
|
||||
validating: boolean
|
||||
}> {
|
||||
mutate: (data?: Data | ((current: Data | undefined) => Data | Promise<Data>), options?: Partial<MutatorOptions<Data>>) => Promise<Data | undefined>
|
||||
}
|
||||
|
||||
// Re-export core types
|
||||
export type { Key, Fetcher, MutatorOptions, BareFetcher, SWRFetcher } from '../../core'
|
||||
@@ -0,0 +1,195 @@
|
||||
import { onDestroy } from 'svelte'
|
||||
import { writable, derived } from 'svelte/store'
|
||||
import { createSWR } from '../../core'
|
||||
import type { SWRConfig, SWRStores, SWRStore, Key, Fetcher, SWRFetcher } from './types'
|
||||
|
||||
// Global SWR instance
|
||||
const globalSWR = createSWR()
|
||||
|
||||
/**
|
||||
* Enhanced useSWR store for Svelte
|
||||
*/
|
||||
export function useSWR<Data = unknown, Error = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: SWRFetcher<Data> | null,
|
||||
config?: Partial<SWRConfig<Data, Error>>
|
||||
): SWRStores<Data, Error> {
|
||||
// Get config from context (using a simple object for now)
|
||||
const contextConfig = {} as Record<string, unknown>
|
||||
const mergedConfig = {
|
||||
...contextConfig,
|
||||
...config
|
||||
}
|
||||
|
||||
// Update config if provided
|
||||
if (mergedConfig) {
|
||||
globalSWR.updateConfig(mergedConfig as any)
|
||||
}
|
||||
|
||||
// Initial state logic
|
||||
let initialData: Data | undefined = undefined
|
||||
let initialError: Error | undefined = undefined
|
||||
let initialIsLoading = false
|
||||
let initialIsValidating = false
|
||||
|
||||
if (key) {
|
||||
const entry = globalSWR.get(key)
|
||||
if (entry) {
|
||||
initialData = entry.data as Data
|
||||
initialError = entry.error as Error
|
||||
initialIsLoading = entry.isLoading
|
||||
initialIsValidating = entry.isValidating
|
||||
}
|
||||
}
|
||||
|
||||
// Create base stores
|
||||
const dataStore = writable<Data | undefined>(initialData)
|
||||
const errorStore = writable<Error | undefined>(initialError)
|
||||
const isLoadingStore = writable<boolean>(initialIsLoading)
|
||||
const isValidatingStore = writable<boolean>(initialIsValidating)
|
||||
|
||||
// Subscribe to SWR changes
|
||||
let unsubscribe: (() => void) | undefined
|
||||
|
||||
const normalizedFetcher = fetcher ? (
|
||||
fetcher.length === 2 ? fetcher as Fetcher<Data> : (k: Key, options: unknown) => {
|
||||
if (Array.isArray(k)) {
|
||||
return (fetcher as (...args: unknown[]) => Promise<Data>)(...k)
|
||||
}
|
||||
return (fetcher as (arg: unknown) => Promise<Data>)(k)
|
||||
}
|
||||
) : null
|
||||
|
||||
if (key) {
|
||||
unsubscribe = globalSWR.subscribe(key, (newData, newError, newIsValidating, newIsLoading) => {
|
||||
dataStore.set(newData as Data)
|
||||
errorStore.set(newError as Error)
|
||||
isValidatingStore.set(newIsValidating ?? false)
|
||||
isLoadingStore.set(newIsLoading ?? false)
|
||||
}, normalizedFetcher as Fetcher<Data>)
|
||||
}
|
||||
|
||||
// Cleanup on destroy if inside a component
|
||||
try {
|
||||
onDestroy(() => {
|
||||
if (unsubscribe) unsubscribe()
|
||||
})
|
||||
} catch (e) {
|
||||
// Ignore error if called outside component initialization
|
||||
// (e.g. in tests without a component context)
|
||||
}
|
||||
|
||||
// Create mutate function
|
||||
const mutate = (mutateData?: Data | Promise<Data> | ((currentData?: Data) => Data | Promise<Data>), options?: unknown) => {
|
||||
if (!key) return Promise.resolve(undefined)
|
||||
return globalSWR.mutate(key, mutateData, options as any)
|
||||
}
|
||||
|
||||
return {
|
||||
data: { subscribe: dataStore.subscribe },
|
||||
error: { subscribe: errorStore.subscribe },
|
||||
isLoading: { subscribe: isLoadingStore.subscribe },
|
||||
isValidating: { subscribe: isValidatingStore.subscribe },
|
||||
loading: { subscribe: isLoadingStore.subscribe },
|
||||
validating: { subscribe: isValidatingStore.subscribe },
|
||||
mutate: mutate as any
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced useSWR store with all state combined
|
||||
*/
|
||||
export function useSWRStore<Data = unknown, Error = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: SWRFetcher<Data> | null,
|
||||
config?: Partial<SWRConfig<Data, Error>>
|
||||
): SWRStore<Data, Error> {
|
||||
const stores = useSWR<Data, Error>(key, fetcher, config)
|
||||
|
||||
let initialData: Data | undefined;
|
||||
let initialError: Error | undefined;
|
||||
let initialIsLoading = false;
|
||||
let initialIsValidating = false;
|
||||
|
||||
stores.data.subscribe(v => initialData = v)();
|
||||
stores.error.subscribe(v => initialError = v)();
|
||||
stores.isLoading.subscribe(v => initialIsLoading = v)();
|
||||
stores.isValidating.subscribe(v => initialIsValidating = v)();
|
||||
|
||||
const combinedStore = writable<{
|
||||
data?: Data
|
||||
error?: Error
|
||||
isLoading: boolean
|
||||
isValidating: boolean
|
||||
loading: boolean
|
||||
validating: boolean
|
||||
}>({
|
||||
data: initialData,
|
||||
error: initialError,
|
||||
isLoading: initialIsLoading,
|
||||
isValidating: initialIsValidating,
|
||||
loading: initialIsLoading,
|
||||
validating: initialIsValidating
|
||||
})
|
||||
|
||||
// Update combined store when individual stores change
|
||||
const unsubscribeData = stores.data.subscribe((data: any) => {
|
||||
combinedStore.update((state: any) => ({ ...state, data }))
|
||||
})
|
||||
|
||||
const unsubscribeError = stores.error.subscribe((error: any) => {
|
||||
combinedStore.update((state: any) => ({ ...state, error }))
|
||||
})
|
||||
|
||||
const unsubscribeLoading = stores.isLoading.subscribe((isLoading: any) => {
|
||||
combinedStore.update((state: any) => ({ ...state, isLoading, loading: isLoading }))
|
||||
})
|
||||
|
||||
const unsubscribeValidating = stores.isValidating.subscribe((isValidating) => {
|
||||
combinedStore.update((state) => ({ ...state, isValidating, validating: isValidating }))
|
||||
})
|
||||
|
||||
// Cleanup on destroy
|
||||
try {
|
||||
onDestroy(() => {
|
||||
unsubscribeData()
|
||||
unsubscribeError()
|
||||
unsubscribeLoading()
|
||||
unsubscribeValidating()
|
||||
})
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
return {
|
||||
...combinedStore,
|
||||
mutate: stores.mutate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for manual mutation
|
||||
*/
|
||||
export function useSWRMutate<Data = unknown, Error = unknown>() {
|
||||
return (key: Key, data?: Data | Promise<Data> | ((currentData?: Data) => Data | Promise<Data>), options?: unknown) => {
|
||||
return globalSWR.mutate(key, data, options as any)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for accessing global SWR instance
|
||||
*/
|
||||
export function useSWRInstance() {
|
||||
return globalSWR
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for prefetching data
|
||||
*/
|
||||
export function useSWRPrefetch<Data = any, Error = any>() {
|
||||
return (key: Key, fetcher: Fetcher<Data>) => {
|
||||
return globalSWR.revalidate(key, { dedupe: true })
|
||||
}
|
||||
}
|
||||
|
||||
export default useSWR
|
||||
Reference in New Issue
Block a user