diff --git a/.gitignore b/.gitignore index 691a887..04916c9 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,6 @@ dist-ssr .env **/agent-eval-report* -packages pids .file-manifest .devcontainer/ diff --git a/packages/spark/README.md b/packages/spark/README.md new file mode 100644 index 0000000..59c9629 --- /dev/null +++ b/packages/spark/README.md @@ -0,0 +1,115 @@ +# @github/spark + +Spark runtime and hooks for low-code React applications. + +## Overview + +The `@github/spark` package provides core functionality for Spark-powered applications: + +- **useKV Hook**: Persistent key-value storage with localStorage and Spark KV integration +- **Spark Runtime**: Mock LLM service, KV storage, and user authentication APIs +- **Vite Plugins**: Build-time integrations for Spark applications + +## Installation + +This package is designed to be used as a workspace dependency: + +```json +{ + "dependencies": { + "@github/spark": "workspace:*" + } +} +``` + +## Usage + +### useKV Hook + +The `useKV` hook provides persistent state management: + +```typescript +import { useKV } from '@github/spark/hooks' + +function MyComponent() { + const [count, setCount, deleteCount] = useKV('counter', 0) + + return ( +
+

Count: {count}

+ + +
+ ) +} +``` + +### Spark Runtime + +Initialize the Spark runtime in your application entry point: + +```typescript +import '@github/spark/spark' +``` + +Access the runtime APIs: + +```typescript +// KV Storage +window.spark.kv.set('key', 'value') +const value = window.spark.kv.get('key') + +// LLM Service +const response = await window.spark.llm.chat([ + { role: 'user', content: 'Hello!' } +]) + +// User Info +const user = window.spark.user.getCurrentUser() +``` + +### Vite Plugins + +Add Spark plugins to your Vite configuration: + +```typescript +import sparkPlugin from '@github/spark/spark-vite-plugin' +import createIconImportProxy from '@github/spark/vitePhosphorIconProxyPlugin' + +export default defineConfig({ + plugins: [ + sparkPlugin(), + createIconImportProxy() + ] +}) +``` + +## API Reference + +### useKV(key: string, defaultValue: T) + +Returns: `[value: T, setValue: (value: T | ((prev: T) => T)) => void, deleteValue: () => void]` + +- `key`: Storage key +- `defaultValue`: Default value if key doesn't exist +- `value`: Current value +- `setValue`: Update the value (supports functional updates) +- `deleteValue`: Delete the value and reset to default + +### window.spark + +Global runtime object with the following APIs: + +- `kv.get(key)`: Get value from KV storage +- `kv.set(key, value)`: Set value in KV storage +- `kv.delete(key)`: Delete key from KV storage +- `kv.clear()`: Clear all KV storage +- `kv.keys()`: Get all keys +- `llm.chat(messages)`: Chat with LLM +- `llm.complete(prompt)`: Complete a prompt +- `user.getCurrentUser()`: Get current user info +- `user.isAuthenticated()`: Check if user is authenticated + +## License + +MIT diff --git a/packages/spark/package.json b/packages/spark/package.json new file mode 100644 index 0000000..95480ec --- /dev/null +++ b/packages/spark/package.json @@ -0,0 +1,28 @@ +{ + "name": "@github/spark", + "version": "1.0.0", + "description": "Spark runtime and hooks for low-code React applications", + "type": "module", + "main": "./src/index.ts", + "types": "./src/types.d.ts", + "exports": { + ".": "./src/index.ts", + "./hooks": "./src/hooks/index.ts", + "./spark": "./src/spark.ts", + "./spark-vite-plugin": "./src/spark-vite-plugin.mjs", + "./vitePhosphorIconProxyPlugin": "./src/vitePhosphorIconProxyPlugin.mjs" + }, + "files": [ + "src" + ], + "scripts": { + "build": "tsc" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "typescript": "~5.7.2" + } +} diff --git a/packages/spark/src/hooks/index.ts b/packages/spark/src/hooks/index.ts new file mode 100644 index 0000000..6f1f16b --- /dev/null +++ b/packages/spark/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/src/index.ts b/packages/spark/src/index.ts new file mode 100644 index 0000000..84619c9 --- /dev/null +++ b/packages/spark/src/index.ts @@ -0,0 +1,11 @@ +/** + * @github/spark - Main Entry Point + * + * This is the main entry point for the Spark package. + * It re-exports all core functionality. + */ + +export { sparkRuntime } from './spark-runtime' +export { useKV } from './hooks/index' +export { default as sparkPlugin } from './spark-vite-plugin.mjs' +export { default as createIconImportProxy } from './vitePhosphorIconProxyPlugin.mjs' diff --git a/packages/spark/src/spark-runtime.ts b/packages/spark/src/spark-runtime.ts new file mode 100644 index 0000000..c9cad1e --- /dev/null +++ b/packages/spark/src/spark-runtime.ts @@ -0,0 +1,72 @@ +/** + * 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() + +export const sparkRuntime = { + kv: { + get: (key: string) => { + try { + const value = kvStorage.get(key) + return value !== undefined ? value : localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)!) : 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 { + kvStorage.clear() + } catch (error) { + console.error('Error clearing KV storage:', error) + } + }, + keys: () => Array.from(kvStorage.keys()) + }, + + llm: { + 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.' + } + }, + complete: async (prompt: string) => { + console.log('Mock LLM complete called with prompt:', prompt) + return 'This is a mock completion from the Spark LLM service.' + } + }, + + user: { + getCurrentUser: () => ({ + id: 'mock-user-id', + name: 'Mock User', + email: 'mock@example.com' + }), + isAuthenticated: () => true + } +} diff --git a/packages/spark/src/spark-vite-plugin.mjs b/packages/spark/src/spark-vite-plugin.mjs new file mode 100644 index 0000000..f175370 --- /dev/null +++ b/packages/spark/src/spark-vite-plugin.mjs @@ -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) { + // Plugin configuration + }, + + transformIndexHtml(html) { + // Inject Spark initialization if needed + return html + } + } +} diff --git a/packages/spark/src/spark.ts b/packages/spark/src/spark.ts new file mode 100644 index 0000000..893d801 --- /dev/null +++ b/packages/spark/src/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/src/types.d.ts b/packages/spark/src/types.d.ts new file mode 100644 index 0000000..21dd62f --- /dev/null +++ b/packages/spark/src/types.d.ts @@ -0,0 +1,29 @@ +/** + * TypeScript Type Definitions for Spark + * + * Global type declarations for window.spark and Spark APIs + */ + +declare global { + interface Window { + spark: { + kv: { + get: (key: string) => any + set: (key: string, value: any) => void + delete: (key: string) => void + clear: () => void + keys: () => string[] + } + llm: { + chat: (messages: any[]) => Promise<{ role: string; content: string }> + complete: (prompt: string) => Promise + } + user: { + getCurrentUser: () => { id: string; name: string; email: string } + isAuthenticated: () => boolean + } + } + } +} + +export {} diff --git a/packages/spark/src/vitePhosphorIconProxyPlugin.mjs b/packages/spark/src/vitePhosphorIconProxyPlugin.mjs new file mode 100644 index 0000000..3e4dee4 --- /dev/null +++ b/packages/spark/src/vitePhosphorIconProxyPlugin.mjs @@ -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) { + // Handle icon imports + if (id.includes('@phosphor-icons/react')) { + return null // Let Vite handle it normally + } + }, + + transform(code, id) { + // Transform icon imports if needed + return null + } + } +} diff --git a/packages/spark/tsconfig.json b/packages/spark/tsconfig.json new file mode 100644 index 0000000..653d99d --- /dev/null +++ b/packages/spark/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"], + "exclude": ["node_modules"] +}