@crossdelta/platform-sdk 0.16.0 → 0.16.2

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.
@@ -3,6 +3,7 @@
3
3
  ## 🚨 CRITICAL: AI Generation Rules
4
4
 
5
5
  **DO NOT create from scratch:**
6
+ - ❌ `src/index.ts` - Entry point created by CLI with port configuration
6
7
  - ❌ `infra/services/<name>.ts` - Created by CLI with port assignment
7
8
  - ❌ `Dockerfile` - Created by CLI
8
9
  - ❌ `package.json` - Created by CLI
@@ -31,9 +32,16 @@ pf new hono-micro services/push-notifications -y
31
32
 
32
33
  ## Entry Point (src/index.ts)
33
34
 
35
+ **⚠️ CRITICAL: Import Order**
36
+ 1. **Environment validation** (`./config/env`) - Validate env vars before loading any modules
37
+ 2. **Telemetry** (`@crossdelta/telemetry`) - Patch modules before they're imported
38
+ 3. All other imports follow
39
+
34
40
  **REST API:**
35
41
  ```ts
42
+ import './config/env'
36
43
  import '@crossdelta/telemetry'
44
+
37
45
  import { Hono } from 'hono'
38
46
 
39
47
  // Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
@@ -49,7 +57,9 @@ console.log(`Service running on http://localhost:${port}`)
49
57
 
50
58
  **Event Consumer:**
51
59
  ```ts
60
+ import './config/env'
52
61
  import '@crossdelta/telemetry'
62
+
53
63
  import { consumeJetStreams } from '@crossdelta/cloudevents'
54
64
  import { Hono } from 'hono'
55
65
 
@@ -80,7 +90,9 @@ consumeJetStreams({
80
90
 
81
91
  **Event Publisher:**
82
92
  ```ts
93
+ import './config/env'
83
94
  import '@crossdelta/telemetry'
95
+
84
96
  import { publish } from '@crossdelta/cloudevents'
85
97
  import { Hono } from 'hono'
86
98
 
@@ -102,9 +114,68 @@ console.log(`Service running on http://localhost:${port}`)
102
114
 
103
115
  ---
104
116
 
105
- ## Environment Validation (Optional)
117
+ ## Environment Validation (src/config/env.ts)
118
+
119
+ **⚠️ REQUIRED: Environment validation must be created for all services**
120
+
121
+ The scaffold automatically creates `src/config/env.ts` with basic validation.
122
+
123
+ **Default generated file:**
124
+
125
+ ```ts
126
+ import { z } from 'zod'
127
+
128
+ const envSchema = z.object({
129
+ MY_SERVICE_PORT: z.string().optional(),
130
+ })
131
+
132
+ const result = envSchema.safeParse(process.env)
133
+ if (!result.success) {
134
+ console.error('❌ Environment validation failed:')
135
+ console.error(result.error.format())
136
+ process.exit(1)
137
+ }
138
+
139
+ export const env = result.data
140
+ ```
141
+
142
+ **Add required environment variables:**
143
+
144
+ ```ts
145
+ import { z } from 'zod'
146
+
147
+ const envSchema = z.object({
148
+ // Port is optional (has fallback in index.ts)
149
+ MY_SERVICE_PORT: z.string().optional(),
150
+
151
+ // Add your required env vars here
152
+ DATABASE_URL: z.string().url(),
153
+ API_KEY: z.string().min(1, 'API_KEY is required'),
154
+ REDIS_URL: z.string().url().optional(),
155
+ })
156
+
157
+ const result = envSchema.safeParse(process.env)
158
+ if (!result.success) {
159
+ console.error('❌ Environment validation failed:')
160
+ console.error(result.error.format())
161
+ process.exit(1)
162
+ }
163
+
164
+ export const env = result.data
165
+ ```
166
+
167
+ **Rules:**
168
+ - ✅ Use `safeParse()` + explicit error handling for better error messages
169
+ - ✅ Export validated `env` object for type-safe access
170
+ - ✅ Use Zod v4 syntax (no params on `.email()`, `.url()`, etc.)
171
+ - ✅ Port is always optional (has fallback in index.ts)
172
+ - ❌ DO NOT add PORT to env schema - it's read directly from process.env
173
+
174
+ ---
175
+
176
+ ## Environment Validation (Optional - Legacy)
106
177
 
107
- **For services with required env vars:**
178
+ **For services with required env vars (DEPRECATED - use src/config/env.ts instead):**
108
179
 
109
180
  ### 1️⃣ Create `src/config/env.ts`:
110
181
 
@@ -3,6 +3,7 @@
3
3
  ## 🚨 CRITICAL: AI Generation Rules
4
4
 
5
5
  **DO NOT generate these files** - they are created by `pf new hono-micro`:
6
+ - ❌ `src/index.ts` - Entry point with port configuration
6
7
  - ❌ `infra/services/<name>.ts` - Infrastructure config with assigned port
7
8
  - ❌ `Dockerfile` - Container configuration
8
9
  - ❌ `package.json` - Dependencies and scripts
@@ -31,9 +32,16 @@ pf new hono-micro services/push-notifications -y
31
32
 
32
33
  ## Entry Point (src/index.ts)
33
34
 
35
+ **⚠️ CRITICAL: Import Order**
36
+ 1. **Environment validation** (`./config/env`) - Validate env vars before loading any modules
37
+ 2. **Telemetry** (`@crossdelta/telemetry`) - Patch modules before they're imported
38
+ 3. All other imports follow
39
+
34
40
  **REST API:**
35
41
  ```ts
42
+ import './config/env'
36
43
  import '@crossdelta/telemetry'
44
+
37
45
  import { serve } from '@hono/node-server'
38
46
  import { Hono } from 'hono'
39
47
 
@@ -50,7 +58,9 @@ serve({ fetch: app.fetch, port }, (info) => {
50
58
 
51
59
  **Event Consumer:**
52
60
  ```ts
61
+ import './config/env'
53
62
  import '@crossdelta/telemetry'
63
+
54
64
  import { consumeJetStreams } from '@crossdelta/cloudevents'
55
65
  import { serve } from '@hono/node-server'
56
66
  import { Hono } from 'hono'
@@ -83,7 +93,9 @@ consumeJetStreams({
83
93
 
84
94
  **Event Publisher:**
85
95
  ```ts
96
+ import './config/env'
86
97
  import '@crossdelta/telemetry'
98
+
87
99
  import { publish } from '@crossdelta/cloudevents'
88
100
  import { serve } from '@hono/node-server'
89
101
  import { Hono } from 'hono'
@@ -107,6 +119,64 @@ serve({ fetch: app.fetch, port }, (info) => {
107
119
 
108
120
  ---
109
121
 
122
+ ## Environment Validation (src/config/env.ts)
123
+
124
+ **⚠️ REQUIRED: Environment validation must be created for all services**
125
+
126
+ The scaffold automatically creates `src/config/env.ts` with basic validation.
127
+
128
+ **Default generated file:**
129
+
130
+ ```ts
131
+ import { z } from 'zod'
132
+
133
+ const envSchema = z.object({
134
+ MY_SERVICE_PORT: z.string().optional(),
135
+ })
136
+
137
+ const result = envSchema.safeParse(process.env)
138
+ if (!result.success) {
139
+ console.error('❌ Environment validation failed:')
140
+ console.error(result.error.format())
141
+ process.exit(1)
142
+ }
143
+
144
+ export const env = result.data
145
+ ```
146
+
147
+ **Add required environment variables:**
148
+
149
+ ```ts
150
+ import { z } from 'zod'
151
+
152
+ const envSchema = z.object({
153
+ // Port is optional (has fallback in index.ts)
154
+ MY_SERVICE_PORT: z.string().optional(),
155
+
156
+ // Add your required env vars here
157
+ DATABASE_URL: z.string().url(),
158
+ API_KEY: z.string().min(1, 'API_KEY is required'),
159
+ REDIS_URL: z.string().url().optional(),
160
+ })
161
+
162
+ const result = envSchema.safeParse(process.env)
163
+ if (!result.success) {
164
+ console.error('❌ Environment validation failed:')
165
+ console.error(result.error.format())
166
+ process.exit(1)
167
+ }
168
+
169
+ export const env = result.data
170
+ ```
171
+
172
+ **Rules:**
173
+ - ✅ Use `safeParse()` + explicit error handling for better error messages
174
+ - ✅ Export validated `env` object for type-safe access
175
+ - ✅ Use Zod v4 syntax (no params on `.email()`, `.url()`, etc.)
176
+ - ✅ Port is always optional (has fallback in index.ts)
177
+ - ❌ DO NOT add PORT to env schema - it's read directly from process.env
178
+
179
+ ---
110
180
  ## Environment Validation (Optional)
111
181
 
112
182
  **For services with required env vars:**
@@ -7,6 +7,7 @@
7
7
  ## 🚨 CRITICAL: AI Generation Rules
8
8
 
9
9
  **DO NOT generate these files** - they are created by `pf new nest-micro`:
10
+ - ❌ `src/main.ts` - Entry point with port configuration
10
11
  - ❌ `infra/services/<name>.ts` - Infrastructure config with assigned port
11
12
  - ❌ `Dockerfile` - Container configuration
12
13
  - ❌ `package.json` - Dependencies and scripts
@@ -39,7 +40,10 @@
39
40
  import { z } from 'zod'
40
41
 
41
42
  export const envSchema = z.object({
42
- PORT: z.string().transform(Number).default('8080'),
43
+ // ❌ WRONG: Don't define service port - it's set by CLI via SERVICE_NAME_PORT
44
+ // PORT: z.string().transform(Number).default('8080'),
45
+
46
+ // ✅ CORRECT: Only define service-specific env vars
43
47
  PUSHER_INSTANCE_ID: z.string().min(1),
44
48
  PUSHER_SECRET_KEY: z.string().min(1),
45
49
  NATS_URL: z.string().url().default('nats://localhost:4222'),
@@ -48,6 +52,12 @@ export const envSchema = z.object({
48
52
  export type Env = z.infer<typeof envSchema>
49
53
  ```
50
54
 
55
+ **⚠️ CRITICAL: Port Configuration**
56
+ - **DO NOT** define `PORT` or `SERVICE_NAME_PORT` in `env.schema.ts`
57
+ - Service port is automatically set by CLI via `MY_SERVICE_PORT` env variable
58
+ - Read port in `main.ts`: `const port = Number(process.env.MY_SERVICE_PORT) || 8080`
59
+ - Port is assigned during `pf new nest-micro` and stored in `.env.local`
60
+
51
61
  ```ts
52
62
  // src/app.module.ts
53
63
  import { Module } from '@nestjs/common'
@@ -77,7 +87,7 @@ export class MyService {
77
87
 
78
88
  doSomething() {
79
89
  const instanceId = this.config.get('PUSHER_INSTANCE_ID') // Type-safe!
80
- const port = this.config.get('PORT') // number (auto-transformed)
90
+ const natsUrl = this.config.get('NATS_URL') // string with default
81
91
  }
82
92
  }
83
93
  ```
@@ -32,12 +32,14 @@ const port = Number(process.env.MY_HONO_SERVICE_PORT) || 8080
32
32
  - The port is written to `.env.local` (e.g., `MY_SERVICE_PORT=4001`)
33
33
  - Service code MUST use the correct env variable name
34
34
  - DO NOT change the port in generated files!
35
+ - **DO NOT include port in env schemas** (e.g., Zod schemas, ConfigModule validation)
35
36
 
36
37
  **When generating service code:**
37
38
  1. Determine the correct env variable name from service name
38
39
  2. Use ONLY that variable: `process.env.MY_SERVICE_PORT`
39
40
  3. DO NOT use generic `PORT` - it causes conflicts!
40
41
  4. Keep the `|| 8080` fallback for when `.env.local` is missing
42
+ 5. **DO NOT add port to env.schema.ts** - it's read directly from process.env
41
43
 
42
44
  ---
43
45
 
@@ -254,7 +256,11 @@ The exact command depends on the framework - see the framework-specific docs:
254
256
  5. Health check endpoint
255
257
  6. Dockerfile
256
258
 
257
- **⚠️ AI MUST NOT generate `infra/services/<name>.ts`** - This file is automatically created by the CLI with the correct port assignment!
259
+ **⚠️ AI MUST NOT generate these files** - They are automatically created by the CLI:
260
+ - ❌ `infra/services/<name>.ts` - CLI creates with correct port assignment
261
+ - ❌ `src/index.ts` (Hono) or `src/main.ts` (NestJS) - CLI creates with correct entry point
262
+ - ❌ `package.json` - CLI creates with correct dependencies
263
+ - ❌ `Dockerfile` - CLI creates with correct runtime
258
264
 
259
265
  ---
260
266
 
@@ -382,6 +388,8 @@ pf event add orders.created --service services/my-service
382
388
  @pusher/push-notifications-server
383
389
  ```
384
390
 
391
+ > **Note:** When using external packages, always check the official documentation for the current API. Example: `@pusher/push-notifications-server` - check [npmjs.com](https://www.npmjs.com/package/@pusher/push-notifications-server) for latest usage patterns and avoid deprecated methods.
392
+
385
393
  ---
386
394
 
387
395
  ## 🚨 CRITICAL: Schema Fields Consistency
@@ -425,40 +433,68 @@ export const OrdersCreatedSchema = z.object({
425
433
 
426
434
  ## Naming Convention
427
435
 
428
- **Architecture Rule:**
436
+ **🚨 CRITICAL: Singular vs. Plural Architecture Rule**
429
437
 
430
438
  | Component | Format | Example | Reason |
431
439
  |-----------|--------|---------|--------|
432
- | **Event Type** | **Singular** | `domain.created` | Describes a single event |
433
- | **Stream** | **PLURAL** | `DOMAINS` | Collection of events for a domain |
440
+ | **Event Type** | **Singular** | `customer.created` | Describes a single event |
441
+ | **Event Folder** | **PLURAL** | `customers/` | Domain grouping (matches stream) |
442
+ | **Stream** | **PLURAL** | `CUSTOMERS` | Collection of events for a domain |
443
+ | **Mock Files** | **PLURAL folder** | `customers/created.mock.json` | Must match event folder |
444
+
445
+ **Merksatz:**
446
+ - **Singular** describes an event
447
+ - **Plural** describes a domain
434
448
 
435
- **Merksatz:** Events sind singular. Streams sind plural.
449
+ ### File Structure Example
450
+
451
+ ```
452
+ packages/contracts/src/events/
453
+ ├── customers/ ✅ PLURAL (domain)
454
+ │ ├── created.ts (customer.created event)
455
+ │ ├── created.mock.json ✅ PLURAL folder
456
+ │ └── updated.ts (customer.updated event)
457
+ └── orders/ ✅ PLURAL (domain)
458
+ ├── created.ts (order.created event)
459
+ └── created.mock.json ✅ PLURAL folder
460
+
461
+ // ❌ WRONG - Mixed singular/plural:
462
+ packages/contracts/src/events/
463
+ ├── customers/created.ts ✅ PLURAL
464
+ └── customer/created.mock.json ❌ SINGULAR - causes inconsistency!
465
+ ```
436
466
 
437
- ### Examples
467
+ ### Contract Examples
438
468
 
439
- | Event Type | Contract | File | Stream |
440
- |------------|----------|------|--------|
441
- | `orders.created` | `OrdersCreatedContract` | `orders-created.ts` | `ORDERS` |
442
- | `domain.created` | `DomainCreatedContract` | `domain-created.ts` | `DOMAINS` ✅ |
469
+ ### Contract Examples
470
+
471
+ | Event Type | Contract | Folder | Stream |
472
+ |------------|----------|--------|--------|
473
+ | `customer.created` | `CustomerCreatedContract` | `customers/` | `CUSTOMERS` ✅ |
474
+ | `order.created` | `OrderCreatedContract` | `orders/` | `ORDERS` ✅ |
475
+ | `domain.created` | `DomainCreatedContract` | `domains/` | `DOMAINS` ✅ |
443
476
 
444
477
  ```typescript
445
478
  // ✅ CORRECT
446
- export const DomainCreatedContract = createContract({
447
- type: 'domain.created', // Singular event
448
- channel: { stream: 'DOMAINS' }, // Plural stream
449
- schema: DomainCreatedSchema,
479
+ export const CustomerCreatedContract = createContract({
480
+ type: 'customer.created', // Singular event
481
+ channel: { stream: 'CUSTOMERS' }, // Plural stream
482
+ schema: CustomerCreatedSchema,
450
483
  })
451
484
 
452
- export const OrdersCreatedContract = createContract({
453
- type: 'orders.created', // Singular event (but namespace plural!)
454
- channel: { stream: 'ORDERS' }, // Plural stream
455
- schema: OrdersCreatedSchema,
485
+ // Folder: packages/contracts/src/events/customers/created.ts ✅ PLURAL
486
+ // Mock: packages/contracts/src/events/customers/created.mock.json PLURAL
487
+
488
+ // ❌ WRONG - Singular stream
489
+ export const CustomerCreatedContract = createContract({
490
+ type: 'customer.created',
491
+ channel: { stream: 'CUSTOMER' }, // ❌ Must be CUSTOMERS (plural!)
492
+ schema: CustomerCreatedSchema,
456
493
  })
457
494
 
458
- // ❌ WRONG
459
- export const DomainCreatedContract = createContract({
460
- type: 'domain.created',
461
- channel: { stream: 'DOMAIN' }, // ❌ Must be DOMAINS (plural!)
495
+ // ❌ WRONG - Mixed folders
496
+ // Folder: packages/contracts/src/events/customers/created.ts ✅
497
+ // Mock: packages/contracts/src/events/customer/created.mock.json ❌ Singular!
462
498
  schema: DomainCreatedSchema,
463
499
  })
464
500
  ```
@@ -509,6 +545,14 @@ consumeJetStreams({
509
545
  | `post-commands` | Runs after files created (e.g., `pf event add`) |
510
546
  | `dependencies` | Extra npm packages (NOT `@crossdelta/*`) |
511
547
 
548
+ **When adding dependencies:**
549
+ - ✅ Search npm for the latest stable version
550
+ - ✅ Check package README for current API usage
551
+ - ✅ Avoid deprecated APIs - look for deprecation warnings in docs
552
+ - ✅ Prefer packages with TypeScript definitions (`@types/*` or built-in)
553
+ - ✅ Check GitHub for recent activity and TypeScript support
554
+ - ⚠️ If using an unfamiliar package, mention: "Check official docs for latest API"
555
+
512
556
  ---
513
557
 
514
558
  ## Service Types
@@ -561,4 +605,6 @@ packages/contracts/src/events/
561
605
  - ✅ NestJS: Business logic in Services + pure helper functions
562
606
  - ✅ Log event type and key identifier in handlers
563
607
  - ✅ Keep handlers thin - delegate to use-cases/services
564
- - ✅ Verify package names on npmjs.com before using
608
+ - ✅ Use current, non-deprecated APIs - check package documentation
609
+ - ✅ Prefer TypeScript-first packages with good type definitions
610
+ - ✅ Check npm for latest package versions and breaking changes
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod'
2
+
3
+ const envSchema = z.object({
4
+ {{envKey}}_PORT: z.string().optional(),
5
+ })
6
+
7
+ const result = envSchema.safeParse(process.env)
8
+ if (!result.success) {
9
+ console.error('❌ Environment validation failed:')
10
+ console.error(result.error.format())
11
+ process.exit(1)
12
+ }
13
+
14
+ export const env = result.data
@@ -1,4 +1,4 @@
1
- // IMPORTANT: telemetry must be imported first to patch modules before they're loaded
1
+ import './config/env'
2
2
  import '@crossdelta/telemetry'
3
3
 
4
4
  import { Hono } from 'hono'
@@ -7,7 +7,7 @@
7
7
  "pulumi": "pulumi"
8
8
  },
9
9
  "dependencies": {
10
- "@crossdelta/cloudevents": "^0.5.3",
10
+ "@crossdelta/cloudevents": "^0.5.4",
11
11
  "@crossdelta/infrastructure": "^0.5.3",
12
12
  "{{scope}}/contracts": "workspace:*",
13
13
  "@pulumi/digitalocean": "^4.55.0",
@@ -11,7 +11,7 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@crossdelta/cloudevents": "^0.5.3",
14
+ "@crossdelta/cloudevents": "^0.5.4",
15
15
  "@crossdelta/infrastructure": "^0.5.3",
16
16
  "zod": "^4.0.0"
17
17
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/platform-sdk",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "Platform toolkit for event-driven microservices — keeping code and infrastructure in lockstep.",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,72 +0,0 @@
1
- # Copilot Instructions (Project-Agnostic, TypeScript-Focused)
2
-
3
- You are assisting in TypeScript-first monorepos using modern TypeScript tooling such as Bun or Node, Turborepo, Hono, NestJS, Zod, event-driven patterns, and Pulumi/Kubernetes infrastructure.
4
-
5
- Always generate **minimal diffs**, never full rewrites.
6
-
7
- ## General Behavior
8
- - Produce short, focused, code-first answers.
9
- - Modify only the necessary parts.
10
- - Analyze only the opened file unless explicitly asked.
11
- - Follow existing architecture and naming conventions.
12
- - Prefer strict typing; avoid `any`.
13
- - **Reuse existing code**: Before implementing new functionality, search the codebase for existing functions, utilities, or services that can be reused. Avoid duplicating logic.
14
-
15
- ## Code Style
16
- - Single quotes, no semicolons, 2-space indent, trailing commas.
17
- - Alphabetically sorted imports; no unused imports.
18
- - Arrow functions over `function` declarations.
19
- - Template literals over string concatenation.
20
- - Prefer pure and functional programming patterns (map/filter/reduce).
21
- - Avoid mutable state and imperative loops.
22
- - No decorative section header blocks (no ASCII art separators like `// ─────────────`).
23
- - Use blank lines to separate logical sections naturally.
24
- - Organize code: types → helpers → higher-order functions.
25
- - Use JSDoc for exported functions and complex logic; keep inline comments minimal.
26
- - Self-documenting code over comments: clear naming, pure functions, obvious control flow.
27
-
28
- ## Validation & Types
29
- - Use Zod for schemas.
30
- - Export inferred types using `z.infer`.
31
- - Do NOT include literal event types inside schemas.
32
- - Do not duplicate validation logic across layers.
33
-
34
- ## Service & Module Structure
35
- - Entry points handle wiring (server start, telemetry, routing, consumers).
36
- - Business logic lives in use-case modules.
37
- - Event handlers must be thin wrappers around use-cases.
38
-
39
- ## Event Handling
40
- - Use shared CloudEvents/messaging libraries, not raw clients.
41
- - Handlers: Schema → inferred type → thin handler → use-case delegation.
42
- - Handlers named `*.event.ts`.
43
-
44
- ## Infrastructure
45
- - Use fluent port builders when available.
46
- - Expose a `/health` endpoint.
47
- - Avoid legacy containerPort usage.
48
-
49
- ## Testing
50
- - Test use-cases, not handlers or frameworks.
51
- - Prefer simple direct tests, no mocks.
52
- - Validate both error and success paths.
53
-
54
- ## AI Output Format
55
- - Provide only necessary files.
56
- - Use relative paths.
57
- - Keep comments minimal.
58
- - Do not generate abstractions not already present.
59
-
60
- ## Service-Specific Guidelines
61
-
62
- When working with specific service types or packages, refer to these detailed guidelines:
63
-
64
- ### Service Generation & Architecture
65
- - [CLI Service Generator](../packages/platform-sdk/docs/generators/service.md) - AI code generation rules
66
-
67
- ### Key Packages
68
- - [@crossdelta/cloudevents](../packages/cloudevents/README.md) - Event handling with NATS and CloudEvents
69
- - [@crossdelta/telemetry](../packages/telemetry/README.md) - OpenTelemetry instrumentation
70
- - [@crossdelta/infrastructure](../packages/infrastructure/README.md) - Pulumi/K8s configuration
71
-
72
- **When generating services**: Always check the appropriate guidelines above to ensure correct patterns, especially for event-driven architectures.