@crossdelta/platform-sdk 0.16.4 → 0.16.6

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.
@@ -15,7 +15,7 @@
15
15
  ```ts
16
16
  // ✅ CORRECT - sorted alphabetically, type imports first
17
17
  import type { DomainCreatedData } from '{workspaceScope}/contracts'
18
- import type { OrdersCreatedData } from '@scope/contracts'
18
+ import type { OrderCreatedData } from '@scope/contracts'
19
19
  import { handleEvent } from '@crossdelta/cloudevents'
20
20
  import PusherPushNotifications from '@pusher/push-notifications-server'
21
21
 
@@ -24,7 +24,7 @@ import PusherPushNotifications from '@pusher/push-notifications-server'
24
24
  import type { DomainCreatedData } from '{workspaceScope}/contracts'
25
25
 
26
26
  // ❌ WRONG - missing 'type' keyword
27
- import { OrdersCreatedData } from '@scope/contracts'
27
+ import { OrderCreatedData } from '@scope/contracts'
28
28
 
29
29
  // ❌ WRONG - unused imports
30
30
  import { handleEvent, publish } from '@crossdelta/cloudevents'
@@ -51,7 +51,7 @@ const apiKey = process.env.API_KEY! // no assertions
51
51
  |------|------------|---------|
52
52
  | Files | kebab-case | `send-notification.use-case.ts` |
53
53
  | Contracts | PascalCase | `OrdersCreatedContract` |
54
- | Types | PascalCase + Data | `OrdersCreatedData` |
54
+ | Types | PascalCase + Data | `OrderCreatedData` |
55
55
 
56
56
  ---
57
57
 
@@ -83,7 +83,7 @@ consumeJetStreams({
83
83
  ```
84
84
 
85
85
  **CRITICAL:** Stream names MUST be PLURAL:
86
- - ✅ `streams: ['ORDERS']` - for orders.created event
86
+ - ✅ `streams: ['ORDERS']` - for order.created event
87
87
  - ✅ `streams: ['DOMAINS']` - for domain.created event
88
88
  - ❌ `streams: ['ORDER']` - WRONG (singular)
89
89
  - ❌ `streams: ['DOMAIN']` - WRONG (singular)
@@ -104,7 +104,7 @@ app.get('/health', (c) => c.json({ status: 'ok' }))
104
104
 
105
105
  app.post('/orders', async (c) => {
106
106
  const data = await c.req.json()
107
- await publish('orders.created', data)
107
+ await publish('order.created', data)
108
108
  return c.json({ success: true })
109
109
  })
110
110
 
@@ -86,7 +86,7 @@ consumeJetStreams({
86
86
  ```
87
87
 
88
88
  **CRITICAL:** Stream names MUST be PLURAL:
89
- - ✅ `streams: ['ORDERS']` - for orders.created event
89
+ - ✅ `streams: ['ORDERS']` - for order.created event
90
90
  - ✅ `streams: ['DOMAINS']` - for domain.created event
91
91
  - ❌ `streams: ['ORDER']` - WRONG (singular)
92
92
  - ❌ `streams: ['DOMAIN']` - WRONG (singular)
@@ -108,7 +108,7 @@ app.get('/health', (c) => c.json({ status: 'ok' }))
108
108
 
109
109
  app.post('/orders', async (c) => {
110
110
  const data = await c.req.json()
111
- await publish('orders.created', data)
111
+ await publish('order.created', data)
112
112
  return c.json({ success: true })
113
113
  })
114
114
 
@@ -242,12 +242,12 @@ export const getService = <T>(serviceClass: Type<T>): T => {
242
242
 
243
243
  ```ts
244
244
  import { handleEvent } from '@crossdelta/cloudevents'
245
- import { OrdersCreatedContract, type OrdersCreatedData } from '{workspaceScope}/contracts'
245
+ import { OrdersCreatedContract, type OrderCreatedData } from '{workspaceScope}/contracts'
246
246
  import { getService } from '../app.context'
247
247
  import { OrdersService } from '../orders/orders.service'
248
248
 
249
- export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
250
- console.log(`[orders.created] Processing orderId=${data.orderId}`)
249
+ export default handleEvent(OrdersCreatedContract, async (data: OrderCreatedData) => {
250
+ console.log(`[order.created] Processing orderId=${data.orderId}`)
251
251
  const ordersService = getService(OrdersService)
252
252
  await ordersService.processOrder(data)
253
253
  })
@@ -55,7 +55,7 @@ const port = Number(process.env.MY_HONO_SERVICE_PORT) || 8080
55
55
  ```typescript
56
56
  // 1. Contract defines routing
57
57
  export const OrdersCreatedContract = createContract({
58
- type: 'orders.created',
58
+ type: 'order.created',
59
59
  channel: { stream: 'ORDERS' },
60
60
  schema: OrderSchema,
61
61
  })
@@ -90,7 +90,7 @@ cd my-platform
90
90
 
91
91
  #### Option A: AI Generation (via GitHub Copilot Chat)
92
92
  ```
93
- "Create an order processing service that consumes orders.created events"
93
+ "Create an order processing service that consumes order.created events"
94
94
  ```
95
95
 
96
96
  **What AI generates:**
@@ -106,7 +106,7 @@ pf new hono-micro services/order-processing -y
106
106
  ```
107
107
 
108
108
  ```post-commands
109
- pf event add orders.created --service services/order-processing
109
+ pf event add order.created --service services/order-processing
110
110
  ```
111
111
 
112
112
  **What `pf event add` does:**
@@ -119,10 +119,10 @@ pf event add orders.created --service services/order-processing
119
119
  **Step 1: Create contract with CLI**
120
120
  ```bash
121
121
  # Create contract with schema
122
- pf event add orders.created --fields "orderId:string,total:number,customerId:string"
122
+ pf event add order.created --fields "orderId:string,total:number,customerId:string"
123
123
 
124
124
  # Or with JSON schema
125
- pf event add orders.created --schema '{"orderId":"string","total":"number"}'
125
+ pf event add order.created --schema '{"orderId":"string","total":"number"}'
126
126
  ```
127
127
 
128
128
  **Step 2: Scaffold service**
@@ -132,7 +132,7 @@ pf new hono-micro services/order-processing
132
132
 
133
133
  **Step 3: Register event and create handler**
134
134
  ```bash
135
- pf event add orders.created --service services/order-processing
135
+ pf event add order.created --service services/order-processing
136
136
  ```
137
137
 
138
138
  **Step 4: Implement use-cases**
@@ -248,8 +248,8 @@ The exact command depends on the framework - see the framework-specific docs:
248
248
  **For Event Consumer services, include a `post-commands` block with ALL events:**
249
249
 
250
250
  ```post-commands
251
- pf event add orders.created --service services/my-service
252
- pf event add customers.updated --service services/my-service
251
+ pf event add order.created --service services/my-service
252
+ pf event add customer.updated --service services/my-service
253
253
  ```
254
254
 
255
255
  **What `pf event add` creates:**
@@ -321,7 +321,7 @@ export const OrdersCreatedHandler = handleEvent(...)
321
321
 
322
322
  ```ts
323
323
  export default handleEvent(OrdersCreatedContract, async (data) => {
324
- console.log(`[orders.created] Processing orderId=${data.orderId}`) // ✅ console.log
324
+ console.log(`[order.created] Processing orderId=${data.orderId}`) // ✅ console.log
325
325
  await processOrder(data)
326
326
  })
327
327
  ```
@@ -341,18 +341,18 @@ pf new hono-micro services/my-service -y
341
341
  import { createContract } from '@crossdelta/cloudevents'
342
342
  import { z } from 'zod'
343
343
 
344
- export const OrdersCreatedSchema = z.object({
344
+ export const OrderCreatedSchema = z.object({
345
345
  orderId: z.string(),
346
346
  total: z.number(),
347
347
  })
348
348
 
349
+ export type OrderCreatedData = z.infer<typeof OrderCreatedSchema>
350
+
349
351
  export const OrdersCreatedContract = createContract({
350
- type: 'orders.created',
352
+ type: 'order.created',
351
353
  channel: { stream: 'ORDERS' },
352
- schema: OrdersCreatedSchema,
354
+ schema: OrderCreatedSchema,
353
355
  })
354
-
355
- export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
356
356
  ```
357
357
 
358
358
  #### `packages/contracts/src/index.ts`
@@ -364,10 +364,10 @@ export * from './events/orders/created'
364
364
  #### `src/events/orders-created.handler.ts`
365
365
  ```ts
366
366
  import { handleEvent } from '@crossdelta/cloudevents'
367
- import { OrdersCreatedContract, type OrdersCreatedData } from '{workspaceScope}/contracts'
367
+ import { OrdersCreatedContract, type OrderCreatedData } from '{workspaceScope}/contracts'
368
368
  import { processOrder } from '../use-cases/process-order.use-case'
369
369
 
370
- export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
370
+ export default handleEvent(OrdersCreatedContract, async (data: OrderCreatedData) => {
371
371
  console.log('📦 Processing order:', data.orderId)
372
372
  await processOrder(data)
373
373
  })
@@ -375,15 +375,15 @@ export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData
375
375
 
376
376
  #### `src/use-cases/process-order.use-case.ts`
377
377
  ```ts
378
- import type { OrdersCreatedData } from '{workspaceScope}/contracts'
378
+ import type { OrderCreatedData } from '{workspaceScope}/contracts'
379
379
 
380
- export const processOrder = async (data: OrdersCreatedData): Promise<void> => {
380
+ export const processOrder = async (data: OrderCreatedData): Promise<void> => {
381
381
  console.log('Processing:', data.orderId)
382
382
  }
383
383
  ```
384
384
 
385
385
  ```post-commands
386
- pf event add orders.created --service services/my-service
386
+ pf event add order.created --service services/my-service
387
387
  ```
388
388
 
389
389
  ```dependencies
@@ -418,14 +418,24 @@ await notifyUser(data.userId) // ❌ WRONG - userId not in schema!
418
418
  ## Contract Schema (Zod 4 Syntax)
419
419
 
420
420
  ```ts
421
- export const OrdersCreatedSchema = z.object({
421
+ export const OrderCreatedSchema = z.object({
422
422
  orderId: z.string(),
423
423
  email: z.string().email(), // ✅ Zod 4: no params
424
424
  total: z.number(),
425
425
  createdAt: z.string().datetime(), // ✅ Zod 4: no params
426
426
  })
427
+
428
+ export type OrderCreatedData = z.infer<typeof OrderCreatedSchema>
429
+
430
+ export const OrdersCreatedContract = createContract({
431
+ type: 'order.created',
432
+ channel: { stream: 'ORDERS' },
433
+ schema: OrderCreatedSchema, // ← Singular schema
434
+ })
427
435
  ```
428
436
 
437
+ **Pattern:** Schema is singular (describes one object), Contract is plural (represents stream)
438
+
429
439
  **Zod 4 Quick Reference:**
430
440
  - ✅ `z.string().email()` - no params
431
441
  - ✅ `z.string().url()` - no params
@@ -439,14 +449,23 @@ export const OrdersCreatedSchema = z.object({
439
449
 
440
450
  | Component | Format | Example | Reason |
441
451
  |-----------|--------|---------|--------|
442
- | **Event Type** | **Singular** | `customer.created` | Describes a single event |
443
- | **Event Folder** | **PLURAL** | `customers/` | Domain grouping (matches stream) |
444
- | **Stream** | **PLURAL** | `CUSTOMERS` | Collection of events for a domain |
445
- | **Mock Files** | **PLURAL folder** | `customers/created.mock.json` | Must match event folder |
452
+ | **Event Type** | **Singular** | `customer.created` | A single fact |
453
+ | **Schema** | **Singular** | `CustomerCreatedSchema` | One event object |
454
+ | **Type** | **Singular** | `CustomerCreatedData` | One event object |
455
+ | **Contract** | **PLURAL** | `CustomersCreatedContract` | Category of events |
456
+ | **Event Folder** | **PLURAL** | `customers/` | Domain (collection) |
457
+ | **Stream** | **PLURAL** | `CUSTOMERS` | Collection of events |
458
+ | **Mock Files** | **PLURAL folder** | `customers/created.mock.json` | Matches event folder |
459
+
460
+ **The final rule:**
461
+ ```
462
+ Schema / Data → singular (one event instance)
463
+ Event Type → singular (one fact)
464
+ Contract → plural (category of events)
465
+ Stream / Domain → plural (collection)
466
+ ```
446
467
 
447
- **Merksatz:**
448
- - **Singular** describes an event
449
- - **Plural** describes a domain
468
+ **Key principle:** Contracts represent event collections, Schemas represent event instances.
450
469
 
451
470
  ### File Structure Example
452
471
 
@@ -468,20 +487,25 @@ packages/contracts/src/events/
468
487
 
469
488
  ### Contract Examples
470
489
 
471
- | Event Type | Contract | Folder | Stream |
472
- |------------|----------|--------|--------|
473
- | `customer.created` | `CustomersCreatedContract` | `customers/` | `CUSTOMERS` |
474
- | `order.created` | `OrdersCreatedContract` | `orders/` | `ORDERS` |
475
- | `domain.created` | `DomainsCreatedContract` | `domains/` | `DOMAINS` |
490
+ | Event Type | Schema | Type | Contract | Folder | Stream |
491
+ |------------|--------|------|----------|--------|--------|
492
+ | `customer.created` | `CustomerCreatedSchema` | `CustomerCreatedData` | `CustomersCreatedContract` | `customers/` | `CUSTOMERS` |
493
+ | `order.created` | `OrderCreatedSchema` | `OrderCreatedData` | `OrdersCreatedContract` | `orders/` | `ORDERS` |
494
+ | `domain.created` | `DomainCreatedSchema` | `DomainCreatedData` | `DomainsCreatedContract` | `domains/` | `DOMAINS` |
476
495
 
477
- **Naming Pattern:** `{PluralDomain}{Action}Contract` - matches folder and stream names
496
+ **Naming Patterns:**
497
+ - Schema/Type: `{SingularDomain}{Action}Schema/Data` - describes one object
498
+ - Contract: `{PluralDomain}{Action}Contract` - matches folder and stream names
478
499
 
479
500
  ```typescript
480
501
  // ✅ CORRECT
502
+ export const CustomerCreatedSchema = z.object({ ... })
503
+ export type CustomerCreatedData = z.infer<typeof CustomerCreatedSchema>
504
+
481
505
  export const CustomersCreatedContract = createContract({
482
506
  type: 'customer.created', // Singular event type
483
507
  channel: { stream: 'CUSTOMERS' }, // Plural stream (matches contract name prefix)
484
- schema: CustomersCreatedSchema,
508
+ schema: CustomerCreatedSchema, // Singular schema
485
509
  })
486
510
 
487
511
  // Folder: packages/contracts/src/events/customers/created.ts ✅ PLURAL
@@ -498,7 +522,7 @@ export const CustomerCreatedContract = createContract({
498
522
  export const CustomersCreatedContract = createContract({
499
523
  type: 'customer.created',
500
524
  channel: { stream: 'CUSTOMER' }, // ❌ Wrong! Should be CUSTOMERS (plural)
501
- schema: CustomersCreatedSchema,
525
+ schema: CustomerCreatedSchema,
502
526
  })
503
527
  ```
504
528
 
@@ -514,7 +538,7 @@ consumeJetStreams({
514
538
  discover: './src/events/**/*.handler.ts',
515
539
  })
516
540
 
517
- // ✅ CORRECT - orders.created event → ORDERS stream
541
+ // ✅ CORRECT - order.created event → ORDERS stream
518
542
  consumeJetStreams({
519
543
  streams: ['ORDERS'], // ✅ Plural!
520
544
  consumer: 'my-service',
@@ -7,7 +7,7 @@
7
7
  "pulumi": "pulumi"
8
8
  },
9
9
  "dependencies": {
10
- "@crossdelta/cloudevents": "^0.5.6",
10
+ "@crossdelta/cloudevents": "^0.5.7",
11
11
  "@crossdelta/infrastructure": "^0.5.3",
12
12
  "{{scope}}/contracts": "workspace:*",
13
13
  "@pulumi/digitalocean": "^4.55.0",
@@ -12,7 +12,7 @@ This package contains **shared event definitions** (contracts) for events that a
12
12
  src/
13
13
  ├── events/ # Event contracts grouped by domain
14
14
  │ ├── orders/ # Orders domain
15
- │ │ ├── created.ts # orders.created event
15
+ │ │ ├── created.ts # order.created event
16
16
  │ │ ├── updated.ts # orders.updated event
17
17
  │ │ └── index.ts # Re-exports
18
18
  │ ├── customers/ # Customers domain
@@ -35,7 +35,7 @@ src/
35
35
  ### Using the CLI (Recommended)
36
36
 
37
37
  ```bash
38
- pf event add products.created --fields "productId:string,name:string,price:number"
38
+ pf event add product.created --fields "productId:string,name:string,price:number"
39
39
  ```
40
40
 
41
41
  This will create `src/events/products/created.ts` with proper domain structure.
@@ -50,9 +50,9 @@ See [Adding Events](#manual-creation) section below.
50
50
 
51
51
  ```ts
52
52
  import { handleEvent } from '@crossdelta/cloudevents'
53
- import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
53
+ import { OrdersCreatedContract, type OrderCreatedData } from '{{scope}}/contracts'
54
54
 
55
- export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
55
+ export default handleEvent(OrdersCreatedContract, async (data: OrderCreatedData) => {
56
56
  // data is fully typed from contract
57
57
  console.log(data.orderId, data.customerId)
58
58
  })
@@ -76,9 +76,9 @@ await publish(OrdersCreatedContract, {
76
76
  ### In Use-Cases
77
77
 
78
78
  ```ts
79
- import type { OrdersCreatedData } from '{{scope}}/contracts'
79
+ import type { OrderCreatedData } from '{{scope}}/contracts'
80
80
 
81
- export const processOrder = async (data: OrdersCreatedData) => {
81
+ export const processOrder = async (data: OrderCreatedData) => {
82
82
  // Use typed event data
83
83
  }
84
84
  ```
@@ -89,10 +89,10 @@ Contracts are **auto-generated** when you create event handlers:
89
89
 
90
90
  ```bash
91
91
  # 1. Create service with event handler
92
- pf new hono-micro notifications --ai -d "Sends emails on orders.created events"
92
+ pf new hono-micro notifications --ai -d "Sends emails on order.created events"
93
93
 
94
94
  # 2. Add event (creates contract, mock, handler)
95
- pf event add orders.created --service services/notifications
95
+ pf event add order.created --service services/notifications
96
96
  ```
97
97
 
98
98
  This creates:
@@ -107,7 +107,7 @@ This creates:
107
107
  import { createContract } from '@crossdelta/cloudevents'
108
108
  import { z } from 'zod'
109
109
 
110
- export const OrdersCreatedSchema = z.object({
110
+ export const OrderCreatedSchema = z.object({
111
111
  orderId: z.string(),
112
112
  customerId: z.string(),
113
113
  total: z.number(),
@@ -119,17 +119,17 @@ export const OrdersCreatedSchema = z.object({
119
119
  })
120
120
 
121
121
  export const OrdersCreatedContract = createContract({
122
- type: 'orders.created',
122
+ type: 'order.created',
123
123
  channel: { stream: 'ORDERS' }, // Stream routing metadata
124
- schema: OrdersCreatedSchema,
124
+ schema: OrderCreatedSchema,
125
125
  })
126
126
 
127
- export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
127
+ export type OrderCreatedData = z.infer<typeof OrdersCreatedContract.schema>
128
128
  ```
129
129
 
130
130
  **Channel Metadata:**
131
131
  - `stream` - NATS JetStream stream name (e.g., `ORDERS`)
132
- - `subject` - Optional, defaults to event type (e.g., `orders.created`)
132
+ - `subject` - Optional, defaults to event type (e.g., `order.created`)
133
133
 
134
134
  **Stream Materialization:**
135
135
  1. **Development**: `pf dev` scans contracts and auto-creates ephemeral streams from channel metadata
@@ -144,10 +144,10 @@ See [`infra/streams/README.md`](../../infra/streams/README.md) for details.
144
144
  pf event list
145
145
 
146
146
  # Publish mock event
147
- pf event publish orders.created
147
+ pf event publish order.created
148
148
 
149
149
  # Publish with custom data
150
- pf event publish orders.created --data '{"orderId":"test-123"}'
150
+ pf event publish order.created --data '{"orderId":"test-123"}'
151
151
  ```
152
152
 
153
153
  ## Guidelines
@@ -161,6 +161,6 @@ pf event publish orders.created --data '{"orderId":"test-123"}'
161
161
 
162
162
  **Naming conventions:**
163
163
  - Contracts: `OrdersCreatedContract` (plural namespace)
164
- - Types: `OrdersCreatedData`
164
+ - Types: `OrderCreatedData`
165
165
  - Files: `orders-created.ts`
166
- - Event types: `orders.created` (plural namespace, dot notation)
166
+ - Event types: `order.created` (plural namespace, dot notation)
@@ -11,7 +11,7 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@crossdelta/cloudevents": "^0.5.6",
14
+ "@crossdelta/cloudevents": "^0.5.7",
15
15
  "@crossdelta/infrastructure": "^0.5.3",
16
16
  "zod": "^4.0.0"
17
17
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/platform-sdk",
3
- "version": "0.16.4",
3
+ "version": "0.16.6",
4
4
  "description": "Platform toolkit for event-driven microservices — keeping code and infrastructure in lockstep.",
5
5
  "keywords": [
6
6
  "cli",
@@ -93,29 +93,6 @@
93
93
  "test:watch": "bun test --watch"
94
94
  },
95
95
  "schematics": "./dist/schematics/collection.json",
96
- "esbuild": {
97
- "external": [
98
- "@crossdelta/infrastructure",
99
- "@faker-js/faker",
100
- "@inquirer/prompts",
101
- "ai",
102
- "@ai-sdk/openai",
103
- "@ai-sdk/anthropic",
104
- "handlebars",
105
- "listr2",
106
- "enquirer",
107
- "execa",
108
- "globby",
109
- "fs-extra",
110
- "chalk",
111
- "ora",
112
- "jiti",
113
- "zod",
114
- "ts-morph",
115
- "commander",
116
- "chokidar"
117
- ]
118
- },
119
96
  "dependencies": {
120
97
  "@ai-sdk/anthropic": "^2.0.53",
121
98
  "@ai-sdk/openai": "^2.0.79",