front-models.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. export default async function createModels2(gql, config={create: 'Upsert', update: 'Upsert', delete: "Delete", findOne: "FindOne", find: 'Find', count: 'Count'}){
  2. const universeQuery = `query universe{
  3. __schema{
  4. types {
  5. name,
  6. kind,
  7. inputFields{
  8. name,
  9. type {
  10. kind,
  11. ofType{
  12. name,
  13. fields{
  14. name,
  15. type{
  16. name,
  17. kind
  18. }
  19. }
  20. }
  21. name,
  22. fields{
  23. name,
  24. type{
  25. name,
  26. kind
  27. }
  28. }
  29. }
  30. }
  31. fields {
  32. name,
  33. type {
  34. kind,
  35. ofType{
  36. name,
  37. fields{
  38. name,
  39. type{
  40. name,
  41. kind
  42. }
  43. }
  44. }
  45. name,
  46. fields{
  47. name,
  48. type{
  49. name,
  50. kind
  51. }
  52. }
  53. }
  54. args{
  55. name,
  56. type{
  57. name,
  58. inputFields{
  59. name,
  60. type{
  61. name,
  62. kind,
  63. ofType{
  64. name
  65. }
  66. }
  67. }
  68. }
  69. }
  70. }
  71. }
  72. }}
  73. `
  74. const universe = await gql.request(universeQuery)
  75. console.log(universe)
  76. const types = []
  77. const inputs = []
  78. for (let type of universe.__schema.types){
  79. if (!["Query", "Mutation"].includes(type.name) && type.kind === "OBJECT" && !type.name.startsWith('__')) types.push(type)
  80. if (!["Query", "Mutation"].includes(type.name) && type.kind === "INPUT_OBJECT" && !type.name.startsWith('__')) inputs.push(type)
  81. }
  82. console.log(types, inputs)
  83. let classes = {}
  84. const inTypes = name => types.find(type => type.name === name)
  85. const inInputs = name => inputs.find(type => type.name === name)
  86. const projectionBuilder = (type, allFields=2) => {
  87. if (!allFields && type.fields[0].name !== '_id') allFields++
  88. if (allFields)
  89. return '{' +
  90. type.fields.map(field => {
  91. return field.name + ((field.type.kind === 'OBJECT' && (inTypes(field.type.name)) && projectionBuilder(inTypes(field.type.name), allFields -1)) ||
  92. (field.type.kind === 'LIST' && (inTypes(field.type.ofType.name)) && projectionBuilder(inTypes(field.type.ofType.name), allFields -1)) || '')
  93. })
  94. .join(',')
  95. + '}'
  96. else return `{_id}`
  97. }
  98. const identityMap = {}
  99. let identityMapHits = 0
  100. let totalObjects = 0
  101. const createClass = (name, type, input) => {
  102. if (!(name in classes)) {
  103. classes[name] = class {
  104. constructor(data={}, empty = false){
  105. totalObjects ++
  106. if (data._id && (data._id in identityMap)){
  107. if (identityMap[data._id].empty && !empty){
  108. identityMap[data._id].empty = false
  109. identityMap[data._id].populate(data)
  110. }
  111. identityMapHits++
  112. console.log(identityMapHits, totalObjects, identityMapHits/totalObjects)
  113. return identityMap[data._id]
  114. }
  115. if (data._id) identityMap[data._id] = this
  116. this.empty = empty
  117. this.populate(data)
  118. }
  119. populate(data){
  120. type.fields.forEach(({name, type:{ ofType, kind, name:otherName}}) => {
  121. ({SCALAR(){
  122. if (data && typeof data === 'object' && name in data) this[name] = data[name]
  123. },
  124. LIST(){
  125. const otherType = inTypes(ofType.name)
  126. if (otherType && data[name])
  127. this[name] = data[name].map(otherEntity => new classes[otherType.name](otherEntity, otherEntity._id && Object.keys(otherEntity).length === 1))
  128. else if (data && typeof data === 'object' && name in data) this[name] = data[name]
  129. },
  130. OBJECT(){
  131. const otherType = inTypes(otherName)
  132. if (otherType && data[name])
  133. this[name] = new classes[otherType.name](data[name], data[name]._id && Object.keys(data[name]).length === 1)
  134. else if (data && typeof data === 'object' && name in data) this[name] = data[name]
  135. }})[kind].call(this)
  136. })
  137. }
  138. get empty(){
  139. return this.then
  140. }
  141. set empty(value){
  142. if (value){
  143. this.then = async (onFullfilled, onRejected) => {
  144. const gqlQuery = `
  145. query ${name}FindOne($query: String){
  146. ${name}FindOne(query: $query)
  147. ${projectionBuilder(type)}
  148. }
  149. `
  150. const data = await gql.request(gqlQuery, {query: JSON.stringify([{_id: this._id}])})
  151. this.populate(data[name + 'FindOne'])
  152. this.empty = false
  153. const thenResult = onFullfilled(this)
  154. if (thenResult && typeof thenResult === 'object' && typeof thenResult.then === 'function'){
  155. return await thenResult
  156. }
  157. return thenResult
  158. }
  159. }
  160. else delete this.then
  161. }
  162. static get type(){
  163. return type
  164. }
  165. static get input(){
  166. return input
  167. }
  168. static get fields(){
  169. return this.type.fields
  170. }
  171. static get inputs(){
  172. return this.input.inputFields
  173. }
  174. async save(){
  175. if (this.empty) throw new ReferenceError('Cannot save empty object')
  176. const data = {}
  177. input.inputFields.forEach(({name, type:{ ofType, kind, name:otherName}}) => {
  178. ({SCALAR(){
  179. if (this[name]) data[name] = this[name]
  180. },
  181. LIST(){
  182. const otherType = inInputs(ofType.name)
  183. if (otherType && this[name] && this[name] instanceof Array)
  184. data[name] = this[name].map(otherEntity => (otherEntity._id ? {_id: otherEntity._id} : otherEntity))
  185. },
  186. INPUT_OBJECT(){
  187. const otherType = inInputs(otherName)
  188. if (otherType && this[name] && typeof this[name] === 'object')
  189. data[name] = (this[name]._id ? {_id: this[name]._id} : this[name])
  190. else data[name] = this[name]
  191. }})[kind].call(this)
  192. })
  193. const gqlQuery = `
  194. mutation ${name}Upsert($data: ${input.name}){
  195. ${name}Upsert(${name.toLowerCase()}: $data)
  196. ${projectionBuilder(type)}
  197. }
  198. `
  199. let result = await gql.request(gqlQuery, {data})
  200. if (result._id && !(result._id in identityMap)){
  201. identityMap[result._id] = this
  202. }
  203. this.populate(result)
  204. }
  205. static invalidate(except=[]){
  206. if (except && except instanceof Array){
  207. }
  208. }
  209. static async find(query={}, cursorCalls={}){
  210. const gqlQuery = `
  211. query ${name}Find($query: String){
  212. ${name}Find(query: $query)
  213. ${projectionBuilder(type)}
  214. }
  215. `
  216. let result = await gql.request(gqlQuery, {query: JSON.stringify([query, cursorCalls])})
  217. return result[name + 'Find'].map(entity => new classes[name](entity))
  218. }
  219. static async findOne(query){
  220. const gqlQuery = `
  221. query ${name}FindOne($query: String){
  222. ${name}FindOne(query: $query)
  223. ${projectionBuilder(type)}
  224. }
  225. `
  226. let result = await gql.request(gqlQuery, {query: JSON.stringify([query])})
  227. return result[name + 'FindOne']
  228. }
  229. static async count(query){
  230. const gqlQuery = `
  231. query ${name}Count($query: String){
  232. ${name}Count(query: $query)
  233. }
  234. `
  235. let result = await gql.request(gqlQuery, {query: JSON.stringify([query])})
  236. return result[name + 'Count']
  237. }
  238. }
  239. Object.defineProperty(classes[name], 'name', {value: name})
  240. }
  241. return classes[name]
  242. }
  243. types.forEach((type) => createClass(type.name, type, inputs.find(input => input.name === `${type.name}Input`)))
  244. return classes;
  245. }