Ivan Asmer 4 years ago
parent
commit
53bebda84a
1 changed files with 249 additions and 0 deletions
  1. 249 0
      src/front-models.js

+ 249 - 0
src/front-models.js

@@ -0,0 +1,249 @@
+
+export default async function createModels2(gql, config={create: 'Upsert', update: 'Upsert', delete: "Delete",  findOne: "FindOne", find: 'Find', count: 'Count'}){
+    const universeQuery = `query universe{
+  __schema{
+     types {
+        name,
+        kind,
+        inputFields{
+          name,
+         type {
+            kind,
+            ofType{
+              name,
+                fields{
+                  name,
+                  type{
+                    name,
+                    kind
+                  }
+                }
+            }
+            name,
+            fields{
+              name,
+              type{
+                name,
+                kind
+              }
+            }
+          }
+          
+                
+        }
+        fields {
+          name,
+         type {
+            kind,
+            ofType{
+              name,
+                fields{
+                  name,
+                  type{
+                    name,
+                    kind
+                  }
+                }
+            }
+            name,
+            fields{
+              name,
+              type{
+                name,
+                kind
+              }
+            }
+          }
+          args{
+            name,
+            type{
+              name,
+                inputFields{
+                    name,
+                        type{
+                          name,
+                          kind,
+                          ofType{
+                            name
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+  }}
+  `
+
+    const universe = await gql.request(universeQuery)
+    console.log(universe)
+    const types  = []
+    const inputs = []
+    for (let type of universe.__schema.types){
+        if (!["Query", "Mutation"].includes(type.name) && type.kind === "OBJECT" && !type.name.startsWith('__')) types.push(type)
+        if (!["Query", "Mutation"].includes(type.name) && type.kind === "INPUT_OBJECT" && !type.name.startsWith('__')) inputs.push(type)
+    }
+    console.log(types, inputs)
+
+    let classes = {}
+
+    const inTypes  = name => types.find(type => type.name === name)
+    const inInputs = name => inputs.find(type => type.name === name)
+
+    const projectionBuilder = (type, allFields=true) => {
+        if (!allFields && type.fields[0].name !== '_id') allFields = true
+        if (allFields)
+            return '{' + 
+                type.fields.map(field => { 
+                    return field.name + ((field.type.kind === 'OBJECT' && (inTypes(field.type.name)) && projectionBuilder(inTypes(field.type.name), false)) ||
+                    (field.type.kind === 'LIST'   && (inTypes(field.type.ofType.name)) && projectionBuilder(inTypes(field.type.ofType.name), false)) || '')
+                })
+                .join(',') 
+                + '}'
+        else return `{_id}`
+    }
+
+    const identityMap = {}
+
+
+    const createClass = (name, type, input) => {
+        if (!(name in classes)) {
+            classes[name] = class {
+                constructor(data={}, empty = false){
+                    if (data._id && data._id in identityMap)
+                        return identityMap[data._id]
+
+                    this.populate(data)
+
+                    this.empty = empty
+
+                    if (this._id) identityMap[this._id] = this
+                }
+
+                populate(data){
+                    type.fields.forEach(({name, type:{ ofType, kind, name:otherName}}) => {
+                        ({SCALAR(){
+                            if (data && typeof data === 'object' && name in data) this[name] = data[name]
+                        },
+                        LIST(){
+                            const otherType = inTypes(ofType.name)
+                            if (otherType  && data[name])
+                                this[name] = data[name].map(otherEntity => new classes[otherType.name](otherEntity, otherEntity._id && Object.keys(otherEntity).length === 1))
+                            else if (data && typeof data === 'object' && name in data) this[name] = data[name]
+                        },
+                        OBJECT(){
+                            const otherType = inTypes(otherName)
+                            if (otherType && data[name])
+                                this[name] = new classes[otherType.name](data[name], data[name]._id && Object.keys(data[name]).length === 1)
+                            else if (data && typeof data === 'object' && name in data) this[name] = data[name]
+                        }})[kind].call(this)
+                    })
+                }
+
+                get empty(){
+                    return this.then
+                }
+
+                set empty(value){
+                    if (value){
+                        this.then = async (onFullfilled, onRejected) => {
+                            const gqlQuery = `
+                                query ${name}FindOne($query: String){
+                                    ${name}FindOne(query: $query)
+                                        ${projectionBuilder(type)}
+                                    
+                                }
+                            `
+                            const data = await gql.request(gqlQuery, {query: JSON.stringify([{_id: this._id}])})
+                            this.populate(data[name + 'FindOne'])
+                            this.empty = false
+                            const thenResult = onFullfilled(this)
+                            if (thenResult && typeof thenResult === 'object' && typeof thenResult.then === 'function'){
+                                return await thenResult
+                            }
+                            return thenResult
+                        }
+                    }
+                    else delete this.then
+                }
+
+                static get type(){
+                    return type
+                }
+
+                static get input(){
+                    return input
+                }
+
+                static get fields(){
+                    return this.type.fields
+                }
+
+                async save(){
+                    if (this.empty) throw new ReferenceError('Cannot save empty object')
+                    const data = {}
+                    input.inputFields.forEach(({name, type:{ ofType, kind, name:otherName}}) => {
+                        ({SCALAR(){
+                            if (this[name]) data[name] = this[name] 
+                        },
+                        LIST(){
+                            const otherType = inInputs(ofType.name)
+                            if (otherType  && this[name] && this[name] instanceof Array)
+                                data[name] = this[name].map(otherEntity => (otherEntity._id ? {_id: otherEntity._id} : otherEntity))
+                        },
+                        INPUT_OBJECT(){
+                            const otherType = inInputs(otherName)
+                            if (otherType && this[name] && typeof this[name] === 'object')
+                                data[name] = (this[name]._id ? {_id: this[name]._id} : this[name])
+                        }})[kind].call(this)
+                    })
+                    const gqlQuery = `
+                                mutation ${name}Upsert($data: ${input.name}){
+                                    ${name}Upsert(${name.toLowerCase()}: $data)
+                                        ${projectionBuilder(type)}
+                                }
+                    `
+                    let result = await gql.request(gqlQuery, {data})
+                    this.populate(result)
+                }
+
+                static async find(query={}, cursorCalls={}){
+                    const gqlQuery = `
+                        query ${name}Find($query: String){
+                            ${name}Find(query: $query)
+                                ${projectionBuilder(type)}
+                        }
+                    `
+                    let result = await gql.request(gqlQuery, {query: JSON.stringify([query, cursorCalls])})
+                    return result[name + 'Find'].map(entity => new classes[name](entity))
+                }
+
+                static async findOne(query){
+                    const gqlQuery = `
+                        query ${name}FindOne($query: String){
+                            ${name}FindOne(query: $query)
+                                ${projectionBuilder(type)}
+                        }
+                    `
+                    let result = await gql.request(gqlQuery, {query: JSON.stringify([query])})
+                    return result[name + 'FindOne']
+                }
+
+                static async count(query){
+                    const gqlQuery = `
+                        query ${name}Count($query: String){
+                            ${name}Count(query: $query)
+                        }
+                    `
+                    let result = await gql.request(gqlQuery, {query: JSON.stringify([query])})
+                    return result[name + 'Count']
+                }
+            }
+            Object.defineProperty(classes[name], 'name', {value: name})
+        }
+        return classes[name]
+    }
+
+    types.forEach((type) => createClass(type.name, type, inputs.find(input => input.name === `${type.name}Input`)))
+    return classes;
+}