@crossdelta/platform-sdk 0.16.3 → 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.
- package/bin/cli.js +96 -94
- package/bin/docs/generators/code-style.md +2 -2
- package/bin/docs/generators/nest.md +2 -2
- package/bin/docs/generators/service.md +74 -70
- package/bin/templates/workspace/infra/package.json.hbs +1 -1
- package/bin/templates/workspace/packages/contracts/package.json.hbs +1 -1
- package/package.json +1 -1
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
// ✅ CORRECT - sorted alphabetically, type imports first
|
|
17
|
-
import type { DomainCreatedData } from '
|
|
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 '
|
|
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 '{
|
|
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 '{
|
|
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
|
-
>
|
|
51
|
-
>
|
|
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/
|
|
292
|
-
3. **`src/
|
|
293
|
-
4. **`src/use-cases/*.
|
|
294
|
-
5. **`
|
|
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 '{
|
|
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 '{
|
|
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` | `
|
|
474
|
-
| `order.created` | `
|
|
475
|
-
| `domain.created` | `
|
|
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
|
|
480
|
-
type: 'customer.created', // Singular event
|
|
481
|
-
channel: { stream: 'CUSTOMERS' }, // Plural stream
|
|
482
|
-
schema:
|
|
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
|
|
490
|
+
// ❌ WRONG - Singular domain in contract name
|
|
489
491
|
export const CustomerCreatedContract = createContract({
|
|
490
492
|
type: 'customer.created',
|
|
491
|
-
channel: { stream: '
|
|
493
|
+
channel: { stream: 'CUSTOMERS' }, // Mismatch: plural stream, singular contract
|
|
492
494
|
schema: CustomerCreatedSchema,
|
|
493
495
|
})
|
|
494
496
|
|
|
495
|
-
// ❌ WRONG -
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
- ❌
|
|
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
|