@crossdelta/platform-sdk 0.8.4 → 0.8.31
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 +230 -265
- package/bin/cli.js +151 -151
- package/bin/templates/workspace/.github/copilot-instructions.md.hbs +2 -1
- package/install.sh +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,137 +1,118 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://unpkg.com/@crossdelta/platform-sdk/logo.png" alt="pf" width="
|
|
2
|
+
<img src="https://unpkg.com/@crossdelta/platform-sdk/logo.png" alt="pf" width="120" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">Platform SDK</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<
|
|
8
|
+
<code>@crossdelta/platform-sdk</code>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<
|
|
12
|
+
<strong>Platform toolkit for event-driven microservices — keeping code and infrastructure in lockstep.</strong><br />
|
|
13
|
+
Scaffold <a href="https://turbo.build/repo">Turborepo</a> workspaces, generate services from natural language,<br />
|
|
14
|
+
and deploy via <a href="https://www.pulumi.com">Pulumi</a> — DigitalOcean-first for speed and simplicity, providers replaceable by design.
|
|
13
15
|
</p>
|
|
14
16
|
|
|
15
17
|
<p align="center">
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
+
<em>Platform SDK (<code>pf</code>) is a platform opinion, not a generator.<br />
|
|
19
|
+
It encodes architectural decisions so teams don't have to rediscover them.</em>
|
|
18
20
|
</p>
|
|
19
21
|
|
|
20
|
-
<
|
|
22
|
+
<p align="center">
|
|
23
|
+
<em>Includes optional AI-assisted service generation (OpenAI / Anthropic).</em>
|
|
24
|
+
</p>
|
|
21
25
|
|
|
22
26
|
<p align="center">
|
|
23
|
-
<a href="https://www.npmjs.com/package/@crossdelta/platform-sdk"><img src="https://img.shields.io/npm/v/@crossdelta/platform-sdk.svg?style=
|
|
24
|
-
|
|
25
|
-
<img src="https://img.shields.io/badge/
|
|
26
|
-
|
|
27
|
-
<img src="https://img.shields.io/badge/Bun-000000?style=for-the-badge&logo=bun&logoColor=white" alt="Bun" />
|
|
28
|
-
|
|
29
|
-
<img src="https://img.shields.io/badge/License-MIT-22c55e?style=for-the-badge" alt="MIT License" />
|
|
27
|
+
<a href="https://www.npmjs.com/package/@crossdelta/platform-sdk"><img src="https://img.shields.io/npm/v/@crossdelta/platform-sdk.svg?style=flat-square" alt="npm version" /></a>
|
|
28
|
+
<img src="https://img.shields.io/badge/bun-%E2%9A%A1-f9f1e1?style=flat-square" alt="Bun" />
|
|
29
|
+
<img src="https://img.shields.io/badge/pulumi-ready-blueviolet?style=flat-square" alt="Pulumi" />
|
|
30
|
+
<img src="https://img.shields.io/badge/DigitalOcean-App_Platform_|_DOKS-0080FF?style=flat-square" alt="DigitalOcean" />
|
|
30
31
|
</p>
|
|
31
32
|
|
|
32
33
|
<p align="center">
|
|
33
34
|
<img src="https://img.shields.io/badge/Hono-E36002?style=flat-square&logo=hono&logoColor=white" alt="Hono" />
|
|
34
35
|
<img src="https://img.shields.io/badge/NestJS-E0234E?style=flat-square&logo=nestjs&logoColor=white" alt="NestJS" />
|
|
35
36
|
<img src="https://img.shields.io/badge/NATS-27AAE1?style=flat-square&logo=natsdotio&logoColor=white" alt="NATS" />
|
|
36
|
-
<img src="https://img.shields.io/badge/Pulumi-8A3391?style=flat-square&logo=pulumi&logoColor=white" alt="Pulumi" />
|
|
37
|
-
<img src="https://img.shields.io/badge/Turborepo-EF4444?style=flat-square&logo=turborepo&logoColor=white" alt="Turborepo" />
|
|
38
|
-
<img src="https://img.shields.io/badge/DigitalOcean-0080FF?style=flat-square&logo=digitalocean&logoColor=white" alt="DigitalOcean" />
|
|
39
37
|
</p>
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
---
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
<a href="#-quick-start">Quick Start</a> •
|
|
45
|
-
<a href="#-5-minute-tutorial">Tutorial</a> •
|
|
46
|
-
<a href="#-commands">Commands</a> •
|
|
47
|
-
<a href="#-ai-assisted-generation">AI Generation</a> •
|
|
48
|
-
<a href="#-deployment">Deployment</a>
|
|
49
|
-
</p>
|
|
41
|
+
## Who is this for?
|
|
50
42
|
|
|
51
|
-
|
|
43
|
+
- **Teams building event-driven backends** — NATS, CloudEvents, type-safe contracts
|
|
44
|
+
- **Startups & internal platforms** that want long-term consistency over short-term speed
|
|
45
|
+
- **Engineers tired of infra & app drift** — ports, env vars, deployments always in sync
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
## 👋 Who is this for?
|
|
56
|
-
|
|
57
|
-
<table>
|
|
58
|
-
<tr>
|
|
59
|
-
<td width="60">🏗️</td>
|
|
60
|
-
<td><strong>Teams building full-stack platforms</strong> — services, events, apps that actually work together</td>
|
|
61
|
-
</tr>
|
|
62
|
-
<tr>
|
|
63
|
-
<td>📡</td>
|
|
64
|
-
<td><strong>Event-driven architectures</strong> — or teams ready to start using events</td>
|
|
65
|
-
</tr>
|
|
66
|
-
<tr>
|
|
67
|
-
<td>😤</td>
|
|
68
|
-
<td><strong>Engineers tired of drift</strong> — ports, env vars, and infra configs that never match</td>
|
|
69
|
-
</tr>
|
|
70
|
-
<tr>
|
|
71
|
-
<td>🚀</td>
|
|
72
|
-
<td><strong>Startups & platform teams</strong> — who want consistency without building everything from scratch</td>
|
|
73
|
-
</tr>
|
|
74
|
-
</table>
|
|
75
|
-
|
|
76
|
-
> **You can start without understanding all the pieces.**
|
|
77
|
-
> The depth reveals itself as you grow.
|
|
47
|
+
You can start without understanding all the pieces. The depth reveals itself as you grow.
|
|
78
48
|
|
|
79
49
|
---
|
|
80
50
|
|
|
81
|
-
##
|
|
51
|
+
## Installation
|
|
82
52
|
|
|
83
53
|
```bash
|
|
84
|
-
# With Bun
|
|
85
54
|
bun add -g @crossdelta/platform-sdk
|
|
55
|
+
# or: npm install -g @crossdelta/platform-sdk
|
|
56
|
+
```
|
|
86
57
|
|
|
87
|
-
|
|
88
|
-
|
|
58
|
+
<details>
|
|
59
|
+
<summary>Alternative: Auto-installer or run without installing</summary>
|
|
89
60
|
|
|
90
|
-
|
|
61
|
+
**Unix/macOS:**
|
|
62
|
+
```bash
|
|
63
|
+
# Auto-installer (installs CLI globally, prompts to install Bun if missing)
|
|
91
64
|
curl -fsSL https://unpkg.com/@crossdelta/platform-sdk@latest/install.sh | bash
|
|
92
65
|
```
|
|
93
66
|
|
|
94
|
-
|
|
67
|
+
**Windows:**
|
|
68
|
+
```powershell
|
|
69
|
+
# Recommended: Use npm directly
|
|
70
|
+
npm install -g @crossdelta/platform-sdk
|
|
71
|
+
|
|
72
|
+
# Or use WSL (Windows Subsystem for Linux)
|
|
73
|
+
wsl bash -c "curl -fsSL https://unpkg.com/@crossdelta/platform-sdk@latest/install.sh | bash"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Run without installing:**
|
|
77
|
+
```bash
|
|
78
|
+
bunx @crossdelta/platform-sdk new workspace my-platform
|
|
79
|
+
# or: npx @crossdelta/platform-sdk new workspace my-platform
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
</details>
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Quick Start
|
|
95
87
|
|
|
96
|
-
**Then create your first platform:**
|
|
97
88
|
```bash
|
|
98
89
|
pf new workspace my-platform -y && cd my-platform
|
|
99
90
|
pf dev
|
|
100
91
|
```
|
|
101
92
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
</tr>
|
|
113
|
-
<tr>
|
|
114
|
-
<td>✅</td>
|
|
115
|
-
<td>Pulumi infrastructure ready for deployment</td>
|
|
116
|
-
</tr>
|
|
117
|
-
<tr>
|
|
118
|
-
<td>✅</td>
|
|
119
|
-
<td>Auto-generated <code>.env.local</code> with service ports</td>
|
|
120
|
-
</tr>
|
|
121
|
-
</table>
|
|
93
|
+
**What you get:**
|
|
94
|
+
- Turborepo monorepo with Biome linting
|
|
95
|
+
- NATS + JetStream for event-driven messaging
|
|
96
|
+
- Pulumi infrastructure (`infra/services/*.ts`)
|
|
97
|
+
- Auto-generated `.env.local` with service ports
|
|
98
|
+
|
|
99
|
+
> **Lockstep by design:**
|
|
100
|
+
> In `pf`, application code and infrastructure are generated from the same source of truth.
|
|
101
|
+
> Service metadata defines ports, env vars, event wiring, and Pulumi configs —
|
|
102
|
+
> eliminating drift between local development, CI, and production.
|
|
122
103
|
|
|
123
104
|
---
|
|
124
105
|
|
|
125
|
-
##
|
|
106
|
+
## 5-Minute Tutorial
|
|
126
107
|
|
|
127
|
-
|
|
108
|
+
After running Quick Start, add services and wire events:
|
|
128
109
|
|
|
129
110
|
```bash
|
|
130
|
-
# 1.
|
|
111
|
+
# 1. Add two services
|
|
131
112
|
pf new hono-micro services/orders
|
|
132
113
|
pf new hono-micro services/notifications
|
|
133
114
|
|
|
134
|
-
# 2. Wire an event
|
|
115
|
+
# 2. Wire an event between them
|
|
135
116
|
pf event add orders.created --service services/notifications
|
|
136
117
|
|
|
137
118
|
# 3. Restart to pick up new services
|
|
@@ -141,194 +122,92 @@ pf dev
|
|
|
141
122
|
pf event publish orders.created
|
|
142
123
|
```
|
|
143
124
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
<table>
|
|
147
|
-
<tr>
|
|
148
|
-
<th align="left">What just happened</th>
|
|
149
|
-
</tr>
|
|
150
|
-
<tr>
|
|
151
|
-
<td>
|
|
152
|
-
📦 Two services with health checks and Pulumi configs<br />
|
|
153
|
-
📝 One event contract in <code>packages/contracts/</code><br />
|
|
154
|
-
🔗 One handler that auto-discovers the event<br />
|
|
155
|
-
✨ <strong>No manual wiring. No config drift. Everything is explicit.</strong>
|
|
156
|
-
</td>
|
|
157
|
-
</tr>
|
|
158
|
-
</table>
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
## 💡 What problem does pf solve?
|
|
163
|
-
|
|
164
|
-
<table>
|
|
165
|
-
<tr>
|
|
166
|
-
<th width="300">❌ The Problem</th>
|
|
167
|
-
<th width="300">✅ With pf</th>
|
|
168
|
-
</tr>
|
|
169
|
-
<tr>
|
|
170
|
-
<td>Application code lives here, infra config lives somewhere else</td>
|
|
171
|
-
<td>Both generated from the same source of truth</td>
|
|
172
|
-
</tr>
|
|
173
|
-
<tr>
|
|
174
|
-
<td>Ports, env vars, and event wiring drift over time</td>
|
|
175
|
-
<td>Change a service → infra stays in sync</td>
|
|
176
|
-
</tr>
|
|
177
|
-
<tr>
|
|
178
|
-
<td>New devs spend days setting up local environments</td>
|
|
179
|
-
<td>Run <code>pf dev</code> → everything starts together</td>
|
|
180
|
-
</tr>
|
|
181
|
-
<tr>
|
|
182
|
-
<td>Manual event subscriptions and handler registration</td>
|
|
183
|
-
<td>Add an event → consumers are wired automatically</td>
|
|
184
|
-
</tr>
|
|
185
|
-
</table>
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## 📋 Commands
|
|
190
|
-
|
|
191
|
-
| Command | What it does |
|
|
192
|
-
|:--------|:-------------|
|
|
193
|
-
| `pf dev` | 🚀 Start NATS + all services in watch mode |
|
|
194
|
-
| `pf new hono-micro <path>` | ⚡ Create a Hono service (lightweight, Bun-optimized) |
|
|
195
|
-
| `pf new nest-micro <path>` | 🏢 Create a NestJS service (full-featured, decorators) |
|
|
196
|
-
| `pf event add <type> --service <path>` | 🔗 Add contract + handler + wire stream |
|
|
197
|
-
| `pf event list` | 📋 List available events and consumers |
|
|
198
|
-
| `pf event publish <type>` | 📤 Publish mock event to NATS |
|
|
199
|
-
| `pf test` | 🧪 Run tests across the monorepo |
|
|
200
|
-
| `pf build` | 📦 Build all packages and services |
|
|
201
|
-
| `pf lint` | ✨ Lint and format with Biome |
|
|
202
|
-
|
|
203
|
-
> `pf` proxies all commands to Turborepo (works from any subdirectory).
|
|
125
|
+
That's it. Two services, one event contract, auto-discovered handlers, no manual wiring.
|
|
204
126
|
|
|
205
127
|
---
|
|
206
128
|
|
|
207
|
-
##
|
|
129
|
+
## 🧭 Commands & Options
|
|
208
130
|
|
|
209
|
-
|
|
131
|
+
### Service generators
|
|
210
132
|
|
|
133
|
+
**Hono** (lightweight, Bun-optimized):
|
|
211
134
|
```bash
|
|
212
|
-
pf
|
|
213
|
-
|
|
214
|
-
pf new hono-micro services/notifications --ai \
|
|
215
|
-
-d "Send emails when orders are created"
|
|
135
|
+
pf new hono-micro services/orders
|
|
216
136
|
```
|
|
217
137
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## 🚢 Deployment
|
|
223
|
-
|
|
138
|
+
**NestJS** (full-featured, decorator-based):
|
|
224
139
|
```bash
|
|
225
|
-
pf
|
|
140
|
+
pf new nest-micro services/orders
|
|
226
141
|
```
|
|
227
142
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
| Trigger | Action |
|
|
231
|
-
|:--------|:-------|
|
|
232
|
-
| PR to `main` | 🧪 Lint + test |
|
|
233
|
-
| Push to `main` | 🚀 Build, push to GHCR, deploy |
|
|
234
|
-
| Changes in `packages/` | 📦 Publish to npm |
|
|
235
|
-
|
|
236
|
-
<details>
|
|
237
|
-
<summary>🔐 Required secrets</summary>
|
|
238
|
-
|
|
239
|
-
<br />
|
|
240
|
-
|
|
241
|
-
| Secret | Description |
|
|
242
|
-
|:-------|:------------|
|
|
243
|
-
| `PULUMI_ACCESS_TOKEN` | [Pulumi Cloud token](https://app.pulumi.com/account/tokens) |
|
|
244
|
-
| `DIGITALOCEAN_TOKEN` | [DO API token](https://cloud.digitalocean.com/account/api/tokens) |
|
|
245
|
-
| `GHCR_TOKEN` | GitHub Container Registry PAT |
|
|
246
|
-
|
|
247
|
-
</details>
|
|
248
|
-
|
|
249
|
-
---
|
|
250
|
-
|
|
251
|
-
## 📋 Requirements
|
|
252
|
-
|
|
253
|
-
| Requirement | Notes |
|
|
254
|
-
|:------------|:------|
|
|
255
|
-
| **Bun** or Node.js ≥ 21 | Bun recommended for speed |
|
|
256
|
-
| **[Pulumi CLI](https://www.pulumi.com/docs/install/)** | For infrastructure deployment |
|
|
257
|
-
| **[Docker](https://www.docker.com/)** | For local NATS |
|
|
258
|
-
|
|
259
|
-
---
|
|
260
|
-
|
|
261
|
-
## 🔍 Under the Hood
|
|
262
|
-
|
|
263
|
-
<details>
|
|
264
|
-
<summary>📁 <b>Project structure</b></summary>
|
|
143
|
+
Both generate the service structure, Pulumi config in `infra/services/`, and wire everything automatically.
|
|
265
144
|
|
|
266
|
-
|
|
145
|
+
### AI-assisted generation (optional)
|
|
267
146
|
|
|
147
|
+
Configure once:
|
|
148
|
+
```bash
|
|
149
|
+
pf setup --ai # OpenAI or Anthropic
|
|
268
150
|
```
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
├── packages/
|
|
274
|
-
│ └── contracts/ # Event contracts (single source of truth)
|
|
275
|
-
├── infra/
|
|
276
|
-
│ └── services/ # Auto-generated Pulumi configs
|
|
277
|
-
└── .env.local # Auto-generated from infra
|
|
151
|
+
|
|
152
|
+
Then use `--ai` with any generator:
|
|
153
|
+
```bash
|
|
154
|
+
pf new hono-micro services/notifications --ai -d "Send emails on order events"
|
|
278
155
|
```
|
|
279
156
|
|
|
280
|
-
|
|
157
|
+
AI generates: service code, event handlers, use cases, tests, and documentation.
|
|
158
|
+
All generated code follows the same conventions and can be written manually.
|
|
281
159
|
|
|
282
|
-
|
|
160
|
+
### Daily commands
|
|
283
161
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
162
|
+
| Command | What it does |
|
|
163
|
+
|---------|--------------|
|
|
164
|
+
| `pf dev` | Start NATS + all services in watch mode |
|
|
165
|
+
| `pf test` | Run tests across the monorepo |
|
|
166
|
+
| `pf lint` | Lint and format with Biome |
|
|
167
|
+
| `pf build` | Build all packages and services |
|
|
168
|
+
| `pf event add <type> --service <path>` | Add contract, mock, handler + wire stream |
|
|
169
|
+
| `pf event list` | List available events and their consumers |
|
|
170
|
+
| `pf event publish <type>` | Publish mock event to NATS |
|
|
171
|
+
| `pf pulumi up` | Deploy infrastructure (runs in `infra/`) |
|
|
288
172
|
|
|
289
|
-
|
|
290
|
-
|:------|:-----------|
|
|
291
|
-
| Monorepo | [Turborepo](https://turbo.build/repo) |
|
|
292
|
-
| Services | [Hono](https://hono.dev) or [NestJS](https://nestjs.com) |
|
|
293
|
-
| Messaging | [NATS](https://nats.io) + JetStream |
|
|
294
|
-
| Events | [CloudEvents](https://cloudevents.io) + Zod validation |
|
|
295
|
-
| Infrastructure | [Pulumi](https://pulumi.com) → DigitalOcean (extensible) |
|
|
296
|
-
| Runtime | [Bun](https://bun.sh) |
|
|
173
|
+
`pf` proxies to Turborepo from any subdirectory.
|
|
297
174
|
|
|
298
|
-
|
|
175
|
+
### Configuration (advanced)
|
|
299
176
|
|
|
300
177
|
<details>
|
|
301
|
-
<summary
|
|
302
|
-
|
|
303
|
-
<br />
|
|
178
|
+
<summary>Workspace configuration via <code>package.json</code></summary>
|
|
304
179
|
|
|
305
180
|
```json
|
|
306
181
|
{
|
|
307
182
|
"pf": {
|
|
308
|
-
"registry": "my-
|
|
183
|
+
"registry": "my-platform/platform",
|
|
309
184
|
"commands": {
|
|
185
|
+
"pulumi": { "cwd": "infra" },
|
|
310
186
|
"deploy": { "cwd": "infra", "command": "pulumi up --yes" }
|
|
311
187
|
},
|
|
312
188
|
"paths": {
|
|
313
189
|
"services": { "path": "services", "watch": true },
|
|
190
|
+
"apps": { "path": "apps", "watch": true },
|
|
191
|
+
"packages": { "path": "packages", "watch": true },
|
|
314
192
|
"contracts": { "path": "packages/contracts" }
|
|
315
193
|
}
|
|
316
194
|
}
|
|
317
195
|
}
|
|
318
196
|
```
|
|
319
197
|
|
|
198
|
+
- `commands`: Custom shortcuts with working directory overrides
|
|
199
|
+
- `paths.<type>.watch`: Enable file watching in dev mode
|
|
200
|
+
- `paths.<type>.ignorePatterns`: Glob patterns to exclude
|
|
201
|
+
|
|
320
202
|
</details>
|
|
321
203
|
|
|
322
204
|
<details>
|
|
323
|
-
<summary
|
|
205
|
+
<summary>Infrastructure configuration via <code>infra/services/*.ts</code></summary>
|
|
324
206
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
// infra/services/orders.ts
|
|
207
|
+
```ts
|
|
329
208
|
import { ports, type K8sServiceConfig } from '@crossdelta/infrastructure'
|
|
330
209
|
|
|
331
|
-
const config: K8sServiceConfig = {
|
|
210
|
+
export const config: K8sServiceConfig = {
|
|
332
211
|
name: 'orders',
|
|
333
212
|
ports: ports().http(4001).build(),
|
|
334
213
|
replicas: 1,
|
|
@@ -338,21 +217,102 @@ const config: K8sServiceConfig = {
|
|
|
338
217
|
limits: { cpu: '150m', memory: '128Mi' },
|
|
339
218
|
},
|
|
340
219
|
}
|
|
341
|
-
|
|
342
|
-
export default config
|
|
343
220
|
```
|
|
344
221
|
|
|
345
222
|
See [@crossdelta/infrastructure](https://www.npmjs.com/package/@crossdelta/infrastructure) for full options.
|
|
346
223
|
|
|
347
224
|
</details>
|
|
348
225
|
|
|
349
|
-
|
|
350
|
-
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 🎯 Why This Exists
|
|
229
|
+
|
|
230
|
+
`pf` solves platform engineering problems:
|
|
231
|
+
|
|
232
|
+
- **Infrastructure drift** — Infra configs diverge from code in separate repos
|
|
233
|
+
- **Manual wiring** — Ports, env vars, service discovery configured by hand
|
|
234
|
+
- **Slow onboarding** — New devs spend days setting up local environments
|
|
235
|
+
|
|
236
|
+
**Solution:** Services auto-generate `infra/services/*.ts`, ports/env derived automatically, event handlers type-safe and auto-discovered.
|
|
237
|
+
|
|
238
|
+
**In short: docker-compose starts services — `pf` prevents systems from drifting apart over time.**
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 🧩 What pf Is / Is Not
|
|
243
|
+
|
|
244
|
+
**✅ pf is:**
|
|
245
|
+
- Opinionated platform toolkit for event-driven microservices
|
|
246
|
+
- Turborepo scaffolder with Pulumi IaC and NATS messaging built-in
|
|
247
|
+
- AI-assisted code generator from natural language descriptions
|
|
248
|
+
- Unified dev workflow — one command to run everything
|
|
249
|
+
- DigitalOcean-first for speed and simplicity — providers replaceable by design
|
|
250
|
+
|
|
251
|
+
**❌ pf is not:**
|
|
252
|
+
- Generic microservice generator — makes architectural choices for you
|
|
253
|
+
- Multi-cloud out of the box — DigitalOcean first, extensible to other providers
|
|
254
|
+
- Runtime manager — scaffolds code, you deploy with Pulumi
|
|
255
|
+
|
|
256
|
+
**Note:** `pf` runs nothing implicitly. No hidden daemons, no automatic deployments. You control when code runs and infrastructure deploys.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 🏗️ Architecture
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
my-platform/
|
|
264
|
+
├── services/ # Microservices
|
|
265
|
+
│ ├── nats/ # NATS message broker (auto-scaffolded)
|
|
266
|
+
│ └── <service>/
|
|
267
|
+
│ └── src/
|
|
268
|
+
│ ├── events/ # Event handlers (*.event.ts)
|
|
269
|
+
│ ├── use-cases/ # Business logic (*.use-case.ts)
|
|
270
|
+
│ ├── types/ # Zod schemas
|
|
271
|
+
│ └── index.ts
|
|
272
|
+
├── packages/
|
|
273
|
+
│ └── contracts/ # Event contracts (Schema Registry)
|
|
274
|
+
├── infra/ # Pulumi Infrastructure-as-Code
|
|
275
|
+
│ └── services/ # Per-service K8s configs
|
|
276
|
+
└── .env.local # Auto-generated from infra
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Conventions
|
|
351
280
|
|
|
352
|
-
|
|
281
|
+
| Location | Purpose |
|
|
282
|
+
|----------|---------|
|
|
283
|
+
| `services/*/src/events/*.event.ts` | Event handlers (auto-discovered) |
|
|
284
|
+
| `services/*/src/use-cases/*.use-case.ts` | Business logic (pure functions) |
|
|
285
|
+
| `packages/contracts/src/events/*.ts` | Event contracts (single source of truth) |
|
|
286
|
+
| `infra/services/*.ts` | Pulumi configs per service |
|
|
353
287
|
|
|
354
|
-
**
|
|
288
|
+
> **Schema Registry pattern:**
|
|
289
|
+
> All event contracts live in `packages/contracts` as the single source of truth.
|
|
290
|
+
> Services import contracts instead of redefining schemas, ensuring
|
|
291
|
+
> type safety, compatibility, and explicit ownership across service boundaries.
|
|
355
292
|
|
|
293
|
+
### Event-Driven Pattern
|
|
294
|
+
|
|
295
|
+
**Add events with one command:**
|
|
296
|
+
```bash
|
|
297
|
+
# Creates contract, mock, handler, and wires the stream automatically
|
|
298
|
+
pf event add orders.created --service services/notifications
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
This generates:
|
|
302
|
+
- `packages/contracts/src/events/orders-created.ts` (contract)
|
|
303
|
+
- `packages/contracts/src/events/orders-created.mock.json` (test data)
|
|
304
|
+
- `services/notifications/src/events/orders-created.event.ts` (handler)
|
|
305
|
+
- Adds stream to service `index.ts` if needed
|
|
306
|
+
|
|
307
|
+
**Test events locally:**
|
|
308
|
+
```bash
|
|
309
|
+
pf event list # Show available events
|
|
310
|
+
pf event publish orders.created # Publish mock event to NATS
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Manual pattern (if you prefer):**
|
|
314
|
+
|
|
315
|
+
**1. Define contract:**
|
|
356
316
|
```typescript
|
|
357
317
|
// packages/contracts/src/events/orders-created.ts
|
|
358
318
|
import { createContract } from '@crossdelta/cloudevents'
|
|
@@ -364,27 +324,25 @@ export const OrdersCreatedContract = createContract({
|
|
|
364
324
|
})
|
|
365
325
|
```
|
|
366
326
|
|
|
367
|
-
**
|
|
327
|
+
**2. Publish (Service A):**
|
|
328
|
+
```typescript
|
|
329
|
+
// services/orders/src/use-cases/create-order.use-case.ts
|
|
330
|
+
await publish(OrdersCreatedContract, { orderId: '123', total: 99.99 })
|
|
331
|
+
```
|
|
368
332
|
|
|
333
|
+
**3. Handle (Service B, auto-discovered):**
|
|
369
334
|
```typescript
|
|
370
335
|
// services/notifications/src/events/orders-created.event.ts
|
|
371
|
-
import { handleEvent } from '@crossdelta/cloudevents'
|
|
372
|
-
import { OrdersCreatedContract } from '@my-org/contracts'
|
|
373
|
-
import { sendOrderNotification } from '../use-cases/send-notification.use-case'
|
|
374
|
-
|
|
375
336
|
export default handleEvent(OrdersCreatedContract, async (data) => {
|
|
376
337
|
await sendOrderNotification(data)
|
|
377
338
|
})
|
|
378
339
|
```
|
|
379
340
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
</details>
|
|
341
|
+
No manual subscriptions. Convention over configuration.
|
|
383
342
|
|
|
384
|
-
|
|
385
|
-
<summary>⚡ <b>NATS message broker</b></summary>
|
|
343
|
+
---
|
|
386
344
|
|
|
387
|
-
|
|
345
|
+
## ⚡ NATS Message Broker
|
|
388
346
|
|
|
389
347
|
Every workspace includes pre-configured NATS with JetStream:
|
|
390
348
|
|
|
@@ -392,39 +350,46 @@ Every workspace includes pre-configured NATS with JetStream:
|
|
|
392
350
|
- Ports: `4222` (client), `8222` (monitoring)
|
|
393
351
|
- Health check: `curl http://localhost:8222/healthz`
|
|
394
352
|
|
|
395
|
-
|
|
353
|
+
See `services/nats/README.md` for details.
|
|
396
354
|
|
|
397
355
|
---
|
|
398
356
|
|
|
399
|
-
##
|
|
357
|
+
## Deployment
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
pulumi login && pulumi up --stack dev
|
|
361
|
+
```
|
|
400
362
|
|
|
401
|
-
|
|
402
|
-
<tr>
|
|
403
|
-
<td width="50%">
|
|
363
|
+
### GitHub Actions (pre-configured)
|
|
404
364
|
|
|
405
|
-
|
|
365
|
+
| Workflow | Trigger | Purpose |
|
|
366
|
+
|----------|---------|---------|
|
|
367
|
+
| `lint-and-tests.yml` | PR to `main` | Lint + test |
|
|
368
|
+
| `build-and-deploy.yml` | Push to `main` | Build, push to GHCR, deploy |
|
|
369
|
+
| `publish-packages.yml` | Changes in `packages/` | Publish to npm |
|
|
406
370
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
- Infrastructure that stays in sync with your code
|
|
371
|
+
<details>
|
|
372
|
+
<summary>Required secrets</summary>
|
|
410
373
|
|
|
411
|
-
|
|
412
|
-
|
|
374
|
+
| Secret | Description |
|
|
375
|
+
|--------|-------------|
|
|
376
|
+
| `PULUMI_ACCESS_TOKEN` | [Pulumi Cloud token](https://app.pulumi.com/account/tokens) |
|
|
377
|
+
| `DIGITALOCEAN_TOKEN` | [DO API token](https://cloud.digitalocean.com/account/api/tokens) |
|
|
378
|
+
| `GHCR_TOKEN` | GitHub Container Registry PAT |
|
|
379
|
+
| `NPM_TOKEN` | [npm access token](https://www.npmjs.com/settings/~/tokens) (only for `publish-packages.yml`) |
|
|
413
380
|
|
|
414
|
-
|
|
381
|
+
</details>
|
|
415
382
|
|
|
416
|
-
|
|
417
|
-
- Multi-cloud out of the box — DigitalOcean first, extensible later
|
|
418
|
-
- Magic — no hidden daemons, you control when things run
|
|
383
|
+
---
|
|
419
384
|
|
|
420
|
-
|
|
421
|
-
</tr>
|
|
422
|
-
</table>
|
|
385
|
+
## 📚 Requirements
|
|
423
386
|
|
|
424
|
-
|
|
387
|
+
- **Bun** (recommended) or Node.js ≥ 21
|
|
388
|
+
- **[Pulumi CLI](https://www.pulumi.com/docs/install/)** for deployment
|
|
389
|
+
- **[Docker](https://www.docker.com/)** for local NATS
|
|
425
390
|
|
|
426
391
|
---
|
|
427
392
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
393
|
+
## 📄 License
|
|
394
|
+
|
|
395
|
+
MIT © Crossdelta
|