expand.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. const { buildSchema, GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLList, GraphQLSchema } = require('graphql');
  2. const ObjectID = require("mongodb").ObjectID;
  3. const bound = 100;
  4. function mmExpandSchema(gqlSchema, defaultQueryFields, defaultMutationFields, scoper){
  5. function scoperTest(query,cursorCalls={limit:[bound]}) {
  6. cursorCalls.limit || (cursorCalls.limit = [bound])
  7. if (!Number.isInteger(cursorCalls.limit[0]) || cursorCalls.limit[0] <= 0 || cursorCalls.limit[0] > bound) cursorCalls.limit = [bound];
  8. return [query, cursorCalls]
  9. }
  10. scoper=scoper || scoperTest
  11. const types = {}
  12. const _typeMap = gqlSchema.getTypeMap()
  13. const buildInTypes = ['Query', 'Mutation', 'ID', 'Float', "String", 'Int', 'Boolean',
  14. 'Query!', 'Mutation!', 'ID!', 'Float!', "String!", 'Int!', 'Boolean!',
  15. '[Query!]', '[Mutation!]', '[ID!]', '[Float!]', "[String!]", '[Int!]', '[Boolean!]',
  16. '[Query]', '[Mutation]', '[ID]', '[Float]', "[String]", '[Int]', '[Boolean]',
  17. ]
  18. async function argToSavables(arg, outputTypeName, Savable){
  19. console.log('argToSavables', arg)
  20. if (!arg) return
  21. if (!Savable.classes[outputTypeName]) return arg
  22. const entity = arg._id ? await Savable.m[outputTypeName].findOne({_id: ObjectID(arg._id)}) :
  23. new Savable.classes[outputTypeName]({})
  24. const {_id, ...data} = arg;
  25. const type = _typeMap[outputTypeName + 'Input']
  26. const fields = type.getFields()
  27. let changed = !_id
  28. for(let [fieldName, value] of Object.entries(data)){
  29. let typeName = fields[fieldName].type.toString()
  30. changed = true
  31. if (!buildInTypes.includes(typeName)){
  32. console.log('recursive', arg[fieldName], typeName)
  33. if (typeName[0] === '['){
  34. const nestedTypeName = typeName.slice(1,-6)
  35. console.log('array',nestedTypeName)
  36. entity[fieldName] = []
  37. if (value) for (let nestedArg of value){
  38. const nestedEntity = await argToSavables(nestedArg, nestedTypeName, Savable)
  39. entity[fieldName].push(nestedEntity)
  40. }
  41. }
  42. else {
  43. const nestedTypeName = typeName.slice(0,-5)
  44. console.log('one', nestedTypeName)
  45. entity[fieldName] = await argToSavables(value, nestedTypeName, Savable)
  46. }
  47. }
  48. else {
  49. entity[fieldName] = value
  50. }
  51. }
  52. changed && await entity.save()
  53. return entity
  54. }
  55. let queryFields = _typeMap.Query ? _typeMap.Query.getFields() : {}
  56. let mutationFields = _typeMap.Mutation ? _typeMap.Mutation.getFields() : {}
  57. for (let [typeName, type] of Object.entries(_typeMap))
  58. if (!buildInTypes.includes(typeName) &&
  59. !typeName.startsWith('__')){
  60. if (typeName.endsWith('Input')){
  61. let outputTypeName = typeName.substr(0, typeName.length - 'Input'.length)
  62. if (outputTypeName in _typeMap){
  63. types[outputTypeName] = type
  64. const queryUpdater = query => {
  65. const checkers = [
  66. function objectID(val){
  67. if (val && typeof val === 'string' && val.length == 24){
  68. try {
  69. const id = ObjectID(val)
  70. if (id.toString() === val) return id
  71. }
  72. catch (e){
  73. return val
  74. }
  75. }
  76. return val
  77. },
  78. function regexp(val){ //probably will be deprecated in future. Reason: interference with "/some string/" string and $regexp mongo ability
  79. if (val && typeof val === 'string' && val.startsWith('/') && val.endsWith('/')){
  80. console.log('regexp found' ,val )
  81. try {
  82. return new RegExp(val.slice(1, -1), 'i')
  83. }
  84. catch (e){
  85. return val
  86. }
  87. }
  88. return val
  89. },
  90. ]
  91. const checker = val => {
  92. const originalVal = val
  93. for (let lambda of checkers){
  94. val = lambda(val)
  95. }
  96. return val !== originalVal && val
  97. }
  98. const walker = obj =>{
  99. for (let [key, value] of Object.entries(obj)){
  100. if (key === '___owner') continue;
  101. let newValue;
  102. if (newValue = checker(value)) obj[key] = newValue;
  103. else if (value && typeof value === 'object'){
  104. obj[key] = walker(value)
  105. }
  106. }
  107. return obj
  108. }
  109. return walker(query)
  110. }
  111. const find = {
  112. type: GraphQLList(_typeMap[outputTypeName]),
  113. args: {query: {type: GraphQLString}},
  114. async resolve(root, args, context, info){
  115. //console.log(root, args, context, info)
  116. const Savable = context.models.SlicedSavable || context.models.Savable
  117. args = JSON.parse(args.query)
  118. queryUpdater(args[0])
  119. //console.log(args)
  120. let results = []
  121. for (let result of Savable.m[outputTypeName].find(...scoper(...args))){
  122. try {result = await result} catch (e) { break }
  123. results.push(result)
  124. }
  125. return results;
  126. }
  127. }
  128. queryFields[`${outputTypeName}Find`] = find
  129. const count = {
  130. type: GraphQLInt,
  131. args: {query: {type: GraphQLString}},
  132. async resolve(root, args, context, info){
  133. const Savable = context.models.SlicedSavable || context.models.Savable
  134. args = JSON.parse(args.query)
  135. queryUpdater(args[0])
  136. return await Savable.m[outputTypeName].count(...args)
  137. }
  138. }
  139. queryFields[`${outputTypeName}Count`] = count
  140. const findOne = {
  141. type: _typeMap[outputTypeName],
  142. args: {query: {type: GraphQLString}},
  143. async resolve(root, args, context, info){
  144. //console.log(root, args, context, info)
  145. const Savable = context.models.SlicedSavable || context.models.Savable
  146. args = JSON.parse(args.query)
  147. let [query] = args
  148. queryUpdater(query)
  149. console.log(query)
  150. let record = Savable.m[outputTypeName].findOne(query, ...args.slice(1))
  151. return record;
  152. }
  153. }
  154. queryFields[`${outputTypeName}FindOne`] = findOne
  155. const lowerCaseName = outputTypeName[0].toLowerCase() + outputTypeName.slice(1)
  156. const del = {
  157. type: _typeMap[outputTypeName],
  158. args: {[lowerCaseName]: {type: _typeMap[typeName]}},
  159. async resolve(root, args, context, info){
  160. //console.log(root, args, context, info)
  161. const Savable = context.models.SlicedSavable || context.models.Savable
  162. const arg = args[lowerCaseName]
  163. let entity;
  164. if (! ('_id' in arg)){
  165. return null;
  166. }
  167. try{
  168. entity = await Savable.m[outputTypeName].findOne({_id: ObjectID(arg._id)})
  169. }
  170. catch (e){
  171. console.log(e)
  172. }
  173. if (entity){
  174. let copy = {...entity}
  175. await entity.delete()
  176. return copy;
  177. }
  178. return entity;
  179. }
  180. }
  181. mutationFields[`${outputTypeName}Delete`] = del
  182. const upsert = {
  183. type: _typeMap[outputTypeName],
  184. args: {[lowerCaseName]: {type: _typeMap[typeName]}},
  185. async resolve(root, args, context, info){
  186. //console.log(root, args, context, info)
  187. const Savable = context.models.SlicedSavable || context.models.Savable
  188. const arg = args[lowerCaseName]
  189. const entity = argToSavables(args[lowerCaseName], outputTypeName, Savable)
  190. return entity;
  191. }
  192. }
  193. mutationFields[`${outputTypeName}Upsert`] = upsert
  194. }
  195. }
  196. }
  197. let newQuery = new GraphQLObjectType({name: 'Query', fields: {...defaultQueryFields, ...queryFields}})
  198. let newMutation = new GraphQLObjectType({name: 'Mutation', fields: {...defaultMutationFields, ...mutationFields}})
  199. let newSchema = new GraphQLSchema({query: newQuery, mutation: newMutation})
  200. return newSchema;
  201. }
  202. module.exports = mmExpandSchema