@crossdelta/platform-sdk 0.5.12 → 0.7.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.
@@ -1,694 +1,56 @@
1
- # {{projectName}} Platform AI Coding Guidelines
2
-
3
- ## Architecture Overview
4
-
5
- Bun-based **monorepo** (Tu }).optional(),
6
- })
7
-
8
- // Export type for use in use-cases
9
- export type OrderCreatedEvent = z.infer<typeof OrderCreatedSchema>
10
-
11
- export default handleEvent() with event-driven microservices:
12
-
13
- | Directory | Stack | Purpose |
14
- |-----------|-------|---------|
15
- | `apps/storefront/` | Qwik + RxDB + Supabase | Offline-first PWA, multi-tenant |
16
- | `services/orders/` | Hono | Microservice (events via NATS) |
17
- | `services/notifications/` | Hono | Event consumer (auto-discovery) |
18
- | `services/api-gateway/` | NestJS | Optional REST entry point |
19
- | `packages/cloudevents/` | TypeScript | CloudEvents + NATS/JetStream toolkit |
20
- | `packages/telemetry/` | OpenTelemetry | Zero-config tracing/metrics |
21
- | `infra/` | Pulumi | IaC for DigitalOcean Kubernetes |
22
-
23
- **Data flow:** Storefront Supabase (realtime sync) Services publish CloudEvents NATS JetStream → Consumer services.
24
-
25
- ## Essential Commands
26
-
27
- ```bash
28
- bun install # Install all workspace dependencies
29
- bun dev # Start all services (generates .env.local first)
30
- bun pf new hono-micro services/NAME # Scaffold new microservice + infra config
31
- bun test # Run tests with Bun
32
- bun lint && bun format # Biome linting/formatting
33
- cd apps/storefront && bun supabase start # Local Supabase (Docker required)
34
- ```
35
-
36
- ## Creating Microservices
37
-
38
- Use the CLI—it scaffolds code + infrastructure + environment in one command:
39
-
40
- ```bash
41
- bun pf new hono-micro services/my-service -y
42
- ```
43
-
44
- This creates `services/my-service/` + `infra/services/my-service.ts`, assigns a port, and updates `.env.local`.
45
-
46
- ### Service Entry Point Pattern
47
-
48
- All service logic belongs in `src/index.ts` - do NOT create separate files for consumers or startup code.
49
-
50
- ```ts
51
- // IMPORTANT: telemetry MUST be first import
52
- import '@crossdelta/telemetry'
53
-
54
- import { Hono } from 'hono'
55
-
56
- const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 4003
57
- const app = new Hono()
58
-
59
- app.get('/health', (c) => c.json({ status: 'ok' }))
60
-
61
- Bun.serve({ port, fetch: app.fetch })
62
- ```
63
-
64
- ## CloudEvents & NATS Messaging
65
-
66
- **Always use `@crossdelta/cloudevents`**, never raw NATS client.
67
-
68
- ### Publishing Events
69
-
70
- ```ts
71
- import { publish } from '@crossdelta/cloudevents'
72
-
73
- await publish('orders.created', { orderId, customerId, total }, {
74
- source: '{{projectName}}://orders-service',
75
- subject: orderId
76
- })
77
- ```
78
-
79
- The `publish` function automatically:
80
- - Constructs the CloudEvent type from the subject (e.g., `orders.created` → `{{projectName}}.orders.created`)
81
- - Adds required CloudEvent metadata (id, time, specversion, datacontenttype)
82
- - Publishes to NATS JetStream
83
-
84
- ### Consuming Events (Auto-Discovery)
85
-
86
- Create handler files in `src/handlers/*.event.ts`:
87
-
88
- ```ts
89
- // services/notifications/src/handlers/order-created.event.ts
90
- import { handleEvent } from '@crossdelta/cloudevents'
91
- import { z } from 'zod'
92
- import { sendNotification } from '../use-cases/send-notification.use-case'
93
-
94
- const OrderCreatedSchema = z.object({
95
- orderId: z.string(),
96
- customerId: z.string(),
97
- total: z.number(),
98
- items: z.array(
99
- z.object({
100
- productId: z.string(),
101
- quantity: z.number(),
102
- price: z.number(),
103
- }),
104
- ).optional(),
105
- })
106
-
107
- // Export type for reuse in other services
108
- export type OrderCreatedEvent = z.infer<typeof OrderCreatedSchema>
109
-
110
- export default handleEvent(
111
- {
112
- schema: OrderCreatedSchema,
113
- type: 'orders.created',
114
- },
115
- async (data) => {
116
- await sendNotification(data)
117
- },
118
- )
119
- ```
120
-
121
- **Important:**
122
- - **Schema** validates only the event data payload (without `type` field)
123
- - **Event type** is declared in the options object, not in the schema
124
- - Event type matches the first parameter of `publish()` (e.g., `'orders.created'`)
125
- - Do NOT include `type: z.literal('...')` in the schema - it's redundant and causes validation errors
126
- - **Always export the inferred type** using `export type EventName = z.infer<typeof EventSchema>` for use in use-cases
127
-
128
- **Naming conventions:**
129
- - Schema constants: PascalCase with `Schema` suffix (e.g., `OrderCreatedSchema`, `UserUpdatedSchema`)
130
- - Exported types: PascalCase with `Event` suffix (e.g., `OrderCreatedEvent`, `UserUpdatedEvent`)
131
-
132
- ### Service Folder Structure
133
-
134
- Organize service code with this structure:
135
-
136
- ```
137
- src/
138
- ├── index.ts # Entry point with telemetry, Hono app, NATS setup
139
- ├── handlers/ # Event handlers (*.event.ts files for NATS events)
140
- │ └── order-created.event.ts
141
- └── use-cases/ # Business logic / use cases
142
- └── send-notification.use-case.ts
143
- ```
144
-
145
- - **`src/handlers/`**: Event handlers that consume CloudEvents from NATS
146
- - **`src/use-cases/`**: Reusable business logic (pure functions or classes)
147
- - Keep `src/index.ts` focused on wiring (server, consumers, routes)
148
-
149
- Start consumption in `src/index.ts` (NOT in a separate file):
150
-
151
- ```ts
152
- // src/index.ts - complete example with event consumption
153
- import '@crossdelta/telemetry'
154
-
155
- import { consumeJetStreamEvents } from '@crossdelta/cloudevents'
156
- import { Hono } from 'hono'
157
-
158
- const port = Number(process.env.PORT || process.env.NOTIFICATIONS_PORT) || 4002
159
- const app = new Hono()
160
-
161
- app.get('/health', (c) => c.json({ status: 'ok' }))
162
-
163
- // Start NATS consumer - handlers are auto-discovered
164
- consumeJetStreamEvents({
165
- stream: 'ORDERS',
166
- subjects: ['orders.>'],
167
- consumer: 'notifications',
168
- discover: './src/handlers/**/*.event.ts',
169
- })
170
-
171
- Bun.serve({ port, fetch: app.fetch })
172
- ```
173
-
174
- ## Storefront: RxDB + Supabase Replication
175
-
176
- ### Adding a New Collection
177
-
178
- 1. Create schema in `apps/storefront/src/db/schemas/generated/`
179
- 2. Add collection definition in `apps/storefront/src/db/collections/definitions/`:
180
-
181
- ```ts
182
- export const ordersCollectionDefinition = defineCollection({
183
- name: 'orders',
184
- creator: { schema: ordersRxdbSchema },
185
- setup: async (collection) => {
186
- await replicateSupabaseTable(collection, 'orders', { tenant: createTenantScopeGuard() })
187
- },
188
- useCollectionQuery,
189
- })
190
- ```
191
-
192
- 3. Register in `apps/storefront/src/db/collections/definitions/index.ts`
193
-
194
- **Supabase tables require:** `id`, `updated_at`, `_deleted` (boolean for soft-delete).
195
-
196
- ## Infrastructure (Pulumi + Kubernetes)
197
-
198
- ### Modern Port Configuration (Fluent API)
199
-
200
- **Use the fluent `ports()` builder** for defining service ports:
201
-
202
- ```ts
203
- import { ports } from '@crossdelta/infrastructure'
204
- import type { K8sServiceConfig } from '@crossdelta/infrastructure'
205
-
206
- // Simple internal HTTP service
207
- const config: K8sServiceConfig = {
208
- name: 'orders',
209
- ports: ports().http(4001).build(),
210
- replicas: 1,
211
- healthCheck: { httpPath: '/health' }, // HTTP health check endpoint
212
- resources: {
213
- requests: { cpu: '50m', memory: '64Mi' },
214
- limits: { cpu: '150m', memory: '128Mi' }
215
- },
216
- env: { PUBLIC_SUPABASE_URL: supabaseUrl },
217
- secrets: { SUPABASE_SERVICE_ROLE_KEY: supabaseServiceRoleKey },
218
- }
219
-
220
- // Public HTTP service
221
- const publicConfig: K8sServiceConfig = {
222
- name: 'api-gateway',
223
- ports: ports().http(4000).public().build(),
224
- ingress: { path: '/api', host: 'api.example.com' },
225
- }
226
-
227
- // Service with multiple ports
228
- const natsConfig: K8sServiceConfig = {
229
- name: 'nats',
230
- ports: ports()
231
- .primary(4222, 'client')
232
- .addHttp(8222, 'monitoring').public()
233
- .add(6222, 'routing')
234
- .build(),
235
- }
236
- ```
237
-
238
- **Port Builder Methods:**
239
- - `.http(port)` - HTTP primary port
240
- - `.https(port)` - HTTPS primary port
241
- - `.grpc(port)` - gRPC primary port
242
- - `.primary(port, name?)` - Custom primary port
243
- - `.add(port, name?, protocol?)` - Add additional port
244
- - `.addHttp(port, name?)` - Add HTTP additional port
245
- - `.addGrpc(port, name?)` - Add gRPC additional port
246
- - `.public()` - Mark last port as public (exposed via ingress)
247
- - `.protocol(type)` - Set protocol for last port
248
- - `.build()` - Build final config
249
-
250
- **Legacy `containerPort` is deprecated** - use the fluent API instead.
251
-
252
- ### Health Checks
253
-
254
- Services should expose health check endpoints:
255
-
256
- ```ts
257
- // HTTP health check (recommended)
258
- healthCheck: {
259
- httpPath: '/health', // GET endpoint
260
- initialDelaySeconds: 10, // Wait before first check
261
- periodSeconds: 10, // Check interval
262
- }
263
-
264
- // For services without HTTP endpoints, Kubernetes will use TCP checks automatically
265
- // based on the primary port
266
- ```
267
-
268
- Health check endpoint implementation:
269
-
270
- ```ts
271
- app.get('/health', (c) => c.json({ status: 'ok' }))
272
- ```
273
-
274
- ### Smart Service Discovery
275
-
276
- **Use `discoverServiceConfigs()` for automatic service discovery:**
277
-
278
- ```ts
279
- import { discoverServiceConfigs, discoverServiceConfigsWithOptions } from '@crossdelta/infrastructure'
280
-
281
- // Simple discovery (backward compatible)
282
- const configs = discoverServiceConfigs('services')
283
-
284
- // Advanced discovery with filtering
285
- const result = discoverServiceConfigsWithOptions({
286
- servicesDir: 'services',
287
- filter: /^api-/, // Pattern matching
288
- exclude: ['test-service'], // Exclude services
289
- env: { // Inject env vars
290
- NODE_ENV: 'production',
291
- NATS_URL: natsUrl,
292
- },
293
- validate: true, // Port conflict checks
294
- })
295
- ```
296
-
297
- **Discovery Options:**
298
- - `filter` - Regex or string pattern to match service names
299
- - `include` - Array of service names to include
300
- - `exclude` - Array of service names to exclude
301
- - `tags` - Filter by service tags
302
- - `env` - Inject environment variables into all services
303
- - `validate` - Enable validation (port conflicts, missing fields)
304
- - `sort` - Sort services by name (default: true)
305
- - `registry` - Custom registry for auto-generated images
306
-
307
- Follow existing configs in `infra/services/`. Don't invent new providers or patterns.
308
-
309
- ## Code Style & Conventions
310
-
311
- ### Biome Configuration
312
-
313
- **CRITICAL:** All code MUST follow the Biome rules configured in the root `biome.json`:
314
-
315
- - **Formatting:** Single quotes, no semicolons, 2-space indent, 120 char width, trailing commas
316
- - **Import Organization:** Imports and exports MUST be sorted alphabetically (use `organizeImports: "on"`)
317
- - **Unused Imports:** Remove all unused imports (lint error)
318
- - **Code Quality:** Follow all Biome linter rules (security, style, complexity)
319
-
320
- **Always run before committing:**
321
- ```bash
322
- bun lint # Check for issues
323
- bun format # Auto-fix formatting and organize imports
324
- ```
325
-
326
- **In your editor:** Enable Biome's "Organize Imports on Save" for automatic sorting.
327
-
328
- ### General Conventions
329
-
330
- - **Functions:** Prefer **arrow functions** and **functional programming patterns** (map, filter, reduce) over imperative loops
331
- - **Immutability:** Use `const` over `let`, avoid mutations, prefer spread operators and array methods
332
- - **Composition:** Small, composable functions over large classes
333
- - **Ports:** Always `process.env.PORT || process.env.SERVICE_PORT || default`
334
- - **Health:** All services expose `GET /health → { status: 'ok' }`
335
- - **Commits:** Conventional Commits (`feat:`, `fix:`, `chore:`, `docs:`)
336
- - **Tests:** Use `bun:test` — see `packages/cloudevents/test/*.test.ts` for patterns
337
-
338
- ### Functional Programming Examples
339
-
340
- **Prefer:**
341
- ```ts
342
- // Arrow functions
343
- const add = (a: number, b: number) => a + b
344
-
345
- // Map/filter/reduce over loops
346
- const activeUsers = users.filter(user => user.active)
347
- const userNames = users.map(user => user.name)
348
- const totalAge = users.reduce((sum, user) => sum + user.age, 0)
349
-
350
- // Composition
351
- const processData = (data: Data[]) =>
352
- data
353
- .filter(isValid)
354
- .map(transform)
355
- .reduce(aggregate, initialValue)
356
- ```
357
-
358
- **Avoid:**
359
- ```ts
360
- // Function declarations (unless needed for hoisting)
361
- function add(a: number, b: number) { return a + b }
362
-
363
- // Imperative loops
364
- const activeUsers = []
365
- for (let i = 0; i < users.length; i++) {
366
- if (users[i].active) {
367
- activeUsers.push(users[i])
368
- }
369
- }
370
- ```
371
-
372
- ## Key Files Reference
373
-
374
- | Pattern | Example |
375
- |---------|---------|
376
- | Hono service entry | `services/orders/src/index.ts` |
377
- | Event handler | `services/notifications/src/handlers/order-created.event.ts` |
378
- | RxDB collection | `apps/storefront/src/db/collections/definitions/orders.ts` |
379
- | Service infra config | `infra/services/orders.ts` |
380
- | Supabase migrations | `apps/storefront/supabase/migrations/` |
381
-
382
- ## AI Code Generation Format
383
-
384
- When generating code via `pf ai generate-service`, use this output format:
385
-
386
- ### Commands to Execute
387
-
388
- List commands that should be run BEFORE the source files are created:
389
-
390
- ```commands
391
- pf new hono-micro services/my-service -y
392
- ```
393
-
394
- These commands will be executed automatically by the CLI.
395
-
396
- **CRITICAL:** The service path in the `pf new` command MUST match the service name provided by the user exactly. If the user specifies `services/my-service`, use `services/my-service`. Do NOT modify or prepend paths.
397
-
398
- ### Dependencies (Optional)
399
-
400
- If the service requires additional npm packages beyond what's scaffolded, list them in a `dependencies` block:
401
-
402
- ```dependencies
403
- @pusher/push-notifications-server
404
- zod
405
- drizzle-orm
406
- ```
407
-
408
- **Format:**
409
- - One package per line
410
- - Use exact package names from npm (e.g., `@scope/package-name`)
411
- - Comments starting with `#` are ignored
412
- - These packages will be installed using the existing integration system
413
-
414
- ### Source Files
415
-
416
- Format source files with path headers. Paths are relative to the service directory (e.g., `src/index.ts`, NOT `services/my-service/src/index.ts`):
417
-
418
- #### `src/index.ts`
419
- ```typescript
420
- // code here
421
- ```
422
-
423
- #### `src/handlers/event-name.event.ts`
424
- ```typescript
425
- import { handleEvent } from '@crossdelta/cloudevents'
426
- import { z } from 'zod'
427
- import { businessLogic } from '../use-cases/business-logic.use-case'
428
-
429
- const EventDataSchema = z.object({
430
- id: z.string(),
431
- // ... other fields
432
- })
433
-
434
- // Export type for use in use-cases
435
- export type EventDataType = z.infer<typeof EventDataSchema>
436
-
437
- export default handleEvent(
438
- {
439
- schema: EventDataSchema,
440
- type: 'resource.action', // e.g., 'orders.created', 'users.updated'
441
- },
442
- async (data) => {
443
- await businessLogic(data)
444
- },
445
- )
446
- ```
447
-
448
- #### `src/use-cases/business-logic.use-case.ts`
449
- ```typescript
450
- import type { EventDataType } from '../handlers/event-name.event'
451
-
452
- /**
453
- * Business logic that processes the event data.
454
- * Uses the exported type from the handler for type safety.
455
- */
456
- export async function businessLogic(data: EventDataType): Promise<void> {
457
- // Full type inference from the handler schema
458
- console.log('Processing:', data.id)
459
-
460
- // All business logic here
461
- // - Validation
462
- // - External API calls
463
- // - Database operations
464
- }
465
- ```
466
-
467
- ### Tests
468
-
469
- Always generate tests for the service. Use Bun's native test runner (`bun:test`):
470
-
471
- #### `src/index.test.ts`
472
- ```typescript
473
- import { describe, expect, it } from 'bun:test'
474
-
475
- describe('Service Health Check', () => {
476
- it('should respond to health check', async () => {
477
- const app = new Hono()
478
- app.get('/health', (c) => c.json({ status: 'ok' }))
479
-
480
- const req = new Request('http://localhost/health')
481
- const res = await app.fetch(req)
482
-
483
- expect(res.status).toBe(200)
484
- const json = await res.json()
485
- expect(json).toEqual({ status: 'ok' })
486
- })
487
- })
488
- ```
489
-
490
- #### `src/use-cases/business-logic.use-case.test.ts`
491
- ```typescript
492
- import { describe, expect, it } from 'bun:test'
493
- import { myUseCase } from './business-logic.use-case'
494
-
495
- describe('BusinessLogic Use Case', () => {
496
- it('should handle valid input correctly', async () => {
497
- const result = await myUseCase({ id: '123' })
498
- expect(result).toBeDefined()
499
- })
500
-
501
- it('should throw error for invalid input', async () => {
502
- await expect(myUseCase({ id: '' })).rejects.toThrow('Invalid input')
503
- })
504
- })
505
- ```
506
-
507
- **Test Guidelines:**
508
- - **Keep tests simple and lightweight** - Bun's test runner is minimal, not a full-featured test framework
509
- - Write tests ONLY for use cases (NOT event handlers)
510
- - Event handlers are thin wrappers - the use cases contain the testable logic
511
- - Use Bun's native test runner: `import { describe, expect, it, beforeEach, afterEach } from 'bun:test'`
512
- - **NO mocking available** - Bun's test runner does NOT have `vi`, `mock`, or similar utilities
513
- - ❌ NO `vi` object (that's Vitest, not Bun)
514
- - ❌ NO `jest.fn()` or `jest.mock()`
515
- - ❌ NO module mocking
516
- - ✅ Use simple, direct testing without mocks
517
- - Use descriptive test names in English
518
- - Test both happy paths and error cases
519
- - Focus on validation and error handling (input params, env vars)
520
- - **DO NOT mock external libraries** - Bun doesn't support mocking, keep tests simple
521
- - Test environment variable validation and input validation
522
- - Keep cleanup simple in `afterEach`: restore `process.env` only
523
-
524
- **Example: Simple validation and error handling tests**
525
- ```typescript
526
- import { describe, expect, it, beforeEach, afterEach } from 'bun:test'
527
- import { sendNotification } from './send-notification.use-case'
528
-
529
- describe('Send Notification Use Case', () => {
530
- const originalEnv = process.env
531
-
532
- beforeEach(() => {
533
- // Save original environment
534
- process.env = { ...originalEnv }
535
- })
536
-
537
- afterEach(() => {
538
- // Restore original environment (no vi.resetModules needed)
539
- process.env = originalEnv
540
- })
541
-
542
- it('should throw error if orderId is missing', async () => {
543
- await expect(
544
- sendNotification({ orderId: '', customerId: 'cust-1' })
545
- ).rejects.toThrow('Missing orderId')
546
- })
547
-
548
- it('should throw error if credentials are not set', async () => {
549
- delete process.env.PUSHER_BEAMS_INSTANCE_ID
550
-
551
- await expect(
552
- sendNotification({ orderId: '123', customerId: 'cust-1' })
553
- ).rejects.toThrow('Missing Pusher Beams credentials')
554
- })
555
- })
556
- ```
557
-
558
- **What to test:**
559
- - ✅ Input validation (missing/empty parameters)
560
- - ✅ Environment variable validation
561
- - ✅ Error messages and error types
562
- - ✅ Basic logic flows
563
- - ❌ Complex mocking scenarios
564
- - ❌ External API integrations (Pusher, AWS, etc.)
565
- - ❌ Module reloading/resetting
566
-
567
- **Test Strategy:**
568
- - Focus on **validation** (input parameters, environment variables)
569
- - Test **error handling** and edge cases
570
- - Keep tests **simple** - avoid complex mocking of external libraries
571
- - Test the **business logic**, not the external API calls
572
- - If a function calls an external API, test the validation BEFORE the call, not the call itself
573
-
574
- **Example of what NOT to do:**
575
- ```typescript
576
- // ❌ BAD - Trying to mock Pusher (not possible in Bun)
577
- import { describe, expect, it, beforeEach, afterEach, vi } from 'bun:test'
578
-
579
- describe('Bad Test', () => {
580
- afterEach(() => {
581
- vi.resetModules() // ❌ This doesn't exist in Bun!
582
- })
583
-
584
- it('should send notification', async () => {
585
- // ❌ Trying to mock external library
586
- const mockPusher = vi.fn() // ❌ vi doesn't exist!
587
- })
588
- })
589
- ```
590
-
591
- **Example of what TO do:**
592
- ```typescript
593
- // ✅ GOOD - Simple validation tests
594
- import { describe, expect, it, beforeEach, afterEach } from 'bun:test'
595
-
596
- describe('Good Test', () => {
597
- const originalEnv = process.env
598
-
599
- beforeEach(() => {
600
- process.env = { ...originalEnv }
601
- })
602
-
603
- afterEach(() => {
604
- process.env = originalEnv
605
- })
606
-
607
- it('should throw error if orderId is missing', async () => {
608
- await expect(
609
- sendNotification({ orderId: '', customerId: 'cust-1' })
610
- ).rejects.toThrow('Missing orderId')
611
- })
612
-
613
- it('should throw error if credentials not set', async () => {
614
- delete process.env.PUSHER_BEAMS_INSTANCE_ID
615
-
616
- await expect(
617
- sendNotification({ orderId: '123', customerId: 'cust-1' })
618
- ).rejects.toThrow('Missing Pusher Beams credentials')
619
- })
620
- })
621
- ```
622
-
623
- ### README
624
-
625
- Generate a service-specific README documenting:
626
-
627
- #### `README.md`
628
- ```markdown
629
- # Service Name
630
-
631
- Brief description of what this service does.
632
-
633
- ## Features
634
-
635
- - Feature 1
636
- - Feature 2
637
-
638
- ## Environment Variables
639
-
640
- | Variable | Description | Required | Default |
641
- |----------|-------------|----------|---------|
642
- | `SERVICE_PORT` | Port the service runs on | No | 4001 |
643
- | `NATS_URL` | NATS server URL | Yes | - |
644
-
645
- ## Events
646
-
647
- ### Publishes
648
- - `{{projectName}}.service.event` - Description
649
-
650
- ### Consumes
651
- - `{{projectName}}.other.event` - Description
652
-
653
- ## API Endpoints
654
-
655
- ### `GET /health`
656
- Health check endpoint.
657
-
658
- **Response:**
659
- \`\`\`json
660
- { "status": "ok" }
661
- \`\`\`
662
-
663
- ## Development
664
-
665
- \`\`\`bash
666
- bun dev # Start in development mode
667
- bun test # Run tests
668
- \`\`\`
669
- ```
670
-
671
- **CRITICAL - You MUST generate ALL of these:**
672
- 1. ✅ `pf new` command (first step - scaffolds the service)
673
- 2. ✅ Source files (`src/index.ts`, `src/handlers/*.event.ts`, `src/use-cases/*.use-case.ts`)
674
- 3. ✅ **Test files for EVERY use case** (`src/use-cases/*.test.ts`) - DO NOT test event handlers
675
- 4. ✅ **Complete README.md** with:
676
- - Service description & features
677
- - Environment variables table
678
- - Events (published/consumed)
679
- - API endpoints documentation
680
- - Development commands
681
-
682
- **DO NOT skip tests or README** - they are REQUIRED, not optional.
683
-
684
- **Test Strategy:**
685
- - Focus on validation (input parameters, environment variables)
686
- - Test error handling and edge cases
687
- - Keep tests simple - avoid complex mocking of external libraries
688
- - Test the business logic, not the external API calls
689
-
690
- **Format notes:**
691
- - Follow the Service Entry Point Pattern and folder structure above
692
- - Biome lint/format runs automatically after generation – don't worry about minor formatting
693
- - **ALL code comments, documentation, and README files MUST be written in English**
694
- - **Tests MUST be compatible with Bun's test runner** (`bun:test` module with `describe`, `it`, `expect`, `beforeEach`, `afterEach`, `vi` for mocking)
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
+
14
+ ## Code Style
15
+ - Single quotes, no semicolons, 2-space indent, trailing commas.
16
+ - Alphabetically sorted imports; no unused imports.
17
+ - Arrow functions over `function` declarations.
18
+ - Prefer pure and functional programming patterns (map/filter/reduce).
19
+ - Avoid mutable state and imperative loops.
20
+ - No decorative section header blocks (no ASCII art separators like `// ─────────────`).
21
+ - Use blank lines to separate logical sections naturally.
22
+ - Organize code: types → helpers → higher-order functions.
23
+ - Use JSDoc for exported functions and complex logic; keep inline comments minimal.
24
+ - Self-documenting code over comments: clear naming, pure functions, obvious control flow.
25
+
26
+ ## Validation & Types
27
+ - Use Zod for schemas.
28
+ - Export inferred types using `z.infer`.
29
+ - Do NOT include literal event types inside schemas.
30
+ - Do not duplicate validation logic across layers.
31
+
32
+ ## Service & Module Structure
33
+ - Entry points handle wiring (server start, telemetry, routing, consumers).
34
+ - Business logic lives in use-case modules.
35
+ - Event handlers must be thin wrappers around use-cases.
36
+
37
+ ## Event Handling
38
+ - Use shared CloudEvents/messaging libraries, not raw clients.
39
+ - Handlers: Schema → inferred type → thin handler → use-case delegation.
40
+ - Handlers named `*.event.ts`.
41
+
42
+ ## Infrastructure
43
+ - Use fluent port builders when available.
44
+ - Expose a `/health` endpoint.
45
+ - Avoid legacy containerPort usage.
46
+
47
+ ## Testing
48
+ - Test use-cases, not handlers or frameworks.
49
+ - Prefer simple direct tests, no mocks.
50
+ - Validate both error and success paths.
51
+
52
+ ## AI Output Format
53
+ - Provide only necessary files.
54
+ - Use relative paths.
55
+ - Keep comments minimal.
56
+ - Do not generate abstractions not already present.