@mettlecast/domain-cli 0.2.60 → 0.2.61

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 (89) hide show
  1. package/dist/builder/build-registry.d.ts +1 -1
  2. package/dist/builder/build-registry.js +1 -36
  3. package/dist/builder/build-types.d.ts +1 -1
  4. package/dist/builder/load-module.d.ts +1 -1
  5. package/dist/cli.js +1 -1
  6. package/dist/commands/add-api.js +2 -2
  7. package/dist/commands/add-domain.js +4 -4
  8. package/dist/commands/add-fixture-factory.js +5 -6
  9. package/dist/commands/build-catalog.d.ts +6 -22
  10. package/dist/commands/build-catalog.js +7 -18
  11. package/dist/commands/build.js +2 -1
  12. package/dist/commands/dev.js +1 -1
  13. package/dist/commands/doctor.js +66 -37
  14. package/dist/commands/explain.js +13 -13
  15. package/dist/commands/generate-openapi.d.ts +10 -1
  16. package/dist/commands/generate-openapi.js +19 -33
  17. package/dist/commands/show.d.ts +2 -3
  18. package/dist/commands/show.js +0 -2
  19. package/dist/commands/test.js +0 -1
  20. package/dist/commands/upgrade-backend.js +3 -3
  21. package/dist/commands/validate.js +12 -90
  22. package/dist/server/api-server.d.ts +1 -1
  23. package/dist/server/mount-routes.d.ts +11 -2
  24. package/dist/server/mount-routes.js +20 -8
  25. package/dist/templates/api-skeleton.d.ts +5 -0
  26. package/dist/templates/api-skeleton.js +28 -27
  27. package/dist/templates/claude-md.js +1 -1
  28. package/dist/templates/patterns/api/create-with-event.d.ts +4 -0
  29. package/dist/templates/patterns/api/create-with-event.js +38 -32
  30. package/dist/templates/patterns/api/idempotent-mutation.d.ts +4 -0
  31. package/dist/templates/patterns/api/idempotent-mutation.js +47 -41
  32. package/dist/templates/patterns/api/paginated-list.d.ts +4 -0
  33. package/dist/templates/patterns/api/paginated-list.js +30 -24
  34. package/dist/templates/patterns/api/simple-crud.d.ts +4 -0
  35. package/dist/templates/patterns/api/simple-crud.js +46 -35
  36. package/dist/templates/patterns/api/streaming-list.d.ts +4 -0
  37. package/dist/templates/patterns/api/streaming-list.js +46 -41
  38. package/dist/templates/patterns/api/system-admin.d.ts +4 -0
  39. package/dist/templates/patterns/api/system-admin.js +59 -52
  40. package/dist/templates/patterns/api/webhook-receiver-style.d.ts +4 -0
  41. package/dist/templates/patterns/api/webhook-receiver-style.js +43 -35
  42. package/dist/types.d.ts +100 -0
  43. package/dist/types.js +1 -0
  44. package/dist/utils/file-helpers.d.ts +0 -2
  45. package/dist/utils/file-helpers.js +2 -3
  46. package/dist/utils/scaffold-config.d.ts +6 -0
  47. package/dist/utils/scaffold-config.js +2 -0
  48. package/package.json +1 -1
  49. package/src/__tests__/build-registry.test.ts +43 -20
  50. package/src/__tests__/build-types.test.ts +4 -7
  51. package/src/__tests__/builder/walkDomainDir.test.ts +19 -21
  52. package/src/__tests__/commands/add-api.test.ts +12 -10
  53. package/src/__tests__/commands/add-domain.test.ts +8 -5
  54. package/src/__tests__/commands/create-project.test.ts +5 -5
  55. package/src/__tests__/commands/dev.test.ts +0 -1
  56. package/src/__tests__/mount-routes.test.ts +64 -23
  57. package/src/__tests__/package-freshness.test.ts +1 -21
  58. package/src/__tests__/smoke/scaffold.test.ts +13 -15
  59. package/src/__tests__/validate.test.ts +21 -103
  60. package/src/builder/build-registry.ts +7 -44
  61. package/src/builder/build-types.ts +1 -1
  62. package/src/cli.ts +1 -1
  63. package/src/commands/add-api.ts +2 -2
  64. package/src/commands/add-domain.ts +4 -4
  65. package/src/commands/add-fixture-factory.ts +5 -6
  66. package/src/commands/build-catalog.ts +13 -35
  67. package/src/commands/build.ts +3 -2
  68. package/src/commands/dev.ts +1 -1
  69. package/src/commands/doctor.ts +68 -37
  70. package/src/commands/explain.ts +13 -13
  71. package/src/commands/generate-openapi.ts +30 -52
  72. package/src/commands/show.ts +2 -5
  73. package/src/commands/test.ts +0 -1
  74. package/src/commands/upgrade-backend.ts +3 -3
  75. package/src/commands/validate.ts +11 -96
  76. package/src/server/api-server.ts +1 -1
  77. package/src/server/mount-routes.ts +21 -10
  78. package/src/templates/api-skeleton.ts +29 -28
  79. package/src/templates/claude-md.ts +1 -1
  80. package/src/templates/patterns/api/create-with-event.ts +39 -33
  81. package/src/templates/patterns/api/idempotent-mutation.ts +48 -42
  82. package/src/templates/patterns/api/paginated-list.ts +31 -25
  83. package/src/templates/patterns/api/simple-crud.ts +47 -36
  84. package/src/templates/patterns/api/streaming-list.ts +47 -42
  85. package/src/templates/patterns/api/system-admin.ts +60 -53
  86. package/src/templates/patterns/api/webhook-receiver-style.ts +48 -40
  87. package/src/types.ts +128 -0
  88. package/src/utils/file-helpers.ts +2 -5
  89. package/src/utils/scaffold-config.ts +9 -0
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Idempotent-mutation API pattern template.
3
3
  * Produces: PUT with idempotency key middleware.
4
+ *
5
+ * Action-first: HTTP endpoints are declared with `defineAction` +
6
+ * `exposure: { type: 'api', ... }`. The legacy `defineApi` factory was
7
+ * removed in #4689.
4
8
  */
5
9
  function camelCase(s) {
6
10
  return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
@@ -8,9 +12,9 @@ function camelCase(s) {
8
12
  export function idempotentMutationTemplate(domain, id, tenancy) {
9
13
  const varName = camelCase(id);
10
14
  const tenantPath = tenancy === 'required' ? '/v1/tenants/{tenantId}' : '/v1';
15
+ const authPosture = tenancy === 'system' ? 'service' : 'required';
11
16
  return `import { z } from 'zod';
12
- import { defineApi } from '@mettlecast/domain-runtime';
13
- import { createKyClient, initOtel } from '@mettlecast/domain-runtime';
17
+ import { defineAction, initOtel } from '@mettlecast/domain-runtime';
14
18
  import type { Result, AppError } from '@mettlecast/domain-runtime';
15
19
  import { ok, err, notFound } from '@mettlecast/domain-runtime';
16
20
 
@@ -36,53 +40,55 @@ const ${varName}Output = z.object({
36
40
  idempotencyKey: '00000000-0000-0000-0000-000000000000',
37
41
  });
38
42
 
39
- // ── API definition ───────────────────────────────────────────
43
+ // ── Action definition (API exposure) ─────────────────────────
40
44
 
41
- export const ${varName} = defineApi({
45
+ export const ${varName} = defineAction({
42
46
  id: '${id}',
43
- path: '${tenantPath}/${domain}/${id}',
44
- tenancy: '${tenancy}',
47
+ backendAccess: 'domain',
48
+ exposure: {
49
+ type: 'api',
50
+ path: '${tenantPath}/${domain}/${id}',
51
+ method: 'PUT',
52
+ auth: '${authPosture}',
53
+ tenancy: '${tenancy}',
54
+ },
45
55
  outboundAccess: 'internal',
46
- versions: {
47
- v1: {
48
- status: 'stable',
49
- input: ${varName}Input,
50
- output: ${varName}Output,
51
- handler: async (input, ctx) => {
52
- // ── Check for duplicate (idempotency key = input.id) ──
53
- const existing = await ctx.store.get('IDEMPOTENT#' + input.id);
56
+ idempotent: true,
57
+ input: ${varName}Input,
58
+ output: ${varName}Output,
59
+ handler: async (input, ctx) => {
60
+ // ── Check for duplicate (idempotency key = input.id) ──
61
+ const existing = await ctx.store.get('IDEMPOTENT#' + input.id);
54
62
 
55
- if (existing) {
56
- await ctx.audit.log('${id}.idempotent.duplicate', input.id);
57
- return {
58
- id: input.id,
59
- status: 'already-processed',
60
- idempotencyKey: input.id,
61
- };
62
- }
63
+ if (existing) {
64
+ await ctx.audit.log('${id}.idempotent.duplicate', input.id);
65
+ return {
66
+ id: input.id,
67
+ status: 'already-processed',
68
+ idempotencyKey: input.id,
69
+ };
70
+ }
63
71
 
64
- // ── Apply mutation ────────────────────────────────────
65
- await ctx.store.put(
66
- '${id.toUpperCase()}#' + input.id,
67
- { id: input.id, payload: input.payload, createdAt: new Date().toISOString() },
68
- );
72
+ // ── Apply mutation ────────────────────────────────────
73
+ await ctx.store.put(
74
+ '${id.toUpperCase()}#' + input.id,
75
+ { id: input.id, payload: input.payload, createdAt: new Date().toISOString() },
76
+ );
69
77
 
70
- // ── Record idempotency token with 24h TTL ─────────────
71
- await ctx.store.put(
72
- 'IDEMPOTENT#' + input.id,
73
- { entityId: input.id },
74
- { ttl: Math.floor(Date.now() / 1000) + 86400 },
75
- );
78
+ // ── Record idempotency token with 24h TTL ─────────────
79
+ await ctx.store.put(
80
+ 'IDEMPOTENT#' + input.id,
81
+ { entityId: input.id },
82
+ { ttl: Math.floor(Date.now() / 1000) + 86400 },
83
+ );
76
84
 
77
- await ctx.audit.log('${id}.idempotent.applied', input.id, { tenantId: ctx.tenant.id });
85
+ await ctx.audit.log('${id}.idempotent.applied', input.id, { tenantId: ctx.tenant.id });
78
86
 
79
- return {
80
- id: input.id,
81
- status: 'applied',
82
- idempotencyKey: input.id,
83
- };
84
- },
85
- },
87
+ return {
88
+ id: input.id,
89
+ status: 'applied',
90
+ idempotencyKey: input.id,
91
+ };
86
92
  },
87
93
  });
88
94
  `;
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Paginated-list API pattern template.
3
3
  * Produces: GET with cursor/limit, pagination contract, EMF metrics.
4
+ *
5
+ * Action-first: HTTP endpoints are declared with `defineAction` +
6
+ * `exposure: { type: 'api', ... }`. The legacy `defineApi` factory was
7
+ * removed in #4689.
4
8
  */
5
9
  export declare function paginatedListTemplate(domain: string, id: string, tenancy: string): string;
6
10
  /**
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Paginated-list API pattern template.
3
3
  * Produces: GET with cursor/limit, pagination contract, EMF metrics.
4
+ *
5
+ * Action-first: HTTP endpoints are declared with `defineAction` +
6
+ * `exposure: { type: 'api', ... }`. The legacy `defineApi` factory was
7
+ * removed in #4689.
4
8
  */
5
9
  function camelCase(s) {
6
10
  return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
@@ -8,9 +12,9 @@ function camelCase(s) {
8
12
  export function paginatedListTemplate(domain, id, tenancy) {
9
13
  const varName = camelCase(id);
10
14
  const tenantPath = tenancy === 'required' ? '/v1/tenants/{tenantId}' : '/v1';
15
+ const authPosture = tenancy === 'system' ? 'service' : 'required';
11
16
  return `import { z } from 'zod';
12
- import { defineApi } from '@mettlecast/domain-runtime';
13
- import { createKyClient, initOtel } from '@mettlecast/domain-runtime';
17
+ import { defineAction, initOtel } from '@mettlecast/domain-runtime';
14
18
  import type { Result, AppError } from '@mettlecast/domain-runtime';
15
19
  import { ok, err, notFound } from '@mettlecast/domain-runtime';
16
20
 
@@ -35,34 +39,36 @@ const PageOutput = z.object({
35
39
  total: z.number().int().optional(),
36
40
  }).default({ items: [], nextCursor: undefined, total: undefined });
37
41
 
38
- // ── API definition ───────────────────────────────────────────
42
+ // ── Action definition (API exposure) ─────────────────────────
39
43
 
40
- export const ${varName} = defineApi({
44
+ export const ${varName} = defineAction({
41
45
  id: '${id}',
42
- path: '${tenantPath}/${domain}/${id}',
43
- tenancy: '${tenancy}',
46
+ backendAccess: 'domain',
47
+ exposure: {
48
+ type: 'api',
49
+ path: '${tenantPath}/${domain}/${id}',
50
+ method: 'GET',
51
+ auth: '${authPosture}',
52
+ tenancy: '${tenancy}',
53
+ },
44
54
  outboundAccess: 'internal',
45
- versions: {
46
- v1: {
47
- status: 'stable',
48
- input: PageInput,
49
- output: PageOutput,
50
- handler: async (input, ctx) => {
51
- const { cursor, limit, filter } = input;
55
+ idempotent: true,
56
+ input: PageInput,
57
+ output: PageOutput,
58
+ handler: async (input, ctx) => {
59
+ const { cursor, limit, filter } = input;
52
60
 
53
- await ctx.audit.log('${id}.list', ctx.tenant.id, { cursor, limit });
61
+ await ctx.audit.log('${id}.list', ctx.tenant.id, { cursor, limit });
54
62
 
55
- // ── paginated data fetch ──────────────────────────────
56
- const items: Array<{ id: string }> = [];
57
- const nextCursor = items.length === limit ? btoa(items[items.length - 1].id) : undefined;
63
+ // ── paginated data fetch ──────────────────────────────
64
+ const items: Array<{ id: string }> = [];
65
+ const nextCursor = items.length === limit ? btoa(items[items.length - 1].id) : undefined;
58
66
 
59
- return {
60
- items,
61
- nextCursor,
62
- total: undefined,
63
- };
64
- },
65
- },
67
+ return {
68
+ items,
69
+ nextCursor,
70
+ total: undefined,
71
+ };
66
72
  },
67
73
  });
68
74
  `;
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Simple CRUD API pattern template.
3
3
  * Produces: GET / POST / PUT / DELETE with Zod schemas and audit logging.
4
+ *
5
+ * Action-first: HTTP endpoints are declared with `defineAction` +
6
+ * `exposure: { type: 'api', ... }`. The legacy `defineApi` factory was
7
+ * removed in #4689.
4
8
  */
5
9
  export declare function simpleCrudTemplate(domain: string, id: string, tenancy: string): string;
6
10
  export declare function simpleCrudListTemplate(domain: string, id: string, tenancy: string): string;
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Simple CRUD API pattern template.
3
3
  * Produces: GET / POST / PUT / DELETE with Zod schemas and audit logging.
4
+ *
5
+ * Action-first: HTTP endpoints are declared with `defineAction` +
6
+ * `exposure: { type: 'api', ... }`. The legacy `defineApi` factory was
7
+ * removed in #4689.
4
8
  */
5
9
  function camelCase(s) {
6
10
  return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
@@ -8,9 +12,9 @@ function camelCase(s) {
8
12
  export function simpleCrudTemplate(domain, id, tenancy) {
9
13
  const varName = camelCase(id);
10
14
  const tenantPath = tenancy === 'required' ? '/v1/tenants/{tenantId}' : '/v1';
15
+ const authPosture = tenancy === 'system' ? 'service' : 'required';
11
16
  return `import { z } from 'zod';
12
- import { defineApi } from '@mettlecast/domain-runtime';
13
- import { createKyClient, initOtel } from '@mettlecast/domain-runtime';
17
+ import { defineAction, initOtel } from '@mettlecast/domain-runtime';
14
18
  import type { Result, AppError } from '@mettlecast/domain-runtime';
15
19
  import { ok, err, notFound } from '@mettlecast/domain-runtime';
16
20
 
@@ -37,28 +41,30 @@ const ${varName}ListOutput = z.object({
37
41
  nextCursor: z.string().optional(),
38
42
  }).default({ items: [], nextCursor: undefined });
39
43
 
40
- // ── API definition ───────────────────────────────────────────
44
+ // ── Action definition (API exposure) ─────────────────────────
41
45
 
42
- export const ${varName} = defineApi({
46
+ export const ${varName} = defineAction({
43
47
  id: '${id}',
44
- path: '${tenantPath}/${domain}/${id}',
45
- tenancy: '${tenancy}',
48
+ backendAccess: 'domain',
49
+ exposure: {
50
+ type: 'api',
51
+ path: '${tenantPath}/${domain}/${id}',
52
+ method: 'POST',
53
+ auth: '${authPosture}',
54
+ tenancy: '${tenancy}',
55
+ },
46
56
  outboundAccess: 'internal',
47
- versions: {
48
- v1: {
49
- status: 'stable',
50
- input: ${varName}Input,
51
- output: ${varName}Output,
52
- handler: async (input, ctx) => {
53
- // Create / Read / Update / Delete based on method
54
- await ctx.audit.log('${id}.v1', ctx.tenant.id, { input });
55
- return {
56
- id: input.id ?? crypto.randomUUID(),
57
- createdAt: new Date().toISOString(),
58
- updatedAt: new Date().toISOString(),
59
- };
60
- },
61
- },
57
+ idempotent: false,
58
+ input: ${varName}Input,
59
+ output: ${varName}Output,
60
+ handler: async (input, ctx) => {
61
+ // Create / Read / Update / Delete based on method
62
+ await ctx.audit.log('${id}.v1', ctx.tenant.id, { input });
63
+ return {
64
+ id: input.id ?? crypto.randomUUID(),
65
+ createdAt: new Date().toISOString(),
66
+ updatedAt: new Date().toISOString(),
67
+ };
62
68
  },
63
69
  });
64
70
  `;
@@ -66,8 +72,11 @@ export const ${varName} = defineApi({
66
72
  export function simpleCrudListTemplate(domain, id, tenancy) {
67
73
  const varName = camelCase(id);
68
74
  const tenantPath = tenancy === 'required' ? '/v1/tenants/{tenantId}' : '/v1';
75
+ const authPosture = tenancy === 'system' ? 'service' : 'required';
69
76
  return `import { z } from 'zod';
70
- import { defineApi } from '@mettlecast/domain-runtime';
77
+ import { defineAction, initOtel } from '@mettlecast/domain-runtime';
78
+
79
+ initOtel();
71
80
 
72
81
  const ${varName}ListInput = z.object({
73
82
  cursor: z.string().optional(),
@@ -89,21 +98,23 @@ const ${varName}ListOutput = z.object({
89
98
  nextCursor: z.string().optional(),
90
99
  }).default({ items: [], nextCursor: undefined });
91
100
 
92
- export const ${varName}List = defineApi({
101
+ export const ${varName}List = defineAction({
93
102
  id: '${id}-list',
94
- path: '${tenantPath}/${domain}/${id}-list',
95
- tenancy: '${tenancy}',
103
+ backendAccess: 'domain',
104
+ exposure: {
105
+ type: 'api',
106
+ path: '${tenantPath}/${domain}/${id}-list',
107
+ method: 'GET',
108
+ auth: '${authPosture}',
109
+ tenancy: '${tenancy}',
110
+ },
96
111
  outboundAccess: 'internal',
97
- versions: {
98
- v1: {
99
- status: 'stable',
100
- input: ${varName}ListInput,
101
- output: ${varName}ListOutput,
102
- handler: async (input, ctx) => {
103
- await ctx.audit.log('${id}-list.v1', ctx.tenant.id);
104
- return { items: [], nextCursor: undefined };
105
- },
106
- },
112
+ idempotent: true,
113
+ input: ${varName}ListInput,
114
+ output: ${varName}ListOutput,
115
+ handler: async (input, ctx) => {
116
+ await ctx.audit.log('${id}-list.v1', ctx.tenant.id);
117
+ return { items: [], nextCursor: undefined };
107
118
  },
108
119
  });
109
120
  `;
@@ -19,6 +19,10 @@
19
19
  * - API Gateway HTTP API with 2.0 payload format (not REST API)
20
20
  * - Lambda runtime v20+
21
21
  *
22
+ * Action-first: HTTP endpoints are declared with `defineAction` +
23
+ * `exposure: { type: 'api', ... }`. The legacy `defineApi` factory was
24
+ * removed in #4689.
25
+ *
22
26
  * @param domain - Domain ID in kebab-case
23
27
  * @param apiId - API ID in kebab-case
24
28
  * @param itemName - Singular item name (e.g. 'ticket', 'member')
@@ -19,6 +19,10 @@
19
19
  * - API Gateway HTTP API with 2.0 payload format (not REST API)
20
20
  * - Lambda runtime v20+
21
21
  *
22
+ * Action-first: HTTP endpoints are declared with `defineAction` +
23
+ * `exposure: { type: 'api', ... }`. The legacy `defineApi` factory was
24
+ * removed in #4689.
25
+ *
22
26
  * @param domain - Domain ID in kebab-case
23
27
  * @param apiId - API ID in kebab-case
24
28
  * @param itemName - Singular item name (e.g. 'ticket', 'member')
@@ -26,7 +30,7 @@
26
30
  */
27
31
  export function streamingListPattern(domain, apiId, itemName) {
28
32
  return `import { z } from 'zod';
29
- import { defineApi } from '@mettlecast/domain-runtime';
33
+ import { defineAction } from '@mettlecast/domain-runtime';
30
34
  import type { Result, AppError } from '@mettlecast/domain-runtime';
31
35
 
32
36
  const ${itemName}Schema = z.object({ id: z.string() });
@@ -39,53 +43,54 @@ const outputSchema = z.object({
39
43
  nextCursor: z.string().optional(),
40
44
  }).default({ items: [] });
41
45
 
42
- export const ${apiId.replace(/-/g, '_')} = defineApi({
46
+ export const ${apiId.replace(/-/g, '_')} = defineAction({
43
47
  id: '${apiId}',
44
- path: '/v1/${domain}/${apiId}',
45
- method: 'GET',
46
- tenancy: 'required',
48
+ backendAccess: 'domain',
49
+ exposure: {
50
+ type: 'api',
51
+ path: '/v1/tenants/{tenantId}/${domain}/${apiId}',
52
+ method: 'GET',
53
+ auth: 'required',
54
+ tenancy: 'required',
55
+ },
47
56
  outboundAccess: 'internal',
48
- versions: {
49
- v1: {
50
- status: 'stable',
51
- input: inputSchema,
52
- output: outputSchema,
53
- handler: async (input, ctx): Promise<Result<z.infer<typeof outputSchema>, AppError>> => {
54
- // Streaming response: each item is yielded as a JSON line
55
- // before the full query completes. The runtime handler
56
- // wrapper converts this generator into a chunked response.
57
- //
58
- // GET /v1/${domain}/${apiId}?limit=100
59
- // Transfer-Encoding: chunked
60
- // Content-Type: application/x-ndjson
61
- //
62
- // {"id":"1"}\\n
63
- // {"id":"2"}\\n
64
- // {"id":"3"}\\n
57
+ idempotent: true,
58
+ input: inputSchema,
59
+ output: outputSchema,
60
+ handler: async (input, ctx): Promise<Result<z.infer<typeof outputSchema>, AppError>> => {
61
+ // Streaming response: each item is yielded as a JSON line
62
+ // before the full query completes. The runtime handler
63
+ // wrapper converts this generator into a chunked response.
64
+ //
65
+ // GET /v1/${domain}/${apiId}?limit=100
66
+ // Transfer-Encoding: chunked
67
+ // Content-Type: application/x-ndjson
68
+ //
69
+ // {"id":"1"}\\n
70
+ // {"id":"2"}\\n
71
+ // {"id":"3"}\\n
65
72
 
66
- let nextCursor: string | undefined;
67
- const items: unknown[] = [];
73
+ let nextCursor: string | undefined;
74
+ const items: unknown[] = [];
68
75
 
69
- // Replace this query with your actual data source.
70
- const rows = await ctx.db.query(
71
- '${domain}',
72
- 'SELECT id FROM ${domain}s LIMIT $1 OFFSET $2',
73
- [input.limit + 1, input.cursor ? parseInt(input.cursor, 10) : 0],
74
- );
76
+ // Replace this query with your actual data source.
77
+ const rows = await ctx.db.query(
78
+ '${domain}',
79
+ 'SELECT id FROM ${domain}s LIMIT $1 OFFSET $2',
80
+ [input.limit + 1, input.cursor ? parseInt(input.cursor, 10) : 0],
81
+ );
75
82
 
76
- for (let i = 0; i < Math.min(rows.length, input.limit); i++) {
77
- items.push(rows[i]);
78
- }
83
+ for (let i = 0; i < Math.min(rows.length, input.limit); i++) {
84
+ items.push(rows[i]);
85
+ }
79
86
 
80
- if (rows.length > input.limit) {
81
- nextCursor = String(
82
- (input.cursor ? parseInt(input.cursor, 10) : 0) + input.limit,
83
- );
84
- }
87
+ if (rows.length > input.limit) {
88
+ nextCursor = String(
89
+ (input.cursor ? parseInt(input.cursor, 10) : 0) + input.limit,
90
+ );
91
+ }
85
92
 
86
- return { ok: true, value: { items, nextCursor } };
87
- },
88
- },
93
+ return { ok: true, value: { items, nextCursor } };
89
94
  },
90
95
  });
91
96
  `;
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * System-admin API pattern template.
3
3
  * Produces: GET/POST with tenancy: 'system', no tenant context.
4
+ *
5
+ * Action-first: HTTP endpoints are declared with `defineAction` +
6
+ * `exposure: { type: 'api', ... }`. The legacy `defineApi` factory was
7
+ * removed in #4689.
4
8
  */
5
9
  export declare function systemAdminTemplate(domain: string, id: string, tenancy: string): string;
6
10
  /**
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * System-admin API pattern template.
3
3
  * Produces: GET/POST with tenancy: 'system', no tenant context.
4
+ *
5
+ * Action-first: HTTP endpoints are declared with `defineAction` +
6
+ * `exposure: { type: 'api', ... }`. The legacy `defineApi` factory was
7
+ * removed in #4689.
4
8
  */
5
9
  function camelCase(s) {
6
10
  return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
@@ -8,8 +12,7 @@ function camelCase(s) {
8
12
  export function systemAdminTemplate(domain, id, tenancy) {
9
13
  const varName = camelCase(id);
10
14
  return `import { z } from 'zod';
11
- import { defineApi } from '@mettlecast/domain-runtime';
12
- import { createKyClient, initOtel } from '@mettlecast/domain-runtime';
15
+ import { defineAction, initOtel } from '@mettlecast/domain-runtime';
13
16
  import type { Result, AppError } from '@mettlecast/domain-runtime';
14
17
  import { ok, err, notFound } from '@mettlecast/domain-runtime';
15
18
 
@@ -27,7 +30,7 @@ const ${varName}Output = z.object({
27
30
  data: z.record(z.unknown()).optional(),
28
31
  }).default({ ok: true });
29
32
 
30
- // ── System health input ──────────────────────────────────────
33
+ // ── System health output ─────────────────────────────────────
31
34
 
32
35
  const SystemHealthOutput = z.object({
33
36
  status: z.enum(['healthy', 'degraded', 'down']),
@@ -38,64 +41,68 @@ const SystemHealthOutput = z.object({
38
41
  })),
39
42
  }).default({ status: 'healthy', uptime: 0, domains: [] });
40
43
 
41
- // ── API definitions ──────────────────────────────────────────
44
+ // ── Action definitions (API exposure) ────────────────────────
42
45
 
43
- export const ${varName} = defineApi({
46
+ export const ${varName} = defineAction({
44
47
  id: '${id}',
45
- path: '/system/${domain}/${id}',
46
- tenancy: '${tenancy}',
48
+ backendAccess: 'platform',
49
+ exposure: {
50
+ type: 'api',
51
+ path: '/system/${domain}/${id}',
52
+ method: 'POST',
53
+ auth: 'service',
54
+ tenancy: '${tenancy}',
55
+ },
47
56
  outboundAccess: 'internal',
48
- versions: {
49
- v1: {
50
- status: 'stable',
51
- input: ${varName}Input,
52
- output: ${varName}Output,
53
- handler: async (input, ctx) => {
54
- await ctx.audit.log('${id}.system', 'system', { command: input.command });
57
+ idempotent: false,
58
+ input: ${varName}Input,
59
+ output: ${varName}Output,
60
+ handler: async (input, ctx) => {
61
+ await ctx.audit.log('${id}.system', 'system', { command: input.command });
55
62
 
56
- switch (input.command) {
57
- case 'status':
58
- return {
59
- ok: true,
60
- data: { status: 'healthy', domains: [] },
61
- };
62
- case 'metrics':
63
- return {
64
- ok: true,
65
- data: {
66
- requestCount: 0,
67
- errorCount: 0,
68
- p99LatencyMs: 0,
69
- },
70
- };
71
- case 'health':
72
- return { ok: true, data: { status: 'healthy' } };
73
- default:
74
- return { ok: false };
75
- }
76
- },
77
- },
63
+ switch (input.command) {
64
+ case 'status':
65
+ return {
66
+ ok: true,
67
+ data: { status: 'healthy', domains: [] },
68
+ };
69
+ case 'metrics':
70
+ return {
71
+ ok: true,
72
+ data: {
73
+ requestCount: 0,
74
+ errorCount: 0,
75
+ p99LatencyMs: 0,
76
+ },
77
+ };
78
+ case 'health':
79
+ return { ok: true, data: { status: 'healthy' } };
80
+ default:
81
+ return { ok: false };
82
+ }
78
83
  },
79
84
  });
80
85
 
81
- export const ${varName}Health = defineApi({
86
+ export const ${varName}Health = defineAction({
82
87
  id: '${id}-health',
83
- path: '/system/${domain}/${id}-health',
84
- tenancy: '${tenancy}',
88
+ backendAccess: 'platform',
89
+ exposure: {
90
+ type: 'api',
91
+ path: '/system/${domain}/${id}-health',
92
+ method: 'GET',
93
+ auth: 'service',
94
+ tenancy: '${tenancy}',
95
+ },
85
96
  outboundAccess: 'internal',
86
- versions: {
87
- v1: {
88
- status: 'stable',
89
- input: z.object({}).default({}),
90
- output: SystemHealthOutput,
91
- handler: async (_input, _ctx) => {
92
- return {
93
- status: 'healthy',
94
- uptime: process.uptime(),
95
- domains: [],
96
- };
97
- },
98
- },
97
+ idempotent: true,
98
+ input: z.object({}).default({}),
99
+ output: SystemHealthOutput,
100
+ handler: async (_input, _ctx) => {
101
+ return {
102
+ status: 'healthy',
103
+ uptime: process.uptime(),
104
+ domains: [],
105
+ };
99
106
  },
100
107
  });
101
108
  `;
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Webhook-receiver-style API pattern template.
3
3
  * Produces: POST with signature verification, provider enum.
4
+ *
5
+ * Action-first: HTTP endpoints are declared with `defineAction` +
6
+ * `exposure: { type: 'api', ... }`. The legacy `defineApi` factory was
7
+ * removed in #4689.
4
8
  */
5
9
  export declare function webhookReceiverStyleTemplate(domain: string, id: string, tenancy: string): string;
6
10
  /**