@mettlecast/domain-cli 0.2.59 → 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 (119) hide show
  1. package/dist/builder/build-registry.d.ts +1 -1
  2. package/dist/builder/build-registry.js +129 -49
  3. package/dist/builder/build-types.d.ts +1 -1
  4. package/dist/builder/load-module.d.ts +1 -1
  5. package/dist/builder/load-module.js +3 -3
  6. package/dist/cli.js +3 -3
  7. package/dist/commands/add-api.js +2 -2
  8. package/dist/commands/add-domain.js +4 -4
  9. package/dist/commands/add-fixture-factory.js +5 -6
  10. package/dist/commands/build-catalog.d.ts +8 -24
  11. package/dist/commands/build-catalog.js +12 -23
  12. package/dist/commands/build-flows.js +1 -1
  13. package/dist/commands/build.js +7 -6
  14. package/dist/commands/check-hashes.js +2 -2
  15. package/dist/commands/create-project.js +1 -1
  16. package/dist/commands/dev.js +1 -1
  17. package/dist/commands/doctor.d.ts +5 -7
  18. package/dist/commands/doctor.js +110 -202
  19. package/dist/commands/explain.js +13 -13
  20. package/dist/commands/generate-openapi.d.ts +10 -1
  21. package/dist/commands/generate-openapi.js +19 -33
  22. package/dist/commands/power-tune.js +2 -2
  23. package/dist/commands/show-dns.d.ts +1 -1
  24. package/dist/commands/show-dns.js +5 -5
  25. package/dist/commands/show.d.ts +2 -3
  26. package/dist/commands/show.js +0 -2
  27. package/dist/commands/test.js +0 -1
  28. package/dist/commands/upgrade-backend.js +3 -3
  29. package/dist/commands/upgrade.js +17 -11
  30. package/dist/commands/validate.js +144 -39
  31. package/dist/server/api-server.d.ts +1 -1
  32. package/dist/server/mount-routes.d.ts +11 -2
  33. package/dist/server/mount-routes.js +20 -8
  34. package/dist/templates/api-skeleton.d.ts +5 -0
  35. package/dist/templates/api-skeleton.js +28 -27
  36. package/dist/templates/claude-md.js +1 -1
  37. package/dist/templates/patterns/api/create-with-event.d.ts +4 -0
  38. package/dist/templates/patterns/api/create-with-event.js +38 -32
  39. package/dist/templates/patterns/api/idempotent-mutation.d.ts +4 -0
  40. package/dist/templates/patterns/api/idempotent-mutation.js +47 -41
  41. package/dist/templates/patterns/api/paginated-list.d.ts +4 -0
  42. package/dist/templates/patterns/api/paginated-list.js +30 -24
  43. package/dist/templates/patterns/api/simple-crud.d.ts +4 -0
  44. package/dist/templates/patterns/api/simple-crud.js +46 -35
  45. package/dist/templates/patterns/api/streaming-list.d.ts +4 -0
  46. package/dist/templates/patterns/api/streaming-list.js +46 -41
  47. package/dist/templates/patterns/api/system-admin.d.ts +4 -0
  48. package/dist/templates/patterns/api/system-admin.js +59 -52
  49. package/dist/templates/patterns/api/webhook-receiver-style.d.ts +4 -0
  50. package/dist/templates/patterns/api/webhook-receiver-style.js +43 -35
  51. package/dist/types.d.ts +100 -0
  52. package/dist/types.js +1 -0
  53. package/dist/utils/file-helpers.d.ts +0 -2
  54. package/dist/utils/file-helpers.js +2 -3
  55. package/dist/utils/header-inject.js +2 -2
  56. package/dist/utils/install-file.d.ts +1 -1
  57. package/dist/utils/install-file.js +1 -1
  58. package/dist/utils/manifest.js +1 -2
  59. package/dist/utils/scaffold-config.d.ts +8 -2
  60. package/dist/utils/scaffold-config.js +3 -1
  61. package/package.json +1 -1
  62. package/src/__tests__/build-registry.test.ts +43 -20
  63. package/src/__tests__/build-types.test.ts +4 -7
  64. package/src/__tests__/builder/walkDomainDir.test.ts +19 -21
  65. package/src/__tests__/commands/add-api.test.ts +12 -10
  66. package/src/__tests__/commands/add-domain.test.ts +8 -5
  67. package/src/__tests__/commands/check-hashes.test.ts +9 -9
  68. package/src/__tests__/commands/create-project.test.ts +5 -5
  69. package/src/__tests__/commands/dev.test.ts +0 -1
  70. package/src/__tests__/commands/upgrade.test.ts +7 -7
  71. package/src/__tests__/doctor.test.ts +60 -67
  72. package/src/__tests__/mount-routes.test.ts +64 -23
  73. package/src/__tests__/package-freshness.test.ts +94 -0
  74. package/src/__tests__/scaffold-src/part-a-layout.test.ts +10 -10
  75. package/src/__tests__/scripts/package-scaffold.test.ts +5 -5
  76. package/src/__tests__/smoke/scaffold.test.ts +13 -15
  77. package/src/__tests__/utils/install-file.test.ts +2 -2
  78. package/src/__tests__/utils/manifest.test.ts +2 -2
  79. package/src/__tests__/validate.test.ts +570 -1
  80. package/src/builder/build-registry.ts +154 -59
  81. package/src/builder/build-types.ts +1 -1
  82. package/src/builder/load-module.ts +3 -3
  83. package/src/cli.ts +4 -4
  84. package/src/commands/add-api.ts +2 -2
  85. package/src/commands/add-domain.ts +4 -4
  86. package/src/commands/add-fixture-factory.ts +5 -6
  87. package/src/commands/build-catalog.ts +18 -40
  88. package/src/commands/build-flows.ts +1 -1
  89. package/src/commands/build.ts +8 -7
  90. package/src/commands/check-hashes.ts +2 -2
  91. package/src/commands/create-project.ts +1 -1
  92. package/src/commands/dev.ts +1 -1
  93. package/src/commands/doctor.ts +120 -218
  94. package/src/commands/explain.ts +13 -13
  95. package/src/commands/generate-openapi.ts +30 -52
  96. package/src/commands/power-tune.ts +2 -2
  97. package/src/commands/show-dns.ts +5 -5
  98. package/src/commands/show.ts +2 -5
  99. package/src/commands/test.ts +0 -1
  100. package/src/commands/upgrade-backend.ts +3 -3
  101. package/src/commands/upgrade.ts +16 -10
  102. package/src/commands/validate.ts +180 -40
  103. package/src/server/api-server.ts +1 -1
  104. package/src/server/mount-routes.ts +21 -10
  105. package/src/templates/api-skeleton.ts +29 -28
  106. package/src/templates/claude-md.ts +1 -1
  107. package/src/templates/patterns/api/create-with-event.ts +39 -33
  108. package/src/templates/patterns/api/idempotent-mutation.ts +48 -42
  109. package/src/templates/patterns/api/paginated-list.ts +31 -25
  110. package/src/templates/patterns/api/simple-crud.ts +47 -36
  111. package/src/templates/patterns/api/streaming-list.ts +47 -42
  112. package/src/templates/patterns/api/system-admin.ts +60 -53
  113. package/src/templates/patterns/api/webhook-receiver-style.ts +48 -40
  114. package/src/types.ts +128 -0
  115. package/src/utils/file-helpers.ts +2 -5
  116. package/src/utils/header-inject.ts +2 -2
  117. package/src/utils/install-file.ts +1 -1
  118. package/src/utils/manifest.ts +1 -2
  119. package/src/utils/scaffold-config.ts +12 -3
@@ -24,7 +24,6 @@ describe('buildRegistry', () => {
24
24
  it('throws if domain.config.ts is missing', async () => {
25
25
  mockWalkDomainDir.mockResolvedValue({
26
26
  domain: undefined,
27
- apis: [],
28
27
  webhooks: [],
29
28
  subscribers: [],
30
29
  actions: [],
@@ -40,7 +39,6 @@ describe('buildRegistry', () => {
40
39
  it('throws if domain.config.ts has no domain export', async () => {
41
40
  mockWalkDomainDir.mockResolvedValue({
42
41
  domain: '/fake/domains/billing/domain.config.ts',
43
- apis: [],
44
42
  webhooks: [],
45
43
  subscribers: [],
46
44
  actions: [],
@@ -54,13 +52,15 @@ describe('buildRegistry', () => {
54
52
  await expect(buildRegistry(DOMAIN_ROOT)).rejects.toThrow("no 'domain' export found");
55
53
  });
56
54
 
57
- it('builds a registry with domain + one api', async () => {
55
+ it('builds a registry with domain + one API-exposed action', async () => {
56
+ // Issue #4689: defineApi was removed; the canonical HTTP endpoint
57
+ // surface is `defineAction({ exposure: { type: 'api', ... } })`.
58
+ // The action is recorded under `registry.actions` with API exposure metadata.
58
59
  mockWalkDomainDir.mockResolvedValue({
59
60
  domain: '/fake/domains/billing/domain.config.ts',
60
- apis: ['/fake/domains/billing/api/get-invoice.ts'],
61
61
  webhooks: [],
62
62
  subscribers: [],
63
- actions: [],
63
+ actions: ['/fake/domains/billing/actions/get-invoice.ts'],
64
64
  schedules: [],
65
65
  jobs: [],
66
66
  integrations: [],
@@ -72,7 +72,20 @@ describe('buildRegistry', () => {
72
72
  { _kind: 'domain', _exportName: 'default', id: 'billing', name: 'Billing', tenancy: 'required' },
73
73
  ])
74
74
  .mockResolvedValueOnce([
75
- { _kind: 'api', _exportName: 'getInvoice', id: 'get-invoice', path: '/invoices/:id', method: 'GET', auth: { type: 'jwt' } },
75
+ {
76
+ _kind: 'action',
77
+ _exportName: 'getInvoice',
78
+ id: 'get-invoice',
79
+ backendAccess: 'domain',
80
+ exposure: {
81
+ type: 'api',
82
+ path: '/v1/tenants/{tenantId}/billing/invoices/{id}',
83
+ method: 'GET',
84
+ auth: 'required',
85
+ tenancy: 'required',
86
+ },
87
+ idempotent: true,
88
+ },
76
89
  ]);
77
90
 
78
91
  const { registry, warnings } = await buildRegistry(DOMAIN_ROOT);
@@ -80,21 +93,18 @@ describe('buildRegistry', () => {
80
93
  expect(warnings).toHaveLength(0);
81
94
  expect(registry.schemaVersion).toBe('1');
82
95
  expect(registry.domain.id).toBe('billing');
83
- expect(registry.apis).toHaveLength(1);
84
- expect(registry.apis[0]!.id).toBe('get-invoice');
85
- expect(registry.apis[0]!.path).toBe('/invoices/:id');
86
- expect(registry.apis[0]!.authType).toBe('jwt');
87
- expect(registry.apis[0]!.handlerFile.replaceAll('\\', '/')).toBe('api/get-invoice.ts');
88
- expect(registry.apis[0]!.outboundAccess).toBe('internal');
96
+ expect(registry.actions).toHaveLength(1);
97
+ expect(registry.actions[0]!.id).toBe('get-invoice');
98
+ expect(registry.actions[0]!.backendAccess).toBe('domain');
99
+ expect(registry.actions[0]!.exposure.type).toBe('api');
89
100
  });
90
101
 
91
- it('preserves explicit outboundAccess on primitives', async () => {
102
+ it('preserves explicit outboundAccess on actions', async () => {
92
103
  mockWalkDomainDir.mockResolvedValue({
93
104
  domain: '/fake/domains/billing/domain.config.ts',
94
- apis: ['/fake/domains/billing/api/sync-invoice.ts'],
95
105
  webhooks: [],
96
106
  subscribers: [],
97
- actions: [],
107
+ actions: ['/fake/domains/billing/actions/sync-invoice.ts'],
98
108
  schedules: [],
99
109
  jobs: [],
100
110
  integrations: [],
@@ -106,21 +116,34 @@ describe('buildRegistry', () => {
106
116
  { _kind: 'domain', _exportName: 'default', id: 'billing', name: 'Billing', tenancy: 'required' },
107
117
  ])
108
118
  .mockResolvedValueOnce([
109
- { _kind: 'api', _exportName: 'syncInvoice', id: 'sync-invoice', path: '/invoices/sync', method: 'POST', auth: { type: 'jwt' }, outboundAccess: 'internet' },
119
+ {
120
+ _kind: 'action',
121
+ _exportName: 'syncInvoice',
122
+ id: 'sync-invoice',
123
+ backendAccess: 'domain',
124
+ exposure: {
125
+ type: 'api',
126
+ path: '/v1/tenants/{tenantId}/billing/invoices/sync',
127
+ method: 'POST',
128
+ auth: 'required',
129
+ tenancy: 'required',
130
+ },
131
+ outboundAccess: 'internet',
132
+ idempotent: false,
133
+ },
110
134
  ]);
111
135
 
112
136
  const { registry } = await buildRegistry(DOMAIN_ROOT);
113
137
 
114
- expect(registry.apis[0]!.outboundAccess).toBe('internet');
138
+ expect(registry.actions[0]!.outboundAccess).toBe('internet');
115
139
  });
116
140
 
117
141
  it('collects warnings when a file fails to load', async () => {
118
142
  mockWalkDomainDir.mockResolvedValue({
119
143
  domain: '/fake/domains/billing/domain.config.ts',
120
- apis: ['/fake/domains/billing/api/broken.ts'],
121
144
  webhooks: [],
122
145
  subscribers: [],
123
- actions: [],
146
+ actions: ['/fake/domains/billing/actions/broken.ts'],
124
147
  schedules: [],
125
148
  jobs: [],
126
149
  integrations: [],
@@ -137,6 +160,6 @@ describe('buildRegistry', () => {
137
160
 
138
161
  expect(warnings).toHaveLength(1);
139
162
  expect(warnings[0]).toContain('tsx error');
140
- expect(registry.apis).toHaveLength(0);
163
+ expect(registry.actions).toHaveLength(0);
141
164
  });
142
165
  });
@@ -8,7 +8,6 @@ describe('buildActionsTypes', () => {
8
8
  schemaVersion: '1',
9
9
  domainRoot: '/test/auth',
10
10
  domain: { id: 'auth', kind: 'domain', name: 'Auth', tenancy: 'required' },
11
- apis: [],
12
11
  webhooks: [],
13
12
  subscribers: [],
14
13
  schedules: [],
@@ -18,7 +17,8 @@ describe('buildActionsTypes', () => {
18
17
  id: 'verify-token',
19
18
  kind: 'action',
20
19
  handlerFile: 'verify-token.ts',
21
- visibility: 'domain',
20
+ backendAccess: 'domain',
21
+ exposure: { type: 'internal' },
22
22
  idempotent: false,
23
23
  },
24
24
  ],
@@ -39,13 +39,12 @@ describe('buildActionsTypes', () => {
39
39
  schemaVersion: '1',
40
40
  domainRoot: '/test/auth',
41
41
  domain: { id: 'auth', kind: 'domain', name: 'Auth', tenancy: 'required' },
42
- apis: [],
43
42
  webhooks: [],
44
43
  subscribers: [],
45
44
  schedules: [],
46
45
  jobs: [],
47
46
  actions: [
48
- { id: 'verify', kind: 'action', handlerFile: 'verify.ts', visibility: 'workspace', idempotent: true },
47
+ { id: 'verify', kind: 'action', handlerFile: 'verify.ts', backendAccess: 'platform', exposure: { type: 'internal' }, idempotent: true },
49
48
  ],
50
49
  integrations: [],
51
50
  events: [],
@@ -55,13 +54,12 @@ describe('buildActionsTypes', () => {
55
54
  schemaVersion: '1',
56
55
  domainRoot: '/test/payments',
57
56
  domain: { id: 'payments', kind: 'domain', name: 'Payments', tenancy: 'required' },
58
- apis: [],
59
57
  webhooks: [],
60
58
  subscribers: [],
61
59
  schedules: [],
62
60
  jobs: [],
63
61
  actions: [
64
- { id: 'charge', kind: 'action', handlerFile: 'charge.ts', visibility: 'domain', idempotent: false },
62
+ { id: 'charge', kind: 'action', handlerFile: 'charge.ts', backendAccess: 'domain', exposure: { type: 'internal' }, idempotent: false },
65
63
  ],
66
64
  integrations: [],
67
65
  events: [],
@@ -78,7 +76,6 @@ describe('buildActionsTypes', () => {
78
76
  schemaVersion: '1',
79
77
  domainRoot: '/test',
80
78
  domain: { id: 'test', kind: 'domain', name: 'Test', tenancy: 'none' },
81
- apis: [],
82
79
  webhooks: [],
83
80
  subscribers: [],
84
81
  schedules: [],
@@ -19,59 +19,59 @@ describe('walkDomainDir recursive walk', () => {
19
19
  }
20
20
  });
21
21
 
22
- it('should find .ts files at root of api directory', async () => {
23
- const apiDir = join(tmpDir, 'api');
24
- await mkdir(apiDir, { recursive: true });
25
- await writeFile(join(apiDir, 'simple.ts'), 'export const api = {}');
22
+ it('should find .ts files at root of actions directory', async () => {
23
+ const actionDir = join(tmpDir, 'actions');
24
+ await mkdir(actionDir, { recursive: true });
25
+ await writeFile(join(actionDir, 'simple.ts'), 'export const action = {}');
26
26
 
27
27
  const paths = await walkDomainDir(tmpDir);
28
28
 
29
- expect(paths.apis).toContain(join(apiDir, 'simple.ts'));
29
+ expect(paths.actions).toContain(join(actionDir, 'simple.ts'));
30
30
  });
31
31
 
32
- it('should recursively find .ts files in nested api directories', async () => {
33
- const nestedDir = join(tmpDir, 'api', 'users', 'v1');
32
+ it('should recursively find .ts files in nested action directories', async () => {
33
+ const nestedDir = join(tmpDir, 'actions', 'users', 'v1');
34
34
  await mkdir(nestedDir, { recursive: true });
35
- await writeFile(join(nestedDir, 'get.ts'), 'export const api = {}');
35
+ await writeFile(join(nestedDir, 'get.ts'), 'export const action = {}');
36
36
 
37
37
  const paths = await walkDomainDir(tmpDir);
38
38
 
39
- expect(paths.apis).toContain(join(nestedDir, 'get.ts'));
39
+ expect(paths.actions).toContain(join(nestedDir, 'get.ts'));
40
40
  });
41
41
 
42
42
  it('should skip __tests__ directories', async () => {
43
- const testDir = join(tmpDir, 'api', '__tests__');
43
+ const testDir = join(tmpDir, 'actions', '__tests__');
44
44
  await mkdir(testDir, { recursive: true });
45
45
  await writeFile(join(testDir, 'test.ts'), 'export const test = {}');
46
46
 
47
47
  const paths = await walkDomainDir(tmpDir);
48
48
 
49
- expect(paths.apis).not.toContain(join(testDir, 'test.ts'));
49
+ expect(paths.actions).not.toContain(join(testDir, 'test.ts'));
50
50
  });
51
51
 
52
52
  it('should skip dist directories', async () => {
53
- const distDir = join(tmpDir, 'api', 'dist');
53
+ const distDir = join(tmpDir, 'actions', 'dist');
54
54
  await mkdir(distDir, { recursive: true });
55
55
  await writeFile(join(distDir, 'bundle.ts'), 'export const bundle = {}');
56
56
 
57
57
  const paths = await walkDomainDir(tmpDir);
58
58
 
59
- expect(paths.apis).not.toContain(join(distDir, 'bundle.ts'));
59
+ expect(paths.actions).not.toContain(join(distDir, 'bundle.ts'));
60
60
  });
61
61
 
62
62
  it('should skip node_modules directories', async () => {
63
- const nodeModulesDir = join(tmpDir, 'api', 'node_modules');
63
+ const nodeModulesDir = join(tmpDir, 'actions', 'node_modules');
64
64
  await mkdir(nodeModulesDir, { recursive: true });
65
65
  await writeFile(join(nodeModulesDir, 'dep.ts'), 'export const dep = {}');
66
66
 
67
67
  const paths = await walkDomainDir(tmpDir);
68
68
 
69
- expect(paths.apis).not.toContain(join(nodeModulesDir, 'dep.ts'));
69
+ expect(paths.actions).not.toContain(join(nodeModulesDir, 'dep.ts'));
70
70
  });
71
71
 
72
72
  it('should discover files in all domain primitive directories', async () => {
73
73
  // Create files in all conventional directories
74
- const dirs = ['api', 'webhooks', 'subscribers', 'actions', 'schedules', 'jobs', 'integrations'];
74
+ const dirs = ['webhooks', 'subscribers', 'actions', 'schedules', 'jobs', 'integrations'];
75
75
 
76
76
  for (const dir of dirs) {
77
77
  const dirPath = join(tmpDir, dir, 'nested', 'deep');
@@ -82,7 +82,6 @@ describe('walkDomainDir recursive walk', () => {
82
82
  const paths = await walkDomainDir(tmpDir);
83
83
 
84
84
  // Verify files are found in correct slots
85
- expect(paths.apis.some(p => p.includes('api-handler.ts'))).toBe(true);
86
85
  expect(paths.webhooks.some(p => p.includes('webhooks-handler.ts'))).toBe(true);
87
86
  expect(paths.subscribers.some(p => p.includes('subscribers-handler.ts'))).toBe(true);
88
87
  expect(paths.actions.some(p => p.includes('actions-handler.ts'))).toBe(true);
@@ -112,7 +111,6 @@ describe('walkDomainDir recursive walk', () => {
112
111
  it('should return empty arrays for missing directories', async () => {
113
112
  const paths = await walkDomainDir(tmpDir);
114
113
 
115
- expect(paths.apis).toEqual([]);
116
114
  expect(paths.webhooks).toEqual([]);
117
115
  expect(paths.subscribers).toEqual([]);
118
116
  expect(paths.actions).toEqual([]);
@@ -129,12 +127,12 @@ describe('walkDomainDir recursive walk', () => {
129
127
  });
130
128
 
131
129
  it('should handle deeply nested files', async () => {
132
- const deepPath = join(tmpDir, 'api', 'v1', 'users', 'admin', 'permissions');
130
+ const deepPath = join(tmpDir, 'actions', 'v1', 'users', 'admin', 'permissions');
133
131
  await mkdir(deepPath, { recursive: true });
134
- await writeFile(join(deepPath, 'check.ts'), 'export const api = {}');
132
+ await writeFile(join(deepPath, 'check.ts'), 'export const action = {}');
135
133
 
136
134
  const paths = await walkDomainDir(tmpDir);
137
135
 
138
- expect(paths.apis.some(p => p.includes('check.ts'))).toBe(true);
136
+ expect(paths.actions.some(p => p.includes('check.ts'))).toBe(true);
139
137
  });
140
138
  });
@@ -11,7 +11,7 @@ describe('add-api command', () => {
11
11
  beforeEach(async () => {
12
12
  tmpDir = await mkdtemp(join(tmpdir(), 'tib-add-api-test-'));
13
13
  domainDir = join(tmpDir, 'demo');
14
- await mkdir(join(domainDir, 'api'), { recursive: true });
14
+ await mkdir(join(domainDir, 'actions'), { recursive: true });
15
15
  await writeFile(
16
16
  join(domainDir, 'domain.config.ts'),
17
17
  `export default { id: 'demo', tenancy: 'required' };\n`,
@@ -34,8 +34,8 @@ describe('add-api command', () => {
34
34
  domainsDir: tmpDir,
35
35
  });
36
36
 
37
- const apiFile = join(domainDir, 'api', 'ping-thing.ts');
38
- const fixtureFile = join(domainDir, 'api', '__tests__', 'ping-thing.fixture.json');
37
+ const apiFile = join(domainDir, 'actions', 'ping-thing.ts');
38
+ const fixtureFile = join(domainDir, 'actions', '__tests__', 'ping-thing.fixture.json');
39
39
 
40
40
  const apiContent = await readFile(apiFile, 'utf8');
41
41
  const fixtureContent = await readFile(fixtureFile, 'utf8');
@@ -45,9 +45,11 @@ describe('add-api command', () => {
45
45
  const parsed = JSON.parse(fixtureContent) as { event: { body: string } };
46
46
  expect(parsed.event.body).toBe('{}');
47
47
 
48
- // The API file must contain .default() on input and output.
48
+ // The API file must contain .default() on input. Output uses a variable.
49
49
  expect(apiContent).toMatch(/input:\s*z\.object\(\{\}\)\.default\(\{\}\)/);
50
- expect(apiContent).toMatch(/output:\s*z\.object\(\{\s*ok:\s*z\.boolean\(\)\s*\}\)\.default\(\{\s*ok:\s*true\s*\}\)/);
50
+ // The output schema is assigned to a variable then referenced via `output: outputSchema`.
51
+ expect(apiContent).toMatch(/output:\s*outputSchema/);
52
+ expect(apiContent).toMatch(/const outputSchema = z\.object\(\{\s*ok:\s*z\.boolean\(\)\s*\}\)\.default\(\{\s*ok:\s*true\s*\}\)/);
51
53
  });
52
54
 
53
55
  it('scaffolds a simple-crud pattern API with .default() on schemas', async () => {
@@ -59,8 +61,8 @@ describe('add-api command', () => {
59
61
  domainsDir: tmpDir,
60
62
  });
61
63
 
62
- const apiFile = join(domainDir, 'api', 'widget.ts');
63
- const fixtureFile = join(domainDir, 'api', '__tests__', 'widget.fixture.json');
64
+ const apiFile = join(domainDir, 'actions', 'widget.ts');
65
+ const fixtureFile = join(domainDir, 'actions', '__tests__', 'widget.fixture.json');
64
66
 
65
67
  const apiContent = await readFile(apiFile, 'utf8');
66
68
  const fixtureContent = await readFile(fixtureFile, 'utf8');
@@ -83,8 +85,8 @@ describe('add-api command', () => {
83
85
  domainsDir: tmpDir,
84
86
  });
85
87
 
86
- const apiFile = join(domainDir, 'api', 'order.ts');
87
- const fixtureFile = join(domainDir, 'api', '__tests__', 'order.fixture.json');
88
+ const apiFile = join(domainDir, 'actions', 'order.ts');
89
+ const fixtureFile = join(domainDir, 'actions', '__tests__', 'order.fixture.json');
88
90
 
89
91
  const apiContent = await readFile(apiFile, 'utf8');
90
92
  const fixtureContent = await readFile(fixtureFile, 'utf8');
@@ -106,7 +108,7 @@ describe('add-api command', () => {
106
108
  domainsDir: tmpDir,
107
109
  });
108
110
 
109
- const apiFile = join(domainDir, 'api', 'console.ts');
111
+ const apiFile = join(domainDir, 'actions', 'console.ts');
110
112
  const apiContent = await readFile(apiFile, 'utf8');
111
113
 
112
114
  // First API: input defaults to command: 'status'; output defaults to { ok: true }.
@@ -33,7 +33,7 @@ describe('add-domain command', () => {
33
33
 
34
34
  // Verify directory structure
35
35
  const domainDir = join(tmpDir, 'test-domain');
36
- await stat(join(domainDir, 'api'));
36
+ await stat(join(domainDir, 'actions'));
37
37
  await stat(join(domainDir, 'subscribers'));
38
38
  await stat(join(domainDir, 'publishes'));
39
39
 
@@ -42,9 +42,10 @@ describe('add-domain command', () => {
42
42
  expect(configContent).toContain(`id: 'test-domain'`);
43
43
  expect(configContent).toContain(`tenancy: 'required'`);
44
44
 
45
- const apiContent = await readFile(join(domainDir, 'api', 'example.ts'), 'utf8');
45
+ const apiContent = await readFile(join(domainDir, 'actions', 'example.ts'), 'utf8');
46
46
  expect(apiContent).toContain(`id: 'example'`);
47
- expect(apiContent).toContain('defineApi');
47
+ expect(apiContent).toContain('defineAction');
48
+ expect(apiContent).toContain("type: 'api'");
48
49
 
49
50
  const eventsContent = await readFile(join(domainDir, 'publishes', 'events.ts'), 'utf8');
50
51
  expect(eventsContent).toContain(`id: 'test-domain.example'`);
@@ -154,10 +155,12 @@ describe('add-domain command', () => {
154
155
  const configContent = await readFile(join(tmpDir, 'system-domain', 'domain.config.ts'), 'utf8');
155
156
  expect(configContent).toContain(`tenancy: 'system'`);
156
157
 
157
- const apiContent = await readFile(join(tmpDir, 'system-domain', 'api', 'example.ts'), 'utf8');
158
+ const apiContent = await readFile(join(tmpDir, 'system-domain', 'actions', 'example.ts'), 'utf8');
158
159
  expect(apiContent).toContain(`tenancy: 'system'`);
159
160
  // For 'system' tenancy, path should not include /v1/tenants/{tenantId}
160
161
  expect(apiContent).toContain(`path: '/v1/system-domain/example'`);
162
+ // System tenancy should default to service-to-service auth.
163
+ expect(apiContent).toContain(`auth: 'service'`);
161
164
  });
162
165
 
163
166
  it('should use correct path structure for required tenancy', async () => {
@@ -167,7 +170,7 @@ describe('add-domain command', () => {
167
170
  domainsDir: tmpDir,
168
171
  });
169
172
 
170
- const apiContent = await readFile(join(tmpDir, 'tenant-domain', 'api', 'example.ts'), 'utf8');
173
+ const apiContent = await readFile(join(tmpDir, 'tenant-domain', 'actions', 'example.ts'), 'utf8');
171
174
  // For 'required' tenancy, path should include /v1/tenants/{tenantId}
172
175
  expect(apiContent).toContain(`path: '/v1/tenants/{tenantId}/tenant-domain/example'`);
173
176
  });
@@ -19,7 +19,7 @@ describe('check-hashes', () => {
19
19
 
20
20
  it('should return ok: true when all files match manifest', async () => {
21
21
  // Set up directory structure
22
- await mkdir(join(tempDir, '.tib'), { recursive: true });
22
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
23
23
  await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
24
24
 
25
25
  // Create a test file
@@ -36,7 +36,7 @@ describe('check-hashes', () => {
36
36
  'infra/modules/test.ts': hash,
37
37
  },
38
38
  };
39
- await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
39
+ await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
40
40
 
41
41
  const result = await runCheckHashes({ projectRoot: tempDir });
42
42
  expect(result.ok).toBe(true);
@@ -46,7 +46,7 @@ describe('check-hashes', () => {
46
46
  });
47
47
 
48
48
  it('should detect modified files', async () => {
49
- await mkdir(join(tempDir, '.tib'), { recursive: true });
49
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
50
50
  await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
51
51
 
52
52
  const originalContent = 'export const MyStack = {};';
@@ -62,7 +62,7 @@ describe('check-hashes', () => {
62
62
  'infra/modules/test.ts': originalHash,
63
63
  },
64
64
  };
65
- await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
65
+ await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
66
66
 
67
67
  // Modify the file
68
68
  const modifiedContent = 'export const MyStack = { foo: "bar" };';
@@ -77,7 +77,7 @@ describe('check-hashes', () => {
77
77
  });
78
78
 
79
79
  it('should detect deleted files', async () => {
80
- await mkdir(join(tempDir, '.tib'), { recursive: true });
80
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
81
81
  await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
82
82
 
83
83
  const content = 'export const MyStack = {};';
@@ -94,7 +94,7 @@ describe('check-hashes', () => {
94
94
  'infra/modules/deleted.ts': 'somehash',
95
95
  },
96
96
  };
97
- await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
97
+ await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
98
98
 
99
99
  const result = await runCheckHashes({ projectRoot: tempDir });
100
100
  expect(result.ok).toBe(false);
@@ -103,7 +103,7 @@ describe('check-hashes', () => {
103
103
  });
104
104
 
105
105
  it('should detect unexpected files', async () => {
106
- await mkdir(join(tempDir, '.tib'), { recursive: true });
106
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
107
107
  await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
108
108
 
109
109
  const content = 'export const MyStack = {};';
@@ -123,7 +123,7 @@ describe('check-hashes', () => {
123
123
  'infra/modules/test.ts': hash,
124
124
  },
125
125
  };
126
- await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
126
+ await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
127
127
 
128
128
  const result = await runCheckHashes({ projectRoot: tempDir });
129
129
  expect(result.ok).toBe(false);
@@ -132,7 +132,7 @@ describe('check-hashes', () => {
132
132
  });
133
133
 
134
134
  it('should throw with helpful message when manifest is missing', async () => {
135
- await mkdir(join(tempDir, '.tib'), { recursive: true });
135
+ await mkdir(join(tempDir, '.mc'), { recursive: true });
136
136
  await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
137
137
 
138
138
  await expect(runCheckHashes({ projectRoot: tempDir })).rejects.toThrow(
@@ -263,7 +263,7 @@ describe('runCreateProject', () => {
263
263
  const coreTarball = await createTestTarball([
264
264
  {
265
265
  path: 'lib/domain-runtime.ts',
266
- content: 'export const defineApi = () => {};',
266
+ content: 'export const defineAction = () => {};',
267
267
  isTemplate: false,
268
268
  },
269
269
  {
@@ -285,14 +285,14 @@ describe('runCreateProject', () => {
285
285
 
286
286
  const authTarball = await createTestTarball([
287
287
  {
288
- path: 'domains/auth/api/login.ts',
289
- content: 'import { defineApi } from "../../lib/domain-runtime";',
288
+ path: 'domains/auth/actions/login.ts',
289
+ content: 'import { defineAction } from "../../lib/domain-runtime";',
290
290
  isTemplate: false,
291
291
  },
292
292
  {
293
293
  path: 'MANIFEST.json',
294
294
  content: createTarballManifest([
295
- { path: 'domains/auth/api/login.ts', isTemplate: false },
295
+ { path: 'domains/auth/actions/login.ts', isTemplate: false },
296
296
  ]),
297
297
  isTemplate: false,
298
298
  },
@@ -319,7 +319,7 @@ describe('runCreateProject', () => {
319
319
  await runCreateProject({ name: 'test-project', outDir: tempDir });
320
320
 
321
321
  // Read the generated auth login file
322
- const authPath = join(outputDir, 'domains/auth/api/login.ts');
322
+ const authPath = join(outputDir, 'domains/auth/actions/login.ts');
323
323
  const authContent = await readFile(authPath, 'utf-8');
324
324
 
325
325
  expect(authContent).toContain('../../lib/domain-runtime');
@@ -34,7 +34,6 @@ const mocks = vi.hoisted(() => {
34
34
  buildRegistry: vi.fn().mockResolvedValue({
35
35
  registry: {
36
36
  domain: { id: 'mock-domain' },
37
- apis: [],
38
37
  webhooks: [],
39
38
  subscribers: [],
40
39
  schedules: [],
@@ -55,7 +55,7 @@ describe('upgrade command', () => {
55
55
  let tmpDir: string;
56
56
 
57
57
  beforeEach(async () => {
58
- tmpDir = await mkdtemp(join(tmpdir(), 'tib-upgrade-test-'));
58
+ tmpDir = await mkdtemp(join(tmpdir(), 'mc-upgrade-test-'));
59
59
  vi.clearAllMocks();
60
60
  });
61
61
 
@@ -213,8 +213,8 @@ describe('upgrade command', () => {
213
213
  });
214
214
  });
215
215
 
216
- describe('drift detection and .tib-upgrade conflict files', () => {
217
- it('writes .tib-upgrade file when file is drifted', async () => {
216
+ describe('drift detection and .mc-upgrade conflict files', () => {
217
+ it('writes .mc-upgrade file when file is drifted', async () => {
218
218
  const manifestChecksum = computeChecksumString('original content');
219
219
  const driftedContent = 'locally modified content by user';
220
220
  const driftedChecksum = computeChecksumString(driftedContent);
@@ -313,8 +313,8 @@ describe('upgrade command', () => {
313
313
  const originalContent = await readFile(join(tmpDir, filePath), 'utf-8');
314
314
  expect(originalContent).toBe(driftedContent);
315
315
 
316
- // Assert .tib-upgrade file was created with new content
317
- const conflictFile = await readFile(join(tmpDir, `${filePath}.tib-upgrade`), 'utf-8');
316
+ // Assert .mc-upgrade file was created with new content
317
+ const conflictFile = await readFile(join(tmpDir, `${filePath}.mc-upgrade`), 'utf-8');
318
318
  expect(conflictFile).toContain(newContent);
319
319
  });
320
320
  });
@@ -620,8 +620,8 @@ describe('upgrade command', () => {
620
620
  const originalContent = await readFile(join(tmpDir, filePath), 'utf-8');
621
621
  expect(originalContent).toBe(driftedContent);
622
622
 
623
- // .tib-upgrade file should be created
624
- const conflictFile = await readFile(join(tmpDir, `${filePath}.tib-upgrade`), 'utf-8');
623
+ // .mc-upgrade file should be created
624
+ const conflictFile = await readFile(join(tmpDir, `${filePath}.mc-upgrade`), 'utf-8');
625
625
  expect(conflictFile).toContain(newContent);
626
626
  });
627
627