Skip to content

Commit 0488f97

Browse files
idoshamuncapJavert
andauthored
docs: update AGENTS.md (#3326)
Co-authored-by: Ante Barić <ante@kickass.website>
1 parent e7e0cef commit 0488f97

4 files changed

Lines changed: 212 additions & 72 deletions

File tree

AGENTS.md

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22

33
This file provides guidance to coding agents when working with code in this repository.
44

5+
## Prerequisites
6+
7+
- **Node.js**: 22.16.0 (managed via Volta)
8+
- **Package Manager**: pnpm 9.14.4
9+
510
## Essential Commands
611

712
**Development:**
8-
- `pnpm run dev` - Start API server with hot reload on port 5000
13+
- `pnpm run dev` - Start API server with hot reload on port 3000
914
- `pnpm run dev:background` - Start background processor
1015
- `pnpm run dev:temporal-worker` - Start Temporal worker
1116
- `pnpm run dev:temporal-server` - Start Temporal server for local development
@@ -35,15 +40,17 @@ This file provides guidance to coding agents when working with code in this repo
3540
- **Mercurius** - GraphQL server with caching, upload support, and subscriptions
3641
- **TypeORM** - Database ORM with entity-based modeling and migrations
3742
- **PostgreSQL** - Primary database with master/slave replication setup. Favor read replica when you're ok with eventually consistent data.
38-
- **Redis** - Caching and pub/sub via ioRedisPool
43+
- **Redis** - Caching and pub/sub via `@dailydotdev/ts-ioredis-pool`
3944
- **Temporal** - Workflow orchestration for background jobs
4045
- **ClickHouse** - Analytics and metrics storage
4146

4247
**Application Entry Points:**
4348
- `src/index.ts` - Main Fastify server setup with GraphQL, auth, and middleware
44-
- `bin/cli.ts` - CLI dispatcher supporting api, background, temporal, cron modes
49+
- `bin/cli.ts` - CLI dispatcher supporting api, background, temporal, cron, personalized-digest modes
4550
- `src/background.ts` - Pub/Sub message handlers and background processing
4651
- `src/cron.ts` - Scheduled task execution
52+
- `src/temporal/` - Temporal workflow definitions and workers
53+
- `src/commands/` - Standalone command implementations (e.g., personalized digest)
4754

4855
**GraphQL Schema Organization:**
4956
- `src/graphql.ts` - Combines all schema modules with transformers and directives
@@ -71,11 +78,12 @@ This file provides guidance to coding agents when working with code in this repo
7178

7279
**Business Domains:**
7380
- **Content**: Posts, comments, bookmarks, feeds, sources
74-
- **Users**: Authentication, preferences, profiles, user experience
75-
- **Organizations**: Squad management, member roles, campaigns
81+
- **Users**: Authentication, preferences, profiles, user experience, streaks
82+
- **Squads**: Squad management, member roles, public requests
83+
- **Organizations**: Organization management, campaigns
7684
- **Notifications**: Push notifications, email digests, alerts
77-
- **Monetization**: Paddle subscription management, premium features
78-
- **Squads**: Squad management, member roles, campaigns
85+
- **Monetization**: Paddle subscription management, premium features, cores/transactions
86+
- **Opportunities**: Job matching, recruiter features, candidate profiles
7987

8088
**Testing Strategy:**
8189
- Jest with supertest for integration testing
@@ -88,4 +96,10 @@ This file provides guidance to coding agents when working with code in this repo
8896
- GrowthBook for feature flags and A/B testing
8997
- OneSignal for push notifications
9098
- Temporal workflows for async job processing
91-
- Rate limiting and caching at multiple layers
99+
- Rate limiting and caching at multiple layers
100+
101+
**Infrastructure as Code:**
102+
- `.infra/` - Pulumi configuration for deployment
103+
- `.infra/crons.ts` - Cron job schedules and resource limits
104+
- `.infra/common.ts` - Worker subscription definitions
105+
- `.infra/index.ts` - Main Pulumi deployment configuration

src/cron/AGENTS.md

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Create a new file in `src/cron/` directory following this pattern:
5656
import { Cron } from './cron';
5757
import { YourEntity } from '../entity';
5858

59-
const cron: Cron = {
59+
export const yourCronName: Cron = {
6060
name: 'your-cron-name', // Must match name in .infra/crons.ts
6161
handler: async (con, logger, pubsub) => {
6262
// Your cron logic here
@@ -75,20 +75,18 @@ const cron: Cron = {
7575
logger.info({ count: results.length }, 'Cron job completed');
7676
},
7777
};
78-
79-
export default cron;
8078
```
8179

8280
### Step 2: Register in Index
8381

8482
Add your cron to `src/cron/index.ts`:
8583

8684
```typescript
87-
import yourCron from './yourCron';
85+
import { yourCronName } from './yourCronName';
8886

8987
export const crons: Cron[] = [
9088
// ... existing crons
91-
yourCron,
89+
yourCronName,
9290
];
9391
```
9492

@@ -202,21 +200,32 @@ handler: async (con, logger, pubsub) => {
202200

203201
### 6. Testing
204202

205-
Create tests in `__tests__/cron/` directory:
203+
Create tests in `__tests__/cron/` directory. Tests use a real database connection (reset before each test run):
206204

207205
```typescript
208-
import cron from '../../src/cron/yourCron';
206+
import { yourCronName } from '../../src/cron/yourCronName';
207+
import { expectSuccessfulCron, saveFixtures } from '../helpers';
208+
import { YourEntity } from '../../src/entity';
209+
import { DataSource } from 'typeorm';
210+
import createOrGetConnection from '../../src/db';
209211

210-
describe('yourCron', () => {
211-
it('should execute successfully', async () => {
212-
const mockCon = createMockConnection();
213-
const mockLogger = createMockLogger();
214-
const mockPubsub = createMockPubsub();
215-
216-
await cron.handler(mockCon, mockLogger, mockPubsub);
217-
218-
// Assertions
219-
});
212+
let con: DataSource;
213+
214+
beforeAll(async () => {
215+
con = await createOrGetConnection();
216+
});
217+
218+
beforeEach(async () => {
219+
// Set up test fixtures
220+
await saveFixtures(con, YourEntity, yourFixtures);
221+
});
222+
223+
it('should execute successfully', async () => {
224+
await expectSuccessfulCron(yourCronName);
225+
226+
// Verify database state after cron execution
227+
const results = await con.getRepository(YourEntity).find();
228+
expect(results).toHaveLength(expectedLength);
220229
});
221230
```
222231

@@ -225,7 +234,7 @@ describe('yourCron', () => {
225234
### Incremental Processing with Checkpoints
226235

227236
```typescript
228-
const cron: Cron = {
237+
export const incrementalUpdate: Cron = {
229238
name: 'incremental-update',
230239
handler: async (con) => {
231240
const checkpointKey = 'last_incremental_update';
@@ -290,13 +299,13 @@ handler: async (con, logger) => {
290299

291300
### Kubernetes CronJobs
292301

293-
Crons are deployed as Kubernetes CronJobs via Pulumi:
302+
Crons are deployed as Kubernetes CronJobs via Pulumi (see `.infra/index.ts` lines 577-591):
294303

295304
- **Command**: `['dumb-init', 'node', 'bin/cli', 'cron', '<cron-name>']`
296-
- **Spot Instances**: Enabled by default (70% weight) for cost optimization
297-
- **Default Limits**: Memory and CPU limits can be overridden per cron
298-
- **Default Deadline**: 300 seconds (5 minutes), configurable per cron
299-
- **Adhoc Environments**: Crons are disabled in adhoc environments (see `.infra/index.ts`)
305+
- **Spot Instances**: Enabled by default for all crons
306+
- **Default Limits**: Uses background worker limits (`512Mi` memory) unless overridden per cron
307+
- **Default Deadline**: 300 seconds (5 minutes), configurable per cron via `activeDeadlineSeconds`
308+
- **Adhoc Environments**: Crons are disabled in adhoc environments
300309

301310
### Resource Configuration
302311

src/graphorm/AGENTS.md

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,9 @@ export const resolvers = {
224224
};
225225
```
226226

227-
### Query with Custom Filtering
227+
### Query with Custom Filtering and Read Replica
228+
229+
The optional fourth parameter enables read replica routing for eventually consistent reads:
228230

229231
```typescript
230232
export const resolvers = {
@@ -239,7 +241,7 @@ export const resolvers = {
239241
.limit(1);
240242
return builder;
241243
},
242-
true // Use read replica
244+
true // for better performance for read queries are required, take into account potential replication lag if doing reads right after writes
243245
);
244246
}
245247
}
@@ -265,6 +267,42 @@ export const resolvers = {
265267
};
266268
```
267269

270+
### Query by Hierarchy
271+
272+
Use `queryByHierarchy` when you need to query a nested field from the resolve tree:
273+
274+
```typescript
275+
export const resolvers = {
276+
Query: {
277+
searchPosts: async (_, args, ctx: Context, info) => {
278+
return graphorm.queryByHierarchy<GQLPost>(
279+
ctx,
280+
info,
281+
['posts', 'edges', 'node'], // Path to the nested field
282+
(builder) => {
283+
builder.queryBuilder.where(`${builder.alias}.visible = true`);
284+
return builder;
285+
},
286+
true // readReplica
287+
);
288+
}
289+
}
290+
};
291+
```
292+
293+
### Query Paginated Integration
294+
295+
Use `queryPaginatedIntegration` for non-database data (e.g., external APIs) while still returning Relay-style pagination:
296+
297+
```typescript
298+
const results = await graphorm.queryPaginatedIntegration<ExternalItem>(
299+
(nodeSize) => false, // hasPreviousPage
300+
(nodeSize) => nodeSize >= limit, // hasNextPage
301+
(node, index) => base64(`cursor:${index}`), // nodeToCursor
302+
async () => fetchFromExternalAPI(args), // fetchData callback
303+
);
304+
```
305+
268306
## Configuration Patterns
269307

270308
### Required Columns
@@ -354,11 +392,12 @@ fields: {
354392

355393
## Best Practices
356394

357-
### 1. Always Use `beforeQuery` for Filtering
395+
### 1. Always Use the Query Builder Callback for Filtering
358396

359-
Don't fetch all data and filter in JavaScript. Filter at the database level:
397+
Don't fetch all data and filter in JavaScript. Filter at the database level using the builder callback parameter:
360398

361399
```typescript
400+
// The callback receives { queryBuilder, alias } and must return the modified builder
362401
(builder) => {
363402
builder.queryBuilder
364403
.andWhere(`${builder.alias}."userId" = :userId`, { userId: ctx.userId })
@@ -395,10 +434,13 @@ Always paginate lists to avoid fetching too much data:
395434
graphorm.queryPaginated(
396435
ctx,
397436
info,
398-
hasPreviousPage,
399-
hasNextPage,
400-
nodeToCursor,
401-
beforeQuery
437+
(nodeSize) => hasPreviousPage(nodeSize),
438+
(nodeSize) => hasNextPage(nodeSize),
439+
(node, index) => nodeToCursor(node, index),
440+
(builder) => {
441+
builder.queryBuilder.limit(limit).offset(offset);
442+
return builder;
443+
}
402444
);
403445
```
404446

@@ -450,6 +492,17 @@ requiredColumns: [
450492
4. **Required Columns**: Only add truly required columns to avoid unnecessary data fetching
451493
5. **Transform Functions**: Keep transforms lightweight - avoid database calls in transforms
452494

495+
## Available Methods
496+
497+
| Method | Description | Returns |
498+
|--------|-------------|---------|
499+
| `query<T>()` | Query multiple results | `Promise<T[]>` |
500+
| `queryOne<T>()` | Query single result or null | `Promise<T \| null>` |
501+
| `queryOneOrFail<T>()` | Query single result or throw | `Promise<T>` |
502+
| `queryPaginated<T>()` | Query with Relay pagination | `Promise<Connection<T>>` |
503+
| `queryByHierarchy<T>()` | Query nested field from resolve tree | `Promise<T[]>` |
504+
| `queryPaginatedIntegration<T>()` | Paginate non-DB data | `Promise<Connection<T>>` |
505+
453506
## Related Files
454507

455508
- **Core Implementation**: `src/graphorm/graphorm.ts`

0 commit comments

Comments
 (0)