@martinffx/atelier 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +285 -0
  4. package/agents/architect.md +38 -0
  5. package/agents/oracle.md +40 -0
  6. package/agents/recon.md +39 -0
  7. package/dist/atelier.js +33082 -0
  8. package/package.json +44 -0
  9. package/skills/code-conventional-commit/SKILL.md +134 -0
  10. package/skills/code-conventional-commit/evals/evals.json +23 -0
  11. package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/commit-message.txt +4 -0
  12. package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/eval_metadata.json +6 -0
  13. package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/eval_metadata.json +6 -0
  14. package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/validation-result.txt +16 -0
  15. package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/commit-message.txt +8 -0
  16. package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/eval_metadata.json +6 -0
  17. package/skills/code-conventional-commit/workspace/iteration-1/report.html +1325 -0
  18. package/skills/code-debug/SKILL.md +271 -0
  19. package/skills/code-debug/references/language-tools.md +139 -0
  20. package/skills/code-debug/references/techniques.md +117 -0
  21. package/skills/code-docs/SKILL.md +171 -0
  22. package/skills/code-docs/references/api-docs.md +116 -0
  23. package/skills/code-docs/references/changelog.md +64 -0
  24. package/skills/code-docs/references/readme-structure.md +94 -0
  25. package/skills/code-git-worktrees/SKILL.md +218 -0
  26. package/skills/code-perf/SKILL.md +163 -0
  27. package/skills/code-perf/references/common-bottlenecks.md +124 -0
  28. package/skills/code-perf/references/optimization-patterns.md +167 -0
  29. package/skills/code-perf/references/profiling-tools.md +126 -0
  30. package/skills/code-review/SKILL.md +78 -0
  31. package/skills/code-review/references/output.md +200 -0
  32. package/skills/code-review/references/reviewers.md +719 -0
  33. package/skills/code-review/references/rq.md +357 -0
  34. package/skills/code-review/references/rs.md +30 -0
  35. package/skills/code-security/SKILL.md +183 -0
  36. package/skills/code-security/references/owasp-top-10.md +129 -0
  37. package/skills/code-security/references/security-tools.md +102 -0
  38. package/skills/code-security/references/vulnerability-patterns.md +129 -0
  39. package/skills/code-stacked-commit/SKILL.md +155 -0
  40. package/skills/code-subagents/SKILL.md +184 -0
  41. package/skills/code-subagents/references/code-quality-reviewer-prompt.md +48 -0
  42. package/skills/code-subagents/references/implementor-prompt.md +79 -0
  43. package/skills/code-subagents/references/spec-reviewer-prompt.md +62 -0
  44. package/skills/oracle-architect/SKILL.md +246 -0
  45. package/skills/oracle-architect/references/api-design.md +345 -0
  46. package/skills/oracle-architect/references/data-modeling.md +318 -0
  47. package/skills/oracle-architect/references/ddd-patterns.md +247 -0
  48. package/skills/oracle-challenge/SKILL.md +126 -0
  49. package/skills/oracle-testing/SKILL.md +237 -0
  50. package/skills/oracle-testing/references/anti-patterns.md +618 -0
  51. package/skills/oracle-testing/references/boundaries.md +579 -0
  52. package/skills/oracle-testing/references/core-testing.md +691 -0
  53. package/skills/oracle-testing/references/edge-testing.md +794 -0
  54. package/skills/oracle-testing/references/stub-driven-tdd.md +322 -0
  55. package/skills/oracle-thinkdeep/SKILL.md +164 -0
  56. package/skills/python-architecture/SKILL.md +148 -0
  57. package/skills/python-architecture/references/data-modeling.md +558 -0
  58. package/skills/python-architecture/references/ddd.md +569 -0
  59. package/skills/python-architecture/references/functional-core.md +337 -0
  60. package/skills/python-build-tools/SKILL.md +153 -0
  61. package/skills/python-build-tools/references/basedpyright.md +321 -0
  62. package/skills/python-build-tools/references/ruff.md +225 -0
  63. package/skills/python-build-tools/references/uv.md +257 -0
  64. package/skills/python-fastapi/SKILL.md +184 -0
  65. package/skills/python-fastapi/references/api-design.md +563 -0
  66. package/skills/python-fastapi/references/dependencies.md +479 -0
  67. package/skills/python-fastapi/references/middleware.md +475 -0
  68. package/skills/python-fastapi/references/validation.md +497 -0
  69. package/skills/python-modern-python/SKILL.md +201 -0
  70. package/skills/python-modern-python/references/async.md +76 -0
  71. package/skills/python-modern-python/references/pattern-matching.md +89 -0
  72. package/skills/python-modern-python/references/typing.md +96 -0
  73. package/skills/python-monorepo/SKILL.md +146 -0
  74. package/skills/python-monorepo/references/docker.md +515 -0
  75. package/skills/python-monorepo/references/namespace-packages.md +426 -0
  76. package/skills/python-monorepo/references/workspace-config.md +341 -0
  77. package/skills/python-sqlalchemy/SKILL.md +119 -0
  78. package/skills/python-sqlalchemy/references/async.md +51 -0
  79. package/skills/python-sqlalchemy/references/models.md +67 -0
  80. package/skills/python-sqlalchemy/references/queries.md +57 -0
  81. package/skills/python-temporal/SKILL.md +128 -0
  82. package/skills/python-temporal/references/patterns.md +87 -0
  83. package/skills/python-temporal/references/testing.md +54 -0
  84. package/skills/python-testing/SKILL.md +161 -0
  85. package/skills/python-testing/references/boundaries.md +449 -0
  86. package/skills/python-testing/references/mocking.md +449 -0
  87. package/skills/python-testing/references/pytest.md +524 -0
  88. package/skills/spec-brainstorm/SKILL.md +325 -0
  89. package/skills/spec-finish/SKILL.md +159 -0
  90. package/skills/spec-implement/SKILL.md +296 -0
  91. package/skills/spec-orchestrator/SKILL.md +156 -0
  92. package/skills/spec-plan/SKILL.md +248 -0
  93. package/skills/typescript-api-design/SKILL.md +396 -0
  94. package/skills/typescript-api-design/references/error-responses.md +469 -0
  95. package/skills/typescript-build-tools/SKILL.md +447 -0
  96. package/skills/typescript-build-tools/references/biome.md +436 -0
  97. package/skills/typescript-build-tools/references/bun.md +432 -0
  98. package/skills/typescript-build-tools/references/turborepo.md +426 -0
  99. package/skills/typescript-build-tools/references/vitest.md +518 -0
  100. package/skills/typescript-drizzle-orm/SKILL.md +398 -0
  101. package/skills/typescript-drizzle-orm/references/cloudflare.md +392 -0
  102. package/skills/typescript-drizzle-orm/references/entity-pattern.md +412 -0
  103. package/skills/typescript-drizzle-orm/references/postgresql.md +191 -0
  104. package/skills/typescript-drizzle-orm/references/repository-pattern.md +479 -0
  105. package/skills/typescript-drizzle-orm/references/sqlite.md +291 -0
  106. package/skills/typescript-dynamodb-toolbox/SKILL.md +370 -0
  107. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +574 -0
  108. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +495 -0
  109. package/skills/typescript-dynamodb-toolbox/references/modeling.md +332 -0
  110. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +631 -0
  111. package/skills/typescript-dynamodb-toolbox/references/testing.md +718 -0
  112. package/skills/typescript-dynamodb-toolbox/references/transactions.md +423 -0
  113. package/skills/typescript-effect-ts/SKILL.md +341 -0
  114. package/skills/typescript-effect-ts/references/core.md +412 -0
  115. package/skills/typescript-effect-ts/references/error-handling.md +468 -0
  116. package/skills/typescript-effect-ts/references/resources.md +476 -0
  117. package/skills/typescript-effect-ts/references/schema.md +536 -0
  118. package/skills/typescript-effect-ts/references/services.md +461 -0
  119. package/skills/typescript-fastify/SKILL.md +405 -0
  120. package/skills/typescript-fastify/references/plugins.md +329 -0
  121. package/skills/typescript-fastify/references/typeid.md +315 -0
  122. package/skills/typescript-functional-patterns/SKILL.md +402 -0
  123. package/skills/typescript-functional-patterns/references/adts.md +497 -0
  124. package/skills/typescript-functional-patterns/references/branded-types.md +542 -0
  125. package/skills/typescript-functional-patterns/references/migration-guide.md +695 -0
  126. package/skills/typescript-functional-patterns/references/option-result.md +585 -0
  127. package/skills/typescript-testing/SKILL.md +353 -0
  128. package/skills/typescript-testing/references/mocking.md +675 -0
  129. package/skills/typescript-testing/references/msw.md +852 -0
  130. package/skills/typescript-testing/references/snapshot-testing.md +774 -0
@@ -0,0 +1,479 @@
1
+ # Repository Pattern
2
+
3
+ Repositories wrap database access, providing a clean abstraction layer with error handling, validation, and business logic.
4
+
5
+ ## Core Concept
6
+
7
+ Repositories:
8
+ - Encapsulate all database operations for an entity
9
+ - Transform between records and entities using entity methods
10
+ - Handle database errors and convert to domain errors
11
+ - Enforce multi-tenancy and authorization boundaries
12
+ - Manage optimistic locking and retries
13
+
14
+ ## Basic Repository
15
+
16
+ ```typescript
17
+ import { eq, and } from 'drizzle-orm'
18
+ import type { DrizzleDB } from '../db'
19
+ import { users } from '../schema'
20
+ import { UserEntity } from '../entities/UserEntity'
21
+ import { NotFoundError } from '../errors'
22
+
23
+ export class UserRepo {
24
+ constructor(private db: DrizzleDB) {}
25
+
26
+ async getById(id: string): Promise<UserEntity> {
27
+ const record = await this.db.query.users.findFirst({
28
+ where: eq(users.id, id),
29
+ })
30
+
31
+ if (!record) {
32
+ throw new NotFoundError('User not found', { userId: id })
33
+ }
34
+
35
+ return UserEntity.fromRecord(record)
36
+ }
37
+
38
+ async list(): Promise<UserEntity[]> {
39
+ const records = await this.db.query.users.findMany({
40
+ orderBy: desc(users.createdAt),
41
+ })
42
+
43
+ return records.map(UserEntity.fromRecord)
44
+ }
45
+
46
+ async create(entity: UserEntity): Promise<UserEntity> {
47
+ const [record] = await this.db
48
+ .insert(users)
49
+ .values(entity.toRecord())
50
+ .returning()
51
+
52
+ return UserEntity.fromRecord(record)
53
+ }
54
+
55
+ async update(entity: UserEntity): Promise<UserEntity> {
56
+ const [record] = await this.db
57
+ .update(users)
58
+ .set(entity.toRecord())
59
+ .where(eq(users.id, entity.id))
60
+ .returning()
61
+
62
+ if (!record) {
63
+ throw new NotFoundError('User not found', { userId: entity.id })
64
+ }
65
+
66
+ return UserEntity.fromRecord(record)
67
+ }
68
+
69
+ async delete(id: string): Promise<void> {
70
+ const result = await this.db
71
+ .delete(users)
72
+ .where(eq(users.id, id))
73
+ .returning()
74
+
75
+ if (result.length === 0) {
76
+ throw new NotFoundError('User not found', { userId: id })
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## Repository with Error Handling
83
+
84
+ Convert database errors to domain errors:
85
+
86
+ ```typescript
87
+ import { eq } from 'drizzle-orm'
88
+ import type { DrizzleDB } from '../db'
89
+ import { users } from '../schema'
90
+ import { UserEntity } from '../entities/UserEntity'
91
+ import { handleDBError, NotFoundError } from '../errors'
92
+
93
+ export class UserRepo {
94
+ constructor(private db: DrizzleDB) {}
95
+
96
+ async create(entity: UserEntity): Promise<UserEntity> {
97
+ try {
98
+ const [record] = await this.db
99
+ .insert(users)
100
+ .values(entity.toRecord())
101
+ .returning()
102
+
103
+ return UserEntity.fromRecord(record)
104
+ } catch (error) {
105
+ // Maps DB errors (23505, 23503, etc) to domain errors
106
+ throw handleDBError(error, { userId: entity.id })
107
+ }
108
+ }
109
+
110
+ async update(entity: UserEntity): Promise<UserEntity> {
111
+ try {
112
+ const [record] = await this.db
113
+ .update(users)
114
+ .set(entity.toRecord())
115
+ .where(eq(users.id, entity.id))
116
+ .returning()
117
+
118
+ if (!record) {
119
+ throw new NotFoundError('User not found', { userId: entity.id })
120
+ }
121
+
122
+ return UserEntity.fromRecord(record)
123
+ } catch (error) {
124
+ throw handleDBError(error, { userId: entity.id })
125
+ }
126
+ }
127
+ }
128
+
129
+ // Error handler in errors.ts
130
+ type ErrorContext = {
131
+ userId?: string
132
+ resourceId?: string
133
+ [key: string]: unknown
134
+ }
135
+
136
+ export function handleDBError(error: unknown, context: ErrorContext = {}): never {
137
+ const code = (error as { code?: string }).code
138
+
139
+ switch (code) {
140
+ case '23505': // unique_violation
141
+ throw new ConflictError('Resource already exists', context)
142
+ case '23503': // foreign_key_violation
143
+ throw new NotFoundError('Referenced resource not found', context)
144
+ case '40001': // serialization_failure (Postgres)
145
+ throw new ServiceUnavailableError('Transaction conflict - please retry', {
146
+ retryable: true,
147
+ ...context,
148
+ })
149
+ case 'OC000': // occ_conflict (AWS DSQL)
150
+ throw new ServiceUnavailableError('Optimistic concurrency conflict', {
151
+ retryable: true,
152
+ ...context,
153
+ })
154
+ default:
155
+ throw error
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## Repository with Multi-Tenancy
161
+
162
+ Enforce organization-level isolation:
163
+
164
+ ```typescript
165
+ import { TypeID } from 'typeid-js'
166
+ import { eq, and } from 'drizzle-orm'
167
+ import type { DrizzleDB } from '../db'
168
+ import { ledgers } from '../schema'
169
+ import { LedgerEntity } from '../entities/LedgerEntity'
170
+ import { NotFoundError } from '../errors'
171
+
172
+ type OrgID = TypeID<'org'>
173
+ type LedgerID = TypeID<'lgr'>
174
+
175
+ export class LedgerRepo {
176
+ constructor(private db: DrizzleDB) {}
177
+
178
+ // ALWAYS include orgId in queries for multi-tenancy
179
+ async getById(orgId: OrgID, ledgerId: LedgerID): Promise<LedgerEntity> {
180
+ const record = await this.db.query.ledgers.findFirst({
181
+ where: and(
182
+ eq(ledgers.id, ledgerId.toString()),
183
+ eq(ledgers.organizationId, orgId.toString()) // Multi-tenancy check
184
+ ),
185
+ })
186
+
187
+ if (!record) {
188
+ throw new NotFoundError('Ledger not found', {
189
+ organizationId: orgId.toString(),
190
+ ledgerId: ledgerId.toString(),
191
+ })
192
+ }
193
+
194
+ return LedgerEntity.fromRecord(record)
195
+ }
196
+
197
+ async list(orgId: OrgID): Promise<LedgerEntity[]> {
198
+ const records = await this.db.query.ledgers.findMany({
199
+ where: eq(ledgers.organizationId, orgId.toString()),
200
+ orderBy: desc(ledgers.created),
201
+ })
202
+
203
+ return records.map(LedgerEntity.fromRecord)
204
+ }
205
+
206
+ async create(entity: LedgerEntity): Promise<LedgerEntity> {
207
+ try {
208
+ const [record] = await this.db
209
+ .insert(ledgers)
210
+ .values(entity.toRecord())
211
+ .returning()
212
+
213
+ return LedgerEntity.fromRecord(record)
214
+ } catch (error) {
215
+ throw handleDBError(error, {
216
+ organizationId: entity.organizationId.toString(),
217
+ ledgerId: entity.id.toString(),
218
+ })
219
+ }
220
+ }
221
+
222
+ async update(orgId: OrgID, entity: LedgerEntity): Promise<LedgerEntity> {
223
+ try {
224
+ const [record] = await this.db
225
+ .update(ledgers)
226
+ .set(entity.toRecord())
227
+ .where(and(
228
+ eq(ledgers.id, entity.id.toString()),
229
+ eq(ledgers.organizationId, orgId.toString()) // Multi-tenancy check
230
+ ))
231
+ .returning()
232
+
233
+ if (!record) {
234
+ throw new NotFoundError('Ledger not found', {
235
+ organizationId: orgId.toString(),
236
+ ledgerId: entity.id.toString(),
237
+ })
238
+ }
239
+
240
+ return LedgerEntity.fromRecord(record)
241
+ } catch (error) {
242
+ throw handleDBError(error, {
243
+ organizationId: orgId.toString(),
244
+ ledgerId: entity.id.toString(),
245
+ })
246
+ }
247
+ }
248
+
249
+ async delete(orgId: OrgID, ledgerId: LedgerID): Promise<void> {
250
+ const result = await this.db
251
+ .delete(ledgers)
252
+ .where(and(
253
+ eq(ledgers.id, ledgerId.toString()),
254
+ eq(ledgers.organizationId, orgId.toString()) // Multi-tenancy check
255
+ ))
256
+ .returning()
257
+
258
+ if (result.length === 0) {
259
+ throw new NotFoundError('Ledger not found', {
260
+ organizationId: orgId.toString(),
261
+ ledgerId: ledgerId.toString(),
262
+ })
263
+ }
264
+ }
265
+ }
266
+ ```
267
+
268
+ ## Repository with Optimistic Locking
269
+
270
+ Handle concurrent updates safely:
271
+
272
+ ```typescript
273
+ import { eq, and, sql } from 'drizzle-orm'
274
+ import type { DrizzleDB } from '../db'
275
+ import { users } from '../schema'
276
+ import { UserEntity } from '../entities/UserEntity'
277
+ import { ConflictError, NotFoundError } from '../errors'
278
+
279
+ export class UserRepo {
280
+ constructor(private db: DrizzleDB) {}
281
+
282
+ async update(entity: UserEntity): Promise<UserEntity> {
283
+ try {
284
+ const result = await this.db
285
+ .update(users)
286
+ .set({
287
+ ...entity.toRecord(),
288
+ lockVersion: sql`${users.lockVersion} + 1`, // Increment version
289
+ })
290
+ .where(and(
291
+ eq(users.id, entity.id),
292
+ eq(users.lockVersion, entity.lockVersion) // Version check
293
+ ))
294
+ .returning()
295
+
296
+ if (result.length === 0) {
297
+ // Either not found or version mismatch
298
+ const exists = await this.db.query.users.findFirst({
299
+ where: eq(users.id, entity.id),
300
+ columns: { id: true, lockVersion: true },
301
+ })
302
+
303
+ if (!exists) {
304
+ throw new NotFoundError('User not found', { userId: entity.id })
305
+ }
306
+
307
+ // Version mismatch - resource was modified
308
+ throw new ConflictError({
309
+ message: 'Resource was modified by another transaction',
310
+ retryable: true, // Service layer can retry
311
+ context: {
312
+ userId: entity.id,
313
+ expectedVersion: entity.lockVersion,
314
+ actualVersion: exists.lockVersion,
315
+ },
316
+ })
317
+ }
318
+
319
+ return UserEntity.fromRecord(result[0])
320
+ } catch (error) {
321
+ throw handleDBError(error, { userId: entity.id })
322
+ }
323
+ }
324
+ }
325
+ ```
326
+
327
+ ## Repository with Transactions
328
+
329
+ Complex operations across multiple tables:
330
+
331
+ ```typescript
332
+ import { TypeID } from 'typeid-js'
333
+ import { eq, and, inArray, sql } from 'drizzle-orm'
334
+ import type { DrizzleDB } from '../db'
335
+ import {
336
+ ledgerTransactions,
337
+ ledgerTransactionEntries,
338
+ ledgerAccounts,
339
+ } from '../schema'
340
+ import { LedgerTransactionEntity } from '../entities/LedgerTransactionEntity'
341
+ import { LedgerAccountEntity } from '../entities/LedgerAccountEntity'
342
+ import { ConflictError } from '../errors'
343
+
344
+ type OrgID = TypeID<'org'>
345
+ type LedgerTransactionID = TypeID<'ltr'>
346
+
347
+ export class LedgerTransactionRepo {
348
+ constructor(private db: DrizzleDB) {}
349
+
350
+ async createTransaction(
351
+ transaction: LedgerTransactionEntity
352
+ ): Promise<LedgerTransactionEntity> {
353
+ // 1. Fetch all affected accounts OUTSIDE transaction
354
+ const accountIds = transaction.entries.map(e => e.accountId.toString())
355
+ const accountRecords = await this.db.select().from(ledgerAccounts)
356
+ .where(inArray(ledgerAccounts.id, accountIds))
357
+
358
+ const accountsById = new Map(
359
+ accountRecords.map(r => [r.id, LedgerAccountEntity.fromRecord(r)])
360
+ )
361
+
362
+ // 2. Calculate balance updates in-memory using entity business logic
363
+ const updatedAccounts = transaction.entries.map(entry => {
364
+ const account = accountsById.get(entry.accountId.toString())
365
+ if (!account) {
366
+ throw new NotFoundError('Account not found', {
367
+ accountId: entry.accountId.toString(),
368
+ })
369
+ }
370
+ // Entity contains the double-entry accounting logic
371
+ return account.applyEntry(entry)
372
+ })
373
+
374
+ // 3. Write atomically in single DB transaction
375
+ return await this.db.transaction(async tx => {
376
+ // Insert transaction record (with upsert for idempotency)
377
+ const [txRecord] = await tx
378
+ .insert(ledgerTransactions)
379
+ .values(transaction.toRecord())
380
+ .onConflictDoUpdate({
381
+ target: ledgerTransactions.idempotencyKey,
382
+ set: { updated: new Date() },
383
+ })
384
+ .returning()
385
+
386
+ // Insert transaction entries
387
+ await tx.insert(ledgerTransactionEntries).values(
388
+ transaction.entries.map(e => e.toRecord())
389
+ )
390
+
391
+ // Update account balances with optimistic locking
392
+ for (const account of updatedAccounts) {
393
+ const result = await tx
394
+ .update(ledgerAccounts)
395
+ .set({
396
+ ...account.toRecord(),
397
+ lockVersion: sql`${ledgerAccounts.lockVersion} + 1`,
398
+ })
399
+ .where(and(
400
+ eq(ledgerAccounts.id, account.id.toString()),
401
+ eq(ledgerAccounts.lockVersion, account.lockVersion)
402
+ ))
403
+ .returning()
404
+
405
+ if (result.length === 0) {
406
+ // Optimistic lock failure - another transaction modified this account
407
+ throw new ConflictError({
408
+ message: `Account ${account.id} was modified by another transaction`,
409
+ retryable: true, // Service layer will retry entire operation
410
+ context: {
411
+ accountId: account.id.toString(),
412
+ transactionId: transaction.id.toString(),
413
+ },
414
+ })
415
+ }
416
+ }
417
+
418
+ return LedgerTransactionEntity.fromRecord(txRecord)
419
+ })
420
+ }
421
+ }
422
+ ```
423
+
424
+ ## Repository with Pagination
425
+
426
+ Cursor-based pagination for large datasets:
427
+
428
+ ```typescript
429
+ import { gt, desc } from 'drizzle-orm'
430
+ import type { DrizzleDB } from '../db'
431
+ import { posts } from '../schema'
432
+ import { PostEntity } from '../entities/PostEntity'
433
+
434
+ interface PaginatedResult<T> {
435
+ items: T[]
436
+ nextCursor?: string
437
+ hasMore: boolean
438
+ }
439
+
440
+ export class PostRepo {
441
+ constructor(private db: DrizzleDB) {}
442
+
443
+ async list(
444
+ limit: number = 20,
445
+ cursor?: string
446
+ ): Promise<PaginatedResult<PostEntity>> {
447
+ const queryLimit = limit + 1 // Fetch one extra to check hasMore
448
+
449
+ const records = await this.db.query.posts.findMany({
450
+ where: cursor ? gt(posts.id, cursor) : undefined,
451
+ orderBy: desc(posts.createdAt),
452
+ limit: queryLimit,
453
+ })
454
+
455
+ const hasMore = records.length > limit
456
+ const items = records.slice(0, limit).map(PostEntity.fromRecord)
457
+ const nextCursor = hasMore ? records[limit - 1].id : undefined
458
+
459
+ return {
460
+ items,
461
+ nextCursor,
462
+ hasMore,
463
+ }
464
+ }
465
+ }
466
+ ```
467
+
468
+ ## Guidelines
469
+
470
+ 1. **Constructor injection** - Pass `db` instance to constructor
471
+ 2. **Return entities** - Always convert records to entities before returning
472
+ 3. **Error context** - Include relevant IDs in error context for debugging
473
+ 4. **Multi-tenancy** - Always filter by `organizationId` when applicable
474
+ 5. **Optimistic locking** - Use `lockVersion` for resources with concurrent updates
475
+ 6. **Transactions** - Use `db.transaction()` for multi-step operations
476
+ 7. **Idempotency** - Use `onConflictDoUpdate` with idempotency keys where applicable
477
+ 8. **Check returning** - Always check if `.returning()` is empty (means not found)
478
+ 9. **Pagination** - Use cursor-based pagination for large result sets
479
+ 10. **Error handling** - Wrap DB operations in try/catch and use `handleDBError()`