Archived
init
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
// import { describe, expect, it, vi, beforeEach, afterEach } from "bun:test";
|
||||
// import { useSWR } from "../index";
|
||||
// import { effectScope } from "vue";
|
||||
|
||||
// function withSetup<T>(composable: () => T): [T, () => void] {
|
||||
// let result: T;
|
||||
// const scope = effectScope();
|
||||
// scope.run(() => {
|
||||
// result = composable();
|
||||
// });
|
||||
// return [result!, () => scope.stop()];
|
||||
// }
|
||||
|
||||
|
||||
// // Mock fetcher
|
||||
// const fetcher = vi.fn((key: string) => Promise.resolve(`data-${key}`));
|
||||
|
||||
// describe("useSWR (Vue)", () => {
|
||||
// beforeEach(() => {
|
||||
// vi.useFakeTimers();
|
||||
// fetcher.mockClear();
|
||||
// });
|
||||
|
||||
// afterEach(() => {
|
||||
// vi.useRealTimers();
|
||||
// });
|
||||
|
||||
// it("fetches data on mount", async () => {
|
||||
// // const { data, isLoading } = useSWR("test-key", fetcher);
|
||||
// const [{data, isLoading}, stop] = withSetup(() => useSWR("test-key", fetcher));
|
||||
|
||||
// // Initial state
|
||||
// expect(isLoading.value).toBe(true);
|
||||
|
||||
// // Wait for fetch
|
||||
// vi.runAllTimers();
|
||||
|
||||
// expect(data.value).toBe("data-test-key");
|
||||
// expect(isLoading.value).toBe(false);
|
||||
// stop();
|
||||
// });
|
||||
|
||||
// // it("deduplicates requests", async () => {
|
||||
// // const [{ data: d1 }, stop1] = withSetup(() =>useSWR("dedupe-key", fetcher));
|
||||
// // const [{ data: d2 }, stop2] = withSetup(() =>useSWR("dedupe-key", fetcher));
|
||||
|
||||
// // vi.runAllTimers();
|
||||
|
||||
// // expect(fetcher).toHaveBeenCalledTimes(1);
|
||||
// // expect(d1.value).toBe("data-dedupe-key");
|
||||
// // expect(d2.value).toBe("data-dedupe-key");
|
||||
// // stop1();
|
||||
// // stop2();
|
||||
// // });
|
||||
|
||||
// // it("mutates data manually", async () => {
|
||||
// // const { data, mutate } = useSWR("mutate-key", fetcher);
|
||||
|
||||
// // vi.runAllTimers();
|
||||
|
||||
// // expect(data.value).toBe("data-mutate-key");
|
||||
|
||||
// // await mutate("new-data");
|
||||
|
||||
// // expect(data.value).toBe("new-data");
|
||||
// // });
|
||||
// });
|
||||
@@ -0,0 +1,5 @@
|
||||
// Vue 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,15 @@
|
||||
{
|
||||
"name": "@supwr/vue",
|
||||
"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,86 @@
|
||||
import { ref, computed, onUnmounted, watch } from 'vue'
|
||||
import { createSWR } from '../../core'
|
||||
import type { Key, Fetcher, SWRConfig as CoreSWRConfig } from '../../core'
|
||||
import type { SWRRefs, SWRRef, SWRConfig } from './types'
|
||||
|
||||
// Create a basic Vue SWR implementation
|
||||
export function createSWRComposables<Data = any, Error = any>(
|
||||
key: Key,
|
||||
fetcher: Fetcher<Data>,
|
||||
config?: SWRConfig<Data, Error>
|
||||
): SWRRefs<Data, Error> {
|
||||
const swr = createSWR<Data, Error>({ fetcher, ...config })
|
||||
|
||||
const data = ref<Data | undefined>()
|
||||
const error = ref<Error | undefined>()
|
||||
const loading = ref(false)
|
||||
const validating = ref(false)
|
||||
|
||||
// Subscribe to SWR changes
|
||||
const unsubscribe = swr.subscribe(key, (newData, newError, newValidating, newLoading) => {
|
||||
data.value = newData
|
||||
error.value = newError
|
||||
validating.value = newValidating
|
||||
loading.value = newLoading
|
||||
})
|
||||
|
||||
// Cleanup on unmount
|
||||
onUnmounted(() => {
|
||||
unsubscribe()
|
||||
})
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
validating,
|
||||
mutate: (mutateData, options) => swr.mutate(key, mutateData as any, options),
|
||||
revalidate: (options) => swr.revalidate(key, options)
|
||||
}
|
||||
}
|
||||
|
||||
export function createSWRRef<Data = any, Error = any>(
|
||||
key: Key,
|
||||
fetcher: Fetcher<Data>,
|
||||
config?: SWRConfig<Data, Error>
|
||||
): SWRRef<Data, Error> {
|
||||
return createSWRComposables(key, fetcher, config) as SWRRef<Data, Error>
|
||||
}
|
||||
|
||||
// Export individual refs for convenience
|
||||
export const swrDataRef = <T>(key: Key, fetcher: Fetcher<T>, config?: SWRConfig<T>) =>
|
||||
createSWRRef(key, fetcher, config).data
|
||||
|
||||
export const swrErrorRef = <T>(key: Key, fetcher: Fetcher<T>, config?: SWRConfig<T>) =>
|
||||
createSWRRef(key, fetcher, config).error
|
||||
|
||||
export const swrLoadingRef = <T>(key: Key, fetcher: Fetcher<T>, config?: SWRConfig<T>) =>
|
||||
createSWRRef(key, fetcher, config).loading
|
||||
|
||||
export const swrValidatingRef = <T>(key: Key, fetcher: Fetcher<T>, config?: SWRConfig<T>) =>
|
||||
createSWRRef(key, fetcher, config).validating
|
||||
|
||||
// Hook-like function for Vue
|
||||
export function useSWR<Data = any, Error = any>(
|
||||
key: Key,
|
||||
fetcher: Fetcher<Data>,
|
||||
config?: SWRConfig<Data, Error>
|
||||
): SWRRefs<Data, Error> {
|
||||
return createSWRComposables(key, fetcher, config)
|
||||
}
|
||||
|
||||
export function useSWRMutate<Data = any, Error = any>(
|
||||
key: Key,
|
||||
fetcher: Fetcher<Data>,
|
||||
config?: SWRConfig<Data, Error>
|
||||
): SWRRefs<Data, Error> {
|
||||
return createSWRComposables(key, fetcher, config)
|
||||
}
|
||||
|
||||
export function useSWRInstance<Data = any, Error = any>(
|
||||
key: Key,
|
||||
fetcher: Fetcher<Data>,
|
||||
config?: SWRConfig<Data, Error>
|
||||
): SWRRefs<Data, Error> {
|
||||
return createSWRComposables(key, fetcher, config)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"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",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Ref, ComputedRef } from 'vue'
|
||||
import type { Key, Fetcher, SWRConfig as CoreSWRConfig, MutatorOptions, BareFetcher } from '../../core'
|
||||
|
||||
export interface SWRRefs<Data = any, Error = any> {
|
||||
data: Ref<Data | undefined>
|
||||
error: Ref<Error | undefined>
|
||||
isLoading: Ref<boolean>
|
||||
isValidating: Ref<boolean>
|
||||
mutate: (data?: Data | Promise<Data> | ((currentData?: Data) => Data | Promise<Data>), options?: any) => Promise<Data | undefined>
|
||||
revalidate: (options?: any) => Promise<boolean>
|
||||
}
|
||||
|
||||
export interface SWRRef<Data = any, Error = any> extends SWRRefs<Data, Error> {
|
||||
// Additional Vue-specific methods can be added here
|
||||
}
|
||||
|
||||
export interface SWRConfig<Data = any, Error = any> extends CoreSWRConfig<Data, Error> {
|
||||
// Vue-specific configuration options
|
||||
suspense?: boolean
|
||||
fallbackData?: Data
|
||||
revalidateOnMount?: boolean
|
||||
revalidateOnFocus?: boolean
|
||||
revalidateOnReconnect?: boolean
|
||||
refreshWhenHidden?: boolean
|
||||
refreshWhenOffline?: boolean
|
||||
compare?: (a: Data | undefined, b: Data | undefined) => boolean
|
||||
}
|
||||
|
||||
// Re-export core types with SWR-compatible names
|
||||
export type { Key, Fetcher, MutatorOptions, BareFetcher, SWRFetcher } from '../../core'
|
||||
@@ -0,0 +1,199 @@
|
||||
import { ref, computed, onUnmounted, Suspense, defineComponent, provide, inject, watch } from 'vue'
|
||||
import { createSWR } from '../../core'
|
||||
import type { SWRConfig as SWRConfiguration, SWRRefs, Key, Fetcher, SWRFetcher } from './types'
|
||||
|
||||
// Global SWR instance
|
||||
const globalSWR = createSWR()
|
||||
|
||||
// Vue injection key for configuration
|
||||
const SWR_CONFIG_KEY = Symbol('swr-config')
|
||||
|
||||
/**
|
||||
* Enhanced useSWR composable for Vue
|
||||
*/
|
||||
export function useSWR<Data = unknown, Error = unknown>(
|
||||
key: Key | null,
|
||||
fetcher?: SWRFetcher<Data> | null,
|
||||
config?: Partial<SWRConfiguration<Data, Error>>
|
||||
): SWRRefs<Data, Error> {
|
||||
// Get config from injection and props
|
||||
const injectedConfig = inject(SWR_CONFIG_KEY, {})
|
||||
const mergedConfig = computed(() => ({
|
||||
...injectedConfig,
|
||||
...config
|
||||
}))
|
||||
|
||||
// Create a stable reference to SWR instance with config
|
||||
const swrRef = ref(globalSWR)
|
||||
|
||||
// Update config if changed
|
||||
watch(mergedConfig, (newConfig) => {
|
||||
if (newConfig) {
|
||||
swrRef.value.updateConfig(newConfig as Partial<SWRConfiguration<Data, Error>> as any)
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// Normalize fetcher to handle different signatures
|
||||
const normalizedFetcher = computed(() => {
|
||||
const f = typeof fetcher === 'function' ? fetcher : null
|
||||
if (!f) return null
|
||||
|
||||
// If it's already a core fetcher, use it as-is
|
||||
if (f.length === 2) return f as Fetcher<Data>
|
||||
|
||||
// Convert bare fetcher to core fetcher
|
||||
return (k: Key, options: unknown) => {
|
||||
if (Array.isArray(k)) {
|
||||
return (f as (...args: unknown[]) => Promise<Data>)(...k)
|
||||
}
|
||||
return (f as (arg: unknown) => Promise<Data>)(k)
|
||||
}
|
||||
})
|
||||
|
||||
// Create refs
|
||||
const data = ref<Data | undefined>(undefined)
|
||||
const error = ref<Error | undefined>(undefined)
|
||||
const loading = ref(false)
|
||||
const validating = ref(false)
|
||||
|
||||
// Subscribe to SWR changes
|
||||
let unsubscribe: (() => void) | undefined
|
||||
|
||||
const updateRefs = (newData: Data | undefined, newError: Error | undefined, newValidating?: boolean, newLoading?: boolean) => {
|
||||
data.value = newData
|
||||
error.value = newError
|
||||
validating.value = newValidating ?? false
|
||||
loading.value = newLoading ?? false
|
||||
}
|
||||
|
||||
// Watch for key and fetcher changes
|
||||
watch([() => key, normalizedFetcher], ([newKey, newFetcher]) => {
|
||||
// Cleanup previous subscription
|
||||
if (unsubscribe) {
|
||||
unsubscribe()
|
||||
}
|
||||
|
||||
// Subscribe to new key if available
|
||||
if (newKey && newFetcher) {
|
||||
const entry = swrRef.value.get(newKey)
|
||||
data.value = (entry?.data ?? mergedConfig.value?.fallbackData) as Data | undefined
|
||||
error.value = entry?.error as Error | undefined
|
||||
validating.value = entry?.isValidating ?? false
|
||||
loading.value = entry?.isLoading ?? false
|
||||
|
||||
unsubscribe = swrRef.value.subscribe(newKey, updateRefs as any, newFetcher as Fetcher<Data>)
|
||||
}
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
// Cleanup on unmount
|
||||
onUnmounted(() => {
|
||||
if (unsubscribe) {
|
||||
unsubscribe()
|
||||
}
|
||||
})
|
||||
|
||||
// Create mutate function
|
||||
const mutate = computed(() =>
|
||||
(mutateData?: Data | Promise<Data> | ((currentData?: Data) => Data | Promise<Data>), options?: unknown) => {
|
||||
if (!key) return Promise.resolve(undefined)
|
||||
return swrRef.value.mutate(key, mutateData, options as any)
|
||||
}
|
||||
)
|
||||
|
||||
// Handle suspense mode
|
||||
if (mergedConfig.value?.suspense) {
|
||||
if (error.value) {
|
||||
throw error.value
|
||||
}
|
||||
if (data.value === undefined) {
|
||||
// In suspense mode, we need to trigger fetch and throw a promise
|
||||
const promise = new Promise<Data>((resolve, reject) => {
|
||||
const u = swrRef.value.subscribe(key, (d, e) => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else if (d !== undefined) {
|
||||
resolve(d as Data)
|
||||
}
|
||||
})
|
||||
|
||||
// Start the fetch if not already loading
|
||||
const entry = swrRef.value.get(key)
|
||||
if (!entry?.isLoading && !entry?.isValidating) {
|
||||
swrRef.value.revalidate(key)
|
||||
}
|
||||
|
||||
return u
|
||||
})
|
||||
|
||||
throw promise
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: data as any,
|
||||
error: error as any,
|
||||
isLoading: loading,
|
||||
isValidating: validating,
|
||||
mutate: mutate.value as any,
|
||||
revalidate: (options?: unknown) => swrRef.value.revalidate(key, options as any)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue configuration provider component
|
||||
*/
|
||||
export const SWRConfig = defineComponent({
|
||||
name: 'SWRConfig',
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
// Provide configuration to child components
|
||||
provide(SWR_CONFIG_KEY, props.value || {})
|
||||
|
||||
return () => slots.default?.()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Interface type for configuration
|
||||
*/
|
||||
export type SWRConfig<Data = unknown, Error = unknown> = SWRConfiguration<Data, Error>
|
||||
|
||||
/**
|
||||
* Hook for manual mutation
|
||||
*/
|
||||
export function useSWRMutate<Data = unknown, Error = unknown>() {
|
||||
return computed(() =>
|
||||
(key: Key, data?: Data | Promise<Data> | ((currentData?: Data) => Data | Promise<Data>), options?: unknown) => {
|
||||
return globalSWR.mutate(key, data, options as any)
|
||||
}
|
||||
).value
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for accessing global SWR instance
|
||||
*/
|
||||
export function useSWRInstance() {
|
||||
return ref(globalSWR)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for prefetching data
|
||||
*/
|
||||
export function useSWRPrefetch<Data = any, Error = any>() {
|
||||
return computed(() =>
|
||||
(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 })
|
||||
}
|
||||
).value
|
||||
}
|
||||
|
||||
export default useSWR
|
||||
Reference in New Issue
Block a user