- Simplify universal modules to be more framework-friendly
- Add comprehensive framework integration documentation (Next.js, Vue, React)
- Implement missing relateMany() batch relationship creation method
- Clean up obsolete test files and improve test coverage
- Reduce browser polyfill complexity while maintaining compatibility
- Remove unused browserFramework entry points for cleaner API surface
📄 3,120 lines added, 3,679 lines removed for net simplification
27 KiB
27 KiB
Vue.js Integration Guide
Complete guide to integrating Brainy with Vue.js applications, covering Vue 3, Nuxt.js, Composition API, Options API, and advanced patterns.
🚀 Quick Start
Installation
npm create vue@latest my-brainy-app
cd my-brainy-app
npm install
npm install @soulcraft/brainy
Basic Setup
// src/composables/useBrainy.js
import { ref, onMounted } from 'vue'
import { Brainy } from '@soulcraft/brainy'
const brain = ref(null)
const isReady = ref(false)
const error = ref(null)
export function useBrainy() {
onMounted(async () => {
try {
brain.value = new Brainy({
storage: { type: 'opfs' } // Browser storage
})
await brain.value.init()
isReady.value = true
} catch (err) {
error.value = err.message
console.error('Brainy initialization failed:', err)
}
})
return {
brain: readonly(brain),
isReady: readonly(isReady),
error: readonly(error)
}
}
🧩 Composition API
Search Component
<!-- src/components/Search.vue -->
<template>
<div class="search-container">
<div v-if="!isReady" class="loading">
<div class="spinner"></div>
<span>Initializing AI...</span>
</div>
<div v-else>
<div class="search-input">
<input
v-model="query"
@input="handleSearch"
placeholder="Search with AI..."
class="input"
/>
</div>
<div v-if="loading" class="loading">
Searching...
</div>
<div v-else class="results">
<div
v-for="result in results"
:key="result.id"
class="result-item"
>
<h3>{{ result.data }}</h3>
<div class="result-meta">
<span>Score: {{ (result.score * 100).toFixed(1) }}%</span>
<span v-if="result.metadata?.type">
Type: {{ result.metadata.type }}
</span>
</div>
</div>
<div v-if="query && !loading && results.length === 0" class="no-results">
No results found for "{{ query }}"
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useBrainy } from '../composables/useBrainy'
import { debounce } from '../utils/debounce'
const { brain, isReady } = useBrainy()
const query = ref('')
const results = ref([])
const loading = ref(false)
const search = async (searchQuery) => {
if (!isReady.value || !searchQuery.trim()) {
results.value = []
return
}
loading.value = true
try {
const searchResults = await brain.value.find(searchQuery)
results.value = searchResults
} catch (error) {
console.error('Search error:', error)
results.value = []
} finally {
loading.value = false
}
}
const debouncedSearch = debounce(search, 300)
const handleSearch = () => debouncedSearch(query.value)
// Watch for query changes
watch(query, (newQuery) => {
if (!newQuery.trim()) {
results.value = []
loading.value = false
}
})
</script>
<style scoped>
.search-container {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.search-input {
margin-bottom: 1.5rem;
}
.input {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 1rem;
}
.input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
color: #6b7280;
}
.spinner {
width: 1.5rem;
height: 1.5rem;
border: 2px solid #e5e7eb;
border-top: 2px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 0.5rem;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.results {
space-y: 1rem;
}
.result-item {
background: white;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.result-item h3 {
margin: 0 0 0.5rem 0;
font-size: 1.125rem;
font-weight: 600;
}
.result-meta {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
color: #6b7280;
}
.no-results {
text-align: center;
padding: 2rem;
color: #6b7280;
}
</style>
Data Management
<!-- src/components/DataManager.vue -->
<template>
<div class="data-manager">
<h2>Data Management</h2>
<div v-if="!isReady" class="loading">
Initializing...
</div>
<div v-else>
<!-- Add Data Form -->
<form @submit.prevent="addData" class="add-form">
<h3>Add New Data</h3>
<div class="form-group">
<label for="data">Data:</label>
<textarea
id="data"
v-model="newItem.data"
placeholder="Enter data..."
required
></textarea>
</div>
<div class="form-group">
<label for="type">Type:</label>
<select id="type" v-model="newItem.type">
<option value="concept">Concept</option>
<option value="document">Document</option>
<option value="person">Person</option>
<option value="project">Project</option>
</select>
</div>
<div class="form-group">
<label for="tags">Tags (comma-separated):</label>
<input
id="tags"
v-model="newItem.tags"
placeholder="tag1, tag2, tag3"
/>
</div>
<button type="submit" :disabled="!newItem.data.trim()">
Add Data
</button>
</form>
<!-- Stats -->
<div v-if="stats" class="stats">
<h3>Statistics</h3>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-label">Total Items:</span>
<span class="stat-value">{{ stats.totalItems }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Storage Type:</span>
<span class="stat-value">{{ stats.storageType }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useBrainy } from '../composables/useBrainy'
const { brain, isReady } = useBrainy()
const newItem = reactive({
data: '',
type: 'concept',
tags: ''
})
const stats = ref(null)
onMounted(async () => {
if (isReady.value) {
await loadStats()
}
})
// Watch for brain readiness
watch(isReady, async (ready) => {
if (ready) {
await loadStats()
}
})
const addData = async () => {
try {
const metadata = {
addedAt: new Date().toISOString(),
tags: newItem.tags.split(',').map(tag => tag.trim()).filter(Boolean)
}
await brain.value.add({
data: newItem.data,
type: newItem.type,
metadata
})
// Reset form
newItem.data = ''
newItem.type = 'concept'
newItem.tags = ''
// Refresh stats
await loadStats()
} catch (error) {
console.error('Failed to add data:', error)
}
}
const loadStats = async () => {
try {
stats.value = await brain.value.stats()
} catch (error) {
console.error('Failed to load stats:', error)
}
}
</script>
<style scoped>
.data-manager {
max-width: 600px;
margin: 0 auto;
padding: 2rem;
}
.add-form {
background: #f9fafb;
padding: 1.5rem;
border-radius: 0.5rem;
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.25rem;
font-weight: 500;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 0.5rem;
border: 1px solid #d1d5db;
border-radius: 0.25rem;
}
.form-group textarea {
height: 100px;
resize: vertical;
}
button {
background: #3b82f6;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
}
button:disabled {
background: #9ca3af;
cursor: not-allowed;
}
.stats {
background: white;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
padding: 1.5rem;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.stat-item {
display: flex;
justify-content: space-between;
}
.stat-label {
font-weight: 500;
}
.stat-value {
color: #3b82f6;
font-weight: 600;
}
</style>
🏗️ Options API
Search Component (Options API)
<!-- src/components/SearchOptions.vue -->
<template>
<div class="search-container">
<div v-if="!isReady" class="loading">
Initializing AI...
</div>
<div v-else>
<input
v-model="query"
@input="handleSearch"
placeholder="Search..."
class="search-input"
/>
<div v-if="loading" class="loading">Searching...</div>
<div class="results">
<div
v-for="result in results"
:key="result.id"
class="result-item"
>
<h3>{{ result.data }}</h3>
<p>Score: {{ (result.score * 100).toFixed(1) }}%</p>
</div>
</div>
</div>
</div>
</template>
<script>
import { Brainy } from '@soulcraft/brainy'
import { debounce } from '../utils/debounce'
export default {
name: 'SearchOptions',
data() {
return {
brain: null,
isReady: false,
query: '',
results: [],
loading: false
}
},
async mounted() {
await this.initBrain()
},
methods: {
async initBrain() {
try {
this.brain = new Brainy({
storage: { type: 'opfs' }
})
await this.brain.init()
this.isReady = true
} catch (error) {
console.error('Brain initialization failed:', error)
}
},
async search(query) {
if (!this.isReady || !query.trim()) {
this.results = []
return
}
this.loading = true
try {
this.results = await this.brain.find(query)
} catch (error) {
console.error('Search error:', error)
this.results = []
} finally {
this.loading = false
}
},
handleSearch: debounce(function() {
this.search(this.query)
}, 300)
},
watch: {
query(newQuery) {
if (!newQuery.trim()) {
this.results = []
this.loading = false
}
}
},
beforeUnmount() {
// Cleanup if needed
this.brain = null
}
}
</script>
🔌 Vue Plugin
Global Brainy Plugin
// src/plugins/brainy.js
import { Brainy } from '@soulcraft/brainy'
export default {
install(app, options = {}) {
const defaultOptions = {
storage: { type: 'opfs' },
...options
}
const brain = new Brainy(defaultOptions)
// Make brain available globally
app.config.globalProperties.$brain = brain
app.provide('brain', brain)
// Auto-initialize
brain.init().catch(error => {
console.error('Brainy plugin initialization failed:', error)
})
// Add global method
app.config.globalProperties.$searchBrain = async (query, options) => {
return await brain.find(query, options)
}
}
}
Plugin Usage
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import BrainyPlugin from './plugins/brainy'
const app = createApp(App)
app.use(BrainyPlugin, {
storage: { type: 'opfs' }
})
app.mount('#app')
<!-- Component using the plugin -->
<template>
<div>
<input v-model="query" @input="search" />
<div v-for="result in results" :key="result.id">
{{ result.data }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
query: '',
results: []
}
},
methods: {
async search() {
// Use the global method
this.results = await this.$searchBrain(this.query)
}
}
}
</script>
🏰 Nuxt.js Integration
Nuxt Plugin
// plugins/brainy.client.js
import { Brainy } from '@soulcraft/brainy'
export default defineNuxtPlugin(async () => {
const brain = new Brainy({
storage: { type: 'opfs' }
})
await brain.init()
return {
provide: {
brain
}
}
})
Nuxt Composable
// composables/useBrainy.js
export const useBrainy = () => {
const { $brain } = useNuxtApp()
const search = async (query, options = {}) => {
return await $brain.find(query, options)
}
const add = async (data, type, metadata) => {
return await $brain.add({ data, type, metadata })
}
const stats = async () => {
return await $brain.stats()
}
return {
brain: $brain,
search,
add,
stats
}
}
Nuxt Page Example
<!-- pages/search.vue -->
<template>
<div>
<h1>AI Search</h1>
<div v-if="pending" class="loading">
Initializing AI...
</div>
<div v-else>
<input
v-model="query"
@input="handleSearch"
placeholder="Search..."
/>
<div v-if="searching" class="loading">Searching...</div>
<div class="results">
<div v-for="result in results" :key="result.id" class="result">
<h3>{{ result.data }}</h3>
<p>Score: {{ (result.score * 100).toFixed(1) }}%</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
const { search } = useBrainy()
const query = ref('')
const results = ref([])
const searching = ref(false)
const pending = ref(true)
// Initialize
onMounted(() => {
pending.value = false
})
const handleSearch = debounce(async () => {
if (!query.value.trim()) {
results.value = []
return
}
searching.value = true
try {
results.value = await search(query.value)
} catch (error) {
console.error('Search failed:', error)
} finally {
searching.value = false
}
}, 300)
</script>
Nuxt Configuration
// nuxt.config.ts
export default defineNuxtConfig({
ssr: false, // Disable SSR for browser-only features
// Or use client-side hydration
nitro: {
experimental: {
wasm: true
}
},
vite: {
define: {
global: 'globalThis'
}
}
})
🛠️ Advanced Patterns
Global State Management with Pinia
// stores/brainy.js
import { defineStore } from 'pinia'
import { Brainy } from '@soulcraft/brainy'
export const useBrainyStore = defineStore('brainy', () => {
const brain = ref(null)
const isReady = ref(false)
const error = ref(null)
const stats = ref(null)
const init = async (options = {}) => {
try {
brain.value = new Brainy(options)
await brain.value.init()
isReady.value = true
await loadStats()
} catch (err) {
error.value = err.message
console.error('Brainy initialization failed:', err)
}
}
const search = async (query, options = {}) => {
if (!isReady.value) throw new Error('Brain not ready')
return await brain.value.find(query, options)
}
const add = async (data, type, metadata) => {
if (!isReady.value) throw new Error('Brain not ready')
const id = await brain.value.add({ data, type, metadata })
await loadStats() // Refresh stats
return id
}
const loadStats = async () => {
if (!isReady.value) return
try {
stats.value = await brain.value.stats()
} catch (err) {
console.error('Failed to load stats:', err)
}
}
return {
brain: readonly(brain),
isReady: readonly(isReady),
error: readonly(error),
stats: readonly(stats),
init,
search,
add,
loadStats
}
})
Real-time Search Component
<!-- src/components/RealTimeSearch.vue -->
<template>
<div class="realtime-search">
<div class="search-bar">
<input
ref="searchInput"
v-model="query"
@input="handleInput"
@focus="showResults = true"
@blur="handleBlur"
placeholder="Search..."
class="search-input"
/>
<div v-if="loading" class="search-spinner">⟳</div>
</div>
<Transition name="dropdown">
<div
v-if="showResults && (results.length > 0 || query.trim())"
class="search-dropdown"
>
<div
v-for="(result, index) in results"
:key="result.id"
:class="['result-item', { active: selectedIndex === index }]"
@mousedown="selectResult(result)"
@mouseenter="selectedIndex = index"
>
<div class="result-content">
<span class="result-text">{{ result.data }}</span>
<span class="result-score">{{ (result.score * 100).toFixed(0) }}%</span>
</div>
</div>
<div v-if="query.trim() && results.length === 0 && !loading" class="no-results">
No results found
</div>
</div>
</Transition>
</div>
</template>
<script setup>
import { ref, nextTick } from 'vue'
import { useBrainyStore } from '../stores/brainy'
import { debounce } from '../utils/debounce'
const emit = defineEmits(['select'])
const brainyStore = useBrainyStore()
const searchInput = ref(null)
const query = ref('')
const results = ref([])
const loading = ref(false)
const showResults = ref(false)
const selectedIndex = ref(-1)
const search = async (searchQuery) => {
if (!brainyStore.isReady || !searchQuery.trim()) {
results.value = []
return
}
loading.value = true
try {
const searchResults = await brainyStore.search(searchQuery, { limit: 10 })
results.value = searchResults
} catch (error) {
console.error('Search error:', error)
results.value = []
} finally {
loading.value = false
}
}
const debouncedSearch = debounce(search, 200)
const handleInput = () => {
selectedIndex.value = -1
debouncedSearch(query.value)
}
const handleBlur = () => {
// Delay hiding to allow clicking on results
setTimeout(() => {
showResults.value = false
}, 150)
}
const selectResult = (result) => {
query.value = result.data
showResults.value = false
emit('select', result)
}
// Keyboard navigation
const handleKeydown = (event) => {
if (!showResults.value) return
switch (event.key) {
case 'ArrowDown':
event.preventDefault()
selectedIndex.value = Math.min(selectedIndex.value + 1, results.value.length - 1)
break
case 'ArrowUp':
event.preventDefault()
selectedIndex.value = Math.max(selectedIndex.value - 1, -1)
break
case 'Enter':
event.preventDefault()
if (selectedIndex.value >= 0) {
selectResult(results.value[selectedIndex.value])
}
break
case 'Escape':
showResults.value = false
searchInput.value?.blur()
break
}
}
onMounted(() => {
document.addEventListener('keydown', handleKeydown)
})
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown)
})
</script>
<style scoped>
.realtime-search {
position: relative;
width: 100%;
}
.search-bar {
position: relative;
display: flex;
align-items: center;
}
.search-input {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 1rem;
}
.search-input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.search-spinner {
position: absolute;
right: 0.75rem;
animation: spin 1s linear infinite;
color: #6b7280;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
z-index: 1000;
max-height: 300px;
overflow-y: auto;
}
.result-item {
padding: 0.75rem;
cursor: pointer;
border-bottom: 1px solid #f3f4f6;
}
.result-item:last-child {
border-bottom: none;
}
.result-item:hover,
.result-item.active {
background: #f3f4f6;
}
.result-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.result-text {
flex: 1;
margin-right: 0.5rem;
}
.result-score {
font-size: 0.875rem;
color: #6b7280;
}
.no-results {
padding: 1rem;
text-align: center;
color: #6b7280;
}
.dropdown-enter-active,
.dropdown-leave-active {
transition: all 0.2s;
}
.dropdown-enter-from,
.dropdown-leave-to {
opacity: 0;
transform: translateY(-10px);
}
</style>
📊 Performance Optimization
Lazy Loading
<!-- src/components/LazyBrainComponent.vue -->
<template>
<div>
<button v-if="!loaded" @click="loadBrain" class="load-button">
Load AI Search
</button>
<Suspense v-else>
<template #default>
<SearchComponent />
</template>
<template #fallback>
<div class="loading">Loading AI...</div>
</template>
</Suspense>
</div>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue'
const loaded = ref(false)
const SearchComponent = defineAsyncComponent(() =>
import('./Search.vue')
)
const loadBrain = () => {
loaded.value = true
}
</script>
Virtual Scrolling for Large Results
<!-- src/components/VirtualResults.vue -->
<template>
<div ref="container" class="virtual-container" @scroll="onScroll">
<div :style="{ height: totalHeight + 'px' }" class="virtual-spacer">
<div
:style="{ transform: `translateY(${offsetY}px)` }"
class="virtual-content"
>
<div
v-for="item in visibleItems"
:key="item.id"
class="result-item"
:style="{ height: itemHeight + 'px' }"
>
<h3>{{ item.data }}</h3>
<p>Score: {{ (item.score * 100).toFixed(1) }}%</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const props = defineProps({
items: Array,
itemHeight: { type: Number, default: 80 }
})
const container = ref(null)
const scrollTop = ref(0)
const containerHeight = ref(400)
const totalHeight = computed(() => props.items.length * props.itemHeight)
const visibleStart = computed(() => Math.floor(scrollTop.value / props.itemHeight))
const visibleEnd = computed(() => Math.min(
visibleStart.value + Math.ceil(containerHeight.value / props.itemHeight) + 1,
props.items.length
))
const visibleItems = computed(() =>
props.items.slice(visibleStart.value, visibleEnd.value)
)
const offsetY = computed(() => visibleStart.value * props.itemHeight)
const onScroll = () => {
scrollTop.value = container.value.scrollTop
}
onMounted(() => {
containerHeight.value = container.value.clientHeight
})
</script>
<style scoped>
.virtual-container {
height: 400px;
overflow: auto;
}
.virtual-spacer {
position: relative;
}
.virtual-content {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.result-item {
padding: 1rem;
border-bottom: 1px solid #e5e7eb;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
🧪 Testing
Component Testing with Vitest
// tests/components/Search.test.js
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import Search from '../src/components/Search.vue'
// Mock Brainy
vi.mock('@soulcraft/brainy', () => ({
Brainy: vi.fn().mockImplementation(() => ({
init: vi.fn().mockResolvedValue(undefined),
find: vi.fn().mockResolvedValue([
{ id: '1', data: 'Test result', score: 0.9 }
])
}))
}))
describe('Search Component', () => {
let wrapper
beforeEach(() => {
wrapper = mount(Search)
})
it('renders search input', () => {
expect(wrapper.find('input').exists()).toBe(true)
})
it('shows loading state initially', () => {
expect(wrapper.text()).toContain('Initializing AI')
})
it('performs search when input changes', async () => {
// Wait for brain to initialize
await wrapper.vm.$nextTick()
const input = wrapper.find('input')
await input.setValue('test query')
await input.trigger('input')
// Wait for debounced search
await new Promise(resolve => setTimeout(resolve, 350))
expect(wrapper.text()).toContain('Test result')
})
})
E2E Testing with Playwright
// tests/e2e/search.spec.js
import { test, expect } from '@playwright/test'
test('search functionality works', async ({ page }) => {
await page.goto('/')
// Wait for brain to initialize
await page.waitForSelector('[data-testid="search-input"]')
// Perform search
await page.fill('[data-testid="search-input"]', 'test query')
// Wait for results
await page.waitForSelector('[data-testid="search-results"]')
// Check results
const results = await page.locator('[data-testid="result-item"]')
await expect(results).toHaveCountGreaterThan(0)
})
🚀 Production Tips
Bundle Optimization
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
define: {
global: 'globalThis'
},
optimizeDeps: {
include: ['@soulcraft/brainy']
},
build: {
rollupOptions: {
output: {
manualChunks: {
'brainy': ['@soulcraft/brainy']
}
}
}
}
})
Error Handling
// src/composables/useBrainyWithErrorHandling.js
import { ref, onMounted } from 'vue'
import { Brainy } from '@soulcraft/brainy'
export function useBrainyWithErrorHandling() {
const brain = ref(null)
const isReady = ref(false)
const error = ref(null)
const retryCount = ref(0)
const init = async () => {
try {
brain.value = new Brainy()
await brain.value.init()
isReady.value = true
error.value = null
retryCount.value = 0
} catch (err) {
error.value = err.message
console.error('Brainy initialization failed:', err)
// Retry logic
if (retryCount.value < 3) {
retryCount.value++
setTimeout(init, 2000 * retryCount.value)
}
}
}
onMounted(init)
return {
brain: readonly(brain),
isReady: readonly(isReady),
error: readonly(error),
retry: init
}
}
🎯 Complete Example
Here's a complete Vue 3 application structure:
my-brainy-vue-app/
├── src/
│ ├── components/
│ │ ├── Search.vue
│ │ ├── DataManager.vue
│ │ └── RealTimeSearch.vue
│ ├── composables/
│ │ ├── useBrainy.js
│ │ └── useBrainyWithErrorHandling.js
│ ├── stores/
│ │ └── brainy.js
│ ├── utils/
│ │ └── debounce.js
│ ├── App.vue
│ └── main.js
├── tests/
│ ├── components/
│ └── e2e/
├── vite.config.js
└── package.json
This provides a complete, production-ready Vue.js application with comprehensive Brainy integration.
📚 Next Steps
- Framework Integration Guide - Multi-framework patterns
- Production Deployment - Deploy to production
- API Reference - Complete API documentation
- Examples Repository - More examples