123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- const { buildSchema, GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLList, GraphQLSchema } = require('graphql');
- const ObjectID = require("mongodb").ObjectID;
- const bound = 100;
- function mmExpandSchema(gqlSchema, defaultQueryFields, defaultMutationFields, scoper){
- function scoperTest(query,cursorCalls={limit:[bound]}) {
- cursorCalls.limit || (cursorCalls.limit = [bound])
- if (!Number.isInteger(cursorCalls.limit[0]) || cursorCalls.limit[0] <= 0 || cursorCalls.limit[0] > bound) cursorCalls.limit = [bound];
- return [query, cursorCalls]
- }
- scoper=scoper || scoperTest
- const types = {}
- const _typeMap = gqlSchema.getTypeMap()
- const buildInTypes = ['Query', 'Mutation', 'ID', 'Float', "String", 'Int', 'Boolean',
- 'Query!', 'Mutation!', 'ID!', 'Float!', "String!", 'Int!', 'Boolean!',
- '[Query!]', '[Mutation!]', '[ID!]', '[Float!]', "[String!]", '[Int!]', '[Boolean!]',
- '[Query]', '[Mutation]', '[ID]', '[Float]', "[String]", '[Int]', '[Boolean]',
- ]
- async function argToSavables(arg, outputTypeName, Savable){
- console.log('argToSavables', arg)
- if (!arg) return
- if (!Savable.classes[outputTypeName]) return arg
- const entity = arg._id ? await Savable.m[outputTypeName].findOne({_id: ObjectID(arg._id)}) :
- new Savable.classes[outputTypeName]({})
- const {_id, ...data} = arg;
- const type = _typeMap[outputTypeName + 'Input']
- const fields = type.getFields()
- let changed = !_id
- for(let [fieldName, value] of Object.entries(data)){
- let typeName = fields[fieldName].type.toString()
- changed = true
- if (!buildInTypes.includes(typeName)){
- console.log('recursive', arg[fieldName], typeName)
- if (typeName[0] === '['){
- const nestedTypeName = typeName.slice(1,-6)
- console.log('array',nestedTypeName)
- entity[fieldName] = []
- if (value) for (let nestedArg of value){
- const nestedEntity = await argToSavables(nestedArg, nestedTypeName, Savable)
- entity[fieldName].push(nestedEntity)
- }
- }
- else {
- const nestedTypeName = typeName.slice(0,-5)
- console.log('one', nestedTypeName)
- entity[fieldName] = await argToSavables(value, nestedTypeName, Savable)
- }
- }
- else {
- entity[fieldName] = value
- }
- }
- changed && await entity.save()
- return entity
- }
- let queryFields = _typeMap.Query ? _typeMap.Query.getFields() : {}
- let mutationFields = _typeMap.Mutation ? _typeMap.Mutation.getFields() : {}
- for (let [typeName, type] of Object.entries(_typeMap))
- if (!buildInTypes.includes(typeName) &&
- !typeName.startsWith('__')){
- if (typeName.endsWith('Input')){
- let outputTypeName = typeName.substr(0, typeName.length - 'Input'.length)
- if (outputTypeName in _typeMap){
- types[outputTypeName] = type
- const queryUpdater = query => {
- const checkers = [
- function objectID(val){
- if (val && typeof val === 'string' && val.length == 24){
- try {
- const id = ObjectID(val)
- if (id.toString() === val) return id
- }
- catch (e){
- return val
- }
- }
- return val
- },
- function regexp(val){ //probably will be deprecated in future. Reason: interference with "/some string/" string and $regexp mongo ability
- if (val && typeof val === 'string' && val.startsWith('/') && val.endsWith('/')){
- console.log('regexp found' ,val )
- try {
- return new RegExp(val.slice(1, -1), 'i')
- }
- catch (e){
- return val
- }
- }
- return val
- },
- ]
- const checker = val => {
- const originalVal = val
- for (let lambda of checkers){
- val = lambda(val)
- }
- return val !== originalVal && val
- }
- const walker = obj =>{
- for (let [key, value] of Object.entries(obj)){
- if (key === '___owner') continue;
- let newValue;
- if (newValue = checker(value)) obj[key] = newValue;
- else if (value && typeof value === 'object'){
- obj[key] = walker(value)
- }
- }
- return obj
- }
-
- return walker(query)
- }
- const find = {
- type: GraphQLList(_typeMap[outputTypeName]),
- args: {query: {type: GraphQLString}},
- async resolve(root, args, context, info){
- //console.log(root, args, context, info)
- const Savable = context.models.SlicedSavable || context.models.Savable
- args = JSON.parse(args.query)
- queryUpdater(args[0])
- //console.log(args)
- let results = []
- for (let result of Savable.m[outputTypeName].find(...scoper(...args))){
- try {result = await result} catch (e) { break }
- results.push(result)
- }
- return results;
- }
- }
- queryFields[`${outputTypeName}Find`] = find
- const count = {
- type: GraphQLInt,
- args: {query: {type: GraphQLString}},
- async resolve(root, args, context, info){
- const Savable = context.models.SlicedSavable || context.models.Savable
- args = JSON.parse(args.query)
- queryUpdater(args[0])
- return await Savable.m[outputTypeName].count(...args)
- }
- }
- queryFields[`${outputTypeName}Count`] = count
- const findOne = {
- type: _typeMap[outputTypeName],
- args: {query: {type: GraphQLString}},
- async resolve(root, args, context, info){
- //console.log(root, args, context, info)
- const Savable = context.models.SlicedSavable || context.models.Savable
- args = JSON.parse(args.query)
- let [query] = args
- queryUpdater(query)
- console.log(query)
- let record = Savable.m[outputTypeName].findOne(query, ...args.slice(1))
- return record;
- }
- }
- queryFields[`${outputTypeName}FindOne`] = findOne
- const lowerCaseName = outputTypeName[0].toLowerCase() + outputTypeName.slice(1)
- const del = {
- type: _typeMap[outputTypeName],
- args: {[lowerCaseName]: {type: _typeMap[typeName]}},
- async resolve(root, args, context, info){
- //console.log(root, args, context, info)
- const Savable = context.models.SlicedSavable || context.models.Savable
- const arg = args[lowerCaseName]
- let entity;
- if (! ('_id' in arg)){
- return null;
- }
- try{
- entity = await Savable.m[outputTypeName].findOne({_id: ObjectID(arg._id)})
- }
- catch (e){
- console.log(e)
- }
- if (entity){
- let copy = {...entity}
- await entity.delete()
- return copy;
- }
- return entity;
- }
- }
- mutationFields[`${outputTypeName}Delete`] = del
- const upsert = {
- type: _typeMap[outputTypeName],
- args: {[lowerCaseName]: {type: _typeMap[typeName]}},
- async resolve(root, args, context, info){
- //console.log(root, args, context, info)
- const Savable = context.models.SlicedSavable || context.models.Savable
- const arg = args[lowerCaseName]
- const entity = argToSavables(args[lowerCaseName], outputTypeName, Savable)
- return entity;
- }
- }
- mutationFields[`${outputTypeName}Upsert`] = upsert
- }
- }
- }
- let newQuery = new GraphQLObjectType({name: 'Query', fields: {...defaultQueryFields, ...queryFields}})
- let newMutation = new GraphQLObjectType({name: 'Mutation', fields: {...defaultMutationFields, ...mutationFields}})
- let newSchema = new GraphQLSchema({query: newQuery, mutation: newMutation})
- return newSchema;
- }
- module.exports = mmExpandSchema
|