export-import.ts75 lines · main
1import { brivenError, NotFoundError } from '@briven/shared';
2import { eq } from 'drizzle-orm';
3
4import { getDb } from '../db/client.js';
5import { deployments, projects } from '../db/schema.js';
6import { getCurrentDeployment, getDeploymentBundle } from './deployments.js';
7
8/**
9 * Project export — schema + functions, bundled as a single JSON object.
10 *
11 * Phase 2 first slice. Data movement (pg_dump streaming) is a follow-up
12 * gated on the data-plane shell-token path; this slice ships only what's
13 * already in the meta-DB so it can land without touching the data plane.
14 *
15 * Wire format is `briven-export.v1.json`:
16 * {
17 * manifest: { version: 1, sourceProjectId, sourceDeploymentId, exportedAt }
18 * schema: SchemaSnapshot — the SchemaDef shape from @briven/schema
19 * functions: { [filename]: source } — keys are relative paths under briven/functions/
20 * }
21 *
22 * `briven import` POSTs this back into a target project's `/v1/projects/:id/deployments`.
23 */
24
25export const EXPORT_VERSION = 1 as const;
26
27export interface ProjectExport {
28 readonly manifest: {
29 readonly version: typeof EXPORT_VERSION;
30 readonly sourceProjectId: string;
31 readonly sourceProjectName: string;
32 readonly sourceDeploymentId: string;
33 readonly exportedAt: string;
34 };
35 readonly schema: Record<string, unknown> | null;
36 readonly functions: Readonly<Record<string, string>>;
37}
38
39export async function buildProjectExport(projectId: string): Promise<ProjectExport> {
40 const db = getDb();
41 const [project] = await db
42 .select({ id: projects.id, name: projects.name })
43 .from(projects)
44 .where(eq(projects.id, projectId))
45 .limit(1);
46 if (!project) throw new NotFoundError('project', projectId);
47
48 const current = await getCurrentDeployment(projectId);
49 if (!current) {
50 throw new brivenError(
51 'no_deployment',
52 'project has no eligible deployment to export — deploy at least once first',
53 { status: 409 },
54 );
55 }
56
57 const [snapshotRow] = await db
58 .select({ schemaSnapshot: deployments.schemaSnapshot })
59 .from(deployments)
60 .where(eq(deployments.id, current.id))
61 .limit(1);
62 const bundle = (await getDeploymentBundle(current.id)) ?? {};
63
64 return {
65 manifest: {
66 version: EXPORT_VERSION,
67 sourceProjectId: project.id,
68 sourceProjectName: project.name,
69 sourceDeploymentId: current.id,
70 exportedAt: new Date().toISOString(),
71 },
72 schema: (snapshotRow?.schemaSnapshot as Record<string, unknown> | null) ?? null,
73 functions: bundle,
74 };
75}