@agentic-kit/db-tools 0.11.5 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +40 -1
  2. package/app-workspace.d.ts +29 -2
  3. package/app-workspace.js +99 -8
  4. package/blueprint-construction.d.ts +60 -0
  5. package/blueprint-construction.js +161 -0
  6. package/context.d.ts +5 -1
  7. package/context.js +30 -5
  8. package/db-probe.d.ts +25 -17
  9. package/db-probe.js +97 -37
  10. package/esm/app-workspace.d.ts +29 -2
  11. package/esm/app-workspace.js +96 -8
  12. package/esm/blueprint-construction.d.ts +60 -0
  13. package/esm/blueprint-construction.js +154 -0
  14. package/esm/context.d.ts +5 -1
  15. package/esm/context.js +31 -6
  16. package/esm/db-probe.d.ts +25 -17
  17. package/esm/db-probe.js +96 -37
  18. package/esm/graphql-http.d.ts +35 -0
  19. package/esm/graphql-http.js +134 -0
  20. package/esm/host.d.ts +46 -0
  21. package/esm/host.js +9 -0
  22. package/esm/index.d.ts +11 -2
  23. package/esm/index.js +6 -0
  24. package/esm/provision-database/env-file.d.ts +23 -2
  25. package/esm/provision-database/env-file.js +86 -27
  26. package/esm/provision-database/pending-ticket.d.ts +33 -0
  27. package/esm/provision-database/pending-ticket.js +63 -0
  28. package/esm/provision-database/provision.d.ts +71 -0
  29. package/esm/provision-database/provision.js +557 -0
  30. package/esm/provision-database/request-database.d.ts +55 -3
  31. package/esm/provision-database/request-database.js +118 -17
  32. package/esm/schema-model/normalize.d.ts +21 -0
  33. package/esm/schema-model/normalize.js +244 -0
  34. package/esm/schema-model.d.ts +110 -0
  35. package/esm/schema-model.js +303 -0
  36. package/esm/tools/add-policies.d.ts +1 -1
  37. package/esm/tools/add-records.d.ts +1 -0
  38. package/esm/tools/add-records.js +18 -6
  39. package/esm/tools/add-relation.js +2 -3
  40. package/esm/tools/create-api-key.d.ts +2 -0
  41. package/esm/tools/create-api-key.js +27 -12
  42. package/esm/tools/manage-entity-types.d.ts +1 -1
  43. package/esm/tools/provision-blueprint.d.ts +6 -0
  44. package/esm/tools/provision-blueprint.js +11 -4
  45. package/esm/tools/provision-database.d.ts +8 -11
  46. package/esm/tools/provision-database.js +23 -243
  47. package/esm/tools/run-codegen.d.ts +19 -2
  48. package/esm/tools/run-codegen.js +184 -97
  49. package/esm/tools/templates.js +3 -15
  50. package/graphql-http.d.ts +35 -0
  51. package/graphql-http.js +140 -0
  52. package/host.d.ts +46 -0
  53. package/host.js +10 -0
  54. package/index.d.ts +11 -2
  55. package/index.js +26 -1
  56. package/package.json +4 -4
  57. package/provision-database/env-file.d.ts +23 -2
  58. package/provision-database/env-file.js +89 -28
  59. package/provision-database/pending-ticket.d.ts +33 -0
  60. package/provision-database/pending-ticket.js +72 -0
  61. package/provision-database/provision.d.ts +71 -0
  62. package/provision-database/provision.js +565 -0
  63. package/provision-database/request-database.d.ts +55 -3
  64. package/provision-database/request-database.js +124 -18
  65. package/schema-model/normalize.d.ts +21 -0
  66. package/schema-model/normalize.js +250 -0
  67. package/schema-model.d.ts +110 -0
  68. package/schema-model.js +309 -0
  69. package/tools/add-policies.d.ts +1 -1
  70. package/tools/add-records.d.ts +1 -0
  71. package/tools/add-records.js +18 -6
  72. package/tools/add-relation.js +2 -3
  73. package/tools/create-api-key.d.ts +2 -0
  74. package/tools/create-api-key.js +27 -12
  75. package/tools/manage-entity-types.d.ts +1 -1
  76. package/tools/provision-blueprint.d.ts +6 -0
  77. package/tools/provision-blueprint.js +11 -4
  78. package/tools/provision-database.d.ts +8 -11
  79. package/tools/provision-database.js +23 -245
  80. package/tools/run-codegen.d.ts +19 -2
  81. package/tools/run-codegen.js +184 -96
  82. package/tools/templates.js +3 -15
@@ -4,138 +4,226 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.prewarmAppWorkspace = exports.runCodegenTool = void 0;
7
+ exports.createRunCodegenTool = createRunCodegenTool;
7
8
  const node_fs_1 = require("node:fs");
8
9
  const promises_1 = require("node:fs/promises");
9
10
  const node_path_1 = __importDefault(require("node:path"));
10
11
  const zod_1 = require("zod");
11
12
  const app_workspace_1 = require("../app-workspace");
12
13
  Object.defineProperty(exports, "prewarmAppWorkspace", { enumerable: true, get: function () { return app_workspace_1.prewarmAppWorkspace; } });
14
+ const graphql_http_1 = require("../graphql-http");
13
15
  const host_1 = require("../host");
16
+ const env_file_1 = require("../provision-database/env-file");
14
17
  const barrels_1 = require("../run-codegen/barrels");
15
18
  const endpoints_1 = require("../run-codegen/endpoints");
16
19
  const DEFAULT_API_ENDPOINT = 'http://api.localhost:3000/graphql';
17
20
  const RunCodegenZod = zod_1.z.object({});
18
- function fail(message) {
19
- return { content: [{ type: 'text', text: message }], details: { success: false, message } };
21
+ function fail(message, extra) {
22
+ return {
23
+ content: [{ type: 'text', text: message }],
24
+ details: { ...extra, success: false, message },
25
+ };
20
26
  }
21
27
  const FETCH_RESET_RE = /Failed to fetch schema|Connection reset|ECONNRESET|socket hang up/i;
28
+ // A refusal is an answer, not a dropped connection: retrying it only waits.
29
+ const REFUSED_RE = /\b403\b|Forbidden|ACCESS_SUSPENDED/;
22
30
  // Read the binding provision_database wrote to the project .env. The
23
31
  // API_ENDPOINT pin identifies which backend owns the binding — it wins over
24
32
  // the app's current backend config, matching context.ts data-plane precedence.
25
33
  async function readProjectEnv(cwd) {
26
34
  try {
27
- const env = await (0, promises_1.readFile)(node_path_1.default.join(cwd, '.env'), 'utf8');
28
- const read = (key) => env.match(new RegExp(`^${key}=(.+)$`, 'm'))?.[1]?.trim();
29
- return { databaseName: read('DATABASE_NAME'), apiEndpoint: read('API_ENDPOINT') };
35
+ const env = (0, env_file_1.readEnv)(await (0, promises_1.readFile)(node_path_1.default.join(cwd, '.env'), 'utf8'));
36
+ return {
37
+ databaseName: env.DATABASE_NAME?.trim() || undefined,
38
+ apiEndpoint: env.API_ENDPOINT?.trim() || undefined,
39
+ };
30
40
  }
31
41
  catch {
32
42
  return {};
33
43
  }
34
44
  }
35
45
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
46
+ // Ask the app API itself; a 403 from codegen alone could be anything.
47
+ async function isDatabaseUnavailable(appEndpoint) {
48
+ const answer = await (0, graphql_http_1.postGraphQL)({ endpoint: appEndpoint, query: '{ __typename }' });
49
+ return (0, graphql_http_1.classifyBackendErrors)(answer.errors) === 'suspended';
50
+ }
51
+ const CODEGEN_ARGS = ['generate', '--config', './graphql-codegen.config.ts'];
52
+ // The template flow resolves codegen through npx. A composed app runs the codegen
53
+ // its lockfile pinned: the linked bin, or the app's own `codegen` script when the
54
+ // bin isn't linked, so nothing is resolved over the network.
55
+ function codegenCommand(appDir, composed) {
56
+ if (!composed)
57
+ return { cmd: 'npx', args: ['@constructive-io/graphql-codegen', ...CODEGEN_ARGS] };
58
+ const bin = node_path_1.default.join(appDir, 'node_modules', '.bin', 'graphql-codegen');
59
+ return (0, node_fs_1.existsSync)(bin) ? { cmd: bin, args: CODEGEN_ARGS } : { cmd: 'pnpm', args: ['run', 'codegen'] };
60
+ }
36
61
  // The codegen CLI introspects three live vhosts (admin/auth/app). Under load the
37
62
  // Constructive backend intermittently resets a connection mid-introspection
38
63
  // ("Failed to fetch schema: Connection reset"), which is transient — a retry
39
64
  // almost always clears it. Re-run the whole generate (it rebuilds all targets)
40
65
  // up to `attempts` times, only on a fetch/reset failure; any other failure is
41
66
  // real and returned immediately (CODEGEN-RETRY-001).
42
- async function runCodegenWithRetry(appDir, env, attempts = 4) {
67
+ async function runCodegenWithRetry(appDir, env, exec, composed, attempts = 4) {
43
68
  const sdk = node_path_1.default.join(appDir, 'src', 'graphql', 'sdk');
69
+ const { cmd, args } = codegenCommand(appDir, composed);
44
70
  let last = { ok: false, out: 'codegen never ran' };
45
71
  for (let i = 1; i <= attempts; i++) {
46
- for (const sub of ['admin', 'auth', 'app']) {
47
- await (0, promises_1.rm)(node_path_1.default.join(sdk, sub), { recursive: true, force: true });
72
+ // A composed app ships SDK stubs so it builds before codegen; codegen prunes
73
+ // its own stale files, so the stubs stay until a run replaces them.
74
+ if (!composed) {
75
+ for (const sub of ['admin', 'auth', 'app']) {
76
+ await (0, promises_1.rm)(node_path_1.default.join(sdk, sub), { recursive: true, force: true });
77
+ }
48
78
  }
49
- last = await (0, app_workspace_1.run)('npx', ['@constructive-io/graphql-codegen', 'generate', '--config', './graphql-codegen.config.ts'], appDir, env);
50
- if (last.ok)
51
- return last;
52
- if (!FETCH_RESET_RE.test(last.out))
53
- return last;
79
+ last = await exec(cmd, args, appDir, env);
80
+ if (last.ok || !FETCH_RESET_RE.test(last.out) || REFUSED_RE.test(last.out))
81
+ return { ...last, attempts: i };
54
82
  if (i < attempts)
55
83
  await sleep(2000 * i);
56
84
  }
57
- return last;
85
+ return { ...last, attempts };
58
86
  }
59
- exports.runCodegenTool = {
60
- name: 'run_codegen',
61
- label: 'Run codegen',
62
- description: "Generate the typed GraphQL SDK for the project's Next.js app (packages/app). Runs the exact deterministic sequence — scaffold packages/app from the constructive-app template if it doesn't exist yet, hoist the graphql override to the root workspace so a single grafast resolves, pin codegen to latest, do one clean install, run codegen (retrying transient backend connection resets), and normalize SDK barrels for Turbopack. Run AFTER provisioning the database and creating schema; it scaffolds packages/app itself, so no separate `pgpm init` step is needed. The scaffold + install are usually prewarmed during provision_database, so this is fast.",
63
- promptSnippet: 'run_codegen: scaffold packages/app (if missing) + regenerate the typed SDK (workspace-config + clean install + codegen-with-retry + barrel-normalize). Run after schema changes. Gated.',
64
- parameters: RunCodegenZod,
65
- async execute(_params, ctx) {
66
- const cwd = ctx.cwd;
67
- const appDir = node_path_1.default.join(cwd, 'packages', 'app');
68
- // Materialize packages/app deterministically if it isn't there yet — this
69
- // replaces the flaky `pgpm init` scaffold step (PGPM-001). When
70
- // provision_database prewarmed it, this no-ops.
71
- const scaffold = await (0, app_workspace_1.ensureAppScaffold)(cwd);
72
- if (!scaffold.ok) {
73
- return fail(scaffold.out);
74
- }
75
- const { databaseName: dbName, apiEndpoint: envApiEndpoint } = await readProjectEnv(cwd);
76
- if (!dbName) {
77
- return fail('DATABASE_NAME missing from .env — provision the database first, then re-run.');
78
- }
79
- const backend = (0, host_1.getHost)().backendConfig();
80
- const apiEndpoint = envApiEndpoint || backend?.apiEndpoint || DEFAULT_API_ENDPOINT;
81
- const planes = (0, endpoints_1.derivePlaneEndpoints)(apiEndpoint, dbName);
82
- // The dev server reads .env.local (NEXT_PUBLIC_DB_NAME + per-DB endpoint
83
- // overrides so it doesn't fall back to localhost vhosts on a remote
84
- // backend); the codegen config reads process.env (passed directly below).
85
- try {
86
- await (0, promises_1.writeFile)(node_path_1.default.join(appDir, '.env.local'), (0, endpoints_1.envLocalContent)(dbName, planes));
87
- }
88
- catch (err) {
89
- return fail(`Failed to write packages/app/.env.local: ${err instanceof Error ? err.message : String(err)}`);
90
- }
91
- // Hoist the graphql override to the root workspace (PNPM-GRAPHQL-OVERRIDE-001)
92
- // and pin codegen to latest (TS-001) before the single clean install. When
93
- // prewarmed by provision_database these are idempotent and the install is a
94
- // fast no-op; otherwise this is the first install (PNPM-BUILDS-001).
95
- await (0, app_workspace_1.ensureWorkspaceConfig)(cwd);
96
- await (0, app_workspace_1.pinCodegenLatest)(appDir);
97
- const install = await (0, app_workspace_1.run)('pnpm', ['install', '--no-frozen-lockfile'], cwd);
98
- if (!install.ok && !app_workspace_1.IGNORED_BUILDS_RE.test(install.out)) {
99
- return fail(`pnpm install failed:\n${install.out}`);
100
- }
101
- const codegen = await runCodegenWithRetry(appDir, {
102
- NEXT_PUBLIC_DB_NAME: dbName,
103
- ...(planes ? (0, endpoints_1.codegenEndpointEnv)(planes) : {}),
104
- });
105
- if (!codegen.ok) {
106
- // A reset that survives every internal retry is a backend OUTAGE, not a code
107
- // or install problem — re-running run_codegen cannot fix it. Say so explicitly
108
- // so the agent marks sdk-codegen `blocked` instead of looping the tool to the
109
- // time cap (CODEGEN-OUTAGE-001 / THRASH-001).
110
- if (FETCH_RESET_RE.test(codegen.out)) {
111
- return fail('codegen failed: BACKEND OUTAGE — the Constructive backend reset the connection ' +
112
- 'during schema introspection, and it stayed down across every internal retry. ' +
113
- 'This is an EXTERNAL outage, not a code or install problem; re-running run_codegen ' +
114
- 'will NOT fix it. Call run_preflight once — if the backend is down, mark ' +
115
- 'sdk-codegen `blocked` (SERVER-001, BLOCKED-PROCEED-001) and hand back to ' +
116
- 'build-orchestrator. Do NOT loop on run_codegen (THRASH-001).\n\n' +
117
- codegen.out);
87
+ // `exec` runs every child process (clone, install, codegen); tests inject it.
88
+ function createRunCodegenTool(exec = app_workspace_1.run) {
89
+ return {
90
+ name: 'run_codegen',
91
+ label: 'Run codegen',
92
+ description: "Generate the typed GraphQL SDK for the project's Next.js app (packages/app). Runs the exact deterministic sequence — scaffold packages/app from the constructive-app template if it doesn't exist yet, hoist the graphql override to the root workspace so a single grafast resolves, pin codegen to latest, do one clean install, run codegen (retrying transient backend connection resets), and normalize SDK barrels for Turbopack. A composed app (packages/app/.constructive/compose.json) skips the scaffold and those fixups: it installs from its own lockfile only when node_modules is missing and runs its local codegen. Run AFTER provisioning the database and creating schema; it scaffolds packages/app itself, so no separate `pgpm init` step is needed. The scaffold + install are usually prewarmed during provision_database, so this is fast.",
93
+ promptSnippet: 'run_codegen: scaffold packages/app (if missing) + regenerate the typed SDK (workspace-config + clean install + codegen-with-retry + barrel-normalize; a composed app keeps its lockfile and local codegen). Run after schema changes. Gated.',
94
+ parameters: RunCodegenZod,
95
+ async execute(_params, ctx) {
96
+ const started = Date.now();
97
+ const since = (t) => Date.now() - t;
98
+ const cwd = ctx.cwd;
99
+ const appDir = node_path_1.default.join(cwd, 'packages', 'app');
100
+ // A prewarm provision_database started may still be installing; let it
101
+ // finish so this run's install never races it.
102
+ await (0, app_workspace_1.settlePrewarm)(cwd);
103
+ const app = (0, app_workspace_1.detectAppKind)(appDir);
104
+ if (app.kind === 'invalid') {
105
+ return fail(`${app.reason} Recompose packages/app, or remove the marker to use the template flow.`);
118
106
  }
119
- return fail(`codegen failed:\n${codegen.out}`);
120
- }
121
- // Normalize directory barrels so Turbopack can resolve them (TURBOPACK-BARREL-001).
122
- const sdkRoot = node_path_1.default.join(appDir, 'src', 'graphql', 'sdk');
123
- const barrelsRewritten = (0, barrels_1.normalizeSdkBarrels)(sdkRoot);
124
- // Report which app submodules actually generated — a bare scaffold with only
125
- // index.ts means codegen had no schema to generate against.
126
- const appSdk = node_path_1.default.join(sdkRoot, 'app');
127
- const generatedSubmodules = ['hooks', 'orm', 'types'].filter((sub) => (0, node_fs_1.existsSync)(node_path_1.default.join(appSdk, sub)));
128
- const message = generatedSubmodules.length > 0
129
- ? `Codegen complete. Generated app submodules: ${generatedSubmodules.join(', ')}. Normalized ${barrelsRewritten} barrel file(s).`
130
- : `Codegen ran, but no app submodules (hooks/orm/types) were generated — the database may have no entity tables yet. Normalized ${barrelsRewritten} barrel file(s).`;
131
- return {
132
- content: [{ type: 'text', text: message }],
133
- details: {
134
- success: generatedSubmodules.length > 0,
135
- message,
136
- barrelsRewritten,
137
- generatedSubmodules,
138
- },
139
- };
140
- },
141
- };
107
+ const composed = app.kind === 'composed';
108
+ const appKind = composed ? 'composed' : 'template';
109
+ const timings = {
110
+ scaffoldMs: 0,
111
+ installMs: 0,
112
+ codegenMs: 0,
113
+ barrelsMs: 0,
114
+ totalMs: 0,
115
+ };
116
+ let installed;
117
+ let codegenAttempts;
118
+ const stepDetails = () => ({
119
+ appKind,
120
+ installed,
121
+ codegenAttempts,
122
+ timings: { ...timings, totalMs: since(started) },
123
+ });
124
+ // Materialize packages/app deterministically if it isn't there yet — this
125
+ // replaces the flaky `pgpm init` scaffold step (PGPM-001). When
126
+ // provision_database prewarmed it, this no-ops.
127
+ if (!composed) {
128
+ const t = Date.now();
129
+ const scaffold = await (0, app_workspace_1.ensureAppScaffold)(cwd, exec);
130
+ timings.scaffoldMs = since(t);
131
+ if (!scaffold.ok) {
132
+ return fail(scaffold.out, stepDetails());
133
+ }
134
+ }
135
+ const { databaseName: dbName, apiEndpoint: envApiEndpoint } = await readProjectEnv(cwd);
136
+ if (!dbName) {
137
+ return fail('DATABASE_NAME missing from .env — provision the database first, then re-run.');
138
+ }
139
+ const backend = (0, host_1.getHost)().backendConfig();
140
+ const apiEndpoint = envApiEndpoint || backend?.apiEndpoint || DEFAULT_API_ENDPOINT;
141
+ const planes = (0, endpoints_1.derivePlaneEndpoints)(apiEndpoint, dbName);
142
+ // The dev server reads .env.local (NEXT_PUBLIC_DB_NAME + per-DB endpoint
143
+ // overrides so it doesn't fall back to localhost vhosts on a remote
144
+ // backend); the codegen config reads process.env (passed directly below).
145
+ try {
146
+ await (0, promises_1.writeFile)(node_path_1.default.join(appDir, '.env.local'), (0, endpoints_1.envLocalContent)(dbName, planes));
147
+ }
148
+ catch (err) {
149
+ return fail(`Failed to write packages/app/.env.local: ${err instanceof Error ? err.message : String(err)}`);
150
+ }
151
+ const tInstall = Date.now();
152
+ if (composed) {
153
+ const install = await (0, app_workspace_1.ensureComposedInstall)(cwd, exec);
154
+ timings.installMs = since(tInstall);
155
+ installed = install.installed;
156
+ if (!install.ok) {
157
+ return fail(install.out, stepDetails());
158
+ }
159
+ }
160
+ else {
161
+ // Hoist the graphql override to the root workspace (PNPM-GRAPHQL-OVERRIDE-001)
162
+ // and pin codegen to latest (TS-001) before the single clean install. When
163
+ // prewarmed by provision_database these are idempotent and the install is a
164
+ // fast no-op; otherwise this is the first install (PNPM-BUILDS-001).
165
+ await (0, app_workspace_1.ensureWorkspaceConfig)(cwd);
166
+ await (0, app_workspace_1.pinCodegenLatest)(appDir);
167
+ const install = await exec('pnpm', ['install', '--no-frozen-lockfile'], cwd);
168
+ timings.installMs = since(tInstall);
169
+ installed = true;
170
+ if (!install.ok && !app_workspace_1.IGNORED_BUILDS_RE.test(install.out)) {
171
+ return fail(`pnpm install failed:\n${install.out}`, stepDetails());
172
+ }
173
+ }
174
+ const tCodegen = Date.now();
175
+ const codegen = await runCodegenWithRetry(appDir, {
176
+ NEXT_PUBLIC_DB_NAME: dbName,
177
+ ...(planes ? (0, endpoints_1.codegenEndpointEnv)(planes) : {}),
178
+ }, exec, composed);
179
+ timings.codegenMs = since(tCodegen);
180
+ codegenAttempts = codegen.attempts;
181
+ if (!codegen.ok) {
182
+ // A refused introspection may be the database itself: deleted or
183
+ // suspended databases answer ACCESS_SUSPENDED on every app API.
184
+ if (REFUSED_RE.test(codegen.out) && planes && (await isDatabaseUnavailable(planes.app))) {
185
+ return fail(`codegen failed: the app database "${dbName}" is unavailable (the backend answered ACCESS_SUSPENDED: it was deleted or is suspended). Re-running run_codegen won't help until it is restored.`, { ...stepDetails(), code: 'database-unavailable' });
186
+ }
187
+ // A reset that survives every internal retry is a backend OUTAGE, not a code
188
+ // or install problem — re-running run_codegen cannot fix it. Say so explicitly
189
+ // so the agent marks sdk-codegen `blocked` instead of looping the tool to the
190
+ // time cap (CODEGEN-OUTAGE-001 / THRASH-001).
191
+ if (FETCH_RESET_RE.test(codegen.out)) {
192
+ return fail('codegen failed: BACKEND OUTAGE — the Constructive backend reset the connection ' +
193
+ 'during schema introspection, and it stayed down across every internal retry. ' +
194
+ 'This is an EXTERNAL outage, not a code or install problem; re-running run_codegen ' +
195
+ 'will NOT fix it. Call run_preflight once — if the backend is down, mark ' +
196
+ 'sdk-codegen `blocked` (SERVER-001, BLOCKED-PROCEED-001) and hand back to ' +
197
+ 'build-orchestrator. Do NOT loop on run_codegen (THRASH-001).\n\n' +
198
+ codegen.out, stepDetails());
199
+ }
200
+ return fail(`codegen failed:\n${codegen.out}`, stepDetails());
201
+ }
202
+ // Normalize directory barrels so Turbopack can resolve them (TURBOPACK-BARREL-001).
203
+ // Only directories that exist are rewritten, so a config without an admin
204
+ // target leaves nothing pointing at one.
205
+ const tBarrels = Date.now();
206
+ const sdkRoot = node_path_1.default.join(appDir, 'src', 'graphql', 'sdk');
207
+ const barrelsRewritten = (0, barrels_1.normalizeSdkBarrels)(sdkRoot);
208
+ timings.barrelsMs = since(tBarrels);
209
+ // Report which app submodules actually generated — a bare scaffold with only
210
+ // index.ts means codegen had no schema to generate against.
211
+ const appSdk = node_path_1.default.join(sdkRoot, 'app');
212
+ const generatedSubmodules = ['hooks', 'orm', 'types'].filter((sub) => (0, node_fs_1.existsSync)(node_path_1.default.join(appSdk, sub)));
213
+ const message = generatedSubmodules.length > 0
214
+ ? `Codegen complete. Generated app submodules: ${generatedSubmodules.join(', ')}. Normalized ${barrelsRewritten} barrel file(s).`
215
+ : `Codegen ran, but no app submodules (hooks/orm/types) were generated — the database may have no entity tables yet. Normalized ${barrelsRewritten} barrel file(s).`;
216
+ return {
217
+ content: [{ type: 'text', text: message }],
218
+ details: {
219
+ success: generatedSubmodules.length > 0,
220
+ message,
221
+ barrelsRewritten,
222
+ generatedSubmodules,
223
+ ...stepDetails(),
224
+ },
225
+ };
226
+ },
227
+ };
228
+ }
229
+ exports.runCodegenTool = createRunCodegenTool();
@@ -4,6 +4,7 @@ exports.deleteTemplateTool = exports.updateTemplateTool = exports.applyTemplateT
4
4
  exports.createTemplatePreviewTables = createTemplatePreviewTables;
5
5
  const harness_1 = require("@agentic-kit/harness");
6
6
  const zod_1 = require("zod");
7
+ const blueprint_construction_1 = require("../blueprint-construction");
7
8
  const context_1 = require("../context");
8
9
  // A stored blueprint field's `type` is an expanded object (`{ name: 'text' }`),
9
10
  // not the bare string the agent passes to provision_blueprint. Flatten it to the
@@ -256,21 +257,8 @@ exports.applyTemplateTool = {
256
257
  const blueprintId = copyResult.copyTemplateToBlueprint?.result;
257
258
  if (!blueprintId)
258
259
  throw new Error('Failed to copy template');
259
- const constructResult = await modules.mutation
260
- .constructBlueprint({ input: { blueprintId, schemaId } }, { select: { result: true } })
261
- .unwrap();
262
- const constructionId = constructResult.constructBlueprint?.result;
263
- if (!constructionId)
264
- throw new Error('constructBlueprint returned no construction id');
265
- const constructions = await modules.blueprintConstruction
266
- .findMany({
267
- where: { id: { equalTo: constructionId } },
268
- first: 1,
269
- select: { tableMap: true },
270
- })
271
- .unwrap();
272
- const tableMap = (constructions?.blueprintConstructions?.nodes?.[0]?.tableMap ?? {});
273
- const createdCount = Object.keys(tableMap).length;
260
+ const { tables } = await (0, blueprint_construction_1.buildBlueprint)(modules, { blueprintId, schemaId }, { signal: ctx.signal });
261
+ const createdCount = Object.keys(tables).length;
274
262
  return done(true, `Applied template "${params.templateName}" — created ${createdCount} table${createdCount === 1 ? '' : 's'}`);
275
263
  }
276
264
  catch (err) {