import React, {useState, useEffect} from 'react'; import logo from './logo.svg'; import './App.css'; import { GraphQLClient } from 'graphql-request'; let gql = new GraphQLClient("/graphql", {headers: localStorage.authToken ? {Authorization: 'Bearer '+localStorage.authToken} : {}}) //TODO: use graphql-tag to get types, or, at least detect query //for proper binding between model objects and result of gql query async function createModels(gql, config={create: 'Upsert', update: 'Upsert', delete: "Delete", findOne: "FindOne", find: 'Find', count: 'Count'}){ const getQuery = name => `query mutations{ __type(name: "${name}") { 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 } } } } } } } }` let { __type: { fields: mutations }} = await gql.request(getQuery('Mutation')) let { __type: { fields: queries }} = await gql.request(getQuery('Query')) console.log(mutations) console.log(queries) let classes = {} const createClass = (name, fields) => { if (!(name in classes)) { classes[name] = class { constructor(data){ Object.assign(this, data) } static get fields(){ return fields } } Object.defineProperty(classes[name], 'name', {value: name}) } return classes[name] } for (let query of queries){ let className = query.type.name || query.type.ofType.name if (className === 'Int'){ //TODO: fuck this className = query.name.match(new RegExp(`([A-Za-z]+)(${Object.values(config).join('|')})`))[1] } let classFields = query.type.fields || query.type.ofType && query.type.ofType.fields console.log(classFields) let _class = createClass(className, classFields) for (let [method, methodName] of Object.entries(config)){ if (!_class[method] && query.name.includes(methodName)){ let methods = { find(q, cursorCalls={}){ let queryText = `query FMFind($q:${query.args[0].type.name}){ ${query.name}(${query.args[0].name}: $q) {${classFields.filter(x => x.type.kind === 'SCALAR').map(x => x.name).join(',')}} } ` console.log(queryText, {q: JSON.stringify([q, cursorCalls])}) return gql.request(queryText, {q: JSON.stringify([q, cursorCalls])}).then(data =>{ return data[query.name].map(record => new _class(record)) }) }, findOne(query){ }, count(q, cursorCalls={}){ let queryText = `query FMFind($q:${query.args[0].type.name}){ ${query.name}(${query.args[0].name}: $q) } ` return gql.request(queryText, {q: JSON.stringify([q, cursorCalls])}).then(data =>{ return data[query.name] }) } } _class[method] = methods[method] } } } 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]}) return {data, PriceItem } } const Th = p =>
const Row = ({top, children}) =>