Archived
init
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// import { describe, expect, it, vi, beforeEach, afterEach } from "bun:test";
|
||||
// import { createRoot } from "solid-js";
|
||||
// import { useSWR } from "../index";
|
||||
|
||||
// // Mock fetcher
|
||||
// const fetcher = vi.fn((key: string) => Promise.resolve(`data-${key}`));
|
||||
|
||||
// describe("useSWR (Solid)", () => {
|
||||
// beforeEach(() => {
|
||||
// vi.useFakeTimers();
|
||||
// fetcher.mockClear();
|
||||
// });
|
||||
|
||||
// afterEach(() => {
|
||||
// vi.useRealTimers();
|
||||
// });
|
||||
|
||||
// it("fetches data on mount", async () => {
|
||||
// createRoot((dispose) => {
|
||||
// const result = useSWR(async () => "test-key", fetcher);
|
||||
|
||||
// // Initial state
|
||||
// expect(result.isLoading).toBe(true);
|
||||
|
||||
// // Wait for fetch
|
||||
// vi.runAllTimers();
|
||||
|
||||
// expect(result.data).toBe("data-test-key");
|
||||
// expect(result.isLoading).toBe(false);
|
||||
|
||||
// dispose();
|
||||
// });
|
||||
// });
|
||||
|
||||
// it("deduplicates requests", async () => {
|
||||
// createRoot((dispose) => {
|
||||
// const r1 = useSWR(() => "dedupe-key", fetcher);
|
||||
// const r2 = useSWR(() => "dedupe-key", fetcher);
|
||||
|
||||
// vi.runAllTimers();
|
||||
|
||||
// expect(fetcher).toHaveBeenCalledTimes(1);
|
||||
// expect(r1.data).toBe("data-dedupe-key");
|
||||
// expect(r2.data).toBe("data-dedupe-key");
|
||||
|
||||
// dispose();
|
||||
// });
|
||||
// });
|
||||
|
||||
// it("mutates data manually", async () => {
|
||||
// await new Promise<void>((resolve) => {
|
||||
// createRoot(async (dispose) => {
|
||||
// const result = useSWR(() => "mutate-key", fetcher);
|
||||
|
||||
// vi.runAllTimers();
|
||||
|
||||
// expect(result.data).toBe("data-mutate-key");
|
||||
|
||||
// await result.mutate("new-data");
|
||||
|
||||
// expect(result.data).toBe("new-data");
|
||||
|
||||
// dispose();
|
||||
// resolve();
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
@@ -0,0 +1,5 @@
|
||||
// Solid SWR exports
|
||||
export { default as useSWR, SWRConfig, useSWRMutate, useSWRInstance, useSWRPrefetch } from './use-swr'
|
||||
|
||||
// Re-export core types
|
||||
export type { Key, Fetcher } from '../../core'
|
||||
@@ -0,0 +1,181 @@
|
||||
import {
|
||||
createSignal,
|
||||
createEffect,
|
||||
onCleanup,
|
||||
batch
|
||||
} from 'solid-js'
|
||||
import { createSWR } from '../../core'
|
||||
import type { SWRSignals, SWRResponse, Key, Fetcher, MutatorOptions } from './types'
|
||||
|
||||
// Global SWR instance
|
||||
const globalSWR = createSWR()
|
||||
|
||||
/**
|
||||
* Create SWR signals for Solid
|
||||
*/
|
||||
export function createSWRSignals<Data = unknown, Error = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<Data>,
|
||||
config?: Partial<import('./types').SWRConfig<Data, Error>>
|
||||
): SWRSignals<Data, Error> {
|
||||
// Update config if provided
|
||||
if (config) {
|
||||
globalSWR.updateConfig(config)
|
||||
}
|
||||
|
||||
// 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
|
||||
} else {
|
||||
initialData = config?.fallbackData
|
||||
}
|
||||
}
|
||||
|
||||
// Create signals
|
||||
const [data, setData] = createSignal<Data | undefined>(initialData)
|
||||
const [error, setError] = createSignal<Error | undefined>(initialError)
|
||||
const [isLoading, setIsLoading] = createSignal<boolean>(initialIsLoading)
|
||||
const [isValidating, setIsValidating] = createSignal<boolean>(initialIsValidating)
|
||||
|
||||
// Subscribe to SWR changes
|
||||
let unsubscribe: (() => void) | undefined
|
||||
|
||||
if (key) {
|
||||
unsubscribe = globalSWR.subscribe(key, (newData, newError, newIsValidating, newIsLoading) => {
|
||||
batch(() => {
|
||||
setData(newData)
|
||||
setError(newError)
|
||||
setIsValidating(newIsValidating ?? false)
|
||||
setIsLoading(newIsLoading ?? false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Cleanup on destroy
|
||||
onCleanup(() => {
|
||||
unsubscribe?.()
|
||||
})
|
||||
|
||||
// Create mutate function
|
||||
const mutate = (mutateData?: Data | ((current: Data | undefined) => Data | Promise<Data>), options?: Partial<MutatorOptions<Data>>) => {
|
||||
if (!key) return Promise.resolve(undefined)
|
||||
return globalSWR.mutate(key, mutateData, options)
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
isLoading,
|
||||
isValidating,
|
||||
loading: isLoading,
|
||||
validating: isValidating,
|
||||
mutate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create SWR response object for Solid
|
||||
*/
|
||||
export function createSWRResponse<Data = unknown, Error = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<Data>,
|
||||
config?: Partial<import('./types').SWRConfig<Data, Error>>
|
||||
): SWRResponse<Data, Error> {
|
||||
const signals = createSWRSignals<Data, Error>(key, fetcher, config)
|
||||
|
||||
return {
|
||||
get data() { return signals.data() },
|
||||
get error() { return signals.error() },
|
||||
get isLoading() { return signals.isLoading() },
|
||||
get isValidating() { return signals.isValidating() },
|
||||
get loading() { return signals.isLoading() },
|
||||
get validating() { return signals.isValidating() },
|
||||
mutate: signals.mutate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook-like function returning array [data, { mutate, error, isLoading, isValidating }]
|
||||
*/
|
||||
export function useSWR<Data = unknown, Error = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<Data>,
|
||||
config?: Partial<import('./types').SWRConfig<Data, Error>>
|
||||
): [Data | undefined, SWRResponse<Data, Error>] {
|
||||
const signals = createSWRSignals<Data, Error>(key, fetcher, config)
|
||||
|
||||
const response: SWRResponse<Data, Error> = {
|
||||
get data() { return signals.data() },
|
||||
get error() { return signals.error() },
|
||||
get isLoading() { return signals.isLoading() },
|
||||
get isValidating() { return signals.isValidating() },
|
||||
get loading() { return signals.isLoading() },
|
||||
get validating() { return signals.isValidating() },
|
||||
mutate: signals.mutate
|
||||
}
|
||||
|
||||
return [signals.data(), response]
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual signal creators
|
||||
*/
|
||||
export function swrDataSignal<Data = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<Data>,
|
||||
config?: Partial<import('./types').SWRConfig<Data, Error>>
|
||||
) {
|
||||
return createSWRSignals<Data, unknown>(key, fetcher, config as any).data
|
||||
}
|
||||
|
||||
export function swrErrorSignal<Error = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<any>,
|
||||
config?: Partial<import('./types').SWRConfig<any, Error>>
|
||||
) {
|
||||
return createSWRSignals<any, Error>(key, fetcher, config).error
|
||||
}
|
||||
|
||||
export function swrLoadingSignal(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<any>,
|
||||
config?: Partial<import('./types').SWRConfig<any, any>>
|
||||
) {
|
||||
return createSWRSignals<any, any>(key, fetcher, config).isLoading
|
||||
}
|
||||
|
||||
export function swrValidatingSignal(
|
||||
key: Key | null,
|
||||
fetcher?: Fetcher<any>,
|
||||
config?: Partial<import('./types').SWRConfig<any, any>>
|
||||
) {
|
||||
return createSWRSignals<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<MutatorOptions<Data>>) => {
|
||||
return globalSWR.mutate(key, data, options)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for accessing global SWR instance
|
||||
*/
|
||||
export function useSWRInstance() {
|
||||
return globalSWR
|
||||
}
|
||||
|
||||
export default createSWRSignals
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"types": ["solid-js", "bun"],
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"exclude": [
|
||||
"../react/**/*",
|
||||
"../../node_modules/@types/react"
|
||||
],
|
||||
"include": ["**/*", "../../core/**/*"]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { SWRConfig as CoreSWRConfig, Key, Fetcher, MutatorOptions, BareFetcher } from '../../core'
|
||||
import type { Accessor, Signal } from 'solid-js'
|
||||
|
||||
export interface SWRConfig<Data = unknown, Error = unknown> extends CoreSWRConfig<Data, Error> {
|
||||
// Solid-specific config
|
||||
signal?: 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 SWRSignals<Data = unknown, Error = unknown> {
|
||||
data: Accessor<Data | undefined>
|
||||
error: Accessor<Error | undefined>
|
||||
isLoading: Accessor<boolean>
|
||||
isValidating: Accessor<boolean>
|
||||
loading: Accessor<boolean>
|
||||
validating: Accessor<boolean>
|
||||
mutate: (data?: Data | ((current: Data | undefined) => Data | Promise<Data>), options?: Partial<MutatorOptions<Data>>) => Promise<Data | undefined>
|
||||
}
|
||||
|
||||
export interface SWRResponse<Data = unknown, Error = unknown> {
|
||||
data: Data | undefined
|
||||
error: Error | undefined
|
||||
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 with SWR-compatible names
|
||||
export type { Key, Fetcher, MutatorOptions, BareFetcher, SWRFetcher } from '../../core'
|
||||
@@ -0,0 +1,190 @@
|
||||
/** @jsxRuntime automatic */
|
||||
/** @jsxImportSource solid-js */
|
||||
|
||||
import { createSignal, createEffect, onCleanup, Suspense, Show, createContext, useContext, createMemo } from 'solid-js'
|
||||
import { createSWR } from '../../core'
|
||||
import type { SWRConfig as SWRConfiguration, SWRResponse, Key, Fetcher, SWRFetcher } from './types'
|
||||
|
||||
// Global SWR instance
|
||||
const globalSWR = createSWR()
|
||||
|
||||
// Solid Context for configuration
|
||||
const ConfigContext = createContext<Partial<SWRConfiguration<any, any>>>({})
|
||||
|
||||
/**
|
||||
* Enhanced useSWR hook for Solid
|
||||
*/
|
||||
export function useSWR<Data = any, Error = any>(
|
||||
key: Key | null,
|
||||
fetcher?: SWRFetcher<Data> | null,
|
||||
config?: Partial<SWRConfiguration<Data, Error>>
|
||||
): SWRResponse<Data, Error> {
|
||||
// Get config from context and props
|
||||
const contextConfig = useContext(ConfigContext)
|
||||
const mergedConfig = createMemo(() => ({
|
||||
...contextConfig,
|
||||
...config
|
||||
}))
|
||||
|
||||
// Create a stable reference to SWR instance with config
|
||||
const swrRef = createMemo(() => globalSWR)
|
||||
|
||||
// Update config if changed
|
||||
createEffect(() => {
|
||||
if (mergedConfig) {
|
||||
swrRef().updateConfig(mergedConfig as any)
|
||||
}
|
||||
})
|
||||
|
||||
// Normalize fetcher to handle different signatures
|
||||
const normalizedFetcher = createMemo(() => {
|
||||
if (!fetcher) return null
|
||||
|
||||
// If it's already a core fetcher, use it as-is
|
||||
if (fetcher.length === 2) return fetcher
|
||||
|
||||
// Convert bare fetcher to core fetcher
|
||||
return (k: Key, options: any) => {
|
||||
if (Array.isArray(k)) {
|
||||
return (fetcher as any)(...k)
|
||||
}
|
||||
return (fetcher as any)(k)
|
||||
}
|
||||
})
|
||||
|
||||
// Create signals
|
||||
const [data, setData] = createSignal<Data | undefined>(
|
||||
key ? swrRef().get(key)?.data ?? mergedConfig()?.fallbackData : mergedConfig()?.fallbackData
|
||||
)
|
||||
const [error, setError] = createSignal<Error | undefined>(
|
||||
key ? swrRef().get(key)?.error as Error : undefined
|
||||
)
|
||||
const [isLoading, setIsLoading] = createSignal<boolean>(
|
||||
key ? swrRef().get(key)?.isLoading ?? false : false
|
||||
)
|
||||
const [isValidating, setIsValidating] = createSignal<boolean>(
|
||||
key ? swrRef().get(key)?.isValidating ?? false : false
|
||||
)
|
||||
|
||||
// Subscribe to SWR changes
|
||||
let unsubscribe: (() => void) | undefined
|
||||
|
||||
createEffect(() => {
|
||||
if (!key || !normalizedFetcher()) return
|
||||
|
||||
unsubscribe = swrRef().subscribe(key, (newData: any, newError: any, newIsValidating?: boolean, newIsLoading?: boolean) => {
|
||||
setData(newData)
|
||||
setError(newError)
|
||||
setIsValidating(newIsValidating ?? false)
|
||||
setIsLoading(newIsLoading ?? false)
|
||||
}, normalizedFetcher() as any)
|
||||
|
||||
return unsubscribe
|
||||
})
|
||||
|
||||
// Cleanup on destroy
|
||||
onCleanup(() => {
|
||||
unsubscribe?.()
|
||||
})
|
||||
|
||||
// Create mutate function
|
||||
const mutate = createMemo(() =>
|
||||
(mutateData?: Data | Promise<Data> | ((currentData?: Data) => Data | Promise<Data>), options?: any): Promise<Data | undefined> => {
|
||||
if (!key) return Promise.resolve(undefined)
|
||||
return swrRef().mutate(key, mutateData, options) as Promise<Data>
|
||||
}
|
||||
)
|
||||
|
||||
// Handle suspense mode
|
||||
if ((mergedConfig as any)?.suspense) {
|
||||
if (error()) {
|
||||
throw error()
|
||||
}
|
||||
if (data() === undefined) {
|
||||
// In suspense mode, we need to trigger fetch and throw a promise
|
||||
const promise = new Promise<Data>((resolve, reject) => {
|
||||
const unsubscribe = swrRef().subscribe(key, (data: any, error: any) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else if (data !== undefined) {
|
||||
resolve(data)
|
||||
}
|
||||
})
|
||||
|
||||
// Start the fetch if not already loading
|
||||
const entry = swrRef().get(key)
|
||||
if (!entry?.isLoading && !entry?.isValidating) {
|
||||
swrRef().revalidate(key)
|
||||
}
|
||||
|
||||
return unsubscribe
|
||||
})
|
||||
|
||||
throw promise
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
get data() { return data() },
|
||||
get error() { return error() },
|
||||
get isLoading() { return isLoading() },
|
||||
get isValidating() { return isValidating() },
|
||||
get loading() { return isLoading() },
|
||||
get validating() { return isValidating() },
|
||||
mutate: mutate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Solid configuration provider component
|
||||
*/
|
||||
export function SWRConfig<Data = any, Error = any>(props: {
|
||||
children: any
|
||||
value?: Partial<SWRConfiguration<Data, Error>>
|
||||
}) {
|
||||
return (
|
||||
<ConfigContext.Provider value={props.value || {}}>
|
||||
{props.children}
|
||||
</ConfigContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface type for configuration
|
||||
*/
|
||||
export type SWRConfig<Data = any, Error = any> = SWRConfiguration<Data, Error>
|
||||
|
||||
/**
|
||||
* Hook for manual mutation
|
||||
*/
|
||||
export function useSWRMutate<Data = any, Error = any>() {
|
||||
return createMemo(() =>
|
||||
(key: Key, data?: Data | Promise<Data> | ((currentData?: Data) => Data | Promise<Data>), options?: any) => {
|
||||
return globalSWR.mutate(key, data, options)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for accessing global SWR instance
|
||||
*/
|
||||
export function useSWRInstance() {
|
||||
return globalSWR
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for prefetching data
|
||||
*/
|
||||
export function useSWRPrefetch<Data = any, Error = any>() {
|
||||
return createMemo(() =>
|
||||
(key: Key, fetcher: Fetcher<Data>) => {
|
||||
const normalizedFetcher = fetcher.length === 2
|
||||
? fetcher
|
||||
: (k: Key) => (fetcher as any)(Array.isArray(k) ? k : [k])
|
||||
|
||||
return globalSWR.revalidate(key, { dedupe: true })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export default useSWR
|
||||
Reference in New Issue
Block a user