-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathparse-schema.ts
More file actions
153 lines (137 loc) · 4.39 KB
/
Copy pathparse-schema.ts
File metadata and controls
153 lines (137 loc) · 4.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { z } from "astro/zod";
import type {
PocketBaseCollection,
PocketBaseSchemaEntry
} from "../types/pocketbase-schema.type";
export interface ParseSchemaOptions {
hasSuperuserRights: boolean;
improveTypes?: boolean;
fieldsToInclude?: Array<string>;
experimentalLiveTypesOnly?: boolean;
}
/**
* Converts PocketBase collection fields into Zod types, handling field types, required status, and custom schemas.
*/
export function parseSchema(
collection: PocketBaseCollection,
customSchemas: Record<string, z.ZodType> | undefined,
options: ParseSchemaOptions
): Record<string, z.ZodType> {
// Prepare the schemas fields
const fields: Record<string, z.ZodType> = {};
// Parse every field in the schema
for (const field of collection.fields) {
// If fieldsToInclude is specified, only include fields that are in the list
if (
options.fieldsToInclude &&
!options.fieldsToInclude.includes(field.name)
) {
continue;
}
// Skip hidden fields if the user does not have superuser rights
if (field.hidden && !options.hasSuperuserRights) {
if (options.fieldsToInclude) {
console.warn(
`"${field.name}" is requested but hidden. Provide superuser credentials to include this field.`
);
}
continue;
}
let fieldType;
// Determine the field type and create the corresponding Zod type
// oxlint-disable-next-line switch-exhaustiveness-check
switch (field.type) {
case "number":
fieldType = z.number();
break;
case "bool":
fieldType = z.boolean();
break;
case "date":
case "autodate":
if (options.experimentalLiveTypesOnly) {
// If experimental live types only mode is enabled, treat dates as strings
fieldType = z.string();
break;
}
// Coerce and parse the value as a date
fieldType = z.coerce.date();
break;
case "geoPoint":
fieldType = z.object({
lon: z.number(),
lat: z.number()
});
break;
case "select": {
if (!field.values) {
throw new Error(
`Field ${field.name} is of type "select" but has no values defined.`
);
}
// Create an enum for the select values
// oxlint-disable-next-line no-unsafe-type-assertion
const values = z.enum(field.values as [string, ...Array<string>]);
// Parse the field type based on the number of values it can have
fieldType = parseSingleOrMultipleValues(field, values);
break;
}
case "relation":
case "file":
// NOTE: Relations are currently not supported and are treated as strings
// NOTE: Files are later transformed to URLs
// Parse the field type based on the number of values it can have
fieldType = parseSingleOrMultipleValues(field, z.string());
break;
case "json":
if (customSchemas && customSchemas[field.name]) {
// Use the user defined custom schema for the field
fieldType = customSchemas[field.name];
} else {
// Parse the field as unknown JSON
fieldType = z.unknown();
}
break;
default:
// Default to a string
fieldType = z.string();
break;
}
const isRequired =
// Check if the field is required
field.required ||
// `onCreate autodate` fields are always set
(field.type === "autodate" && field.onCreate) ||
// Improve number and boolean types by providing default values
(options.improveTypes &&
(field.type === "number" || field.type === "bool"));
// If the field is not required, mark it as optional
if (!isRequired) {
fieldType = z.preprocess(
(val) => val || undefined,
z.optional(fieldType)
);
}
// Add the field to the fields object
fields[field.name] = fieldType;
}
return fields;
}
/**
* Parse the field type based on the number of values it can have
*
* @param field Field to parse
* @param type Type of each value
*
* @returns The parsed field type
*/
export function parseSingleOrMultipleValues(
field: PocketBaseSchemaEntry,
type: z.ZodType
): z.ZodType {
// If the select allows multiple values, create an array of the enum
if (field.maxSelect === undefined || field.maxSelect <= 1) {
return type;
}
return z.array(type);
}