@graffy/pg 0.15.11 → 0.15.13-alpha.1

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/Readme.md CHANGED
@@ -10,9 +10,137 @@ import pg from '@graffy/pg';
10
10
  graffyStore.use(pg(options));
11
11
  ```
12
12
 
13
- Connection parameters should be set in environment variables. Uses the [pg](https://github.com/brianc/node-postgres) library.
13
+ Uses the [pg](https://github.com/brianc/node-postgres) library.
14
14
 
15
- ## Options
15
+ ### Query filters
16
16
 
17
- In this document, _property names_ and _paths_ refer to the structures in the Graffy graph objects, _columns_ refer to Postgres table columns and _args_ refer to structures in the filtering and pagination arguments of Graffy query objects.
17
+ Query filters are passed in `$key` and are JSON-based, somewhat like MongoDB.
18
18
 
19
+ 1. Filters expressions follow a **property**, **operator**, **value** order. Values are scalar values (strings or numbers).
20
+ 2. Property names are always object keys. They may be strings with dots `.`.
21
+ 3. Operators are placed in objects as well and have a leading `$`.
22
+ 4. Values are JSON values.
23
+ 5. Multiple properties in an object are combined with `AND`. Items in an arrays are combined with `OR`.
24
+ 6. The supported operators are:
25
+ - `$eq`: optional in most cases.
26
+ - `$lt`, `$lte`, `$gt`, `$gte`: Ranges
27
+ - `$re`, `$ire`: Regex match (case sensitive and insensitive versions)
28
+ - `$text`: Full text search, always case insensitive
29
+ - `$not`: Modifies other filters or inverts a condition
30
+ - `$and`, `$or`: Combines conditions; optional in most cases
31
+ - `$all`, `$has`, `$any`: Apply conditions to the elements of a collection (list or map)
32
+
33
+ #### Basic
34
+
35
+ 1. `{ foo: 5 }` and `{ foo: { $eq: 5 } }` compile to SQL `foo = 5`.
36
+ 2. `{ foo: { $gt: 5, $lt: 6 } }` becomes `foo > 5 AND foo < 6`.
37
+ 3. `{ foo: { $ire: '^wor.*' } }` becomes `foo ~* "^wor.*"`.
38
+ 4. `{ foo: { $text: 'potatoes' } }` becomes `foo @@ websearch_to_tsquery('potatoes')`.
39
+ For this to work, `foo` must be a TSVector column.
40
+
41
+ #### Or
42
+
43
+ 1. `{ foo: [5, 6] }` means *foo equals 5 or foo equals 6*, and the compiler is smart enough to simplify this to `foo IN (5, 6)`. There is no separate $in.
44
+ 2. `{ foo: [ 5, { $gt: 6 } ] }` becomes `foo = 5 OR foo > 6`
45
+ 3. `[ { foo: 6 }, { bar: 7 } ]` becomes `foo = 6 OR bar = 7`
46
+
47
+ #### Not
48
+
49
+ 1. `{ foo: { $not: 6 } }` becomes `foo <> 6` (the SQL not equals operator)
50
+ 2. `{ foo: { $not: [5, 6] }` becomes `foo NOT IN (5, 6)`
51
+ 3. `{ foo: { $not: [ 5, { $gt: 6 } ] } }` becomes `NOT (foo = 5 OR foo > 6)`
52
+
53
+ #### Logic
54
+
55
+ 1. By default, objects mean `AND` and arrays mean `OR`:
56
+ `[ { foo: 5, bar: 6 }, { baz: 7, qux: 4 } ]` becomes
57
+ `(foo = 5 AND bar = 6) OR (baz = 7 AND qux = 4)`
58
+ 2. Use `$and` and `$or` operators explicitly to use any structure in either context:
59
+
60
+ `{ $and: [ { $or: { foo: 5, bar: 6 } }, { $or: { baz: 7, qux: 4 } } ] }` becomes
61
+ `(foo = 5 OR bar = 6) AND (baz = 7 OR qux = 4)`.
62
+
63
+ #### Contains and Contained by
64
+
65
+ 1. `{ tags: { $cts: ['foo', 'bar'] } }` becomes `tags @> '{"foo","bar"}'`.
66
+ Tags must contain both *foo* and *bar*. Note that the array of conditions here does not have `OR` semantics.
67
+ 2. `{ tags: { $ctd: ['foo', 'bar', 'baz'] } }` becomes `tags <@ '{"foo","bar","baz"}'`.
68
+ Every tag must be one of *foo*, *bar* or *baz*.
69
+
70
+ #### Notes
71
+
72
+ 1. We drop several MongoDB operators while retaining the capability:
73
+ - `$ne`: Use $not instead.
74
+ - `$in`: Use an array of values; it is a combination of implicit $or and $eq.
75
+ - `$nin`: Use $not and an array of values.
76
+ - `$exists`: Use `null` or `{ $not: null }`. Postgres does not distinguish between `undefined` and `null`, and neither does Graffy (in this context at least; it's complicated.)
77
+ 2. Graffy `$has` is equivalent to MongoDB `$all` (each of the provided conditions is met by at least one element in the array). Graffy `$all` (every element of the array meets a condition) has no direct MongoDB equivalent, but can be expressed as `{ $not: { $elemMatch: { $not: (cond) } } }`
78
+ 3. Graffy has a separate operator for case-insensitive regex, and configures the text search locale in the database object rather than the query. This makes MongoDB's regex `$options`, full text `$language` etc. unnecessary.
79
+ 4. Graffy does not have equivalents for MongoDB operators `$type`, `$expr`, `$jsonSchema`, `$where`, `$mod`, `$size` and the geospatial operators.
80
+
81
+ ### Order by
82
+
83
+ The root of the Graffy filter object must be an object. (Use `$or` if required.) The property `$order` specifies the order. Its value must be an array of order specifiers, each of which may be a string property name or an object with property names, sort direction, collation and text search relevance. (TBD)
84
+
85
+ ### Full Text Search
86
+
87
+ A full-text search query typically has three requirements:
88
+
89
+ - Filter: `{ tsv: { $text: 'query' } }`. Return only results that match.
90
+ - Order: `{ $order: [{ $text: ['tsv', 'query'] }], ... }`. Sort results by relevance.
91
+ - Projection: `{ tsv: { query: true } }`. Return snippets of the document surrounding matches.
92
+
93
+ In all three, `tsv` is a computed column of type TSVector.
94
+
95
+ ### Aggregations
96
+
97
+ In Graffy PG, aggregations are specified using the `$group` argument in $key, and special properties like `$count`, `$sum` etc. in the projection. `$group` may be `true` or an array.
98
+
99
+ Consider a table of books with columns `authorId` and `copiesSold`. We want the to compute aggregates on the `copiesSold` column.
100
+
101
+ #### Without Group By
102
+
103
+ Let's say we want the total copies sold of all the books in our database. We use `$group: true`, like:
104
+
105
+ ```js
106
+ {
107
+ books: {
108
+ $key: { $group: true },
109
+ $sum: { copiesSold: true }
110
+ }
111
+ ```
112
+
113
+ Note how the field to sum is specified; this way, multiple fields may be specified.
114
+
115
+ As always, the result will mirror the query:
116
+
117
+ ```js
118
+ books: [{
119
+ $key: { $group: true },
120
+ $sum: { copiesSold: 12345 }
121
+ }]
122
+ ```
123
+
124
+ #### With Group By
125
+
126
+ Now let's say we want the separate totals for each author. As we might have a very large number of authors, we might need to paginate over the results.
127
+
128
+ The grouping properties (e.g. `authorId`) may also be included in the projection, and these values may even be used to construct links.
129
+
130
+ ```js
131
+ {
132
+ books: {
133
+ $key: { $group: ['authorId'], $first: 30 },
134
+ authorId: true,
135
+ author: { namme: true }, // Link to ['users', authorId]
136
+ $sum: { copiesSold: true }
137
+ }
138
+ ```
139
+
140
+ #### Aggregate functions
141
+
142
+ Graffy supports the following aggregate functions.
143
+
144
+ - `$count` of rows; this is just specified as `$count: true`, without any fields under it. (All other aggregate functions require fields to be specified.)
145
+ - `$sum`, `$avg`, `$max`, `$min`
146
+ - `$card` (cardinality), or the number of unique values in a column
package/index.cjs CHANGED
@@ -185,7 +185,7 @@ function getCompatibleTypes(value) {
185
185
  return "text";
186
186
  }
187
187
  function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
188
- function lookup(string, type) {
188
+ function lookup2(string, type) {
189
189
  if (string.substr(0, 3) === "el$")
190
190
  return sql__default["default"]`"${sql.raw(string)}"`;
191
191
  return getLookupSql(string, type);
@@ -194,20 +194,20 @@ function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
194
194
  const lType = left.substr(0, 3) === "el$" ? "any" : getColumnType(left);
195
195
  const rType = getCompatibleTypes(right);
196
196
  if (lType === "any" || rType === "any" || rType === lType) {
197
- return sql__default["default"]`${lookup(left)} ${sql.raw(op)} ${right}`;
197
+ return sql__default["default"]`${lookup2(left)} ${sql.raw(op)} ${right}`;
198
198
  } else {
199
- return sql__default["default"]`(${lookup(left, rType)})::${sql.raw(rType)} ${sql.raw(op)} ${right}`;
199
+ return sql__default["default"]`(${lookup2(left, rType)})::${sql.raw(rType)} ${sql.raw(op)} ${right}`;
200
200
  }
201
201
  }
202
202
  function getNodeSql(ast) {
203
203
  switch (ast[0]) {
204
204
  case "$eq":
205
205
  if (ast[2] === null)
206
- return sql__default["default"]`${lookup(ast[1])} IS NULL`;
206
+ return sql__default["default"]`${lookup2(ast[1])} IS NULL`;
207
207
  return binop("=", ast[1], ast[2]);
208
208
  case "$neq":
209
209
  if (ast[2] === null)
210
- return sql__default["default"]`${lookup(ast[1])} IS NOT NULL`;
210
+ return sql__default["default"]`${lookup2(ast[1])} IS NOT NULL`;
211
211
  return binop("<>", ast[1], ast[2]);
212
212
  case "$lt":
213
213
  return binop("<", ast[1], ast[2]);
@@ -222,20 +222,20 @@ function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
222
222
  case "$ire":
223
223
  return binop("~*", ast[1], ast[2]);
224
224
  case "$in":
225
- return sql__default["default"]`${lookup(ast[1])} IN (${sql.join(ast[2])})`;
225
+ return sql__default["default"]`${lookup2(ast[1])} IN (${sql.join(ast[2])})`;
226
226
  case "$nin":
227
- return sql__default["default"]`${lookup(ast[1])} NOT IN (${sql.join(ast[2])})`;
227
+ return sql__default["default"]`${lookup2(ast[1])} NOT IN (${sql.join(ast[2])})`;
228
228
  case "$cts":
229
- return sql__default["default"]`${lookup(ast[1])} @> ${ast[2]}`;
229
+ return sql__default["default"]`${lookup2(ast[1])} @> ${ast[2]}`;
230
230
  case "$ctd":
231
- return sql__default["default"]`${lookup(ast[1])} <@ ${ast[2]}`;
231
+ return sql__default["default"]`${lookup2(ast[1])} <@ ${ast[2]}`;
232
232
  case "$ovl":
233
233
  switch (getColumnType(ast[1])) {
234
234
  case "jsonb":
235
235
  case "any":
236
- return sql__default["default"]`${lookup(ast[1])} ?| ${Array.isArray(ast[2]) ? ast[2] : Object.keys(ast[2])}`;
236
+ return sql__default["default"]`${lookup2(ast[1])} ?| ${Array.isArray(ast[2]) ? ast[2] : Object.keys(ast[2])}`;
237
237
  case "array":
238
- return sql__default["default"]`${lookup(ast[1])} && ${ast[2]}`;
238
+ return sql__default["default"]`${lookup2(ast[1])} && ${ast[2]}`;
239
239
  default:
240
240
  throw Error("pg.getSql_ovl_unknown_column_type");
241
241
  }
@@ -246,11 +246,11 @@ function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
246
246
  case "$not":
247
247
  return sql__default["default"]`NOT (${getNodeSql(ast[1])})`;
248
248
  case "$any":
249
- return sql__default["default"]`(SELECT bool_or(${getNodeSql(ast[3])}) FROM UNNEST(${lookup(ast[1])}) ${lookup(ast[2])})`;
249
+ return sql__default["default"]`(SELECT bool_or(${getNodeSql(ast[3])}) FROM UNNEST(${lookup2(ast[1])}) ${lookup2(ast[2])})`;
250
250
  case "$all":
251
- return sql__default["default"]`(SELECT bool_and(${getNodeSql(ast[3])}) FROM UNNEST(${lookup(ast[1])}) ${lookup(ast[2])})`;
251
+ return sql__default["default"]`(SELECT bool_and(${getNodeSql(ast[3])}) FROM UNNEST(${lookup2(ast[1])}) ${lookup2(ast[2])})`;
252
252
  case "$has":
253
- return sql__default["default"]`(SELECT bool_or(${sql.join(ast[3].map((node) => getNodeSql(node)), `) AND bool_or(`)}) FROM UNNEST(${lookup(ast[1])}) ${lookup(ast[2])})`;
253
+ return sql__default["default"]`(SELECT bool_or(${sql.join(ast[3].map((node) => getNodeSql(node)), `) AND bool_or(`)}) FROM UNNEST(${lookup2(ast[1])}) ${lookup2(ast[2])})`;
254
254
  default:
255
255
  throw Error("pg.getSql_unknown_operator: " + ast[0]);
256
256
  }
@@ -271,8 +271,40 @@ const getJsonBuildValue = (value) => {
271
271
  return sql__default["default"]`${value}::text`;
272
272
  return sql__default["default"]`${JSON.stringify(stripAttributes(value))}::jsonb`;
273
273
  };
274
- const getSelectCols = (table) => {
275
- return sql__default["default"]`to_jsonb("${sql.raw(table)}")`;
274
+ const lookup = (prop, type) => {
275
+ const [prefix, ...suffix] = common.encodePath(prop);
276
+ const op = type === "text" ? sql__default["default"]`#>>` : sql__default["default"]`#>`;
277
+ return suffix.length ? sql__default["default"]`"${sql.raw(prefix)}" ${op} ${suffix}` : sql__default["default"]`"${sql.raw(prefix)}"`;
278
+ };
279
+ const getType = (prop) => {
280
+ const [_prefix, ...suffix] = common.encodePath(prop);
281
+ return suffix.length ? "jsonb" : "any";
282
+ };
283
+ const aggSql = {
284
+ $sum: (prop) => sql__default["default"]`sum((${lookup(prop)})::numeric)`,
285
+ $card: (prop) => sql__default["default"]`count(distinct(${lookup(prop)}))`,
286
+ $avg: (prop) => sql__default["default"]`sum((${lookup(prop)})::numeric)`,
287
+ $max: (prop) => sql__default["default"]`sum((${lookup(prop)})::numeric)`,
288
+ $min: (prop) => sql__default["default"]`sum((${lookup(prop)})::numeric)`
289
+ };
290
+ const getSelectCols = (table, projection = null) => {
291
+ if (!projection)
292
+ return sql__default["default"]`to_jsonb("${sql.raw(table)}")`;
293
+ const sqls = [];
294
+ for (const key in projection) {
295
+ if (key === "$count") {
296
+ sqls.push(sql__default["default"]`'$count', count(*)`);
297
+ } else if (aggSql[key]) {
298
+ const subSqls = [];
299
+ for (const prop in projection[key]) {
300
+ subSqls.push(sql__default["default"]`${prop}::text, ${aggSql[key](prop)}`);
301
+ }
302
+ sqls.push(sql__default["default"]`${key}::text, jsonb_build_object(${sql.join(subSqls, ", ")})`);
303
+ } else {
304
+ sqls.push(sql__default["default"]`${key}::text, "${sql.raw(key)}"`);
305
+ }
306
+ }
307
+ return sql__default["default"]`jsonb_build_object(${sql.join(sqls, ", ")})`;
276
308
  };
277
309
  const getInsert = (row, options) => {
278
310
  const cols = [];
@@ -322,20 +354,20 @@ const getArgMeta = (key, prefix, idCol) => getJsonBuildObject({
322
354
  $ref: sql__default["default"]`jsonb_build_array(${sql.join(prefix.map((k) => sql__default["default"]`${k}::text`))}, "${sql.raw(idCol)}")`,
323
355
  $ver: nowTimestamp
324
356
  });
357
+ const getAggMeta = (key, $group) => getJsonBuildObject({
358
+ $key: sql.join([key, getJsonBuildObject({ $group })].filter(Boolean), " || "),
359
+ $ver: nowTimestamp
360
+ });
325
361
  function getArgSql(_c, options) {
326
362
  var _d = _c, { $first, $last, $after, $before, $since, $until, $all, $cursor: _ } = _d, rest = __objRest(_d, ["$first", "$last", "$after", "$before", "$since", "$until", "$all", "$cursor"]);
327
- const _a = rest, { $order } = _a, filter = __objRest(_a, ["$order"]);
363
+ const _a = rest, { $order, $group } = _a, filter = __objRest(_a, ["$order", "$group"]);
328
364
  const { prefix, idCol } = options;
329
- const lookup = (prop, type) => {
330
- const [prefix2, ...suffix] = common.encodePath(prop);
331
- const op = type === "text" ? sql__default["default"]`#>>` : sql__default["default"]`#>`;
332
- return suffix.length ? sql__default["default"]`"${sql.raw(prefix2)}" ${op} ${suffix}` : sql__default["default"]`"${sql.raw(prefix2)}"`;
333
- };
334
- const getType = (prop) => {
335
- const [_prefix, ...suffix] = common.encodePath(prop);
336
- return suffix.length ? "jsonb" : "any";
337
- };
338
- const meta = (key2) => getArgMeta(key2, prefix, idCol);
365
+ if ($order && $group) {
366
+ throw Error("pg_arg.order_and_group_unsupported in " + prefix);
367
+ }
368
+ const meta = (key2) => $group ? getAggMeta(key2, $group) : getArgMeta(key2, prefix, idCol);
369
+ const groupCols = Array.isArray($group) && $group.length ? $group.map((col) => lookup(col)) : void 0;
370
+ const group = groupCols ? sql.join(groupCols, ", ") : void 0;
339
371
  const hasRangeArg = $before || $after || $since || $until || $first || $last || $all || $order;
340
372
  let key;
341
373
  const where = [];
@@ -344,7 +376,7 @@ function getArgSql(_c, options) {
344
376
  key = sql__default["default"]`${JSON.stringify(filter)}::jsonb`;
345
377
  }
346
378
  if (!hasRangeArg)
347
- return { meta: meta(key), where, limit: 1 };
379
+ return { meta: meta(key), where, group, limit: 1 };
348
380
  if (common.isEmpty(rest)) {
349
381
  throw Error("pg_arg.pagination_only_unsupported in " + prefix);
350
382
  }
@@ -355,13 +387,14 @@ function getArgSql(_c, options) {
355
387
  });
356
388
  const orderQuery = $order && getJsonBuildObject({ $order: sql__default["default"]`${JSON.stringify($order)}::jsonb` });
357
389
  const cursorQuery = getJsonBuildObject({
358
- $cursor: sql__default["default"]`jsonb_build_array(${sql.join(orderCols)})`
390
+ $cursor: sql__default["default"]`jsonb_build_array(${sql.join(groupCols || orderCols)})`
359
391
  });
360
392
  key = sql__default["default"]`(${sql.join([key, orderQuery, cursorQuery].filter(Boolean), ` || `)})`;
361
393
  return {
362
394
  meta: meta(key),
363
395
  where,
364
- order: sql.join(orderCols.map((col) => sql__default["default"]`${col} ${$last ? sql__default["default"]`DESC` : sql__default["default"]`ASC`}`), `, `),
396
+ order: $order && sql.join(orderCols.map((col) => sql__default["default"]`${col} ${$last ? sql__default["default"]`DESC` : sql__default["default"]`ASC`}`), `, `),
397
+ group,
365
398
  limit: $first || $last
366
399
  };
367
400
  }
@@ -395,39 +428,57 @@ function getBoundCond(orderCols, bound, kind) {
395
428
  }
396
429
  }
397
430
  const MAX_LIMIT = 4096;
398
- function selectByArgs(args, options) {
431
+ function selectByArgs(args, projection, options) {
399
432
  const { table } = options;
400
- const { where, order, limit, meta } = getArgSql(args, options);
433
+ const { where, order, group, limit, meta } = getArgSql(args, options);
401
434
  const clampedLimit = Math.min(MAX_LIMIT, limit || MAX_LIMIT);
402
435
  return sql__default["default"]`
403
436
  SELECT
404
- ${getSelectCols(table)} || ${meta}
437
+ ${getSelectCols(table, projection)} || ${meta}
405
438
  FROM "${sql.raw(table)}"
406
439
  ${where.length ? sql__default["default"]`WHERE ${sql.join(where, ` AND `)}` : sql.empty}
440
+ ${group ? sql__default["default"]`GROUP BY ${group}` : sql.empty}
407
441
  ${order ? sql__default["default"]`ORDER BY ${order}` : sql.empty}
408
442
  LIMIT ${clampedLimit}
409
443
  `;
410
444
  }
411
- function selectByIds(ids, options) {
445
+ function selectByIds(ids, projection, options) {
412
446
  const { table, idCol } = options;
413
447
  return sql__default["default"]`
414
448
  SELECT
415
- ${getSelectCols(table)} || ${getIdMeta(options)}
449
+ ${getSelectCols(table, projection)} || ${getIdMeta(options)}
416
450
  FROM "${sql.raw(table)}"
417
451
  WHERE "${sql.raw(idCol)}" IN (${sql.join(ids)})
418
452
  `;
419
453
  }
420
- function patch(object, arg, options) {
454
+ function getSingleSql(arg, options) {
421
455
  const { table, idCol } = options;
422
- const { where, meta } = common.isPlainObject(arg) ? getArgSql(arg, options) : { where: [sql__default["default"]`"${sql.raw(idCol)}" = ${arg}`], meta: getIdMeta(options) };
456
+ if (!common.isPlainObject(arg)) {
457
+ return {
458
+ where: sql__default["default"]`"${sql.raw(idCol)}" = ${arg}`,
459
+ meta: getIdMeta(options)
460
+ };
461
+ }
462
+ const { where, meta } = getArgSql(arg, options);
423
463
  if (!where || !where.length)
424
464
  throw Error("pg_write.no_condition");
465
+ return {
466
+ where: sql__default["default"]`"${sql.raw(idCol)}" = (
467
+ SELECT "${sql.raw(idCol)}"
468
+ FROM "${sql.raw(table)}"
469
+ WHERE ${sql.join(where, ` AND `)}
470
+ LIMIT 1
471
+ )`,
472
+ meta
473
+ };
474
+ }
475
+ function patch(object, arg, options) {
476
+ const { table } = options;
477
+ const { where, meta } = getSingleSql(arg, options);
425
478
  const row = object;
426
479
  return sql__default["default"]`
427
480
  UPDATE "${sql.raw(table)}" SET ${getUpdates(row, options)}
428
- WHERE ${common.isPlainObject(arg) ? sql__default["default"]`"${sql.raw(idCol)}" = (
429
- SELECT "${sql.raw(idCol)}" FROM "${sql.raw(table)}"
430
- WHERE ${sql.join(where, ` AND `)} LIMIT 1)` : sql.join(where, ` AND `)}
481
+ WHERE ${where}
431
482
  RETURNING (${getSelectCols(table)} || ${meta})`;
432
483
  }
433
484
  function put(object, arg, options) {
@@ -447,6 +498,14 @@ function put(object, arg, options) {
447
498
  ON CONFLICT (${conflictTarget}) DO UPDATE SET (${cols}) = (${vals})
448
499
  RETURNING (${getSelectCols(table)} || ${meta})`;
449
500
  }
501
+ function del(arg, options) {
502
+ const { table } = options;
503
+ const { where } = getSingleSql(arg, options);
504
+ return sql__default["default"]`
505
+ DELETE FROM "${sql.raw(table)}"
506
+ WHERE ${where}
507
+ RETURNING (${getJsonBuildObject({ $key: arg })})`;
508
+ }
450
509
  const log = debug__default["default"]("graffy:pg:db");
451
510
  class Db {
452
511
  constructor(connection) {
@@ -492,14 +551,14 @@ class Db {
492
551
  const promises = [];
493
552
  const results = [];
494
553
  const { prefix } = tableOptions;
495
- const getByArgs = async (args) => {
496
- const result = await this.readSql(selectByArgs(args, tableOptions));
554
+ const getByArgs = async (args, projection) => {
555
+ const result = await this.readSql(selectByArgs(args, projection, tableOptions));
497
556
  const wrappedGraph = common.encodeGraph(common.wrapObject(result, prefix));
498
557
  log("getByArgs", wrappedGraph);
499
558
  common.merge(results, wrappedGraph);
500
559
  };
501
560
  const getByIds = async () => {
502
- const result = await this.readSql(selectByIds(Object.keys(idQueries), tableOptions));
561
+ const result = await this.readSql(selectByIds(Object.keys(idQueries), null, tableOptions));
503
562
  result.forEach((object) => {
504
563
  const wrappedGraph = common.encodeGraph(common.wrapObject(object, prefix));
505
564
  log("getByIds", wrappedGraph);
@@ -513,10 +572,12 @@ class Db {
513
572
  if (node.prefix) {
514
573
  for (const childNode of node.children) {
515
574
  const childArgs = common.decodeArgs(childNode);
516
- promises.push(getByArgs(__spreadValues(__spreadValues({}, args), childArgs)));
575
+ const projection = childNode.children ? common.decodeQuery(childNode.children) : true;
576
+ promises.push(getByArgs(__spreadValues(__spreadValues({}, args), childArgs), projection));
517
577
  }
518
578
  } else {
519
- promises.push(getByArgs(args));
579
+ const projection = node.children ? common.decodeQuery(node.children) : true;
580
+ promises.push(getByArgs(args, projection));
520
581
  }
521
582
  } else {
522
583
  idQueries[node.key] = node.children;
@@ -529,15 +590,15 @@ class Db {
529
590
  return common.slice(common.finalize(results, common.wrap(query, prefix)), rootQuery).known || [];
530
591
  }
531
592
  async write(rootChange, tableOptions) {
532
- const sqls = [];
533
- const addToQuery = (sql2) => sqls.push(sql2);
534
593
  const { prefix } = tableOptions;
535
594
  const change = common.unwrap(rootChange, prefix);
536
- for (const node of change) {
595
+ const sqls = change.map((node) => {
596
+ const arg = common.decodeArgs(node);
537
597
  if (common.isRange(node)) {
538
- throw Error(node.key === node.end ? "pg_write.delete_unsupported" : "pg_write.write_range_unsupported");
598
+ if (node.key === node.end)
599
+ return del(arg, tableOptions);
600
+ throw Error("pg_write.write_range_unsupported");
539
601
  }
540
- const arg = common.decodeArgs(node);
541
602
  const object = common.decodeGraph(node.children);
542
603
  if (common.isPlainObject(arg)) {
543
604
  common.mergeObject(object, arg);
@@ -547,8 +608,8 @@ class Db {
547
608
  if (object.$put && object.$put !== true) {
548
609
  throw Error("pg_write.partial_put_unsupported");
549
610
  }
550
- object.$put ? addToQuery(put(object, arg, tableOptions)) : addToQuery(patch(object, arg, tableOptions));
551
- }
611
+ return object.$put ? put(object, arg, tableOptions) : patch(object, arg, tableOptions);
612
+ });
552
613
  const result = [];
553
614
  await Promise.all(sqls.map((sql2) => this.writeSql(sql2).then((object) => {
554
615
  common.merge(result, common.encodeGraph(common.wrapObject(object, prefix)));
@@ -557,7 +618,7 @@ class Db {
557
618
  return result;
558
619
  }
559
620
  }
560
- const pg = ({ table, idCol, verCol, links, connection }) => (store) => {
621
+ const pg = ({ table, idCol, verCol, connection }) => (store) => {
561
622
  store.on("read", read);
562
623
  store.on("write", write);
563
624
  const prefix = store.path;
@@ -565,13 +626,13 @@ const pg = ({ table, idCol, verCol, links, connection }) => (store) => {
565
626
  prefix,
566
627
  table: table || prefix[prefix.length - 1] || "default",
567
628
  idCol: idCol || "id",
568
- verCol: verCol || "updatedAt",
569
- links: links || {}
629
+ verCol: verCol || "updatedAt"
570
630
  };
571
631
  const defaultDb = new Db(connection);
572
632
  function read(query, options, next) {
573
- const _a = options, { transactionDb = defaultDb } = _a, readOpts = __objRest(_a, ["transactionDb"]);
574
- const readPromise = transactionDb.read(query, tableOpts, readOpts);
633
+ const { pgClient } = options;
634
+ const db = pgClient ? new Db(pgClient) : defaultDb;
635
+ const readPromise = db.read(query, tableOpts);
575
636
  const remainingQuery = common.remove(query, prefix);
576
637
  const nextPromise = next(remainingQuery);
577
638
  return Promise.all([readPromise, nextPromise]).then(([readRes, nextRes]) => {
@@ -579,8 +640,9 @@ const pg = ({ table, idCol, verCol, links, connection }) => (store) => {
579
640
  });
580
641
  }
581
642
  function write(change, options, next) {
582
- const _a = options, { transactionDb = defaultDb } = _a, writeOpts = __objRest(_a, ["transactionDb"]);
583
- const writePromise = transactionDb.write(change, tableOpts, writeOpts);
643
+ const { pgClient } = options;
644
+ const db = pgClient ? new Db(pgClient) : defaultDb;
645
+ const writePromise = db.write(change, tableOpts);
584
646
  const remainingChange = common.remove(change, prefix);
585
647
  const nextPromise = next(remainingChange);
586
648
  return Promise.all([writePromise, nextPromise]).then(([writeRes, nextRes]) => {
package/index.mjs CHANGED
@@ -26,7 +26,7 @@ var __objRest = (source, exclude) => {
26
26
  }
27
27
  return target;
28
28
  };
29
- import { isEmpty, encodePath, isPlainObject, unwrap, decodeArgs, slice, finalize, wrap, isRange, decodeGraph, mergeObject, merge, encodeGraph, wrapObject, remove } from "@graffy/common";
29
+ import { isEmpty, encodePath, isPlainObject, unwrap, decodeArgs, decodeQuery, slice, finalize, wrap, isRange, decodeGraph, mergeObject, merge, encodeGraph, wrapObject, remove } from "@graffy/common";
30
30
  import { Pool, Client } from "pg";
31
31
  import sql, { join, raw, Sql, empty } from "sql-template-tag";
32
32
  import debug from "debug";
@@ -177,7 +177,7 @@ function getCompatibleTypes(value) {
177
177
  return "text";
178
178
  }
179
179
  function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
180
- function lookup(string, type) {
180
+ function lookup2(string, type) {
181
181
  if (string.substr(0, 3) === "el$")
182
182
  return sql`"${raw(string)}"`;
183
183
  return getLookupSql(string, type);
@@ -186,20 +186,20 @@ function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
186
186
  const lType = left.substr(0, 3) === "el$" ? "any" : getColumnType(left);
187
187
  const rType = getCompatibleTypes(right);
188
188
  if (lType === "any" || rType === "any" || rType === lType) {
189
- return sql`${lookup(left)} ${raw(op)} ${right}`;
189
+ return sql`${lookup2(left)} ${raw(op)} ${right}`;
190
190
  } else {
191
- return sql`(${lookup(left, rType)})::${raw(rType)} ${raw(op)} ${right}`;
191
+ return sql`(${lookup2(left, rType)})::${raw(rType)} ${raw(op)} ${right}`;
192
192
  }
193
193
  }
194
194
  function getNodeSql(ast) {
195
195
  switch (ast[0]) {
196
196
  case "$eq":
197
197
  if (ast[2] === null)
198
- return sql`${lookup(ast[1])} IS NULL`;
198
+ return sql`${lookup2(ast[1])} IS NULL`;
199
199
  return binop("=", ast[1], ast[2]);
200
200
  case "$neq":
201
201
  if (ast[2] === null)
202
- return sql`${lookup(ast[1])} IS NOT NULL`;
202
+ return sql`${lookup2(ast[1])} IS NOT NULL`;
203
203
  return binop("<>", ast[1], ast[2]);
204
204
  case "$lt":
205
205
  return binop("<", ast[1], ast[2]);
@@ -214,20 +214,20 @@ function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
214
214
  case "$ire":
215
215
  return binop("~*", ast[1], ast[2]);
216
216
  case "$in":
217
- return sql`${lookup(ast[1])} IN (${join(ast[2])})`;
217
+ return sql`${lookup2(ast[1])} IN (${join(ast[2])})`;
218
218
  case "$nin":
219
- return sql`${lookup(ast[1])} NOT IN (${join(ast[2])})`;
219
+ return sql`${lookup2(ast[1])} NOT IN (${join(ast[2])})`;
220
220
  case "$cts":
221
- return sql`${lookup(ast[1])} @> ${ast[2]}`;
221
+ return sql`${lookup2(ast[1])} @> ${ast[2]}`;
222
222
  case "$ctd":
223
- return sql`${lookup(ast[1])} <@ ${ast[2]}`;
223
+ return sql`${lookup2(ast[1])} <@ ${ast[2]}`;
224
224
  case "$ovl":
225
225
  switch (getColumnType(ast[1])) {
226
226
  case "jsonb":
227
227
  case "any":
228
- return sql`${lookup(ast[1])} ?| ${Array.isArray(ast[2]) ? ast[2] : Object.keys(ast[2])}`;
228
+ return sql`${lookup2(ast[1])} ?| ${Array.isArray(ast[2]) ? ast[2] : Object.keys(ast[2])}`;
229
229
  case "array":
230
- return sql`${lookup(ast[1])} && ${ast[2]}`;
230
+ return sql`${lookup2(ast[1])} && ${ast[2]}`;
231
231
  default:
232
232
  throw Error("pg.getSql_ovl_unknown_column_type");
233
233
  }
@@ -238,11 +238,11 @@ function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
238
238
  case "$not":
239
239
  return sql`NOT (${getNodeSql(ast[1])})`;
240
240
  case "$any":
241
- return sql`(SELECT bool_or(${getNodeSql(ast[3])}) FROM UNNEST(${lookup(ast[1])}) ${lookup(ast[2])})`;
241
+ return sql`(SELECT bool_or(${getNodeSql(ast[3])}) FROM UNNEST(${lookup2(ast[1])}) ${lookup2(ast[2])})`;
242
242
  case "$all":
243
- return sql`(SELECT bool_and(${getNodeSql(ast[3])}) FROM UNNEST(${lookup(ast[1])}) ${lookup(ast[2])})`;
243
+ return sql`(SELECT bool_and(${getNodeSql(ast[3])}) FROM UNNEST(${lookup2(ast[1])}) ${lookup2(ast[2])})`;
244
244
  case "$has":
245
- return sql`(SELECT bool_or(${join(ast[3].map((node) => getNodeSql(node)), `) AND bool_or(`)}) FROM UNNEST(${lookup(ast[1])}) ${lookup(ast[2])})`;
245
+ return sql`(SELECT bool_or(${join(ast[3].map((node) => getNodeSql(node)), `) AND bool_or(`)}) FROM UNNEST(${lookup2(ast[1])}) ${lookup2(ast[2])})`;
246
246
  default:
247
247
  throw Error("pg.getSql_unknown_operator: " + ast[0]);
248
248
  }
@@ -263,8 +263,40 @@ const getJsonBuildValue = (value) => {
263
263
  return sql`${value}::text`;
264
264
  return sql`${JSON.stringify(stripAttributes(value))}::jsonb`;
265
265
  };
266
- const getSelectCols = (table) => {
267
- return sql`to_jsonb("${raw(table)}")`;
266
+ const lookup = (prop, type) => {
267
+ const [prefix, ...suffix] = encodePath(prop);
268
+ const op = type === "text" ? sql`#>>` : sql`#>`;
269
+ return suffix.length ? sql`"${raw(prefix)}" ${op} ${suffix}` : sql`"${raw(prefix)}"`;
270
+ };
271
+ const getType = (prop) => {
272
+ const [_prefix, ...suffix] = encodePath(prop);
273
+ return suffix.length ? "jsonb" : "any";
274
+ };
275
+ const aggSql = {
276
+ $sum: (prop) => sql`sum((${lookup(prop)})::numeric)`,
277
+ $card: (prop) => sql`count(distinct(${lookup(prop)}))`,
278
+ $avg: (prop) => sql`sum((${lookup(prop)})::numeric)`,
279
+ $max: (prop) => sql`sum((${lookup(prop)})::numeric)`,
280
+ $min: (prop) => sql`sum((${lookup(prop)})::numeric)`
281
+ };
282
+ const getSelectCols = (table, projection = null) => {
283
+ if (!projection)
284
+ return sql`to_jsonb("${raw(table)}")`;
285
+ const sqls = [];
286
+ for (const key in projection) {
287
+ if (key === "$count") {
288
+ sqls.push(sql`'$count', count(*)`);
289
+ } else if (aggSql[key]) {
290
+ const subSqls = [];
291
+ for (const prop in projection[key]) {
292
+ subSqls.push(sql`${prop}::text, ${aggSql[key](prop)}`);
293
+ }
294
+ sqls.push(sql`${key}::text, jsonb_build_object(${join(subSqls, ", ")})`);
295
+ } else {
296
+ sqls.push(sql`${key}::text, "${raw(key)}"`);
297
+ }
298
+ }
299
+ return sql`jsonb_build_object(${join(sqls, ", ")})`;
268
300
  };
269
301
  const getInsert = (row, options) => {
270
302
  const cols = [];
@@ -314,20 +346,20 @@ const getArgMeta = (key, prefix, idCol) => getJsonBuildObject({
314
346
  $ref: sql`jsonb_build_array(${join(prefix.map((k) => sql`${k}::text`))}, "${raw(idCol)}")`,
315
347
  $ver: nowTimestamp
316
348
  });
349
+ const getAggMeta = (key, $group) => getJsonBuildObject({
350
+ $key: join([key, getJsonBuildObject({ $group })].filter(Boolean), " || "),
351
+ $ver: nowTimestamp
352
+ });
317
353
  function getArgSql(_c, options) {
318
354
  var _d = _c, { $first, $last, $after, $before, $since, $until, $all, $cursor: _ } = _d, rest = __objRest(_d, ["$first", "$last", "$after", "$before", "$since", "$until", "$all", "$cursor"]);
319
- const _a = rest, { $order } = _a, filter = __objRest(_a, ["$order"]);
355
+ const _a = rest, { $order, $group } = _a, filter = __objRest(_a, ["$order", "$group"]);
320
356
  const { prefix, idCol } = options;
321
- const lookup = (prop, type) => {
322
- const [prefix2, ...suffix] = encodePath(prop);
323
- const op = type === "text" ? sql`#>>` : sql`#>`;
324
- return suffix.length ? sql`"${raw(prefix2)}" ${op} ${suffix}` : sql`"${raw(prefix2)}"`;
325
- };
326
- const getType = (prop) => {
327
- const [_prefix, ...suffix] = encodePath(prop);
328
- return suffix.length ? "jsonb" : "any";
329
- };
330
- const meta = (key2) => getArgMeta(key2, prefix, idCol);
357
+ if ($order && $group) {
358
+ throw Error("pg_arg.order_and_group_unsupported in " + prefix);
359
+ }
360
+ const meta = (key2) => $group ? getAggMeta(key2, $group) : getArgMeta(key2, prefix, idCol);
361
+ const groupCols = Array.isArray($group) && $group.length ? $group.map((col) => lookup(col)) : void 0;
362
+ const group = groupCols ? join(groupCols, ", ") : void 0;
331
363
  const hasRangeArg = $before || $after || $since || $until || $first || $last || $all || $order;
332
364
  let key;
333
365
  const where = [];
@@ -336,7 +368,7 @@ function getArgSql(_c, options) {
336
368
  key = sql`${JSON.stringify(filter)}::jsonb`;
337
369
  }
338
370
  if (!hasRangeArg)
339
- return { meta: meta(key), where, limit: 1 };
371
+ return { meta: meta(key), where, group, limit: 1 };
340
372
  if (isEmpty(rest)) {
341
373
  throw Error("pg_arg.pagination_only_unsupported in " + prefix);
342
374
  }
@@ -347,13 +379,14 @@ function getArgSql(_c, options) {
347
379
  });
348
380
  const orderQuery = $order && getJsonBuildObject({ $order: sql`${JSON.stringify($order)}::jsonb` });
349
381
  const cursorQuery = getJsonBuildObject({
350
- $cursor: sql`jsonb_build_array(${join(orderCols)})`
382
+ $cursor: sql`jsonb_build_array(${join(groupCols || orderCols)})`
351
383
  });
352
384
  key = sql`(${join([key, orderQuery, cursorQuery].filter(Boolean), ` || `)})`;
353
385
  return {
354
386
  meta: meta(key),
355
387
  where,
356
- order: join(orderCols.map((col) => sql`${col} ${$last ? sql`DESC` : sql`ASC`}`), `, `),
388
+ order: $order && join(orderCols.map((col) => sql`${col} ${$last ? sql`DESC` : sql`ASC`}`), `, `),
389
+ group,
357
390
  limit: $first || $last
358
391
  };
359
392
  }
@@ -387,39 +420,57 @@ function getBoundCond(orderCols, bound, kind) {
387
420
  }
388
421
  }
389
422
  const MAX_LIMIT = 4096;
390
- function selectByArgs(args, options) {
423
+ function selectByArgs(args, projection, options) {
391
424
  const { table } = options;
392
- const { where, order, limit, meta } = getArgSql(args, options);
425
+ const { where, order, group, limit, meta } = getArgSql(args, options);
393
426
  const clampedLimit = Math.min(MAX_LIMIT, limit || MAX_LIMIT);
394
427
  return sql`
395
428
  SELECT
396
- ${getSelectCols(table)} || ${meta}
429
+ ${getSelectCols(table, projection)} || ${meta}
397
430
  FROM "${raw(table)}"
398
431
  ${where.length ? sql`WHERE ${join(where, ` AND `)}` : empty}
432
+ ${group ? sql`GROUP BY ${group}` : empty}
399
433
  ${order ? sql`ORDER BY ${order}` : empty}
400
434
  LIMIT ${clampedLimit}
401
435
  `;
402
436
  }
403
- function selectByIds(ids, options) {
437
+ function selectByIds(ids, projection, options) {
404
438
  const { table, idCol } = options;
405
439
  return sql`
406
440
  SELECT
407
- ${getSelectCols(table)} || ${getIdMeta(options)}
441
+ ${getSelectCols(table, projection)} || ${getIdMeta(options)}
408
442
  FROM "${raw(table)}"
409
443
  WHERE "${raw(idCol)}" IN (${join(ids)})
410
444
  `;
411
445
  }
412
- function patch(object, arg, options) {
446
+ function getSingleSql(arg, options) {
413
447
  const { table, idCol } = options;
414
- const { where, meta } = isPlainObject(arg) ? getArgSql(arg, options) : { where: [sql`"${raw(idCol)}" = ${arg}`], meta: getIdMeta(options) };
448
+ if (!isPlainObject(arg)) {
449
+ return {
450
+ where: sql`"${raw(idCol)}" = ${arg}`,
451
+ meta: getIdMeta(options)
452
+ };
453
+ }
454
+ const { where, meta } = getArgSql(arg, options);
415
455
  if (!where || !where.length)
416
456
  throw Error("pg_write.no_condition");
457
+ return {
458
+ where: sql`"${raw(idCol)}" = (
459
+ SELECT "${raw(idCol)}"
460
+ FROM "${raw(table)}"
461
+ WHERE ${join(where, ` AND `)}
462
+ LIMIT 1
463
+ )`,
464
+ meta
465
+ };
466
+ }
467
+ function patch(object, arg, options) {
468
+ const { table } = options;
469
+ const { where, meta } = getSingleSql(arg, options);
417
470
  const row = object;
418
471
  return sql`
419
472
  UPDATE "${raw(table)}" SET ${getUpdates(row, options)}
420
- WHERE ${isPlainObject(arg) ? sql`"${raw(idCol)}" = (
421
- SELECT "${raw(idCol)}" FROM "${raw(table)}"
422
- WHERE ${join(where, ` AND `)} LIMIT 1)` : join(where, ` AND `)}
473
+ WHERE ${where}
423
474
  RETURNING (${getSelectCols(table)} || ${meta})`;
424
475
  }
425
476
  function put(object, arg, options) {
@@ -439,6 +490,14 @@ function put(object, arg, options) {
439
490
  ON CONFLICT (${conflictTarget}) DO UPDATE SET (${cols}) = (${vals})
440
491
  RETURNING (${getSelectCols(table)} || ${meta})`;
441
492
  }
493
+ function del(arg, options) {
494
+ const { table } = options;
495
+ const { where } = getSingleSql(arg, options);
496
+ return sql`
497
+ DELETE FROM "${raw(table)}"
498
+ WHERE ${where}
499
+ RETURNING (${getJsonBuildObject({ $key: arg })})`;
500
+ }
442
501
  const log = debug("graffy:pg:db");
443
502
  class Db {
444
503
  constructor(connection) {
@@ -484,14 +543,14 @@ class Db {
484
543
  const promises = [];
485
544
  const results = [];
486
545
  const { prefix } = tableOptions;
487
- const getByArgs = async (args) => {
488
- const result = await this.readSql(selectByArgs(args, tableOptions));
546
+ const getByArgs = async (args, projection) => {
547
+ const result = await this.readSql(selectByArgs(args, projection, tableOptions));
489
548
  const wrappedGraph = encodeGraph(wrapObject(result, prefix));
490
549
  log("getByArgs", wrappedGraph);
491
550
  merge(results, wrappedGraph);
492
551
  };
493
552
  const getByIds = async () => {
494
- const result = await this.readSql(selectByIds(Object.keys(idQueries), tableOptions));
553
+ const result = await this.readSql(selectByIds(Object.keys(idQueries), null, tableOptions));
495
554
  result.forEach((object) => {
496
555
  const wrappedGraph = encodeGraph(wrapObject(object, prefix));
497
556
  log("getByIds", wrappedGraph);
@@ -505,10 +564,12 @@ class Db {
505
564
  if (node.prefix) {
506
565
  for (const childNode of node.children) {
507
566
  const childArgs = decodeArgs(childNode);
508
- promises.push(getByArgs(__spreadValues(__spreadValues({}, args), childArgs)));
567
+ const projection = childNode.children ? decodeQuery(childNode.children) : true;
568
+ promises.push(getByArgs(__spreadValues(__spreadValues({}, args), childArgs), projection));
509
569
  }
510
570
  } else {
511
- promises.push(getByArgs(args));
571
+ const projection = node.children ? decodeQuery(node.children) : true;
572
+ promises.push(getByArgs(args, projection));
512
573
  }
513
574
  } else {
514
575
  idQueries[node.key] = node.children;
@@ -521,15 +582,15 @@ class Db {
521
582
  return slice(finalize(results, wrap(query, prefix)), rootQuery).known || [];
522
583
  }
523
584
  async write(rootChange, tableOptions) {
524
- const sqls = [];
525
- const addToQuery = (sql2) => sqls.push(sql2);
526
585
  const { prefix } = tableOptions;
527
586
  const change = unwrap(rootChange, prefix);
528
- for (const node of change) {
587
+ const sqls = change.map((node) => {
588
+ const arg = decodeArgs(node);
529
589
  if (isRange(node)) {
530
- throw Error(node.key === node.end ? "pg_write.delete_unsupported" : "pg_write.write_range_unsupported");
590
+ if (node.key === node.end)
591
+ return del(arg, tableOptions);
592
+ throw Error("pg_write.write_range_unsupported");
531
593
  }
532
- const arg = decodeArgs(node);
533
594
  const object = decodeGraph(node.children);
534
595
  if (isPlainObject(arg)) {
535
596
  mergeObject(object, arg);
@@ -539,8 +600,8 @@ class Db {
539
600
  if (object.$put && object.$put !== true) {
540
601
  throw Error("pg_write.partial_put_unsupported");
541
602
  }
542
- object.$put ? addToQuery(put(object, arg, tableOptions)) : addToQuery(patch(object, arg, tableOptions));
543
- }
603
+ return object.$put ? put(object, arg, tableOptions) : patch(object, arg, tableOptions);
604
+ });
544
605
  const result = [];
545
606
  await Promise.all(sqls.map((sql2) => this.writeSql(sql2).then((object) => {
546
607
  merge(result, encodeGraph(wrapObject(object, prefix)));
@@ -549,7 +610,7 @@ class Db {
549
610
  return result;
550
611
  }
551
612
  }
552
- const pg = ({ table, idCol, verCol, links, connection }) => (store) => {
613
+ const pg = ({ table, idCol, verCol, connection }) => (store) => {
553
614
  store.on("read", read);
554
615
  store.on("write", write);
555
616
  const prefix = store.path;
@@ -557,13 +618,13 @@ const pg = ({ table, idCol, verCol, links, connection }) => (store) => {
557
618
  prefix,
558
619
  table: table || prefix[prefix.length - 1] || "default",
559
620
  idCol: idCol || "id",
560
- verCol: verCol || "updatedAt",
561
- links: links || {}
621
+ verCol: verCol || "updatedAt"
562
622
  };
563
623
  const defaultDb = new Db(connection);
564
624
  function read(query, options, next) {
565
- const _a = options, { transactionDb = defaultDb } = _a, readOpts = __objRest(_a, ["transactionDb"]);
566
- const readPromise = transactionDb.read(query, tableOpts, readOpts);
625
+ const { pgClient } = options;
626
+ const db = pgClient ? new Db(pgClient) : defaultDb;
627
+ const readPromise = db.read(query, tableOpts);
567
628
  const remainingQuery = remove(query, prefix);
568
629
  const nextPromise = next(remainingQuery);
569
630
  return Promise.all([readPromise, nextPromise]).then(([readRes, nextRes]) => {
@@ -571,8 +632,9 @@ const pg = ({ table, idCol, verCol, links, connection }) => (store) => {
571
632
  });
572
633
  }
573
634
  function write(change, options, next) {
574
- const _a = options, { transactionDb = defaultDb } = _a, writeOpts = __objRest(_a, ["transactionDb"]);
575
- const writePromise = transactionDb.write(change, tableOpts, writeOpts);
635
+ const { pgClient } = options;
636
+ const db = pgClient ? new Db(pgClient) : defaultDb;
637
+ const writePromise = db.write(change, tableOpts);
576
638
  const remainingChange = remove(change, prefix);
577
639
  const nextPromise = next(remainingChange);
578
640
  return Promise.all([writePromise, nextPromise]).then(([writeRes, nextRes]) => {
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.11",
5
+ "version": "0.15.13-alpha.1",
6
6
  "main": "./index.cjs",
7
7
  "exports": {
8
8
  "import": "./index.mjs",
@@ -16,9 +16,11 @@
16
16
  },
17
17
  "license": "Apache-2.0",
18
18
  "dependencies": {
19
- "@graffy/common": "0.15.11",
20
- "pg": "^8.7.1",
19
+ "@graffy/common": "0.15.13-alpha.1",
21
20
  "debug": "^4.3.2",
22
21
  "sql-template-tag": "^4.0.0"
22
+ },
23
+ "peerDependencies": {
24
+ "pg": "^8.0.0"
23
25
  }
24
26
  }
package/types/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- export function pg({ table, idCol, verCol, links, connection }: {
1
+ export function pg({ table, idCol, verCol, connection }: {
2
2
  table?: string;
3
3
  idCol?: string;
4
4
  verCol?: string;
5
- links?: object;
6
5
  connection?: any;
7
6
  }): (store: any) => void;
@@ -1,6 +1,8 @@
1
1
  export const nowTimestamp: Sql;
2
2
  export function getJsonBuildObject(variadic: any): Sql;
3
- export function getSelectCols(table: any): Sql;
3
+ export function lookup(prop: any, type: any): Sql;
4
+ export function getType(prop: any): "any" | "jsonb";
5
+ export function getSelectCols(table: any, projection?: any): Sql;
4
6
  export function getInsert(row: any, options: any): {
5
7
  cols: Sql;
6
8
  vals: Sql;
@@ -5,12 +5,13 @@
5
5
  @param {object} options
6
6
 
7
7
  @typedef { import('sql-template-tag').Sql } Sql
8
- @return {{ meta: Sql, where: Sql[], order?: Sql, limit: number }}
8
+ @return {{ meta: Sql, where: Sql[], order?: Sql, group?: Sql, limit: number }}
9
9
  */
10
10
  export default function getArgSql({ $first, $last, $after, $before, $since, $until, $all, $cursor: _, ...rest }: object, options: object): {
11
11
  meta: Sql;
12
12
  where: Sql[];
13
13
  order?: Sql;
14
+ group?: Sql;
14
15
  limit: number;
15
16
  };
16
17
  /**
@@ -2,3 +2,4 @@ export function getIdMeta({ idCol }: {
2
2
  idCol: any;
3
3
  }): import("sql-template-tag").Sql;
4
4
  export function getArgMeta(key: any, prefix: any, idCol: any): import("sql-template-tag").Sql;
5
+ export function getAggMeta(key: any, $group: any): import("sql-template-tag").Sql;
@@ -1,2 +1,2 @@
1
- export function selectByArgs(args: any, options: any): import("sql-template-tag").Sql;
2
- export function selectByIds(ids: any, options: any): import("sql-template-tag").Sql;
1
+ export function selectByArgs(args: any, projection: any, options: any): import("sql-template-tag").Sql;
2
+ export function selectByIds(ids: any, projection: any, options: any): import("sql-template-tag").Sql;
@@ -1,2 +1,3 @@
1
1
  export function patch(object: any, arg: any, options: any): import("sql-template-tag").Sql;
2
2
  export function put(object: any, arg: any, options: any): import("sql-template-tag").Sql;
3
+ export function del(arg: any, options: any): import("sql-template-tag").Sql;