@crossdelta/platform-sdk 0.9.3 → 0.10.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.
- package/bin/cli.js +42434 -262
- package/bin/docs/generators/hono-bun.md +101 -0
- package/bin/docs/generators/hono-node.md +116 -0
- package/bin/docs/generators/nest.md +265 -0
- package/bin/docs/generators/nest.md.new +351 -0
- package/bin/docs/generators/service.md +501 -78
- package/bin/templates/hono-microservice/biome.json.hbs +3 -0
- package/bin/templates/hono-microservice/tsconfig.json.hbs +1 -0
- package/bin/templates/nest-microservice/biome.json.hbs +10 -0
- package/bin/templates/nest-microservice/src/app.context.ts.hbs +17 -0
- package/bin/templates/nest-microservice/src/events/events.module.ts.hbs +8 -0
- package/bin/templates/nest-microservice/src/events/events.service.ts.hbs +25 -0
- package/bin/templates/nest-microservice/src/main.ts.hbs +9 -0
- package/bin/templates/workspace/.github/copilot-instructions.md.hbs +1 -0
- package/bin/templates/workspace/biome.json.hbs +1 -1
- package/bin/templates/workspace/package.json.hbs +2 -6
- package/bin/templates/workspace/packages/contracts/package.json.hbs +3 -1
- package/bin/templates/workspace/packages/contracts/tsconfig.json.hbs +0 -1
- package/package.json +24 -3
- package/schemas/service-types.schema.json +33 -0
- package/bin/templates/hono-microservice/src/index.ts.hbs +0 -18
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
# Service Generator Instructions
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
252
|
+
**⚠️ ALWAYS use PLURAL namespace:**
|
|
253
|
+
- ✅ `orders.created`, `customers.updated`, `payments.completed`
|
|
254
|
+
- ❌ `order.created`, `customer.updated`, `payment.completed`
|
|
22
255
|
|
|
23
|
-
|
|
256
|
+
This ensures JetStream stream names match: `ORDERS`, `CUSTOMERS`, `PAYMENTS`
|
|
24
257
|
|
|
25
|
-
|
|
26
|
-
|
|
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',
|
|
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
|
-
**
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
56
|
-
```
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
429
|
+
### Complete Example Response
|
|
66
430
|
|
|
67
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
440
|
+
export const OrdersCreatedSchema = z.object({
|
|
441
|
+
orderId: z.string(),
|
|
442
|
+
total: z.number(),
|
|
72
443
|
})
|
|
73
444
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
457
|
+
import { Hono } from 'hono'
|
|
458
|
+
import { consumeJetStreamStreams, ensureJetStreamStreams } from '@crossdelta/cloudevents'
|
|
84
459
|
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
468
|
+
consumeJetStreamStreams({
|
|
469
|
+
streams: ['ORDERS'],
|
|
470
|
+
consumer: 'my-service',
|
|
471
|
+
discover: './src/events/**/*.event.ts',
|
|
472
|
+
})
|
|
92
473
|
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
496
|
+
#### `src/use-cases/process-order.use-case.ts`
|
|
497
|
+
```typescript
|
|
498
|
+
import type { OrdersCreatedData } from '@my-scope/contracts'
|
|
103
499
|
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
519
|
+
#### `README.md`
|
|
520
|
+
```markdown
|
|
521
|
+
# My Service
|
|
111
522
|
|
|
112
|
-
|
|
523
|
+
Event consumer that listens to `orders.created`.
|
|
113
524
|
|
|
114
|
-
##
|
|
525
|
+
## Events
|
|
115
526
|
|
|
116
|
-
|
|
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
|
-
|
|
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
|
|
554
|
+
│ ├── index.ts # Entry point (YOU create this)
|
|
134
555
|
│ ├── events/
|
|
135
|
-
│ │ └── orders-created.event.ts
|
|
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.
|
|
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
|
-
│ ├──
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|