Letting AI Write Your FlowQuery — And Why You Still Need to Review It
AI can chain FlowQuery calls with impressive accuracy — until it silently drops a tenant scope or builds an unindexed aggregation pipeline. Here's how to catch the mistakes that compile but corrupt.
The Premise
FlowQuery is Logic Bee's chainable ORM for tenant-scoped database operations. Its fluent API reads like pseudocode, which makes it a dream for AI code generation. The problem? Syntactically correct FlowQuery can still be semantically dangerous.
The Story
A developer asks the AI to build a reporting hook that aggregates monthly revenue across all posted invoices. The AI generates clean, readable code:
const invoices = await fc.docs.flowQuery()
.user(bob.flowUser)
.flowOptions(eventOptions.invoiceNaoQueryOptions)
.query({ 'data.status': 'posted' })
.getMany(undefined, 'FinanceInterface.Invoice')
Looks perfect. But during code review, the developer catches three issues that would have gone unnoticed in a test environment.
Where AI FlowQuery Goes Wrong
1. Missing Tenant Boundaries
The most critical mistake is also the quietest. When AI generates a helper function that internally creates a new FlowQuery chain, it sometimes forgets to pass the user context:
// ❌ AI-generated helper — no user scoping
async function getLineItems(fc, docId) {
return fc.docs.flowQuery()
.query({ 'data.parentId': docId }) // missing .user()
.getMany()
}
// ✅ Corrected
async function getLineItems(fc, bob, docId) {
return fc.docs.flowQuery()
.user(bob.flowUser) // always scope to tenant
.flowOptions(eventOptions.lineNaoQueryOptions)
.query({ 'data.parentId': docId })
.getMany(undefined, 'FinanceInterface.InvoiceLine')
}
2. Fetching Too Much Data
AI tends to use .getMany() when .getOne() would suffice, or fetches full documents when only a few fields are needed. In production with thousands of documents, this matters:
// ❌ AI default: fetch everything
const allOrders = await fc.docs.flowQuery()
.user(bob.flowUser)
.flowOptions(eventOptions.orderNaoQueryOptions)
.query({ 'data.customerId': customerId })
.getMany()
// ✅ Better: limit and project
const recentOrders = await fc.docs.flowQuery()
.user(bob.flowUser)
.flowOptions(eventOptions.orderNaoQueryOptions)
.query({ 'data.customerId': customerId })
.sort({ 'data.createdAt': -1 })
.limit(10)
.getMany(undefined, 'SalesInterface.Order')
3. Aggregation Pipeline Misuse
When asked to "sum" or "group" data, AI sometimes fetches all documents into memory and reduces in JavaScript instead of using database-level aggregation:
// ❌ AI approach: fetch-then-reduce
const invoices = await fc.docs.flowQuery()...getMany()
const total = invoices.reduce((sum, inv) => sum + inv.data.amount, 0)
// ✅ Better: use the database
const pipeline = [
{ $match: { 'data.status': 'posted' } },
{ $group: { _id: null, total: { $sum: '$data.amount' } } }
]
The 3-Point FlowQuery Review
Every time you review AI-generated FlowQuery code, check these three things:
Scoping
-
.user(bob.flowUser)on every chain — including helper functions -
.flowOptions(eventOptions.*)matches the correct collection
Performance
-
.getMany()has appropriate limits or pagination - Aggregations happen at the database level, not in JavaScript
- Queries use indexed fields
Correctness
- TypeScript interface passed to
.getMany(undefined, 'Interface.Type')for type safety -
.getOne()used when only one document is expected - Null checks after
.getOne()calls
The Takeaway
FlowQuery's fluent API makes AI-generated code look deceptively correct. The developer's job isn't to rewrite it — it's to verify the three dimensions the AI consistently underweights: tenant scoping, query performance, and data volume awareness.
Trust the syntax. Verify the semantics.
