@crossdelta/platform-sdk 0.10.1 → 0.11.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.
@@ -2,301 +2,65 @@
2
2
 
3
3
  **Generic guidelines for all service types. Framework-specific details are in separate files:**
4
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)
5
+ - `hono-node.md` - Hono with Node.js runtime
9
6
  - `nest.md` - NestJS framework
10
7
 
11
8
  ---
12
9
 
13
10
  ## 🚨 CRITICAL: @crossdelta/cloudevents API
14
11
 
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):**
12
+ **ONLY these exports exist:**
13
+ - `ensureJetStreams` / `consumeJetStreams` (preferred)
14
+ - `ensureJetStreamStream` / `consumeJetStreamEvents` (legacy)
15
+ - `handleEvent`, `publish`, `createContract`
16
+
31
17
  ```ts
32
- // Setup multiple streams at once (PREFERRED)
33
- await ensureJetStreamStreams({
34
- streams: [
35
- { stream: 'ORDERS', subjects: ['orders.*'] },
36
- { stream: 'CUSTOMERS', subjects: ['customers.*'] },
37
- ]
18
+ await ensureJetStreams({
19
+ streams: [{ stream: 'ORDERS', subjects: ['orders.*'] }]
38
20
  })
39
21
 
40
- // Consume from multiple streams (PREFERRED)
41
- consumeJetStreamStreams({
42
- streams: ['ORDERS', 'CUSTOMERS'],
22
+ consumeJetStreams({
23
+ streams: ['ORDERS'],
43
24
  consumer: 'my-service',
44
25
  discover: './src/events/**/*.event.ts',
45
26
  })
46
27
  ```
47
28
 
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
29
+ **DO NOT USE (these do NOT exist):** `stream`, `consumer`, `subscribe`, `name:`, `handler:`, `topic:`
52
30
 
53
31
  ---
54
32
 
55
33
  ## 🚨 CRITICAL: @crossdelta/telemetry Import
56
34
 
57
- **Telemetry is a side-effect import. Use EXACTLY:**
58
35
  ```ts
59
- import '@crossdelta/telemetry'
36
+ import '@crossdelta/telemetry' // side-effect, MUST be FIRST import
60
37
  ```
61
38
 
62
- **DO NOT USE:**
63
- - ❌ `@crossdelta/telemetry/register` - DOES NOT EXIST
64
- - ❌ `import { ... } from '@crossdelta/telemetry'` - No named exports needed
65
- - ❌ Any subpath imports
39
+ **DO NOT USE:** `@crossdelta/telemetry/register`, named exports
66
40
 
67
41
  ---
68
42
 
69
- ## 🚨 CRITICAL: Event Consumer Workflow
70
-
71
- **FOR EVENT CONSUMER SERVICES (services that "consume", "listen to", "react to" events):**
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`
43
+ ## 🚨 CRITICAL: Commands Block (REQUIRED - MUST BE FIRST)
202
44
 
203
- ### Contract Schema (YOU write this!)
45
+ Your response **MUST START** with a `commands` block that scaffolds the service.
46
+ The exact command depends on the framework - see the framework-specific docs:
47
+ - `hono-bun.md` / `hono-node.md` → `pf new hono-micro`
48
+ - `nest.md` → `pf new nest-micro`
204
49
 
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
-
241
- ### Naming Convention (CRITICAL!)
242
-
243
- **Event Type → Contract Name → File Name:**
244
-
245
- | Event Type | Contract Name | File Name |
246
- |------------|---------------|-----------|
247
- | `orders.created` | `OrdersCreatedContract` | `orders-created.ts` |
248
- | `users.registered` | `UsersRegisteredContract` | `users-registered.ts` |
249
- | `payments.completed` | `PaymentsCompletedContract` | `payments-completed.ts` |
250
- | `customers.updated` | `CustomersUpdatedContract` | `customers-updated.ts` |
251
-
252
- **⚠️ ALWAYS use PLURAL namespace:**
253
- - ✅ `orders.created`, `customers.updated`, `payments.completed`
254
- - ❌ `order.created`, `customer.updated`, `payment.completed`
255
-
256
- This ensures JetStream stream names match: `ORDERS`, `CUSTOMERS`, `PAYMENTS`
257
-
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
50
+ **Without this command, the service cannot be built!**
279
51
 
280
52
  ---
281
53
 
282
- ## 🚨 CRITICAL: Post-Generation Commands (REQUIRED for Event Consumers)
54
+ ## 🚨 CRITICAL: Post-Commands Block (REQUIRED for Event Consumers)
283
55
 
284
- **For Event Consumer services, you MUST include a `post-commands` block with ALL events the service listens to.**
56
+ **For Event Consumer services, include a `post-commands` block with ALL events:**
285
57
 
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
58
  ```post-commands
294
- pf event add orders.created --service services/notifications
295
- pf event add customers.updated --service services/notifications
59
+ pf event add orders.created --service services/my-service
60
+ pf event add customers.updated --service services/my-service
296
61
  ```
297
62
 
298
63
  **What `pf event add` creates:**
299
- - `src/events/<event>.event.ts` - Handler file that imports your contract
300
64
  - `packages/contracts/src/events/<event>.mock.json` - Test mock data
301
65
  - Adds export to `packages/contracts/src/index.ts`
302
66
 
@@ -304,136 +68,66 @@ pf event add customers.updated --service services/notifications
304
68
 
305
69
  ---
306
70
 
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
325
- import { createContract } from '@crossdelta/cloudevents'
326
- import { z } from 'zod'
327
-
328
- export const OrdersCreatedSchema = z.object({
329
- orderId: z.string(),
330
- customerId: z.string(),
331
- total: z.number(),
332
- })
333
-
334
- export const OrdersCreatedContract = createContract({
335
- type: 'orders.created',
336
- schema: OrdersCreatedSchema,
337
- })
338
-
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
- \`\`\`
71
+ ## 🚨 CRITICAL: Event Consumer Workflow
351
72
 
352
- \`\`\`post-commands
353
- pf event add orders.created --service services/my-notifications
354
- pf event add customer.updated --service services/my-notifications
355
- \`\`\`
73
+ **FOR EVENT CONSUMER SERVICES (services that "consume", "listen to", "react to" events):**
356
74
 
357
- \`\`\`dependencies
358
- @pusher/push-notifications-server
359
- \`\`\`
75
+ ### What YOU (the AI) generate:
360
76
 
361
- ---
77
+ 1. **`packages/contracts/src/events/<event>.ts`** - Contract with correct schema fields
78
+ 2. **`src/index.ts`** - Entry point with NATS stream/consumer setup
79
+ 3. **`src/events/<event>.event.ts`** - Event handler that calls the use-case
80
+ 4. **`src/use-cases/*.use-case.ts`** - Business logic (called by handlers)
81
+ 5. **`src/use-cases/*.test.ts`** - Tests for use-cases
82
+ 6. **`README.md`** - Documentation
362
83
 
363
- ## Dependencies Block
84
+ ### Handler Location (CRITICAL!)
364
85
 
365
- Only include packages **not already in the workspace root**:
86
+ **⚠️ MUST be in `src/events/*.event.ts` - NEVER in `handlers/` subdirectory**
366
87
 
367
- ```dependencies
368
- @pusher/push-notifications-server
369
- drizzle-orm
370
88
  ```
89
+ ✅ CORRECT:
90
+ services/my-service/src/events/
91
+ ├── orders-created.event.ts
92
+ └── customers-updated.event.ts
371
93
 
372
- **Note:** `{{scope}}/contracts`, `@crossdelta/cloudevents`, and `@crossdelta/telemetry` are already available via workspace root – do NOT add them here.
373
-
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
94
+ WRONG:
95
+ services/my-service/src/events/handlers/ # NEVER create!
384
96
  ```
385
97
 
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(),
398
- })
98
+ ### Handler Export Pattern
399
99
 
400
- export const OrdersCreatedContract = createContract({
401
- type: 'orders.created',
402
- schema: OrdersCreatedSchema,
403
- })
100
+ ```ts
101
+ // ✅ CORRECT: Default export
102
+ export default handleEvent(OrdersCreatedContract, async (data) => { ... })
404
103
 
405
- export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
104
+ // WRONG: Named export
105
+ export const OrdersCreatedHandler = handleEvent(...)
406
106
  ```
407
107
 
408
- #### `src/index.ts`
409
- ```typescript
410
- import '@crossdelta/telemetry'
411
-
412
- import { Hono } from 'hono'
413
- // ... rest of code
414
- ```
108
+ ### Handler Logging
415
109
 
416
- ### Post-Commands Block (after files)
110
+ **Handlers must be thin** - use `console.log` for logging, then delegate to use-case/service:
417
111
 
418
- ```post-commands
419
- pf event add orders.created --service services/my-service
112
+ ```ts
113
+ export default handleEvent(OrdersCreatedContract, async (data) => {
114
+ console.log(`[orders.created] Processing orderId=${data.orderId}`) // ✅ console.log
115
+ await processOrder(data)
116
+ })
420
117
  ```
421
118
 
422
- ### Dependencies Block (if needed)
119
+ **Services/use-cases** can use structured logging (NestJS: `new Logger(ServiceName.name)`).
423
120
 
424
- ```dependencies
425
- @pusher/push-notifications-server
426
- lodash
427
- ```
121
+ ---
428
122
 
429
- ### Complete Example Response
123
+ ## 🚨 Response Structure
430
124
 
431
125
  ```commands
432
126
  pf new hono-micro services/my-service -y
433
127
  ```
434
128
 
435
129
  #### `packages/contracts/src/events/orders-created.ts`
436
- ```typescript
130
+ ```ts
437
131
  import { createContract } from '@crossdelta/cloudevents'
438
132
  import { z } from 'zod'
439
133
 
@@ -450,41 +144,10 @@ export const OrdersCreatedContract = createContract({
450
144
  export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
451
145
  ```
452
146
 
453
- #### `src/index.ts`
454
- ```typescript
455
- import '@crossdelta/telemetry'
456
-
457
- import { Hono } from 'hono'
458
- import { consumeJetStreamStreams, ensureJetStreamStreams } from '@crossdelta/cloudevents'
459
-
460
- const app = new Hono()
461
- const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 8080
462
-
463
- const start = async () => {
464
- await ensureJetStreamStreams({
465
- streams: [{ stream: 'ORDERS', subjects: ['orders.*'] }],
466
- })
467
-
468
- consumeJetStreamStreams({
469
- streams: ['ORDERS'],
470
- consumer: 'my-service',
471
- discover: './src/events/**/*.event.ts',
472
- })
473
-
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)
482
- ```
483
-
484
147
  #### `src/events/orders-created.event.ts`
485
- ```typescript
148
+ ```ts
486
149
  import { handleEvent } from '@crossdelta/cloudevents'
487
- import { OrdersCreatedContract, type OrdersCreatedData } from '@my-scope/contracts'
150
+ import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
488
151
  import { processOrder } from '../use-cases/process-order.use-case'
489
152
 
490
153
  export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
@@ -494,189 +157,131 @@ export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData
494
157
  ```
495
158
 
496
159
  #### `src/use-cases/process-order.use-case.ts`
497
- ```typescript
498
- import type { OrdersCreatedData } from '@my-scope/contracts'
160
+ ```ts
161
+ import type { OrdersCreatedData } from '{{scope}}/contracts'
499
162
 
500
163
  export const processOrder = async (data: OrdersCreatedData): Promise<void> => {
501
- console.log('Order processed:', data.orderId)
502
- // Add your business logic here
164
+ console.log('Processing:', data.orderId)
503
165
  }
504
166
  ```
505
167
 
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'
168
+ ```post-commands
169
+ pf event add orders.created --service services/my-service
170
+ ```
510
171
 
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
- })
172
+ ```dependencies
173
+ @pusher/push-notifications-server
517
174
  ```
518
175
 
519
- #### `README.md`
520
- ```markdown
521
- # My Service
176
+ ---
522
177
 
523
- Event consumer that listens to `orders.created`.
178
+ ## 🚨 CRITICAL: Schema Fields Consistency
524
179
 
525
- ## Events
180
+ **ONLY use fields that exist in YOUR generated schema!**
526
181
 
527
- - `orders.created` → processes order
528
- ```
182
+ ```ts
183
+ // If you generate this schema:
184
+ export const DomainCreatedSchema = z.object({
185
+ domainId: z.string(),
186
+ name: z.string(),
187
+ ownerEmail: z.string().email(),
188
+ })
529
189
 
530
- ```post-commands
531
- pf event add orders.created --service services/my-service
190
+ // Then ONLY use these fields in service code:
191
+ await sendEmail(data.ownerEmail) // EXISTS in schema
192
+ await notifyUser(data.userId) // ❌ WRONG - userId not in schema!
532
193
  ```
533
194
 
534
- ```dependencies
535
- ```
195
+ **DO NOT hallucinate fields** like `userId`, `customerId`, etc. unless they are in YOUR schema.
536
196
 
537
- ### Format Rules
197
+ ---
198
+
199
+ ## Contract Schema (Zod 4 Syntax)
200
+
201
+ ```ts
202
+ export const OrdersCreatedSchema = z.object({
203
+ orderId: z.string(),
204
+ email: z.string().email(), // ✅ Zod 4: no params
205
+ total: z.number(),
206
+ createdAt: z.string().datetime(), // ✅ Zod 4: no params
207
+ })
208
+ ```
538
209
 
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
210
+ **Zod 4 Quick Reference:**
211
+ - `z.string().email()` - no params
212
+ - `z.string().url()` - no params
213
+ - `z.string().email('Invalid')` - DEPRECATED
545
214
 
546
215
  ---
547
216
 
548
- ## Required Files
217
+ ## Naming Convention
549
218
 
550
- **Event Consumer (what YOU generate):**
551
- ```
552
- services/my-service/
553
- ├── src/
554
- │ ├── index.ts # Entry point (YOU create this)
555
- │ ├── events/
556
- │ │ └── orders-created.event.ts # Handler calling use-case (YOU create this)
557
- │ └── use-cases/
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)
219
+ | Event Type | Contract | File |
220
+ |------------|----------|------|
221
+ | `orders.created` | `OrdersCreatedContract` | `orders-created.ts` |
561
222
 
562
- packages/contracts/src/events/
563
- └── orders-created.ts # Contract with schema (YOU create this)
564
- ```
223
+ **⚠️ ALWAYS use PLURAL namespace:** `orders.created` ✅, `order.created` ❌
565
224
 
566
- **Created by `pf event add` (mock data only):**
567
- ```
568
- packages/contracts/src/events/
569
- └── orders-created.mock.json # Created by CLI
570
- ```
225
+ ---
571
226
 
572
- **Event Publisher:**
573
- ```
574
- services/my-service/
575
- ├── src/
576
- │ ├── [entry-point] # Framework-specific entry point
577
- │ └── use-cases/
578
- │ ├── create-order.use-case.ts
579
- │ └── create-order.test.ts
580
- └── README.md
581
- ```
227
+ ## Block Explanations
582
228
 
583
- **IMPORTANT:** Do NOT create `src/types/` directories. All types come from contracts:
584
- - `OrdersCreatedData` → import from `@scope/contracts`
585
- - Custom request/response types define inline or in use-case files
229
+ | Block | Purpose |
230
+ |-------|---------|
231
+ | `commands` | Scaffolds service (REQUIRED FIRST) |
232
+ | `post-commands` | Runs after files created (e.g., `pf event add`) |
233
+ | `dependencies` | Extra npm packages (NOT `@crossdelta/*`) |
586
234
 
587
235
  ---
588
236
 
589
237
  ## Service Types
590
238
 
591
239
  ### 📤 Event Publisher (REST API)
592
-
593
240
  **Keywords:** "publishes", "creates", "manages", "REST API"
594
241
 
595
- Services that expose HTTP endpoints and publish events to NATS.
596
-
597
242
  ### 📥 Event Consumer (NATS)
598
-
599
243
  **Keywords:** "consumes", "listens to", "reacts to", "handles events"
600
244
 
601
- Services that listen to NATS events and process them. See 3-Step Workflow above.
602
245
 
603
246
  ### 🔄 Hybrid (Both)
604
-
605
247
  Combines REST endpoints + NATS consumer.
606
248
 
607
249
  ---
608
250
 
609
- ## Use-Case Rules
610
-
611
- - Live in `src/use-cases/*.use-case.ts`
612
- - Pure functions, no framework imports
613
- - Import types from `@scope/contracts`
614
- - Inject dependencies as parameters (Map, services, etc.)
615
- - NO manual validation (adapters handle that)
616
- - **Event handlers:** Always log the event type and a key identifier (e.g., `console.log('📦 Processing order:', data.orderId)`) at the start of the use-case for debugging visibility
251
+ ## Required Files (Hono)
617
252
 
618
- ```ts
619
- import type { OrdersCreatedData } from '{{scope}}/contracts'
620
-
621
- export const processOrder = async (
622
- data: OrdersCreatedData,
623
- orderStore: Map<string, any>
624
- ) => {
625
- console.log('📦 Processing order:', data.orderId)
626
-
627
- orderStore.set(data.orderId, { ...data, processedAt: new Date() })
628
- return { success: true }
629
- }
630
253
  ```
254
+ services/my-service/src/
255
+ ├── index.ts
256
+ ├── events/orders-created.event.ts
257
+ └── use-cases/
258
+ ├── process-order.use-case.ts
259
+ └── process-order.test.ts
631
260
 
632
- ---
633
-
634
- ## Testing Rules
635
-
636
- - Test ONLY use-cases
637
- - Use appropriate test framework for the runtime
638
- - Focus on error handling and validation
639
- - No mocking - prefer simple direct tests
640
-
641
- Example:
642
- ```ts
643
- import { describe, expect, it } from 'bun:test'
644
- import { processOrder } from './process-order.use-case'
645
-
646
- describe('Process Order', () => {
647
- it('should store order', async () => {
648
- const store = new Map()
649
- const result = await processOrder({ orderId: '123', customerId: 'c1', total: 100 }, store)
650
- expect(store.has('123')).toBe(true)
651
- })
652
- })
261
+ packages/contracts/src/events/
262
+ └── orders-created.ts
653
263
  ```
654
264
 
655
- ---
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
265
+ **Note:** For NestJS structure, see `nest.md` - NestJS uses Services instead of use-cases.
662
266
 
663
267
  ---
664
268
 
665
269
  ## Absolute Rules
666
270
 
667
271
  **DO NOT:**
668
- - Generate full rewrites
669
- - Use raw NATS clients
670
- - Put business logic in handlers or entry points
671
- - Use `src/handlers/` (use `src/events/`)
672
- - Insert semicolons
673
- - Edit `packages/contracts/src/index.ts` directly (CLI handles exports)
272
+ - Use raw NATS clients
273
+ - Put business logic in handlers or entry points
274
+ - Use `src/handlers/` (use `src/events/`)
275
+ - Create `src/types/` directory (use contracts)
276
+ - Insert semicolons
277
+ - Edit `packages/contracts/src/index.ts` (CLI handles exports)
278
+ - ❌ Create `use-cases/` folder in NestJS (use Services instead)
674
279
 
675
280
  **DO:**
676
- - Follow naming convention (plural event types)
677
- - Create contracts in `packages/contracts/src/events/`
678
- - Create event handlers in `src/events/` that call use-cases
679
- - Export schema separately from contract
680
- - Keep handlers thin - delegate to use-cases
681
- - Keep code minimal and clean
682
- - Run `pf audit` after adding new dependencies to check for vulnerabilities
281
+ - Contracts in `packages/contracts/src/events/`
282
+ - Handlers in `src/events/*.event.ts`
283
+ - Hono: Use-cases in `src/use-cases/*.use-case.ts`
284
+ - NestJS: Business logic in Services + pure helper functions
285
+ - Log event type and key identifier in handlers
286
+ - Keep handlers thin - delegate to use-cases/services
287
+ - Verify package names on npmjs.com before using