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

feat(core,yoga,apollo): subscriptions #21

Merged
merged 9 commits into from
Nov 21, 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
2 changes: 1 addition & 1 deletion .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ jobs:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}

- name: Lint pull request title
run: echo $PR_TITLE | yarn commitlint --verbose
run: echo $PR_TITLE | pnpm commitlint --verbose
env:
PR_TITLE: ${{ github.event.pull_request.title }}
44 changes: 23 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,31 +51,33 @@
},
"dependencies": {
"@apollo/server": "4.9.3",
"@deepkit/app": "1.0.1-alpha.105",
"@deepkit/broker": "1.0.1-alpha.105",
"@deepkit/bson": "1.0.1-alpha.105",
"@deepkit/core": "1.0.1-alpha.105",
"@deepkit/core-rxjs": "1.0.1-alpha.105",
"@deepkit/event": "1.0.1-alpha.105",
"@deepkit/filesystem": "1.0.1-alpha.105",
"@deepkit/framework": "1.0.1-alpha.106",
"@deepkit/http": "1.0.1-alpha.105",
"@deepkit/injector": "1.0.1-alpha.105",
"@deepkit/logger": "1.0.1-alpha.105",
"@deepkit/orm": "~1.0.1-alpha.105",
"@deepkit/rpc": "~1.0.1-alpha.105",
"@deepkit/rpc-tcp": "1.0.1-alpha.105",
"@deepkit/sql": "1.0.1-alpha.105",
"@deepkit/stopwatch": "1.0.1-alpha.105",
"@deepkit/template": "1.0.1-alpha.105",
"@deepkit/type": "1.0.1-alpha.105",
"@deepkit/type-compiler": "1.0.1-alpha.105",
"@deepkit/workflow": "1.0.1-alpha.105",
"@deepkit/app": "1.0.1-alpha.108",
"@deepkit/broker": "1.0.1-alpha.108",
"@deepkit/bson": "1.0.1-alpha.108",
"@deepkit/core": "1.0.1-alpha.108",
"@deepkit/core-rxjs": "1.0.1-alpha.108",
"@deepkit/event": "1.0.1-alpha.108",
"@deepkit/filesystem": "1.0.1-alpha.108",
"@deepkit/framework": "1.0.1-alpha.108",
"@deepkit/http": "1.0.1-alpha.108",
"@deepkit/injector": "1.0.1-alpha.108",
"@deepkit/logger": "1.0.1-alpha.108",
"@deepkit/orm": "~1.0.1-alpha.108",
"@deepkit/rpc": "~1.0.1-alpha.108",
"@deepkit/rpc-tcp": "1.0.1-alpha.108",
"@deepkit/sql": "1.0.1-alpha.108",
"@deepkit/stopwatch": "1.0.1-alpha.108",
"@deepkit/template": "1.0.1-alpha.108",
"@deepkit/type": "1.0.1-alpha.108",
"@deepkit/type-compiler": "1.0.1-alpha.108",
"@deepkit/workflow": "1.0.1-alpha.108",
"@graphql-tools/utils": "^10.0.8",
"graphql": "^16.8.0",
"graphql-request": "^6.1.0",
"graphql-scalars": "^1.22.2",
"graphql-yoga": "^5.0.0",
"radash": "^11.0.0",
"rxjs": "^7.8.1",
"tslib": "2.6.2"
},
"packageManager": "[email protected]",
Expand All @@ -88,7 +90,7 @@
},
"pnpm": {
"patchedDependencies": {
"@deepkit/[email protected]": "patches/@[email protected].patch"
"[email protected]": "patches/[email protected].patch"
}
}
}
6 changes: 3 additions & 3 deletions packages/apollo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
},
"peerDependencies": {
"@deepkit-graphql/core": "*",
"@deepkit/app": "^1.0.1-alpha.105",
"@deepkit/http": "^1.0.1-alpha.105",
"@deepkit/framework": "^1.0.1-alpha.106",
"@deepkit/app": "^1.0.1-alpha.108",
"@deepkit/http": "^1.0.1-alpha.108",
"@deepkit/framework": "^1.0.1-alpha.108",
"graphql": "^16.8.0"
},
"publishConfig": {
Expand Down
11 changes: 7 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
},
"peerDependencies": {
"graphql": "^16.8.0",
"@deepkit/core": "^1.0.1-alpha.105",
"@deepkit/http": "^1.0.1-alpha.105",
"@deepkit/type": "^1.0.1-alpha.105",
"@deepkit/injector": "^1.0.1-alpha.105"
"@graphql-tools/utils": "^10.0.8",
"@deepkit/core": "^1.0.1-alpha.108",
"@deepkit/http": "^1.0.1-alpha.108",
"@deepkit/type": "^1.0.1-alpha.108",
"@deepkit/broker": "^1.0.1-alpha.108",
"rxjs": "^7.8.1",
"@deepkit/injector": "^1.0.1-alpha.108"
},
"publishConfig": {
"access": "public"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ exports[`mutation args validation 1`] = `
},
"errors": [
[GraphQLError: Validation error:
data.username(minLength): Min length is 6],
data.username(minLength): Min length is 6 caused by value "Test"],
],
}
`;
Expand Down
42 changes: 42 additions & 0 deletions packages/core/src/lib/decorators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,48 @@ test('mutation middleware is invoked', async () => {
expect(testResolverSpy).toHaveBeenCalled();
});

test('subscription middleware is invoked', async () => {
class TestMiddleware implements GraphQLMiddleware {
execute(context: Context<unknown>, next: () => void): void {
next();
}
}

@graphql.resolver()
class TestResolver {
@graphql.subscription().middleware(TestMiddleware)
async *create(): AsyncGenerator<boolean> {
yield true;
}
}

const injectorContext = new InjectorContext(
new InjectorModule([TestResolver, TestMiddleware]),
);

const testResolver = injectorContext.get(TestResolver);

const testResolverSpy = jest.spyOn(testResolver, 'create');

const testMiddleware = injectorContext.get(TestMiddleware);

const testMiddlewareExecuteSpy = jest.spyOn(testMiddleware, 'execute');

const resolvers = new Resolvers([testResolver]);

const schema = buildSchema(resolvers, injectorContext);

await executeGraphQL({
schema,
contextValue: {},
source: `subscription { create }`,
});

expect(testMiddlewareExecuteSpy).toHaveBeenCalled();

expect(testResolverSpy).toHaveBeenCalled();
});

test('mutation', async () => {
interface User {
readonly id: integer & PositiveNoZero;
Expand Down
53 changes: 37 additions & 16 deletions packages/core/src/lib/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '@deepkit/type';

import { requireTypeName } from './utils';
import { Instance, InternalMiddleware } from './types';
import { InternalMiddleware } from './types';

export const typeResolvers = new Map<string, ClassType>();

Expand All @@ -33,6 +33,7 @@ export class GraphQLClassMetadata {
readonly mutations = new Map<string, GraphQLPropertyMetadata>();
readonly queries = new Map<string, GraphQLPropertyMetadata>();
readonly resolveFields = new Map<string, GraphQLPropertyMetadata>();
readonly subscriptions = new Map<string, GraphQLPropertyMetadata>();
readonly checks = new Set<(decorator: GraphQLClassDecorator) => void>();
}

Expand Down Expand Up @@ -70,16 +71,20 @@ export class GraphQLClassDecorator {
this.t.checks.forEach(check => check(this));
}

addMutation(property: string, mutation: GraphQLPropertyMetadata) {
this.t.mutations.set(property, mutation);
addMutation(property: string, metadata: GraphQLPropertyMetadata) {
this.t.mutations.set(property, metadata);
}

addQuery(property: string, query: GraphQLPropertyMetadata) {
this.t.queries.set(property, query);
addQuery(property: string, metadata: GraphQLPropertyMetadata) {
this.t.queries.set(property, metadata);
}

addResolveField(property: string, field: GraphQLPropertyMetadata) {
this.t.resolveFields.set(property, field);
addSubscription(property: string, metadata: GraphQLPropertyMetadata) {
this.t.subscriptions.set(property, metadata);
}

addResolveField(property: string, metadata: GraphQLPropertyMetadata) {
this.t.resolveFields.set(property, metadata);
}

addCheck(check: (decorator: GraphQLClassDecorator) => void) {
Expand All @@ -98,6 +103,12 @@ interface GraphQLQueryOptions {
deprecationReason?: string;
}

interface GraphQLSubscriptionOptions {
name?: string;
description?: string;
deprecationReason?: string;
}

interface GraphQLMutationOptions {
name?: string;
description?: string;
Expand All @@ -116,6 +127,8 @@ export class GraphQLPropertyMetadata implements GraphQLQueryOptions {
classType: ClassType;
middleware: Set<InternalMiddleware> = new Set<InternalMiddleware>();
type: 'query' | 'mutation' | 'subscription' | 'resolveField';
// TODO
// returnType: Type;
description?: string;
deprecationReason?: string;
readonly checks = new Set<(decorator: GraphQLClassDecorator) => void>();
Expand Down Expand Up @@ -145,7 +158,8 @@ class GraphQLPropertyDecorator {
break;

case 'subscription':
throw new Error('Not yet implemented');
gqlClassDecorator.addSubscription(property, this.t)(classType);
break;

default:
throw new Error('Invalid type');
Expand Down Expand Up @@ -175,6 +189,15 @@ class GraphQLPropertyDecorator {
this.t.deprecationReason = options?.deprecationReason;
}

subscription(options?: GraphQLSubscriptionOptions) {
if (options?.name) {
this.t.name = options.name;
}
this.t.type = 'subscription';
this.t.description = options?.description;
this.t.deprecationReason = options?.deprecationReason;
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
resolveField(options?: GraphQLResolveFieldOptions) {
if (options?.name) {
Expand Down Expand Up @@ -203,13 +226,6 @@ class GraphQLPropertyDecorator {
});
}

// TODO: subscriptions
// subscription(options?: GraphQLSubscriptionOptions) {
// this.t.type = 'subscription';
// this.t.description = options?.description;
// this.t.deprecationReason = options?.deprecationReason;
// }

// eslint-disable-next-line functional/prefer-readonly-type
middleware(...middleware: InternalMiddleware[]) {
this.t.middleware = new Set(middleware);
Expand Down Expand Up @@ -258,7 +274,12 @@ type MergedGraphQL<T extends any[]> = GraphQLMerge<

export type MergedGraphQLDecorator = Omit<
MergedGraphQL<[typeof gqlClassDecorator, typeof gqlPropertyDecorator]>,
'addMutation' | 'addQuery' | 'addResolveField' | 'onDecorator' | 'addCheck'
| 'addMutation'
| 'addQuery'
| 'addResolveField'
| 'addSubscription'
| 'onDecorator'
| 'addCheck'
>;

export const graphql: MergedGraphQLDecorator = mergeDecorator(
Expand Down
27 changes: 27 additions & 0 deletions packages/core/src/lib/directives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
GraphQLDirective,
GraphQLSkipDirective,
GraphQLIncludeDirective,
} from 'graphql';
import {
GraphQLDeferDirective,
GraphQLStreamDirective,
} from '@graphql-tools/utils';

export const DIRECTIVE_META_NAME = 'directive';

export type Directive<
D extends GraphQLDirective | string,
A extends any[] = [],
> = { __meta?: [typeof DIRECTIVE_META_NAME, D, A] };

export type Defer = Directive<typeof GraphQLDeferDirective>;

export type Stream = Directive<typeof GraphQLStreamDirective>;

export type Skip = Directive<typeof GraphQLSkipDirective>;

export type Include = Directive<typeof GraphQLIncludeDirective>;

// TODO: use jsdoc @deprecated instead
// export type Deprecated = Directive<'deprecated', { reason: string }>;
8 changes: 6 additions & 2 deletions packages/core/src/lib/driver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { GraphQLSchema } from 'graphql';
import { ApplicationServer, WebWorker, WebWorkerFactory } from '@deepkit/framework';
import {
ApplicationServer,
WebWorker,
WebWorkerFactory,
} from '@deepkit/framework';
import * as https from 'node:https';
import * as http from 'node:http';

Expand Down Expand Up @@ -29,6 +33,6 @@ export abstract class Driver {
(this.appServer as any).httpWorker.start();
httpWorker = this.appServer.getHttpWorker() as any;
}
return httpWorker.servers || httpWorker.server;
return httpWorker.servers || httpWorker.server;
}
}
8 changes: 8 additions & 0 deletions packages/core/src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ export class UnknownTypeNameError extends Error {
super('Unknown type name');
}
}

export class InvalidSubscriptionTypeError extends Error {
constructor(readonly type: Type, className: string, methodName: string) {
super(
`The return type of "${methodName}" method on "${className}" class must be AsyncGenerator<T>, AsyncIterable<T>, Observable<T> or BrokerBus<T> when @graphql.subscription() decorator is used`,
);
}
}
Loading
Loading