@crossdelta/platform-sdk 0.13.1 → 0.13.3
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/package.json +3 -1
- package/scripts/postinstall.js +53 -0
- package/bin/cli.js +0 -312
- package/bin/docs/generators/README.md +0 -56
- package/bin/docs/generators/code-style.md +0 -96
- package/bin/docs/generators/hono-bun.md +0 -181
- package/bin/docs/generators/hono-node.md +0 -194
- package/bin/docs/generators/nest.md +0 -358
- package/bin/docs/generators/service.md +0 -564
- package/bin/docs/generators/testing.md +0 -97
- package/bin/integration.collection.json +0 -18
- package/bin/templates/hono-microservice/Dockerfile.hbs +0 -16
- package/bin/templates/hono-microservice/biome.json.hbs +0 -3
- package/bin/templates/hono-microservice/src/index.ts.hbs +0 -18
- package/bin/templates/hono-microservice/tsconfig.json.hbs +0 -14
- package/bin/templates/nest-microservice/Dockerfile.hbs +0 -37
- package/bin/templates/nest-microservice/biome.json.hbs +0 -3
- package/bin/templates/nest-microservice/src/app.context.ts.hbs +0 -17
- package/bin/templates/nest-microservice/src/events/events.module.ts.hbs +0 -8
- package/bin/templates/nest-microservice/src/events/events.service.ts.hbs +0 -22
- package/bin/templates/nest-microservice/src/main.ts.hbs +0 -34
- package/bin/templates/workspace/.github/README.md +0 -70
- package/bin/templates/workspace/.github/actions/check-image-tag-exists/action.yml +0 -27
- package/bin/templates/workspace/.github/actions/check-image-tag-exists/index.js +0 -179
- package/bin/templates/workspace/.github/actions/check-path-changes/action.yml +0 -21
- package/bin/templates/workspace/.github/actions/check-path-changes/index.js +0 -192
- package/bin/templates/workspace/.github/actions/detect-skipped-services/action.yml +0 -38
- package/bin/templates/workspace/.github/actions/generate-scope-matrix/action.yml +0 -17
- package/bin/templates/workspace/.github/actions/generate-scope-matrix/index.js +0 -355
- package/bin/templates/workspace/.github/actions/prepare-build-context/action.yml +0 -49
- package/bin/templates/workspace/.github/actions/resolve-scope-tags/action.yml +0 -31
- package/bin/templates/workspace/.github/actions/resolve-scope-tags/index.js +0 -398
- package/bin/templates/workspace/.github/actions/setup-bun-install/action.yml.hbs +0 -57
- package/bin/templates/workspace/.github/copilot-chat-configuration.json +0 -49
- package/bin/templates/workspace/.github/copilot-instructions.md.hbs +0 -72
- package/bin/templates/workspace/.github/dependabot.yml +0 -18
- package/bin/templates/workspace/.github/workflows/build-and-deploy.yml.hbs +0 -228
- package/bin/templates/workspace/.github/workflows/lint-and-tests.yml.hbs +0 -32
- package/bin/templates/workspace/.github/workflows/publish-packages.yml +0 -154
- package/bin/templates/workspace/apps/.gitkeep +0 -0
- package/bin/templates/workspace/biome.json.hbs +0 -62
- package/bin/templates/workspace/bunfig.toml.hbs +0 -5
- package/bin/templates/workspace/docs/.gitkeep +0 -0
- package/bin/templates/workspace/editorconfig.hbs +0 -9
- package/bin/templates/workspace/gitignore.hbs +0 -15
- package/bin/templates/workspace/infra/Pulumi.dev.yaml.hbs +0 -5
- package/bin/templates/workspace/infra/Pulumi.yaml.hbs +0 -6
- package/bin/templates/workspace/infra/index.ts.hbs +0 -56
- package/bin/templates/workspace/infra/package.json.hbs +0 -21
- package/bin/templates/workspace/infra/services/.gitkeep +0 -0
- package/bin/templates/workspace/infra/tsconfig.json.hbs +0 -15
- package/bin/templates/workspace/npmrc.hbs +0 -2
- package/bin/templates/workspace/package.json.hbs +0 -51
- package/bin/templates/workspace/packages/.gitkeep +0 -0
- package/bin/templates/workspace/packages/contracts/README.md.hbs +0 -166
- package/bin/templates/workspace/packages/contracts/package.json.hbs +0 -22
- package/bin/templates/workspace/packages/contracts/src/events/index.ts +0 -16
- package/bin/templates/workspace/packages/contracts/src/index.ts +0 -10
- package/bin/templates/workspace/packages/contracts/src/stream-policies.ts.hbs +0 -40
- package/bin/templates/workspace/packages/contracts/tsconfig.json.hbs +0 -7
- package/bin/templates/workspace/pnpm-workspace.yaml.hbs +0 -5
- package/bin/templates/workspace/turbo.json +0 -37
- package/bin/templates/workspace/turbo.json.hbs +0 -29
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# Generator Documentation
|
|
2
|
-
|
|
3
|
-
This directory contains AI instructions for code generators.
|
|
4
|
-
|
|
5
|
-
## Structure
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
generators/
|
|
9
|
-
├── README.md # This file
|
|
10
|
-
├── service.md # Main service generator instructions
|
|
11
|
-
├── code-style.md # Code style, formatting, naming conventions
|
|
12
|
-
├── testing.md # Testing rules and patterns
|
|
13
|
-
├── hono-bun.md # Hono with Bun runtime specifics
|
|
14
|
-
├── hono-node.md # Hono with Node.js runtime specifics
|
|
15
|
-
└── nest.md # NestJS framework specifics
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## How Instructions Are Loaded
|
|
19
|
-
|
|
20
|
-
Instructions are configured in `package.json` under `generatorConfig.docs`:
|
|
21
|
-
|
|
22
|
-
```json
|
|
23
|
-
{
|
|
24
|
-
"generatorConfig": {
|
|
25
|
-
"docs": {
|
|
26
|
-
"base": ["service.md", "code-style.md", "testing.md"],
|
|
27
|
-
"frameworks": {
|
|
28
|
-
"hono": { "bun": "hono-bun.md", "node": "hono-node.md" },
|
|
29
|
-
"nest": "nest.md"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
**Load Order:**
|
|
37
|
-
1. **Base docs** - Always loaded (service.md, code-style.md, testing.md)
|
|
38
|
-
2. **Framework-specific** - Based on `serviceType` and `packageManager`
|
|
39
|
-
3. **Workspace instructions** - `.github/copilot-instructions.md`, `docs/ai-guidelines.md`
|
|
40
|
-
|
|
41
|
-
## Adding New Generators
|
|
42
|
-
|
|
43
|
-
To add instructions for a new framework:
|
|
44
|
-
|
|
45
|
-
1. Create `<framework>.md` in this directory
|
|
46
|
-
2. Add to `generatorConfig.docs.frameworks` in `package.json`
|
|
47
|
-
3. Follow the existing format from `hono-bun.md` or `nest.md`
|
|
48
|
-
|
|
49
|
-
## Template Variables
|
|
50
|
-
|
|
51
|
-
The following variables are replaced at runtime:
|
|
52
|
-
|
|
53
|
-
| Variable | Description |
|
|
54
|
-
|----------|-------------|
|
|
55
|
-
| `{{scope}}` | Package scope (e.g., `@orderboss`) |
|
|
56
|
-
| `{{AVAILABLE_CONTRACTS}}` | Auto-generated list of existing contracts |
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# Code Style
|
|
2
|
-
|
|
3
|
-
## Formatting
|
|
4
|
-
|
|
5
|
-
- Single quotes, no semicolons, 2-space indent, trailing commas
|
|
6
|
-
- Arrow functions over `function`
|
|
7
|
-
- Template literals over concatenation
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Imports
|
|
12
|
-
|
|
13
|
-
**Must be alphabetically sorted** (Biome enforces this):
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
// ✅ CORRECT - sorted alphabetically, type imports first
|
|
17
|
-
import type { DomainCreatedData } from '@my-platform/contracts'
|
|
18
|
-
import type { OrdersCreatedData } from '@scope/contracts'
|
|
19
|
-
import { handleEvent } from '@crossdelta/cloudevents'
|
|
20
|
-
import PusherPushNotifications from '@pusher/push-notifications-server'
|
|
21
|
-
|
|
22
|
-
// ❌ WRONG - unsorted
|
|
23
|
-
import PusherPushNotifications from '@pusher/push-notifications-server'
|
|
24
|
-
import type { DomainCreatedData } from '@my-platform/contracts'
|
|
25
|
-
|
|
26
|
-
// ❌ WRONG - missing 'type' keyword
|
|
27
|
-
import { OrdersCreatedData } from '@scope/contracts'
|
|
28
|
-
|
|
29
|
-
// ❌ WRONG - unused imports
|
|
30
|
-
import { handleEvent, publish } from '@crossdelta/cloudevents'
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## Environment Variables
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
// ✅ CORRECT
|
|
39
|
-
const apiKey = process.env.API_KEY
|
|
40
|
-
if (!apiKey) throw new Error('Missing API_KEY')
|
|
41
|
-
|
|
42
|
-
// ❌ WRONG
|
|
43
|
-
const apiKey = process.env.API_KEY! // no assertions
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## Naming
|
|
49
|
-
|
|
50
|
-
| Type | Convention | Example |
|
|
51
|
-
|------|------------|---------|
|
|
52
|
-
| Files | kebab-case | `send-notification.use-case.ts` |
|
|
53
|
-
| Contracts | PascalCase | `OrdersCreatedContract` |
|
|
54
|
-
| Types | PascalCase + Data | `OrdersCreatedData` |
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## Config Validation (Type Narrowing)
|
|
59
|
-
|
|
60
|
-
Use validation functions that return typed objects - avoids `!` assertions:
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
// ✅ CORRECT - validation returns typed object
|
|
64
|
-
export const validateBeamsConfig = (config: {
|
|
65
|
-
instanceId?: string
|
|
66
|
-
secretKey?: string
|
|
67
|
-
}): { instanceId: string; secretKey: string } => {
|
|
68
|
-
if (!config.instanceId) throw new Error('Missing PUSHER_BEAMS_INSTANCE_ID')
|
|
69
|
-
if (!config.secretKey) throw new Error('Missing PUSHER_BEAMS_SECRET_KEY')
|
|
70
|
-
return { instanceId: config.instanceId, secretKey: config.secretKey }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Usage - no assertions needed
|
|
74
|
-
const config = validateBeamsConfig({
|
|
75
|
-
instanceId: process.env.PUSHER_BEAMS_INSTANCE_ID,
|
|
76
|
-
secretKey: process.env.PUSHER_BEAMS_SECRET_KEY,
|
|
77
|
-
})
|
|
78
|
-
new PusherClient(config) // ✅ Typed correctly
|
|
79
|
-
|
|
80
|
-
// ❌ WRONG - non-null assertions (Biome error)
|
|
81
|
-
validateConfig({ instanceId, secretKey })
|
|
82
|
-
new PusherClient({ instanceId: instanceId!, secretKey: secretKey! })
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Zod 4
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
// ✅ No params
|
|
91
|
-
z.string().email()
|
|
92
|
-
z.string().datetime()
|
|
93
|
-
|
|
94
|
-
// ❌ Deprecated
|
|
95
|
-
z.string().email('Invalid')
|
|
96
|
-
```
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
# Hono (Bun Runtime)
|
|
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
|
-
|
|
19
|
-
## 🚨 CRITICAL: Commands Block (REQUIRED FIRST)
|
|
20
|
-
|
|
21
|
-
```commands
|
|
22
|
-
pf new hono-micro services/<service-name> -y
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Example:** User asks for "push notifications service" → generate:
|
|
26
|
-
```commands
|
|
27
|
-
pf new hono-micro services/push-notifications -y
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Entry Point (src/index.ts)
|
|
33
|
-
|
|
34
|
-
**REST API:**
|
|
35
|
-
```ts
|
|
36
|
-
import '@crossdelta/telemetry'
|
|
37
|
-
import { Hono } from 'hono'
|
|
38
|
-
|
|
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
|
|
42
|
-
const app = new Hono()
|
|
43
|
-
|
|
44
|
-
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
45
|
-
|
|
46
|
-
Bun.serve({ port, fetch: app.fetch })
|
|
47
|
-
console.log(`Service running on http://localhost:${port}`)
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Event Consumer:**
|
|
51
|
-
```ts
|
|
52
|
-
import '@crossdelta/telemetry'
|
|
53
|
-
import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
54
|
-
import { Hono } from 'hono'
|
|
55
|
-
|
|
56
|
-
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
57
|
-
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
58
|
-
const app = new Hono()
|
|
59
|
-
|
|
60
|
-
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
61
|
-
|
|
62
|
-
Bun.serve({ port, fetch: app.fetch })
|
|
63
|
-
console.log(`Service running on http://localhost:${port}`)
|
|
64
|
-
|
|
65
|
-
// Services NEVER create streams!
|
|
66
|
-
// - Development: pf dev auto-creates ephemeral streams from contracts
|
|
67
|
-
// - Production: Pulumi materializes persistent streams
|
|
68
|
-
consumeJetStreams({
|
|
69
|
-
streams: ['ORDERS'], // ⚠️ MUST be PLURAL! Extract from contract's channel.stream
|
|
70
|
-
consumer: 'my-service',
|
|
71
|
-
discover: './src/events/**/*.handler.ts',
|
|
72
|
-
})
|
|
73
|
-
```
|
|
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
|
-
|
|
81
|
-
**Event Publisher:**
|
|
82
|
-
```ts
|
|
83
|
-
import '@crossdelta/telemetry'
|
|
84
|
-
import { publish } from '@crossdelta/cloudevents'
|
|
85
|
-
import { Hono } from 'hono'
|
|
86
|
-
|
|
87
|
-
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
88
|
-
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
89
|
-
const app = new Hono()
|
|
90
|
-
|
|
91
|
-
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
92
|
-
|
|
93
|
-
app.post('/orders', async (c) => {
|
|
94
|
-
const data = await c.req.json()
|
|
95
|
-
await publish('orders.created', data)
|
|
96
|
-
return c.json({ success: true })
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
Bun.serve({ port, fetch: app.fetch })
|
|
100
|
-
console.log(`Service running on http://localhost:${port}`)
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Environment Validation (Optional)
|
|
106
|
-
|
|
107
|
-
**For services with required env vars:**
|
|
108
|
-
|
|
109
|
-
### 1️⃣ Create `src/config/env.ts`:
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
// src/config/env.ts
|
|
113
|
-
import { z } from 'zod'
|
|
114
|
-
|
|
115
|
-
// ⚠️ DO NOT include SERVICE_NAME_PORT here - it's already handled by template!
|
|
116
|
-
// Only validate YOUR custom env vars
|
|
117
|
-
const envSchema = z.object({
|
|
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'),
|
|
120
|
-
NATS_URL: z.string().url().default('nats://localhost:4222'),
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
export type Env = z.infer<typeof envSchema>
|
|
124
|
-
|
|
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
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### 2️⃣ **CRITICAL: Import in `src/index.ts` early!**
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
// src/index.ts
|
|
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
|
-
|
|
146
|
-
import { Hono } from 'hono'
|
|
147
|
-
|
|
148
|
-
const port = Number(process.env.MY_SERVICE_PORT) || 8080 // ← Handled by template
|
|
149
|
-
const app = new Hono()
|
|
150
|
-
|
|
151
|
-
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
152
|
-
|
|
153
|
-
app.post('/notify', async (c) => {
|
|
154
|
-
const data = await c.req.json()
|
|
155
|
-
// Use validated env vars
|
|
156
|
-
await sendPush(env.PUSHER_INSTANCE_ID, env.PUSHER_SECRET_KEY, data)
|
|
157
|
-
return c.json({ success: true })
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
Bun.serve({ port, fetch: app.fetch })
|
|
161
|
-
console.log(`Service running on http://localhost:${port}`)
|
|
162
|
-
```
|
|
163
|
-
|
|
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
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## Rules
|
|
175
|
-
|
|
176
|
-
- ✅ `Bun.serve({ port, fetch: app.fetch })`
|
|
177
|
-
- ✅ Import telemetry FIRST
|
|
178
|
-
- ✅ Always include `/health`
|
|
179
|
-
- ✅ Use port env var from prompt (e.g., `PUSH_NOTIFICATIONS_PORT`)
|
|
180
|
-
- ❌ `export default Bun.serve(...)` - wrong
|
|
181
|
-
- ❌ `export default app` - not needed
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# Hono (Node.js Runtime)
|
|
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
|
-
|
|
19
|
-
## 🚨 CRITICAL: Commands Block (REQUIRED FIRST)
|
|
20
|
-
|
|
21
|
-
```commands
|
|
22
|
-
pf new hono-micro services/<service-name> -y
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Example:** User asks for "push notifications service" → generate:
|
|
26
|
-
```commands
|
|
27
|
-
pf new hono-micro services/push-notifications -y
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Entry Point (src/index.ts)
|
|
33
|
-
|
|
34
|
-
**REST API:**
|
|
35
|
-
```ts
|
|
36
|
-
import '@crossdelta/telemetry'
|
|
37
|
-
import { serve } from '@hono/node-server'
|
|
38
|
-
import { Hono } from 'hono'
|
|
39
|
-
|
|
40
|
-
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
41
|
-
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
42
|
-
const app = new Hono()
|
|
43
|
-
|
|
44
|
-
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
45
|
-
|
|
46
|
-
serve({ fetch: app.fetch, port }, (info) => {
|
|
47
|
-
console.log(`Server running on http://localhost:${info.port}`)
|
|
48
|
-
})
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**Event Consumer:**
|
|
52
|
-
```ts
|
|
53
|
-
import '@crossdelta/telemetry'
|
|
54
|
-
import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
55
|
-
import { serve } from '@hono/node-server'
|
|
56
|
-
import { Hono } from 'hono'
|
|
57
|
-
|
|
58
|
-
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
59
|
-
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
60
|
-
const app = new Hono()
|
|
61
|
-
|
|
62
|
-
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
63
|
-
|
|
64
|
-
serve({ fetch: app.fetch, port }, (info) => {
|
|
65
|
-
console.log(`Server running on http://localhost:${info.port}`)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// Services NEVER create streams!
|
|
69
|
-
// - Development: pf dev auto-creates ephemeral streams from contracts
|
|
70
|
-
// - Production: Pulumi materializes persistent streams
|
|
71
|
-
consumeJetStreams({
|
|
72
|
-
streams: ['ORDERS'], // ⚠️ MUST be PLURAL! Extract from contract's channel.stream
|
|
73
|
-
consumer: 'my-service',
|
|
74
|
-
discover: './src/events/**/*.handler.ts',
|
|
75
|
-
})
|
|
76
|
-
```
|
|
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
|
-
|
|
84
|
-
**Event Publisher:**
|
|
85
|
-
```ts
|
|
86
|
-
import '@crossdelta/telemetry'
|
|
87
|
-
import { publish } from '@crossdelta/cloudevents'
|
|
88
|
-
import { serve } from '@hono/node-server'
|
|
89
|
-
import { Hono } from 'hono'
|
|
90
|
-
|
|
91
|
-
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
92
|
-
const port = Number(process.env.MY_SERVICE_PORT) || 8080
|
|
93
|
-
const app = new Hono()
|
|
94
|
-
|
|
95
|
-
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
96
|
-
|
|
97
|
-
app.post('/orders', async (c) => {
|
|
98
|
-
const data = await c.req.json()
|
|
99
|
-
await publish('orders.created', data)
|
|
100
|
-
return c.json({ success: true })
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
serve({ fetch: app.fetch, port }, (info) => {
|
|
104
|
-
console.log(`Server running on http://localhost:${info.port}`)
|
|
105
|
-
})
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## Environment Validation (Optional)
|
|
111
|
-
|
|
112
|
-
**For services with required env vars:**
|
|
113
|
-
|
|
114
|
-
### 1️⃣ Create `src/config/env.ts`:
|
|
115
|
-
|
|
116
|
-
```ts
|
|
117
|
-
// src/config/env.ts
|
|
118
|
-
import { z } from 'zod'
|
|
119
|
-
|
|
120
|
-
// ⚠️ DO NOT include SERVICE_NAME_PORT here - it's already handled by template!
|
|
121
|
-
// Only validate YOUR custom env vars
|
|
122
|
-
const envSchema = z.object({
|
|
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'),
|
|
125
|
-
NATS_URL: z.string().url().default('nats://localhost:4222'),
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
export type Env = z.infer<typeof envSchema>
|
|
129
|
-
|
|
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
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### 2️⃣ **CRITICAL: Import in `src/index.ts` early!**
|
|
142
|
-
|
|
143
|
-
```ts
|
|
144
|
-
// src/index.ts
|
|
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
|
-
|
|
151
|
-
import { serve } from '@hono/node-server'
|
|
152
|
-
import { Hono } from 'hono'
|
|
153
|
-
import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
154
|
-
|
|
155
|
-
const port = Number(process.env.NOTIFICATIONS_PORT) || 8080 // ← Handled by template
|
|
156
|
-
|
|
157
|
-
const app = new Hono()
|
|
158
|
-
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
159
|
-
|
|
160
|
-
app.post('/notify', async (c) => {
|
|
161
|
-
const data = await c.req.json()
|
|
162
|
-
// Use validated env vars
|
|
163
|
-
await sendPush(env.PUSHER_INSTANCE_ID, env.PUSHER_SECRET_KEY, data)
|
|
164
|
-
return c.json({ success: true })
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
consumeJetStreams({
|
|
168
|
-
streams: ['DOMAINS'],
|
|
169
|
-
consumer: 'notifications',
|
|
170
|
-
discover: './src/events/**/*.handler.ts',
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
serve({ fetch: app.fetch, port }, (info) => {
|
|
174
|
-
console.log(`Server running on http://localhost:${info.port}`)
|
|
175
|
-
})
|
|
176
|
-
```
|
|
177
|
-
|
|
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
|
|
185
|
-
|
|
186
|
-
---
|
|
187
|
-
|
|
188
|
-
## Rules
|
|
189
|
-
|
|
190
|
-
- ✅ `serve()` from `@hono/node-server`
|
|
191
|
-
- ✅ Import telemetry FIRST
|
|
192
|
-
- ✅ Always include `/health`
|
|
193
|
-
- ✅ Use port env var from prompt (e.g., `API_GATEWAY_PORT`)
|
|
194
|
-
- ❌ `Bun.serve()` - wrong runtime
|