@crossdelta/platform-sdk 0.12.0 → 0.13.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.
@@ -7,26 +7,223 @@
7
7
 
8
8
  ---
9
9
 
10
+ ## 🚨 CRITICAL: Port Configuration
11
+
12
+ Services MUST read their port from environment variables using this pattern:
13
+
14
+ ```typescript
15
+ // Convert service name to ENV key format
16
+ // Example: my-hono-service → MY_HONO_SERVICE_PORT
17
+ const port = Number(process.env.MY_HONO_SERVICE_PORT) || 8080
18
+ ```
19
+
20
+ **Port ENV variable naming:**
21
+ - Take service name from package.json `name` field (without scope)
22
+ - Convert to SCREAMING_SNAKE_CASE
23
+ - Append `_PORT`
24
+
25
+ **Examples:**
26
+ - `@my-platform/orders` → `ORDERS_PORT`
27
+ - `@my-platform/my-nest-service` → `MY_NEST_SERVICE_PORT`
28
+ - `@my-platform/api-gateway` → `API_GATEWAY_PORT`
29
+
30
+ **⚠️ CRITICAL: DO NOT hardcode or modify the port!**
31
+ - The `pf new` command automatically assigns a unique port
32
+ - The port is written to `.env.local` (e.g., `MY_SERVICE_PORT=4001`)
33
+ - Service code MUST use the correct env variable name
34
+ - DO NOT change the port in generated files!
35
+
36
+ **When generating service code:**
37
+ 1. Determine the correct env variable name from service name
38
+ 2. Use ONLY that variable: `process.env.MY_SERVICE_PORT`
39
+ 3. DO NOT use generic `PORT` - it causes conflicts!
40
+ 4. Keep the `|| 8080` fallback for when `.env.local` is missing
41
+
42
+ ---
43
+
44
+ ## 🚨 CRITICAL: Stream Architecture
45
+
46
+ > **Services NEVER create streams!**
47
+ >
48
+ > In development, `pf dev` ensures ephemeral streams derived from contracts.
49
+ > In production, streams are materialized explicitly via Pulumi.
50
+
51
+ ---
52
+
53
+ ## Development Workflow
54
+
55
+ ### 1️⃣ **Initial Setup** (once per workspace)
56
+
57
+ ```bash
58
+ pf new workspace my-platform
59
+ cd my-platform
60
+ ```
61
+
62
+ ### 2️⃣ **Create Service** (AI or manual)
63
+
64
+ #### Option A: AI Generation (via GitHub Copilot Chat)
65
+ ```
66
+ "Create an order processing service that consumes orders.created events"
67
+ ```
68
+
69
+ **What AI generates:**
70
+ 1. **Contract** (`packages/contracts/src/events/orders/created.ts`) - With proper Zod schema in domain-grouped structure
71
+ 2. **Service** (`services/order-processing/`) - Scaffolded via `pf new hono-micro`
72
+ 3. **Event Handler** (`services/order-processing/src/events/orders-created.handler.ts`)
73
+ 4. **Use-Cases** (`services/order-processing/src/use-cases/`)
74
+ 5. **Tests** (`services/order-processing/src/use-cases/*.test.ts`)
75
+
76
+ **What AI includes in response:**
77
+ ```commands
78
+ pf new hono-micro services/order-processing -y
79
+ ```
80
+
81
+ ```post-commands
82
+ pf event add orders.created --service services/order-processing
83
+ ```
84
+
85
+ **What `pf event add` does:**
86
+ - Creates `packages/contracts/src/events/orders-created.mock.json` (test mock)
87
+ - Adds export to `packages/contracts/src/index.ts`
88
+ - Skips contract creation if already exists (AI's schema is preserved!)
89
+
90
+ #### Option B: Manual Creation
91
+
92
+ **Step 1: Create contract with CLI**
93
+ ```bash
94
+ # Create contract with schema
95
+ pf event add orders.created --fields "orderId:string,total:number,customerId:string"
96
+
97
+ # Or with JSON schema
98
+ pf event add orders.created --schema '{"orderId":"string","total":"number"}'
99
+ ```
100
+
101
+ **Step 2: Scaffold service**
102
+ ```bash
103
+ pf new hono-micro services/order-processing
104
+ ```
105
+
106
+ **Step 3: Register event and create handler**
107
+ ```bash
108
+ pf event add orders.created --service services/order-processing
109
+ ```
110
+
111
+ **Step 4: Implement use-cases**
112
+ ```bash
113
+ # Edit services/order-processing/src/use-cases/process-order.use-case.ts
114
+ ```
115
+
116
+ #### Option C: Pure Manual (no CLI)
117
+
118
+ ```bash
119
+ # 1. Create contract manually (domain-grouped)
120
+ mkdir -p packages/contracts/src/events/orders
121
+ # Edit packages/contracts/src/events/orders/created.ts
122
+
123
+ # 2. Scaffold service
124
+ pf new hono-micro services/order-processing
125
+
126
+ # 3. Create handler manually
127
+ # Edit services/order-processing/src/events/orders-created.handler.ts
128
+
129
+ # 4. Implement use-cases
130
+ # Edit services/order-processing/src/use-cases/
131
+ ```
132
+
133
+ ### 3️⃣ **Run Development Environment**
134
+
135
+ ```bash
136
+ pf dev # Starts NATS + creates ephemeral streams from contracts + all services
137
+ ```
138
+
139
+ **What happens:**
140
+ - **NATS** starts in Docker with JetStream enabled
141
+ - **Ephemeral streams** auto-created from contract channel metadata (memory storage, 1h retention)
142
+ - **Services** start and consume from streams
143
+ - **Hot reload** watches for changes
144
+
145
+ **Stream Creation:** `pf dev` scans `packages/contracts/src/index.ts` for contracts with `channel.stream` metadata and creates ephemeral streams automatically. No manual stream creation needed!
146
+
147
+ ### 4️⃣ **Deploy to Production**
148
+
149
+ ```bash
150
+ cd infra
151
+ pulumi up
152
+ ```
153
+
154
+ **What happens:**
155
+ 1. **`infra/streams/`** collects contracts via `collectStreamDefinitions()`
156
+ 2. **Streams materialized** via Pulumi with retention policies
157
+ 3. **Services deployed** to Kubernetes
158
+
159
+ ---
160
+
161
+ ## �🚨 CRITICAL: Stream Architecture
162
+
163
+ > **Services NEVER create streams!**
164
+ > **Dev:** Streams are auto-created (ephemeral, memory)
165
+ > **Prod:** Streams are materialized via Pulumi from contracts
166
+
167
+ ### Contracts → Streams → Infra Flow
168
+
169
+ ```typescript
170
+ // 1. Contract defines conceptually (packages/contracts)
171
+ export const OrdersCreatedContract = createContract({
172
+ type: 'orders.created',
173
+ channel: { stream: 'ORDERS' }, // Routing metadata
174
+ schema: OrderSchema,
175
+ })
176
+
177
+ // 2. Services consume (no stream creation!)
178
+ consumeJetStreams({
179
+ streams: ['ORDERS'],
180
+ consumer: 'my-service',
181
+ discover: './src/events/**/*.handler.ts',
182
+ })
183
+
184
+ // 3. Infra materializes (infra/streams/)
185
+ import { collectStreamDefinitions } from '@crossdelta/infrastructure'
186
+ import * as contracts from '@workspace/contracts'
187
+
188
+ // Generic logic in library, workspace data injected
189
+ const streams = collectStreamDefinitions(contracts)
190
+ deployStreams(provider, namespace, streams)
191
+ ```
192
+
193
+ **Architecture layers:**
194
+ - **@crossdelta/infrastructure:** Generic stream collection logic
195
+ - **Contracts:** Define event semantics + channel routing (workspace data)
196
+ - **`pf dev`:** Ensures ephemeral streams (transient, memory-only)
197
+ - **Platform-Infra:** Materializes streams via Pulumi (persistent, retention)
198
+ - **Services:** Consume streams (never create!)
199
+
200
+ **Separation of concerns:**
201
+ - **Libraries contain logic** (generic, reusable)
202
+ - **Workspaces contain data** (contracts, policies)
203
+ - **No workspace imports inside libraries**
204
+
205
+ ---
206
+
10
207
  ## 🚨 CRITICAL: @crossdelta/cloudevents API
11
208
 
12
209
  **ONLY these exports exist:**
13
- - `ensureJetStreams` / `consumeJetStreams` (preferred)
14
- - `ensureJetStreamStream` / `consumeJetStreamEvents` (legacy)
210
+ - `consumeJetStreams` (preferred) - Consume from multiple streams
211
+ - `consumeJetStreamEvents` (legacy) - Consume from single stream
15
212
  - `handleEvent`, `publish`, `createContract`
16
213
 
17
214
  ```ts
18
- await ensureJetStreams({
19
- streams: [{ stream: 'ORDERS', subjects: ['orders.*'] }]
20
- })
21
-
215
+ // Services only consume (DO NOT use ensureJetStreams!)
22
216
  consumeJetStreams({
23
217
  streams: ['ORDERS'],
24
218
  consumer: 'my-service',
25
- discover: './src/events/**/*.event.ts',
219
+ discover: './src/events/**/*.handler.ts',
26
220
  })
27
221
  ```
28
222
 
29
- **DO NOT USE (these do NOT exist):** `stream`, `consumer`, `subscribe`, `name:`, `handler:`, `topic:`
223
+ **DO NOT USE in services:**
224
+ - ❌ `ensureJetStreams()` - Only for infra materialization
225
+ - ❌ `ensureJetStreamStream()` - Legacy, only for infra
226
+ - ❌ Manual stream creation - Services never create streams!
30
227
 
31
228
  ---
32
229
 
@@ -49,6 +246,16 @@ The exact command depends on the framework - see the framework-specific docs:
49
246
 
50
247
  **Without this command, the service cannot be built!**
51
248
 
249
+ **What `pf new` creates automatically:**
250
+ 1. Service directory structure (`services/<name>/`)
251
+ 2. Infrastructure config (`infra/services/<name>.ts`) with assigned port
252
+ 3. Package.json with dependencies
253
+ 4. Entry point (`src/index.ts` or `src/main.ts`) with port configuration
254
+ 5. Health check endpoint
255
+ 6. Dockerfile
256
+
257
+ **⚠️ AI MUST NOT generate `infra/services/<name>.ts`** - This file is automatically created by the CLI with the correct port assignment!
258
+
52
259
  ---
53
260
 
54
261
  ## 🚨 CRITICAL: Post-Commands Block (REQUIRED for Event Consumers)
@@ -74,22 +281,23 @@ pf event add customers.updated --service services/my-service
74
281
 
75
282
  ### What YOU (the AI) generate:
76
283
 
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
284
+ 1. **`packages/contracts/src/events/<domain>/<event>.ts`** - Contract with correct schema fields in domain-grouped structure (e.g., `events/orders/created.ts`)
285
+ 2. **`src/events/<event>.handler.ts`** - Event handler that calls the use-case
286
+ 3. **`src/use-cases/*.use-case.ts`** - Business logic (called by handlers)
287
+ 4. **`src/use-cases/*.test.ts`** - Tests for use-cases
288
+ 5. **`README.md`** - Documentation
289
+
290
+ **⚠️ DO NOT generate `src/index.ts` or `src/main.ts`** - These are created by `pf new` with correct port config!
83
291
 
84
292
  ### Handler Location (CRITICAL!)
85
293
 
86
- **⚠️ MUST be in `src/events/*.event.ts` - NEVER in `handlers/` subdirectory**
294
+ **⚠️ MUST be in `src/events/*.handler.ts` - NEVER in `handlers/` subdirectory**
87
295
 
88
296
  ```
89
297
  ✅ CORRECT:
90
298
  services/my-service/src/events/
91
- ├── orders-created.event.ts
92
- └── customers-updated.event.ts
299
+ ├── orders-created.handler.ts
300
+ └── customers-updated.handler.ts
93
301
 
94
302
  ❌ WRONG:
95
303
  services/my-service/src/events/handlers/ # NEVER create!
@@ -126,7 +334,7 @@ export default handleEvent(OrdersCreatedContract, async (data) => {
126
334
  pf new hono-micro services/my-service -y
127
335
  ```
128
336
 
129
- #### `packages/contracts/src/events/orders-created.ts`
337
+ #### `packages/contracts/src/events/orders/created.ts`
130
338
  ```ts
131
339
  import { createContract } from '@crossdelta/cloudevents'
132
340
  import { z } from 'zod'
@@ -138,13 +346,14 @@ export const OrdersCreatedSchema = z.object({
138
346
 
139
347
  export const OrdersCreatedContract = createContract({
140
348
  type: 'orders.created',
349
+ channel: { stream: 'ORDERS' },
141
350
  schema: OrdersCreatedSchema,
142
351
  })
143
352
 
144
353
  export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
145
354
  ```
146
355
 
147
- #### `src/events/orders-created.event.ts`
356
+ #### `src/events/orders-created.handler.ts`
148
357
  ```ts
149
358
  import { handleEvent } from '@crossdelta/cloudevents'
150
359
  import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
@@ -216,11 +425,79 @@ export const OrdersCreatedSchema = z.object({
216
425
 
217
426
  ## Naming Convention
218
427
 
219
- | Event Type | Contract | File |
220
- |------------|----------|------|
221
- | `orders.created` | `OrdersCreatedContract` | `orders-created.ts` |
428
+ **Architecture Rule:**
429
+
430
+ | Component | Format | Example | Reason |
431
+ |-----------|--------|---------|--------|
432
+ | **Event Type** | **Singular** | `domain.created` | Describes a single event |
433
+ | **Stream** | **PLURAL** | `DOMAINS` | Collection of events for a domain |
434
+
435
+ **Merksatz:** Events sind singular. Streams sind plural.
436
+
437
+ ### Examples
438
+
439
+ | Event Type | Contract | File | Stream |
440
+ |------------|----------|------|--------|
441
+ | `orders.created` | `OrdersCreatedContract` | `orders-created.ts` | `ORDERS` ✅ |
442
+ | `domain.created` | `DomainCreatedContract` | `domain-created.ts` | `DOMAINS` ✅ |
443
+
444
+ ```typescript
445
+ // ✅ CORRECT
446
+ export const DomainCreatedContract = createContract({
447
+ type: 'domain.created', // Singular event
448
+ channel: { stream: 'DOMAINS' }, // Plural stream
449
+ schema: DomainCreatedSchema,
450
+ })
451
+
452
+ export const OrdersCreatedContract = createContract({
453
+ type: 'orders.created', // Singular event (but namespace plural!)
454
+ channel: { stream: 'ORDERS' }, // Plural stream
455
+ schema: OrdersCreatedSchema,
456
+ })
457
+
458
+ // ❌ WRONG
459
+ export const DomainCreatedContract = createContract({
460
+ type: 'domain.created',
461
+ channel: { stream: 'DOMAIN' }, // ❌ Must be DOMAINS (plural!)
462
+ schema: DomainCreatedSchema,
463
+ })
464
+ ```
465
+
466
+ ### Stream Names in consumeJetStreams
467
+
468
+ **CRITICAL:** When consuming from streams, use the **PLURAL** stream name from the contract's `channel.stream`:
469
+
470
+ ```typescript
471
+ // ✅ CORRECT - domain.created event → DOMAINS stream
472
+ consumeJetStreams({
473
+ streams: ['DOMAINS'], // ✅ Plural!
474
+ consumer: 'my-service',
475
+ discover: './src/events/**/*.handler.ts',
476
+ })
477
+
478
+ // ✅ CORRECT - orders.created event → ORDERS stream
479
+ consumeJetStreams({
480
+ streams: ['ORDERS'], // ✅ Plural!
481
+ consumer: 'my-service',
482
+ discover: './src/events/**/*.handler.ts',
483
+ })
484
+
485
+ // ❌ WRONG - Don't use singular!
486
+ consumeJetStreams({
487
+ streams: ['DOMAIN'], // ❌ Wrong! Should be DOMAINS
488
+ consumer: 'my-service',
489
+ discover: './src/events/**/*.handler.ts',
490
+ })
491
+
492
+ // ❌ WRONG - Don't use lowercase!
493
+ consumeJetStreams({
494
+ streams: ['domains'], // ❌ Wrong! Should be DOMAINS (uppercase)
495
+ consumer: 'my-service',
496
+ discover: './src/events/**/*.handler.ts',
497
+ })
498
+ ```
222
499
 
223
- **⚠️ ALWAYS use PLURAL namespace:** `orders.created` ✅, `order.created`
500
+ **Rule:** Extract the stream name from your contract's `channel.stream` field and use it EXACTLY in `consumeJetStreams({ streams: ['...'] })`.
224
501
 
225
502
  ---
226
503
 
@@ -253,7 +530,7 @@ Combines REST endpoints + NATS consumer.
253
530
  ```
254
531
  services/my-service/src/
255
532
  ├── index.ts
256
- ├── events/orders-created.event.ts
533
+ ├── events/orders-created.handler.ts
257
534
  └── use-cases/
258
535
  ├── process-order.use-case.ts
259
536
  └── process-order.test.ts
@@ -279,7 +556,7 @@ packages/contracts/src/events/
279
556
 
280
557
  **DO:**
281
558
  - ✅ Contracts in `packages/contracts/src/events/`
282
- - ✅ Handlers in `src/events/*.event.ts`
559
+ - ✅ Handlers in `src/events/*.handler.ts`
283
560
  - ✅ Hono: Use-cases in `src/use-cases/*.use-case.ts`
284
561
  - ✅ NestJS: Business logic in Services + pure helper functions
285
562
  - ✅ Log event type and key identifier in handlers
@@ -0,0 +1,18 @@
1
+ // IMPORTANT: telemetry must be imported first to patch modules before they're loaded
2
+ import '@crossdelta/telemetry'
3
+
4
+ import { Hono } from 'hono'
5
+
6
+ const port = Number(process.env.{{envKey}}_PORT) || 8080
7
+ const app = new Hono()
8
+
9
+ app.get('/health', (c) => {
10
+ return c.json({ status: 'ok' })
11
+ })
12
+
13
+ Bun.serve({
14
+ port,
15
+ fetch: app.fetch,
16
+ })
17
+
18
+ console.log(`🚀 Service ready at http://localhost:${port}`)
@@ -1,25 +1,22 @@
1
1
  import { Injectable, Logger } from '@nestjs/common'
2
- import { consumeJetStreamStreams, ensureJetStreamStreams } from '@crossdelta/cloudevents'
2
+ import { consumeJetStreams } from '@crossdelta/cloudevents'
3
3
 
4
4
  @Injectable()
5
5
  export class EventsService {
6
6
  private readonly logger = new Logger(EventsService.name)
7
7
 
8
8
  async startConsumers(): Promise<void> {
9
- // Configure your streams here
10
- // Example:
11
- // await ensureJetStreamStreams({
12
- // streams: [
13
- // { stream: 'ORDERS', subjects: ['orders.*'] },
14
- // ],
15
- // })
9
+ // Services NEVER create streams!
10
+ // - Development: pf dev auto-creates ephemeral streams from contracts
11
+ // - Production: Pulumi materializes persistent streams
16
12
 
17
- // consumeJetStreamStreams({
13
+ // Uncomment and configure when you have events:
14
+ // consumeJetStreams({
18
15
  // streams: ['ORDERS'],
19
16
  // consumer: '{{serviceName}}',
20
17
  // discover: './src/events/**/*.event.ts',
21
18
  // })
22
19
 
23
- this.logger.log('Event consumers ready (configure streams in events.service.ts)')
20
+ this.logger.log('Event consumers ready')
24
21
  }
25
22
  }
@@ -8,7 +8,7 @@ import { AppModule } from './app.module'
8
8
  import { setAppContext } from './app.context'
9
9
  import { EventsService } from './events/events.service'
10
10
 
11
- const port = Number(process.env.PORT || process.env.{{envKey}}_PORT) || {{defaultPort}}
11
+ const port = Number(process.env.{{envKey}}_PORT) || {{defaultPort}}
12
12
  const serviceName = '{{displayName}}'
13
13
 
14
14
  const logger = new ConsoleLogger({
@@ -37,8 +37,8 @@
37
37
  }
38
38
  },
39
39
  "dependencies": {
40
- "@crossdelta/cloudevents": "^0.5.1",
41
- "@crossdelta/telemetry": "^0.1.4"
40
+ "@crossdelta/cloudevents": "latest",
41
+ "@crossdelta/telemetry": "latest"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@biomejs/biome": "2.3.7",
@@ -10,18 +10,39 @@ This package contains **shared event definitions** (contracts) for events that a
10
10
 
11
11
  ```
12
12
  src/
13
- ├── events/ # Event contracts and mock data
14
- │ ├── orders-created.ts # Contract definition
15
- │ ├── orders-created.mock.json # Mock data for testing
16
- └── index.ts # Re-exports
17
- └── index.ts # Main exports
13
+ ├── events/ # Event contracts grouped by domain
14
+ │ ├── orders/ # Orders domain
15
+ ├── created.ts # orders.created event
16
+ │ ├── updated.ts # orders.updated event
17
+ │ │ └── index.ts # Re-exports
18
+ │ ├── customers/ # Customers domain
19
+ │ │ ├── updated.ts # customers.updated event
20
+ │ │ └── index.ts # Re-exports
21
+ │ └── index.ts # Re-exports all domains
22
+ ├── stream-policies.ts # NATS JetStream retention policies
23
+ └── index.ts # Main export file
18
24
  ```
19
25
 
20
26
  **Contract files contain:**
21
27
  - Zod schema definition
22
- - Contract object (type + schema)
28
+ - Contract object (type + schema + **channel metadata**)
23
29
  - TypeScript type inference
24
- - Mock JSON for `pf event:publish`
30
+
31
+ **Channel metadata** defines which NATS JetStream stream the event belongs to. This enables infrastructure-as-code workflows where streams are auto-created in dev (`pf dev`) and materialized via Pulumi in production.
32
+
33
+ ## Adding New Events
34
+
35
+ ### Using the CLI (Recommended)
36
+
37
+ ```bash
38
+ pf event add products.created --fields "productId:string,name:string,price:number"
39
+ ```
40
+
41
+ This will create `src/events/products/created.ts` with proper domain structure.
42
+
43
+ ### Manual Creation
44
+
45
+ See [Adding Events](#manual-creation) section below.
25
46
 
26
47
  ## Usage
27
48
 
@@ -82,7 +103,7 @@ This creates:
82
103
  ### Manual Contract Creation
83
104
 
84
105
  ```ts
85
- // packages/contracts/src/events/orders-created.ts
106
+ // packages/contracts/src/events/orders/created.ts
86
107
  import { createContract } from '@crossdelta/cloudevents'
87
108
  import { z } from 'zod'
88
109
 
@@ -99,12 +120,23 @@ export const OrdersCreatedSchema = z.object({
99
120
 
100
121
  export const OrdersCreatedContract = createContract({
101
122
  type: 'orders.created',
123
+ channel: { stream: 'ORDERS' }, // Stream routing metadata
102
124
  schema: OrdersCreatedSchema,
103
125
  })
104
126
 
105
127
  export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
106
128
  ```
107
129
 
130
+ **Channel Metadata:**
131
+ - `stream` - NATS JetStream stream name (e.g., `ORDERS`)
132
+ - `subject` - Optional, defaults to event type (e.g., `orders.created`)
133
+
134
+ **Stream Materialization:**
135
+ 1. **Development**: `pf dev` scans contracts and auto-creates ephemeral streams from channel metadata
136
+ 2. **Production**: Pulumi collects streams from contracts and materializes with retention policies
137
+
138
+ See [`infra/streams/README.md`](../../infra/streams/README.md) for details.
139
+
108
140
  ## Testing with Mocks
109
141
 
110
142
  ```bash
@@ -11,7 +11,8 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@crossdelta/cloudevents": "^0.5.2",
14
+ "@crossdelta/cloudevents": "^0.5.3",
15
+ "@crossdelta/infrastructure": "^0.5.0",
15
16
  "zod": "^4.0.0"
16
17
  },
17
18
  "devDependencies": {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Event Contracts Index
3
+ *
4
+ * Re-exports all event contracts for convenient importing.
5
+ * Services can import from '{{scope}}/contracts' instead of deep imports.
6
+ *
7
+ * Structure:
8
+ * - events/<domain>/<event>.ts - Individual event contracts
9
+ * - events/<domain>/index.ts - Domain-specific exports
10
+ * - This file: Re-exports all domains
11
+ */
12
+
13
+ // Add your event domains here as you create them:
14
+ // export * from './orders'
15
+ // export * from './customers'
16
+ // export * from './products'
@@ -1 +1,10 @@
1
1
  // Export your event contracts and schemas here
2
+ // Use the CLI to generate contracts:
3
+ //
4
+ // pf event add orders.created --fields "orderId:string,total:number"
5
+ //
6
+ // This will create: src/events/orders/created.ts
7
+ // Then uncomment the exports below:
8
+
9
+ // export * from './events'
10
+ // export * from './stream-policies'
@@ -0,0 +1,40 @@
1
+ /**
2
+ * {{workspaceName}} Stream Policies
3
+ *
4
+ * Business rules for NATS JetStream stream configuration.
5
+ * Defines retention, replication, and storage policies per stream.
6
+ *
7
+ * Architecture:
8
+ * - This file: Business rules (data)
9
+ * - @crossdelta/infrastructure: Deployment logic
10
+ * - infra/streams: Connects business rules + deployment logic
11
+ */
12
+
13
+ import type { StreamPolicy } from '@crossdelta/infrastructure'
14
+
15
+ // Time constants for readability
16
+ const DAYS = 24 * 60 * 60 * 1000
17
+ const MB = 1024 * 1024
18
+
19
+ /**
20
+ * Stream-specific policies
21
+ * Add entries here as you create new streams via event contracts
22
+ */
23
+ export const STREAM_POLICIES: Record<string, Partial<StreamPolicy>> = {
24
+ // Example:
25
+ // ORDERS: {
26
+ // maxAge: 14 * DAYS,
27
+ // replicas: 3,
28
+ // },
29
+ }
30
+
31
+ /**
32
+ * Default stream policy
33
+ * Applied to all streams without specific overrides
34
+ */
35
+ export const DEFAULT_POLICY: Partial<StreamPolicy> = {
36
+ maxAge: 7 * DAYS, // 7 days default retention
37
+ storage: 'file', // Persistent storage
38
+ replicas: 1, // Single replica for non-critical streams
39
+ maxMsgSize: 1 * MB, // 1MB max message size
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/platform-sdk",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Platform toolkit for event-driven microservices — keeping code and infrastructure in lockstep.",
5
5
  "keywords": [
6
6
  "cli",