diff --git a/packages/spark-tools/rollup.config.ts b/packages/spark-tools/rollup.config.ts new file mode 100644 index 0000000..563b53f --- /dev/null +++ b/packages/spark-tools/rollup.config.ts @@ -0,0 +1,33 @@ +import typescript from '@rollup/plugin-typescript' +import resolve from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs' +import terser from '@rollup/plugin-terser' +import del from 'rollup-plugin-delete' + +export default { + input: { + index: 'src/index.ts', + spark: 'src/spark.ts', + sparkVitePlugin: 'src/sparkVitePlugin.ts', + vitePhosphorIconProxyPlugin: 'src/vitePhosphorIconProxyPlugin.ts', + }, + output: { + dir: 'dist', + format: 'es', + sourcemap: true, + preserveModules: false, + }, + external: ['react', 'react-dom', 'vite'], + plugins: [ + del({ targets: 'dist/*' }), + resolve(), + commonjs(), + typescript({ + tsconfig: './tsconfig.json', + declaration: true, + declarationDir: 'dist', + rootDir: 'src', + }), + terser(), + ], +} diff --git a/packages/spark-tools/src/hooks/index.ts b/packages/spark-tools/src/hooks/index.ts new file mode 100644 index 0000000..6f1f16b --- /dev/null +++ b/packages/spark-tools/src/hooks/index.ts @@ -0,0 +1,94 @@ +import { useState, useEffect, useCallback } from 'react' + +/** + * useKV Hook - Persistent key-value storage with localStorage and window.spark.kv integration + * + * This hook provides persistent state management that syncs with localStorage + * and integrates with the Spark KV storage system if available. + * + * @param key - Storage key + * @param defaultValue - Default value if key doesn't exist + * @returns Tuple of [value, setValue, deleteValue] + */ +export function useKV( + key: string, + defaultValue: T +): [T, (value: T | ((prev: T) => T)) => void, () => void] { + // Initialize state from localStorage or default value + const [value, setValueInternal] = useState(() => { + try { + // Try to get from window.spark.kv first + if (typeof window !== 'undefined' && window.spark?.kv) { + const sparkValue = window.spark.kv.get(key) + if (sparkValue !== undefined) { + return sparkValue as T + } + } + + // Fallback to localStorage + const item = localStorage.getItem(key) + return item ? JSON.parse(item) : defaultValue + } catch (error) { + console.error('Error reading from storage:', error) + return defaultValue + } + }) + + // Set value and sync to storage + const setValue = useCallback( + (newValue: T | ((prev: T) => T)) => { + try { + setValueInternal((prevValue) => { + const valueToStore = + typeof newValue === 'function' + ? (newValue as (prev: T) => T)(prevValue) + : newValue + + // Store in localStorage + localStorage.setItem(key, JSON.stringify(valueToStore)) + + // Store in window.spark.kv if available + if (typeof window !== 'undefined' && window.spark?.kv) { + window.spark.kv.set(key, valueToStore) + } + + return valueToStore + }) + } catch (error) { + console.error('Error writing to storage:', error) + } + }, + [key] + ) + + // Delete value from storage + const deleteValue = useCallback(() => { + try { + localStorage.removeItem(key) + if (typeof window !== 'undefined' && window.spark?.kv) { + window.spark.kv.delete(key) + } + setValueInternal(defaultValue) + } catch (error) { + console.error('Error deleting from storage:', error) + } + }, [key, defaultValue]) + + // Sync with localStorage changes from other tabs + useEffect(() => { + const handleStorageChange = (e: StorageEvent) => { + if (e.key === key && e.newValue !== null) { + try { + setValueInternal(JSON.parse(e.newValue)) + } catch (error) { + console.error('Error parsing storage event:', error) + } + } + } + + window.addEventListener('storage', handleStorageChange) + return () => window.removeEventListener('storage', handleStorageChange) + }, [key]) + + return [value, setValue, deleteValue] +} diff --git a/packages/spark-tools/src/index.ts b/packages/spark-tools/src/index.ts new file mode 100644 index 0000000..4e3669a --- /dev/null +++ b/packages/spark-tools/src/index.ts @@ -0,0 +1,8 @@ +/** + * @github/spark - Main Hooks Entry Point + * + * This is the entry point for the hooks exports from the Spark package. + */ + +export { useKV } from './hooks/index' +export { sparkRuntime } from './lib/spark-runtime' diff --git a/packages/spark-tools/src/lib/spark-runtime.ts b/packages/spark-tools/src/lib/spark-runtime.ts new file mode 100644 index 0000000..d507e05 --- /dev/null +++ b/packages/spark-tools/src/lib/spark-runtime.ts @@ -0,0 +1,87 @@ +/** + * Spark Runtime - Core runtime services for Spark applications + * + * This module provides mock implementations of Spark services including: + * - KV storage (key-value store) + * - LLM service (language model integration) + * - User authentication + */ + +// Mock KV Storage +const kvStorage = new Map() + +// Create llm function with additional properties +const llmFunction = async (prompt: string, model?: string, jsonMode?: boolean): Promise => { + console.log('Mock LLM called with prompt:', prompt, 'model:', model, 'jsonMode:', jsonMode) + return 'This is a mock response from the Spark LLM service.' +} + +llmFunction.chat = async (messages: any[]) => { + console.log('Mock LLM chat called with messages:', messages) + return { + role: 'assistant', + content: 'This is a mock response from the Spark LLM service.' + } +} + +llmFunction.complete = async (prompt: string) => { + console.log('Mock LLM complete called with prompt:', prompt) + return 'This is a mock completion from the Spark LLM service.' +} + +export const sparkRuntime = { + kv: { + get: (key: string): T | undefined => { + try { + const value = kvStorage.get(key) + if (value !== undefined) { + return value as T + } + const stored = localStorage.getItem(key) + return stored ? JSON.parse(stored) : undefined + } catch (error) { + console.error('Error getting KV value:', error) + return undefined + } + }, + set: (key: string, value: any) => { + try { + kvStorage.set(key, value) + localStorage.setItem(key, JSON.stringify(value)) + } catch (error) { + console.error('Error setting KV value:', error) + } + }, + delete: (key: string) => { + try { + kvStorage.delete(key) + localStorage.removeItem(key) + } catch (error) { + console.error('Error deleting KV value:', error) + } + }, + clear: () => { + try { + // Get keys before clearing + const keysToRemove = Array.from(kvStorage.keys()) + kvStorage.clear() + // Clear corresponding keys from localStorage + keysToRemove.forEach(key => localStorage.removeItem(key)) + } catch (error) { + console.error('Error clearing KV storage:', error) + } + }, + keys: () => Array.from(kvStorage.keys()) + }, + + llm: llmFunction, + + user: { + getCurrentUser: () => ({ + id: 'mock-user-id', + name: 'Mock User', + email: 'mock@example.com' + }), + isAuthenticated: () => true + } +} diff --git a/packages/spark-tools/src/lib/spark.ts b/packages/spark-tools/src/lib/spark.ts new file mode 100644 index 0000000..893d801 --- /dev/null +++ b/packages/spark-tools/src/lib/spark.ts @@ -0,0 +1,22 @@ +/** + * Spark Initialization Module + * + * This module initializes the Spark runtime and makes it available globally + * via window.spark. It should be imported early in the application lifecycle. + */ + +import { sparkRuntime } from './spark-runtime' + +// Declare global window.spark +declare global { + interface Window { + spark: typeof sparkRuntime + } +} + +// Initialize window.spark +if (typeof window !== 'undefined') { + window.spark = sparkRuntime +} + +export default sparkRuntime diff --git a/packages/spark-tools/src/spark.ts b/packages/spark-tools/src/spark.ts new file mode 100644 index 0000000..2370340 --- /dev/null +++ b/packages/spark-tools/src/spark.ts @@ -0,0 +1,8 @@ +/** + * Spark Module - Main export for spark runtime + * + * Re-export spark runtime for '@github/spark/spark' imports + */ + +export { default } from './lib/spark' +export { sparkRuntime } from './lib/spark-runtime' diff --git a/packages/spark-tools/src/sparkVitePlugin.ts b/packages/spark-tools/src/sparkVitePlugin.ts new file mode 100644 index 0000000..7cd910e --- /dev/null +++ b/packages/spark-tools/src/sparkVitePlugin.ts @@ -0,0 +1,21 @@ +/** + * Spark Vite Plugin + * + * This plugin integrates Spark functionality into the Vite build process. + * It handles initialization and configuration of Spark features. + */ + +export default function sparkPlugin() { + return { + name: 'spark-vite-plugin', + + configResolved(config: any) { + // Plugin configuration + }, + + transformIndexHtml(html: string) { + // Inject Spark initialization if needed + return html + } + } +} diff --git a/packages/spark-tools/src/vitePhosphorIconProxyPlugin.ts b/packages/spark-tools/src/vitePhosphorIconProxyPlugin.ts new file mode 100644 index 0000000..5a5faab --- /dev/null +++ b/packages/spark-tools/src/vitePhosphorIconProxyPlugin.ts @@ -0,0 +1,24 @@ +/** + * Vite Phosphor Icon Proxy Plugin + * + * This plugin provides a proxy for Phosphor icon imports to improve + * build performance and bundle size. + */ + +export default function createIconImportProxy() { + return { + name: 'vite-phosphor-icon-proxy', + + resolveId(id: string) { + // Handle icon imports + if (id.includes('@phosphor-icons/react')) { + return null // Let Vite handle it normally + } + }, + + transform(code: string, id: string) { + // Transform icon imports if needed + return null + } + } +} diff --git a/packages/spark-tools/tsconfig.json b/packages/spark-tools/tsconfig.json new file mode 100644 index 0000000..c45bb94 --- /dev/null +++ b/packages/spark-tools/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}