@mesh-tech/mesh-cli 0.14.0 → 0.16.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/dist/bin/mesh.js +11485 -10591
- package/dist/bin/mesh.js.map +4 -4
- package/dist/build-info.json +2 -2
- package/dist/src/commands/app-check.d.ts +97 -0
- package/dist/src/commands/app-check.d.ts.map +1 -0
- package/dist/src/commands/app-check.js +502 -0
- package/dist/src/commands/app-check.js.map +1 -0
- package/dist/src/commands/create-app.d.ts +28 -0
- package/dist/src/commands/create-app.d.ts.map +1 -1
- package/dist/src/commands/create-app.js +40 -7
- package/dist/src/commands/create-app.js.map +1 -1
- package/dist/src/commands/dev-doctor.d.ts +28 -12
- package/dist/src/commands/dev-doctor.d.ts.map +1 -1
- package/dist/src/commands/dev-doctor.js +9 -5
- package/dist/src/commands/dev-doctor.js.map +1 -1
- package/dist/src/commands/dev.d.ts +11 -0
- package/dist/src/commands/dev.d.ts.map +1 -1
- package/dist/src/commands/dev.js +70 -1
- package/dist/src/commands/dev.js.map +1 -1
- package/dist/src/commands/hub/index.d.ts.map +1 -1
- package/dist/src/commands/hub/index.js +65 -4
- package/dist/src/commands/hub/index.js.map +1 -1
- package/dist/src/commands/local/auth-provision.d.ts +34 -0
- package/dist/src/commands/local/auth-provision.d.ts.map +1 -1
- package/dist/src/commands/local/auth-provision.js +78 -1
- package/dist/src/commands/local/auth-provision.js.map +1 -1
- package/dist/src/commands/local/hub-local.d.ts +38 -0
- package/dist/src/commands/local/hub-local.d.ts.map +1 -1
- package/dist/src/commands/local/hub-local.js +137 -0
- package/dist/src/commands/local/hub-local.js.map +1 -1
- package/dist/src/commands/local/index.d.ts.map +1 -1
- package/dist/src/commands/local/index.js +31 -3
- package/dist/src/commands/local/index.js.map +1 -1
- package/dist/src/commands/local/seed-zitadel.d.ts +10 -3
- package/dist/src/commands/local/seed-zitadel.d.ts.map +1 -1
- package/dist/src/commands/local/seed-zitadel.js +259 -78
- package/dist/src/commands/local/seed-zitadel.js.map +1 -1
- package/dist/src/commands/local/stack.d.ts +30 -0
- package/dist/src/commands/local/stack.d.ts.map +1 -1
- package/dist/src/commands/local/stack.js +48 -0
- package/dist/src/commands/local/stack.js.map +1 -1
- package/dist/src/program.d.ts.map +1 -1
- package/dist/src/program.js +2 -0
- package/dist/src/program.js.map +1 -1
- package/fragments/base/index.ts.hbs +9 -0
- package/fragments/base/package.json.hbs +1 -1
- package/fragments/service/api/package.json.hbs +5 -0
- package/fragments/service/api/src/index.ts.hbs +6 -4
- package/fragments/temporal/worker/package.json.hbs +2 -0
- package/fragments/temporal/worker/src/activities.ts.hbs +6 -1
- package/fragments/temporal/worker/src/worker.ts.hbs +7 -1
- package/fragments/temporal/worker/src/workflows.ts.hbs +4 -1
- package/package.json +2 -2
- package/skills/core/SKILL.md +3 -1
- package/stack/docker-compose.hub.yml +5 -0
- package/stack/docker-compose.yml +35 -0
- package/templates/api-auth/AGENTS.md.hbs +6 -0
- package/templates/api-auth/README.md.hbs +11 -8
- package/templates/api-auth/api/package.json.hbs +2 -1
- package/templates/api-auth/api/{src → scripts}/seed-authz.ts.hbs +3 -3
- package/templates/api-auth/api/src/index.ts.hbs +20 -9
- package/templates/api-auth/index.ts.hbs +78 -24
- package/templates/api-auth/package.json.hbs +1 -0
- package/templates/api-role-gating/api/package.json.hbs +1 -0
- package/templates/api-role-gating/api/src/main.ts.hbs +6 -1
- package/templates/external-service/api/package.json.hbs +1 -0
- package/templates/external-service/api/src/index.ts.hbs +7 -2
- package/templates/workflow/api/package.json.hbs +1 -0
- package/templates/workflow/api/src/index.ts.hbs +10 -5
- package/templates/workflow/worker/package.json.hbs +1 -0
- package/templates/workflow/worker/src/activities.ts.hbs +7 -3
- package/templates/workflow/worker/src/encryption-codec.ts +3 -2
- package/templates/workflow/worker/src/worker.ts.hbs +7 -1
- package/templates/workflow/worker/src/workflows.ts.hbs +3 -3
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* GET /api/report permission-gated (needs report:view via SpiceDB → else 403)
|
|
11
11
|
*
|
|
12
12
|
* Environment (mesh dev injects all of these — local or tethered):
|
|
13
|
-
* PORT, ZITADEL_ISSUER,
|
|
13
|
+
* PORT, ZITADEL_ISSUER, ZITADEL_PROJECT_ID, SPICEDB_HTTP_ENDPOINT, SPICEDB_TOKEN
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { Hono } from "hono";
|
|
@@ -20,22 +20,31 @@ import { zitadelJwtScheme } from "@mesh-tech/authn/schemes/zitadel-jwt";
|
|
|
20
20
|
import { createAuthnMiddleware, type AuthnVariables } from "@mesh-tech/authn/hono";
|
|
21
21
|
import { createAuthz } from "@mesh-tech/authz/runtime";
|
|
22
22
|
import { createAuthzMiddleware, type AuthzVariables } from "@mesh-tech/authz/hono";
|
|
23
|
+
import { createLogger, requestLogger } from "@mesh-tech/logger";
|
|
23
24
|
import { schema } from "./schema.js";
|
|
24
25
|
import { spiceDbHttpProvider } from "./spicedb-http-provider.js";
|
|
25
26
|
|
|
26
27
|
const PORT = parseInt(process.env.PORT ?? "3000", 10);
|
|
27
28
|
const ZITADEL_ISSUER = process.env.ZITADEL_ISSUER ?? "http://localhost:8080";
|
|
29
|
+
// Every service logs through @mesh-tech/logger (the Mesh app contract, gate 0.4).
|
|
30
|
+
const log = createLogger({ service: "{{name}}-api" });
|
|
28
31
|
|
|
29
32
|
// --- authn: who is calling ---
|
|
30
33
|
|
|
31
34
|
// Audience: locally, mesh dev injects the seeded CLI app's clientId
|
|
32
|
-
// (tokens come from `mesh login local`);
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
+
// (tokens come from `mesh login local`); deployed, the app's OWN Zitadel
|
|
36
|
+
// project id (ZitadelAppIdentity in index.ts) is the audience, and passing it
|
|
37
|
+
// as `projectId` makes the scheme read the project-keyed roles claim.
|
|
38
|
+
const ZITADEL_PROJECT_ID = process.env.ZITADEL_PROJECT_ID;
|
|
39
|
+
const AUTH_AUDIENCE = process.env.AUTH_AUDIENCE ?? ZITADEL_PROJECT_ID ?? "{{name}}";
|
|
35
40
|
|
|
36
41
|
const authn = createAuthn({
|
|
37
42
|
schemes: {
|
|
38
|
-
"zitadel-jwt": zitadelJwtScheme({
|
|
43
|
+
"zitadel-jwt": zitadelJwtScheme({
|
|
44
|
+
issuer: ZITADEL_ISSUER,
|
|
45
|
+
audience: AUTH_AUDIENCE,
|
|
46
|
+
...(ZITADEL_PROJECT_ID ? { projectId: ZITADEL_PROJECT_ID } : {}),
|
|
47
|
+
}),
|
|
39
48
|
},
|
|
40
49
|
});
|
|
41
50
|
const { middleware: requireAuth, public: publicRoute } = createAuthnMiddleware(authn);
|
|
@@ -49,13 +58,14 @@ const authz = createAuthz({
|
|
|
49
58
|
provider: spiceDbHttpProvider({
|
|
50
59
|
endpoint: process.env.SPICEDB_HTTP_ENDPOINT ?? "http://localhost:8443",
|
|
51
60
|
// "local-dev-key" is the `mesh start` stack's key — local ONLY. In
|
|
52
|
-
// any deployed env
|
|
53
|
-
// loudly rather than silently authing with the public default.
|
|
61
|
+
// any deployed env `spicedb.link()` (index.ts) injects SPICEDB_TOKEN;
|
|
62
|
+
// fail loudly rather than silently authing with the public default.
|
|
54
63
|
presharedKey:
|
|
64
|
+
process.env.SPICEDB_TOKEN ??
|
|
55
65
|
process.env.SPICEDB_PRESHARED_KEY ??
|
|
56
66
|
(process.env.NODE_ENV === "production"
|
|
57
67
|
? (() => {
|
|
58
|
-
throw new Error("
|
|
68
|
+
throw new Error("SPICEDB_TOKEN is required in production");
|
|
59
69
|
})()
|
|
60
70
|
: "local-dev-key"),
|
|
61
71
|
}),
|
|
@@ -73,6 +83,7 @@ const m = createAuthzMiddleware(authz, {
|
|
|
73
83
|
// --- routes ---
|
|
74
84
|
|
|
75
85
|
const app = new Hono<{ Variables: AuthnVariables & AuthzVariables }>();
|
|
86
|
+
app.use("*", requestLogger(log));
|
|
76
87
|
|
|
77
88
|
// Open: no token needed (also excluded from the oauth2-proxy in the cloud)
|
|
78
89
|
app.get("/health", publicRoute(), (c) => c.json({ status: "ok" }));
|
|
@@ -92,5 +103,5 @@ app.get("/api/report", m.require("report", "view", { id: () => "quarterly" }), (
|
|
|
92
103
|
);
|
|
93
104
|
|
|
94
105
|
serve({ fetch: app.fetch, port: PORT }, () => {
|
|
95
|
-
|
|
106
|
+
log.info({ port: PORT, issuer: ZITADEL_ISSUER }, "{{name}} api listening");
|
|
96
107
|
});
|
|
@@ -1,54 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* {{titleCase name}} — Platform-authenticated API
|
|
2
|
+
* {{titleCase name}} — Platform-authenticated API (authz mode "policy-engine").
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* authn: Zitadel JWTs verified in-process (@mesh-tech/authn) against the app's
|
|
5
|
+
* OWN Zitadel project. authz: schema-driven permission checks in the app's OWN
|
|
6
|
+
* SpiceDB (@mesh-tech/authz). Everything the Hub needs to administer people,
|
|
7
|
+
* roles and API keys for this app is published from here — nothing is created
|
|
8
|
+
* by hand in Zitadel and nothing lives in a runbook (the Mesh app contract,
|
|
9
|
+
* gate H: `@mesh-tech/app-kit#apps` → references/app-contract.md).
|
|
6
10
|
*
|
|
7
|
-
*
|
|
11
|
+
* Wires, IN ORDER:
|
|
12
|
+
* 1. ZitadelConnector — authed provider for the platform Zitadel.
|
|
13
|
+
* 2. ZitadelAppIdentity — this app's OWN project; roles = schema.coarseRoles.
|
|
14
|
+
* 3. AppEnvironment — namespace + providers, bound to the app's project.
|
|
15
|
+
* 4. SpiceDB — the app's OWN SpiceDB instance (shared-DB mode).
|
|
16
|
+
* 5. SpiceDBSchema — compiles api/src/schema.ts to .zed + ops-hub
|
|
17
|
+
* metadata, applies it, exports the metadata to SSM.
|
|
18
|
+
* 6. api Service — Hono app: authn + authz middleware, linked to SpiceDB.
|
|
19
|
+
* 7. AppAuthzPointer — LAST. Tells the Hub where this app's authz lives.
|
|
20
|
+
*
|
|
21
|
+
* Prerequisite: the target platform must have Zitadel enabled — ZitadelConnector
|
|
22
|
+
* eager-reads it at preview time.
|
|
23
|
+
*
|
|
24
|
+
* Local: mesh start && mesh dev (the local platform's Zitadel + SpiceDB)
|
|
8
25
|
* Deploy: mesh deploy up
|
|
9
26
|
*/
|
|
10
27
|
|
|
11
28
|
import * as pulumi from "@pulumi/pulumi";
|
|
12
|
-
import * as path from "node:path";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
14
29
|
import { mesh } from "@mesh-tech/app-kit";
|
|
30
|
+
import { compileOpsHubMetadata, compileSpiceDBSchema } from "@mesh-tech/authz";
|
|
31
|
+
// The authz schema is the single source of truth, shared by the api (enforces
|
|
32
|
+
// it) and this Pulumi program (compiles it to SpiceDB + ops-hub metadata + roles).
|
|
33
|
+
import { schema as schemaDef } from "./api/src/schema.js";
|
|
15
34
|
|
|
16
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
35
|
const { tenant, platform, stack, deploy } = mesh.apps.getConfig(pulumi);
|
|
36
|
+
const appName = "{{name}}";
|
|
37
|
+
|
|
38
|
+
// --- 1. Connector — authed provider for the platform Zitadel. ---
|
|
39
|
+
const connector = new mesh.auth.ZitadelConnector("idp", { tenant, env: platform.env });
|
|
18
40
|
|
|
19
|
-
// ---
|
|
41
|
+
// --- 2. App identity — this app's OWN Zitadel project + roles. ---
|
|
42
|
+
// The schema's coarse roles become Zitadel PROJECT ROLES: the vocabulary the
|
|
43
|
+
// Hub grants people and API keys against. An `api`-type application is the
|
|
44
|
+
// resource server tokens are minted for (its clientId is the JWT audience).
|
|
45
|
+
const identity = new mesh.auth.ZitadelAppIdentity("identity", {
|
|
46
|
+
connector,
|
|
47
|
+
tenant,
|
|
48
|
+
env: platform.env,
|
|
49
|
+
project: { name: appName },
|
|
50
|
+
roles: [...schemaDef.coarseRoles],
|
|
51
|
+
applications: {
|
|
52
|
+
api: { type: "api" },
|
|
53
|
+
},
|
|
54
|
+
});
|
|
20
55
|
|
|
56
|
+
// --- 3. Environment — bound to the app's own project via zitadelAppProjectId. ---
|
|
21
57
|
const env = new mesh.apps.AppEnvironment("env", {
|
|
22
58
|
tenant,
|
|
23
59
|
platform,
|
|
24
60
|
stack,
|
|
25
61
|
deploy,
|
|
26
|
-
appName
|
|
62
|
+
appName,
|
|
27
63
|
namespace: true,
|
|
28
|
-
|
|
64
|
+
database: false,
|
|
65
|
+
zitadelAppProjectId: identity.projectId,
|
|
29
66
|
});
|
|
30
67
|
|
|
31
|
-
// ---
|
|
68
|
+
// --- 4. SpiceDB — the app's OWN instance (omit `rds` → the platform's shared RDS). ---
|
|
69
|
+
// `httpEnabled` serves the REST gateway the starter spicedb-http-provider talks to.
|
|
70
|
+
const spicedb = new mesh.auth.SpiceDB("spicedb", { env, httpEnabled: true });
|
|
32
71
|
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
// --- 5. Schema — compile, apply, and publish the ops-hub metadata. ---
|
|
73
|
+
// `opsHubMetadataRef` is the SSM path the pointer below hands to the Hub; without
|
|
74
|
+
// it the Hub's create-key and role catalog answer 409 METADATA_UNPUBLISHED.
|
|
75
|
+
const schema = new mesh.auth.SpiceDBSchema("schema", {
|
|
76
|
+
env,
|
|
77
|
+
spicedb,
|
|
78
|
+
compiled: {
|
|
79
|
+
zed: compileSpiceDBSchema(schemaDef).zed,
|
|
80
|
+
metadata: compileOpsHubMetadata(schemaDef),
|
|
81
|
+
},
|
|
82
|
+
});
|
|
41
83
|
|
|
84
|
+
// --- 6. api Service — in-process authn + authz; SpiceDB reached via link(). ---
|
|
42
85
|
const api = new mesh.apps.Service("api", {
|
|
43
86
|
env,
|
|
87
|
+
title: "{{titleCase name}} API",
|
|
88
|
+
description: "Platform-authenticated API for {{titleCase name}}",
|
|
44
89
|
runtime: "node",
|
|
45
90
|
src: "./api",
|
|
46
91
|
port: 3000,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
92
|
+
// spicedb.link() injects SPICEDB_ENDPOINT (gRPC) + SPICEDB_TOKEN and the IAM
|
|
93
|
+
// to reach the instance; the HTTP gateway address rides alongside for the
|
|
94
|
+
// starter provider.
|
|
95
|
+
link: [spicedb.link()],
|
|
96
|
+
environment: {
|
|
97
|
+
ZITADEL_ISSUER: pulumi.output(env.zitadel).apply((z) => z?.issuer ?? ""),
|
|
98
|
+
ZITADEL_PROJECT_ID: identity.projectId,
|
|
99
|
+
SPICEDB_HTTP_ENDPOINT: spicedb.httpEndpoint,
|
|
52
100
|
},
|
|
53
101
|
ingress: env.buildIngress({
|
|
54
102
|
subdomain: "{{name}}-api",
|
|
@@ -58,8 +106,14 @@ const api = new mesh.apps.Service("api", {
|
|
|
58
106
|
resources: { cpu: "100m", memory: "128Mi" },
|
|
59
107
|
});
|
|
60
108
|
|
|
109
|
+
// --- 7. AppAuthzPointer — LAST. The Hub's App → Access tab reads this. ---
|
|
110
|
+
new mesh.auth.AppAuthzPointer("authz", { env, mode: "policy-engine", opsHubMetadataRef: schema.opsHubMetadataRef });
|
|
111
|
+
|
|
61
112
|
// --- Outputs ---
|
|
62
113
|
|
|
63
114
|
export const app = env.register({
|
|
64
115
|
description: "{{titleCase name}} — platform-authenticated API (authn + authz)",
|
|
65
116
|
});
|
|
117
|
+
|
|
118
|
+
/** The app's own Zitadel project id (grant people and API keys against this). */
|
|
119
|
+
export const zitadelProjectId = identity.projectId;
|
|
@@ -10,17 +10,22 @@ import { serve } from "@hono/node-server";
|
|
|
10
10
|
import { Hono } from "hono";
|
|
11
11
|
import { createAuthnMiddleware, type AuthnVariables } from "@mesh-tech/authn/hono";
|
|
12
12
|
import { createAuthzMiddleware, type AuthzVariables } from "@mesh-tech/authz/hono";
|
|
13
|
+
import { createLogger, requestLogger } from "@mesh-tech/logger";
|
|
13
14
|
import { createAuthnFromEnv } from "./authn.js";
|
|
14
15
|
import { createAppAuthz } from "./authz.js";
|
|
15
16
|
|
|
16
17
|
type TestAuth = { subjectSub: string; coarseRole?: string | readonly string[] };
|
|
17
18
|
type AppEnv = { Variables: AuthnVariables & AuthzVariables };
|
|
18
19
|
|
|
20
|
+
// Every service logs through @mesh-tech/logger (the Mesh app contract, gate 0.4).
|
|
21
|
+
const log = createLogger({ service: "{{name}}-api" });
|
|
22
|
+
|
|
19
23
|
/** The role-gated route paths (kept in one place so authn + authz agree). */
|
|
20
24
|
const GATED = ["/admin", "/reports"] as const;
|
|
21
25
|
|
|
22
26
|
export const createApp = (deps: { testAuth?: TestAuth } = {}) => {
|
|
23
27
|
const app = new Hono<AppEnv>();
|
|
28
|
+
app.use("*", requestLogger(log));
|
|
24
29
|
const m = createAuthzMiddleware(createAppAuthz());
|
|
25
30
|
|
|
26
31
|
// Open: no token needed.
|
|
@@ -54,5 +59,5 @@ export const createApp = (deps: { testAuth?: TestAuth } = {}) => {
|
|
|
54
59
|
if (process.env.NODE_ENV !== "test" && process.argv[1]?.endsWith("main.js")) {
|
|
55
60
|
const port = Number(process.env.PORT ?? 3000);
|
|
56
61
|
serve({ fetch: createApp().fetch, port });
|
|
57
|
-
|
|
62
|
+
log.info({ port, mode: "role-gating" }, "{{name}} api listening");
|
|
58
63
|
}
|
|
@@ -19,6 +19,7 @@ import { Hono } from "hono";
|
|
|
19
19
|
import { resolveCredentials } from "@mesh-tech/app-kit/credentials";
|
|
20
20
|
import { traceExternalCall } from "@mesh-tech/app-kit/tracing";
|
|
21
21
|
import { enhanceFetch } from "@mesh-tech/http-client";
|
|
22
|
+
import { createLogger, requestLogger } from "@mesh-tech/logger";
|
|
22
23
|
|
|
23
24
|
interface VendorpayCredentials extends Record<string, string> {
|
|
24
25
|
baseUrl: string;
|
|
@@ -27,7 +28,11 @@ interface VendorpayCredentials extends Record<string, string> {
|
|
|
27
28
|
|
|
28
29
|
const vendorFetch = enhanceFetch();
|
|
29
30
|
|
|
31
|
+
// Every service logs through @mesh-tech/logger (the Mesh app contract, gate
|
|
32
|
+
// 0.4): structured, masked, trace-correlated lines the Hub can search.
|
|
33
|
+
const log = createLogger({ service: "{{name}}-api" });
|
|
30
34
|
const app = new Hono();
|
|
35
|
+
app.use("*", requestLogger(log));
|
|
31
36
|
|
|
32
37
|
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
33
38
|
|
|
@@ -62,7 +67,7 @@ app.post("/payments/quote", async (c) => {
|
|
|
62
67
|
// Log vendor interactions with the service name — these lines are what
|
|
63
68
|
// the Hub's Integrations → Monitoring → Logs view surfaces (it searches
|
|
64
69
|
// consumer logs for mentions of the external service).
|
|
65
|
-
|
|
70
|
+
log.info({ externalService: "vendorpay", amount, status: response.status }, "vendorpay quote requested");
|
|
66
71
|
|
|
67
72
|
if (!response.ok) {
|
|
68
73
|
return c.json(
|
|
@@ -77,5 +82,5 @@ app.post("/payments/quote", async (c) => {
|
|
|
77
82
|
|
|
78
83
|
const port = Number(process.env.PORT ?? 3000);
|
|
79
84
|
serve({ fetch: app.fetch, port }, () => {
|
|
80
|
-
|
|
85
|
+
log.info({ port }, "{{titleCase name}} API listening");
|
|
81
86
|
});
|
|
@@ -12,11 +12,16 @@
|
|
|
12
12
|
import { Hono } from "hono";
|
|
13
13
|
import { serve } from "@hono/node-server";
|
|
14
14
|
import { createTemporalClient, type WithClient } from "@mesh-tech/app-kit/temporal-client";
|
|
15
|
+
import { createLogger, requestLogger } from "@mesh-tech/logger";
|
|
15
16
|
|
|
16
17
|
const PORT = parseInt(process.env.PORT ?? "3000", 10);
|
|
17
18
|
const TEMPORAL_TASK_QUEUE = process.env.TEMPORAL_TASK_QUEUE ?? "{{name}}";
|
|
18
19
|
|
|
20
|
+
// Every service logs through @mesh-tech/logger (the Mesh app contract, gate
|
|
21
|
+
// 0.4): structured, masked, trace-correlated lines the Hub can search.
|
|
22
|
+
const log = createLogger({ service: "{{name}}-api" });
|
|
19
23
|
const app = new Hono();
|
|
24
|
+
app.use("*", requestLogger(log));
|
|
20
25
|
|
|
21
26
|
// createTemporalClient returns a withClient wrapper (auth is applied
|
|
22
27
|
// per-call); with no TEMPORAL_AUTH_* env it runs unauthenticated — the
|
|
@@ -61,7 +66,7 @@ app.post("/workflows/greet", async (c) => {
|
|
|
61
66
|
});
|
|
62
67
|
return c.json({ workflowId, result });
|
|
63
68
|
} catch (error) {
|
|
64
|
-
|
|
69
|
+
log.error({ err: error }, "Error starting workflow");
|
|
65
70
|
return c.json({ error: "Failed to start workflow" }, 500);
|
|
66
71
|
}
|
|
67
72
|
});
|
|
@@ -92,7 +97,7 @@ app.post("/workflows/task", async (c) => {
|
|
|
92
97
|
message: "Workflow started. Check Temporal Web UI for progress.",
|
|
93
98
|
});
|
|
94
99
|
} catch (error) {
|
|
95
|
-
|
|
100
|
+
log.error({ err: error }, "Error starting workflow");
|
|
96
101
|
return c.json({ error: "Failed to start workflow" }, 500);
|
|
97
102
|
}
|
|
98
103
|
});
|
|
@@ -112,7 +117,7 @@ app.get("/workflows/:id", async (c) => {
|
|
|
112
117
|
closeTime: describe.closeTime,
|
|
113
118
|
});
|
|
114
119
|
} catch (error) {
|
|
115
|
-
|
|
120
|
+
log.error({ err: error }, "Error getting workflow");
|
|
116
121
|
return c.json({ error: "Failed to get workflow status" }, 500);
|
|
117
122
|
}
|
|
118
123
|
});
|
|
@@ -121,10 +126,10 @@ app.get("/workflows/:id", async (c) => {
|
|
|
121
126
|
initClient()
|
|
122
127
|
.then(() => {
|
|
123
128
|
serve({ fetch: app.fetch, port: PORT, hostname: "0.0.0.0" }, () => {
|
|
124
|
-
|
|
129
|
+
log.info({ port: PORT }, "{{titleCase name}} API listening");
|
|
125
130
|
});
|
|
126
131
|
})
|
|
127
132
|
.catch((err) => {
|
|
128
|
-
|
|
133
|
+
log.error({ err }, "Failed to initialize");
|
|
129
134
|
process.exit(1);
|
|
130
135
|
});
|
|
@@ -13,11 +13,15 @@
|
|
|
13
13
|
* - Use Date.now(), Math.random(), etc.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
// `log` carries the activity context and reaches the platform logger the worker
|
|
17
|
+
// installs — never the console (the Mesh app contract, gate 0.4).
|
|
18
|
+
import { log } from "@temporalio/activity";
|
|
19
|
+
|
|
16
20
|
/**
|
|
17
21
|
* Generate a greeting message
|
|
18
22
|
*/
|
|
19
23
|
export async function greet(name: string): Promise<string> {
|
|
20
|
-
|
|
24
|
+
log.info("Generating greeting", { name });
|
|
21
25
|
return `Hello, ${name}!`;
|
|
22
26
|
}
|
|
23
27
|
|
|
@@ -25,7 +29,7 @@ export async function greet(name: string): Promise<string> {
|
|
|
25
29
|
* Process a task (simulated)
|
|
26
30
|
*/
|
|
27
31
|
export async function processTask(taskId: string): Promise<string> {
|
|
28
|
-
|
|
32
|
+
log.info("Processing task", { taskId });
|
|
29
33
|
|
|
30
34
|
// Simulate processing time
|
|
31
35
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -40,7 +44,7 @@ export async function sendNotification(
|
|
|
40
44
|
userId: string,
|
|
41
45
|
message: string
|
|
42
46
|
): Promise<boolean> {
|
|
43
|
-
|
|
47
|
+
log.info("Sending notification", { userId, message });
|
|
44
48
|
|
|
45
49
|
// Simulate sending
|
|
46
50
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
ValueError,
|
|
17
17
|
} from "@temporalio/common";
|
|
18
18
|
import proto from "@temporalio/proto";
|
|
19
|
+
import { logger } from "@mesh-tech/logger";
|
|
19
20
|
|
|
20
21
|
const { temporal } = proto;
|
|
21
22
|
|
|
@@ -148,9 +149,9 @@ export class EncryptionCodec implements PayloadCodec {
|
|
|
148
149
|
export async function createEncryptionCodec(): Promise<EncryptionCodec | undefined> {
|
|
149
150
|
const encodingKey = process.env.TEMPORAL_ENCODING_KEY;
|
|
150
151
|
if (!encodingKey) {
|
|
151
|
-
|
|
152
|
+
logger.warn("No TEMPORAL_ENCODING_KEY set, payloads will not be encrypted");
|
|
152
153
|
return undefined;
|
|
153
154
|
}
|
|
154
|
-
|
|
155
|
+
logger.info("Payload encryption enabled");
|
|
155
156
|
return EncryptionCodec.create(encodingKey);
|
|
156
157
|
}
|
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { createTemporalWorker, resolveWorkflows } from "@mesh-tech/app-kit/temporal";
|
|
6
|
+
import { createLogger } from "@mesh-tech/logger";
|
|
6
7
|
import * as activities from "./activities.js";
|
|
7
8
|
|
|
9
|
+
// The worker process logs through @mesh-tech/logger (the Mesh app contract,
|
|
10
|
+
// gate 0.4). Activities use `log` from @temporalio/activity and workflows `log`
|
|
11
|
+
// from @temporalio/workflow — both route to the Runtime logger app-kit installs.
|
|
12
|
+
const log = createLogger({ service: "{{name}}-worker" });
|
|
13
|
+
|
|
8
14
|
async function run() {
|
|
9
15
|
const { worker, cleanup } = await createTemporalWorker({
|
|
10
16
|
activities,
|
|
@@ -19,6 +25,6 @@ async function run() {
|
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
run().catch((err) => {
|
|
22
|
-
|
|
28
|
+
log.error({ err }, "Worker failed to start");
|
|
23
29
|
process.exit(1);
|
|
24
30
|
});
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - Use proxyActivities() to call activities
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { proxyActivities, sleep } from "@temporalio/workflow";
|
|
14
|
+
import { log, proxyActivities, sleep } from "@temporalio/workflow";
|
|
15
15
|
import type * as activities from "./activities.js";
|
|
16
16
|
|
|
17
17
|
// Configure activity retries and timeouts
|
|
@@ -43,7 +43,7 @@ export async function taskWorkflow(
|
|
|
43
43
|
taskId: string,
|
|
44
44
|
userId: string
|
|
45
45
|
): Promise<{ status: string; taskId: string }> {
|
|
46
|
-
|
|
46
|
+
log.info("Processing task", { taskId, userId });
|
|
47
47
|
|
|
48
48
|
// Process the task
|
|
49
49
|
const result = await processTask(taskId);
|
|
@@ -51,7 +51,7 @@ export async function taskWorkflow(
|
|
|
51
51
|
// Wait before completing (demonstrates durable timers)
|
|
52
52
|
await sleep("2 seconds");
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
log.info("Task completed", { taskId, result });
|
|
55
55
|
|
|
56
56
|
return {
|
|
57
57
|
status: result,
|