@intentius/chant-lexicon-temporal 0.1.5

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 (43) hide show
  1. package/dist/integrity.json +15 -0
  2. package/dist/manifest.json +8 -0
  3. package/dist/meta.json +26 -0
  4. package/dist/rules/tmp001.ts +53 -0
  5. package/dist/rules/tmp002.ts +45 -0
  6. package/dist/rules/tmp010-cron-syntax.ts +64 -0
  7. package/dist/rules/tmp011-namespace-reference.ts +49 -0
  8. package/dist/skills/chant-temporal-ops.md +184 -0
  9. package/dist/skills/chant-temporal.md +201 -0
  10. package/dist/types/index.d.ts +3 -0
  11. package/package.json +32 -0
  12. package/src/codegen/docs-cli.ts +7 -0
  13. package/src/codegen/docs.ts +23 -0
  14. package/src/codegen/generate-cli.ts +17 -0
  15. package/src/codegen/generate.ts +82 -0
  16. package/src/codegen/package-cli.ts +14 -0
  17. package/src/codegen/package.ts +55 -0
  18. package/src/composites/cloud-stack.ts +74 -0
  19. package/src/composites/composites.test.ts +131 -0
  20. package/src/composites/dev-stack.ts +81 -0
  21. package/src/config.ts +150 -0
  22. package/src/coverage.test.ts +9 -0
  23. package/src/coverage.ts +37 -0
  24. package/src/example.test.ts +59 -0
  25. package/src/generated/lexicon-temporal.json +26 -0
  26. package/src/index.ts +29 -0
  27. package/src/lint/post-synth/post-synth.test.ts +152 -0
  28. package/src/lint/post-synth/tmp010-cron-syntax.ts +64 -0
  29. package/src/lint/post-synth/tmp011-namespace-reference.ts +49 -0
  30. package/src/lint/rules/index.ts +2 -0
  31. package/src/lint/rules/lint-rules.test.ts +150 -0
  32. package/src/lint/rules/tmp001.ts +53 -0
  33. package/src/lint/rules/tmp002.ts +45 -0
  34. package/src/plugin.test.ts +97 -0
  35. package/src/plugin.ts +286 -0
  36. package/src/resources.ts +121 -0
  37. package/src/serializer.test.ts +292 -0
  38. package/src/serializer.ts +310 -0
  39. package/src/skills/chant-temporal-ops.md +184 -0
  40. package/src/skills/chant-temporal.md +201 -0
  41. package/src/validate-cli.ts +7 -0
  42. package/src/validate.test.ts +9 -0
  43. package/src/validate.ts +92 -0
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CLI entry point for `npm run docs` in lexicon-temporal.
4
+ */
5
+ import { generateDocs } from "./docs";
6
+
7
+ await generateDocs({ verbose: true });
@@ -0,0 +1,23 @@
1
+ import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
2
+
3
+ /**
4
+ * Generate documentation site for the Temporal lexicon.
5
+ */
6
+ export async function generateDocs(options?: { verbose?: boolean }): Promise<void> {
7
+ const config = {
8
+ name: "temporal",
9
+ displayName: "Temporal",
10
+ description: "Temporal lexicon documentation",
11
+ distDir: "./dist",
12
+ outDir: "./docs",
13
+ serviceFromType: (type: string) => type.split("::")[1] ?? type,
14
+ resourceTypeUrl: (type: string) => `#${type}`,
15
+ };
16
+
17
+ const result = docsPipeline(config);
18
+ writeDocsSite(config, result);
19
+
20
+ if (options?.verbose) {
21
+ console.error("Documentation generated");
22
+ }
23
+ }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Thin entry point for `npm run generate` in lexicon-temporal.
4
+ */
5
+ import { generate, writeGeneratedFiles } from "./generate";
6
+ import { dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const result = await generate({ verbose: true });
10
+ // src/codegen/generate-cli.ts → dirname x3 → package root
11
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
12
+ writeGeneratedFiles(result, pkgDir);
13
+
14
+ console.error(`temporal: ${result.resources} resources (hand-written)`);
15
+ if (result.warnings.length > 0) {
16
+ console.error(`${result.warnings.length} warnings`);
17
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Temporal lexicon generation step.
3
+ *
4
+ * All 4 resources (TemporalServer, TemporalNamespace, SearchAttribute, TemporalSchedule)
5
+ * are hand-written. There is no remote spec to fetch or parse. This module builds
6
+ * the required lexiconJSON catalog and typesDTS stubs so that packagePipeline
7
+ * can hash and write dist/ artifacts correctly.
8
+ */
9
+
10
+ import { mkdirSync, writeFileSync } from "fs";
11
+ import { join } from "path";
12
+ import type { GenerateResult } from "@intentius/chant/codegen/generate";
13
+
14
+ export type { GenerateResult };
15
+
16
+ // ── Lexicon catalog (meta.json) ──────────────────────────────────────
17
+ // Describes the 4 hand-written resources so tooling (LSP, MCP, docs) can
18
+ // enumerate them without parsing the TypeScript source at runtime.
19
+
20
+ const LEXICON_JSON = JSON.stringify(
21
+ {
22
+ TemporalServer: {
23
+ resourceType: "Temporal::Server",
24
+ kind: "resource",
25
+ lexicon: "temporal",
26
+ description: "Temporal server deployment — emits docker-compose.yml and Helm values",
27
+ },
28
+ TemporalNamespace: {
29
+ resourceType: "Temporal::Namespace",
30
+ kind: "resource",
31
+ lexicon: "temporal",
32
+ description: "Temporal namespace — emits namespace create command in temporal-setup.sh",
33
+ },
34
+ SearchAttribute: {
35
+ resourceType: "Temporal::SearchAttribute",
36
+ kind: "resource",
37
+ lexicon: "temporal",
38
+ description: "Temporal search attribute — emits search-attribute create command in temporal-setup.sh",
39
+ },
40
+ TemporalSchedule: {
41
+ resourceType: "Temporal::Schedule",
42
+ kind: "resource",
43
+ lexicon: "temporal",
44
+ description: "Temporal schedule — emits SDK schedule creation TypeScript to schedules/<id>.ts",
45
+ },
46
+ },
47
+ null,
48
+ 2,
49
+ );
50
+
51
+ // ── Type declarations stub (types/index.d.ts) ────────────────────────
52
+ // All types are declared in src/resources.ts and re-exported from src/index.ts.
53
+ // The dist/types/index.d.ts file is a stub that satisfies the BundleSpec shape.
54
+
55
+ const TYPES_DTS = `// Types for @intentius/chant-lexicon-temporal are declared in src/resources.ts.
56
+ // They are available via the package's main export: import { ... } from "@intentius/chant-lexicon-temporal";
57
+ export {};
58
+ `;
59
+
60
+ // ── Generate ─────────────────────────────────────────────────────────
61
+
62
+ export async function generate(opts?: { verbose?: boolean; force?: boolean }): Promise<GenerateResult> {
63
+ if (opts?.verbose) {
64
+ console.error("temporal: all resources are hand-written — building catalog from static definitions");
65
+ }
66
+ return {
67
+ lexiconJSON: LEXICON_JSON,
68
+ typesDTS: TYPES_DTS,
69
+ indexTS: "",
70
+ resources: 4,
71
+ properties: 0,
72
+ enums: 0,
73
+ warnings: [],
74
+ };
75
+ }
76
+
77
+ export function writeGeneratedFiles(_result: GenerateResult, pkgDir: string): void {
78
+ // Write the catalog to src/generated/ so createCatalogResource can locate it at runtime.
79
+ const generatedDir = join(pkgDir, "src", "generated");
80
+ mkdirSync(generatedDir, { recursive: true });
81
+ writeFileSync(join(generatedDir, "lexicon-temporal.json"), LEXICON_JSON, "utf-8");
82
+ }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CLI entry point for `npm run bundle` in lexicon-temporal.
4
+ */
5
+ import { packageLexicon } from "./package";
6
+ import { writeBundleSpec } from "@intentius/chant/codegen/package";
7
+ import { join, dirname } from "path";
8
+ import { fileURLToPath } from "url";
9
+
10
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
11
+ const { spec, stats } = await packageLexicon({ verbose: true });
12
+ writeBundleSpec(spec, join(pkgDir, "dist"));
13
+
14
+ console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Temporal lexicon packaging — delegates to core packagePipeline.
3
+ */
4
+
5
+ import { readFileSync } from "fs";
6
+ import { temporalPlugin } from "../plugin";
7
+ import { join, dirname } from "path";
8
+ import { fileURLToPath } from "url";
9
+ import {
10
+ packagePipeline,
11
+ collectSkills,
12
+ type PackageOptions,
13
+ type PackageResult,
14
+ } from "@intentius/chant/codegen/package";
15
+ import { generate } from "./generate";
16
+
17
+ export type { PackageOptions, PackageResult };
18
+
19
+ // package.ts is at src/codegen/package.ts — 2 dirname calls reach src/
20
+ // then join(pkgDir, "..") is the package root where package.json lives
21
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
22
+
23
+ /**
24
+ * Package the Temporal lexicon into a distributable BundleSpec.
25
+ */
26
+ export async function packageLexicon(opts: PackageOptions = {}): Promise<PackageResult> {
27
+ const pkgJson = JSON.parse(readFileSync(join(pkgDir, "..", "package.json"), "utf-8"));
28
+
29
+ return packagePipeline(
30
+ {
31
+ generate: (genOpts) => generate({ verbose: genOpts.verbose, force: genOpts.force }),
32
+
33
+ buildManifest: (_genResult) => {
34
+ return {
35
+ name: "temporal",
36
+ version: pkgJson.version ?? "0.0.1",
37
+ chantVersion: ">=0.1.0",
38
+ namespace: "Temporal",
39
+ intrinsics: [],
40
+ pseudoParameters: {},
41
+ };
42
+ },
43
+
44
+ srcDir: pkgDir,
45
+
46
+ collectSkills: () => {
47
+ const skillDefs = temporalPlugin.skills?.() ?? [];
48
+ return collectSkills(skillDefs);
49
+ },
50
+
51
+ version: pkgJson.version ?? "0.0.1",
52
+ },
53
+ opts,
54
+ );
55
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * TemporalCloudStack composite — a Temporal Cloud namespace + search attributes.
3
+ *
4
+ * Bundles a TemporalNamespace with an optional set of SearchAttribute entities.
5
+ * No server resource is included — this composite assumes you are connecting
6
+ * to Temporal Cloud (or a remotely managed server).
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * export const { ns, searchAttributes } = TemporalCloudStack({
11
+ * namespace: "prod",
12
+ * retention: "30d",
13
+ * searchAttributes: [
14
+ * { name: "Project", type: "Keyword" },
15
+ * { name: "Priority", type: "Int" },
16
+ * ],
17
+ * });
18
+ * ```
19
+ */
20
+
21
+ import { TemporalNamespace, SearchAttribute } from "../resources";
22
+ import type { SearchAttributeProps } from "../resources";
23
+
24
+ export interface TemporalCloudStackConfig {
25
+ /** Namespace name (required). */
26
+ namespace: string;
27
+ /**
28
+ * Workflow history retention.
29
+ * @default "30d"
30
+ */
31
+ retention?: string;
32
+ /** Human-readable description for the namespace. */
33
+ description?: string;
34
+ /**
35
+ * Search attribute definitions to register in this namespace.
36
+ * Each entry creates a SearchAttribute entity scoped to this namespace.
37
+ */
38
+ searchAttributes?: Array<Pick<SearchAttributeProps, "name" | "type">>;
39
+ }
40
+
41
+ export interface TemporalCloudStackResources {
42
+ ns: InstanceType<typeof TemporalNamespace>;
43
+ searchAttributes: InstanceType<typeof SearchAttribute>[];
44
+ }
45
+
46
+ /**
47
+ * Create a Temporal Cloud namespace stack.
48
+ *
49
+ * Returns a TemporalNamespace and an array of SearchAttribute entities.
50
+ * Export the namespace and spread the search attributes from your chant project:
51
+ *
52
+ * ```typescript
53
+ * export const { ns, searchAttributes } = TemporalCloudStack({ namespace: "prod" });
54
+ * export const [projectAttr, priorityAttr] = searchAttributes;
55
+ * ```
56
+ */
57
+ export function TemporalCloudStack(config: TemporalCloudStackConfig): TemporalCloudStackResources {
58
+ const ns = new TemporalNamespace({
59
+ name: config.namespace,
60
+ retention: config.retention ?? "30d",
61
+ ...(config.description ? { description: config.description } : {}),
62
+ } as Record<string, unknown>);
63
+
64
+ const searchAttributes = (config.searchAttributes ?? []).map(
65
+ ({ name, type }) =>
66
+ new SearchAttribute({
67
+ name,
68
+ type,
69
+ namespace: config.namespace,
70
+ } as Record<string, unknown>),
71
+ );
72
+
73
+ return { ns, searchAttributes };
74
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Composite unit tests — TemporalDevStack, TemporalCloudStack.
3
+ */
4
+
5
+ import { describe, test, expect } from "vitest";
6
+ import { TemporalDevStack } from "./dev-stack";
7
+ import { TemporalCloudStack } from "./cloud-stack";
8
+ import { DECLARABLE_MARKER } from "@intentius/chant/declarable";
9
+
10
+ function getProps(entity: unknown): Record<string, unknown> {
11
+ return (entity as { props: Record<string, unknown> }).props;
12
+ }
13
+
14
+ function getEntityType(entity: unknown): string {
15
+ return (entity as Record<string, unknown>).entityType as string;
16
+ }
17
+
18
+ // ── TemporalDevStack ─────────────────────────────────────────────────
19
+
20
+ describe("TemporalDevStack: basic", () => {
21
+ test("returns server and ns", () => {
22
+ const result = TemporalDevStack();
23
+ expect(result.server).toBeDefined();
24
+ expect(result.ns).toBeDefined();
25
+ });
26
+
27
+ test("server has entityType Temporal::Server", () => {
28
+ const { server } = TemporalDevStack();
29
+ expect(getEntityType(server)).toBe("Temporal::Server");
30
+ });
31
+
32
+ test("ns has entityType Temporal::Namespace", () => {
33
+ const { ns } = TemporalDevStack();
34
+ expect(getEntityType(ns)).toBe("Temporal::Namespace");
35
+ });
36
+
37
+ test("both entities have DECLARABLE_MARKER", () => {
38
+ const { server, ns } = TemporalDevStack();
39
+ expect((server as Record<symbol, unknown>)[DECLARABLE_MARKER]).toBe(true);
40
+ expect((ns as Record<symbol, unknown>)[DECLARABLE_MARKER]).toBe(true);
41
+ });
42
+
43
+ test("defaults: namespace=default, retention=7d, mode=dev", () => {
44
+ const { server, ns } = TemporalDevStack();
45
+ expect(getProps(ns).name).toBe("default");
46
+ expect(getProps(ns).retention).toBe("7d");
47
+ expect(getProps(server).mode).toBe("dev");
48
+ });
49
+
50
+ test("config overrides namespace and retention", () => {
51
+ const { ns } = TemporalDevStack({ namespace: "my-app", retention: "14d" });
52
+ expect(getProps(ns).name).toBe("my-app");
53
+ expect(getProps(ns).retention).toBe("14d");
54
+ });
55
+
56
+ test("config passes version and ports to server", () => {
57
+ const { server } = TemporalDevStack({ version: "1.25.0", port: 7234, uiPort: 8081 });
58
+ expect(getProps(server).version).toBe("1.25.0");
59
+ expect(getProps(server).port).toBe(7234);
60
+ expect(getProps(server).uiPort).toBe(8081);
61
+ });
62
+
63
+ test("description is forwarded to namespace when provided", () => {
64
+ const { ns } = TemporalDevStack({ description: "local dev namespace" });
65
+ expect(getProps(ns).description).toBe("local dev namespace");
66
+ });
67
+
68
+ test("description is absent when not provided", () => {
69
+ const { ns } = TemporalDevStack();
70
+ expect(getProps(ns).description).toBeUndefined();
71
+ });
72
+ });
73
+
74
+ // ── TemporalCloudStack ───────────────────────────────────────────────
75
+
76
+ describe("TemporalCloudStack: basic", () => {
77
+ test("returns ns and searchAttributes array", () => {
78
+ const result = TemporalCloudStack({ namespace: "prod" });
79
+ expect(result.ns).toBeDefined();
80
+ expect(result.searchAttributes).toBeDefined();
81
+ });
82
+
83
+ test("ns has entityType Temporal::Namespace", () => {
84
+ const { ns } = TemporalCloudStack({ namespace: "prod" });
85
+ expect(getEntityType(ns)).toBe("Temporal::Namespace");
86
+ });
87
+
88
+ test("defaults retention to 30d", () => {
89
+ const { ns } = TemporalCloudStack({ namespace: "prod" });
90
+ expect(getProps(ns).name).toBe("prod");
91
+ expect(getProps(ns).retention).toBe("30d");
92
+ });
93
+
94
+ test("custom retention is forwarded", () => {
95
+ const { ns } = TemporalCloudStack({ namespace: "staging", retention: "14d" });
96
+ expect(getProps(ns).retention).toBe("14d");
97
+ });
98
+
99
+ test("returns empty searchAttributes when none specified", () => {
100
+ const { searchAttributes } = TemporalCloudStack({ namespace: "prod" });
101
+ expect(searchAttributes).toHaveLength(0);
102
+ });
103
+
104
+ test("creates SearchAttribute entities for each entry", () => {
105
+ const { searchAttributes } = TemporalCloudStack({
106
+ namespace: "prod",
107
+ searchAttributes: [
108
+ { name: "Project", type: "Keyword" },
109
+ { name: "Priority", type: "Int" },
110
+ ],
111
+ });
112
+ expect(searchAttributes).toHaveLength(2);
113
+ expect(getEntityType(searchAttributes[0])).toBe("Temporal::SearchAttribute");
114
+ expect(getEntityType(searchAttributes[1])).toBe("Temporal::SearchAttribute");
115
+ });
116
+
117
+ test("search attributes are scoped to the namespace", () => {
118
+ const { searchAttributes } = TemporalCloudStack({
119
+ namespace: "prod",
120
+ searchAttributes: [{ name: "Project", type: "Keyword" }],
121
+ });
122
+ expect(getProps(searchAttributes[0]).namespace).toBe("prod");
123
+ expect(getProps(searchAttributes[0]).name).toBe("Project");
124
+ expect(getProps(searchAttributes[0]).type).toBe("Keyword");
125
+ });
126
+
127
+ test("description is forwarded to namespace", () => {
128
+ const { ns } = TemporalCloudStack({ namespace: "prod", description: "Production namespace" });
129
+ expect(getProps(ns).description).toBe("Production namespace");
130
+ });
131
+ });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * TemporalDevStack composite — a local dev server + default namespace.
3
+ *
4
+ * Wires together a TemporalServer (dev mode) and a TemporalNamespace so
5
+ * that `chant build` emits a docker-compose.yml and temporal-setup.sh
6
+ * ready for local development.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * export const { server, ns } = TemporalDevStack({
11
+ * namespace: "my-app",
12
+ * retention: "7d",
13
+ * });
14
+ * ```
15
+ */
16
+
17
+ import { TemporalServer, TemporalNamespace } from "../resources";
18
+
19
+ export interface TemporalDevStackConfig {
20
+ /**
21
+ * Temporal server version.
22
+ * @default "1.26.2"
23
+ */
24
+ version?: string;
25
+ /**
26
+ * gRPC port for the Temporal frontend.
27
+ * @default 7233
28
+ */
29
+ port?: number;
30
+ /**
31
+ * Port for the Temporal Web UI.
32
+ * @default 8080
33
+ */
34
+ uiPort?: number;
35
+ /**
36
+ * Namespace to create on first run.
37
+ * @default "default"
38
+ */
39
+ namespace?: string;
40
+ /**
41
+ * Workflow history retention for the namespace.
42
+ * @default "7d"
43
+ */
44
+ retention?: string;
45
+ /**
46
+ * Human-readable description for the namespace.
47
+ */
48
+ description?: string;
49
+ }
50
+
51
+ export interface TemporalDevStackResources {
52
+ server: InstanceType<typeof TemporalServer>;
53
+ ns: InstanceType<typeof TemporalNamespace>;
54
+ }
55
+
56
+ /**
57
+ * Create a local Temporal dev stack.
58
+ *
59
+ * Returns a TemporalServer (dev mode) and a TemporalNamespace wired to the
60
+ * same default namespace. Export both from your chant project:
61
+ *
62
+ * ```typescript
63
+ * export const { server, ns } = TemporalDevStack({ namespace: "my-app" });
64
+ * ```
65
+ */
66
+ export function TemporalDevStack(config: TemporalDevStackConfig = {}): TemporalDevStackResources {
67
+ const server = new TemporalServer({
68
+ mode: "dev",
69
+ version: config.version,
70
+ port: config.port,
71
+ uiPort: config.uiPort,
72
+ } as Record<string, unknown>);
73
+
74
+ const ns = new TemporalNamespace({
75
+ name: config.namespace ?? "default",
76
+ retention: config.retention ?? "7d",
77
+ ...(config.description ? { description: config.description } : {}),
78
+ } as Record<string, unknown>);
79
+
80
+ return { server, ns };
81
+ }
package/src/config.ts ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Temporal worker profile — connection configuration for `chant run`.
3
+ *
4
+ * Add this to `chant.config.ts` to define how chant connects to Temporal:
5
+ *
6
+ * ```ts
7
+ * import type { TemporalChantConfig } from "@intentius/chant-lexicon-temporal";
8
+ *
9
+ * export default {
10
+ * lexicons: ["temporal"],
11
+ * temporal: {
12
+ * profiles: {
13
+ * local: {
14
+ * address: "localhost:7233",
15
+ * namespace: "default",
16
+ * taskQueue: "my-deploy",
17
+ * autoStart: true,
18
+ * },
19
+ * cloud: {
20
+ * address: "myns.a2dd6.tmprl.cloud:7233",
21
+ * namespace: "myns.a2dd6",
22
+ * taskQueue: "my-deploy",
23
+ * tls: true,
24
+ * apiKey: { env: "TEMPORAL_API_KEY" },
25
+ * },
26
+ * },
27
+ * defaultProfile: "local",
28
+ * } satisfies TemporalChantConfig,
29
+ * };
30
+ * ```
31
+ *
32
+ * ChantConfig uses `.passthrough()` in its Zod schema so the `temporal` key
33
+ * is accepted at runtime without core changes. Issue #8 (`chant run`) will
34
+ * read these profiles when starting workers.
35
+ */
36
+
37
+ /**
38
+ * Activity timeout and retry configuration for infrastructure activity groups.
39
+ *
40
+ * Pre-built profiles match the four activity groups typically seen in infra workflows:
41
+ * fast/idempotent operations, long-running infra (GKE, kubectl apply --wait),
42
+ * K8s wait loops, and human-gate activities.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * import { TEMPORAL_ACTIVITY_PROFILES } from "@intentius/chant-lexicon-temporal";
47
+ * import { proxyActivities } from "@temporalio/workflow";
48
+ *
49
+ * const { applyInfra } = proxyActivities<typeof infraActivities>(
50
+ * TEMPORAL_ACTIVITY_PROFILES.longInfra
51
+ * );
52
+ * ```
53
+ */
54
+ export interface TemporalActivityProfile {
55
+ /** Maximum time allowed for a single activity execution attempt. */
56
+ startToCloseTimeout: string;
57
+ /**
58
+ * Time after the last heartbeat before Temporal marks the activity as timed out.
59
+ * Required for activities that call `activity.heartbeat()` to signal liveness.
60
+ */
61
+ heartbeatTimeout?: string;
62
+ /** Retry policy for failed activity attempts. */
63
+ retry?: {
64
+ /** Initial wait before the first retry (e.g. "5s"). */
65
+ initialInterval?: string;
66
+ /** Multiplier applied to the interval on each retry (e.g. 2). */
67
+ backoffCoefficient?: number;
68
+ /** Maximum number of attempts including the first (0 = unlimited). */
69
+ maximumAttempts?: number;
70
+ /** Cap on retry intervals (e.g. "5m"). */
71
+ maximumInterval?: string;
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Named activity profiles for common infrastructure workflow patterns.
77
+ *
78
+ * Import and spread into `proxyActivities()` so retry/timeout configuration
79
+ * lives in the lexicon rather than inline in workflow code.
80
+ */
81
+ export const TEMPORAL_ACTIVITY_PROFILES = {
82
+ /**
83
+ * Fast, idempotent operations: `chant build`, `kubectl apply` without `--wait`,
84
+ * fetching nameservers, reading cluster status.
85
+ */
86
+ fastIdempotent: {
87
+ startToCloseTimeout: "5m",
88
+ retry: { maximumAttempts: 3, initialInterval: "5s", backoffCoefficient: 2 },
89
+ },
90
+
91
+ /**
92
+ * Long-running infra: GKE cluster creation via Config Connector (~10-20 min),
93
+ * `kubectl apply --wait` for large resource sets, Helm installs.
94
+ * Must heartbeat; 60 s silence → Temporal marks the activity dead.
95
+ */
96
+ longInfra: {
97
+ startToCloseTimeout: "20m",
98
+ heartbeatTimeout: "60s",
99
+ retry: { maximumAttempts: 3, initialInterval: "30s", backoffCoefficient: 2 },
100
+ },
101
+
102
+ /**
103
+ * K8s wait loops: polling for StatefulSet rollout, ExternalDNS A-records,
104
+ * DNS propagation. Medium timeout with heartbeating.
105
+ */
106
+ k8sWait: {
107
+ startToCloseTimeout: "15m",
108
+ heartbeatTimeout: "60s",
109
+ retry: { maximumAttempts: 3, initialInterval: "10s", backoffCoefficient: 2 },
110
+ },
111
+
112
+ /**
113
+ * Human-gate activities: waiting for an operator action (DNS delegation, approval).
114
+ * Very long timeout, single attempt — no retry on human-gate timeouts.
115
+ */
116
+ humanGate: {
117
+ startToCloseTimeout: "48h",
118
+ heartbeatTimeout: "90s",
119
+ retry: { maximumAttempts: 1 },
120
+ },
121
+ } as const satisfies Record<string, TemporalActivityProfile>;
122
+
123
+ export interface TemporalWorkerProfile {
124
+ /** Temporal server gRPC address. e.g. "localhost:7233" or "myns.a2dd6.tmprl.cloud:7233" */
125
+ address: string;
126
+ /** Temporal namespace to connect to */
127
+ namespace: string;
128
+ /** Task queue the worker polls */
129
+ taskQueue: string;
130
+ /** TLS configuration. Pass `true` or `{}` for Temporal Cloud default TLS */
131
+ tls?: boolean | { serverNameOverride?: string };
132
+ /**
133
+ * API key for Temporal Cloud authentication.
134
+ * String value: used as-is (Bearer token).
135
+ * Object form: reads from process.env at runtime.
136
+ */
137
+ apiKey?: string | { env: string };
138
+ /**
139
+ * Automatically start `temporal server start-dev` before the worker.
140
+ * Only applicable for local development profiles.
141
+ */
142
+ autoStart?: boolean;
143
+ }
144
+
145
+ export interface TemporalChantConfig {
146
+ /** Named connection profiles */
147
+ profiles: Record<string, TemporalWorkerProfile>;
148
+ /** Profile used when --profile flag is omitted from `chant run` */
149
+ defaultProfile?: string;
150
+ }
@@ -0,0 +1,9 @@
1
+ import { test, expect } from "vitest";
2
+ import { analyze } from "./coverage";
3
+
4
+ test("coverage is 100% — all 4 resources are hand-written", () => {
5
+ const result = analyze();
6
+ expect(result.total).toBe(4);
7
+ expect(result.covered).toBe(4);
8
+ expect(result.missing).toHaveLength(0);
9
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Coverage analysis for the Temporal lexicon.
3
+ *
4
+ * All 4 resources are hand-written (no remote spec), so coverage is always
5
+ * 100% by definition. This module satisfies the lexicon completeness checklist
6
+ * and provides a consistent interface for the plugin's coverage() lifecycle method.
7
+ */
8
+
9
+ export interface CoverageResult {
10
+ total: number;
11
+ covered: number;
12
+ missing: string[];
13
+ }
14
+
15
+ const RESOURCES = [
16
+ "Temporal::Server",
17
+ "Temporal::Namespace",
18
+ "Temporal::SearchAttribute",
19
+ "Temporal::Schedule",
20
+ ] as const;
21
+
22
+ export function analyze(): CoverageResult {
23
+ return {
24
+ total: RESOURCES.length,
25
+ covered: RESOURCES.length,
26
+ missing: [],
27
+ };
28
+ }
29
+
30
+ export function printCoverageResult(result: CoverageResult, verbose?: boolean): void {
31
+ if (verbose) {
32
+ console.error(
33
+ `Coverage: ${result.covered}/${result.total} resources (${Math.round((result.covered / result.total) * 100)}%).`,
34
+ );
35
+ console.error("All resources are hand-written — no remote spec to track against.");
36
+ }
37
+ }