@agentic-kit/db-tools 0.2.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.
- package/LICENSE +23 -0
- package/README.md +138 -0
- package/app-workspace.d.ts +16 -0
- package/app-workspace.js +145 -0
- package/context.d.ts +62 -0
- package/context.js +282 -0
- package/db-probe.d.ts +30 -0
- package/db-probe.js +50 -0
- package/esm/app-workspace.d.ts +16 -0
- package/esm/app-workspace.js +134 -0
- package/esm/context.d.ts +62 -0
- package/esm/context.js +270 -0
- package/esm/db-probe.d.ts +30 -0
- package/esm/db-probe.js +47 -0
- package/esm/host.d.ts +115 -0
- package/esm/host.js +23 -0
- package/esm/index.d.ts +20 -0
- package/esm/index.js +52 -0
- package/esm/policy/add-policies-to-table.d.ts +26 -0
- package/esm/policy/add-policies-to-table.js +66 -0
- package/esm/policy/provision-helpers.d.ts +38 -0
- package/esm/policy/provision-helpers.js +50 -0
- package/esm/provision-database/credential.d.ts +14 -0
- package/esm/provision-database/credential.js +19 -0
- package/esm/provision-database/env-file.d.ts +37 -0
- package/esm/provision-database/env-file.js +85 -0
- package/esm/provision-database/manifest.d.ts +10 -0
- package/esm/provision-database/manifest.js +48 -0
- package/esm/provision-database/pg-fixups.d.ts +16 -0
- package/esm/provision-database/pg-fixups.js +132 -0
- package/esm/provision-database/preset-match.d.ts +13 -0
- package/esm/provision-database/preset-match.js +66 -0
- package/esm/provision-database/presets.d.ts +4 -0
- package/esm/provision-database/presets.js +13 -0
- package/esm/provision-database/request-database.d.ts +61 -0
- package/esm/provision-database/request-database.js +130 -0
- package/esm/provision-database/resolve.d.ts +27 -0
- package/esm/provision-database/resolve.js +49 -0
- package/esm/records/meta.d.ts +41 -0
- package/esm/records/meta.js +167 -0
- package/esm/run-codegen/barrels.d.ts +3 -0
- package/esm/run-codegen/barrels.js +48 -0
- package/esm/run-codegen/endpoints.d.ts +8 -0
- package/esm/run-codegen/endpoints.js +46 -0
- package/esm/schema-resolve.d.ts +23 -0
- package/esm/schema-resolve.js +69 -0
- package/esm/tool-schema.d.ts +3 -0
- package/esm/tool-schema.js +10 -0
- package/esm/tools/add-policies.d.ts +24 -0
- package/esm/tools/add-policies.js +94 -0
- package/esm/tools/add-records.d.ts +18 -0
- package/esm/tools/add-records.js +122 -0
- package/esm/tools/add-relation-schema.d.ts +29 -0
- package/esm/tools/add-relation-schema.js +56 -0
- package/esm/tools/add-relation.d.ts +10 -0
- package/esm/tools/add-relation.js +91 -0
- package/esm/tools/create-api-key.d.ts +25 -0
- package/esm/tools/create-api-key.js +214 -0
- package/esm/tools/describe-schema.d.ts +19 -0
- package/esm/tools/describe-schema.js +130 -0
- package/esm/tools/manage-entity-types.d.ts +40 -0
- package/esm/tools/manage-entity-types.js +201 -0
- package/esm/tools/mutations.d.ts +35 -0
- package/esm/tools/mutations.js +230 -0
- package/esm/tools/provision-blueprint.d.ts +21 -0
- package/esm/tools/provision-blueprint.js +109 -0
- package/esm/tools/provision-database.d.ts +18 -0
- package/esm/tools/provision-database.js +260 -0
- package/esm/tools/run-codegen.d.ts +12 -0
- package/esm/tools/run-codegen.js +137 -0
- package/esm/tools/templates.d.ts +71 -0
- package/esm/tools/templates.js +331 -0
- package/host.d.ts +115 -0
- package/host.js +28 -0
- package/index.d.ts +20 -0
- package/index.js +76 -0
- package/package.json +49 -0
- package/policy/add-policies-to-table.d.ts +26 -0
- package/policy/add-policies-to-table.js +71 -0
- package/policy/provision-helpers.d.ts +38 -0
- package/policy/provision-helpers.js +58 -0
- package/provision-database/credential.d.ts +14 -0
- package/provision-database/credential.js +22 -0
- package/provision-database/env-file.d.ts +37 -0
- package/provision-database/env-file.js +91 -0
- package/provision-database/manifest.d.ts +10 -0
- package/provision-database/manifest.js +56 -0
- package/provision-database/pg-fixups.d.ts +16 -0
- package/provision-database/pg-fixups.js +170 -0
- package/provision-database/preset-match.d.ts +13 -0
- package/provision-database/preset-match.js +70 -0
- package/provision-database/presets.d.ts +4 -0
- package/provision-database/presets.js +17 -0
- package/provision-database/request-database.d.ts +61 -0
- package/provision-database/request-database.js +134 -0
- package/provision-database/resolve.d.ts +27 -0
- package/provision-database/resolve.js +53 -0
- package/records/meta.d.ts +41 -0
- package/records/meta.js +172 -0
- package/run-codegen/barrels.d.ts +3 -0
- package/run-codegen/barrels.js +54 -0
- package/run-codegen/endpoints.d.ts +8 -0
- package/run-codegen/endpoints.js +51 -0
- package/schema-resolve.d.ts +23 -0
- package/schema-resolve.js +75 -0
- package/tool-schema.d.ts +3 -0
- package/tool-schema.js +13 -0
- package/tools/add-policies.d.ts +24 -0
- package/tools/add-policies.js +97 -0
- package/tools/add-records.d.ts +18 -0
- package/tools/add-records.js +125 -0
- package/tools/add-relation-schema.d.ts +29 -0
- package/tools/add-relation-schema.js +60 -0
- package/tools/add-relation.d.ts +10 -0
- package/tools/add-relation.js +94 -0
- package/tools/create-api-key.d.ts +25 -0
- package/tools/create-api-key.js +220 -0
- package/tools/describe-schema.d.ts +19 -0
- package/tools/describe-schema.js +133 -0
- package/tools/manage-entity-types.d.ts +40 -0
- package/tools/manage-entity-types.js +206 -0
- package/tools/mutations.d.ts +35 -0
- package/tools/mutations.js +233 -0
- package/tools/provision-blueprint.d.ts +21 -0
- package/tools/provision-blueprint.js +112 -0
- package/tools/provision-database.d.ts +18 -0
- package/tools/provision-database.js +266 -0
- package/tools/run-codegen.d.ts +12 -0
- package/tools/run-codegen.js +141 -0
- package/tools/templates.d.ts +71 -0
- package/tools/templates.js +335 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type HarnessTool } from '@agentic-kit/harness';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
declare const ParamZod: z.ZodObject<{}, z.core.$strip>;
|
|
4
|
+
type SchemaField = {
|
|
5
|
+
name: string;
|
|
6
|
+
type: string;
|
|
7
|
+
isRequired: boolean;
|
|
8
|
+
defaultValue: string | null;
|
|
9
|
+
};
|
|
10
|
+
type SchemaTable = {
|
|
11
|
+
name: string;
|
|
12
|
+
fields: SchemaField[];
|
|
13
|
+
policies: string[];
|
|
14
|
+
};
|
|
15
|
+
export type DescribeSchemaDetails = {
|
|
16
|
+
tables: SchemaTable[];
|
|
17
|
+
};
|
|
18
|
+
export declare const describeSchemaTool: HarnessTool<typeof ParamZod, DescribeSchemaDetails>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { fieldTypeToTypeName, isInternalPolicy } from '@agentic-kit/harness';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { resolveProjectContext } from '../context';
|
|
4
|
+
const ParamZod = z.object({});
|
|
5
|
+
function toDefaultValueString(value) {
|
|
6
|
+
if (value == null)
|
|
7
|
+
return null;
|
|
8
|
+
if (typeof value === 'string')
|
|
9
|
+
return value;
|
|
10
|
+
return JSON.stringify(value);
|
|
11
|
+
}
|
|
12
|
+
function formatText(details) {
|
|
13
|
+
const lines = [];
|
|
14
|
+
if (details.tables.length === 0) {
|
|
15
|
+
lines.push('No application tables yet.');
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
lines.push(`Tables (${details.tables.length}):`);
|
|
19
|
+
for (const table of details.tables) {
|
|
20
|
+
lines.push(` ${table.name}`);
|
|
21
|
+
for (const field of table.fields) {
|
|
22
|
+
const req = field.isRequired ? ' required' : '';
|
|
23
|
+
const def = field.defaultValue != null ? ` default=${field.defaultValue}` : '';
|
|
24
|
+
lines.push(` - ${field.name}: ${field.type}${req}${def}`);
|
|
25
|
+
}
|
|
26
|
+
if (table.policies.length > 0) {
|
|
27
|
+
lines.push(` policies: ${table.policies.join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return lines.join('\n');
|
|
32
|
+
}
|
|
33
|
+
export const describeSchemaTool = {
|
|
34
|
+
name: 'describe_schema',
|
|
35
|
+
label: 'Describe schema',
|
|
36
|
+
description: 'Inspect the current Constructive database schema for this project: application tables with their fields (name, type, required, default). Read-only.',
|
|
37
|
+
promptSnippet: 'describe_schema: list the project database tables and their fields. Read-only — call before modifying schema.',
|
|
38
|
+
parameters: ParamZod,
|
|
39
|
+
async execute(_params, ctx) {
|
|
40
|
+
const resolved = await resolveProjectContext(ctx.cwd);
|
|
41
|
+
if (!resolved.context) {
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: 'text', text: resolved.reason }],
|
|
44
|
+
details: { tables: [] },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const { api, databaseId } = resolved.context;
|
|
48
|
+
const tablesResult = await api.table
|
|
49
|
+
.findMany({
|
|
50
|
+
select: { id: true, name: true, category: true },
|
|
51
|
+
where: {
|
|
52
|
+
databaseId: { equalTo: databaseId },
|
|
53
|
+
or: [{ category: { equalTo: 'APP' } }, { name: { equalTo: 'users' } }],
|
|
54
|
+
},
|
|
55
|
+
orderBy: ['NAME_ASC'],
|
|
56
|
+
})
|
|
57
|
+
.execute();
|
|
58
|
+
if (!tablesResult.ok) {
|
|
59
|
+
const message = tablesResult.errors.map((e) => e.message).join('; ');
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: 'text', text: `Failed to read schema: ${message}` }],
|
|
62
|
+
details: { tables: [] },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const tableNodes = tablesResult.data.tables?.nodes ?? [];
|
|
66
|
+
const tableIds = tableNodes.map((t) => t.id);
|
|
67
|
+
const fieldsByTable = new Map();
|
|
68
|
+
const policiesByTable = new Map();
|
|
69
|
+
if (tableIds.length > 0) {
|
|
70
|
+
const [fieldsResult, policiesResult] = await Promise.all([
|
|
71
|
+
api.field
|
|
72
|
+
.findMany({
|
|
73
|
+
select: {
|
|
74
|
+
name: true,
|
|
75
|
+
type: true,
|
|
76
|
+
isRequired: true,
|
|
77
|
+
defaultValue: true,
|
|
78
|
+
tableId: true,
|
|
79
|
+
},
|
|
80
|
+
where: { tableId: { in: tableIds } },
|
|
81
|
+
})
|
|
82
|
+
.execute(),
|
|
83
|
+
api.policy
|
|
84
|
+
.findMany({
|
|
85
|
+
select: { tableId: true, policyType: true },
|
|
86
|
+
where: { tableId: { in: tableIds } },
|
|
87
|
+
})
|
|
88
|
+
.execute(),
|
|
89
|
+
]);
|
|
90
|
+
if (!fieldsResult.ok) {
|
|
91
|
+
const message = fieldsResult.errors.map((e) => e.message).join('; ');
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'text', text: `Failed to read fields: ${message}` }],
|
|
94
|
+
details: { tables: [] },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
for (const field of fieldsResult.data.fields?.nodes ?? []) {
|
|
98
|
+
const list = fieldsByTable.get(field.tableId) ?? [];
|
|
99
|
+
list.push({
|
|
100
|
+
name: field.name,
|
|
101
|
+
type: fieldTypeToTypeName(field.type),
|
|
102
|
+
isRequired: Boolean(field.isRequired),
|
|
103
|
+
defaultValue: toDefaultValueString(field.defaultValue),
|
|
104
|
+
});
|
|
105
|
+
fieldsByTable.set(field.tableId, list);
|
|
106
|
+
}
|
|
107
|
+
// One policy spans CRUD as several rows; collapse to distinct types per table.
|
|
108
|
+
if (policiesResult.ok) {
|
|
109
|
+
for (const policy of policiesResult.data.policies?.nodes ?? []) {
|
|
110
|
+
if (isInternalPolicy(policy.policyType))
|
|
111
|
+
continue;
|
|
112
|
+
const list = policiesByTable.get(policy.tableId) ?? [];
|
|
113
|
+
if (!list.includes(policy.policyType))
|
|
114
|
+
list.push(policy.policyType);
|
|
115
|
+
policiesByTable.set(policy.tableId, list);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const tables = tableNodes.map((table) => ({
|
|
120
|
+
name: table.name,
|
|
121
|
+
fields: fieldsByTable.get(table.id) ?? [],
|
|
122
|
+
policies: policiesByTable.get(table.id) ?? [],
|
|
123
|
+
}));
|
|
124
|
+
const details = { tables };
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: 'text', text: formatText(details) }],
|
|
127
|
+
details,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { HarnessTool } from '@agentic-kit/harness';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
declare const ManageEntityTypesZod: z.ZodObject<{
|
|
4
|
+
action: z.ZodEnum<{
|
|
5
|
+
create: "create";
|
|
6
|
+
delete: "delete";
|
|
7
|
+
list: "list";
|
|
8
|
+
}>;
|
|
9
|
+
name: z.ZodOptional<z.ZodString>;
|
|
10
|
+
entity_type_id: z.ZodOptional<z.ZodString>;
|
|
11
|
+
description: z.ZodOptional<z.ZodString>;
|
|
12
|
+
prefix: z.ZodOptional<z.ZodString>;
|
|
13
|
+
parent_entity: z.ZodOptional<z.ZodString>;
|
|
14
|
+
is_visible: z.ZodOptional<z.ZodBoolean>;
|
|
15
|
+
has_invites: z.ZodOptional<z.ZodBoolean>;
|
|
16
|
+
has_profiles: z.ZodOptional<z.ZodBoolean>;
|
|
17
|
+
has_limits: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
+
has_levels: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
has_storage: z.ZodOptional<z.ZodBoolean>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
export type EntityTypeSummary = {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string | null;
|
|
24
|
+
description: string | null;
|
|
25
|
+
prefix: string | null;
|
|
26
|
+
parentEntity: string | null;
|
|
27
|
+
isVisible: boolean | null;
|
|
28
|
+
entityTableName: string | null;
|
|
29
|
+
installedModules: string[] | null;
|
|
30
|
+
};
|
|
31
|
+
export type ManageEntityTypesDetails = {
|
|
32
|
+
success: boolean;
|
|
33
|
+
message: string;
|
|
34
|
+
entityTypes?: EntityTypeSummary[];
|
|
35
|
+
};
|
|
36
|
+
export declare function defaultPrefix(name: string): string;
|
|
37
|
+
type Params = z.infer<typeof ManageEntityTypesZod>;
|
|
38
|
+
export declare function validateManageEntityTypes(params: Params): string | null;
|
|
39
|
+
export declare const manageEntityTypesTool: HarnessTool<typeof ManageEntityTypesZod, ManageEntityTypesDetails>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { resolveProjectContext } from '../context';
|
|
3
|
+
const ManageEntityTypesZod = z.object({
|
|
4
|
+
action: z
|
|
5
|
+
.enum(['list', 'create', 'delete'])
|
|
6
|
+
.describe('list: show the entity types provisioned in this database. create: provision a new entity type (creates its entity table + membership wiring). delete: remove the provision registration. Registrations are immutable once provisioned — there is no update; to change one, delete it and create a new one.'),
|
|
7
|
+
name: z.string().describe('Entity type name. Required for create.').optional(),
|
|
8
|
+
entity_type_id: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe('Entity type provision id. Required for delete — get it from action "list".')
|
|
11
|
+
.optional(),
|
|
12
|
+
description: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe('Human-readable description of the entity type. Create only.')
|
|
15
|
+
.optional(),
|
|
16
|
+
prefix: z
|
|
17
|
+
.string()
|
|
18
|
+
.describe('Table-name prefix for the provisioned tables (e.g. "org" → org table, org_memberships). Create only; defaults to the snake_case of name.')
|
|
19
|
+
.optional(),
|
|
20
|
+
parent_entity: z
|
|
21
|
+
.string()
|
|
22
|
+
.describe('Parent entity table name, for hierarchical types (e.g. a "team" under an "org"). Create only; the backend defaults it to the root entity (org).')
|
|
23
|
+
.optional(),
|
|
24
|
+
is_visible: z
|
|
25
|
+
.boolean()
|
|
26
|
+
.describe('Whether the type shows up in end-user entity listings. Create only.')
|
|
27
|
+
.optional(),
|
|
28
|
+
has_invites: z
|
|
29
|
+
.boolean()
|
|
30
|
+
.describe('Provision an invites module ({prefix}_invites + claimed invites). Create only.')
|
|
31
|
+
.optional(),
|
|
32
|
+
has_profiles: z
|
|
33
|
+
.boolean()
|
|
34
|
+
.describe('Provision a profiles table for members of this type. Create only.')
|
|
35
|
+
.optional(),
|
|
36
|
+
has_limits: z
|
|
37
|
+
.boolean()
|
|
38
|
+
.describe('Provision usage-limits tracking for this type. Create only.')
|
|
39
|
+
.optional(),
|
|
40
|
+
has_levels: z
|
|
41
|
+
.boolean()
|
|
42
|
+
.describe('Provision levels/achievements support for this type. Create only.')
|
|
43
|
+
.optional(),
|
|
44
|
+
has_storage: z
|
|
45
|
+
.boolean()
|
|
46
|
+
.describe('Provision a storage module (files + buckets tables) for this type. Create only.')
|
|
47
|
+
.optional(),
|
|
48
|
+
});
|
|
49
|
+
const SUMMARY_SELECT = {
|
|
50
|
+
id: true,
|
|
51
|
+
name: true,
|
|
52
|
+
description: true,
|
|
53
|
+
prefix: true,
|
|
54
|
+
parentEntity: true,
|
|
55
|
+
isVisible: true,
|
|
56
|
+
outEntityTableName: true,
|
|
57
|
+
outInstalledModules: true,
|
|
58
|
+
};
|
|
59
|
+
function toSummary(row) {
|
|
60
|
+
return {
|
|
61
|
+
id: row.id,
|
|
62
|
+
name: row.name,
|
|
63
|
+
description: row.description,
|
|
64
|
+
prefix: row.prefix,
|
|
65
|
+
parentEntity: row.parentEntity,
|
|
66
|
+
isVisible: row.isVisible,
|
|
67
|
+
entityTableName: row.outEntityTableName,
|
|
68
|
+
installedModules: row.outInstalledModules,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function formatSummary(s) {
|
|
72
|
+
const parts = [
|
|
73
|
+
`- ${s.name ?? '?'} (id: ${s.id})`,
|
|
74
|
+
` table: ${s.entityTableName ?? '?'}, prefix: ${s.prefix ?? '?'}`,
|
|
75
|
+
];
|
|
76
|
+
if (s.parentEntity)
|
|
77
|
+
parts.push(` parent: ${s.parentEntity}`);
|
|
78
|
+
if (s.description)
|
|
79
|
+
parts.push(` description: ${s.description}`);
|
|
80
|
+
if (s.installedModules?.length)
|
|
81
|
+
parts.push(` modules: ${s.installedModules.join(', ')}`);
|
|
82
|
+
return parts.join('\n');
|
|
83
|
+
}
|
|
84
|
+
export function defaultPrefix(name) {
|
|
85
|
+
return name
|
|
86
|
+
.trim()
|
|
87
|
+
.toLowerCase()
|
|
88
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
89
|
+
.replace(/^_+|_+$/g, '');
|
|
90
|
+
}
|
|
91
|
+
export function validateManageEntityTypes(params) {
|
|
92
|
+
switch (params.action) {
|
|
93
|
+
case 'create':
|
|
94
|
+
if (!params.name?.trim())
|
|
95
|
+
return 'create requires "name".';
|
|
96
|
+
if (params.entity_type_id)
|
|
97
|
+
return 'create does not take "entity_type_id" — it mints a new one.';
|
|
98
|
+
return null;
|
|
99
|
+
case 'delete':
|
|
100
|
+
if (!params.entity_type_id)
|
|
101
|
+
return 'delete requires "entity_type_id" (use action "list" to find it).';
|
|
102
|
+
return null;
|
|
103
|
+
default:
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export const manageEntityTypesTool = {
|
|
108
|
+
name: 'manage_entity_types',
|
|
109
|
+
label: 'Manage entity types',
|
|
110
|
+
description: 'List, create, or delete entity types in the project database. An entity type (e.g. organization, team, project) provisions its own entity table plus membership wiring, and is the unit that API keys can be scoped to. Registrations are immutable once provisioned — to change one, delete it and create a new one. Deleting removes the registration; the already-provisioned entity table stays in the API schema.',
|
|
111
|
+
promptSnippet: 'manage_entity_types: list/create/delete entity types (orgs, teams, …) in the project database. list is free; create/delete are gated.',
|
|
112
|
+
parameters: ManageEntityTypesZod,
|
|
113
|
+
async execute(params, ctx) {
|
|
114
|
+
const resolved = await resolveProjectContext(ctx.cwd);
|
|
115
|
+
if (!resolved.context) {
|
|
116
|
+
return {
|
|
117
|
+
content: [{ type: 'text', text: resolved.reason }],
|
|
118
|
+
details: { success: false, message: resolved.reason },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const invalid = validateManageEntityTypes(params);
|
|
122
|
+
if (invalid) {
|
|
123
|
+
return {
|
|
124
|
+
content: [{ type: 'text', text: invalid }],
|
|
125
|
+
details: { success: false, message: invalid },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const { modules, databaseId } = resolved.context;
|
|
129
|
+
try {
|
|
130
|
+
switch (params.action) {
|
|
131
|
+
case 'list': {
|
|
132
|
+
const result = await modules.entityTypeProvision
|
|
133
|
+
.findMany({
|
|
134
|
+
where: { databaseId: { equalTo: databaseId } },
|
|
135
|
+
select: SUMMARY_SELECT,
|
|
136
|
+
})
|
|
137
|
+
.unwrap();
|
|
138
|
+
const entityTypes = result.entityTypeProvisions.nodes.map((row) => toSummary(row));
|
|
139
|
+
const message = entityTypes.length === 0
|
|
140
|
+
? 'No entity types are provisioned in this database yet.'
|
|
141
|
+
: `${entityTypes.length} entity type${entityTypes.length === 1 ? '' : 's'}:\n${entityTypes.map(formatSummary).join('\n')}`;
|
|
142
|
+
return {
|
|
143
|
+
content: [{ type: 'text', text: message }],
|
|
144
|
+
details: { success: true, message, entityTypes },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
case 'create': {
|
|
148
|
+
const name = params.name.trim();
|
|
149
|
+
const result = await modules.entityTypeProvision
|
|
150
|
+
.create({
|
|
151
|
+
data: {
|
|
152
|
+
databaseId,
|
|
153
|
+
name,
|
|
154
|
+
prefix: params.prefix?.trim() || defaultPrefix(name),
|
|
155
|
+
...(params.description !== undefined && { description: params.description }),
|
|
156
|
+
...(params.parent_entity !== undefined && { parentEntity: params.parent_entity }),
|
|
157
|
+
...(params.is_visible !== undefined && { isVisible: params.is_visible }),
|
|
158
|
+
...(params.has_invites !== undefined && { hasInvites: params.has_invites }),
|
|
159
|
+
...(params.has_profiles !== undefined && { hasProfiles: params.has_profiles }),
|
|
160
|
+
...(params.has_limits !== undefined && { hasLimits: params.has_limits }),
|
|
161
|
+
...(params.has_levels !== undefined && { hasLevels: params.has_levels }),
|
|
162
|
+
...(params.has_storage && {
|
|
163
|
+
storage: [{}],
|
|
164
|
+
}),
|
|
165
|
+
},
|
|
166
|
+
select: SUMMARY_SELECT,
|
|
167
|
+
})
|
|
168
|
+
.unwrap();
|
|
169
|
+
const row = result.createEntityTypeProvision?.entityTypeProvision;
|
|
170
|
+
if (!row)
|
|
171
|
+
throw new Error('createEntityTypeProvision returned no row.');
|
|
172
|
+
const summary = toSummary(row);
|
|
173
|
+
const message = `Created entity type "${summary.name}" (id: ${summary.id}) — entity table "${summary.entityTableName}"${summary.installedModules?.length ? `, modules: ${summary.installedModules.join(', ')}` : ''}. Run run_codegen to pull it into the typed SDK.`;
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: 'text', text: message }],
|
|
176
|
+
details: { success: true, message, entityTypes: [summary] },
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
case 'delete': {
|
|
180
|
+
const result = await modules.entityTypeProvision
|
|
181
|
+
.delete({
|
|
182
|
+
where: { id: params.entity_type_id },
|
|
183
|
+
select: { id: true, name: true, outEntityTableName: true },
|
|
184
|
+
})
|
|
185
|
+
.unwrap();
|
|
186
|
+
const row = result.deleteEntityTypeProvision?.entityTypeProvision;
|
|
187
|
+
const name = row?.name ?? params.entity_type_id;
|
|
188
|
+
const message = `Deleted entity type registration "${name}". Note: the provisioned entity table${row?.outEntityTableName ? ` "${row.outEntityTableName}"` : ''} remains in the API schema with its data.`;
|
|
189
|
+
return {
|
|
190
|
+
content: [{ type: 'text', text: message }],
|
|
191
|
+
details: { success: true, message },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
const message = err instanceof Error ? err.message : 'Failed to manage entity types';
|
|
198
|
+
return { content: [{ type: 'text', text: message }], details: { success: false, message } };
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type HarnessTool } from '@agentic-kit/harness';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export type MutationDetails = {
|
|
4
|
+
success: boolean;
|
|
5
|
+
message: string;
|
|
6
|
+
};
|
|
7
|
+
declare const DeleteTableZod: z.ZodObject<{
|
|
8
|
+
table_name: z.ZodString;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export declare const deleteTableTool: HarnessTool<typeof DeleteTableZod, MutationDetails>;
|
|
11
|
+
declare const CreateFieldZod: z.ZodObject<{
|
|
12
|
+
table_name: z.ZodString;
|
|
13
|
+
field_name: z.ZodString;
|
|
14
|
+
type: z.ZodString;
|
|
15
|
+
is_required: z.ZodOptional<z.ZodBoolean>;
|
|
16
|
+
default_value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNull]>>;
|
|
17
|
+
is_unique: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export declare const createFieldTool: HarnessTool<typeof CreateFieldZod, MutationDetails>;
|
|
20
|
+
declare const UpdateFieldZod: z.ZodObject<{
|
|
21
|
+
table_name: z.ZodString;
|
|
22
|
+
field_name: z.ZodString;
|
|
23
|
+
new_name: z.ZodOptional<z.ZodString>;
|
|
24
|
+
new_type: z.ZodOptional<z.ZodString>;
|
|
25
|
+
is_required: z.ZodOptional<z.ZodBoolean>;
|
|
26
|
+
default_value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNull]>>;
|
|
27
|
+
is_unique: z.ZodOptional<z.ZodBoolean>;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
export declare const updateFieldTool: HarnessTool<typeof UpdateFieldZod, MutationDetails>;
|
|
30
|
+
declare const DeleteFieldZod: z.ZodObject<{
|
|
31
|
+
table_name: z.ZodString;
|
|
32
|
+
field_name: z.ZodString;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export declare const deleteFieldTool: HarnessTool<typeof DeleteFieldZod, MutationDetails>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { toFieldDefault, toFieldType } from '@agentic-kit/harness';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { resolveProjectContext } from '../context';
|
|
4
|
+
import { findUniqueConstraintId, resolveField, resolveSchema, resolveTable } from '../schema-resolve';
|
|
5
|
+
function ok(message) {
|
|
6
|
+
return {
|
|
7
|
+
content: [{ type: 'text', text: message }],
|
|
8
|
+
details: { success: true, message },
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function fail(message) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: 'text', text: message }],
|
|
14
|
+
details: { success: false, message },
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// ── delete_table ──────────────────────────────────────────────────────────
|
|
18
|
+
const DeleteTableZod = z.object({
|
|
19
|
+
table_name: z.string().describe('Name of the table to delete (snake_case)'),
|
|
20
|
+
});
|
|
21
|
+
export const deleteTableTool = {
|
|
22
|
+
name: 'delete_table',
|
|
23
|
+
label: 'Delete table',
|
|
24
|
+
description: 'Permanently delete a table and its data from the project database.',
|
|
25
|
+
promptSnippet: 'delete_table: drop an existing table by name. Destructive — gated.',
|
|
26
|
+
parameters: DeleteTableZod,
|
|
27
|
+
async execute(params, ctx) {
|
|
28
|
+
const resolved = await resolveProjectContext(ctx.cwd);
|
|
29
|
+
if (!resolved.context)
|
|
30
|
+
return fail(resolved.reason);
|
|
31
|
+
try {
|
|
32
|
+
const schema = await resolveSchema(resolved.context);
|
|
33
|
+
const table = resolveTable(schema, params.table_name);
|
|
34
|
+
await resolved.context.api.table
|
|
35
|
+
.delete({ where: { id: table.id }, select: { id: true } })
|
|
36
|
+
.unwrap();
|
|
37
|
+
return ok(`Deleted table ${params.table_name}`);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
return fail(err instanceof Error ? err.message : 'Failed to delete table');
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
// ── create_field ──────────────────────────────────────────────────────────
|
|
45
|
+
const CreateFieldZod = z.object({
|
|
46
|
+
table_name: z.string().describe('Name of the table to add the field to'),
|
|
47
|
+
field_name: z.string().describe('Field name in snake_case'),
|
|
48
|
+
type: z
|
|
49
|
+
.string()
|
|
50
|
+
.describe('PostgreSQL type: text, integer, smallint, numeric, boolean, date, timestamp, timestamptz, time, json, jsonb, inet, email, url, image, upload'),
|
|
51
|
+
is_required: z.boolean().describe('Whether the field is NOT NULL. Defaults to false.').optional(),
|
|
52
|
+
default_value: z
|
|
53
|
+
.union([z.string(), z.null()])
|
|
54
|
+
.describe('SQL default expression, e.g. "now()", "\'draft\'"')
|
|
55
|
+
.optional(),
|
|
56
|
+
is_unique: z
|
|
57
|
+
.boolean()
|
|
58
|
+
.describe('Whether the field has a unique constraint. Defaults to false.')
|
|
59
|
+
.optional(),
|
|
60
|
+
});
|
|
61
|
+
export const createFieldTool = {
|
|
62
|
+
name: 'create_field',
|
|
63
|
+
label: 'Create field',
|
|
64
|
+
description: 'Add a new field (column) to an existing table in the project database.',
|
|
65
|
+
promptSnippet: 'create_field: add a column to an existing table. Gated.',
|
|
66
|
+
parameters: CreateFieldZod,
|
|
67
|
+
async execute(params, ctx) {
|
|
68
|
+
const resolved = await resolveProjectContext(ctx.cwd);
|
|
69
|
+
if (!resolved.context)
|
|
70
|
+
return fail(resolved.reason);
|
|
71
|
+
const { api, databaseId } = resolved.context;
|
|
72
|
+
try {
|
|
73
|
+
const schema = await resolveSchema(resolved.context);
|
|
74
|
+
const table = resolveTable(schema, params.table_name);
|
|
75
|
+
const result = await api.field
|
|
76
|
+
.create({
|
|
77
|
+
data: {
|
|
78
|
+
name: params.field_name,
|
|
79
|
+
type: toFieldType(params.type),
|
|
80
|
+
tableId: table.id,
|
|
81
|
+
databaseId,
|
|
82
|
+
isRequired: params.is_required ?? false,
|
|
83
|
+
defaultValue: toFieldDefault(params.default_value),
|
|
84
|
+
},
|
|
85
|
+
select: { id: true, name: true, type: true },
|
|
86
|
+
})
|
|
87
|
+
.unwrap();
|
|
88
|
+
const createdId = result.createField?.field?.id;
|
|
89
|
+
if (params.is_unique && createdId) {
|
|
90
|
+
try {
|
|
91
|
+
await api.uniqueConstraint
|
|
92
|
+
.create({
|
|
93
|
+
data: {
|
|
94
|
+
tableId: table.id,
|
|
95
|
+
databaseId,
|
|
96
|
+
fieldIds: [createdId],
|
|
97
|
+
name: `${table.name}_${params.field_name}_key`,
|
|
98
|
+
type: 'u',
|
|
99
|
+
},
|
|
100
|
+
select: { id: true },
|
|
101
|
+
})
|
|
102
|
+
.unwrap();
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
console.warn('Failed to create unique constraint:', err);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return ok(`Added field ${params.table_name}.${params.field_name}`);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
return fail(err instanceof Error ? err.message : 'Failed to create field');
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
// ── update_field ──────────────────────────────────────────────────────────
|
|
116
|
+
const UpdateFieldZod = z.object({
|
|
117
|
+
table_name: z.string().describe('Name of the table containing the field'),
|
|
118
|
+
field_name: z.string().describe('Current field name'),
|
|
119
|
+
new_name: z.string().describe('New field name in snake_case (if renaming)').optional(),
|
|
120
|
+
new_type: z.string().describe('New PostgreSQL type (if changing type)').optional(),
|
|
121
|
+
is_required: z.boolean().describe('Set NOT NULL constraint').optional(),
|
|
122
|
+
default_value: z
|
|
123
|
+
.union([z.string(), z.null()])
|
|
124
|
+
.describe('New SQL default expression, or null to remove')
|
|
125
|
+
.optional(),
|
|
126
|
+
is_unique: z.boolean().describe('Set or remove unique constraint').optional(),
|
|
127
|
+
});
|
|
128
|
+
export const updateFieldTool = {
|
|
129
|
+
name: 'update_field',
|
|
130
|
+
label: 'Update field',
|
|
131
|
+
description: 'Modify an existing field: rename, change type, toggle required/unique, or set default.',
|
|
132
|
+
promptSnippet: 'update_field: rename/retype/toggle an existing column. Gated.',
|
|
133
|
+
parameters: UpdateFieldZod,
|
|
134
|
+
async execute(params, ctx) {
|
|
135
|
+
const resolved = await resolveProjectContext(ctx.cwd);
|
|
136
|
+
if (!resolved.context)
|
|
137
|
+
return fail(resolved.reason);
|
|
138
|
+
const { api, databaseId } = resolved.context;
|
|
139
|
+
try {
|
|
140
|
+
const schema = await resolveSchema(resolved.context);
|
|
141
|
+
const table = resolveTable(schema, params.table_name);
|
|
142
|
+
const field = resolveField(table, params.field_name);
|
|
143
|
+
const patch = {};
|
|
144
|
+
if (params.new_name)
|
|
145
|
+
patch.name = params.new_name;
|
|
146
|
+
if (params.new_type)
|
|
147
|
+
patch.type = toFieldType(params.new_type);
|
|
148
|
+
if (params.is_required !== undefined)
|
|
149
|
+
patch.isRequired = params.is_required;
|
|
150
|
+
if (params.default_value !== undefined) {
|
|
151
|
+
patch.defaultValue =
|
|
152
|
+
params.default_value === null ? null : (toFieldDefault(params.default_value) ?? null);
|
|
153
|
+
}
|
|
154
|
+
if (Object.keys(patch).length > 0) {
|
|
155
|
+
await api.field
|
|
156
|
+
.update({
|
|
157
|
+
where: { id: field.id },
|
|
158
|
+
data: patch,
|
|
159
|
+
select: { id: true, name: true, type: true },
|
|
160
|
+
})
|
|
161
|
+
.unwrap();
|
|
162
|
+
}
|
|
163
|
+
if (params.is_unique !== undefined) {
|
|
164
|
+
const existingUniqueId = findUniqueConstraintId(table, field.id);
|
|
165
|
+
if (params.is_unique && !existingUniqueId) {
|
|
166
|
+
try {
|
|
167
|
+
await api.uniqueConstraint
|
|
168
|
+
.create({
|
|
169
|
+
data: {
|
|
170
|
+
tableId: table.id,
|
|
171
|
+
databaseId,
|
|
172
|
+
fieldIds: [field.id],
|
|
173
|
+
name: `${table.name}_${params.new_name ?? field.name}_key`,
|
|
174
|
+
type: 'u',
|
|
175
|
+
},
|
|
176
|
+
select: { id: true },
|
|
177
|
+
})
|
|
178
|
+
.unwrap();
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
console.warn('Failed to create unique constraint:', err);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (!params.is_unique && existingUniqueId) {
|
|
185
|
+
try {
|
|
186
|
+
await api.uniqueConstraint
|
|
187
|
+
.delete({ where: { id: existingUniqueId }, select: { id: true } })
|
|
188
|
+
.unwrap();
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
console.warn('Failed to delete unique constraint:', err);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return ok(`Updated field ${params.table_name}.${params.field_name}`);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return fail(err instanceof Error ? err.message : 'Failed to update field');
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
// ── delete_field ──────────────────────────────────────────────────────────
|
|
203
|
+
const DeleteFieldZod = z.object({
|
|
204
|
+
table_name: z.string().describe('Name of the table containing the field'),
|
|
205
|
+
field_name: z.string().describe('Name of the field to delete'),
|
|
206
|
+
});
|
|
207
|
+
export const deleteFieldTool = {
|
|
208
|
+
name: 'delete_field',
|
|
209
|
+
label: 'Delete field',
|
|
210
|
+
description: 'Permanently delete a field (column) from an existing table.',
|
|
211
|
+
promptSnippet: 'delete_field: drop a column from a table by name. Destructive — gated.',
|
|
212
|
+
parameters: DeleteFieldZod,
|
|
213
|
+
async execute(params, ctx) {
|
|
214
|
+
const resolved = await resolveProjectContext(ctx.cwd);
|
|
215
|
+
if (!resolved.context)
|
|
216
|
+
return fail(resolved.reason);
|
|
217
|
+
try {
|
|
218
|
+
const schema = await resolveSchema(resolved.context);
|
|
219
|
+
const table = resolveTable(schema, params.table_name);
|
|
220
|
+
const field = resolveField(table, params.field_name);
|
|
221
|
+
await resolved.context.api.field
|
|
222
|
+
.delete({ where: { id: field.id }, select: { id: true } })
|
|
223
|
+
.unwrap();
|
|
224
|
+
return ok(`Deleted field ${params.table_name}.${params.field_name}`);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
return fail(err instanceof Error ? err.message : 'Failed to delete field');
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BlueprintZod, type HarnessTool } from '@agentic-kit/harness';
|
|
2
|
+
type ProvisionedField = {
|
|
3
|
+
name: string;
|
|
4
|
+
type: string;
|
|
5
|
+
isRequired: boolean;
|
|
6
|
+
defaultValue: string | null;
|
|
7
|
+
};
|
|
8
|
+
type ProvisionedTable = {
|
|
9
|
+
name: string;
|
|
10
|
+
fields: ProvisionedField[];
|
|
11
|
+
policies: string[];
|
|
12
|
+
relationCount: number;
|
|
13
|
+
};
|
|
14
|
+
export type ProvisionBlueprintDetails = {
|
|
15
|
+
created: number;
|
|
16
|
+
total: number;
|
|
17
|
+
tables: ProvisionedTable[];
|
|
18
|
+
error: string | null;
|
|
19
|
+
};
|
|
20
|
+
export declare const provisionBlueprintTool: HarnessTool<typeof BlueprintZod, ProvisionBlueprintDetails>;
|
|
21
|
+
export {};
|