graphql-sock
Version:
GraphQL Semantic Output Conversion Kit - converts a cutting edge SDL file that supports semantic nullability into a more traditional SDL file legacy tools can support.
175 lines (174 loc) • 7.66 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.semanticToNullable = semanticToNullable;
exports.semanticToStrict = semanticToStrict;
exports.convertFieldConfig = convertFieldConfig;
const graphql_1 = require("graphql");
const graphql = __importStar(require("graphql"));
// If GraphQL doesn't have this helper function, then it doesn't natively support GraphQLSemanticNonNull
const isSemanticNonNullType =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(_a = graphql.isSemanticNonNullType) !== null && _a !== void 0 ? _a : (() => false);
function convertSchema(schema, toStrict) {
const config = schema.toConfig();
const convertType = makeConvertType(toStrict);
const derivedSchema = new graphql_1.GraphQLSchema(Object.assign(Object.assign({}, config), { query: convertType(config.query), mutation: convertType(config.mutation), subscription: convertType(config.subscription), types: config.types
.filter((t) => !t.name.startsWith("__"))
.map((t) => convertType(t)), directives: config.directives.filter((d) => d.name !== "semanticNonNull") }));
return derivedSchema;
}
function semanticToNullable(schema) {
return convertSchema(schema, false);
}
function semanticToStrict(schema) {
return convertSchema(schema, true);
}
function makeConvertType(toStrict) {
const cache = new Map();
function convertFields(fields) {
return () => {
return Object.fromEntries(Object.entries(fields).map(([fieldName, inSpec]) => {
const spec = convertFieldConfig(inSpec, toStrict);
return [
fieldName,
Object.assign(Object.assign({}, spec), { type: convertType(spec.type) }),
];
}));
};
}
function convertTypes(types) {
if (!types) {
return undefined;
}
return () => types.map((t) => convertType(t));
}
function convertType(type) {
if (!type) {
return type;
}
if (isSemanticNonNullType(type)) {
const unwrapped = convertType(type.ofType);
// Here's where we do our thing!
if (toStrict) {
return new graphql_1.GraphQLNonNull(unwrapped);
}
else {
return unwrapped;
}
}
else if ((0, graphql_1.isNonNullType)(type)) {
return new graphql_1.GraphQLNonNull(convertType(type.ofType));
}
else if ((0, graphql_1.isListType)(type)) {
return new graphql_1.GraphQLList(convertType(type.ofType));
}
if (type.name.startsWith("__")) {
return null;
}
if (cache.has(type.name)) {
return cache.get(type.name);
}
const newType = (() => {
if ((0, graphql_1.isObjectType)(type)) {
const config = type.toConfig();
return new graphql_1.GraphQLObjectType(Object.assign(Object.assign({}, config), { fields: convertFields(config.fields), interfaces: convertTypes(config.interfaces) }));
}
else if ((0, graphql_1.isInterfaceType)(type)) {
const config = type.toConfig();
return new graphql_1.GraphQLInterfaceType(Object.assign(Object.assign({}, config), { fields: convertFields(config.fields), interfaces: convertTypes(config.interfaces) }));
}
else if ((0, graphql_1.isUnionType)(type)) {
const config = type.toConfig();
return new graphql_1.GraphQLUnionType(Object.assign(Object.assign({}, config), { types: convertTypes(config.types) }));
}
else {
return type;
}
})();
cache.set(type.name, newType);
return newType;
}
return convertType;
}
/**
* Takes a GraphQL field config and checks to see if the `@semanticNonNull`
* directive was applied; if so, converts to a field config that adds
* GraphQLNonNull wrapper types in the relevant places if `toStrict` is true.
*
* @see {@url https://www.apollographql.com/docs/kotlin/advanced/nullability/#semanticnonnull}
*/
function convertFieldConfig(spec, toStrict) {
var _a, _b, _c, _d;
const directive = (_b = (_a = spec.astNode) === null || _a === void 0 ? void 0 : _a.directives) === null || _b === void 0 ? void 0 : _b.find((d) => d.name.value === "semanticNonNull");
if (!directive) {
return spec;
}
/** The AST node with the semanticNonNull directive removed */
const filteredAstNode = Object.assign(Object.assign({}, spec.astNode), { directives: spec.astNode.directives.filter((d) => d.name.value !== "semanticNonNull") });
const levelsArg = (_c = directive.arguments) === null || _c === void 0 ? void 0 : _c.find((a) => a.name.value === "levels");
const levels = ((_d = levelsArg === null || levelsArg === void 0 ? void 0 : levelsArg.value) === null || _d === void 0 ? void 0 : _d.kind) === graphql_1.Kind.LIST
? levelsArg.value.values
.filter((v) => v.kind === graphql_1.Kind.INT)
.map((v) => Number(v.value))
: [0];
function recurse(type, level) {
if (isSemanticNonNullType(type)) {
// Strip semantic-non-null types; this should never happen but if someone
// uses both semantic-non-null and the `@semanticNonNull` directive, we
// want the directive to win (I guess?)
return recurse(type.ofType, level);
}
else if ((0, graphql_1.isNonNullType)(type)) {
const inner = recurse(type.ofType, level);
if ((0, graphql_1.isNonNullType)(inner)) {
return inner;
}
else {
// Carry the non-null through no matter what semantic says
return new graphql_1.GraphQLNonNull(inner);
}
}
else if ((0, graphql_1.isListType)(type)) {
const inner = new graphql_1.GraphQLList(recurse(type.ofType, level + 1));
if (toStrict && levels.includes(level)) {
return new graphql_1.GraphQLNonNull(inner);
}
else {
return inner;
}
}
else {
if (toStrict && levels.includes(level)) {
return new graphql_1.GraphQLNonNull(type);
}
else {
return type;
}
}
}
return Object.assign(Object.assign({}, spec), { type: recurse(spec.type, 0), astNode: filteredAstNode });
}
;