Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
## 2026-05-16 - [Batch FeathersJS user fetches with $in operator to fix N+1 query]
**Learning:** FeathersJS allows passing `$in` clauses through the query parameter (e.g. `user_id: { $in: ownerIds }`). When writing custom Feathers service logic, you can easily parse this array and pass it to Drizzle's `inArray()` to perform a batched query, instead of looping over `service.get(id)` causing N+1 database roundtrips.
**Action:** When implementing or updating custom Feathers `find()` methods, extract and parse the `$in` parameters to support batched Drizzle `inArray()` lookups, and always replace `Promise.all(ids.map(id => service.get(id)))` with a single batched `find()` call.
## 2024-05-24 - [Avoid N+1 file I/O operations in loops]
**Learning:** Functions like `loadConfig()` which perform synchronous or asynchronous file I/O (such as `fs.readFile` and `yaml.load`) create significant latency when called iteratively inside loops or array mappings. They cause O(N) disk operations for operations that should only take O(1).
**Action:** When performing mapping or looping over arrays in business logic or handlers, always hoist configuration loading or expensive file-system checks outside the loop, evaluate it once, and pass the cached result as an argument to the per-item processor.
24 changes: 17 additions & 7 deletions apps/agor-daemon/src/services/worktrees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import { existsSync } from 'node:fs';
import { mkdir } from 'node:fs/promises';
import { homedir } from 'node:os';
import { dirname, join } from 'node:path';
import { ENVIRONMENT, isWorktreeRbacEnabled, loadConfig, PAGINATION } from '@agor/core/config';
import {
type AgorConfig,
ENVIRONMENT,
isWorktreeRbacEnabled,
loadConfig,
PAGINATION,
} from '@agor/core/config';
import { type Database, WorktreeRepository, type WorktreeWithZoneAndSessions } from '@agor/core/db';
import { renderWorktreeSnapshot } from '@agor/core/environment/render-snapshot';
import type { Application } from '@agor/core/feathers';
Expand Down Expand Up @@ -226,8 +232,10 @@ export class WorktreesService extends DrizzleService<Worktree, Partial<Worktree>
* so admins can set org-wide defaults in config.yaml. Explicit values on the
* input always win; defaults fill in only when the caller omits the field.
*/
private async applyWorktreeCreateDefaults(data: Partial<Worktree>): Promise<Partial<Worktree>> {
const config = await loadConfig();
private applyWorktreeCreateDefaults(
data: Partial<Worktree>,
config: AgorConfig
): Partial<Worktree> {
const defaults = config.worktrees;
if (!defaults) return data;

Expand All @@ -251,13 +259,15 @@ export class WorktreesService extends DrizzleService<Worktree, Partial<Worktree>
data: Partial<Worktree> | Partial<Worktree>[],
params?: WorktreeParams
): Promise<Worktree | Worktree[]> {
const config = await loadConfig();

if (Array.isArray(data)) {
const withDefaults = await Promise.all(
data.map((item) => this.applyWorktreeCreateDefaults(item))
);
// ⚑ Bolt Performance Optimization:
// Load config once outside the loop instead of reading and parsing the file O(N) times.
const withDefaults = data.map((item) => this.applyWorktreeCreateDefaults(item, config));
return super.create(withDefaults, params) as Promise<Worktree[]>;
}
const withDefaults = await this.applyWorktreeCreateDefaults(data);
const withDefaults = this.applyWorktreeCreateDefaults(data, config);
return super.create(withDefaults, params) as Promise<Worktree>;
}

Expand Down