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

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
@@ -2,28 +2,17 @@ import {
2
2
  BoundQuery,
3
3
  escapeIdent,
4
4
  escapeIdPart,
5
- RecordId,
6
5
  surql,
7
6
  Table,
7
+ toSurqlString as baseToSurqlString,
8
8
  } from "surrealdb";
9
- import * as z4 from "zod/v4/core";
10
- import z from "zod";
9
+ import * as core from "zod/v4/core";
10
+ import * as _schema_ from "./zod/schema";
11
+ import * as _core_ from "./zod/core";
11
12
  import dedent from "dedent";
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";
24
-
25
- export type ZodTypeName = z4.$ZodType["_zod"]["def"]["type"];
26
- export type SurrealZodTypeName = SurrealZodType["_zod"]["def"]["type"];
13
+
14
+ const OPEN_ISSUE_FOR_SUPPORT =
15
+ " If you need this, please open an issue on the repository so we can look into your use case and possible implementation. https://github.com/msanchezdev/surreal-zod/issues";
27
16
 
28
17
  /////////////////////////////////////
29
18
  /////////////////////////////////////
@@ -34,25 +23,25 @@ export type SurrealZodTypeName = SurrealZodType["_zod"]["def"]["type"];
34
23
  /////////////////////////////////////
35
24
 
36
25
  export function tableToSurql(
37
- table: SurrealZodTable,
26
+ table: _schema_.ZodSurrealTable,
38
27
  statement: "define",
39
28
  defineOptions?: DefineTableOptions,
40
29
  ): BoundQuery<[undefined]>;
41
30
  export function tableToSurql(
42
- table: SurrealZodTable,
31
+ table: _schema_.ZodSurrealTable,
43
32
  statement: "remove",
44
33
  removeOptions?: RemoveTableOptions,
45
34
  ): BoundQuery<[undefined]>;
46
35
  export function tableToSurql(
47
- table: SurrealZodTable,
36
+ table: _schema_.ZodSurrealTable,
48
37
  statement: "info",
49
38
  ): BoundQuery<[TableInfo]>;
50
39
  export function tableToSurql(
51
- table: SurrealZodTable,
40
+ table: _schema_.ZodSurrealTable,
52
41
  statement: "structure",
53
42
  ): BoundQuery<[TableStructure]>;
54
43
  export function tableToSurql(
55
- table: SurrealZodTable,
44
+ table: _schema_.ZodSurrealTable,
56
45
  statement: "define" | "info" | "structure" | "remove",
57
46
  options?: DefineTableOptions | RemoveTableOptions,
58
47
  ): BoundQuery<[TableInfo | TableStructure]> {
@@ -82,7 +71,7 @@ export type RemoveTableOptions = {
82
71
  };
83
72
 
84
73
  export function removeTable(
85
- table: SurrealZodTable,
74
+ table: _schema_.ZodSurrealTable,
86
75
  options?: RemoveTableOptions,
87
76
  ): BoundQuery<[undefined]> {
88
77
  const name = table._zod.def.name;
@@ -104,7 +93,9 @@ export interface TableInfo {
104
93
  tables: Record<string, string>;
105
94
  }
106
95
 
107
- export function infoTable(table: SurrealZodTable): BoundQuery<[TableInfo]> {
96
+ export function infoTable(
97
+ table: _schema_.ZodSurrealTable,
98
+ ): BoundQuery<[TableInfo]> {
108
99
  const name = table._zod.def.name;
109
100
  const query = surql`INFO FOR TABLE`;
110
101
  query.append(` ${escapeIdent(name)}`);
@@ -133,7 +124,7 @@ export interface FieldStructure {
133
124
  }
134
125
 
135
126
  export function structureTable(
136
- table: SurrealZodTable,
127
+ table: _schema_.ZodSurrealTable,
137
128
  ): BoundQuery<[TableStructure]> {
138
129
  const name = table._zod.def.name;
139
130
  const query = surql`INFO FOR TABLE`;
@@ -148,7 +139,7 @@ export type DefineTableOptions = {
148
139
  };
149
140
 
150
141
  export function defineTable(
151
- schema: SurrealZodTable,
142
+ schema: _schema_.ZodSurrealTable,
152
143
  options?: DefineTableOptions,
153
144
  ): BoundQuery<[undefined, ...undefined[]]> {
154
145
  const def = schema._zod.def;
@@ -194,17 +185,45 @@ export function defineTable(
194
185
  query.append(";\n");
195
186
 
196
187
  if (options?.fields) {
197
- for (const [fieldName, fieldSchema] of Object.entries(def.fields)) {
188
+ for (const [fieldName, fieldSchema] of Object.entries(def.fields) as [
189
+ string,
190
+ _core_.$ZodSurrealType,
191
+ ][]) {
192
+ let defaultValue: DefineFieldOptions["default"];
193
+ let comment: DefineFieldOptions["comment"];
194
+ let readonly: DefineFieldOptions["readonly"];
195
+ let assert: DefineFieldOptions["assert"];
196
+ let value: DefineFieldOptions["value"];
197
+ if (fieldSchema instanceof _schema_.ZodSurrealField) {
198
+ defaultValue = fieldSchema._zod.def.surreal.field?.default;
199
+ comment = fieldSchema._zod.def.surreal.field?.comment;
200
+ readonly = fieldSchema._zod.def.surreal.field?.readonly;
201
+ assert = fieldSchema._zod.def.surreal.field?.assert;
202
+ value = fieldSchema._zod.def.surreal.field?.value;
203
+ }
204
+
198
205
  query.append(
199
206
  defineField(
200
207
  fieldName,
201
208
  table.name,
202
209
  fieldName === "id"
203
- ? (fieldSchema as SurrealZodRecordId)._zod.def.innerType
210
+ ? (fieldSchema as _schema_.ZodSurrealdRecordId)._zod.def.innerType
204
211
  : fieldSchema,
205
212
  {
206
213
  exists: options.exists,
207
214
  schemafull: surreal.schemafull,
215
+ default: defaultValue,
216
+ comment,
217
+ readonly,
218
+ assert,
219
+ value,
220
+ // @ts-expect-error - @internal
221
+ // We need to add the table schema to the fullParents set to avoid
222
+ // infinite recursion. Child fields keep a separate context from
223
+ // the table or other fields.
224
+ // The resting point for fullParents is in the inferSurrealType function,
225
+ // on the switch case for the "lazy" type.
226
+ fullParents: new Set([schema]),
208
227
  },
209
228
  ),
210
229
  );
@@ -214,19 +233,23 @@ export function defineTable(
214
233
  return query;
215
234
  }
216
235
 
217
- export function isNormalTable(
218
- table: SurrealZodTable,
219
- ): table is SurrealZodTableNormal {
220
- return table._zod.def.surreal.tableType === "normal";
221
- }
222
-
223
236
  export function isRelationTable(
224
- table: SurrealZodTable,
225
- ): table is SurrealZodTableRelation {
226
- return table._zod.def.surreal.tableType === "relation";
237
+ table: unknown,
238
+ ): table is _schema_.ZodSurrealTable<
239
+ string,
240
+ {
241
+ [K in "in" | "out" | "id"]: _schema_.ZodSurrealdRecordId;
242
+ },
243
+ _schema_.SurrealZodTableConfig,
244
+ "relation"
245
+ > {
246
+ return (
247
+ table instanceof _schema_.ZodSurrealTable &&
248
+ table._zod.def.surreal.tableType === "relation"
249
+ );
227
250
  }
228
251
 
229
- export interface ZodToSurqlOptions<S extends z4.$ZodObject> {
252
+ export interface ZodToSurqlOptions<S extends _schema_.ZodSurrealObject> {
230
253
  table: string | Table;
231
254
  schemafull?: boolean;
232
255
  exists?: "ignore" | "error" | "overwrite";
@@ -238,22 +261,25 @@ export interface ZodToSurqlOptions<S extends z4.$ZodObject> {
238
261
  export type DefineFieldOptions = {
239
262
  exists?: "ignore" | "error" | "overwrite";
240
263
  schemafull?: boolean;
264
+ default?: {
265
+ value: BoundQuery;
266
+ always?: boolean;
267
+ prase?: boolean;
268
+ };
269
+ comment?: string;
270
+ readonly?: boolean;
271
+ assert?: BoundQuery;
272
+ value?: BoundQuery;
273
+ // @internal
274
+ // fullParents?: Set<core.$ZodType>;
241
275
  };
242
276
 
243
- function defineField(
277
+ export function defineField(
244
278
  name: string,
245
279
  table: string,
246
- schema: SurrealZodType,
280
+ schema: _core_.$ZodSurrealType,
247
281
  options?: DefineFieldOptions,
248
282
  ) {
249
- // const context: ZodSurrealTypeContext = {
250
- // name,
251
- // table,
252
- // rootSchema: schema,
253
- // children: [],
254
- // asserts: [],
255
- // transforms: [],
256
- // };
257
283
  const query = surql`DEFINE FIELD`;
258
284
 
259
285
  if (options?.exists === "ignore") {
@@ -267,26 +293,45 @@ function defineField(
267
293
  const context: ZodSurrealTypeContext = {
268
294
  type: new Set(),
269
295
  depth: 0,
296
+ parents: new Set(),
297
+ // @ts-expect-error - @internal
298
+ fullParents: options?.fullParents ?? new Set(),
270
299
  children: [],
271
300
  flexible: false,
272
301
  };
273
- query.append(` TYPE ${inferSurrealType(schema, context)}`);
302
+ query.append(` TYPE ${inferSurrealType(schema, context).type}`);
274
303
  if (options?.schemafull && context.flexible) {
275
304
  query.append(" FLEXIBLE");
276
305
  }
277
306
 
278
- // if (options.exists === "ignore") {
279
- // query.append(" IF NOT EXISTS");
280
- // } else if (options.exists === "overwrite") {
281
- // query.append(" OVERWRITE");
282
- // }
307
+ if (options?.readonly) {
308
+ query.append(" READONLY");
309
+ }
283
310
 
284
- // query.append(` ${name} ON TABLE ${table.name}`);
311
+ if (options?.default) {
312
+ query.append(
313
+ options.default.always
314
+ ? ` DEFAULT ALWAYS ${formatQuery(inlineQueryParameters(options.default.value))}`
315
+ : ` DEFAULT ${formatQuery(inlineQueryParameters(options.default.value))}`,
316
+ );
317
+ }
318
+
319
+ if (options?.value) {
320
+ query.append(` VALUE ${inlineQueryParameters(options.value)}`);
321
+ }
322
+
323
+ if (options?.assert) {
324
+ query.append(` ASSERT ${inlineQueryParameters(options.assert)}`);
325
+ }
326
+
327
+ if (options?.comment) {
328
+ query.append(` COMMENT ${JSON.stringify(options.comment)}`);
329
+ }
285
330
 
286
331
  // const type =
287
332
  // name === "id"
288
333
  // ? inferSurrealType(
289
- // (schema as unknown as SurrealZodRecordId)._zod.def.innerType,
334
+ // (schema as unknown as ZodSurrealRecordId)._zod.def.innerType,
290
335
  // [],
291
336
  // context,
292
337
  // )
@@ -329,14 +374,17 @@ function defineField(
329
374
  query.append(`;\n`);
330
375
 
331
376
  if (context.children.length > 0) {
377
+ context.fullParents.add(schema);
332
378
  for (const { name: childName, type: childType } of context.children) {
333
379
  query.append(
334
380
  defineField(
335
- `${escapeIdent(name)}.${escapeIdent(childName)}`,
381
+ `${escapeIdent(name)}.${childName === "*" ? childName : escapeIdent(childName)}`,
336
382
  table,
337
- childType as SurrealZodType,
383
+ childType as _core_.$ZodSurrealType,
338
384
  {
339
385
  exists: options?.exists,
386
+ // @ts-expect-error - @internal
387
+ fullParents: context.fullParents,
340
388
  },
341
389
  ),
342
390
  );
@@ -355,304 +403,700 @@ type ZodSurrealTypeContext = {
355
403
  // transforms: string[];
356
404
  // default?: { value: any; always: boolean };
357
405
  type: Set<string>;
406
+ parents: Set<_core_.$ZodSurrealType>;
407
+ fullParents: Set<_core_.$ZodSurrealType>;
358
408
  depth: number;
359
409
  children: ZodSurrealChildType[];
360
410
  flexible: boolean;
361
411
  };
362
- type ZodSurrealChildType = { name: string; type: z4.$ZodType };
412
+ type ZodSurrealChildType = { name: string; type: core.$ZodType };
413
+
414
+ function createContext(
415
+ override?: Partial<ZodSurrealTypeContext>,
416
+ ): ZodSurrealTypeContext {
417
+ return {
418
+ type: new Set<string>(),
419
+ depth: 0,
420
+ parents: new Set<_core_.$ZodSurrealType>(),
421
+ fullParents: new Set<_core_.$ZodSurrealType>(),
422
+ children: [],
423
+ flexible: false,
424
+ ...override,
425
+ };
426
+ }
363
427
 
364
428
  export function inferSurrealType(
365
- type: SurrealZodType,
366
- context: ZodSurrealTypeContext,
367
- ): string {
368
- const schema = type as SurrealZodTypes;
429
+ type: _core_.$ZodSurrealType,
430
+ context?: ZodSurrealTypeContext,
431
+ ): { type: string; context: ZodSurrealTypeContext } {
432
+ context ??= createContext();
433
+
434
+ function enter(
435
+ type: _core_.$ZodSurrealType,
436
+ ctx?: Omit<Partial<ZodSurrealTypeContext>, "parents"> | true,
437
+ ) {
438
+ context ??= createContext();
439
+ if (context.parents.has(type)) {
440
+ console.warn("Recursive type detected", type._zod.def.type);
441
+ context.type.add("any");
442
+ // throw new Error("Recursive type detected");
443
+ return { type: "any", context };
444
+ }
445
+
446
+ const newContext = ctx
447
+ ? {
448
+ children: [],
449
+ flexible: false,
450
+ type: new Set<string>(),
451
+ depth: context.depth + 1,
452
+ ...(typeof ctx === "object" ? ctx : {}),
453
+ parents: context.parents,
454
+ fullParents: context.fullParents,
455
+ }
456
+ : context;
457
+ context.parents.add(type);
458
+ context.fullParents.add(type);
459
+ const result = inferSurrealType(type, newContext);
460
+ context.fullParents.delete(type);
461
+ context.parents.delete(type);
462
+ return result;
463
+ }
464
+
465
+ const schema = type as _schema_.ZodSurrealTypes;
369
466
  if (!("_zod" in schema)) {
370
- throw new Error(
371
- "Invalid schema provided, make sure you are using zod v4 as zod v3 is currently not supported.",
372
- );
467
+ throw new Error("Invalid schema provided, make sure you are using zod v4+");
373
468
  }
374
469
 
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
- // }
470
+ // console.log(schema);
383
471
 
384
472
  const def = schema._zod.def;
385
- const childIndent = " ".repeat(context.depth + 1);
386
473
  // const checks = getChecks(schema);
387
474
  // parseChecks(context.name, checks, context, def.type);
388
475
  // console.log(zodToSexpr(type));
476
+ // console.log(def);
477
+ if (def.surreal.type) {
478
+ context.type.add(def.surreal.type);
479
+ } else
480
+ switch (/* isSurrealZodSchemaDef(def) ? */ def.type /*: def.type*/) {
481
+ // case "record_id": {
482
+ // const table = (def as ZodSurrealdRecordId["_zod"]["def"]).table;
483
+ // if (table) {
484
+ // context.type.add(`record<${table.map(escapeIdent).join(" | ")}>`);
485
+ // } else {
486
+ // context.type.add("record");
487
+ // }
488
+ // break;
489
+ // }
490
+ // case "uuid": {
491
+ // context.type.add("uuid");
492
+ // break;
493
+ // }
494
+ // case "table": {
495
+ // throw new Error(
496
+ // `Table type cannot be used as a field type.${OPEN_ISSUE_FOR_SUPPORT}`,
497
+ // );
498
+ // }
389
499
 
390
- switch (def.surreal?.type ?? def.type) {
391
- case "any":
392
- case "unknown": {
393
- context.type.add("any");
394
- break;
395
- }
396
- case "never":
397
- case "undefined": {
398
- context.type.add("none");
399
- break;
400
- }
401
- case "optional": {
402
- inferSurrealType(
403
- (type as SurrealZodOptional)._zod.def.innerType,
404
- context,
405
- );
406
- context.type.add("none");
407
- break;
408
- }
409
- case "nonoptional": {
410
- inferSurrealType(
411
- (type as SurrealZodNonOptional)._zod.def.innerType,
412
- context,
413
- );
500
+ // case "void":
501
+ // case "never":
502
+ // case "undefined": {
503
+ // context.type.add("none");
504
+ // break;
505
+ // }
506
+ case "optional": {
507
+ enter(def.innerType);
508
+ context.type.add("none");
509
+ break;
510
+ }
511
+ case "nonoptional": {
512
+ enter(def.innerType);
414
513
 
415
- if (context.type.size > 1 && context.type.has("none")) {
416
- context.type.delete("none");
514
+ if (context.type.size > 1 && context.type.has("none")) {
515
+ context.type.delete("none");
516
+ }
517
+ break;
417
518
  }
418
- break;
419
- }
420
- case "null": {
421
- context.type.add("null");
422
- break;
423
- }
424
- case "nullable": {
425
- inferSurrealType(
426
- (type as SurrealZodNullable)._zod.def.innerType,
427
- context,
428
- );
429
- context.type.add("null");
430
- break;
431
- }
432
- case "boolean": {
433
- context.type.add("bool");
434
- break;
435
- }
436
- case "string": {
437
- context.type.add("string");
438
- break;
439
- }
440
- case "bigint":
441
- case "number": {
442
- context.type.add("number");
443
- break;
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";
519
+ case "nullable": {
520
+ enter(def.innerType);
521
+ context.type.add("null");
522
+ break;
523
+ }
524
+ // case "boolean": {
525
+ // context.type.add("bool");
526
+ // break;
527
+ // }
528
+ // case "string": {
529
+ // // Needs override, this will not work with original zod types
530
+ // // if (isStringFormat(def)) {
531
+ // // switch (def.format) {
532
+ // // case "uuid":
533
+ // // case "guid":
534
+ // // context.type.add("uuid");
535
+ // // break TYPE_CHECK;
536
+ // // }
537
+ // // }
538
+
539
+ // context.type.add("string");
540
+ // break;
541
+ // }
542
+ // case "bigint": {
543
+ // if (!isBigIntFormat(def as core.$ZodBigIntFormatDef)) {
544
+ // context.type.add("int");
545
+ // break;
457
546
  // }
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`;
547
+
548
+ // switch ((def as core.$ZodBigIntFormatDef).format) {
549
+ // case "int64":
550
+ // case "uint64":
551
+ // context.type.add("int");
552
+ // break;
553
+ // default:
554
+ // throw new Error(
555
+ // `Unsupported bigint format: ${(def as core.$ZodBigIntFormatDef).format}`,
556
+ // );
465
557
  // }
466
- // type += "}";
467
- // context.type.add(type);
468
558
  // break;
469
559
  // }
560
+ // case "number": {
561
+ // if (!isNumberFormat(def as core.$ZodNumberDef)) {
562
+ // context.type.add("number");
563
+ // break;
564
+ // }
470
565
 
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 });
566
+ // switch ((def as core.$ZodNumberFormatDef).format) {
567
+ // case "uint32":
568
+ // case "safeint":
569
+ // case "int32":
570
+ // context.type.add("int");
571
+ // break;
572
+ // case "float64":
573
+ // case "float32":
574
+ // context.type.add("float");
575
+ // break;
576
+ // default:
577
+ // throw new Error(
578
+ // `Unsupported number format: ${(def as core.$ZodNumberFormatDef).format}. ${OPEN_ISSUE_FOR_SUPPORT}`,
579
+ // );
580
+ // }
581
+ // break;
582
+ // }
583
+ // case "date": {
584
+ // context.type.add("datetime");
585
+ // break;
586
+ // }
587
+ case "object": {
588
+ const shape = (def as core.$ZodObjectDef).shape;
589
+ const catchall = (def as core.$ZodObjectDef).catchall;
590
+ const isStrict = catchall?._zod.traits.has("$ZodNever");
591
+ const isLoose = catchall?._zod.traits.has("$ZodUnknown");
592
+
593
+ // buggy syntax
594
+ // if (isStrict) {
595
+ // let type = "{";
596
+ // if (Object.keys(shape).length > 0) {
597
+ // type += "\n";
598
+ // }
599
+ // for (const [key, value] of Object.entries(shape)) {
600
+ // const childContext: ZodSurrealTypeContext = {
601
+ // type: new Set(),
602
+ // depth: context.depth + 1,
603
+ // children: [],
604
+ // };
605
+ // type += `${childIndent}${escapeIdent(key)}: ${inferSurrealType(value, childContext).inner},\n`;
606
+ // }
607
+ // type += "}";
608
+ // context.type.add(type);
609
+ // break;
610
+ // }
611
+
612
+ context.type.add("object");
613
+ if (isLoose) context.flexible = true;
614
+ for (const [key, value] of Object.entries(shape)) {
615
+ context.children.push({ name: key, type: value as core.$ZodType });
616
+ }
617
+ break;
475
618
  }
476
- break;
477
- }
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");
619
+ case "array": {
620
+ const { type: element } = enter(def.element, true);
621
+ if (element === "any") {
622
+ context.type.add("array");
623
+ break;
624
+ }
625
+
626
+ context.type.add(`array<${element}>`);
627
+ break;
628
+ }
629
+ case "set": {
630
+ const { type: element } = enter(def.valueType, true);
631
+ if (element === "any") {
632
+ context.type.add("array");
633
+ break;
634
+ }
635
+
636
+ context.type.add(`array<${element}>`);
637
+ break;
638
+ }
639
+ case "enum": {
640
+ const values = def.entries;
641
+ for (const key in values) {
642
+ const value = values[key];
643
+ context.type.add(toSurqlString(value));
644
+ }
645
+ break;
646
+ }
647
+ case "union": {
648
+ for (const option of def.options) {
649
+ // context.type.add(inferSurrealType(option, context).inner);
650
+ enter(option);
651
+ }
652
+ break;
653
+ }
654
+ // case "intersection": {
655
+ // // TODO: Find a way to handle intersections
656
+ // // Maybe a new function where we build a new object schema (or primitive one)
657
+ // // And keep track of all the types that are used in the intersection
658
+ // //
659
+ // // const left = def.left;
660
+ // // const right = def.right;
661
+ // // inferSurrealType(left, context);
662
+ // // inferSurrealType(right, context);
663
+ // // inferSurrealType(z.never(), context);
664
+ // // context.type.add("any");
665
+ // context.type.add("any");
666
+ // break;
667
+ // }
668
+ case "tuple": {
669
+ if (def.rest) {
670
+ context.type.add("array");
671
+ break;
672
+ }
673
+
674
+ const types = new Set<string>();
675
+ for (const item of def.items) {
676
+ types.add(enter(item, true).type);
677
+ }
678
+ context.type.add(`[${Array.from(types).join(", ")}]`);
679
+ break;
680
+ }
681
+ // case "record": {
682
+ // context.type.add("object");
683
+ // // Currently there is no way to restrict the key type of an object, so we just use *
684
+ // // context.children.push({ name: "*", type: def.keyType });
685
+ // // All commented out code is because of this. Check is only done in JS side.
686
+ // enter((def as core.$ZodRecordDef).keyType, true);
687
+ // /* const isPartial =
688
+ // def.keyType._zod.values === undefined &&
689
+ // !keyContext.type.has("string") &&
690
+ // !keyContext.type.has("number") &&
691
+ // !keyContext.type.has("int") &&
692
+ // !keyContext.type.has("float") &&
693
+ // !keyContext.type.has("decimal"); */
694
+ // context.children.push({
695
+ // name: "*",
696
+ // type: /* isPartial ? z.optional(def.valueType) : */ (
697
+ // def as core.$ZodRecordDef
698
+ // ).valueType,
699
+ // });
700
+ // break;
701
+ // }
702
+ // case "map": {
703
+ // context.type.add("object");
704
+ // // Currently there is no way to restrict the key type of an object, so we just use *
705
+ // // Surreal doesnt have a map type, so we use object instead. We cant really support non PropertyKey values
706
+ // // unless we serialize the keys to strings.
707
+ // const { context: keyContext } = enter(
708
+ // (def as core.$ZodMapDef).keyType,
709
+ // true,
710
+ // );
711
+ // for (const key of keyContext.type) {
712
+ // if (!["string", "number", "float", "int", "decimal"].includes(key)) {
713
+ // throw new Error(`Unsupported key type: ${key}`);
714
+ // }
715
+ // }
716
+ // context.children.push({
717
+ // name: "*",
718
+ // type: (def as core.$ZodMapDef).valueType,
719
+ // });
720
+ // break;
721
+ // }
722
+ case "literal": {
723
+ for (const value of (def as core.$ZodLiteralDef<any>).values) {
724
+ context.type.add(toSurqlString(value));
725
+ }
726
+ break;
727
+ }
728
+ case "file": {
729
+ throw new Error(
730
+ `File type cannot be used as a field type.${OPEN_ISSUE_FOR_SUPPORT}`,
731
+ );
732
+ }
733
+ case "transform": {
734
+ break;
735
+ }
736
+ case "readonly":
737
+ case "catch":
738
+ case "prefault":
739
+ case "default": {
740
+ enter(def.innerType);
741
+ break;
742
+ }
743
+ case "success": {
744
+ context.type.add("string");
745
+ break;
746
+ }
747
+ case "nan": {
748
+ context.type.add("number");
749
+ break;
750
+ }
751
+ case "pipe": {
752
+ enter(def.in);
753
+ enter(def.out);
754
+ break;
755
+ }
756
+ case "template_literal": {
757
+ context.type.add("string");
758
+ break;
759
+ }
760
+ case "lazy": {
761
+ const innerType = def.getter();
762
+ // All that cascading to get here, we dont want to keep complex types if
763
+ // recursive.
764
+ if (context.fullParents.has(type) || context.parents.has(type)) {
765
+ context.type.add("any");
766
+ } else {
767
+ enter(innerType);
768
+ }
769
+ break;
770
+ }
771
+ case "promise": {
772
+ // We will not support promises for now, this can be uncommented after
773
+ // support is added
774
+ // const { inner: innerType } =
775
+ // enter(def.innerType);
776
+ // context.type.add(innerType);
777
+ throw new Error(
778
+ `Promise type cannot be used as a field type.${OPEN_ISSUE_FOR_SUPPORT}`,
779
+ );
780
+ }
781
+ case "function": {
782
+ throw new Error(
783
+ `Function type cannot be used as a field type.${OPEN_ISSUE_FOR_SUPPORT}`,
784
+ );
785
+ }
786
+ case "custom": {
787
+ throw new Error(
788
+ `Custom type cannot be used as a field type.${OPEN_ISSUE_FOR_SUPPORT}`,
789
+ );
790
+ }
791
+ case "symbol": {
792
+ throw new Error(
793
+ `Symbol type cannot be used as a field type.${OPEN_ISSUE_FOR_SUPPORT}`,
794
+ );
484
795
  }
485
- break;
486
- }
487
- case "table": {
488
- throw new Error("Table type cannot be used as a field type");
489
796
  }
490
- }
491
797
 
492
- if (context.type.has("any")) {
493
- return "any";
494
- }
798
+ const inner =
799
+ context.type.has("any") || context.type.size === 0
800
+ ? "any"
801
+ : Array.from(context.type).join(" | ");
495
802
 
496
- return Array.from(context.type).join(" | ");
803
+ return { type: inner, context };
497
804
  }
498
805
 
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;
806
+ // function isSurrealZodSchemaDef(
807
+ // def: core.$ZodTypeDef | ZodSurrealTypeDef,
808
+ // ): def is ZodSurrealTypeDef {
809
+ // return "surreal" in def && def.surreal.type !== undefined;
506
810
  // }
507
811
 
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
- // }
812
+ // function isStringFormat(
813
+ // def: core.$ZodStringDef,
814
+ // ): def is core.$ZodStringFormatTypes["_zod"]["def"] {
815
+ // return def.type === "string" && "format" in def;
523
816
  // }
524
817
 
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
- // }
818
+ // function isBigIntFormat(
819
+ // def: core.$ZodBigIntDef,
820
+ // ): def is core.$ZodBigIntFormatDef {
821
+ // return def.type === "bigint" && "format" in def;
637
822
  // }
638
823
 
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
- // }
824
+ // function isNumberFormat(
825
+ // def: core.$ZodNumberDef,
826
+ // ): def is core.$ZodNumberFormatDef {
827
+ // return def.type === "number" && "format" in def;
658
828
  // }
829
+
830
+ // // function getChecks(_schema: z4.$ZodType | SurrealZodType) {
831
+ // // const schema = _schema as z4.$ZodTypes | SurrealZodTypes;
832
+ // // const checks = schema._zod.def.checks ?? [];
833
+ // // if ("check" in schema._zod.def) {
834
+ // // checks.unshift(schema as z4.$ZodCheck);
835
+ // // }
836
+ // // return checks;
837
+ // // }
838
+
839
+ // // function parseChecks(
840
+ // // name: string,
841
+ // // checks: z4.$ZodCheck[],
842
+ // // context: ZodSurrealTypeContext,
843
+ // // type: ZodTypeName | SurrealZodTypeName,
844
+ // // ) {
845
+ // // for (const check of checks) {
846
+ // // const { transform, assert } = parseCheck(name, check, type);
847
+ // // if (transform) {
848
+ // // context.transforms.push(transform);
849
+ // // }
850
+ // // if (assert) {
851
+ // // context.asserts.push(assert);
852
+ // // }
853
+ // // }
854
+ // // }
855
+
856
+ // // export const checkMap = {
857
+ // // never(name: string) {
858
+ // // return `THROW 'Field "${name}" must never be present'`;
859
+ // // },
860
+ // // min_length(name: string, value: number, type: ZodTypeName) {
861
+ // // if (type === "array") {
862
+ // // return `$value.len() >= ${value} || { THROW 'Field "${name}" must have at least ${value} ${value === 1 ? "item" : "items"}' };`;
863
+ // // }
864
+
865
+ // // if (type === "string") {
866
+ // // return `$value.len() >= ${value} || { THROW 'Field "${name}" must be at least ${value} ${value === 1 ? "character" : "characters"} long' };`;
867
+ // // }
868
+
869
+ // // throw new Error(`Invalid type: ${type}`);
870
+ // // },
871
+ // // max_length(name: string, value: number, type: ZodTypeName) {
872
+ // // if (type === "array") {
873
+ // // return `$value.len() <= ${value} || { THROW 'Field "${name}" must have at most ${value} ${value === 1 ? "item" : "items"}' };`;
874
+ // // }
875
+
876
+ // // if (type === "string") {
877
+ // // return `$value.len() <= ${value} || { THROW 'Field "${name}" must be at most ${value} ${value === 1 ? "character" : "characters"} long' };`;
878
+ // // }
879
+
880
+ // // throw new Error(`Invalid type: ${type}`);
881
+ // // },
882
+ // // greater_than(name: string, value: z4.util.Numeric, inclusive: boolean) {
883
+ // // return `$value ${inclusive ? ">=" : ">"} ${value} || { THROW 'Field "${name}" must be greater than ${inclusive ? "or equal to" : ""} ${value}' };`;
884
+ // // },
885
+ // // less_than(name: string, value: z4.util.Numeric, inclusive: boolean) {
886
+ // // return `$value ${inclusive ? "<=" : "<"} ${value} || { THROW 'Field "${name}" must be less than ${inclusive ? "or equal to" : ""} ${value}' };`;
887
+ // // },
888
+ // // length_equals(name: string, value: number, type: ZodTypeName = "string") {
889
+ // // if (type === "array") {
890
+ // // return `$value.len() == ${value} || { THROW 'Field "${name}" must have exactly ${value} ${value === 1 ? "item" : "items"}' };`;
891
+ // // }
892
+
893
+ // // if (type === "string") {
894
+ // // return `$value.len() == ${value} || { THROW 'Field "${name}" must be exactly ${value} ${value === 1 ? "character" : "characters"} long' };`;
895
+ // // }
896
+
897
+ // // throw new Error(`Invalid type: ${type}`);
898
+ // // },
899
+
900
+ // // string_format: {
901
+ // // email: (name: string) => {
902
+ // // const regex =
903
+ // // /^[A-Za-z0-9'_+-]+(?:\.[A-Za-z0-9'_+-]+)*@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/;
904
+ // // return `string::matches($value, ${regex}) || { THROW "Field '${name}' must be a valid email address" };`;
905
+ // // },
906
+ // // url: (
907
+ // // name: string,
908
+ // // def?: Pick<z4.$ZodCheckURLParams, "hostname" | "protocol" | "normalize">,
909
+ // // ) => {
910
+ // // return dedent`
911
+ // // LET $url = {
912
+ // // scheme: parse::url::scheme($value),
913
+ // // host: parse::url::host($value),
914
+ // // domain: parse::url::domain($value),
915
+ // // path: parse::url::path($value),
916
+ // // port: parse::url::port($value),
917
+ // // query: parse::url::query($value),
918
+ // // hash: parse::url::fragment($value),
919
+ // // };
920
+ // // $url.scheme || { THROW "Field '${name}' must be a valid URL" };
921
+ // // ${
922
+ // // def?.hostname
923
+ // // ? `($url.host ?? "").matches(${def.hostname}) || { THROW "Field '${name}' must match hostname ${def.hostname.toString().replace(/\\/g, "\\\\")}" };`
924
+ // // : ""
925
+ // // }
926
+ // // ${
927
+ // // def?.protocol
928
+ // // ? `($url.scheme ?? "").matches(${def.protocol}) || { THROW "Field '${name}' must match protocol ${def.protocol.toString().replace(/\\/g, "\\\\")}" };`
929
+ // // : ""
930
+ // // }
931
+ // // $url.scheme + "://" + ($url.host ?? "") + (
932
+ // // IF $url.port && (
933
+ // // ($url.scheme == "http" && $url.port != 80) ||
934
+ // // ($url.scheme == "https" && $url.port != 443)
935
+ // // ) { ":" + <string>$url.port } ?? ""
936
+ // // )
937
+ // // + ($url.path ?? "")
938
+ // // + (IF $url.query { "?" + $url.query } ?? "")
939
+ // // + (IF $url.fragment { "#" + $url.fragment } ?? "");
940
+ // // `;
941
+ // // },
942
+ // // },
943
+ // // };
944
+
945
+ // // function parseCheck(
946
+ // // name: string,
947
+ // // _check: z4.$ZodCheck,
948
+ // // type: ZodTypeName,
949
+ // // ): { transform?: string; assert?: string } {
950
+ // // const check = _check as z4.$ZodChecks;
951
+ // // const def = check._zod.def;
952
+ // // switch (def.check) {
953
+ // // case "min_length":
954
+ // // return { assert: checkMap.min_length(name, def.minimum, type) };
955
+ // // case "max_length":
956
+ // // return { assert: checkMap.max_length(name, def.maximum, type) };
957
+ // // case "greater_than":
958
+ // // return { assert: checkMap.greater_than(name, def.value, def.inclusive) };
959
+ // // case "less_than":
960
+ // // return { assert: checkMap.less_than(name, def.value, def.inclusive) };
961
+ // // case "length_equals":
962
+ // // return { assert: checkMap.length_equals(name, def.length, type) };
963
+ // // case "string_format":
964
+ // // return assertionForStringFormat(name, check);
965
+ // // default:
966
+ // // return { assert: `THROW 'Unknown check: ${def.check}';` };
967
+ // // }
968
+ // // }
969
+
970
+ // // // Remove look-around, look-behind, and look-ahead as they are not supported by SurrealDB
971
+ // // function assertionForStringFormat(
972
+ // // name: string,
973
+ // // _check: z4.$ZodCheck,
974
+ // // ): { transform?: string; assert?: string } {
975
+ // // const check = _check as z4.$ZodStringFormatChecks;
976
+ // // const def = check._zod.def;
977
+
978
+ // // switch (def.format) {
979
+ // // case "email": {
980
+ // // return { assert: checkMap.string_format.email(name) };
981
+ // // }
982
+ // // case "url": {
983
+ // // const code = checkMap.string_format.url(name, def);
984
+ // // return def.normalize ? { transform: code } : { assert: code };
985
+ // // }
986
+ // // default:
987
+ // // return { assert: `THROW 'Unsupported string format: ${def.format}';` };
988
+ // // }
989
+ // // }
990
+
991
+ const SurrealQLStatements = [
992
+ "ACCESS",
993
+ "ALTER",
994
+ "BEGIN",
995
+ "BREAK",
996
+ "CANCEL",
997
+ "COMMIT",
998
+ "CONTINUE",
999
+ "CREATE",
1000
+ "DEFINE",
1001
+ "DELETE",
1002
+ "FOR",
1003
+ "IF",
1004
+ "INFO",
1005
+ "INSERT",
1006
+ "KILL",
1007
+ "LET",
1008
+ "LIVE",
1009
+ "REBUILD",
1010
+ "RELATE",
1011
+ "REMOVE",
1012
+ "RETURN",
1013
+ "SELECT",
1014
+ "SHOW",
1015
+ "SLEEP",
1016
+ "THROW",
1017
+ "UPDATE",
1018
+ "UPSERT",
1019
+ "USE",
1020
+ "EXPLAIN",
1021
+ "FETCH",
1022
+ ];
1023
+
1024
+ function hasMultipleStatements(query: string): boolean {
1025
+ let inString: false | "'" | '"' = false;
1026
+ let escaping = false;
1027
+ let statements = 0;
1028
+ let buffer = "";
1029
+
1030
+ for (let i = 0; i < query.length; i++) {
1031
+ const ch = query[i];
1032
+ const nextBuffer = `${buffer}${ch}`;
1033
+
1034
+ if (ch === "\n") return true;
1035
+ if (inString) {
1036
+ if (escaping) {
1037
+ escaping = false;
1038
+ continue;
1039
+ }
1040
+ if (ch === "\\") {
1041
+ escaping = true;
1042
+ continue;
1043
+ }
1044
+ if (ch === inString) {
1045
+ inString = false;
1046
+ continue;
1047
+ }
1048
+ continue;
1049
+ }
1050
+ if (ch === '"' || ch === "'") {
1051
+ inString = ch;
1052
+ continue;
1053
+ }
1054
+
1055
+ if (ch === " ") {
1056
+ if (SurrealQLStatements.includes(buffer.toUpperCase())) {
1057
+ return true;
1058
+ }
1059
+ continue;
1060
+ }
1061
+ if (
1062
+ ch === ";" ||
1063
+ // Single line Comments
1064
+ nextBuffer === "--" ||
1065
+ nextBuffer === "//" ||
1066
+ ch === "#"
1067
+ ) {
1068
+ statements++;
1069
+ buffer = "";
1070
+ continue;
1071
+ }
1072
+
1073
+ buffer = nextBuffer;
1074
+
1075
+ if (statements > 1) return true;
1076
+ }
1077
+ if (statements > 0 && (buffer || inString || escaping)) return true;
1078
+ return statements > 1;
1079
+ }
1080
+
1081
+ export function formatQuery(query: string) {
1082
+ if (hasMultipleStatements(query)) {
1083
+ return `{\n ${dedent.withOptions({ alignValues: true })` ${query.trimStart()}`}\n}`;
1084
+ }
1085
+
1086
+ return query.trim();
1087
+ }
1088
+
1089
+ export function inlineQueryParameters(query: BoundQuery): string {
1090
+ let value = query.query;
1091
+ for (const [paramName, paramValue] of Object.entries(query.bindings ?? {})) {
1092
+ value = value.replace(
1093
+ new RegExp(`\\$${paramName}\\b`, "g"),
1094
+ toSurqlString(paramValue),
1095
+ );
1096
+ }
1097
+ return value;
1098
+ }
1099
+
1100
+ function toSurqlString(value: unknown): string {
1101
+ return baseToSurqlString(value).replace(/^s"/g, '"');
1102
+ }