@graphprotocol/graph-cli
Version:
CLI for building for and deploying to The Graph
254 lines (253 loc) • 11.1 kB
JavaScript
"use strict";
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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable unicorn/no-array-for-each */
const immutable_1 = __importDefault(require("immutable"));
const typesCodegen = __importStar(require("./types"));
const tsCodegen = __importStar(require("./typescript"));
class IdField {
constructor(idField) {
const typeName = idField.getIn(['type', 'type', 'name', 'value']);
this.kind = typeName === 'Bytes' ? IdField.BYTES : IdField.STRING;
}
typeName() {
return this.kind === IdField.BYTES ? 'Bytes' : 'string';
}
gqlTypeName() {
return this.kind === IdField.BYTES ? 'Bytes' : 'String';
}
tsNamedType() {
return tsCodegen.namedType(this.typeName());
}
tsValueFrom() {
return this.kind === IdField.BYTES ? 'Value.fromBytes(id)' : 'Value.fromString(id)';
}
tsValueKind() {
return this.kind === IdField.BYTES ? 'ValueKind.BYTES' : 'ValueKind.STRING';
}
tsValueToString() {
return this.kind == IdField.BYTES ? 'id.toBytes().toHexString()' : 'id.toString()';
}
tsToString() {
return this.kind == IdField.BYTES ? 'id.toHexString()' : 'id';
}
static fromFields(fields) {
const idField = fields.find(field => field.getIn(['name', 'value']) === 'id');
return new IdField(idField);
}
static fromTypeDef(def) {
return IdField.fromFields(def.get('fields'));
}
}
IdField.BYTES = Symbol('Bytes');
IdField.STRING = Symbol('String');
class SchemaCodeGenerator {
constructor(schema) {
this.schema = schema;
this.schema = schema;
}
generateModuleImports() {
return [
tsCodegen.moduleImports([
// Base classes
'TypedMap',
'Entity',
'Value',
'ValueKind',
// APIs
'store',
// Basic Scalar types
'Bytes',
'BigInt',
'BigDecimal',
], '@graphprotocol/graph-ts'),
];
}
generateTypes() {
return this.schema.ast
.get('definitions')
.filter((def) => this._isEntityTypeDefinition(def))
.map((def) => this._generateEntityType(def));
}
_isEntityTypeDefinition(def) {
return (def.get('kind') === 'ObjectTypeDefinition' &&
def
.get('directives')
.find((directive) => directive.getIn(['name', 'value']) === 'entity') !== undefined);
}
_isInterfaceDefinition(def) {
return def.get('kind') === 'InterfaceTypeDefinition';
}
_generateEntityType(def) {
const name = def.getIn(['name', 'value']);
const klass = tsCodegen.klass(name, { export: true, extends: 'Entity' });
const fields = def.get('fields');
const idField = IdField.fromFields(fields);
// Generate and add a constructor
klass.addMethod(this._generateConstructor(name, fields));
// Generate and add save() and getById() methods
this._generateStoreMethods(name, idField).forEach(method => klass.addMethod(method));
// Generate and add entity field getters and setters
def
.get('fields')
.reduce((methods, field) => methods.concat(this._generateEntityFieldMethods(def, field)), immutable_1.default.List())
.forEach((method) => klass.addMethod(method));
return klass;
}
_generateConstructor(_entityName, fields) {
const idField = IdField.fromFields(fields);
return tsCodegen.method('constructor', [tsCodegen.param('id', idField.tsNamedType())], undefined, `
super()
this.set('id', ${idField.tsValueFrom()})
`);
}
_generateStoreMethods(entityName, idField) {
return immutable_1.default.List.of(tsCodegen.method('save', [], tsCodegen.namedType('void'), `
let id = this.get('id')
assert(id != null,
'Cannot save ${entityName} entity without an ID')
if (id) {
assert(id.kind == ${idField.tsValueKind()},
\`Entities of type ${entityName} must have an ID of type ${idField.gqlTypeName()} but the id '\${id.displayData()}' is of type \${id.displayKind()}\`)
store.set('${entityName}', ${idField.tsValueToString()}, this)
}`), tsCodegen.staticMethod('load', [tsCodegen.param('id', tsCodegen.namedType(idField.typeName()))], tsCodegen.nullableType(tsCodegen.namedType(entityName)), `
return changetype<${entityName} | null>(store.get('${entityName}', ${idField.tsToString()}))
`));
}
_generateEntityFieldMethods(entityDef, fieldDef) {
return (immutable_1.default
.List([
this._generateEntityFieldGetter(entityDef, fieldDef),
this._generateEntityFieldSetter(entityDef, fieldDef),
])
// generator can return null if the field is not supported
// so we filter all falsy values
.filter(Boolean));
}
_generateEntityFieldGetter(_entityDef, fieldDef) {
const name = fieldDef.getIn(['name', 'value']);
const gqlType = fieldDef.get('type');
const fieldValueType = this._valueTypeFromGraphQl(gqlType);
const returnType = this._typeFromGraphQl(gqlType);
const isNullable = returnType instanceof tsCodegen.NullableType;
const getNonNullable = `return ${typesCodegen.valueToAsc('value!', fieldValueType)}`;
const getNullable = `if (!value || value.kind == ValueKind.NULL) {
return null
} else {
return ${typesCodegen.valueToAsc('value', fieldValueType)}
}`;
return tsCodegen.method(`get ${name}`, [], returnType, `
let value = this.get('${name}')
${isNullable ? getNullable : getNonNullable}
`);
}
_generateEntityFieldSetter(_entityDef, fieldDef) {
const name = fieldDef.getIn(['name', 'value']);
const isDerivedField = !!fieldDef
.get('directives')
.find((directive) => directive.getIn(['name', 'value']) === 'derivedFrom');
// We cannot have setters for derived fields
if (isDerivedField)
return null;
const gqlType = fieldDef.get('type');
const fieldValueType = this._valueTypeFromGraphQl(gqlType);
const paramType = this._typeFromGraphQl(gqlType);
const isNullable = paramType instanceof tsCodegen.NullableType;
const paramTypeString = isNullable ? paramType.inner.toString() : paramType.toString();
const isArray = paramType instanceof tsCodegen.ArrayType;
if (isArray && paramType.inner instanceof tsCodegen.NullableType) {
const baseType = this._baseType(gqlType);
throw new Error(`
GraphQL schema can't have List's with Nullable members.
Error in '${name}' field of type '[${baseType}]'.
Suggestion: add an '!' to the member type of the List, change from '[${baseType}]' to '[${baseType}!]'`);
}
const setNonNullable = `
this.set('${name}', ${typesCodegen.valueFromAsc(`value`, fieldValueType)})
`;
const setNullable = `
if (!value) {
this.unset('${name}')
} else {
this.set('${name}', ${typesCodegen.valueFromAsc(`<${paramTypeString}>value`, fieldValueType)})
}
`;
return tsCodegen.method(`set ${name}`, [tsCodegen.param('value', paramType)], undefined, isNullable ? setNullable : setNonNullable);
}
_resolveFieldType(gqlType) {
const typeName = gqlType.getIn(['name', 'value']);
// If this is a reference to another type, the field has the type of
// the referred type's id field
const typeDef = this.schema.ast
.get('definitions')
.find((def) => (this._isEntityTypeDefinition(def) || this._isInterfaceDefinition(def)) &&
def.getIn(['name', 'value']) === typeName);
if (typeDef) {
return IdField.fromTypeDef(typeDef).typeName();
}
return typeName;
}
/** Return the type that values for this field must have. For scalar
* types, that's the type from the subgraph schema. For references to
* other entity types, this is the same as the type of the id of the
* referred type, i.e., `string` or `Bytes`*/
_valueTypeFromGraphQl(gqlType) {
if (gqlType.get('kind') === 'NonNullType') {
return this._valueTypeFromGraphQl(gqlType.get('type'));
}
if (gqlType.get('kind') === 'ListType') {
return '[' + this._valueTypeFromGraphQl(gqlType.get('type')) + ']';
}
return this._resolveFieldType(gqlType);
}
/** Determine the base type of `gqlType` by removing any non-null
* constraints and using the type of elements of lists */
_baseType(gqlType) {
if (gqlType.get('kind') === 'NonNullType') {
return this._baseType(gqlType.get('type'));
}
if (gqlType.get('kind') === 'ListType') {
return this._baseType(gqlType.get('type'));
}
return gqlType.getIn(['name', 'value']);
}
_typeFromGraphQl(gqlType, nullable = true) {
if (gqlType.get('kind') === 'NonNullType') {
return this._typeFromGraphQl(gqlType.get('type'), false);
}
if (gqlType.get('kind') === 'ListType') {
const type = tsCodegen.arrayType(this._typeFromGraphQl(gqlType.get('type')));
return nullable ? tsCodegen.nullableType(type) : type;
}
// NamedType
const type = tsCodegen.namedType(typesCodegen.ascTypeForValue(this._resolveFieldType(gqlType)));
// In AssemblyScript, primitives cannot be nullable.
return nullable && !type.isPrimitive() ? tsCodegen.nullableType(type) : type;
}
}
exports.default = SchemaCodeGenerator;