blob: a4634cb7de859461f8d7b9866395b0fba01bd4ae [file] [log] [blame]
gioc31bf142025-06-16 07:48:20 +00001import { z } from "zod";
2import { Node } from "@xyflow/react";
gio10ff1342025-07-05 10:22:15 +00003import {
4 ConfigSchema,
5 Domain,
6 DomainSchema,
7 ServiceType,
8 ServiceTypeSchema,
9 VolumeType,
10 VolumeTypeSchema,
11} from "./types.js";
gioc31bf142025-06-16 07:48:20 +000012
13export const serviceAnalyzisSchema = z.object({
14 name: z.string(),
15 location: z.string(),
16 configVars: z.array(
17 z.object({
18 name: z.string(),
19 category: z.enum(["CommandLineFlag", "EnvironmentVariable"]),
20 type: z.optional(z.enum(["String", "Number", "Boolean"])),
21 semanticType: z.optional(
22 z.enum([
23 "EXPANDED_ENV_VAR",
24 "PORT",
25 "FILESYSTEM_PATH",
26 "DATABASE_URL",
27 "SQLITE_PATH",
28 "POSTGRES_URL",
29 "POSTGRES_PASSWORD",
30 "POSTGRES_USER",
31 "POSTGRES_DB",
32 "POSTGRES_PORT",
33 "POSTGRES_HOST",
34 "POSTGRES_SSL",
35 "MONGO_URL",
36 "MONGO_PASSWORD",
37 "MONGO_USER",
38 "MONGO_DB",
39 "MONGO_PORT",
40 "MONGO_HOST",
41 "MONGO_SSL",
42 ]),
43 ),
44 }),
45 ),
46});
47
gio10ff1342025-07-05 10:22:15 +000048export const BoundEnvVarSchema = z.union([
49 z.object({
50 id: z.string(),
51 source: z.null(),
52 name: z.string(),
53 value: z.string(),
54 isEditting: z.boolean().optional(),
55 }),
56 z.object({
57 id: z.string(),
58 source: z.string().nullable(),
59 portId: z.string(),
60 name: z.string(),
61 alias: z.string(),
62 isEditting: z.boolean(),
63 }),
64 z.object({
65 id: z.string(),
66 source: z.string().nullable(),
67 name: z.string(),
68 alias: z.string(),
69 isEditting: z.boolean(),
70 }),
71 z.object({
72 id: z.string(),
73 source: z.string().nullable(),
74 name: z.string(),
75 isEditting: z.boolean(),
76 }),
77 z.object({
78 id: z.string(),
79 source: z.string().nullable(),
80 }),
81]);
82
gioc31bf142025-06-16 07:48:20 +000083export type BoundEnvVar =
84 | {
85 id: string;
86 source: string | null;
87 }
88 | {
89 id: string;
90 source: string | null;
91 name: string;
92 isEditting: boolean;
93 }
94 | {
95 id: string;
96 source: string | null;
97 name: string;
98 alias: string;
99 isEditting: boolean;
100 }
101 | {
102 id: string;
103 source: string | null;
104 portId: string;
105 name: string;
106 alias: string;
107 isEditting: boolean;
gio1dacf1c2025-07-03 16:39:04 +0000108 }
109 | {
110 id: string;
111 source: null;
112 name: string;
113 value: string;
114 isEditting?: boolean;
gioc31bf142025-06-16 07:48:20 +0000115 };
116
117export type EnvVar = {
118 name: string;
119 value: string;
120};
121
122export type InitData = {
123 label: string;
124 envVars: BoundEnvVar[];
125 ports: Port[];
126};
127
128export type NodeData = InitData & {
129 activeField?: string | undefined;
130 state?: string | null;
131};
132
133export type PortConnectedTo = {
134 serviceId: string;
135 portId: string;
136};
137
138export type NetworkData = NodeData & {
139 domain: string;
140};
141
142export type NetworkNode = Node<NetworkData> & {
143 type: "network";
144};
145
146export type GatewayHttpsData = NodeData & {
147 readonly?: boolean;
148 network?: string;
149 subdomain?: string;
150 https?: PortConnectedTo;
151 auth?: {
152 enabled: boolean;
153 groups: string[];
154 noAuthPathPatterns: string[];
155 };
156};
157
158export type GatewayHttpsNode = Node<GatewayHttpsData> & {
159 type: "gateway-https";
160};
161
162export type GatewayTCPData = NodeData & {
163 readonly?: boolean;
164 network?: string;
165 subdomain?: string;
166 exposed: PortConnectedTo[];
167 selected?: {
168 serviceId?: string;
169 portId?: string;
170 };
171};
172
173export type GatewayTCPNode = Node<GatewayTCPData> & {
174 type: "gateway-tcp";
175};
176
gio10ff1342025-07-05 10:22:15 +0000177export const PortSchema = z.object({
178 id: z.string(),
179 name: z.string(),
180 value: z.number(),
181});
182
183export type Port = z.infer<typeof PortSchema>;
gioc31bf142025-06-16 07:48:20 +0000184
185export type ServiceData = NodeData & {
186 type: ServiceType;
187 repository?:
188 | {
189 id: number;
190 repoNodeId: string;
191 }
192 | {
193 id: number;
194 repoNodeId: string;
195 branch: string;
196 }
197 | {
198 id: number;
199 repoNodeId: string;
200 branch: string;
201 rootDir: string;
202 };
gio10ff1342025-07-05 10:22:15 +0000203 volume?: string[];
204 preBuildCommands?: string;
205 isChoosingPortToConnect?: boolean;
gioc31bf142025-06-16 07:48:20 +0000206 dev?:
207 | {
208 enabled: false;
209 expose?: Domain;
210 }
211 | {
212 enabled: true;
213 expose?: Domain;
214 codeServerNodeId: string;
215 sshNodeId: string;
216 };
gio69ff7592025-07-03 06:27:21 +0000217 model?: {
218 name: "gemini" | "claude";
219 apiKey?: string;
gio69148322025-06-19 23:16:12 +0400220 };
gioc31bf142025-06-16 07:48:20 +0000221 info?: z.infer<typeof serviceAnalyzisSchema>;
222};
223
224export type ServiceNode = Node<ServiceData> & {
225 type: "app";
226};
227
228export type VolumeData = NodeData & {
229 type: VolumeType;
230 size: string;
231 attachedTo: string[];
232};
233
234export type VolumeNode = Node<VolumeData> & {
235 type: "volume";
236};
237
gio10ff1342025-07-05 10:22:15 +0000238export type PostgreSQLData = NodeData & {};
gioc31bf142025-06-16 07:48:20 +0000239
240export type PostgreSQLNode = Node<PostgreSQLData> & {
241 type: "postgresql";
242};
243
gio10ff1342025-07-05 10:22:15 +0000244export type MongoDBData = NodeData & {};
gioc31bf142025-06-16 07:48:20 +0000245
246export type MongoDBNode = Node<MongoDBData> & {
247 type: "mongodb";
248};
249
250export type GithubData = NodeData & {
251 repository?: {
252 id: number;
253 sshURL: string;
254 fullName: string;
255 };
256};
257
258export type GithubNode = Node<GithubData> & {
259 type: "github";
260};
261
262export type NANode = Node<NodeData> & {
263 type: undefined;
264};
265
266export type AppNode =
267 | NetworkNode
268 | GatewayHttpsNode
269 | GatewayTCPNode
270 | ServiceNode
271 | VolumeNode
272 | PostgreSQLNode
273 | MongoDBNode
274 | GithubNode
275 | NANode;
276
277export type NodeType = Exclude<Pick<AppNode, "type">["type"], undefined>;
278
gio10ff1342025-07-05 10:22:15 +0000279export const NetworkSchema = z.object({
gioc31bf142025-06-16 07:48:20 +0000280 name: z.string().min(1),
281 domain: z.string().min(1),
282 hasAuth: z.boolean(),
283});
284
gio10ff1342025-07-05 10:22:15 +0000285export type Network = z.infer<typeof NetworkSchema>;
gioc31bf142025-06-16 07:48:20 +0000286
287export const accessSchema = z.discriminatedUnion("type", [
288 z.object({
289 type: z.literal("https"),
290 name: z.string(),
291 address: z.string(),
giocc5ce582025-06-25 07:45:21 +0400292 agentName: z.string().optional(),
gioc31bf142025-06-16 07:48:20 +0000293 }),
294 z.object({
295 type: z.literal("ssh"),
296 name: z.string(),
297 host: z.string(),
298 port: z.number(),
299 }),
300 z.object({
301 type: z.literal("tcp"),
302 name: z.string(),
303 host: z.string(),
304 port: z.number(),
305 }),
306 z.object({
307 type: z.literal("udp"),
308 name: z.string(),
309 host: z.string(),
310 port: z.number(),
311 }),
312 z.object({
313 type: z.literal("postgresql"),
314 name: z.string(),
315 host: z.string(),
316 port: z.number(),
317 database: z.string(),
318 username: z.string(),
319 password: z.string(),
320 }),
321 z.object({
322 type: z.literal("mongodb"),
323 name: z.string(),
324 host: z.string(),
325 port: z.number(),
326 database: z.string(),
327 username: z.string(),
328 password: z.string(),
329 }),
330]);
331
332export const serviceInfoSchema = z.object({
333 name: z.string(),
334 workers: z.array(
335 z.object({
336 id: z.string(),
gioa70535a2025-07-02 15:50:25 +0000337 commit: z
338 .object({
gioc31bf142025-06-16 07:48:20 +0000339 hash: z.string(),
340 message: z.string(),
gioa70535a2025-07-02 15:50:25 +0000341 })
342 .nullable()
343 .optional(),
344 commands: z
345 .array(
gioc31bf142025-06-16 07:48:20 +0000346 z.object({
347 command: z.string(),
348 state: z.string(),
349 }),
gioa70535a2025-07-02 15:50:25 +0000350 )
351 .optional(),
gioc31bf142025-06-16 07:48:20 +0000352 }),
353 ),
354});
355
356export const envSchema = z.object({
gio69ff7592025-07-03 06:27:21 +0000357 deployKeyPublic: z.string().optional(),
358 instanceId: z.string().optional(),
359 networks: z.array(
360 z.object({
361 name: z.string(),
362 domain: z.string(),
363 hasAuth: z.boolean(),
364 }),
365 ),
gioc31bf142025-06-16 07:48:20 +0000366 integrations: z.object({
367 github: z.boolean(),
gio69148322025-06-19 23:16:12 +0400368 gemini: z.boolean(),
gio69ff7592025-07-03 06:27:21 +0000369 anthropic: z.boolean(),
gioc31bf142025-06-16 07:48:20 +0000370 }),
371 services: z.array(serviceInfoSchema),
372 user: z.object({
373 id: z.string(),
374 username: z.string(),
375 }),
376 access: z.array(accessSchema),
377});
378
379export type ServiceInfo = z.infer<typeof serviceInfoSchema>;
380export type Env = z.infer<typeof envSchema>;
giocc5ce582025-06-25 07:45:21 +0400381export type Access = z.infer<typeof accessSchema>;
gio74c6f752025-07-05 04:10:58 +0000382export type AgentAccess = Required<Extract<Access, { type: "https" }>>;
gio10ff1342025-07-05 10:22:15 +0000383
384const NodeBaseSchema = z.object({
385 id: z.string(),
386 position: z.object({
387 x: z.number(),
388 y: z.number(),
389 }),
390});
391
392export const NodeBaseDataSchema = z.object({
393 label: z.string(),
394 envVars: z.array(BoundEnvVarSchema),
395 ports: z.array(PortSchema),
396});
397
398export const NetworkNodeSchema = z
399 .object({
400 type: z.literal("network"),
401 data: z
402 .object({
403 domain: z.string(),
404 })
405 .extend(NodeBaseDataSchema.shape),
406 })
407 .extend(NodeBaseSchema.shape);
408
409export const GithubNodeSchema = z
410 .object({
411 type: z.literal("github"),
412 data: z
413 .object({
414 repository: z
415 .object({
416 id: z.number(),
417 sshURL: z.string(),
418 fullName: z.string(),
419 })
420 .optional(),
421 })
422 .extend(NodeBaseDataSchema.shape),
423 })
424 .extend(NodeBaseSchema.shape);
425
426export const GatewayHttpsNodeSchema = z
427 .object({
428 type: z.literal("gateway-https"),
429 data: z
430 .object({
431 readonly: z.boolean().optional(), // TODO: remove this
432 network: z.string().optional(),
433 subdomain: z.string().optional(),
434 https: z
435 .object({
436 serviceId: z.string(),
437 portId: z.string(),
438 })
439 .optional(),
440 })
441 .extend(NodeBaseDataSchema.shape),
442 })
443 .extend(NodeBaseSchema.shape);
444
445export const GatewayTCPNodeSchema = z
446 .object({
447 type: z.literal("gateway-tcp"),
448 data: z
449 .object({
450 readonly: z.boolean().optional(),
451 network: z.string().optional(),
452 subdomain: z.string().optional(),
453 exposed: z.array(
454 z.object({
455 serviceId: z.string(),
456 portId: z.string(),
457 }),
458 ),
459 selected: z
460 .object({
461 serviceId: z.string().optional(),
462 portId: z.string().optional(),
463 })
464 .optional(),
465 })
466 .extend(NodeBaseDataSchema.shape),
467 })
468 .extend(NodeBaseSchema.shape);
469
470export const ServiceNodeSchema = z
471 .object({
472 type: z.literal("app"),
473 data: z
474 .object({
475 type: ServiceTypeSchema,
476 repository: z
477 .union([
478 z.object({
479 id: z.number(),
480 repoNodeId: z.string(),
481 branch: z.string(),
482 rootDir: z.string(),
483 }),
484 z.object({
485 id: z.number(),
486 repoNodeId: z.string(),
487 branch: z.string(),
488 }),
489 z.object({
490 id: z.number(),
491 repoNodeId: z.string(),
492 }),
493 ])
494 .optional(),
495 volume: z.array(z.string()).optional(),
496 preBuildCommands: z.string().optional(),
497 isChoosingPortToConnect: z.boolean().optional(),
498 dev: z
499 .discriminatedUnion("enabled", [
500 z.object({
501 enabled: z.literal(false),
502 expose: DomainSchema.optional(),
503 }),
504 z.object({
505 enabled: z.literal(true),
506 expose: DomainSchema.optional(),
507 codeServerNodeId: z.string(),
508 sshNodeId: z.string(),
509 }),
510 ])
511 .optional(),
512 model: z
513 .object({
514 name: z.enum(["gemini", "claude"]),
515 apiKey: z.string().optional(),
516 })
517 .optional(),
518 info: serviceAnalyzisSchema.optional(),
519
520 ports: z.array(
521 z.object({
522 id: z.string(),
523 name: z.string(),
524 value: z.number(),
525 }),
526 ),
527 activeField: z.string().optional(),
528 state: z.string().nullable().optional(),
529 })
530 .extend(NodeBaseDataSchema.shape),
531 })
532 .extend(NodeBaseSchema.shape);
533
534export const VolumeNodeSchema = z
535 .object({
536 type: z.literal("volume"),
537 data: z
538 .object({
539 size: z.string(),
540 type: VolumeTypeSchema,
541 attachedTo: z.array(z.string()),
542 })
543 .extend(NodeBaseDataSchema.shape),
544 })
545 .extend(NodeBaseSchema.shape);
546
547export const PostgreSQLNodeSchema = z
548 .object({
549 type: z.literal("postgresql"),
550 data: z.object({}).extend(NodeBaseDataSchema.shape),
551 })
552 .extend(NodeBaseSchema.shape);
553
554export const MongoDBNodeSchema = z
555 .object({
556 type: z.literal("mongodb"),
557 data: z.object({}).extend(NodeBaseDataSchema.shape),
558 })
559 .extend(NodeBaseSchema.shape);
560
561export const NodeSchema = z.discriminatedUnion("type", [
562 NetworkNodeSchema,
563 GithubNodeSchema,
564 GatewayHttpsNodeSchema,
565 GatewayTCPNodeSchema,
566 ServiceNodeSchema,
567 VolumeNodeSchema,
568 PostgreSQLNodeSchema,
569 MongoDBNodeSchema,
570]);
571
572export const EdgeSchema = z.object({
573 id: z.string(),
574 source: z.string(),
575 sourceHandle: z.string().optional(),
576 target: z.string(),
577 targetHandle: z.string().optional(),
578});
579
gio52441602025-07-06 18:22:56 +0000580export const ViewportTransformSchema = z.object({
581 transformX: z.number(),
582 transformY: z.number(),
583 transformZoom: z.number(),
584 width: z.number(),
585 height: z.number(),
586});
587
gio10ff1342025-07-05 10:22:15 +0000588export const GraphSchema = z.object({
589 nodes: z.array(NodeSchema),
590 edges: z.array(EdgeSchema),
591 viewport: z
592 .object({
593 x: z.number(),
594 y: z.number(),
595 zoom: z.number(),
596 })
597 .optional(),
gio52441602025-07-06 18:22:56 +0000598 viewportTransform: ViewportTransformSchema.optional(),
gio10ff1342025-07-05 10:22:15 +0000599});
600
601export const GraphOrConfigSchema = z.discriminatedUnion("type", [
602 z.object({
603 type: z.literal("graph"),
604 graph: GraphSchema,
605 }),
606 z.object({
607 type: z.literal("config"),
608 config: ConfigSchema,
609 }),
610]);
611
gio56e9f472025-07-07 03:33:38 +0000612export const GraphConfigOrDraft = z.discriminatedUnion("type", [
613 z.object({
614 type: z.literal("graph"),
615 graph: GraphSchema,
616 }),
617 z.object({
618 type: z.literal("config"),
619 config: ConfigSchema,
620 }),
621 z.object({
622 type: z.literal("draft"),
623 }),
624]);
625
gio10ff1342025-07-05 10:22:15 +0000626export type Edge = z.infer<typeof EdgeSchema>;
gio52441602025-07-06 18:22:56 +0000627export type ViewportTransform = z.infer<typeof ViewportTransformSchema>;
gio10ff1342025-07-05 10:22:15 +0000628export type Graph = z.infer<typeof GraphSchema>;