@crossdelta/platform-sdk 0.9.4 → 0.10.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.
@@ -0,0 +1,101 @@
1
+ # Hono Service (Bun Runtime) - AI Generator Guidelines
2
+
3
+ These guidelines are for generating Hono microservices that run with **Bun runtime**.
4
+
5
+ ---
6
+
7
+ ## Entry Point Template (src/index.ts)
8
+
9
+ **Basic REST API:**
10
+ ```ts
11
+ import '@crossdelta/telemetry'
12
+
13
+ import { Hono } from 'hono'
14
+
15
+ const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 8080
16
+ const app = new Hono()
17
+
18
+ app.get('/health', (c) => c.json({ status: 'ok' }))
19
+
20
+ // Your routes here
21
+ app.get('/', (c) => c.text('Hello Hono!'))
22
+
23
+ Bun.serve({ port, fetch: app.fetch })
24
+ ```
25
+
26
+ **Event Consumer (NATS) - Multiple Streams:**
27
+ ```ts
28
+ import '@crossdelta/telemetry'
29
+
30
+ import { consumeJetStreamStreams, ensureJetStreamStreams } from '@crossdelta/cloudevents'
31
+ import { Hono } from 'hono'
32
+
33
+ const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 8080
34
+ const app = new Hono()
35
+
36
+ app.get('/health', (c) => c.json({ status: 'ok' }))
37
+
38
+ Bun.serve({ port, fetch: app.fetch })
39
+
40
+ await ensureJetStreamStreams({
41
+ streams: [
42
+ { stream: 'ORDERS', subjects: ['orders.*'] },
43
+ { stream: 'CUSTOMERS', subjects: ['customers.*'] },
44
+ ]
45
+ })
46
+
47
+ consumeJetStreamStreams({
48
+ streams: ['ORDERS', 'CUSTOMERS'],
49
+ consumer: 'my-service',
50
+ discover: './src/events/**/*.event.ts',
51
+ })
52
+ ```
53
+
54
+ **Event Publisher (REST + CloudEvents):**
55
+ ```ts
56
+ import '@crossdelta/telemetry'
57
+
58
+ import { publish } from '@crossdelta/cloudevents'
59
+ import { Hono } from 'hono'
60
+
61
+ const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 8080
62
+ const app = new Hono()
63
+
64
+ app.get('/health', (c) => c.json({ status: 'ok' }))
65
+
66
+ app.post('/orders', async (c) => {
67
+ const data = await c.req.json()
68
+ await publish('orders.created', data, { source: 'my-service' })
69
+ return c.json({ success: true })
70
+ })
71
+
72
+ Bun.serve({ port, fetch: app.fetch })
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Key Points
78
+
79
+ - ✅ Use `Bun.serve({ port, fetch: app.fetch })` for server
80
+ - ✅ Import telemetry FIRST (before any other imports)
81
+ - ✅ Use the port env var name provided in the prompt (e.g., `PUSH_NOTIFICATIONS_PORT`)
82
+ - ✅ Always include `/health` endpoint
83
+ - ✅ Keep routes simple and delegate logic to use-cases
84
+ - ✅ Use `ensureJetStreamStreams` + `consumeJetStreamStreams` for multiple event types
85
+
86
+ **DO NOT:**
87
+ - ❌ `export default Bun.serve(...)` - Bun.serve returns Server, not for export
88
+ - ❌ `export default app` - Not needed for Bun runtime
89
+ - ❌ Multiple `ensureJetStreamStream` calls - use batch version instead
90
+
91
+ ---
92
+
93
+ ## Dev Command
94
+
95
+ ```json
96
+ {
97
+ "scripts": {
98
+ "dev": "bun run --hot src/index.ts"
99
+ }
100
+ }
101
+ ```
@@ -0,0 +1,116 @@
1
+ # Hono Service (Node.js Runtime) - AI Generator Guidelines
2
+
3
+ These guidelines are for generating Hono microservices that run with **Node.js runtime** (pnpm, npm, yarn).
4
+
5
+ ---
6
+
7
+ ## Entry Point Template (src/index.ts)
8
+
9
+ **Basic REST API:**
10
+ ```ts
11
+ import '@crossdelta/telemetry'
12
+
13
+ import { serve } from '@hono/node-server'
14
+ import { Hono } from 'hono'
15
+
16
+ const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 8080
17
+ const app = new Hono()
18
+
19
+ app.get('/health', (c) => c.json({ status: 'ok' }))
20
+
21
+ // Your routes here
22
+ app.get('/', (c) => c.text('Hello Hono!'))
23
+
24
+ serve({
25
+ fetch: app.fetch,
26
+ port
27
+ }, (info) => {
28
+ console.log(`Server is running on http://localhost:${info.port}`)
29
+ })
30
+ ```
31
+
32
+ **Event Consumer (NATS) - Multiple Streams:**
33
+ ```ts
34
+ import '@crossdelta/telemetry'
35
+
36
+ import { consumeJetStreamStreams, ensureJetStreamStreams } from '@crossdelta/cloudevents'
37
+ import { serve } from '@hono/node-server'
38
+ import { Hono } from 'hono'
39
+
40
+ const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 8080
41
+ const app = new Hono()
42
+
43
+ app.get('/health', (c) => c.json({ status: 'ok' }))
44
+
45
+ serve({
46
+ fetch: app.fetch,
47
+ port
48
+ }, (info) => {
49
+ console.log(`Server is running on http://localhost:${info.port}`)
50
+ })
51
+
52
+ await ensureJetStreamStreams({
53
+ streams: [
54
+ { stream: 'ORDERS', subjects: ['orders.*'] },
55
+ { stream: 'CUSTOMERS', subjects: ['customers.*'] },
56
+ ]
57
+ })
58
+
59
+ consumeJetStreamStreams({
60
+ streams: ['ORDERS', 'CUSTOMERS'],
61
+ consumer: 'my-service',
62
+ discover: './src/events/**/*.event.ts',
63
+ })
64
+ ```
65
+
66
+ **Event Publisher (REST + CloudEvents):**
67
+ ```ts
68
+ import '@crossdelta/telemetry'
69
+
70
+ import { publish } from '@crossdelta/cloudevents'
71
+ import { serve } from '@hono/node-server'
72
+ import { Hono } from 'hono'
73
+
74
+ const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 8080
75
+ const app = new Hono()
76
+
77
+ app.get('/health', (c) => c.json({ status: 'ok' }))
78
+
79
+ app.post('/orders', async (c) => {
80
+ const data = await c.req.json()
81
+ await publish('orders.created', data, { source: 'my-service' })
82
+ return c.json({ success: true })
83
+ })
84
+
85
+ serve({
86
+ fetch: app.fetch,
87
+ port
88
+ }, (info) => {
89
+ console.log(`Server is running on http://localhost:${info.port}`)
90
+ })
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Key Points
96
+
97
+ - ✅ Use `serve()` from `@hono/node-server` (NOT `Bun.serve`)
98
+ - ✅ Import `serve` from `@hono/node-server`
99
+ - ✅ Import telemetry FIRST (before any other imports)
100
+ - ✅ Use the port env var name provided in the prompt (e.g., `API_GATEWAY_PORT`)
101
+ - ✅ Always include `/health` endpoint
102
+ - ✅ Include callback with console.log for server startup
103
+ - ✅ Keep routes simple and delegate logic to use-cases
104
+ - ✅ Use `ensureJetStreamStreams` + `consumeJetStreamStreams` for multiple event types
105
+
106
+ ---
107
+
108
+ ## Dev Command
109
+
110
+ ```json
111
+ {
112
+ "scripts": {
113
+ "dev": "tsx watch src/index.ts"
114
+ }
115
+ }
116
+ ```
@@ -0,0 +1,265 @@
1
+ # NestJS Service - AI Generator Guidelines
2
+
3
+ These guidelines are for generating NestJS microservices.
4
+
5
+ **For handler location and structure rules (shared for all frameworks), see:** [Service Generator - Handler Location & Structure](./service.md#handler-location--structure-critical)
6
+
7
+ ---
8
+
9
+ ## 🚨 CRITICAL: Commands Block (REQUIRED - MUST BE FIRST IN YOUR RESPONSE)
10
+
11
+ Your response **MUST** start with a commands block that scaffolds the NestJS service structure:
12
+
13
+ ```commands
14
+ pf new nest-micro <service-path> -y
15
+ ```
16
+
17
+ **Example:** If user asks for "orders service", generate:
18
+ ```commands
19
+ pf new nest-micro services/orders -y
20
+ ```
21
+
22
+ This command creates: `package.json`, `tsconfig.json`, NestJS structure, and basic files.
23
+ **Without this command, the service cannot be built or run!**
24
+
25
+ - Services MUST be in `services/` directory
26
+ - User says "orders" → Generate path: `services/orders`
27
+ - Always include `-y` flag to skip prompts
28
+
29
+ ---
30
+
31
+ ## NestJS Event Consumer Architecture
32
+
33
+ For NestJS event consumers, we use NestJS's application context to bridge
34
+ `@crossdelta/cloudevents` handlers with NestJS's dependency injection system.
35
+
36
+ ### Entry Point (src/main.ts)
37
+
38
+ ```ts
39
+ import '@crossdelta/telemetry'
40
+
41
+ import { NestFactory } from '@nestjs/core'
42
+ import { AppModule } from './app.module'
43
+ import { EventsService } from './events/events.service'
44
+
45
+ async function bootstrap() {
46
+ const app = await NestFactory.create(AppModule)
47
+ const port = Number(process.env.MY_SERVICE_PORT) || 8080
48
+
49
+ // Get EventsService from DI container and start consuming
50
+ const eventsService = app.get(EventsService)
51
+ await eventsService.startConsumers()
52
+
53
+ await app.listen(port)
54
+ console.log(`Service running on http://localhost:${port}`)
55
+ }
56
+
57
+ bootstrap()
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Events Module (src/events/events.module.ts)
63
+
64
+ ```ts
65
+ import { Module } from '@nestjs/common'
66
+ import { EventsService } from './events.service'
67
+
68
+ @Module({
69
+ providers: [EventsService],
70
+ exports: [EventsService],
71
+ })
72
+ export class EventsModule {}
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Events Service (src/events/events.service.ts)
78
+
79
+ ```ts
80
+ import { Injectable, Logger } from '@nestjs/common'
81
+ import { consumeJetStreamStreams, ensureJetStreamStreams } from '@crossdelta/cloudevents'
82
+
83
+ @Injectable()
84
+ export class EventsService {
85
+ private readonly logger = new Logger(EventsService.name)
86
+
87
+ async startConsumers(): Promise<void> {
88
+ await ensureJetStreamStreams({
89
+ streams: [
90
+ { stream: 'ORDERS', subjects: ['orders.*'] },
91
+ ],
92
+ })
93
+
94
+ consumeJetStreamStreams({
95
+ streams: ['ORDERS'],
96
+ consumer: 'my-service',
97
+ discover: './src/events/**/*.event.ts',
98
+ })
99
+ }
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Accessing NestJS Services in Handlers
106
+
107
+ For handlers that need NestJS DI (database, external services), use the app context:
108
+
109
+ ### App Context Singleton (src/app.context.ts)
110
+
111
+ ```ts
112
+ import type { INestApplication, Type } from '@nestjs/common'
113
+
114
+ let app: INestApplication
115
+
116
+ export const setAppContext = (nestApp: INestApplication): void => {
117
+ app = nestApp
118
+ }
119
+
120
+ export const getAppContext = (): INestApplication => {
121
+ if (!app) throw new Error('App context not initialized')
122
+ return app
123
+ }
124
+
125
+ export const getService = <T>(serviceClass: Type<T>): T => {
126
+ return getAppContext().get(serviceClass)
127
+ }
128
+ ```
129
+
130
+ ### Updated main.ts with App Context
131
+
132
+ ```ts
133
+ import '@crossdelta/telemetry'
134
+
135
+ import { NestFactory } from '@nestjs/core'
136
+ import { AppModule } from './app.module'
137
+ import { setAppContext } from './app.context'
138
+ import { EventsService } from './events/events.service'
139
+
140
+ async function bootstrap() {
141
+ const app = await NestFactory.create(AppModule)
142
+ const port = Number(process.env.MY_SERVICE_PORT) || 8080
143
+
144
+ // Make app context available to event handlers
145
+ setAppContext(app)
146
+
147
+ // Start event consumers
148
+ const eventsService = app.get(EventsService)
149
+ await eventsService.startConsumers()
150
+
151
+ await app.listen(port)
152
+ console.log(`Service running on http://localhost:${port}`)
153
+ }
154
+
155
+ bootstrap()
156
+ ```
157
+
158
+ ### Handler with DI Access (src/events/orders-created.event.ts)
159
+
160
+ ```ts
161
+ import { handleEvent } from '@crossdelta/cloudevents'
162
+ import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
163
+ import { getService } from '../app.context'
164
+ import { OrdersService } from '../orders/orders.service'
165
+
166
+ export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
167
+ // Get service from NestJS DI container
168
+ const ordersService = getService(OrdersService)
169
+ await ordersService.processOrder(data)
170
+ })
171
+ ```
172
+
173
+ ### Orders Service (src/orders/orders.service.ts)
174
+
175
+ ```ts
176
+ import { Injectable, Logger } from '@nestjs/common'
177
+ import type { OrdersCreatedData } from '{{scope}}/contracts'
178
+
179
+ @Injectable()
180
+ export class OrdersService {
181
+ private readonly logger = new Logger(OrdersService.name)
182
+
183
+ async processOrder(data: OrdersCreatedData): Promise<void> {
184
+ this.logger.log(`Processing order: ${data.orderId}`)
185
+ // Business logic with full DI access
186
+ }
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ## App Module Structure
193
+
194
+ ```ts
195
+ // src/app.module.ts
196
+ import { Module } from '@nestjs/common'
197
+ import { AppController } from './app.controller'
198
+ import { EventsModule } from './events/events.module'
199
+ import { OrdersModule } from './orders/orders.module'
200
+
201
+ @Module({
202
+ imports: [EventsModule, OrdersModule],
203
+ controllers: [AppController],
204
+ })
205
+ export class AppModule {}
206
+ ```
207
+
208
+ ```ts
209
+ // src/app.controller.ts
210
+ import { Controller, Get } from '@nestjs/common'
211
+
212
+ @Controller()
213
+ export class AppController {
214
+ @Get('health')
215
+ health() {
216
+ return { status: 'ok' }
217
+ }
218
+ }
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Testing
224
+
225
+ ### Unit Test for Service
226
+
227
+ ```ts
228
+ // src/orders/orders.service.spec.ts
229
+ import { Test, TestingModule } from '@nestjs/testing'
230
+ import { OrdersService } from './orders.service'
231
+
232
+ describe('OrdersService', () => {
233
+ let service: OrdersService
234
+
235
+ beforeEach(async () => {
236
+ const module: TestingModule = await Test.createTestingModule({
237
+ providers: [OrdersService],
238
+ }).compile()
239
+
240
+ service = module.get<OrdersService>(OrdersService)
241
+ })
242
+
243
+ it('should process order', async () => {
244
+ const mockData = {
245
+ orderId: 'ord_123',
246
+ customerId: 'cust_456',
247
+ total: 99.99,
248
+ }
249
+
250
+ await expect(service.processOrder(mockData)).resolves.not.toThrow()
251
+ })
252
+ })
253
+ ```
254
+
255
+ ---
256
+
257
+ ## Dev Command
258
+
259
+ ```json
260
+ {
261
+ "scripts": {
262
+ "start:dev": "nest start --watch"
263
+ }
264
+ }
265
+ ```