@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 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!**
@@ -7,7 +7,7 @@
7
7
  "pulumi": "pulumi"
8
8
  },
9
9
  "dependencies": {
10
- "@crossdelta/cloudevents": "^0.7.11",
10
+ "@crossdelta/cloudevents": "^0.7.12",
11
11
  "@crossdelta/infrastructure": "^0.7.6",
12
12
  "{{scope}}/contracts": "workspace:*",
13
13
  "@pulumi/digitalocean": "^4.55.0",
@@ -19,7 +19,7 @@
19
19
  "clean": "rm -rf dist"
20
20
  },
21
21
  "dependencies": {
22
- "@crossdelta/cloudevents": "^0.7.11",
22
+ "@crossdelta/cloudevents": "^0.7.12",
23
23
  "@crossdelta/infrastructure": "^0.7.6",
24
24
  "zod": "^4.0.0"
25
25
  },
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
- `const port = Number(process.env.${envKey}_PORT) || 8080`,
1362
- "const app = new Hono()",
1363
- "",
1364
- "app.get('/health', (c) => {",
1365
- " return c.json({ status: 'ok' })",
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
- `const port = Number(process.env.${envKey}_PORT) || 8080`,
1292
- "const app = new Hono()",
1293
- "",
1294
- "app.get('/health', (c) => {",
1295
- " return c.json({ status: 'ok' })",
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
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/platform-sdk",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "description": "Platform toolkit for event-driven microservices — keeping code and infrastructure in lockstep.",
5
5
  "keywords": [
6
6
  "cli",