@mettlecast/domain-cli 0.2.22 → 0.2.23

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 (69) hide show
  1. package/dist/cli.js +60 -3
  2. package/dist/commands/add-api.d.ts +1 -1
  3. package/dist/commands/add-api.js +45 -17
  4. package/dist/commands/add-fixture-factory.d.ts +16 -0
  5. package/dist/commands/add-fixture-factory.js +60 -0
  6. package/dist/commands/add-module.js +4 -5
  7. package/dist/commands/add-seed-page.js +4 -5
  8. package/dist/commands/build-flows.js +1 -1
  9. package/dist/commands/dev.d.ts +30 -3
  10. package/dist/commands/dev.js +52 -11
  11. package/dist/commands/doctor.d.ts +22 -0
  12. package/dist/commands/doctor.js +341 -6
  13. package/dist/commands/generate-openapi.d.ts +21 -0
  14. package/dist/commands/generate-openapi.js +117 -0
  15. package/dist/commands/generate-sdk.d.ts +25 -0
  16. package/dist/commands/generate-sdk.js +98 -0
  17. package/dist/commands/init.d.ts +14 -0
  18. package/dist/commands/init.js +62 -0
  19. package/dist/commands/reseed-page.js +5 -6
  20. package/dist/commands/upgrade.d.ts +2 -0
  21. package/dist/commands/upgrade.js +28 -6
  22. package/dist/commands/why.d.ts +47 -0
  23. package/dist/commands/why.js +129 -0
  24. package/dist/templates/api-skeleton.d.ts +5 -1
  25. package/dist/templates/api-skeleton.js +28 -6
  26. package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
  27. package/dist/templates/patterns/api/create-with-event.js +14 -1
  28. package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
  29. package/dist/templates/patterns/api/idempotent-mutation.js +20 -0
  30. package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
  31. package/dist/templates/patterns/api/paginated-list.js +12 -2
  32. package/dist/templates/patterns/api/simple-crud.d.ts +5 -0
  33. package/dist/templates/patterns/api/simple-crud.js +22 -4
  34. package/dist/templates/patterns/api/streaming-list.d.ts +27 -0
  35. package/dist/templates/patterns/api/streaming-list.js +91 -0
  36. package/dist/templates/patterns/api/system-admin.d.ts +5 -0
  37. package/dist/templates/patterns/api/system-admin.js +14 -4
  38. package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
  39. package/dist/templates/patterns/api/webhook-receiver-style.js +22 -0
  40. package/dist/utils/s3-fetch.js +23 -32
  41. package/package.json +4 -1
  42. package/src/__tests__/commands/add-api.test.ts +160 -0
  43. package/src/__tests__/commands/dev.test.ts +162 -0
  44. package/src/__tests__/commands/why.test.ts +199 -0
  45. package/src/__tests__/doctor.test.ts +336 -1
  46. package/src/__tests__/smoke/scaffold.test.ts +574 -0
  47. package/src/cli.ts +67 -5
  48. package/src/commands/add-api.ts +68 -19
  49. package/src/commands/add-fixture-factory.ts +75 -0
  50. package/src/commands/add-module.ts +4 -5
  51. package/src/commands/add-seed-page.ts +4 -5
  52. package/src/commands/build-flows.ts +2 -2
  53. package/src/commands/dev.ts +78 -11
  54. package/src/commands/doctor.ts +379 -12
  55. package/src/commands/generate-openapi.ts +154 -0
  56. package/src/commands/generate-sdk.ts +125 -0
  57. package/src/commands/init.ts +78 -0
  58. package/src/commands/reseed-page.ts +5 -6
  59. package/src/commands/upgrade.ts +26 -6
  60. package/src/commands/why.ts +171 -0
  61. package/src/templates/api-skeleton.ts +32 -6
  62. package/src/templates/patterns/api/create-with-event.ts +15 -1
  63. package/src/templates/patterns/api/idempotent-mutation.ts +21 -0
  64. package/src/templates/patterns/api/paginated-list.ts +13 -2
  65. package/src/templates/patterns/api/simple-crud.ts +23 -4
  66. package/src/templates/patterns/api/streaming-list.ts +91 -0
  67. package/src/templates/patterns/api/system-admin.ts +15 -4
  68. package/src/templates/patterns/api/webhook-receiver-style.ts +23 -0
  69. package/src/utils/s3-fetch.ts +26 -34
@@ -0,0 +1,62 @@
1
+ /**
2
+ * init — bootstrap a freshly scaffolded project so it is ready for
3
+ * local development and CI. Runs npm install, builds the CLI, and
4
+ * runs a baseline doctor check.
5
+ *
6
+ * Usage: npx mc-domain-module init
7
+ */
8
+ import { execSync } from 'node:child_process';
9
+ import { existsSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { cliLogger } from '../utils/logger.js';
12
+ import { runDoctor } from './doctor.js';
13
+ export async function runInit(options = {}) {
14
+ const projectRoot = options.projectRoot ?? process.cwd();
15
+ const skipInstall = options.skipInstallIfExists !== false;
16
+ // ── 1. npm install ──────────────────────────────────────────────────
17
+ const nodeModulesDir = join(projectRoot, 'node_modules');
18
+ if (skipInstall && existsSync(nodeModulesDir)) {
19
+ cliLogger.info('node_modules exists — skipping npm install');
20
+ }
21
+ else {
22
+ cliLogger.info('Running npm install...');
23
+ execSync('npm install --legacy-peer-deps', {
24
+ cwd: projectRoot,
25
+ stdio: 'inherit',
26
+ });
27
+ }
28
+ // ── 2. Build the CLI ────────────────────────────────────────────────
29
+ const cliDir = join(projectRoot, 'packages', 'domain-cli');
30
+ if (existsSync(cliDir)) {
31
+ cliLogger.info('Installing domain-cli dependencies...');
32
+ execSync('npm install --legacy-peer-deps', {
33
+ cwd: cliDir,
34
+ stdio: 'inherit',
35
+ });
36
+ cliLogger.info('Building domain-cli...');
37
+ execSync('npm run build', {
38
+ cwd: cliDir,
39
+ stdio: 'inherit',
40
+ });
41
+ }
42
+ // ── 3. Baseline doctor ──────────────────────────────────────────────
43
+ cliLogger.info('Running doctor (baseline)...');
44
+ const report = await runDoctor({ projectRoot, strict: true });
45
+ const passes = report.checks.filter(c => c.status === 'PASS').length;
46
+ const fails = report.checks.filter(c => c.status === 'FAIL').length;
47
+ const warns = report.checks.filter(c => c.status === 'WARN').length;
48
+ // eslint-disable-next-line no-console
49
+ console.log(`\nDoctor baseline: ${passes} PASS, ${fails} FAIL, ${warns} WARN`);
50
+ if (report.exitCode !== 0) {
51
+ // eslint-disable-next-line no-console
52
+ console.log('\nDoctor found issues — this is expected on a fresh scaffold.\n' +
53
+ 'Run `npx mc-domain-module doctor` for details.\n');
54
+ }
55
+ else {
56
+ // eslint-disable-next-line no-console
57
+ console.log('\nProject is ready!');
58
+ }
59
+ }
60
+ export async function runInitCli(opts = {}) {
61
+ await runInit(opts);
62
+ }
@@ -3,6 +3,7 @@ import { join, resolve, dirname } from 'node:path';
3
3
  import { execSync } from 'node:child_process';
4
4
  import { createGunzip } from 'node:zlib';
5
5
  import { Readable } from 'node:stream';
6
+ import ky from 'ky';
6
7
  import { cliLogger } from '../utils/logger.js';
7
8
  import { fetchVersionsJson, fetchModulesJson, fetchModuleTarball, } from '../utils/s3-fetch.js';
8
9
  import { readManifest } from '../utils/manifest.js';
@@ -73,21 +74,19 @@ function detectGitRemote(projectDir) {
73
74
  }
74
75
  async function createGitHubPR(owner, repo, token, branch, title, body, labels) {
75
76
  const url = `https://api.github.com/repos/${owner}/${repo}/pulls`;
76
- const res = await fetch(url, {
77
- method: 'POST',
77
+ const res = await ky.post(url, {
78
78
  headers: {
79
79
  Authorization: `Bearer ${token}`,
80
80
  Accept: 'application/vnd.github+json',
81
- 'Content-Type': 'application/json',
82
81
  'X-GitHub-Api-Version': '2022-11-28',
83
82
  },
84
- body: JSON.stringify({
83
+ json: {
85
84
  title,
86
85
  body,
87
86
  head: branch,
88
87
  base: 'develop',
89
- labels,
90
- }),
88
+ ...(labels && labels.length > 0 && { labels }),
89
+ },
91
90
  });
92
91
  if (!res.ok) {
93
92
  const err = await res.text();
@@ -1,5 +1,7 @@
1
1
  export interface UpgradeOptions {
2
2
  dryRun: boolean;
3
+ /** Read-only variant: compute diffs, print a CI-parseable summary, exit 0 (no drift) or 1 (drift detected). Never writes files. */
4
+ check: boolean;
3
5
  projectDir: string;
4
6
  githubToken?: string;
5
7
  frontendComponents?: boolean;
@@ -3,6 +3,7 @@ import { Readable } from 'node:stream';
3
3
  import { unlink } from 'node:fs/promises';
4
4
  import { join, resolve } from 'node:path';
5
5
  import { execSync } from 'node:child_process';
6
+ import ky from 'ky';
6
7
  import { cliLogger } from '../utils/logger.js';
7
8
  import { fetchVersionsJson, fetchModulesJson, fetchModuleTarball, fetchScaffoldFile, } from '../utils/s3-fetch.js';
8
9
  import { computeChecksumString } from '../utils/checksum.js';
@@ -117,21 +118,19 @@ function detectGitRemote(projectDir) {
117
118
  }
118
119
  async function createGitHubPR(owner, repo, token, branch, title, body, labels) {
119
120
  const url = `https://api.github.com/repos/${owner}/${repo}/pulls`;
120
- const res = await fetch(url, {
121
- method: 'POST',
121
+ const res = await ky.post(url, {
122
122
  headers: {
123
123
  Authorization: `Bearer ${token}`,
124
124
  Accept: 'application/vnd.github+json',
125
- 'Content-Type': 'application/json',
126
125
  'X-GitHub-Api-Version': '2022-11-28',
127
126
  },
128
- body: JSON.stringify({
127
+ json: {
129
128
  title,
130
129
  body,
131
130
  head: branch,
132
131
  base: 'develop',
133
132
  ...(labels && labels.length > 0 && { labels }),
134
- }),
133
+ },
135
134
  });
136
135
  if (!res.ok) {
137
136
  const err = await res.text();
@@ -252,7 +251,8 @@ function buildFrontendComponentsPrBody(currentVersion, targetVersion, results, c
252
251
  }
253
252
  export async function runUpgrade(packageSpec, opts) {
254
253
  const projectDir = resolve(opts.projectDir ?? process.cwd());
255
- console.log(`\nTIB Upgrade${opts.dryRun ? ' (dry run)' : ''}\n`);
254
+ const actionLabel = opts.check ? 'check' : opts.dryRun ? 'dry run' : 'upgrade';
255
+ console.log(`\nTIB Upgrade (${actionLabel})\n`);
256
256
  console.log(`Project: ${projectDir}\n`);
257
257
  // 1. Read manifest + scaffold-config
258
258
  const manifest = await readManifest(projectDir);
@@ -420,6 +420,28 @@ export async function runUpgrade(packageSpec, opts) {
420
420
  console.log(` ${r.path} — new version written to ${r.path}.tib-upgrade`);
421
421
  }
422
422
  }
423
+ if (opts.check) {
424
+ // Read-only CI gate — report drift and exit.
425
+ const totalChanged = added.length + updated.length + deleted.length;
426
+ if (totalChanged === 0 && conflicts.length === 0) {
427
+ console.log('\n✓ No drift — project is up to date with scaffold.\n');
428
+ process.exitCode = 0;
429
+ }
430
+ else {
431
+ console.log(`\n✗ Drift detected: ${totalChanged} file(s) changed, ${conflicts.length} conflict(s).`);
432
+ if (added.length > 0)
433
+ console.log(` Added : ${added.map((r) => r.path).join(', ')}`);
434
+ if (updated.length > 0)
435
+ console.log(` Updated : ${updated.map((r) => r.path).join(', ')}`);
436
+ if (deleted.length > 0)
437
+ console.log(` Deleted : ${deleted.map((r) => r.path).join(', ')}`);
438
+ if (conflicts.length > 0)
439
+ console.log(` Conflicts: ${conflicts.map((r) => r.path).join(', ')}`);
440
+ console.log('\nRun `npx mc-domain-module upgrade` to apply changes.\n');
441
+ process.exitCode = 1;
442
+ }
443
+ return;
444
+ }
423
445
  if (opts.dryRun) {
424
446
  console.log('\nDry run complete — no files were written.\n');
425
447
  return;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Command to explain the project's current scaffold state and any available upgrades.
3
+ * Reads .mc/manifest.json for the current scaffold version + enabled module list,
4
+ * fetches the latest version from S3, and prints a diff to stdout.
5
+ */
6
+ /**
7
+ * Options for the why command.
8
+ */
9
+ export interface WhyOptions {
10
+ /** Project root directory. Defaults to process.cwd(). */
11
+ projectRoot?: string;
12
+ /** Override S3 bucket used to fetch versions.json. Defaults to the public TIB bucket. */
13
+ scaffoldBucket?: string;
14
+ /** Output raw JSON instead of a human-readable report. */
15
+ json?: boolean;
16
+ }
17
+ /**
18
+ * Report describing the project's current scaffold state and any pending upgrade.
19
+ */
20
+ export interface WhyReport {
21
+ /** Scaffold version recorded in .mc/manifest.json (or "unknown" if no manifest). */
22
+ currentVersion: string;
23
+ /** Module IDs currently enabled in the project. */
24
+ enabledModules: string[];
25
+ /** Latest version published to the scaffold S3 bucket. */
26
+ latestVersion: string;
27
+ /** True when the current pinned version is older than the latest. */
28
+ upgradeAvailable: boolean;
29
+ /** Number of releases between current and latest (-1 if versions cannot be compared). */
30
+ versionsBehind: number;
31
+ /** All known scaffold versions (most recent first). */
32
+ allVersions: string[];
33
+ }
34
+ /**
35
+ * Execute the why command: gather local + remote scaffold state, return a report.
36
+ * @param options - WhyOptions with optional projectRoot and scaffoldBucket.
37
+ * @returns A WhyReport describing current vs. latest state.
38
+ */
39
+ export declare function runWhy(options?: WhyOptions): Promise<WhyReport>;
40
+ /**
41
+ * Format a WhyReport as a multi-line human-readable string.
42
+ */
43
+ export declare function formatWhyReport(report: WhyReport): string;
44
+ /**
45
+ * CLI entry point: print a human-readable (or JSON) report and return the report.
46
+ */
47
+ export declare function runWhyCli(options?: WhyOptions): Promise<WhyReport>;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Command to explain the project's current scaffold state and any available upgrades.
3
+ * Reads .mc/manifest.json for the current scaffold version + enabled module list,
4
+ * fetches the latest version from S3, and prints a diff to stdout.
5
+ */
6
+ import { readFile } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+ import { fetchVersionsJson } from '../utils/s3-fetch.js';
9
+ import { cliLogger } from '../utils/logger.js';
10
+ const DEFAULT_BUCKET = 'mc-scaffold';
11
+ const MANIFEST_PATH_SEGMENTS = ['.mc', 'manifest.json'];
12
+ /**
13
+ * Read the project's current scaffold state from .mc/manifest.json.
14
+ * Returns sensible defaults when the manifest is missing or unreadable.
15
+ */
16
+ async function readManifestState(projectRoot) {
17
+ const manifestPath = join(projectRoot, ...MANIFEST_PATH_SEGMENTS);
18
+ try {
19
+ const content = await readFile(manifestPath, 'utf-8');
20
+ const parsed = JSON.parse(content);
21
+ const scaffoldVersion = typeof parsed.scaffoldVersion === 'string' ? parsed.scaffoldVersion : 'unknown';
22
+ const enabledModules = Array.isArray(parsed.enabledModules)
23
+ ? parsed.enabledModules.filter((m) => typeof m === 'string')
24
+ : [];
25
+ return { scaffoldVersion, enabledModules };
26
+ }
27
+ catch (err) {
28
+ if (err.code === 'ENOENT') {
29
+ return { scaffoldVersion: 'unknown', enabledModules: [] };
30
+ }
31
+ throw err;
32
+ }
33
+ }
34
+ /**
35
+ * Compute the number of releases between two version strings, using the order
36
+ * returned by versions.json (most recent first). Returns -1 when either version
37
+ * is not present in the list.
38
+ */
39
+ function computeVersionsBehind(current, latest, all) {
40
+ if (current === 'unknown')
41
+ return -1;
42
+ const latestIdx = all.indexOf(latest);
43
+ const currentIdx = all.indexOf(current);
44
+ if (latestIdx === -1 || currentIdx === -1)
45
+ return -1;
46
+ return Math.max(0, currentIdx - latestIdx);
47
+ }
48
+ /**
49
+ * Execute the why command: gather local + remote scaffold state, return a report.
50
+ * @param options - WhyOptions with optional projectRoot and scaffoldBucket.
51
+ * @returns A WhyReport describing current vs. latest state.
52
+ */
53
+ export async function runWhy(options = {}) {
54
+ const projectRoot = options.projectRoot ?? process.cwd();
55
+ const bucket = options.scaffoldBucket ?? DEFAULT_BUCKET;
56
+ const { scaffoldVersion, enabledModules } = await readManifestState(projectRoot);
57
+ cliLogger.debug({ scaffoldVersion, enabledModules }, 'why: read local manifest');
58
+ const versionsJson = await fetchVersionsJson(bucket);
59
+ const latestVersion = versionsJson.latest;
60
+ const allVersions = versionsJson.versions;
61
+ const upgradeAvailable = latestVersion !== scaffoldVersion;
62
+ const versionsBehind = computeVersionsBehind(scaffoldVersion, latestVersion, allVersions);
63
+ return {
64
+ currentVersion: scaffoldVersion,
65
+ enabledModules,
66
+ latestVersion,
67
+ upgradeAvailable,
68
+ versionsBehind,
69
+ allVersions,
70
+ };
71
+ }
72
+ /**
73
+ * Format a WhyReport as a multi-line human-readable string.
74
+ */
75
+ export function formatWhyReport(report) {
76
+ const lines = [];
77
+ lines.push('=== Scaffold State ===');
78
+ lines.push(`Current version : ${report.currentVersion}`);
79
+ lines.push(`Latest version : ${report.latestVersion}`);
80
+ if (report.upgradeAvailable) {
81
+ if (report.versionsBehind > 0) {
82
+ lines.push(`Upgrade available: yes (${report.versionsBehind} release(s) behind)`);
83
+ }
84
+ else {
85
+ lines.push('Upgrade available: yes');
86
+ }
87
+ }
88
+ else {
89
+ lines.push('Upgrade available: no — you are on the latest version');
90
+ }
91
+ lines.push('');
92
+ lines.push(`Enabled modules : ${report.enabledModules.length === 0 ? '(none)' : ''}`);
93
+ for (const m of report.enabledModules) {
94
+ lines.push(` - ${m}`);
95
+ }
96
+ lines.push('');
97
+ lines.push('Pending upgrade diff:');
98
+ if (!report.upgradeAvailable) {
99
+ lines.push(' (none)');
100
+ }
101
+ else {
102
+ const idx = report.allVersions.indexOf(report.currentVersion);
103
+ if (idx <= 0) {
104
+ lines.push(` (full jump: ${report.currentVersion} → ${report.latestVersion})`);
105
+ }
106
+ else {
107
+ const newer = report.allVersions.slice(0, idx);
108
+ for (const v of newer) {
109
+ lines.push(` ${v} (newer than ${report.currentVersion})`);
110
+ }
111
+ }
112
+ }
113
+ return lines.join('\n') + '\n';
114
+ }
115
+ /**
116
+ * CLI entry point: print a human-readable (or JSON) report and return the report.
117
+ */
118
+ export async function runWhyCli(options = {}) {
119
+ const report = await runWhy(options);
120
+ if (options.json) {
121
+ // eslint-disable-next-line no-console
122
+ console.log(JSON.stringify(report, null, 2));
123
+ }
124
+ else {
125
+ // eslint-disable-next-line no-console
126
+ process.stdout.write(formatWhyReport(report));
127
+ }
128
+ return report;
129
+ }
@@ -11,8 +11,12 @@
11
11
  export declare function apiSkeletonTemplate(domainId: string, apiId: string, tenancy: string): string;
12
12
  /**
13
13
  * Generates a fixture JSON skeleton for API testing.
14
+ * The fixture embeds the input schema's default shape as the example payload
15
+ * in the event body so that the test driver can deserialize it directly.
14
16
  * @param domainId - Domain ID in kebab-case.
15
17
  * @param apiId - API ID in kebab-case.
18
+ * @param exampleBody - JSON-stringified example payload matching the input
19
+ * schema's default shape. Defaults to '{}'.
16
20
  * @returns JSON string with mock event and context for testing.
17
21
  */
18
- export declare function apiFixtureSkeleton(domainId: string, apiId: string): string;
22
+ export declare function apiFixtureSkeleton(domainId: string, apiId: string, exampleBody?: string): string;
@@ -18,8 +18,24 @@ function camelCase(s) {
18
18
  */
19
19
  export function apiSkeletonTemplate(domainId, apiId, tenancy) {
20
20
  const tenantPath = tenancy === 'required' ? '/v1/tenants/{tenantId}' : '/v1';
21
+ const outputType = `${camelCase(domainId)}${camelCase(apiId)}Output`;
21
22
  return `import { z } from 'zod';
22
23
  import { defineApi } from '@mettlecast/domain-runtime';
24
+ import { createKyClient, initOtel } from '@mettlecast/domain-runtime';
25
+ import type { Result, AppError } from '@mettlecast/domain-runtime';
26
+ import { ok, err, notFound } from '@mettlecast/domain-runtime';
27
+
28
+ // Initialise OpenTelemetry tracing for the process. Idempotent —
29
+ // safe to call at module scope. Sets up OTLP export (when
30
+ // OTEL_EXPORTER_OTLP_ENDPOINT is set) or console-export for
31
+ // local dev. Auto-instruments outbound HTTP calls via ky.
32
+ initOtel();
33
+
34
+ // Shared HTTP client with retry on 503/429. Replace this with
35
+ // your own client if you need custom headers or auth.
36
+ const http = createKyClient();
37
+
38
+ const outputSchema = z.object({ ok: z.boolean() }).default({ ok: true });
23
39
 
24
40
  export const ${camelCase(apiId)} = defineApi({
25
41
  id: '${apiId}',
@@ -29,10 +45,12 @@ export const ${camelCase(apiId)} = defineApi({
29
45
  versions: {
30
46
  v1: {
31
47
  status: 'stable',
32
- input: z.object({}).optional(),
33
- output: z.object({ ok: z.boolean() }),
34
- handler: async (_input, _ctx) => {
35
- return { ok: true };
48
+ input: z.object({}).default({}),
49
+ output: outputSchema,
50
+ handler: async (_input, _ctx): Promise<Result<z.infer<typeof outputSchema>, AppError>> => {
51
+ // Replace this with your handler logic.
52
+ // Return ok(value) on success, err({ ... }) on failure.
53
+ return ok({ ok: true });
36
54
  },
37
55
  },
38
56
  },
@@ -45,11 +63,15 @@ export const ${camelCase(apiId)} = defineApi({
45
63
  }
46
64
  /**
47
65
  * Generates a fixture JSON skeleton for API testing.
66
+ * The fixture embeds the input schema's default shape as the example payload
67
+ * in the event body so that the test driver can deserialize it directly.
48
68
  * @param domainId - Domain ID in kebab-case.
49
69
  * @param apiId - API ID in kebab-case.
70
+ * @param exampleBody - JSON-stringified example payload matching the input
71
+ * schema's default shape. Defaults to '{}'.
50
72
  * @returns JSON string with mock event and context for testing.
51
73
  */
52
- export function apiFixtureSkeleton(domainId, apiId) {
74
+ export function apiFixtureSkeleton(domainId, apiId, exampleBody = '{}') {
53
75
  return JSON.stringify({
54
76
  description: `Fixture for ${domainId}.${apiId}`,
55
77
  event: {
@@ -68,7 +90,7 @@ export function apiFixtureSkeleton(domainId, apiId) {
68
90
  },
69
91
  pathParameters: {},
70
92
  headers: { 'content-type': 'application/json', 'accept-version': '1' },
71
- body: null,
93
+ body: exampleBody,
72
94
  },
73
95
  }, null, 2) + '\n';
74
96
  }
@@ -3,3 +3,8 @@
3
3
  * Produces: POST handler + publishes a domain event after mutation.
4
4
  */
5
5
  export declare function createWithEventTemplate(domain: string, id: string, tenancy: string): string;
6
+ /**
7
+ * Example body string used as the event body in the add-api fixture for the
8
+ * create-with-event pattern. Mirrors the default shape of the input schema.
9
+ */
10
+ export declare const createWithEventExampleBody: string;
@@ -10,17 +10,25 @@ export function createWithEventTemplate(domain, id, tenancy) {
10
10
  const tenantPath = tenancy === 'required' ? '/v1/tenants/{tenantId}' : '/v1';
11
11
  return `import { z } from 'zod';
12
12
  import { defineApi } from '@mettlecast/domain-runtime';
13
+ import { createKyClient, initOtel } from '@mettlecast/domain-runtime';
14
+ import type { Result, AppError } from '@mettlecast/domain-runtime';
15
+ import { ok, err, notFound } from '@mettlecast/domain-runtime';
16
+
17
+ initOtel();
13
18
 
14
19
  // ── Zod schemas ──────────────────────────────────────────────
15
20
 
16
21
  const ${varName}Input = z.object({
17
22
  name: z.string().min(1),
18
23
  payload: z.record(z.unknown()).optional(),
19
- });
24
+ }).default({ name: 'Example' });
20
25
 
21
26
  const ${varName}Output = z.object({
22
27
  id: z.string().uuid(),
23
28
  status: z.enum(['created', 'pending']),
29
+ }).default({
30
+ id: '00000000-0000-0000-0000-000000000000',
31
+ status: 'created',
24
32
  });
25
33
 
26
34
  // ── API definition ───────────────────────────────────────────
@@ -63,3 +71,8 @@ export const ${varName} = defineApi({
63
71
  });
64
72
  `;
65
73
  }
74
+ /**
75
+ * Example body string used as the event body in the add-api fixture for the
76
+ * create-with-event pattern. Mirrors the default shape of the input schema.
77
+ */
78
+ export const createWithEventExampleBody = JSON.stringify({ name: 'Example' });
@@ -3,3 +3,8 @@
3
3
  * Produces: PUT with idempotency key middleware.
4
4
  */
5
5
  export declare function idempotentMutationTemplate(domain: string, id: string, tenancy: string): string;
6
+ /**
7
+ * Example body string used as the event body in the add-api fixture for the
8
+ * idempotent-mutation pattern. Mirrors the default shape of the input schema.
9
+ */
10
+ export declare const idempotentMutationExampleBody: string;
@@ -10,18 +10,30 @@ export function idempotentMutationTemplate(domain, id, tenancy) {
10
10
  const tenantPath = tenancy === 'required' ? '/v1/tenants/{tenantId}' : '/v1';
11
11
  return `import { z } from 'zod';
12
12
  import { defineApi } from '@mettlecast/domain-runtime';
13
+ import { createKyClient, initOtel } from '@mettlecast/domain-runtime';
14
+ import type { Result, AppError } from '@mettlecast/domain-runtime';
15
+ import { ok, err, notFound } from '@mettlecast/domain-runtime';
16
+
17
+ initOtel();
13
18
 
14
19
  // ── Zod schemas ──────────────────────────────────────────────
15
20
 
16
21
  const ${varName}Input = z.object({
17
22
  id: z.string().uuid(),
18
23
  payload: z.record(z.unknown()),
24
+ }).default({
25
+ id: '00000000-0000-0000-0000-000000000000',
26
+ payload: {},
19
27
  });
20
28
 
21
29
  const ${varName}Output = z.object({
22
30
  id: z.string().uuid(),
23
31
  status: z.enum(['applied', 'already-processed']),
24
32
  idempotencyKey: z.string(),
33
+ }).default({
34
+ id: '00000000-0000-0000-0000-000000000000',
35
+ status: 'applied',
36
+ idempotencyKey: '00000000-0000-0000-0000-000000000000',
25
37
  });
26
38
 
27
39
  // ── API definition ───────────────────────────────────────────
@@ -74,3 +86,11 @@ export const ${varName} = defineApi({
74
86
  });
75
87
  `;
76
88
  }
89
+ /**
90
+ * Example body string used as the event body in the add-api fixture for the
91
+ * idempotent-mutation pattern. Mirrors the default shape of the input schema.
92
+ */
93
+ export const idempotentMutationExampleBody = JSON.stringify({
94
+ id: '00000000-0000-0000-0000-000000000000',
95
+ payload: {},
96
+ });
@@ -3,3 +3,8 @@
3
3
  * Produces: GET with cursor/limit, pagination contract, EMF metrics.
4
4
  */
5
5
  export declare function paginatedListTemplate(domain: string, id: string, tenancy: string): string;
6
+ /**
7
+ * Example body string used as the event body in the add-api fixture for the
8
+ * paginated-list pattern. Mirrors the default shape of the input schema.
9
+ */
10
+ export declare const paginatedListExampleBody = "{}";
@@ -10,6 +10,11 @@ export function paginatedListTemplate(domain, id, tenancy) {
10
10
  const tenantPath = tenancy === 'required' ? '/v1/tenants/{tenantId}' : '/v1';
11
11
  return `import { z } from 'zod';
12
12
  import { defineApi } from '@mettlecast/domain-runtime';
13
+ import { createKyClient, initOtel } from '@mettlecast/domain-runtime';
14
+ import type { Result, AppError } from '@mettlecast/domain-runtime';
15
+ import { ok, err, notFound } from '@mettlecast/domain-runtime';
16
+
17
+ initOtel();
13
18
 
14
19
  // ── Pagination contract ──────────────────────────────────────
15
20
 
@@ -22,13 +27,13 @@ const PageInput = z.object({
22
27
  .max(100)
23
28
  .default(20),
24
29
  filter: z.string().optional(),
25
- });
30
+ }).default({});
26
31
 
27
32
  const PageOutput = z.object({
28
33
  items: z.array(z.object({ id: z.string().uuid() })),
29
34
  nextCursor: z.string().optional(),
30
35
  total: z.number().int().optional(),
31
- });
36
+ }).default({ items: [], nextCursor: undefined, total: undefined });
32
37
 
33
38
  // ── API definition ───────────────────────────────────────────
34
39
 
@@ -61,3 +66,8 @@ export const ${varName} = defineApi({
61
66
  });
62
67
  `;
63
68
  }
69
+ /**
70
+ * Example body string used as the event body in the add-api fixture for the
71
+ * paginated-list pattern. Mirrors the default shape of the input schema.
72
+ */
73
+ export const paginatedListExampleBody = '{}';
@@ -4,3 +4,8 @@
4
4
  */
5
5
  export declare function simpleCrudTemplate(domain: string, id: string, tenancy: string): string;
6
6
  export declare function simpleCrudListTemplate(domain: string, id: string, tenancy: string): string;
7
+ /**
8
+ * Example body string used as the event body in the add-api fixture for the
9
+ * simple-crud pattern. Mirrors the default shape of the input schema.
10
+ */
11
+ export declare const simpleCrudExampleBody = "{}";
@@ -10,23 +10,32 @@ export function simpleCrudTemplate(domain, id, tenancy) {
10
10
  const tenantPath = tenancy === 'required' ? '/v1/tenants/{tenantId}' : '/v1';
11
11
  return `import { z } from 'zod';
12
12
  import { defineApi } from '@mettlecast/domain-runtime';
13
+ import { createKyClient, initOtel } from '@mettlecast/domain-runtime';
14
+ import type { Result, AppError } from '@mettlecast/domain-runtime';
15
+ import { ok, err, notFound } from '@mettlecast/domain-runtime';
16
+
17
+ initOtel();
13
18
 
14
19
  // ── Zod schemas ──────────────────────────────────────────────
15
20
 
16
21
  const ${varName}Input = z.object({
17
22
  id: z.string().uuid().optional(),
18
- });
23
+ }).default({});
19
24
 
20
25
  const ${varName}Output = z.object({
21
26
  id: z.string().uuid(),
22
27
  createdAt: z.string().datetime(),
23
28
  updatedAt: z.string().datetime(),
29
+ }).default({
30
+ id: '00000000-0000-0000-0000-000000000000',
31
+ createdAt: '2026-01-01T00:00:00.000Z',
32
+ updatedAt: '2026-01-01T00:00:00.000Z',
24
33
  });
25
34
 
26
35
  const ${varName}ListOutput = z.object({
27
36
  items: z.array(${varName}Output),
28
37
  nextCursor: z.string().optional(),
29
- });
38
+ }).default({ items: [], nextCursor: undefined });
30
39
 
31
40
  // ── API definition ───────────────────────────────────────────
32
41
 
@@ -62,18 +71,22 @@ import { defineApi } from '@mettlecast/domain-runtime';
62
71
  const ${varName}ListInput = z.object({
63
72
  cursor: z.string().optional(),
64
73
  limit: z.number().int().min(1).max(100).default(20),
65
- });
74
+ }).default({});
66
75
 
67
76
  const ${varName}Output = z.object({
68
77
  id: z.string().uuid(),
69
78
  createdAt: z.string().datetime(),
70
79
  updatedAt: z.string().datetime(),
80
+ }).default({
81
+ id: '00000000-0000-0000-0000-000000000000',
82
+ createdAt: '2026-01-01T00:00:00.000Z',
83
+ updatedAt: '2026-01-01T00:00:00.000Z',
71
84
  });
72
85
 
73
86
  const ${varName}ListOutput = z.object({
74
87
  items: z.array(${varName}Output),
75
88
  nextCursor: z.string().optional(),
76
- });
89
+ }).default({ items: [], nextCursor: undefined });
77
90
 
78
91
  export const ${varName}List = defineApi({
79
92
  id: '${id}-list',
@@ -93,3 +106,8 @@ export const ${varName}List = defineApi({
93
106
  });
94
107
  `;
95
108
  }
109
+ /**
110
+ * Example body string used as the event body in the add-api fixture for the
111
+ * simple-crud pattern. Mirrors the default shape of the input schema.
112
+ */
113
+ export const simpleCrudExampleBody = '{}';