From 2db3719b8180dc2eaaf7c2ca43105f5a48392c11 Mon Sep 17 00:00:00 2001 From: Gustavo Inacio Date: Thu, 16 Mar 2023 15:06:55 -0300 Subject: [PATCH] cli: add derived loader --- packages/cli/src/codegen/schema.test.ts | 10 +-- packages/cli/src/codegen/schema.ts | 109 +++++++++++++++++++++++- packages/cli/src/type-generator.ts | 1 + packages/ts/index.ts | 1 + 4 files changed, 113 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/codegen/schema.test.ts b/packages/cli/src/codegen/schema.test.ts index 3335db31d..2bedf2540 100644 --- a/packages/cli/src/codegen/schema.test.ts +++ b/packages/cli/src/codegen/schema.test.ts @@ -30,6 +30,7 @@ const testEntity = (generatedTypes: any[], expectedEntity: any) => { expect(members).toStrictEqual(expectedEntity.members); for (const expectedMethod of expectedEntity.methods) { + const method = methods.find((method: any) => method.name === expectedMethod.name); expectedMethod.static @@ -231,14 +232,9 @@ describe('Schema code generator', () => { { name: 'get wallets', params: [], - returnType: new NullableType(new ArrayType(new NamedType('string'))), + returnType: new NamedType('WalletLoader'), body: ` - let value = this.get('wallets') - if (!value || value.kind == ValueKind.NULL) { - return null - } else { - return value.toStringArray() - } + return new WalletLoader("Account", this.get('id')!.toString(), "wallets") `, }, { diff --git a/packages/cli/src/codegen/schema.ts b/packages/cli/src/codegen/schema.ts index 3625dd5eb..f344f5c84 100644 --- a/packages/cli/src/codegen/schema.ts +++ b/packages/cli/src/codegen/schema.ts @@ -87,6 +87,16 @@ export default class SchemaCodeGenerator { .map((def: any) => this._generateEntityType(def)); } + generateDerivedLoaders() { + const fields = this.schema.ast.get('definitions') + .filter((def: any) => this._isEntityTypeDefinition(def)) + .map((def: any) => def.get('fields')) + .flatten(1) + .filter((def: any) => this._isDerivedField(def)) + .map((def: any) => this._getTypeNameForField(def)); + + return [...new Set(fields)].map((typeName: any) => this._generateDerivedLoader(typeName)); + } _isEntityTypeDefinition(def: immutable.Map) { return ( def.get('kind') === 'ObjectTypeDefinition' && @@ -96,6 +106,10 @@ export default class SchemaCodeGenerator { ); } + _isDerivedField(field: any): boolean { + return field.get('directives') + .find((directive: any) => directive.getIn(['name', 'value']) === 'derivedFrom') !== undefined; + } _isInterfaceDefinition(def: immutable.Map) { return def.get('kind') === 'InterfaceTypeDefinition'; } @@ -124,6 +138,39 @@ export default class SchemaCodeGenerator { return klass; } + _generateDerivedLoader(typeName: string): any { + // Loader + const klass = tsCodegen.klass(`${typeName}Loader`, { export: true, extends: 'Entity' }); + + klass.addMember(tsCodegen.klassMember("_entity", "string")) + klass.addMember(tsCodegen.klassMember("_field", "string")) + klass.addMember(tsCodegen.klassMember("_id", "string")) + // Generate and add a constructor + klass.addMethod(tsCodegen.method('constructor', [tsCodegen.param('entity', 'string'), tsCodegen.param('id', 'string'), tsCodegen.param('field', 'string')], undefined, ` + super(); + this._entity = entity; + this._id = id; + this._field = field; +`)); + + // Generate load() method for the Loader + klass.addMethod(tsCodegen.method('load', [], `${typeName}[]`, ` + let value = store.loadRelated(this._entity, this._id, this._field); + return changetype<${typeName}[]>(value); + `)) + + return klass; + } + _getTypeNameForField(gqlType: any): any { + if (gqlType.get('kind') === 'NonNullType') { + return this._getTypeNameForField(gqlType.get('type')) + } else if (gqlType.get('kind') === 'ListType') { + return this._getTypeNameForField(gqlType.get('type')) + } else { + return gqlType.getIn(['name', 'value']) + } + } + _generateConstructor(_entityName: string, fields: immutable.List) { const idField = IdField.fromFields(fields); return tsCodegen.method( @@ -172,7 +219,10 @@ export default class SchemaCodeGenerator { ]); } - _generateEntityFieldGetter(_entityDef: any, fieldDef: immutable.Map) { + _generateEntityFieldGetter(entityDef: any, fieldDef: immutable.Map) { + if (this._isDerivedField(fieldDef)) { + return this._generateDerivedFieldGetter(entityDef, fieldDef); + } const name = fieldDef.getIn(['name', 'value']); const gqlType = fieldDef.get('type'); const fieldValueType = this._valueTypeFromGraphQl(gqlType); @@ -196,8 +246,64 @@ export default class SchemaCodeGenerator { `, ); } + _generateDerivedFieldGetter(entityDef: any, fieldDef: immutable.Map) { + let entityName = entityDef.getIn(['name', 'value']); + let name = fieldDef.getIn(['name', 'value']); + let gqlType = fieldDef.get('type'); + let returnType = this._returnTypeForDervied(gqlType) + return tsCodegen.method( + `get ${name}`, + [], + returnType, + ` + return new ${returnType}('${entityName}', this.get('id')!.toString(), '${name}') + `, + ) + } + + _returnTypeForDervied(gqlType: any): any { + if (gqlType.get('kind') === 'NonNullType') { + return this._returnTypeForDervied(gqlType.get('type')) + } else if (gqlType.get('kind') === 'ListType') { + return this._returnTypeForDervied(gqlType.get('type')) + } else { + const type = tsCodegen.namedType( + gqlType.getIn(['name', 'value']) + 'Loader' + ); + return type; + } + } + + _generatedEntityDerivedFieldGetter(_entityDef: any, fieldDef: immutable.Map) { + 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: any, fieldDef: immutable.Map) { + // We shouldn't have setters for derived fields + // if (this._isDerivedField(fieldDef)) { + // return; + // } const name = fieldDef.getIn(['name', 'value']); const gqlType = fieldDef.get('type'); const fieldValueType = this._valueTypeFromGraphQl(gqlType); @@ -297,3 +403,4 @@ Suggestion: add an '!' to the member type of the List, change from '[${baseType} return nullable && !type.isPrimitive() ? tsCodegen.nullableType(type) : type; } } + diff --git a/packages/cli/src/type-generator.ts b/packages/cli/src/type-generator.ts index 3ad2f4514..92abbb90f 100644 --- a/packages/cli/src/type-generator.ts +++ b/packages/cli/src/type-generator.ts @@ -153,6 +153,7 @@ export default class TypeGenerator { GENERATED_FILE_NOTE, ...codeGenerator.generateModuleImports(), ...codeGenerator.generateTypes(), + ...codeGenerator.generateDerivedLoaders(), ].join('\n'), { parser: 'typescript', diff --git a/packages/ts/index.ts b/packages/ts/index.ts index 8d82b4a34..b2361cbdc 100644 --- a/packages/ts/index.ts +++ b/packages/ts/index.ts @@ -23,6 +23,7 @@ export * from './common/value'; */ export declare namespace store { function get(entity: string, id: string): Entity | null; + function loadRelated(entity: string, id: string, field: string): Array; function set(entity: string, id: string, data: Entity): void; function remove(entity: string, id: string): void; }