// --------------------------------------------------------------------------- // useCollection + useMutation — pre-built AnyDB data hooks // --------------------------------------------------------------------------- // PRE-INSTALLED in every AUTO_DB React project. CodeWriterAgent just imports: // import { useCollection, useMutation } from "@/hooks/use-anydb" // const { data, loading, error, refetch } = useCollection('products', { sort: { created_at: -1 }, limit: 20 }) // const { mutate, loading: saving } = useMutation('products') // --------------------------------------------------------------------------- import { useState, useEffect, useCallback } from "react"; // AnyDB import — always available (stub if not connected, real client if connected) import anydb from "@/lib/anydb"; // Seed data fallback — pre-populated per category so the site looks functional // even before AnyDB is connected. Replace with real data by connecting AnyDB. const SEED_DATA: Record = { users: [ { _id: "u1", name: "Alice Johnson", email: "alice@example.com", plan: "pro", role: "admin", avatar_url: "", created_at: "2025-01-15T10:00:00Z" }, { _id: "u2", name: "Bob Smith", email: "bob@example.com", plan: "free", role: "user", avatar_url: "", created_at: "2025-02-20T14:30:00Z" }, { _id: "u3", name: "Carol Williams", email: "carol@example.com", plan: "enterprise", role: "user", avatar_url: "", created_at: "2025-03-10T09:15:00Z" }, ], products: [ { _id: "p1", name: "Classic Leather Jacket", price_cents: 18900, category: "clothing", stock: 45, rating: 4.8, created_at: "2025-01-10T00:00:00Z" }, { _id: "p2", name: "Premium Denim Jeans", price_cents: 8900, category: "clothing", stock: 120, rating: 4.6, created_at: "2025-01-15T00:00:00Z" }, { _id: "p3", name: "Cashmere Sweater", price_cents: 12900, category: "clothing", stock: 30, rating: 4.9, created_at: "2025-02-01T00:00:00Z" }, { _id: "p4", name: "Silk Blend Shirt", price_cents: 6900, category: "clothing", stock: 85, rating: 4.5, created_at: "2025-02-15T00:00:00Z" }, ], orders: [ { _id: "o1", customer: "Alice Johnson", total_cents: 28900, status: "completed", created_at: "2025-03-01T10:00:00Z" }, { _id: "o2", customer: "Bob Smith", total_cents: 8900, status: "processing", created_at: "2025-03-05T14:00:00Z" }, { _id: "o3", customer: "Carol Williams", total_cents: 45800, status: "completed", created_at: "2025-03-08T09:00:00Z" }, { _id: "o4", customer: "David Lee", total_cents: 12900, status: "pending", created_at: "2025-03-10T16:00:00Z" }, { _id: "o5", customer: "Eve Chen", total_cents: 6900, status: "cancelled", created_at: "2025-03-12T11:00:00Z" }, ], metrics: [ { _id: "m1", name: "revenue", value: 48500, unit: "dollars", recorded_at: "2025-03-01T00:00:00Z" }, { _id: "m2", name: "users", value: 2340, unit: "count", recorded_at: "2025-03-01T00:00:00Z" }, { _id: "m3", name: "conversion", value: 3.2, unit: "percent", recorded_at: "2025-03-01T00:00:00Z" }, { _id: "m4", name: "uptime", value: 99.99, unit: "percent", recorded_at: "2025-03-01T00:00:00Z" }, ], posts: [ { _id: "post1", title: "Getting Started with Our Platform", slug: "getting-started", excerpt: "Learn how to set up your account and start building.", category: "Tutorial", author_id: "u1", status: "published", published_at: "2025-02-15T10:00:00Z" }, { _id: "post2", title: "Advanced Analytics Deep Dive", slug: "analytics-deep-dive", excerpt: "Explore powerful analytics features for data-driven decisions.", category: "Technology", author_id: "u1", status: "published", published_at: "2025-03-01T10:00:00Z" }, { _id: "post3", title: "Best Practices for Team Collaboration", slug: "team-collaboration", excerpt: "Tips for getting the most out of collaborative features.", category: "Business", author_id: "u2", status: "published", published_at: "2025-03-10T10:00:00Z" }, ], subscribers: [], contact_messages: [], }; // Seed-data collection simulator function createSeedCollection(name: string) { const data = SEED_DATA[name] || []; return { find: async (filter: any = {}, opts: any = {}) => { let results = [...data]; // Basic filter if (Object.keys(filter).length > 0) { results = results.filter(item => Object.entries(filter).every(([k, v]) => item[k] === v) ); } // Sort if (opts.sort) { const [key, dir] = Object.entries(opts.sort)[0] as [string, number]; results.sort((a, b) => dir === 1 ? (a[key] > b[key] ? 1 : -1) : (a[key] < b[key] ? 1 : -1)); } const total = results.length; // Pagination if (opts.skip) results = results.slice(opts.skip); if (opts.limit) results = results.slice(0, opts.limit); return { data: results, total, page: 1, limit: opts.limit || 50, pages: 1 }; }, insertOne: async (doc: any) => { const newDoc = { ...doc, _id: `seed_${Date.now()}` }; data.push(newDoc); return newDoc; }, updateOne: async (filter: any, update: any) => { const idx = data.findIndex(d => Object.entries(filter).every(([k, v]) => d[k] === v)); if (idx >= 0) Object.assign(data[idx], update); return { modifiedCount: idx >= 0 ? 1 : 0 }; }, deleteOne: async (filter: any) => { const idx = data.findIndex(d => Object.entries(filter).every(([k, v]) => d[k] === v)); if (idx >= 0) data.splice(idx, 1); return { deletedCount: idx >= 0 ? 1 : 0 }; }, aggregate: async (pipeline: any[]) => { // Basic count aggregation support if (pipeline.some(s => s.$count)) return [{ value: data.length }]; if (pipeline.some(s => s.$group)) { const group = pipeline.find(s => s.$group); if (group.$group._id === null) { const sumField = Object.entries(group.$group).find(([k, v]: any) => v?.$sum); if (sumField) { const field = (sumField[1] as any).$sum?.replace('$', ''); const total = data.reduce((acc, d) => acc + (d[field] || 0), 0); return [{ value: total }]; } return [{ value: data.length }]; } } return data; }, }; } // Unified collection getter — tries AnyDB first, falls back to seed data async function getCollection(name: string) { try { const col = await anydb.collection(name); // Test if it's a real connection (stub returns empty instantly) const test = await col.find({}, { limit: 1 }); if (test && test.data !== undefined) return col; } catch { // AnyDB not connected — use seed data } return createSeedCollection(name); } interface CollectionOptions { filter?: Record; sort?: Record; limit?: number; skip?: number; autoFetch?: boolean; } interface CollectionResult { data: T[]; total: number; loading: boolean; error: string | null; refetch: () => Promise; } /** * Fetch data from an AnyDB collection with loading/error states. * * Usage: * const { data: products, loading, refetch } = useCollection('products', { limit: 20 }) * const { data: orders } = useCollection('orders', { filter: { status: 'active' }, sort: { created_at: -1 } }) */ export function useCollection( collectionName: string, options: CollectionOptions = {} ): CollectionResult { const { filter = {}, sort, limit = 50, skip = 0, autoFetch = true } = options; const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { setLoading(true); setError(null); try { const col = await getCollection(collectionName); const result = await col.find(filter, { sort, limit, skip }); setData((result.data as T[]) || []); setTotal(result.total || 0); } catch (err: any) { setError(err.message || "Failed to fetch data"); console.error(`[useCollection] ${collectionName}:`, err); } finally { setLoading(false); } }, [collectionName, JSON.stringify(filter), JSON.stringify(sort), limit, skip]); useEffect(() => { if (autoFetch) fetchData(); }, [fetchData, autoFetch]); return { data, total, loading, error, refetch: fetchData }; } interface MutationResult { mutate: (document: Record) => Promise; update: (filter: Record, updates: Record) => Promise; remove: (filter: Record) => Promise; loading: boolean; error: string | null; } /** * Write to an AnyDB collection (insert, update, delete). * * Usage: * const { mutate, loading } = useMutation('contact_messages') * await mutate({ name, email, message }) // insertOne * await update({ _id: id }, { status: 'read' }) // updateOne * await remove({ _id: id }) // deleteOne */ export function useMutation(collectionName: string): MutationResult { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const mutate = useCallback(async (document: Record) => { setLoading(true); setError(null); try { const col = await getCollection(collectionName); const result = await col.insertOne({ ...document, created_at: new Date().toISOString(), }); return result; } catch (err: any) { setError(err.message || "Failed to save"); throw err; } finally { setLoading(false); } }, [collectionName]); const update = useCallback(async (filter: Record, updates: Record) => { setLoading(true); setError(null); try { const col = await getCollection(collectionName); const result = await col.updateOne(filter, updates); return result; } catch (err: any) { setError(err.message || "Failed to update"); throw err; } finally { setLoading(false); } }, [collectionName]); const remove = useCallback(async (filter: Record) => { setLoading(true); setError(null); try { const col = await getCollection(collectionName); const result = await col.deleteOne(filter); return result; } catch (err: any) { setError(err.message || "Failed to delete"); throw err; } finally { setLoading(false); } }, [collectionName]); return { mutate, update, remove, loading, error }; } interface AggregateResult { data: T[]; loading: boolean; error: string | null; refetch: () => Promise; } /** * Run an aggregation pipeline on an AnyDB collection. * * Usage: * const { data: stats } = useAggregate('orders', [ * { $group: { _id: '$status', count: { $sum: 1 }, total: { $sum: '$total_cents' } } } * ]) */ export function useAggregate( collectionName: string, pipeline: Record[] ): AggregateResult { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { setLoading(true); setError(null); try { const col = await getCollection(collectionName); const result = await col.aggregate(pipeline); setData((result as T[]) || []); } catch (err: any) { setError(err.message || "Aggregation failed"); } finally { setLoading(false); } }, [collectionName, JSON.stringify(pipeline)]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; }