Skip to content

Commit

Permalink
Merge pull request #351 from thefrontside/dl/update-hydraphql-0.1.0
Browse files Browse the repository at this point in the history
Update @frontside/hydraphql to 0.1.1
  • Loading branch information
wKich authored Oct 5, 2023
2 parents 8ea30a0 + 5cf89a0 commit 96506ec
Show file tree
Hide file tree
Showing 18 changed files with 448 additions and 202 deletions.
7 changes: 7 additions & 0 deletions .changeset/friendly-yaks-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@frontside/backstage-plugin-graphql-backend-module-catalog': patch
'@frontside/backstage-plugin-graphql-backend-node': patch
'@frontside/backstage-plugin-graphql-backend': patch
---

Update @frontside/hydraphql to 0.1.1
39 changes: 22 additions & 17 deletions plugins/graphql-backend-module-catalog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,51 @@ Some key features are currently missing. These features may change the schema in
1. `viewer` query for retrieving data for the current user.

- [GraphQL modules](#graphql-modules)
- [Catalog module](#catalog-module)
- [Relation module](#relation-module)
- [Backstage Plugins](#backstage-plugins)
- [Experimental Backend System](#experimental-backend-system)
- [Directives API](#directives-api)
- [`@relation` directive](#relation-directive)
- [Catalog Data loader](#catalog-data-loader-advanced)

## GraphQL modules

### Catalog module
This package provides two GraphQL modules:
- `Catalog` module – provides basic Catalog GraphQL types and `@relation` directive
- `Relation` module – provides only `@relation` directive

The `Catalog` module is installed just as any other Backstage Module:
[`@frontside/backstage-plugin-graphql-backend`](../graphql-backend/README.md)
### Backstage Plugins

For the [Backstage plugin system](https://backstage.io/docs/plugins/backend-plugin),
you can learn how to add GraphQL modules by checking out [GraphQL Modules](../graphql-backend/README.md#graphql-modules)
section in `@frontside/backstage-plugin-graphql-backend` package.

This package exports `Catalog` and `Relation` modules

### Experimental Backend System

For the [experimental backend system](https://backstage.io/docs/plugins/experimental-backend),
you can add them as a plugin modules:

- To use `Catalog` GraphQL module
```ts
// packages/backend/src/index.ts
import { graphqlModuleCatalog } from '@frontside/backstage-plugin-graphql-backend-module-catalog';

const backend = createBackend();

// GraphQL
backend.use(graphqlPlugin());
backend.use(graphqlModuleCatalog());
```

### Relation module

If you don't want to use basic Catalog types for some reason, but
still want to use `@relation` directive, you can install `Relation` module

- To use `Relation` GraphQL module
```ts
// packages/backend/src/index.ts
import { graphqlModuleRelationResolver } from '@frontside/backstage-plugin-graphql-backend-module-catalog';

const backend = createBackend();

// GraphQL
backend.use(graphqlPlugin());
backend.use(graphqlModuleRelationResolver());
```

If you don't want to use basic Catalog types for some reason, but
still want to use `@relation` directive, you can install `Relation` module

## Directives API

### `@relation`
Expand Down
2 changes: 1 addition & 1 deletion plugins/graphql-backend-module-catalog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@backstage/catalog-model": "^1.4.1",
"@backstage/plugin-catalog-node": "^1.4.3",
"@frontside/backstage-plugin-graphql-backend": "^0.1.0",
"@frontside/hydraphql": "^0.0.1",
"@frontside/hydraphql": "^0.1.1",
"@graphql-tools/load-files": "^7.0.0",
"@graphql-tools/utils": "^10.0.0",
"dataloader": "^2.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ exports[`graphql-catalog codegen should generate the correct code: graphql 1`] =

directive @discriminationAlias(type: String!, value: String!) repeatable on INTERFACE

directive @field(at: _DirectiveArgument_!, default: _DirectiveArgument_) on FIELD_DEFINITION
directive @field(at: _DirectiveArgument_, default: _DirectiveArgument_) on FIELD_DEFINITION

directive @implements(interface: String!) on INTERFACE | OBJECT

directive @relation(kind: String, name: String, nodeType: String) on FIELD_DEFINITION

directive @resolve(at: _DirectiveArgument_, from: String) on FIELD_DEFINITION
directive @resolve(at: _DirectiveArgument_, from: String, nodeType: String) on FIELD_DEFINITION

interface API implements Entity & Node & Ownable {
annotations: [KeyValuePair!]
Expand Down Expand Up @@ -2436,7 +2436,7 @@ export type DiscriminationAliasDirectiveArgs = {
export type DiscriminationAliasDirectiveResolver<Result, Parent, ContextType = any, Args = DiscriminationAliasDirectiveArgs> = DirectiveResolverFn<Result, Parent, ContextType, Args>;

export type FieldDirectiveArgs = {
at: Scalars['_DirectiveArgument_']['input'];
at?: Maybe<Scalars['_DirectiveArgument_']['input']>;
default?: Maybe<Scalars['_DirectiveArgument_']['input']>;
};

Expand All @@ -2459,6 +2459,7 @@ export type RelationDirectiveResolver<Result, Parent, ContextType = any, Args =
export type ResolveDirectiveArgs = {
at?: Maybe<Scalars['_DirectiveArgument_']['input']>;
from?: Maybe<Scalars['String']['input']>;
nodeType?: Maybe<Scalars['String']['input']>;
};

export type ResolveDirectiveResolver<Result, Parent, ContextType = any, Args = ResolveDirectiveArgs> = DirectiveResolverFn<Result, Parent, ContextType, Args>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { JsonObject } from '@backstage/types';
import {
createGraphQLApp,
GraphQLContext,
CoreSync,
} from '@frontside/hydraphql';

import * as graphql from 'graphql';
Expand All @@ -12,15 +11,15 @@ import { Module } from 'graphql-modules';
import { envelop, useEngine } from '@envelop/core';
import { useDataLoader } from '@envelop/dataloader';
import { useGraphQLModules } from '@envelop/graphql-modules';
import { RelationSync } from '../relation';
import { Relation } from '../relation';

export async function createGraphQLAPI(
TestModule: Module,
loader: (context: GraphQLContext) => DataLoader<any, any>,
generateOpaqueTypes?: boolean,
) {
const application = await createGraphQLApp({
modules: [CoreSync(), RelationSync(), TestModule],
modules: [Relation(), TestModule],
generateOpaqueTypes,
});

Expand Down
25 changes: 9 additions & 16 deletions plugins/graphql-backend-module-catalog/src/catalog/catalog.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { createModule, TypeDefs } from 'graphql-modules';
import { createModule } from 'graphql-modules';
import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json';
import { relationDirectiveMapper } from '../relationDirectiveMapper';
import {
createDirectiveMapperProvider,
GraphQLModule,
encodeId,
} from '@frontside/hydraphql';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { loadFiles, loadFilesSync } from '@graphql-tools/load-files';
import { loadFilesSync } from '@graphql-tools/load-files';
import { resolvePackagePath } from '@backstage/backend-common';
import { CATALOG_SOURCE } from '../constants';

Expand All @@ -16,12 +16,11 @@ const catalogSchemaPath = resolvePackagePath(
);

/** @public */
export const CatalogSync = (
typeDefs: TypeDefs = loadFilesSync(catalogSchemaPath),
) =>
createModule({
export const Catalog = (): GraphQLModule => ({
mappers: { relation: relationDirectiveMapper },
module: createModule({
id: 'catalog',
typeDefs,
typeDefs: loadFilesSync(catalogSchemaPath),
resolvers: {
JSON: GraphQLJSON,
JSONObject: GraphQLJSONObject,
Expand Down Expand Up @@ -55,11 +54,5 @@ export const CatalogSync = (
}),
},
},
providers: [
createDirectiveMapperProvider('relation', relationDirectiveMapper),
],
});

/** @public */
export const Catalog = async () =>
CatalogSync(await loadFiles(catalogSchemaPath));
})
});
25 changes: 9 additions & 16 deletions plugins/graphql-backend-module-catalog/src/relation/relation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TypeDefs, createModule } from 'graphql-modules';
import { createModule } from 'graphql-modules';
import { relationDirectiveMapper } from '../relationDirectiveMapper';
import { createDirectiveMapperProvider } from '@frontside/hydraphql';
import { loadFiles, loadFilesSync } from '@graphql-tools/load-files';
import { GraphQLModule } from '@frontside/hydraphql';
import { loadFilesSync } from '@graphql-tools/load-files';
import { resolvePackagePath } from '@backstage/backend-common';

const relationSchemaPath = resolvePackagePath(
Expand All @@ -10,17 +10,10 @@ const relationSchemaPath = resolvePackagePath(
);

/** @public */
export const RelationSync = (
typeDefs: TypeDefs = loadFilesSync(relationSchemaPath),
) =>
createModule({
export const Relation = (): GraphQLModule => ({
mappers: { relation: relationDirectiveMapper },
module: createModule({
id: 'relation',
typeDefs,
providers: [
createDirectiveMapperProvider('relation', relationDirectiveMapper),
],
});

/** @public */
export const Relation = async () =>
RelationSync(await loadFiles(relationSchemaPath));
typeDefs: loadFilesSync(relationSchemaPath),
})
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
CoreSync,
transformSchema,
encodeId,
decodeId,
Expand All @@ -8,13 +7,12 @@ import DataLoader from 'dataloader';
import { DocumentNode, GraphQLNamedType, printType } from 'graphql';
import { createModule, gql } from 'graphql-modules';
import { createGraphQLAPI } from './__testUtils__';
import { RelationSync } from './relation/relation';
import { Relation } from './relation/relation';

describe('mapRelationDirective', () => {
const transform = (source: DocumentNode) =>
transformSchema([
CoreSync(),
RelationSync(),
Relation(),
createModule({
id: 'mapRelationDirective',
typeDefs: source,
Expand Down
104 changes: 9 additions & 95 deletions plugins/graphql-backend-module-catalog/src/relationDirectiveMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,23 @@ import { connectionFromArray } from 'graphql-relay';
import { Entity, parseEntityRef } from '@backstage/catalog-model';
import {
GraphQLFieldConfig,
GraphQLID,
GraphQLInt,
GraphQLInterfaceType,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLString,
isInputType,
isInterfaceType,
isListType,
isNonNullType,
isObjectType,
isUnionType,
} from 'graphql';
import {
DirectiveMapperAPI,
ResolverContext,
unboxNamedType,
encodeId,
isConnectionType,
createConnectionType,
getNodeTypeForConnection
} from '@frontside/hydraphql';
import { CATALOG_SOURCE } from './constants';

function isConnectionType(type: unknown): type is GraphQLInterfaceType {
return (
(isInterfaceType(type) && type.name === 'Connection') ||
(isNonNullType(type) && isConnectionType(type.ofType))
);
}

function filterEntityRefs(
entity: Entity | undefined,
relationType?: string,
Expand All @@ -49,42 +37,6 @@ function filterEntityRefs(
);
}

function createConnectionType(
nodeType: GraphQLInterfaceType | GraphQLObjectType,
fieldType: GraphQLInterfaceType,
): GraphQLObjectType {
const wrappedEdgeType = fieldType.getFields().edges.type as GraphQLNonNull<
GraphQLList<GraphQLNonNull<GraphQLInterfaceType>>
>;
const edgeType = wrappedEdgeType.ofType.ofType.ofType as GraphQLInterfaceType;

return new GraphQLObjectType({
name: `${nodeType.name}Connection`,
fields: {
...fieldType.toConfig().fields,
edges: {
type: new GraphQLNonNull(
new GraphQLList(
new GraphQLNonNull(
new GraphQLObjectType({
name: `${nodeType.name}Edge`,
fields: {
...edgeType.toConfig().fields,
node: {
type: new GraphQLNonNull(nodeType),
},
},
interfaces: [edgeType],
}),
),
),
),
},
},
interfaces: [fieldType],
});
}

export function relationDirectiveMapper(
_fieldName: string,
field: GraphQLFieldConfig<{ id: string }, ResolverContext>,
Expand All @@ -108,51 +60,13 @@ export function relationDirectiveMapper(

if (isConnectionType(fieldType)) {
if (directive.nodeType) {
const nodeType = api.typeMap[directive.nodeType];

if (!nodeType) {
throw new Error(
`The interface "${directive.nodeType}" is not defined in the schema`,
);
}
if (isInputType(nodeType)) {
throw new Error(
`The interface "${directive.nodeType}" is an input type and can't be used in a Connection`,
);
}
if (isUnionType(nodeType)) {
const resolveType = nodeType.resolveType;
if (resolveType)
throw new Error(
`The "resolveType" function has already been implemented for "${nodeType.name}" union which may lead to undefined behavior`,
);
const iface = (api.typeMap[directive.nodeType] =
new GraphQLInterfaceType({
name: directive.nodeType,
interfaces: [api.typeMap.Node as GraphQLInterfaceType],
fields: { id: { type: new GraphQLNonNull(GraphQLID) } },
resolveType: (...args) =>
(api.typeMap.Node as GraphQLInterfaceType).resolveType?.(...args),
}));
const types = nodeType.getTypes().map(type => type.name);
types.forEach(typeName => {
const type = api.typeMap[typeName];
if (isInterfaceType(type)) {
api.typeMap[typeName] = new GraphQLInterfaceType({
...type.toConfig(),
interfaces: [...type.getInterfaces(), iface],
});
}
if (isObjectType(type)) {
api.typeMap[typeName] = new GraphQLObjectType({
...type.toConfig(),
interfaces: [...type.getInterfaces(), iface],
});
}
});
const nodeType = getNodeTypeForConnection(
directive.nodeType,
(name) => api.typeMap[name],
(name, type) => (api.typeMap[name] = type),
);

field.type = createConnectionType(iface, fieldType);
} else {
if (nodeType) {
field.type = createConnectionType(nodeType, fieldType);
}
} else {
Expand Down
6 changes: 3 additions & 3 deletions plugins/graphql-backend-module-catalog/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CoreSync, transformSchema } from '@frontside/hydraphql';
import { transformSchema } from '@frontside/hydraphql';
import { printSchemaWithDirectives } from '@graphql-tools/utils';
import { CatalogSync } from './catalog';
import { Catalog } from './catalog';

export const schema = printSchemaWithDirectives(
transformSchema([CoreSync(), CatalogSync()]),
transformSchema([Catalog()]),
);
Loading

0 comments on commit 96506ec

Please sign in to comment.