@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.d.ts +144 -90
- package/lib/index.js +359 -186
- package/lib/index.js.map +1 -1
- package/package.json +8 -6
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
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
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.
|
|
642
|
+
return this.generateDocumentTsType(typeNode);
|
|
657
643
|
case "type":
|
|
658
|
-
return this.
|
|
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(
|
|
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.
|
|
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
|
-
|
|
683
|
-
const type = this.
|
|
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.
|
|
679
|
+
props.push(this.generateTsObjectProperty(key, attribute));
|
|
694
680
|
});
|
|
695
681
|
const rest = typeNode.rest;
|
|
696
|
-
if (rest
|
|
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.
|
|
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
|
|
714
|
-
const derefType =
|
|
715
|
-
t.
|
|
716
|
-
|
|
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
|
-
|
|
707
|
+
props.push(derefType);
|
|
719
708
|
}
|
|
720
709
|
return t.tsTypeLiteral(props);
|
|
721
710
|
}
|
|
722
711
|
generateInlineTsType(typeNode) {
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
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
|
-
|
|
725
|
+
generateDocumentTsType(document) {
|
|
750
726
|
const props = Object.entries(document.attributes).map(
|
|
751
|
-
([key, node]) => this.
|
|
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
|
|
757
|
-
|
|
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
|
-
|
|
760
|
-
|
|
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,
|