- SimpleQueryExecutor handles dependency resolution cleanly with topological sorting
- Pipes work well for data transformation
- Component embedding with pre-population prevents infinite loops
- TargetBatcher optimizes N+1 queries effectively
Current Issue: Query execution happens in multiple places (main render, components, loops)
Opportunity: Create a single QueryContext that handles all query execution patterns
interface QueryContext {
// Core execution
execute(queryName: string): Promise<any>
executeAll(queries: Record<string, Query>): Promise<Map<string, any>>
// Live subscriptions
subscribe(queryName: string, callback: (data: any) => void): () => void
// Batching
batchRequests<T>(requests: Request[]): Promise<T[]>
}Current Issue: Components can't easily share queries with parent or subscribe to live data Opportunity: Allow components to declare query dependencies that bubble up
# Component that composes with parent queries
#profile(kind: 0):
queries:
$profile: inherit # Use parent's $profile query
$posts: # Add new query
kinds: [1]
authors: [target.pubkey]
live: true # Subscribe to live updatesCurrent Issue: Actions and queries are separate systems Opportunity: Unify them as "operations" that can compose
interface Operation {
type: 'query' | 'action' | 'pipe'
execute(context: Context): Promise<any>
dependencies?: string[] // Other operations this depends on
live?: boolean // Can subscribe to updates
}Current Issue: Pipes are powerful but complex with JQ parser Opportunity: Create composable pipe primitives
// Instead of complex JQ expressions, use composable functions
const pipes = {
first: (arr) => arr[0],
pluck: (key) => (obj) => obj[key],
filter: (pred) => (arr) => arr.filter(pred),
map: (fn) => (arr) => arr.map(fn),
// Compose them
compose: (...fns) => (x) => fns.reduce((v, f) => f(v), x)
}
// Usage: pipe: [first, pluck('tags'), filter(t => t[0] === 'p')]Current Issue: Components can't easily subscribe to live queries
Opportunity: Add live flag to component queries
// In ComponentWrapper
if (componentDef.queries?.some(q => q.live)) {
// Set up live subscription that updates component state
useLiveQueries(componentDef.queries, (updates) => {
setQueryResults(prev => ({ ...prev, ...updates }))
})
}Current Issue: Multiple resolution systems (resolveVariables, resolveExpression, etc.) Opportunity: Single resolver that handles all cases
class VariableResolver {
resolve(expression: string, context: Context): any {
// Handle all cases:
// - $queryName
// - user.pubkey
// - target.field
// - loopVar.property
// - action results
// - nested paths: $query.field.subfield
}
}Current Issue: Components can't react to query completion or errors Opportunity: Add lifecycle hooks
#profile(kind: 0):
onQueryComplete: |
console.log('Profile loaded:', $profile)
onError: |
toast.error('Failed to load profile')Current Issue: Cache invalidation is manual Opportunity: Smart cache with auto-invalidation
class SmartCache {
// Auto-invalidate based on:
// - Time (TTL)
// - New events published
// - Related query updates
// - Manual invalidation
invalidateRelated(queryName: string) {
// Find queries that depend on this one
const dependents = this.findDependents(queryName)
dependents.forEach(q => this.invalidate(q))
}
}-
High Priority (Core functionality):
- Unified variable resolution
- Component live queries
- Smart cache invalidation
-
Medium Priority (Developer experience):
- Simplified pipe system
- Component lifecycle hooks
- Query composition
-
Low Priority (Future enhancements):
- Full operation unification
- Advanced batching strategies
- Composability: Every feature should compose well with others
- Predictability: Behavior should be obvious from syntax
- Performance: Batch by default, cache aggressively
- Reactivity: Live updates should "just work"