@crossdelta/platform-sdk 0.16.2 → 0.16.4

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.
@@ -14,14 +14,14 @@
14
14
 
15
15
  ```ts
16
16
  // ✅ CORRECT - sorted alphabetically, type imports first
17
- import type { DomainCreatedData } from '@my-platform/contracts'
17
+ import type { DomainCreatedData } from '{workspaceScope}/contracts'
18
18
  import type { OrdersCreatedData } from '@scope/contracts'
19
19
  import { handleEvent } from '@crossdelta/cloudevents'
20
20
  import PusherPushNotifications from '@pusher/push-notifications-server'
21
21
 
22
22
  // ❌ WRONG - unsorted
23
23
  import PusherPushNotifications from '@pusher/push-notifications-server'
24
- import type { DomainCreatedData } from '@my-platform/contracts'
24
+ import type { DomainCreatedData } from '{workspaceScope}/contracts'
25
25
 
26
26
  // ❌ WRONG - missing 'type' keyword
27
27
  import { OrdersCreatedData } from '@scope/contracts'
@@ -242,7 +242,7 @@ 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 '{{scope}}/contracts'
245
+ import { OrdersCreatedContract, type OrdersCreatedData } from '{workspaceScope}/contracts'
246
246
  import { getService } from '../app.context'
247
247
  import { OrdersService } from '../orders/orders.service'
248
248
 
@@ -299,7 +299,7 @@ export class AppController {
299
299
 
300
300
  ```ts
301
301
  // src/notifications/send-notification.ts (pure function)
302
- import type { DomainCreatedData } from '{{scope}}/contracts'
302
+ import type { DomainCreatedData } from '{workspaceScope}/contracts'
303
303
 
304
304
  export const buildNotificationPayload = (data: DomainCreatedData) => ({
305
305
  title: 'Domain Created',
@@ -47,8 +47,33 @@ const port = Number(process.env.MY_HONO_SERVICE_PORT) || 8080
47
47
 
48
48
  > **Services NEVER create streams!**
49
49
  >
50
- > In development, `pf dev` ensures ephemeral streams derived from contracts.
51
- > In production, streams are materialized explicitly via Pulumi.
50
+ > **Dev:** `pf dev` auto-creates ephemeral streams from contracts (memory, 1h retention)
51
+ > **Prod:** Streams materialized via Pulumi with retention policies
52
+
53
+ ### Architecture Flow
54
+
55
+ ```typescript
56
+ // 1. Contract defines routing
57
+ export const OrdersCreatedContract = createContract({
58
+ type: 'orders.created',
59
+ channel: { stream: 'ORDERS' },
60
+ schema: OrderSchema,
61
+ })
62
+
63
+ // 2. Services consume (never create!)
64
+ consumeJetStreams({
65
+ streams: ['ORDERS'],
66
+ consumer: 'my-service',
67
+ discover: './src/events/**/*.handler.ts',
68
+ })
69
+
70
+ // 3. Infra materializes (infra/streams/)
71
+ import { collectStreamDefinitions } from '@crossdelta/infrastructure'
72
+ const streams = collectStreamDefinitions(contracts)
73
+ deployStreams(provider, namespace, streams)
74
+ ```
75
+
76
+ **Key Principle:** Contracts define **what** (routing), infrastructure defines **how** (retention).
52
77
 
53
78
  ---
54
79
 
@@ -160,52 +185,6 @@ pulumi up
160
185
 
161
186
  ---
162
187
 
163
- ## �🚨 CRITICAL: Stream Architecture
164
-
165
- > **Services NEVER create streams!**
166
- > **Dev:** Streams are auto-created (ephemeral, memory)
167
- > **Prod:** Streams are materialized via Pulumi from contracts
168
-
169
- ### Contracts → Streams → Infra Flow
170
-
171
- ```typescript
172
- // 1. Contract defines conceptually (packages/contracts)
173
- export const OrdersCreatedContract = createContract({
174
- type: 'orders.created',
175
- channel: { stream: 'ORDERS' }, // Routing metadata
176
- schema: OrderSchema,
177
- })
178
-
179
- // 2. Services consume (no stream creation!)
180
- consumeJetStreams({
181
- streams: ['ORDERS'],
182
- consumer: 'my-service',
183
- discover: './src/events/**/*.handler.ts',
184
- })
185
-
186
- // 3. Infra materializes (infra/streams/)
187
- import { collectStreamDefinitions } from '@crossdelta/infrastructure'
188
- import * as contracts from '@workspace/contracts'
189
-
190
- // Generic logic in library, workspace data injected
191
- const streams = collectStreamDefinitions(contracts)
192
- deployStreams(provider, namespace, streams)
193
- ```
194
-
195
- **Architecture layers:**
196
- - **@crossdelta/infrastructure:** Generic stream collection logic
197
- - **Contracts:** Define event semantics + channel routing (workspace data)
198
- - **`pf dev`:** Ensures ephemeral streams (transient, memory-only)
199
- - **Platform-Infra:** Materializes streams via Pulumi (persistent, retention)
200
- - **Services:** Consume streams (never create!)
201
-
202
- **Separation of concerns:**
203
- - **Libraries contain logic** (generic, reusable)
204
- - **Workspaces contain data** (contracts, policies)
205
- - **No workspace imports inside libraries**
206
-
207
- ---
208
-
209
188
  ## 🚨 CRITICAL: @crossdelta/cloudevents API
210
189
 
211
190
  **ONLY these exports exist:**
@@ -285,13 +264,30 @@ pf event add customers.updated --service services/my-service
285
264
 
286
265
  **FOR EVENT CONSUMER SERVICES (services that "consume", "listen to", "react to" events):**
287
266
 
267
+ ### ⚠️ CRITICAL: Contract Exports (MUST DO FIRST!)
268
+
269
+ **Before generating ANY files, check if the contract export exists:**
270
+
271
+ ```typescript
272
+ // packages/contracts/src/index.ts
273
+ export * from './events/orders/created' // ← MUST exist!
274
+ ```
275
+
276
+ **If missing, ADD IT before generating handler!** Services import from `{workspaceScope}/contracts`, not individual files.
277
+
278
+ **Common Error if missing:**
279
+ ```
280
+ Export named 'OrderCreatedContract' not found in module 'packages/contracts/src/index.ts'
281
+ ```
282
+
288
283
  ### What YOU (the AI) generate:
289
284
 
290
285
  1. **`packages/contracts/src/events/<domain>/<event>.ts`** - Contract with correct schema fields in domain-grouped structure (e.g., `events/orders/created.ts`)
291
- 2. **`src/events/<event>.handler.ts`** - Event handler that calls the use-case
292
- 3. **`src/use-cases/*.use-case.ts`** - Business logic (called by handlers)
293
- 4. **`src/use-cases/*.test.ts`** - Tests for use-cases
294
- 5. **`README.md`** - Documentation
286
+ 2. **`packages/contracts/src/index.ts`** - ⚠️ **ADD EXPORT** for the contract (CRITICAL!)
287
+ 3. **`src/events/<event>.handler.ts`** - Event handler that calls the use-case
288
+ 4. **`src/use-cases/*.use-case.ts`** - Business logic (called by handlers)
289
+ 5. **`src/use-cases/*.test.ts`** - Tests for use-cases
290
+ 6. **`README.md`** - Documentation
295
291
 
296
292
  **⚠️ DO NOT generate `src/index.ts` or `src/main.ts`** - These are created by `pf new` with correct port config!
297
293
 
@@ -359,10 +355,16 @@ export const OrdersCreatedContract = createContract({
359
355
  export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
360
356
  ```
361
357
 
358
+ #### `packages/contracts/src/index.ts`
359
+ ```ts
360
+ // Export all contracts (REQUIRED - handlers import from here!)
361
+ export * from './events/orders/created'
362
+ ```
363
+
362
364
  #### `src/events/orders-created.handler.ts`
363
365
  ```ts
364
366
  import { handleEvent } from '@crossdelta/cloudevents'
365
- import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
367
+ import { OrdersCreatedContract, type OrdersCreatedData } from '{workspaceScope}/contracts'
366
368
  import { processOrder } from '../use-cases/process-order.use-case'
367
369
 
368
370
  export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
@@ -373,7 +375,7 @@ export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData
373
375
 
374
376
  #### `src/use-cases/process-order.use-case.ts`
375
377
  ```ts
376
- import type { OrdersCreatedData } from '{{scope}}/contracts'
378
+ import type { OrdersCreatedData } from '{workspaceScope}/contracts'
377
379
 
378
380
  export const processOrder = async (data: OrdersCreatedData): Promise<void> => {
379
381
  console.log('Processing:', data.orderId)
@@ -466,36 +468,37 @@ packages/contracts/src/events/
466
468
 
467
469
  ### Contract Examples
468
470
 
469
- ### Contract Examples
470
-
471
471
  | Event Type | Contract | Folder | Stream |
472
472
  |------------|----------|--------|--------|
473
- | `customer.created` | `CustomerCreatedContract` | `customers/` | `CUSTOMERS` ✅ |
474
- | `order.created` | `OrderCreatedContract` | `orders/` | `ORDERS` ✅ |
475
- | `domain.created` | `DomainCreatedContract` | `domains/` | `DOMAINS` ✅ |
473
+ | `customer.created` | `CustomersCreatedContract` | `customers/` | `CUSTOMERS` ✅ |
474
+ | `order.created` | `OrdersCreatedContract` | `orders/` | `ORDERS` ✅ |
475
+ | `domain.created` | `DomainsCreatedContract` | `domains/` | `DOMAINS` ✅ |
476
+
477
+ **Naming Pattern:** `{PluralDomain}{Action}Contract` - matches folder and stream names
476
478
 
477
479
  ```typescript
478
480
  // ✅ CORRECT
479
- export const CustomerCreatedContract = createContract({
480
- type: 'customer.created', // Singular event
481
- channel: { stream: 'CUSTOMERS' }, // Plural stream
482
- schema: CustomerCreatedSchema,
481
+ export const CustomersCreatedContract = createContract({
482
+ type: 'customer.created', // Singular event type
483
+ channel: { stream: 'CUSTOMERS' }, // Plural stream (matches contract name prefix)
484
+ schema: CustomersCreatedSchema,
483
485
  })
484
486
 
485
487
  // Folder: packages/contracts/src/events/customers/created.ts ✅ PLURAL
486
488
  // Mock: packages/contracts/src/events/customers/created.mock.json ✅ PLURAL
487
489
 
488
- // ❌ WRONG - Singular stream
490
+ // ❌ WRONG - Singular domain in contract name
489
491
  export const CustomerCreatedContract = createContract({
490
492
  type: 'customer.created',
491
- channel: { stream: 'CUSTOMER' }, // Must be CUSTOMERS (plural!)
493
+ channel: { stream: 'CUSTOMERS' }, // Mismatch: plural stream, singular contract
492
494
  schema: CustomerCreatedSchema,
493
495
  })
494
496
 
495
- // ❌ WRONG - Mixed folders
496
- // Folder: packages/contracts/src/events/customers/created.ts ✅
497
- // Mock: packages/contracts/src/events/customer/created.mock.json ❌ Singular!
498
- schema: DomainCreatedSchema,
497
+ // ❌ WRONG - Singular stream
498
+ export const CustomersCreatedContract = createContract({
499
+ type: 'customer.created',
500
+ channel: { stream: 'CUSTOMER' }, // ❌ Wrong! Should be CUSTOMERS (plural)
501
+ schema: CustomersCreatedSchema,
499
502
  })
500
503
  ```
501
504
 
@@ -595,11 +598,12 @@ packages/contracts/src/events/
595
598
  - ❌ Use `src/handlers/` (use `src/events/`)
596
599
  - ❌ Create `src/types/` directory (use contracts)
597
600
  - ❌ Insert semicolons
598
- - ❌ Edit `packages/contracts/src/index.ts` (CLI handles exports)
601
+ - ❌ Create contracts without adding exports to `packages/contracts/src/index.ts`
599
602
  - ❌ Create `use-cases/` folder in NestJS (use Services instead)
600
603
 
601
604
  **DO:**
602
605
  - ✅ Contracts in `packages/contracts/src/events/`
606
+ - ✅ **ALWAYS add contract exports to `packages/contracts/src/index.ts`** (CRITICAL!)
603
607
  - ✅ Handlers in `src/events/*.handler.ts`
604
608
  - ✅ Hono: Use-cases in `src/use-cases/*.use-case.ts`
605
609
  - ✅ NestJS: Business logic in Services + pure helper functions
@@ -7,7 +7,7 @@
7
7
  "pulumi": "pulumi"
8
8
  },
9
9
  "dependencies": {
10
- "@crossdelta/cloudevents": "^0.5.4",
10
+ "@crossdelta/cloudevents": "^0.5.6",
11
11
  "@crossdelta/infrastructure": "^0.5.3",
12
12
  "{{scope}}/contracts": "workspace:*",
13
13
  "@pulumi/digitalocean": "^4.55.0",
@@ -11,7 +11,7 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@crossdelta/cloudevents": "^0.5.4",
14
+ "@crossdelta/cloudevents": "^0.5.6",
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.2",
3
+ "version": "0.16.4",
4
4
  "description": "Platform toolkit for event-driven microservices — keeping code and infrastructure in lockstep.",
5
5
  "keywords": [
6
6
  "cli",