@crossdelta/platform-sdk 0.20.1 → 0.21.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.
- package/bin/cli.mjs +1 -1
- package/bin/docs/generators/hono-bun.md +6 -1
- package/bin/docs/generators/service.md +23 -0
- package/bin/templates/workspace/infra/package.json.hbs +1 -1
- package/bin/templates/workspace/packages/contracts/package.json.hbs +1 -1
- package/dist/facade.js +22 -10
- package/dist/facade.mjs +22 -10
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -427,7 +427,7 @@ export default handleEvent(${Lr(o)}Contract, async (data) => {
|
|
|
427
427
|
console.log('Processing ${o}:', data)
|
|
428
428
|
// TODO: Implement handler logic
|
|
429
429
|
})
|
|
430
|
-
`.trim();return{success:!0,message:`Would create handler for ${o}`,changes:[{type:"create",path:n,description:`Create event handler for ${o}`,preview:i.slice(0,500)}]}}return t.logger.debug(`Handler created: ${o} at ${n} (service: ${s})`),{success:!0}},vm={"stream.wired":fm,"contract.created":hm,"handler.created":ym},Wr=(e,t,r)=>e.map(n=>{let o=vm[n.kind];return o?o(n,t,r):(t.logger.warn(`Unknown effect kind: ${n.kind}`),{success:!1,message:`Unknown effect kind: ${n.kind}`})});import{join as Rt}from"path";import{deriveEventNames as ad}from"@crossdelta/cloudevents";var Hr=(e,t,r={})=>({ok:!0,operation:e,summary:t,artifacts:r.artifacts??[],changes:r.changes??[],diagnostics:r.diagnostics??[],next:r.next,data:r.data});f();import{join as Gr}from"path";import{readFileSync as km}from"fs";import{join as Cm}from"path";import xm from"handlebars";var fe=(e,t)=>ho(e,t??`Templates directory not found. Searched in: ${e.join(", ")}`),Br=(e,t={})=>{let r=km(e,"utf-8");return xm.compile(r)(t)},Tt=e=>(t,r={})=>{let n=Cm(e,t);return Br(n,r)};var G=e=>e.toUpperCase().replace(/-/g,"_");var Ss=e=>e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ");var wm=()=>typeof import.meta?.url=="string"?W(import.meta.url):typeof __dirname=="string"?__dirname:process.cwd(),Pm=()=>{let e=wm(),t=ee();return fe([Gr(e,"templates","hono-microservice"),Gr(e,"..","hono-microservice","templates"),Gr(t,"bin","templates","hono-microservice")],"Hono templates directory not found.")},$t=(e,t={})=>Tt(Pm())(e,t);function bs(){return $t("tsconfig.json.hbs")}function Es(e="1.2.23"){return $t("Dockerfile.hbs",{bunVersion:e})}function Ts(){return $t("biome.json.hbs")}function $s(e,t=[]){return $t("src/config/env.ts.hbs",{envKey:G(e),envVars:t})}var Sm=e=>{let t=["import './config/env'","import '@crossdelta/telemetry'",""];return e&&t.push("import { consumeJetStreams } from '@crossdelta/cloudevents'"),t.push("import { Hono } from 'hono'"),t},bm=e=>[`const port = Number(process.env.${e}_PORT) || 8080`,"const app = new Hono()","","app.get('/health', (c) => {"," return c.json({ status: 'ok' })","})"],Em=(e,t)=>["","// Start NATS JetStream consumer","consumeJetStreams({",` streams: [${t.map(r=>`'${r}'`).join(", ")}],`,` consumer: '${e}',`," discover: './src/events/**/*.handler.ts',","})"],Tm=()=>["","Bun.serve({"," port,"," fetch: app.fetch,","})","","console.log(`\u{1F680} Service ready at http://localhost:${port}`)"],$m=e=>{let t=G(e.serviceName);return`${[...Sm(e.hasEvents),"",...bm(t),...e.hasEvents?Em(e.serviceName,e.streams):[],...Tm()].join(`
|
|
430
|
+
`.trim();return{success:!0,message:`Would create handler for ${o}`,changes:[{type:"create",path:n,description:`Create event handler for ${o}`,preview:i.slice(0,500)}]}}return t.logger.debug(`Handler created: ${o} at ${n} (service: ${s})`),{success:!0}},vm={"stream.wired":fm,"contract.created":hm,"handler.created":ym},Wr=(e,t,r)=>e.map(n=>{let o=vm[n.kind];return o?o(n,t,r):(t.logger.warn(`Unknown effect kind: ${n.kind}`),{success:!1,message:`Unknown effect kind: ${n.kind}`})});import{join as Rt}from"path";import{deriveEventNames as ad}from"@crossdelta/cloudevents";var Hr=(e,t,r={})=>({ok:!0,operation:e,summary:t,artifacts:r.artifacts??[],changes:r.changes??[],diagnostics:r.diagnostics??[],next:r.next,data:r.data});f();import{join as Gr}from"path";import{readFileSync as km}from"fs";import{join as Cm}from"path";import xm from"handlebars";var fe=(e,t)=>ho(e,t??`Templates directory not found. Searched in: ${e.join(", ")}`),Br=(e,t={})=>{let r=km(e,"utf-8");return xm.compile(r)(t)},Tt=e=>(t,r={})=>{let n=Cm(e,t);return Br(n,r)};var G=e=>e.toUpperCase().replace(/-/g,"_");var Ss=e=>e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ");var wm=()=>typeof import.meta?.url=="string"?W(import.meta.url):typeof __dirname=="string"?__dirname:process.cwd(),Pm=()=>{let e=wm(),t=ee();return fe([Gr(e,"templates","hono-microservice"),Gr(e,"..","hono-microservice","templates"),Gr(t,"bin","templates","hono-microservice")],"Hono templates directory not found.")},$t=(e,t={})=>Tt(Pm())(e,t);function bs(){return $t("tsconfig.json.hbs")}function Es(e="1.2.23"){return $t("Dockerfile.hbs",{bunVersion:e})}function Ts(){return $t("biome.json.hbs")}function $s(e,t=[]){return $t("src/config/env.ts.hbs",{envKey:G(e),envVars:t})}var Sm=e=>{let t=["import './config/env'","import '@crossdelta/telemetry'",""];return e&&t.push("import { consumeJetStreams, isConsumerConnected } from '@crossdelta/cloudevents'"),t.push("import { Hono } from 'hono'"),t},bm=(e,t)=>{let r=[`const port = Number(process.env.${e}_PORT) || 8080`,"const app = new Hono()","","app.get('/health', (c) => {"," return c.json({ status: 'ok' })","})"];return t&&r.push("","app.get('/health/ready', (c) => {"," const ready = isConsumerConnected()"," return c.json({ status: ready ? 'ok' : 'not-ready' }, ready ? 200 : 503)","})"),r},Em=(e,t)=>["","// Start NATS JetStream consumer","consumeJetStreams({",` streams: [${t.map(r=>`'${r}'`).join(", ")}],`,` consumer: '${e}',`," discover: './src/events/**/*.handler.ts',","})"],Tm=()=>["","Bun.serve({"," port,"," fetch: app.fetch,","})","","console.log(`\u{1F680} Service ready at http://localhost:${port}`)"],$m=e=>{let t=G(e.serviceName);return`${[...Sm(e.hasEvents),"",...bm(t,e.hasEvents),...e.hasEvents?Em(e.serviceName,e.streams):[],...Tm()].join(`
|
|
431
431
|
`)}
|
|
432
432
|
`},Am=e=>{let t={hono:"^4.6.14","@crossdelta/telemetry":"workspace:*",zod:"^4.1.0",[`${e.workspaceScope}/contracts`]:"workspace:*"};e.hasEvents&&(t["@crossdelta/cloudevents"]="workspace:*");let r={name:e.packageName,version:"0.0.1",type:"module",scripts:{"start:dev":"bun --watch src/index.ts",start:"bun src/index.ts",test:"bun test"},dependencies:t,devDependencies:{"@types/bun":"^1.2.23"}};return JSON.stringify(r,null,2)},Im=e=>`# ${e.serviceName}
|
|
433
433
|
|
|
@@ -60,7 +60,7 @@ console.log(`Service running on http://localhost:${port}`)
|
|
|
60
60
|
import './config/env'
|
|
61
61
|
import '@crossdelta/telemetry'
|
|
62
62
|
|
|
63
|
-
import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
63
|
+
import { consumeJetStreams, isConsumerConnected } from '@crossdelta/cloudevents'
|
|
64
64
|
import { Hono } from 'hono'
|
|
65
65
|
|
|
66
66
|
// Replace MY_SERVICE with actual service name in SCREAMING_SNAKE_CASE
|
|
@@ -69,6 +69,11 @@ const app = new Hono()
|
|
|
69
69
|
|
|
70
70
|
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
71
71
|
|
|
72
|
+
app.get('/health/ready', (c) => {
|
|
73
|
+
const ready = isConsumerConnected()
|
|
74
|
+
return c.json({ status: ready ? 'ok' : 'not-ready' }, ready ? 200 : 503)
|
|
75
|
+
})
|
|
76
|
+
|
|
72
77
|
Bun.serve({ port, fetch: app.fetch })
|
|
73
78
|
console.log(`Service running on http://localhost:${port}`)
|
|
74
79
|
|
|
@@ -82,6 +82,29 @@ const port = Number(process.env.MY_HONO_SERVICE_PORT) || 8080
|
|
|
82
82
|
|
|
83
83
|
---
|
|
84
84
|
|
|
85
|
+
## 🚨 CRITICAL: Readiness Probes
|
|
86
|
+
|
|
87
|
+
Every event-consuming service **must** expose a readiness endpoint:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { consumeJetStreams, isConsumerConnected } from '@crossdelta/cloudevents'
|
|
91
|
+
|
|
92
|
+
app.get('/health/ready', (c) => {
|
|
93
|
+
const ready = isConsumerConnected()
|
|
94
|
+
return c.json({ status: ready ? 'ok' : 'not-ready' }, ready ? 200 : 503)
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The infra config must include `readinessPath`:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
healthCheck: { httpPath: '/health', readinessPath: '/health/ready' },
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This ensures Kubernetes only routes traffic to pods with a live NATS connection.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
85
108
|
## 🚨 CRITICAL: Stream Architecture
|
|
86
109
|
|
|
87
110
|
> **Services NEVER create streams!**
|
package/dist/facade.js
CHANGED
|
@@ -1352,19 +1352,31 @@ var generateImports = (hasEvents) => {
|
|
|
1352
1352
|
""
|
|
1353
1353
|
];
|
|
1354
1354
|
if (hasEvents) {
|
|
1355
|
-
imports.push("import { consumeJetStreams } from '@crossdelta/cloudevents'");
|
|
1355
|
+
imports.push("import { consumeJetStreams, isConsumerConnected } from '@crossdelta/cloudevents'");
|
|
1356
1356
|
}
|
|
1357
1357
|
imports.push("import { Hono } from 'hono'");
|
|
1358
1358
|
return imports;
|
|
1359
1359
|
};
|
|
1360
|
-
var generateServerSetup = (envKey) =>
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1360
|
+
var generateServerSetup = (envKey, hasEvents) => {
|
|
1361
|
+
const lines = [
|
|
1362
|
+
`const port = Number(process.env.${envKey}_PORT) || 8080`,
|
|
1363
|
+
"const app = new Hono()",
|
|
1364
|
+
"",
|
|
1365
|
+
"app.get('/health', (c) => {",
|
|
1366
|
+
" return c.json({ status: 'ok' })",
|
|
1367
|
+
"})"
|
|
1368
|
+
];
|
|
1369
|
+
if (hasEvents) {
|
|
1370
|
+
lines.push(
|
|
1371
|
+
"",
|
|
1372
|
+
"app.get('/health/ready', (c) => {",
|
|
1373
|
+
" const ready = isConsumerConnected()",
|
|
1374
|
+
" return c.json({ status: ready ? 'ok' : 'not-ready' }, ready ? 200 : 503)",
|
|
1375
|
+
"})"
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
return lines;
|
|
1379
|
+
};
|
|
1368
1380
|
var generateJetStreamConsumer = (serviceName, streams) => [
|
|
1369
1381
|
"",
|
|
1370
1382
|
"// Start NATS JetStream consumer",
|
|
@@ -1388,7 +1400,7 @@ var generateIndexTs = (vars) => {
|
|
|
1388
1400
|
const parts = [
|
|
1389
1401
|
...generateImports(vars.hasEvents),
|
|
1390
1402
|
"",
|
|
1391
|
-
...generateServerSetup(envKey),
|
|
1403
|
+
...generateServerSetup(envKey, vars.hasEvents),
|
|
1392
1404
|
...vars.hasEvents ? generateJetStreamConsumer(vars.serviceName, vars.streams) : [],
|
|
1393
1405
|
...generateBunServe()
|
|
1394
1406
|
];
|
package/dist/facade.mjs
CHANGED
|
@@ -1282,19 +1282,31 @@ var generateImports = (hasEvents) => {
|
|
|
1282
1282
|
""
|
|
1283
1283
|
];
|
|
1284
1284
|
if (hasEvents) {
|
|
1285
|
-
imports.push("import { consumeJetStreams } from '@crossdelta/cloudevents'");
|
|
1285
|
+
imports.push("import { consumeJetStreams, isConsumerConnected } from '@crossdelta/cloudevents'");
|
|
1286
1286
|
}
|
|
1287
1287
|
imports.push("import { Hono } from 'hono'");
|
|
1288
1288
|
return imports;
|
|
1289
1289
|
};
|
|
1290
|
-
var generateServerSetup = (envKey) =>
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1290
|
+
var generateServerSetup = (envKey, hasEvents) => {
|
|
1291
|
+
const lines = [
|
|
1292
|
+
`const port = Number(process.env.${envKey}_PORT) || 8080`,
|
|
1293
|
+
"const app = new Hono()",
|
|
1294
|
+
"",
|
|
1295
|
+
"app.get('/health', (c) => {",
|
|
1296
|
+
" return c.json({ status: 'ok' })",
|
|
1297
|
+
"})"
|
|
1298
|
+
];
|
|
1299
|
+
if (hasEvents) {
|
|
1300
|
+
lines.push(
|
|
1301
|
+
"",
|
|
1302
|
+
"app.get('/health/ready', (c) => {",
|
|
1303
|
+
" const ready = isConsumerConnected()",
|
|
1304
|
+
" return c.json({ status: ready ? 'ok' : 'not-ready' }, ready ? 200 : 503)",
|
|
1305
|
+
"})"
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
return lines;
|
|
1309
|
+
};
|
|
1298
1310
|
var generateJetStreamConsumer = (serviceName, streams) => [
|
|
1299
1311
|
"",
|
|
1300
1312
|
"// Start NATS JetStream consumer",
|
|
@@ -1318,7 +1330,7 @@ var generateIndexTs = (vars) => {
|
|
|
1318
1330
|
const parts = [
|
|
1319
1331
|
...generateImports(vars.hasEvents),
|
|
1320
1332
|
"",
|
|
1321
|
-
...generateServerSetup(envKey),
|
|
1333
|
+
...generateServerSetup(envKey, vars.hasEvents),
|
|
1322
1334
|
...vars.hasEvents ? generateJetStreamConsumer(vars.serviceName, vars.streams) : [],
|
|
1323
1335
|
...generateBunServe()
|
|
1324
1336
|
];
|