surreal-zod 0.0.0-alpha.1 → 0.0.0-alpha.10

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/src/surql.ts CHANGED
@@ -9,171 +9,336 @@ import {
9
9
  import * as z4 from "zod/v4/core";
10
10
  import z from "zod";
11
11
  import dedent from "dedent";
12
- import type sz from ".";
13
- import type { SurrealZodType, SurrealZodTypes } from "./zod";
12
+ import type {
13
+ SurrealZodNonOptional,
14
+ SurrealZodNullable,
15
+ SurrealZodObject,
16
+ SurrealZodOptional,
17
+ SurrealZodRecordId,
18
+ SurrealZodTable,
19
+ SurrealZodTableNormal,
20
+ SurrealZodTableRelation,
21
+ SurrealZodType,
22
+ SurrealZodTypes,
23
+ } from "./zod/schema";
14
24
 
15
25
  export type ZodTypeName = z4.$ZodType["_zod"]["def"]["type"];
16
26
  export type SurrealZodTypeName = SurrealZodType["_zod"]["def"]["type"];
17
27
 
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;
28
+ /////////////////////////////////////
29
+ /////////////////////////////////////
30
+ ////////// //////////
31
+ ////////// Table //////////
32
+ ////////// //////////
33
+ /////////////////////////////////////
34
+ /////////////////////////////////////
35
+
36
+ export function tableToSurql(
37
+ table: SurrealZodTable,
38
+ statement: "define",
39
+ defineOptions?: DefineTableOptions,
40
+ ): BoundQuery<[undefined]>;
41
+ export function tableToSurql(
42
+ table: SurrealZodTable,
43
+ statement: "remove",
44
+ removeOptions?: RemoveTableOptions,
45
+ ): BoundQuery<[undefined]>;
46
+ export function tableToSurql(
47
+ table: SurrealZodTable,
48
+ statement: "info",
49
+ ): BoundQuery<[TableInfo]>;
50
+ export function tableToSurql(
51
+ table: SurrealZodTable,
52
+ statement: "structure",
53
+ ): BoundQuery<[TableStructure]>;
54
+ export function tableToSurql(
55
+ table: SurrealZodTable,
56
+ statement: "define" | "info" | "structure" | "remove",
57
+ options?: DefineTableOptions | RemoveTableOptions,
58
+ ): BoundQuery<[TableInfo | TableStructure]> {
59
+ if (statement === "define") {
60
+ return defineTable(table, options as DefineTableOptions);
61
+ }
62
+ if (statement === "info") {
63
+ return infoTable(table);
64
+ }
65
+ if (statement === "structure") {
66
+ return structureTable(table);
67
+ }
68
+ if (statement === "remove") {
69
+ return removeTable(table, options as RemoveTableOptions);
70
+ }
71
+ throw new Error(`Invalid statement: ${statement}`);
25
72
  }
26
73
 
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;
74
+ export type RemoveTableOptions = {
75
+ /**
76
+ * What to do if the table is missing.
77
+ * - "ignore": Ignore the error and continue.
78
+ * - "error": Throw an error if the table is missing.
79
+ * @default "error"
80
+ */
81
+ missing?: "ignore" | "error";
82
+ };
39
83
 
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
- );
84
+ export function removeTable(
85
+ table: SurrealZodTable,
86
+ options?: RemoveTableOptions,
87
+ ): BoundQuery<[undefined]> {
88
+ const name = table._zod.def.name;
89
+ const query = surql`REMOVE TABLE`;
90
+ const removeOptions = options as RemoveTableOptions;
91
+ if (removeOptions?.missing === "ignore") {
92
+ query.append(" IF EXISTS");
45
93
  }
94
+ query.append(` ${escapeIdent(name)}`);
95
+ query.append(";");
96
+ return query;
97
+ }
46
98
 
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
- }
99
+ export interface TableInfo {
100
+ events: Record<string, string>;
101
+ fields: Record<string, string>;
102
+ indexes: Record<string, string>;
103
+ lives: Record<string, string>;
104
+ tables: Record<string, string>;
105
+ }
106
+
107
+ export function infoTable(table: SurrealZodTable): BoundQuery<[TableInfo]> {
108
+ const name = table._zod.def.name;
109
+ const query = surql`INFO FOR TABLE`;
110
+ query.append(` ${escapeIdent(name)}`);
111
+ query.append(";");
112
+ return query;
113
+ }
114
+
115
+ export interface TableStructure {
116
+ events: unknown[];
117
+ fields: FieldStructure[];
118
+ indexes: unknown[];
119
+ lives: unknown[];
120
+ tables: unknown[];
121
+ }
55
122
 
56
- // @ts-expect-error - extend is not a method of z4.$ZodObject
57
- return [schema.extend({ id: z.any() }), query];
123
+ export interface FieldStructure {
124
+ name: string;
125
+ kind: string;
126
+ permissions: {
127
+ create: boolean;
128
+ select: boolean;
129
+ update: boolean;
130
+ };
131
+ readonly: boolean;
132
+ what: string;
58
133
  }
59
134
 
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;
135
+ export function structureTable(
136
+ table: SurrealZodTable,
137
+ ): BoundQuery<[TableStructure]> {
138
+ const name = table._zod.def.name;
139
+ const query = surql`INFO FOR TABLE`;
140
+ query.append(` ${escapeIdent(name)}`);
141
+ query.append(" STRUCTURE;");
142
+ return query;
143
+ }
144
+
145
+ export type DefineTableOptions = {
146
+ exists?: "ignore" | "error" | "overwrite";
147
+ fields?: boolean;
148
+ };
149
+
150
+ export function defineTable(
151
+ schema: SurrealZodTable,
152
+ options?: DefineTableOptions,
153
+ ): BoundQuery<[undefined, ...undefined[]]> {
154
+ const def = schema._zod.def;
155
+ const surreal = schema._zod.def.surreal;
156
+ const table = new Table(def.name);
157
+
65
158
  const query = surql`DEFINE TABLE`;
66
159
 
67
- if (options.exists === "ignore") {
160
+ if (options?.exists === "ignore") {
68
161
  query.append(" IF NOT EXISTS");
69
- } else if (options.exists === "overwrite") {
162
+ } else if (options?.exists === "overwrite") {
70
163
  query.append(" OVERWRITE");
71
164
  }
72
165
  // Looks like passing Table instance is not supported yet
73
166
  query.append(` ${escapeIdPart(table.name)}`);
167
+ query.append(` TYPE ${surreal.tableType.toUpperCase()}`);
74
168
 
75
- if (options.drop) {
169
+ if (isRelationTable(schema)) {
170
+ const fromTables = schema._zod.def.fields.in._zod.def.table;
171
+ if (fromTables) {
172
+ query.append(` FROM ${fromTables.map(escapeIdent).join(" | ")}`);
173
+ }
174
+ const toTables = schema._zod.def.fields.out._zod.def.table;
175
+ if (toTables) {
176
+ query.append(` TO ${toTables.map(escapeIdent).join(" | ")}`);
177
+ }
178
+ }
179
+
180
+ if (surreal.drop) {
76
181
  query.append(" DROP");
77
182
  }
78
183
 
79
- if (options.schemafull) {
184
+ if (surreal.schemafull) {
80
185
  query.append(" SCHEMAFULL");
81
186
  } else {
82
187
  query.append(" SCHEMALESS");
83
188
  }
84
189
 
85
- if (options.comment) {
86
- query.append(surql` COMMENT ${options.comment}`);
190
+ if (surreal.comment) {
191
+ query.append(surql` COMMENT ${surreal.comment}`);
87
192
  }
88
193
 
89
194
  query.append(";\n");
90
195
 
196
+ if (options?.fields) {
197
+ for (const [fieldName, fieldSchema] of Object.entries(def.fields)) {
198
+ query.append(
199
+ defineField(
200
+ fieldName,
201
+ table.name,
202
+ fieldName === "id"
203
+ ? (fieldSchema as SurrealZodRecordId)._zod.def.innerType
204
+ : fieldSchema,
205
+ {
206
+ exists: options.exists,
207
+ schemafull: surreal.schemafull,
208
+ },
209
+ ),
210
+ );
211
+ }
212
+ }
213
+
91
214
  return query;
92
215
  }
93
216
 
94
- function defineField(options: {
95
- name: string;
217
+ export function isNormalTable(
218
+ table: SurrealZodTable,
219
+ ): table is SurrealZodTableNormal {
220
+ return table._zod.def.surreal.tableType === "normal";
221
+ }
222
+
223
+ export function isRelationTable(
224
+ table: SurrealZodTable,
225
+ ): table is SurrealZodTableRelation {
226
+ return table._zod.def.surreal.tableType === "relation";
227
+ }
228
+
229
+ export interface ZodToSurqlOptions<S extends z4.$ZodObject> {
96
230
  table: string | Table;
97
- type: z4.$ZodType;
231
+ schemafull?: boolean;
98
232
  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
- }
233
+ drop?: boolean;
234
+ comment?: string;
235
+ schema: S;
236
+ }
111
237
 
112
- const context: ZodSurrealTypeContext = {
113
- name,
114
- table,
115
- rootSchema: schema,
116
- children: [],
117
- asserts: [],
118
- transforms: [],
119
- };
238
+ export type DefineFieldOptions = {
239
+ exists?: "ignore" | "error" | "overwrite";
240
+ schemafull?: boolean;
241
+ };
242
+
243
+ function defineField(
244
+ name: string,
245
+ table: string,
246
+ schema: SurrealZodType,
247
+ options?: DefineFieldOptions,
248
+ ) {
249
+ // const context: ZodSurrealTypeContext = {
250
+ // name,
251
+ // table,
252
+ // rootSchema: schema,
253
+ // children: [],
254
+ // asserts: [],
255
+ // transforms: [],
256
+ // };
120
257
  const query = surql`DEFINE FIELD`;
121
258
 
122
- if (options.exists === "ignore") {
259
+ if (options?.exists === "ignore") {
123
260
  query.append(" IF NOT EXISTS");
124
- } else if (options.exists === "overwrite") {
261
+ } else if (options?.exists === "overwrite") {
125
262
  query.append(" OVERWRITE");
126
263
  }
127
264
 
128
- query.append(` ${name} ON TABLE ${table.name}`);
265
+ query.append(` ${name} ON TABLE ${escapeIdent(table)}`);
129
266
 
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(`}`);
267
+ const context: ZodSurrealTypeContext = {
268
+ type: new Set(),
269
+ depth: 0,
270
+ children: [],
271
+ flexible: false,
272
+ };
273
+ query.append(` TYPE ${inferSurrealType(schema, context)}`);
274
+ if (options?.schemafull && context.flexible) {
275
+ query.append(" FLEXIBLE");
152
276
  }
153
277
 
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
- }
278
+ // if (options.exists === "ignore") {
279
+ // query.append(" IF NOT EXISTS");
280
+ // } else if (options.exists === "overwrite") {
281
+ // query.append(" OVERWRITE");
282
+ // }
283
+
284
+ // query.append(` ${name} ON TABLE ${table.name}`);
285
+
286
+ // const type =
287
+ // name === "id"
288
+ // ? inferSurrealType(
289
+ // (schema as unknown as SurrealZodRecordId)._zod.def.innerType,
290
+ // [],
291
+ // context,
292
+ // )
293
+ // : inferSurrealType(schema, [], context);
294
+
295
+ // query.append(` TYPE ${type}`);
296
+
297
+ // if (context.default) {
298
+ // query.append(
299
+ // context.default.always
300
+ // ? ` DEFAULT ALWAYS ${JSON.stringify(context.default.value)}`
301
+ // : ` DEFAULT ${JSON.stringify(context.default.value)}`,
302
+ // );
303
+ // }
304
+
305
+ // if (context.transforms.length > 0) {
306
+ // query.append(` VALUE {\n`);
307
+ // for (const transform of context.transforms) {
308
+ // query.append(
309
+ // dedent.withOptions({ alignValues: true })`
310
+ // //
311
+ // ${transform}\n`.slice(3),
312
+ // );
313
+ // }
314
+ // query.append(`}`);
315
+ // }
316
+
317
+ // if (context.asserts.length > 0) {
318
+ // query.append(` ASSERT {\n`);
319
+ // for (const assert of context.asserts) {
320
+ // query.append(
321
+ // dedent.withOptions({ alignValues: true })`
322
+ // //
323
+ // ${assert}\n`.slice(3),
324
+ // );
325
+ // }
326
+ // query.append(`}`);
327
+ // }
165
328
 
166
329
  query.append(`;\n`);
167
330
 
168
331
  if (context.children.length > 0) {
169
332
  for (const { name: childName, type: childType } of context.children) {
170
333
  query.append(
171
- defineField({
172
- name: `${name}.${childName}`,
334
+ defineField(
335
+ `${escapeIdent(name)}.${escapeIdent(childName)}`,
173
336
  table,
174
- type: childType,
175
- exists: options.exists,
176
- }),
337
+ childType as SurrealZodType,
338
+ {
339
+ exists: options?.exists,
340
+ },
341
+ ),
177
342
  );
178
343
  }
179
344
  }
@@ -182,312 +347,312 @@ function defineField(options: {
182
347
  }
183
348
 
184
349
  type ZodSurrealTypeContext = {
185
- name: string;
186
- table: Table;
187
- rootSchema: z4.$ZodType;
350
+ // name: string;
351
+ // table: Table;
352
+ // rootSchema: z4.$ZodType;
353
+ // children: ZodSurrealChildType[];
354
+ // asserts: string[];
355
+ // transforms: string[];
356
+ // default?: { value: any; always: boolean };
357
+ type: Set<string>;
358
+ depth: number;
188
359
  children: ZodSurrealChildType[];
189
- asserts: string[];
190
- transforms: string[];
191
- default?: { value: any; always: boolean };
360
+ flexible: boolean;
192
361
  };
193
362
  type ZodSurrealChildType = { name: string; type: z4.$ZodType };
194
363
 
195
- export function zodTypeToSurrealType(
196
- type: z4.$ZodType | SurrealZodType,
197
- parents: string[] = [],
364
+ export function inferSurrealType(
365
+ type: SurrealZodType,
198
366
  context: ZodSurrealTypeContext,
199
367
  ): string {
200
- const schema = type as z4.$ZodTypes | SurrealZodTypes;
368
+ const schema = type as SurrealZodTypes;
201
369
  if (!("_zod" in schema)) {
202
370
  throw new Error(
203
371
  "Invalid schema provided, make sure you are using zod v4 as zod v3 is currently not supported.",
204
372
  );
205
373
  }
206
374
 
375
+ // if ("surreal" in schema._zod.def) {
376
+ // return schema._zod.def.surreal.type;
377
+ // } else {
378
+ // throw new Error(
379
+ // // @ts-expect-error - zod core not supported
380
+ // `Invalid surreal schema provided. Received ${schema._zod.def.type}`,
381
+ // );
382
+ // }
383
+
207
384
  const def = schema._zod.def;
208
- const checks = getChecks(schema);
209
- parseChecks(context.name, checks, context, def.type);
385
+ const childIndent = " ".repeat(context.depth + 1);
386
+ // const checks = getChecks(schema);
387
+ // parseChecks(context.name, checks, context, def.type);
210
388
  // console.log(zodToSexpr(type));
211
389
 
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";
390
+ switch (def.surreal?.type ?? def.type) {
391
+ case "any":
392
+ case "unknown": {
393
+ context.type.add("any");
394
+ break;
251
395
  }
396
+ case "never":
252
397
  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`;
398
+ context.type.add("none");
399
+ break;
284
400
  }
285
401
  case "optional": {
286
- const inner = zodTypeToSurrealType(
287
- // TODO: remove any
288
- (def as any).innerType,
289
- [...parents, def.type],
402
+ inferSurrealType(
403
+ (type as SurrealZodOptional)._zod.def.innerType,
290
404
  context,
291
405
  );
292
- if (parents.includes("optional") || parents.includes("nonoptional")) {
293
- return inner;
294
- }
295
- return `option<${inner}>`;
406
+ context.type.add("none");
407
+ break;
296
408
  }
297
409
  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],
410
+ inferSurrealType(
411
+ (type as SurrealZodNonOptional)._zod.def.innerType,
303
412
  context,
304
413
  );
414
+
415
+ if (context.type.size > 1 && context.type.has("none")) {
416
+ context.type.delete("none");
417
+ }
418
+ break;
305
419
  }
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
- );
420
+ case "null": {
421
+ context.type.add("null");
422
+ break;
316
423
  }
317
- case "array": {
318
- const inner = zodTypeToSurrealType(
319
- // TODO: remove any
320
- (def as any).element,
321
- [...parents, def.type],
424
+ case "nullable": {
425
+ inferSurrealType(
426
+ (type as SurrealZodNullable)._zod.def.innerType,
322
427
  context,
323
428
  );
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);
429
+ context.type.add("null");
430
+ break;
356
431
  }
357
- if (assert) {
358
- context.asserts.push(assert);
432
+ case "boolean": {
433
+ context.type.add("bool");
434
+ break;
359
435
  }
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"}' };`;
436
+ case "string": {
437
+ context.type.add("string");
438
+ break;
367
439
  }
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"}' };`;
440
+ case "bigint":
441
+ case "number": {
442
+ context.type.add("number");
443
+ break;
378
444
  }
445
+ case "object": {
446
+ const _schema = schema as SurrealZodObject;
447
+ const shape = _schema._zod.def.shape;
448
+ const catchall = _schema._zod.def.catchall;
449
+ const isStrict = catchall?._zod.traits.has("$ZodNever");
450
+ const isLoose = catchall?._zod.traits.has("$ZodUnknown");
451
+
452
+ // buggy syntax
453
+ // if (isStrict) {
454
+ // let type = "{";
455
+ // if (Object.keys(shape).length > 0) {
456
+ // type += "\n";
457
+ // }
458
+ // for (const [key, value] of Object.entries(shape)) {
459
+ // const childContext: ZodSurrealTypeContext = {
460
+ // type: new Set(),
461
+ // depth: context.depth + 1,
462
+ // children: [],
463
+ // };
464
+ // type += `${childIndent}${escapeIdent(key)}: ${inferSurrealType(value, childContext)},\n`;
465
+ // }
466
+ // type += "}";
467
+ // context.type.add(type);
468
+ // break;
469
+ // }
379
470
 
380
- if (type === "string") {
381
- return `$value.len() <= ${value} || { THROW 'Field "${name}" must be at most ${value} ${value === 1 ? "character" : "characters"} long' };`;
471
+ context.type.add("object");
472
+ if (isLoose) context.flexible = true;
473
+ for (const [key, value] of Object.entries(shape)) {
474
+ context.children.push({ name: key, type: value });
475
+ }
476
+ break;
382
477
  }
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"}' };`;
478
+ case "record_id": {
479
+ const table = (def as SurrealZodRecordId["_zod"]["def"]).table;
480
+ if (table) {
481
+ context.type.add(`record<${table.map(escapeIdent).join(" | ")}>`);
482
+ } else {
483
+ context.type.add("record");
484
+ }
485
+ break;
395
486
  }
396
-
397
- if (type === "string") {
398
- return `$value.len() == ${value} || { THROW 'Field "${name}" must be exactly ${value} ${value === 1 ? "character" : "characters"} long' };`;
487
+ case "table": {
488
+ throw new Error("Table type cannot be used as a field type");
399
489
  }
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
490
  }
472
- }
473
491
 
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
+ if (context.type.has("any")) {
493
+ return "any";
492
494
  }
495
+
496
+ return Array.from(context.type).join(" | ");
493
497
  }
498
+
499
+ // function getChecks(_schema: z4.$ZodType | SurrealZodType) {
500
+ // const schema = _schema as z4.$ZodTypes | SurrealZodTypes;
501
+ // const checks = schema._zod.def.checks ?? [];
502
+ // if ("check" in schema._zod.def) {
503
+ // checks.unshift(schema as z4.$ZodCheck);
504
+ // }
505
+ // return checks;
506
+ // }
507
+
508
+ // function parseChecks(
509
+ // name: string,
510
+ // checks: z4.$ZodCheck[],
511
+ // context: ZodSurrealTypeContext,
512
+ // type: ZodTypeName | SurrealZodTypeName,
513
+ // ) {
514
+ // for (const check of checks) {
515
+ // const { transform, assert } = parseCheck(name, check, type);
516
+ // if (transform) {
517
+ // context.transforms.push(transform);
518
+ // }
519
+ // if (assert) {
520
+ // context.asserts.push(assert);
521
+ // }
522
+ // }
523
+ // }
524
+
525
+ // export const checkMap = {
526
+ // never(name: string) {
527
+ // return `THROW 'Field "${name}" must never be present'`;
528
+ // },
529
+ // min_length(name: string, value: number, type: ZodTypeName) {
530
+ // if (type === "array") {
531
+ // return `$value.len() >= ${value} || { THROW 'Field "${name}" must have at least ${value} ${value === 1 ? "item" : "items"}' };`;
532
+ // }
533
+
534
+ // if (type === "string") {
535
+ // return `$value.len() >= ${value} || { THROW 'Field "${name}" must be at least ${value} ${value === 1 ? "character" : "characters"} long' };`;
536
+ // }
537
+
538
+ // throw new Error(`Invalid type: ${type}`);
539
+ // },
540
+ // max_length(name: string, value: number, type: ZodTypeName) {
541
+ // if (type === "array") {
542
+ // return `$value.len() <= ${value} || { THROW 'Field "${name}" must have at most ${value} ${value === 1 ? "item" : "items"}' };`;
543
+ // }
544
+
545
+ // if (type === "string") {
546
+ // return `$value.len() <= ${value} || { THROW 'Field "${name}" must be at most ${value} ${value === 1 ? "character" : "characters"} long' };`;
547
+ // }
548
+
549
+ // throw new Error(`Invalid type: ${type}`);
550
+ // },
551
+ // greater_than(name: string, value: z4.util.Numeric, inclusive: boolean) {
552
+ // return `$value ${inclusive ? ">=" : ">"} ${value} || { THROW 'Field "${name}" must be greater than ${inclusive ? "or equal to" : ""} ${value}' };`;
553
+ // },
554
+ // less_than(name: string, value: z4.util.Numeric, inclusive: boolean) {
555
+ // return `$value ${inclusive ? "<=" : "<"} ${value} || { THROW 'Field "${name}" must be less than ${inclusive ? "or equal to" : ""} ${value}' };`;
556
+ // },
557
+ // length_equals(name: string, value: number, type: ZodTypeName = "string") {
558
+ // if (type === "array") {
559
+ // return `$value.len() == ${value} || { THROW 'Field "${name}" must have exactly ${value} ${value === 1 ? "item" : "items"}' };`;
560
+ // }
561
+
562
+ // if (type === "string") {
563
+ // return `$value.len() == ${value} || { THROW 'Field "${name}" must be exactly ${value} ${value === 1 ? "character" : "characters"} long' };`;
564
+ // }
565
+
566
+ // throw new Error(`Invalid type: ${type}`);
567
+ // },
568
+
569
+ // string_format: {
570
+ // email: (name: string) => {
571
+ // const regex =
572
+ // /^[A-Za-z0-9'_+-]+(?:\.[A-Za-z0-9'_+-]+)*@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/;
573
+ // return `string::matches($value, ${regex}) || { THROW "Field '${name}' must be a valid email address" };`;
574
+ // },
575
+ // url: (
576
+ // name: string,
577
+ // def?: Pick<z4.$ZodCheckURLParams, "hostname" | "protocol" | "normalize">,
578
+ // ) => {
579
+ // return dedent`
580
+ // LET $url = {
581
+ // scheme: parse::url::scheme($value),
582
+ // host: parse::url::host($value),
583
+ // domain: parse::url::domain($value),
584
+ // path: parse::url::path($value),
585
+ // port: parse::url::port($value),
586
+ // query: parse::url::query($value),
587
+ // hash: parse::url::fragment($value),
588
+ // };
589
+ // $url.scheme || { THROW "Field '${name}' must be a valid URL" };
590
+ // ${
591
+ // def?.hostname
592
+ // ? `($url.host ?? "").matches(${def.hostname}) || { THROW "Field '${name}' must match hostname ${def.hostname.toString().replace(/\\/g, "\\\\")}" };`
593
+ // : ""
594
+ // }
595
+ // ${
596
+ // def?.protocol
597
+ // ? `($url.scheme ?? "").matches(${def.protocol}) || { THROW "Field '${name}' must match protocol ${def.protocol.toString().replace(/\\/g, "\\\\")}" };`
598
+ // : ""
599
+ // }
600
+ // $url.scheme + "://" + ($url.host ?? "") + (
601
+ // IF $url.port && (
602
+ // ($url.scheme == "http" && $url.port != 80) ||
603
+ // ($url.scheme == "https" && $url.port != 443)
604
+ // ) { ":" + <string>$url.port } ?? ""
605
+ // )
606
+ // + ($url.path ?? "")
607
+ // + (IF $url.query { "?" + $url.query } ?? "")
608
+ // + (IF $url.fragment { "#" + $url.fragment } ?? "");
609
+ // `;
610
+ // },
611
+ // },
612
+ // };
613
+
614
+ // function parseCheck(
615
+ // name: string,
616
+ // _check: z4.$ZodCheck,
617
+ // type: ZodTypeName,
618
+ // ): { transform?: string; assert?: string } {
619
+ // const check = _check as z4.$ZodChecks;
620
+ // const def = check._zod.def;
621
+ // switch (def.check) {
622
+ // case "min_length":
623
+ // return { assert: checkMap.min_length(name, def.minimum, type) };
624
+ // case "max_length":
625
+ // return { assert: checkMap.max_length(name, def.maximum, type) };
626
+ // case "greater_than":
627
+ // return { assert: checkMap.greater_than(name, def.value, def.inclusive) };
628
+ // case "less_than":
629
+ // return { assert: checkMap.less_than(name, def.value, def.inclusive) };
630
+ // case "length_equals":
631
+ // return { assert: checkMap.length_equals(name, def.length, type) };
632
+ // case "string_format":
633
+ // return assertionForStringFormat(name, check);
634
+ // default:
635
+ // return { assert: `THROW 'Unknown check: ${def.check}';` };
636
+ // }
637
+ // }
638
+
639
+ // // Remove look-around, look-behind, and look-ahead as they are not supported by SurrealDB
640
+ // function assertionForStringFormat(
641
+ // name: string,
642
+ // _check: z4.$ZodCheck,
643
+ // ): { transform?: string; assert?: string } {
644
+ // const check = _check as z4.$ZodStringFormatChecks;
645
+ // const def = check._zod.def;
646
+
647
+ // switch (def.format) {
648
+ // case "email": {
649
+ // return { assert: checkMap.string_format.email(name) };
650
+ // }
651
+ // case "url": {
652
+ // const code = checkMap.string_format.url(name, def);
653
+ // return def.normalize ? { transform: code } : { assert: code };
654
+ // }
655
+ // default:
656
+ // return { assert: `THROW 'Unsupported string format: ${def.format}';` };
657
+ // }
658
+ // }