@tdk-landscape/tdk-cli-core 1.3.76 → 1.3.77

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.
Files changed (76) hide show
  1. package/README.md +58 -12
  2. package/cli/bin/tdk.js +22 -2
  3. package/cli/dist/cli.js +9 -2
  4. package/cli/dist/commands/completion.js +30 -3
  5. package/cli/dist/commands/config.js +59 -1
  6. package/cli/dist/commands/doctor.js +286 -98
  7. package/cli/dist/commands/eject.js +77 -0
  8. package/cli/dist/commands/networks.js +17 -4
  9. package/cli/dist/commands/project.js +28 -5
  10. package/cli/dist/commands/resource.js +118 -10
  11. package/cli/dist/commands/up.js +93 -21
  12. package/cli/dist/commands/upgrade.js +163 -68
  13. package/cli/dist/generator/template-engine.js +77 -13
  14. package/cli/dist/types/index.js +6 -1
  15. package/cli/dist/utils/cold-preflight.js +198 -0
  16. package/cli/dist/utils/constants.js +4 -0
  17. package/cli/dist/utils/docker.js +15 -1
  18. package/cli/dist/utils/doctor-runtime.js +48 -16
  19. package/cli/dist/utils/doctor-wiring.js +15 -7
  20. package/cli/dist/utils/errors.js +5 -5
  21. package/cli/dist/utils/exec-async.js +30 -0
  22. package/cli/dist/utils/generated-paths.js +49 -0
  23. package/cli/dist/utils/platform.js +53 -0
  24. package/cli/dist/utils/port-assignment.js +13 -20
  25. package/cli/dist/utils/project-templates.js +3 -2
  26. package/cli/dist/utils/resource-features.js +6 -2
  27. package/cli/dist/utils/service-manifest.js +73 -0
  28. package/cli/dist/utils/services.js +8 -1
  29. package/cli/dist/utils/tar.js +27 -5
  30. package/cli/dist/utils/tilt-process.js +53 -0
  31. package/cli/dist/utils/tilt.js +3 -1
  32. package/cli/dist/utils/which.js +26 -0
  33. package/cli/dist/utils/windows-doctor.js +175 -0
  34. package/cli/package.json +1 -1
  35. package/cli/templates/TILT_RESOURCE_DEFAULTS.star.hbs +1 -2
  36. package/cli/templates/TILT_TECH_STACK.star.hbs +0 -1
  37. package/cli/templates/Tiltfile.hbs +0 -1
  38. package/cli/templates/spec.master.hbs +1 -2
  39. package/discovery/AGENTS.md +1 -1
  40. package/discovery/constants.star +2 -2
  41. package/discovery/discovery_orchestrator.star +2 -2
  42. package/discovery/manifest/constants.star +1 -1
  43. package/discovery/manifest/loading.star +7 -3
  44. package/discovery/registry.star +17 -3
  45. package/engine/README.md +2 -0
  46. package/engine/schemas/service-schema.json +47 -21
  47. package/engine/scripts/generate-manifest.sh +2 -1
  48. package/engine/topologies/platform/docker/compose/compose.star +37 -5
  49. package/engine/topologies/tilt/common/utils.star +54 -11
  50. package/engine/topologies/tilt/manifest/AGENTS.md +1 -1
  51. package/engine/topologies/tilt/manifest/constants.star +3 -0
  52. package/engine/topologies/tilt/manifest/schema.star +2 -4
  53. package/engine/topologies/tilt/manifest/validator.star +1 -1
  54. package/engine/topologies/tilt/resources/infra-loader.star +14 -3
  55. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +15 -11
  56. package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +15 -6
  57. package/examples/tdk-example/LICENSE +21 -0
  58. package/examples/tdk-example/README.md +23 -0
  59. package/examples/tdk-example/TILT_SERVICE_DEFAULTS.star +42 -0
  60. package/examples/tdk-example/TILT_TECH_STACK.star +67 -0
  61. package/examples/tdk-example/Tiltfile +25 -0
  62. package/examples/tdk-example/services/platform/messaging/docker-compose.yml +26 -0
  63. package/examples/tdk-example/services/shop/orders-api/package.json +20 -0
  64. package/examples/tdk-example/services/shop/orders-api/service.json +14 -0
  65. package/examples/tdk-example/services/shop/orders-api/src/index.ts +81 -0
  66. package/examples/tdk-example/services/shop/orders-app/index.html +12 -0
  67. package/examples/tdk-example/services/shop/orders-app/package.json +20 -0
  68. package/examples/tdk-example/services/shop/orders-app/public/.gitkeep +0 -0
  69. package/examples/tdk-example/services/shop/orders-app/service.json +9 -0
  70. package/examples/tdk-example/services/shop/orders-app/src/main.tsx +9 -0
  71. package/examples/tdk-example/services/shop/orders-app/src/orders.tsx +67 -0
  72. package/examples/tdk-example/services/shop/orders-app/src/style.css +15 -0
  73. package/examples/tdk-example/services/shop/orders-worker/package.json +18 -0
  74. package/examples/tdk-example/services/shop/orders-worker/service.json +13 -0
  75. package/examples/tdk-example/services/shop/orders-worker/src/index.ts +33 -0
  76. package/package.json +2 -1
@@ -0,0 +1,42 @@
1
+ # =============================================================================
2
+ # TILT_SERVICE_DEFAULTS.star - Platform Runtime Configuration
3
+ # =============================================================================
4
+
5
+ BASE_PORT_FRONTEND = 3000
6
+ BASE_PORT_BACKEND = 4000
7
+
8
+ HEALTH_CHECK_PATH = "/health"
9
+ HEALTH_CHECK_PATH_LIVE = "/health/live"
10
+ HEALTH_CHECK_PATH_READY = "/health/ready"
11
+ HEALTH_CHECK_INTERVAL = 10
12
+ HEALTH_CHECK_TIMEOUT = 5
13
+ HEALTH_CHECK_RETRIES = 3
14
+
15
+ MEMORY_LIMITS = {
16
+ "frontend": 512,
17
+ "backend": 1024,
18
+ "worker": 768,
19
+ "infra": 256,
20
+ }
21
+
22
+ DOCKER_BASE_IMAGES = {
23
+ "bun": "oven/bun:1.2",
24
+ "node": "node:20-alpine",
25
+ "nginx": "nginx:alpine",
26
+ }
27
+
28
+ NETWORK_PREFIX = "tdk"
29
+
30
+ exports = {
31
+ "BASE_PORT_FRONTEND": BASE_PORT_FRONTEND,
32
+ "BASE_PORT_BACKEND": BASE_PORT_BACKEND,
33
+ "HEALTH_CHECK_PATH": HEALTH_CHECK_PATH,
34
+ "HEALTH_CHECK_PATH_LIVE": HEALTH_CHECK_PATH_LIVE,
35
+ "HEALTH_CHECK_PATH_READY": HEALTH_CHECK_PATH_READY,
36
+ "HEALTH_CHECK_INTERVAL": HEALTH_CHECK_INTERVAL,
37
+ "HEALTH_CHECK_TIMEOUT": HEALTH_CHECK_TIMEOUT,
38
+ "HEALTH_CHECK_RETRIES": HEALTH_CHECK_RETRIES,
39
+ "MEMORY_LIMITS": MEMORY_LIMITS,
40
+ "DOCKER_BASE_IMAGES": DOCKER_BASE_IMAGES,
41
+ "NETWORK_PREFIX": NETWORK_PREFIX,
42
+ }
@@ -0,0 +1,67 @@
1
+ # =============================================================================
2
+ # TILT_TECH_STACK.star - Technology Stack Configuration
3
+ # =============================================================================
4
+
5
+ RUNTIME = "bun"
6
+ RUNTIME_VERSION = "1.2"
7
+
8
+ BUNDLER = "vite"
9
+ BUNDLER_VERSION = "5"
10
+
11
+ ORM = "prisma"
12
+ ORM_VERSION = "7"
13
+
14
+ MESSAGING = "nats"
15
+ MESSAGING_VERSION = "2"
16
+ NATS_SERVER = "nats://nats:4222"
17
+
18
+ TESTING = "vitest"
19
+ TESTING_VERSION = "1"
20
+
21
+ LINTING = "biome"
22
+ LINTING_VERSION = "1.5"
23
+
24
+ WEB_FRAMEWORK = "hono"
25
+ WEB_FRAMEWORK_VERSION = "4"
26
+
27
+ CONTAINER_PLATFORM = "docker"
28
+ COMPOSE_VERSION = "3.8"
29
+
30
+ def assert_tech_stack(loaded_stack):
31
+ required = {
32
+ "bundler": BUNDLER,
33
+ "runtime": RUNTIME,
34
+ }
35
+
36
+ for key, expected in required.items():
37
+ actual = loaded_stack.get(key)
38
+ if actual != expected:
39
+ fail("Tech stack mismatch: {} should be '{}' but got '{}'".format(
40
+ key, expected, actual
41
+ ))
42
+
43
+ print("✅ Tech stack validated: {} / {} / {}".format(
44
+ BUNDLER, RUNTIME, TESTING
45
+ ))
46
+
47
+ exports = {
48
+ "RUNTIME": RUNTIME,
49
+ "RUNTIME_VERSION": RUNTIME_VERSION,
50
+ "BUNDLER": BUNDLER,
51
+ "BUNDLER_VERSION": BUNDLER_VERSION,
52
+ "ORM": ORM,
53
+ "ORM_VERSION": ORM_VERSION,
54
+ "MESSAGING": MESSAGING,
55
+ "MESSAGING_VERSION": MESSAGING_VERSION,
56
+ "NATS_SERVER": NATS_SERVER,
57
+ "TESTING": TESTING,
58
+ "TESTING_VERSION": TESTING_VERSION,
59
+ "LINTING": LINTING,
60
+ "LINTING_VERSION": LINTING_VERSION,
61
+ "WEB_FRAMEWORK": WEB_FRAMEWORK,
62
+ "WEB_FRAMEWORK_VERSION": WEB_FRAMEWORK_VERSION,
63
+ "CONTAINER_PLATFORM": CONTAINER_PLATFORM,
64
+ "COMPOSE_VERSION": COMPOSE_VERSION,
65
+ "assert_tech_stack": assert_tech_stack,
66
+ "TECH_STACK": exports,
67
+ }
@@ -0,0 +1,25 @@
1
+ # TDK Example Tiltfile
2
+ # Minimal Tiltfile demonstrating TDK PSR model
3
+
4
+ # Must be set before the extension loads: discovery/constants.star reads it to
5
+ # find this project's spec.master (services/identity, services/appointment)
6
+ # instead of falling back to the beauty-crm monorepo's default scan roots.
7
+ os.environ['TDK_PROJECT_ROOT'] = config.main_dir
8
+
9
+ # Load TDK extension
10
+ v1alpha1.extension_repo(name='tdk-cli', url='https://github.com/tdk-landscape/tdk-cli')
11
+ v1alpha1.extension(name='tdk-cli', repo_name='tdk-cli', repo_path='')
12
+
13
+ # Load TDK utilities
14
+ load('ext://tdk-cli', 'Utils', 'Manifest', 'Config')
15
+
16
+ print("🚀 TDK Example Project")
17
+ print(" Project → Stack → Resource")
18
+ print("")
19
+
20
+ # Default to all services if no args
21
+ config.define_string_list("to-run", args=True)
22
+ cfg = config.parse()
23
+
24
+ # Let TDK handle service discovery and registration
25
+ # Services are discovered from service.json files automatically
@@ -0,0 +1,26 @@
1
+ services:
2
+ nats:
3
+ image: nats:2.10-alpine
4
+ container_name: tdk_example_nats
5
+ command: ["--jetstream", "--store_dir=/data/jetstream", "--http_port", "8222"]
6
+ restart: unless-stopped
7
+ networks:
8
+ tdk_example_backend:
9
+ aliases:
10
+ - tdk_example_nats
11
+ volumes:
12
+ - tdk_example_nats_data:/data
13
+ healthcheck:
14
+ test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8222/healthz"]
15
+ interval: 5s
16
+ timeout: 3s
17
+ start_period: 5s
18
+ retries: 20
19
+
20
+ networks:
21
+ tdk_example_backend:
22
+ external: true
23
+ name: tdk_example_backend
24
+
25
+ volumes:
26
+ tdk_example_nats_data:
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "orders-api",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "bun build src/index.ts --outdir dist --target bun --packages external",
7
+ "start": "bun run dist/index.js",
8
+ "dev": "bun --watch src/index.ts"
9
+ },
10
+ "dependencies": {
11
+ "@hono/node-server": "^1.19.0",
12
+ "hono": "^4.9.0",
13
+ "nats": "^2.29.3",
14
+ "pg": "^8.16.3"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^20.0.0",
18
+ "@types/pg": "^8.15.5"
19
+ }
20
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://tdk-landscape.github.io/schema.service.json",
3
+ "schemaVersion": 1,
4
+ "appName": "orders-api",
5
+ "appType": "backend",
6
+ "stack": "shop",
7
+ "port": 4000,
8
+ "featuresEnabled": ["nats"],
9
+ "params": {
10
+ "NATS_URL": "nats://tdk_example_nats:4222"
11
+ },
12
+ "healthCheckPath": "/health",
13
+ "dependsOn": []
14
+ }
@@ -0,0 +1,81 @@
1
+ import { serve } from "@hono/node-server";
2
+ import { connect, type NatsConnection } from "nats";
3
+ import { Hono } from "hono";
4
+ import { Pool } from "pg";
5
+
6
+ const port = Number(process.env.PORT ?? 4000);
7
+ const databaseUrl = process.env.DATABASE_URL;
8
+ const natsUrl = process.env.NATS_URL ?? "nats://tdk_example_nats:4222";
9
+ if (!databaseUrl) throw new Error("DATABASE_URL is required");
10
+
11
+ const pgUrl = new URL(databaseUrl);
12
+ pgUrl.searchParams.delete("schema");
13
+ const db = new Pool({ connectionString: pgUrl.toString() });
14
+ let nats: NatsConnection;
15
+ let workerReady = false;
16
+
17
+ async function connectDependencies(): Promise<void> {
18
+ while (true) {
19
+ try {
20
+ await db.query(`
21
+ CREATE TABLE IF NOT EXISTS demo_orders (
22
+ id bigserial PRIMARY KEY,
23
+ item text NOT NULL,
24
+ created_at timestamptz NOT NULL DEFAULT now(),
25
+ worker_seen_at timestamptz
26
+ )
27
+ `);
28
+ nats = await connect({ servers: natsUrl });
29
+ return;
30
+ } catch (error) {
31
+ console.error("waiting for PostgreSQL and NATS", error);
32
+ await new Promise((resolve) => setTimeout(resolve, 1500));
33
+ }
34
+ }
35
+ }
36
+
37
+ const app = new Hono();
38
+ app.use("*", async (c, next) => {
39
+ c.header("Access-Control-Allow-Origin", "*");
40
+ c.header("Access-Control-Allow-Headers", "content-type");
41
+ c.header("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
42
+ await next();
43
+ });
44
+ app.options("*", (c) => c.body(null, 204));
45
+ app.get("/health", (c) => c.json({ ok: true }));
46
+ app.get("/worker-ready", (c) => c.json({ ready: workerReady }, workerReady ? 200 : 503));
47
+ app.get("/", async (c) => {
48
+ const result = await db.query("SELECT id, item, created_at AS \"createdAt\", worker_seen_at AS \"workerSeenAt\" FROM demo_orders ORDER BY id DESC LIMIT 20");
49
+ return c.json({ orders: result.rows });
50
+ });
51
+ app.post("/", async (c) => {
52
+ if (!workerReady) return c.json({ error: "orders worker is not ready" }, 503);
53
+ const body = await c.req.json<{ item?: unknown }>();
54
+ if (typeof body.item !== "string" || body.item.trim().length === 0) {
55
+ return c.json({ error: "item is required" }, 400);
56
+ }
57
+ const result = await db.query(
58
+ "INSERT INTO demo_orders (item) VALUES ($1) RETURNING id, item, created_at AS \"createdAt\", worker_seen_at AS \"workerSeenAt\"",
59
+ [body.item.trim()],
60
+ );
61
+ const order = result.rows[0];
62
+ nats.publish("demo.orders.created", new TextEncoder().encode(String(order.id)));
63
+ return c.json({ order }, 201);
64
+ });
65
+ app.get("/:id", async (c) => {
66
+ const result = await db.query(
67
+ "SELECT id, item, created_at AS \"createdAt\", worker_seen_at AS \"workerSeenAt\" FROM demo_orders WHERE id = $1",
68
+ [c.req.param("id")],
69
+ );
70
+ if (!result.rows[0]) return c.json({ error: "order not found" }, 404);
71
+ return c.json({ order: result.rows[0] });
72
+ });
73
+
74
+ await connectDependencies();
75
+ const readySubscription = nats.subscribe("demo.orders.worker-ready");
76
+ await nats.flush();
77
+ void (async () => {
78
+ for await (const _message of readySubscription) workerReady = true;
79
+ })();
80
+ serve({ fetch: app.fetch, port });
81
+ console.log(`orders-api listening on ${port}`);
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>TDK Orders</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "orders-app",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "vite build",
7
+ "dev": "vite --host 0.0.0.0"
8
+ },
9
+ "dependencies": {
10
+ "@vitejs/plugin-react": "^5.0.4",
11
+ "vite": "^7.1.7",
12
+ "react": "^19.1.1",
13
+ "react-dom": "^19.1.1"
14
+ },
15
+ "devDependencies": {
16
+ "@types/react": "^19.1.16",
17
+ "@types/react-dom": "^19.1.9",
18
+ "typescript": "^5.9.2"
19
+ }
20
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://tdk-landscape.github.io/schema.service.json",
3
+ "schemaVersion": 1,
4
+ "appName": "orders-app",
5
+ "appType": "frontend",
6
+ "stack": "shop",
7
+ "port": 3000,
8
+ "dependsOn": ["orders-api"]
9
+ }
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./orders.js";
4
+
5
+ createRoot(document.getElementById("root")!).render(
6
+ <React.StrictMode>
7
+ <App />
8
+ </React.StrictMode>,
9
+ );
@@ -0,0 +1,67 @@
1
+ import { useEffect, useState } from "react";
2
+ import "./style.css";
3
+
4
+ type Order = { id: number; item: string; workerSeenAt: string | null };
5
+ const api = `${window.location.protocol}//api.${window.location.hostname.replace(/^app\./, "")}/api/orders`;
6
+
7
+ export function App() {
8
+ const [item, setItem] = useState("coffee");
9
+ const [orders, setOrders] = useState<Order[]>([]);
10
+ const [message, setMessage] = useState("Loading orders…");
11
+
12
+ async function refresh() {
13
+ try {
14
+ const response = await fetch(api);
15
+ const data = (await response.json()) as { orders: Order[] };
16
+ setOrders(data.orders);
17
+ setMessage("API and database are connected");
18
+ } catch {
19
+ setMessage("Waiting for the orders API");
20
+ }
21
+ }
22
+
23
+ useEffect(() => {
24
+ void refresh();
25
+ const timer = window.setInterval(() => void refresh(), 1500);
26
+ return () => window.clearInterval(timer);
27
+ }, []);
28
+
29
+ async function createOrder(event: React.FormEvent) {
30
+ event.preventDefault();
31
+ const response = await fetch(api, {
32
+ method: "POST",
33
+ headers: { "content-type": "application/json" },
34
+ body: JSON.stringify({ item }),
35
+ });
36
+ if (!response.ok) {
37
+ setMessage(`Could not create order (${response.status})`);
38
+ return;
39
+ }
40
+ setMessage("Order saved; waiting for NATS worker observation…");
41
+ await refresh();
42
+ }
43
+
44
+ return (
45
+ <main>
46
+ <p className="eyebrow">TDK · Hono · PostgreSQL · NATS · Vite · Traefik</p>
47
+ <h1>Order desk</h1>
48
+ <p className="status">{message}</p>
49
+ <form onSubmit={createOrder}>
50
+ <label htmlFor="item">What should the worker process?</label>
51
+ <div className="row">
52
+ <input id="item" value={item} onChange={(event) => setItem(event.target.value)} />
53
+ <button type="submit">Create order</button>
54
+ </div>
55
+ </form>
56
+ <h2>Persisted orders</h2>
57
+ <ul>
58
+ {orders.map((order) => (
59
+ <li key={order.id}>
60
+ <span>#{order.id} · {order.item}</span>
61
+ <strong>{order.workerSeenAt ? "Worker observed" : "Queued"}</strong>
62
+ </li>
63
+ ))}
64
+ </ul>
65
+ </main>
66
+ );
67
+ }
@@ -0,0 +1,15 @@
1
+ :root { font-family: Inter, ui-sans-serif, system-ui, sans-serif; color: #13231e; background: #edf2ee; }
2
+ * { box-sizing: border-box; }
3
+ body { margin: 0; min-width: 320px; }
4
+ main { width: min(720px, calc(100% - 32px)); margin: 12vh auto; padding: 40px; background: white; border: 1px solid #d8e1dc; border-radius: 18px; box-shadow: 0 20px 60px #163c2512; }
5
+ .eyebrow { color: #537363; font-size: 12px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; }
6
+ h1 { margin: 12px 0; font-size: clamp(38px, 8vw, 64px); letter-spacing: -.06em; }
7
+ .status { color: #587064; }
8
+ label { display: block; margin: 28px 0 8px; font-weight: 650; }
9
+ .row { display: flex; gap: 10px; }
10
+ input { min-width: 0; flex: 1; padding: 12px 14px; border: 1px solid #cad7cf; border-radius: 8px; font: inherit; }
11
+ button { padding: 12px 18px; border: 0; border-radius: 8px; color: white; background: #176447; font: inherit; font-weight: 700; cursor: pointer; }
12
+ h2 { margin-top: 34px; font-size: 18px; }
13
+ ul { display: grid; gap: 8px; padding: 0; list-style: none; }
14
+ li { display: flex; justify-content: space-between; gap: 12px; padding: 12px; background: #f4f7f4; border-radius: 8px; }
15
+ li strong { color: #176447; }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "orders-worker",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "bun build src/index.ts --outdir dist --target bun --packages external",
7
+ "start": "bun run dist/index.js",
8
+ "dev": "bun --watch src/index.ts"
9
+ },
10
+ "dependencies": {
11
+ "nats": "^2.29.3",
12
+ "pg": "^8.16.3"
13
+ },
14
+ "devDependencies": {
15
+ "@types/node": "^20.0.0",
16
+ "@types/pg": "^8.15.5"
17
+ }
18
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://tdk-landscape.github.io/schema.service.json",
3
+ "schemaVersion": 1,
4
+ "appName": "orders-worker",
5
+ "appType": "worker",
6
+ "stack": "shop",
7
+ "port": 4001,
8
+ "featuresEnabled": ["nats"],
9
+ "params": {
10
+ "NATS_URL": "nats://tdk_example_nats:4222"
11
+ },
12
+ "dependsOn": ["orders-api"]
13
+ }
@@ -0,0 +1,33 @@
1
+ import { connect, type NatsConnection } from "nats";
2
+ import { Pool } from "pg";
3
+
4
+ const databaseUrl = process.env.DATABASE_URL;
5
+ const natsUrl = process.env.NATS_URL ?? "nats://tdk_example_nats:4222";
6
+ if (!databaseUrl) throw new Error("DATABASE_URL is required");
7
+
8
+ const pgUrl = new URL(databaseUrl);
9
+ pgUrl.searchParams.delete("schema");
10
+ const db = new Pool({ connectionString: pgUrl.toString() });
11
+ let nc: NatsConnection | undefined;
12
+ while (!nc) {
13
+ try {
14
+ nc = await connect({ servers: natsUrl });
15
+ } catch (error) {
16
+ console.error("waiting for NATS", error);
17
+ await new Promise((resolve) => setTimeout(resolve, 1500));
18
+ }
19
+ }
20
+ const subscription = nc.subscribe("demo.orders.created", { queue: "orders-workers" });
21
+ await nc.flush();
22
+ nc.publish("demo.orders.worker-ready", new Uint8Array());
23
+ console.log("orders-worker subscribed to demo.orders.created");
24
+
25
+ for await (const message of subscription) {
26
+ const id = new TextDecoder().decode(message.data);
27
+ const result = await db.query(
28
+ "UPDATE demo_orders SET worker_seen_at = now() WHERE id = $1 RETURNING id, item",
29
+ [id],
30
+ );
31
+ if (result.rows[0]) console.log(`worker observed order ${id}: ${result.rows[0].item}`);
32
+ else console.error(`worker received unknown order ${id}`);
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdk-landscape/tdk-cli-core",
3
- "version": "1.3.76",
3
+ "version": "1.3.77",
4
4
  "description": "Run your whole microservice landscape locally without Kubernetes. Scaffold services and run them with hot reload, built on Tilt.",
5
5
  "homepage": "https://tdk-landscape.github.io/tdk-website",
6
6
  "repository": {
@@ -26,6 +26,7 @@
26
26
  "specs",
27
27
  "ext",
28
28
  "shared-platform-engineering/docker-templates",
29
+ "examples/tdk-example",
29
30
  "cli/bin",
30
31
  "cli/dist/**/*.js",
31
32
  "cli/templates",