@graffy/pg 0.15.8-alpha.5 → 0.15.8

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/index.cjs CHANGED
@@ -1,20 +1,548 @@
1
- "use strict";var e=Object.defineProperty,t=Object.defineProperties,r=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,i=(t,r,n)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[r]=n,$=(e,t)=>{for(var r in t||(t={}))a.call(t,r)&&i(e,r,t[r]);if(n)for(var r of n(t))o.call(t,r)&&i(e,r,t[r]);return e},s=(e,n)=>t(e,r(n)),l=(e,t)=>{var r={};for(var i in e)a.call(e,i)&&t.indexOf(i)<0&&(r[i]=e[i]);if(null!=e&&n)for(var i of n(e))t.indexOf(i)<0&&o.call(e,i)&&(r[i]=e[i]);return r};Object.defineProperty(exports,"__esModule",{value:!0}),exports[Symbol.toStringTag]="Module";var u=require("pg"),c=require("@graffy/common"),f=require("@graffy/testing"),d=require("debug"),p=require("sql-template-tag");function w(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var h=w(d),g=w(p);const b=h.default("graffy:pg:link");function y(e,t){return e.map((function e(r){if("string"==typeof r&&"$"===r[0]&&"$"===r[1])return c.unwrapObject(t,c.encodePath(r.slice(2)));if(Array.isArray(r))return r.map(e);if("object"==typeof r&&r){const t={};for(const n in r)t[n]=e(r[n]);return t}return r}))}function j(e,t,{links:r}){const n=[];for(let a in r){const o=c.encodePath(a),i=c.unwrap(t,o);if(i)for(const t of e){const e=y(r[a],t),[l,u]=c.splitRef(e);if(l){const r=[];c.mergeObject(t,c.wrapObject(r,o));const a=i.map((t=>{const[n,a]=c.splitArgs(c.decodeArgs(t)),o=$($({},u),a);return r.push({$key:c.isEmpty(a)?"":s($({},a),{$all:!0}),$ref:e.slice(0,-1).concat([s($({},o),{$all:!0})])}),n?s($({},c.encodeArgs(o)),{children:[t],version:t.version,prefix:!0}):$($({},t),c.encodeArgs(o))}));c.add(n,c.wrap(a,c.encodePath(e.slice(0,-1))))}else c.mergeObject(t,c.wrapObject({$ref:e},o)),c.add(n,c.wrap(i,c.encodePath(e)))}}return b("Linked Result",JSON.stringify(e,null,2),f.format(n)),n}const m={$eq:!0,$lt:!0,$gt:!0,$lte:!0,$gte:!0,$re:!0,$ire:!0,$text:!0,$and:!0,$or:!0,$any:!0,$all:!0,$has:!0},O={$eq:"$neq",$neq:"$eq",$in:"$nin",$nin:"$in",$lt:"$gte",$gte:"$lt",$gt:"$lte",$lte:"$gt"};function E(e){return q=0,N(_(e))}let q;function _(e,t,r){if(!e||"object"!=typeof e){if(r&&t)return[r,t,e];if(t)return["$eq",t,e];throw Error("pgast.expected_prop_before:"+JSON.stringify(e))}return Array.isArray(e)?["$or",e.map((e=>_(e,t,r)))]:["$and",Object.entries(e).map((([e,n])=>{if("$or"===e||"$and"===e)return[e,_(n,t,r)[1]];if("$not"===e)return[e,_(n,t,r)];if("$any"===e||"$all"===e){const r="el$"+q++;return[e,t,r,_(n,r)]}if("$has"===e){const r="el$"+q++;return[e,t,r,_(n,r)[1]]}if("$"===e[0]){if(!m[e])throw Error("pgast.invalid_op:"+e);if(r)throw Error("pgast.unexpected_op:"+r+" before:"+e);if(!t)throw Error("pgast.expected_prop_before:"+e);return _(n,t,e)}if(t){if("."===e[0])return _(n,t+e);throw Error("pgast.unexpected_prop",e)}return _(n,e)}))]}function N(e){const t=e[0];if("$and"===t||"$or"===t?e[1]=e[1].map((e=>N(e))):"$not"===t?e[1]=N(e[1]):"$all"===t||"$any"===t?e[3]=N(e[3]):"$has"===t&&(e[3]=e[3].map((e=>N(e)))),"$or"===t){const{eqmap:t,noneq:r,change:n}=e[1].reduce(((e,t)=>{if("$eq"!==t[0])e.noneq.push(t);else{if(e.eqmap[t[1]])return e.change=!0,e.eqmap[t[1]].push(t[2]),e;e.eqmap[t[1]]=[t[2]]}return e}),{eqmap:{},noneq:[],change:!1});n&&(e[1]=[...r,...Object.entries(t).map((([e,t])=>t.length>1?["$in",e,t]:["$eq",e,t[0]]))])}if("$and"===t||"$or"===t){if(!e[1].length)throw Error("pgast.expected_children:"+t);return 1===e[1].length?e[1][0]:e}if("$not"===t){const[t,...r]=e[1],n=O[t];return n?[n,...r]:e}if("$any"===t||"$all"===t){const[r,n,a,o]=e,[i,$,s]=o;return"$eq"!==i&&"$in"!==i||a!==$?e:["$any"===t?"$ovl":"$ctd",n,"$eq"===i?[s]:s]}if("$has"===t){const[t,r,n,a]=e;return a.every((([e,t])=>"$eq"===e&&t===n))?["$cts",r,a.map((([e,t,r])=>r))]:e}return e}const S=g.default`cast(extract(epoch from now()) as integer)`,v=e=>{const t=p.join(Object.entries(e).map((([e,t])=>g.default`'${p.raw(e)}', ${t}`)));return g.default`jsonb_build_object(${t})`},C=e=>g.default`to_jsonb("${p.raw(e)}")`,A=({idCol:e})=>v({$key:g.default`"${p.raw(e)}"`,$ver:S});function R(e,t){var r=e,{$first:n,$last:a,$after:o,$before:i,$since:$,$until:s,$all:u,$cursor:f}=r,d=l(r,["$first","$last","$after","$before","$since","$until","$all","$cursor"]);const w=d,{$order:h}=w,b=l(w,["$order"]),{prefix:y,idCol:j}=t,m=e=>{const[t,...r]=c.encodePath(e);return r.length?g.default`"${p.raw(t)}" #>> '{"${r.join('","')}"}'`:g.default`"${p.raw(t)}"`},O=e=>((e,t,r)=>v({$key:e,$ref:g.default`jsonb_build_array(${p.join(t.map((e=>g.default`${e}::text`)))}, "${p.raw(r)}")`,$ver:S}))(e,y,j),q=i||o||$||s||n||a||u||h;let _;const N=[];if(c.isEmpty(b)||(N.push(function(e,t){function r(e){return"el$"===e.substr(0,3)?g.default`"${p.raw(e)}"`:t(e)}return function e(t){switch(t[0]){case"$eq":return null===t[2]?g.default`${r(t[1])} IS NULL`:g.default`${r(t[1])} = ${t[2]}`;case"$neq":return null===t[2]?g.default`${r(t[1])} IS NOT NULL`:g.default`${r(t[1])} <> ${t[2]}`;case"$lt":return g.default`${r(t[1])} < ${t[2]}`;case"$lte":return g.default`${r(t[1])} <= ${t[2]}`;case"$gt":return g.default`${r(t[1])} > ${t[2]}`;case"$gte":return g.default`${r(t[1])} >= ${t[2]}`;case"$re":return g.default`${r(t[1])} ~ ${t[2]}`;case"$ire":return g.default`${r(t[1])} ~* ${t[2]}`;case"$in":return g.default`${r(t[1])} IN (${p.join(t[2])})`;case"$nin":return g.default`${r(t[1])} NOT IN (${p.join(t[2])})`;case"$cts":return g.default`${r(t[1])} @> ${t[2]}`;case"$ctd":return g.default`${r(t[1])} <@ ${t[2]}`;case"$ovp":return g.default`${r(t[1])} && ${t[2]}`;case"$and":return g.default`(${p.join(t[1].map((t=>e(t))),") AND (")})`;case"$or":return g.default`(${p.join(t[1].map((t=>e(t))),") OR (")})`;case"$not":return g.default`NOT (${e(t[1])})`;case"$any":return g.default`(SELECT bool_or(${e(t[3])}) FROM UNNEST(${r(t[1])}) ${r(t[2])})`;case"$all":return g.default`(SELECT bool_and(${e(t[3])}) FROM UNNEST(${r(t[1])}) ${r(t[2])})`;case"$has":return g.default`(SELECT bool_or(${p.join(t[3].map((t=>e(t))),") AND bool_or(")}) FROM UNNEST(${r(t[1])}) ${r(t[2])})`;default:throw Error("pg.getSql_unknown_operator: "+t[0])}}(E(e))}(b,m)),_=g.default`${JSON.stringify(b)}::jsonb`),!q)return{meta:O(_),where:N,limit:1};if(c.isEmpty(d))throw Error("pg_arg.pagination_only_unsupported in "+y);const C=(h||[j]).map(m);Object.entries({$after:o,$before:i,$since:$,$until:s}).forEach((([e,t])=>{t&&N.push(T(C,t,e))}));const A=h&&v({$order:g.default`${JSON.stringify(h)}::jsonb`}),R=v({$cursor:g.default`jsonb_build_array(${p.join(C)})`});return _=g.default`(${p.join([_,A,R].filter(Boolean)," || ")})`,{meta:O(_),where:N,order:p.join(C.map((e=>g.default`${e} ${a?g.default`DESC`:g.default`ASC`}`)),", "),limit:n||a}}function T(e,t,r){if(!Array.isArray(t))throw Error("pg_arg.bad_query bound : "+JSON.stringify(t));const n=e[0],a=t[0];if(e.length>1&&t.length>1){const o=T(e.slice(1),t.slice(1),r);switch(r){case"$after":case"$since":return g.default`${n} > ${a} OR ${n} = ${a} AND (${o})`;case"$before":case"$until":return g.default`${n} < ${a} OR ${n} = ${a} AND (${o})`}}else switch(r){case"$after":return g.default`${n} > ${a}`;case"$since":return g.default`${n} >= ${a}`;case"$before":return g.default`${n} < ${a}`;case"$until":return g.default`${n} <= ${a}`}}function x(e,t,r){const{table:n,idCol:a}=r,{where:o,meta:i}=c.isPlainObject(t)?R(t,r):{where:[g.default`"${p.raw(a)}" = ${t}`],meta:A(r)};if(!o||!o.length)throw Error("pg_write.no_condition");const $=e;return g.default`
2
- UPDATE "${p.raw(n)}" SET ${((e,t)=>p.join(Object.entries(e).filter((([e])=>e!==t.idCol&&"$"!==e[0])).map((([e,t])=>g.default`"${p.raw(e)}" = ${"object"==typeof t&&t?g.default`"${p.raw(e)}" || ${t}`:t}`)).concat(g.default`"${p.raw(t.verCol)}" = ${S}`),", "))($,r)}
3
- WHERE ${p.join(o," AND ")}
4
- LIMIT 1
5
- RETURNING (${C(n)} || ${i})`}function P(e,t,r){const{idCol:n,table:a}=r,o=e;let i,$;c.isPlainObject(t)?(({meta:i}=R(t,r)),$=p.join(Object.keys(t).map((e=>g.default`"${p.raw(e)}"`)))):(i=A(r),$=g.default`"${p.raw(n)}"`);const{cols:s,vals:l}=((e,t)=>{const r=[],n=[];return Object.entries(e).filter((([e])=>e!==t.verCol&&"$"!==e[0])).concat([[t.verCol,S]]).forEach((([e,t])=>{r.push(g.default`"${p.raw(e)}"`),n.push(t)})),{cols:p.join(r,", "),vals:p.join(n,", ")}})(o,r);return g.default`
6
- INSERT INTO "${p.raw(a)}" (${s}) VALUES (${l})
7
- ON CONFLICT (${$}) DO UPDATE SET (${s}) = (${l})
8
- RETURNING (${C(a)} || ${i})`}const D=h.default("graffy:pg:db");class I{constructor(e){"object"==typeof e&&e&&(e instanceof u.Pool||e instanceof u.Client)?this.client=e:this.client=new u.Pool(e)}async query(e){e.rowMode="array",D("Making SQL query: "+e.text,e.values);try{return await this.client.query(e)}catch(t){throw Error("pg.sql_error "+t.message+" in "+e.text+" with "+e.values)}}async readSql(e){let t=await this.query(e);return t=t.rows.flat(),D("Read result",t),t}async writeSql(e){let t=await this.query(e);if(D("Rows written",t.rowCount),!t.rowCount)throw Error("pg.nothing_written "+e.text+" with "+e.values);return t.rows[0][0]}async read(e,t){const r={},n=[],a=[],o=[],{idCol:i,prefix:s}=t,l=async(e,r)=>{const n=await this.readSql(function(e,t){const{table:r}=t,{where:n,order:a,limit:o,meta:i}=R(e,t),$=Math.min(4096,o||4096);return g.default`
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __spreadValues = (a, b) => {
8
+ for (var prop in b || (b = {}))
9
+ if (__hasOwnProp.call(b, prop))
10
+ __defNormalProp(a, prop, b[prop]);
11
+ if (__getOwnPropSymbols)
12
+ for (var prop of __getOwnPropSymbols(b)) {
13
+ if (__propIsEnum.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ }
16
+ return a;
17
+ };
18
+ var __objRest = (source, exclude) => {
19
+ var target = {};
20
+ for (var prop in source)
21
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
22
+ target[prop] = source[prop];
23
+ if (source != null && __getOwnPropSymbols)
24
+ for (var prop of __getOwnPropSymbols(source)) {
25
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
26
+ target[prop] = source[prop];
27
+ }
28
+ return target;
29
+ };
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports[Symbol.toStringTag] = "Module";
32
+ var pg$1 = require("pg");
33
+ var common = require("@graffy/common");
34
+ var sql = require("sql-template-tag");
35
+ var debug = require("debug");
36
+ function _interopDefaultLegacy(e) {
37
+ return e && typeof e === "object" && "default" in e ? e : { "default": e };
38
+ }
39
+ var sql__default = /* @__PURE__ */ _interopDefaultLegacy(sql);
40
+ var debug__default = /* @__PURE__ */ _interopDefaultLegacy(debug);
41
+ const valid = {
42
+ $eq: true,
43
+ $lt: true,
44
+ $gt: true,
45
+ $lte: true,
46
+ $gte: true,
47
+ $re: true,
48
+ $ire: true,
49
+ $text: true,
50
+ $and: true,
51
+ $or: true,
52
+ $any: true,
53
+ $all: true,
54
+ $has: true,
55
+ $cts: true,
56
+ $ctd: true,
57
+ $ovl: true
58
+ };
59
+ const inverse = {
60
+ $eq: "$neq",
61
+ $neq: "$eq",
62
+ $in: "$nin",
63
+ $nin: "$in",
64
+ $lt: "$gte",
65
+ $gte: "$lt",
66
+ $gt: "$lte",
67
+ $lte: "$gt"
68
+ };
69
+ function getAst(filter) {
70
+ counter = 0;
71
+ return simplify(construct(filter));
72
+ }
73
+ let counter;
74
+ function construct(node, prop, op) {
75
+ if (!node || typeof node !== "object" || prop && op) {
76
+ if (op && prop)
77
+ return [op, prop, node];
78
+ if (prop)
79
+ return ["$eq", prop, node];
80
+ throw Error("pgast.expected_prop_before:" + JSON.stringify(node));
81
+ }
82
+ if (Array.isArray(node)) {
83
+ return ["$or", node.map((item) => construct(item, prop, op))];
84
+ }
85
+ return [
86
+ "$and",
87
+ Object.entries(node).map(([key, val]) => {
88
+ if (key === "$or" || key === "$and") {
89
+ return [key, construct(val, prop, op)[1]];
90
+ }
91
+ if (key === "$not") {
92
+ return [key, construct(val, prop, op)];
93
+ }
94
+ if (key === "$any" || key === "$all") {
95
+ const elkey = "el$" + counter++;
96
+ return [key, prop, elkey, construct(val, elkey)];
97
+ }
98
+ if (key === "$has") {
99
+ const elkey = "el$" + counter++;
100
+ return [key, prop, elkey, construct(val, elkey)[1]];
101
+ }
102
+ if (key[0] === "$") {
103
+ if (!valid[key])
104
+ throw Error("pgast.invalid_op:" + key);
105
+ if (op)
106
+ throw Error("pgast.unexpected_op:" + op + " before:" + key);
107
+ if (!prop)
108
+ throw Error("pgast.expected_prop_before:" + key);
109
+ return construct(val, prop, key);
110
+ }
111
+ if (prop) {
112
+ if (key[0] === ".")
113
+ return construct(val, prop + key);
114
+ throw Error("pgast.unexpected_prop: " + key);
115
+ }
116
+ return construct(val, key);
117
+ })
118
+ ];
119
+ }
120
+ function simplify(node) {
121
+ const op = node[0];
122
+ if (op === "$and" || op === "$or") {
123
+ node[1] = node[1].map((subnode) => simplify(subnode));
124
+ } else if (op === "$not") {
125
+ node[1] = simplify(node[1]);
126
+ } else if (op === "$all" || op === "$any") {
127
+ node[3] = simplify(node[3]);
128
+ } else if (op === "$has") {
129
+ node[3] = node[3].map((subnode) => simplify(subnode));
130
+ }
131
+ if (op === "$or") {
132
+ const { eqmap, noneq, change } = node[1].reduce((acc, item) => {
133
+ if (item[0] !== "$eq") {
134
+ acc.noneq.push(item);
135
+ } else if (acc.eqmap[item[1]]) {
136
+ acc.change = true;
137
+ acc.eqmap[item[1]].push(item[2]);
138
+ return acc;
139
+ } else {
140
+ acc.eqmap[item[1]] = [item[2]];
141
+ }
142
+ return acc;
143
+ }, { eqmap: {}, noneq: [], change: false });
144
+ if (change) {
145
+ node[1] = [
146
+ ...noneq,
147
+ ...Object.entries(eqmap).map(([prop, val]) => val.length > 1 ? ["$in", prop, val] : ["$eq", prop, val[0]])
148
+ ];
149
+ }
150
+ }
151
+ if (op === "$and" || op === "$or") {
152
+ if (!node[1].length)
153
+ throw Error("pgast.expected_children:" + op);
154
+ return node[1].length === 1 ? node[1][0] : node;
155
+ }
156
+ if (op === "$not") {
157
+ const [subop, ...subargs] = node[1];
158
+ const invop = inverse[subop];
159
+ return invop ? [invop, ...subargs] : node;
160
+ }
161
+ if (op === "$any" || op === "$all") {
162
+ const [_, list, elkey, subnode] = node;
163
+ const [subop, elk, val] = subnode;
164
+ return (subop === "$eq" || subop === "$in") && elkey === elk ? [op === "$any" ? "$ovl" : "$ctd", list, subop === "$eq" ? [val] : val] : node;
165
+ }
166
+ if (op === "$has") {
167
+ const [_, list, elkey, limbs] = node;
168
+ return limbs.every(([subop, elk]) => subop === "$eq" && elk === elkey) ? ["$cts", list, limbs.map(([_op, _prop, val]) => val)] : node;
169
+ }
170
+ return node;
171
+ }
172
+ function defaultColumnType() {
173
+ return "jsonb";
174
+ }
175
+ function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
176
+ function lhs(string) {
177
+ if (string.substr(0, 3) === "el$")
178
+ return sql__default["default"]`"${sql.raw(string)}"`;
179
+ return getLookupSql(string);
180
+ }
181
+ function getNodeSql(ast) {
182
+ switch (ast[0]) {
183
+ case "$eq":
184
+ if (ast[2] === null)
185
+ return sql__default["default"]`${lhs(ast[1])} IS NULL`;
186
+ return sql__default["default"]`${lhs(ast[1])} = ${ast[2]}`;
187
+ case "$neq":
188
+ if (ast[2] === null)
189
+ return sql__default["default"]`${lhs(ast[1])} IS NOT NULL`;
190
+ return sql__default["default"]`${lhs(ast[1])} <> ${ast[2]}`;
191
+ case "$lt":
192
+ return sql__default["default"]`${lhs(ast[1])} < ${ast[2]}`;
193
+ case "$lte":
194
+ return sql__default["default"]`${lhs(ast[1])} <= ${ast[2]}`;
195
+ case "$gt":
196
+ return sql__default["default"]`${lhs(ast[1])} > ${ast[2]}`;
197
+ case "$gte":
198
+ return sql__default["default"]`${lhs(ast[1])} >= ${ast[2]}`;
199
+ case "$re":
200
+ return sql__default["default"]`${lhs(ast[1])} ~ ${ast[2]}`;
201
+ case "$ire":
202
+ return sql__default["default"]`${lhs(ast[1])} ~* ${ast[2]}`;
203
+ case "$in":
204
+ return sql__default["default"]`${lhs(ast[1])} IN (${sql.join(ast[2])})`;
205
+ case "$nin":
206
+ return sql__default["default"]`${lhs(ast[1])} NOT IN (${sql.join(ast[2])})`;
207
+ case "$cts":
208
+ return sql__default["default"]`${lhs(ast[1])} @> ${ast[2]}`;
209
+ case "$ctd":
210
+ return sql__default["default"]`${lhs(ast[1])} <@ ${ast[2]}`;
211
+ case "$ovl":
212
+ switch (getColumnType(ast[1])) {
213
+ case "jsonb":
214
+ return sql__default["default"]`${lhs(ast[1])} ?| ${Array.isArray(ast[2]) ? ast[2] : Object.keys(ast[2])}`;
215
+ case "array":
216
+ return sql__default["default"]`${lhs(ast[1])} && ${ast[2]}`;
217
+ default:
218
+ throw Error("pg.getSql_ovl_unknown_column_type");
219
+ }
220
+ case "$and":
221
+ return sql__default["default"]`(${sql.join(ast[1].map((node) => getNodeSql(node)), `) AND (`)})`;
222
+ case "$or":
223
+ return sql__default["default"]`(${sql.join(ast[1].map((node) => getNodeSql(node)), `) OR (`)})`;
224
+ case "$not":
225
+ return sql__default["default"]`NOT (${getNodeSql(ast[1])})`;
226
+ case "$any":
227
+ return sql__default["default"]`(SELECT bool_or(${getNodeSql(ast[3])}) FROM UNNEST(${lhs(ast[1])}) ${lhs(ast[2])})`;
228
+ case "$all":
229
+ return sql__default["default"]`(SELECT bool_and(${getNodeSql(ast[3])}) FROM UNNEST(${lhs(ast[1])}) ${lhs(ast[2])})`;
230
+ case "$has":
231
+ return sql__default["default"]`(SELECT bool_or(${sql.join(ast[3].map((node) => getNodeSql(node)), `) AND bool_or(`)}) FROM UNNEST(${lhs(ast[1])}) ${lhs(ast[2])})`;
232
+ default:
233
+ throw Error("pg.getSql_unknown_operator: " + ast[0]);
234
+ }
235
+ }
236
+ return getNodeSql(getAst(filter));
237
+ }
238
+ const nowTimestamp = sql__default["default"]`cast(extract(epoch from now()) as integer)`;
239
+ const getJsonBuildObject = (variadic) => {
240
+ const args = sql.join(Object.entries(variadic).map(([name, value]) => {
241
+ return sql__default["default"]`'${sql.raw(name)}', ${getJsonBuildValue(value)}`;
242
+ }));
243
+ return sql__default["default"]`jsonb_build_object(${args})`;
244
+ };
245
+ const getJsonBuildValue = (value) => {
246
+ if (value instanceof sql.Sql)
247
+ return value;
248
+ if (typeof value === "string")
249
+ return sql__default["default"]`${value}::text`;
250
+ return sql__default["default"]`${stripAttributes(value)}::jsonb`;
251
+ };
252
+ const getSelectCols = (table) => {
253
+ return sql__default["default"]`to_jsonb("${sql.raw(table)}")`;
254
+ };
255
+ const getInsert = (row, options) => {
256
+ const cols = [];
257
+ const vals = [];
258
+ Object.entries(row).filter(([name]) => name !== options.verCol && name[0] !== "$").concat([[options.verCol, nowTimestamp]]).forEach(([col, val]) => {
259
+ cols.push(sql__default["default"]`"${sql.raw(col)}"`);
260
+ vals.push(val);
261
+ });
262
+ return { cols: sql.join(cols, ", "), vals: sql.join(vals, ", ") };
263
+ };
264
+ const getUpdates = (row, options) => {
265
+ return sql.join(Object.entries(row).filter(([name]) => name !== options.idCol && name[0] !== "$").map(([name, value]) => {
266
+ return sql__default["default"]`"${sql.raw(name)}" = ${typeof value === "object" && value && !value.$put ? sql__default["default"]`jsonb_strip_nulls(${getJsonUpdate(value, name, [])})` : stripAttributes(value)}`;
267
+ }).concat(sql__default["default"]`"${sql.raw(options.verCol)}" = ${nowTimestamp}`), ", ");
268
+ };
269
+ function getJsonUpdate(_a, col, path) {
270
+ var _b = _a, { $put } = _b, object = __objRest(_b, ["$put"]);
271
+ if ($put)
272
+ return object;
273
+ const curr = sql__default["default"]`"${sql.raw(col)}"${path.length ? sql__default["default"]`#>${path}` : sql.empty}`;
274
+ if (common.isEmpty(object))
275
+ return curr;
276
+ return sql__default["default"]`(case jsonb_typeof(${curr})
277
+ when 'object' then ${curr}
278
+ else '{}'::jsonb
279
+ end) || jsonb_build_object(${sql.join(Object.entries(object).map(([key, value]) => sql__default["default"]`${key}::text, ${typeof value === "object" && value ? getJsonUpdate(value, col, path.concat(key)) : sql__default["default"]`${getJsonBuildValue(value)}`}`), ", ")})`;
280
+ }
281
+ function stripAttributes(object) {
282
+ if (typeof object !== "object" || !object || Array.isArray(object)) {
283
+ return object;
284
+ }
285
+ const _a = object, { $put } = _a, rest = __objRest(_a, ["$put"]);
286
+ return rest;
287
+ }
288
+ const getIdMeta = ({ idCol }) => getJsonBuildObject({
289
+ $key: sql__default["default"]`"${sql.raw(idCol)}"`,
290
+ $ver: nowTimestamp
291
+ });
292
+ const getArgMeta = (key, prefix, idCol) => getJsonBuildObject({
293
+ $key: key,
294
+ $ref: sql__default["default"]`jsonb_build_array(${sql.join(prefix.map((k) => sql__default["default"]`${k}::text`))}, "${sql.raw(idCol)}")`,
295
+ $ver: nowTimestamp
296
+ });
297
+ function getArgSql(_c, options) {
298
+ var _d = _c, { $first, $last, $after, $before, $since, $until, $all, $cursor: _ } = _d, rest = __objRest(_d, ["$first", "$last", "$after", "$before", "$since", "$until", "$all", "$cursor"]);
299
+ const _a = rest, { $order } = _a, filter = __objRest(_a, ["$order"]);
300
+ const { prefix, idCol } = options;
301
+ const lookup = (prop) => {
302
+ const [prefix2, ...suffix] = common.encodePath(prop);
303
+ return suffix.length ? sql__default["default"]`"${sql.raw(prefix2)}" #> '{"${suffix.join('","')}"}'` : sql__default["default"]`"${sql.raw(prefix2)}"`;
304
+ };
305
+ const meta = (key2) => getArgMeta(key2, prefix, idCol);
306
+ const hasRangeArg = $before || $after || $since || $until || $first || $last || $all || $order;
307
+ let key;
308
+ const where = [];
309
+ if (!common.isEmpty(filter)) {
310
+ where.push(getSql(filter, lookup));
311
+ key = sql__default["default"]`${JSON.stringify(filter)}::jsonb`;
312
+ }
313
+ if (!hasRangeArg)
314
+ return { meta: meta(key), where, limit: 1 };
315
+ if (common.isEmpty(rest)) {
316
+ throw Error("pg_arg.pagination_only_unsupported in " + prefix);
317
+ }
318
+ const orderCols = ($order || [idCol]).map(lookup);
319
+ Object.entries({ $after, $before, $since, $until }).forEach(([name, value]) => {
320
+ if (value)
321
+ where.push(getBoundCond(orderCols, value, name));
322
+ });
323
+ const orderQuery = $order && getJsonBuildObject({ $order: sql__default["default"]`${JSON.stringify($order)}::jsonb` });
324
+ const cursorQuery = getJsonBuildObject({
325
+ $cursor: sql__default["default"]`jsonb_build_array(${sql.join(orderCols)})`
326
+ });
327
+ key = sql__default["default"]`(${sql.join([key, orderQuery, cursorQuery].filter(Boolean), ` || `)})`;
328
+ return {
329
+ meta: meta(key),
330
+ where,
331
+ order: sql.join(orderCols.map((col) => sql__default["default"]`${col} ${$last ? sql__default["default"]`DESC` : sql__default["default"]`ASC`}`), `, `),
332
+ limit: $first || $last
333
+ };
334
+ }
335
+ function getBoundCond(orderCols, bound, kind) {
336
+ if (!Array.isArray(bound)) {
337
+ throw Error("pg_arg.bad_query bound : " + JSON.stringify(bound));
338
+ }
339
+ const lhs = orderCols[0];
340
+ const rhs = bound[0];
341
+ if (orderCols.length > 1 && bound.length > 1) {
342
+ const subCond = getBoundCond(orderCols.slice(1), bound.slice(1), kind);
343
+ switch (kind) {
344
+ case "$after":
345
+ case "$since":
346
+ return sql__default["default"]`${lhs} > ${rhs} OR ${lhs} = ${rhs} AND (${subCond})`;
347
+ case "$before":
348
+ case "$until":
349
+ return sql__default["default"]`${lhs} < ${rhs} OR ${lhs} = ${rhs} AND (${subCond})`;
350
+ }
351
+ } else {
352
+ switch (kind) {
353
+ case "$after":
354
+ return sql__default["default"]`${lhs} > ${rhs}`;
355
+ case "$since":
356
+ return sql__default["default"]`${lhs} >= ${rhs}`;
357
+ case "$before":
358
+ return sql__default["default"]`${lhs} < ${rhs}`;
359
+ case "$until":
360
+ return sql__default["default"]`${lhs} <= ${rhs}`;
361
+ }
362
+ }
363
+ }
364
+ const MAX_LIMIT = 4096;
365
+ function selectByArgs(args, options) {
366
+ const { table } = options;
367
+ const { where, order, limit, meta } = getArgSql(args, options);
368
+ const clampedLimit = Math.min(MAX_LIMIT, limit || MAX_LIMIT);
369
+ return sql__default["default"]`
9
370
  SELECT
10
- ${C(r)} || ${i}
11
- FROM "${p.raw(r)}"
12
- ${n.length?g.default`WHERE ${p.join(n," AND ")}`:p.empty}
13
- ${a?g.default`ORDER BY ${a}`:p.empty}
14
- LIMIT ${$}
15
- `}(e,t));c.add(o,j(n,r,t));const i=c.encodeGraph(c.wrapObject(n,s));D("getByArgs",i),c.merge(a,i)},u=c.unwrap(e,s);for(const f of u){const e=c.decodeArgs(f);if(c.isPlainObject(e))if(f.prefix)for(const t of f.children){const r=c.decodeArgs(t);n.push(l($($({},e),r),t.children))}else n.push(l(e,f.children));else r[f.key]=f.children}return c.isEmpty(r)||n.push((async()=>{(await this.readSql(function(e,t){const{table:r,idCol:n}=t;return g.default`
371
+ ${getSelectCols(table)} || ${meta}
372
+ FROM "${sql.raw(table)}"
373
+ ${where.length ? sql__default["default"]`WHERE ${sql.join(where, ` AND `)}` : sql.empty}
374
+ ${order ? sql__default["default"]`ORDER BY ${order}` : sql.empty}
375
+ LIMIT ${clampedLimit}
376
+ `;
377
+ }
378
+ function selectByIds(ids, options) {
379
+ const { table, idCol } = options;
380
+ return sql__default["default"]`
16
381
  SELECT
17
- ${C(r)} || ${A(t)}
18
- FROM "${p.raw(r)}"
19
- WHERE "${p.raw(n)}" IN (${p.join(e)})
20
- `}(Object.keys(r),t))).forEach((e=>{const n=e[i],$=r[n];c.add(o,j([e],$,t));const l=c.encodeGraph(c.wrapObject(e,s));D("getByIds",l),c.merge(a,l)}))})()),await Promise.all(n),D("dbRead",e,a),c.slice(c.finalize(a,e),e).known||[]}async write(e,t){const r=[],n=e=>r.push(e),{prefix:a}=t,o=c.unwrap(e,a);for(const $ of o){if(c.isRange($))throw Error($.key===$.end?"pg_write.delete_unsupported":"pg_write.write_range_unsupported");const e=c.decodeArgs($),r=c.decodeGraph($.children);if(c.isPlainObject(e)?c.mergeObject(r,e):r.id=e,r.$put&&!0!==r.$put)throw Error("pg_write.partial_put_unsupported");r.$put?n(P(r,e,t)):n(x(r,e,t))}const i=[];return await Promise.all(r.map((e=>this.writeSql(e).then((e=>{c.merge(i,c.encodeGraph(c.wrapObject(e,a)))}))))),D("dbWrite",e,i),i}}exports.pg=({table:e,idCol:t,verCol:r,links:n,connection:a})=>o=>{o.on("read",(function(e,t){const r=t,{transactionDb:n=s}=r,a=l(r,["transactionDb"]);return n.read(e,$,a)})),o.on("write",(function(e,t){const r=t,{transactionDb:n=s}=r,a=l(r,["transactionDb"]);return n.write(e,$,a)}));const i=o.path,$={prefix:i,table:e||i[i.length-1]||"default",idCol:t||"id",verCol:r||"updatedAt",links:n||{}},s=new I(a)};
382
+ ${getSelectCols(table)} || ${getIdMeta(options)}
383
+ FROM "${sql.raw(table)}"
384
+ WHERE "${sql.raw(idCol)}" IN (${sql.join(ids)})
385
+ `;
386
+ }
387
+ function patch(object, arg, options) {
388
+ const { table, idCol } = options;
389
+ const { where, meta } = common.isPlainObject(arg) ? getArgSql(arg, options) : { where: [sql__default["default"]`"${sql.raw(idCol)}" = ${arg}`], meta: getIdMeta(options) };
390
+ if (!where || !where.length)
391
+ throw Error("pg_write.no_condition");
392
+ const row = object;
393
+ return sql__default["default"]`
394
+ UPDATE "${sql.raw(table)}" SET ${getUpdates(row, options)}
395
+ WHERE ${common.isPlainObject(arg) ? sql__default["default"]`"${sql.raw(idCol)}" = (
396
+ SELECT "${sql.raw(idCol)}" FROM "${sql.raw(table)}"
397
+ WHERE ${sql.join(where, ` AND `)} LIMIT 1)` : sql.join(where, ` AND `)}
398
+ RETURNING (${getSelectCols(table)} || ${meta})`;
399
+ }
400
+ function put(object, arg, options) {
401
+ const { idCol, table } = options;
402
+ const row = object;
403
+ let meta, conflictTarget;
404
+ if (common.isPlainObject(arg)) {
405
+ ({ meta } = getArgSql(arg, options));
406
+ conflictTarget = sql.join(Object.keys(arg).map((col) => sql__default["default"]`"${sql.raw(col)}"`));
407
+ } else {
408
+ meta = getIdMeta(options);
409
+ conflictTarget = sql__default["default"]`"${sql.raw(idCol)}"`;
410
+ }
411
+ const { cols, vals } = getInsert(row, options);
412
+ return sql__default["default"]`
413
+ INSERT INTO "${sql.raw(table)}" (${cols}) VALUES (${vals})
414
+ ON CONFLICT (${conflictTarget}) DO UPDATE SET (${cols}) = (${vals})
415
+ RETURNING (${getSelectCols(table)} || ${meta})`;
416
+ }
417
+ const log = debug__default["default"]("graffy:pg:db");
418
+ class Db {
419
+ constructor(connection) {
420
+ if (typeof connection === "object" && connection && (connection instanceof pg$1.Pool || connection instanceof pg$1.Client)) {
421
+ this.client = connection;
422
+ } else {
423
+ this.client = new pg$1.Pool(connection);
424
+ }
425
+ }
426
+ async query(sql2) {
427
+ sql2.rowMode = "array";
428
+ log("Making SQL query: " + sql2.text, sql2.values);
429
+ try {
430
+ return await this.client.query(sql2);
431
+ } catch (e) {
432
+ const message = [
433
+ e.message,
434
+ e.detail,
435
+ e.hint,
436
+ e.where,
437
+ sql2.text,
438
+ JSON.stringify(sql2.values)
439
+ ].filter(Boolean).join("; ");
440
+ throw Error("pg.sql_error " + message);
441
+ }
442
+ }
443
+ async readSql(sql2) {
444
+ let result = await this.query(sql2);
445
+ result = result.rows.flat();
446
+ log("Read result", result);
447
+ return result;
448
+ }
449
+ async writeSql(sql2) {
450
+ let res = await this.query(sql2);
451
+ log("Rows written", res.rowCount);
452
+ if (!res.rowCount) {
453
+ throw Error("pg.nothing_written " + sql2.text + " with " + sql2.values);
454
+ }
455
+ return res.rows[0][0];
456
+ }
457
+ async read(rootQuery, tableOptions) {
458
+ const idQueries = {};
459
+ const promises = [];
460
+ const results = [];
461
+ const { prefix } = tableOptions;
462
+ const getByArgs = async (args) => {
463
+ const result = await this.readSql(selectByArgs(args, tableOptions));
464
+ const wrappedGraph = common.encodeGraph(common.wrapObject(result, prefix));
465
+ log("getByArgs", wrappedGraph);
466
+ common.merge(results, wrappedGraph);
467
+ };
468
+ const getByIds = async () => {
469
+ const result = await this.readSql(selectByIds(Object.keys(idQueries), tableOptions));
470
+ result.forEach((object) => {
471
+ const wrappedGraph = common.encodeGraph(common.wrapObject(object, prefix));
472
+ log("getByIds", wrappedGraph);
473
+ common.merge(results, wrappedGraph);
474
+ });
475
+ };
476
+ const query = common.unwrap(rootQuery, prefix);
477
+ for (const node of query) {
478
+ const args = common.decodeArgs(node);
479
+ if (common.isPlainObject(args)) {
480
+ if (node.prefix) {
481
+ for (const childNode of node.children) {
482
+ const childArgs = common.decodeArgs(childNode);
483
+ promises.push(getByArgs(__spreadValues(__spreadValues({}, args), childArgs)));
484
+ }
485
+ } else {
486
+ promises.push(getByArgs(args));
487
+ }
488
+ } else {
489
+ idQueries[node.key] = node.children;
490
+ }
491
+ }
492
+ if (!common.isEmpty(idQueries))
493
+ promises.push(getByIds());
494
+ await Promise.all(promises);
495
+ log("dbRead", rootQuery, results);
496
+ return common.slice(common.finalize(results, rootQuery), rootQuery).known || [];
497
+ }
498
+ async write(rootChange, tableOptions) {
499
+ const sqls = [];
500
+ const addToQuery = (sql2) => sqls.push(sql2);
501
+ const { prefix } = tableOptions;
502
+ const change = common.unwrap(rootChange, prefix);
503
+ for (const node of change) {
504
+ if (common.isRange(node)) {
505
+ throw Error(node.key === node.end ? "pg_write.delete_unsupported" : "pg_write.write_range_unsupported");
506
+ }
507
+ const arg = common.decodeArgs(node);
508
+ const object = common.decodeGraph(node.children);
509
+ if (common.isPlainObject(arg)) {
510
+ common.mergeObject(object, arg);
511
+ } else {
512
+ object.id = arg;
513
+ }
514
+ if (object.$put && object.$put !== true) {
515
+ throw Error("pg_write.partial_put_unsupported");
516
+ }
517
+ object.$put ? addToQuery(put(object, arg, tableOptions)) : addToQuery(patch(object, arg, tableOptions));
518
+ }
519
+ const result = [];
520
+ await Promise.all(sqls.map((sql2) => this.writeSql(sql2).then((object) => {
521
+ common.merge(result, common.encodeGraph(common.wrapObject(object, prefix)));
522
+ })));
523
+ log("dbWrite", rootChange, result);
524
+ return result;
525
+ }
526
+ }
527
+ const pg = ({ table, idCol, verCol, links, connection }) => (store) => {
528
+ store.on("read", read);
529
+ store.on("write", write);
530
+ const prefix = store.path;
531
+ const tableOpts = {
532
+ prefix,
533
+ table: table || prefix[prefix.length - 1] || "default",
534
+ idCol: idCol || "id",
535
+ verCol: verCol || "updatedAt",
536
+ links: links || {}
537
+ };
538
+ const defaultDb = new Db(connection);
539
+ function read(query, options) {
540
+ const _a = options, { transactionDb = defaultDb } = _a, readOpts = __objRest(_a, ["transactionDb"]);
541
+ return transactionDb.read(query, tableOpts, readOpts);
542
+ }
543
+ function write(change, options) {
544
+ const _a = options, { transactionDb = defaultDb } = _a, writeOpts = __objRest(_a, ["transactionDb"]);
545
+ return transactionDb.write(change, tableOpts, writeOpts);
546
+ }
547
+ };
548
+ exports.pg = pg;
package/index.mjs CHANGED
@@ -1,20 +1,540 @@
1
- var r=Object.defineProperty,e=Object.defineProperties,t=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,$=Object.prototype.propertyIsEnumerable,i=(e,t,n)=>t in e?r(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,a=(r,e)=>{for(var t in e||(e={}))o.call(e,t)&&i(r,t,e[t]);if(n)for(var t of n(e))$.call(e,t)&&i(r,t,e[t]);return r},s=(r,n)=>e(r,t(n)),c=(r,e)=>{var t={};for(var i in r)o.call(r,i)&&e.indexOf(i)<0&&(t[i]=r[i]);if(null!=r&&n)for(var i of n(r))e.indexOf(i)<0&&$.call(r,i)&&(t[i]=r[i]);return t};import{Pool as l,Client as u}from"pg";import{encodePath as f,unwrap as p,splitRef as h,mergeObject as d,wrapObject as g,splitArgs as b,decodeArgs as y,isEmpty as w,encodeArgs as m,add as E,wrap as O,unwrapObject as _,isPlainObject as q,slice as N,finalize as S,isRange as j,decodeGraph as v,merge as C,encodeGraph as R}from"@graffy/common";import{format as T}from"@graffy/testing";import x from"debug";import A,{join as D,raw as I,empty as k}from"sql-template-tag";const L=x("graffy:pg:link");function M(r,e){return r.map((function r(t){if("string"==typeof t&&"$"===t[0]&&"$"===t[1])return _(e,f(t.slice(2)));if(Array.isArray(t))return t.map(r);if("object"==typeof t&&t){const e={};for(const n in t)e[n]=r(t[n]);return e}return t}))}function U(r,e,{links:t}){const n=[];for(let o in t){const $=f(o),i=p(e,$);if(i)for(const e of r){const r=M(t[o],e),[c,l]=h(r);if(c){const t=[];d(e,g(t,$));const o=i.map((e=>{const[n,o]=b(y(e)),$=a(a({},l),o);return t.push({$key:w(o)?"":s(a({},o),{$all:!0}),$ref:r.slice(0,-1).concat([s(a({},$),{$all:!0})])}),n?s(a({},m($)),{children:[e],version:e.version,prefix:!0}):a(a({},e),m($))}));E(n,O(o,f(r.slice(0,-1))))}else d(e,g({$ref:r},$)),E(n,O(i,f(r)))}}return L("Linked Result",JSON.stringify(r,null,2),T(n)),n}const P={$eq:!0,$lt:!0,$gt:!0,$lte:!0,$gte:!0,$re:!0,$ire:!0,$text:!0,$and:!0,$or:!0,$any:!0,$all:!0,$has:!0},F={$eq:"$neq",$neq:"$eq",$in:"$nin",$nin:"$in",$lt:"$gte",$gte:"$lt",$gt:"$lte",$lte:"$gt"};function J(r){return B=0,H(W(r))}let B;function W(r,e,t){if(!r||"object"!=typeof r){if(t&&e)return[t,e,r];if(e)return["$eq",e,r];throw Error("pgast.expected_prop_before:"+JSON.stringify(r))}return Array.isArray(r)?["$or",r.map((r=>W(r,e,t)))]:["$and",Object.entries(r).map((([r,n])=>{if("$or"===r||"$and"===r)return[r,W(n,e,t)[1]];if("$not"===r)return[r,W(n,e,t)];if("$any"===r||"$all"===r){const t="el$"+B++;return[r,e,t,W(n,t)]}if("$has"===r){const t="el$"+B++;return[r,e,t,W(n,t)[1]]}if("$"===r[0]){if(!P[r])throw Error("pgast.invalid_op:"+r);if(t)throw Error("pgast.unexpected_op:"+t+" before:"+r);if(!e)throw Error("pgast.expected_prop_before:"+r);return W(n,e,r)}if(e){if("."===r[0])return W(n,e+r);throw Error("pgast.unexpected_prop",r)}return W(n,r)}))]}function H(r){const e=r[0];if("$and"===e||"$or"===e?r[1]=r[1].map((r=>H(r))):"$not"===e?r[1]=H(r[1]):"$all"===e||"$any"===e?r[3]=H(r[3]):"$has"===e&&(r[3]=r[3].map((r=>H(r)))),"$or"===e){const{eqmap:e,noneq:t,change:n}=r[1].reduce(((r,e)=>{if("$eq"!==e[0])r.noneq.push(e);else{if(r.eqmap[e[1]])return r.change=!0,r.eqmap[e[1]].push(e[2]),r;r.eqmap[e[1]]=[e[2]]}return r}),{eqmap:{},noneq:[],change:!1});n&&(r[1]=[...t,...Object.entries(e).map((([r,e])=>e.length>1?["$in",r,e]:["$eq",r,e[0]]))])}if("$and"===e||"$or"===e){if(!r[1].length)throw Error("pgast.expected_children:"+e);return 1===r[1].length?r[1][0]:r}if("$not"===e){const[e,...t]=r[1],n=F[e];return n?[n,...t]:r}if("$any"===e||"$all"===e){const[t,n,o,$]=r,[i,a,s]=$;return"$eq"!==i&&"$in"!==i||o!==a?r:["$any"===e?"$ovl":"$ctd",n,"$eq"===i?[s]:s]}if("$has"===e){const[e,t,n,o]=r;return o.every((([r,e])=>"$eq"===r&&e===n))?["$cts",t,o.map((([r,e,t])=>t))]:r}return r}const G=A`cast(extract(epoch from now()) as integer)`,Q=r=>{const e=D(Object.entries(r).map((([r,e])=>A`'${I(r)}', ${e}`)));return A`jsonb_build_object(${e})`},V=r=>A`to_jsonb("${I(r)}")`,Y=({idCol:r})=>Q({$key:A`"${I(r)}"`,$ver:G});function z(r,e){var t=r,{$first:n,$last:o,$after:$,$before:i,$since:a,$until:s,$all:l,$cursor:u}=t,p=c(t,["$first","$last","$after","$before","$since","$until","$all","$cursor"]);const h=p,{$order:d}=h,g=c(h,["$order"]),{prefix:b,idCol:y}=e,m=r=>{const[e,...t]=f(r);return t.length?A`"${I(e)}" #>> '{"${t.join('","')}"}'`:A`"${I(e)}"`},E=r=>((r,e,t)=>Q({$key:r,$ref:A`jsonb_build_array(${D(e.map((r=>A`${r}::text`)))}, "${I(t)}")`,$ver:G}))(r,b,y),O=i||$||a||s||n||o||l||d;let _;const q=[];if(w(g)||(q.push(function(r,e){function t(r){return"el$"===r.substr(0,3)?A`"${I(r)}"`:e(r)}return function r(e){switch(e[0]){case"$eq":return null===e[2]?A`${t(e[1])} IS NULL`:A`${t(e[1])} = ${e[2]}`;case"$neq":return null===e[2]?A`${t(e[1])} IS NOT NULL`:A`${t(e[1])} <> ${e[2]}`;case"$lt":return A`${t(e[1])} < ${e[2]}`;case"$lte":return A`${t(e[1])} <= ${e[2]}`;case"$gt":return A`${t(e[1])} > ${e[2]}`;case"$gte":return A`${t(e[1])} >= ${e[2]}`;case"$re":return A`${t(e[1])} ~ ${e[2]}`;case"$ire":return A`${t(e[1])} ~* ${e[2]}`;case"$in":return A`${t(e[1])} IN (${D(e[2])})`;case"$nin":return A`${t(e[1])} NOT IN (${D(e[2])})`;case"$cts":return A`${t(e[1])} @> ${e[2]}`;case"$ctd":return A`${t(e[1])} <@ ${e[2]}`;case"$ovp":return A`${t(e[1])} && ${e[2]}`;case"$and":return A`(${D(e[1].map((e=>r(e))),") AND (")})`;case"$or":return A`(${D(e[1].map((e=>r(e))),") OR (")})`;case"$not":return A`NOT (${r(e[1])})`;case"$any":return A`(SELECT bool_or(${r(e[3])}) FROM UNNEST(${t(e[1])}) ${t(e[2])})`;case"$all":return A`(SELECT bool_and(${r(e[3])}) FROM UNNEST(${t(e[1])}) ${t(e[2])})`;case"$has":return A`(SELECT bool_or(${D(e[3].map((e=>r(e))),") AND bool_or(")}) FROM UNNEST(${t(e[1])}) ${t(e[2])})`;default:throw Error("pg.getSql_unknown_operator: "+e[0])}}(J(r))}(g,m)),_=A`${JSON.stringify(g)}::jsonb`),!O)return{meta:E(_),where:q,limit:1};if(w(p))throw Error("pg_arg.pagination_only_unsupported in "+b);const N=(d||[y]).map(m);Object.entries({$after:$,$before:i,$since:a,$until:s}).forEach((([r,e])=>{e&&q.push(K(N,e,r))}));const S=d&&Q({$order:A`${JSON.stringify(d)}::jsonb`}),j=Q({$cursor:A`jsonb_build_array(${D(N)})`});return _=A`(${D([_,S,j].filter(Boolean)," || ")})`,{meta:E(_),where:q,order:D(N.map((r=>A`${r} ${o?A`DESC`:A`ASC`}`)),", "),limit:n||o}}function K(r,e,t){if(!Array.isArray(e))throw Error("pg_arg.bad_query bound : "+JSON.stringify(e));const n=r[0],o=e[0];if(r.length>1&&e.length>1){const $=K(r.slice(1),e.slice(1),t);switch(t){case"$after":case"$since":return A`${n} > ${o} OR ${n} = ${o} AND (${$})`;case"$before":case"$until":return A`${n} < ${o} OR ${n} = ${o} AND (${$})`}}else switch(t){case"$after":return A`${n} > ${o}`;case"$since":return A`${n} >= ${o}`;case"$before":return A`${n} < ${o}`;case"$until":return A`${n} <= ${o}`}}function X(r,e,t){const{table:n,idCol:o}=t,{where:$,meta:i}=q(e)?z(e,t):{where:[A`"${I(o)}" = ${e}`],meta:Y(t)};if(!$||!$.length)throw Error("pg_write.no_condition");const a=r;return A`
2
- UPDATE "${I(n)}" SET ${((r,e)=>D(Object.entries(r).filter((([r])=>r!==e.idCol&&"$"!==r[0])).map((([r,e])=>A`"${I(r)}" = ${"object"==typeof e&&e?A`"${I(r)}" || ${e}`:e}`)).concat(A`"${I(e.verCol)}" = ${G}`),", "))(a,t)}
3
- WHERE ${D($," AND ")}
4
- LIMIT 1
5
- RETURNING (${V(n)} || ${i})`}function Z(r,e,t){const{idCol:n,table:o}=t,$=r;let i,a;q(e)?(({meta:i}=z(e,t)),a=D(Object.keys(e).map((r=>A`"${I(r)}"`)))):(i=Y(t),a=A`"${I(n)}"`);const{cols:s,vals:c}=((r,e)=>{const t=[],n=[];return Object.entries(r).filter((([r])=>r!==e.verCol&&"$"!==r[0])).concat([[e.verCol,G]]).forEach((([r,e])=>{t.push(A`"${I(r)}"`),n.push(e)})),{cols:D(t,", "),vals:D(n,", ")}})($,t);return A`
6
- INSERT INTO "${I(o)}" (${s}) VALUES (${c})
7
- ON CONFLICT (${a}) DO UPDATE SET (${s}) = (${c})
8
- RETURNING (${V(o)} || ${i})`}const rr=x("graffy:pg:db");class er{constructor(r){this.client="object"==typeof r&&r&&(r instanceof l||r instanceof u)?r:new l(r)}async query(r){r.rowMode="array",rr("Making SQL query: "+r.text,r.values);try{return await this.client.query(r)}catch(e){throw Error("pg.sql_error "+e.message+" in "+r.text+" with "+r.values)}}async readSql(r){let e=await this.query(r);return e=e.rows.flat(),rr("Read result",e),e}async writeSql(r){let e=await this.query(r);if(rr("Rows written",e.rowCount),!e.rowCount)throw Error("pg.nothing_written "+r.text+" with "+r.values);return e.rows[0][0]}async read(r,e){const t={},n=[],o=[],$=[],{idCol:i,prefix:s}=e,c=async(r,t)=>{const n=await this.readSql(function(r,e){const{table:t}=e,{where:n,order:o,limit:$,meta:i}=z(r,e),a=Math.min(4096,$||4096);return A`
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+ var __objRest = (source, exclude) => {
18
+ var target = {};
19
+ for (var prop in source)
20
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
21
+ target[prop] = source[prop];
22
+ if (source != null && __getOwnPropSymbols)
23
+ for (var prop of __getOwnPropSymbols(source)) {
24
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
25
+ target[prop] = source[prop];
26
+ }
27
+ return target;
28
+ };
29
+ import { Pool, Client } from "pg";
30
+ import { isEmpty, encodePath, isPlainObject, unwrap, decodeArgs, slice, finalize, isRange, decodeGraph, mergeObject, merge, encodeGraph, wrapObject } from "@graffy/common";
31
+ import sql, { join, raw, empty, Sql } from "sql-template-tag";
32
+ import debug from "debug";
33
+ const valid = {
34
+ $eq: true,
35
+ $lt: true,
36
+ $gt: true,
37
+ $lte: true,
38
+ $gte: true,
39
+ $re: true,
40
+ $ire: true,
41
+ $text: true,
42
+ $and: true,
43
+ $or: true,
44
+ $any: true,
45
+ $all: true,
46
+ $has: true,
47
+ $cts: true,
48
+ $ctd: true,
49
+ $ovl: true
50
+ };
51
+ const inverse = {
52
+ $eq: "$neq",
53
+ $neq: "$eq",
54
+ $in: "$nin",
55
+ $nin: "$in",
56
+ $lt: "$gte",
57
+ $gte: "$lt",
58
+ $gt: "$lte",
59
+ $lte: "$gt"
60
+ };
61
+ function getAst(filter) {
62
+ counter = 0;
63
+ return simplify(construct(filter));
64
+ }
65
+ let counter;
66
+ function construct(node, prop, op) {
67
+ if (!node || typeof node !== "object" || prop && op) {
68
+ if (op && prop)
69
+ return [op, prop, node];
70
+ if (prop)
71
+ return ["$eq", prop, node];
72
+ throw Error("pgast.expected_prop_before:" + JSON.stringify(node));
73
+ }
74
+ if (Array.isArray(node)) {
75
+ return ["$or", node.map((item) => construct(item, prop, op))];
76
+ }
77
+ return [
78
+ "$and",
79
+ Object.entries(node).map(([key, val]) => {
80
+ if (key === "$or" || key === "$and") {
81
+ return [key, construct(val, prop, op)[1]];
82
+ }
83
+ if (key === "$not") {
84
+ return [key, construct(val, prop, op)];
85
+ }
86
+ if (key === "$any" || key === "$all") {
87
+ const elkey = "el$" + counter++;
88
+ return [key, prop, elkey, construct(val, elkey)];
89
+ }
90
+ if (key === "$has") {
91
+ const elkey = "el$" + counter++;
92
+ return [key, prop, elkey, construct(val, elkey)[1]];
93
+ }
94
+ if (key[0] === "$") {
95
+ if (!valid[key])
96
+ throw Error("pgast.invalid_op:" + key);
97
+ if (op)
98
+ throw Error("pgast.unexpected_op:" + op + " before:" + key);
99
+ if (!prop)
100
+ throw Error("pgast.expected_prop_before:" + key);
101
+ return construct(val, prop, key);
102
+ }
103
+ if (prop) {
104
+ if (key[0] === ".")
105
+ return construct(val, prop + key);
106
+ throw Error("pgast.unexpected_prop: " + key);
107
+ }
108
+ return construct(val, key);
109
+ })
110
+ ];
111
+ }
112
+ function simplify(node) {
113
+ const op = node[0];
114
+ if (op === "$and" || op === "$or") {
115
+ node[1] = node[1].map((subnode) => simplify(subnode));
116
+ } else if (op === "$not") {
117
+ node[1] = simplify(node[1]);
118
+ } else if (op === "$all" || op === "$any") {
119
+ node[3] = simplify(node[3]);
120
+ } else if (op === "$has") {
121
+ node[3] = node[3].map((subnode) => simplify(subnode));
122
+ }
123
+ if (op === "$or") {
124
+ const { eqmap, noneq, change } = node[1].reduce((acc, item) => {
125
+ if (item[0] !== "$eq") {
126
+ acc.noneq.push(item);
127
+ } else if (acc.eqmap[item[1]]) {
128
+ acc.change = true;
129
+ acc.eqmap[item[1]].push(item[2]);
130
+ return acc;
131
+ } else {
132
+ acc.eqmap[item[1]] = [item[2]];
133
+ }
134
+ return acc;
135
+ }, { eqmap: {}, noneq: [], change: false });
136
+ if (change) {
137
+ node[1] = [
138
+ ...noneq,
139
+ ...Object.entries(eqmap).map(([prop, val]) => val.length > 1 ? ["$in", prop, val] : ["$eq", prop, val[0]])
140
+ ];
141
+ }
142
+ }
143
+ if (op === "$and" || op === "$or") {
144
+ if (!node[1].length)
145
+ throw Error("pgast.expected_children:" + op);
146
+ return node[1].length === 1 ? node[1][0] : node;
147
+ }
148
+ if (op === "$not") {
149
+ const [subop, ...subargs] = node[1];
150
+ const invop = inverse[subop];
151
+ return invop ? [invop, ...subargs] : node;
152
+ }
153
+ if (op === "$any" || op === "$all") {
154
+ const [_, list, elkey, subnode] = node;
155
+ const [subop, elk, val] = subnode;
156
+ return (subop === "$eq" || subop === "$in") && elkey === elk ? [op === "$any" ? "$ovl" : "$ctd", list, subop === "$eq" ? [val] : val] : node;
157
+ }
158
+ if (op === "$has") {
159
+ const [_, list, elkey, limbs] = node;
160
+ return limbs.every(([subop, elk]) => subop === "$eq" && elk === elkey) ? ["$cts", list, limbs.map(([_op, _prop, val]) => val)] : node;
161
+ }
162
+ return node;
163
+ }
164
+ function defaultColumnType() {
165
+ return "jsonb";
166
+ }
167
+ function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
168
+ function lhs(string) {
169
+ if (string.substr(0, 3) === "el$")
170
+ return sql`"${raw(string)}"`;
171
+ return getLookupSql(string);
172
+ }
173
+ function getNodeSql(ast) {
174
+ switch (ast[0]) {
175
+ case "$eq":
176
+ if (ast[2] === null)
177
+ return sql`${lhs(ast[1])} IS NULL`;
178
+ return sql`${lhs(ast[1])} = ${ast[2]}`;
179
+ case "$neq":
180
+ if (ast[2] === null)
181
+ return sql`${lhs(ast[1])} IS NOT NULL`;
182
+ return sql`${lhs(ast[1])} <> ${ast[2]}`;
183
+ case "$lt":
184
+ return sql`${lhs(ast[1])} < ${ast[2]}`;
185
+ case "$lte":
186
+ return sql`${lhs(ast[1])} <= ${ast[2]}`;
187
+ case "$gt":
188
+ return sql`${lhs(ast[1])} > ${ast[2]}`;
189
+ case "$gte":
190
+ return sql`${lhs(ast[1])} >= ${ast[2]}`;
191
+ case "$re":
192
+ return sql`${lhs(ast[1])} ~ ${ast[2]}`;
193
+ case "$ire":
194
+ return sql`${lhs(ast[1])} ~* ${ast[2]}`;
195
+ case "$in":
196
+ return sql`${lhs(ast[1])} IN (${join(ast[2])})`;
197
+ case "$nin":
198
+ return sql`${lhs(ast[1])} NOT IN (${join(ast[2])})`;
199
+ case "$cts":
200
+ return sql`${lhs(ast[1])} @> ${ast[2]}`;
201
+ case "$ctd":
202
+ return sql`${lhs(ast[1])} <@ ${ast[2]}`;
203
+ case "$ovl":
204
+ switch (getColumnType(ast[1])) {
205
+ case "jsonb":
206
+ return sql`${lhs(ast[1])} ?| ${Array.isArray(ast[2]) ? ast[2] : Object.keys(ast[2])}`;
207
+ case "array":
208
+ return sql`${lhs(ast[1])} && ${ast[2]}`;
209
+ default:
210
+ throw Error("pg.getSql_ovl_unknown_column_type");
211
+ }
212
+ case "$and":
213
+ return sql`(${join(ast[1].map((node) => getNodeSql(node)), `) AND (`)})`;
214
+ case "$or":
215
+ return sql`(${join(ast[1].map((node) => getNodeSql(node)), `) OR (`)})`;
216
+ case "$not":
217
+ return sql`NOT (${getNodeSql(ast[1])})`;
218
+ case "$any":
219
+ return sql`(SELECT bool_or(${getNodeSql(ast[3])}) FROM UNNEST(${lhs(ast[1])}) ${lhs(ast[2])})`;
220
+ case "$all":
221
+ return sql`(SELECT bool_and(${getNodeSql(ast[3])}) FROM UNNEST(${lhs(ast[1])}) ${lhs(ast[2])})`;
222
+ case "$has":
223
+ return sql`(SELECT bool_or(${join(ast[3].map((node) => getNodeSql(node)), `) AND bool_or(`)}) FROM UNNEST(${lhs(ast[1])}) ${lhs(ast[2])})`;
224
+ default:
225
+ throw Error("pg.getSql_unknown_operator: " + ast[0]);
226
+ }
227
+ }
228
+ return getNodeSql(getAst(filter));
229
+ }
230
+ const nowTimestamp = sql`cast(extract(epoch from now()) as integer)`;
231
+ const getJsonBuildObject = (variadic) => {
232
+ const args = join(Object.entries(variadic).map(([name, value]) => {
233
+ return sql`'${raw(name)}', ${getJsonBuildValue(value)}`;
234
+ }));
235
+ return sql`jsonb_build_object(${args})`;
236
+ };
237
+ const getJsonBuildValue = (value) => {
238
+ if (value instanceof Sql)
239
+ return value;
240
+ if (typeof value === "string")
241
+ return sql`${value}::text`;
242
+ return sql`${stripAttributes(value)}::jsonb`;
243
+ };
244
+ const getSelectCols = (table) => {
245
+ return sql`to_jsonb("${raw(table)}")`;
246
+ };
247
+ const getInsert = (row, options) => {
248
+ const cols = [];
249
+ const vals = [];
250
+ Object.entries(row).filter(([name]) => name !== options.verCol && name[0] !== "$").concat([[options.verCol, nowTimestamp]]).forEach(([col, val]) => {
251
+ cols.push(sql`"${raw(col)}"`);
252
+ vals.push(val);
253
+ });
254
+ return { cols: join(cols, ", "), vals: join(vals, ", ") };
255
+ };
256
+ const getUpdates = (row, options) => {
257
+ return join(Object.entries(row).filter(([name]) => name !== options.idCol && name[0] !== "$").map(([name, value]) => {
258
+ return sql`"${raw(name)}" = ${typeof value === "object" && value && !value.$put ? sql`jsonb_strip_nulls(${getJsonUpdate(value, name, [])})` : stripAttributes(value)}`;
259
+ }).concat(sql`"${raw(options.verCol)}" = ${nowTimestamp}`), ", ");
260
+ };
261
+ function getJsonUpdate(_a, col, path) {
262
+ var _b = _a, { $put } = _b, object = __objRest(_b, ["$put"]);
263
+ if ($put)
264
+ return object;
265
+ const curr = sql`"${raw(col)}"${path.length ? sql`#>${path}` : empty}`;
266
+ if (isEmpty(object))
267
+ return curr;
268
+ return sql`(case jsonb_typeof(${curr})
269
+ when 'object' then ${curr}
270
+ else '{}'::jsonb
271
+ end) || jsonb_build_object(${join(Object.entries(object).map(([key, value]) => sql`${key}::text, ${typeof value === "object" && value ? getJsonUpdate(value, col, path.concat(key)) : sql`${getJsonBuildValue(value)}`}`), ", ")})`;
272
+ }
273
+ function stripAttributes(object) {
274
+ if (typeof object !== "object" || !object || Array.isArray(object)) {
275
+ return object;
276
+ }
277
+ const _a = object, { $put } = _a, rest = __objRest(_a, ["$put"]);
278
+ return rest;
279
+ }
280
+ const getIdMeta = ({ idCol }) => getJsonBuildObject({
281
+ $key: sql`"${raw(idCol)}"`,
282
+ $ver: nowTimestamp
283
+ });
284
+ const getArgMeta = (key, prefix, idCol) => getJsonBuildObject({
285
+ $key: key,
286
+ $ref: sql`jsonb_build_array(${join(prefix.map((k) => sql`${k}::text`))}, "${raw(idCol)}")`,
287
+ $ver: nowTimestamp
288
+ });
289
+ function getArgSql(_c, options) {
290
+ var _d = _c, { $first, $last, $after, $before, $since, $until, $all, $cursor: _ } = _d, rest = __objRest(_d, ["$first", "$last", "$after", "$before", "$since", "$until", "$all", "$cursor"]);
291
+ const _a = rest, { $order } = _a, filter = __objRest(_a, ["$order"]);
292
+ const { prefix, idCol } = options;
293
+ const lookup = (prop) => {
294
+ const [prefix2, ...suffix] = encodePath(prop);
295
+ return suffix.length ? sql`"${raw(prefix2)}" #> '{"${suffix.join('","')}"}'` : sql`"${raw(prefix2)}"`;
296
+ };
297
+ const meta = (key2) => getArgMeta(key2, prefix, idCol);
298
+ const hasRangeArg = $before || $after || $since || $until || $first || $last || $all || $order;
299
+ let key;
300
+ const where = [];
301
+ if (!isEmpty(filter)) {
302
+ where.push(getSql(filter, lookup));
303
+ key = sql`${JSON.stringify(filter)}::jsonb`;
304
+ }
305
+ if (!hasRangeArg)
306
+ return { meta: meta(key), where, limit: 1 };
307
+ if (isEmpty(rest)) {
308
+ throw Error("pg_arg.pagination_only_unsupported in " + prefix);
309
+ }
310
+ const orderCols = ($order || [idCol]).map(lookup);
311
+ Object.entries({ $after, $before, $since, $until }).forEach(([name, value]) => {
312
+ if (value)
313
+ where.push(getBoundCond(orderCols, value, name));
314
+ });
315
+ const orderQuery = $order && getJsonBuildObject({ $order: sql`${JSON.stringify($order)}::jsonb` });
316
+ const cursorQuery = getJsonBuildObject({
317
+ $cursor: sql`jsonb_build_array(${join(orderCols)})`
318
+ });
319
+ key = sql`(${join([key, orderQuery, cursorQuery].filter(Boolean), ` || `)})`;
320
+ return {
321
+ meta: meta(key),
322
+ where,
323
+ order: join(orderCols.map((col) => sql`${col} ${$last ? sql`DESC` : sql`ASC`}`), `, `),
324
+ limit: $first || $last
325
+ };
326
+ }
327
+ function getBoundCond(orderCols, bound, kind) {
328
+ if (!Array.isArray(bound)) {
329
+ throw Error("pg_arg.bad_query bound : " + JSON.stringify(bound));
330
+ }
331
+ const lhs = orderCols[0];
332
+ const rhs = bound[0];
333
+ if (orderCols.length > 1 && bound.length > 1) {
334
+ const subCond = getBoundCond(orderCols.slice(1), bound.slice(1), kind);
335
+ switch (kind) {
336
+ case "$after":
337
+ case "$since":
338
+ return sql`${lhs} > ${rhs} OR ${lhs} = ${rhs} AND (${subCond})`;
339
+ case "$before":
340
+ case "$until":
341
+ return sql`${lhs} < ${rhs} OR ${lhs} = ${rhs} AND (${subCond})`;
342
+ }
343
+ } else {
344
+ switch (kind) {
345
+ case "$after":
346
+ return sql`${lhs} > ${rhs}`;
347
+ case "$since":
348
+ return sql`${lhs} >= ${rhs}`;
349
+ case "$before":
350
+ return sql`${lhs} < ${rhs}`;
351
+ case "$until":
352
+ return sql`${lhs} <= ${rhs}`;
353
+ }
354
+ }
355
+ }
356
+ const MAX_LIMIT = 4096;
357
+ function selectByArgs(args, options) {
358
+ const { table } = options;
359
+ const { where, order, limit, meta } = getArgSql(args, options);
360
+ const clampedLimit = Math.min(MAX_LIMIT, limit || MAX_LIMIT);
361
+ return sql`
9
362
  SELECT
10
- ${V(t)} || ${i}
11
- FROM "${I(t)}"
12
- ${n.length?A`WHERE ${D(n," AND ")}`:k}
13
- ${o?A`ORDER BY ${o}`:k}
14
- LIMIT ${a}
15
- `}(r,e));E($,U(n,t,e));const i=R(g(n,s));rr("getByArgs",i),C(o,i)},l=p(r,s);for(const u of l){const r=y(u);if(q(r))if(u.prefix)for(const e of u.children){const t=y(e);n.push(c(a(a({},r),t),e.children))}else n.push(c(r,u.children));else t[u.key]=u.children}return w(t)||n.push((async()=>{(await this.readSql(function(r,e){const{table:t,idCol:n}=e;return A`
363
+ ${getSelectCols(table)} || ${meta}
364
+ FROM "${raw(table)}"
365
+ ${where.length ? sql`WHERE ${join(where, ` AND `)}` : empty}
366
+ ${order ? sql`ORDER BY ${order}` : empty}
367
+ LIMIT ${clampedLimit}
368
+ `;
369
+ }
370
+ function selectByIds(ids, options) {
371
+ const { table, idCol } = options;
372
+ return sql`
16
373
  SELECT
17
- ${V(t)} || ${Y(e)}
18
- FROM "${I(t)}"
19
- WHERE "${I(n)}" IN (${D(r)})
20
- `}(Object.keys(t),e))).forEach((r=>{const n=r[i],a=t[n];E($,U([r],a,e));const c=R(g(r,s));rr("getByIds",c),C(o,c)}))})()),await Promise.all(n),rr("dbRead",r,o),N(S(o,r),r).known||[]}async write(r,e){const t=[],n=r=>t.push(r),{prefix:o}=e,$=p(r,o);for(const a of $){if(j(a))throw Error(a.key===a.end?"pg_write.delete_unsupported":"pg_write.write_range_unsupported");const r=y(a),t=v(a.children);if(q(r)?d(t,r):t.id=r,t.$put&&!0!==t.$put)throw Error("pg_write.partial_put_unsupported");t.$put?n(Z(t,r,e)):n(X(t,r,e))}const i=[];return await Promise.all(t.map((r=>this.writeSql(r).then((r=>{C(i,R(g(r,o)))}))))),rr("dbWrite",r,i),i}}const tr=({table:r,idCol:e,verCol:t,links:n,connection:o})=>$=>{$.on("read",(function(r,e){const t=e,{transactionDb:n=s}=t,o=c(t,["transactionDb"]);return n.read(r,a,o)})),$.on("write",(function(r,e){const t=e,{transactionDb:n=s}=t,o=c(t,["transactionDb"]);return n.write(r,a,o)}));const i=$.path,a={prefix:i,table:r||i[i.length-1]||"default",idCol:e||"id",verCol:t||"updatedAt",links:n||{}},s=new er(o)};export{tr as pg};
374
+ ${getSelectCols(table)} || ${getIdMeta(options)}
375
+ FROM "${raw(table)}"
376
+ WHERE "${raw(idCol)}" IN (${join(ids)})
377
+ `;
378
+ }
379
+ function patch(object, arg, options) {
380
+ const { table, idCol } = options;
381
+ const { where, meta } = isPlainObject(arg) ? getArgSql(arg, options) : { where: [sql`"${raw(idCol)}" = ${arg}`], meta: getIdMeta(options) };
382
+ if (!where || !where.length)
383
+ throw Error("pg_write.no_condition");
384
+ const row = object;
385
+ return sql`
386
+ UPDATE "${raw(table)}" SET ${getUpdates(row, options)}
387
+ WHERE ${isPlainObject(arg) ? sql`"${raw(idCol)}" = (
388
+ SELECT "${raw(idCol)}" FROM "${raw(table)}"
389
+ WHERE ${join(where, ` AND `)} LIMIT 1)` : join(where, ` AND `)}
390
+ RETURNING (${getSelectCols(table)} || ${meta})`;
391
+ }
392
+ function put(object, arg, options) {
393
+ const { idCol, table } = options;
394
+ const row = object;
395
+ let meta, conflictTarget;
396
+ if (isPlainObject(arg)) {
397
+ ({ meta } = getArgSql(arg, options));
398
+ conflictTarget = join(Object.keys(arg).map((col) => sql`"${raw(col)}"`));
399
+ } else {
400
+ meta = getIdMeta(options);
401
+ conflictTarget = sql`"${raw(idCol)}"`;
402
+ }
403
+ const { cols, vals } = getInsert(row, options);
404
+ return sql`
405
+ INSERT INTO "${raw(table)}" (${cols}) VALUES (${vals})
406
+ ON CONFLICT (${conflictTarget}) DO UPDATE SET (${cols}) = (${vals})
407
+ RETURNING (${getSelectCols(table)} || ${meta})`;
408
+ }
409
+ const log = debug("graffy:pg:db");
410
+ class Db {
411
+ constructor(connection) {
412
+ if (typeof connection === "object" && connection && (connection instanceof Pool || connection instanceof Client)) {
413
+ this.client = connection;
414
+ } else {
415
+ this.client = new Pool(connection);
416
+ }
417
+ }
418
+ async query(sql2) {
419
+ sql2.rowMode = "array";
420
+ log("Making SQL query: " + sql2.text, sql2.values);
421
+ try {
422
+ return await this.client.query(sql2);
423
+ } catch (e) {
424
+ const message = [
425
+ e.message,
426
+ e.detail,
427
+ e.hint,
428
+ e.where,
429
+ sql2.text,
430
+ JSON.stringify(sql2.values)
431
+ ].filter(Boolean).join("; ");
432
+ throw Error("pg.sql_error " + message);
433
+ }
434
+ }
435
+ async readSql(sql2) {
436
+ let result = await this.query(sql2);
437
+ result = result.rows.flat();
438
+ log("Read result", result);
439
+ return result;
440
+ }
441
+ async writeSql(sql2) {
442
+ let res = await this.query(sql2);
443
+ log("Rows written", res.rowCount);
444
+ if (!res.rowCount) {
445
+ throw Error("pg.nothing_written " + sql2.text + " with " + sql2.values);
446
+ }
447
+ return res.rows[0][0];
448
+ }
449
+ async read(rootQuery, tableOptions) {
450
+ const idQueries = {};
451
+ const promises = [];
452
+ const results = [];
453
+ const { prefix } = tableOptions;
454
+ const getByArgs = async (args) => {
455
+ const result = await this.readSql(selectByArgs(args, tableOptions));
456
+ const wrappedGraph = encodeGraph(wrapObject(result, prefix));
457
+ log("getByArgs", wrappedGraph);
458
+ merge(results, wrappedGraph);
459
+ };
460
+ const getByIds = async () => {
461
+ const result = await this.readSql(selectByIds(Object.keys(idQueries), tableOptions));
462
+ result.forEach((object) => {
463
+ const wrappedGraph = encodeGraph(wrapObject(object, prefix));
464
+ log("getByIds", wrappedGraph);
465
+ merge(results, wrappedGraph);
466
+ });
467
+ };
468
+ const query = unwrap(rootQuery, prefix);
469
+ for (const node of query) {
470
+ const args = decodeArgs(node);
471
+ if (isPlainObject(args)) {
472
+ if (node.prefix) {
473
+ for (const childNode of node.children) {
474
+ const childArgs = decodeArgs(childNode);
475
+ promises.push(getByArgs(__spreadValues(__spreadValues({}, args), childArgs)));
476
+ }
477
+ } else {
478
+ promises.push(getByArgs(args));
479
+ }
480
+ } else {
481
+ idQueries[node.key] = node.children;
482
+ }
483
+ }
484
+ if (!isEmpty(idQueries))
485
+ promises.push(getByIds());
486
+ await Promise.all(promises);
487
+ log("dbRead", rootQuery, results);
488
+ return slice(finalize(results, rootQuery), rootQuery).known || [];
489
+ }
490
+ async write(rootChange, tableOptions) {
491
+ const sqls = [];
492
+ const addToQuery = (sql2) => sqls.push(sql2);
493
+ const { prefix } = tableOptions;
494
+ const change = unwrap(rootChange, prefix);
495
+ for (const node of change) {
496
+ if (isRange(node)) {
497
+ throw Error(node.key === node.end ? "pg_write.delete_unsupported" : "pg_write.write_range_unsupported");
498
+ }
499
+ const arg = decodeArgs(node);
500
+ const object = decodeGraph(node.children);
501
+ if (isPlainObject(arg)) {
502
+ mergeObject(object, arg);
503
+ } else {
504
+ object.id = arg;
505
+ }
506
+ if (object.$put && object.$put !== true) {
507
+ throw Error("pg_write.partial_put_unsupported");
508
+ }
509
+ object.$put ? addToQuery(put(object, arg, tableOptions)) : addToQuery(patch(object, arg, tableOptions));
510
+ }
511
+ const result = [];
512
+ await Promise.all(sqls.map((sql2) => this.writeSql(sql2).then((object) => {
513
+ merge(result, encodeGraph(wrapObject(object, prefix)));
514
+ })));
515
+ log("dbWrite", rootChange, result);
516
+ return result;
517
+ }
518
+ }
519
+ const pg = ({ table, idCol, verCol, links, connection }) => (store) => {
520
+ store.on("read", read);
521
+ store.on("write", write);
522
+ const prefix = store.path;
523
+ const tableOpts = {
524
+ prefix,
525
+ table: table || prefix[prefix.length - 1] || "default",
526
+ idCol: idCol || "id",
527
+ verCol: verCol || "updatedAt",
528
+ links: links || {}
529
+ };
530
+ const defaultDb = new Db(connection);
531
+ function read(query, options) {
532
+ const _a = options, { transactionDb = defaultDb } = _a, readOpts = __objRest(_a, ["transactionDb"]);
533
+ return transactionDb.read(query, tableOpts, readOpts);
534
+ }
535
+ function write(change, options) {
536
+ const _a = options, { transactionDb = defaultDb } = _a, writeOpts = __objRest(_a, ["transactionDb"]);
537
+ return transactionDb.write(change, tableOpts, writeOpts);
538
+ }
539
+ };
540
+ export { pg };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graffy/pg",
3
3
  "description": "The standard Postgres module for Graffy. Each instance this module mounts a Postgres table as a Graffy subtree.",
4
4
  "author": "aravind (https://github.com/aravindet)",
5
- "version": "0.15.8-alpha.5",
5
+ "version": "0.15.8",
6
6
  "main": "./index.cjs",
7
7
  "exports": {
8
8
  "import": "./index.mjs",
@@ -17,8 +17,7 @@
17
17
  "license": "Apache-2.0",
18
18
  "dependencies": {
19
19
  "pg": "^8.7.1",
20
- "@graffy/common": "0.15.8-alpha.5",
21
- "@graffy/testing": "0.15.8-alpha.5",
20
+ "@graffy/common": "0.15.8",
22
21
  "debug": "^4.3.2",
23
22
  "sql-template-tag": "^4.0.0"
24
23
  }
@@ -1 +1,3 @@
1
- export default function getSql(filter: any, getLookupSql: any): any;
1
+ export default function getSql(filter: any, getLookupSql: any, getColumnType?: typeof defaultColumnType): any;
2
+ declare function defaultColumnType(): string;
3
+ export {};
@@ -1,8 +1,9 @@
1
- export const nowTimestamp: import("sql-template-tag").Sql;
2
- export function getJsonBuildObject(variadic: any): import("sql-template-tag").Sql;
3
- export function getSelectCols(table: any): import("sql-template-tag").Sql;
1
+ export const nowTimestamp: Sql;
2
+ export function getJsonBuildObject(variadic: any): Sql;
3
+ export function getSelectCols(table: any): Sql;
4
4
  export function getInsert(row: any, options: any): {
5
- cols: import("sql-template-tag").Sql;
6
- vals: import("sql-template-tag").Sql;
5
+ cols: Sql;
6
+ vals: Sql;
7
7
  };
8
- export function getUpdates(row: any, options: any): import("sql-template-tag").Sql;
8
+ export function getUpdates(row: any, options: any): Sql;
9
+ import { Sql } from "sql-template-tag";
@@ -1,3 +0,0 @@
1
- export function linkResult(objects: any, query: any, { links: linkSpecs }: {
2
- links: any;
3
- }): any[];