surreal-zod 0.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "surreal-zod",
3
+ "version": "0.0.0-alpha.1",
4
+ "module": "index.ts",
5
+ "scripts": {
6
+ "build": "tsc"
7
+ },
8
+ "files": [
9
+ "lib",
10
+ "src"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "bun": "./src/index.ts",
15
+ "import": "./lib/index.js"
16
+ }
17
+ },
18
+ "devDependencies": {
19
+ "@biomejs/biome": "^2.3.3",
20
+ "@types/bun": "latest",
21
+ "chalk": "^5.6.2",
22
+ "get-port": "^7.1.0",
23
+ "zod": "^3.25.0 || ^4.0.0"
24
+ },
25
+ "peerDependencies": {
26
+ "typescript": "^5",
27
+ "zod": "^3.25.0 || ^4.0.0"
28
+ },
29
+ "type": "module",
30
+ "dependencies": {
31
+ "@surrealdb/node": "2.3.4",
32
+ "dedent": "^1.7.0",
33
+ "surrealdb": "^2.0.0-alpha.13"
34
+ }
35
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import * as sz from "./zod";
2
+ export { sz };
3
+ export default sz;
package/src/print.ts ADDED
@@ -0,0 +1,184 @@
1
+ // import type * as z3 from 'zod/v3'
2
+ import type * as z4 from "zod/v4/core";
3
+
4
+ export function zodToSexpr(schema: z4.$ZodType) {
5
+ if ("_zod" in schema) {
6
+ return zod4ToSexpr(schema);
7
+ }
8
+
9
+ throw new Error("Zod 3 not yet supported");
10
+ }
11
+
12
+ function zod4ToSexpr(_schema: z4.$ZodType, depth = 0): string {
13
+ const indent = " ".repeat(depth);
14
+ const childIndent = " ".repeat(depth + 1);
15
+
16
+ const schema = _schema as z4.$ZodTypes;
17
+ const def = schema._zod.def;
18
+ const checks = def.checks ?? [];
19
+ if ("check" in def) {
20
+ checks.push(schema as z4.$ZodCheck);
21
+ }
22
+ const type = def.type;
23
+
24
+ if (type === "object") {
25
+ return `(object)`;
26
+ }
27
+
28
+ // Primitives
29
+ switch (type) {
30
+ case "string": {
31
+ const constraints = formatChecks(checks);
32
+ return constraints.length > 0 ? `(string [${constraints}])` : `(string)`;
33
+ }
34
+ case "number": {
35
+ // const constraints = formatChecks(checks);
36
+ // return constraints.length > 0 ? `(number [${constraints}])` : `(number)`;
37
+ return `(number)`;
38
+ }
39
+ case "optional": {
40
+ const inner = zod4ToSexpr(def.innerType, depth + 1);
41
+ return `(optional ${inner})`;
42
+ }
43
+ case "nullable": {
44
+ const inner = zod4ToSexpr(def.innerType, depth + 1);
45
+ return `(nullable ${inner})`;
46
+ }
47
+ case "nonoptional": {
48
+ const inner = zod4ToSexpr(def.innerType, depth + 1);
49
+ return `(nonoptional ${inner})`;
50
+ }
51
+ case "array": {
52
+ const inner = zod4ToSexpr(def.element, depth + 1);
53
+ return `(array ${inner})`;
54
+ }
55
+ case "bigint": {
56
+ return `(bigint)`;
57
+ }
58
+ case "boolean": {
59
+ return `(boolean)`;
60
+ }
61
+ case "symbol": {
62
+ return `(symbol)`;
63
+ }
64
+ case "undefined": {
65
+ return `(undefined)`;
66
+ }
67
+ case "null": {
68
+ return `(null)`;
69
+ }
70
+ case "any": {
71
+ return `(any)`;
72
+ }
73
+ default: {
74
+ return `(unknown-type ${type || "?"})`;
75
+ }
76
+ }
77
+
78
+ // console.log("unknown type", def);
79
+
80
+ // if (type === "number") {
81
+ // const constraints = formatChecks(checks);
82
+ // return constraints.length > 0 ? `(number [${constraints}])` : `(number)`;
83
+ // }
84
+ }
85
+
86
+ function breakLine(sexpr: string, depth: number) {
87
+ // return sexpr.length > 80
88
+ // ? `\n${" ".repeat(depth)}${sexpr}\n${" ".repeat(depth)}`
89
+ // : sexpr;
90
+ return sexpr;
91
+ }
92
+
93
+ function formatChecks(checks: z4.$ZodCheck[]) {
94
+ return checks.map((check) => formatCheck(check)).join(" ");
95
+ }
96
+
97
+ function formatCheck(_check: z4.$ZodCheck) {
98
+ const check = _check as z4.$ZodChecks;
99
+ const def = check._zod.def;
100
+ switch (def.check) {
101
+ case "min_length":
102
+ return `min:${def.minimum}`;
103
+ case "max_length":
104
+ return `max:${def.maximum}`;
105
+ case "length_equals":
106
+ return `length:${def.length}`;
107
+ case "string_format":
108
+ return parseStringFormat(check);
109
+ // case "bigint_format":
110
+ // return `bigint_format=${def.format}`;
111
+ // case "number_format":
112
+ // return `number_format=${def.format}`;
113
+ case "greater_than":
114
+ return `${def.inclusive ? ">=" : ">"}${def.value}`;
115
+ case "less_than":
116
+ return `${def.inclusive ? "<=" : "<"}${def.value}`;
117
+ // case "max_size":
118
+ // return `max_size=${def.maximum}`;
119
+ // case "min_size":
120
+ // return `min_size=${def.minimum}`;
121
+ // case "mime_type":
122
+ // return `mime_type=${def.mime}`;
123
+ // case "multiple_of":
124
+ // return `multiple_of=${def.value}`;
125
+ // case "size_equals":
126
+ // return `size_equals=${def.size}`;
127
+ // case undefined: {
128
+ // return `[unknown ${inspect(def, { colors: true })}]`;
129
+ // }
130
+ default:
131
+ return `[${def.check} ?]`;
132
+ }
133
+ }
134
+
135
+ function parseStringFormat(_check: z4.$ZodCheck) {
136
+ const check = _check as z4.$ZodStringFormatChecks;
137
+ const def = check._zod.def;
138
+
139
+ const coerce = "coerce" in def ? `coerce:${def.coerce} ` : "";
140
+
141
+ if (def.format === "starts_with") {
142
+ return `starts_with:"${def.prefix}"`;
143
+ } else if (def.format === "ends_with") {
144
+ return `ends_with:"${def.suffix}"`;
145
+ } else if (def.format === "includes") {
146
+ return `includes:"${def.includes}"`;
147
+ } else if (def.format === "regex") {
148
+ return `regex:${def.pattern}`;
149
+ } else if (def.format === "uuid") {
150
+ return `format:uuid${def.version || ""}`;
151
+ } else if (def.format === "xid") {
152
+ return `format:xid`;
153
+ } else if (def.format === "url") {
154
+ const opts = [
155
+ def.normalize ? `normalize` : null,
156
+ def.hostname ? `hostname:${def.hostname}` : null,
157
+ def.protocol ? `protocol:${def.protocol}` : null,
158
+ ]
159
+ .filter(Boolean)
160
+ .join(", ");
161
+
162
+ return `format:url${opts.length > 0 ? `(${opts})` : ""}`;
163
+ }
164
+
165
+ return `${coerce}format:${def.format}`;
166
+ }
167
+
168
+ function zodObjectToSexpr(schema: z4.$ZodObject, level = 0) {
169
+ if (!("_zod" in schema)) {
170
+ throw new Error(
171
+ "Invalid schema provided, make sure you are using zod v4 as zod v3 is currently not supported.",
172
+ );
173
+ }
174
+ const def = schema._zod.def;
175
+ let sexpr = `(${def.type}${def.checks ? ` ${formatChecks(def.checks)}` : ""}`;
176
+
177
+ for (const [propName, propSchema] of Object.entries(def.shape)) {
178
+ sexpr += `${zod4ToSexpr(propSchema, level + 1)}\n`;
179
+ }
180
+
181
+ sexpr += ")";
182
+
183
+ return sexpr;
184
+ }
package/src/surql.ts ADDED
@@ -0,0 +1,493 @@
1
+ import {
2
+ BoundQuery,
3
+ escapeIdent,
4
+ escapeIdPart,
5
+ RecordId,
6
+ surql,
7
+ Table,
8
+ } from "surrealdb";
9
+ import * as z4 from "zod/v4/core";
10
+ import z from "zod";
11
+ import dedent from "dedent";
12
+ import type sz from ".";
13
+ import type { SurrealZodType, SurrealZodTypes } from "./zod";
14
+
15
+ export type ZodTypeName = z4.$ZodType["_zod"]["def"]["type"];
16
+ export type SurrealZodTypeName = SurrealZodType["_zod"]["def"]["type"];
17
+
18
+ export interface ZodToSurqlOptions<S extends z4.$ZodObject> {
19
+ table: string | Table;
20
+ schemafull?: boolean;
21
+ exists?: "ignore" | "error" | "overwrite";
22
+ drop?: boolean;
23
+ comment?: string;
24
+ schema: S;
25
+ }
26
+
27
+ export function zodToSurql<S extends z4.$ZodObject>(
28
+ options: ZodToSurqlOptions<S>,
29
+ ): [
30
+ S extends z4.$ZodObject<infer Shape>
31
+ ? z4.$ZodObject<Shape & { id: z4.$ZodCustom<RecordId> }>
32
+ : never,
33
+ BoundQuery,
34
+ ] {
35
+ const table =
36
+ typeof options.table === "string"
37
+ ? new Table(options.table)
38
+ : options.table;
39
+
40
+ const schema = options.schema;
41
+ if (!("_zod" in schema)) {
42
+ throw new Error(
43
+ "Invalid schema provided, make sure you are using zod v4 as zod v3 is currently not supported.",
44
+ );
45
+ }
46
+
47
+ const def = schema._zod.def;
48
+ const shape = def.shape;
49
+ const query = defineTable(options);
50
+ for (const [key, value] of Object.entries(shape)) {
51
+ query.append(
52
+ defineField({ name: key, table, type: value, exists: options.exists }),
53
+ );
54
+ }
55
+
56
+ // @ts-expect-error - extend is not a method of z4.$ZodObject
57
+ return [schema.extend({ id: z.any() }), query];
58
+ }
59
+
60
+ function defineTable<S extends z4.$ZodObject>(options: ZodToSurqlOptions<S>) {
61
+ const table =
62
+ typeof options.table === "string"
63
+ ? new Table(options.table)
64
+ : options.table;
65
+ const query = surql`DEFINE TABLE`;
66
+
67
+ if (options.exists === "ignore") {
68
+ query.append(" IF NOT EXISTS");
69
+ } else if (options.exists === "overwrite") {
70
+ query.append(" OVERWRITE");
71
+ }
72
+ // Looks like passing Table instance is not supported yet
73
+ query.append(` ${escapeIdPart(table.name)}`);
74
+
75
+ if (options.drop) {
76
+ query.append(" DROP");
77
+ }
78
+
79
+ if (options.schemafull) {
80
+ query.append(" SCHEMAFULL");
81
+ } else {
82
+ query.append(" SCHEMALESS");
83
+ }
84
+
85
+ if (options.comment) {
86
+ query.append(surql` COMMENT ${options.comment}`);
87
+ }
88
+
89
+ query.append(";\n");
90
+
91
+ return query;
92
+ }
93
+
94
+ function defineField(options: {
95
+ name: string;
96
+ table: string | Table;
97
+ type: z4.$ZodType;
98
+ exists?: "ignore" | "error" | "overwrite";
99
+ }) {
100
+ const name = options.name;
101
+ const table =
102
+ typeof options.table === "string"
103
+ ? new Table(options.table)
104
+ : options.table;
105
+ const schema = options.type as z4.$ZodTypes;
106
+ if (!("_zod" in schema)) {
107
+ throw new Error(
108
+ "Invalid field schema provided, make sure you are using zod v4 as zod v3 is currently not supported.",
109
+ );
110
+ }
111
+
112
+ const context: ZodSurrealTypeContext = {
113
+ name,
114
+ table,
115
+ rootSchema: schema,
116
+ children: [],
117
+ asserts: [],
118
+ transforms: [],
119
+ };
120
+ const query = surql`DEFINE FIELD`;
121
+
122
+ if (options.exists === "ignore") {
123
+ query.append(" IF NOT EXISTS");
124
+ } else if (options.exists === "overwrite") {
125
+ query.append(" OVERWRITE");
126
+ }
127
+
128
+ query.append(` ${name} ON TABLE ${table.name}`);
129
+
130
+ const type = zodTypeToSurrealType(schema, [], context);
131
+
132
+ query.append(` TYPE ${type}`);
133
+
134
+ if (context.default) {
135
+ query.append(
136
+ context.default.always
137
+ ? ` DEFAULT ALWAYS ${JSON.stringify(context.default.value)}`
138
+ : ` DEFAULT ${JSON.stringify(context.default.value)}`,
139
+ );
140
+ }
141
+
142
+ if (context.transforms.length > 0) {
143
+ query.append(` VALUE {\n`);
144
+ for (const transform of context.transforms) {
145
+ query.append(
146
+ dedent.withOptions({ alignValues: true })`
147
+ //
148
+ ${transform}\n`.slice(3),
149
+ );
150
+ }
151
+ query.append(`}`);
152
+ }
153
+
154
+ if (context.asserts.length > 0) {
155
+ query.append(` ASSERT {\n`);
156
+ for (const assert of context.asserts) {
157
+ query.append(
158
+ dedent.withOptions({ alignValues: true })`
159
+ //
160
+ ${assert}\n`.slice(3),
161
+ );
162
+ }
163
+ query.append(`}`);
164
+ }
165
+
166
+ query.append(`;\n`);
167
+
168
+ if (context.children.length > 0) {
169
+ for (const { name: childName, type: childType } of context.children) {
170
+ query.append(
171
+ defineField({
172
+ name: `${name}.${childName}`,
173
+ table,
174
+ type: childType,
175
+ exists: options.exists,
176
+ }),
177
+ );
178
+ }
179
+ }
180
+
181
+ return query;
182
+ }
183
+
184
+ type ZodSurrealTypeContext = {
185
+ name: string;
186
+ table: Table;
187
+ rootSchema: z4.$ZodType;
188
+ children: ZodSurrealChildType[];
189
+ asserts: string[];
190
+ transforms: string[];
191
+ default?: { value: any; always: boolean };
192
+ };
193
+ type ZodSurrealChildType = { name: string; type: z4.$ZodType };
194
+
195
+ export function zodTypeToSurrealType(
196
+ type: z4.$ZodType | SurrealZodType,
197
+ parents: string[] = [],
198
+ context: ZodSurrealTypeContext,
199
+ ): string {
200
+ const schema = type as z4.$ZodTypes | SurrealZodTypes;
201
+ if (!("_zod" in schema)) {
202
+ throw new Error(
203
+ "Invalid schema provided, make sure you are using zod v4 as zod v3 is currently not supported.",
204
+ );
205
+ }
206
+
207
+ const def = schema._zod.def;
208
+ const checks = getChecks(schema);
209
+ parseChecks(context.name, checks, context, def.type);
210
+ // console.log(zodToSexpr(type));
211
+
212
+ switch (def.type) {
213
+ case "string":
214
+ return "string";
215
+ case "boolean":
216
+ return "bool";
217
+ case "object": {
218
+ const isInArray = context.rootSchema._zod.def.type === "array";
219
+ // TODO: remove any
220
+ for (const [key, value] of Object.entries((def as any).shape)) {
221
+ context.children.push({
222
+ name: isInArray ? `*.${key}` : key,
223
+ // TODO: remove as
224
+ type: value as z4.$ZodType,
225
+ });
226
+ }
227
+ return "object";
228
+ }
229
+ case "number":
230
+ return "number";
231
+ case "null":
232
+ return "NULL";
233
+ // case "bigint":
234
+ // return "bigint";
235
+ // case "symbol":
236
+ // return "symbol";
237
+ case "any": {
238
+ //===============================
239
+ // Surreal Specific Types
240
+ //===============================
241
+ if ("surrealType" in def) {
242
+ if (def.surrealType === "record_id") {
243
+ if (def.what) {
244
+ return `record<${Object.keys(def.what).join(" | ")}>`;
245
+ } else {
246
+ return "record";
247
+ }
248
+ }
249
+ }
250
+ return "any";
251
+ }
252
+ case "undefined": {
253
+ return "NONE";
254
+ }
255
+ case "default": {
256
+ // if (typeof def.defaultValue === "function") {
257
+ // context.default = { value: def.defaultValue(), always: false };
258
+ // } else {
259
+ // console.log(
260
+ // "default",
261
+ // Object.getOwnPropertyDescriptor(def, "defaultValue").get?.toString(),
262
+ // );
263
+ // TODO: remove any
264
+ context.default = { value: (def as any).defaultValue, always: false };
265
+ // }
266
+ return zodTypeToSurrealType(
267
+ // TODO: remove any
268
+ (def as any).innerType,
269
+ [...parents, def.type],
270
+ context,
271
+ );
272
+ }
273
+ case "nullable": {
274
+ const inner = zodTypeToSurrealType(
275
+ // TODO: remove any
276
+ (def as any).innerType,
277
+ [...parents, def.type],
278
+ context,
279
+ );
280
+ if (parents.includes("nullable")) {
281
+ return inner;
282
+ }
283
+ return `${inner} | NULL`;
284
+ }
285
+ case "optional": {
286
+ const inner = zodTypeToSurrealType(
287
+ // TODO: remove any
288
+ (def as any).innerType,
289
+ [...parents, def.type],
290
+ context,
291
+ );
292
+ if (parents.includes("optional") || parents.includes("nonoptional")) {
293
+ return inner;
294
+ }
295
+ return `option<${inner}>`;
296
+ }
297
+ case "nonoptional": {
298
+ // just a marker for children optional to skip the option<...> wrapper
299
+ return zodTypeToSurrealType(
300
+ // TODO: remove any
301
+ (def as any).innerType,
302
+ [...parents, def.type],
303
+ context,
304
+ );
305
+ }
306
+ case "union": {
307
+ // TODO: remove any
308
+ return (
309
+ (def as any).options
310
+ // TODO: remove any
311
+ .map((option: any) =>
312
+ zodTypeToSurrealType(option, [...parents, def.type], context),
313
+ )
314
+ .join(" | ")
315
+ );
316
+ }
317
+ case "array": {
318
+ const inner = zodTypeToSurrealType(
319
+ // TODO: remove any
320
+ (def as any).element,
321
+ [...parents, def.type],
322
+ context,
323
+ );
324
+
325
+ return `array<${inner}>`;
326
+ }
327
+ case "custom": {
328
+ return "any";
329
+ }
330
+ default: {
331
+ console.log("unknown type", def.type);
332
+ return "any";
333
+ }
334
+ }
335
+ }
336
+
337
+ function getChecks(_schema: z4.$ZodType | SurrealZodType) {
338
+ const schema = _schema as z4.$ZodTypes | SurrealZodTypes;
339
+ const checks = schema._zod.def.checks ?? [];
340
+ if ("check" in schema._zod.def) {
341
+ checks.unshift(schema as z4.$ZodCheck);
342
+ }
343
+ return checks;
344
+ }
345
+
346
+ function parseChecks(
347
+ name: string,
348
+ checks: z4.$ZodCheck[],
349
+ context: ZodSurrealTypeContext,
350
+ type: ZodTypeName | SurrealZodTypeName,
351
+ ) {
352
+ for (const check of checks) {
353
+ const { transform, assert } = parseCheck(name, check, type);
354
+ if (transform) {
355
+ context.transforms.push(transform);
356
+ }
357
+ if (assert) {
358
+ context.asserts.push(assert);
359
+ }
360
+ }
361
+ }
362
+
363
+ export const checkMap = {
364
+ min_length(name: string, value: number, type: ZodTypeName) {
365
+ if (type === "array") {
366
+ return `$value.len() >= ${value} || { THROW 'Field "${name}" must have at least ${value} ${value === 1 ? "item" : "items"}' };`;
367
+ }
368
+
369
+ if (type === "string") {
370
+ return `$value.len() >= ${value} || { THROW 'Field "${name}" must be at least ${value} ${value === 1 ? "character" : "characters"} long' };`;
371
+ }
372
+
373
+ throw new Error(`Invalid type: ${type}`);
374
+ },
375
+ max_length(name: string, value: number, type: ZodTypeName) {
376
+ if (type === "array") {
377
+ return `$value.len() <= ${value} || { THROW 'Field "${name}" must have at most ${value} ${value === 1 ? "item" : "items"}' };`;
378
+ }
379
+
380
+ if (type === "string") {
381
+ return `$value.len() <= ${value} || { THROW 'Field "${name}" must be at most ${value} ${value === 1 ? "character" : "characters"} long' };`;
382
+ }
383
+
384
+ throw new Error(`Invalid type: ${type}`);
385
+ },
386
+ greater_than(name: string, value: z4.util.Numeric, inclusive: boolean) {
387
+ return `$value ${inclusive ? ">=" : ">"} ${value} || { THROW 'Field "${name}" must be greater than ${inclusive ? "or equal to" : ""} ${value}' };`;
388
+ },
389
+ less_than(name: string, value: z4.util.Numeric, inclusive: boolean) {
390
+ return `$value ${inclusive ? "<=" : "<"} ${value} || { THROW 'Field "${name}" must be less than ${inclusive ? "or equal to" : ""} ${value}' };`;
391
+ },
392
+ length_equals(name: string, value: number, type: ZodTypeName = "string") {
393
+ if (type === "array") {
394
+ return `$value.len() == ${value} || { THROW 'Field "${name}" must have exactly ${value} ${value === 1 ? "item" : "items"}' };`;
395
+ }
396
+
397
+ if (type === "string") {
398
+ return `$value.len() == ${value} || { THROW 'Field "${name}" must be exactly ${value} ${value === 1 ? "character" : "characters"} long' };`;
399
+ }
400
+
401
+ throw new Error(`Invalid type: ${type}`);
402
+ },
403
+
404
+ string_format: {
405
+ email: (name: string) => {
406
+ const regex =
407
+ /^[A-Za-z0-9'_+-]+(?:\.[A-Za-z0-9'_+-]+)*@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/;
408
+ return `string::matches($value, ${regex}) || { THROW "Field '${name}' must be a valid email address" };`;
409
+ },
410
+ url: (
411
+ name: string,
412
+ def?: Pick<z4.$ZodCheckURLParams, "hostname" | "protocol" | "normalize">,
413
+ ) => {
414
+ return dedent`
415
+ LET $url = {
416
+ scheme: parse::url::scheme($value),
417
+ host: parse::url::host($value),
418
+ domain: parse::url::domain($value),
419
+ path: parse::url::path($value),
420
+ port: parse::url::port($value),
421
+ query: parse::url::query($value),
422
+ hash: parse::url::fragment($value),
423
+ };
424
+ $url.scheme || { THROW "Field '${name}' must be a valid URL" };
425
+ ${
426
+ def?.hostname
427
+ ? `($url.host ?? "").matches(${def.hostname}) || { THROW "Field '${name}' must match hostname ${def.hostname.toString().replace(/\\/g, "\\\\")}" };`
428
+ : ""
429
+ }
430
+ ${
431
+ def?.protocol
432
+ ? `($url.scheme ?? "").matches(${def.protocol}) || { THROW "Field '${name}' must match protocol ${def.protocol.toString().replace(/\\/g, "\\\\")}" };`
433
+ : ""
434
+ }
435
+ $url.scheme + "://" + ($url.host ?? "") + (
436
+ IF $url.port && (
437
+ ($url.scheme == "http" && $url.port != 80) ||
438
+ ($url.scheme == "https" && $url.port != 443)
439
+ ) { ":" + <string>$url.port } ?? ""
440
+ )
441
+ + ($url.path ?? "")
442
+ + (IF $url.query { "?" + $url.query } ?? "")
443
+ + (IF $url.fragment { "#" + $url.fragment } ?? "");
444
+ `;
445
+ },
446
+ },
447
+ };
448
+
449
+ function parseCheck(
450
+ name: string,
451
+ _check: z4.$ZodCheck,
452
+ type: ZodTypeName,
453
+ ): { transform?: string; assert?: string } {
454
+ const check = _check as z4.$ZodChecks;
455
+ const def = check._zod.def;
456
+ switch (def.check) {
457
+ case "min_length":
458
+ return { assert: checkMap.min_length(name, def.minimum, type) };
459
+ case "max_length":
460
+ return { assert: checkMap.max_length(name, def.maximum, type) };
461
+ case "greater_than":
462
+ return { assert: checkMap.greater_than(name, def.value, def.inclusive) };
463
+ case "less_than":
464
+ return { assert: checkMap.less_than(name, def.value, def.inclusive) };
465
+ case "length_equals":
466
+ return { assert: checkMap.length_equals(name, def.length, type) };
467
+ case "string_format":
468
+ return assertionForStringFormat(name, check);
469
+ default:
470
+ return { assert: `THROW 'Unknown check: ${def.check}';` };
471
+ }
472
+ }
473
+
474
+ // Remove look-around, look-behind, and look-ahead as they are not supported by SurrealDB
475
+ function assertionForStringFormat(
476
+ name: string,
477
+ _check: z4.$ZodCheck,
478
+ ): { transform?: string; assert?: string } {
479
+ const check = _check as z4.$ZodStringFormatChecks;
480
+ const def = check._zod.def;
481
+
482
+ switch (def.format) {
483
+ case "email": {
484
+ return { assert: checkMap.string_format.email(name) };
485
+ }
486
+ case "url": {
487
+ const code = checkMap.string_format.url(name, def);
488
+ return def.normalize ? { transform: code } : { assert: code };
489
+ }
490
+ default:
491
+ return { assert: `THROW 'Unsupported string format: ${def.format}';` };
492
+ }
493
+ }