@crossdelta/platform-sdk 0.7.10 β 0.7.11
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
CHANGED
|
@@ -33,17 +33,42 @@
|
|
|
33
33
|
<br />
|
|
34
34
|
|
|
35
35
|
---
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Pick your package manager
|
|
41
|
+
bun add -g @crossdelta/platform-sdk
|
|
42
|
+
npm install -g @crossdelta/platform-sdk
|
|
43
|
+
pnpm add -g @crossdelta/platform-sdk
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
<details>
|
|
47
|
+
<summary>Alternative: Auto-installer or use without installing</summary>
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Auto-installer (detects package manager, offers to install Bun if needed)
|
|
51
|
+
curl -fsSL https://unpkg.com/@crossdelta/platform-sdk@latest/install.sh | bash
|
|
52
|
+
|
|
53
|
+
# Or run directly without global install
|
|
54
|
+
bunx @crossdelta/platform-sdk new workspace my-platform
|
|
55
|
+
npx @crossdelta/platform-sdk new workspace my-platform
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
</details>
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
37
63
|
|
|
38
64
|
```bash
|
|
39
|
-
# Create workspace
|
|
40
|
-
|
|
65
|
+
# Create workspace
|
|
66
|
+
pf new workspace my-platform -y
|
|
41
67
|
cd my-platform
|
|
42
68
|
|
|
43
69
|
# Generate microservice with AI
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-d "Handle order creation and payment"
|
|
70
|
+
pf setup --ai # Configure AI provider (first time only)
|
|
71
|
+
pf new hono-micro services/orders --ai -d "Handle order creation and payment"
|
|
47
72
|
|
|
48
73
|
# Start development
|
|
49
74
|
pf dev
|
|
@@ -71,43 +96,8 @@ pf pulumi up # β runs in infra/ directory
|
|
|
71
96
|
|
|
72
97
|
**Why:** Keeps commands consistent across the team and works from any subdirectory.
|
|
73
98
|
|
|
74
|
-
**How it works:** `pf` walks up to find your workspace root, then proxies to your scripts. Configured commands via `pf.commands` can override the working directory or command. Registered `pf` commands (like `pf new`) take precedence.
|
|
75
|
-
|
|
76
99
|
> **π Note:** You can be productive with `pf` in minutes using the commands above. The sections below are reference documentationβexplore them when you need specific details.
|
|
77
100
|
|
|
78
|
-
### No runtime installed?
|
|
79
|
-
|
|
80
|
-
If you don't have a JavaScript runtime, use our installer that sets everything up.
|
|
81
|
-
**The installer is optional** β you can always use `bunx`/`npx` or a global install.
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
# Auto-installer (detects bun/pnpm/yarn/npm; if none found it can install Bun)
|
|
85
|
-
curl -fsSL https://unpkg.com/@crossdelta/platform-sdk@latest/install.sh | bash
|
|
86
|
-
|
|
87
|
-
# Then use short commands
|
|
88
|
-
pf new workspace my-platform
|
|
89
|
-
pf new hono-micro services/orders --ai -d "..."
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
> **Security:** The installer asks permission before installing Bun (if needed) and does not run any workspace commands automatically.
|
|
93
|
-
|
|
94
|
-
<details>
|
|
95
|
-
<summary><strong>π What does the installer do?</strong></summary>
|
|
96
|
-
|
|
97
|
-
The script ([view source](https://unpkg.com/@crossdelta/platform-sdk@latest/install.sh)):
|
|
98
|
-
1. Checks for bun/pnpm/yarn/npm
|
|
99
|
-
2. If none found, **asks permission** to install Bun
|
|
100
|
-
3. Installs `pf` CLI globally
|
|
101
|
-
|
|
102
|
-
**Manual installation:**
|
|
103
|
-
```bash
|
|
104
|
-
# With Bun/npm/yarn/pnpm
|
|
105
|
-
bun add -g @crossdelta/platform-sdk
|
|
106
|
-
# or: npm install -g @crossdelta/platform-sdk
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
</details>
|
|
110
|
-
|
|
111
101
|
<br />
|
|
112
102
|
|
|
113
103
|
---
|
|
@@ -161,30 +151,23 @@ When you create a workspace with `pf`, you get a **Turborepo monorepo** with thi
|
|
|
161
151
|
|
|
162
152
|
```
|
|
163
153
|
my-platform/
|
|
164
|
-
βββ services/ # Microservices (
|
|
165
|
-
β βββ orders/ # Example: Order processing service (Event Publisher)
|
|
166
|
-
β βββ notifications/# Example: Notification service (Event Consumer)
|
|
154
|
+
βββ services/ # Microservices (generate with pf new hono-micro)
|
|
167
155
|
β βββ nats/ # NATS message broker (auto-scaffolded)
|
|
168
|
-
βββ apps/ # Frontend apps (optional
|
|
156
|
+
βββ apps/ # Frontend apps (optional)
|
|
169
157
|
βββ packages/ # Shared libraries
|
|
170
|
-
β
|
|
171
|
-
β
|
|
172
|
-
β
|
|
173
|
-
β βββ cloudevents/ # Event publishing/consuming toolkit
|
|
174
|
-
β βββ telemetry/ # OpenTelemetry setup
|
|
175
|
-
β βββ infrastructure/ # Pulumi utilities and K8s builders
|
|
158
|
+
β βββ contracts/ # Event contracts (Schema Registry)
|
|
159
|
+
β βββ src/
|
|
160
|
+
β βββ index.ts
|
|
176
161
|
βββ infra/ # Pulumi Infrastructure-as-Code
|
|
177
162
|
β βββ index.ts # Main Pulumi program
|
|
178
|
-
β βββ services/ # Per-service K8s configs
|
|
179
|
-
β βββ orders.ts
|
|
180
|
-
β βββ notifications.ts
|
|
163
|
+
β βββ services/ # Per-service K8s configs (auto-generated)
|
|
181
164
|
βββ .github/
|
|
182
|
-
β βββ workflows/ # CI/CD pipelines
|
|
165
|
+
β βββ workflows/ # CI/CD pipelines
|
|
183
166
|
βββ turbo.json # Turborepo task orchestration
|
|
184
167
|
βββ .env.local # Auto-generated from infra configs
|
|
185
168
|
```
|
|
186
169
|
|
|
187
|
-
> **Note:**
|
|
170
|
+
> **Note:** Services use external packages (`@crossdelta/cloudevents`, `@crossdelta/telemetry`, `@crossdelta/infrastructure`) installed via npm. Only `packages/contracts` is local to your workspace.
|
|
188
171
|
|
|
189
172
|
### Key Architectural Decisions
|
|
190
173
|
|
|
@@ -201,11 +184,11 @@ my-platform/
|
|
|
201
184
|
Services communicate via **CloudEvents** over **NATS JetStream** using the **Schema Registry** as single source of truth:
|
|
202
185
|
|
|
203
186
|
```typescript
|
|
204
|
-
// packages/contracts/src/events/
|
|
187
|
+
// packages/contracts/src/events/orders-created.ts (Schema Registry)
|
|
205
188
|
import { createContract } from '@crossdelta/cloudevents'
|
|
206
189
|
import { z } from 'zod'
|
|
207
190
|
|
|
208
|
-
export const
|
|
191
|
+
export const OrdersCreatedContract = createContract({
|
|
209
192
|
type: 'orders.created',
|
|
210
193
|
schema: z.object({
|
|
211
194
|
orderId: z.string(),
|
|
@@ -214,24 +197,24 @@ export const OrderCreatedContract = createContract({
|
|
|
214
197
|
}),
|
|
215
198
|
})
|
|
216
199
|
|
|
217
|
-
export type
|
|
200
|
+
export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
|
|
218
201
|
|
|
219
202
|
// Service A publishes an event (Event Publisher)
|
|
220
203
|
import { publish } from '@crossdelta/cloudevents'
|
|
221
|
-
import {
|
|
204
|
+
import { OrdersCreatedContract } from '@my-platform/contracts'
|
|
222
205
|
|
|
223
|
-
await publish(
|
|
206
|
+
await publish(OrdersCreatedContract, {
|
|
224
207
|
orderId: '123',
|
|
225
208
|
customerId: 'cust-456',
|
|
226
209
|
total: 99.99
|
|
227
210
|
})
|
|
228
211
|
|
|
229
212
|
// Service B auto-discovers and handles it (Event Consumer)
|
|
230
|
-
// File: services/notifications/src/events/
|
|
213
|
+
// File: services/notifications/src/events/orders-created.event.ts
|
|
231
214
|
import { handleEvent } from '@crossdelta/cloudevents'
|
|
232
|
-
import {
|
|
215
|
+
import { OrdersCreatedContract, type OrdersCreatedData } from '@my-platform/contracts'
|
|
233
216
|
|
|
234
|
-
export default handleEvent(
|
|
217
|
+
export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
|
|
235
218
|
await sendNotification(data)
|
|
236
219
|
})
|
|
237
220
|
```
|
|
@@ -467,21 +450,21 @@ await publish('orders.created', { orderId: 'ord_123', total: 99.99 })
|
|
|
467
450
|
### Consume Events (Auto-Discovered)
|
|
468
451
|
|
|
469
452
|
```typescript
|
|
470
|
-
// services/notifications/src/events/
|
|
453
|
+
// services/notifications/src/events/orders-created.event.ts
|
|
471
454
|
import { handleEvent } from '@crossdelta/cloudevents'
|
|
472
455
|
import { z } from 'zod'
|
|
473
456
|
|
|
474
|
-
const
|
|
457
|
+
const OrdersCreatedSchema = z.object({
|
|
475
458
|
orderId: z.string(),
|
|
476
459
|
total: z.number(),
|
|
477
460
|
})
|
|
478
461
|
|
|
479
462
|
// Export type for use in use-cases
|
|
480
|
-
export type
|
|
463
|
+
export type OrdersCreatedEvent = z.infer<typeof OrdersCreatedSchema>
|
|
481
464
|
|
|
482
465
|
export default handleEvent(
|
|
483
466
|
{
|
|
484
|
-
schema:
|
|
467
|
+
schema: OrdersCreatedSchema,
|
|
485
468
|
type: 'orders.created',
|
|
486
469
|
},
|
|
487
470
|
async (data) => {
|
package/bin/cli.js
CHANGED
|
@@ -142,7 +142,7 @@ export type ${y} = z.infer<typeof ${g}.schema>
|
|
|
142
142
|
`;(0,H.writeFileSync)(x,w,"utf-8");let v=(0,H.readFileSync)(l,"utf-8"),k=`export * from './events/${m}'`;if(!v.includes(k)){let M=v.split(`
|
|
143
143
|
`),K=M.findLastIndex(Be=>Be.startsWith("export"));K>=0?M.splice(K+1,0,k):M.push("",k),v=M.join(`
|
|
144
144
|
`),(0,H.writeFileSync)(l,v,"utf-8")}let O=(0,H.readFileSync)(r,"utf-8").replace(/import\s+\{[^}]*\}\s+from\s+['"]\.\.\/types\/[^'"]+['"]/,`import { ${g}, type ${y} } from '${p}/contracts'`).replace(/handleEvent\s*\(\s*\{[\s\S]*?type:\s*['"]([^'"]+)['"][\s\S]*?schema:\s*\w+Schema[\s\S]*?\}\s*,/,`handleEvent(${g},`).replace(new RegExp(`\\b${i.replace("Schema","Event")}\\b`,"g"),y);(0,H.writeFileSync)(r,O,"utf-8");let _=`${m}.mock.json`,C=(0,Se.join)(c,_),T={eventName:t,description:`Mock data for ${t} event`,data:n};Object.keys(o).length>0&&(T.faker=o),(0,H.writeFileSync)(C,JSON.stringify(T,null,2),"utf-8")},o5=async(r,e={})=>{let{outputDir:t,overwrite:s=!1}=e;if(!(0,H.existsSync)(r))return S.error(`Event handler not found: ${r}`),{mockPath:null};let i=(0,H.readFileSync)(r,"utf-8");if(i.includes("Contract")&&(i.includes("contracts'")||i.includes('contracts"')))return await i5(r,i,e);let n=Z4(i);if(!n)return S.error(`Could not parse event handler: ${r}`),{mockPath:null};let{eventType:o,schemaCode:a,schemaName:c}=n,l=a;if(!a){let y=(0,Se.dirname)(r),w=(0,Se.dirname)(y),v=(0,Se.join)(w,"types","events.ts");if((0,H.existsSync)(v)){let k=(0,H.readFileSync)(v,"utf-8"),A=k.indexOf(`export const ${c}`);if(A>=0){let O=k.indexOf("z.object(",A),_=k.indexOf("{",O),C=1,T=_+1;for(;T<k.length&&C>0;)k[T]==="{"&&C++,k[T]==="}"&&C--,T++;let M=k.indexOf(")",T);M>=0&&(l=k.substring(A,M+1).replace(/^export\s+/,""))}}if(!l)return S.error(`Could not load schema ${c} from types file`),{mockPath:null}}let{data:u,faker:h}=await Q4(l,c,r),f=ae();if((i.includes("from '../types/")||i.includes('from "../types/'))&&!t)return await n5(r,f,o,l,c,u,h),{mockPath:null,skipped:!0,reason:"Migrated to Advanced Mode (contracts)"};let p=t??lt().eventsPath;if(!(0,H.existsSync)(p))return S.error(`Contracts events directory not found: ${p}`),{mockPath:null};let b=`${Wg(o)}.mock.json`,x=(0,Se.join)(p,b);if((0,H.existsSync)(x)&&!s)return S.warn(`Mock file already exists: ${x}`),{mockPath:null};let g={eventName:o,description:`Mock data for ${o} event`,data:u};return Object.keys(h).length>0&&(g.faker=h),(0,H.writeFileSync)(x,JSON.stringify(g,null,2),"utf-8"),{mockPath:x}},Jo=async(r,e={})=>{let t=(0,Se.join)(r,"src","events");if(!(0,H.existsSync)(t))return{mockPaths:[],skippedHandlers:[]};let s=(0,H.readdirSync)(t).filter(a=>a.endsWith(".event.ts")).map(a=>(0,Se.join)(t,a)),i=await Promise.all(s.map(a=>o5(a,e))),n=i.filter(a=>a.mockPath!==null).map(a=>a.mockPath),o=i.filter(a=>a.skipped).map((a,c)=>`${(0,Se.basename)(s[c])} (${a.reason})`);return{mockPaths:n,skippedHandlers:o}};var Zr=require("node:fs"),s2=require("node:path"),O0=require("node:process"),i2=require("execa");var T0=require("node:child_process"),Qg=require("node:path"),e2=require("execa");var Yg=require("node:fs"),Jg=require("node:path"),Zg=E(Kg()),F0=(r,e)=>{let t=(0,Jg.resolve)(e,r);return(0,Yg.existsSync)(t)?(0,Zg.config)({path:t,processEnv:{}}).parsed||{}:{}},Xg=(r,e)=>{let t=F0(r,e),s=[];for(let[i,n]of Object.entries(t))if(i.endsWith("_PORT")&&n){let o=Number.parseInt(n,10);!Number.isNaN(o)&&o>0&&s.push(o)}return s};var E5={...process.env,NODE_NO_WARNINGS:"1"};async function vr(r,e,{cwd:t=process.cwd(),task:s,shell:i,context:n=r,quiet:o=!1,nonInteractive:a=!1,env:c}={}){try{e.length===0&&(e=r.split(" ").slice(1));let l=o?"pipe":s||a?["ignore","pipe","pipe"]:"inherit",u={...E5};c?u={...u,...c}:(s||a)&&(u={...u,CI:"true"});let h=(0,e2.execa)(r,e,{cwd:t,stdio:l,all:s||a?!0:void 0,shell:i??!0,env:u});h.all&&h.all.on("data",f=>{let d=f.toString().trim();s?(s.output=d,d.length&&S.storeLog(d,n)):a&&d.length&&console.log(d)}),await h}catch(l){let u=l instanceof Error?l.message:String(l),h=S5(u);throw new Error(h)}}var S5=r=>{let e=r.match(/error: (.+)/);return e?e[1].trim():r};function t2(r,e,t={}){let{cwd:s=process.cwd(),envFile:i,detached:n=!0,pipeStdout:o=!1,onStdout:a,onStderr:c,onExit:l}=t,u=i?F0(i,s):{},h=(0,Qg.resolve)(s,"node_modules",".bin"),f=process.env.PATH||"",d=`${h}:${f}`,p={...process.env,...u,PATH:d,FORCE_COLOR:"1"},m=(0,T0.spawn)(r,e,{cwd:s,env:p,stdio:["inherit",o?"pipe":"inherit","pipe"],detached:n});return a&&o&&m.stdout?.on("data",a),c&&m.stderr?.on("data",c),l&&m.on("exit",l),m}function P0(r,e,t={}){let{cwd:s=process.cwd()}=t;(0,T0.spawn)(r,e,{cwd:s,stdio:"inherit",shell:!0}).on("exit",n=>process.exit(n||0))}async function r2(r){let{execSync:e}=await import("node:child_process");for(let t of r)try{let i=e(`lsof -ti:${t}`,{encoding:"utf8",stdio:"pipe"}).trim().split(`
|
|
145
|
-
`).filter(Boolean);for(let n of i)try{process.kill(Number.parseInt(n,10),"SIGKILL")}catch{}}catch{}}var Xo=r=>Mi(r);function n2(r){let e=(0,s2.join)(nn(),"node_modules",r);return(0,Zr.existsSync)(e)}var o2=r=>Object.keys(Dr.scripts??{}).includes(r);function I0(){if((0,Zr.existsSync)("bun.lock")||(0,Zr.existsSync)("bun.lockb"))return"bun";if((0,Zr.existsSync)("pnpm-lock.yaml"))return"pnpm";if((0,Zr.existsSync)("yarn.lock"))return"yarn";if((0,Zr.existsSync)("package-lock.json"))return"npm"}function Le(){return I0()??B0()}function B0(){return Xo("bun")?"bun":Xo("pnpm")?"pnpm":Xo("yarn")?"yarn":"npm"}function a2(){return["bun","pnpm","yarn","npm"].filter(Xo)}async function Cr(r,e){let t=k5(r,e),{mergedOptions:s,packages:i}=t,{cwd:n=process.cwd(),flags:o=[],task:a}=s,c=s.packageManager??Le(),l=[],u=Array.isArray(i)?i:[i];c==="bun"&&(u=u.map(f=>f.replace("git+ssh://",""))),c==="pnpm"&&
|
|
145
|
+
`).filter(Boolean);for(let n of i)try{process.kill(Number.parseInt(n,10),"SIGKILL")}catch{}}catch{}}var Xo=r=>Mi(r);function n2(r){let e=(0,s2.join)(nn(),"node_modules",r);return(0,Zr.existsSync)(e)}var o2=r=>Object.keys(Dr.scripts??{}).includes(r);function I0(){if((0,Zr.existsSync)("bun.lock")||(0,Zr.existsSync)("bun.lockb"))return"bun";if((0,Zr.existsSync)("pnpm-lock.yaml"))return"pnpm";if((0,Zr.existsSync)("yarn.lock"))return"yarn";if((0,Zr.existsSync)("package-lock.json"))return"npm"}function Le(){return I0()??B0()}function B0(){return Xo("bun")?"bun":Xo("pnpm")?"pnpm":Xo("yarn")?"yarn":"npm"}function a2(){return["bun","pnpm","yarn","npm"].filter(Xo)}async function Cr(r,e){let t=k5(r,e),{mergedOptions:s,packages:i}=t,{cwd:n=process.cwd(),flags:o=[],task:a}=s,c=s.packageManager??Le(),l=[],u=Array.isArray(i)?i:[i];c==="bun"&&(u=u.map(f=>f.replace("git+ssh://",""))),c==="pnpm"&&l.push("--config.engine-strict=false"),["yarn","npm"].includes(c)&&l.push("--ignore-engines");let h=u.length?c==="npm"?["install",...o,...l,...u]:["add",...o,...l,...u]:["install",...l];await vr(c,h,{cwd:n,task:a})}async function c2(r){let e=["-g"],t=[...r.flags??[],...e];await Cr({...r,flags:t,packageManager:r.packageManager??Le()})}function l2(r,e,t){let{args:s,mergedOptions:i}=h2(e??[],t),n=i.manager??Le(),{command:o,args:a}=u2(n);(0,i2.execaSync)(o,[...a,r,...s],{cwd:i.cwd??(0,O0.cwd)(),stdio:"inherit",preferLocal:!0})}async function Mt(r,e,t){let{args:s,mergedOptions:i}=h2(e??[],t),n=i.manager??Le(),{command:o,args:a}=u2(n);await vr(o,[...a,r,...s],i)}async function Qo(r,e){let{script:t,mergedOptions:s}=_5(r,e),i=s.packageManager??Le();await vr(i,["run",t,...s.args??[]],{cwd:s.cwd??(0,O0.cwd)(),task:s.task})}function u2(r){let t={bun:"bunx",pnpm:"npx",yarn:"npx",npm:"npx"}[r];if(!t)throw new Error(`No executor found for the detected package manager: ${r}`);let[s,...i]=t.split(" ");return{command:s,args:i}}function k5(r,e){return Array.isArray(r)?{packages:r,mergedOptions:{...e,packages:r}}:{packages:r.packages??[],mergedOptions:r}}function h2(r,e){return Array.isArray(r)?{args:r,mergedOptions:e??{}}:{args:[],mergedOptions:r}}function _5(r,e){return typeof r=="string"?{script:r,mergedOptions:e??{}}:{script:r.script,mergedOptions:r}}function A5(r){let e=r.split(/\s+/),t=e[0],s=e.slice(1);if(t==="pf"){t=process.argv[1];let i=s.filter(o=>o.startsWith("-"));s=[...s.filter(o=>!o.startsWith("-")),...i]}return{executable:t,args:s}}async function F5(r,e){try{let{executable:t,args:s}=A5(r.command);return await vr(t,s,{cwd:e,shell:!1}),{command:r.command,success:!0}}catch(t){return{command:r.command,success:!1,error:t instanceof Error?t.message:String(t)}}}async function f2(r,e){let t=[];for(let s of r){let i=await F5(s,e);if(t.push(i),!i.success)break}return t}function d2(r){let e=[/^pf\s+new\s+/,/^pf\s+add\s+/,/^bun\s+pf\s+new\s+/,/^bun\s+pf\s+add\s+/];return r.filter(t=>e.some(s=>s.test(t.command)))}function p2(r){for(let e of r){let t=e.command.match(/(?:bun\s+)?pf\s+new\s+\S+\s+(\S+)/);if(t)return t[1]}return null}var qs=require("node:fs"),m2=require("node:path");var x2=r=>{let{eventsPath:e}=lt(r);if(!(0,qs.existsSync)(e))return[];let t=[];try{let s=(0,qs.readdirSync)(e).filter(i=>i.endsWith(".ts")&&!i.endsWith(".mock.json")&&i!=="index.ts");for(let i of s){let n=(0,m2.join)(e,i),o=(0,qs.readFileSync)(n,"utf-8"),a=T5(o,n);a&&t.push(a)}}catch(s){return console.warn("Warning: Could not scan contracts package:",s),[]}return t},T5=(r,e)=>{try{let t=r.match(/export const (\w+Contract) = createContract/);if(!t)return null;let s=t[1],i=r.match(/export type (\w+(?:Data|Event)) = z\.infer/);if(!i)return null;let n=i[1],o=r.match(/type:\s*['"]([^'"]+)['"]/);if(!o)return null;let a=o[1],c=P5(r);return{name:s,typeName:n,eventType:a,fields:c,filePath:e}}catch{return null}},P5=r=>{let e=[],t=r.match(/z\.object\({([^}]+)\}/);if(!t)return e;let s=t[1],i=/(\w+):\s*z\.(\w+)\([^)]*\)/g,n;for(;(n=i.exec(s))!==null;){let[,o,a]=n,l={string:"string",number:"number",boolean:"boolean",array:"array",object:"object",date:"date"}[a]||a;e.push(`${o}: ${l}`)}if(r.includes("z.array(")){let o=/(\w+):\s*z\.array\(/g;for(;(n=o.exec(r))!==null;){let a=n[1];e.some(c=>c.startsWith(a))||e.push(`${a}: array`)}}return e},g2=(r,e)=>{if(r.length===0)return`
|
|
146
146
|
**Available Contracts:**
|
|
147
147
|
|
|
148
148
|
No contracts are currently defined. Use Basic Mode with inline schemas for all events.
|
|
@@ -11,9 +11,9 @@ This package contains **shared event definitions** (contracts) for events that a
|
|
|
11
11
|
```
|
|
12
12
|
src/
|
|
13
13
|
βββ events/ # Event contracts and mock data
|
|
14
|
-
β βββ
|
|
15
|
-
β βββ
|
|
16
|
-
β βββ index.ts
|
|
14
|
+
β βββ orders-created.ts # Contract definition
|
|
15
|
+
β βββ orders-created.mock.json # Mock data for testing
|
|
16
|
+
β βββ index.ts # Re-exports
|
|
17
17
|
βββ index.ts # Main exports
|
|
18
18
|
```
|
|
19
19
|
|
|
@@ -29,9 +29,9 @@ src/
|
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
31
|
import { handleEvent } from '@crossdelta/cloudevents'
|
|
32
|
-
import {
|
|
32
|
+
import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
|
|
33
33
|
|
|
34
|
-
export default handleEvent(
|
|
34
|
+
export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
|
|
35
35
|
// data is fully typed from contract
|
|
36
36
|
console.log(data.orderId, data.customerId)
|
|
37
37
|
})
|
|
@@ -41,10 +41,10 @@ export default handleEvent(OrderCreatedContract, async (data: OrderCreatedData)
|
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
43
|
import { publish } from '@crossdelta/cloudevents'
|
|
44
|
-
import {
|
|
44
|
+
import { OrdersCreatedContract } from '{{scope}}/contracts'
|
|
45
45
|
|
|
46
46
|
// Type-safe publishing with contract
|
|
47
|
-
await publish(
|
|
47
|
+
await publish(OrdersCreatedContract, {
|
|
48
48
|
orderId: 'order-123',
|
|
49
49
|
customerId: 'cust-456',
|
|
50
50
|
total: 99.99,
|
|
@@ -55,9 +55,9 @@ await publish(OrderCreatedContract, {
|
|
|
55
55
|
### In Use-Cases
|
|
56
56
|
|
|
57
57
|
```ts
|
|
58
|
-
import type {
|
|
58
|
+
import type { OrdersCreatedData } from '{{scope}}/contracts'
|
|
59
59
|
|
|
60
|
-
export const processOrder = async (data:
|
|
60
|
+
export const processOrder = async (data: OrdersCreatedData) => {
|
|
61
61
|
// Use typed event data
|
|
62
62
|
}
|
|
63
63
|
```
|
|
@@ -68,25 +68,25 @@ Contracts are **auto-generated** when you create event handlers:
|
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
70
|
# 1. Create service with event handler
|
|
71
|
-
pf new hono-micro notifications --ai -d "Sends emails on
|
|
71
|
+
pf new hono-micro notifications --ai -d "Sends emails on orders.created events"
|
|
72
72
|
|
|
73
73
|
# 2. Generate contract (if missing) and mock data
|
|
74
74
|
pf event:generate services/notifications
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
This creates:
|
|
78
|
-
- `packages/contracts/src/events/
|
|
79
|
-
- `packages/contracts/src/events/
|
|
78
|
+
- `packages/contracts/src/events/orders-created.ts` (contract)
|
|
79
|
+
- `packages/contracts/src/events/orders-created.mock.json` (mock)
|
|
80
80
|
- Updates `packages/contracts/src/index.ts` (exports)
|
|
81
81
|
|
|
82
82
|
### Manual Contract Creation
|
|
83
83
|
|
|
84
84
|
```ts
|
|
85
|
-
// packages/contracts/src/events/
|
|
85
|
+
// packages/contracts/src/events/orders-created.ts
|
|
86
86
|
import { createContract } from '@crossdelta/cloudevents'
|
|
87
87
|
import { z } from 'zod'
|
|
88
88
|
|
|
89
|
-
export const
|
|
89
|
+
export const OrdersCreatedSchema = z.object({
|
|
90
90
|
orderId: z.string(),
|
|
91
91
|
customerId: z.string(),
|
|
92
92
|
total: z.number(),
|
|
@@ -97,12 +97,12 @@ export const OrderCreatedSchema = z.object({
|
|
|
97
97
|
})),
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
-
export const
|
|
101
|
-
type: '
|
|
102
|
-
schema:
|
|
100
|
+
export const OrdersCreatedContract = createContract({
|
|
101
|
+
type: 'orders.created',
|
|
102
|
+
schema: OrdersCreatedSchema,
|
|
103
103
|
})
|
|
104
104
|
|
|
105
|
-
export type
|
|
105
|
+
export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
## Testing with Mocks
|
|
@@ -112,10 +112,10 @@ export type OrderCreatedData = z.infer<typeof OrderCreatedContract.schema>
|
|
|
112
112
|
pf event:list
|
|
113
113
|
|
|
114
114
|
# Publish mock event
|
|
115
|
-
pf event:publish
|
|
115
|
+
pf event:publish orders.created
|
|
116
116
|
|
|
117
117
|
# Publish with custom data
|
|
118
|
-
pf event:publish
|
|
118
|
+
pf event:publish orders.created --data '{"orderId":"test-123"}'
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
## Guidelines
|
|
@@ -128,7 +128,7 @@ pf event:publish order.created --data '{"orderId":"test-123"}'
|
|
|
128
128
|
- β Don't include event handlers in this package
|
|
129
129
|
|
|
130
130
|
**Naming conventions:**
|
|
131
|
-
- Contracts: `
|
|
132
|
-
- Types: `
|
|
133
|
-
- Files: `
|
|
134
|
-
- Event types: `
|
|
131
|
+
- Contracts: `OrdersCreatedContract` (plural namespace)
|
|
132
|
+
- Types: `OrdersCreatedData`
|
|
133
|
+
- Files: `orders-created.ts`
|
|
134
|
+
- Event types: `orders.created` (plural namespace, dot notation)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crossdelta/platform-sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.11",
|
|
4
4
|
"description": "Your AI-powered platform engineer. Scaffold complete Turborepo workspaces, generate microservice boilerplate with natural language, and deploy to the cloud β all from one CLI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|