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

Add Core module to schema transformation implicitly #9

Merged
merged 2 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/cool-panthers-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@frontside/hydraphql": minor
---

Change a way of declaring GraphQL schema mappers
5 changes: 5 additions & 0 deletions .changeset/healthy-pants-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@frontside/hydraphql": patch
---

#4 Add `Core` module to schema transformation implicitly
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
"graphql-relay": "^0.10.0",
"lodash": "^4.17.21",
"pascal-case": "^3.1.2",
"reflect-metadata": "^0.1.13",
"tslib": "^2.6.2"
},
"devDependencies": {
Expand Down
7 changes: 3 additions & 4 deletions src/__testUtils__/createApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ import { envelop, useEngine } from "@envelop/core";
import { useDataLoader } from "@envelop/dataloader";
import { useGraphQLModules } from "@envelop/graphql-modules";
import { createGraphQLApp } from "../createGraphQLApp.js";
import type { GraphQLContext } from "../types.js";
import { CoreSync } from "../core/core.js";
import type { GraphQLContext, GraphQLModule } from "../types.js";

export async function createGraphQLAPI(
TestModule: Module,
TestModule: GraphQLModule | Module,
loader: (
context: Record<string, unknown> & GraphQLContext,
) => DataLoader<string, unknown>,
generateOpaqueTypes?: boolean,
) {
const application = await createGraphQLApp({
modules: [CoreSync(), TestModule],
modules: [TestModule],
generateOpaqueTypes,
});

Expand Down
27 changes: 11 additions & 16 deletions src/core/core.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { loadFiles, loadFilesSync } from "@graphql-tools/load-files";
import { loadFilesSync } from "@graphql-tools/load-files";
import { createModule } from "graphql-modules";
import type { TypeDefs } from "graphql-modules";
import { createDirectiveMapperProvider } from "../mapperProvider.js";
import type { ResolverContext } from "../types.js";
import type { GraphQLModule, ResolverContext } from "../types.js";
import { fieldDirectiveMapper } from "./fieldDirectiveMapper.js";
import { resolveDirectiveMapper } from "./resolveDirectiveMapper.js";
import { coreSchemaPath } from "./coreSchemaPath.cjs";

/** @public */
export const CoreSync = (typeDefs: TypeDefs = loadFilesSync(coreSchemaPath)) =>
createModule({
export const CoreSync = (): GraphQLModule => ({
mappers: {
field: fieldDirectiveMapper,
resolve: resolveDirectiveMapper,
},
module: createModule({
id: "core",
typeDefs,
typeDefs: loadFilesSync(coreSchemaPath),
resolvers: {
Node: {
id: async (
Expand All @@ -30,11 +31,5 @@ export const CoreSync = (typeDefs: TypeDefs = loadFilesSync(coreSchemaPath)) =>
ids.map((id) => ({ id })),
},
},
providers: [
createDirectiveMapperProvider("field", fieldDirectiveMapper),
createDirectiveMapperProvider("resolve", resolveDirectiveMapper),
],
});

/** @public */
export const Core = async () => CoreSync(await loadFiles(coreSchemaPath));
}),
});
7 changes: 3 additions & 4 deletions src/createGraphQLApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import { createApplication } from "graphql-modules";
import type { Module } from "graphql-modules";
import { transformSchema } from "./transformSchema.js";
import { loadSchema } from "./loadSchema.js";
import { GraphQLModule } from "./types.js";

/** @public */
export interface createGraphQLAppOptions {
schema?: string | string[];
modules?: Module[];
modules?: (GraphQLModule | Module)[];
generateOpaqueTypes?: boolean;
}

/** @public */
export async function createGraphQLApp(options: createGraphQLAppOptions) {
const modules = options.modules ?? [];
if (options.schema) {
Expand All @@ -21,6 +20,6 @@ export async function createGraphQLApp(options: createGraphQLAppOptions) {
});
return createApplication({
schemaBuilder: () => schema,
modules,
modules: modules.map((m) => ("id" in m ? m : m.module)),
});
}
1 change: 0 additions & 1 deletion src/createLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { GraphQLError } from "graphql";
import type { BatchLoadFn, NodeQuery, GraphQLContext } from "./types.js";
import { decodeId } from "./helpers.js";

/** @public */
export const createLoader = <TContext extends Record<string, unknown>>(
loaders: Record<string, BatchLoadFn<TContext & GraphQLContext>>,
options?: Options<string, unknown>,
Expand Down
3 changes: 0 additions & 3 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@ import {
import type { GraphQLNamedType, GraphQLOutputType } from "graphql";
import type { NodeId, NodeQuery } from "./types.js";

/** @public */
export function encodeId({ source, typename, query }: NodeId): string {
return `${typename}@${source}@${JSON.stringify(query ?? {})}`;
}

/** @public */
export function decodeId(id: string): NodeId {
const [typename, source, ...query] = id.split("@");
const parsedQuery: unknown = JSON.parse(query.join("@"));
Expand All @@ -29,7 +27,6 @@ export function decodeId(id: string): NodeId {
return { typename, source, query: parsedQuery };
}

/** @public */
export function unboxNamedType(type: GraphQLOutputType): GraphQLNamedType {
if (isNonNullType(type)) {
return unboxNamedType(type.ofType);
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ export * from "./createGraphQLApp.js";
export * from "./core/core.js";
export * from "./helpers.js";
export { transformSchema } from "./transformSchema.js";
export { createDirectiveMapperProvider } from "./mapperProvider.js";
export type {
GraphQLContext,
ResolverContext,
Expand Down
2 changes: 0 additions & 2 deletions src/mapDirectives.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { createModule, gql } from "graphql-modules";
import { createGraphQLAPI } from "./__testUtils__/createApi.js";
import { transformSchema } from "./transformSchema.js";
import type { NodeId } from "./types.js";
import { CoreSync } from "./core/core.js";
import { decodeId, encodeId } from "./helpers.js";
import { createLoader } from "./createLoader.js";
import { describe, test } from "node:test";
Expand All @@ -15,7 +14,6 @@ describe("mapDirectives", () => {
const transform = (source: DocumentNode, generateOpaqueTypes?: boolean) =>
transformSchema(
[
CoreSync(),
createModule({
id: "mapDirectives",
typeDefs: source,
Expand Down
21 changes: 0 additions & 21 deletions src/mapperProvider.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { printSchemaWithDirectives } from "@graphql-tools/utils";
import { transformSchema } from "./transformSchema.js";
import { CoreSync } from "./core/core.js";

export const schema = printSchemaWithDirectives(transformSchema([CoreSync()]));
export const schema = printSchemaWithDirectives(transformSchema());
27 changes: 12 additions & 15 deletions src/transformSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,32 @@ import { makeExecutableSchema } from "@graphql-tools/schema";
import { validateSchema } from "graphql";
import type { Module, Resolvers } from "graphql-modules";
import { mapDirectives } from "./mapDirectives.js";
import type { FieldDirectiveMapper } from "./types.js";
import { toPrivateProp } from "./mapperProvider.js";
import type { FieldDirectiveMapper, GraphQLModule } from "./types.js";
import { CoreSync } from "./index.js";

/** @public */
export function transformSchema(
modules: Module[] = [],
additionalModules: (GraphQLModule | Module)[] = [],
{ generateOpaqueTypes }: { generateOpaqueTypes?: boolean } = {},
) {
const modules = [CoreSync(), ...additionalModules];
const directiveMappers: Record<string, FieldDirectiveMapper> = {};
const typeDefs: DocumentNode[] = modules.flatMap((gqlModule) => {
const typeDefs: DocumentNode[] = modules.flatMap((m) => {
const { module: gqlModule, mappers = {} } = "id" in m ? { module: m } : m;
const documents = gqlModule.typeDefs;
documents.forEach((document) => {
document.definitions.forEach((definition) => {
if (definition.kind !== Kind.DIRECTIVE_DEFINITION) return;
const directiveName = definition.name.value;
const provider = gqlModule.providers?.find(
(p) => toPrivateProp(directiveName) in p,
);
if (provider)
directiveMappers[directiveName] = (
provider as unknown as Record<string, FieldDirectiveMapper>
)[toPrivateProp(directiveName)];
const mapper = mappers?.[directiveName];
if (mapper) directiveMappers[directiveName] = mapper;
});
});
return documents;
});
const resolvers: Resolvers = modules.flatMap(
({ config }) => config.resolvers ?? [],
);
const resolvers: Resolvers = modules.flatMap((m) => {
const { config } = "id" in m ? m : m.module;
return config.resolvers ?? [];
});

const schema = mapDirectives(makeExecutableSchema({ typeDefs, resolvers }), {
directiveMappers,
Expand Down
15 changes: 6 additions & 9 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,39 @@ import type {
GraphQLNamedType,
GraphQLObjectType,
} from "graphql";
import type { Application } from "graphql-modules";
import type { Application, Module } from "graphql-modules";

/** @public */
export interface NodeQuery {
ref?: string;
args?: Record<string, unknown>;
}

/** @public */
export interface NodeId {
source: string;
typename: string;
query?: NodeQuery;
}

/** @public */
export type BatchLoadFn<Context extends GraphQLContext> = (
keys: readonly (NodeQuery | undefined)[],
context: Context,
) => PromiseLike<ArrayLike<unknown>>;

/** @public */
export interface GraphQLContext {
application: Application;
}

/** @public */
export interface ResolverContext extends GraphQLContext {
loader: DataLoader<string, unknown>;
}

/** @public */
export type OmitFirst<T extends unknown[]> = T extends [
x: unknown,
...args: infer R,
]
? R
: [];

/** @public */
export interface DirectiveMapperAPI {
getImplementingTypes: (interfaceName: string) => GraphQLObjectType[];
getDirective: (
Expand All @@ -53,7 +46,6 @@ export interface DirectiveMapperAPI {
typeMap: Partial<Record<string, GraphQLNamedType>>;
}

/** @public */
export type FieldDirectiveMapper = (
fieldName: string,
field: GraphQLFieldConfig<
Expand All @@ -69,3 +61,8 @@ export interface NamedType {
implements: string | null;
discriminates: Set<string>;
}

export interface GraphQLModule {
mappers?: Record<string, FieldDirectiveMapper>;
module: Module;
}
8 changes: 0 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1243,7 +1243,6 @@ __metadata:
pascal-case: ^3.1.2
pinst: ^3.0.0
prettier: ^3.0.3
reflect-metadata: ^0.1.13
tslib: ^2.6.2
tsx: ^3.12.7
typescript: ^5.2.2
Expand Down Expand Up @@ -6559,13 +6558,6 @@ __metadata:
languageName: node
linkType: hard

"reflect-metadata@npm:^0.1.13":
version: 0.1.13
resolution: "reflect-metadata@npm:0.1.13"
checksum: 798d379a7b6f6455501145419505c97dd11cbc23857a386add2b9ef15963ccf15a48d9d15507afe01d4cd74116df8a213247200bac00320bd7c11ddeaa5e8fb4
languageName: node
linkType: hard

"regenerator-runtime@npm:^0.14.0":
version: 0.14.0
resolution: "regenerator-runtime@npm:0.14.0"
Expand Down
Loading