feat: add core registry schemas
This commit is contained in:
165
scripts/seed.ts
Normal file
165
scripts/seed.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import "dotenv/config";
|
||||
|
||||
import { existsSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { and, eq, or } from "drizzle-orm";
|
||||
|
||||
import {
|
||||
type Persona,
|
||||
type PublishedRegistryRow,
|
||||
type RegistryEntry,
|
||||
type Template,
|
||||
assertNoReferencedRegistryDeletions,
|
||||
buildRegistrySeedPlan,
|
||||
loadPersonaFiles,
|
||||
loadTemplateFiles,
|
||||
} from "../packages/core/src/index.js";
|
||||
import { createDbClient } from "../packages/db/src/client.js";
|
||||
import {
|
||||
agentPersonas,
|
||||
runBindings,
|
||||
runs,
|
||||
workflowTemplates,
|
||||
} from "../packages/db/src/schema/index.js";
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
|
||||
if (!databaseUrl) {
|
||||
throw new Error("DATABASE_URL is required to seed registries");
|
||||
}
|
||||
|
||||
const personasDirectory = fileURLToPath(new URL("../docs/schemas/personas", import.meta.url));
|
||||
const templatesDirectory = fileURLToPath(new URL("../docs/schemas/templates", import.meta.url));
|
||||
const client = createDbClient(databaseUrl);
|
||||
|
||||
try {
|
||||
const personas = loadRegistryDirectory(personasDirectory, loadPersonaFiles);
|
||||
const templates = loadRegistryDirectory(templatesDirectory, loadTemplateFiles);
|
||||
const result = {
|
||||
personas: await seedPersonas(personas),
|
||||
templates: await seedTemplates(templates),
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
|
||||
function loadRegistryDirectory<TDefinition>(
|
||||
directory: string,
|
||||
load: (directory: string) => RegistryEntry<TDefinition>[],
|
||||
) {
|
||||
if (!existsSync(directory)) {
|
||||
throw new Error(`Registry directory does not exist: ${directory}`);
|
||||
}
|
||||
|
||||
return load(directory);
|
||||
}
|
||||
|
||||
async function seedPersonas(entries: RegistryEntry<Persona>[]) {
|
||||
const existing = await client.db
|
||||
.select({
|
||||
id: agentPersonas.id,
|
||||
name: agentPersonas.name,
|
||||
version: agentPersonas.version,
|
||||
hash: agentPersonas.hash,
|
||||
})
|
||||
.from(agentPersonas);
|
||||
const publishedRows: PublishedRegistryRow[] = [];
|
||||
|
||||
for (const row of existing) {
|
||||
const reference = await client.db
|
||||
.select({ id: runBindings.id })
|
||||
.from(runBindings)
|
||||
.where(or(eq(runBindings.personaId, row.id), eq(runBindings.personaHash, row.hash)))
|
||||
.limit(1);
|
||||
publishedRows.push({ ...row, referencedByRun: reference.length > 0 });
|
||||
}
|
||||
|
||||
const plan = buildRegistrySeedPlan(entries, publishedRows);
|
||||
assertNoReferencedRegistryDeletions("persona", plan);
|
||||
await deleteUnreferencedPersonas(plan.missingUnreferenced);
|
||||
for (const entry of plan.inserts) {
|
||||
await client.db.insert(agentPersonas).values({
|
||||
name: entry.name,
|
||||
version: entry.version,
|
||||
hash: entry.hash,
|
||||
definition: entry.definition,
|
||||
});
|
||||
}
|
||||
|
||||
return summarizePlan(plan);
|
||||
}
|
||||
|
||||
async function seedTemplates(entries: RegistryEntry<Template>[]) {
|
||||
const existing = await client.db
|
||||
.select({
|
||||
id: workflowTemplates.id,
|
||||
name: workflowTemplates.name,
|
||||
version: workflowTemplates.version,
|
||||
hash: workflowTemplates.hash,
|
||||
})
|
||||
.from(workflowTemplates);
|
||||
const publishedRows: PublishedRegistryRow[] = [];
|
||||
|
||||
for (const row of existing) {
|
||||
const reference = await client.db
|
||||
.select({ id: runs.id })
|
||||
.from(runs)
|
||||
.where(or(eq(runs.templateId, row.id), eq(runs.templateHash, row.hash)))
|
||||
.limit(1);
|
||||
publishedRows.push({ ...row, referencedByRun: reference.length > 0 });
|
||||
}
|
||||
|
||||
const plan = buildRegistrySeedPlan(entries, publishedRows);
|
||||
assertNoReferencedRegistryDeletions("template", plan);
|
||||
await deleteUnreferencedTemplates(plan.missingUnreferenced);
|
||||
for (const entry of plan.inserts) {
|
||||
await client.db.insert(workflowTemplates).values({
|
||||
name: entry.name,
|
||||
version: entry.version,
|
||||
hash: entry.hash,
|
||||
definition: entry.definition,
|
||||
});
|
||||
}
|
||||
|
||||
return summarizePlan(plan);
|
||||
}
|
||||
|
||||
function summarizePlan<TDefinition>(plan: ReturnType<typeof buildRegistrySeedPlan<TDefinition>>) {
|
||||
return {
|
||||
deleted: plan.missingUnreferenced.length,
|
||||
inserted: plan.inserts.length,
|
||||
unchanged: plan.unchanged.length,
|
||||
missingReferenced: plan.missingReferenced.map((entry) => `${entry.name}@${entry.version}`),
|
||||
missingUnreferenced: plan.missingUnreferenced.map((entry) => `${entry.name}@${entry.version}`),
|
||||
};
|
||||
}
|
||||
|
||||
async function deleteUnreferencedPersonas(entries: PublishedRegistryRow[]) {
|
||||
for (const entry of entries) {
|
||||
await client.db
|
||||
.delete(agentPersonas)
|
||||
.where(
|
||||
and(
|
||||
eq(agentPersonas.name, entry.name),
|
||||
eq(agentPersonas.version, entry.version),
|
||||
eq(agentPersonas.hash, entry.hash),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteUnreferencedTemplates(entries: PublishedRegistryRow[]) {
|
||||
for (const entry of entries) {
|
||||
await client.db
|
||||
.delete(workflowTemplates)
|
||||
.where(
|
||||
and(
|
||||
eq(workflowTemplates.name, entry.name),
|
||||
eq(workflowTemplates.version, entry.version),
|
||||
eq(workflowTemplates.hash, entry.hash),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user