fromAnnotation static method

Future<GraphQLNeo4Model> fromAnnotation(
  1. Element element,
  2. ConstantReader annotation
)

Implementation

static Future<GraphQLNeo4Model> fromAnnotation(
    Element element, ConstantReader annotation) async {
  final model =
      await SunnyGraphQLModel.initFromAnnotation(element, annotation);
  Map<RegExp, String> typeNameMappers = {};
  Map<RegExp, String> fieldNameMappers = {};
  String moduleName = 'models';
  List<String> externalFragments = [];
  if (annotation.read('fragments').isList) {
    externalFragments = [
      for (var v in annotation.read('fragments').listValue)
        v.toStringValue()!,
    ];
  }
  if (annotation.read('moduleName').isString) {
    moduleName = annotation.read('moduleName').stringValue;
  }
  if (!annotation.read('typeNameMappers').isNull) {
    typeNameMappers = annotation.read('typeNameMappers').mapValue.map(
        (key, value) =>
            MapEntry(RegExp(key!.toStringValue()!), value!.toStringValue()!));
  }
  if (!annotation.read('fieldNameMappers').isNull) {
    fieldNameMappers = annotation.read('fieldNameMappers').mapValue.map(
        (key, value) =>
            MapEntry(RegExp(key!.toStringValue()!), value!.toStringValue()!));
  }
  final neo4jModel = GraphQLNeo4Model(
    moduleName,
    model,
    fieldNameMappers: fieldNameMappers,
    typeNameMappers: typeNameMappers,
  );

  model.doc.definitions
      .whereType<ObjectTypeDefinitionNode>()
      .where((def) =>
          def.directives.any((element) => element.name.value == 'entity'))
      .forEach((def) {
    var name = def.name.value;
    var entity = GraphQLEntity(name);
    var ops = {...GraphOpType.values};
    final exclude = def.directives.getDirective('exclude');
    if (exclude != null) {
      if (exclude.arguments.isEmpty) {
        ops.removeAll(GraphOpType.values);
      } else {
        exclude.arguments.forEach((arg) {});
      }
    }

    final mixinNames = def.directives.mixinNames;
    final serviceMixinNames = def.directives.mixinApiNames;
    final serviceInterfaceNames = def.directives.interfaceApiNames;
    entity.mixins.addAll(mixinNames);
    entity.serviceMixins.addAll(serviceMixinNames);
    entity.serviceInterfaces.addAll(serviceInterfaceNames);

    entity.ops.addAll(ops);
    entity.fields.addAll(def.fields.map((fieldDefinition) {
      // final relationship = fieldDefinition.directives.where((directive) => directive.name.value == 'relationship').firstOr();

      final fieldDef = FieldDefinition.ofField(
        model,
        fieldDefinition,
        entityName: def.name.value,
      );

      final relation = fieldDef.relationship;
      if (relation?.eagerPrefix != null) {
        // Create union
      }

      // Look for an alternate selection
      var selectionFragmentPrefix = fieldDefinition.directives
              .getDirectiveValue("partial", "prefix")
              ?.stringValueOrNull ??
          '';

      if (relation?.propsType != null) {
        // neo4j graphql convention
        final relationFieldType =
            '${entity.name}${capitalize(fieldDefinition.name.value)}Connection';
        final mappedFieldType = fieldDefinition.type.toRawType();
        final propsType = model.doc
                .findByName<ast.InterfaceTypeDefinitionNode>(
                    relation!.propsType!) ??
            (throw "Cannot find relationship interface ${relation.propsType}");
        final relationType = fieldDef.joinRecordType ?? relation.propsType;
        final selectionFragment =
            '${fieldDefinition.type.toRawType()}${selectionFragmentPrefix}Fragment';
        model.fragments['${relationType}Fragment'] =
            ast.FragmentDefinitionNode(
                name: "${relationType}Fragment".toNameNode(),
                typeCondition: ast.TypeConditionNode(
                    on: relationFieldType.toNamedType()),
                selectionSet: ast.SelectionSetNode(selections: [
                  FieldNode(
                    name: "edges".toNameNode(),
                    selectionSet: ast.SelectionSetNode(selections: [
                      ast.FieldNode(
                        name: 'node'.toNameNode(),
                        selectionSet: ast.SelectionSetNode(
                          selections: [
                            ast.FragmentSpreadNode(
                                name: selectionFragment.toNameNode()),
                          ],
                        ),
                      ),
                      ...propsType.fields
                          .map((f) => ast.FieldNode(name: f.name)),
                    ]),
                  ),
                ]));
        if (!fieldDefinition.isLazy) {
          model.fragmentDepends
              .add('${entity.name}Fragment', '${relationType}Fragment');
        }
        model.fragmentDepends.add('${relationType}Fragment',
            '${fieldDefinition.type.toRawType()}Fragment');
        // We need to add a custom join type.
        final name =
            buildJoinRecordName(def.name.value, fieldDefinition.name.value);
        // Use the more lax Entity for the T2 type parameter. This makes it much easier to dynamically set lists without getting
        // casting errors.  The lists can be pretty easily cast on the way out.
        model.typedefs[name] =
            'JoinRecord<$mappedFieldType, Entity, ${propsType.name.value}>';
        model.joinRecords.add(propsType.name.value);

        // model.addDefinition(
        //   ast.ObjectTypeDefinitionNode(
        //     name: NameNode(value: relation.propsType!),
        //     directives: [
        //       directiveNode('joinRecord', args: {
        //         'nodeType': mappedFieldType,
        //       }),
        //     ],
        //     // interfaces: [
        //     //   NamedTypeNode(
        //     //     name: NameNode(value: '${relation.propsType!}<${fieldDefinition.type.toRawType()}>'),
        //     //   ),
        //     //   NamedTypeNode(name: NameNode(value: "JoinTypeMixin<${fieldDefinition.type.toRawType()}>")),
        //     // ],
        //     fields: [
        //       FieldDefinitionNode(
        //         name: NameNode(value: "node"),
        //         type: NamedTypeNode(
        //             name: NameNode(
        //           value: fieldDefinition.type.toRawType(),
        //         )),
        //       ),
        //       ...propsType.fields.where((element) {
        //         return !(['timestamp', 'exclude', 'ignore']
        //             .any((directiveName) => element.directives.hasDirective(directiveName)));
        //       }),
        //     ],
        //   ),
        // );
      }

      return fieldDef;
    }));

    var mappedFields = def.fields.map((fieldDefinition) {
      var entityField = entity.fields.firstWhere(
          (element) => element.name == fieldDefinition.name.value);

      var propsName = entityField.relationship?.propsType;
      if (propsName != null) {
        final mappedType = entityField.isList
            ? ast.ListTypeNode(
                type:
                    entityField.joinRecordType!.toNamedType(isNonNull: true),
                isNonNull: fieldDefinition.type.isNonNull,
              )
            : entityField.joinRecordType!.toNamedType();
        return ast.FieldDefinitionNode(
          name: entityField.name.toNameNode(),
          type: mappedType,
          directives: fieldDefinition.directives,
          args: fieldDefinition.args,
          description: fieldDefinition.description,
          span: fieldDefinition.span,
        );
      } else {
        return fieldDefinition;
      }
    }).toList();

    def.fields.clear();
    def.fields.addAll(mappedFields);
    neo4jModel._addEntity(entity);
    neo4jModel.entities[entity.name] = entity;
  });

  // Go back over the models, and add eager join selections to the fragment definitions.  These eager joins cannot be added to
  // the source model since they don't technically exist until runtime.
  neo4jModel.entities.values.forEach((entity) {
    var relatedFields = entity.fields
        .where((field) => field.isRelationship && field.isEager)
        .toList();
    if (relatedFields.isNotEmpty) {
      // final frag = neo4jModel.model.fragments['${entity.name}Fragment'];
      // if (frag == null) {
      //   _log.warning("MISSING FRAGMENT for ${entity.name}, which has eager joinType fields");
      // } else {
      for (var relatedField in relatedFields) {
        // Look for an alternate selection

        final frag = neo4jModel.model.fragments['${entity.name}Fragment'];
        if (frag == null) {
          _log.warning(
              "MISSING FRAGMENT for ${entity.name}, which has eager joinType fields");
        } else {
          frag.selectionSet.selections.removeWhere((element) =>
              element is ast.FieldNode &&
              element.name.value == relatedField.name);
          late String fieldNodeName;
          late String fieldFragmentName;

          if (relatedField.joinRecordType != null) {
            fieldNodeName = '${relatedField.name}Connection';
            fieldFragmentName =
                '${entity.name}${capitalize(singularize(relatedField.name))}';
          } else {
            fieldNodeName = '${relatedField.name}';
            fieldFragmentName =
                '${relatedField.eagerPrefix ?? ''}${relatedField.typeNode.toGQLType(withNullability: false)}';
          }
          frag.selectionSet.selections.add(
            fieldNode(
              fieldNodeName,
              selectionSet: ['...${fieldFragmentName}Fragment'],
            ),
          );
        }
      }
    }
  });

  neo4jModel.externalFragments.addAll(externalFragments);
  return neo4jModel;
}