@orpc/zod 0.18.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.js CHANGED
@@ -340,6 +340,22 @@ function zodCoerceInternal(schema, value, options) {
340
340
  return value;
341
341
  }
342
342
 
343
+ // src/converter.ts
344
+ import { JSONSchemaFormat } from "@orpc/openapi";
345
+
346
+ // ../../node_modules/.pnpm/escape-string-regexp@5.0.0/node_modules/escape-string-regexp/index.js
347
+ function escapeStringRegexp(string) {
348
+ if (typeof string !== "string") {
349
+ throw new TypeError("Expected a string");
350
+ }
351
+ return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
352
+ }
353
+
354
+ // src/converter.ts
355
+ import {
356
+ ZodFirstPartyTypeKind as ZodFirstPartyTypeKind2
357
+ } from "zod";
358
+
343
359
  // src/schemas.ts
344
360
  import wcmatch from "wildcard-match";
345
361
  import {
@@ -475,8 +491,463 @@ var oz = {
475
491
  regexp,
476
492
  url
477
493
  };
494
+
495
+ // src/converter.ts
496
+ var NON_LOGIC_KEYWORDS = [
497
+ // Core Documentation Keywords
498
+ "$anchor",
499
+ "$comment",
500
+ "$defs",
501
+ "$id",
502
+ "title",
503
+ "description",
504
+ // Value Keywords
505
+ "default",
506
+ "deprecated",
507
+ "examples",
508
+ // Metadata Keywords
509
+ "$schema",
510
+ "definitions",
511
+ // Legacy, but still used
512
+ "readOnly",
513
+ "writeOnly",
514
+ // Display and UI Hints
515
+ "contentMediaType",
516
+ "contentEncoding",
517
+ "format",
518
+ // Custom Extensions
519
+ "$vocabulary",
520
+ "$dynamicAnchor",
521
+ "$dynamicRef"
522
+ ];
523
+ var UNSUPPORTED_JSON_SCHEMA = { not: {} };
524
+ var UNDEFINED_JSON_SCHEMA = { const: "undefined" };
525
+ function zodToJsonSchema(schema, options) {
526
+ if (schema["~standard"].vendor !== "zod") {
527
+ console.warn(`Generate JSON schema not support ${schema["~standard"].vendor} yet`);
528
+ return {};
529
+ }
530
+ const schema__ = schema;
531
+ if (!options?.isHandledCustomJSONSchema) {
532
+ const customJSONSchema = getCustomJSONSchema(schema__._def, options);
533
+ if (customJSONSchema) {
534
+ const json = zodToJsonSchema(schema__, {
535
+ ...options,
536
+ isHandledCustomJSONSchema: true
537
+ });
538
+ return {
539
+ ...json,
540
+ ...customJSONSchema
541
+ };
542
+ }
543
+ }
544
+ const childOptions = { ...options, isHandledCustomJSONSchema: false };
545
+ const customType = getCustomZodType(schema__._def);
546
+ switch (customType) {
547
+ case "Blob": {
548
+ return { type: "string", contentMediaType: "*/*" };
549
+ }
550
+ case "File": {
551
+ const mimeType = getCustomZodFileMimeType(schema__._def) ?? "*/*";
552
+ return { type: "string", contentMediaType: mimeType };
553
+ }
554
+ case "Invalid Date": {
555
+ return { const: "Invalid Date" };
556
+ }
557
+ case "RegExp": {
558
+ return {
559
+ type: "string",
560
+ pattern: "^\\/(.*)\\/([a-z]*)$"
561
+ };
562
+ }
563
+ case "URL": {
564
+ return { type: "string", format: JSONSchemaFormat.URI };
565
+ }
566
+ }
567
+ const _expectedCustomType = customType;
568
+ const typeName = schema__._def.typeName;
569
+ switch (typeName) {
570
+ case ZodFirstPartyTypeKind2.ZodString: {
571
+ const schema_ = schema__;
572
+ const json = { type: "string" };
573
+ for (const check of schema_._def.checks) {
574
+ switch (check.kind) {
575
+ case "base64":
576
+ json.contentEncoding = "base64";
577
+ break;
578
+ case "cuid":
579
+ json.pattern = "^[0-9A-HJKMNP-TV-Z]{26}$";
580
+ break;
581
+ case "email":
582
+ json.format = JSONSchemaFormat.Email;
583
+ break;
584
+ case "url":
585
+ json.format = JSONSchemaFormat.URI;
586
+ break;
587
+ case "uuid":
588
+ json.format = JSONSchemaFormat.UUID;
589
+ break;
590
+ case "regex":
591
+ json.pattern = check.regex.source;
592
+ break;
593
+ case "min":
594
+ json.minLength = check.value;
595
+ break;
596
+ case "max":
597
+ json.maxLength = check.value;
598
+ break;
599
+ case "length":
600
+ json.minLength = check.value;
601
+ json.maxLength = check.value;
602
+ break;
603
+ case "includes":
604
+ json.pattern = escapeStringRegexp(check.value);
605
+ break;
606
+ case "startsWith":
607
+ json.pattern = `^${escapeStringRegexp(check.value)}`;
608
+ break;
609
+ case "endsWith":
610
+ json.pattern = `${escapeStringRegexp(check.value)}$`;
611
+ break;
612
+ case "emoji":
613
+ json.pattern = "^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$";
614
+ break;
615
+ case "nanoid":
616
+ json.pattern = "^[a-zA-Z0-9_-]{21}$";
617
+ break;
618
+ case "cuid2":
619
+ json.pattern = "^[0-9a-z]+$";
620
+ break;
621
+ case "ulid":
622
+ json.pattern = "^[0-9A-HJKMNP-TV-Z]{26}$";
623
+ break;
624
+ case "datetime":
625
+ json.format = JSONSchemaFormat.DateTime;
626
+ break;
627
+ case "date":
628
+ json.format = JSONSchemaFormat.Date;
629
+ break;
630
+ case "time":
631
+ json.format = JSONSchemaFormat.Time;
632
+ break;
633
+ case "duration":
634
+ json.format = JSONSchemaFormat.Duration;
635
+ break;
636
+ case "ip":
637
+ json.format = JSONSchemaFormat.IPv4;
638
+ break;
639
+ case "jwt":
640
+ json.pattern = "^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$";
641
+ break;
642
+ case "base64url":
643
+ json.pattern = "^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$";
644
+ break;
645
+ default: {
646
+ const _expect = check.kind;
647
+ }
648
+ }
649
+ }
650
+ return json;
651
+ }
652
+ case ZodFirstPartyTypeKind2.ZodNumber: {
653
+ const schema_ = schema__;
654
+ const json = { type: "number" };
655
+ for (const check of schema_._def.checks) {
656
+ switch (check.kind) {
657
+ case "int":
658
+ json.type = "integer";
659
+ break;
660
+ case "min":
661
+ json.minimum = check.value;
662
+ break;
663
+ case "max":
664
+ json.maximum = check.value;
665
+ break;
666
+ case "multipleOf":
667
+ json.multipleOf = check.value;
668
+ break;
669
+ default: {
670
+ const _expect = check.kind;
671
+ }
672
+ }
673
+ }
674
+ return json;
675
+ }
676
+ case ZodFirstPartyTypeKind2.ZodNaN: {
677
+ return { const: "NaN" };
678
+ }
679
+ case ZodFirstPartyTypeKind2.ZodBigInt: {
680
+ const json = { type: "string", pattern: "^-?[0-9]+$" };
681
+ return json;
682
+ }
683
+ case ZodFirstPartyTypeKind2.ZodBoolean: {
684
+ return { type: "boolean" };
685
+ }
686
+ case ZodFirstPartyTypeKind2.ZodDate: {
687
+ const schema2 = { type: "string", format: JSONSchemaFormat.Date };
688
+ return schema2;
689
+ }
690
+ case ZodFirstPartyTypeKind2.ZodNull: {
691
+ return { type: "null" };
692
+ }
693
+ case ZodFirstPartyTypeKind2.ZodVoid:
694
+ case ZodFirstPartyTypeKind2.ZodUndefined: {
695
+ return UNDEFINED_JSON_SCHEMA;
696
+ }
697
+ case ZodFirstPartyTypeKind2.ZodLiteral: {
698
+ const schema_ = schema__;
699
+ return { const: schema_._def.value };
700
+ }
701
+ case ZodFirstPartyTypeKind2.ZodEnum: {
702
+ const schema_ = schema__;
703
+ return {
704
+ enum: schema_._def.values
705
+ };
706
+ }
707
+ case ZodFirstPartyTypeKind2.ZodNativeEnum: {
708
+ const schema_ = schema__;
709
+ return {
710
+ enum: Object.values(schema_._def.values)
711
+ };
712
+ }
713
+ case ZodFirstPartyTypeKind2.ZodArray: {
714
+ const schema_ = schema__;
715
+ const def = schema_._def;
716
+ const json = { type: "array" };
717
+ if (def.exactLength) {
718
+ json.maxItems = def.exactLength.value;
719
+ json.minItems = def.exactLength.value;
720
+ }
721
+ if (def.minLength) {
722
+ json.minItems = def.minLength.value;
723
+ }
724
+ if (def.maxLength) {
725
+ json.maxItems = def.maxLength.value;
726
+ }
727
+ return json;
728
+ }
729
+ case ZodFirstPartyTypeKind2.ZodTuple: {
730
+ const schema_ = schema__;
731
+ const prefixItems = [];
732
+ const json = { type: "array" };
733
+ for (const item of schema_._def.items) {
734
+ prefixItems.push(zodToJsonSchema(item, childOptions));
735
+ }
736
+ if (prefixItems?.length) {
737
+ json.prefixItems = prefixItems;
738
+ }
739
+ if (schema_._def.rest) {
740
+ const items = zodToJsonSchema(schema_._def.rest, childOptions);
741
+ if (items) {
742
+ json.items = items;
743
+ }
744
+ }
745
+ return json;
746
+ }
747
+ case ZodFirstPartyTypeKind2.ZodObject: {
748
+ const schema_ = schema__;
749
+ const json = { type: "object" };
750
+ const properties = {};
751
+ const required = [];
752
+ for (const [key, value] of Object.entries(schema_.shape)) {
753
+ const { schema: schema2, matches } = extractJSONSchema(
754
+ zodToJsonSchema(value, childOptions),
755
+ (schema3) => schema3 === UNDEFINED_JSON_SCHEMA
756
+ );
757
+ if (schema2) {
758
+ properties[key] = schema2;
759
+ }
760
+ if (matches.length === 0) {
761
+ required.push(key);
762
+ }
763
+ }
764
+ if (Object.keys(properties).length) {
765
+ json.properties = properties;
766
+ }
767
+ if (required.length) {
768
+ json.required = required;
769
+ }
770
+ const additionalProperties = zodToJsonSchema(
771
+ schema_._def.catchall,
772
+ childOptions
773
+ );
774
+ if (schema_._def.unknownKeys === "strict") {
775
+ json.additionalProperties = additionalProperties === UNSUPPORTED_JSON_SCHEMA ? false : additionalProperties;
776
+ } else {
777
+ if (additionalProperties && additionalProperties !== UNSUPPORTED_JSON_SCHEMA) {
778
+ json.additionalProperties = additionalProperties;
779
+ }
780
+ }
781
+ return json;
782
+ }
783
+ case ZodFirstPartyTypeKind2.ZodRecord: {
784
+ const schema_ = schema__;
785
+ const json = { type: "object" };
786
+ json.additionalProperties = zodToJsonSchema(
787
+ schema_._def.valueType,
788
+ childOptions
789
+ );
790
+ return json;
791
+ }
792
+ case ZodFirstPartyTypeKind2.ZodSet: {
793
+ const schema_ = schema__;
794
+ return {
795
+ type: "array",
796
+ items: zodToJsonSchema(schema_._def.valueType, childOptions)
797
+ };
798
+ }
799
+ case ZodFirstPartyTypeKind2.ZodMap: {
800
+ const schema_ = schema__;
801
+ return {
802
+ type: "array",
803
+ items: {
804
+ type: "array",
805
+ prefixItems: [
806
+ zodToJsonSchema(schema_._def.keyType, childOptions),
807
+ zodToJsonSchema(schema_._def.valueType, childOptions)
808
+ ],
809
+ maxItems: 2,
810
+ minItems: 2
811
+ }
812
+ };
813
+ }
814
+ case ZodFirstPartyTypeKind2.ZodUnion:
815
+ case ZodFirstPartyTypeKind2.ZodDiscriminatedUnion: {
816
+ const schema_ = schema__;
817
+ const anyOf = [];
818
+ for (const s of schema_._def.options) {
819
+ anyOf.push(zodToJsonSchema(s, childOptions));
820
+ }
821
+ return { anyOf };
822
+ }
823
+ case ZodFirstPartyTypeKind2.ZodIntersection: {
824
+ const schema_ = schema__;
825
+ const allOf = [];
826
+ for (const s of [schema_._def.left, schema_._def.right]) {
827
+ allOf.push(zodToJsonSchema(s, childOptions));
828
+ }
829
+ return { allOf };
830
+ }
831
+ case ZodFirstPartyTypeKind2.ZodLazy: {
832
+ const schema_ = schema__;
833
+ const maxLazyDepth = childOptions?.maxLazyDepth ?? 5;
834
+ const lazyDepth = childOptions?.lazyDepth ?? 0;
835
+ if (lazyDepth > maxLazyDepth) {
836
+ return {};
837
+ }
838
+ return zodToJsonSchema(schema_._def.getter(), {
839
+ ...childOptions,
840
+ lazyDepth: lazyDepth + 1
841
+ });
842
+ }
843
+ case ZodFirstPartyTypeKind2.ZodUnknown:
844
+ case ZodFirstPartyTypeKind2.ZodAny:
845
+ case void 0: {
846
+ return {};
847
+ }
848
+ case ZodFirstPartyTypeKind2.ZodOptional: {
849
+ const schema_ = schema__;
850
+ const inner = zodToJsonSchema(schema_._def.innerType, childOptions);
851
+ return {
852
+ anyOf: [UNDEFINED_JSON_SCHEMA, inner]
853
+ };
854
+ }
855
+ case ZodFirstPartyTypeKind2.ZodReadonly: {
856
+ const schema_ = schema__;
857
+ return zodToJsonSchema(schema_._def.innerType, childOptions);
858
+ }
859
+ case ZodFirstPartyTypeKind2.ZodDefault: {
860
+ const schema_ = schema__;
861
+ return zodToJsonSchema(schema_._def.innerType, childOptions);
862
+ }
863
+ case ZodFirstPartyTypeKind2.ZodEffects: {
864
+ const schema_ = schema__;
865
+ if (schema_._def.effect.type === "transform" && childOptions?.mode === "output") {
866
+ return {};
867
+ }
868
+ return zodToJsonSchema(schema_._def.schema, childOptions);
869
+ }
870
+ case ZodFirstPartyTypeKind2.ZodCatch: {
871
+ const schema_ = schema__;
872
+ return zodToJsonSchema(schema_._def.innerType, childOptions);
873
+ }
874
+ case ZodFirstPartyTypeKind2.ZodBranded: {
875
+ const schema_ = schema__;
876
+ return zodToJsonSchema(schema_._def.type, childOptions);
877
+ }
878
+ case ZodFirstPartyTypeKind2.ZodPipeline: {
879
+ const schema_ = schema__;
880
+ return zodToJsonSchema(
881
+ childOptions?.mode === "output" ? schema_._def.out : schema_._def.in,
882
+ childOptions
883
+ );
884
+ }
885
+ case ZodFirstPartyTypeKind2.ZodNullable: {
886
+ const schema_ = schema__;
887
+ const inner = zodToJsonSchema(schema_._def.innerType, childOptions);
888
+ return {
889
+ anyOf: [{ type: "null" }, inner]
890
+ };
891
+ }
892
+ }
893
+ const _expected = typeName;
894
+ return UNSUPPORTED_JSON_SCHEMA;
895
+ }
896
+ function extractJSONSchema(schema, check, matches = []) {
897
+ if (check(schema)) {
898
+ matches.push(schema);
899
+ return { schema: void 0, matches };
900
+ }
901
+ if (typeof schema === "boolean") {
902
+ return { schema, matches };
903
+ }
904
+ if (schema.anyOf && Object.keys(schema).every(
905
+ (k) => k === "anyOf" || NON_LOGIC_KEYWORDS.includes(k)
906
+ )) {
907
+ const anyOf = schema.anyOf.map((s) => extractJSONSchema(s, check, matches).schema).filter((v) => !!v);
908
+ if (anyOf.length === 1 && typeof anyOf[0] === "object") {
909
+ return { schema: { ...schema, anyOf: void 0, ...anyOf[0] }, matches };
910
+ }
911
+ return {
912
+ schema: {
913
+ ...schema,
914
+ anyOf
915
+ },
916
+ matches
917
+ };
918
+ }
919
+ if (schema.oneOf && Object.keys(schema).every(
920
+ (k) => k === "oneOf" || NON_LOGIC_KEYWORDS.includes(k)
921
+ )) {
922
+ const oneOf = schema.oneOf.map((s) => extractJSONSchema(s, check, matches).schema).filter((v) => !!v);
923
+ if (oneOf.length === 1 && typeof oneOf[0] === "object") {
924
+ return { schema: { ...schema, oneOf: void 0, ...oneOf[0] }, matches };
925
+ }
926
+ return {
927
+ schema: {
928
+ ...schema,
929
+ oneOf
930
+ },
931
+ matches
932
+ };
933
+ }
934
+ return { schema, matches };
935
+ }
936
+ var ZodToJsonSchemaConverter = class {
937
+ condition(schema) {
938
+ return Boolean(schema && schema["~standard"].vendor === "zod");
939
+ }
940
+ convert(schema, options) {
941
+ const jsonSchema = schema;
942
+ return zodToJsonSchema(jsonSchema, { mode: options.strategy });
943
+ }
944
+ };
478
945
  export {
946
+ NON_LOGIC_KEYWORDS,
947
+ UNDEFINED_JSON_SCHEMA,
948
+ UNSUPPORTED_JSON_SCHEMA,
479
949
  ZodCoercer,
950
+ ZodToJsonSchemaConverter,
480
951
  blob,
481
952
  file,
482
953
  getCustomJSONSchema,
@@ -486,6 +957,7 @@ export {
486
957
  openapi,
487
958
  oz,
488
959
  regexp,
489
- url
960
+ url,
961
+ zodToJsonSchema
490
962
  };
491
963
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,44 @@
1
+ import type { Schema } from '@orpc/contract';
2
+ import type { JSONSchema, SchemaConverter, SchemaConvertOptions } from '@orpc/openapi';
3
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
4
+ export declare const NON_LOGIC_KEYWORDS: ("$anchor" | "$comment" | "$defs" | "$dynamicAnchor" | "$dynamicRef" | "$id" | "$schema" | "$vocabulary" | "contentEncoding" | "contentMediaType" | "default" | "definitions" | "deprecated" | "description" | "examples" | "format" | "readOnly" | "title" | "writeOnly")[];
5
+ export declare const UNSUPPORTED_JSON_SCHEMA: {
6
+ not: {};
7
+ };
8
+ export declare const UNDEFINED_JSON_SCHEMA: {
9
+ const: string;
10
+ };
11
+ export interface ZodToJsonSchemaOptions {
12
+ /**
13
+ * Max depth of lazy type, if it exceeds.
14
+ *
15
+ * Used `{}` when reach max depth
16
+ *
17
+ * @default 5
18
+ */
19
+ maxLazyDepth?: number;
20
+ /**
21
+ * The length used to track the depth of lazy type
22
+ *
23
+ * @internal
24
+ */
25
+ lazyDepth?: number;
26
+ /**
27
+ * The expected json schema for input or output zod schema
28
+ *
29
+ * @default input
30
+ */
31
+ mode?: 'input' | 'output';
32
+ /**
33
+ * Track if current level schema is handled custom json schema to prevent recursive
34
+ *
35
+ * @internal
36
+ */
37
+ isHandledCustomJSONSchema?: boolean;
38
+ }
39
+ export declare function zodToJsonSchema(schema: StandardSchemaV1, options?: ZodToJsonSchemaOptions): Exclude<JSONSchema.JSONSchema, boolean>;
40
+ export declare class ZodToJsonSchemaConverter implements SchemaConverter {
41
+ condition(schema: Schema): boolean;
42
+ convert(schema: Schema, options: SchemaConvertOptions): JSONSchema.JSONSchema;
43
+ }
44
+ //# sourceMappingURL=converter.d.ts.map
@@ -1,3 +1,4 @@
1
1
  export * from './coercer';
2
+ export * from './converter';
2
3
  export * from './schemas';
3
4
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/zod",
3
3
  "type": "module",
4
- "version": "0.18.0",
4
+ "version": "0.20.0",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.unnoq.com",
7
7
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "dist"
30
30
  ],
31
31
  "peerDependencies": {
32
- "@orpc/openapi": "0.18.0"
32
+ "@orpc/openapi": "0.20.0"
33
33
  },
34
34
  "dependencies": {
35
35
  "json-schema-typed": "^8.0.1",