TypeScript Guide

Full TypeScript support with type inference and type safety.

Type Inference

Storken automatically infers types from your initial values:

const [useStorken] = create({
  initialValues: {
    count: 0,              // inferred as number
    name: 'John',          // inferred as string
    isActive: true,        // inferred as boolean
    data: null as Data | null  // explicit type annotation
  }
})

// TypeScript knows the types
const [count, setCount] = useStorken('count')
// count: number
// setCount: (value: number | ((prev: number) => number)) => void

Generic Types

Explicitly specify types using generics for better type safety:

interface User {
  id: string
  name: string
  email: string
  role: 'admin' | 'user'
}

// Explicit type annotation
const [user, setUser] = useStorken<User | null>('user', null)

// Type-safe updates
setUser({
  id: '123',
  name: 'John Doe',
  email: 'john@example.com',
  role: 'admin'
})

// TypeScript will error on invalid properties
setUser({
  id: '123',
  name: 'John',
  // Error: Property 'email' is missing
})

Typed Configurations

Create fully typed store configurations:

import { create, StorkenConfig } from 'storken'

interface AppState {
  user: User | null
  theme: 'light' | 'dark'
  todos: Todo[]
  settings: Settings
}

const config: StorkenConfig<AppState> = {
  initialValues: {
    user: null,
    theme: 'light',
    todos: [],
    settings: defaultSettings
  },
  getters: {
    user: async (): Promise<User> => {
      const res = await fetch('/api/user')
      return res.json()
    }
  },
  setters: {
    todos: async (storken, todos: Todo[]) => {
      await saveTodos(todos)
    }
  }
}

const [useStorken] = create(config)

Custom Hook Types

Create typed custom hooks for your store:

// store.ts
export const [useStorken, get, set, sky] = create({
  initialValues: {
    user: null as User | null,
    theme: 'light' as Theme,
    notifications: [] as Notification[]
  }
})

// hooks/useUser.ts
export function useUser() {
  const [user, setUser, resetUser, loading] = useStorken<User | null>('user')
  
  const login = async (credentials: Credentials) => {
    const user = await authService.login(credentials)
    setUser(user)
    return user
  }
  
  const logout = () => {
    resetUser()
    authService.logout()
  }
  
  return {
    user,
    loading,
    login,
    logout,
    isAuthenticated: !!user
  }
}

Plugin Types

Type your custom plugins for better IDE support:

import { StorkenPlugin, StorkenInstance } from 'storken'

interface LoggerPluginOptions {
  prefix?: string
  logLevel?: 'debug' | 'info' | 'warn' | 'error'
}

interface LoggerPluginAPI {
  log: (message: string) => void
  setLevel: (level: LoggerPluginOptions['logLevel']) => void
}

const createLoggerPlugin = (
  options?: LoggerPluginOptions
): StorkenPlugin<LoggerPluginAPI> => {
  return (storken: StorkenInstance) => {
    let level = options?.logLevel || 'info'
    const prefix = options?.prefix || '[Storken]'
    
    storken.on('set', (value) => {
      if (level !== 'error') {
        console.log(`${prefix} ${storken.key} updated:`, value)
      }
    })
    
    return {
      log: (message: string) => {
        console.log(`${prefix} ${message}`)
      },
      setLevel: (newLevel) => {
        level = newLevel
      }
    }
  }
}

// Use with type safety
const [useStorken] = create({
  plugins: {
    logger: createLoggerPlugin({ prefix: '[App]' })
  }
})

const [value, , , , , plugins] = useStorken('key')
plugins.logger.log('Hello')  // Fully typed!

Async Types

Handle async operations with proper typing:

interface ApiResponse<T> {
  data: T
  error?: string
  status: number
}

const [useStorken] = create({
  getters: {
    posts: async (storken, userId: string): Promise<Post[]> => {
      const response = await fetch(`/api/users/${userId}/posts`)
      const result: ApiResponse<Post[]> = await response.json()
      
      if (result.error) {
        throw new Error(result.error)
      }
      
      return result.data
    }
  }
})

// Usage with proper typing
function UserPosts({ userId }: { userId: string }) {
  const [posts, , , loading, refetch] = useStorken<Post[]>('posts')
  
  useEffect(() => {
    refetch(userId)  // Pass typed arguments
  }, [userId, refetch])
  
  if (loading) return <Loading />
  
  return (
    <div>
      {posts?.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  )
}

Utility Types

Storken exports useful utility types:

import type {
  StorkenConfig,      // Configuration object type
  StorkenHookReturn,  // Hook return tuple type
  StorkenGetter,      // Getter function type
  StorkenSetter,      // Setter function type
  StorkenPlugin,      // Plugin function type
  StorkenInstance,    // Store instance type
  Sky                 // Sky instance type
} from 'storken'

// Use utility types for your abstractions
type AppGetter<T> = StorkenGetter<T, [userId: string]>
type AppSetter<T> = StorkenSetter<T>

// Create typed helpers
function createAppStore<T extends Record<string, any>>(
  config: StorkenConfig<T>
) {
  return create(config)
}

💡 TypeScript Best Practices

  • • Always define interfaces for complex state shapes
  • • Use generics when the type isn't inferrable
  • • Export types alongside your store for reusability
  • • Leverage type inference where possible
  • • Use strict mode for better type safety