@sanity/codegen 5.0.0-next-major.12 → 5.0.0-next-major.14

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/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs$1, { readFile } from "node:fs/promises";
2
2
  import json5 from "json5";
3
3
  import * as z from "zod";
4
- import { parse } from "groq-js";
4
+ import { parse, typeEvaluate } from "groq-js";
5
5
  import createDebug from "debug";
6
6
  import glob from "globby";
7
7
  import fs, { existsSync } from "node:fs";
@@ -13,6 +13,8 @@ import * as t from "@babel/types";
13
13
  import traverse, { Scope } from "@babel/traverse";
14
14
  import { loadConfig, createMatchPath } from "tsconfig-paths";
15
15
  import register from "@babel/register";
16
+ import process from "node:process";
17
+ import { createSelector } from "reselect";
16
18
  import { CodeGenerator } from "@babel/generator";
17
19
  const configDefinition = z.object({
18
20
  path: z.string().or(z.array(z.string())).default([
@@ -417,9 +419,28 @@ function resolveExportSpecifier({
417
419
  cause: `noBinding:${importName}`
418
420
  });
419
421
  }
422
+ const isRecord = (value) => (typeof value == "object" || typeof value == "function") && !!value;
423
+ class QueryExtractionError extends Error {
424
+ variable;
425
+ filename;
426
+ constructor({ variable, cause, filename }) {
427
+ super(
428
+ `Error while extracting query ${variable ? `from variable '${variable.id.name}' ` : ""}in ${filename}: ${isRecord(cause) && typeof cause.message == "string" ? cause.message : "Unknown error"}`
429
+ ), this.name = "QueryExtractionError", this.cause = cause, this.variable = variable, this.filename = filename;
430
+ }
431
+ }
432
+ class QueryEvaluationError extends Error {
433
+ variable;
434
+ filename;
435
+ constructor({ variable, cause, filename }) {
436
+ super(
437
+ `Error while evaluating query ${variable ? `from variable '${variable.id.name}' ` : ""}in ${filename}: ${isRecord(cause) && typeof cause.message == "string" ? cause.message : "Unknown error"}`
438
+ ), this.name = "QueryEvaluationError", this.cause = cause, this.variable = variable, this.filename = filename;
439
+ }
440
+ }
420
441
  const __filename$1 = fileURLToPath(import.meta.url), require$2 = createRequire(__filename$1), groqTagName = "groq", defineQueryFunctionName = "defineQuery", groqModuleName = "groq", nextSanityModuleName = "next-sanity", ignoreValue = "@sanity-typegen-ignore";
421
442
  function findQueriesInSource(source, filename, babelConfig = getBabelConfig(), resolver = require$2.resolve) {
422
- const queries = [], file = parseSourceFile(source, filename, babelConfig);
443
+ const queries = [], errors = [], file = parseSourceFile(source, filename, babelConfig);
423
444
  return traverse$1(file, {
424
445
  // Look for variable declarations, e.g. `const myQuery = groq`... and extract the query.
425
446
  // The variable name is used as the name of the query result type
@@ -428,25 +449,23 @@ function findQueriesInSource(source, filename, babelConfig = getBabelConfig(), r
428
449
  if (t.isIdentifier(node.id) && (isGroqTemplateTag || isDefineQueryCall)) {
429
450
  if (declarationLeadingCommentContains(path2, ignoreValue))
430
451
  return;
431
- const queryName = `${node.id.name}`, queryResult = resolveExpression({
432
- node: init,
433
- file,
434
- scope,
435
- babelConfig,
436
- filename,
437
- resolver
438
- }), location = node.loc ? {
439
- start: {
440
- ...node.loc?.start
441
- },
442
- end: {
443
- ...node.loc?.end
444
- }
445
- } : {};
446
- queries.push({ name: queryName, result: queryResult, location });
452
+ const { id, start, end } = node, variable = { id, ...start && { start }, ...end && { end } };
453
+ try {
454
+ const query = resolveExpression({
455
+ node: init,
456
+ file,
457
+ scope,
458
+ babelConfig,
459
+ filename,
460
+ resolver
461
+ });
462
+ queries.push({ variable, query, filename });
463
+ } catch (cause) {
464
+ errors.push(new QueryExtractionError({ filename, variable, cause }));
465
+ }
447
466
  }
448
467
  }
449
- }), queries;
468
+ }), { filename, queries, errors };
450
469
  }
451
470
  function declarationLeadingCommentContains(path2, comment) {
452
471
  const variableDeclaration = path2.find((node) => node.isVariableDeclaration());
@@ -498,7 +517,7 @@ function getResolver(cwd) {
498
517
  return resolve2.paths = (request) => require$1.resolve.paths(request), resolve2;
499
518
  }
500
519
  const debug = createDebug("sanity:codegen:findQueries:debug");
501
- async function* findQueriesInPath({
520
+ function findQueriesInPath({
502
521
  path: path2,
503
522
  babelOptions = getBabelConfig(),
504
523
  resolver = getResolver()
@@ -511,138 +530,105 @@ async function* findQueriesInPath({
511
530
  // we never want to look in node_modules
512
531
  onlyFiles: !0
513
532
  }).sort();
514
- for (const filename of files)
515
- if (typeof filename == "string") {
516
- debug(`Found file "${filename}"`);
517
- try {
518
- const source = await fs$1.readFile(filename, "utf8"), queries = findQueriesInSource(source, filename, babelOptions, resolver);
519
- for (const query of queries) {
520
- if (queryNames.has(query.name))
521
- throw new Error(
522
- `Duplicate query name found: "${query.name}". Query names must be unique across all files.`
523
- );
524
- queryNames.add(query.name);
533
+ async function* getQueries() {
534
+ for (const filename of files)
535
+ if (typeof filename == "string") {
536
+ debug(`Found file "${filename}"`);
537
+ try {
538
+ const source = await fs$1.readFile(filename, "utf8"), pluckedModuleResult = findQueriesInSource(source, filename, babelOptions, resolver);
539
+ for (const { variable } of pluckedModuleResult.queries) {
540
+ if (queryNames.has(variable.id.name))
541
+ throw new Error(
542
+ `Duplicate query name found: "${variable.id.name}". Query names must be unique across all files.`
543
+ );
544
+ queryNames.add(variable.id.name);
545
+ }
546
+ yield pluckedModuleResult;
547
+ } catch (cause) {
548
+ debug(`Error in file "${filename}"`, cause), yield {
549
+ filename,
550
+ queries: [],
551
+ errors: [new QueryExtractionError({ cause, filename })]
552
+ };
525
553
  }
526
- yield { type: "queries", filename, queries };
527
- } catch (error) {
528
- debug(`Error in file "${filename}"`, error), yield { type: "error", error, filename };
529
554
  }
530
- }
555
+ }
556
+ return { files, queries: getQueries() };
531
557
  }
532
558
  function registerBabel(babelOptions) {
533
559
  const options = babelOptions || getBabelConfig();
534
560
  register({ ...options, extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"] });
535
561
  }
536
- const REFERENCE_SYMBOL_NAME = "internalGroqTypeReferenceTo", ALL_SCHEMA_TYPES = "AllSanitySchemaTypes";
537
- class TypeGenerator {
538
- // Simple set to keep track of generated type names, to avoid conflicts
539
- generatedTypeName = /* @__PURE__ */ new Set();
540
- // Map between type names and their generated type names, used to resolve the correct generated type name
541
- typeNameMap = /* @__PURE__ */ new Map();
542
- // Map between type nodes and their generated type names, used for query mapping
543
- typeNodeNameMap = /* @__PURE__ */ new Map();
562
+ function resultSuffix(variableName) {
563
+ if (!variableName) return "result";
564
+ const isUpperSnake = /^[A-Z0-9_]+$/.test(variableName), isSnake = /^[a-z0-9_]+$/.test(variableName) && variableName.includes("_");
565
+ return /^[a-z][A-Za-z0-9]*$/.test(variableName) ? `${variableName}Result` : isUpperSnake ? `${variableName}_RESULT` : isSnake ? `${variableName}_result` : `${variableName.replace(/[^A-Za-z0-9]/g, "")}Result`;
566
+ }
567
+ const INTERNAL_REFERENCE_SYMBOL = t.identifier("internalGroqTypeReferenceTo"), ALL_SANITY_SCHEMA_TYPES = t.identifier("AllSanitySchemaTypes"), SANITY_QUERIES = t.identifier("SanityQueries"), RESERVED_IDENTIFIERS = /* @__PURE__ */ new Set();
568
+ RESERVED_IDENTIFIERS.add(SANITY_QUERIES.name);
569
+ RESERVED_IDENTIFIERS.add(ALL_SANITY_SCHEMA_TYPES.name);
570
+ RESERVED_IDENTIFIERS.add(INTERNAL_REFERENCE_SYMBOL.name);
571
+ function normalizePath(root, filename) {
572
+ const resolved = path.resolve(root, filename);
573
+ return path.relative(root, resolved);
574
+ }
575
+ function sanitizeIdentifier(input) {
576
+ return `${input.replace(/^\d/, "_").replace(/[^$\w]+(.)/g, (_, char) => char.toUpperCase())}`;
577
+ }
578
+ function normalizeIdentifier(input) {
579
+ const sanitized = sanitizeIdentifier(input);
580
+ return `${sanitized.charAt(0).toUpperCase()}${sanitized.slice(1)}`;
581
+ }
582
+ function getUniqueIdentifierForName(name, currentIdentifiers) {
583
+ const desiredName = normalizeIdentifier(name);
584
+ let resultingName = desiredName, index = 2;
585
+ for (; currentIdentifiers.has(resultingName) || RESERVED_IDENTIFIERS.has(resultingName); )
586
+ resultingName = `${desiredName}_${index}`, index++;
587
+ return t.identifier(resultingName);
588
+ }
589
+ function computeOnce(fn) {
590
+ const ref = { current: void 0, computed: !1 };
591
+ return function() {
592
+ return ref.computed || (ref.current = fn(), ref.computed = !0), ref.current;
593
+ };
594
+ }
595
+ function weakMapMemo(fn) {
596
+ const cache = /* @__PURE__ */ new WeakMap();
597
+ return function(arg) {
598
+ if (cache.has(arg)) return cache.get(arg);
599
+ const result = fn(arg);
600
+ return cache.set(arg, result), result;
601
+ };
602
+ }
603
+ function generateCode(node) {
604
+ return `${new CodeGenerator(node).generate().code.trim()}
605
+
606
+ `;
607
+ }
608
+ class SchemaTypeGenerator {
544
609
  schema;
610
+ tsTypes = /* @__PURE__ */ new Map();
611
+ identifiers = /* @__PURE__ */ new Map();
545
612
  constructor(schema) {
546
- this.schema = schema, this.schema.forEach((s) => {
547
- this.getTypeName(s.name, s);
548
- });
549
- }
550
- /**
551
- * Generate TypeScript types for the given schema
552
- * @returns string
553
- * @internal
554
- * @beta
555
- */
556
- generateSchemaTypes() {
557
- const typeDeclarations = [], schemaNames = /* @__PURE__ */ new Set();
558
- return this.schema.forEach((schema) => {
559
- const typeLiteral = this.getTypeNodeType(schema), schemaName = this.typeNodeNameMap.get(schema);
560
- if (!schemaName)
561
- throw new Error(`Schema name not found for schema ${schema.name}`);
562
- schemaNames.add(schemaName);
563
- const typeAlias = t.tsTypeAliasDeclaration(t.identifier(schemaName), null, typeLiteral);
564
- typeDeclarations.push(t.exportNamedDeclaration(typeAlias));
565
- }), typeDeclarations.push(
566
- t.exportNamedDeclaration(
567
- t.tsTypeAliasDeclaration(
568
- t.identifier(this.getTypeName(ALL_SCHEMA_TYPES)),
569
- null,
570
- t.tsUnionType(
571
- [...schemaNames].map((typeName) => t.tsTypeReference(t.identifier(typeName)))
572
- )
573
- )
574
- )
575
- ), typeDeclarations.map((decl) => new CodeGenerator(decl).generate().code).join(`
576
-
577
- `);
578
- }
579
- /**
580
- * Takes a identifier and a type node and generates a type alias for the type node.
581
- * @param identifierName - The name of the type to generated
582
- * @param typeNode - The type node to generate the type for
583
- * @returns
584
- * @internal
585
- * @beta
586
- */
587
- generateTypeNodeTypes(identifierName, typeNode) {
588
- const type = this.getTypeNodeType(typeNode), typeName = this.getTypeName(identifierName, typeNode), typeAlias = t.tsTypeAliasDeclaration(t.identifier(typeName), null, type);
589
- return new CodeGenerator(t.exportNamedDeclaration(typeAlias)).generate().code.trim();
590
- }
591
- static generateKnownTypes() {
592
- const typeOperator = t.tsTypeOperator(t.tsSymbolKeyword(), "unique"), identifier = t.identifier(REFERENCE_SYMBOL_NAME);
593
- identifier.typeAnnotation = t.tsTypeAnnotation(typeOperator);
594
- const decleration = t.variableDeclaration("const", [t.variableDeclarator(identifier)]);
595
- return decleration.declare = !0, new CodeGenerator(t.exportNamedDeclaration(decleration)).generate().code.trim();
596
- }
597
- /**
598
- * Takes a list of queries from the codebase and generates a type declaration
599
- * for SanityClient to consume.
600
- *
601
- * Note: only types that have previously been generated with `generateTypeNodeTypes`
602
- * will be included in the query map.
603
- *
604
- * @param queries - A list of queries to generate a type declaration for
605
- * @returns
606
- * @internal
607
- * @beta
608
- */
609
- generateQueryMap(queries) {
610
- const typesByQuerystring = {};
611
- for (const query of queries) {
612
- const name = this.typeNodeNameMap.get(query.typeNode);
613
- name && (typesByQuerystring[query.query] ??= [], typesByQuerystring[query.query].push(name));
613
+ this.schema = schema;
614
+ const uniqueTypeNames = /* @__PURE__ */ new Set();
615
+ for (const type of schema) {
616
+ if (uniqueTypeNames.has(type.name))
617
+ throw new Error(
618
+ `Duplicate type name "${type.name}" in schema. Type names must be unique within the same schema.`
619
+ );
620
+ uniqueTypeNames.add(type.name);
614
621
  }
615
- const queryReturnInterface = t.tsInterfaceDeclaration(
616
- t.identifier("SanityQueries"),
617
- null,
618
- [],
619
- t.tsInterfaceBody(
620
- Object.entries(typesByQuerystring).map(([query, types]) => t.tsPropertySignature(
621
- t.stringLiteral(query),
622
- t.tsTypeAnnotation(
623
- t.tsUnionType(types.map((type) => t.tsTypeReference(t.identifier(type))))
624
- )
625
- ))
626
- )
627
- ), declareModule = t.declareModule(
628
- t.stringLiteral("@sanity/client"),
629
- t.blockStatement([queryReturnInterface])
630
- ), clientImport = t.importDeclaration([], t.stringLiteral("@sanity/client"));
631
- return new CodeGenerator(t.program([clientImport, declareModule])).generate().code.trim();
632
- }
633
- /**
634
- * Since we are sanitizing identifiers we migt end up with collisions. Ie there might be a type mux.video and muxVideo, both these
635
- * types would be sanityized into MuxVideo. To avoid this we keep track of the generated type names and add a index to the name.
636
- * When we reference a type we also keep track of the original name so we can reference the correct type later.
637
- */
638
- getTypeName(name, typeNode) {
639
- const desiredName = uppercaseFirstLetter(sanitizeIdentifier(name));
640
- let generatedName = desiredName, i = 2;
641
- for (; this.generatedTypeName.has(generatedName); )
642
- generatedName = `${desiredName}_${i++}`;
643
- return this.generatedTypeName.add(generatedName), this.typeNameMap.set(name, generatedName), typeNode && this.typeNodeNameMap.set(typeNode, generatedName), generatedName;
622
+ for (const type of schema) {
623
+ const currentIdentifierNames = new Set(
624
+ Array.from(this.identifiers.values()).map((id) => id.name)
625
+ ), uniqueIdentifier = getUniqueIdentifierForName(type.name, currentIdentifierNames);
626
+ this.identifiers.set(type.name, uniqueIdentifier);
627
+ }
628
+ for (const type of schema)
629
+ this.tsTypes.set(type.name, this.generateTsType(type));
644
630
  }
645
- getTypeNodeType(typeNode) {
631
+ generateTsType(typeNode) {
646
632
  switch (typeNode.type) {
647
633
  case "string":
648
634
  return typeNode.value !== void 0 ? t.tsLiteralType(t.stringLiteral(typeNode.value)) : t.tsStringKeyword();
@@ -653,9 +639,9 @@ class TypeGenerator {
653
639
  case "unknown":
654
640
  return t.tsUnknownKeyword();
655
641
  case "document":
656
- return this.generateDocumentType(typeNode);
642
+ return this.generateDocumentTsType(typeNode);
657
643
  case "type":
658
- return this.getTypeNodeType(typeNode.value);
644
+ return this.generateTsType(typeNode.value);
659
645
  case "array":
660
646
  return this.generateArrayTsType(typeNode);
661
647
  case "object":
@@ -667,20 +653,20 @@ class TypeGenerator {
667
653
  case "null":
668
654
  return t.tsNullKeyword();
669
655
  default:
670
- throw new Error(`Type "${typeNode.type}" not found in schema`);
656
+ throw new Error(
657
+ `Encountered unsupported node type "${// @ts-expect-error This should never happen
658
+ typeNode.type}" while generating schema types`
659
+ );
671
660
  }
672
661
  }
673
662
  // Helper function used to generate TS types for array type nodes.
674
663
  generateArrayTsType(typeNode) {
675
- const typeNodes = this.getTypeNodeType(typeNode.of);
676
- return t.tsTypeReference(
677
- t.identifier("Array"),
678
- t.tsTypeParameterInstantiation([typeNodes])
679
- );
664
+ const typeNodes = this.generateTsType(typeNode.of);
665
+ return t.tsTypeReference(t.identifier("Array"), t.tsTypeParameterInstantiation([typeNodes]));
680
666
  }
681
667
  // Helper function used to generate TS types for object properties.
682
- generateObjectProperty(key, attribute) {
683
- const type = this.getTypeNodeType(attribute.value), propertySignature = t.tsPropertySignature(
668
+ generateTsObjectProperty(key, attribute) {
669
+ const type = this.generateTsType(attribute.value), propertySignature = t.tsPropertySignature(
684
670
  t.identifier(sanitizeIdentifier(key)),
685
671
  t.tsTypeAnnotation(type)
686
672
  );
@@ -690,16 +676,16 @@ class TypeGenerator {
690
676
  generateObjectTsType(typeNode) {
691
677
  const props = [];
692
678
  Object.entries(typeNode.attributes).forEach(([key, attribute]) => {
693
- props.push(this.generateObjectProperty(key, attribute));
679
+ props.push(this.generateTsObjectProperty(key, attribute));
694
680
  });
695
681
  const rest = typeNode.rest;
696
- if (rest !== void 0)
682
+ if (rest)
697
683
  switch (rest.type) {
698
684
  case "unknown":
699
685
  return t.tsUnknownKeyword();
700
686
  case "object": {
701
687
  Object.entries(rest.attributes).forEach(([key, attribute]) => {
702
- props.push(this.generateObjectProperty(key, attribute));
688
+ props.push(this.generateTsObjectProperty(key, attribute));
703
689
  });
704
690
  break;
705
691
  }
@@ -710,56 +696,243 @@ class TypeGenerator {
710
696
  default:
711
697
  throw new Error(`Type "${rest.type}" not found in schema`);
712
698
  }
713
- if (typeNode.dereferencesTo !== void 0) {
714
- const derefType = t.tsPropertySignature(
715
- t.identifier(REFERENCE_SYMBOL_NAME),
716
- t.tsTypeAnnotation(t.tsLiteralType(t.stringLiteral(typeNode.dereferencesTo)))
699
+ if (typeNode.dereferencesTo) {
700
+ const derefType = Object.assign(
701
+ t.tsPropertySignature(
702
+ INTERNAL_REFERENCE_SYMBOL,
703
+ t.tsTypeAnnotation(t.tsLiteralType(t.stringLiteral(typeNode.dereferencesTo)))
704
+ ),
705
+ { computed: !0, optional: !0 }
717
706
  );
718
- derefType.computed = !0, derefType.optional = !0, props.push(derefType);
707
+ props.push(derefType);
719
708
  }
720
709
  return t.tsTypeLiteral(props);
721
710
  }
722
711
  generateInlineTsType(typeNode) {
723
- const referencedTypeNode = this.schema.find((schema) => schema.name === typeNode.name);
724
- if (referencedTypeNode === void 0) {
725
- const generatedName2 = this.typeNameMap.get(typeNode.name);
726
- if (generatedName2)
727
- return t.tsTypeReference(t.identifier(generatedName2));
728
- const missing = t.tsUnknownKeyword();
729
- return missing.trailingComments = [
730
- {
731
- type: "CommentLine",
732
- value: ` Unable to locate the referenced type "${typeNode.name}" in schema`
733
- }
734
- ], missing;
735
- }
736
- const generatedName = this.typeNameMap.get(referencedTypeNode.name);
737
- return generatedName ? t.tsTypeReference(t.identifier(generatedName)) : t.tsUnknownKeyword();
712
+ const id = this.identifiers.get(typeNode.name);
713
+ return id ? t.tsTypeReference(id) : t.addComment(
714
+ t.tsUnknownKeyword(),
715
+ "trailing",
716
+ ` Unable to locate the referenced type "${typeNode.name}" in schema`,
717
+ !0
718
+ );
738
719
  }
739
720
  // Helper function used to generate TS types for union type nodes.
740
721
  generateUnionTsType(typeNode) {
741
- if (typeNode.of.length === 0)
742
- return t.tsNeverKeyword();
743
- if (typeNode.of.length === 1)
744
- return this.getTypeNodeType(typeNode.of[0]);
745
- const typeNodes = typeNode.of.map((node) => this.getTypeNodeType(node));
746
- return t.tsUnionType(typeNodes);
722
+ return typeNode.of.length === 0 ? t.tsNeverKeyword() : typeNode.of.length === 1 ? this.generateTsType(typeNode.of[0]) : t.tsUnionType(typeNode.of.map((node) => this.generateTsType(node)));
747
723
  }
748
724
  // Helper function used to generate TS types for document type nodes.
749
- generateDocumentType(document) {
725
+ generateDocumentTsType(document) {
750
726
  const props = Object.entries(document.attributes).map(
751
- ([key, node]) => this.generateObjectProperty(key, node)
727
+ ([key, node]) => this.generateTsObjectProperty(key, node)
752
728
  );
753
729
  return t.tsTypeLiteral(props);
754
730
  }
731
+ typeNames() {
732
+ return this.schema.map((schemaType) => schemaType.name);
733
+ }
734
+ getType(typeName) {
735
+ const tsType = this.tsTypes.get(typeName), id = this.identifiers.get(typeName);
736
+ if (tsType && id) return { tsType, id };
737
+ }
738
+ hasType(typeName) {
739
+ return this.tsTypes.has(typeName);
740
+ }
741
+ evaluateQuery = weakMapMemo(
742
+ ({ query }) => {
743
+ const ast = safeParseQuery(query), typeNode = typeEvaluate(ast, this.schema), tsType = this.generateTsType(typeNode), stats = walkAndCountQueryTypeNodeStats(typeNode);
744
+ return { tsType, stats };
745
+ }
746
+ );
747
+ *[Symbol.iterator]() {
748
+ for (const { name } of this.schema)
749
+ yield { name, ...this.getType(name) };
750
+ }
755
751
  }
756
- function uppercaseFirstLetter(input) {
757
- return input.charAt(0).toUpperCase() + input.slice(1);
752
+ function walkAndCountQueryTypeNodeStats(typeNode) {
753
+ switch (typeNode.type) {
754
+ case "unknown":
755
+ return { allTypes: 1, unknownTypes: 1, emptyUnions: 0 };
756
+ case "array": {
757
+ const acc = walkAndCountQueryTypeNodeStats(typeNode.of);
758
+ return acc.allTypes += 1, acc;
759
+ }
760
+ case "object": {
761
+ if (typeNode.rest && typeNode.rest.type === "unknown")
762
+ return { allTypes: 2, unknownTypes: 1, emptyUnions: 0 };
763
+ const restStats = typeNode.rest ? walkAndCountQueryTypeNodeStats(typeNode.rest) : { allTypes: 0, unknownTypes: 0, emptyUnions: 0 };
764
+ return restStats.allTypes += 1, Object.values(typeNode.attributes).reduce((acc, attribute) => {
765
+ const { allTypes, unknownTypes, emptyUnions } = walkAndCountQueryTypeNodeStats(
766
+ attribute.value
767
+ );
768
+ return {
769
+ allTypes: acc.allTypes + allTypes,
770
+ unknownTypes: acc.unknownTypes + unknownTypes,
771
+ emptyUnions: acc.emptyUnions + emptyUnions
772
+ };
773
+ }, restStats);
774
+ }
775
+ case "union":
776
+ return typeNode.of.length === 0 ? { allTypes: 1, unknownTypes: 0, emptyUnions: 1 } : typeNode.of.reduce(
777
+ (acc, type) => {
778
+ const { allTypes, unknownTypes, emptyUnions } = walkAndCountQueryTypeNodeStats(type);
779
+ return {
780
+ allTypes: acc.allTypes + allTypes,
781
+ unknownTypes: acc.unknownTypes + unknownTypes,
782
+ emptyUnions: acc.emptyUnions + emptyUnions
783
+ };
784
+ },
785
+ { allTypes: 1, unknownTypes: 0, emptyUnions: 0 }
786
+ // count the union type itself
787
+ );
788
+ default:
789
+ return { allTypes: 1, unknownTypes: 0, emptyUnions: 0 };
790
+ }
758
791
  }
759
- function sanitizeIdentifier(input) {
760
- return `${input.replace(/^\d/, "_").replace(/[^$\w]+(.)/g, (_, char) => char.toUpperCase())}`;
792
+ class TypeGenerator {
793
+ getInternalReferenceSymbolDeclaration = computeOnce(() => {
794
+ const typeOperator = t.tsTypeOperator(t.tsSymbolKeyword(), "unique"), id = INTERNAL_REFERENCE_SYMBOL;
795
+ id.typeAnnotation = t.tsTypeAnnotation(typeOperator);
796
+ const declaration = t.variableDeclaration("const", [t.variableDeclarator(id)]);
797
+ declaration.declare = !0;
798
+ const ast = t.exportNamedDeclaration(declaration), code = generateCode(ast);
799
+ return { id, code, ast };
800
+ });
801
+ getSchemaTypeGenerator = createSelector(
802
+ [(options) => options.schema],
803
+ (schema) => new SchemaTypeGenerator(schema)
804
+ );
805
+ getSchemaTypeDeclarations = createSelector(
806
+ [
807
+ (options) => options.root,
808
+ (options) => options.schemaPath,
809
+ this.getSchemaTypeGenerator
810
+ ],
811
+ (root = process.cwd(), schemaPath, schema) => Array.from(schema).map(({ id, name, tsType }, index) => {
812
+ const typeAlias = t.tsTypeAliasDeclaration(id, null, tsType);
813
+ let ast = t.exportNamedDeclaration(typeAlias);
814
+ index === 0 && schemaPath && (ast = t.addComments(ast, "leading", [
815
+ { type: "CommentLine", value: ` Source: ${normalizePath(root, schemaPath)}` }
816
+ ]));
817
+ const code = generateCode(ast);
818
+ return { id, code, name, tsType, ast };
819
+ })
820
+ );
821
+ getAllSanitySchemaTypesDeclaration = createSelector(
822
+ [this.getSchemaTypeDeclarations],
823
+ (schemaTypes) => {
824
+ const ast = t.exportNamedDeclaration(
825
+ t.tsTypeAliasDeclaration(
826
+ ALL_SANITY_SCHEMA_TYPES,
827
+ null,
828
+ schemaTypes.length ? t.tsUnionType(schemaTypes.map(({ id }) => t.tsTypeReference(id))) : t.tsNeverKeyword()
829
+ )
830
+ ), code = generateCode(ast);
831
+ return { id: ALL_SANITY_SCHEMA_TYPES, code, ast };
832
+ }
833
+ );
834
+ static async getEvaluatedModules({
835
+ root = process.cwd(),
836
+ reporter: report,
837
+ schemaTypeGenerator,
838
+ schemaTypeDeclarations,
839
+ queries: extractedModules
840
+ }) {
841
+ if (!extractedModules)
842
+ return report?.stream.evaluatedModules.end(), [];
843
+ const currentIdentifiers = new Set(schemaTypeDeclarations.map(({ id }) => id.name)), evaluatedModuleResults = [];
844
+ for await (const { filename, ...extractedModule } of extractedModules) {
845
+ const queries = [], errors = [...extractedModule.errors];
846
+ for (const extractedQuery of extractedModule.queries) {
847
+ const { variable } = extractedQuery;
848
+ try {
849
+ const { tsType, stats } = schemaTypeGenerator.evaluateQuery(extractedQuery), id = getUniqueIdentifierForName(resultSuffix(variable.id.name), currentIdentifiers), typeAlias = t.tsTypeAliasDeclaration(id, null, tsType), trimmedQuery = extractedQuery.query.replace(/(\r\n|\n|\r)/gm, "").trim(), ast = t.addComments(t.exportNamedDeclaration(typeAlias), "leading", [
850
+ { type: "CommentLine", value: ` Source: ${normalizePath(root, filename)}` },
851
+ { type: "CommentLine", value: ` Variable: ${variable.id.name}` },
852
+ { type: "CommentLine", value: ` Query: ${trimmedQuery}` }
853
+ ]), evaluatedQueryResult = {
854
+ id,
855
+ code: generateCode(ast),
856
+ ast,
857
+ stats,
858
+ tsType,
859
+ ...extractedQuery
860
+ };
861
+ currentIdentifiers.add(id.name), queries.push(evaluatedQueryResult);
862
+ } catch (cause) {
863
+ errors.push(new QueryEvaluationError({ variable, cause, filename }));
864
+ }
865
+ }
866
+ const evaluatedModule = {
867
+ filename,
868
+ queries,
869
+ errors
870
+ };
871
+ report?.stream.evaluatedModules.emit(evaluatedModule), evaluatedModuleResults.push(evaluatedModule);
872
+ }
873
+ return report?.stream.evaluatedModules.end(), evaluatedModuleResults;
874
+ }
875
+ static async getQueryMapDeclaration({
876
+ overloadClientMethods = !0,
877
+ evaluatedModules
878
+ }) {
879
+ if (!overloadClientMethods) return { code: "", ast: t.program([]) };
880
+ const queries = evaluatedModules.flatMap((module) => module.queries);
881
+ if (!queries.length) return { code: "", ast: t.program([]) };
882
+ const typesByQuerystring = {};
883
+ for (const { id, query } of queries)
884
+ typesByQuerystring[query] ??= [], typesByQuerystring[query].push(id.name);
885
+ const queryReturnInterface = t.tsInterfaceDeclaration(
886
+ SANITY_QUERIES,
887
+ null,
888
+ [],
889
+ t.tsInterfaceBody(
890
+ Object.entries(typesByQuerystring).map(([query, types]) => t.tsPropertySignature(
891
+ t.stringLiteral(query),
892
+ t.tsTypeAnnotation(
893
+ types.length ? t.tsUnionType(types.map((type) => t.tsTypeReference(t.identifier(type)))) : t.tsNeverKeyword()
894
+ )
895
+ ))
896
+ )
897
+ ), declareModule = t.declareModule(
898
+ t.stringLiteral("@sanity/client"),
899
+ t.blockStatement([queryReturnInterface])
900
+ ), clientImport = t.addComments(
901
+ t.importDeclaration([], t.stringLiteral("@sanity/client")),
902
+ "leading",
903
+ [{ type: "CommentLine", value: " Query TypeMap" }]
904
+ ), ast = t.program([clientImport, declareModule]);
905
+ return { code: generateCode(ast), ast };
906
+ }
907
+ async generateTypes(options) {
908
+ const { reporter: report } = options, internalReferenceSymbol = this.getInternalReferenceSymbolDeclaration(), schemaTypeDeclarations = this.getSchemaTypeDeclarations(options), allSanitySchemaTypesDeclaration = this.getAllSanitySchemaTypesDeclaration(options);
909
+ report?.event.generatedSchemaTypes({
910
+ internalReferenceSymbol,
911
+ schemaTypeDeclarations,
912
+ allSanitySchemaTypesDeclaration
913
+ });
914
+ const program = t.program([]);
915
+ let code = "";
916
+ for (const declaration of schemaTypeDeclarations)
917
+ program.body.push(declaration.ast), code += declaration.code;
918
+ program.body.push(allSanitySchemaTypesDeclaration.ast), code += allSanitySchemaTypesDeclaration.code, program.body.push(internalReferenceSymbol.ast), code += internalReferenceSymbol.code;
919
+ const evaluatedModules = await TypeGenerator.getEvaluatedModules({
920
+ ...options,
921
+ schemaTypeDeclarations,
922
+ schemaTypeGenerator: this.getSchemaTypeGenerator(options)
923
+ });
924
+ for (const { queries } of evaluatedModules)
925
+ for (const query of queries)
926
+ program.body.push(query.ast), code += query.code;
927
+ const queryMapDeclaration = await TypeGenerator.getQueryMapDeclaration({
928
+ ...options,
929
+ evaluatedModules
930
+ });
931
+ return program.body.push(...queryMapDeclaration.ast.body), code += queryMapDeclaration.code, report?.event.generatedQueryTypes({ queryMapDeclaration }), { code, ast: program };
932
+ }
761
933
  }
762
934
  export {
935
+ QueryExtractionError,
763
936
  TypeGenerator,
764
937
  configDefinition,
765
938
  findQueriesInPath,