From f89a0bcb6a49b0b1a2be718d115f6ffd84b7f3dd Mon Sep 17 00:00:00 2001 From: eric-burel Date: Fri, 1 Oct 2021 11:22:56 +0200 Subject: [PATCH] split client and server code in mongo and graphql --- README.md | 2 +- package.json | 2 +- packages/graphql/extendModel.server.ts | 461 ++++++++++++++++++ packages/graphql/extendModel.ts | 90 +--- packages/graphql/index.ts | 4 - packages/graphql/package.json | 7 +- packages/graphql/server/index.ts | 9 + packages/graphql/server/resolvers/index.ts | 1 + .../server/resolvers/utilityResolvers.ts | 14 + packages/graphql/tsconfig.build.json | 2 +- packages/graphql/webpack.config.server.js | 10 + packages/mongo/client/index.ts | 4 + packages/mongo/connector.ts | 2 +- packages/mongo/index.ts | 1 + packages/mongo/package.json | 4 +- packages/mongo/tsconfig.build.json | 2 +- packages/mongo/webpack.config.client.js | 11 + ...ack.config.js => webpack.config.server.js} | 5 +- packages/react-hooks/create.ts | 2 +- packages/schema/package.json | 1 - packages/schema/schema-utils.ts | 14 - webpack/webpack.config.base.client.prod.js | 6 +- webpack/webpack.config.base.common.prod.js | 2 + webpack/webpack.config.base.server.prod.js | 8 +- 24 files changed, 542 insertions(+), 122 deletions(-) create mode 100644 packages/graphql/extendModel.server.ts create mode 100644 packages/graphql/server/index.ts create mode 100644 packages/graphql/server/resolvers/utilityResolvers.ts create mode 100644 packages/graphql/webpack.config.server.js create mode 100644 packages/mongo/client/index.ts create mode 100644 packages/mongo/webpack.config.client.js rename packages/mongo/{webpack.config.js => webpack.config.server.js} (56%) diff --git a/README.md b/README.md index a46a0333..2d2ac480 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ If you want to connect your local Vulcan NPM install to an existing application, It's a 2 step process: -- you publish the packages locally using Yalc `yarn run local-publish` +- you publish the packages locally using Yalc `yarn run publish:local` - you install them, using Yalc, in your app. We use Yalc and not `yarn link` because linking is not sufficient, it raises a lot of issues with locally installed packages. diff --git a/package.json b/package.json index f11bb6b2..130d7ff4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "scripts": { "postinstall": "lerna bootstrap --force-local", "build": "lerna run build", - "local-publish": "lerna exec yalc publish # then, use 'yalc link ' in your project", + "publish:local": "lerna exec yalc publish # then, use 'yalc link ' in your project", "link-yarn-legacy": "lerna exec yarn link && ./scripts/link-duplicates.sh # deprecated, symlink creates issue with React, Webpack...", "unlink-yarn-legacy": "lerna exec yarn unlink", "clean": "rm -Rf ./node_modules ./packages/*/dist ./packages/*/node_modules", diff --git a/packages/graphql/extendModel.server.ts b/packages/graphql/extendModel.server.ts new file mode 100644 index 00000000..179e842d --- /dev/null +++ b/packages/graphql/extendModel.server.ts @@ -0,0 +1,461 @@ +/** + * Create a Vulcan model with graphql capabilities + * + * Implementation is based on an extension system + * + * End user is supposed to use only the "createGraphqlModel" function + */ +import { + VulcanGraphqlModel, + MutationCallbackDefinitions, + VulcanGraphqlSchema, + VulcanGraphqlModelServer, + VulcanGraphqlSchemaServer, +} from "./typings"; +import { VulcanModel, createModel, CreateModelOptions } from "@vulcanjs/model"; +import { + getDefaultFragmentText, + getDefaultFragmentName, +} from "./fragments/defaultFragment"; +import { camelCaseify, Merge } from "@vulcanjs/utils"; +import { + MutationResolverDefinitions, + QueryResolverDefinitions, +} from "./server/typings"; +import { + buildDefaultMutationResolvers, + buildDefaultQueryResolvers, +} from "./server/resolvers"; +// don't forget to reexport common code +export * from "./extendModel"; +import extendModel, { GraphqlModelOptions } from "./extendModel"; +/** + * This type is meant to be exposed server side + *  @server-only + */ +export interface GraphqlModelOptionsServer extends GraphqlModelOptions { + /** 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; +} + +/** + * Adds server-only fields as well + * @param options + * @returns + */ +export const extendModelServer = + ( + options: GraphqlModelOptionsServer + ) /*: 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; + }; + +export interface CreateGraphqlModelOptionsServer + extends CreateModelOptions { + graphql: GraphqlModelOptionsServer; +} + +/** + * @server-only + */ +export const createGraphqlModelServer = ( + options: 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"; +// import escapeStringRegexp from "escape-string-regexp"; +// import { +// validateIntlField, +// getIntlString, +// isIntlField, +// schemaHasIntlFields, +// schemaHasIntlField, +// } from "./intl"; +// import clone from "lodash/clone"; +// import isEmpty from "lodash/isEmpty"; +// import merge from "lodash/merge"; +// import _omit from "lodash/omit"; +// import mergeWith from "lodash/mergeWith"; +// import { createSchema, isCollectionType } from "./schema_utils"; + +// // will be set to `true` if there is one or more intl schema fields +// export let hasIntlFields = false; + +// // see https://github.com/dburles/meteor-collection-helpers/blob/master/collection-helpers.js +// Mongo.Collection.prototype.helpers = function (helpers) { +// var self = this; + +// if (self._transform && !self._helpers) +// throw new Meteor.Error( +// "Can't apply helpers to '" + +// self._name + +// "' a transform function already exists!" +// ); + +// if (!self._helpers) { +// self._helpers = function Document(doc) { +// return Object.assign(this, doc); +// }; +// self._transform = function (doc) { +// return new self._helpers(doc); +// }; +// } + +// Object.keys(helpers).forEach(function (key) { +// self._helpers.prototype[key] = helpers[key]; +// }); +// }; + +// export const extendCollection = (collection, options) => { +// const newOptions = mergeWith({}, collection.options, options, (a, b) => { +// if (Array.isArray(a) && Array.isArray(b)) { +// return a.concat(b); +// } +// if (Array.isArray(a) && b) { +// return a.concat([b]); +// } +// if (Array.isArray(b) && a) { +// return b.concat([a]); +// } +// }); +// collection = createCollection(newOptions); +// return collection; +// }; + +// /* + +// Note: this currently isn't used because it would need to be called +// after all collections have been initialized, otherwise we can't figure out +// if resolved field is resolving to a collection type or not + +// */ +// export const addAutoRelations = () => { +// Collections.forEach((collection) => { +// const schema = collection.simpleSchema()._schema; +// // add "auto-relations" to schema resolvers +// Object.keys(schema).map((fieldName) => { +// const field = schema[fieldName]; +// // if no resolver or relation is provided, try to guess relation and add it to schema +// if (field.resolveAs) { +// const { resolver, relation, type } = field.resolveAs; +// if (isCollectionType(type) && !resolver && !relation) { +// field.resolveAs.relation = +// field.type === Array ? "hasMany" : "hasOne"; +// } +// } +// }); +// }); +// }; + +// /* + +// Pass an existing collection to overwrite it instead of creating a new one + +// */ +// export const createCollection = (options) => { +// const { +// typeName, +// collectionName = generateCollectionNameFromTypeName(typeName), +// dbCollectionName, +// } = options; +// let { schema, apiSchema, dbSchema } = options; + +// // decorate collection with options +// collection.options = options; + +// // add views +// collection.views = []; + +// //register individual collection callback +// registerCollectionCallback(typeName.toLowerCase()); + +// // if schema has at least one intl field, add intl callback just before +// // `${collectionName}.collection` callbacks run to make sure it always runs last +// if (schemaHasIntlFields(schema)) { +// hasIntlFields = true; // we have at least one intl field +// addCallback(`${typeName.toLowerCase()}.collection`, addIntlFields); +// } + +// if (schema) { +// // attach schema to collection +// collection.attachSchema(createSchema(schema, apiSchema, dbSchema)); +// } + +// // ------------------------------------- Default Fragment -------------------------------- // + +// const defaultFragment = getDefaultFragmentText(collection); +// if (defaultFragment) registerFragment(defaultFragment); + +// // ------------------------------------- Parameters -------------------------------- // + +// // legacy +// collection.getParameters = (terms = {}, apolloClient, context) => { +// // console.log(terms); + +// const currentSchema = collection.simpleSchema()._schema; + +// let parameters = { +// selector: {}, +// options: {}, +// }; + +// if (collection.defaultView) { +// parameters = Utils.deepExtend( +// true, +// parameters, +// collection.defaultView(terms, apolloClient, context) +// ); +// } + +// // handle view option +// if (terms.view && collection.views[terms.view]) { +// const viewFn = collection.views[terms.view]; +// const view = viewFn(terms, apolloClient, context); +// let mergedParameters = Utils.deepExtend(true, parameters, view); + +// if ( +// mergedParameters.options && +// mergedParameters.options.sort && +// view.options && +// view.options.sort +// ) { +// // If both the default view and the selected view have sort options, +// // don't merge them together; take the selected view's sort. (Otherwise +// // they merge in the wrong order, so that the default-view's sort takes +// // precedence over the selected view's sort.) +// mergedParameters.options.sort = view.options.sort; +// } +// parameters = mergedParameters; +// } + +// // iterate over posts.parameters callbacks +// parameters = runCallbacks( +// `${typeName.toLowerCase()}.parameters`, +// parameters, +// clone(terms), +// apolloClient, +// context +// ); +// // OpenCRUD backwards compatibility +// parameters = runCallbacks( +// `${collectionName.toLowerCase()}.parameters`, +// parameters, +// clone(terms), +// apolloClient, +// context +// ); + +// if (Meteor.isClient) { +// parameters = runCallbacks( +// `${typeName.toLowerCase()}.parameters.client`, +// parameters, +// clone(terms), +// apolloClient +// ); +// // OpenCRUD backwards compatibility +// parameters = runCallbacks( +// `${collectionName.toLowerCase()}.parameters.client`, +// parameters, +// clone(terms), +// apolloClient +// ); +// } + +// // note: check that context exists to avoid calling this from withList during SSR +// if (Meteor.isServer && context) { +// parameters = runCallbacks( +// `${typeName.toLowerCase()}.parameters.server`, +// parameters, +// clone(terms), +// context +// ); +// // OpenCRUD backwards compatibility +// parameters = runCallbacks( +// `${collectionName.toLowerCase()}.parameters.server`, +// parameters, +// clone(terms), +// context +// ); +// } + +// // sort using terms.orderBy (overwrite defaultView's sort) +// if (terms.orderBy && !isEmpty(terms.orderBy)) { +// parameters.options.sort = terms.orderBy; +// } + +// // if there is no sort, default to sorting by createdAt descending +// if (!parameters.options.sort) { +// parameters.options.sort = { createdAt: -1 }; +// } + +// // extend sort to sort posts by _id to break ties, unless there's already an id sort +// // NOTE: always do this last to avoid overriding another sort +// //if (!(parameters.options.sort && parameters.options.sort._id)) { +// // parameters = Utils.deepExtend(true, parameters, { options: { sort: { _id: -1 } } }); +// //} + +// // remove any null fields (setting a field to null means it should be deleted) +// Object.keys(parameters.selector).forEach((key) => { +// if (parameters.selector[key] === null) delete parameters.selector[key]; +// }); +// if (parameters.options.sort) { +// Object.keys(parameters.options.sort).forEach((key) => { +// if (parameters.options.sort[key] === null) +// delete parameters.options.sort[key]; +// }); +// } + +// if (terms.query) { +// const query = escapeStringRegexp(terms.query); +// const searchableFieldNames = Object.keys(currentSchema).filter( +// (fieldName) => currentSchema[fieldName].searchable +// ); +// if (searchableFieldNames.length) { +// parameters = Utils.deepExtend(true, parameters, { +// selector: { +// $or: searchableFieldNames.map((fieldName) => ({ +// [fieldName]: { $regex: query, $options: "i" }, +// })), +// }, +// }); +// } else { +// // eslint-disable-next-line no-console +// console.warn( +// `Warning: terms.query is set but schema ${collection.options.typeName} has no searchable field. Set "searchable: true" for at least one field to enable search.` +// ); +// } +// } + +// // limit number of items to 1000 by default +// const maxDocuments = getSetting("maxDocumentsPerRequest", 1000); +// const limit = terms.limit || parameters.options.limit; +// parameters.options.limit = +// !limit || limit < 1 || limit > maxDocuments ? maxDocuments : limit; + +// // console.log(JSON.stringify(parameters, 2)); + +// return parameters; +// }; + +// if (existingCollection) { +// Collections[existingCollectionIndex] = existingCollection; +// } else { +// Collections.push(collection); +// } + +// return collection; +// }; + +// //register collection creation hook for each collection +// function registerCollectionCallback(typeName) { +// registerCallback({ +// name: `${typeName}.collection`, +// iterator: { schema: "the schema of the collection" }, +// properties: [ +// { schema: "The schema of the collection" }, +// { +// validationErrors: +// "An Object that can be used to accumulate validation errors", +// }, +// ], +// runs: "sync", +// returns: "schema", +// description: "Modifies schemas on collection creation", +// }); +// } + +// //register colleciton creation hook +// registerCallback({ +// name: "*.collection", +// iterator: { schema: "the schema of the collection" }, +// properties: [ +// { schema: "The schema of the collection" }, +// { +// validationErrors: +// "An object that can be used to accumulate validation errors", +// }, +// ], +// runs: "sync", +// returns: "schema", +// description: "Modifies schemas on collection creation", +// }); + +// // generate foo_intl fields +// export function addIntlFields(schema) { +// Object.keys(schema).forEach((fieldName) => { +// const fieldSchema = schema[fieldName]; +// if (isIntlField(fieldSchema) && !schemaHasIntlField(schema, fieldName)) { +// // remove `intl` to avoid treating new _intl field as a field to internationalize +// // eslint-disable-next-line no-unused-vars +// const { intl, ...propertiesToCopy } = schema[fieldName]; + +// schema[`${fieldName}_intl`] = { +// ...propertiesToCopy, // copy properties from regular field +// hidden: true, +// type: Array, +// isIntlData: true, +// }; + +// delete schema[`${fieldName}_intl`].intl; + +// schema[`${fieldName}_intl.$`] = { +// type: getIntlString(), +// }; + +// // if original field is required, enable custom validation function instead of `optional` property +// if (!schema[fieldName].optional) { +// schema[`${fieldName}_intl`].optional = true; +// schema[`${fieldName}_intl`].custom = validateIntlField; +// } + +// // make original non-intl field optional +// schema[fieldName].optional = true; +// } +// }); +// return schema; +// } + +export default extendModel; diff --git a/packages/graphql/extendModel.ts b/packages/graphql/extendModel.ts index 05bed648..5acb82dc 100644 --- a/packages/graphql/extendModel.ts +++ b/packages/graphql/extendModel.ts @@ -5,27 +5,13 @@ * * End user is supposed to use only the "createGraphqlModel" function */ -import { - VulcanGraphqlModel, - MutationCallbackDefinitions, - VulcanGraphqlSchema, - VulcanGraphqlModelServer, - VulcanGraphqlSchemaServer, -} from "./typings"; +import { VulcanGraphqlModel, VulcanGraphqlSchema } from "./typings"; import { VulcanModel, createModel, CreateModelOptions } from "@vulcanjs/model"; import { getDefaultFragmentText, getDefaultFragmentName, } from "./fragments/defaultFragment"; import { camelCaseify, Merge } from "@vulcanjs/utils"; -import { - MutationResolverDefinitions, - QueryResolverDefinitions, -} from "./server/typings"; -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) @@ -37,7 +23,7 @@ import { * 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 GraphqlModelOptions { +export interface GraphqlModelOptions { 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) } @@ -54,17 +40,6 @@ export interface GraphqlModelOptionsShared extends GraphqlModelOptions { /** 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 GraphqlModelOptionsServer extends GraphqlModelOptions { - /** 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; -} // Reusable model extension function const extendModel = @@ -112,58 +87,12 @@ const extendModel = return finalModel; }; -/** - * Adds server-only fields as well - * @param options - * @returns - */ -export const extendModelServer = - ( - options: GraphqlModelOptionsServer - ) /*: 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; - }; - export interface CreateGraphqlModelOptionsShared extends CreateModelOptions { // 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: GraphqlModelOptionsShared; } -export interface CreateGraphqlModelOptionsServer - extends CreateModelOptions { - graphql: GraphqlModelOptionsServer; -} /** * Let's you create a full-fledged graphql model * @@ -181,21 +110,6 @@ export const createGraphqlModel = ( return model; }; -/** - * @server-only - */ -export const createGraphqlModelServer = ( - options: 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/index.ts b/packages/graphql/index.ts index 94513138..7fb173a8 100644 --- a/packages/graphql/index.ts +++ b/packages/graphql/index.ts @@ -4,7 +4,3 @@ export * from "./extendModel"; export * from "./fragments/defaultFragment"; export * from "./fragments/typings"; export * from "./fragments/utils"; -// TODO: splint client and server export -export * from "./server/apolloSchema"; -export * from "./server/typings"; -export * from "./server/resolvers"; diff --git a/packages/graphql/package.json b/packages/graphql/package.json index cf07c32a..6e9c85aa 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -21,7 +21,8 @@ "deepmerge": "^4.2.2", "graphql-date": "^1.0.3", "graphql-type-json": "^0.3.2", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "moment-timezone": "^0.5.33" }, "peerDependencies": { "apollo-server": "^2.18.2", @@ -34,7 +35,9 @@ }, "scripts": { "test": "echo \"Error: run tests from root\" && exit 1", - "build": "webpack --config ./webpack.config.js" + "build:common": "webpack --config ./webpack.config.js", + "build:server": "webpack --config ./webpack.config.server.js", + "build": "yarn run build:common && yarn run build:server" }, "bugs": { "url": "https://github.com/VulcanJS/vulcan-npm/issues" diff --git a/packages/graphql/server/index.ts b/packages/graphql/server/index.ts new file mode 100644 index 00000000..ad790f8a --- /dev/null +++ b/packages/graphql/server/index.ts @@ -0,0 +1,9 @@ +// Exports will be available at "@vulcanjs//server" + +// always reexport shared exports +export * from "../index"; +// server-only +export * from "../extendModel.server"; +export * from "./apolloSchema"; +export * from "./typings"; +export * from "./resolvers"; diff --git a/packages/graphql/server/resolvers/index.ts b/packages/graphql/server/resolvers/index.ts index a01b033d..1a8663f6 100644 --- a/packages/graphql/server/resolvers/index.ts +++ b/packages/graphql/server/resolvers/index.ts @@ -3,4 +3,5 @@ export * from "./defaultMutationResolvers"; export * from "./typings"; export * from "./connector"; export * from "./mutators"; +export * from "./utilityResolvers"; export * from "./context"; diff --git a/packages/graphql/server/resolvers/utilityResolvers.ts b/packages/graphql/server/resolvers/utilityResolvers.ts new file mode 100644 index 00000000..cb8a176a --- /dev/null +++ b/packages/graphql/server/resolvers/utilityResolvers.ts @@ -0,0 +1,14 @@ +import moment from "moment-timezone"; + +export const formattedDateResolver = (fieldName) => { + return async (document = {}, args: any = {}, context: any = {}) => { + const { format } = args; + const { timezone /*= getSetting("timezone")*/ } = context; + if (!document[fieldName]) return; + let m = moment(document[fieldName]); + if (timezone) { + m = m.tz(timezone); + } + return format === "ago" ? m.fromNow() : m.format(format); + }; +}; diff --git a/packages/graphql/tsconfig.build.json b/packages/graphql/tsconfig.build.json index 7c7fdaf8..e33a1427 100644 --- a/packages/graphql/tsconfig.build.json +++ b/packages/graphql/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "include": ["./index.ts"] + "include": ["./index.ts", "./server/index.ts"] } diff --git a/packages/graphql/webpack.config.server.js b/packages/graphql/webpack.config.server.js new file mode 100644 index 00000000..d833f022 --- /dev/null +++ b/packages/graphql/webpack.config.server.js @@ -0,0 +1,10 @@ +const merge = require("webpack-merge"); +const path = require("path"); +const baseConfig = require("../../webpack/webpack.config.base.server.prod"); +//const merge = require('webpack-merge') +module.exports = merge(baseConfig, { + entry: path.resolve(__dirname, "./server/index.ts"), + output: { + path: path.resolve(__dirname, "dist/server"), + }, +}); diff --git a/packages/mongo/client/index.ts b/packages/mongo/client/index.ts new file mode 100644 index 00000000..b1b3d5e1 --- /dev/null +++ b/packages/mongo/client/index.ts @@ -0,0 +1,4 @@ +// Exports will be available at "@vulcanjs/mongo/client" +// Reexport the "filterFunction" that can be reused by Mingo client-side +// TODO: it may still leak mongoose client-side because of the typings, we need to check how to solve this +export * from "../mongoParams"; diff --git a/packages/mongo/connector.ts b/packages/mongo/connector.ts index 45fa41d6..6f0c14cb 100644 --- a/packages/mongo/connector.ts +++ b/packages/mongo/connector.ts @@ -1,4 +1,4 @@ -import { Connector } from "@vulcanjs/graphql"; +import { Connector } from "@vulcanjs/graphql/server"; import { VulcanModel } from "@vulcanjs/model"; // Compute a Mongo selector import { filterFunction } from "./mongoParams"; diff --git a/packages/mongo/index.ts b/packages/mongo/index.ts index 50280630..aebc15ed 100644 --- a/packages/mongo/index.ts +++ b/packages/mongo/index.ts @@ -1,2 +1,3 @@ +// Exports will be available at "@vulcanjs/mongo" export * from "./mongoParams"; export * from "./connector"; diff --git a/packages/mongo/package.json b/packages/mongo/package.json index 8a8e48d1..4b7beda4 100644 --- a/packages/mongo/package.json +++ b/packages/mongo/package.json @@ -15,7 +15,9 @@ }, "scripts": { "test": "echo \"Error: run tests from root\" && exit 1", - "build": "webpack --config ./webpack.config.js" + "build:client": "webpack --config ./webpack.config.client.js", + "build:server": "webpack --config ./webpack.config.server.js", + "build": "yarn run build:client && yarn run build:server" }, "bugs": { "url": "https://github.com/VulcanJS/vulcan-npm/issues" diff --git a/packages/mongo/tsconfig.build.json b/packages/mongo/tsconfig.build.json index 7c7fdaf8..73f1712a 100644 --- a/packages/mongo/tsconfig.build.json +++ b/packages/mongo/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "include": ["./index.ts"] + "include": ["./index.ts", "./client/index.ts"] } diff --git a/packages/mongo/webpack.config.client.js b/packages/mongo/webpack.config.client.js new file mode 100644 index 00000000..652dadbf --- /dev/null +++ b/packages/mongo/webpack.config.client.js @@ -0,0 +1,11 @@ +const merge = require("webpack-merge"); +const path = require("path"); +const baseConfig = require("../../webpack/webpack.config.base.client.prod"); + +module.exports = merge(baseConfig, { + entry: path.resolve(__dirname, "./client/index.ts"), + output: { + // NOTE: @vulcanjs/mongo is a server-first package, so to get isomorphic code you need to import @vulcanjs/mongo/client + path: path.resolve(__dirname, "dist"), + }, +}); diff --git a/packages/mongo/webpack.config.js b/packages/mongo/webpack.config.server.js similarity index 56% rename from packages/mongo/webpack.config.js rename to packages/mongo/webpack.config.server.js index af370835..788abd5c 100644 --- a/packages/mongo/webpack.config.js +++ b/packages/mongo/webpack.config.server.js @@ -1,10 +1,11 @@ const merge = require("webpack-merge"); const path = require("path"); -const baseConfig = require("../../webpack/webpack.config.base.common.prod"); -//const merge = require('webpack-merge') +const baseConfig = require("../../webpack/webpack.config.base.server.prod"); + module.exports = merge(baseConfig, { entry: path.resolve(__dirname, "./index.ts"), output: { + // NOTE: @vulcanjs/mongo is a server-first package, so here the default build is server path: path.resolve(__dirname, "dist"), }, }); diff --git a/packages/react-hooks/create.ts b/packages/react-hooks/create.ts index baf3e7bb..a1eaa39e 100644 --- a/packages/react-hooks/create.ts +++ b/packages/react-hooks/create.ts @@ -28,7 +28,7 @@ import { useMutation, MutationResult, gql, FetchResult } from "@apollo/client"; -import { filterFunction } from "@vulcanjs/mongo"; +import { filterFunction } from "@vulcanjs/mongo/client"; import { createClientTemplate, getModelFragment, diff --git a/packages/schema/package.json b/packages/schema/package.json index f2bc00ed..e9fa594d 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -26,7 +26,6 @@ }, "dependencies": { "lodash": "^4.17.21", - "moment-timezone": "^0.5.33", "simpl-schema": "^1.12.0" } } diff --git a/packages/schema/schema-utils.ts b/packages/schema/schema-utils.ts index 6bc3125b..df22e182 100644 --- a/packages/schema/schema-utils.ts +++ b/packages/schema/schema-utils.ts @@ -12,20 +12,6 @@ import _omit from "lodash/omit"; // import SimpleSchema from "simpl-schema"; import { VulcanDocument, VulcanFieldSchema, VulcanSchema } from "./typings"; -export const formattedDateResolver = (fieldName) => { - return async (document = {}, args: any = {}, context: any = {}) => { - const { format } = args; - const { timezone /*= getSetting("timezone")*/ } = context; - if (!document[fieldName]) return; - const moment = (await import("moment-timezone")).default; - let m = moment(document[fieldName]); - if (timezone) { - m = m.tz(timezone); - } - return format === "ago" ? m.fromNow() : m.format(format); - }; -}; - /* export const createSchema = (schema, apiSchema = {}, dbSchema = {}) => { let modifiedSchema = { ...schema }; diff --git a/webpack/webpack.config.base.client.prod.js b/webpack/webpack.config.base.client.prod.js index 6d74400c..77676489 100644 --- a/webpack/webpack.config.base.client.prod.js +++ b/webpack/webpack.config.base.client.prod.js @@ -2,9 +2,11 @@ const merge = require("webpack-merge"); const commonConfig = require("./webpack.config.base.common.prod"); // @see https://webpack.js.org/guides/typescript/ module.exports = merge(commonConfig, { - entry: "./index.client.ts", + entry: "./client/index.ts", + // Doesn't work (.d.ts files won't be renamed as expected) + //entry: "./index.client.ts", output: { - filename: "index.client.js", + // filename: "client.js", }, resolve: { mainFiles: ["index.client.ts", "index.client.js", "index.ts", "index.js"], diff --git a/webpack/webpack.config.base.common.prod.js b/webpack/webpack.config.base.common.prod.js index 36dfe944..4313c36a 100644 --- a/webpack/webpack.config.base.common.prod.js +++ b/webpack/webpack.config.base.common.prod.js @@ -47,6 +47,8 @@ module.exports = { loader: "ts-loader", options: { configFile: "tsconfig.build.json", + // will only include files imported by index.ts/index.server.ts etc. + onlyCompileBundledFiles: true, }, }, ], diff --git a/webpack/webpack.config.base.server.prod.js b/webpack/webpack.config.base.server.prod.js index 5776c7b3..533990fd 100644 --- a/webpack/webpack.config.base.server.prod.js +++ b/webpack/webpack.config.base.server.prod.js @@ -3,10 +3,14 @@ const path = require("path"); const commonConfig = require("./webpack.config.base.common.prod"); // @see https://webpack.js.org/guides/typescript/ module.exports = merge(commonConfig, { - entry: "./index.server.ts", + entry: "server/index.ts", + // Having an entry file would be nice but doesn't work, because you cannot rename the output declaration files (.d.ts) + // @see https://stackoverflow.com/questions/69403209/output-filename-with-ts-loader-do-not-rename-the-declaration-files + //entry: "./index.server.ts", target: "node", output: { - filename: "index.server.js", + // @see https://stackoverflow.com/questions/69403209/output-filename-with-ts-loader-do-not-rename-the-declaration-files + // filename: "server.js", }, resolve: { mainFiles: ["index.server.ts", "index.server.js", "index.ts", "index.js"],