@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.
- package/README.md +17 -4
- package/bin/cli.js +144 -136
- package/bin/docs/generators/hono-bun.md +70 -26
- package/bin/docs/generators/hono-node.md +76 -26
- package/bin/docs/generators/nest.md +25 -7
- package/bin/docs/generators/service.md +302 -25
- package/bin/templates/hono-microservice/src/index.ts.hbs +18 -0
- package/bin/templates/nest-microservice/src/events/events.service.ts.hbs +7 -10
- package/bin/templates/nest-microservice/src/main.ts.hbs +1 -1
- package/bin/templates/workspace/infra/services/.gitkeep +0 -0
- package/bin/templates/workspace/package.json.hbs +2 -2
- package/bin/templates/workspace/packages/contracts/README.md.hbs +40 -8
- package/bin/templates/workspace/packages/contracts/package.json.hbs +2 -1
- package/bin/templates/workspace/packages/contracts/src/events/index.ts +16 -0
- package/bin/templates/workspace/packages/contracts/src/index.ts +9 -0
- package/bin/templates/workspace/packages/contracts/src/stream-policies.ts.hbs +40 -0
- package/package.json +1 -1
- package/bin/templates/workspace/infra/services/nats.ts.hbs +0 -55
- package/bin/templates/workspace/services/nats/README.md +0 -107
- package/bin/templates/workspace/services/nats/nats.conf +0 -31
- package/bin/templates/workspace/services/nats/nats.prod.conf +0 -27
- package/bin/templates/workspace/services/nats/package.json.hbs +0 -7
- package/bin/templates/workspace/services/nats/scripts/start-dev.sh.hbs +0 -55
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Hono (Bun Runtime)
|
|
2
2
|
|
|
3
|
+
## 🚨 CRITICAL: AI Generation Rules
|
|
4
|
+
|
|
5
|
+
**DO NOT create from scratch:**
|
|
6
|
+
- ❌ `infra/services/<name>.ts` - Created by CLI with port assignment
|
|
7
|
+
- ❌ `Dockerfile` - Created by CLI
|
|
8
|
+
- ❌ `package.json` - Created by CLI
|
|
9
|
+
|
|
10
|
+
**Always generate:**
|
|
11
|
+
- ✅ Event handlers (`src/events/*.handler.ts`)
|
|
12
|
+
- ✅ Use-cases (`src/use-cases/*.use-case.ts`)
|
|
13
|
+
- ✅ Tests (`src/**/*.test.ts`)
|
|
14
|
+
- ✅ README.md
|
|
15
|
+
- ✅ Contracts (`packages/contracts/src/events/`)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
3
19
|
## 🚨 CRITICAL: Commands Block (REQUIRED FIRST)
|
|
4
20
|
|
|
5
21
|
```commands
|
|
@@ -20,7 +36,9 @@ pf new hono-micro services/push-notifications -y
|
|
|
20
36
|
import '@crossdelta/telemetry'
|
|
21
37
|
import { Hono } from 'hono'
|
|
22
38
|
|
|
23
|
-
|
|
39
|
+
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
40
|
+
// Example: my-hono-service → MY_HONO_SERVICE_PORT
|
|
41
|
+
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
24
42
|
const app = new Hono()
|
|
25
43
|
|
|
26
44
|
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
@@ -32,10 +50,11 @@ console.log(`Service running on http://localhost:${port}`)
|
|
|
32
50
|
**Event Consumer:**
|
|
33
51
|
```ts
|
|
34
52
|
import '@crossdelta/telemetry'
|
|
35
|
-
import { consumeJetStreams
|
|
53
|
+
import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
36
54
|
import { Hono } from 'hono'
|
|
37
55
|
|
|
38
|
-
|
|
56
|
+
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
57
|
+
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
39
58
|
const app = new Hono()
|
|
40
59
|
|
|
41
60
|
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
@@ -43,24 +62,30 @@ app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
|
43
62
|
Bun.serve({ port, fetch: app.fetch })
|
|
44
63
|
console.log(`Service running on http://localhost:${port}`)
|
|
45
64
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
// Services NEVER create streams!
|
|
66
|
+
// - Development: pf dev auto-creates ephemeral streams from contracts
|
|
67
|
+
// - Production: Pulumi materializes persistent streams
|
|
50
68
|
consumeJetStreams({
|
|
51
|
-
streams: ['ORDERS'],
|
|
69
|
+
streams: ['ORDERS'], // ⚠️ MUST be PLURAL! Extract from contract's channel.stream
|
|
52
70
|
consumer: 'my-service',
|
|
53
|
-
discover: './src/events/**/*.
|
|
71
|
+
discover: './src/events/**/*.handler.ts',
|
|
54
72
|
})
|
|
55
73
|
```
|
|
56
74
|
|
|
75
|
+
**CRITICAL:** Stream names MUST be PLURAL:
|
|
76
|
+
- ✅ `streams: ['ORDERS']` - for orders.created event
|
|
77
|
+
- ✅ `streams: ['DOMAINS']` - for domain.created event
|
|
78
|
+
- ❌ `streams: ['ORDER']` - WRONG (singular)
|
|
79
|
+
- ❌ `streams: ['DOMAIN']` - WRONG (singular)
|
|
80
|
+
|
|
57
81
|
**Event Publisher:**
|
|
58
82
|
```ts
|
|
59
83
|
import '@crossdelta/telemetry'
|
|
60
84
|
import { publish } from '@crossdelta/cloudevents'
|
|
61
85
|
import { Hono } from 'hono'
|
|
62
86
|
|
|
63
|
-
|
|
87
|
+
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
88
|
+
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
64
89
|
const app = new Hono()
|
|
65
90
|
|
|
66
91
|
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
@@ -79,51 +104,70 @@ console.log(`Service running on http://localhost:${port}`)
|
|
|
79
104
|
|
|
80
105
|
## Environment Validation (Optional)
|
|
81
106
|
|
|
82
|
-
For services with required env vars
|
|
107
|
+
**For services with required env vars:**
|
|
108
|
+
|
|
109
|
+
### 1️⃣ Create `src/config/env.ts`:
|
|
83
110
|
|
|
84
111
|
```ts
|
|
85
112
|
// src/config/env.ts
|
|
86
113
|
import { z } from 'zod'
|
|
87
114
|
|
|
115
|
+
// ⚠️ DO NOT include SERVICE_NAME_PORT here - it's already handled by template!
|
|
116
|
+
// Only validate YOUR custom env vars
|
|
88
117
|
const envSchema = z.object({
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
PUSHER_SECRET_KEY: z.string().min(1),
|
|
118
|
+
PUSHER_INSTANCE_ID: z.string().min(1, 'PUSHER_INSTANCE_ID is required'),
|
|
119
|
+
PUSHER_SECRET_KEY: z.string().min(1, 'PUSHER_SECRET_KEY is required'),
|
|
92
120
|
NATS_URL: z.string().url().default('nats://localhost:4222'),
|
|
93
121
|
})
|
|
94
122
|
|
|
95
123
|
export type Env = z.infer<typeof envSchema>
|
|
96
124
|
|
|
97
|
-
// Validate
|
|
98
|
-
|
|
125
|
+
// Validate and crash process if invalid (like @nestjs/config)
|
|
126
|
+
const result = envSchema.safeParse(process.env)
|
|
127
|
+
if (!result.success) {
|
|
128
|
+
console.error('❌ Environment validation failed:')
|
|
129
|
+
console.error(result.error.format())
|
|
130
|
+
process.exit(1) // ← Force crash with exit code 1
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const env = result.data
|
|
99
134
|
```
|
|
100
135
|
|
|
136
|
+
### 2️⃣ **CRITICAL: Import in `src/index.ts` early!**
|
|
137
|
+
|
|
101
138
|
```ts
|
|
102
139
|
// src/index.ts
|
|
103
|
-
import '@crossdelta/telemetry'
|
|
140
|
+
import '@crossdelta/telemetry' // ← MUST be first!
|
|
141
|
+
|
|
142
|
+
// ⚠️ CRITICAL: Import env IMMEDIATELY after telemetry
|
|
143
|
+
// This executes validation and crashes process if env invalid
|
|
144
|
+
import { env } from './config/env' // ← THIS LINE IS REQUIRED!
|
|
145
|
+
|
|
104
146
|
import { Hono } from 'hono'
|
|
105
|
-
import { env } from './config/env'
|
|
106
147
|
|
|
148
|
+
const port = Number(process.env.MY_SERVICE_PORT) || 8080 // ← Handled by template
|
|
107
149
|
const app = new Hono()
|
|
108
150
|
|
|
109
151
|
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
110
152
|
|
|
111
153
|
app.post('/notify', async (c) => {
|
|
112
154
|
const data = await c.req.json()
|
|
113
|
-
// Use validated env
|
|
155
|
+
// Use validated env vars
|
|
114
156
|
await sendPush(env.PUSHER_INSTANCE_ID, env.PUSHER_SECRET_KEY, data)
|
|
115
157
|
return c.json({ success: true })
|
|
116
158
|
})
|
|
117
159
|
|
|
118
|
-
Bun.serve({ port
|
|
119
|
-
console.log(`Service running on http://localhost:${
|
|
160
|
+
Bun.serve({ port, fetch: app.fetch })
|
|
161
|
+
console.log(`Service running on http://localhost:${port}`)
|
|
120
162
|
```
|
|
121
163
|
|
|
122
|
-
**
|
|
123
|
-
- ✅
|
|
124
|
-
- ✅
|
|
125
|
-
- ✅
|
|
126
|
-
- ✅
|
|
164
|
+
**CRITICAL:**
|
|
165
|
+
- ✅ **MUST import `env` in `src/index.ts`** - without import, validation never runs!
|
|
166
|
+
- ✅ Import early (after telemetry, before everything else)
|
|
167
|
+
- ✅ Use `safeParse()` + `process.exit(1)` - Bun doesn't crash on top-level throws
|
|
168
|
+
- ✅ Explicit exit ensures Turbo shows red X (like NestJS)
|
|
169
|
+
- ❌ **DO NOT** include `SERVICE_NAME_PORT` in env schema - already handled by CLI template
|
|
170
|
+
- ❌ **DO NOT** validate inside handlers or use-cases
|
|
127
171
|
|
|
128
172
|
---
|
|
129
173
|
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Hono (Node.js Runtime)
|
|
2
2
|
|
|
3
|
+
## 🚨 CRITICAL: AI Generation Rules
|
|
4
|
+
|
|
5
|
+
**DO NOT generate these files** - they are created by `pf new hono-micro`:
|
|
6
|
+
- ❌ `infra/services/<name>.ts` - Infrastructure config with assigned port
|
|
7
|
+
- ❌ `Dockerfile` - Container configuration
|
|
8
|
+
- ❌ `package.json` - Dependencies and scripts
|
|
9
|
+
|
|
10
|
+
**Always generate:**
|
|
11
|
+
- ✅ Event handlers (`src/events/*.handler.ts`)
|
|
12
|
+
- ✅ Use-cases (`src/use-cases/*.use-case.ts`)
|
|
13
|
+
- ✅ Tests (`src/**/*.test.ts`)
|
|
14
|
+
- ✅ README.md
|
|
15
|
+
- ✅ Contracts (`packages/contracts/src/events/`)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
3
19
|
## 🚨 CRITICAL: Commands Block (REQUIRED FIRST)
|
|
4
20
|
|
|
5
21
|
```commands
|
|
@@ -21,7 +37,8 @@ import '@crossdelta/telemetry'
|
|
|
21
37
|
import { serve } from '@hono/node-server'
|
|
22
38
|
import { Hono } from 'hono'
|
|
23
39
|
|
|
24
|
-
|
|
40
|
+
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
41
|
+
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
25
42
|
const app = new Hono()
|
|
26
43
|
|
|
27
44
|
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
@@ -34,11 +51,12 @@ serve({ fetch: app.fetch, port }, (info) => {
|
|
|
34
51
|
**Event Consumer:**
|
|
35
52
|
```ts
|
|
36
53
|
import '@crossdelta/telemetry'
|
|
37
|
-
import { consumeJetStreams
|
|
54
|
+
import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
38
55
|
import { serve } from '@hono/node-server'
|
|
39
56
|
import { Hono } from 'hono'
|
|
40
57
|
|
|
41
|
-
|
|
58
|
+
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
59
|
+
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
42
60
|
const app = new Hono()
|
|
43
61
|
|
|
44
62
|
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
@@ -47,17 +65,22 @@ serve({ fetch: app.fetch, port }, (info) => {
|
|
|
47
65
|
console.log(`Server running on http://localhost:${info.port}`)
|
|
48
66
|
})
|
|
49
67
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
// Services NEVER create streams!
|
|
69
|
+
// - Development: pf dev auto-creates ephemeral streams from contracts
|
|
70
|
+
// - Production: Pulumi materializes persistent streams
|
|
54
71
|
consumeJetStreams({
|
|
55
|
-
streams: ['ORDERS'],
|
|
72
|
+
streams: ['ORDERS'], // ⚠️ MUST be PLURAL! Extract from contract's channel.stream
|
|
56
73
|
consumer: 'my-service',
|
|
57
|
-
discover: './src/events/**/*.
|
|
74
|
+
discover: './src/events/**/*.handler.ts',
|
|
58
75
|
})
|
|
59
76
|
```
|
|
60
77
|
|
|
78
|
+
**CRITICAL:** Stream names MUST be PLURAL:
|
|
79
|
+
- ✅ `streams: ['ORDERS']` - for orders.created event
|
|
80
|
+
- ✅ `streams: ['DOMAINS']` - for domain.created event
|
|
81
|
+
- ❌ `streams: ['ORDER']` - WRONG (singular)
|
|
82
|
+
- ❌ `streams: ['DOMAIN']` - WRONG (singular)
|
|
83
|
+
|
|
61
84
|
**Event Publisher:**
|
|
62
85
|
```ts
|
|
63
86
|
import '@crossdelta/telemetry'
|
|
@@ -65,7 +88,8 @@ import { publish } from '@crossdelta/cloudevents'
|
|
|
65
88
|
import { serve } from '@hono/node-server'
|
|
66
89
|
import { Hono } from 'hono'
|
|
67
90
|
|
|
68
|
-
|
|
91
|
+
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
92
|
+
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
69
93
|
const app = new Hono()
|
|
70
94
|
|
|
71
95
|
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
@@ -85,53 +109,79 @@ serve({ fetch: app.fetch, port }, (info) => {
|
|
|
85
109
|
|
|
86
110
|
## Environment Validation (Optional)
|
|
87
111
|
|
|
88
|
-
For services with required env vars
|
|
112
|
+
**For services with required env vars:**
|
|
113
|
+
|
|
114
|
+
### 1️⃣ Create `src/config/env.ts`:
|
|
89
115
|
|
|
90
116
|
```ts
|
|
91
117
|
// src/config/env.ts
|
|
92
118
|
import { z } from 'zod'
|
|
93
119
|
|
|
120
|
+
// ⚠️ DO NOT include SERVICE_NAME_PORT here - it's already handled by template!
|
|
121
|
+
// Only validate YOUR custom env vars
|
|
94
122
|
const envSchema = z.object({
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
PUSHER_SECRET_KEY: z.string().min(1),
|
|
123
|
+
PUSHER_INSTANCE_ID: z.string().min(1, 'PUSHER_INSTANCE_ID is required'),
|
|
124
|
+
PUSHER_SECRET_KEY: z.string().min(1, 'PUSHER_SECRET_KEY is required'),
|
|
98
125
|
NATS_URL: z.string().url().default('nats://localhost:4222'),
|
|
99
126
|
})
|
|
100
127
|
|
|
101
128
|
export type Env = z.infer<typeof envSchema>
|
|
102
129
|
|
|
103
|
-
// Validate
|
|
104
|
-
|
|
130
|
+
// Validate and crash process if invalid (like @nestjs/config)
|
|
131
|
+
const result = envSchema.safeParse(process.env)
|
|
132
|
+
if (!result.success) {
|
|
133
|
+
console.error('❌ Environment validation failed:')
|
|
134
|
+
console.error(result.error.format())
|
|
135
|
+
process.exit(1) // ← Force crash with exit code 1
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const env = result.data
|
|
105
139
|
```
|
|
106
140
|
|
|
141
|
+
### 2️⃣ **CRITICAL: Import in `src/index.ts` early!**
|
|
142
|
+
|
|
107
143
|
```ts
|
|
108
144
|
// src/index.ts
|
|
109
|
-
import '@crossdelta/telemetry'
|
|
145
|
+
import '@crossdelta/telemetry' // ← MUST be first!
|
|
146
|
+
|
|
147
|
+
// ⚠️ CRITICAL: Import env IMMEDIATELY after telemetry
|
|
148
|
+
// This executes validation and crashes process if env invalid
|
|
149
|
+
import { env } from './config/env' // ← THIS LINE IS REQUIRED!
|
|
150
|
+
|
|
110
151
|
import { serve } from '@hono/node-server'
|
|
111
152
|
import { Hono } from 'hono'
|
|
112
|
-
import {
|
|
153
|
+
import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
113
154
|
|
|
114
|
-
const
|
|
155
|
+
const port = Number(process.env.NOTIFICATIONS_PORT) || 8080 // ← Handled by template
|
|
115
156
|
|
|
157
|
+
const app = new Hono()
|
|
116
158
|
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
117
159
|
|
|
118
160
|
app.post('/notify', async (c) => {
|
|
119
161
|
const data = await c.req.json()
|
|
120
|
-
// Use validated env
|
|
162
|
+
// Use validated env vars
|
|
121
163
|
await sendPush(env.PUSHER_INSTANCE_ID, env.PUSHER_SECRET_KEY, data)
|
|
122
164
|
return c.json({ success: true })
|
|
123
165
|
})
|
|
124
166
|
|
|
125
|
-
|
|
167
|
+
consumeJetStreams({
|
|
168
|
+
streams: ['DOMAINS'],
|
|
169
|
+
consumer: 'notifications',
|
|
170
|
+
discover: './src/events/**/*.handler.ts',
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
serve({ fetch: app.fetch, port }, (info) => {
|
|
126
174
|
console.log(`Server running on http://localhost:${info.port}`)
|
|
127
175
|
})
|
|
128
176
|
```
|
|
129
177
|
|
|
130
|
-
**
|
|
131
|
-
- ✅
|
|
132
|
-
- ✅
|
|
133
|
-
- ✅
|
|
134
|
-
- ✅
|
|
178
|
+
**CRITICAL:**
|
|
179
|
+
- ✅ **MUST import `env` in `src/index.ts`** - without import, validation never runs!
|
|
180
|
+
- ✅ Import early (after telemetry, before everything else)
|
|
181
|
+
- ✅ Use `safeParse()` + `process.exit(1)` - ensures proper crash behavior
|
|
182
|
+
- ✅ Explicit exit ensures Turbo shows red X (like NestJS)
|
|
183
|
+
- ❌ **DO NOT** include `SERVICE_NAME_PORT` in env schema - already handled by CLI template
|
|
184
|
+
- ❌ **DO NOT** validate inside handlers or use-cases
|
|
135
185
|
|
|
136
186
|
---
|
|
137
187
|
|
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## 🚨 CRITICAL: AI Generation Rules
|
|
8
|
+
|
|
9
|
+
**DO NOT generate these files** - they are created by `pf new nest-micro`:
|
|
10
|
+
- ❌ `infra/services/<name>.ts` - Infrastructure config with assigned port
|
|
11
|
+
- ❌ `Dockerfile` - Container configuration
|
|
12
|
+
- ❌ `package.json` - Dependencies and scripts
|
|
13
|
+
|
|
14
|
+
**Always generate:**
|
|
15
|
+
- ✅ Modules (`src/**/*.module.ts`)
|
|
16
|
+
- ✅ Services (`src/**/*.service.ts`)
|
|
17
|
+
- ✅ Controllers (`src/**/*.controller.ts`)
|
|
18
|
+
- ✅ Tests (`src/**/*.spec.ts`)
|
|
19
|
+
- ✅ README.md
|
|
20
|
+
- ✅ Contracts (`packages/contracts/src/events/`)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
7
24
|
## 🚨 CRITICAL: Use NestJS Built-in Features
|
|
8
25
|
|
|
9
26
|
**NEVER create manual implementations when NestJS provides the feature:**
|
|
@@ -87,7 +104,7 @@ src/
|
|
|
87
104
|
├── events/
|
|
88
105
|
│ ├── events.module.ts
|
|
89
106
|
│ ├── events.service.ts
|
|
90
|
-
│ └── domain-created.
|
|
107
|
+
│ └── domain-created.handler.ts # Handler → calls service
|
|
91
108
|
└── notifications/
|
|
92
109
|
├── notifications.module.ts
|
|
93
110
|
├── notifications.service.ts # Business logic HERE
|
|
@@ -126,6 +143,7 @@ import { EventsService } from './events/events.service'
|
|
|
126
143
|
|
|
127
144
|
async function bootstrap() {
|
|
128
145
|
const app = await NestFactory.create(AppModule)
|
|
146
|
+
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
129
147
|
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
130
148
|
|
|
131
149
|
setAppContext(app)
|
|
@@ -161,21 +179,21 @@ export class EventsModule {}
|
|
|
161
179
|
|
|
162
180
|
```ts
|
|
163
181
|
import { Injectable, Logger } from '@nestjs/common'
|
|
164
|
-
import { consumeJetStreams
|
|
182
|
+
import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
165
183
|
|
|
166
184
|
@Injectable()
|
|
167
185
|
export class EventsService {
|
|
168
186
|
private readonly logger = new Logger(EventsService.name)
|
|
169
187
|
|
|
170
188
|
async startConsumers(): Promise<void> {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
189
|
+
// Services NEVER create streams!
|
|
190
|
+
// - Development: pf dev auto-creates ephemeral streams from contracts
|
|
191
|
+
// - Production: Pulumi materializes persistent streams
|
|
174
192
|
|
|
175
193
|
consumeJetStreams({
|
|
176
194
|
streams: ['ORDERS'],
|
|
177
195
|
consumer: 'my-service',
|
|
178
|
-
discover: './src/events/**/*.
|
|
196
|
+
discover: './src/events/**/*.handler.ts',
|
|
179
197
|
})
|
|
180
198
|
}
|
|
181
199
|
}
|
|
@@ -208,7 +226,7 @@ export const getService = <T>(serviceClass: Type<T>): T => {
|
|
|
208
226
|
|
|
209
227
|
---
|
|
210
228
|
|
|
211
|
-
## Event Handler (src/events/orders-created.
|
|
229
|
+
## Event Handler (src/events/orders-created.handler.ts)
|
|
212
230
|
|
|
213
231
|
**Handlers must be thin** - just log and delegate to a service:
|
|
214
232
|
|