| 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>; |