@crossdelta/platform-sdk 0.9.4 → 0.10.1

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.
@@ -1,6 +1,68 @@
1
1
  # Service Generator Instructions
2
2
 
3
- These rules define how AI-generated scaffolded services must be structured.
3
+ **Generic guidelines for all service types. Framework-specific details are in separate files:**
4
+ - `hono-bun.md` - Hono with Bun runtime
5
+ - `hono-n```post-commands
6
+ pf event add orders.created --service services/my-notifications
7
+ pf event add customers.updated --service services/my-notifications
8
+ ```md` - Hono with Node.js runtime (tsx + @hono/node-server)
9
+ - `nest.md` - NestJS framework
10
+
11
+ ---
12
+
13
+ ## 🚨 CRITICAL: @crossdelta/cloudevents API
14
+
15
+ **ONLY these exports exist in @crossdelta/cloudevents:**
16
+ - `ensureJetStreamStreams` - create/ensure multiple JetStream streams (preferred)
17
+ - `consumeJetStreamStreams` - consume from multiple streams (preferred)
18
+ - `ensureJetStreamStream` - create/ensure a single stream (legacy)
19
+ - `consumeJetStreamEvents` - consume from single stream (legacy)
20
+ - `handleEvent` - create event handler
21
+ - `publish` - publish events
22
+ - `createContract` - create shared event contract
23
+
24
+ **DO NOT USE (these do NOT exist):**
25
+ - ❌ `stream` - DOES NOT EXIST
26
+ - ❌ `consumer` - DOES NOT EXIST
27
+ - ❌ `subscribe` - DOES NOT EXIST
28
+ - ❌ Any other function not listed above
29
+
30
+ **EXACT function signatures (use batch versions for multiple streams):**
31
+ ```ts
32
+ // Setup multiple streams at once (PREFERRED)
33
+ await ensureJetStreamStreams({
34
+ streams: [
35
+ { stream: 'ORDERS', subjects: ['orders.*'] },
36
+ { stream: 'CUSTOMERS', subjects: ['customers.*'] },
37
+ ]
38
+ })
39
+
40
+ // Consume from multiple streams (PREFERRED)
41
+ consumeJetStreamStreams({
42
+ streams: ['ORDERS', 'CUSTOMERS'],
43
+ consumer: 'my-service',
44
+ discover: './src/events/**/*.event.ts',
45
+ })
46
+ ```
47
+
48
+ **WRONG parameter names (DO NOT USE):**
49
+ - ❌ `name:` - use `stream:` instead
50
+ - ❌ `handler:` - use `discover:` instead (auto-discovery pattern)
51
+ - ❌ `topic:` - use `subjects:` instead
52
+
53
+ ---
54
+
55
+ ## 🚨 CRITICAL: @crossdelta/telemetry Import
56
+
57
+ **Telemetry is a side-effect import. Use EXACTLY:**
58
+ ```ts
59
+ import '@crossdelta/telemetry'
60
+ ```
61
+
62
+ **DO NOT USE:**
63
+ - ❌ `@crossdelta/telemetry/register` - DOES NOT EXIST
64
+ - ❌ `import { ... } from '@crossdelta/telemetry'` - No named exports needed
65
+ - ❌ Any subpath imports
4
66
 
5
67
  ---
6
68
 
@@ -8,6 +70,174 @@ These rules define how AI-generated scaffolded services must be structured.
8
70
 
9
71
  **FOR EVENT CONSUMER SERVICES (services that "consume", "listen to", "react to" events):**
10
72
 
73
+ ### What YOU (the AI) generate for Event Consumers:
74
+
75
+ 1. **`packages/contracts/src/events/<event>.ts`** - Contract with **correct schema fields**
76
+ 2. **`src/index.ts`** - Entry point with NATS stream/consumer setup
77
+ 3. **`src/events/<event>.event.ts`** - Event handler that calls the use-case
78
+ 4. **`src/use-cases/*.use-case.ts`** - Business logic (called by handlers)
79
+ 5. **`src/use-cases/*.test.ts`** - Tests for use-cases
80
+ 6. **`README.md`** - Documentation
81
+
82
+ The `pf event add` commands in `post-commands` will create:
83
+ - Mock data for testing
84
+ - Index exports (if not already present)
85
+
86
+ ### Event Handler Template (YOU write this!)
87
+
88
+ ```typescript
89
+ // src/events/orders-created.event.ts
90
+ import { handleEvent } from '@crossdelta/cloudevents'
91
+ import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
92
+ import { sendOrderNotification } from '../use-cases/send-order-notification.use-case'
93
+
94
+ export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
95
+ await sendOrderNotification(data)
96
+ })
97
+ ```
98
+
99
+ **Rules:**
100
+ - Handler imports the Contract from `{{scope}}/contracts`
101
+ - Handler calls the corresponding use-case function
102
+ - Handler is thin - NO business logic, just delegation
103
+ - Use-case name should match the handler purpose (e.g., `send-order-notification` for a notification service)
104
+
105
+ ### Handler Location & Structure (CRITICAL!)
106
+
107
+ **⚠️ MUST be in `src/events/*.event.ts` - NEVER in `handlers/` subdirectory**
108
+
109
+ ```
110
+ ✅ CORRECT:
111
+ services/my-service/src/events/
112
+ ├── orders-created.event.ts # Handler here
113
+ ├── customers-updated.event.ts # Handler here
114
+ └── events.service.ts
115
+
116
+ ❌ WRONG (DO NOT CREATE):
117
+ services/my-service/src/events/
118
+ ├── handlers/ # NEVER create this folder!
119
+ │ └── orders-created.handler.ts # Handlers NEVER here!
120
+ └── events.module.ts (with named imports)
121
+ ```
122
+
123
+ **Handler Export Pattern:**
124
+
125
+ ```typescript
126
+ // ✅ CORRECT: Default export
127
+ export default handleEvent(OrdersCreatedContract, async (data) => {
128
+ // Handler code
129
+ })
130
+
131
+ // ❌ WRONG: Named export
132
+ export const OrdersCreatedHandler = handleEvent(...)
133
+ ```
134
+
135
+ **Module Pattern:**
136
+
137
+ ```typescript
138
+ // ✅ CORRECT: EventsModule only provides EventsService
139
+ import { Module } from '@nestjs/common'
140
+ import { EventsService } from './events.service'
141
+
142
+ @Module({
143
+ providers: [EventsService],
144
+ exports: [EventsService],
145
+ })
146
+ export class EventsModule {}
147
+
148
+ // ❌ WRONG: Importing handlers and registering as providers
149
+ import { OrdersCreatedHandler } from './handlers/orders-created.handler'
150
+
151
+ @Module({
152
+ providers: [EventsService, OrdersCreatedHandler], // ❌ WRONG!
153
+ })
154
+ ```
155
+
156
+ **Auto-Discovery Mechanism:**
157
+
158
+ Handlers are **automatically discovered** by the EventsService via glob pattern. Do NOT manually import them:
159
+
160
+ ```typescript
161
+ // ✅ CORRECT: EventsService uses discover glob
162
+ await consumeJetStreamStreams({
163
+ streams: ['ORDERS', 'CUSTOMERS'],
164
+ consumer: 'my-service',
165
+ discover: './src/events/**/*.event.ts', // Auto-discovers all handlers
166
+ })
167
+
168
+ // ❌ WRONG: Manual handler imports not needed - discovery handles it!
169
+ import { OrdersCreatedHandler } from './events/orders-created.event' // Don't do this
170
+ ```
171
+
172
+ **Common Mistakes (from real failures):**
173
+
174
+ | Mistake | Why It Fails | Solution |
175
+ |---------|-------------|----------|
176
+ | Create `/handlers` subdirectory | Breaks auto-discovery path; creates duplicate files | Keep ALL handlers in `src/events/*.event.ts` |
177
+ | Named exports: `export const OrdersCreatedHandler` | CLI doesn't recognize as handler; causes import/export mismatches | Use `export default handleEvent(...)` ALWAYS |
178
+ | Import handlers in `events.module.ts` | Auto-discovery doesn't require imports; causes TS2614 errors | Remove handler imports; let discover glob find them |
179
+ | Register handlers as module providers | Not a NestJS injectable; breaks DI container | Only register EventsService as provider |
180
+
181
+ **Real-World Example of the Error:**
182
+
183
+ ```
184
+ // ❌ What went wrong in test-ws-jack
185
+ src/events/events.module.ts:
186
+ import { OrdersCreatedHandler } from './handlers/orders-created.handler' ← File doesn't exist with named export
187
+
188
+ Error: TS2614 [ERROR]: Module "/services/my-nest-service/src/events/orders-created.event.ts" has no exported member "OrdersCreatedHandler"
189
+
190
+ Reason: Handler is `export default handleEvent(...)`, NOT `export const OrdersCreatedHandler`
191
+ Folder structure should NOT have `/handlers` subdirectory
192
+ ```
193
+
194
+ **Checklist Before Committing Handler Code:**
195
+
196
+ - [ ] All handlers in `src/events/*.event.ts` (NOT in subdirectories)
197
+ - [ ] Handler use `export default handleEvent(Contract, ...)`
198
+ - [ ] `events.module.ts` only provides `EventsService`
199
+ - [ ] `events.service.ts` uses `discover: './src/events/**/*.event.ts'`
200
+ - [ ] No `/handlers` subdirectory exists
201
+ - [ ] No handler imports in `events.module.ts`
202
+
203
+ ### Contract Schema (YOU write this!)
204
+
205
+ **Create the contract with the EXACT fields your use-case needs.**
206
+
207
+ **⚠️ Zod 4 Syntax:** Use `z.string().email()` without parameters. For custom messages, use `.check()`:
208
+
209
+ ```typescript
210
+ // packages/contracts/src/events/orders-created.ts
211
+ import { createContract } from '@crossdelta/cloudevents'
212
+ import { z } from 'zod'
213
+
214
+ export const OrdersCreatedSchema = z.object({
215
+ orderId: z.string(),
216
+ customerId: z.string(),
217
+ email: z.string().email(), // ✅ Zod 4: no params
218
+ total: z.number(),
219
+ items: z.array(z.object({
220
+ productId: z.string(),
221
+ quantity: z.number(),
222
+ })),
223
+ createdAt: z.string().datetime(), // ✅ Zod 4: no params
224
+ })
225
+
226
+ export const OrdersCreatedContract = createContract({
227
+ type: 'orders.created',
228
+ schema: OrdersCreatedSchema,
229
+ })
230
+
231
+ export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
232
+ ```
233
+
234
+ **Zod 4 Quick Reference:**
235
+ - ✅ `z.string().email()` - no params
236
+ - ✅ `z.string().url()` - no params
237
+ - ✅ `z.string().uuid()` - no params
238
+ - ✅ `z.string().datetime()` - no params
239
+ - ❌ `z.string().email('Invalid')` - DEPRECATED
240
+
11
241
  ### Naming Convention (CRITICAL!)
12
242
 
13
243
  **Event Type → Contract Name → File Name:**
@@ -17,13 +247,81 @@ These rules define how AI-generated scaffolded services must be structured.
17
247
  | `orders.created` | `OrdersCreatedContract` | `orders-created.ts` |
18
248
  | `users.registered` | `UsersRegisteredContract` | `users-registered.ts` |
19
249
  | `payments.completed` | `PaymentsCompletedContract` | `payments-completed.ts` |
250
+ | `customers.updated` | `CustomersUpdatedContract` | `customers-updated.ts` |
20
251
 
21
- **Rule:** Always use **plural namespace** (`orders.`, `users.`, `payments.`) to match JetStream stream subjects (`orders.>`, `users.>`, etc.)
252
+ **⚠️ ALWAYS use PLURAL namespace:**
253
+ - ✅ `orders.created`, `customers.updated`, `payments.completed`
254
+ - ❌ `order.created`, `customer.updated`, `payment.completed`
22
255
 
23
- ### 3-Step Workflow
256
+ This ensures JetStream stream names match: `ORDERS`, `CUSTOMERS`, `PAYMENTS`
24
257
 
25
- **STEP 1: CREATE Contract** (`packages/contracts/src/events/<event-name>.ts`):
26
- ```ts
258
+ ---
259
+
260
+ ## 🚨 CRITICAL: Commands Block (REQUIRED - MUST BE FIRST IN YOUR RESPONSE)
261
+
262
+ Your response **MUST** start with a commands block that scaffolds the service structure:
263
+
264
+ ```commands
265
+ pf new hono-micro <service-path> -y
266
+ ```
267
+
268
+ **Example:** If user asks for "orders service", generate:
269
+ ```commands
270
+ pf new hono-micro services/orders -y
271
+ ```
272
+
273
+ This command creates: `package.json`, `tsconfig.json`, `Dockerfile`, and basic structure.
274
+ **Without this command, the service cannot be built or run!**
275
+
276
+ - Services MUST be in `services/` directory
277
+ - User says "orders" → Generate path: `services/orders`
278
+ - Always include `-y` flag to skip prompts
279
+
280
+ ---
281
+
282
+ ## 🚨 CRITICAL: Post-Generation Commands (REQUIRED for Event Consumers)
283
+
284
+ **For Event Consumer services, you MUST include a `post-commands` block with ALL events the service listens to.**
285
+
286
+ Your response MUST include this block right after the files:
287
+
288
+ ```post-commands
289
+ pf event add <event.type> --service <service-path>
290
+ ```
291
+
292
+ **Example:** Service that listens to `orders.created` AND `customers.updated`:
293
+ ```post-commands
294
+ pf event add orders.created --service services/notifications
295
+ pf event add customers.updated --service services/notifications
296
+ ```
297
+
298
+ **What `pf event add` creates:**
299
+ - `src/events/<event>.event.ts` - Handler file that imports your contract
300
+ - `packages/contracts/src/events/<event>.mock.json` - Test mock data
301
+ - Adds export to `packages/contracts/src/index.ts`
302
+
303
+ **⚠️ IMPORTANT:** `pf event add` skips contract creation if it already exists - so YOUR contract with the correct schema is preserved!
304
+
305
+ ---
306
+
307
+ ## 🚨 CRITICAL: Response Structure for Event Consumers
308
+
309
+ Your response MUST follow this exact structure:
310
+
311
+ 1. **`commands` block** (scaffold service)
312
+ 2. **Contract files** (`packages/contracts/src/events/<event>.ts` with correct schema)
313
+ 3. **Service files** (src/index.ts, use-cases, tests, README)
314
+ 4. **`post-commands` block** (wire event handlers via CLI)
315
+ 5. **`dependencies` block** (if needed)
316
+
317
+ **Example complete response structure:**
318
+
319
+ \`\`\`commands
320
+ pf new hono-micro services/my-notifications -y
321
+ \`\`\`
322
+
323
+ #### \`packages/contracts/src/events/orders-created.ts\`
324
+ \`\`\`typescript
27
325
  import { createContract } from '@crossdelta/cloudevents'
28
326
  import { z } from 'zod'
29
327
 
@@ -34,119 +332,248 @@ export const OrdersCreatedSchema = z.object({
34
332
  })
35
333
 
36
334
  export const OrdersCreatedContract = createContract({
37
- type: 'orders.created', // plural namespace!
335
+ type: 'orders.created',
38
336
  schema: OrdersCreatedSchema,
39
337
  })
40
338
 
41
339
  export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
340
+ \`\`\`
341
+
342
+ #### \`src/index.ts\`
343
+ \`\`\`typescript
344
+ // Entry point code...
345
+ \`\`\`
346
+
347
+ #### \`src/use-cases/send-notification.use-case.ts\`
348
+ \`\`\`typescript
349
+ // Use-case code using OrdersCreatedData type...
350
+ \`\`\`
351
+
352
+ \`\`\`post-commands
353
+ pf event add orders.created --service services/my-notifications
354
+ pf event add customer.updated --service services/my-notifications
355
+ \`\`\`
356
+
357
+ \`\`\`dependencies
358
+ @pusher/push-notifications-server
359
+ \`\`\`
360
+
361
+ ---
362
+
363
+ ## Dependencies Block
364
+
365
+ Only include packages **not already in the workspace root**:
366
+
367
+ ```dependencies
368
+ @pusher/push-notifications-server
369
+ drizzle-orm
42
370
  ```
43
371
 
44
- **STEP 2: Create Event Handler** (`src/events/orders-created.event.ts`):
45
- ```ts
46
- import { handleEvent } from '@crossdelta/cloudevents'
47
- import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
48
- import { processOrder } from '../use-cases/process-order.use-case'
372
+ **Note:** `{{scope}}/contracts`, `@crossdelta/cloudevents`, and `@crossdelta/telemetry` are already available via workspace root – do NOT add them here.
49
373
 
50
- export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
51
- await processOrder(data)
374
+ ---
375
+
376
+ ## Output Format
377
+
378
+ **CRITICAL: Response MUST use Markdown format with specific block markers!**
379
+
380
+ ### Commands Block (REQUIRED - at start)
381
+
382
+ ```commands
383
+ pf new hono-micro services/my-service -y
384
+ ```
385
+
386
+ ### File Blocks
387
+
388
+ Each file uses a header with backtick-wrapped path, followed by a code block:
389
+
390
+ #### `packages/contracts/src/events/orders-created.ts`
391
+ ```typescript
392
+ import { createContract } from '@crossdelta/cloudevents'
393
+ import { z } from 'zod'
394
+
395
+ export const OrdersCreatedSchema = z.object({
396
+ orderId: z.string(),
397
+ total: z.number(),
52
398
  })
399
+
400
+ export const OrdersCreatedContract = createContract({
401
+ type: 'orders.created',
402
+ schema: OrdersCreatedSchema,
403
+ })
404
+
405
+ export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
53
406
  ```
54
407
 
55
- **STEP 3: Setup Consumer** (`src/index.ts`):
56
- ```ts
408
+ #### `src/index.ts`
409
+ ```typescript
57
410
  import '@crossdelta/telemetry'
58
411
 
59
- import { consumeJetStreamEvents, ensureJetStreamStream } from '@crossdelta/cloudevents'
60
412
  import { Hono } from 'hono'
413
+ // ... rest of code
414
+ ```
61
415
 
62
- const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 4003
63
- const app = new Hono()
416
+ ### Post-Commands Block (after files)
417
+
418
+ ```post-commands
419
+ pf event add orders.created --service services/my-service
420
+ ```
421
+
422
+ ### Dependencies Block (if needed)
423
+
424
+ ```dependencies
425
+ @pusher/push-notifications-server
426
+ lodash
427
+ ```
64
428
 
65
- app.get('/health', (c) => c.json({ status: 'ok' }))
429
+ ### Complete Example Response
66
430
 
67
- Bun.serve({ port, fetch: app.fetch })
431
+ ```commands
432
+ pf new hono-micro services/my-service -y
433
+ ```
434
+
435
+ #### `packages/contracts/src/events/orders-created.ts`
436
+ ```typescript
437
+ import { createContract } from '@crossdelta/cloudevents'
438
+ import { z } from 'zod'
68
439
 
69
- await ensureJetStreamStream({
70
- stream: 'ORDERS',
71
- subjects: ['orders.>'],
440
+ export const OrdersCreatedSchema = z.object({
441
+ orderId: z.string(),
442
+ total: z.number(),
72
443
  })
73
444
 
74
- consumeJetStreamEvents({
75
- stream: 'ORDERS',
76
- consumer: 'my-service',
77
- discover: './src/events/**/*.event.ts',
445
+ export const OrdersCreatedContract = createContract({
446
+ type: 'orders.created',
447
+ schema: OrdersCreatedSchema,
78
448
  })
449
+
450
+ export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
79
451
  ```
80
452
 
81
- ---
453
+ #### `src/index.ts`
454
+ ```typescript
455
+ import '@crossdelta/telemetry'
82
456
 
83
- ## ⚠️ Code Quality Guidelines
457
+ import { Hono } from 'hono'
458
+ import { consumeJetStreamStreams, ensureJetStreamStreams } from '@crossdelta/cloudevents'
84
459
 
85
- - **Verify package names** on npmjs.com before using
86
- - Use exact package names: `@crossdelta/cloudevents`, `@crossdelta/telemetry`
87
- - ❌ **DO NOT hallucinate** APIs or package names
460
+ const app = new Hono()
461
+ const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 8080
88
462
 
89
- ---
463
+ const start = async () => {
464
+ await ensureJetStreamStreams({
465
+ streams: [{ stream: 'ORDERS', subjects: ['orders.*'] }],
466
+ })
90
467
 
91
- ## Commands Block (REQUIRED - MUST BE FIRST)
468
+ consumeJetStreamStreams({
469
+ streams: ['ORDERS'],
470
+ consumer: 'my-service',
471
+ discover: './src/events/**/*.event.ts',
472
+ })
92
473
 
93
- ```commands
94
- pf new hono-micro services/my-service -y
474
+ app.get('/health', (c) => c.json({ status: 'ok' }))
475
+
476
+ return app.listen(port, () => {
477
+ console.log(`Server running on http://localhost:${port}`)
478
+ })
479
+ }
480
+
481
+ start().catch(console.error)
95
482
  ```
96
483
 
97
- - Services MUST be in `services/` directory
98
- - User says "orders" → Generate: `services/orders`
484
+ #### `src/events/orders-created.event.ts`
485
+ ```typescript
486
+ import { handleEvent } from '@crossdelta/cloudevents'
487
+ import { OrdersCreatedContract, type OrdersCreatedData } from '@my-scope/contracts'
488
+ import { processOrder } from '../use-cases/process-order.use-case'
99
489
 
100
- ---
490
+ export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
491
+ console.log('📦 Processing order:', data.orderId)
492
+ await processOrder(data)
493
+ })
494
+ ```
101
495
 
102
- ## Post-Generation Commands
496
+ #### `src/use-cases/process-order.use-case.ts`
497
+ ```typescript
498
+ import type { OrdersCreatedData } from '@my-scope/contracts'
103
499
 
104
- For Event Consumer services:
500
+ export const processOrder = async (data: OrdersCreatedData): Promise<void> => {
501
+ console.log('Order processed:', data.orderId)
502
+ // Add your business logic here
503
+ }
504
+ ```
105
505
 
106
- ```post-commands
107
- pf event add <event.type> --service services/my-service
506
+ #### `src/use-cases/process-order.test.ts`
507
+ ```typescript
508
+ import { describe, expect, it } from 'bun:test'
509
+ import { processOrder } from './process-order.use-case'
510
+
511
+ describe('processOrder', () => {
512
+ it('should process order', async () => {
513
+ const data = { orderId: '123', total: 100 }
514
+ await expect(processOrder(data)).resolves.toBeUndefined()
515
+ })
516
+ })
108
517
  ```
109
518
 
110
- **Note:** `pf event add` creates contract, mock, handler, and adds the export to `packages/contracts/src/index.ts`.
519
+ #### `README.md`
520
+ ```markdown
521
+ # My Service
111
522
 
112
- ---
523
+ Event consumer that listens to `orders.created`.
113
524
 
114
- ## Dependencies Block
525
+ ## Events
115
526
 
116
- Only include packages **not already in the workspace root**:
527
+ - `orders.created` processes order
528
+ ```
529
+
530
+ ```post-commands
531
+ pf event add orders.created --service services/my-service
532
+ ```
117
533
 
118
534
  ```dependencies
119
- zod
120
- @pusher/push-notifications-server
121
535
  ```
122
536
 
123
- **Note:** `{{scope}}/contracts`, `@crossdelta/cloudevents`, and `@crossdelta/telemetry` are already available via workspace root – do NOT add them here.
537
+ ### Format Rules
538
+
539
+ 1. **Commands block** MUST come first with `pf new` command
540
+ 2. **File headers** use `#### \`path/to/file.ts\`` format
541
+ 3. **Code blocks** use triple backticks with language (typescript, markdown, etc.)
542
+ 4. **Post-commands** block after all files
543
+ 5. **Dependencies** block at end (can be empty)
544
+ 6. **Paths**: Use relative paths for service files, full paths for contracts
124
545
 
125
546
  ---
126
547
 
127
548
  ## Required Files
128
549
 
129
- **Event Consumer:**
550
+ **Event Consumer (what YOU generate):**
130
551
  ```
131
552
  services/my-service/
132
553
  ├── src/
133
- │ ├── index.ts # NATS consumer setup
554
+ │ ├── index.ts # Entry point (YOU create this)
134
555
  │ ├── events/
135
- │ │ └── orders-created.event.ts # Event handler
556
+ │ │ └── orders-created.event.ts # Handler calling use-case (YOU create this)
136
557
  │ └── use-cases/
137
- │ ├── process-order.use-case.ts
138
- │ └── process-order.test.ts
139
- └── README.md
558
+ │ ├── process-order.use-case.ts # Business logic (YOU create this)
559
+ │ └── process-order.test.ts # Tests (YOU create this)
560
+ └── README.md # Documentation (YOU create this)
561
+
562
+ packages/contracts/src/events/
563
+ └── orders-created.ts # Contract with schema (YOU create this)
564
+ ```
140
565
 
566
+ **Created by `pf event add` (mock data only):**
567
+ ```
141
568
  packages/contracts/src/events/
142
- └── orders-created.ts # Contract (export added by CLI)
569
+ └── orders-created.mock.json # Created by CLI
143
570
  ```
144
571
 
145
572
  **Event Publisher:**
146
573
  ```
147
574
  services/my-service/
148
575
  ├── src/
149
- │ ├── index.ts # REST API + publish()
576
+ │ ├── [entry-point] # Framework-specific entry point
150
577
  │ └── use-cases/
151
578
  │ ├── create-order.use-case.ts
152
579
  │ └── create-order.test.ts
@@ -165,28 +592,13 @@ services/my-service/
165
592
 
166
593
  **Keywords:** "publishes", "creates", "manages", "REST API"
167
594
 
168
- ```ts
169
- import '@crossdelta/telemetry'
170
-
171
- import { publish } from '@crossdelta/cloudevents'
172
- import { Hono } from 'hono'
173
-
174
- const app = new Hono()
175
-
176
- app.post('/orders', async (c) => {
177
- const data = await c.req.json()
178
- await publish('orders.created', data, { source: 'my-service' })
179
- return c.json({ success: true })
180
- })
181
-
182
- Bun.serve({ port: 4001, fetch: app.fetch })
183
- ```
595
+ Services that expose HTTP endpoints and publish events to NATS.
184
596
 
185
597
  ### 📥 Event Consumer (NATS)
186
598
 
187
599
  **Keywords:** "consumes", "listens to", "reacts to", "handles events"
188
600
 
189
- See 3-Step Workflow above.
601
+ Services that listen to NATS events and process them. See 3-Step Workflow above.
190
602
 
191
603
  ### 🔄 Hybrid (Both)
192
604
 
@@ -222,10 +634,11 @@ export const processOrder = async (
222
634
  ## Testing Rules
223
635
 
224
636
  - Test ONLY use-cases
225
- - Use `import { describe, expect, it } from 'bun:test'`
226
- - NO mocking (Bun doesn't support it)
637
+ - Use appropriate test framework for the runtime
227
638
  - Focus on error handling and validation
639
+ - No mocking - prefer simple direct tests
228
640
 
641
+ Example:
229
642
  ```ts
230
643
  import { describe, expect, it } from 'bun:test'
231
644
  import { processOrder } from './process-order.use-case'
@@ -241,12 +654,20 @@ describe('Process Order', () => {
241
654
 
242
655
  ---
243
656
 
657
+ ## Code Quality Guidelines
658
+
659
+ - ✅ **Verify package names** on npmjs.com before using
660
+ - ✅ Use exact package names: `@crossdelta/cloudevents`, `@crossdelta/telemetry`
661
+ - ❌ **DO NOT hallucinate** APIs or package names
662
+
663
+ ---
664
+
244
665
  ## Absolute Rules
245
666
 
246
667
  **DO NOT:**
247
668
  - Generate full rewrites
248
669
  - Use raw NATS clients
249
- - Put logic in handlers or index.ts
670
+ - Put business logic in handlers or entry points
250
671
  - Use `src/handlers/` (use `src/events/`)
251
672
  - Insert semicolons
252
673
  - Edit `packages/contracts/src/index.ts` directly (CLI handles exports)
@@ -254,6 +675,8 @@ describe('Process Order', () => {
254
675
  **DO:**
255
676
  - Follow naming convention (plural event types)
256
677
  - Create contracts in `packages/contracts/src/events/`
678
+ - Create event handlers in `src/events/` that call use-cases
257
679
  - Export schema separately from contract
680
+ - Keep handlers thin - delegate to use-cases
258
681
  - Keep code minimal and clean
259
682
  - Run `pf audit` after adding new dependencies to check for vulnerabilities