From 2982ca3aa00aac0d03d3e0e72db0a50ab4e0ca9d Mon Sep 17 00:00:00 2001 From: eric-burel Date: Fri, 24 Sep 2021 16:09:35 +0200 Subject: [PATCH] better typings for server code --- packages/graphql/extendModel.ts | 125 +++++++++++++++--- packages/graphql/server/parseModel.ts | 19 +-- packages/graphql/server/resolvers/mutators.ts | 22 ++- .../server/tests/mutators/callbacks.test.ts | 17 ++- .../server/tests/mutators/mutators.test.ts | 4 +- .../tests/typeDefs/parseAllModels.test.ts | 9 +- .../server/tests/typeDefs/parseModel.test.ts | 14 +- packages/graphql/typings.ts | 27 ++-- packages/schema/typings/schema.ts | 40 +++++- packages/utils/typescript.ts | 4 + test/integration/server/crud.test.ts | 4 +- 11 files changed, 212 insertions(+), 73 deletions(-) diff --git a/packages/graphql/extendModel.ts b/packages/graphql/extendModel.ts index 3c4d0b8a..b8f347dd 100644 --- a/packages/graphql/extendModel.ts +++ b/packages/graphql/extendModel.ts @@ -9,30 +9,63 @@ import { VulcanGraphqlModel, MutationCallbackDefinitions, VulcanGraphqlSchema, + VulcanGraphqlModelServer, } from "./typings"; import { VulcanModel, createModel, CreateModelOptions } from "@vulcanjs/model"; import { getDefaultFragmentText, getDefaultFragmentName, } from "./fragments/defaultFragment"; -import { camelCaseify } from "@vulcanjs/utils"; +import { camelCaseify, Merge } from "@vulcanjs/utils"; import { MutationResolverDefinitions, QueryResolverDefinitions, } from "./server/typings"; - -interface CreateGraphqlModelSharedOptions { +import { + buildDefaultMutationResolvers, + buildDefaultQueryResolvers, +} from "./server/resolvers"; +/** + * Typing is tricky here: + * - we want to tell the user when they tried to use a server-only field client-side, and point them to the right solution (type: never) + * - we want the sever version to define those server only fields correctly (type: whatever) + * - we want to define functions that accept any of those types (eg when they use only the shared fields) + * + * This type is meant for internal use + * + * This SO question is similar but all answsers break inheritance, using types instead of cleaner interfaces + * @see https://stackoverflow.com/questions/41285211/overriding-interface-property-type-defined-in-typescript-d-ts-file + */ +interface CreateGraphqlModelOptions { typeName: string; // Canonical name of the model = its graphQL type name multiTypeName: string; // Plural version, to be defined manually (automated pluralization leads to unexpected results) } -interface CreateGraphqlModelServerOptions { +/** + * This type is meant to be exposed in the "default", shared helper + * + * It adds "never" types to help the user detecting when they do something wrong (trying to define server fields client side) + */ +export interface CreateGraphqlModelOptionsShared + extends CreateGraphqlModelOptions { + /** This is a server-only field. Please use "createGraphqlModelServer" if you want to create a model server-side */ + queryResolvers?: never; + /** This is a server-only field. Please use "createGraphqlModelServer" if you want to create a model server-side */ + mutationResolvers?: never; + /** This is a server-only field. Please use "createGraphqlModelServer" if you want to create a model server-side */ + callbacks?: never; +} +/** + * This type is meant to be exposed server side + *  @server-only + */ +export interface CreateGraphqlModelOptionsServer + extends CreateGraphqlModelOptions { + /** Custom query resolvers (single, multi). Set to "null" if you don't want Vulcan to set any resolvers. Leave undefined if you want to use default resolvers. */ queryResolvers?: Partial; + /** Custom mutation resolvers (create, update, delete). Set to "null" if you don't want Vulcan to set any resolvers. Leave undefined if you want to use default resolvers. */ mutationResolvers?: Partial; callbacks?: MutationCallbackDefinitions; } -interface CreateGraphqlModelOptions - extends CreateGraphqlModelSharedOptions, - CreateGraphqlModelServerOptions {} // Reusable model extension function const extendModel = @@ -41,12 +74,7 @@ const extendModel = ) /*: ExtendModelFunc*/ => (model: VulcanModel): VulcanGraphqlModel => { const name = model.name; - const { - typeName = name, - multiTypeName, - queryResolvers, - mutationResolvers, - } = options; + const { typeName = name, multiTypeName } = options; const singleResolverName = camelCaseify(typeName); const multiResolverName = camelCaseify(multiTypeName); @@ -75,14 +103,10 @@ const extendModel = ); } - // server-only const extendedGraphqlModel = { ...graphqlModel, defaultFragment, defaultFragmentName, - // server-only - queryResolvers, - mutationResolvers, }; const finalModel: VulcanGraphqlModel = { ...model, @@ -92,11 +116,57 @@ const extendModel = }; /** - * Helper to simplify the syntax + * Adds server-only fields as well + * @param options + * @returns + */ +export const extendModelServer = + ( + options: CreateGraphqlModelOptionsServer + ) /*: ExtendModelFunc*/ => + (model: VulcanModel): VulcanGraphqlModelServer => { + const extendedModel = extendModel(options)(model); + const { + mutationResolvers: mutationResolversFromOptions, + queryResolvers: queryResolversFromOptions, + } = options; + let mutationResolvers = mutationResolversFromOptions, + queryResolvers = queryResolversFromOptions; + // NOTE: we use default only if the value is "undefined", if it is explicitely "null" we leave it empty (user will define resolvers manually) + if (typeof mutationResolvers === "undefined") { + mutationResolvers = buildDefaultMutationResolvers({ + typeName: extendedModel.graphql.typeName, + }); + } + if (typeof queryResolvers === "undefined") { + queryResolvers = buildDefaultQueryResolvers({ + typeName: extendedModel.graphql.typeName, + }); + } + /** + * If mutationResolvers and queryResolvers are not explicitely null, + * use the default ones + */ + const finalModel = extendedModel as VulcanGraphqlModelServer; + finalModel.graphql = { + ...extendedModel.graphql, + mutationResolvers: mutationResolvers ? mutationResolvers : undefined, + queryResolvers: queryResolvers ? queryResolvers : undefined, + }; + const name = model.name; + return finalModel; + }; + +/** + * Let's you create a full-fledged graphql model + * + * Equivalent to a Vulcan Meteor createCollection */ export const createGraphqlModel = ( options: CreateModelOptions & { - graphql: CreateGraphqlModelOptions; + // we use the "Shared" version of the type, that is meant to be used for exported functions + // => it will display nicer messages when you try to mistakenly use a server-only field + graphql: CreateGraphqlModelOptionsShared; } ): VulcanGraphqlModel => { // TODO: @@ -108,6 +178,23 @@ export const createGraphqlModel = ( return model; }; +/** + * @server-only + */ +export const createGraphqlModelServer = ( + options: CreateModelOptions & { + graphql: CreateGraphqlModelOptionsServer; + } +): VulcanGraphqlModelServer => { + // TODO: + const { graphql, ...baseOptions } = options; + const model = createModel({ + ...baseOptions, + extensions: [extendModelServer(options.graphql)], + }) as VulcanGraphqlModelServer; + return model; +}; + //// CODE FROM CREATE COLLECTION //import { Mongo } from "meteor/mongo"; //import SimpleSchema from "simpl-schema"; diff --git a/packages/graphql/server/parseModel.ts b/packages/graphql/server/parseModel.ts index f80ed542..02fc8b8c 100644 --- a/packages/graphql/server/parseModel.ts +++ b/packages/graphql/server/parseModel.ts @@ -26,7 +26,7 @@ import { import _isEmpty from "lodash/isEmpty"; import _initial from "lodash/initial"; -import { VulcanGraphqlModel } from "../typings"; +import { VulcanGraphqlModel, VulcanGraphqlModelServer } from "../typings"; import { ModelResolverMap, AnyResolverMap } from "./typings"; import { ParsedModelMutationResolvers, @@ -203,7 +203,9 @@ interface ParseModelOutput schemaResolvers?: Array; resolvers?: ModelResolverMap; } -export const parseModel = (model: VulcanGraphqlModel): ParseModelOutput => { +export const parseModel = ( + model: VulcanGraphqlModelServer +): ParseModelOutput => { const typeDefs: Array = []; // const { @@ -216,10 +218,11 @@ export const parseModel = (model: VulcanGraphqlModel): ParseModelOutput => { const { schema, name: modelName } = model; const { typeName, multiTypeName } = model.graphql; - const { nestedFieldsList, fields, resolvers: schemaResolvers } = parseSchema( - schema, - typeName - ); + const { + nestedFieldsList, + fields, + resolvers: schemaResolvers, + } = parseSchema(schema, typeName); const { mainType } = fields; @@ -258,8 +261,8 @@ export const parseModel = (model: VulcanGraphqlModel): ParseModelOutput => { const resolvers: ModelResolverMap = {}; let queries; let mutations; - const queryDefinitions = model.graphql.queryResolvers; // TODO: get from Model? - const mutationDefinitions = model.graphql.mutationResolvers; // TODO: get from Model? + const queryDefinitions = model.graphql?.queryResolvers; // TODO: get from Model? + const mutationDefinitions = model.graphql?.mutationResolvers; // TODO: get from Model? if (queryDefinitions) { const parsedQueries = parseQueryResolvers({ queryResolverDefinitions: queryDefinitions, diff --git a/packages/graphql/server/resolvers/mutators.ts b/packages/graphql/server/resolvers/mutators.ts index 798535f1..80fca49c 100644 --- a/packages/graphql/server/resolvers/mutators.ts +++ b/packages/graphql/server/resolvers/mutators.ts @@ -41,7 +41,12 @@ import { throwError } from "./errors"; import { ModelMutationPermissionsOptions } from "@vulcanjs/model"; import { isMemberOf } from "@vulcanjs/permissions"; import { getModelConnector } from "./context"; -import { UpdateInput, DeleteInput, FilterableInput } from "../../typings"; +import { + UpdateInput, + DeleteInput, + FilterableInput, + VulcanGraphqlModelServer, +} from "../../typings"; import { deprecate } from "@vulcanjs/utils"; import cloneDeep from "lodash/cloneDeep"; import isEmpty from "lodash/isEmpty"; @@ -49,7 +54,6 @@ import { ContextWithUser } from "./typings"; import { VulcanDocument } from "@vulcanjs/schema"; import { DefaultMutatorName, VulcanGraphqlModel } from "../../typings"; import { restrictViewableFields } from "@vulcanjs/permissions"; -import { Options } from "graphql/utilities/extendSchema"; /** * Throws if some data are invalid @@ -62,7 +66,7 @@ const validateMutationData = async ({ context, properties, }: { - model: VulcanGraphqlModel; // data model + model: VulcanGraphqlModelServer; // data model mutatorName: DefaultMutatorName; context: Object; // Graphql context properties: Object; // arguments of the callback, can vary depending on the mutator @@ -186,7 +190,7 @@ async function getSelector({ } interface CreateMutatorInput { - model: VulcanGraphqlModel; + model: VulcanGraphqlModelServer; document?: VulcanDocument; data: VulcanDocument; context?: ContextWithUser; @@ -269,8 +273,6 @@ export const createMutator = async ({ let autoValue; // TODO: run for nested if (schema[fieldName].onCreate) { - // TS is triggering an error for unknown reasons because there is already an if - // @ts-expect-error Cannot invoke an object which is possibly 'undefined'. autoValue = await schema[fieldName].onCreate(properties); // eslint-disable-line no-await-in-loop } if (typeof autoValue !== "undefined") { @@ -315,7 +317,7 @@ export const createMutator = async ({ }; interface UpdateMutatorCommonInput { - model: VulcanGraphqlModel; + model: VulcanGraphqlModelServer; /** * Using a "set" syntax * @deprecated @@ -494,8 +496,6 @@ export const updateMutator = async ({ for (let fieldName of Object.keys(schema)) { let autoValue; if (schema[fieldName].onUpdate) { - // TS is triggering an error for unknown reasons because there is already an if - // @ts-expect-error Cannot invoke an object which is possibly 'undefined'. autoValue = await schema[fieldName].onUpdate(properties); // eslint-disable-line no-await-in-loop } if (typeof autoValue !== "undefined") { @@ -567,7 +567,7 @@ export const updateMutator = async ({ }; interface DeleteMutatorCommonInput { - model: VulcanGraphqlModel; + model: VulcanGraphqlModelServer; currentUser?: any; context?: ContextWithUser; validate?: boolean; @@ -664,8 +664,6 @@ export const deleteMutator = async ({ /* Run fields onDelete */ for (let fieldName of Object.keys(schema)) { if (schema[fieldName].onDelete) { - // TS is triggering an error for unknown reasons because there is already an if - // @ts-expect-error Cannot invoke an object which is possibly 'undefined'. await schema[fieldName].onDelete(properties); // eslint-disable-line no-await-in-loop } } diff --git a/packages/graphql/server/tests/mutators/callbacks.test.ts b/packages/graphql/server/tests/mutators/callbacks.test.ts index 1a436653..f5970f12 100644 --- a/packages/graphql/server/tests/mutators/callbacks.test.ts +++ b/packages/graphql/server/tests/mutators/callbacks.test.ts @@ -1,4 +1,7 @@ -import { createGraphqlModel } from "../../../extendModel"; +import { + createGraphqlModel, + createGraphqlModelServer, +} from "../../../extendModel"; import { VulcanGraphqlModel } from "../../../typings"; import { createMutator } from "../../resolvers/mutators"; import { Connector } from "../../resolvers"; @@ -77,7 +80,7 @@ describe("graphql/resolvers/mutators callbacks", function () { test("run asynchronous 'after' callback before document is returned", async function () { const after = jest.fn(async (doc) => ({ ...doc, createdAfter: 1 })); const create = jest.fn(async (data) => ({ _id: 1, ...data })); - const Foo = createGraphqlModel({ + const Foo = createGraphqlModelServer({ schema, name: "Foo", graphql: merge({}, modelGraphqlOptions, { @@ -109,7 +112,7 @@ describe("graphql/resolvers/mutators callbacks", function () { test("run asynchronous 'before' callback before document is saved", async function () { const before = jest.fn(async (doc) => ({ ...doc, createdBefore: 1 })); const create = jest.fn(async (data) => ({ _id: 1, ...data })); - const Foo = createGraphqlModel({ + const Foo = createGraphqlModelServer({ schema, name: "Foo", graphql: merge({}, modelGraphqlOptions, { @@ -150,7 +153,7 @@ describe("graphql/resolvers/mutators callbacks", function () { setTimeout(() => resolve(true), 10000) ) ); - const Foo = createGraphqlModel({ + const Foo = createGraphqlModelServer({ ...defaultModelOptions, graphql: merge({}, modelGraphqlOptions, { callbacks: { @@ -179,9 +182,9 @@ describe("graphql/resolvers/mutators callbacks", function () { test("return a custom validation error", async () => { const errSpy = jest .spyOn(console, "error") - .mockImplementationOnce(() => { }); // silences console.error + .mockImplementationOnce(() => {}); // silences console.error const validate = jest.fn(() => ["ERROR"]); - const Foo = createGraphqlModel({ + const Foo = createGraphqlModelServer({ ...defaultModelOptions, graphql: merge({}, modelGraphqlOptions, { callbacks: { @@ -207,7 +210,7 @@ describe("graphql/resolvers/mutators callbacks", function () { data, validate: true, // enable document validation }); - } catch (e) { } + } catch (e) {} expect(validate).toHaveBeenCalledTimes(1); expect(errSpy).toHaveBeenCalled(); }); diff --git a/packages/graphql/server/tests/mutators/mutators.test.ts b/packages/graphql/server/tests/mutators/mutators.test.ts index 2eeed213..bfe1b36c 100644 --- a/packages/graphql/server/tests/mutators/mutators.test.ts +++ b/packages/graphql/server/tests/mutators/mutators.test.ts @@ -204,7 +204,9 @@ describe("graphql/resolvers/mutators", function () { }), findOne: async () => ({ id: "1" }), update: async () => ({ id: "1" }), - delete: async () => {}, + delete: async () => { + return true; + }, }, }, }; diff --git a/packages/graphql/server/tests/typeDefs/parseAllModels.test.ts b/packages/graphql/server/tests/typeDefs/parseAllModels.test.ts index 940e4046..93d2e05c 100644 --- a/packages/graphql/server/tests/typeDefs/parseAllModels.test.ts +++ b/packages/graphql/server/tests/typeDefs/parseAllModels.test.ts @@ -4,7 +4,10 @@ */ import { createModel } from "@vulcanjs/model"; -import extendModel, { createGraphqlModel } from "../../../extendModel"; +import { + createGraphqlModelServer, + extendModelServer, +} from "../../../extendModel"; import { VulcanGraphqlModel } from "../../../typings"; import { parseAllModels } from "../../parseAllModels"; import { buildDefaultQueryResolvers } from "../../resolvers/defaultQueryResolvers"; @@ -12,7 +15,7 @@ import { buildDefaultMutationResolvers } from "../../resolvers/defaultMutationRe describe("graphql/typeDefs", () => { describe("parseAllModels", () => { - const Foo = createGraphqlModel({ + const Foo = createGraphqlModelServer({ schema: { foo: { type: String, @@ -40,7 +43,7 @@ describe("graphql/typeDefs", () => { }, name: "Bar", extensions: [ - extendModel({ + extendModelServer({ multiTypeName: "Bars", typeName: "Bar", queryResolvers: buildDefaultQueryResolvers({ typeName: "Foo" }), diff --git a/packages/graphql/server/tests/typeDefs/parseModel.test.ts b/packages/graphql/server/tests/typeDefs/parseModel.test.ts index e95e00a8..c0c2159d 100644 --- a/packages/graphql/server/tests/typeDefs/parseModel.test.ts +++ b/packages/graphql/server/tests/typeDefs/parseModel.test.ts @@ -3,8 +3,10 @@ * (= type definitions) */ -import { createGraphqlModel } from "../../../extendModel"; -import { VulcanGraphqlModel } from "../../../typings"; +import { + createGraphqlModel, + createGraphqlModelServer, +} from "../../../extendModel"; import { normalizeGraphQLSchema } from "../../../testUtils"; import { parseSchema } from "../../parseSchema"; import { parseModel } from "../../parseModel"; @@ -12,8 +14,8 @@ import { buildDefaultMutationResolvers } from "../../resolvers/defaultMutationRe import { buildDefaultQueryResolvers } from "../../resolvers/defaultQueryResolvers"; import { getGraphQLType } from "../../../utils"; -const FooModel = (schema): VulcanGraphqlModel => - createGraphqlModel({ +const FooModel = (schema) => + createGraphqlModelServer({ schema, name: "Foo", graphql: { multiTypeName: "Foos", typeName: "Foo" }, @@ -1074,7 +1076,7 @@ describe("graphql/typeDefs", () => { describe("model resolvers", () => { test("generate query resolver types", () => { - const Foo = createGraphqlModel({ + const Foo = createGraphqlModelServer({ schema: { foo: { type: String, @@ -1101,7 +1103,7 @@ describe("graphql/typeDefs", () => { expect(multi.query).toContain("foos("); }); test("generate mutation resolver types", () => { - const Foo = createGraphqlModel({ + const Foo = createGraphqlModelServer({ schema: { foo: { type: String, diff --git a/packages/graphql/typings.ts b/packages/graphql/typings.ts index 1176705c..79ac2890 100644 --- a/packages/graphql/typings.ts +++ b/packages/graphql/typings.ts @@ -10,6 +10,7 @@ import { VulcanFieldSchema, VulcanSchema, } from "@vulcanjs/schema"; +import { Merge } from "@vulcanjs/utils"; import { ContextWithUser } from "./server/resolvers"; // SCHEMA TYPINGS @@ -30,13 +31,19 @@ export interface RelationDefinition { } interface VulcanGraphqlFieldSchema extends VulcanFieldSchema { - // GRAPHQL - resolveAs?: Array | ResolveAsDefinition; relation?: RelationDefinition; // define a relation to another model typeName?: string; // the GraphQL type to resolve the field with } +// @server-only +interface VulcanGraphqlFieldSchemaServer extends VulcanGraphqlFieldSchema { + // this is a custom function => it has to be defined only on the server + resolveAs?: Array | ResolveAsDefinition; +} // Base schema + GraphQL Fields export type VulcanGraphqlSchema = VulcanSchema; +// @server-only +export type VulcanGraphqlSchemaServer = + VulcanSchema; // MODEL TYPINGS // Those typings extends the raw model/schema system @@ -44,7 +51,7 @@ export interface VulcanGraphqlModelSkeleton extends VulcanModel { graphql: Pick; } // information relevant for server and client -interface GraphqlSharedModel { +interface GraphqlModel { typeName: string; multiTypeName: string; // plural name for the multi resolver multiResolverName: string; @@ -53,17 +60,14 @@ interface GraphqlSharedModel { defaultFragmentName?: string; } // Client only model fields -interface GraphqlClientModel {} +// interface GraphqlClientModel extends GraphqlModel {} + // Server only model fields -interface GraphqlServerModel { +export interface GraphqlModelServer extends GraphqlModel { queryResolvers?: QueryResolverDefinitions; mutationResolvers?: MutationResolverDefinitions; callbacks?: MutationCallbackDefinitions; } -// With client and server fields (at this point, we can't actually differentiate them) -export type GraphqlModel = GraphqlSharedModel & - GraphqlServerModel & - GraphqlClientModel; // TODO: not used yet. A schema for graphql might contain those additional fields. // export interface VulcanFieldSchemaGraphql extends VulcanFieldSchema { @@ -74,6 +78,11 @@ export type GraphqlModel = GraphqlSharedModel & export interface VulcanGraphqlModel extends VulcanModel { graphql: GraphqlModel; } +// @server-only +export interface VulcanGraphqlModelServer + extends VulcanModel { + graphql: GraphqlModelServer; +} // Mutations export type DefaultMutatorName = "create" | "update" | "delete"; diff --git a/packages/schema/typings/schema.ts b/packages/schema/typings/schema.ts index 6e334a63..330eac05 100644 --- a/packages/schema/typings/schema.ts +++ b/packages/schema/typings/schema.ts @@ -96,10 +96,6 @@ interface VulcanField { group?: FieldGroup; // form fieldset group arrayItem?: any; // properties for array items - onCreate?: (input: OnCreateInput) => Promise | TField; // field insert callback, called server-side - onUpdate?: (input: OnUpdateInput) => Promise | TField; // field edit callback, called server-side - onDelete?: (input: OnDeleteInput) => Promise | TField; // field remove callback, called server-side - searchable?: boolean; // whether a field is searchable description?: string; // description/help beforeComponent?: any; // before form component @@ -113,24 +109,56 @@ interface VulcanField { orderable?: boolean; // field can be used to order results when querying for data (backwards-compatibility) sortable?: boolean; // field can be used to order results when querying for data - apiOnly?: boolean; // field should not be inserted in database - intl?: boolean; // set to `true` to make a field international isIntlData?: boolean; // marker for the actual schema fields that hold intl strings intlId?: string; // set an explicit i18n key for a field } +// @server-only +interface VulcanFieldServer extends VulcanField { + apiOnly?: boolean; // field should not be inserted in database + onCreate?: (input: OnCreateInput) => Promise | TField; // field insert callback, called server-side + onUpdate?: (input: OnUpdateInput) => Promise | TField; // field edit callback, called server-side + onDelete?: (input: OnDeleteInput) => Promise | TField; // field remove callback, called server-side +} +interface VulcanFieldShared extends VulcanField { + /** This is a server-only field. You may want to use VulcanSchemaServer type instead. */ + apiOnly?: never; + /** This is a server-only field. You may want to use VulcanSchemaServer type instead. */ + onCreate?: never; + /** This is a server-only field. You may want to use VulcanSchemaServer type instead. */ + onUpdate?: never; + /** This is a server-only field. You may want to use VulcanSchemaServer type instead. */ + onDelete?: never; +} + export interface VulcanFieldSchema extends VulcanField, SchemaDefinition { type: VulcanFieldType; } +// @server-only +export type VulcanFieldSchemaServer = VulcanFieldSchema & + VulcanFieldServer; +export type VulcanFieldSchemaShared = VulcanFieldSchema & + VulcanFieldShared; + export type VulcanFieldType = SchemaDefinition["type"] | VulcanFieldSchema; // Extendable Vulcan schema export type VulcanSchema = { [key: string]: VulcanFieldSchema & TSchemaFieldExtension; }; +/** + * @server-only + */ +export type VulcanSchemaServer = { + [key: string]: VulcanFieldSchemaServer & TSchemaFieldExtension; +}; +/** Safer type that adds server-only fields with "never" type and a nice error message */ +export type VulcanSchemaShared = { + [key: string]: VulcanFieldSchemaServer & TSchemaFieldExtension; +}; /** * Version obtained after running new SimpleSchema({...})._schema diff --git a/packages/utils/typescript.ts b/packages/utils/typescript.ts index 14411ae6..e75e3d37 100644 --- a/packages/utils/typescript.ts +++ b/packages/utils/typescript.ts @@ -3,3 +3,7 @@ export type NestedPartial = { ? Array> : NestedPartial; }; + +// @see https://stackoverflow.com/questions/41285211/overriding-interface-property-type-defined-in-typescript-d-ts-file +// Let's you override some types +export type Merge = Omit> & N; diff --git a/test/integration/server/crud.test.ts b/test/integration/server/crud.test.ts index c7f09e0d..118681c7 100644 --- a/test/integration/server/crud.test.ts +++ b/test/integration/server/crud.test.ts @@ -14,8 +14,8 @@ import { buildApolloSchema } from "@vulcanjs/graphql"; import { MongoMemoryServer } from "mongodb-memory-server"; // @see https://github.com/nodkz/mongodb-memory-server import { contextFromReq } from "./utils/context"; -import { createGraphqlModel } from "@vulcanjs/graphql"; import { buildDefaultQueryResolvers } from "@vulcanjs/graphql"; +import { createGraphqlModelServer } from "@vulcanjs/graphql"; let mongod; let mongoUri; @@ -40,7 +40,7 @@ afterAll(async () => { describe("crud operations", () => { test("setup an apollo server", () => { const models = [ - createGraphqlModel({ + createGraphqlModelServer({ name: "Contributor", schema: { _id: {