blob: ef868da3e58cb94ed97db8a09c479b91b7a77c6e [file] [log] [blame]
import { z } from "zod";
import { Node } from "@xyflow/react";
import {
ConfigSchema,
Domain,
DomainSchema,
ServiceType,
ServiceTypeSchema,
VolumeType,
VolumeTypeSchema,
} from "./types.js";
export const serviceAnalyzisSchema = z.object({
name: z.string(),
location: z.string(),
configVars: z.array(
z.object({
name: z.string(),
category: z.enum(["CommandLineFlag", "EnvironmentVariable"]),
type: z.optional(z.enum(["String", "Number", "Boolean"])),
semanticType: z.optional(
z.enum([
"EXPANDED_ENV_VAR",
"PORT",
"FILESYSTEM_PATH",
"DATABASE_URL",
"SQLITE_PATH",
"POSTGRES_URL",
"POSTGRES_PASSWORD",
"POSTGRES_USER",
"POSTGRES_DB",
"POSTGRES_PORT",
"POSTGRES_HOST",
"POSTGRES_SSL",
"MONGO_URL",
"MONGO_PASSWORD",
"MONGO_USER",
"MONGO_DB",
"MONGO_PORT",
"MONGO_HOST",
"MONGO_SSL",
]),
),
}),
),
});
export const BoundEnvVarSchema = z.union([
z.object({
id: z.string(),
source: z.null(),
name: z.string(),
value: z.string(),
isEditting: z.boolean().optional(),
}),
z.object({
id: z.string(),
source: z.string().nullable(),
portId: z.string(),
name: z.string(),
alias: z.string(),
isEditting: z.boolean(),
}),
z.object({
id: z.string(),
source: z.string().nullable(),
name: z.string(),
alias: z.string(),
isEditting: z.boolean(),
}),
z.object({
id: z.string(),
source: z.string().nullable(),
name: z.string(),
isEditting: z.boolean(),
}),
z.object({
id: z.string(),
source: z.string().nullable(),
}),
]);
export type BoundEnvVar =
| {
id: string;
source: string | null;
}
| {
id: string;
source: string | null;
name: string;
isEditting: boolean;
}
| {
id: string;
source: string | null;
name: string;
alias: string;
isEditting: boolean;
}
| {
id: string;
source: string | null;
portId: string;
name: string;
alias: string;
isEditting: boolean;
}
| {
id: string;
source: null;
name: string;
value: string;
isEditting?: boolean;
};
export type EnvVar = {
name: string;
value: string;
};
export type InitData = {
label: string;
envVars: BoundEnvVar[];
ports: Port[];
};
export type NodeData = InitData & {
activeField?: string | undefined;
state?: string | null;
};
export type PortConnectedTo = {
serviceId: string;
portId: string;
};
export type NetworkData = NodeData & {
domain: string;
};
export type NetworkNode = Node<NetworkData> & {
type: "network";
};
export type GatewayHttpsData = NodeData & {
readonly?: boolean;
network?: string;
subdomain?: string;
https?: PortConnectedTo;
auth?: {
enabled: boolean;
groups?: string[];
noAuthPathPatterns?: string[];
};
};
export type GatewayHttpsNode = Node<GatewayHttpsData> & {
type: "gateway-https";
};
export type GatewayTCPData = NodeData & {
readonly?: boolean;
network?: string;
subdomain?: string;
exposed: PortConnectedTo[];
selected?: {
serviceId?: string;
portId?: string;
};
};
export type GatewayTCPNode = Node<GatewayTCPData> & {
type: "gateway-tcp";
};
export const PortSchema = z.object({
id: z.string(),
name: z.string(),
value: z.number(),
});
export type Port = z.infer<typeof PortSchema>;
export type ServiceData = NodeData & {
type: ServiceType;
repository?:
| {
id: number;
repoNodeId: string;
}
| {
id: number;
repoNodeId: string;
branch: string;
}
| {
id: number;
repoNodeId: string;
branch: string;
rootDir: string;
};
volume?: string[];
preBuildCommands?: string;
isChoosingPortToConnect?: boolean;
dev?:
| {
enabled: false;
expose?: Domain;
}
| {
enabled: true;
mode: "VM";
expose?: Domain;
codeServerNodeId: string;
sshNodeId: string;
}
| {
enabled: true;
mode: "PROXY";
address: string;
};
model?: {
name: "gemini" | "claude";
apiKey?: string;
};
info?: z.infer<typeof serviceAnalyzisSchema>;
};
export type ServiceNode = Node<ServiceData> & {
type: "app";
};
export type VolumeData = NodeData & {
type: VolumeType;
size: string;
attachedTo: string[];
};
export type VolumeNode = Node<VolumeData> & {
type: "volume";
};
export type PostgreSQLData = NodeData & {};
export type PostgreSQLNode = Node<PostgreSQLData> & {
type: "postgresql";
};
export type MongoDBData = NodeData & {};
export type MongoDBNode = Node<MongoDBData> & {
type: "mongodb";
};
export type GithubData = NodeData & {
repository?: {
id: number;
sshURL: string;
fullName: string;
};
};
export type GithubNode = Node<GithubData> & {
type: "github";
};
export type NANode = Node<NodeData> & {
type: undefined;
};
export type AppNode =
| NetworkNode
| GatewayHttpsNode
| GatewayTCPNode
| ServiceNode
| VolumeNode
| PostgreSQLNode
| MongoDBNode
| GithubNode
| NANode;
export type NodeType = Exclude<Pick<AppNode, "type">["type"], undefined>;
export const NetworkSchema = z.object({
name: z.string().min(1),
domain: z.string().min(1),
hasAuth: z.boolean(),
});
export type Network = z.infer<typeof NetworkSchema>;
export const accessSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("https"),
name: z.string(),
address: z.string(),
agentName: z.string().optional(),
}),
z.object({
type: z.literal("ssh"),
name: z.string(),
host: z.string(),
port: z.number(),
}),
z.object({
type: z.literal("tcp"),
name: z.string(),
host: z.string(),
port: z.number(),
}),
z.object({
type: z.literal("udp"),
name: z.string(),
host: z.string(),
port: z.number(),
}),
z.object({
type: z.literal("postgresql"),
name: z.string(),
host: z.string(),
port: z.number(),
database: z.string(),
username: z.string(),
password: z.string(),
}),
z.object({
type: z.literal("mongodb"),
name: z.string(),
host: z.string(),
port: z.number(),
database: z.string(),
username: z.string(),
password: z.string(),
}),
z.object({
type: z.literal("env_var"),
name: z.string(),
var: z.string(),
}),
]);
export const serviceInfoSchema = z.object({
name: z.string(),
workers: z.array(
z.object({
id: z.string(),
commit: z
.object({
hash: z.string(),
message: z.string(),
})
.nullable()
.optional(),
commands: z
.array(
z.object({
command: z.string(),
state: z.string(),
}),
)
.optional(),
}),
),
});
export const envSchema = z.object({
deployKeyPublic: z.string().optional(),
instanceId: z.string().optional(),
networks: z.array(
z.object({
name: z.string(),
domain: z.string(),
hasAuth: z.boolean(),
}),
),
integrations: z.object({
github: z.boolean(),
gemini: z.boolean(),
anthropic: z.boolean(),
}),
services: z.array(serviceInfoSchema),
user: z.object({
id: z.string(),
username: z.string(),
}),
access: z.array(accessSchema),
});
export type ServiceInfo = z.infer<typeof serviceInfoSchema>;
export type Env = z.infer<typeof envSchema>;
export type Access = z.infer<typeof accessSchema>;
export type AgentAccess = Required<Extract<Access, { type: "https" }>>;
const NodeBaseSchema = z.object({
id: z.string(),
position: z.object({
x: z.number(),
y: z.number(),
}),
});
export const NodeBaseDataSchema = z.object({
label: z.string(),
envVars: z.array(BoundEnvVarSchema),
ports: z.array(PortSchema),
});
export const NetworkNodeSchema = z
.object({
type: z.literal("network"),
data: z
.object({
domain: z.string(),
})
.extend(NodeBaseDataSchema.shape),
})
.extend(NodeBaseSchema.shape);
export const GithubNodeSchema = z
.object({
type: z.literal("github"),
data: z
.object({
repository: z
.object({
id: z.number(),
sshURL: z.string(),
fullName: z.string(),
})
.optional(),
})
.extend(NodeBaseDataSchema.shape),
})
.extend(NodeBaseSchema.shape);
export const GatewayHttpsNodeSchema = z
.object({
type: z.literal("gateway-https"),
data: z
.object({
readonly: z.boolean().optional(), // TODO: remove this
network: z.string().optional(),
subdomain: z.string().optional(),
https: z
.object({
serviceId: z.string(),
portId: z.string(),
})
.optional(),
auth: z
.object({
enabled: z.boolean(),
groups: z.array(z.string()).optional(),
noAuthPathPatterns: z.array(z.string()).optional(),
})
.optional(),
})
.extend(NodeBaseDataSchema.shape),
})
.extend(NodeBaseSchema.shape);
export const GatewayTCPNodeSchema = z
.object({
type: z.literal("gateway-tcp"),
data: z
.object({
readonly: z.boolean().optional(),
network: z.string().optional(),
subdomain: z.string().optional(),
exposed: z.array(
z.object({
serviceId: z.string(),
portId: z.string(),
}),
),
selected: z
.object({
serviceId: z.string().optional(),
portId: z.string().optional(),
})
.optional(),
})
.extend(NodeBaseDataSchema.shape),
})
.extend(NodeBaseSchema.shape);
export const ServiceNodeSchema = z
.object({
type: z.literal("app"),
data: z
.object({
type: ServiceTypeSchema,
repository: z
.union([
z.object({
id: z.number(),
repoNodeId: z.string(),
branch: z.string(),
rootDir: z.string(),
}),
z.object({
id: z.number(),
repoNodeId: z.string(),
branch: z.string(),
}),
z.object({
id: z.number(),
repoNodeId: z.string(),
}),
])
.optional(),
volume: z.array(z.string()).optional(),
preBuildCommands: z.string().optional(),
isChoosingPortToConnect: z.boolean().optional(),
dev: z
.union([
z.object({
enabled: z.literal(false),
expose: DomainSchema.optional(),
}),
z.object({
enabled: z.literal(true),
mode: z.literal("VM"),
expose: DomainSchema.optional(),
codeServerNodeId: z.string(),
sshNodeId: z.string(),
}),
z.object({
enabled: z.literal(true),
mode: z.literal("PROXY"),
address: z.string(),
}),
])
.optional(),
model: z
.object({
name: z.enum(["gemini", "claude"]),
apiKey: z.string().optional(),
})
.optional(),
info: serviceAnalyzisSchema.optional(),
ports: z.array(
z.object({
id: z.string(),
name: z.string(),
value: z.number(),
}),
),
activeField: z.string().optional(),
state: z.string().nullable().optional(),
})
.extend(NodeBaseDataSchema.shape),
})
.extend(NodeBaseSchema.shape);
export const VolumeNodeSchema = z
.object({
type: z.literal("volume"),
data: z
.object({
size: z.string(),
type: VolumeTypeSchema,
attachedTo: z.array(z.string()),
})
.extend(NodeBaseDataSchema.shape),
})
.extend(NodeBaseSchema.shape);
export const PostgreSQLNodeSchema = z
.object({
type: z.literal("postgresql"),
data: z.object({}).extend(NodeBaseDataSchema.shape),
})
.extend(NodeBaseSchema.shape);
export const MongoDBNodeSchema = z
.object({
type: z.literal("mongodb"),
data: z.object({}).extend(NodeBaseDataSchema.shape),
})
.extend(NodeBaseSchema.shape);
export const NodeSchema = z.discriminatedUnion("type", [
NetworkNodeSchema,
GithubNodeSchema,
GatewayHttpsNodeSchema,
GatewayTCPNodeSchema,
ServiceNodeSchema,
VolumeNodeSchema,
PostgreSQLNodeSchema,
MongoDBNodeSchema,
]);
export const EdgeSchema = z.object({
id: z.string(),
source: z.string(),
sourceHandle: z.string().optional(),
target: z.string(),
targetHandle: z.string().optional(),
});
export const ViewportTransformSchema = z.object({
transformX: z.number(),
transformY: z.number(),
transformZoom: z.number(),
width: z.number(),
height: z.number(),
});
export const GraphSchema = z.object({
nodes: z.array(NodeSchema),
edges: z.array(EdgeSchema),
viewport: z
.object({
x: z.number(),
y: z.number(),
zoom: z.number(),
})
.optional(),
viewportTransform: ViewportTransformSchema.optional(),
});
export const GraphOrConfigSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("graph"),
graph: GraphSchema,
}),
z.object({
type: z.literal("config"),
config: ConfigSchema,
}),
]);
export const GraphConfigOrDraft = z.discriminatedUnion("type", [
z.object({
type: z.literal("graph"),
graph: GraphSchema,
}),
z.object({
type: z.literal("config"),
config: ConfigSchema,
}),
z.object({
type: z.literal("draft"),
}),
]);
export type Edge = z.infer<typeof EdgeSchema>;
export type ViewportTransform = z.infer<typeof ViewportTransformSchema>;
export type Graph = z.infer<typeof GraphSchema>;