Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: loading interfaces for derived loaders #1457

Merged
merged 5 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/four-pandas-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphprotocol/graph-cli': minor
---

do not generate loader for interfaces
26 changes: 25 additions & 1 deletion packages/cli/src/codegen/schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from 'assert';
import * as graphql from 'graphql/language';
import prettier from 'prettier';
import Schema from '../schema';
Expand Down Expand Up @@ -27,7 +28,6 @@ const testEntity = (generatedTypes: any[], expectedEntity: any) => {
expectedMethod.static
? expect(method instanceof StaticMethod).toBe(true)
: expect(method instanceof Method).toBe(true);

expect(method.params).toStrictEqual(expectedMethod.params);
expect(method.returnType).toStrictEqual(expectedMethod.returnType);
expect(formatTS(method.body)).toBe(formatTS(expectedMethod.body));
Expand Down Expand Up @@ -536,4 +536,28 @@ describe('Schema code generator', () => {
],
});
});

test('no derived loader for interface', () => {
const codegen = createSchemaCodeGen(`
interface IExample {
id: ID!
main: Main!
num: Int!
}

type Example1 implements IExample @entity {
id: ID!
main: Main!
num: Int!
}

type Main @entity {
id: ID!
examples: [IExample!]! @derivedFrom(field: "main")
}
`);

const generateDerivedLoaders = codegen.generateDerivedLoaders().filter(Boolean);
assert(generateDerivedLoaders.length === 0);
});
});
64 changes: 54 additions & 10 deletions packages/cli/src/codegen/schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable unicorn/no-array-for-each */
import debug from 'debug';
import type {
DefinitionNode,
FieldDefinitionNode,
Expand All @@ -8,6 +7,7 @@ import type {
ObjectTypeDefinitionNode,
TypeNode,
} from 'graphql/language';
import debug from '../debug';
import Schema from '../schema';
import * as typesCodegen from './types';
import * as tsCodegen from './typescript';
Expand Down Expand Up @@ -67,6 +67,8 @@ class IdField {
}
}

const schemaCodeGeneratorDebug = debug('graph-cli:SchemaCodeGenerator');

export default class SchemaCodeGenerator {
constructor(private schema: Schema) {
this.schema = schema;
Expand Down Expand Up @@ -99,26 +101,43 @@ export default class SchemaCodeGenerator {
return this.schema.ast.definitions
.map(def => {
if (this._isEntityTypeDefinition(def)) {
schemaCodeGeneratorDebug.extend('generateTypes')(
`Generating entity type for ${def.name.value}`,
);
return this._generateEntityType(def);
}
})
.filter(Boolean) as Array<tsCodegen.Class>;
}

generateDerivedLoaders() {
// This gets all the interfaces in the schema
// We can think of more optimized ways to do this
const interfaces = (
this.schema.ast.definitions.filter(def =>
this._isInterfaceDefinition(def),
) as InterfaceTypeDefinitionNode[]
).map(def => def.name.value);

const fields = (
(
this.schema.ast.definitions.filter(def =>
this._isEntityTypeDefinition(def),
) as ObjectTypeDefinitionNode[]
this.schema.ast.definitions.filter(def => {
return this._isEntityTypeDefinition(def);
}) as ObjectTypeDefinitionNode[]
)
.flatMap((def: ObjectTypeDefinitionNode) => def.fields)
.filter(def => this._isDerivedField(def))
.filter(def => def?.type !== undefined) as FieldDefinitionNode[]
).map(def => this._getTypeNameForField(def.type));
schemaCodeGeneratorDebug.extend('generateDerivedLoaders')(
`Generating derived loaders for ${fields}`,
);

return [...new Set(fields)].map(typeName => {
return this._generateDerivedLoader(typeName);
// do not support interfaces
if (!interfaces.includes(typeName)) {
return this._generateDerivedLoader(typeName);
}
});
}

Expand All @@ -131,8 +150,7 @@ export default class SchemaCodeGenerator {

_isDerivedField(field: FieldDefinitionNode | undefined): boolean {
return (
field?.directives?.find((directive: any) => directive.name.value === 'derivedFrom') !==
undefined
field?.directives?.find(directive => directive.name.value === 'derivedFrom') !== undefined
);
}
_isInterfaceDefinition(def: DefinitionNode): def is InterfaceTypeDefinitionNode {
Expand Down Expand Up @@ -287,13 +305,15 @@ export default class SchemaCodeGenerator {

_generateEntityFieldGetter(_entityDef: ObjectTypeDefinitionNode, fieldDef: FieldDefinitionNode) {
const isDerivedField = this._isDerivedField(fieldDef);
const codegenDebug = debug('codegen');
const name = fieldDef.name.value;

if (isDerivedField) {
codegenDebug(`Generating derived field getter for ${fieldDef.name.value}`);
schemaCodeGeneratorDebug.extend('_generateEntityFieldGetter')(
`Generating derived field getter for ${name}`,
);
return this._generateDerivedFieldGetter(_entityDef, fieldDef);
}

const name = fieldDef.name.value;
const gqlType = fieldDef.type;
const fieldValueType = this._valueTypeFromGraphQl(gqlType);
const returnType = this._typeFromGraphQl(gqlType);
Expand Down Expand Up @@ -329,8 +349,19 @@ export default class SchemaCodeGenerator {
_generateDerivedFieldGetter(entityDef: ObjectTypeDefinitionNode, fieldDef: FieldDefinitionNode) {
const entityName = entityDef.name.value;
const name = fieldDef.name.value;
schemaCodeGeneratorDebug.extend('_generateDerivedFieldGetter')(
`Generating derived field '${name}' getter for Entity '${entityName}'`,
);
const gqlType = fieldDef.type;
schemaCodeGeneratorDebug.extend('_generateDerivedFieldGetter')(
"Derived field's type: %M",
gqlType,
);
const returnType = this._returnTypeForDervied(gqlType);
schemaCodeGeneratorDebug.extend('_generateDerivedFieldGetter')(
"Derived field's return type: %M",
returnType,
);
const obj = this.schema.ast.definitions.find(def => {
if (def.kind === 'ObjectTypeDefinition') {
const defobj = def as ObjectTypeDefinitionNode;
Expand All @@ -339,6 +370,19 @@ export default class SchemaCodeGenerator {
return false;
}) as ObjectTypeDefinitionNode;

if (!obj) {
schemaCodeGeneratorDebug.extend('_generateDerivedFieldGetter')(
"Could not find object type definition for derived field's base type: %M",
obj,
);
return null;
}

schemaCodeGeneratorDebug.extend('_generateDerivedFieldGetter')(
"Found object type definition for derived field's base type: %M",
obj,
);

const idf = IdField.fromTypeDef(obj);
const idIsBytes = idf.typeName() == 'Bytes';
const toValueString = idIsBytes ? '.toBytes().toHexString()' : '.toString()';
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ debugFactory.formatters.L = immutableList => {
};

debugFactory.formatters.M = immutableMap => {
if (immutableMap.toMap != null) {
if (immutableMap?.toMap != null) {
return JSON.stringify(immutableMap.toMap());
}

if (typeof immutableMap.toJS === 'function') {
if (typeof immutableMap?.toJS === 'function') {
return JSON.stringify(immutableMap.toJS());
}

Expand Down
14 changes: 13 additions & 1 deletion packages/cli/src/type-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ import DataSourceTemplateCodeGenerator from './codegen/template';
import { GENERATED_FILE_NOTE } from './codegen/typescript';
import { displayPath } from './command-helpers/fs';
import { Spinner, step, withSpinner } from './command-helpers/spinner';
import debug from './debug';
import { applyMigrations } from './migrations';
import Protocol from './protocols';
import Schema from './schema';
import Subgraph from './subgraph';
import Watcher from './watcher';

const typeGenDebug = debug('graph-cli:type-generator');

export interface TypeGeneratorOptions {
sourceDir?: string;
subgraphManifest: string;
Expand Down Expand Up @@ -52,6 +55,9 @@ export default class TypeGenerator {

async generateTypes() {
if (this.protocol.name === 'substreams') {
typeGenDebug.extend('generateTypes')(
'Subgraph uses a substream datasource. Skipping code generation.',
);
toolbox.print.success(
'Subgraph uses a substream datasource. Codegeneration is not required.',
);
Expand All @@ -70,10 +76,12 @@ export default class TypeGenerator {

// Not all protocols support/have ABIs.
if (this.protocol.hasABIs()) {
typeGenDebug.extend('generateTypes')('Generating types for ABIs');
const abis = await this.protocolTypeGenerator.loadABIs(subgraph);
await this.protocolTypeGenerator.generateTypesForABIs(abis);
}

typeGenDebug.extend('generateTypes')('Generating types for templates');
await this.generateTypesForDataSourceTemplates(subgraph);

// Not all protocols support/have ABIs.
Expand All @@ -83,6 +91,7 @@ export default class TypeGenerator {
}

const schema = await this.loadSchema(subgraph);
typeGenDebug.extend('generateTypes')('Generating types for schema');
await this.generateTypesForSchema(schema);

toolbox.print.success('\nTypes generated successfully\n');
Expand Down Expand Up @@ -113,7 +122,10 @@ export default class TypeGenerator {
}

async loadSubgraph({ quiet } = { quiet: false }) {
const subgraphLoadOptions = { protocol: this.protocol, skipValidation: false };
const subgraphLoadOptions = {
protocol: this.protocol,
skipValidation: false,
};

if (quiet) {
return (
Expand Down
Loading