@blitznocode/blitz-orm 0.6.4 → 0.7.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +3326 -37
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3305 -35
- package/dist/index.mjs.map +1 -0
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,36 +1,3306 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
${y}`})(),d=s.dbConnectors[0].id,a=c.typeDB.get(d)?.session,w=c.typeDB.get(d)?.client;if(!a){console.log("Session Status: ","NO SESSION");return}a.close();let[{dbName:b}]=s.dbConnectors;await(await w.databases.get(b)).delete(),await w.databases.create(b);let p=await w.session(s.dbConnectors[0].dbName,SessionType.SCHEMA),u=await p.transaction(TransactionType.WRITE);await u.query.define(f),await u.commit(),await u.close();let h=await p.transaction(TransactionType.READ),o="match $a sub thing;";(await(await h.query.match(o)).collect()).forEach(async y=>{y.get("a");}),await h.close();};var wt=(s,r,c)=>c?r:`${s}\xB7${r}`,Et=s=>{let r=s.split("\xB7");return r[r.length-1]},X=(s,r)=>Object.values(Object.fromEntries(Object.entries(s).filter(([c,i])=>r(c,i))))[0],G=(s,r)=>Object.fromEntries(Object.entries(s).filter(([c,i])=>r(c,i))),Ft=s=>{let r=[],c=produce(s,f=>traverse(f,({key:d,value:a,meta:w})=>{if(w.depth===2&&(d&&(a.dataFields=a.dataFields?.map(b=>({...b,dbPath:wt(d,b.path,b.shared)}))),a.extends)){let b=f.entities[a.extends]||f.relations[a.extends];if(a.allExtends=[a.extends,...b.allExtends||[]],a.idFields=b.idFields?(a.idFields||[]).concat(b.idFields):a.idFields,a.dataFields=b.dataFields?(a.dataFields||[]).concat(b.dataFields.map(Q=>{let p=a.extends,u=s.entities[p]||s.relations[p];for(;!u.dataFields?.find(h=>h.path===Q.path);)p=u.extends,u=s.entities[p]||s.relations[p];return {...Q,dbPath:wt(p,Q.path,Q.shared)}})):a.dataFields,a.linkFields=b.linkFields?(a.linkFields||[]).concat(b.linkFields):a.linkFields,"roles"in b){let Q=a,p=b;Q.roles=Q.roles||{},Q.roles={...Q.roles,...p.roles},Object.keys(Q.roles).length===0&&(Q.roles={});}}},{traversalType:"breadth-first"}));return traverse(s,({key:f,value:d,meta:a})=>{if(f==="linkFields"){let b=(()=>{if(!a.nodePath)throw new Error("No path");let[p,u]=a.nodePath.split(".");return {thing:u,thingType:p==="entities"?"entity":p==="relations"?"relation":""}})(),Q=Array.isArray(d)?d.map(p=>({...p,...b})):[{...d,...b}];r.push(...Q);}}),produce(c,f=>traverse(f,({value:d,key:a,meta:w})=>{if(w.depth===2&&d.idFields&&!d.id){d.name=a;let b=()=>{if(w.nodePath?.split(".")[0]==="entities")return "entity";if(w.nodePath?.split(".")[0]==="relations")return "relation";throw new Error("Unsupported node attributes")};d.thingType=b(),d.computedFields=[],d.virtualFields=[],"roles"in d&&Object.entries(d.roles).forEach(([p,u])=>{u.playedBy=r.filter(h=>h.relation===a&&h.plays===p)||[],u.name=p;}),"linkFields"in d&&d.linkFields&&d.linkFields?.forEach(p=>{if(p.target==="relation"){p.oppositeLinkFieldsPlayedBy=[{plays:p.path,thing:p.relation,thingType:"relation"}];return}let u=r.filter(o=>o.relation===p.relation&&o.plays!==p.plays)||[];p.oppositeLinkFieldsPlayedBy=u;let{filter:h}=p;p.oppositeLinkFieldsPlayedBy=p.oppositeLinkFieldsPlayedBy.filter(o=>o.target==="role"),h&&Array.isArray(h)&&(p.oppositeLinkFieldsPlayedBy=p.oppositeLinkFieldsPlayedBy.filter(o=>h.some(n=>o.thing===n.$role)),p.oppositeLinkFieldsPlayedBy=p.oppositeLinkFieldsPlayedBy.filter(o=>h.some(n=>o.thing===n.$thing))),h&&!Array.isArray(h)&&(p.oppositeLinkFieldsPlayedBy=p.oppositeLinkFieldsPlayedBy.filter(o=>o.$role===h.$role),p.oppositeLinkFieldsPlayedBy=p.oppositeLinkFieldsPlayedBy.filter(o=>o.thing===h.$thing));});}if(typeof d=="object"&&"playedBy"in d){if([...new Set(d.playedBy?.map(b=>b.thing))].length>1)throw new Error(`Unsupported: roleFields can be only played by one thing. Role: ${a} path:${w.nodePath}`);if(d.playedBy.length===0)throw new Error(`Unsupported: roleFields should be played at least by one thing. Role: ${a}, path:${w.nodePath}`)}if(w.depth===4&&(d.default||d.computedValue)){let[b,Q]=w.nodePath?.split(".")||[];d.isVirtual?f[b][Q].virtualFields.push(d.path):f[b][Q].computedFields.push(d.path);}}))},D=(s,r)=>{if(r.$entity){if(!(r.$entity in s.entities))throw new Error(`Missing entity '${r.$entity}' in the schema`);return s.entities[r.$entity]}if(r.$relation){if(!(r.$relation in s.relations))throw new Error(`Missing relation '${r.$relation}' in the schema`);return s.relations[r.$relation]}throw new Error(`Wrong schema or query for ${JSON.stringify(r)}`)},z=(s,r)=>{let c=s.dataFields?.map(n=>n.path)||[],i=s.linkFields?.map(n=>n.path)||[],f="roles"in s?listify(s.roles,n=>n):[],d=[...c||[],...i||[],...f||[]],w=[...["$entity","$op","$id","$tempId","$bzId","$relation","$parentKey","$filter","$fields","$excludedFields"],...d];if(!r)return {fields:d,dataFields:c,roleFields:f,linkFields:i};let b=r.$fields?r.$fields.map(n=>{if(typeof n=="string")return n;if("$path"in n&&typeof n.$path=="string")return n.$path;throw new Error(" Wrongly structured query")}):listify(r,n=>n),Q=r.$filter?listify(r.$filter,n=>n.toString().startsWith("$")?void 0:n.toString()).filter(n=>n&&c?.includes(n)):[],p=r.$filter?listify(r.$filter,n=>n.toString().startsWith("$")?void 0:n.toString()).filter(n=>n&&[...f||[],...i||[]].includes(n)):[],u=[...b,...Q].filter(n=>!w.includes(n)).filter(n=>n),h=r.$filter?G(r.$filter,(n,e)=>Q.includes(n)):{},o=r.$filter?G(r.$filter,(n,e)=>p.includes(n)):{};return {fields:d,dataFields:c,roleFields:f,linkFields:i,usedFields:b,usedLinkFields:i.filter(n=>b.includes(n)),usedRoleFields:f.filter(n=>b.includes(n)),unidentifiedFields:u,...Q.length?{localFilters:h}:{},...p.length?{nestedFilters:o}:{}}},Lt=(s,r)=>{let c=r.$localFilters&&listify(r.$localFilters,(f,d)=>`has ${s.dataFields?.find(w=>w.path===f)?.dbPath} '${d}'`);return c?.length?`, ${c.join(",")}`:""},U=s=>s!==null,tt=(s,r)=>Object.values(s).reduce((c,i)=>(i.extends===r&&c.push(i.name),c),[]);var ee=(s,r)=>s.map(i=>{let f=[...i.conceptMaps],a=i.owner.asThing().type.label.name,w=r.entities[a]?r.entities[a]:r.relations[a];if(!w.idFields)throw new Error(`No idFields defined for ${a}`);let b=r.entities[a]?"entity":"relation",Q=f.map(u=>{let h=u.get("attribute")?.asAttribute();return h?[Et(h.type.label.name),h.value]:[]}),p=Object.fromEntries(Q);return {...p,[`$${b}`]:a,$id:p[w.idFields[0]]}}),ne=(s,r,c)=>s.flatMap(f=>f.conceptMaps.map(a=>{let w=new Map;return r.forEach(b=>{let Q=a.get(`${b}_id`)?.asAttribute().value.toString(),p=a.get(b),u=p?.isEntity()?p.asEntity().type.label.name:p?.asRelation().type.label.name,o={id:Q,entityName:(()=>u?(c.entities[u]??c.relations[u]).allExtends?.includes(b)?b:u:b)()};Q&&w.set(b,o);}),w})),ie=(s,r,c)=>s.flatMap(f=>f.conceptMaps.map(a=>{let w=a.get(`${r}_id`)?.asAttribute().value.toString(),b=a.get(`${c}_id`)?.asAttribute().value.toString();return {ownerId:w,path:c,roleId:b}})),re=(s,r)=>{let c=listify(s.roles,(i,f)=>{if([...new Set(f.playedBy?.map(w=>w.thing))].length!==1)throw new Error("a role can be played by two entities throws the same relation");if(!f.playedBy)throw new Error("Role not being played by nobody");let d=f.playedBy[0].plays,a=tt(r.entities,d);return [d,...a]});return unique(flat(c))},J=async(s,r)=>{let{schema:c,bqlRequest:i,config:f,tqlRequest:d}=s,{rawTqlRes:a}=r;if(i){if(!a)throw new Error("TQL query not executed")}else throw new Error("BQL request not parsed");let{query:w}=i;if(!w){if(a.insertions?.length===0&&!d?.deletions){r.bqlRes={};return}let{mutation:h}=i;if(!h)throw new Error("TQL mutation not executed");let n=[...h.things,...h.edges].map(e=>{let y=a.insertions?.find(l=>l.get(`${e.$bzId}`))?.get(`${e.$bzId}`);if(e.$op==="create"||e.$op==="update"||e.$op==="link"){let l=y?.asThing().iid;return f.mutation?.noMetadata?mapEntries(e,(t,m)=>[t.toString().startsWith("$")?Symbol.for(t):t,m]):{$dbId:l,...e,[e.path]:e.$id}}if(e.$op==="delete"||e.$op==="unlink")return e;if(e.$op!=="match")throw new Error(`Unsupported op ${e.$op}`)}).filter(e=>e);r.bqlRes=n;return}if(!a.entity)throw new Error("TQL query not executed");let b=ee(a.entity,c),Q=a.relations?.map(h=>{let o=c.relations[h.relation],n=re(o,c),e=ne(h.conceptMapGroups,[...n,o.name],c);return {name:h.relation,links:e}}),p=a.roles?.map(h=>({name:h.ownerPath,links:ie(h.conceptMapGroups,h.ownerPath,h.path)})),u=r.cache||{entities:new Map,relations:new Map,roleLinks:new Map};b.forEach(h=>{let o=h.$entity||h.$relation,n=h.$id,e=u.entities.get(o)||new Map;e.set(n,{...h,$show:!0}),u.entities.set(o,e);}),Q?.forEach(h=>{let o=h.name,n=u.relations.get(o)||[];n.push(...h.links),u.relations.set(o,n),h.links.forEach(e=>{[...e.entries()].forEach(([y,{entityName:l,id:t}])=>{let m=u.entities.get(l)||new Map,g={[c.entities[l]?.thingType||c.relations[l].thingType]:l,$id:t,...m.get(t)};m.set(t,g),u.entities.set(l,m);});});}),p?.forEach(h=>{let o=c.relations[h.name]||c.entities[h.name];h.links.forEach(n=>{let e=u.roleLinks.get(n.ownerId)||{},y=e[n.path];y?isArray(y)?y.push(n.roleId):isString(y)&&y!==n.roleId&&(y=[y,n.roleId]):y=n.roleId,e[n.path]=y,u.roleLinks.set(n.ownerId,e),o.roles[n.path].playedBy?.forEach(l=>{let t=u.entities.get(l.thing)||new Map,m={$entity:l.thing,$id:n.roleId,...t.get(n.roleId)};t.set(n.roleId,m),u.entities.set(l.thing,t);});});}),r.cache=u;};var oe=/((\/\/.*$)|(\/\*.*\*\/))/gm,se=/(\s*async\s*|\s*function\s*)+/,ae=/(?:function\s*[^(\s]*\s*|\s*=>\s*|^\s*)\(([^)]*)\)/,le=/[ ,\n\r\t]+/,Rt=s=>{let r=s.toString().replace(oe,"").replace(se,"").trim(),c=ae.exec(r);if(!c)return [];let i;for(let f of c.slice(1))if(f){i=f;break}return i?(i=i.replace(/\{\s*/g,"").replace(/\s*\}/g,""),i.split(le).filter(f=>f.trim()!=="")):[]};var et=({currentThing:s,fieldSchema:r,mandatoryDependencies:c=!1})=>{if(!r||!r.default||!r.default.value)throw new Error("Virtual field: No field schema found, or wrongly configured");let i=r.default.value,d=Rt(i).filter(w=>!(w in s));if(c&&d.length)throw new Error(`Virtual field: Missing arguments ${d.join(", ")}`);return "default"in r?r.default?.value(s):void 0};var de=(s,r)=>s.length===1&&typeof s[0]!="string"&&s[0].$id===r?s[0]:s,Mt=s=>typeof s=="string"||!!s.$show,Tt=(s,r)=>produce(s,c=>traverse(c,({value:i})=>{if(Array.isArray(i)||typeof i!="object"||i===null)return;i.$tempId&&(i.$tempId=`_:${i.$tempId}`),i.$fields&&delete i.$fields,i.$filter&&delete i.$filter,i.$show&&delete i.$show,i.$bzId&&delete i.$bzId,r.query?.noMetadata&&(i.$entity||i.$relation)&&(delete i.$entity,delete i.$relation,delete i.$id),Object.getOwnPropertySymbols(i).forEach(d=>{delete i[d];}),i.$excludedFields&&(i.$excludedFields.forEach(d=>{delete i[d];}),delete i.$excludedFields);})),dt=(s,r,c,i)=>s.map(([f,d])=>{if(!r||!r.includes(f))return null;if(!c.$fields||c.$fields.includes(i))return f;let a=c.$fields.find(w=>isObject(w)&&w.$path===i);if(a){let w={...G(d,(Q,p)=>Q.startsWith("$"))},b=a.$fields?{...w,$fields:a.$fields}:w;if(a.$id){if(Array.isArray(a.$id))return a.$id.includes(f)?b:null;if(a.$id===f)return b}if(b.$fields&&b.$fields.includes("id")&&!b.$show){let Q="";return b.$fields.forEach(p=>{p==="id"?Q=b.$id:Q=b;}),Q}return b}return null}).filter(f=>f),pe=(s,r)=>{let c={};return r.forEach(i=>{c[i.$bzId]=i.$id;}),s.forEach(i=>{Object.keys(i).forEach(f=>{c[i[f]]&&f!=="$tempId"&&(i[f]=c[i[f]]);});}),s},fe=(s,r)=>{let c=r.map(f=>f.$bzId),i=!1;return s.forEach(f=>{Object.keys(f).forEach(d=>{c.includes(f[d])&&(i=!0);});}),i},qt=async(s,r)=>{let{bqlRequest:c,config:i,schema:f}=s,{cache:d}=r;if(!c)throw new Error("BQL request not parsed");let{query:a}=c;if(!a){let y=r.bqlRes[0]?r.bqlRes:[r.bqlRes],l=s.bqlRequest?.mutation?.things;if(fe(y,l)){let $=pe(y,l);r.bqlRes=$[1]?$:$[0];}let m=Tt(r.bqlRes,i);r.bqlRes=m;return}if(!d)return;let w="$entity"in a?a.$entity:a.$relation,b=d.entities.get(w.name);if(!b){r.bqlRes=null;return}let p=listify(a.$filter,y=>y).some(y=>w.dataFields?.find(l=>l.path===y)?.validations?.unique),u=!Array.isArray(c.query)&&(c.query?.$id&&!Array.isArray(c.query?.$id)||p);if(Array.isArray(s.rawBqlRequest))throw new Error("Query arrays not implemented yet");let h=b,o=[...h].length?[...h].map(([y,l])=>({...s.rawBqlRequest,$id:y})):s.rawBqlRequest,n=produce(o,y=>traverse(y,({value:l})=>{let t=l;if(!t?.$entity&&!t?.$relation)return;let m="$entity"in t?t.$entity:t.$relation;if(m){let $=Array.isArray(t.$id)?t.$id:[t.$id],g="$relation"in t?f.relations[t.$relation]:f.entities[t.$entity],{dataFields:k,roleFields:O,fields:F}=z(g);i.query?.returnNulls&&(t.$fields?t.$fields.map(A=>typeof A=="string"?A:A.$path):F).forEach(A=>{t[A]=null;});let q=d.entities.get(m);if(!q)return;[...q].forEach(([L,A])=>{if($.includes(L)){let R=t.$fields?t.$fields:k,{virtualFields:S}=g;S?.forEach(M=>{if(R?.includes(M)&&(t[M]===void 0||t[M]===null)){let P=g.dataFields?.find(x=>x.path===M),C=et({currentThing:A,fieldSchema:P});t[M]=C;}}),listify(A,(M,P)=>{M.startsWith("$")||R?.includes(M)&&(t[M]=P);});let E=d.roleLinks.get(L),B=t.$fields?t.$fields.filter(M=>typeof M=="string"):O,I=t.$fields?.filter(M=>typeof M=="object")?.map(M=>M.$path)||[];Object.entries(E||{}).forEach(([M,P])=>{if(![...B,...I].includes(M))return;if(!("roles"in g))throw new Error("No roles in schema");let C=Array.isArray(P)?[...new Set(P)]:[P],{cardinality:x,playedBy:v}=g.roles[M],j=[...new Set(v?.map(V=>V.thing))]?.flatMap(V=>{let Y=d.entities.get(V);return Y?dt([...Y],C,t,M):[]});if(j?.length){if(j.length===1&&j[0]===t.$id)return;if(x==="ONE"){t[M]=j[0];return}t[M]=j.filter(V=>typeof V=="string"||typeof V=="object"&&V?.$show);}});}});let T=g.linkFields;T&&T.forEach(L=>{let A=d.relations.get(L.relation),R=L.oppositeLinkFieldsPlayedBy;if(!A)return null;if(L.target==="relation"){let S=[...A].reduce((E,B)=>{let I=B.get(L.plays)?.id;if(I&&I===$[0]){let M=B.get(L.relation);if(!M)return E;E[M.entityName]||(E[M.entityName]=new Set),E[M.entityName].add(M.id);}return E},{});return Object.entries(S).map(([E,B])=>{let I=d.entities.get(E);if(!I)return null;let M=dt([...I],[...B.values()],t,L.path).filter(U).filter(Mt);if(M.length===0)return null;if(M&&M.length){if(L.cardinality==="ONE")return t[L.path]=M[0],null;t[L.path]=M;}return null}),null}return L.target==="role"&&R.forEach(S=>{if(!A)return;let E=[...A].reduce((B,I)=>{let M=I.get(L.plays)?.id;if(M&&M===$[0]){let P=I.get(S.plays);if(!P)return B;B[P.entityName]||(B[P.entityName]=new Set),B[P.entityName].add(P.id);}return B},{});Object.entries(E).forEach(([B,I])=>{let M=d.entities.get(B);if(!M)return;let P=dt([...M],[...I.values()],t,L.path).filter(U).filter(Mt);if(P.length!==0&&P&&P.length){if(L.cardinality==="ONE"){t[L.path]=P[0];return}t[L.path]=P;let C={},x=N=>(N.$path===L.path&&(C=N),N?.$fields?.forEach(x)),v="";a.$fields?.forEach(x),C&&(Array.isArray(C.$id)||(v=C.$id)),t[L.path]=de(P,v);}});}),null});}})),e=Tt(n,i);r.bqlRes=u?e[0]:e;};var nt=async s=>{let{schema:r,bqlRequest:c}=s;if(!c?.query)throw new Error("BQL query not parsed");let{query:i}=c,f="$entity"in i?i.$entity:i.$relation,d=f.defaultDBConnector.path||f.name;if(!d)throw new Error(`No thing path in ${JSON.stringify(f)}`);if(!f.idFields)throw new Error("No id fields");let[a]=f.idFields,w=`$${d}_id`,b=`, has ${a} ${w};`;i.$id&&(Array.isArray(i.$id)?b+=` ${w} like "${i.$id.join("|")}";`:b+=` ${w} "${i.$id}";`);let Q=Lt(f,i),p="roles"in f?listify(f.roles,(n,e)=>({path:n,var:`$${n}`,schema:e})):[],u=`match $${d} isa ${d}, has attribute $attribute ${Q} ${b} get; group $${d};`,h=p.map(n=>{if(!n.schema.playedBy||[...new Set(n.schema.playedBy?.map(y=>y.thing))].length!==1)throw new Error("Unsupported: Role played by multiple linkfields or none");let e=n.schema.playedBy[0].thing;return {path:n.path,owner:d,request:`match $${d} (${n.path}: ${n.var} ) isa ${d} ${b} ${n.var} isa ${e}, has id ${n.var}_id; get; group $${d};`}}),o=f.linkFields?.flatMap(n=>{let e=`$${n.plays}_id`,y=`, has ${a} ${e};`;i.$id&&(Array.isArray(i.$id)?y+=` ${e} like "${i.$id.join("|")}";`:y+=` ${e} "${i.$id}";`);let l=`match $${n.plays} isa ${d}${Q} ${y}`,t=n.target==="relation",$=`$${n.relation}`,g=`${t?$:""} (${n.plays}: $${n.plays}`,k=n.oppositeLinkFieldsPlayedBy.map(R=>t?null:`${R.plays}: $${R.plays}`),O=[g,...k].filter(R=>R).join(",");if(r.relations[n.relation]===void 0)throw new Error(`Relation ${n.relation} not found in schema`);let F=r.relations[n.relation].defaultDBConnector.path||n.relation,q=`) isa ${F};`,T=n.oppositeLinkFieldsPlayedBy.map(R=>`$${t?R.thing:R.plays} isa ${R.thing}, has id $${t?R.thing:R.plays}_id;`).join(" "),L=`get; group $${n.plays};`,A=`${l} ${O} ${q} ${T} ${L}`;return {relation:F,entity:d,request:A}});s.tqlRequest={entity:u,...h?.length?{roles:h}:{},...o?.length?{relations:o}:{}};};var Ot=async s=>{let{rawBqlRequest:r,schema:c}=s;if(!("$entity"in r)&&!("$relation"in r))throw new Error("No entity specified in query");let i=D(c,r);if(!i)throw new Error(`Thing '${r}' not found in schema`);let{unidentifiedFields:f,localFilters:d,nestedFilters:a}=z(i,r);if(f&&f.length>0)throw new Error(`Unknown fields: [${f.join(",")}] in ${JSON.stringify(r)}`);s.bqlRequest={query:{...s.rawBqlRequest,...i.thingType==="entity"?{$entity:i}:{},...i.thingType==="relation"?{$relation:i}:{},...d?{$localFilters:d}:{},...a?{$nestedFilters:a}:{}}};};var it=async(s,r)=>{let c=r.dbConnectors[0].id,i=s.typeDB.get(c)?.session,f=s.typeDB.get(c)?.client;if(!i||!i.isOpen()){if(!f)throw new Error("Client not found");i=await f.session(r.dbConnectors[0].dbName,SessionType.DATA),s.typeDB.set(c,{client:f,session:i});}return {client:f,session:i}};var rt=async(s,r)=>{let{dbHandles:c,bqlRequest:i,tqlRequest:f,config:d}=s;if(!i)throw new Error("BQL request not parsed");if(!f)throw new Error("TQL request not built");if(!f.entity)throw new Error("BQL request error, no entities");let{query:a}=i;if(!a)throw new Error("BQL request is not a query");let{session:w}=await it(c,d),b=await w.transaction(TransactionType.READ);if(!b)throw new Error("Can't create transaction");let Q=b.query.getGroup(f.entity),p=f.roles?.map(e=>({...e,stream:b.query.getGroup(e.request)})),u=f.relations?.map(e=>({...e,stream:b.query.getGroup(e.request)})),h=await Q.collect(),o=await Promise.all(p?.map(async e=>({path:e.path,ownerPath:e.owner,conceptMapGroups:await e.stream.collect()}))||[]),n=await Promise.all(u?.map(async e=>({relation:e.relation,entity:e.entity,conceptMapGroups:await e.stream.collect()}))||[]);await b.close(),r.rawTqlRes={entity:h,...o?.length&&{roles:o},...n?.length&&{relations:n}};};var pt=async(s,r)=>{let{bqlRequest:c,schema:i}=s,{cache:f}=r;if(!c)throw new Error("BQL request not parsed");if(!f)throw new Error("Cache not initialized");let{query:d}=c;if(!d)return;let{$fields:a}=d;if(!("$entity"in d)&&!("$relation"in d))throw new Error("Node attributes not supported");let w="$entity"in d?d.$entity:d.$relation;if(!a||!Array.isArray(a))return;let b=a.filter(o=>typeof o!="string"&&o.$path),Q=w.linkFields?.filter(o=>b.findIndex(n=>n.$path===o.path)!==-1).flatMap(o=>o.oppositeLinkFieldsPlayedBy)||[],p="roles"in w?listify(w.roles,(o,n)=>b.findIndex(e=>e.$path===o)!==-1?n:null).flatMap(o=>o?.playedBy).filter(o=>o):[],h=[...Q,...p]?.map(o=>{if(!o)return null;let{thing:n}=o,e=tt(i.entities,n);return [n,...e].map(y=>{let l=f.entities.get(y);if(!l)return null;let t=Array.from(l.values()).reduce((T,L)=>("$show"in L||T.push(L.$id),T),[]);if(t.length===0)return null;let m=i.entities[y]?{...i.entities[y],thingType:"entity"}:{...i.relations[y],thingType:"relation"},$=a.find(T=>typeof T=="object"&&T.$path===o.plays),g=$?.$id,k=g?Array.isArray(g)?g:[g]:[],O=$?.$filter,q={query:{$id:k.length?k.filter(T=>t.includes(T)):t,$fields:$?.$fields,...m.thingType==="entity"?{$entity:m}:{},...m.thingType==="relation"?{$relation:m}:{},...O?{$localFilters:O}:{}}};return {req:{...s,bqlRequest:q},res:r,pipeline:[nt,rt,J,pt]}}).filter(U)}).filter(U);if(h?.length)return flat(h)};var At=async s=>{let{bqlRequest:r,schema:c}=s;if(!r)throw new Error("BQL request not parsed");let{mutation:i}=r;if(!i)throw new Error("BQL request is not a mutation");let f=o=>{let n=o.$op,e=`$${o.$bzId}`,y=D(c,o),{idFields:l,defaultDBConnector:t}=y,m=t?.path||o.$entity||o.$relation,$=o.$id,g=l?.[0],k=listify(o,(E,B)=>{if(E.startsWith("$")||E===g||B===void 0||B===null)return "";let I=y.dataFields?.find(C=>C.path===E);if(!I?.path)return "";let P=I.dbPath;if(["TEXT","ID","EMAIL"].includes(I.contentType))return `has ${P} '${B}'`;if(["NUMBER","BOOLEAN"].includes(I.contentType))return `has ${P} ${B}`;if(I.contentType==="DATE"){if(Number.isNaN(B.valueOf()))throw new Error("Invalid format, Nan Date");return B instanceof Date?`has ${P} ${B.toISOString().replace("Z","")}`:`has ${P} ${new Date(B).toISOString().replace("Z","")}`}throw new Error(`Unsupported contentType ${I.contentType}`)}).filter(E=>E),O=`${e}-atts`,F=listify(o,E=>{if(E.startsWith("$")||E===g)return "";let B=y.dataFields?.find(P=>P.path===E);if(!B?.path)return "";let M=B.dbPath;return `{${O} isa ${M};}`}).filter(E=>E),q=o[Symbol.for("isLocalId")],T=isArray($)?`like '${$.join("|")}'`:`'${$}'`,L=!q&&$?[`has ${g} ${T}`]:[],A=[...L,...k].filter(E=>E).join(","),R=()=>{if(n==="delete"||n==="unlink"||n==="match")return `${e} isa ${[m,...L].filter(E=>E).join(",")};`;if(n==="update"){if(!F.length)throw new Error("update without attributes");return `${e} isa ${[m,...L].filter(E=>E).join(",")}, has ${O};
|
|
33
|
-
${F.join(" or ")};`}return ""},S=()=>n==="update"||n==="link"||n==="match"?`${e} isa ${[m,...L].filter(E=>E).join(",")};`:"";if(o.$entity||o.$relation)return {op:n,deletionMatch:R(),insertionMatch:S(),insertion:n==="create"?`${e} isa ${[m,A].filter(E=>E).join(",")};`:n==="update"&&k.length?`${e} ${k.join(",")};`:"",deletion:n==="delete"?`${e} isa ${m};`:n==="update"&&F.length?`${e} has ${O};`:""};throw new Error("in attributes")},d=o=>{let n=o.$op,e=D(c,o),y=`$${o.$bzId}`,l=o.$id,t=e.defaultDBConnector?.path||o.$relation,m="roles"in e?listify(e.roles,S=>S):[],$=o.$relation&&"roles"in e&&mapEntries(e.roles,(S,E)=>[S,E.dbConnector?.path||S]),g=listify(o,(S,E)=>{if(!m.includes(S))return null;if(!("roles"in e))throw new Error("This should have roles! ");let B=$[S];return Array.isArray(E)?E.map(I=>({path:B,id:I})):{path:B,id:E}}).filter(S=>S).flat(),k=g.map(S=>{if(!S?.path)throw new Error("Object without path");return `${S.path}: $${S.id}`}),O=g.length>0?`( ${k.join(" , ")} )`:"",F=O?`${y} ${O} ${o[Symbol.for("edgeType")]==="linkField"||n==="delete"||n==="unlink"?`isa ${t}`:""}`:"",q=`${y} ${o[Symbol.for("edgeType")]==="linkField"||n==="delete"?`isa ${t}`:""}`,T=()=>F?n==="link"?`${F};`:n==="create"?`${F}, has id '${l}';`:"":"",L=()=>F&&n==="match"?`${F};`:"",A=()=>F?n==="delete"?`${F};`:n==="match"?`${F};`:"":"",R=()=>F?n==="delete"?`${q};`:n==="unlink"?`${y} ${O};`:"":"";return {deletionMatch:A(),insertionMatch:L(),deletion:R(),insertion:T(),op:""}},a=(o,n)=>{let e=n==="edges"?d:f;if(Array.isArray(o))return o.map(g=>{let{preDeletionBatch:k,insertionMatch:O,deletionMatch:F,insertion:q,deletion:T}=e(g);return shake({preDeletionBatch:k,insertionMatch:O,deletionMatch:F,insertion:q,deletion:T},L=>!L)}).filter(g=>g);let{preDeletionBatch:y,insertionMatch:l,deletionMatch:t,insertion:m,deletion:$}=e(o);return shake({preDeletionBatch:y,insertionMatch:l,deletionMatch:t,insertion:m,deletion:$},g=>!g)},w=a(i.things),b=Array.isArray(w)?w:[w],Q=a(i.edges,"edges"),p=Array.isArray(Q)?Q:[Q],u=[...b,...p],h=shake({insertionMatches:u.map(o=>o.insertionMatch).join(" ").trim(),deletionMatches:u.map(o=>o.deletionMatch).join(" ").trim(),insertions:u.map(o=>o.insertion).join(" ").trim(),deletions:u.map(o=>o.deletion).join(" ").trim()},o=>!o);s.tqlRequest=h;};var Re=s=>{if(!s.startsWith("_:"))throw new Error("ID must start with '_:'.");let r=s.substring(2);if(!/^[a-zA-Z0-9-_]+$/.test(r))throw new Error("$tempId must contain only alphanumeric characters, hyphens, and underscores.");if(s.length>36)throw new Error("$tempId must not be longer than 36 characters.");return r},kt=async s=>{let{rawBqlRequest:r,schema:c}=s,f=(Q=>produce(Q,p=>traverse(p,({value:u,key:h,parent:o})=>{isObject(u)&&(u=shake(u,n=>n===void 0)),h==="$tempId"&&(o[h]=Re(u));})))(r),a=(Q=>produce(Q,p=>traverse(p,({value:u,meta:h,key:o})=>{if(isObject(u)){if(u.$arrayOp)throw new Error("Array op not supported yet");if(o==="$filter"||h.nodePath?.includes(".$filter."))return;let n=u;if(n.$op==="create"&&n.$id)throw new Error("Can't write to computed field $id. Try writing to the id field directly.");let e=D(c,n),l=h.nodePath?.split(".")?.filter(F=>Number.isNaN(parseInt(F,10))).join(".");if(!e)throw new Error(`Schema not found for ${n.$entity||n.$relation}`);n.$bzId=n.$tempId??`T_${v4()}`,n[Symbol.for("schema")]=e,n[Symbol.for("dbId")]=e.defaultDBConnector.id;let{usedLinkFields:t,usedRoleFields:m}=z(e,n),$=t.map(F=>({fieldType:"linkField",path:F,schema:e.linkFields.find(q=>q.path===F)})),g=e.thingType==="relation"?m.map(F=>({fieldType:"roleField",path:F,schema:X(e.roles,q=>q===F)})):[];if($.some(F=>F.schema?.target==="role")&&$.some(F=>F.schema?.target==="relation"))throw new Error("Unsupported: Can't use a link field with target === 'role' and another with target === 'relation' in the same mutation.");let k=g.filter(F=>[...new Set(F.schema.playedBy?.map(q=>q.thing))].length!==1);if(k.length>1)throw new Error(`Field: ${k[0].path} - If a role can be played by multiple things, you must specify the thing in the mutation: ${JSON.stringify(k[0].schema.playedBy)}. Schema: ${JSON.stringify(k[0].schema)}`);let O=h.nodePath;if([...$,...g].forEach(F=>{let q=n[F.path];if(q===void 0)return;let T=F.schema;if(!T)throw new Error(`Field ${F.path} not found in schema`);let L=F.fieldType==="roleField"?T?.playedBy[0]:T,R=(()=>T&&"relation"in T&&L?.relation===n.$relation?"$self":L?.relation?L?.relation:"$self")(),S=R==="$self"?e:c.relations[R];if(X(S.roles,(C,x)=>C===F.path)?.playedBy?.length===0)throw new Error(`unused role: ${O}.${F.path}`);if(!T)throw new Error(`Field ${F.path} not found in schema`);let B=F.fieldType==="linkField"?T?.oppositeLinkFieldsPlayedBy:T?.playedBy;if(!B)throw new Error(`No opposite fields found for ${JSON.stringify(T)}`);if([...new Set(B?.map(C=>C.thing))].length>1)throw new Error(`Field: ${F.path} - If a role can be played by multiple things, you must specify the thing in the mutation: ${JSON.stringify(B)}. Schema: ${JSON.stringify(T)}`);if(T.cardinality==="ONE"&&Array.isArray(q))throw new Error("Can't have an array in a cardinality === ONE link field");if(T.cardinality==="MANY"&&q!==null&&!Array.isArray(q)&&!q.$arrayOp)throw new Error(`${F.fieldType==="linkField"?T.path:T.name} is a cardinality === MANY thing. Use an array or a $arrayOp object`);if(q?.$entity||q?.$relation)return;let[I]=B,M="plays"in T?"linkField":"roleField",P={[`$${I.thingType}`]:I.thing,[Symbol.for("relation")]:R,[Symbol.for("edgeType")]:M,[Symbol.for("parent")]:{path:O,...n.$id?{$id:n.$id}:{},...n.$tempId?{$tempId:n.$tempId}:{},...n.filter?{filter:n.filter}:{},links:B},[Symbol.for("role")]:I.plays,[Symbol.for("oppositeRole")]:"plays"in T?T.plays:void 0,[Symbol.for("relFieldSchema")]:T};if(isObject(q)&&(n[F.path]={...P,...q}),Array.isArray(q))if(q.every(C=>isObject(C)))n[F.path]=q.map(C=>({...P,...C}));else if(q.every(C=>typeof C=="string"))n[F.path]=q.map(C=>({...P,$op:n.$op==="create"?"link":"replace",$id:C}));else throw new Error(`Invalid array value for ${F.path}`);if(typeof q=="string"&&(n[F.path]={...P,$op:n.$op==="create"?"link":"replace",$id:q}),q===null){let C={...P,$op:"unlink"};n[F.path]=T.cardinality==="MANY"?[C]:C;}}),!l&&!n.$entity&&!n.$relation)throw new Error("Root things must specify $entity or $relation")}})))(f),b=(Q=>produce(Q,p=>traverse(p,({parent:u,key:h,value:o,meta:n})=>{if(isObject(o)){if(Object.keys(o).length===0)throw new Error("Empty object!");if(h==="$filter"||n.nodePath?.includes(".$filter."))return;let e=o,y=n.nodePath?.split(".");if(e.$tempId&&!(e.$op===void 0||e.$op==="link"||e.$op==="create"||e.$op==="update"))throw new Error(`Invalid op ${e.$op} for tempId. TempIds can be created, or when created in another part of the same mutation. In the future maybe we can use them to catch stuff in the DB as well and group them under the same tempId.`);let l=y?.filter(N=>Number.isNaN(parseInt(N,10))).join("."),t=l?Array.isArray(u)?y?.slice(0,-1).join("."):n.nodePath:n.nodePath||"",m=D(c,e),{unidentifiedFields:$,dataFields:g,roleFields:k,linkFields:O}=z(m,e),F=current(e)[Symbol.for("parent")],q=l&&F.path,L=(q?getNodeByPath(p,q):p)?.$op;if(l&&!L)throw new Error("Error: Parent $op not detected");let A=e[Symbol.for("relFieldSchema")];e.$op==="replace"&&L==="create"&&(e.$op="link");let R=Object.keys(e).some(N=>g?.includes(N)),S=Object.keys(e).some(N=>[...k,...O].includes(N)),E=()=>{if(e.$op)return e.$op;if(l&&!e.$id&&!e.$tempId&&L!=="create"&&A.cardinality==="ONE")throw new Error(`Please specify if it is a create or an update. Path: ${n.nodePath}`);if(e.$tempId)return "create";if((e.$id||e.$filter)&&R)return "update";if((e.$id||e.$filter)&&l&&!R&&!S)return "link";if(!e.$filter&&!e.$id&&!e.$tempId)return "create";if((e.$id||e.$filter)&&!R&&S)return "match";throw new Error("Wrong op")};if(e.$op||(e.$op=E()),u||(e.$parentKey=""),typeof u=="object"&&(Array.isArray(u)&&(e[Symbol.for("index")]=h),e[Symbol.for("path")]=t,e[Symbol.for("isRoot")]=!l,e[Symbol.for("depth")]=l?.split(".").length),!e.$entity&&!e.$relation)throw new Error(`Node ${JSON.stringify(e)} without $entity/$relation`);let{idFields:B,computedFields:I,virtualFields:M}=m;if(!B)throw new Error("No idFields found");let[P]=B,C=listify(e,(N,j)=>j!==void 0?N:void 0),x=C.filter(N=>M?.includes(N));if(x.length>0)throw new Error(`Virtual fields can't be sent to DB: "${x.join(",")}"`);if(I.filter(N=>!C.includes(N)).forEach(N=>{let j=m.dataFields?.find(_=>_.path===N),Y=m.linkFields?.find(_=>_.path===N)?.oppositeLinkFieldsPlayedBy[0],Ut="roles"in m?X(m.roles,(_,ke)=>_===N):void 0,$t=j||Y||Ut;if(!$t)throw new Error(`no field Def for ${N}`);if(N===P&&e.$op==="create"&&!e[N]){let _=et({currentThing:e,fieldSchema:$t,mandatoryDependencies:!0});e[N]=_,e.$id=_;}}),$.length>0)throw new Error(`Unknown fields: [${$.join(",")}] in ${JSON.stringify(e)}`)}})))(a);s.filledBqlRequest=b;};var Dt=async s=>{let{filledBqlRequest:r,schema:c}=s,i=p=>{let u=[],h=[],o=l=>{if(l.$id)return l.$id;let t=D(c,l),{idFields:m}=t;if(!m)throw new Error(`no idFields: ${JSON.stringify(l)}`);let[$]=m;if(!$)throw new Error(`no idField: ${JSON.stringify(l)}`);let g=t.dataFields?.find(F=>F.path===$),k=l.$op==="create"?g?.default?.value():null,O=l[$]||l.$id||k;if(!O)throw new Error(`no idValue: ${JSON.stringify(l)}`);return O},n=l=>{if(l.$op==="create"){let t=o(l);if(u.find(m=>m.$id===t))throw new Error(`Duplicate id ${t} for node ${JSON.stringify(l)}`);if(h.find(m=>m.$bzId===l.$bzId))throw new Error(`Duplicate $bzid ${l.$bzId} for node ${JSON.stringify(l)}`);u.push({...l,$id:t});return}l.$tempId&&l.$op==="match"||u.push(l);},e=l=>{if(l.$op==="create"){let t=o(l);if(u.find(m=>m.$id===t),h.find(m=>m.$bzId===l.$bzId))throw new Error(`Duplicate %bzId ${l.$bzIdd} for edge ${JSON.stringify(l)}`);h.push({...l,$id:t});return}h.push(l);};return traverse(p,({value:l})=>{if(!isObject(l))return;let t=l;if(t.$entity||t.$relation){if(!t.$op)throw new Error(`Operation should be defined at this step ${JSON.stringify(t)}`);if(!t.$bzId)throw new Error("[internal error] BzId not found");let m=D(c,t),{dataFields:$,roleFields:g,linkFields:k,usedFields:O}=z(m,t),F=()=>{if(t.$op==="create"||t.$op==="delete")return t.$op;if(t.$op==="update"){let T=O.filter(R=>$?.includes(R)),L=O.filter(R=>g?.includes(R)),A=O.filter(R=>k?.includes(R));if(T.length>0)return "update";if(L.length>0||A.length>0)return "match";throw new Error(`No fields on an $op:"update" for node ${JSON.stringify(t)}`)}return "match"},q={...t.$entity&&{$entity:t.$entity},...t.$relation&&{$relation:t.$relation},...t.$id&&{$id:t.$id},...t.$tempId&&{$tempId:t.$tempId},...t.$filter&&{$filter:t.$filter},...shake(pick(t,$||[""])),$op:F(),$bzId:t.$bzId,[Symbol.for("dbId")]:m.defaultDBConnector.id,[Symbol.for("path")]:t[Symbol.for("path")],[Symbol.for("parent")]:t[Symbol.for("parent")],[Symbol.for("isRoot")]:t[Symbol.for("isRoot")],[Symbol.for("isLocalId")]:t[Symbol.for("isLocalId")]||!1};if(n(q),t[Symbol.for("relation")]&&t[Symbol.for("edgeType")]==="linkField"){if((t.$op==="link"||t.$op==="unlink")&&(t.$id||t.$filter)){if(t.$tempId)throw new Error("can't specify a existing and a new element at once. Use an id/filter or a tempId");u.push({...t,$op:"match"});}let T=t[Symbol.for("relation")]===t.$relation,L=T?t.$bzId:v4(),R=t[Symbol.for("parent")].path,E=(R?getNodeByPath(p,R):p).$bzId;if(!E)throw new Error("No parent id found");if(t[Symbol.for("relation")]==="$self")return;let B=()=>{if(t.$op==="delete")return T?"match":"delete";if(t.$op==="unlink")return T?"unlink":"delete";if(t.$op==="link"||t.$op==="create")return T?"link":"create";if(t.$op==="replace")throw new Error("Unsupported: Nested replaces not implemented yet");return "match"},I={$relation:t[Symbol.for("relation")],$bzId:L,...t.$tempId?{$tempId:t.$tempId}:{},$op:B(),...T?{}:{[t[Symbol.for("role")]]:t.$bzId},[t[Symbol.for("oppositeRole")]]:E,[Symbol.for("dbId")]:c.relations[t[Symbol.for("relation")]].defaultDBConnector.id,[Symbol.for("edgeType")]:"linkField",[Symbol.for("info")]:"normal linkField",[Symbol.for("path")]:t[Symbol.for("path")],[Symbol.for("parent")]:t[Symbol.for("parent")]};e(I),(t.$op==="unlink"||B()==="unlink")&&T&&e({$relation:t[Symbol.for("relation")],$bzId:L,$op:"match",[t[Symbol.for("oppositeRole")]]:E,[Symbol.for("dbId")]:c.relations[t[Symbol.for("relation")]].defaultDBConnector.id,[Symbol.for("edgeType")]:"linkField",[Symbol.for("info")]:"additional ownrelation unlink linkField",[Symbol.for("path")]:t[Symbol.for("path")],[Symbol.for("parent")]:t[Symbol.for("parent")]});}if(t.$relation){let T=G(t,(R,S)=>g.includes(R)),L=mapEntries(T,(R,S)=>isArray(S)?[R,S]:isObject(S)?[R,S.$bzId]:[R,S]),A=G(l,(R,S)=>R.startsWith("$")||R.startsWith("Symbol"));if(Object.keys(T).filter(R=>!R.startsWith("$")).length>0){if(t.$op==="create"||t.$op==="delete"){let R=()=>{if(t.$op==="create")return "link";if(t.$op==="delete")return "match";throw new Error("Unsupported parent of edge op")},S=mapEntries(L,(B,I)=>Array.isArray(I)?[B,I.map(M=>M.$bzId||M)]:[B,I.$bzId||I]),E={...A,$relation:t.$relation,$op:R(),...S,$bzId:t.$bzId,[Symbol.for("path")]:t[Symbol.for("path")],[Symbol.for("dbId")]:m.defaultDBConnector.id,[Symbol.for("info")]:"coming from created or deleted relation",[Symbol.for("edgeType")]:"roleField on C/D"};e(E);return}if(t.$op==="match"||t.$op==="update"&&Object.keys(T).length>0){let R=0;Object.entries(T).forEach(([S,E])=>{let B=isArray(E)?E:[E],I=M=>M==="create"||M==="replace"?"link":M;B.forEach(M=>{let P=I(M.$op);if(P==="replace")throw new Error("Not supported yet: replace on roleFields");if(P==="unlink"&&R>0)throw R+=1,new Error("Not supported yet: Cannot unlink more than one role at a time, please split into two mutations");let C={...A,$relation:t.$relation,$op:P==="delete"?"unlink":P,[S]:M.$bzId,$bzId:t.$bzId,[Symbol.for("dbId")]:m.defaultDBConnector.id,[Symbol.for("parent")]:t[Symbol.for("parent")],[Symbol.for("path")]:t[Symbol.for("path")],[Symbol.for("info")]:"updating roleFields",[Symbol.for("edgeType")]:"roleField on L/U/R"};e(C);});});}}}}}),[u,h]};if(!r)throw new Error("Undefined filledBqlRequest");let [f,d]=i(r),a=f.reduce((p,u)=>{if(!u.$bzId)return [...p,u];let h=p.findIndex(o=>o.$bzId===u.$bzId);if(h===-1)return [...p,u];if(p[h].$op==="create"&&u.$op==="match")return p;if(p[h].$op==="match"&&(u.$op==="create"||u.$op==="match"))return [...p.slice(0,h),u,...p.slice(h+1)];throw new Error(`Unsupported operation combination for $tempId "${u.$tempId}". Existing: ${p[h].$op}. Current: ${u.$op}`)},[]),w=d.reduce((p,u)=>{let h=p.find(o=>(o.$id&&o.$id===u.$id||o.$bzId&&o.$bzId===u.$bzId)&&o.$relation===u.$relation&&o.$op===u.$op);if(h){let o={...h};return Object.keys(u).forEach(e=>{if(typeof e=="symbol"||e.startsWith("$"))return;let y=h[e],l=u[e];Array.isArray(y)&&Array.isArray(l)?o[e]=Array.from(new Set([...y,...l])):!Array.isArray(y)&&Array.isArray(l)?y!==void 0?o[e]=Array.from(new Set([y,...l])):o[e]=l:Array.isArray(y)&&!Array.isArray(l)?l!==void 0&&(o[e]=Array.from(new Set([...y,l]))):y||(o[e]=l);}),[...p.filter(e=>!((e.$id&&e.$id===u.$id||e.$bzId&&e.$bzId===u.$bzId)&&e.$relation===u.$relation&&e.$op===u.$op)),o]}return [...p,u]},[]);[...new Set(w.map(p=>p.$relation))];s.bqlRequest={mutation:{things:a,edges:w}};};var xt=async s=>{let{filledBqlRequest:r}=s;if(Array.isArray(r))return;let i=r,d=(e=>{if(Array.isArray(e)){let y=[],l=null,t=null;if(traverse(e,({value:$,key:g,meta:k})=>{k.depth===2&&(g==="$relation"?l=$:g==="$entity"?t=$:g==="$id"&&typeof $=="string"&&y.push($));}),!l&&!t)throw new Error("Neither $relation nor $entity found in the blocks");let m={$id:y};return l&&(m.$relation=l),t&&(m.$entity=t),m}else if(isObject(e)){let y={...e.$relation&&{$relation:e.$relation},...e.$entity&&{$entity:e.$entity},$id:e.$id||e.id};return traverse(e,({key:l,value:t,meta:m})=>{if(l==="$op"&&t==="match"&&m.nodePath){let $=m.nodePath.split(".").slice(0,-1),g=y;$.forEach(k=>{g.$fields||(g.$fields=[]);let O=g.$fields.find(F=>F.$path===k);O||(O={$path:k},g.$fields.push(O)),g=O;});}}),y}return e})(r),a=await st(d,s.config,s.schema,s.dbHandles),w=(e,y)=>{let l=e.$id||e.id||e.$bzId;return `${e.$objectPath?l?e.$objectPath:e.$objectPath.split(".")[0]:"root"}${l?`-${l}`:""}.${y}`},Q=a?(e=>produce(e,y=>traverse(y,l=>{let{key:t,parent:m}=l;m&&t&&!t.includes("$")&&(Array.isArray(m[t])?m[t].forEach($=>{typeof $!="string"&&($.$objectPath=w(m,t));}):isObject(m[t])&&(m[t].$objectPath=w(m,t)));})))(a):{},p={};(e=>produce(e,y=>traverse(y,l=>{let{key:t,parent:m}=l;if(m&&t&&m.$id&&!t.includes("$")){let $=w(m,t);if(Array.isArray(m[t])){let g=[];m[t].forEach(k=>{isObject(k)?g.push(k.$id.toString()):g.push(k.toString());}),p[$]=g;}else {let g=m[t];isObject(g)?p[$]=g.$id.toString():p[$]=g.toString();}}})))(Q);let h=(e,y)=>{let l=!1,t=Array.isArray(p[e])?"MANY":"ONE",m=Array.isArray(p[e])?p[e]:[p[e]];return m&&(l=(Array.isArray(y)?m.filter(g=>y.includes(g)):m.filter(g=>g===y)).length>0),{found:l,cardinality:t,isOccupied:!!p[e]}},o=(e,y)=>{let l=[],t=Array.isArray(p[e])?p[e]:[p[e]],m=y.map($=>$.$id);return t&&(l=t.filter($=>!m.includes($))),l};a&&(i=(e=>produce(e,y=>traverse(y,l=>{let{key:t,value:m,parent:$}=l;if($&&t&&!t.includes("$")&&(Array.isArray(m)||isObject(m))&&!Array.isArray($)&&(Array.isArray($[t])?$[t].forEach(g=>{typeof g!="string"&&(g.$objectPath=w($,t));}):isObject($[t])&&($[t].$objectPath=w($,t))),t&&$&&!t?.includes("$")&&(Array.isArray(m)||isObject(m))&&!Array.isArray($)){let g=Array.isArray(m)?m:[m],k={},O=[],F=[],q=w($,t);g.forEach(L=>{let A=L.$id||L.id,{found:R,cardinality:S,isOccupied:E}=h(q,A);if(L.$op&&A)switch(L.$op){case"delete":if(!R)throw new Error(`[BQLE-Q-M-2] Cannot delete $id:"${A}" because it is not linked to $id:"${$.$id}"`);break;case"update":if(!R)throw new Error(`[BQLE-Q-M-2] Cannot update $id:"${A}" because it is not linked to $id:"${$.$id}"`);break;case"unlink":if(!R)throw new Error(`[BQLE-Q-M-2] Cannot unlink $id:"${A}" because it is not linked to $id:"${$.$id}"`);break;case"link":if(R)throw new Error(`[BQLE-Q-M-2] Cannot link $id:"${A}" because it is already linked to $id:"${$.$id}"`);break;case"replace":O.push(L),L.$op="link",R&&F.push(A);break;case"create":O.push(L);break;}else if(L.$op==="link"&&!R&&S==="ONE"&&E)throw new Error(`[BQLE-Q-M-2] Cannot link on:"${L.$objectPath}" because it is already occupied.`)}),O.length>0&&o(q,O).forEach(A=>{let R=g.find(E=>E.$id===A&&E.$op==="link"),[S]=O;if(S.$entity?k.$entity=S.$entity:S.$relation&&(k.$relation=S.$relation),!R&&!g.some(E=>E.$id===A)){let E={...k,$op:"unlink",$id:A,$bzId:`T_${v4()}`};Array.isArray(m)?m.push(E):g=[m,E];}else if(R){let E=g.findIndex(B=>B.$id===A&&B.$op==="link");E>-1&&g.splice(E,1);}});let T=g.filter(L=>!F.includes(L.$id));$[t]=isObject(m)&&T.length===1?T[0]:T;}})))(i),s.filledBqlRequest=i);};var jt=async(s,r)=>{let{dbHandles:c,tqlRequest:i,bqlRequest:f,config:d}=s;if(!i)throw new Error("TQL request not built");if(!(i.deletions&&i.deletionMatches||i.insertions))throw new Error("TQL request error, no things");if(!f?.mutation)throw new Error("BQL mutation not parsed");let{session:a}=await it(c,d),w=await a.transaction(TransactionType.WRITE);if(!w)throw new Error("Can't create transaction");let b=i.deletionMatches&&i.deletions&&`match ${i.deletionMatches} delete ${i.deletions}`,Q=i.insertions&&`${i.insertionMatches?`match ${i.insertionMatches}`:""} insert ${i.insertions}`;b&&w.query.delete(b);let p=Q&&w.query.insert(Q);try{let u=p?await p.collect():void 0;await w.commit(),await w.close(),r.rawTqlRes={insertions:u};}catch(u){throw await w.close(),new Error(`Transaction failed: ${u.message}`)}};var zt={query:[Ot,nt,rt,J,pt],mutation:[kt,xt,Dt,At,jt,J]},Ae=[qt],at=async(s,r,c={},i=!0)=>{for(let f of s){let d=await f(r,c);if(d&&Array.isArray(d))for(let a of d)await at(a.pipeline,a.req,a.res,!1);}return i?(await at(Ae,r,c,!1),r.config.query?.debugger===!0&&typeof c.bqlRes=="object"?{...c.bqlRes,$debugger:{tqlRequest:r.tqlRequest}}:c.bqlRes):c.bqlRes},st=(s,r,c,i)=>at(zt.query,{config:r,schema:c,rawBqlRequest:s,dbHandles:i},{}),Vt=(s,r,c,i)=>at(zt.mutation,{config:r,schema:c,rawBqlRequest:s,dbHandles:i},{});var mt=class{schema;config;dbHandles;constructor({schema:r,config:c}){this.schema=r,this.config=c;}init=async()=>{let r={typeDB:new Map},c=Ft(this.schema);await Promise.all(this.config.dbConnectors.map(async i=>{if(i.provider==="typeDB"&&i.dbName){let[f,d]=await tryit(TypeDB.coreDriver)(i.url);if(f){let a=`[BORM:${i.provider}:${i.dbName}:core] ${f.message??"Can't create TypeDB Client"}`;throw new Error(a)}try{let a=await d.session(i.dbName,SessionType.DATA);r.typeDB.set(i.id,{client:d,session:a});}catch(a){let w=`[BORM:${i.provider}:${i.dbName}:session] ${(a.messageTemplate?._messageBody()||a.message)??"Can't create TypeDB Session"}`;throw new Error(w)}}if(i.provider==="typeDBCluster"&&i.dbName){let[f,d]=await tryit(TypeDB.enterpriseDriver)(i.addresses,i.credentials);if(f){let a=`[BORM:${i.provider}:${i.dbName}:core] ${f.message??"Can't create TypeDB Cluster Client"}`;throw new Error(a)}try{let a=await d.session(i.dbName,SessionType.DATA);r.typeDB.set(i.id,{client:d,session:a});}catch(a){let w=`[BORM:${i.provider}:${i.dbName}:session] ${(a.messageTemplate?._messageBody()||a.message)??"Can't create TypeDB Session"}`;throw new Error(w)}}})),this.schema=c,this.dbHandles=r;};#t=async()=>{if(!this.dbHandles&&(await this.init(),!this.dbHandles))throw new Error("Can't init BormClient")};introspect=async()=>(await this.#t(),this.schema);define=async()=>(await this.#t(),bt(this.config,this.schema,this.dbHandles));query=async(r,c)=>{await this.#t();let i={...this.config,query:{...lt.query,...this.config.query,...c}};return st(r,i,this.schema,this.dbHandles)};mutate=async(r,c)=>{await this.#t();let i={...this.config,mutation:{...lt.mutation,...this.config.mutation,...c}};return Vt(r,i,this.schema,this.dbHandles)};close=async()=>{this.dbHandles&&this.dbHandles.typeDB.forEach(async({client:r,session:c})=>{console.log("Closing session"),await c.close(),console.log("Closing client"),await r.close();});}},Ii=mt;//! reads all the insertions and gets the first match. This means each id must be unique
|
|
34
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { tryit } from "radash";
|
|
3
|
+
import { TypeDB, SessionType as SessionType3 } from "typedb-driver";
|
|
4
|
+
|
|
5
|
+
// src/default.config.ts
|
|
6
|
+
var defaultConfig = {
|
|
7
|
+
query: {
|
|
8
|
+
noMetadata: false,
|
|
9
|
+
simplifiedLinks: true,
|
|
10
|
+
debugger: false,
|
|
11
|
+
returnNulls: false
|
|
12
|
+
},
|
|
13
|
+
mutation: {
|
|
14
|
+
noMetadata: false,
|
|
15
|
+
preQuery: true,
|
|
16
|
+
ignoreNonexistingThings: false
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/define/index.ts
|
|
21
|
+
import { SessionType, TransactionType } from "typedb-driver";
|
|
22
|
+
var removeDuplicateObjects = (arr) => {
|
|
23
|
+
const uniqueObjects = [];
|
|
24
|
+
const uniqueMap = /* @__PURE__ */ new Map();
|
|
25
|
+
arr.forEach((obj) => {
|
|
26
|
+
const { dbPath, contentType } = obj;
|
|
27
|
+
const key = `${dbPath}-${contentType}`;
|
|
28
|
+
if (!uniqueMap.has(key)) {
|
|
29
|
+
uniqueMap.set(key, true);
|
|
30
|
+
uniqueObjects.push(obj);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return uniqueObjects;
|
|
34
|
+
};
|
|
35
|
+
var bormDefine = async (config, schema, dbHandles) => {
|
|
36
|
+
const convertSchema = () => {
|
|
37
|
+
let output = "";
|
|
38
|
+
const usedAttributes = [];
|
|
39
|
+
output += "\n";
|
|
40
|
+
Object.keys(schema.entities).forEach((entityName) => {
|
|
41
|
+
const entity = schema.entities[entityName];
|
|
42
|
+
const { idFields, dataFields, linkFields, name } = entity;
|
|
43
|
+
const commonDataFields = [];
|
|
44
|
+
const commonLinkFields = [];
|
|
45
|
+
const commonIdFields = [];
|
|
46
|
+
if (entity.extends) {
|
|
47
|
+
const parentEntity = schema.entities[entity.extends];
|
|
48
|
+
if (parentEntity.dataFields) {
|
|
49
|
+
parentEntity.dataFields.forEach((dataField) => {
|
|
50
|
+
commonDataFields.push(dataField.dbPath);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (parentEntity.linkFields) {
|
|
54
|
+
parentEntity.linkFields.forEach((linkField) => {
|
|
55
|
+
commonLinkFields.push(linkField.path);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (parentEntity.idFields) {
|
|
59
|
+
parentEntity.idFields.forEach((idField) => {
|
|
60
|
+
commonIdFields.push(idField);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
output += `${name} sub ${entity.extends ? entity.extends : "entity"},
|
|
65
|
+
`;
|
|
66
|
+
const idsAsData = [];
|
|
67
|
+
if (idFields && idFields.length > 0) {
|
|
68
|
+
const setIds = new Set(idFields);
|
|
69
|
+
const newIdFields = Array.from(setIds);
|
|
70
|
+
const idFieldsString = newIdFields.map((field) => `${field}`).join(", ");
|
|
71
|
+
if (!commonIdFields.includes(idFieldsString)) {
|
|
72
|
+
output += ` owns ${idFieldsString} @key,
|
|
73
|
+
`;
|
|
74
|
+
idsAsData.push(idFieldsString);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (dataFields && dataFields.length > 0) {
|
|
78
|
+
dataFields.forEach((field) => {
|
|
79
|
+
if (!commonDataFields.includes(field.dbPath) && !idsAsData.includes(field.dbPath)) {
|
|
80
|
+
output += ` owns ${field.dbPath},
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
usedAttributes.push({ dbPath: field.dbPath, contentType: field.contentType });
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (linkFields && linkFields.length > 0) {
|
|
87
|
+
const usedLinkFields = [];
|
|
88
|
+
linkFields.forEach((linkField) => {
|
|
89
|
+
const { relation, plays } = linkField;
|
|
90
|
+
if (!commonLinkFields.includes(linkField.path) && !usedLinkFields.includes(`${relation}:${plays}`)) {
|
|
91
|
+
output += ` plays ${relation}:${plays},
|
|
92
|
+
`;
|
|
93
|
+
usedLinkFields.push(`${relation}:${plays}`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
output = output.replace(/,\s*$/, ";\n");
|
|
98
|
+
output += "\n";
|
|
99
|
+
});
|
|
100
|
+
Object.keys(schema.relations).forEach((relationName) => {
|
|
101
|
+
const relation = schema.relations[relationName];
|
|
102
|
+
const { idFields, dataFields, roles, name, linkFields } = relation;
|
|
103
|
+
const commonDataFields = [];
|
|
104
|
+
const commonLinkFields = [];
|
|
105
|
+
const commonRoleFields = [];
|
|
106
|
+
const commonIdFields = [];
|
|
107
|
+
if (relation.extends) {
|
|
108
|
+
const parentRelation = schema.relations[relation.extends];
|
|
109
|
+
if (parentRelation.dataFields) {
|
|
110
|
+
parentRelation.dataFields.forEach((dataField) => {
|
|
111
|
+
commonDataFields.push(dataField.dbPath);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (parentRelation.linkFields) {
|
|
115
|
+
parentRelation.linkFields.forEach((linkField) => {
|
|
116
|
+
commonLinkFields.push(linkField.dbPath);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (parentRelation.roles) {
|
|
120
|
+
const roleFields = Object.values(parentRelation.roles);
|
|
121
|
+
roleFields.forEach((roleField) => {
|
|
122
|
+
commonRoleFields.push(roleField.name);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (parentRelation.idFields) {
|
|
126
|
+
parentRelation.idFields.forEach((idField) => {
|
|
127
|
+
commonIdFields.push(idField);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
output += `${name} sub ${relation.extends ? relation.extends : "relation"},
|
|
132
|
+
`;
|
|
133
|
+
const idsAsData = [];
|
|
134
|
+
if (idFields && idFields.length > 0) {
|
|
135
|
+
const setIds = new Set(idFields);
|
|
136
|
+
const newIdFields = Array.from(setIds);
|
|
137
|
+
const idFieldsString = newIdFields.map((field) => `${field}`).join(", ");
|
|
138
|
+
if (!commonIdFields.includes(idFieldsString)) {
|
|
139
|
+
output += ` owns ${idFieldsString} @key,
|
|
140
|
+
`;
|
|
141
|
+
idsAsData.push(idFieldsString);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (dataFields && dataFields.length > 0) {
|
|
145
|
+
dataFields.forEach((field) => {
|
|
146
|
+
if (!commonDataFields.includes(field.dbPath) && !idsAsData.includes(field.dbPath)) {
|
|
147
|
+
output += ` owns ${field.dbPath},
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
usedAttributes.push({ dbPath: field.dbPath, contentType: field.contentType });
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (roles) {
|
|
154
|
+
Object.keys(roles).forEach((roleName) => {
|
|
155
|
+
if (!commonRoleFields.includes(roleName)) {
|
|
156
|
+
output += ` relates ${roleName},
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (linkFields && linkFields.length > 0) {
|
|
162
|
+
const usedLinkFields = [];
|
|
163
|
+
linkFields.forEach((linkField) => {
|
|
164
|
+
const { plays } = linkField;
|
|
165
|
+
if (!commonLinkFields.includes(linkField.path) && !usedLinkFields.includes(`${relation}:${plays}`)) {
|
|
166
|
+
output += ` plays ${linkField.relation}:${plays},
|
|
167
|
+
`;
|
|
168
|
+
usedLinkFields.push(`${relation}:${plays}`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
output = output.replace(/,\s*$/, ";\n");
|
|
173
|
+
output += "\n";
|
|
174
|
+
});
|
|
175
|
+
let attributes = "define\n\n";
|
|
176
|
+
const newUsedAttributes = removeDuplicateObjects(usedAttributes);
|
|
177
|
+
newUsedAttributes.forEach((attribute) => {
|
|
178
|
+
attributes += `${attribute.dbPath} sub attribute,
|
|
179
|
+
`;
|
|
180
|
+
if (attribute.contentType === "TEXT" || attribute.contentType === "ID") {
|
|
181
|
+
attributes += " value string;\n";
|
|
182
|
+
} else if (attribute.contentType === "EMAIL") {
|
|
183
|
+
attributes += " value string,\n";
|
|
184
|
+
attributes += " regex '^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$';\n";
|
|
185
|
+
} else if (attribute.contentType === "DATE") {
|
|
186
|
+
attributes += " value datetime;\n";
|
|
187
|
+
} else if (attribute.contentType === "BOOLEAN") {
|
|
188
|
+
attributes += " value boolean;\n";
|
|
189
|
+
} else if (attribute.contentType === "NUMBER") {
|
|
190
|
+
attributes += " value long;\n";
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return `${attributes}
|
|
194
|
+
|
|
195
|
+
${output}`;
|
|
196
|
+
};
|
|
197
|
+
const typeDBString = convertSchema();
|
|
198
|
+
const singleHandlerV0 = config.dbConnectors[0].id;
|
|
199
|
+
const session = dbHandles.typeDB.get(singleHandlerV0)?.session;
|
|
200
|
+
const client = dbHandles.typeDB.get(singleHandlerV0)?.client;
|
|
201
|
+
if (!session) {
|
|
202
|
+
console.log("Session Status: ", "NO SESSION");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
session.close();
|
|
206
|
+
const [{ dbName }] = config.dbConnectors;
|
|
207
|
+
const db = await client.databases.get(dbName);
|
|
208
|
+
await db.delete();
|
|
209
|
+
await client.databases.create(dbName);
|
|
210
|
+
const schemaSession = await client.session(config.dbConnectors[0].dbName, SessionType.SCHEMA);
|
|
211
|
+
const schemaTransaction = await schemaSession.transaction(TransactionType.WRITE);
|
|
212
|
+
await schemaTransaction.query.define(typeDBString);
|
|
213
|
+
await schemaTransaction.commit();
|
|
214
|
+
await schemaTransaction.close();
|
|
215
|
+
const getSchemaTransaction = await schemaSession.transaction(TransactionType.READ);
|
|
216
|
+
const getSchemaQuery = "match $a sub thing;";
|
|
217
|
+
const getSchemaStream = await getSchemaTransaction.query.match(getSchemaQuery);
|
|
218
|
+
const schemaThings = await getSchemaStream.collect();
|
|
219
|
+
schemaThings.forEach(async (conceptMap) => {
|
|
220
|
+
const thing = conceptMap.get("a");
|
|
221
|
+
const { _label } = thing;
|
|
222
|
+
const { _name } = _label;
|
|
223
|
+
});
|
|
224
|
+
await getSchemaTransaction.close();
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// src/helpers.ts
|
|
228
|
+
import { produce } from "immer";
|
|
229
|
+
import { traverse } from "object-traversal";
|
|
230
|
+
import { listify } from "radash";
|
|
231
|
+
var getDbPath = (thing, attribute, shared) => shared ? attribute : `${thing}\xB7${attribute}`;
|
|
232
|
+
var oFind = (obj, fn) => Object.values(Object.fromEntries(Object.entries(obj).filter(([k, v]) => fn(k, v))))[0];
|
|
233
|
+
var oFilter = (obj, fn) => Object.fromEntries(Object.entries(obj).filter(([k, v]) => fn(k, v)));
|
|
234
|
+
var enrichSchema = (schema) => {
|
|
235
|
+
const allLinkedFields = [];
|
|
236
|
+
const withExtensionsSchema = produce(
|
|
237
|
+
schema,
|
|
238
|
+
(draft) => traverse(
|
|
239
|
+
draft,
|
|
240
|
+
({ key, value, meta }) => {
|
|
241
|
+
if (meta.depth !== 2) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (key) {
|
|
245
|
+
value.dataFields = value.dataFields?.map((df) => ({
|
|
246
|
+
...df,
|
|
247
|
+
dbPath: getDbPath(key, df.path, df.shared)
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
if (value.extends) {
|
|
251
|
+
const extendedSchema = draft.entities[value.extends] || draft.relations[value.extends];
|
|
252
|
+
value.allExtends = [value.extends, ...extendedSchema.allExtends || []];
|
|
253
|
+
value;
|
|
254
|
+
value.idFields = extendedSchema.idFields ? (value.idFields || []).concat(extendedSchema.idFields) : value.idFields;
|
|
255
|
+
value.dataFields = extendedSchema.dataFields ? (value.dataFields || []).concat(
|
|
256
|
+
extendedSchema.dataFields.map((df) => {
|
|
257
|
+
let deepExtendedThing = value.extends;
|
|
258
|
+
let deepSchema = schema.entities[deepExtendedThing] || schema.relations[deepExtendedThing];
|
|
259
|
+
while (!deepSchema.dataFields?.find((deepDf) => deepDf.path === df.path)) {
|
|
260
|
+
deepExtendedThing = deepSchema.extends;
|
|
261
|
+
deepSchema = schema.entities[deepExtendedThing] || schema.relations[deepExtendedThing];
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
...df,
|
|
265
|
+
dbPath: getDbPath(deepExtendedThing, df.path, df.shared)
|
|
266
|
+
};
|
|
267
|
+
})
|
|
268
|
+
) : value.dataFields;
|
|
269
|
+
value.linkFields = extendedSchema.linkFields ? (value.linkFields || []).concat(extendedSchema.linkFields) : value.linkFields;
|
|
270
|
+
if ("roles" in extendedSchema) {
|
|
271
|
+
const val = value;
|
|
272
|
+
const extendedRelationSchema = extendedSchema;
|
|
273
|
+
val.roles = val.roles || {};
|
|
274
|
+
val.roles = {
|
|
275
|
+
...val.roles,
|
|
276
|
+
...extendedRelationSchema.roles
|
|
277
|
+
};
|
|
278
|
+
if (Object.keys(val.roles).length === 0) {
|
|
279
|
+
val.roles = {};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
{ traversalType: "breadth-first" }
|
|
285
|
+
)
|
|
286
|
+
);
|
|
287
|
+
traverse(schema, ({ key, value, meta }) => {
|
|
288
|
+
if (key === "linkFields") {
|
|
289
|
+
const getThingTypes = () => {
|
|
290
|
+
if (!meta.nodePath) {
|
|
291
|
+
throw new Error("No path");
|
|
292
|
+
}
|
|
293
|
+
const [thingPath, thing] = meta.nodePath.split(".");
|
|
294
|
+
const thingType = thingPath === "entities" ? "entity" : thingPath === "relations" ? "relation" : "";
|
|
295
|
+
return {
|
|
296
|
+
thing,
|
|
297
|
+
thingType
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
const thingTypes = getThingTypes();
|
|
301
|
+
const withThing = !Array.isArray(value) ? [
|
|
302
|
+
{
|
|
303
|
+
...value,
|
|
304
|
+
...thingTypes
|
|
305
|
+
}
|
|
306
|
+
] : value.map((x) => ({ ...x, ...thingTypes }));
|
|
307
|
+
allLinkedFields.push(...withThing);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
const enrichedSchema = produce(
|
|
311
|
+
withExtensionsSchema,
|
|
312
|
+
(draft) => traverse(draft, ({ value, key, meta }) => {
|
|
313
|
+
if (meta.depth === 2 && value.idFields && !value.id) {
|
|
314
|
+
value.name = key;
|
|
315
|
+
const thingType = () => {
|
|
316
|
+
if (meta.nodePath?.split(".")[0] === "entities") {
|
|
317
|
+
return "entity";
|
|
318
|
+
}
|
|
319
|
+
if (meta.nodePath?.split(".")[0] === "relations") {
|
|
320
|
+
return "relation";
|
|
321
|
+
}
|
|
322
|
+
throw new Error("Unsupported node attributes");
|
|
323
|
+
};
|
|
324
|
+
value.thingType = thingType();
|
|
325
|
+
value.computedFields = [];
|
|
326
|
+
value.virtualFields = [];
|
|
327
|
+
if ("roles" in value) {
|
|
328
|
+
const val = value;
|
|
329
|
+
Object.entries(val.roles).forEach(([roleKey, role]) => {
|
|
330
|
+
role.playedBy = allLinkedFields.filter((x) => x.relation === key && x.plays === roleKey) || [];
|
|
331
|
+
role.name = roleKey;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
if ("linkFields" in value && value.linkFields) {
|
|
335
|
+
const val = value;
|
|
336
|
+
val.linkFields?.forEach((linkField) => {
|
|
337
|
+
if (linkField.target === "relation") {
|
|
338
|
+
linkField.oppositeLinkFieldsPlayedBy = [
|
|
339
|
+
{
|
|
340
|
+
plays: linkField.path,
|
|
341
|
+
thing: linkField.relation,
|
|
342
|
+
thingType: "relation"
|
|
343
|
+
}
|
|
344
|
+
];
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const allOppositeLinkFields = allLinkedFields.filter((x) => x.relation === linkField.relation && x.plays !== linkField.plays) || [];
|
|
348
|
+
linkField.oppositeLinkFieldsPlayedBy = allOppositeLinkFields;
|
|
349
|
+
const { filter } = linkField;
|
|
350
|
+
linkField.oppositeLinkFieldsPlayedBy = linkField.oppositeLinkFieldsPlayedBy.filter(
|
|
351
|
+
(x) => x.target === "role"
|
|
352
|
+
);
|
|
353
|
+
if (filter && Array.isArray(filter)) {
|
|
354
|
+
linkField.oppositeLinkFieldsPlayedBy = linkField.oppositeLinkFieldsPlayedBy.filter(
|
|
355
|
+
(lf) => (
|
|
356
|
+
// @ts-expect-error - TODO description
|
|
357
|
+
filter.some((ft) => lf.thing === ft.$role)
|
|
358
|
+
)
|
|
359
|
+
);
|
|
360
|
+
linkField.oppositeLinkFieldsPlayedBy = linkField.oppositeLinkFieldsPlayedBy.filter(
|
|
361
|
+
(lf) => (
|
|
362
|
+
// @ts-expect-error - TODO description
|
|
363
|
+
filter.some((ft) => lf.thing === ft.$thing)
|
|
364
|
+
)
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
if (filter && !Array.isArray(filter)) {
|
|
368
|
+
linkField.oppositeLinkFieldsPlayedBy = linkField.oppositeLinkFieldsPlayedBy.filter(
|
|
369
|
+
// @ts-expect-error - TODO description
|
|
370
|
+
(lf) => lf.$role === filter.$role
|
|
371
|
+
);
|
|
372
|
+
linkField.oppositeLinkFieldsPlayedBy = linkField.oppositeLinkFieldsPlayedBy.filter(
|
|
373
|
+
// @ts-expect-error - TODO description
|
|
374
|
+
(lf) => lf.thing === filter.$thing
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (typeof value === "object" && "playedBy" in value) {
|
|
381
|
+
if ([...new Set(value.playedBy?.map((x) => x.thing))].length > 1) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`Unsupported: roleFields can be only played by one thing. Role: ${key} path:${meta.nodePath}`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
if (value.playedBy.length === 0) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Unsupported: roleFields should be played at least by one thing. Role: ${key}, path:${meta.nodePath}`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (meta.depth === 4 && (value.default || value.computedValue)) {
|
|
393
|
+
const [type, thingId] = meta.nodePath?.split(".") || [];
|
|
394
|
+
if (value.isVirtual) {
|
|
395
|
+
draft[type][thingId].virtualFields.push(value.path);
|
|
396
|
+
} else {
|
|
397
|
+
draft[type][thingId].computedFields.push(value.path);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
})
|
|
401
|
+
);
|
|
402
|
+
return enrichedSchema;
|
|
403
|
+
};
|
|
404
|
+
var getCurrentSchema = (schema, node) => {
|
|
405
|
+
if (node.$entity) {
|
|
406
|
+
if (!(node.$entity in schema.entities)) {
|
|
407
|
+
throw new Error(`Missing entity '${node.$entity}' in the schema`);
|
|
408
|
+
}
|
|
409
|
+
return schema.entities[node.$entity];
|
|
410
|
+
}
|
|
411
|
+
if (node.$relation) {
|
|
412
|
+
if (!(node.$relation in schema.relations)) {
|
|
413
|
+
throw new Error(`Missing relation '${node.$relation}' in the schema`);
|
|
414
|
+
}
|
|
415
|
+
return schema.relations[node.$relation];
|
|
416
|
+
}
|
|
417
|
+
throw new Error(`Wrong schema or query for ${JSON.stringify(node, null, 2)}`);
|
|
418
|
+
};
|
|
419
|
+
var getCurrentFields = (currentSchema, node) => {
|
|
420
|
+
const availableDataFields = currentSchema.dataFields?.map((x) => x.path) || [];
|
|
421
|
+
const availableLinkFields = currentSchema.linkFields?.map((x) => x.path) || [];
|
|
422
|
+
const availableRoleFields = "roles" in currentSchema ? listify(currentSchema.roles, (k) => k) : [];
|
|
423
|
+
const availableFields = [
|
|
424
|
+
...availableDataFields || [],
|
|
425
|
+
...availableLinkFields || [],
|
|
426
|
+
...availableRoleFields || []
|
|
427
|
+
];
|
|
428
|
+
const reservedRootFields = [
|
|
429
|
+
"$entity",
|
|
430
|
+
"$op",
|
|
431
|
+
"$id",
|
|
432
|
+
"$tempId",
|
|
433
|
+
"$bzId",
|
|
434
|
+
"$relation",
|
|
435
|
+
"$parentKey",
|
|
436
|
+
"$filter",
|
|
437
|
+
"$fields",
|
|
438
|
+
"$excludedFields"
|
|
439
|
+
];
|
|
440
|
+
const allowedFields = [...reservedRootFields, ...availableFields];
|
|
441
|
+
if (!node) {
|
|
442
|
+
return {
|
|
443
|
+
fields: availableFields,
|
|
444
|
+
dataFields: availableDataFields,
|
|
445
|
+
roleFields: availableRoleFields,
|
|
446
|
+
linkFields: availableLinkFields
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const usedFields = node.$fields ? node.$fields.map((x) => {
|
|
450
|
+
if (typeof x === "string") {
|
|
451
|
+
return x;
|
|
452
|
+
}
|
|
453
|
+
if ("$path" in x && typeof x.$path === "string") {
|
|
454
|
+
return x.$path;
|
|
455
|
+
}
|
|
456
|
+
throw new Error(" Wrongly structured query");
|
|
457
|
+
}) : listify(node, (k) => k);
|
|
458
|
+
const localFilterFields = !node.$filter ? [] : listify(node.$filter, (k) => k.toString().startsWith("$") ? void 0 : k.toString()).filter(
|
|
459
|
+
(x) => x && availableDataFields?.includes(x)
|
|
460
|
+
);
|
|
461
|
+
const nestedFilterFields = !node.$filter ? [] : listify(node.$filter, (k) => k.toString().startsWith("$") ? void 0 : k.toString()).filter(
|
|
462
|
+
(x) => x && [...availableRoleFields || [], ...availableLinkFields || []]?.includes(x)
|
|
463
|
+
);
|
|
464
|
+
const unidentifiedFields = [...usedFields, ...localFilterFields].filter((x) => !allowedFields.includes(x)).filter((x) => x);
|
|
465
|
+
const localFilters = !node.$filter ? {} : oFilter(node.$filter, (k, _v) => localFilterFields.includes(k));
|
|
466
|
+
const nestedFilters = !node.$filter ? {} : oFilter(node.$filter, (k, _v) => nestedFilterFields.includes(k));
|
|
467
|
+
return {
|
|
468
|
+
fields: availableFields,
|
|
469
|
+
dataFields: availableDataFields,
|
|
470
|
+
roleFields: availableRoleFields,
|
|
471
|
+
linkFields: availableLinkFields,
|
|
472
|
+
usedFields,
|
|
473
|
+
usedLinkFields: availableLinkFields.filter((x) => usedFields.includes(x)),
|
|
474
|
+
usedRoleFields: availableRoleFields.filter((x) => usedFields.includes(x)),
|
|
475
|
+
unidentifiedFields,
|
|
476
|
+
...localFilterFields.length ? { localFilters } : {},
|
|
477
|
+
...nestedFilterFields.length ? { nestedFilters } : {}
|
|
478
|
+
};
|
|
479
|
+
};
|
|
480
|
+
var notNull = (value) => {
|
|
481
|
+
return value !== null;
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// src/pipeline/postprocess/parseTQLMutation.ts
|
|
485
|
+
import { mapEntries } from "radash";
|
|
486
|
+
var parseTQLMutation = async (req, res) => {
|
|
487
|
+
const { bqlRequest, config, tqlRequest } = req;
|
|
488
|
+
const { rawTqlRes } = res;
|
|
489
|
+
if (!bqlRequest) {
|
|
490
|
+
throw new Error("BQL request not parsed");
|
|
491
|
+
} else if (!rawTqlRes) {
|
|
492
|
+
throw new Error("TQL query not executed");
|
|
493
|
+
}
|
|
494
|
+
const { query } = bqlRequest;
|
|
495
|
+
if (!query) {
|
|
496
|
+
if (rawTqlRes.insertions?.length === 0 && !tqlRequest?.deletions) {
|
|
497
|
+
res.bqlRes = {};
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const { mutation } = bqlRequest;
|
|
501
|
+
if (!mutation) {
|
|
502
|
+
throw new Error("TQL mutation not executed");
|
|
503
|
+
}
|
|
504
|
+
const expected = [...mutation.things, ...mutation.edges];
|
|
505
|
+
const result = expected.map((exp) => {
|
|
506
|
+
const currentNode = rawTqlRes.insertions?.find((y) => y.get(`${exp.$bzId}`))?.get(`${exp.$bzId}`);
|
|
507
|
+
if (exp.$op === "create" || exp.$op === "update" || exp.$op === "link") {
|
|
508
|
+
const dbIdd = currentNode?.asThing().iid;
|
|
509
|
+
if (config.mutation?.noMetadata) {
|
|
510
|
+
return mapEntries(exp, (k, v) => [
|
|
511
|
+
k.toString().startsWith("$") ? Symbol.for(k) : k,
|
|
512
|
+
v
|
|
513
|
+
]);
|
|
514
|
+
}
|
|
515
|
+
return { $dbId: dbIdd, ...exp, ...{ [exp.path]: exp.$id } };
|
|
516
|
+
}
|
|
517
|
+
if (exp.$op === "delete" || exp.$op === "unlink") {
|
|
518
|
+
return exp;
|
|
519
|
+
}
|
|
520
|
+
if (exp.$op === "match") {
|
|
521
|
+
return void 0;
|
|
522
|
+
}
|
|
523
|
+
throw new Error(`Unsupported op ${exp.$op}`);
|
|
524
|
+
}).filter((z) => z);
|
|
525
|
+
res.bqlRes = result;
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// src/pipeline/postprocess/fieldsOperator.ts
|
|
531
|
+
import { isObject } from "radash";
|
|
532
|
+
|
|
533
|
+
// src/pipeline/postprocess/buildBQLTree.ts
|
|
534
|
+
import { produce as produce2 } from "immer";
|
|
535
|
+
import { traverse as traverse2 } from "object-traversal";
|
|
536
|
+
import { isObject as isObject2, listify as listify2 } from "radash";
|
|
537
|
+
|
|
538
|
+
// src/engine/helpers.ts
|
|
539
|
+
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*.*\*\/))/gm;
|
|
540
|
+
var STRIP_KEYWORDS = /(\s*async\s*|\s*function\s*)+/;
|
|
541
|
+
var ARGUMENT_NAMES = /(?:function\s*[^(\s]*\s*|\s*=>\s*|^\s*)\(([^)]*)\)/;
|
|
542
|
+
var ARGUMENT_SPLIT = /[ ,\n\r\t]+/;
|
|
543
|
+
var getParamNames = (func) => {
|
|
544
|
+
const fnStr = func.toString().replace(STRIP_COMMENTS, "").replace(STRIP_KEYWORDS, "").trim();
|
|
545
|
+
const matches = ARGUMENT_NAMES.exec(fnStr);
|
|
546
|
+
if (!matches) {
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
549
|
+
let match;
|
|
550
|
+
for (const matchGroup of matches.slice(1)) {
|
|
551
|
+
if (matchGroup) {
|
|
552
|
+
match = matchGroup;
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (!match) {
|
|
557
|
+
return [];
|
|
558
|
+
}
|
|
559
|
+
match = match.replace(/\{\s*/g, "").replace(/\s*\}/g, "");
|
|
560
|
+
return match.split(ARGUMENT_SPLIT).filter((part) => part.trim() !== "");
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// src/engine/compute.ts
|
|
564
|
+
var compute = ({
|
|
565
|
+
currentThing,
|
|
566
|
+
fieldSchema,
|
|
567
|
+
mandatoryDependencies = false
|
|
568
|
+
}) => {
|
|
569
|
+
if (!fieldSchema || !fieldSchema.default || !fieldSchema.default.value) {
|
|
570
|
+
throw new Error("Virtual field: No field schema found, or wrongly configured");
|
|
571
|
+
}
|
|
572
|
+
const fn = fieldSchema.default.value;
|
|
573
|
+
const args = getParamNames(fn);
|
|
574
|
+
const missingArgs = args.filter((arg) => !(arg in currentThing));
|
|
575
|
+
if (mandatoryDependencies && missingArgs.length) {
|
|
576
|
+
throw new Error(`Virtual field: Missing arguments ${missingArgs.join(", ")}`);
|
|
577
|
+
}
|
|
578
|
+
const computedValue = "default" in fieldSchema ? fieldSchema.default?.value(currentThing) : void 0;
|
|
579
|
+
return computedValue;
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
// src/pipeline/postprocess/buildBQLTree.ts
|
|
583
|
+
var isOne = (children, $id) => {
|
|
584
|
+
if (children.length === 1 && typeof children[0] !== "string" ? children[0].$id === $id : false) {
|
|
585
|
+
return children[0];
|
|
586
|
+
}
|
|
587
|
+
return children;
|
|
588
|
+
};
|
|
589
|
+
var isStringOrHasShow = (value) => {
|
|
590
|
+
return typeof value === "string" || !!value.$show;
|
|
591
|
+
};
|
|
592
|
+
var cleanOutput = (obj, config) => produce2(
|
|
593
|
+
obj,
|
|
594
|
+
(draft) => traverse2(draft, ({ value }) => {
|
|
595
|
+
if (Array.isArray(value) || !(typeof value === "object") || value === null) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (value.$tempId) {
|
|
599
|
+
value.$tempId = `_:${value.$tempId}`;
|
|
600
|
+
}
|
|
601
|
+
if (value.$fields) {
|
|
602
|
+
delete value.$fields;
|
|
603
|
+
}
|
|
604
|
+
if (value.$filter) {
|
|
605
|
+
delete value.$filter;
|
|
606
|
+
}
|
|
607
|
+
if (value.$show) {
|
|
608
|
+
delete value.$show;
|
|
609
|
+
}
|
|
610
|
+
if (value.$bzId) {
|
|
611
|
+
delete value.$bzId;
|
|
612
|
+
}
|
|
613
|
+
if (config.query?.noMetadata && (value.$entity || value.$relation)) {
|
|
614
|
+
delete value.$entity;
|
|
615
|
+
delete value.$relation;
|
|
616
|
+
delete value.$id;
|
|
617
|
+
}
|
|
618
|
+
const symbols = Object.getOwnPropertySymbols(value);
|
|
619
|
+
symbols.forEach((symbol) => {
|
|
620
|
+
delete value[symbol];
|
|
621
|
+
});
|
|
622
|
+
if (value.$excludedFields) {
|
|
623
|
+
value.$excludedFields.forEach((field) => {
|
|
624
|
+
delete value[field];
|
|
625
|
+
});
|
|
626
|
+
delete value.$excludedFields;
|
|
627
|
+
}
|
|
628
|
+
})
|
|
629
|
+
);
|
|
630
|
+
var filterChildrenEntities = (things, ids, node, path) => things.map(([id, entity]) => {
|
|
631
|
+
if (!ids) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
if (!ids.includes(id)) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
if (!node.$fields) {
|
|
638
|
+
return id;
|
|
639
|
+
}
|
|
640
|
+
if (node.$fields.includes(path)) {
|
|
641
|
+
return id;
|
|
642
|
+
}
|
|
643
|
+
const currentFieldConf = node.$fields.find((f) => isObject2(f) && f.$path === path);
|
|
644
|
+
if (currentFieldConf) {
|
|
645
|
+
const onlyMetadataEntity = {
|
|
646
|
+
...oFilter(entity, (k, _v) => k.startsWith("$"))
|
|
647
|
+
};
|
|
648
|
+
const withFieldsEntity = currentFieldConf.$fields ? {
|
|
649
|
+
...onlyMetadataEntity,
|
|
650
|
+
$fields: currentFieldConf.$fields
|
|
651
|
+
} : onlyMetadataEntity;
|
|
652
|
+
if (currentFieldConf.$id) {
|
|
653
|
+
if (Array.isArray(currentFieldConf.$id)) {
|
|
654
|
+
if (currentFieldConf.$id.includes(id)) {
|
|
655
|
+
return withFieldsEntity;
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
if (currentFieldConf.$id === id) {
|
|
660
|
+
return withFieldsEntity;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (withFieldsEntity.$fields && withFieldsEntity.$fields.includes("id") && !withFieldsEntity.$show) {
|
|
664
|
+
let returnVal = "";
|
|
665
|
+
withFieldsEntity.$fields.forEach((field) => {
|
|
666
|
+
if (field === "id") {
|
|
667
|
+
returnVal = withFieldsEntity.$id;
|
|
668
|
+
} else {
|
|
669
|
+
returnVal = withFieldsEntity;
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
return returnVal;
|
|
673
|
+
}
|
|
674
|
+
return withFieldsEntity;
|
|
675
|
+
}
|
|
676
|
+
return null;
|
|
677
|
+
}).filter((x) => x);
|
|
678
|
+
var replaceBzIds = (resItems, things) => {
|
|
679
|
+
const mapping = {};
|
|
680
|
+
things.forEach((thing) => {
|
|
681
|
+
mapping[thing.$bzId] = thing.$id;
|
|
682
|
+
});
|
|
683
|
+
resItems.forEach((item) => {
|
|
684
|
+
Object.keys(item).forEach((key) => {
|
|
685
|
+
if (mapping[item[key]] && key !== "$tempId") {
|
|
686
|
+
item[key] = mapping[item[key]];
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
return resItems;
|
|
691
|
+
};
|
|
692
|
+
var hasMatches = (resItems, things) => {
|
|
693
|
+
const bzIds = things.map((thing) => thing.$bzId);
|
|
694
|
+
let found = false;
|
|
695
|
+
resItems.forEach((item) => {
|
|
696
|
+
Object.keys(item).forEach((key) => {
|
|
697
|
+
if (bzIds.includes(item[key])) {
|
|
698
|
+
found = true;
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
});
|
|
702
|
+
return found;
|
|
703
|
+
};
|
|
704
|
+
var buildBQLTree = async (req, res) => {
|
|
705
|
+
const { bqlRequest, config, schema } = req;
|
|
706
|
+
const { cache } = res;
|
|
707
|
+
if (!bqlRequest) {
|
|
708
|
+
throw new Error("BQL request not parsed");
|
|
709
|
+
}
|
|
710
|
+
const { query } = bqlRequest;
|
|
711
|
+
if (!query) {
|
|
712
|
+
const resItems = res.bqlRes[0] ? res.bqlRes : [res.bqlRes];
|
|
713
|
+
const things = req.bqlRequest?.mutation?.things;
|
|
714
|
+
const matchesFound = hasMatches(resItems, things);
|
|
715
|
+
if (matchesFound) {
|
|
716
|
+
const replaced = replaceBzIds(resItems, things);
|
|
717
|
+
res.bqlRes = replaced[1] ? replaced : replaced[0];
|
|
718
|
+
}
|
|
719
|
+
const output = cleanOutput(res.bqlRes, config);
|
|
720
|
+
res.bqlRes = output;
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (!cache) {
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
const thingSchema = "$entity" in query ? query.$entity : query.$relation;
|
|
727
|
+
const entityMap = cache.entities.get(thingSchema.name);
|
|
728
|
+
if (!entityMap) {
|
|
729
|
+
res.bqlRes = null;
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const filterFields = listify2(query.$filter, (x) => x);
|
|
733
|
+
const atLeastOneUnique = filterFields.some(
|
|
734
|
+
(x) => thingSchema.dataFields?.find((y) => y.path === x)?.validations?.unique
|
|
735
|
+
);
|
|
736
|
+
const monoOutput = !Array.isArray(bqlRequest.query) && (bqlRequest.query?.$id && !Array.isArray(bqlRequest.query?.$id) || atLeastOneUnique);
|
|
737
|
+
if (Array.isArray(req.rawBqlRequest)) {
|
|
738
|
+
throw new Error("Query arrays not implemented yet");
|
|
739
|
+
}
|
|
740
|
+
const rootThings = entityMap;
|
|
741
|
+
const structuredAnswer = [...rootThings].length ? [...rootThings].map(([id, _entity]) => ({
|
|
742
|
+
...req.rawBqlRequest,
|
|
743
|
+
$id: id
|
|
744
|
+
})) : req.rawBqlRequest;
|
|
745
|
+
const bqlTree = produce2(
|
|
746
|
+
structuredAnswer,
|
|
747
|
+
(draft) => traverse2(draft, ({ value: val }) => {
|
|
748
|
+
const value = val;
|
|
749
|
+
if (!value?.$entity && !value?.$relation) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const thingName = "$entity" in value ? value.$entity : value.$relation;
|
|
753
|
+
if (thingName) {
|
|
754
|
+
const currentIds = Array.isArray(value.$id) ? value.$id : [value.$id];
|
|
755
|
+
const currentSchema = "$relation" in value ? schema.relations[value.$relation] : schema.entities[value.$entity];
|
|
756
|
+
const { dataFields, roleFields, fields } = getCurrentFields(currentSchema);
|
|
757
|
+
if (config.query?.returnNulls) {
|
|
758
|
+
const queriedPaths = value.$fields ? value.$fields.map((x) => typeof x === "string" ? x : x.$path) : fields;
|
|
759
|
+
queriedPaths.forEach((path) => {
|
|
760
|
+
value[path] = null;
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
const currentEntities = cache.entities.get(thingName);
|
|
764
|
+
if (!currentEntities) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
[...currentEntities].forEach(([id, entity]) => {
|
|
768
|
+
if (currentIds.includes(id)) {
|
|
769
|
+
const queriedDataFields = value.$fields ? value.$fields : dataFields;
|
|
770
|
+
const { virtualFields } = currentSchema;
|
|
771
|
+
virtualFields?.forEach((virtualField) => {
|
|
772
|
+
if (queriedDataFields?.includes(virtualField) && // @ts-expect-error - No need to compute if it was received somehow from the DB or if it is not a virtual field
|
|
773
|
+
(value[virtualField] === void 0 || value[virtualField] === null)) {
|
|
774
|
+
const fieldSchema = currentSchema.dataFields?.find((x) => x.path === virtualField);
|
|
775
|
+
const computedValue = compute({ currentThing: entity, fieldSchema });
|
|
776
|
+
value[virtualField] = computedValue;
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
listify2(entity, (k, v) => {
|
|
780
|
+
if (k.startsWith("$")) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (!queriedDataFields?.includes(k)) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
value[k] = v;
|
|
787
|
+
});
|
|
788
|
+
const links = cache.roleLinks.get(id);
|
|
789
|
+
const flatRoleFields = value.$fields ? value.$fields.filter((x) => typeof x === "string") : roleFields;
|
|
790
|
+
const embeddedRoleFields = value.$fields?.filter((x) => typeof x === "object")?.map((y) => y.$path) || [];
|
|
791
|
+
Object.entries(links || {}).forEach(([rolePath, linkedIds]) => {
|
|
792
|
+
if (![...flatRoleFields, ...embeddedRoleFields].includes(rolePath)) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
if (!("roles" in currentSchema)) {
|
|
796
|
+
throw new Error("No roles in schema");
|
|
797
|
+
}
|
|
798
|
+
const uniqueLinkedIds = !Array.isArray(linkedIds) ? [linkedIds] : [...new Set(linkedIds)];
|
|
799
|
+
const { cardinality, playedBy } = currentSchema.roles[rolePath];
|
|
800
|
+
const thingNames = [...new Set(playedBy?.map((x) => x.thing))];
|
|
801
|
+
const children = thingNames?.flatMap((x) => {
|
|
802
|
+
const thingEntities = cache.entities.get(x);
|
|
803
|
+
if (!thingEntities) {
|
|
804
|
+
return [];
|
|
805
|
+
}
|
|
806
|
+
return filterChildrenEntities([...thingEntities], uniqueLinkedIds, value, rolePath);
|
|
807
|
+
});
|
|
808
|
+
if (children?.length) {
|
|
809
|
+
if (children.length === 1 && children[0] === value.$id) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (cardinality === "ONE") {
|
|
813
|
+
value[rolePath] = children[0];
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
value[rolePath] = children.filter((x) => typeof x === "string" || typeof x === "object" && x?.$show);
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
const currentLinkFields = currentSchema.linkFields;
|
|
822
|
+
if (currentLinkFields) {
|
|
823
|
+
currentLinkFields.forEach((linkField) => {
|
|
824
|
+
const currentRelation = cache.relations.get(linkField.relation);
|
|
825
|
+
const tunnel = linkField.oppositeLinkFieldsPlayedBy;
|
|
826
|
+
if (!currentRelation) {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
if (linkField.target === "relation") {
|
|
830
|
+
const linkedEntities = [...currentRelation].reduce((acc, relation) => {
|
|
831
|
+
const id = relation.get(linkField.plays)?.id;
|
|
832
|
+
if (id && id === currentIds[0]) {
|
|
833
|
+
const opposingRole = relation.get(linkField.relation);
|
|
834
|
+
if (!opposingRole) {
|
|
835
|
+
return acc;
|
|
836
|
+
}
|
|
837
|
+
if (!acc[opposingRole.entityName]) {
|
|
838
|
+
acc[opposingRole.entityName] = /* @__PURE__ */ new Set();
|
|
839
|
+
}
|
|
840
|
+
acc[opposingRole.entityName].add(opposingRole.id);
|
|
841
|
+
}
|
|
842
|
+
return acc;
|
|
843
|
+
}, {});
|
|
844
|
+
Object.entries(linkedEntities).map(([key, linkedEntityVal]) => {
|
|
845
|
+
const allCurrentLinkFieldThings = cache.entities.get(key);
|
|
846
|
+
if (!allCurrentLinkFieldThings) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
const children = filterChildrenEntities(
|
|
850
|
+
[...allCurrentLinkFieldThings],
|
|
851
|
+
[...linkedEntityVal.values()],
|
|
852
|
+
value,
|
|
853
|
+
linkField.path
|
|
854
|
+
).filter(notNull).filter(isStringOrHasShow);
|
|
855
|
+
if (children.length === 0) {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
if (children && children.length) {
|
|
859
|
+
if (linkField.cardinality === "ONE") {
|
|
860
|
+
value[linkField.path] = children[0];
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
value[linkField.path] = children;
|
|
864
|
+
}
|
|
865
|
+
return null;
|
|
866
|
+
});
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
if (linkField.target === "role") {
|
|
870
|
+
tunnel.forEach((t) => {
|
|
871
|
+
if (!currentRelation) {
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
const linkedEntities = [...currentRelation].reduce((acc, relation) => {
|
|
875
|
+
const id = relation.get(linkField.plays)?.id;
|
|
876
|
+
if (id && id === currentIds[0]) {
|
|
877
|
+
const opposingRole = relation.get(t.plays);
|
|
878
|
+
if (!opposingRole) {
|
|
879
|
+
return acc;
|
|
880
|
+
}
|
|
881
|
+
if (!acc[opposingRole.entityName]) {
|
|
882
|
+
acc[opposingRole.entityName] = /* @__PURE__ */ new Set();
|
|
883
|
+
}
|
|
884
|
+
acc[opposingRole.entityName].add(opposingRole.id);
|
|
885
|
+
}
|
|
886
|
+
return acc;
|
|
887
|
+
}, {});
|
|
888
|
+
Object.entries(linkedEntities).forEach(([key, linkedEntityVal]) => {
|
|
889
|
+
const allCurrentLinkFieldThings = cache.entities.get(key);
|
|
890
|
+
if (!allCurrentLinkFieldThings) {
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const children = filterChildrenEntities(
|
|
894
|
+
[...allCurrentLinkFieldThings],
|
|
895
|
+
[...linkedEntityVal.values()],
|
|
896
|
+
value,
|
|
897
|
+
linkField.path
|
|
898
|
+
).filter(notNull).filter(isStringOrHasShow);
|
|
899
|
+
if (children.length === 0) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (children && children.length) {
|
|
903
|
+
if (linkField.cardinality === "ONE") {
|
|
904
|
+
value[linkField.path] = children[0];
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
value[linkField.path] = children;
|
|
908
|
+
let filtered = {};
|
|
909
|
+
const filterFunc = (o) => {
|
|
910
|
+
if (o.$path === linkField.path) {
|
|
911
|
+
filtered = o;
|
|
912
|
+
}
|
|
913
|
+
return o?.$fields?.forEach(filterFunc);
|
|
914
|
+
};
|
|
915
|
+
let pathAndIdMatch = "";
|
|
916
|
+
query.$fields?.forEach(filterFunc);
|
|
917
|
+
if (filtered) {
|
|
918
|
+
if (!Array.isArray(filtered.$id)) {
|
|
919
|
+
pathAndIdMatch = filtered.$id;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
value[linkField.path] = isOne(children, pathAndIdMatch);
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return null;
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
})
|
|
932
|
+
);
|
|
933
|
+
const withoutFieldFilters = cleanOutput(bqlTree, config);
|
|
934
|
+
res.bqlRes = monoOutput ? withoutFieldFilters[0] : withoutFieldFilters;
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
// src/pipeline/preprocess/buildTQLMutation.ts
|
|
938
|
+
import { isArray, listify as listify3, mapEntries as mapEntries2, shake } from "radash";
|
|
939
|
+
var buildTQLMutation = async (req) => {
|
|
940
|
+
const { bqlRequest, schema } = req;
|
|
941
|
+
if (!bqlRequest) {
|
|
942
|
+
throw new Error("BQL request not parsed");
|
|
943
|
+
}
|
|
944
|
+
const { mutation } = bqlRequest;
|
|
945
|
+
if (!mutation) {
|
|
946
|
+
throw new Error("BQL request is not a mutation");
|
|
947
|
+
}
|
|
948
|
+
const nodeToTypeQL = (node) => {
|
|
949
|
+
const op = node.$op;
|
|
950
|
+
const bzId = `$${node.$bzId}`;
|
|
951
|
+
const currentSchema = getCurrentSchema(schema, node);
|
|
952
|
+
const { idFields, defaultDBConnector } = currentSchema;
|
|
953
|
+
const thingDbPath = defaultDBConnector?.path || node.$entity || node.$relation;
|
|
954
|
+
const idValue = node.$id;
|
|
955
|
+
const idField = idFields?.[0];
|
|
956
|
+
const attributes = listify3(node, (k, v) => {
|
|
957
|
+
if (k.startsWith("$") || k === idField || v === void 0 || v === null) {
|
|
958
|
+
return "";
|
|
959
|
+
}
|
|
960
|
+
const currentDataField = currentSchema.dataFields?.find((x) => x.path === k);
|
|
961
|
+
const fieldDbPath = currentDataField?.path;
|
|
962
|
+
if (!fieldDbPath) {
|
|
963
|
+
return "";
|
|
964
|
+
}
|
|
965
|
+
const dbField = currentDataField.dbPath;
|
|
966
|
+
if (["TEXT", "ID", "EMAIL"].includes(currentDataField.contentType)) {
|
|
967
|
+
return `has ${dbField} '${v}'`;
|
|
968
|
+
}
|
|
969
|
+
if (["NUMBER", "BOOLEAN"].includes(currentDataField.contentType)) {
|
|
970
|
+
return `has ${dbField} ${v}`;
|
|
971
|
+
}
|
|
972
|
+
if (currentDataField.contentType === "DATE") {
|
|
973
|
+
if (Number.isNaN(v.valueOf())) {
|
|
974
|
+
throw new Error("Invalid format, Nan Date");
|
|
975
|
+
}
|
|
976
|
+
if (v instanceof Date) {
|
|
977
|
+
return `has ${dbField} ${v.toISOString().replace("Z", "")}`;
|
|
978
|
+
}
|
|
979
|
+
return `has ${dbField} ${new Date(v).toISOString().replace("Z", "")}`;
|
|
980
|
+
}
|
|
981
|
+
throw new Error(`Unsupported contentType ${currentDataField.contentType}`);
|
|
982
|
+
}).filter((x) => x);
|
|
983
|
+
const attributesVar = `${bzId}-atts`;
|
|
984
|
+
const matchAttributes = listify3(node, (k) => {
|
|
985
|
+
if (k.startsWith("$") || k === idField) {
|
|
986
|
+
return "";
|
|
987
|
+
}
|
|
988
|
+
const currentDataField = currentSchema.dataFields?.find((x) => x.path === k);
|
|
989
|
+
const fieldDbPath = currentDataField?.path;
|
|
990
|
+
if (!fieldDbPath) {
|
|
991
|
+
return "";
|
|
992
|
+
}
|
|
993
|
+
const dbField = currentDataField.dbPath;
|
|
994
|
+
return `{${attributesVar} isa ${dbField};}`;
|
|
995
|
+
}).filter((x) => x);
|
|
996
|
+
const isLocalId = node[Symbol.for("isLocalId")];
|
|
997
|
+
const idValueTQL = isArray(idValue) ? `like '${idValue.join("|")}'` : `'${idValue}'`;
|
|
998
|
+
const idAttributes = !isLocalId && idValue ? (
|
|
999
|
+
// if it is a relation, add only the id fields in the lines where we add the roles also so it does not get defined twice
|
|
1000
|
+
[`has ${idField} ${idValueTQL}`]
|
|
1001
|
+
) : [];
|
|
1002
|
+
const allAttributes = [...idAttributes, ...attributes].filter((x) => x).join(",");
|
|
1003
|
+
const getDeletionMatchInNodes = () => {
|
|
1004
|
+
if (op === "delete" || op === "unlink" || op === "match") {
|
|
1005
|
+
return `${bzId} isa ${[thingDbPath, ...idAttributes].filter((x) => x).join(",")};`;
|
|
1006
|
+
}
|
|
1007
|
+
if (op === "update") {
|
|
1008
|
+
if (!matchAttributes.length) {
|
|
1009
|
+
throw new Error("update without attributes");
|
|
1010
|
+
}
|
|
1011
|
+
return `${bzId} isa ${[thingDbPath, ...idAttributes].filter((x) => x).join(",")}, has ${attributesVar};
|
|
1012
|
+
${matchAttributes.join(" or ")};`;
|
|
1013
|
+
}
|
|
1014
|
+
return "";
|
|
1015
|
+
};
|
|
1016
|
+
const getInsertionMatchInNodes = () => {
|
|
1017
|
+
if (op === "update" || op === "link" || op === "match") {
|
|
1018
|
+
return `${bzId} isa ${[thingDbPath, ...idAttributes].filter((x) => x).join(",")};`;
|
|
1019
|
+
}
|
|
1020
|
+
return "";
|
|
1021
|
+
};
|
|
1022
|
+
if (node.$entity || node.$relation) {
|
|
1023
|
+
return {
|
|
1024
|
+
op,
|
|
1025
|
+
deletionMatch: getDeletionMatchInNodes(),
|
|
1026
|
+
insertionMatch: getInsertionMatchInNodes(),
|
|
1027
|
+
insertion: op === "create" ? `${bzId} isa ${[thingDbPath, allAttributes].filter((x) => x).join(",")};` : op === "update" && attributes.length ? `${bzId} ${attributes.join(",")};` : "",
|
|
1028
|
+
deletion: op === "delete" ? `${bzId} isa ${thingDbPath};` : op === "update" && matchAttributes.length ? `${bzId} has ${attributesVar};` : ""
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
throw new Error("in attributes");
|
|
1032
|
+
};
|
|
1033
|
+
const edgeToTypeQL = (node) => {
|
|
1034
|
+
const op = node.$op;
|
|
1035
|
+
const currentSchema = getCurrentSchema(schema, node);
|
|
1036
|
+
const bzId = `$${node.$bzId}`;
|
|
1037
|
+
const idValue = node.$id;
|
|
1038
|
+
const relationDbPath = currentSchema.defaultDBConnector?.path || node.$relation;
|
|
1039
|
+
const roleFields = "roles" in currentSchema ? listify3(currentSchema.roles, (k) => k) : [];
|
|
1040
|
+
const roleDbPaths = node.$relation && "roles" in currentSchema && mapEntries2(currentSchema.roles, (k, v) => [k, v.dbConnector?.path || k]);
|
|
1041
|
+
const fromRoleFields = listify3(node, (k, v) => {
|
|
1042
|
+
if (!roleFields.includes(k)) {
|
|
1043
|
+
return null;
|
|
1044
|
+
}
|
|
1045
|
+
if (!("roles" in currentSchema)) {
|
|
1046
|
+
throw new Error("This should have roles! ");
|
|
1047
|
+
}
|
|
1048
|
+
const roleDbPath = roleDbPaths[k];
|
|
1049
|
+
if (Array.isArray(v)) {
|
|
1050
|
+
return v.map((x) => ({ path: roleDbPath, id: x }));
|
|
1051
|
+
}
|
|
1052
|
+
return { path: roleDbPath, id: v };
|
|
1053
|
+
}).filter((x) => x).flat();
|
|
1054
|
+
const fromRoleFieldsTql = fromRoleFields.map((x) => {
|
|
1055
|
+
if (!x?.path) {
|
|
1056
|
+
throw new Error("Object without path");
|
|
1057
|
+
}
|
|
1058
|
+
return `${x.path}: $${x.id}`;
|
|
1059
|
+
});
|
|
1060
|
+
const roles = fromRoleFields.length > 0 ? `( ${fromRoleFieldsTql.join(" , ")} )` : "";
|
|
1061
|
+
const relationTql = !roles ? "" : `${bzId} ${roles} ${node[Symbol.for("edgeType")] === "linkField" || op === "delete" || op === "unlink" ? `isa ${relationDbPath}` : ""}`;
|
|
1062
|
+
const relationTqlWithoutRoles = `${bzId} ${node[Symbol.for("edgeType")] === "linkField" || op === "delete" ? `isa ${relationDbPath}` : ""}`;
|
|
1063
|
+
const getInsertionsInEdges = () => {
|
|
1064
|
+
if (!relationTql) {
|
|
1065
|
+
return "";
|
|
1066
|
+
}
|
|
1067
|
+
if (op === "link") {
|
|
1068
|
+
return `${relationTql};`;
|
|
1069
|
+
}
|
|
1070
|
+
if (op === "create") {
|
|
1071
|
+
return `${relationTql}, has id '${idValue}';`;
|
|
1072
|
+
}
|
|
1073
|
+
return "";
|
|
1074
|
+
};
|
|
1075
|
+
const getInsertionMatchInEdges = () => {
|
|
1076
|
+
if (!relationTql) {
|
|
1077
|
+
return "";
|
|
1078
|
+
}
|
|
1079
|
+
if (op === "match") {
|
|
1080
|
+
return `${relationTql};`;
|
|
1081
|
+
}
|
|
1082
|
+
return "";
|
|
1083
|
+
};
|
|
1084
|
+
const getDeletionMatchInEdges = () => {
|
|
1085
|
+
if (!relationTql) {
|
|
1086
|
+
return "";
|
|
1087
|
+
}
|
|
1088
|
+
if (op === "delete") {
|
|
1089
|
+
return `${relationTql};`;
|
|
1090
|
+
}
|
|
1091
|
+
if (op === "unlink") {
|
|
1092
|
+
}
|
|
1093
|
+
if (op === "match") {
|
|
1094
|
+
return `${relationTql};`;
|
|
1095
|
+
}
|
|
1096
|
+
return "";
|
|
1097
|
+
};
|
|
1098
|
+
const getDeletionsInEdges = () => {
|
|
1099
|
+
if (!relationTql) {
|
|
1100
|
+
return "";
|
|
1101
|
+
}
|
|
1102
|
+
if (op === "delete") {
|
|
1103
|
+
return `${relationTqlWithoutRoles};`;
|
|
1104
|
+
}
|
|
1105
|
+
if (op === "unlink") {
|
|
1106
|
+
return `${bzId} ${roles};`;
|
|
1107
|
+
}
|
|
1108
|
+
return "";
|
|
1109
|
+
};
|
|
1110
|
+
return {
|
|
1111
|
+
// preDeletionBatch: getPreDeletionBatch(),
|
|
1112
|
+
deletionMatch: getDeletionMatchInEdges(),
|
|
1113
|
+
insertionMatch: getInsertionMatchInEdges(),
|
|
1114
|
+
deletion: getDeletionsInEdges(),
|
|
1115
|
+
insertion: getInsertionsInEdges(),
|
|
1116
|
+
op: ""
|
|
1117
|
+
};
|
|
1118
|
+
};
|
|
1119
|
+
const toTypeQL = (nodes, mode) => {
|
|
1120
|
+
const typeQL = mode === "edges" ? edgeToTypeQL : nodeToTypeQL;
|
|
1121
|
+
if (Array.isArray(nodes)) {
|
|
1122
|
+
return nodes.map((x) => {
|
|
1123
|
+
const { preDeletionBatch: preDeletionBatch2, insertionMatch: insertionMatch2, deletionMatch: deletionMatch2, insertion: insertion2, deletion: deletion2 } = typeQL(x);
|
|
1124
|
+
return shake({ preDeletionBatch: preDeletionBatch2, insertionMatch: insertionMatch2, deletionMatch: deletionMatch2, insertion: insertion2, deletion: deletion2 }, (z) => !z);
|
|
1125
|
+
}).filter((y) => y);
|
|
1126
|
+
}
|
|
1127
|
+
const { preDeletionBatch, insertionMatch, deletionMatch, insertion, deletion } = typeQL(nodes);
|
|
1128
|
+
return shake({ preDeletionBatch, insertionMatch, deletionMatch, insertion, deletion }, (z) => !z);
|
|
1129
|
+
};
|
|
1130
|
+
const nodeOperations = toTypeQL(mutation.things);
|
|
1131
|
+
const arrayNodeOperations = Array.isArray(nodeOperations) ? nodeOperations : [nodeOperations];
|
|
1132
|
+
const edgeOperations = toTypeQL(mutation.edges, "edges");
|
|
1133
|
+
const arrayEdgeOperations = Array.isArray(edgeOperations) ? edgeOperations : [edgeOperations];
|
|
1134
|
+
const allOperations = [...arrayNodeOperations, ...arrayEdgeOperations];
|
|
1135
|
+
const tqlRequest = shake(
|
|
1136
|
+
{
|
|
1137
|
+
// preDeletionBatch: allOperations.flatMap((x) => x.preDeletionBatch).filter((y) => y !== undefined),
|
|
1138
|
+
insertionMatches: allOperations.map((x) => x.insertionMatch).join(" ").trim(),
|
|
1139
|
+
deletionMatches: allOperations.map((x) => x.deletionMatch).join(" ").trim(),
|
|
1140
|
+
insertions: allOperations.map((x) => x.insertion).join(" ").trim(),
|
|
1141
|
+
deletions: allOperations.map((x) => x.deletion).join(" ").trim()
|
|
1142
|
+
// ...(typeQLRelations?.length && { relations: typeQLRelations }),
|
|
1143
|
+
},
|
|
1144
|
+
(x) => !x
|
|
1145
|
+
);
|
|
1146
|
+
req.tqlRequest = tqlRequest;
|
|
1147
|
+
};
|
|
35
1148
|
|
|
36
|
-
|
|
1149
|
+
// src/pipeline/preprocess/fill.ts
|
|
1150
|
+
import { produce as produce3, current } from "immer";
|
|
1151
|
+
import { traverse as traverse3, getNodeByPath } from "object-traversal";
|
|
1152
|
+
import { isObject as isObject3, listify as listify4, shake as shake2 } from "radash";
|
|
1153
|
+
import { v4 as uuidv4 } from "uuid";
|
|
1154
|
+
var sanitizeTempId = (id) => {
|
|
1155
|
+
if (!id.startsWith("_:")) {
|
|
1156
|
+
throw new Error("ID must start with '_:'.");
|
|
1157
|
+
}
|
|
1158
|
+
const sanitizedId = id.substring(2);
|
|
1159
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(sanitizedId)) {
|
|
1160
|
+
throw new Error("$tempId must contain only alphanumeric characters, hyphens, and underscores.");
|
|
1161
|
+
}
|
|
1162
|
+
if (id.length > 36) {
|
|
1163
|
+
throw new Error("$tempId must not be longer than 36 characters.");
|
|
1164
|
+
}
|
|
1165
|
+
return sanitizedId;
|
|
1166
|
+
};
|
|
1167
|
+
var fillBQLMutation = async (req) => {
|
|
1168
|
+
const { rawBqlRequest, schema } = req;
|
|
1169
|
+
const shakeBqlRequest = (blocks) => {
|
|
1170
|
+
return produce3(
|
|
1171
|
+
blocks,
|
|
1172
|
+
(draft) => traverse3(draft, ({ value: val, key, parent }) => {
|
|
1173
|
+
if (isObject3(val)) {
|
|
1174
|
+
val = shake2(val, (att) => att === void 0);
|
|
1175
|
+
}
|
|
1176
|
+
if (key === "$tempId") {
|
|
1177
|
+
parent[key] = sanitizeTempId(val);
|
|
1178
|
+
}
|
|
1179
|
+
})
|
|
1180
|
+
);
|
|
1181
|
+
};
|
|
1182
|
+
const shakedBqlRequest = shakeBqlRequest(rawBqlRequest);
|
|
1183
|
+
const stringToObjects = (blocks) => {
|
|
1184
|
+
return produce3(
|
|
1185
|
+
blocks,
|
|
1186
|
+
(draft) => traverse3(draft, ({ value: val, meta, key }) => {
|
|
1187
|
+
if (isObject3(val)) {
|
|
1188
|
+
if (val.$arrayOp) {
|
|
1189
|
+
throw new Error("Array op not supported yet");
|
|
1190
|
+
}
|
|
1191
|
+
if (key === "$filter" || meta.nodePath?.includes(".$filter.")) {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
const value = val;
|
|
1195
|
+
if (value.$op === "create" && value.$id) {
|
|
1196
|
+
throw new Error("Can't write to computed field $id. Try writing to the id field directly.");
|
|
1197
|
+
}
|
|
1198
|
+
const currentSchema = getCurrentSchema(schema, value);
|
|
1199
|
+
const nodePathArray = meta.nodePath?.split(".");
|
|
1200
|
+
const notRoot = nodePathArray?.filter((x) => Number.isNaN(parseInt(x, 10))).join(".");
|
|
1201
|
+
if (!currentSchema) {
|
|
1202
|
+
throw new Error(`Schema not found for ${value.$entity || value.$relation}`);
|
|
1203
|
+
}
|
|
1204
|
+
value.$bzId = value.$tempId ?? `T_${uuidv4()}`;
|
|
1205
|
+
value[Symbol.for("schema")] = currentSchema;
|
|
1206
|
+
value[Symbol.for("dbId")] = currentSchema.defaultDBConnector.id;
|
|
1207
|
+
const { usedLinkFields, usedRoleFields } = getCurrentFields(currentSchema, value);
|
|
1208
|
+
const usedLinkFieldsMap = usedLinkFields.map(
|
|
1209
|
+
(linkFieldPath) => ({
|
|
1210
|
+
fieldType: "linkField",
|
|
1211
|
+
path: linkFieldPath,
|
|
1212
|
+
// @ts-expect-error - TODO description
|
|
1213
|
+
schema: currentSchema.linkFields.find((y) => y.path === linkFieldPath)
|
|
1214
|
+
})
|
|
1215
|
+
);
|
|
1216
|
+
const usedRoleFieldsMap = currentSchema.thingType === "relation" ? usedRoleFields.map(
|
|
1217
|
+
(roleFieldPath) => ({
|
|
1218
|
+
fieldType: "roleField",
|
|
1219
|
+
path: roleFieldPath,
|
|
1220
|
+
schema: oFind(currentSchema.roles, (k) => k === roleFieldPath)
|
|
1221
|
+
})
|
|
1222
|
+
) : [];
|
|
1223
|
+
if (usedLinkFieldsMap.some((x) => x.schema?.target === "role") && usedLinkFieldsMap.some((x) => x.schema?.target === "relation")) {
|
|
1224
|
+
throw new Error(
|
|
1225
|
+
"Unsupported: Can't use a link field with target === 'role' and another with target === 'relation' in the same mutation."
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
const multiplayedRoles = usedRoleFieldsMap.filter(
|
|
1229
|
+
(roleField) => [...new Set(roleField.schema.playedBy?.map((x) => x.thing))].length !== 1
|
|
1230
|
+
);
|
|
1231
|
+
if (multiplayedRoles.length > 1) {
|
|
1232
|
+
throw new Error(
|
|
1233
|
+
`Field: ${multiplayedRoles[0].path} - If a role can be played by multiple things, you must specify the thing in the mutation: ${JSON.stringify(
|
|
1234
|
+
multiplayedRoles[0].schema.playedBy
|
|
1235
|
+
)}. Schema: ${JSON.stringify(multiplayedRoles[0].schema)}`
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
const currentPath = meta.nodePath;
|
|
1239
|
+
[...usedLinkFieldsMap, ...usedRoleFieldsMap]?.forEach((currentField) => {
|
|
1240
|
+
const currentValue = value[currentField.path];
|
|
1241
|
+
if (currentValue === void 0) {
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
const currentFieldSchema = currentField.schema;
|
|
1245
|
+
if (!currentFieldSchema) {
|
|
1246
|
+
throw new Error(`Field ${currentField.path} not found in schema`);
|
|
1247
|
+
}
|
|
1248
|
+
const currentEdgeSchema = (
|
|
1249
|
+
// @ts-expect-error - TODO description
|
|
1250
|
+
currentField.fieldType === "roleField" ? currentFieldSchema?.playedBy[0] : currentFieldSchema
|
|
1251
|
+
);
|
|
1252
|
+
const getCurrentRelation = () => {
|
|
1253
|
+
if (currentFieldSchema && "relation" in currentFieldSchema && currentEdgeSchema?.relation === value.$relation) {
|
|
1254
|
+
return "$self";
|
|
1255
|
+
}
|
|
1256
|
+
if (currentEdgeSchema?.relation) {
|
|
1257
|
+
return currentEdgeSchema?.relation;
|
|
1258
|
+
}
|
|
1259
|
+
return "$self";
|
|
1260
|
+
};
|
|
1261
|
+
const relation = getCurrentRelation();
|
|
1262
|
+
const relationSchema = relation === "$self" ? currentSchema : schema.relations[relation];
|
|
1263
|
+
const currentFieldRole = oFind(relationSchema.roles, (k, _v) => k === currentField.path);
|
|
1264
|
+
if (currentFieldRole?.playedBy?.length === 0) {
|
|
1265
|
+
throw new Error(`unused role: ${currentPath}.${currentField.path}`);
|
|
1266
|
+
}
|
|
1267
|
+
if (!currentFieldSchema) {
|
|
1268
|
+
throw new Error(`Field ${currentField.path} not found in schema`);
|
|
1269
|
+
}
|
|
1270
|
+
const oppositeFields = currentField.fieldType === "linkField" ? currentFieldSchema?.oppositeLinkFieldsPlayedBy : currentFieldSchema?.playedBy;
|
|
1271
|
+
if (!oppositeFields) {
|
|
1272
|
+
throw new Error(`No opposite fields found for ${JSON.stringify(currentFieldSchema)}`);
|
|
1273
|
+
}
|
|
1274
|
+
if ([...new Set(oppositeFields?.map((x) => x.thing))].length > 1) {
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
`Field: ${currentField.path} - If a role can be played by multiple things, you must specify the thing in the mutation: ${JSON.stringify(
|
|
1277
|
+
oppositeFields
|
|
1278
|
+
)}. Schema: ${JSON.stringify(currentFieldSchema)}`
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
if (currentFieldSchema.cardinality === "ONE") {
|
|
1282
|
+
if (Array.isArray(currentValue)) {
|
|
1283
|
+
throw new Error("Can't have an array in a cardinality === ONE link field");
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (currentFieldSchema.cardinality === "MANY" && currentValue !== null && !Array.isArray(currentValue) && !currentValue.$arrayOp) {
|
|
1287
|
+
throw new Error(
|
|
1288
|
+
`${// @ts-expect-error - TODO description
|
|
1289
|
+
currentField.fieldType === "linkField" ? currentFieldSchema.path : currentFieldSchema.name} is a cardinality === MANY thing. Use an array or a $arrayOp object`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
if (currentValue?.$entity || currentValue?.$relation) {
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const [childrenLinkField] = oppositeFields;
|
|
1296
|
+
const currentFieldType = "plays" in currentFieldSchema ? "linkField" : "roleField";
|
|
1297
|
+
const childrenThingObj = {
|
|
1298
|
+
[`$${childrenLinkField.thingType}`]: childrenLinkField.thing,
|
|
1299
|
+
[Symbol.for("relation")]: relation,
|
|
1300
|
+
[Symbol.for("edgeType")]: currentFieldType,
|
|
1301
|
+
[Symbol.for("parent")]: {
|
|
1302
|
+
path: currentPath,
|
|
1303
|
+
...value.$id ? { $id: value.$id } : {},
|
|
1304
|
+
...value.$tempId ? { $tempId: value.$tempId } : {},
|
|
1305
|
+
...value.filter ? { filter: value.filter } : {},
|
|
1306
|
+
links: oppositeFields
|
|
1307
|
+
},
|
|
1308
|
+
[Symbol.for("role")]: childrenLinkField.plays,
|
|
1309
|
+
// this is the currentChildren
|
|
1310
|
+
// this is the parent
|
|
1311
|
+
[Symbol.for("oppositeRole")]: "plays" in currentFieldSchema ? currentFieldSchema.plays : void 0,
|
|
1312
|
+
// todo
|
|
1313
|
+
[Symbol.for("relFieldSchema")]: currentFieldSchema
|
|
1314
|
+
};
|
|
1315
|
+
if (isObject3(currentValue)) {
|
|
1316
|
+
value[currentField.path] = {
|
|
1317
|
+
...childrenThingObj,
|
|
1318
|
+
...currentValue
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
if (Array.isArray(currentValue)) {
|
|
1322
|
+
if (currentValue.every((x) => isObject3(x))) {
|
|
1323
|
+
value[currentField.path] = currentValue.map((y) => {
|
|
1324
|
+
return {
|
|
1325
|
+
...childrenThingObj,
|
|
1326
|
+
...y
|
|
1327
|
+
};
|
|
1328
|
+
});
|
|
1329
|
+
} else if (currentValue.every((x) => typeof x === "string")) {
|
|
1330
|
+
value[currentField.path] = currentValue.map((y) => ({
|
|
1331
|
+
...childrenThingObj,
|
|
1332
|
+
$op: value.$op === "create" ? "link" : "replace",
|
|
1333
|
+
$id: y
|
|
1334
|
+
}));
|
|
1335
|
+
} else {
|
|
1336
|
+
throw new Error(`Invalid array value for ${currentField.path}`);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (typeof currentValue === "string") {
|
|
1340
|
+
value[currentField.path] = {
|
|
1341
|
+
...childrenThingObj,
|
|
1342
|
+
$op: value.$op === "create" ? "link" : "replace",
|
|
1343
|
+
// if the parent is being created, then is not a replace, is a new link
|
|
1344
|
+
$id: currentValue
|
|
1345
|
+
// todo: now all strings are ids and not tempIds, but in the future this might change
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
if (currentValue === null) {
|
|
1349
|
+
const neutralObject = {
|
|
1350
|
+
...childrenThingObj,
|
|
1351
|
+
$op: "unlink"
|
|
1352
|
+
// todo: embedded => delete
|
|
1353
|
+
};
|
|
1354
|
+
value[currentField.path] = currentFieldSchema.cardinality === "MANY" ? [neutralObject] : neutralObject;
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
if (!notRoot && !value.$entity && !value.$relation) {
|
|
1358
|
+
throw new Error("Root things must specify $entity or $relation");
|
|
1359
|
+
}
|
|
1360
|
+
if (!notRoot) {
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
})
|
|
1364
|
+
);
|
|
1365
|
+
};
|
|
1366
|
+
const withObjects = stringToObjects(shakedBqlRequest);
|
|
1367
|
+
const fill = (blocks) => {
|
|
1368
|
+
return produce3(
|
|
1369
|
+
blocks,
|
|
1370
|
+
(draft) => traverse3(draft, ({ parent, key, value: val, meta }) => {
|
|
1371
|
+
if (isObject3(val)) {
|
|
1372
|
+
if (Object.keys(val).length === 0) {
|
|
1373
|
+
throw new Error("Empty object!");
|
|
1374
|
+
}
|
|
1375
|
+
if (key === "$filter" || meta.nodePath?.includes(".$filter.")) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const value = val;
|
|
1379
|
+
const nodePathArray = meta.nodePath?.split(".");
|
|
1380
|
+
if (value.$tempId) {
|
|
1381
|
+
if (!(value.$op === void 0 || value.$op === "link" || value.$op === "create" || value.$op === "update")) {
|
|
1382
|
+
throw new Error(
|
|
1383
|
+
`Invalid op ${value.$op} for tempId. TempIds can be created, or when created in another part of the same mutation. In the future maybe we can use them to catch stuff in the DB as well and group them under the same tempId.`
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
const notRoot = nodePathArray?.filter((x) => Number.isNaN(parseInt(x, 10))).join(".");
|
|
1388
|
+
const currentPath = !notRoot ? meta.nodePath || "" : Array.isArray(parent) ? nodePathArray?.slice(0, -1).join(".") : meta.nodePath;
|
|
1389
|
+
const currentSchema = getCurrentSchema(schema, value);
|
|
1390
|
+
const { unidentifiedFields, dataFields, roleFields, linkFields } = getCurrentFields(currentSchema, value);
|
|
1391
|
+
const parentMeta = current(value)[Symbol.for("parent")];
|
|
1392
|
+
const parentPath = notRoot && parentMeta.path;
|
|
1393
|
+
const parentNode = !parentPath ? draft : getNodeByPath(draft, parentPath);
|
|
1394
|
+
const parentOp = parentNode?.$op;
|
|
1395
|
+
if (notRoot && !parentOp) {
|
|
1396
|
+
throw new Error("Error: Parent $op not detected");
|
|
1397
|
+
}
|
|
1398
|
+
const currentFieldSchema = value[Symbol.for("relFieldSchema")];
|
|
1399
|
+
if (value.$op === "replace") {
|
|
1400
|
+
if (parentOp === "create") {
|
|
1401
|
+
value.$op = "link";
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
const hasUpdatedDataFields = Object.keys(value).some((x) => dataFields?.includes(x));
|
|
1405
|
+
const hasUpdatedChildren = Object.keys(value).some((x) => [...roleFields, ...linkFields]?.includes(x));
|
|
1406
|
+
const getOp = () => {
|
|
1407
|
+
if (value.$op) {
|
|
1408
|
+
return value.$op;
|
|
1409
|
+
}
|
|
1410
|
+
if (notRoot && !value.$id && !value.$tempId && parentOp !== "create" && currentFieldSchema.cardinality === "ONE") {
|
|
1411
|
+
throw new Error(`Please specify if it is a create or an update. Path: ${meta.nodePath}`);
|
|
1412
|
+
}
|
|
1413
|
+
if (value.$tempId) {
|
|
1414
|
+
return "create";
|
|
1415
|
+
}
|
|
1416
|
+
if ((value.$id || value.$filter) && hasUpdatedDataFields) {
|
|
1417
|
+
return "update";
|
|
1418
|
+
}
|
|
1419
|
+
if ((value.$id || value.$filter) && notRoot && !hasUpdatedDataFields && !hasUpdatedChildren) {
|
|
1420
|
+
return "link";
|
|
1421
|
+
}
|
|
1422
|
+
if (!value.$filter && !value.$id && !value.$tempId) {
|
|
1423
|
+
return "create";
|
|
1424
|
+
}
|
|
1425
|
+
if ((value.$id || value.$filter) && !hasUpdatedDataFields && hasUpdatedChildren) {
|
|
1426
|
+
return "match";
|
|
1427
|
+
}
|
|
1428
|
+
throw new Error("Wrong op");
|
|
1429
|
+
};
|
|
1430
|
+
if (!value.$op) {
|
|
1431
|
+
value.$op = getOp();
|
|
1432
|
+
}
|
|
1433
|
+
if (!parent) {
|
|
1434
|
+
value.$parentKey = "";
|
|
1435
|
+
}
|
|
1436
|
+
if (typeof parent === "object") {
|
|
1437
|
+
const ArParent = Array.isArray(parent);
|
|
1438
|
+
if (ArParent) {
|
|
1439
|
+
value[Symbol.for("index")] = key;
|
|
1440
|
+
}
|
|
1441
|
+
value[Symbol.for("path")] = currentPath;
|
|
1442
|
+
value[Symbol.for("isRoot")] = !notRoot;
|
|
1443
|
+
value[Symbol.for("depth")] = notRoot?.split(".").length;
|
|
1444
|
+
}
|
|
1445
|
+
if (!value.$entity && !value.$relation) {
|
|
1446
|
+
throw new Error(`Node ${JSON.stringify(value)} without $entity/$relation`);
|
|
1447
|
+
}
|
|
1448
|
+
const { idFields, computedFields, virtualFields } = currentSchema;
|
|
1449
|
+
if (!idFields) {
|
|
1450
|
+
throw new Error("No idFields found");
|
|
1451
|
+
}
|
|
1452
|
+
const [idField] = idFields;
|
|
1453
|
+
const filledFields = listify4(value, (attKey, v) => v !== void 0 ? attKey : void 0);
|
|
1454
|
+
const virtualFilledFields = filledFields.filter((x) => virtualFields?.includes(x));
|
|
1455
|
+
if (virtualFilledFields.length > 0) {
|
|
1456
|
+
throw new Error(`Virtual fields can't be sent to DB: "${virtualFilledFields.join(",")}"`);
|
|
1457
|
+
}
|
|
1458
|
+
const missingComputedFields = computedFields.filter((x) => !filledFields.includes(x));
|
|
1459
|
+
missingComputedFields.forEach((fieldPath) => {
|
|
1460
|
+
const currentFieldDef = currentSchema.dataFields?.find((x) => x.path === fieldPath);
|
|
1461
|
+
const currentLinkDef = currentSchema.linkFields?.find((x) => x.path === fieldPath);
|
|
1462
|
+
const currentLinkedDef = currentLinkDef?.oppositeLinkFieldsPlayedBy[0];
|
|
1463
|
+
const currentRoleDef = "roles" in currentSchema ? oFind(currentSchema.roles, (k, _v) => k === fieldPath) : void 0;
|
|
1464
|
+
const currentDef = currentFieldDef || currentLinkedDef || currentRoleDef;
|
|
1465
|
+
if (!currentDef) {
|
|
1466
|
+
throw new Error(`no field Def for ${fieldPath}`);
|
|
1467
|
+
}
|
|
1468
|
+
if (fieldPath === idField && value.$op === "create" && !value[fieldPath]) {
|
|
1469
|
+
const defaultValue = compute({
|
|
1470
|
+
currentThing: value,
|
|
1471
|
+
fieldSchema: currentDef,
|
|
1472
|
+
//id is always a datafield.
|
|
1473
|
+
mandatoryDependencies: true
|
|
1474
|
+
//can't send to db without every dependency being there
|
|
1475
|
+
});
|
|
1476
|
+
value[fieldPath] = defaultValue;
|
|
1477
|
+
value.$id = defaultValue;
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
if (unidentifiedFields.length > 0) {
|
|
1481
|
+
throw new Error(`Unknown fields: [${unidentifiedFields.join(",")}] in ${JSON.stringify(value)}`);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
})
|
|
1485
|
+
);
|
|
1486
|
+
};
|
|
1487
|
+
const filledBQLMutation = fill(withObjects);
|
|
1488
|
+
if (Array.isArray(filledBQLMutation)) {
|
|
1489
|
+
req.filledBqlRequest = filledBQLMutation;
|
|
1490
|
+
} else {
|
|
1491
|
+
req.filledBqlRequest = filledBQLMutation;
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
// src/pipeline/preprocess/parseBQLMutation.ts
|
|
1496
|
+
import { getNodeByPath as getNodeByPath2, traverse as traverse4 } from "object-traversal";
|
|
1497
|
+
import { isArray as isArray2, isObject as isObject4, mapEntries as mapEntries3, pick, shake as shake3 } from "radash";
|
|
1498
|
+
import { v4 as uuidv42 } from "uuid";
|
|
1499
|
+
var parseBQLMutation = async (req) => {
|
|
1500
|
+
const { filledBqlRequest, schema } = req;
|
|
1501
|
+
const listNodes = (blocks) => {
|
|
1502
|
+
const nodes = [];
|
|
1503
|
+
const edges = [];
|
|
1504
|
+
const getIdValue = (node) => {
|
|
1505
|
+
if (node.$id) {
|
|
1506
|
+
return node.$id;
|
|
1507
|
+
}
|
|
1508
|
+
const currentSchema = getCurrentSchema(schema, node);
|
|
1509
|
+
const { idFields } = currentSchema;
|
|
1510
|
+
if (!idFields) {
|
|
1511
|
+
throw new Error(`no idFields: ${JSON.stringify(node)}`);
|
|
1512
|
+
}
|
|
1513
|
+
const [idField] = idFields;
|
|
1514
|
+
if (!idField) {
|
|
1515
|
+
throw new Error(`no idField: ${JSON.stringify(node)}`);
|
|
1516
|
+
}
|
|
1517
|
+
const idDataField = currentSchema.dataFields?.find((x) => x.path === idField);
|
|
1518
|
+
const idDefaultValue = node.$op === "create" ? idDataField?.default?.value() : null;
|
|
1519
|
+
const idValue = node[idField] || node.$id || idDefaultValue;
|
|
1520
|
+
if (!idValue) {
|
|
1521
|
+
throw new Error(`no idValue: ${JSON.stringify(node)}`);
|
|
1522
|
+
}
|
|
1523
|
+
return idValue;
|
|
1524
|
+
};
|
|
1525
|
+
const toNodes = (node) => {
|
|
1526
|
+
if (node.$op === "create") {
|
|
1527
|
+
const idValue = getIdValue(node);
|
|
1528
|
+
if (nodes.find((x) => x.$id === idValue)) {
|
|
1529
|
+
throw new Error(`Duplicate id ${idValue} for node ${JSON.stringify(node)}`);
|
|
1530
|
+
}
|
|
1531
|
+
if (edges.find((x) => x.$bzId === node.$bzId)) {
|
|
1532
|
+
throw new Error(`Duplicate $bzid ${node.$bzId} for node ${JSON.stringify(node)}`);
|
|
1533
|
+
}
|
|
1534
|
+
nodes.push({ ...node, $id: idValue });
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
if (node.$tempId && node.$op === "match") {
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
nodes.push(node);
|
|
1541
|
+
};
|
|
1542
|
+
const toEdges = (edge) => {
|
|
1543
|
+
if (edge.$op === "create") {
|
|
1544
|
+
const idValue = getIdValue(edge);
|
|
1545
|
+
if (nodes.find((x) => x.$id === idValue)) {
|
|
1546
|
+
}
|
|
1547
|
+
if (edges.find((x) => x.$bzId === edge.$bzId)) {
|
|
1548
|
+
throw new Error(`Duplicate %bzId ${edge.$bzIdd} for edge ${JSON.stringify(edge)}`);
|
|
1549
|
+
}
|
|
1550
|
+
edges.push({ ...edge, $id: idValue });
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
edges.push(edge);
|
|
1554
|
+
};
|
|
1555
|
+
const listOp = ({ value: val }) => {
|
|
1556
|
+
if (!isObject4(val)) {
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
const value = val;
|
|
1560
|
+
if (value.$entity || value.$relation) {
|
|
1561
|
+
if (!value.$op) {
|
|
1562
|
+
throw new Error(`Operation should be defined at this step ${JSON.stringify(value)}`);
|
|
1563
|
+
}
|
|
1564
|
+
if (!value.$bzId) {
|
|
1565
|
+
throw new Error("[internal error] BzId not found");
|
|
1566
|
+
}
|
|
1567
|
+
const currentThingSchema = getCurrentSchema(schema, value);
|
|
1568
|
+
const {
|
|
1569
|
+
dataFields: dataFieldPaths,
|
|
1570
|
+
roleFields: roleFieldPaths,
|
|
1571
|
+
linkFields: linkFieldPaths,
|
|
1572
|
+
usedFields
|
|
1573
|
+
} = getCurrentFields(currentThingSchema, value);
|
|
1574
|
+
const getChildOp = () => {
|
|
1575
|
+
if (value.$op === "create" || value.$op === "delete") {
|
|
1576
|
+
return value.$op;
|
|
1577
|
+
}
|
|
1578
|
+
if (value.$op === "update") {
|
|
1579
|
+
const usedDataFields = usedFields.filter((x) => dataFieldPaths?.includes(x));
|
|
1580
|
+
const usedRoleFields = usedFields.filter((x) => roleFieldPaths?.includes(x));
|
|
1581
|
+
const usedLinkFields = usedFields.filter((x) => linkFieldPaths?.includes(x));
|
|
1582
|
+
if (usedDataFields.length > 0) {
|
|
1583
|
+
return "update";
|
|
1584
|
+
}
|
|
1585
|
+
if (usedRoleFields.length > 0 || usedLinkFields.length > 0) {
|
|
1586
|
+
return "match";
|
|
1587
|
+
}
|
|
1588
|
+
throw new Error(`No fields on an $op:"update" for node ${JSON.stringify(value)}`);
|
|
1589
|
+
}
|
|
1590
|
+
return "match";
|
|
1591
|
+
};
|
|
1592
|
+
const dataObj = {
|
|
1593
|
+
...value.$entity && { $entity: value.$entity },
|
|
1594
|
+
...value.$relation && { $relation: value.$relation },
|
|
1595
|
+
...value.$id && { $id: value.$id },
|
|
1596
|
+
...value.$tempId && { $tempId: value.$tempId },
|
|
1597
|
+
...value.$filter && { $filter: value.$filter },
|
|
1598
|
+
...shake3(pick(value, dataFieldPaths || [""])),
|
|
1599
|
+
$op: getChildOp(),
|
|
1600
|
+
$bzId: value.$bzId,
|
|
1601
|
+
[Symbol.for("dbId")]: currentThingSchema.defaultDBConnector.id,
|
|
1602
|
+
// [Symbol.for('dependencies')]: value[Symbol.for('dependencies')],
|
|
1603
|
+
[Symbol.for("path")]: value[Symbol.for("path")],
|
|
1604
|
+
[Symbol.for("parent")]: value[Symbol.for("parent")],
|
|
1605
|
+
[Symbol.for("isRoot")]: value[Symbol.for("isRoot")],
|
|
1606
|
+
[Symbol.for("isLocalId")]: value[Symbol.for("isLocalId")] || false
|
|
1607
|
+
};
|
|
1608
|
+
toNodes(dataObj);
|
|
1609
|
+
if (value[Symbol.for("relation")] && value[Symbol.for("edgeType")] === "linkField") {
|
|
1610
|
+
if (value.$op === "link" || value.$op === "unlink") {
|
|
1611
|
+
if (value.$id || value.$filter) {
|
|
1612
|
+
if (value.$tempId) {
|
|
1613
|
+
throw new Error("can't specify a existing and a new element at once. Use an id/filter or a tempId");
|
|
1614
|
+
}
|
|
1615
|
+
nodes.push({ ...value, $op: "match" });
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
const ownRelation = value[Symbol.for("relation")] === value.$relation;
|
|
1619
|
+
const linkTempId = ownRelation ? value.$bzId : uuidv42();
|
|
1620
|
+
const parentMeta = value[Symbol.for("parent")];
|
|
1621
|
+
const parentPath = parentMeta.path;
|
|
1622
|
+
const parentNode = !parentPath ? blocks : getNodeByPath2(blocks, parentPath);
|
|
1623
|
+
const parentId = parentNode.$bzId;
|
|
1624
|
+
if (!parentId) {
|
|
1625
|
+
throw new Error("No parent id found");
|
|
1626
|
+
}
|
|
1627
|
+
if (value[Symbol.for("relation")] === "$self") {
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
const getLinkObjOp = () => {
|
|
1631
|
+
if (value.$op === "delete") {
|
|
1632
|
+
if (ownRelation) {
|
|
1633
|
+
return "match";
|
|
1634
|
+
}
|
|
1635
|
+
return "delete";
|
|
1636
|
+
}
|
|
1637
|
+
if (value.$op === "unlink") {
|
|
1638
|
+
if (ownRelation) {
|
|
1639
|
+
return "unlink";
|
|
1640
|
+
}
|
|
1641
|
+
return "delete";
|
|
1642
|
+
}
|
|
1643
|
+
if (value.$op === "link" || value.$op === "create") {
|
|
1644
|
+
if (ownRelation) {
|
|
1645
|
+
return "link";
|
|
1646
|
+
}
|
|
1647
|
+
return "create";
|
|
1648
|
+
}
|
|
1649
|
+
if (value.$op === "replace") {
|
|
1650
|
+
throw new Error("Unsupported: Nested replaces not implemented yet");
|
|
1651
|
+
}
|
|
1652
|
+
return "match";
|
|
1653
|
+
};
|
|
1654
|
+
const edgeType1 = {
|
|
1655
|
+
$relation: value[Symbol.for("relation")],
|
|
1656
|
+
$bzId: linkTempId,
|
|
1657
|
+
...value.$tempId ? { $tempId: value.$tempId } : {},
|
|
1658
|
+
$op: getLinkObjOp(),
|
|
1659
|
+
// roles
|
|
1660
|
+
...!ownRelation ? { [value[Symbol.for("role")]]: value.$bzId } : {},
|
|
1661
|
+
[value[Symbol.for("oppositeRole")]]: parentId,
|
|
1662
|
+
[Symbol.for("dbId")]: schema.relations[value[Symbol.for("relation")]].defaultDBConnector.id,
|
|
1663
|
+
[Symbol.for("edgeType")]: "linkField",
|
|
1664
|
+
[Symbol.for("info")]: "normal linkField",
|
|
1665
|
+
[Symbol.for("path")]: value[Symbol.for("path")],
|
|
1666
|
+
[Symbol.for("parent")]: value[Symbol.for("parent")]
|
|
1667
|
+
};
|
|
1668
|
+
toEdges(edgeType1);
|
|
1669
|
+
if ((value.$op === "unlink" || getLinkObjOp() === "unlink") && ownRelation) {
|
|
1670
|
+
toEdges({
|
|
1671
|
+
$relation: value[Symbol.for("relation")],
|
|
1672
|
+
$bzId: linkTempId,
|
|
1673
|
+
$op: "match",
|
|
1674
|
+
[value[Symbol.for("oppositeRole")]]: parentId,
|
|
1675
|
+
[Symbol.for("dbId")]: schema.relations[value[Symbol.for("relation")]].defaultDBConnector.id,
|
|
1676
|
+
[Symbol.for("edgeType")]: "linkField",
|
|
1677
|
+
[Symbol.for("info")]: "additional ownrelation unlink linkField",
|
|
1678
|
+
[Symbol.for("path")]: value[Symbol.for("path")],
|
|
1679
|
+
[Symbol.for("parent")]: value[Symbol.for("parent")]
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
if (value.$relation) {
|
|
1684
|
+
const rolesObjFiltered = oFilter(value, (k, _v) => roleFieldPaths.includes(k));
|
|
1685
|
+
const rolesObjOnlyIds = mapEntries3(rolesObjFiltered, (k, v) => {
|
|
1686
|
+
if (isArray2(v)) {
|
|
1687
|
+
return [k, v];
|
|
1688
|
+
}
|
|
1689
|
+
if (isObject4(v)) {
|
|
1690
|
+
return [k, v.$bzId];
|
|
1691
|
+
}
|
|
1692
|
+
return [k, v];
|
|
1693
|
+
});
|
|
1694
|
+
const objWithMetaDataOnly = oFilter(val, (k, _v) => {
|
|
1695
|
+
return k.startsWith("$") || k.startsWith("Symbol");
|
|
1696
|
+
});
|
|
1697
|
+
if (Object.keys(rolesObjFiltered).filter((x) => !x.startsWith("$")).length > 0) {
|
|
1698
|
+
if (value.$op === "create" || value.$op === "delete") {
|
|
1699
|
+
const getEdgeOp = () => {
|
|
1700
|
+
if (value.$op === "create") {
|
|
1701
|
+
return "link";
|
|
1702
|
+
}
|
|
1703
|
+
if (value.$op === "delete") {
|
|
1704
|
+
return "match";
|
|
1705
|
+
}
|
|
1706
|
+
throw new Error("Unsupported parent of edge op");
|
|
1707
|
+
};
|
|
1708
|
+
const rolesObjOnlyIdsGrouped = mapEntries3(rolesObjOnlyIds, (k, v) => {
|
|
1709
|
+
if (Array.isArray(v)) {
|
|
1710
|
+
return [k, v.map((vNested) => vNested.$bzId || vNested)];
|
|
1711
|
+
}
|
|
1712
|
+
return [k, v.$bzId || v];
|
|
1713
|
+
});
|
|
1714
|
+
const edgeType2 = {
|
|
1715
|
+
...objWithMetaDataOnly,
|
|
1716
|
+
$relation: value.$relation,
|
|
1717
|
+
$op: getEdgeOp(),
|
|
1718
|
+
...rolesObjOnlyIdsGrouped,
|
|
1719
|
+
// override role fields by ids or tempIDs
|
|
1720
|
+
$bzId: value.$bzId,
|
|
1721
|
+
[Symbol.for("path")]: value[Symbol.for("path")],
|
|
1722
|
+
[Symbol.for("dbId")]: currentThingSchema.defaultDBConnector.id,
|
|
1723
|
+
[Symbol.for("info")]: "coming from created or deleted relation",
|
|
1724
|
+
[Symbol.for("edgeType")]: "roleField on C/D"
|
|
1725
|
+
};
|
|
1726
|
+
toEdges(edgeType2);
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
if (value.$op === "match" || value.$op === "update" && Object.keys(rolesObjFiltered).length > 0) {
|
|
1730
|
+
let totalUnlinks = 0;
|
|
1731
|
+
Object.entries(rolesObjFiltered).forEach(([role, operations]) => {
|
|
1732
|
+
const operationsArray = isArray2(operations) ? operations : [operations];
|
|
1733
|
+
const getOp = (childOp) => {
|
|
1734
|
+
if (childOp === "create" || childOp === "replace") {
|
|
1735
|
+
return "link";
|
|
1736
|
+
}
|
|
1737
|
+
return childOp;
|
|
1738
|
+
};
|
|
1739
|
+
operationsArray.forEach((operation) => {
|
|
1740
|
+
if (!operation) {
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
const op = getOp(operation.$op);
|
|
1744
|
+
if (op === "replace") {
|
|
1745
|
+
throw new Error("Not supported yet: replace on roleFields");
|
|
1746
|
+
}
|
|
1747
|
+
if (op === "unlink" && totalUnlinks > 0) {
|
|
1748
|
+
totalUnlinks += 1;
|
|
1749
|
+
throw new Error(
|
|
1750
|
+
"Not supported yet: Cannot unlink more than one role at a time, please split into two mutations"
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
const edgeType3 = {
|
|
1754
|
+
...objWithMetaDataOnly,
|
|
1755
|
+
$relation: value.$relation,
|
|
1756
|
+
$op: op === "delete" ? "unlink" : op,
|
|
1757
|
+
[role]: operation.$bzId,
|
|
1758
|
+
$bzId: value.$bzId,
|
|
1759
|
+
[Symbol.for("dbId")]: currentThingSchema.defaultDBConnector.id,
|
|
1760
|
+
[Symbol.for("parent")]: value[Symbol.for("parent")],
|
|
1761
|
+
[Symbol.for("path")]: value[Symbol.for("path")],
|
|
1762
|
+
[Symbol.for("info")]: "updating roleFields",
|
|
1763
|
+
[Symbol.for("edgeType")]: "roleField on L/U/R"
|
|
1764
|
+
};
|
|
1765
|
+
toEdges(edgeType3);
|
|
1766
|
+
if (op === "unlink") {
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
traverse4(blocks, listOp);
|
|
1776
|
+
return [nodes, edges];
|
|
1777
|
+
};
|
|
1778
|
+
if (!filledBqlRequest) {
|
|
1779
|
+
throw new Error("Undefined filledBqlRequest");
|
|
1780
|
+
}
|
|
1781
|
+
const [parsedThings, parsedEdges] = listNodes(filledBqlRequest);
|
|
1782
|
+
const mergedThings = parsedThings.reduce((acc, thing) => {
|
|
1783
|
+
if (!thing.$bzId) {
|
|
1784
|
+
return [...acc, thing];
|
|
1785
|
+
}
|
|
1786
|
+
const existingIndex = acc.findIndex((t) => t.$bzId === thing.$bzId);
|
|
1787
|
+
if (existingIndex === -1) {
|
|
1788
|
+
return [...acc, thing];
|
|
1789
|
+
}
|
|
1790
|
+
if (acc[existingIndex].$op === "create" && thing.$op === "match") {
|
|
1791
|
+
return acc;
|
|
1792
|
+
}
|
|
1793
|
+
if (acc[existingIndex].$op === "match" && (thing.$op === "create" || thing.$op === "match")) {
|
|
1794
|
+
return [...acc.slice(0, existingIndex), thing, ...acc.slice(existingIndex + 1)];
|
|
1795
|
+
}
|
|
1796
|
+
throw new Error(
|
|
1797
|
+
`Unsupported operation combination for $tempId "${thing.$tempId}". Existing: ${acc[existingIndex].$op}. Current: ${thing.$op}`
|
|
1798
|
+
);
|
|
1799
|
+
}, []);
|
|
1800
|
+
const mergedEdges = parsedEdges.reduce((acc, curr) => {
|
|
1801
|
+
const existingEdge = acc.find(
|
|
1802
|
+
(r) => (r.$id && r.$id === curr.$id || r.$bzId && r.$bzId === curr.$bzId) && r.$relation === curr.$relation && r.$op === curr.$op
|
|
1803
|
+
);
|
|
1804
|
+
if (existingEdge) {
|
|
1805
|
+
const newRelation = { ...existingEdge };
|
|
1806
|
+
Object.keys(curr).forEach((key) => {
|
|
1807
|
+
if (typeof key === "symbol" || key.startsWith("$")) {
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
const existingVal = existingEdge[key];
|
|
1811
|
+
const currVal = curr[key];
|
|
1812
|
+
if (Array.isArray(existingVal) && Array.isArray(currVal)) {
|
|
1813
|
+
newRelation[key] = Array.from(/* @__PURE__ */ new Set([...existingVal, ...currVal]));
|
|
1814
|
+
} else if (!Array.isArray(existingVal) && Array.isArray(currVal)) {
|
|
1815
|
+
if (existingVal !== void 0) {
|
|
1816
|
+
newRelation[key] = Array.from(/* @__PURE__ */ new Set([existingVal, ...currVal]));
|
|
1817
|
+
} else {
|
|
1818
|
+
newRelation[key] = currVal;
|
|
1819
|
+
}
|
|
1820
|
+
} else if (Array.isArray(existingVal) && !Array.isArray(currVal)) {
|
|
1821
|
+
if (currVal !== void 0) {
|
|
1822
|
+
newRelation[key] = Array.from(/* @__PURE__ */ new Set([...existingVal, currVal]));
|
|
1823
|
+
}
|
|
1824
|
+
} else if (!existingVal) {
|
|
1825
|
+
newRelation[key] = currVal;
|
|
1826
|
+
}
|
|
1827
|
+
});
|
|
1828
|
+
const newAcc = acc.filter(
|
|
1829
|
+
(r) => !((r.$id && r.$id === curr.$id || r.$bzId && r.$bzId === curr.$bzId) && r.$relation === curr.$relation && r.$op === curr.$op)
|
|
1830
|
+
);
|
|
1831
|
+
return [...newAcc, newRelation];
|
|
1832
|
+
}
|
|
1833
|
+
return [...acc, curr];
|
|
1834
|
+
}, []);
|
|
1835
|
+
const uniqueRelations = [...new Set(mergedEdges.map((x) => x.$relation))];
|
|
1836
|
+
const _checkCardinality = () => {
|
|
1837
|
+
const problematicEdges = {};
|
|
1838
|
+
uniqueRelations.forEach((relation) => {
|
|
1839
|
+
const cardinalityOneRoles = Object.keys(schema.relations[relation].roles).filter(
|
|
1840
|
+
(role) => schema.relations[relation].roles[role].cardinality === "ONE"
|
|
1841
|
+
);
|
|
1842
|
+
cardinalityOneRoles.forEach((oneRole) => {
|
|
1843
|
+
const idMapping = {};
|
|
1844
|
+
mergedEdges.forEach((edge) => {
|
|
1845
|
+
if (edge.$relation === relation && edge[oneRole]) {
|
|
1846
|
+
const oneId = edge[oneRole];
|
|
1847
|
+
const otherRole = Object.keys(edge).find(
|
|
1848
|
+
(role) => role !== "$relation" && role !== "$op" && role !== "$id" && role !== oneRole
|
|
1849
|
+
);
|
|
1850
|
+
if (otherRole) {
|
|
1851
|
+
const otherId = edge[otherRole];
|
|
1852
|
+
if (!idMapping[otherId]) {
|
|
1853
|
+
idMapping[otherId] = /* @__PURE__ */ new Set();
|
|
1854
|
+
}
|
|
1855
|
+
idMapping[otherId].add(oneId);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
Object.entries(idMapping).forEach(([otherId, oneIds]) => {
|
|
1860
|
+
if (oneIds.size > 1) {
|
|
1861
|
+
throw new Error(
|
|
1862
|
+
`${relation} has illegal cardinality: The ${oneRole} role is linked to multiple ${Object.keys(
|
|
1863
|
+
idMapping[otherId]
|
|
1864
|
+
).join(",")} roles.`
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
});
|
|
1869
|
+
});
|
|
1870
|
+
if (Object.keys(problematicEdges).length > 0) {
|
|
1871
|
+
let errorMessage = "";
|
|
1872
|
+
Object.entries(problematicEdges).forEach(([otherId, errorSet]) => {
|
|
1873
|
+
errorMessage += `"${otherId}" is connected to many entities. ${Array.from(errorSet).join(" ")}The relation's role is of cardinality ONE.
|
|
1874
|
+
`;
|
|
1875
|
+
});
|
|
1876
|
+
throw new Error(errorMessage);
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
req.bqlRequest = {
|
|
1880
|
+
mutation: {
|
|
1881
|
+
things: mergedThings,
|
|
1882
|
+
edges: mergedEdges
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1887
|
+
// src/pipeline/transaction/runTQLMutation.ts
|
|
1888
|
+
import { TransactionType as TransactionType2 } from "typedb-driver";
|
|
1889
|
+
|
|
1890
|
+
// src/pipeline/transaction/helpers.ts
|
|
1891
|
+
import { SessionType as SessionType2 } from "typedb-driver";
|
|
1892
|
+
var getSessionOrOpenNewOne = async (dbHandles, config) => {
|
|
1893
|
+
const singleHandlerV0 = config.dbConnectors[0].id;
|
|
1894
|
+
let session = dbHandles.typeDB.get(singleHandlerV0)?.session;
|
|
1895
|
+
const client = dbHandles.typeDB.get(singleHandlerV0)?.client;
|
|
1896
|
+
if (!session || !session.isOpen()) {
|
|
1897
|
+
if (!client) {
|
|
1898
|
+
throw new Error("Client not found");
|
|
1899
|
+
}
|
|
1900
|
+
session = await client.session(config.dbConnectors[0].dbName, SessionType2.DATA);
|
|
1901
|
+
dbHandles.typeDB.set(singleHandlerV0, { client, session });
|
|
1902
|
+
}
|
|
1903
|
+
return { client, session };
|
|
1904
|
+
};
|
|
1905
|
+
|
|
1906
|
+
// src/pipeline/transaction/runTQLMutation.ts
|
|
1907
|
+
var runTQLMutation = async (req, res) => {
|
|
1908
|
+
const { dbHandles, tqlRequest, bqlRequest, config } = req;
|
|
1909
|
+
if (!tqlRequest) {
|
|
1910
|
+
throw new Error("TQL request not built");
|
|
1911
|
+
}
|
|
1912
|
+
if (!(tqlRequest.deletions && tqlRequest.deletionMatches || tqlRequest.insertions)) {
|
|
1913
|
+
throw new Error("TQL request error, no things");
|
|
1914
|
+
}
|
|
1915
|
+
if (!bqlRequest?.mutation) {
|
|
1916
|
+
throw new Error("BQL mutation not parsed");
|
|
1917
|
+
}
|
|
1918
|
+
const { session } = await getSessionOrOpenNewOne(dbHandles, config);
|
|
1919
|
+
const mutateTransaction = await session.transaction(TransactionType2.WRITE);
|
|
1920
|
+
if (!mutateTransaction) {
|
|
1921
|
+
throw new Error("Can't create transaction");
|
|
1922
|
+
}
|
|
1923
|
+
const tqlDeletion = tqlRequest.deletionMatches && tqlRequest.deletions && `match ${tqlRequest.deletionMatches} delete ${tqlRequest.deletions}`;
|
|
1924
|
+
const tqlInsertion = tqlRequest.insertions && `${tqlRequest.insertionMatches ? `match ${tqlRequest.insertionMatches}` : ""} insert ${tqlRequest.insertions}`;
|
|
1925
|
+
if (tqlDeletion) {
|
|
1926
|
+
await mutateTransaction.query.delete(tqlDeletion);
|
|
1927
|
+
}
|
|
1928
|
+
const insertionsStream = tqlInsertion && mutateTransaction.query.insert(tqlInsertion);
|
|
1929
|
+
try {
|
|
1930
|
+
const insertionsRes = insertionsStream ? await insertionsStream.collect() : void 0;
|
|
1931
|
+
await mutateTransaction.commit();
|
|
1932
|
+
await mutateTransaction.close();
|
|
1933
|
+
res.rawTqlRes = { insertions: insertionsRes };
|
|
1934
|
+
} catch (e) {
|
|
1935
|
+
await mutateTransaction.close();
|
|
1936
|
+
throw new Error(`Transaction failed: ${e.message}`);
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
|
|
1940
|
+
// src/pipeline/preprocess/buildTQLQuery.ts
|
|
1941
|
+
var separator = "___";
|
|
1942
|
+
var newBuildTQLQuery = async (req) => {
|
|
1943
|
+
const { enrichedBqlQuery } = req;
|
|
1944
|
+
if (!enrichedBqlQuery) {
|
|
1945
|
+
throw new Error("BQL query not enriched");
|
|
1946
|
+
}
|
|
1947
|
+
let tqlStr = "";
|
|
1948
|
+
const processFilters = ($filter, $var) => {
|
|
1949
|
+
let simpleHas = "";
|
|
1950
|
+
let orHas = "";
|
|
1951
|
+
for (const key in $filter) {
|
|
1952
|
+
const filterKey = $filter[key];
|
|
1953
|
+
if (Array.isArray(filterKey)) {
|
|
1954
|
+
for (let i = 0; i < filterKey.length; i++) {
|
|
1955
|
+
orHas += `{$${$var} has ${key} "${filterKey[i]}";}`;
|
|
1956
|
+
if (i < filterKey.length - 1) {
|
|
1957
|
+
orHas += "or";
|
|
1958
|
+
} else {
|
|
1959
|
+
orHas += ";";
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
} else {
|
|
1963
|
+
simpleHas += `, has ${key} "${filterKey}"`;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
simpleHas += "; \n";
|
|
1967
|
+
tqlStr += simpleHas;
|
|
1968
|
+
tqlStr += orHas;
|
|
1969
|
+
};
|
|
1970
|
+
const processDataFields = (dataFields, $path) => {
|
|
1971
|
+
const postStrParts = [];
|
|
1972
|
+
const asMetaDataParts = [];
|
|
1973
|
+
const virtualMetaDataParts = [];
|
|
1974
|
+
let $asMetaData = "";
|
|
1975
|
+
let $virtualMetaData = "";
|
|
1976
|
+
for (let i = 0; i < dataFields.length; i++) {
|
|
1977
|
+
if (!dataFields[i].$isVirtual) {
|
|
1978
|
+
postStrParts.push(` ${dataFields[i].$dbPath}`);
|
|
1979
|
+
} else {
|
|
1980
|
+
virtualMetaDataParts.push(`${dataFields[i].$dbPath}`);
|
|
1981
|
+
}
|
|
1982
|
+
asMetaDataParts.push(`{${dataFields[i].$dbPath}:${dataFields[i].$as}}`);
|
|
1983
|
+
}
|
|
1984
|
+
const postStr = `${postStrParts.join(",")};
|
|
1985
|
+
`;
|
|
1986
|
+
$asMetaData = asMetaDataParts.join(",");
|
|
1987
|
+
$virtualMetaData = virtualMetaDataParts.join(",");
|
|
1988
|
+
const $metaData = `$metadata:{as:[${$asMetaData}],virtual:[${$virtualMetaData}]}`;
|
|
1989
|
+
tqlStr += `$${$path} as "${$path}.${$metaData}.$dataFields": `;
|
|
1990
|
+
tqlStr += postStr;
|
|
1991
|
+
};
|
|
1992
|
+
const processRoleFields = (roleFields, $path, dotPath) => {
|
|
1993
|
+
for (const roleField of roleFields) {
|
|
1994
|
+
const { $fields, $as, $justId, $idNotIncluded, $filterByUnique } = roleField;
|
|
1995
|
+
const $metaData = `$metadata:{as:${$as},justId:${$justId ? "T" : "F"},idNotIncluded:${$idNotIncluded},filterByUnique:${$filterByUnique}}`;
|
|
1996
|
+
tqlStr += `"${dotPath}.${$metaData}.${roleField.$var}":{
|
|
1997
|
+
`;
|
|
1998
|
+
tqlStr += " match \n";
|
|
1999
|
+
if (roleField.$filter) {
|
|
2000
|
+
tqlStr += ` $${$path}${separator}${roleField.$var} isa ${roleField.$thing}`;
|
|
2001
|
+
processFilters(roleField.$filter, `${$path}${separator}${roleField.$var}`);
|
|
2002
|
+
}
|
|
2003
|
+
tqlStr += ` $${$path} (${roleField.$var}: $${$path}${separator}${roleField.$var}) isa ${roleField.$intermediary};
|
|
2004
|
+
`;
|
|
2005
|
+
if ($fields) {
|
|
2006
|
+
tqlStr += " fetch \n";
|
|
2007
|
+
}
|
|
2008
|
+
const dataFields = $fields?.filter((f) => f.$fieldType === "data");
|
|
2009
|
+
if (dataFields && dataFields.length > 0) {
|
|
2010
|
+
processDataFields(dataFields, `${$path}${separator}${roleField.$var}`, `${$path}.${roleField.$var}`);
|
|
2011
|
+
}
|
|
2012
|
+
const linkFields = $fields?.filter((f) => f.$fieldType === "link");
|
|
2013
|
+
if (linkFields && linkFields.length > 0) {
|
|
2014
|
+
processLinkFields(linkFields, `${$path}${separator}${roleField.$var}`, `${$path}.${roleField.$var}`);
|
|
2015
|
+
}
|
|
2016
|
+
const roleFields2 = $fields?.filter((f) => f.$fieldType === "role");
|
|
2017
|
+
if (roleFields2 && roleFields2.length > 0) {
|
|
2018
|
+
processRoleFields(roleFields2, `${$path}${separator}${roleField.$var}`, `${$path}.${roleField.$var}`);
|
|
2019
|
+
}
|
|
2020
|
+
tqlStr += "}; \n";
|
|
2021
|
+
}
|
|
2022
|
+
};
|
|
2023
|
+
const processLinkFields = (linkFields, $path, dotPath) => {
|
|
2024
|
+
for (const linkField of linkFields) {
|
|
2025
|
+
const { $fields, $as, $justId, $idNotIncluded, $filterByUnique, $playedBy } = linkField;
|
|
2026
|
+
const $metaData = `$metadata:{as:${$as},justId:${$justId ? "T" : "F"},idNotIncluded:${$idNotIncluded},filterByUnique:${$filterByUnique}}`;
|
|
2027
|
+
tqlStr += `"${dotPath}.${$metaData}.${linkField.$var}":{
|
|
2028
|
+
`;
|
|
2029
|
+
tqlStr += " match \n";
|
|
2030
|
+
if (linkField.$filter) {
|
|
2031
|
+
tqlStr += ` $${$path}${separator}${linkField.$var} isa ${linkField.$thing}`;
|
|
2032
|
+
processFilters(linkField.$filter, `${$path}${separator}${linkField.$var}`);
|
|
2033
|
+
}
|
|
2034
|
+
if (linkField.$target === "role") {
|
|
2035
|
+
tqlStr += ` $${$path}_intermediary (${linkField.$plays}: $${$path}, ${$playedBy.plays}: $${$path}${separator}${linkField.$var}) isa ${linkField.$intermediary};
|
|
2036
|
+
`;
|
|
2037
|
+
} else {
|
|
2038
|
+
tqlStr += ` $${$path}${separator}${linkField.$var} (${linkField.$plays}: $${$path}) isa ${linkField.$thing};
|
|
2039
|
+
`;
|
|
2040
|
+
}
|
|
2041
|
+
if ($fields) {
|
|
2042
|
+
tqlStr += " fetch \n";
|
|
2043
|
+
}
|
|
2044
|
+
const dataFields = $fields?.filter((f) => f.$fieldType === "data");
|
|
2045
|
+
if (dataFields && dataFields.length > 0) {
|
|
2046
|
+
processDataFields(dataFields, `${$path}${separator}${linkField.$var}`);
|
|
2047
|
+
}
|
|
2048
|
+
const linkFields2 = $fields?.filter((f) => f.$fieldType === "link");
|
|
2049
|
+
if (linkFields2 && linkFields2.length > 0) {
|
|
2050
|
+
processLinkFields(linkFields2, `${$path}${separator}${linkField.$var}`, `${$path}.${linkField.$var}`);
|
|
2051
|
+
}
|
|
2052
|
+
const roleFields = $fields?.filter((f) => f.$fieldType === "role");
|
|
2053
|
+
if (roleFields && roleFields.length > 0) {
|
|
2054
|
+
processRoleFields(roleFields, `${$path}${separator}${linkField.$var}`, `${$path}.${linkField.$var}`);
|
|
2055
|
+
}
|
|
2056
|
+
tqlStr += "}; \n";
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
const isBatched = enrichedBqlQuery.length > 1;
|
|
2060
|
+
const tqlStrings = [];
|
|
2061
|
+
const builder = (enrichedBqlQuery2) => {
|
|
2062
|
+
if (isBatched) {
|
|
2063
|
+
for (const query of enrichedBqlQuery2) {
|
|
2064
|
+
const { $path, $thing, $filter, $fields } = query;
|
|
2065
|
+
tqlStr += `match
|
|
2066
|
+
$${$path} isa ${$thing} `;
|
|
2067
|
+
if ($filter) {
|
|
2068
|
+
processFilters($filter, $path);
|
|
2069
|
+
} else {
|
|
2070
|
+
tqlStr += "; ";
|
|
2071
|
+
}
|
|
2072
|
+
if ($fields) {
|
|
2073
|
+
tqlStr += "fetch \n";
|
|
2074
|
+
}
|
|
2075
|
+
const dataFields = $fields?.filter((f) => f.$fieldType === "data");
|
|
2076
|
+
if (dataFields && dataFields.length > 0) {
|
|
2077
|
+
processDataFields(dataFields, $path);
|
|
2078
|
+
}
|
|
2079
|
+
const linkFields = $fields?.filter((f) => f.$fieldType === "link");
|
|
2080
|
+
if (linkFields && linkFields.length > 0) {
|
|
2081
|
+
processLinkFields(linkFields, $path, $path);
|
|
2082
|
+
}
|
|
2083
|
+
const roleFields = $fields?.filter((f) => f.$fieldType === "role");
|
|
2084
|
+
if (roleFields && roleFields.length > 0) {
|
|
2085
|
+
processRoleFields(roleFields, $path, $path);
|
|
2086
|
+
}
|
|
2087
|
+
tqlStrings.push(tqlStr);
|
|
2088
|
+
tqlStr = "";
|
|
2089
|
+
}
|
|
2090
|
+
} else {
|
|
2091
|
+
for (const query of enrichedBqlQuery2) {
|
|
2092
|
+
const { $path, $thing, $filter, $fields } = query;
|
|
2093
|
+
tqlStr += `match
|
|
2094
|
+
$${$path} isa ${$thing} `;
|
|
2095
|
+
if ($filter) {
|
|
2096
|
+
processFilters($filter, $path);
|
|
2097
|
+
} else {
|
|
2098
|
+
tqlStr += "; ";
|
|
2099
|
+
}
|
|
2100
|
+
if ($fields) {
|
|
2101
|
+
tqlStr += "fetch \n";
|
|
2102
|
+
}
|
|
2103
|
+
const dataFields = $fields?.filter((f) => f.$fieldType === "data");
|
|
2104
|
+
if (dataFields && dataFields.length > 0) {
|
|
2105
|
+
processDataFields(dataFields, $path);
|
|
2106
|
+
}
|
|
2107
|
+
const linkFields = $fields?.filter((f) => f.$fieldType === "link");
|
|
2108
|
+
if (linkFields && linkFields.length > 0) {
|
|
2109
|
+
processLinkFields(linkFields, $path, $path);
|
|
2110
|
+
}
|
|
2111
|
+
const roleFields = $fields?.filter((f) => f.$fieldType === "role");
|
|
2112
|
+
if (roleFields && roleFields.length > 0) {
|
|
2113
|
+
processRoleFields(roleFields, $path, $path);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
builder(enrichedBqlQuery);
|
|
2119
|
+
req.tqlRequest = isBatched ? tqlStrings : tqlStr;
|
|
2120
|
+
};
|
|
2121
|
+
|
|
2122
|
+
// src/pipeline/preprocess/enrichBQLQuery.ts
|
|
2123
|
+
import { produce as produce4 } from "immer";
|
|
2124
|
+
import { traverse as traverse5 } from "object-traversal";
|
|
2125
|
+
import { isObject as isObject5 } from "radash";
|
|
2126
|
+
var getAllFields = (currentSchema) => {
|
|
2127
|
+
const dataFields = currentSchema.dataFields?.map((field) => field.path) || [];
|
|
2128
|
+
const linkFields = currentSchema.linkFields?.map((field) => field.path) || [];
|
|
2129
|
+
const roleFields = Object.keys(currentSchema.roles || {}) || [];
|
|
2130
|
+
const allFields = [...dataFields, ...linkFields, ...roleFields];
|
|
2131
|
+
return allFields;
|
|
2132
|
+
};
|
|
2133
|
+
var checkFilterByUnique = ($filter, currentSchema) => {
|
|
2134
|
+
const fields = Object.keys($filter || {});
|
|
2135
|
+
return fields.some((field) => {
|
|
2136
|
+
if (!Array.isArray($filter[field])) {
|
|
2137
|
+
const isIdField = currentSchema.idFields?.includes(field);
|
|
2138
|
+
const isUniqueDataField = currentSchema.dataFields?.some(
|
|
2139
|
+
(f) => (f.dbPath === field || f.path === field) && f?.validations?.unique
|
|
2140
|
+
);
|
|
2141
|
+
return isIdField || isUniqueDataField;
|
|
2142
|
+
}
|
|
2143
|
+
return false;
|
|
2144
|
+
});
|
|
2145
|
+
};
|
|
2146
|
+
var processFilter = ($filter, currentSchema) => {
|
|
2147
|
+
const dataFields = currentSchema.dataFields?.map((field) => ({ path: field.path, dbPath: field.dbPath })) || [];
|
|
2148
|
+
const linkFields = currentSchema.linkFields?.map((field) => ({ path: field.path, dbPath: field.dbPath })) || [];
|
|
2149
|
+
const roleFields = Object.keys(currentSchema.roles || {}).map((field) => ({ path: field, dbPath: field })) || [];
|
|
2150
|
+
const allFields = [...dataFields, ...linkFields, ...roleFields];
|
|
2151
|
+
return Object.entries($filter || {}).reduce((newFilter, [filterKey, filterValue]) => {
|
|
2152
|
+
const field = allFields.find((o) => o.path === filterKey);
|
|
2153
|
+
newFilter[field?.dbPath || filterKey] = filterValue;
|
|
2154
|
+
return newFilter;
|
|
2155
|
+
}, {});
|
|
2156
|
+
};
|
|
2157
|
+
var enrichBQLQuery = async (req) => {
|
|
2158
|
+
const { rawBqlRequest: rawBqlQuery, schema } = req;
|
|
2159
|
+
if (!Array.isArray(rawBqlQuery)) {
|
|
2160
|
+
if (!("$entity" in rawBqlQuery) && !("$relation" in rawBqlQuery) && (!("$thing" in rawBqlQuery) || !("$thingType" in rawBqlQuery))) {
|
|
2161
|
+
throw new Error("No entity specified in query");
|
|
2162
|
+
}
|
|
2163
|
+
} else {
|
|
2164
|
+
for (const item of rawBqlQuery) {
|
|
2165
|
+
if (!("$entity" in item) && !("$relation" in item) && (!("$thing" in item) || !("$thingType" in item))) {
|
|
2166
|
+
throw new Error("No entity specified in query");
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
const createDataField = (field, fieldStr, $justId, dbPath, isVirtual) => {
|
|
2171
|
+
return {
|
|
2172
|
+
$path: fieldStr,
|
|
2173
|
+
$dbPath: dbPath,
|
|
2174
|
+
$thingType: "attribute",
|
|
2175
|
+
$as: field.$as || fieldStr,
|
|
2176
|
+
$var: fieldStr,
|
|
2177
|
+
$fieldType: "data",
|
|
2178
|
+
$justId,
|
|
2179
|
+
$id: field.$id,
|
|
2180
|
+
$filter: field.$filter,
|
|
2181
|
+
$isVirtual: isVirtual,
|
|
2182
|
+
// ...(typeof field !== 'string' && { $fields: [...field.$fields, ...['id']] }),
|
|
2183
|
+
$filterProcessed: true
|
|
2184
|
+
};
|
|
2185
|
+
};
|
|
2186
|
+
const createLinkField = (field, fieldStr, linkField, $justId, dbPath) => {
|
|
2187
|
+
const { target, oppositeLinkFieldsPlayedBy } = linkField;
|
|
2188
|
+
return oppositeLinkFieldsPlayedBy.map((playedBy) => {
|
|
2189
|
+
const $thingType = target === "role" ? playedBy.thingType : "relation";
|
|
2190
|
+
const $thing = target === "role" ? playedBy.thing : linkField.relation;
|
|
2191
|
+
const node = { [`$${$thingType}`]: $thing };
|
|
2192
|
+
const currentSchema = getCurrentSchema(schema, node);
|
|
2193
|
+
const idNotIncluded = field?.$fields?.filter(
|
|
2194
|
+
(field2) => currentSchema?.idFields?.includes(field2) || currentSchema?.idFields?.includes(field2.$path)
|
|
2195
|
+
).length === 0;
|
|
2196
|
+
let fields = [];
|
|
2197
|
+
if (typeof field !== "string") {
|
|
2198
|
+
if (field.$fields) {
|
|
2199
|
+
if (idNotIncluded) {
|
|
2200
|
+
fields = [...field.$fields, ...currentSchema.idFields];
|
|
2201
|
+
} else {
|
|
2202
|
+
fields = field.$fields;
|
|
2203
|
+
}
|
|
2204
|
+
} else {
|
|
2205
|
+
fields = getAllFields(currentSchema);
|
|
2206
|
+
}
|
|
2207
|
+
} else {
|
|
2208
|
+
fields = ["id"];
|
|
2209
|
+
}
|
|
2210
|
+
if (field.$excludedFields) {
|
|
2211
|
+
fields = fields.filter((f) => !field.$excludedFields.includes(f.$path));
|
|
2212
|
+
}
|
|
2213
|
+
return {
|
|
2214
|
+
$thingType,
|
|
2215
|
+
$plays: linkField.plays,
|
|
2216
|
+
$playedBy: playedBy,
|
|
2217
|
+
$path: playedBy.path,
|
|
2218
|
+
$dbPath: dbPath,
|
|
2219
|
+
$as: field.$as || fieldStr,
|
|
2220
|
+
$var: fieldStr,
|
|
2221
|
+
$thing,
|
|
2222
|
+
$fields: fields,
|
|
2223
|
+
$fieldType: "link",
|
|
2224
|
+
$target: target,
|
|
2225
|
+
$intermediary: playedBy.relation,
|
|
2226
|
+
$justId,
|
|
2227
|
+
$id: field.$id,
|
|
2228
|
+
$filter: processFilter(field.$filter, currentSchema),
|
|
2229
|
+
$idNotIncluded: idNotIncluded,
|
|
2230
|
+
$filterByUnique: checkFilterByUnique(field.$filter, currentSchema),
|
|
2231
|
+
$filterProcessed: true
|
|
2232
|
+
};
|
|
2233
|
+
});
|
|
2234
|
+
};
|
|
2235
|
+
const createRoleField = (field, fieldStr, roleField, $justId, dbPath) => {
|
|
2236
|
+
return roleField.playedBy.map((playedBy) => {
|
|
2237
|
+
const { thing, thingType, relation } = playedBy;
|
|
2238
|
+
const node = { [`$${thingType}`]: thing };
|
|
2239
|
+
const currentSchema = getCurrentSchema(schema, node);
|
|
2240
|
+
const idNotIncluded = field?.$fields?.filter(
|
|
2241
|
+
(field2) => currentSchema?.idFields?.includes(field2) || currentSchema?.idFields?.includes(field2.$path)
|
|
2242
|
+
).length === 0;
|
|
2243
|
+
let fields = [];
|
|
2244
|
+
if (typeof field !== "string") {
|
|
2245
|
+
if (field.$fields) {
|
|
2246
|
+
if (idNotIncluded) {
|
|
2247
|
+
fields = [...field.$fields, ...currentSchema.idFields];
|
|
2248
|
+
} else {
|
|
2249
|
+
fields = field.$fields;
|
|
2250
|
+
}
|
|
2251
|
+
} else {
|
|
2252
|
+
fields = getAllFields(currentSchema);
|
|
2253
|
+
}
|
|
2254
|
+
} else {
|
|
2255
|
+
fields = ["id"];
|
|
2256
|
+
}
|
|
2257
|
+
if (field.$excludedFields) {
|
|
2258
|
+
fields = fields.filter((f) => !field.$excludedFields.includes(f.$path));
|
|
2259
|
+
}
|
|
2260
|
+
return {
|
|
2261
|
+
$thingType: thingType,
|
|
2262
|
+
$path: fieldStr,
|
|
2263
|
+
$dbPath: dbPath,
|
|
2264
|
+
$as: field.$as || fieldStr,
|
|
2265
|
+
$var: fieldStr,
|
|
2266
|
+
$thing: thing,
|
|
2267
|
+
$fields: fields,
|
|
2268
|
+
$fieldType: "role",
|
|
2269
|
+
$intermediary: relation,
|
|
2270
|
+
$justId,
|
|
2271
|
+
$id: field.$id,
|
|
2272
|
+
$filter: processFilter(field.$filter, currentSchema),
|
|
2273
|
+
$idNotIncluded: idNotIncluded,
|
|
2274
|
+
$filterByUnique: checkFilterByUnique(field.$filter, currentSchema),
|
|
2275
|
+
$playedBy: playedBy,
|
|
2276
|
+
$filterProcessed: true
|
|
2277
|
+
};
|
|
2278
|
+
});
|
|
2279
|
+
};
|
|
2280
|
+
const processField = (field, schema2) => {
|
|
2281
|
+
const fieldStr = typeof field === "string" ? field : field.$path;
|
|
2282
|
+
const justId = typeof field === "string";
|
|
2283
|
+
const isDataField = schema2.dataFields?.find((dataField) => dataField.path === fieldStr);
|
|
2284
|
+
const isLinkField = schema2.linkFields?.find((linkField) => linkField.path === fieldStr);
|
|
2285
|
+
const isRoleField = schema2.roles?.[fieldStr];
|
|
2286
|
+
if (isDataField) {
|
|
2287
|
+
return createDataField(field, fieldStr, justId, isDataField.dbPath, isDataField.isVirtual);
|
|
2288
|
+
} else if (isLinkField) {
|
|
2289
|
+
return createLinkField(field, fieldStr, isLinkField, justId, isLinkField.dbPath);
|
|
2290
|
+
} else if (isRoleField) {
|
|
2291
|
+
return createRoleField(field, fieldStr, isRoleField, justId, isRoleField.dbPath);
|
|
2292
|
+
}
|
|
2293
|
+
return null;
|
|
2294
|
+
};
|
|
2295
|
+
const parser = (blocks) => {
|
|
2296
|
+
return produce4(
|
|
2297
|
+
blocks,
|
|
2298
|
+
(draft) => traverse5(draft, (context) => {
|
|
2299
|
+
const { value: val } = context;
|
|
2300
|
+
const value = val;
|
|
2301
|
+
if (isObject5(value)) {
|
|
2302
|
+
if (value.$id) {
|
|
2303
|
+
const node = value.$entity || value.$relation ? value : { [`$${value.$thingType}`]: value.$thing };
|
|
2304
|
+
const currentSchema = getCurrentSchema(schema, node);
|
|
2305
|
+
value.$path = currentSchema.name;
|
|
2306
|
+
if (!Array.isArray(value.$id)) {
|
|
2307
|
+
value.$filterByUnique = true;
|
|
2308
|
+
}
|
|
2309
|
+
if (currentSchema?.idFields?.length === 1) {
|
|
2310
|
+
const [idField] = currentSchema.idFields;
|
|
2311
|
+
value.$filter = { ...value.$filter, ...{ [idField]: value.$id } };
|
|
2312
|
+
delete value.$id;
|
|
2313
|
+
} else {
|
|
2314
|
+
throw new Error("Multiple ids not yet enabled / composite ids");
|
|
2315
|
+
}
|
|
2316
|
+
} else if ("$entity" in value || "$relation" in value) {
|
|
2317
|
+
const currentSchema = getCurrentSchema(schema, value);
|
|
2318
|
+
value.$path = currentSchema.name;
|
|
2319
|
+
value.$as = currentSchema.name;
|
|
2320
|
+
}
|
|
2321
|
+
if (value.$entity) {
|
|
2322
|
+
value.$thing = value.$entity;
|
|
2323
|
+
value.$thingType = "entity";
|
|
2324
|
+
delete value.$entity;
|
|
2325
|
+
} else if (value.$relation) {
|
|
2326
|
+
value.$thing = value.$relation;
|
|
2327
|
+
value.$thingType = "relation";
|
|
2328
|
+
delete value.$relation;
|
|
2329
|
+
}
|
|
2330
|
+
if (isObject5(value) && "$thing" in value) {
|
|
2331
|
+
const node = value.$entity || value.$relation ? value : { [`$${value.$thingType}`]: value.$thing };
|
|
2332
|
+
const currentSchema = getCurrentSchema(schema, node);
|
|
2333
|
+
if (value.$filter) {
|
|
2334
|
+
value.$filterByUnique = checkFilterByUnique(value.$filter, currentSchema);
|
|
2335
|
+
if (!value.$filterProcessed) {
|
|
2336
|
+
value.$filter = processFilter(value.$filter, currentSchema);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
if (value.$fields) {
|
|
2340
|
+
const idFieldIncluded = value.$fields.filter(
|
|
2341
|
+
(field) => currentSchema?.idFields?.includes(field) || currentSchema?.idFields?.includes(field.$path)
|
|
2342
|
+
).length > 0;
|
|
2343
|
+
if (!idFieldIncluded) {
|
|
2344
|
+
value.$fields = [...value.$fields, ...currentSchema.idFields];
|
|
2345
|
+
value.$idNotIncluded = true;
|
|
2346
|
+
}
|
|
2347
|
+
const newFields = value.$fields?.flatMap((field) => {
|
|
2348
|
+
const processed = processField(field, currentSchema);
|
|
2349
|
+
if (Array.isArray(processed)) {
|
|
2350
|
+
return processed;
|
|
2351
|
+
} else {
|
|
2352
|
+
return [processed];
|
|
2353
|
+
}
|
|
2354
|
+
}).filter(Boolean);
|
|
2355
|
+
value.$fields = newFields;
|
|
2356
|
+
} else {
|
|
2357
|
+
const allFields = getAllFields(currentSchema);
|
|
2358
|
+
const newFields = allFields?.flatMap((field) => {
|
|
2359
|
+
const processed = processField(field, currentSchema);
|
|
2360
|
+
if (Array.isArray(processed)) {
|
|
2361
|
+
return processed;
|
|
2362
|
+
} else {
|
|
2363
|
+
return [processed];
|
|
2364
|
+
}
|
|
2365
|
+
}).filter(Boolean);
|
|
2366
|
+
value.$fields = newFields;
|
|
2367
|
+
}
|
|
2368
|
+
if (value.$excludedFields) {
|
|
2369
|
+
value.$fields = value.$fields.filter((f) => !value.$excludedFields.includes(f.$path));
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
})
|
|
2374
|
+
);
|
|
2375
|
+
};
|
|
2376
|
+
const enrichedBqlQuery = parser(Array.isArray(rawBqlQuery) ? rawBqlQuery : [rawBqlQuery]);
|
|
2377
|
+
req.enrichedBqlQuery = enrichedBqlQuery;
|
|
2378
|
+
};
|
|
2379
|
+
|
|
2380
|
+
// src/pipeline/transaction/runTQLQuery.ts
|
|
2381
|
+
import { TransactionType as TransactionType3 } from "typedb-driver";
|
|
2382
|
+
import { parallel } from "radash";
|
|
2383
|
+
var newRunTQLQuery = async (req, res) => {
|
|
2384
|
+
const { dbHandles, enrichedBqlQuery, tqlRequest, config } = req;
|
|
2385
|
+
if (!enrichedBqlQuery) {
|
|
2386
|
+
throw new Error("BQL request not parsed");
|
|
2387
|
+
}
|
|
2388
|
+
if (!tqlRequest) {
|
|
2389
|
+
throw new Error("TQL request not built");
|
|
2390
|
+
}
|
|
2391
|
+
const isBatched = Array.isArray(tqlRequest);
|
|
2392
|
+
if (isBatched) {
|
|
2393
|
+
const resArray = await parallel(tqlRequest.length, tqlRequest, async (queryString) => {
|
|
2394
|
+
const { session } = await getSessionOrOpenNewOne(dbHandles, config);
|
|
2395
|
+
const transaction = await session.transaction(TransactionType3.READ);
|
|
2396
|
+
if (!transaction) {
|
|
2397
|
+
throw new Error("Can't create transaction");
|
|
2398
|
+
}
|
|
2399
|
+
const tqlStream = transaction.query.fetch(queryString);
|
|
2400
|
+
const tqlRes = await tqlStream.collect();
|
|
2401
|
+
await transaction.close();
|
|
2402
|
+
return tqlRes;
|
|
2403
|
+
});
|
|
2404
|
+
res.rawTqlRes = resArray;
|
|
2405
|
+
res.isBatched = true;
|
|
2406
|
+
} else {
|
|
2407
|
+
const { session } = await getSessionOrOpenNewOne(dbHandles, config);
|
|
2408
|
+
const transaction = await session.transaction(TransactionType3.READ);
|
|
2409
|
+
if (!transaction) {
|
|
2410
|
+
throw new Error("Can't create transaction");
|
|
2411
|
+
}
|
|
2412
|
+
const tqlStream = transaction.query.fetch(tqlRequest);
|
|
2413
|
+
const tqlRes = await tqlStream.collect();
|
|
2414
|
+
await transaction.close();
|
|
2415
|
+
res.rawTqlRes = tqlRes;
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
|
|
2419
|
+
// src/pipeline/postprocess/parseTQLQuery.ts
|
|
2420
|
+
var parseMetaData = (str) => {
|
|
2421
|
+
const asRegex = /as:([a-zA-Z0-9_\-·]+)/;
|
|
2422
|
+
const justIdRegex = /justId:([a-zA-Z0-9_\-·]+)/;
|
|
2423
|
+
const idNotIncludedRegex = /idNotIncluded:([a-zA-Z0-9_\-·]+)/;
|
|
2424
|
+
const filterByUniqueRegex = /filterByUnique:([a-zA-Z0-9_\-·]+)/;
|
|
2425
|
+
const asMatch = str.match(asRegex);
|
|
2426
|
+
const justIdMatch = str.match(justIdRegex);
|
|
2427
|
+
const idNotIncludedMatch = str.match(idNotIncludedRegex);
|
|
2428
|
+
const filterByUniqueMatch = str.match(filterByUniqueRegex);
|
|
2429
|
+
return {
|
|
2430
|
+
as: asMatch ? asMatch[1] : null,
|
|
2431
|
+
justId: justIdMatch ? justIdMatch[1] : null,
|
|
2432
|
+
idNotIncluded: idNotIncludedMatch ? idNotIncludedMatch[1] : null,
|
|
2433
|
+
filterByUnique: filterByUniqueMatch ? filterByUniqueMatch[1] : null
|
|
2434
|
+
};
|
|
2435
|
+
};
|
|
2436
|
+
var parseArrayMetadata = (str) => {
|
|
2437
|
+
try {
|
|
2438
|
+
const convertToJson = (str2) => {
|
|
2439
|
+
let jsonString = str2.replace("$metadata:", "");
|
|
2440
|
+
jsonString = jsonString.replace(/([a-zA-Z0-9_\-·]+)(?=:)/g, '"$1"');
|
|
2441
|
+
jsonString = jsonString.replace(/:(\s*)([a-zA-Z0-9_\-·]+)/g, (match, p1, p2) => {
|
|
2442
|
+
if (/^{.*}$/.test(p2)) {
|
|
2443
|
+
return `:${p2}`;
|
|
2444
|
+
} else {
|
|
2445
|
+
return `:${p1}"${p2}"`;
|
|
2446
|
+
}
|
|
2447
|
+
});
|
|
2448
|
+
jsonString = jsonString.replace(/\[([^\]]+)\]/g, (match, p1) => {
|
|
2449
|
+
return `[${p1.split(",").map((s) => {
|
|
2450
|
+
if (s.trim().startsWith("{") && s.trim().endsWith("}")) {
|
|
2451
|
+
return s.trim();
|
|
2452
|
+
} else {
|
|
2453
|
+
return `"${s.trim()}"`;
|
|
2454
|
+
}
|
|
2455
|
+
}).join(",")}]`;
|
|
2456
|
+
});
|
|
2457
|
+
return jsonString;
|
|
2458
|
+
};
|
|
2459
|
+
const converted = convertToJson(str);
|
|
2460
|
+
const parsed = JSON.parse(converted);
|
|
2461
|
+
return parsed;
|
|
2462
|
+
} catch (e) {
|
|
2463
|
+
console.error(e);
|
|
2464
|
+
return { as: [], virtual: [] };
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2467
|
+
var parseTQLQuery = async (req, res) => {
|
|
2468
|
+
const { enrichedBqlQuery, rawBqlRequest, schema, config } = req;
|
|
2469
|
+
const { rawTqlRes, isBatched } = res;
|
|
2470
|
+
if (!enrichedBqlQuery) {
|
|
2471
|
+
throw new Error("BQL request not enriched");
|
|
2472
|
+
} else if (!rawTqlRes) {
|
|
2473
|
+
throw new Error("TQL query not executed");
|
|
2474
|
+
}
|
|
2475
|
+
const parseDataFields = (dataFields, currentSchema) => {
|
|
2476
|
+
const { $metaData } = dataFields;
|
|
2477
|
+
const { as: $as, virtual } = parseArrayMetadata($metaData);
|
|
2478
|
+
const mainDataFields = Object.entries(dataFields).filter(([key]) => key !== "type" && !key.includes("$")).map(([key, value]) => {
|
|
2479
|
+
const field = currentSchema.dataFields?.find((f) => f.path === key || f.dbPath === key);
|
|
2480
|
+
const isIdField = key === "id";
|
|
2481
|
+
const $asKey = Array.isArray($as) ? $as.find((o) => o[key])?.[key] : key;
|
|
2482
|
+
let fieldValue;
|
|
2483
|
+
if (field?.cardinality === "ONE") {
|
|
2484
|
+
fieldValue = value[0] ? value[0].value : config.query?.returnNulls ? null : void 0;
|
|
2485
|
+
if (isIdField && !config.query?.noMetadata) {
|
|
2486
|
+
return [
|
|
2487
|
+
[$asKey, fieldValue],
|
|
2488
|
+
["$id", fieldValue]
|
|
2489
|
+
].filter(([_, v]) => v !== void 0);
|
|
2490
|
+
}
|
|
2491
|
+
} else if (field?.cardinality === "MANY") {
|
|
2492
|
+
fieldValue = value.map((o) => o.value);
|
|
2493
|
+
}
|
|
2494
|
+
return [[$asKey, fieldValue]].filter(([_, v]) => v !== void 0);
|
|
2495
|
+
}).flat();
|
|
2496
|
+
const virtualFields = virtual.map((key) => {
|
|
2497
|
+
const $asKey = $as.find((o) => o[key])?.[key];
|
|
2498
|
+
const field = currentSchema.dataFields?.find((f) => f.isVirtual && f.dbPath === key);
|
|
2499
|
+
const computedValue = compute({ currentThing: Object.fromEntries(mainDataFields), fieldSchema: field });
|
|
2500
|
+
return [$asKey, computedValue];
|
|
2501
|
+
});
|
|
2502
|
+
return Object.fromEntries([...mainDataFields, ...virtualFields]);
|
|
2503
|
+
};
|
|
2504
|
+
const parseRoleFields = (roleFields) => {
|
|
2505
|
+
return roleFields.reduce((roleFieldsRes, roleField) => {
|
|
2506
|
+
const { $roleFields, $metaData, $cardinality } = roleField;
|
|
2507
|
+
const { as, justId, idNotIncluded, filterByUnique } = parseMetaData($metaData);
|
|
2508
|
+
const items = $roleFields.map((item) => {
|
|
2509
|
+
const { dataFields, currentSchema, linkFields, roleFields: roleFields2, schemaValue } = parseFields(item);
|
|
2510
|
+
const parsedDataFields = parseDataFields(dataFields, currentSchema);
|
|
2511
|
+
if (justId === "T") {
|
|
2512
|
+
return parsedDataFields.id;
|
|
2513
|
+
} else {
|
|
2514
|
+
const parsedLinkFields = parseLinkFields(linkFields);
|
|
2515
|
+
const parsedRoleFields = parseRoleFields(roleFields2);
|
|
2516
|
+
const resDataFields = { ...parsedDataFields };
|
|
2517
|
+
if (idNotIncluded === "true") {
|
|
2518
|
+
currentSchema?.idFields?.forEach((field) => delete resDataFields[field]);
|
|
2519
|
+
}
|
|
2520
|
+
return {
|
|
2521
|
+
...resDataFields,
|
|
2522
|
+
...parsedLinkFields,
|
|
2523
|
+
...parsedRoleFields,
|
|
2524
|
+
...!config.query?.noMetadata && { ...schemaValue }
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
});
|
|
2528
|
+
if (items.length > 0) {
|
|
2529
|
+
roleFieldsRes[as] = $cardinality === "MANY" && filterByUnique === "false" ? items : items[0];
|
|
2530
|
+
} else if (config.query?.returnNulls) {
|
|
2531
|
+
roleFieldsRes[as] = null;
|
|
2532
|
+
}
|
|
2533
|
+
return roleFieldsRes;
|
|
2534
|
+
}, {});
|
|
2535
|
+
};
|
|
2536
|
+
const parseLinkFields = (linkFields) => {
|
|
2537
|
+
return linkFields.reduce((linkFieldsRes, linkField) => {
|
|
2538
|
+
const { $linkFields, $metaData, $cardinality } = linkField;
|
|
2539
|
+
const { as, justId, idNotIncluded, filterByUnique } = parseMetaData($metaData);
|
|
2540
|
+
const items = $linkFields.map((item) => {
|
|
2541
|
+
const { dataFields, currentSchema, linkFields: linkFields2, roleFields, schemaValue } = parseFields(item);
|
|
2542
|
+
const parsedDataFields = parseDataFields(dataFields, currentSchema);
|
|
2543
|
+
if (justId === "T") {
|
|
2544
|
+
return parsedDataFields.id;
|
|
2545
|
+
} else {
|
|
2546
|
+
const parsedLinkFields = parseLinkFields(linkFields2);
|
|
2547
|
+
const parsedRoleFields = parseRoleFields(roleFields);
|
|
2548
|
+
const resDataFields = { ...parsedDataFields };
|
|
2549
|
+
if (idNotIncluded === "true") {
|
|
2550
|
+
currentSchema.idFields?.forEach((field) => delete resDataFields[field]);
|
|
2551
|
+
}
|
|
2552
|
+
return {
|
|
2553
|
+
...resDataFields,
|
|
2554
|
+
...parsedLinkFields,
|
|
2555
|
+
...parsedRoleFields,
|
|
2556
|
+
...!config.query?.noMetadata && { ...schemaValue }
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
});
|
|
2560
|
+
linkFieldsRes[as] = items.length > 0 ? $cardinality === "MANY" && filterByUnique === "false" ? items : items[0] : config.query?.returnNulls ? null : void 0;
|
|
2561
|
+
return linkFieldsRes;
|
|
2562
|
+
}, {});
|
|
2563
|
+
};
|
|
2564
|
+
const parseFields = (obj) => {
|
|
2565
|
+
const keys = Object.keys(obj);
|
|
2566
|
+
const dataFieldsKey = keys.find((key) => key.endsWith(".$dataFields"));
|
|
2567
|
+
if (!dataFieldsKey) {
|
|
2568
|
+
throw new Error("No datafields");
|
|
2569
|
+
}
|
|
2570
|
+
const dataFields = obj[dataFieldsKey];
|
|
2571
|
+
const metaDataKey = dataFieldsKey.split(".")[dataFieldsKey.split(".").length - 2];
|
|
2572
|
+
dataFields.$metaData = metaDataKey;
|
|
2573
|
+
if (dataFields.length === 0) {
|
|
2574
|
+
throw new Error("No datafields");
|
|
2575
|
+
}
|
|
2576
|
+
const dataFieldsThing = dataFields.type;
|
|
2577
|
+
const schemaValue = {
|
|
2578
|
+
$thing: dataFieldsThing.label,
|
|
2579
|
+
$thingType: dataFieldsThing.root
|
|
2580
|
+
};
|
|
2581
|
+
const node = { [`$${schemaValue.$thingType}`]: schemaValue.$thing };
|
|
2582
|
+
const currentSchema = getCurrentSchema(schema, node);
|
|
2583
|
+
const linkFields = keys.filter(
|
|
2584
|
+
(key) => !key.endsWith(".$dataFields") && currentSchema.linkFields?.some((o) => o.path === key.split(".").pop())
|
|
2585
|
+
).map((key) => ({
|
|
2586
|
+
$linkFields: obj[key],
|
|
2587
|
+
$key: key.split(".").pop(),
|
|
2588
|
+
$metaData: key.split(".")[key.split(".").length - 2],
|
|
2589
|
+
$cardinality: currentSchema?.linkFields?.find((o) => o.path === key.split(".").pop())?.cardinality
|
|
2590
|
+
}));
|
|
2591
|
+
const roleFields = keys.filter((key) => !key.endsWith(".$dataFields") && currentSchema.roles?.[key.split(".").pop()]).map((key) => ({
|
|
2592
|
+
$roleFields: obj[key],
|
|
2593
|
+
$key: key.split(".").pop(),
|
|
2594
|
+
$metaData: key.split(".")[key.split(".").length - 2],
|
|
2595
|
+
// @ts-expect-error todo
|
|
2596
|
+
$cardinality: currentSchema.roles[key.split(".").pop()].cardinality
|
|
2597
|
+
}));
|
|
2598
|
+
return { dataFields, schemaValue, currentSchema, linkFields, roleFields };
|
|
2599
|
+
};
|
|
2600
|
+
const realParse = (tqlRes) => {
|
|
2601
|
+
return tqlRes.map((resItem) => {
|
|
2602
|
+
const { dataFields, currentSchema, linkFields, roleFields, schemaValue } = parseFields(resItem);
|
|
2603
|
+
const parsedDataFields = parseDataFields(dataFields, currentSchema);
|
|
2604
|
+
const parsedLinkFields = parseLinkFields(linkFields);
|
|
2605
|
+
const parsedRoleFields = parseRoleFields(roleFields);
|
|
2606
|
+
const idNotIncluded = rawBqlRequest?.$fields?.every(
|
|
2607
|
+
// @ts-expect-error todo
|
|
2608
|
+
(field) => !currentSchema?.idFields?.includes(field) && !currentSchema?.idFields?.includes(field.$path)
|
|
2609
|
+
);
|
|
2610
|
+
const finalObj = {
|
|
2611
|
+
...parsedLinkFields,
|
|
2612
|
+
...parsedRoleFields,
|
|
2613
|
+
...!config.query?.noMetadata ? { ...schemaValue } : {},
|
|
2614
|
+
...!config.query?.noMetadata && rawBqlRequest.$id ? { $id: Array.isArray(rawBqlRequest.$id) ? parsedDataFields["id"] : rawBqlRequest.$id } : {},
|
|
2615
|
+
...idNotIncluded ? Object.fromEntries(
|
|
2616
|
+
Object.entries(parsedDataFields).filter(([key]) => !currentSchema?.idFields?.includes(key))
|
|
2617
|
+
) : parsedDataFields
|
|
2618
|
+
};
|
|
2619
|
+
return finalObj;
|
|
2620
|
+
});
|
|
2621
|
+
};
|
|
2622
|
+
const parser = (tqlRes) => {
|
|
2623
|
+
const processResponse = (resItems) => {
|
|
2624
|
+
const parsedItems = realParse(resItems);
|
|
2625
|
+
return rawBqlRequest.$id && !Array.isArray(rawBqlRequest.$id) || enrichedBqlQuery[0].$filterByUnique ? parsedItems[0] ?? null : parsedItems.length === 0 ? null : parsedItems;
|
|
2626
|
+
};
|
|
2627
|
+
return isBatched ? tqlRes.map(processResponse) : processResponse(tqlRes);
|
|
2628
|
+
};
|
|
2629
|
+
const parsedTqlRes = parser(rawTqlRes);
|
|
2630
|
+
res.bqlRes = parsedTqlRes;
|
|
2631
|
+
};
|
|
2632
|
+
|
|
2633
|
+
// src/pipeline/preprocess/newPreQuery.ts
|
|
2634
|
+
import { traverse as traverse6 } from "object-traversal";
|
|
2635
|
+
import { isObject as isObject6 } from "radash";
|
|
2636
|
+
import { produce as produce5 } from "immer";
|
|
2637
|
+
import { v4 as uuidv43 } from "uuid";
|
|
2638
|
+
var newPreQuery = async (req) => {
|
|
2639
|
+
const { filledBqlRequest, config, schema } = req;
|
|
2640
|
+
const getFieldKeys = (block, noDataFields) => {
|
|
2641
|
+
return Object.keys(block).filter((key) => {
|
|
2642
|
+
if (!key.startsWith("$")) {
|
|
2643
|
+
if (noDataFields) {
|
|
2644
|
+
const currentSchema = getCurrentSchema(schema, block);
|
|
2645
|
+
if (!currentSchema.dataFields?.find((field) => field.path === key)) {
|
|
2646
|
+
return true;
|
|
2647
|
+
} else {
|
|
2648
|
+
return false;
|
|
2649
|
+
}
|
|
2650
|
+
} else {
|
|
2651
|
+
return true;
|
|
2652
|
+
}
|
|
2653
|
+
} else {
|
|
2654
|
+
return false;
|
|
2655
|
+
}
|
|
2656
|
+
});
|
|
2657
|
+
};
|
|
2658
|
+
if (!filledBqlRequest) {
|
|
2659
|
+
throw new Error("[BQLE-M-0] No filledBqlRequest found");
|
|
2660
|
+
}
|
|
2661
|
+
console.log("filledBql: ", JSON.stringify(filledBqlRequest, null, 2));
|
|
2662
|
+
if (config.mutation?.preQuery === false) {
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
const ops = [];
|
|
2666
|
+
traverse6(filledBqlRequest, ({ parent, key, value }) => {
|
|
2667
|
+
if (parent && key && !key.includes("$") && isObject6(parent)) {
|
|
2668
|
+
const values = Array.isArray(parent[key]) ? parent[key] : [parent[key]];
|
|
2669
|
+
values.forEach((val) => {
|
|
2670
|
+
if (isObject6(val)) {
|
|
2671
|
+
if (parent.$op !== "create") {
|
|
2672
|
+
if (!ops.includes(val.$op)) {
|
|
2673
|
+
ops.push(val.$op);
|
|
2674
|
+
}
|
|
2675
|
+
} else {
|
|
2676
|
+
if (val.$op === "delete" || val.$op === "unlink") {
|
|
2677
|
+
throw new Error(`Cannot ${val.$op} under a create`);
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
} else if (!parent && isObject6(value)) {
|
|
2683
|
+
if (!ops.includes(value.$op)) {
|
|
2684
|
+
ops.push(value.$op);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
});
|
|
2688
|
+
if (!ops.includes("delete") && !ops.includes("unlink") && !ops.includes("replace") && !ops.includes("update") && !ops.includes("link")) {
|
|
2689
|
+
return;
|
|
2690
|
+
}
|
|
2691
|
+
const convertMutationToQuery = (blocks) => {
|
|
2692
|
+
const processBlock = (block, root) => {
|
|
2693
|
+
const $fields = [];
|
|
2694
|
+
const filteredBlock = {};
|
|
2695
|
+
const toRemoveFromRoot = ["$op", "$bzId", "$parentKey"];
|
|
2696
|
+
const toRemove = ["$relation", "$entity", "$id", ...toRemoveFromRoot];
|
|
2697
|
+
for (const k in block) {
|
|
2698
|
+
if (toRemoveFromRoot.includes(k)) {
|
|
2699
|
+
continue;
|
|
2700
|
+
}
|
|
2701
|
+
if (toRemove.includes(k) && !root) {
|
|
2702
|
+
continue;
|
|
2703
|
+
}
|
|
2704
|
+
if (!k.includes("$") && (isObject6(block[k]) || Array.isArray(block[k]))) {
|
|
2705
|
+
const v = block[k];
|
|
2706
|
+
if (Array.isArray(v) && v.length > 0) {
|
|
2707
|
+
v.forEach((opBlock) => {
|
|
2708
|
+
$fields.push({
|
|
2709
|
+
$path: k,
|
|
2710
|
+
...processBlock(opBlock)
|
|
2711
|
+
// $as: opBlock.$bzId,
|
|
2712
|
+
// $as: `${k}_${includedKeys}`,
|
|
2713
|
+
});
|
|
2714
|
+
});
|
|
2715
|
+
} else {
|
|
2716
|
+
$fields.push({
|
|
2717
|
+
$path: k,
|
|
2718
|
+
...processBlock(v)
|
|
2719
|
+
// $as: v.$bzId,
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
} else {
|
|
2723
|
+
filteredBlock[k] = block[k];
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
return {
|
|
2727
|
+
...filteredBlock,
|
|
2728
|
+
$fields
|
|
2729
|
+
// $as: block.$bzId,
|
|
2730
|
+
};
|
|
2731
|
+
};
|
|
2732
|
+
return blocks.map((block) => processBlock(block, true));
|
|
2733
|
+
};
|
|
2734
|
+
const preQueryReq = convertMutationToQuery(Array.isArray(filledBqlRequest) ? filledBqlRequest : [filledBqlRequest]);
|
|
2735
|
+
const preQueryRes = await queryPipeline(preQueryReq, req.config, req.schema, req.dbHandles);
|
|
2736
|
+
const getObjectPath = (parent, key) => {
|
|
2737
|
+
const idField = parent.$id || parent.id || parent.$bzId;
|
|
2738
|
+
if (parent.$objectPath) {
|
|
2739
|
+
const { $objectPath } = parent;
|
|
2740
|
+
const root = $objectPath?.beforePath || "root";
|
|
2741
|
+
const ids = Array.isArray($objectPath.ids) ? `[${$objectPath.ids}]` : $objectPath.ids;
|
|
2742
|
+
const final2 = `${root}.${ids}___${$objectPath.key}`;
|
|
2743
|
+
const new$objectPath = {
|
|
2744
|
+
beforePath: final2,
|
|
2745
|
+
ids: idField,
|
|
2746
|
+
key
|
|
2747
|
+
};
|
|
2748
|
+
return new$objectPath;
|
|
2749
|
+
} else {
|
|
2750
|
+
return {
|
|
2751
|
+
beforePath: "root",
|
|
2752
|
+
ids: idField,
|
|
2753
|
+
key
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
const objectPathToKey = ($objectPath, hardId) => {
|
|
2758
|
+
const root = $objectPath?.beforePath || "root";
|
|
2759
|
+
const ids = hardId ? hardId : Array.isArray($objectPath?.ids) ? `[${$objectPath?.ids}]` : $objectPath?.ids;
|
|
2760
|
+
const final2 = `${root}.${ids}___${$objectPath?.key}`;
|
|
2761
|
+
return final2;
|
|
2762
|
+
};
|
|
2763
|
+
const convertManyPaths = (input) => {
|
|
2764
|
+
if (input.includes("[") && input.includes("]")) {
|
|
2765
|
+
const [prefix, itemsWithBrackets, suffix] = input.split(/[[\]]/);
|
|
2766
|
+
const items = itemsWithBrackets.split(",");
|
|
2767
|
+
return items.map((item) => `${prefix}${item}${suffix}`);
|
|
2768
|
+
} else {
|
|
2769
|
+
return [input];
|
|
2770
|
+
}
|
|
2771
|
+
};
|
|
2772
|
+
const cache = {};
|
|
2773
|
+
const cachePaths = (blocks) => {
|
|
2774
|
+
return produce5(
|
|
2775
|
+
blocks,
|
|
2776
|
+
(draft) => traverse6(draft, (context) => {
|
|
2777
|
+
const { key, parent } = context;
|
|
2778
|
+
if (parent && key && parent.$id && !key.includes("$")) {
|
|
2779
|
+
const newObjPath = getObjectPath(parent, key);
|
|
2780
|
+
const cacheKey = objectPathToKey(newObjPath);
|
|
2781
|
+
if (Array.isArray(parent[key])) {
|
|
2782
|
+
const cacheArray = [];
|
|
2783
|
+
parent[key].forEach((val) => {
|
|
2784
|
+
if (isObject6(val)) {
|
|
2785
|
+
val.$objectPath = newObjPath;
|
|
2786
|
+
cacheArray.push(val.$id.toString());
|
|
2787
|
+
} else if (val) {
|
|
2788
|
+
cacheArray.push(val.toString());
|
|
2789
|
+
}
|
|
2790
|
+
});
|
|
2791
|
+
cache[cacheKey] = { $objectPath: newObjPath, $ids: cacheArray };
|
|
2792
|
+
} else {
|
|
2793
|
+
const val = parent[key];
|
|
2794
|
+
if (isObject6(val)) {
|
|
2795
|
+
cache[cacheKey] = { $objectPath: newObjPath, $ids: [val.$id.toString()] };
|
|
2796
|
+
val.$objectPath = newObjPath;
|
|
2797
|
+
} else if (val) {
|
|
2798
|
+
cache[cacheKey] = { $objectPath: newObjPath, $ids: [val.toString()] };
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
})
|
|
2803
|
+
);
|
|
2804
|
+
};
|
|
2805
|
+
cachePaths(preQueryRes || {});
|
|
2806
|
+
const fillObjectPaths = (blocks) => {
|
|
2807
|
+
return produce5(
|
|
2808
|
+
blocks,
|
|
2809
|
+
(draft) => traverse6(draft, (context) => {
|
|
2810
|
+
const { key, value, parent } = context;
|
|
2811
|
+
if (parent && key && !key.includes("$") && (Array.isArray(value) || isObject6(value)) && !Array.isArray(parent)) {
|
|
2812
|
+
if (Array.isArray(parent[key])) {
|
|
2813
|
+
parent[key].forEach(
|
|
2814
|
+
(o) => {
|
|
2815
|
+
if (typeof o !== "string") {
|
|
2816
|
+
o.$objectPath = getObjectPath(parent, key);
|
|
2817
|
+
o.$parentIsCreate = parent.$op === "create";
|
|
2818
|
+
o.$grandChildOfCreate = parent.$parentIsCreate || parent.$grandChildOfCreate;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
);
|
|
2822
|
+
} else if (isObject6(parent[key])) {
|
|
2823
|
+
parent[key].$parentIsCreate = parent.$op === "create";
|
|
2824
|
+
parent[key].$grandChildOfCreate = parent.$parentIsCreate || parent.$grandChildOfCreate;
|
|
2825
|
+
parent[key].$objectPath = getObjectPath(parent, key);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
})
|
|
2829
|
+
);
|
|
2830
|
+
};
|
|
2831
|
+
const bqlWithObjectPaths = fillObjectPaths(filledBqlRequest);
|
|
2832
|
+
const splitBzIds = (blocks) => {
|
|
2833
|
+
const processBlocks = (operationBlocks, parentOperationBlock) => {
|
|
2834
|
+
let processIds = [];
|
|
2835
|
+
operationBlocks.forEach((operationBlock) => {
|
|
2836
|
+
const fieldCount = Object.keys(operationBlock).filter((key) => !key.startsWith("$")).length;
|
|
2837
|
+
if (Array.isArray(operationBlock.$id) && fieldCount > 0) {
|
|
2838
|
+
const splitBlocksById = operationBlock.$id.map((id) => {
|
|
2839
|
+
return { ...operationBlock, $id: id };
|
|
2840
|
+
});
|
|
2841
|
+
processIds = [...processIds, ...splitBlocksById];
|
|
2842
|
+
} else {
|
|
2843
|
+
processIds.push(operationBlock);
|
|
2844
|
+
}
|
|
2845
|
+
});
|
|
2846
|
+
let newOperationBlocks = [];
|
|
2847
|
+
const fieldsWithoutMultiples = [];
|
|
2848
|
+
const fieldsWithMultiples = processIds.filter((operationBlock) => {
|
|
2849
|
+
const ops2 = ["delete", "update", "unlink"];
|
|
2850
|
+
const isCorrectOp = ops2.includes(operationBlock.$op || "");
|
|
2851
|
+
const fieldCount = Object.keys(operationBlock).filter((key) => !key.startsWith("$")).length;
|
|
2852
|
+
const isMultiple = !operationBlock.$id || Array.isArray(operationBlock.$id) && fieldCount > 0 || operationBlock.$filter;
|
|
2853
|
+
if (isMultiple && isCorrectOp) {
|
|
2854
|
+
return true;
|
|
2855
|
+
} else {
|
|
2856
|
+
fieldsWithoutMultiples.push(operationBlock);
|
|
2857
|
+
}
|
|
2858
|
+
});
|
|
2859
|
+
if (fieldsWithMultiples.length > 0) {
|
|
2860
|
+
fieldsWithMultiples.forEach((opBlock) => {
|
|
2861
|
+
const getAllKeyCombinations = (obj) => {
|
|
2862
|
+
const allKeys = Object.keys(obj);
|
|
2863
|
+
const combinableKeys = allKeys.filter((key) => !key.startsWith("$"));
|
|
2864
|
+
const allCombinations2 = [];
|
|
2865
|
+
const generateCombinations = (index, currentObj) => {
|
|
2866
|
+
if (index === combinableKeys.length) {
|
|
2867
|
+
const fullObj = { ...currentObj };
|
|
2868
|
+
allKeys.forEach((key) => {
|
|
2869
|
+
if (key.startsWith("$")) {
|
|
2870
|
+
fullObj[key] = obj[key];
|
|
2871
|
+
}
|
|
2872
|
+
});
|
|
2873
|
+
allCombinations2.push(fullObj);
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
const newObjInclude = { ...currentObj, [combinableKeys[index]]: obj[combinableKeys[index]] };
|
|
2877
|
+
generateCombinations(index + 1, newObjInclude);
|
|
2878
|
+
generateCombinations(index + 1, currentObj);
|
|
2879
|
+
};
|
|
2880
|
+
generateCombinations(0, {});
|
|
2881
|
+
return allCombinations2;
|
|
2882
|
+
};
|
|
2883
|
+
const allCombinations = getAllKeyCombinations(opBlock).filter(
|
|
2884
|
+
(opBlock2) => !(opBlock2.$op === "update" && Object.keys(opBlock2).filter((key) => !key.startsWith("$")).length === 0)
|
|
2885
|
+
);
|
|
2886
|
+
let included = [];
|
|
2887
|
+
const emptyObjCKey = objectPathToKey(opBlock.$objectPath);
|
|
2888
|
+
const cacheR = cache[emptyObjCKey];
|
|
2889
|
+
let remaining = cacheR ? cache[emptyObjCKey].$ids : [];
|
|
2890
|
+
const combinationsFromCache = allCombinations.map((combination, index) => {
|
|
2891
|
+
const _currentSchema = getCurrentSchema(schema, combination);
|
|
2892
|
+
const combinableKeys = Object.keys(combination).filter(
|
|
2893
|
+
(fieldKey) => !fieldKey.includes("$") && !_currentSchema.dataFields?.some((o) => o.path === fieldKey)
|
|
2894
|
+
);
|
|
2895
|
+
const cachesFound = combinableKeys.map((key) => {
|
|
2896
|
+
const multiples = Array.isArray(combination[key]) ? combination[key].length > 1 ? combination[key].filter(
|
|
2897
|
+
(opBlock2) => !opBlock2.$id || Array.isArray(opBlock2.$id) || opBlock2.$filter
|
|
2898
|
+
) : combination[key] : [combination[key]];
|
|
2899
|
+
const processedMultiples = multiples.map((multiple) => {
|
|
2900
|
+
const cKey = objectPathToKey(multiple.$objectPath);
|
|
2901
|
+
const getIdsToKey = (searchKey) => {
|
|
2902
|
+
const ids = [];
|
|
2903
|
+
const searchSegments = searchKey.split(".");
|
|
2904
|
+
const starting = searchSegments.slice(0, searchSegments.length - 1).join(".");
|
|
2905
|
+
const ending = searchSegments.slice(searchSegments.length - 1, searchSegments.length)[0].split("___")[1];
|
|
2906
|
+
for (const key2 in cache) {
|
|
2907
|
+
if (key2.startsWith(starting) && key2.endsWith(ending)) {
|
|
2908
|
+
const searchSegments2 = key2.split(".");
|
|
2909
|
+
const id = searchSegments2.slice(searchSegments2.length - 1, searchSegments2.length)[0].split("___")[0];
|
|
2910
|
+
ids.push(id);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
return ids;
|
|
2914
|
+
};
|
|
2915
|
+
const idsToKey = getIdsToKey(cKey);
|
|
2916
|
+
return { idsToKey, key, multiple };
|
|
2917
|
+
});
|
|
2918
|
+
return processedMultiples;
|
|
2919
|
+
}).filter((key) => key !== void 0);
|
|
2920
|
+
const findCommonIds = (data) => {
|
|
2921
|
+
const preppedArray = data.map((operations) => {
|
|
2922
|
+
const ids = operations.map((operation) => {
|
|
2923
|
+
return operation.idsToKey;
|
|
2924
|
+
});
|
|
2925
|
+
return ids;
|
|
2926
|
+
});
|
|
2927
|
+
const findCommonElements = (arr) => {
|
|
2928
|
+
if (arr.length > 0) {
|
|
2929
|
+
const flatArray = arr.reduce((acc, val) => acc.concat(val), []);
|
|
2930
|
+
return flatArray.reduce((acc, subArr) => {
|
|
2931
|
+
if (!acc) {
|
|
2932
|
+
return subArr;
|
|
2933
|
+
}
|
|
2934
|
+
return subArr.filter((item) => acc.includes(item));
|
|
2935
|
+
});
|
|
2936
|
+
} else {
|
|
2937
|
+
return [];
|
|
2938
|
+
}
|
|
2939
|
+
};
|
|
2940
|
+
return findCommonElements(preppedArray);
|
|
2941
|
+
};
|
|
2942
|
+
const commonIds = findCommonIds(cachesFound);
|
|
2943
|
+
const filteredCommonIds = commonIds.filter((id) => !included.includes(id));
|
|
2944
|
+
included = [...included, ...filteredCommonIds];
|
|
2945
|
+
remaining = remaining.filter((id) => !filteredCommonIds.includes(id));
|
|
2946
|
+
if (index === allCombinations.length - 1 && remaining.length > 0) {
|
|
2947
|
+
const newOp = {
|
|
2948
|
+
...combination,
|
|
2949
|
+
$id: remaining
|
|
2950
|
+
};
|
|
2951
|
+
return newOp;
|
|
2952
|
+
} else if (filteredCommonIds.length > 0) {
|
|
2953
|
+
const newOp = {
|
|
2954
|
+
...combination,
|
|
2955
|
+
$id: filteredCommonIds
|
|
2956
|
+
};
|
|
2957
|
+
return newOp;
|
|
2958
|
+
}
|
|
2959
|
+
}).filter((combination) => combination !== void 0);
|
|
2960
|
+
const returnFields = combinationsFromCache.length === 0 && !parentOperationBlock?.$id ? fieldsWithMultiples : combinationsFromCache;
|
|
2961
|
+
newOperationBlocks = [...fieldsWithoutMultiples, ...returnFields].map(processOperationBlock);
|
|
2962
|
+
});
|
|
2963
|
+
} else {
|
|
2964
|
+
newOperationBlocks = processIds.map(processOperationBlock);
|
|
2965
|
+
}
|
|
2966
|
+
return newOperationBlocks;
|
|
2967
|
+
};
|
|
2968
|
+
const processOperationBlock = (operationBlock) => {
|
|
2969
|
+
const newBlock = { ...operationBlock, $bzId: `T_${uuidv43()}` };
|
|
2970
|
+
const currentSchema = getCurrentSchema(schema, operationBlock);
|
|
2971
|
+
Object.keys(operationBlock).filter((fieldKey) => !fieldKey.includes("$") && !currentSchema.dataFields?.some((o) => o.path === fieldKey)).forEach((fieldKey) => {
|
|
2972
|
+
const operationBlocks = Array.isArray(operationBlock[fieldKey]) ? operationBlock[fieldKey] : [operationBlock[fieldKey]];
|
|
2973
|
+
const newOperationBlocks = processBlocks(operationBlocks, operationBlock);
|
|
2974
|
+
newBlock[fieldKey] = newOperationBlocks;
|
|
2975
|
+
});
|
|
2976
|
+
return newBlock;
|
|
2977
|
+
};
|
|
2978
|
+
let newBlocks = [];
|
|
2979
|
+
blocks.forEach((block) => {
|
|
2980
|
+
newBlocks = [...newBlocks, ...processBlocks([block])];
|
|
2981
|
+
});
|
|
2982
|
+
const splitBlocks = newBlocks.map(processOperationBlock);
|
|
2983
|
+
return splitBlocks;
|
|
2984
|
+
};
|
|
2985
|
+
const splitBql = splitBzIds(Array.isArray(bqlWithObjectPaths) ? bqlWithObjectPaths : [bqlWithObjectPaths]);
|
|
2986
|
+
const processReplaces = (blocks) => {
|
|
2987
|
+
return blocks.map((block) => {
|
|
2988
|
+
const fields = getFieldKeys(block, true);
|
|
2989
|
+
const newBlock = { ...block };
|
|
2990
|
+
fields.forEach((field) => {
|
|
2991
|
+
const opBlocks = Array.isArray(block[field]) ? block[field] : [block[field]];
|
|
2992
|
+
const newOpBlocks = [];
|
|
2993
|
+
opBlocks.forEach((opBlock) => {
|
|
2994
|
+
if (opBlock.$op === "replace") {
|
|
2995
|
+
const cacheKey = objectPathToKey(opBlock.$objectPath);
|
|
2996
|
+
const cacheKeys = convertManyPaths(cacheKey);
|
|
2997
|
+
const foundKeys = cacheKeys.map((cacheKey2) => {
|
|
2998
|
+
return cache[cacheKey2];
|
|
2999
|
+
});
|
|
3000
|
+
const cacheFound = foundKeys.length === cacheKeys.length;
|
|
3001
|
+
const linksToNotInclude = [];
|
|
3002
|
+
if (cacheFound) {
|
|
3003
|
+
foundKeys.forEach((foundKey) => {
|
|
3004
|
+
if (foundKey) {
|
|
3005
|
+
const unlinkIds = [];
|
|
3006
|
+
foundKey.$ids.filter((id) => {
|
|
3007
|
+
linksToNotInclude.push(id);
|
|
3008
|
+
return id !== opBlock.$id;
|
|
3009
|
+
}).forEach((id) => {
|
|
3010
|
+
unlinkIds.push(id);
|
|
3011
|
+
});
|
|
3012
|
+
newOpBlocks.push({ ...opBlock, $op: "unlink", $id: unlinkIds, $bzId: `T_${uuidv43()}` });
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
if (Array.isArray(opBlock.$id) ? !opBlock.$id.every((id) => linksToNotInclude.includes(id)) : (
|
|
3017
|
+
// @ts-expect-error todo
|
|
3018
|
+
!linksToNotInclude.includes(opBlock.$id)
|
|
3019
|
+
)) {
|
|
3020
|
+
newOpBlocks.push({ ...opBlock, $op: "link", $bzId: `T_${uuidv43()}` });
|
|
3021
|
+
}
|
|
3022
|
+
} else {
|
|
3023
|
+
newOpBlocks.push(opBlock);
|
|
3024
|
+
}
|
|
3025
|
+
});
|
|
3026
|
+
newBlock[field] = processReplaces(newOpBlocks);
|
|
3027
|
+
});
|
|
3028
|
+
return newBlock;
|
|
3029
|
+
});
|
|
3030
|
+
};
|
|
3031
|
+
const processedReplaces = fillObjectPaths(processReplaces(fillObjectPaths(splitBql)));
|
|
3032
|
+
const throwErrors = (blocks) => {
|
|
3033
|
+
return produce5(
|
|
3034
|
+
blocks,
|
|
3035
|
+
(draft) => traverse6(draft, (context) => {
|
|
3036
|
+
const { key, value, parent } = context;
|
|
3037
|
+
if (key && parent && !key?.includes("$") && (Array.isArray(value) || isObject6(value)) && !Array.isArray(parent)) {
|
|
3038
|
+
const values = Array.isArray(value) ? value : [value];
|
|
3039
|
+
values.forEach((thing) => {
|
|
3040
|
+
const parentSchema = getCurrentSchema(schema, parent);
|
|
3041
|
+
const findCardinality = () => {
|
|
3042
|
+
const dataFieldFound = parentSchema.dataFields?.find((field) => field.path === thing.$objectPath.key);
|
|
3043
|
+
if (dataFieldFound) {
|
|
3044
|
+
return dataFieldFound.cardinality;
|
|
3045
|
+
}
|
|
3046
|
+
const linkFieldFound = parentSchema.linkFields?.find((field) => field.path === thing.$objectPath.key);
|
|
3047
|
+
if (linkFieldFound) {
|
|
3048
|
+
return linkFieldFound.cardinality;
|
|
3049
|
+
}
|
|
3050
|
+
const roleFieldFound = parentSchema.roles?.[thing.$objectPath.key];
|
|
3051
|
+
if (linkFieldFound) {
|
|
3052
|
+
return roleFieldFound.cardinality;
|
|
3053
|
+
}
|
|
3054
|
+
};
|
|
3055
|
+
const cacheFound = cache[objectPathToKey(thing.$objectPath)];
|
|
3056
|
+
const processArrayIdsFound = (arrayOfIds, cacheOfIds) => {
|
|
3057
|
+
return arrayOfIds.every((id) => cacheOfIds.includes(id));
|
|
3058
|
+
};
|
|
3059
|
+
const isOccupied = thing.$id ? Array.isArray(thing.$id) ? (
|
|
3060
|
+
// @ts-expect-error todo
|
|
3061
|
+
processArrayIdsFound(thing.$id, cacheFound.$ids)
|
|
3062
|
+
) : (
|
|
3063
|
+
// @ts-expect-error todo
|
|
3064
|
+
cacheFound?.$ids.includes(thing.$id)
|
|
3065
|
+
) : cacheFound;
|
|
3066
|
+
const cardinality = findCardinality();
|
|
3067
|
+
if (thing.$op === "link" && isOccupied && cardinality === "ONE") {
|
|
3068
|
+
throw new Error(
|
|
3069
|
+
`[BQLE-Q-M-2] Cannot link on:"${objectPathToKey(thing.$objectPath)}" because it is already occupied.`
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
3072
|
+
if (thing.$op) {
|
|
3073
|
+
switch (thing.$op) {
|
|
3074
|
+
case "delete":
|
|
3075
|
+
if (!isOccupied) {
|
|
3076
|
+
if (!config.mutation?.ignoreNonexistingThings) {
|
|
3077
|
+
throw new Error(
|
|
3078
|
+
`[BQLE-Q-M-2] Cannot delete $id:"${thing.$id}" because it is not linked to $id:"${parent.$id}"`
|
|
3079
|
+
);
|
|
3080
|
+
} else {
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
break;
|
|
3084
|
+
case "update":
|
|
3085
|
+
if (!isOccupied) {
|
|
3086
|
+
if (!config.mutation?.ignoreNonexistingThings) {
|
|
3087
|
+
throw new Error(
|
|
3088
|
+
`[BQLE-Q-M-2] Cannot update $id:"${thing.$id}" because it is not linked to $id:"${parent.$id}"`
|
|
3089
|
+
);
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
break;
|
|
3093
|
+
case "unlink":
|
|
3094
|
+
if (!isOccupied) {
|
|
3095
|
+
if (!config.mutation?.ignoreNonexistingThings) {
|
|
3096
|
+
throw new Error(
|
|
3097
|
+
`[BQLE-Q-M-2] Cannot unlink $id:"${thing.$id}" because it is not linked to $id:"${parent.$id}"`
|
|
3098
|
+
);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
break;
|
|
3102
|
+
case "link":
|
|
3103
|
+
if (isOccupied) {
|
|
3104
|
+
throw new Error(
|
|
3105
|
+
`[BQLE-Q-M-2] Cannot link $id:"${thing.$id}" because it is already linked to $id:"${parent.$id}"`
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
break;
|
|
3109
|
+
default:
|
|
3110
|
+
break;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
});
|
|
3114
|
+
}
|
|
3115
|
+
})
|
|
3116
|
+
);
|
|
3117
|
+
};
|
|
3118
|
+
throwErrors(processedReplaces);
|
|
3119
|
+
const fillPaths = (blocks) => {
|
|
3120
|
+
return produce5(
|
|
3121
|
+
blocks,
|
|
3122
|
+
(draft) => traverse6(draft, (context) => {
|
|
3123
|
+
const { parent, key, value, meta } = context;
|
|
3124
|
+
if (isObject6(value)) {
|
|
3125
|
+
value[Symbol.for("path")] = meta.nodePath;
|
|
3126
|
+
delete value.$objectPath;
|
|
3127
|
+
delete value.$parentIsCreate;
|
|
3128
|
+
}
|
|
3129
|
+
if (key && parent && !key?.includes("$") && (Array.isArray(value) || isObject6(value)) && !Array.isArray(parent)) {
|
|
3130
|
+
const values = Array.isArray(value) ? value : [value];
|
|
3131
|
+
values.forEach((val) => {
|
|
3132
|
+
if (isObject6(val)) {
|
|
3133
|
+
val[Symbol.for("parent")] = {
|
|
3134
|
+
// @ts-expect-error todo
|
|
3135
|
+
...value[Symbol.for("parent")],
|
|
3136
|
+
path: parent[Symbol.for("path")]
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
})
|
|
3142
|
+
);
|
|
3143
|
+
};
|
|
3144
|
+
const final = fillPaths(processedReplaces);
|
|
3145
|
+
console.log("final", JSON.stringify(final, null, 2));
|
|
3146
|
+
req.filledBqlRequest = final;
|
|
3147
|
+
};
|
|
3148
|
+
|
|
3149
|
+
// src/pipeline/pipeline.ts
|
|
3150
|
+
var Pipelines = {
|
|
3151
|
+
query: [enrichBQLQuery, newBuildTQLQuery, newRunTQLQuery, parseTQLQuery],
|
|
3152
|
+
mutation: [
|
|
3153
|
+
fillBQLMutation,
|
|
3154
|
+
newPreQuery,
|
|
3155
|
+
parseBQLMutation,
|
|
3156
|
+
buildTQLMutation,
|
|
3157
|
+
runTQLMutation,
|
|
3158
|
+
parseTQLMutation,
|
|
3159
|
+
buildBQLTree
|
|
3160
|
+
]
|
|
3161
|
+
};
|
|
3162
|
+
var runPipeline = async (pipeline, req, res = {}, root = true) => {
|
|
3163
|
+
for (const operation of pipeline) {
|
|
3164
|
+
const next = await operation(req, res);
|
|
3165
|
+
if (next && Array.isArray(next)) {
|
|
3166
|
+
for (const nextPipeline of next) {
|
|
3167
|
+
await runPipeline(nextPipeline.pipeline, nextPipeline.req, nextPipeline.res, false);
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
if (root) {
|
|
3172
|
+
if (req.config.query?.debugger === true && typeof res.bqlRes === "object") {
|
|
3173
|
+
return { ...res.bqlRes, $debugger: { tqlRequest: req.tqlRequest } };
|
|
3174
|
+
}
|
|
3175
|
+
return res.bqlRes;
|
|
3176
|
+
}
|
|
3177
|
+
return res.bqlRes;
|
|
3178
|
+
};
|
|
3179
|
+
var queryPipeline = (bqlRequest, bormConfig, bormSchema, dbHandles) => runPipeline(
|
|
3180
|
+
Pipelines.query,
|
|
3181
|
+
{
|
|
3182
|
+
config: bormConfig,
|
|
3183
|
+
schema: bormSchema,
|
|
3184
|
+
rawBqlRequest: bqlRequest,
|
|
3185
|
+
dbHandles
|
|
3186
|
+
},
|
|
3187
|
+
{}
|
|
3188
|
+
);
|
|
3189
|
+
var mutationPipeline = (bqlRequest, bormConfig, bormSchema, dbHandles) => runPipeline(
|
|
3190
|
+
Pipelines.mutation,
|
|
3191
|
+
{
|
|
3192
|
+
config: bormConfig,
|
|
3193
|
+
schema: bormSchema,
|
|
3194
|
+
rawBqlRequest: bqlRequest,
|
|
3195
|
+
dbHandles
|
|
3196
|
+
},
|
|
3197
|
+
{}
|
|
3198
|
+
);
|
|
3199
|
+
|
|
3200
|
+
// src/index.ts
|
|
3201
|
+
var BormClient = class {
|
|
3202
|
+
schema;
|
|
3203
|
+
config;
|
|
3204
|
+
dbHandles;
|
|
3205
|
+
constructor({ schema, config }) {
|
|
3206
|
+
this.schema = schema;
|
|
3207
|
+
this.config = config;
|
|
3208
|
+
}
|
|
3209
|
+
init = async () => {
|
|
3210
|
+
const dbHandles = { typeDB: /* @__PURE__ */ new Map() };
|
|
3211
|
+
const enrichedSchema = enrichSchema(this.schema);
|
|
3212
|
+
await Promise.all(
|
|
3213
|
+
this.config.dbConnectors.map(async (dbc) => {
|
|
3214
|
+
if (dbc.provider === "typeDB" && dbc.dbName) {
|
|
3215
|
+
const [clientErr, client] = await tryit(TypeDB.coreDriver)(dbc.url);
|
|
3216
|
+
if (clientErr) {
|
|
3217
|
+
const message = `[BORM:${dbc.provider}:${dbc.dbName}:core] ${// clientErr.messageTemplate?._messageBody() ?? "Can't create TypeDB Client"
|
|
3218
|
+
clientErr.message ?? "Can't create TypeDB Client"}`;
|
|
3219
|
+
throw new Error(message);
|
|
3220
|
+
}
|
|
3221
|
+
try {
|
|
3222
|
+
const session = await client.session(dbc.dbName, SessionType3.DATA);
|
|
3223
|
+
dbHandles.typeDB.set(dbc.id, { client, session });
|
|
3224
|
+
} catch (sessionErr) {
|
|
3225
|
+
const message = `[BORM:${dbc.provider}:${dbc.dbName}:session] ${// eslint-disable-next-line no-underscore-dangle
|
|
3226
|
+
(sessionErr.messageTemplate?._messageBody() || sessionErr.message) ?? "Can't create TypeDB Session"}`;
|
|
3227
|
+
throw new Error(message);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
if (dbc.provider === "typeDBCluster" && dbc.dbName) {
|
|
3231
|
+
const [clientErr, client] = await tryit(TypeDB.enterpriseDriver)(dbc.addresses, dbc.credentials);
|
|
3232
|
+
if (clientErr) {
|
|
3233
|
+
const message = `[BORM:${dbc.provider}:${dbc.dbName}:core] ${// clientErr.messageTemplate?._messageBody() ?? "Can't create TypeDB Client"
|
|
3234
|
+
clientErr.message ?? "Can't create TypeDB Cluster Client"}`;
|
|
3235
|
+
throw new Error(message);
|
|
3236
|
+
}
|
|
3237
|
+
try {
|
|
3238
|
+
const session = await client.session(dbc.dbName, SessionType3.DATA);
|
|
3239
|
+
dbHandles.typeDB.set(dbc.id, { client, session });
|
|
3240
|
+
} catch (sessionErr) {
|
|
3241
|
+
const message = `[BORM:${dbc.provider}:${dbc.dbName}:session] ${// eslint-disable-next-line no-underscore-dangle
|
|
3242
|
+
(sessionErr.messageTemplate?._messageBody() || sessionErr.message) ?? "Can't create TypeDB Session"}`;
|
|
3243
|
+
throw new Error(message);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
})
|
|
3247
|
+
);
|
|
3248
|
+
this.schema = enrichedSchema;
|
|
3249
|
+
this.dbHandles = dbHandles;
|
|
3250
|
+
};
|
|
3251
|
+
#enforceConnection = async () => {
|
|
3252
|
+
if (!this.dbHandles) {
|
|
3253
|
+
await this.init();
|
|
3254
|
+
if (!this.dbHandles) {
|
|
3255
|
+
throw new Error("Can't init BormClient");
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
};
|
|
3259
|
+
introspect = async () => {
|
|
3260
|
+
await this.#enforceConnection();
|
|
3261
|
+
return this.schema;
|
|
3262
|
+
};
|
|
3263
|
+
define = async () => {
|
|
3264
|
+
await this.#enforceConnection();
|
|
3265
|
+
return bormDefine(this.config, this.schema, this.dbHandles);
|
|
3266
|
+
};
|
|
3267
|
+
/// no types yet, but we can do "as ..." after getting the type fro the schema
|
|
3268
|
+
query = async (query, queryConfig) => {
|
|
3269
|
+
await this.#enforceConnection();
|
|
3270
|
+
const qConfig = {
|
|
3271
|
+
...this.config,
|
|
3272
|
+
query: { ...defaultConfig.query, ...this.config.query, ...queryConfig }
|
|
3273
|
+
};
|
|
3274
|
+
return queryPipeline(query, qConfig, this.schema, this.dbHandles);
|
|
3275
|
+
};
|
|
3276
|
+
mutate = async (mutation, mutationConfig) => {
|
|
3277
|
+
await this.#enforceConnection();
|
|
3278
|
+
const mConfig = {
|
|
3279
|
+
...this.config,
|
|
3280
|
+
mutation: {
|
|
3281
|
+
...defaultConfig.mutation,
|
|
3282
|
+
...this.config.mutation,
|
|
3283
|
+
...mutationConfig
|
|
3284
|
+
}
|
|
3285
|
+
};
|
|
3286
|
+
return mutationPipeline(mutation, mConfig, this.schema, this.dbHandles);
|
|
3287
|
+
};
|
|
3288
|
+
close = async () => {
|
|
3289
|
+
if (!this.dbHandles) {
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
this.dbHandles.typeDB.forEach(async ({ client, session }) => {
|
|
3293
|
+
console.log("Closing session");
|
|
3294
|
+
await session.close();
|
|
3295
|
+
console.log("Closing client");
|
|
3296
|
+
await client.close();
|
|
3297
|
+
});
|
|
3298
|
+
};
|
|
3299
|
+
};
|
|
3300
|
+
var src_default = BormClient;
|
|
3301
|
+
export {
|
|
3302
|
+
src_default as default
|
|
3303
|
+
};
|
|
3304
|
+
//! reads all the insertions and gets the first match. This means each id must be unique
|
|
3305
|
+
//! disabled as it has false positives
|
|
3306
|
+
//# sourceMappingURL=index.mjs.map
|