소스 검색

+thenable, find, save, recursive slicing

Ivan Asmer 4 년 전
부모
커밋
5b6d0c8c01
2개의 변경된 파일241개의 추가작업 그리고 7개의 파일을 삭제
  1. 1 1
      package.json
  2. 240 6
      src/App.js

+ 1 - 1
package.json

@@ -29,5 +29,5 @@
       "last 1 safari version"
     ]
   },
-  "proxy": "http://localhost:4000/"
+  "proxy": "http://mo.ed.asmer.org.ua/"
 }

+ 240 - 6
src/App.js

@@ -67,7 +67,7 @@ async function createModels(gql, config={create: 'Upsert', update: 'Upsert', del
     let classes = {}
 
     const createClass = (name, fields) => {
-        if (!(name in classes)) 
+        if (!(name in classes)) {
             classes[name] = class {
                 constructor(data){
                     Object.assign(this, data)
@@ -77,6 +77,8 @@ async function createModels(gql, config={create: 'Upsert', update: 'Upsert', del
                     return fields
                 }
             }
+            Object.defineProperty(classes[name], 'name', {value: name})
+        }
         return classes[name]
     }
 
@@ -129,6 +131,233 @@ async function createModels(gql, config={create: 'Upsert', update: 'Upsert', del
     return classes
 }
 
+
+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
+                }
+
+                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){
+                    const gqlQuery = `
+                        query ${name}Find($query: String){
+                            ${name}Find(query: $query)
+                                ${projectionBuilder(type)}
+                        }
+                    `
+                    let result = await gql.request(gqlQuery, {query: JSON.stringify(query)})
+                    return result[name + 'Find'].map(entity => new classes[name](entity))
+                }
+
+            }
+            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`)))
+    console.log(classes)
+    classes.Good.find([{}]).then(goods => console.log(goods))
+}
+
 const dataReader = async () => {
     const { PriceItem } = await createModels(gql)
     let data            = await PriceItem.find({}, {limit: [10]})
@@ -159,15 +388,16 @@ function ModelGrid({model, rowHeight, gridHeight, overload, query, cellDoubleCli
     const jsonQuery = JSON.stringify(query)
 
     const [totalCount, setTotalCount] = useState()
+
     if (!totalCount || oldQuery !== jsonQuery) { 
-        debugger;
-        model.count(query).then(count => {debugger; setTotalCount(count); setOldQuery(jsonQuery)})
+        model && model.count(query).then(count => { setTotalCount(count); setOldQuery(jsonQuery)})
     }
 
     const [gridContentRef, setGridContentRef] = useState()
     const [scroll, setScroll]                 = useState(0)
 
 
+
     const  onScreenFirstRowIndex = Math.floor(scroll/rowHeight)
     let skip                  =  onScreenFirstRowIndex  - (overloadedRowCount - onScreenRowCount)/2;
 
@@ -178,10 +408,12 @@ function ModelGrid({model, rowHeight, gridHeight, overload, query, cellDoubleCli
     const [records, setRecords] = useState()
     const cursorCalls = {skip: [skip], limit: [overloadedRowCount]}
 
+
+    if (!model) return <>No model</>;
+
     if (sort.length) cursorCalls.sort = [sort]
 
     if (!records || oldQuery !== jsonQuery) { 
-        debugger;
         model.find(query, cursorCalls)
                                             .then(records => (setRecords(records), setOldQuery(jsonQuery)))
     }
@@ -220,13 +452,15 @@ function App() {
     let [models, setModels] = useState()
     let [queryText,  setQueryText]  = useState('{manufacturerName: "TOYOTA"}')
     let [query,  setQuery]  = useState({})
-    models || createModels(gql).then(models => setModels(models))
+    models || createModels2(gql).then(models => setModels(models))
 
     console.log(query)
+    console.log('MODELS', models)
 
     return (
         <div className="App">
-            {models && <ModelGrid model={models.PriceItem} rowHeight={50} gridHeight={700} overload={5} query={query} 
+            {models && Object.entries(models).map(([key, model]) => <div>{key}</div>)}
+            {models && <ModelGrid model={models.jk} rowHeight={50} gridHeight={700} overload={5} query={query} 
                         cellDoubleClick={(name, text, _id) => setQueryText(`{${name}:\`${text}\`}`)}/> }
             <textarea onChange={e => setQueryText(e.target.value)} value={queryText} />
             <button onClick={() => setQuery(eval(`(${queryText})`))}>Run</button>