Skip to content

Commit

Permalink
expose Locales helper for legacy code in i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-burel committed Dec 3, 2021
1 parent 6f4bd3b commit cc6a006
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 86 deletions.
62 changes: 60 additions & 2 deletions packages/graphql/extendModel.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,34 @@
* End user is supposed to use only the "createGraphqlModel" function
*/
import {
VulcanGraphqlModel,
//VulcanGraphqlModel,
MutationCallbackDefinitions,
VulcanGraphqlSchema,
//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,
formattedDateResolver,
} from "./server/resolvers";
// don't forget to reexport common code
export * from "./extendModel";
import extendModel, { GraphqlModelOptions } from "./extendModel";
import cloneDeep from "lodash/cloneDeep";
import isEmpty from "lodash/isEmpty";
/**
* This type is meant to be exposed server side
*  @server-only
Expand All @@ -41,6 +46,58 @@ export interface GraphqlModelOptionsServer extends GraphqlModelOptions {
callbacks?: MutationCallbackDefinitions;
}

/**
* Add formattedDateResolver and other server specific fields
* @param schema
* @returns
*/
const extendSchemaServer = (
schema: VulcanGraphqlSchemaServer
): VulcanGraphqlSchemaServer => {
// if this is a date field, and fieldFormatted doesn't already exist in the schema
// or as a resolveAs field, then add fieldFormatted to apiSchema
// This code previously lived in "createSchema" function
const extendedSchema = cloneDeep(schema);
const apiSchema: any = {};
Object.keys(schema).forEach((fieldName) => {
const field = schema[fieldName];
const { arrayItem, type, canRead } = field;
const formattedFieldName = `${fieldName}Formatted`;
if (Array.isArray(field.resolveAs)) return extendedSchema;
if (
type === Date &&
!schema[formattedFieldName] &&
// TODO: will work only if resolveAs is not an array
field?.resolveAs?.fieldName !== formattedFieldName
) {
// TODO: apiSchema typing is slightly different from normal schema, we need to figure this out
apiSchema[formattedFieldName] = {
typeName: "String",
canRead,
arguments: 'format: String = "YYYY/MM/DD"',
resolver: formattedDateResolver(fieldName),
};
}
});
// if apiSchema contains fields, copy them over to main schema
// TODO: we could do it in one stap now
if (!isEmpty(apiSchema)) {
Object.keys(apiSchema).forEach((fieldName) => {
const field = apiSchema[fieldName];
const { canRead = ["guests"], description, ...resolveAs } = field;
extendedSchema[fieldName] = {
type: Object,
optional: true,
apiOnly: true,
canRead,
description,
resolveAs,
};
});
}
return extendedSchema;
};

/**
* Adds server-only fields as well
* @param options
Expand Down Expand Up @@ -79,6 +136,7 @@ export const extendModelServer =
mutationResolvers: mutationResolvers ? mutationResolvers : undefined,
queryResolvers: queryResolvers ? queryResolvers : undefined,
};
finalModel.schema = extendSchemaServer(finalModel.schema);
const name = model.name;
return finalModel;
};
Expand Down
16 changes: 15 additions & 1 deletion packages/i18n/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,23 @@
"files": [
"dist/"
],
"exports": {
".": "./dist/index.js",
"./server": "./dist/server/index.js"
},
"types": "./dist/index.d.ts",
"typesVersions": {
"*": {
"server": [
"./dist/server/index.d.ts"
]
}
},
"scripts": {
"test": "echo \"Error: no test specified\" && 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"
},
"author": "",
"license": "MIT",
Expand Down
40 changes: 40 additions & 0 deletions packages/i18n/server/graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* In the future we might want to standardize those exports
* @see https://github.com/VulcanJS/vulcan-next/issues/9
*/
import { getLocale, getStrings } from "./locales";

//addGraphQLSchema(localeType);

const locale = async (root, { localeId }, context) => {
const locale = getLocale(localeId);
const strings = getStrings(localeId);
const localeObject = { ...locale, strings };
return localeObject;
};

const typeDefs = `type Locale {
id: String,
label: String
dynamic: Boolean
strings: JSON
}
type Query {
locale(localeId: String): Locale
}
`;
const resolvers = {
Query: { locale },
};

//addGraphQLQuery("locale(localeId: String): Locale");
//addGraphQLResolvers({ Query: { locale } });

/**
* To be merged in your own schema
*/
export const graphql = {
typeDefs,
resolvers,
};
1 change: 1 addition & 0 deletions packages/i18n/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./graphql";
44 changes: 44 additions & 0 deletions packages/i18n/server/locales.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Previously was defined in "intl"
*
* However we try to avoid this registry pattern as much as possible in Vulcan Next
*/

export interface LocaleType {
id: string;
}

export const Locales: Array<LocaleType> = [];

/**
*  @deprecated Instead we should expose a Locale graphql model
* @param locale
*/
export const registerLocale = (locale: LocaleType) => {
Locales.push(locale);
};

/**
* @deprecated
*/
export const getLocale = (localeId: string) => {
return Locales.find((locale) => locale.id === localeId);
};

export const Strings: { [key: string]: any } = {};

export const Domains = {};

export const addStrings = (localeId, strings) => {
if (typeof Strings[localeId] === "undefined") {
Strings[localeId] = {};
}
Strings[localeId] = {
...Strings[localeId],
...strings,
};
};

export const getStrings = (localeId) => {
return Strings[localeId];
};
10 changes: 10 additions & 0 deletions packages/i18n/webpack.config.server.js
Original file line number Diff line number Diff line change
@@ -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"),
},
});
83 changes: 82 additions & 1 deletion packages/model/createModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
import { VulcanModel, ModelPermissionsOptions } from "./typings";
import { VulcanSchema } from "@vulcanjs/schema";
import cloneDeep from "lodash/cloneDeep";

export type ExtendModelFunc<TExtended extends VulcanModel = VulcanModel> = (
model: VulcanModel
Expand All @@ -16,14 +17,94 @@ export interface CreateModelOptions<TSchema = VulcanSchema> {
permissions?: ModelPermissionsOptions;
extensions?: Array<ExtendModelFunc>;
}

/**
* Convert a schema to a valid schema
* => in Vulcan Next we correctly split apiSchema and dbSchema so no
* need of such a function
* +
* we try to have a unified syntax for schema
*
* We convert to a valid Simple-Schema only has a last resort when
* we actually need Simple-Schema (eg for validation)
*
* @deprecated Use a server model instead of API schema ; use a custom
* connector instead of dbSchema ; for other fields we parse them directly in createModel
*
* @param schema
* @param apiSchema
* @param dbSchema
* @returns
*/
export const createSchema = (
schema: VulcanSchema,
apiSchema = {},
dbSchema = {}
) => {
let modifiedSchema = cloneDeep(schema);

Object.keys(modifiedSchema).forEach((fieldName) => {
const field = schema[fieldName];
const { arrayItem, type, canRead } = field;

/*
// legacy
if (field.resolveAs) {
// backwards compatibility: copy resolveAs.type to resolveAs.typeName
if (!field.resolveAs.typeName) {
field.resolveAs.typeName = field.resolveAs.type;
}
}
// legacy
if (field.relation) {
// for now, "translate" new relation field syntax into resolveAs
const { typeName, fieldName, kind } = field.relation;
field.resolveAs = {
typeName,
fieldName,
relation: kind,
};
}
*/

// find any field with an `arrayItem` property defined and add corresponding
// `foo.$` array item field to schema
// TODO: not sure if we still need this
if (arrayItem) {
modifiedSchema[`${fieldName}.$`] = arrayItem;
}

// for added security, remove any API-related permission checks from db fields
/*
const filteredDbSchema = {};
const blacklistedFields = ["canRead", "canCreate", "canUpdate"];
Object.keys(dbSchema).forEach((dbFieldName) => {
filteredDbSchema[dbFieldName] = _omit(
dbSchema[dbFieldName],
blacklistedFields
);
});
*/
// add dbSchema *after* doing the apiSchema stuff so we are sure
// its fields are not exposed through the GraphQL API
//modifiedSchema = { ...modifiedSchema, ...filteredDbSchema };
});

return modifiedSchema; //new SimpleSchema(modifiedSchema);
};

// It can return a raw or an extended model type
export const createModel = <TModelDefinition extends VulcanModel>(
options: CreateModelOptions
): TModelDefinition => {
const { schema, name, extensions = [], permissions = {} } = options;

const model: VulcanModel = {
schema,
// TODO: ideally we shouldn't even need this parsing step
// however, this means correctly typing our schemas for arrays
// and improving the graphql pipeline
schema: createSchema(schema),
name,
permissions,
options,
Expand Down
Loading

0 comments on commit cc6a006

Please sign in to comment.