1import { z } from "zod";
2import {
3 PROPERTY_TYPES,
4 VIEW_TYPES,
5 RELATION_CARDINALITIES,
6 ROLLUP_AGGREGATES,
7 MAX_CSV_IMPORT_BODY_BYTES
8} from "../../constants.js";
9
10
23const propertyTypeSchema = z
24 .string()
25 .min(1)
26 .transform((t) => t.toUpperCase())
27 .pipe(z.enum(PROPERTY_TYPES));
28
29const SelectOptions = z
30 .object({
31 name: z.string().min(1).max(50),
32 color: z.string().max(20).optional()
33 })
34 .array()
35 .min(1)
36 .max(100);
37
38const PropertyConfig = z
39 .object({
40 options: SelectOptions.optional(),
41 format: z.string().max(30).optional(),
42 relationId: z.string().min(1).optional(),
43 sourcePropertyId: z.string().min(1).optional(),
44 aggregate: z.enum(ROLLUP_AGGREGATES).optional(),
45 targetDatabaseId: z.string().min(1).optional(),
46 button: z.record(z.unknown()).optional(),
47 min: z.number().optional(),
48 max: z.number().optional()
49 })
50 .passthrough();
51
52export const CreatePropertyDto = z.object({
53 name: z.string().min(1).max(100),
54 type: propertyTypeSchema,
55 config: PropertyConfig.default({}),
56 required: z.boolean().optional(),
57 isVisible: z.boolean().optional(),
58 default: z.unknown().optional()
59});
60export type CreatePropertyInput = z.infer<typeof CreatePropertyDto>;
61
62export const UpdatePropertyDto = z.object({
63 name: z.string().min(1).max(100).optional(),
64 config: PropertyConfig.optional(),
65 required: z.boolean().optional(),
66 isVisible: z.boolean().optional(),
67 default: z.unknown().optional()
68});
69export type UpdatePropertyInput = z.infer<typeof UpdatePropertyDto>;
70
71const ViewConfig = z
72 .object({
73 group: z.object({ propertyId: z.string(), dir: z.enum(["asc", "desc"]).default("asc") }).optional(),
74 sort: z.array(z.object({ propertyId: z.string(), dir: z.enum(["asc", "desc"]).default("asc") })).max(10).optional(),
75 filter: z.record(z.unknown()).nullable().optional(),
76 cardPropertyId: z.string().optional(),
77 datePropertyId: z.string().optional(),
78 startDatePropertyId: z.string().optional(),
79 endDatePropertyId: z.string().optional(),
80 chart: z.record(z.unknown()).optional(),
81 buttons: z.array(z.string()).optional(),
82 visibleProperties: z.array(z.string()).optional(),
83 pageSize: z.number().int().min(10).max(500).optional()
84 })
85 .passthrough();
86
87export const CreateViewDto = z.object({
88 name: z.string().min(1).max(50),
89 type: z.enum(VIEW_TYPES),
90 config: ViewConfig.default({})
91});
92export type CreateViewInput = z.infer<typeof CreateViewDto>;
93
94export const UpdateViewDto = z.object({
95 name: z.string().min(1).max(50).optional(),
96 config: ViewConfig.optional(),
97 filter: z.record(z.unknown()).nullable().optional(),
98 sort: z.array(z.object({ propertyId: z.string(), dir: z.enum(["asc", "desc"]).default("asc") })).max(10).nullable().optional(),
99 group: z.object({ propertyId: z.string(), dir: z.enum(["asc", "desc"]).default("asc") }).nullable().optional(),
100
101
102
103 pageSize: z.number().int().min(10).max(500).nullable().optional()
104});
105export type UpdateViewInput = z.infer<typeof UpdateViewDto>;
106
107export const UpdateDatabaseDto = z.object({
108 title: z.string().max(200).optional(),
109 icon: z.string().max(20).nullable().optional(),
110 description: z.string().max(500).nullable().optional()
111});
112export type UpdateDatabaseInput = z.infer<typeof UpdateDatabaseDto>;
113
114export const CreateRelationDto = z.object({
115 name: z.string().min(1).max(100),
116 targetDatabaseId: z.string().min(1),
117 reverseName: z.string().min(1).max(100).optional(),
118 twoWay: z.boolean().default(true),
119 cardinality: z.enum(RELATION_CARDINALITIES).default("MANY_TO_MANY")
120});
121export type CreateRelationInput = z.infer<typeof CreateRelationDto>;
122
123export const CreateFormulaDto = z.object({
124 name: z.string().min(1).max(100),
125 expression: z.string().min(1).max(2000),
126
133 ast: z.unknown().optional(),
134 returnType: z.string().max(50).optional()
135});
136export type CreateFormulaInput = z.infer<typeof CreateFormulaDto>;
137
138export const ValidateFormulaDto = z.object({
139 expression: z.string().min(1).max(2000),
140 sampleRecordIds: z.array(z.string().max(50)).max(100).optional()
141});
142export type ValidateFormulaInput = z.infer<typeof ValidateFormulaDto>;
143
144export const CreateRecordDto = z.object({
145 title: z.string().max(200).optional(),
146
147 properties: z.record(z.unknown()).default({}),
148
149 links: z.record(z.array(z.string()).default([])).default({})
150});
151export type CreateRecordInput = z.infer<typeof CreateRecordDto>;
152
153export const UpdateRecordDto = z.object({
154 title: z.string().max(200).optional(),
155 properties: z.record(z.unknown()).optional(),
156 icon: z.string().max(20).nullable().optional(),
157 coverUrl: z.string().url().max(2048).nullable().optional(),
158 archived: z.boolean().optional(),
159 links: z.record(z.array(z.string())).optional()
160});
161export type UpdateRecordInput = z.infer<typeof UpdateRecordDto>;
162
163export const BulkRecordsDto = z.object({
164 op: z.enum(["update", "delete"]),
165 recordIds: z.array(z.string().min(1)).min(1).max(500),
166 properties: z.record(z.unknown()).optional()
167});
168export type BulkRecordsInput = z.infer<typeof BulkRecordsDto>;
169
170export const ListRecordsQuery = z.object({
171 viewId: z.string().min(1).optional(),
172 filter: z.string().max(8192).optional(),
173 sort: z.string().max(2048).optional(),
174 group: z.string().max(2048).optional(),
175 cursor: z.string().optional(),
176 limit: z.string().optional(),
177 include: z.enum(["none", "relations"]).optional()
178});
179export type ListRecordsInput = z.infer<typeof ListRecordsQuery>;
180
181
195export const DatabaseQueryDto = z.object({
196 viewId: z.string().min(1).optional(),
197 select: z.array(z.string().min(1)).max(64).optional(),
198 filter: z.unknown().optional(),
199 sort: z.array(z.object({ propertyId: z.string().min(1), direction: z.enum(["asc", "desc"]).default("asc") })).max(16).optional(),
200 group: z.array(z.object({ propertyId: z.string().min(1), direction: z.enum(["asc", "desc"]).optional() })).max(16).optional(),
201 aggregate: z
202 .array(
203 z.object({
204 propertyId: z.string().min(1),
205 fn: z.enum(["COUNT", "SUM", "AVERAGE", "MIN", "MAX", "UNIQUE"]),
206 perGroup: z.boolean().optional()
207 })
208 )
209 .max(8)
210 .optional(),
211 includeArchived: z.boolean().default(false),
212 cursor: z.string().nullable().optional(),
213 pageSize: z.number().int().min(1).max(200).default(50)
214});
215export type DatabaseQueryInput = z.infer<typeof DatabaseQueryDto>;
216
217export const ListFormulasQuery = z.object({});
218
219
220
221
222export const CsvImportColumn = z.object({
223
224 index: z.number().int().min(0),
225
226 header: z.string(),
227
228 propertyId: z.string().nullable(),
229
230 reason: z.string().default("")
231});
232export type CsvImportColumnInput = z.infer<typeof CsvImportColumn>;
233
234
241export const CsvImportDto = z.object({
242
243 csv: z.string().min(1).max(MAX_CSV_IMPORT_BODY_BYTES),
244
245 dryRun: z.boolean().default(true),
246
251 mappings: z.record(z.string().min(1)).optional(),
252
253 titleHeader: z.string().optional()
254});
255export type CsvImportInput = z.infer<typeof CsvImportDto>;
256