json-fs-query 2.3.1 → 3.1.0

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.
Files changed (64) hide show
  1. package/README.md +79 -7
  2. package/browser/jsonFsQuery.js +12650 -13107
  3. package/browser/jsonFsQuery.min.js +1 -1
  4. package/dist/data/introspect/create.js +30 -1
  5. package/dist/data/introspect/create.min.js +1 -1
  6. package/dist/data/queries/groupBy.d.ts +20 -1
  7. package/dist/data/queries/groupBy.js +71 -9
  8. package/dist/data/queries/groupBy.min.js +1 -1
  9. package/dist/data/queries/limitOffset.d.ts +1 -1
  10. package/dist/data/queries/limitOffset.js +1 -1
  11. package/dist/data/queries/selectFields.d.ts +6 -4
  12. package/dist/data/queries/selectFields.js +15 -10
  13. package/dist/data/queries/selectFields.min.js +1 -1
  14. package/dist/data/utilities/databaseLock.js +2 -1
  15. package/dist/data/utilities/databaseLock.min.js +1 -1
  16. package/dist/data/utilities/parsers/findCommand.d.ts +2 -1
  17. package/dist/data/utilities/parsers/findCommand.js +4 -4
  18. package/dist/data/utilities/parsers/findCommand.min.js +1 -1
  19. package/dist/data/utilities/parsers/findConditions.d.ts +8 -9
  20. package/dist/data/utilities/parsers/findConditions.js +29 -41
  21. package/dist/data/utilities/parsers/findConditions.min.js +1 -1
  22. package/dist/data/utilities/parsers/findGroupBy.d.ts +6 -4
  23. package/dist/data/utilities/parsers/findGroupBy.js +17 -9
  24. package/dist/data/utilities/parsers/findGroupBy.min.js +1 -1
  25. package/dist/data/utilities/parsers/findInsertValues.d.ts +4 -3
  26. package/dist/data/utilities/parsers/findInsertValues.js +9 -9
  27. package/dist/data/utilities/parsers/findInsertValues.min.js +1 -1
  28. package/dist/data/utilities/parsers/findJoinClauses.d.ts +4 -3
  29. package/dist/data/utilities/parsers/findJoinClauses.js +20 -13
  30. package/dist/data/utilities/parsers/findJoinClauses.min.js +1 -1
  31. package/dist/data/utilities/parsers/findJoinEntity.d.ts +1 -1
  32. package/dist/data/utilities/parsers/findJoinEntity.js +3 -5
  33. package/dist/data/utilities/parsers/findJoinEntity.min.js +1 -1
  34. package/dist/data/utilities/parsers/findLimit.d.ts +4 -3
  35. package/dist/data/utilities/parsers/findLimit.js +8 -8
  36. package/dist/data/utilities/parsers/findLimit.min.js +1 -1
  37. package/dist/data/utilities/parsers/findMergeJoins.d.ts +4 -3
  38. package/dist/data/utilities/parsers/findMergeJoins.js +19 -12
  39. package/dist/data/utilities/parsers/findMergeJoins.min.js +1 -1
  40. package/dist/data/utilities/parsers/findOffset.d.ts +4 -3
  41. package/dist/data/utilities/parsers/findOffset.js +8 -8
  42. package/dist/data/utilities/parsers/findOffset.min.js +1 -1
  43. package/dist/data/utilities/parsers/findSelects.d.ts +4 -3
  44. package/dist/data/utilities/parsers/findSelects.js +13 -14
  45. package/dist/data/utilities/parsers/findSelects.min.js +1 -1
  46. package/dist/data/utilities/parsers/findSortClauses.d.ts +4 -3
  47. package/dist/data/utilities/parsers/findSortClauses.js +12 -12
  48. package/dist/data/utilities/parsers/findSortClauses.min.js +1 -1
  49. package/dist/data/utilities/parsers/findUpdateValues.d.ts +5 -7
  50. package/dist/data/utilities/parsers/findUpdateValues.js +10 -13
  51. package/dist/data/utilities/parsers/findUpdateValues.min.js +1 -1
  52. package/dist/data/utilities/parsers/parseQuery.js +83 -9
  53. package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
  54. package/dist/data/utilities/parsers/splitClauses.d.ts +29 -0
  55. package/dist/data/utilities/parsers/splitClauses.js +139 -0
  56. package/dist/data/utilities/parsers/splitClauses.min.js +1 -0
  57. package/dist/data/utilities/retrieveFile.js +2 -1
  58. package/dist/data/utilities/retrieveFile.min.js +1 -1
  59. package/dist/data/utilities/runQuery.js +5 -4
  60. package/dist/data/utilities/runQuery.min.js +1 -1
  61. package/package.json +5 -3
  62. package/dist/data/utilities/parsers/removeFromQuery.d.ts +0 -2
  63. package/dist/data/utilities/parsers/removeFromQuery.js +0 -8
  64. package/dist/data/utilities/parsers/removeFromQuery.min.js +0 -1
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.create = void 0;
7
+ require("core-js/modules/es.array.includes.js");
7
8
  require("core-js/modules/es.json.stringify.js");
8
9
  require("core-js/modules/esnext.iterator.constructor.js");
9
10
  require("core-js/modules/esnext.iterator.find.js");
@@ -12,11 +13,28 @@ var _record = require("./definitions/record");
12
13
  var _registerRecord = require("../utilities/registerRecord");
13
14
  var _config = require("../utilities/config");
14
15
  var _typeCheck = require("./policies/typeCheck");
16
+ const knownKeyProperties = ['type', 'fields', 'lookup', 'references'];
17
+ /**
18
+ * Reject a key with a property key() would ignore (a typo such as `keys` for `fields`), which has to be
19
+ * checked on the keys as given, before key() drops it.
20
+ * @param recordName
21
+ * @param keys
22
+ */
23
+ const assertKnownKeyProperties = (recordName, keys) => {
24
+ for (const key of keys) {
25
+ for (const property of Object.keys(key)) {
26
+ if (!knownKeyProperties.includes(property)) {
27
+ throw new Error(`Unknown property "${property}" on ${key.type ?? 'index'} key of ${recordName}; must be ${knownKeyProperties.join(', ')}`);
28
+ }
29
+ }
30
+ }
31
+ };
15
32
  /**
16
33
  * Reject a definition that could never be written correctly, before anything is created: a
17
34
  * default that fails its field's type check (otherwise every insert leaving that field out would
18
35
  * fail instead), including a null default on a field that isn't nullable, and a nullable field in
19
- * the primary key (an entry's file name comes from its primary key value).
36
+ * the primary key (an entry's file name comes from its primary key value). Also a key with no fields,
37
+ * or with a field that isn't in the definition.
20
38
  * @param recordName
21
39
  * @param recordContent
22
40
  */
@@ -31,6 +49,16 @@ const assertValidDefinition = (recordName, recordContent) => {
31
49
  throw new Error(`Invalid default for field "${field.name}" of ${recordName}: ${err.message}`);
32
50
  }
33
51
  }
52
+ for (const key of recordContent.keys) {
53
+ if (key.fields.length < 1) {
54
+ throw new Error(`The ${key.type} key of ${recordName} must have at least one field`);
55
+ }
56
+ for (const fieldName of key.fields) {
57
+ if (!recordContent.definition.some(field => field.name === fieldName)) {
58
+ throw new Error(`The ${key.type} key of ${recordName} names field "${fieldName}", which isn't in the definition`);
59
+ }
60
+ }
61
+ }
34
62
  const primaryKey = recordContent.keys.find(key => key.type === 'primary');
35
63
  for (const fieldName of primaryKey ? primaryKey.fields : []) {
36
64
  if (recordContent.definition.some(field => field.name === fieldName && field.nullable)) {
@@ -59,6 +87,7 @@ const create = async (recordName, definition = [], keys = []) => {
59
87
  if (existsSync(`${databasePath}${recordName}.json`)) {
60
88
  return null;
61
89
  }
90
+ assertKnownKeyProperties(recordName, keys);
62
91
  const recordContent = (0, _record.record)({
63
92
  path: recordName,
64
93
  definition,
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.create=void 0,require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.some.js");var _record=require("./definitions/record"),_registerRecord=require("../utilities/registerRecord"),_config=require("../utilities/config"),_typeCheck=require("./policies/typeCheck");const assertValidDefinition=(e,r)=>{for(const t of r.definition)if(Object.prototype.hasOwnProperty.call(t,"default"))try{(0,_typeCheck.typeCheck)(t.type,t.default,t.nullable)}catch(r){throw new Error(`Invalid default for field "${t.name}" of ${e}: ${r.message}`)}const t=r.keys.find((e=>"primary"===e.type));for(const i of t?t.fields:[])if(r.definition.some((e=>e.name===i&&e.nullable)))throw new Error(`Primary key field "${i}" of ${e} can't be nullable`)},create=async(e,r=[],t=[])=>{const{writeFile:i,mkdir:s}=require("fs/promises"),{existsSync:o}=require("fs"),n=(0,_config.getSetting)("databasePath","database/");if("__RECORDS"===e)return null;if(o(`${n}${e}.json`))return null;const a=(0,_record.record)({path:e,definition:r,keys:t});return assertValidDefinition(e,a),await s(n,{recursive:!0}),await i(`${n}${e}.json`,JSON.stringify(a,null,2)),await s(`${n}${e}`,{recursive:!0}),await(0,_registerRecord.registerRecord)(e),a};exports.create=create;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.create=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.some.js");var _record=require("./definitions/record"),_registerRecord=require("../utilities/registerRecord"),_config=require("../utilities/config"),_typeCheck=require("./policies/typeCheck");const knownKeyProperties=["type","fields","lookup","references"],assertKnownKeyProperties=(e,r)=>{for(const t of r)for(const r of Object.keys(t))if(!knownKeyProperties.includes(r))throw new Error(`Unknown property "${r}" on ${t.type??"index"} key of ${e}; must be ${knownKeyProperties.join(", ")}`)},assertValidDefinition=(e,r)=>{for(const t of r.definition)if(Object.prototype.hasOwnProperty.call(t,"default"))try{(0,_typeCheck.typeCheck)(t.type,t.default,t.nullable)}catch(r){throw new Error(`Invalid default for field "${t.name}" of ${e}: ${r.message}`)}for(const t of r.keys){if(t.fields.length<1)throw new Error(`The ${t.type} key of ${e} must have at least one field`);for(const i of t.fields)if(!r.definition.some((e=>e.name===i)))throw new Error(`The ${t.type} key of ${e} names field "${i}", which isn't in the definition`)}const t=r.keys.find((e=>"primary"===e.type));for(const i of t?t.fields:[])if(r.definition.some((e=>e.name===i&&e.nullable)))throw new Error(`Primary key field "${i}" of ${e} can't be nullable`)},create=async(e,r=[],t=[])=>{const{writeFile:i,mkdir:o}=require("fs/promises"),{existsSync:s}=require("fs"),n=(0,_config.getSetting)("databasePath","database/");if("__RECORDS"===e)return null;if(s(`${n}${e}.json`))return null;assertKnownKeyProperties(e,t);const a=(0,_record.record)({path:e,definition:r,keys:t});return assertValidDefinition(e,a),await o(n,{recursive:!0}),await i(`${n}${e}.json`,JSON.stringify(a,null,2)),await o(`${n}${e}`,{recursive:!0}),await(0,_registerRecord.registerRecord)(e),a};exports.create=create;
@@ -1,2 +1,21 @@
1
- export declare const groupBy: (dataSet?: any[], groupProperty?: string) => any;
1
+ /**
2
+ * One group of a grouped read: the field and the value its rows share, then either the rows themselves or, for
3
+ * `group by a, b`, the groups within it by the next field. A group of rows missing the field has `missing: true`
4
+ * and no value; `null` is a value of its own (see Null and missing fields in the README).
5
+ */
6
+ export type Group = {
7
+ field: string;
8
+ value?: any;
9
+ missing?: true;
10
+ rows?: Object[];
11
+ groups?: Group[];
12
+ };
13
+ /**
14
+ * Group rows by each field in turn. The groups are a list in the order their first row came, so a sort before
15
+ * grouping orders the groups too (and the rows within each one).
16
+ * @param dataSet
17
+ * @param fields
18
+ * @returns The rows as they were when there's no field, otherwise the groups.
19
+ */
20
+ export declare const groupBy: (dataSet?: Object[], fields?: string[]) => Object[] | Group[];
2
21
  //# sourceMappingURL=groupBy.d.ts.map
@@ -4,19 +4,81 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.groupBy = void 0;
7
+ require("core-js/modules/es.json.stringify.js");
7
8
  require("core-js/modules/esnext.iterator.constructor.js");
9
+ require("core-js/modules/esnext.iterator.map.js");
8
10
  require("core-js/modules/esnext.iterator.reduce.js");
9
- const groupBy = (dataSet = [], groupProperty = '') => {
10
- if (!groupProperty) {
11
+ require("core-js/modules/esnext.map.delete-all.js");
12
+ require("core-js/modules/esnext.map.every.js");
13
+ require("core-js/modules/esnext.map.filter.js");
14
+ require("core-js/modules/esnext.map.find.js");
15
+ require("core-js/modules/esnext.map.find-key.js");
16
+ require("core-js/modules/esnext.map.includes.js");
17
+ require("core-js/modules/esnext.map.key-of.js");
18
+ require("core-js/modules/esnext.map.map-keys.js");
19
+ require("core-js/modules/esnext.map.map-values.js");
20
+ require("core-js/modules/esnext.map.merge.js");
21
+ require("core-js/modules/esnext.map.reduce.js");
22
+ require("core-js/modules/esnext.map.some.js");
23
+ require("core-js/modules/esnext.map.update.js");
24
+ // An object's keys in sorted order, at any depth, so { a, b } and { b, a } are the same value; arrays keep their order
25
+ const sortedKeys = value => {
26
+ if (Array.isArray(value)) {
27
+ return value.map(sortedKeys);
28
+ }
29
+ if (value !== null && typeof value === 'object') {
30
+ return Object.keys(value).sort().reduce((sorted, key) => {
31
+ sorted[key] = sortedKeys(value[key]);
32
+ return sorted;
33
+ }, {});
34
+ }
35
+ return value;
36
+ };
37
+ // Rows share a group when their values are the same, type included: 1 and '1' are two groups, as are null and missing
38
+ const groupKey = (row, field) => {
39
+ if (!Object.prototype.hasOwnProperty.call(row, field)) {
40
+ return 'missing';
41
+ }
42
+ const value = row[field];
43
+ return value !== null && typeof value === 'object' ? `json:${JSON.stringify(sortedKeys(value))}` : `${typeof value}:${String(value)}`;
44
+ };
45
+ /**
46
+ * Group rows by each field in turn. The groups are a list in the order their first row came, so a sort before
47
+ * grouping orders the groups too (and the rows within each one).
48
+ * @param dataSet
49
+ * @param fields
50
+ * @returns The rows as they were when there's no field, otherwise the groups.
51
+ */
52
+ const groupBy = (dataSet = [], fields = []) => {
53
+ if (!fields.length) {
11
54
  return dataSet;
12
55
  }
13
- return dataSet.reduce((groupedData, data) => {
14
- const property = data[groupProperty];
15
- if (!Object.prototype.hasOwnProperty.call(groupedData, property)) {
16
- groupedData[property] = [];
56
+ const [field, ...rest] = fields;
57
+ const groups = new Map();
58
+ for (const row of dataSet) {
59
+ const key = groupKey(row, field);
60
+ if (!groups.has(key)) {
61
+ groups.set(key, key === 'missing' ? {
62
+ field,
63
+ missing: true,
64
+ rows: []
65
+ } : {
66
+ field,
67
+ value: row[field],
68
+ rows: []
69
+ });
17
70
  }
18
- groupedData[property].push(data);
19
- return groupedData;
20
- }, {});
71
+ groups.get(key).rows.push(row);
72
+ }
73
+ return [...groups.values()].map(({
74
+ rows,
75
+ ...group
76
+ }) => rest.length ? {
77
+ ...group,
78
+ groups: groupBy(rows, rest)
79
+ } : {
80
+ ...group,
81
+ rows
82
+ });
21
83
  };
22
84
  exports.groupBy = groupBy;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.groupBy=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.reduce.js");const groupBy=(e=[],r="")=>r?e.reduce(((e,o)=>{const t=o[r];return Object.prototype.hasOwnProperty.call(e,t)||(e[t]=[]),e[t].push(o),e}),{}):e;exports.groupBy=groupBy;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.groupBy=void 0,require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.reduce.js"),require("core-js/modules/esnext.map.delete-all.js"),require("core-js/modules/esnext.map.every.js"),require("core-js/modules/esnext.map.filter.js"),require("core-js/modules/esnext.map.find.js"),require("core-js/modules/esnext.map.find-key.js"),require("core-js/modules/esnext.map.includes.js"),require("core-js/modules/esnext.map.key-of.js"),require("core-js/modules/esnext.map.map-keys.js"),require("core-js/modules/esnext.map.map-values.js"),require("core-js/modules/esnext.map.merge.js"),require("core-js/modules/esnext.map.reduce.js"),require("core-js/modules/esnext.map.some.js"),require("core-js/modules/esnext.map.update.js");const sortedKeys=e=>Array.isArray(e)?e.map(sortedKeys):null!==e&&"object"==typeof e?Object.keys(e).sort().reduce(((s,r)=>(s[r]=sortedKeys(e[r]),s)),{}):e,groupKey=(e,s)=>{if(!Object.prototype.hasOwnProperty.call(e,s))return"missing";const r=e[s];return null!==r&&"object"==typeof r?`json:${JSON.stringify(sortedKeys(r))}`:`${typeof r}:${String(r)}`},groupBy=(e=[],s=[])=>{if(!s.length)return e;const[r,...o]=s,t=new Map;for(const s of e){const e=groupKey(s,r);t.has(e)||t.set(e,"missing"===e?{field:r,missing:!0,rows:[]}:{field:r,value:s[r],rows:[]}),t.get(e).rows.push(s)}return[...t.values()].map((({rows:e,...s})=>o.length?{...s,groups:groupBy(e,o)}:{...s,rows:e}))};exports.groupBy=groupBy;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Slice a dataSet down to at most `limit` rows, starting after the first `offset` of them.
2
+ * Slice a dataSet down to at most `limit` rows (or groups, after group by), starting after the first `offset` of them.
3
3
  * @param dataSet
4
4
  * @param offset
5
5
  * @param limit
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.limitOffset = void 0;
7
7
  /**
8
- * Slice a dataSet down to at most `limit` rows, starting after the first `offset` of them.
8
+ * Slice a dataSet down to at most `limit` rows (or groups, after group by), starting after the first `offset` of them.
9
9
  * @param dataSet
10
10
  * @param offset
11
11
  * @param limit
@@ -1,10 +1,12 @@
1
1
  import { ParsedSelect } from '../utilities/parsers/findSelects';
2
+ import { Group } from './groupBy';
2
3
  /**
3
- * Shape each row down to only the selected properties (aliased, when given). dataSet is either a plain array of
4
- * rows, or (once groupBy has run) a plain object keyed by group value, each value an array of rows - both are
5
- * shaped the same way, row by row.
4
+ * Shape each row down to only the selected properties (aliased, when given). dataSet is either a list of rows, or
5
+ * (once groupBy has run) a list of groups, whose rows are shaped the same way at any depth; the groups keep their
6
+ * field and value, selected or not.
6
7
  * @param dataSet
7
8
  * @param selectClauses
9
+ * @param grouped Whether dataSet is a list of groups (see groupBy)
8
10
  */
9
- export declare const selectFields: (dataSet?: Object[] | Object, selectClauses?: ParsedSelect[]) => Object[] | Object;
11
+ export declare const selectFields: (dataSet?: Object[] | Group[], selectClauses?: ParsedSelect[], grouped?: boolean) => Object[] | Group[];
10
12
  //# sourceMappingURL=selectFields.d.ts.map
@@ -14,23 +14,28 @@ const shapeRow = (row, selectClauses) => selectClauses.reduce((shaped, {
14
14
  shaped[alias || property] = row[property];
15
15
  return shaped;
16
16
  }, {});
17
+ const shapeGroup = (group, selectClauses) => group.groups ? {
18
+ ...group,
19
+ groups: group.groups.map(inner => shapeGroup(inner, selectClauses))
20
+ } : {
21
+ ...group,
22
+ rows: group.rows.map(row => shapeRow(row, selectClauses))
23
+ };
17
24
  /**
18
- * Shape each row down to only the selected properties (aliased, when given). dataSet is either a plain array of
19
- * rows, or (once groupBy has run) a plain object keyed by group value, each value an array of rows - both are
20
- * shaped the same way, row by row.
25
+ * Shape each row down to only the selected properties (aliased, when given). dataSet is either a list of rows, or
26
+ * (once groupBy has run) a list of groups, whose rows are shaped the same way at any depth; the groups keep their
27
+ * field and value, selected or not.
21
28
  * @param dataSet
22
29
  * @param selectClauses
30
+ * @param grouped Whether dataSet is a list of groups (see groupBy)
23
31
  */
24
- const selectFields = (dataSet = [], selectClauses = []) => {
32
+ const selectFields = (dataSet = [], selectClauses = [], grouped = false) => {
25
33
  if (!selectClauses.length) {
26
34
  return dataSet;
27
35
  }
28
- if (Array.isArray(dataSet)) {
29
- return dataSet.map(row => shapeRow(row, selectClauses));
36
+ if (grouped) {
37
+ return dataSet.map(group => shapeGroup(group, selectClauses));
30
38
  }
31
- return Object.keys(dataSet).reduce((shaped, key) => {
32
- shaped[key] = dataSet[key].map(row => shapeRow(row, selectClauses));
33
- return shaped;
34
- }, {});
39
+ return dataSet.map(row => shapeRow(row, selectClauses));
35
40
  };
36
41
  exports.selectFields = selectFields;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.selectFields=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.reduce.js");const shapeRow=(e,s)=>s.reduce(((s,{property:r,alias:t})=>(s[t||r]=e[r],s)),{}),selectFields=(e=[],s=[])=>s.length?Array.isArray(e)?e.map((e=>shapeRow(e,s))):Object.keys(e).reduce(((r,t)=>(r[t]=e[t].map((e=>shapeRow(e,s))),r)),{}):e;exports.selectFields=selectFields;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.selectFields=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.reduce.js");const shapeRow=(e,s)=>s.reduce(((s,{property:r,alias:o})=>(s[o||r]=e[r],s)),{}),shapeGroup=(e,s)=>e.groups?{...e,groups:e.groups.map((e=>shapeGroup(e,s)))}:{...e,rows:e.rows.map((e=>shapeRow(e,s)))},selectFields=(e=[],s=[],r=!1)=>s.length?r?e.map((e=>shapeGroup(e,s))):e.map((e=>shapeRow(e,s))):e;exports.selectFields=selectFields;
@@ -20,7 +20,8 @@ require("core-js/modules/esnext.map.some.js");
20
20
  require("core-js/modules/esnext.map.update.js");
21
21
  var jsEnv = _interopRequireWildcard(require("browser-or-node"));
22
22
  var _config = require("./config");
23
- function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
23
+ var e;
24
+ function _interopRequireWildcard(t, r) { if (!r && t && t.__esModule) return t; var n, a, o = Object.defineProperty, f = { __proto__: null, default: t }; if (Object(t) !== t) return f; if (e || "function" != typeof WeakMap || (e = new WeakMap()), e) { if (e.has(t)) return e.get(t); e.set(t, f); } for (a in t) "default" !== a && {}.hasOwnProperty.call(t, a) && ((n = o && Object.getOwnPropertyDescriptor(t, a)) && (n.get || n.set) ? o(f, a, n) : f[a] = t[a]); return f; }
24
25
  const queues = new Map();
25
26
  /**
26
27
  * Run `task` once every earlier task on the same database (the configured databasePath) has finished, so that calls
@@ -1 +1 @@
1
- "use strict";require("core-js/modules/esnext.weak-map.delete-all.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.withDatabaseLock=void 0,require("core-js/modules/esnext.map.delete-all.js"),require("core-js/modules/esnext.map.every.js"),require("core-js/modules/esnext.map.filter.js"),require("core-js/modules/esnext.map.find.js"),require("core-js/modules/esnext.map.find-key.js"),require("core-js/modules/esnext.map.includes.js"),require("core-js/modules/esnext.map.key-of.js"),require("core-js/modules/esnext.map.map-keys.js"),require("core-js/modules/esnext.map.map-values.js"),require("core-js/modules/esnext.map.merge.js"),require("core-js/modules/esnext.map.reduce.js"),require("core-js/modules/esnext.map.some.js"),require("core-js/modules/esnext.map.update.js");var jsEnv=_interopRequireWildcard(require("browser-or-node")),_config=require("./config");function _interopRequireWildcard(e,r){if("function"==typeof WeakMap)var s=new WeakMap,t=new WeakMap;return(_interopRequireWildcard=function(e,r){if(!r&&e&&e.__esModule)return e;var o,a,u={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return u;if(o=r?t:s){if(o.has(e))return o.get(e);o.set(e,u)}for(const r in e)"default"!==r&&{}.hasOwnProperty.call(e,r)&&((a=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,r))&&(a.get||a.set)?o(u,r,a):u[r]=e[r]);return u})(e,r)}const queues=new Map,withDatabaseLock=async e=>{if(!jsEnv.isNode)return e();const r=(0,_config.resolveDatabasePath)((0,_config.getSetting)("databasePath","database/")),s=(0,_config.trackDatabaseCall)(),t=queues.get(r)||Promise.resolve();let o;const a=new Promise((e=>{o=e})),u=t.then((()=>a));queues.set(r,u),await t;try{return await e()}finally{s(),o(),queues.get(r)===u&&queues.delete(r)}};exports.withDatabaseLock=withDatabaseLock;
1
+ "use strict";require("core-js/modules/esnext.weak-map.delete-all.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.withDatabaseLock=void 0,require("core-js/modules/esnext.map.delete-all.js"),require("core-js/modules/esnext.map.every.js"),require("core-js/modules/esnext.map.filter.js"),require("core-js/modules/esnext.map.find.js"),require("core-js/modules/esnext.map.find-key.js"),require("core-js/modules/esnext.map.includes.js"),require("core-js/modules/esnext.map.key-of.js"),require("core-js/modules/esnext.map.map-keys.js"),require("core-js/modules/esnext.map.map-values.js"),require("core-js/modules/esnext.map.merge.js"),require("core-js/modules/esnext.map.reduce.js"),require("core-js/modules/esnext.map.some.js"),require("core-js/modules/esnext.map.update.js");var e,jsEnv=_interopRequireWildcard(require("browser-or-node")),_config=require("./config");function _interopRequireWildcard(s,r){if(!r&&s&&s.__esModule)return s;var t,a,o=Object.defineProperty,u={__proto__:null,default:s};if(Object(s)!==s)return u;if(e||"function"!=typeof WeakMap||(e=new WeakMap),e){if(e.has(s))return e.get(s);e.set(s,u)}for(a in s)"default"!==a&&{}.hasOwnProperty.call(s,a)&&((t=o&&Object.getOwnPropertyDescriptor(s,a))&&(t.get||t.set)?o(u,a,t):u[a]=s[a]);return u}const queues=new Map,withDatabaseLock=async e=>{if(!jsEnv.isNode)return e();const s=(0,_config.resolveDatabasePath)((0,_config.getSetting)("databasePath","database/")),r=(0,_config.trackDatabaseCall)(),t=queues.get(s)||Promise.resolve();let a;const o=new Promise((e=>{a=e})),u=t.then((()=>o));queues.set(s,u),await t;try{return await e()}finally{r(),a(),queues.get(s)===u&&queues.delete(s)}};exports.withDatabaseLock=withDatabaseLock;
@@ -2,9 +2,10 @@ import { ParsedQuery } from './parseQuery';
2
2
  export type ParsedCommand = string | 'delete' | 'insert' | 'read' | 'update';
3
3
  export type ParsedEntity = string;
4
4
  /**
5
- * Extract command clauses from the query.
5
+ * Read the command and record name from the start of a query's head (see splitClauses).
6
6
  * @param parsed
7
7
  * @param query
8
+ * @returns The rest of the head (a joined record, `.joined`, for findJoinEntity).
8
9
  */
9
10
  export declare const findCommand: (parsed: ParsedQuery, query: string) => string;
10
11
  //# sourceMappingURL=findCommand.d.ts.map
@@ -4,12 +4,12 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.findCommand = void 0;
7
- var _removeFromQuery = require("./removeFromQuery");
8
- const commandMatch = /^(delete|insert|read|update)\s+([a-z0-9_-]+)[\w\n]*/i;
7
+ const commandMatch = /^(delete|insert|read|update)\s+([a-z0-9_-]+)/i;
9
8
  /**
10
- * Extract command clauses from the query.
9
+ * Read the command and record name from the start of a query's head (see splitClauses).
11
10
  * @param parsed
12
11
  * @param query
12
+ * @returns The rest of the head (a joined record, `.joined`, for findJoinEntity).
13
13
  */
14
14
  const findCommand = (parsed, query) => {
15
15
  const commandFound = query.match(commandMatch);
@@ -18,7 +18,7 @@ const findCommand = (parsed, query) => {
18
18
  if (commandFound) {
19
19
  parsed['command'] = commandFound[1].toLowerCase();
20
20
  parsed['entity'] = commandFound[2];
21
- return (0, _removeFromQuery.removeFromQuery)(commandFound[0], query);
21
+ return query.slice(commandFound[0].length).trim();
22
22
  }
23
23
  return query;
24
24
  };
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findCommand=void 0;var _removeFromQuery=require("./removeFromQuery");const commandMatch=/^(delete|insert|read|update)\s+([a-z0-9_-]+)[\w\n]*/i,findCommand=(e,o)=>{const r=o.match(commandMatch);return e.command=void 0,e.entity=void 0,r?(e.command=r[1].toLowerCase(),e.entity=r[2],(0,_removeFromQuery.removeFromQuery)(r[0],o)):o};exports.findCommand=findCommand;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findCommand=void 0;const commandMatch=/^(delete|insert|read|update)\s+([a-z0-9_-]+)/i,findCommand=(e,t)=>{const d=t.match(commandMatch);return e.command=void 0,e.entity=void 0,d?(e.command=d[1].toLowerCase(),e.entity=d[2],t.slice(d[0].length).trim()):t};exports.findCommand=findCommand;
@@ -8,15 +8,14 @@ export type ParsedCondition = {
8
8
  type?: string;
9
9
  };
10
10
  /**
11
- * Extract the where clause from the query into a tree (see ConditionNode): `where` followed by conditions joined with
12
- * `and` / `or`, `not`, and brackets, nested to any depth. Each condition is `<field> <comparator> <value>`, or
13
- * `<field> is null`, `is not null`, `exists` or `does not exist`. A comparison with the literal null (`= null`) is
14
- * refused: it would never match, as null equals nothing (see where). The expression ends at the next clause keyword
15
- * outside brackets, so clauses may come in any order; several separate `where`s (or `and where` after another clause)
16
- * are joined with `and`, as before.
11
+ * Read a where clause body (see splitClauses) into a tree (see ConditionNode): conditions joined with `and` / `or`,
12
+ * `not`, and brackets, nested to any depth. Each condition is `<field> <comparator> <value>`, or `<field> is null`,
13
+ * `is not null`, `exists` or `does not exist`. A comparison with the literal null (`= null`) is refused: it would never
14
+ * match, as null equals nothing (see where).
17
15
  * @param parsed
18
- * @param query
19
- * @throws {Error} For the errors readExpression and readCondition describe, and `or where` after another clause.
16
+ * @param body
17
+ * @returns The rest of the body, after the expression.
18
+ * @throws {Error} For the errors readExpression and readCondition describe.
20
19
  */
21
- export declare const findConditions: (parsed: ParsedQuery, query: string) => string;
20
+ export declare const findConditions: (parsed: ParsedQuery, body: string) => string;
22
21
  //# sourceMappingURL=findConditions.d.ts.map
@@ -10,15 +10,13 @@ require("core-js/modules/esnext.iterator.flat-map.js");
10
10
  var _parseLiteralValue = require("./parseLiteralValue");
11
11
  var _conditionTree = require("../conditionTree");
12
12
  var _maskStrings = require("./maskStrings");
13
+ var _splitClauses = require("./splitClauses");
13
14
  // Keywords count only as whole words: in `on t.x = u.color where ...` the `or` is part of the name `color`
14
15
  const wordEnd = '(?![a-z0-9_.-])';
15
16
  const keyword = words => new RegExp(`^(?:${words})${wordEnd}`, 'i');
16
17
  // One condition, at the start of the text: a field, then `is [not] null` / `exists` / `does not exist`, or a comparator
17
18
  // and a value (strings and arrays are placeholders by then, see maskStrings)
18
19
  const conditionMatch = new RegExp(`^([a-z0-9_-]+\\.?[a-z0-9_-]*)\\s+(?:(is\\s+not\\s+null|is\\s+null|exists|does\\s+not\\s+exist)${wordEnd}|(<>|!=|>=|<=|=|>|<|in|between|like)\\s+(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false)))`, 'i');
19
- // The clause keywords that end a where expression (outside brackets), and `and on` (a join clause's own `and`)
20
- const expressionEnd = keyword('sort|group|limit|offset|select|merge|on|where|and\\s+on');
21
- const whereMatch = new RegExp(`(^|[^a-z0-9._-])((?:(and|or)\\s+)?where)${wordEnd}`, 'i');
22
20
  /**
23
21
  * Read one condition from a match of conditionMatch.
24
22
  * @param match
@@ -62,15 +60,15 @@ const join = (kind, nodes) => {
62
60
  };
63
61
  };
64
62
  /**
65
- * Read one where expression from the masked query, starting at `start` (just after its `where`). Precedence, loosest
66
- * first: `or`, `and`, `not`; brackets group. `and where` / `or where` are the older spellings of `and` / `or`.
63
+ * Read one where expression from the start of a where clause body (masked, see maskStrings). Precedence, loosest
64
+ * first: `or`, `and`, `not`; brackets group.
67
65
  * @param text
68
- * @param start
69
- * @returns The expression's tree and where it ends (the next clause keyword outside brackets, or the end).
70
- * @throws {Error} For an unbalanced bracket, empty brackets, or an operator with no condition after it.
66
+ * @returns The expression's tree and where it ends (at the first text that can't carry it on, or the end).
67
+ * @throws {Error} For an unbalanced bracket, empty brackets, an operator with no condition after it, or two conditions
68
+ * with no and/or between them.
71
69
  */
72
- const readExpression = (text, start) => {
73
- let at = start;
70
+ const readExpression = text => {
71
+ let at = 0;
74
72
  const rest = () => text.slice(at);
75
73
  const skipSpace = () => {
76
74
  at += rest().length - rest().trimStart().length;
@@ -93,6 +91,10 @@ const readExpression = (text, start) => {
93
91
  at += condition[0].length;
94
92
  return readCondition(condition);
95
93
  }
94
+ // The 2.x `and where` / `or where` inside brackets, where splitClauses doesn't look
95
+ if ((after === 'and' || after === 'or') && keyword('where').test(rest())) {
96
+ throw new Error(`"${after} where" is no longer supported: ${_splitClauses.whereHint}`);
97
+ }
96
98
  if (take(keyword('not'))) {
97
99
  return {
98
100
  not: factor('not')
@@ -115,11 +117,7 @@ const readExpression = (text, start) => {
115
117
  const term = after => {
116
118
  const nodes = [factor(after)];
117
119
  for (;;) {
118
- skipSpace();
119
- if (keyword('and\\s+on').test(rest())) {
120
- break;
121
- }
122
- const connector = take(keyword('and(?:\\s+where)?'));
120
+ const connector = take(keyword('and'));
123
121
  if (!connector) {
124
122
  break;
125
123
  }
@@ -130,7 +128,7 @@ const readExpression = (text, start) => {
130
128
  const expression = after => {
131
129
  const nodes = [term(after)];
132
130
  for (;;) {
133
- const connector = take(keyword('or(?:\\s+where)?'));
131
+ const connector = take(keyword('or'));
134
132
  if (!connector) {
135
133
  break;
136
134
  }
@@ -143,7 +141,7 @@ const readExpression = (text, start) => {
143
141
  if (rest().startsWith(')')) {
144
142
  throw new Error(`Unexpected ) in a where clause: ${shown()}`);
145
143
  }
146
- if (rest() && !expressionEnd.test(rest()) && conditionMatch.test(rest())) {
144
+ if (conditionMatch.test(rest())) {
147
145
  throw new Error(`Missing and/or before: ${shown()}`);
148
146
  }
149
147
  return {
@@ -152,31 +150,21 @@ const readExpression = (text, start) => {
152
150
  };
153
151
  };
154
152
  /**
155
- * Extract the where clause from the query into a tree (see ConditionNode): `where` followed by conditions joined with
156
- * `and` / `or`, `not`, and brackets, nested to any depth. Each condition is `<field> <comparator> <value>`, or
157
- * `<field> is null`, `is not null`, `exists` or `does not exist`. A comparison with the literal null (`= null`) is
158
- * refused: it would never match, as null equals nothing (see where). The expression ends at the next clause keyword
159
- * outside brackets, so clauses may come in any order; several separate `where`s (or `and where` after another clause)
160
- * are joined with `and`, as before.
153
+ * Read a where clause body (see splitClauses) into a tree (see ConditionNode): conditions joined with `and` / `or`,
154
+ * `not`, and brackets, nested to any depth. Each condition is `<field> <comparator> <value>`, or `<field> is null`,
155
+ * `is not null`, `exists` or `does not exist`. A comparison with the literal null (`= null`) is refused: it would never
156
+ * match, as null equals nothing (see where).
161
157
  * @param parsed
162
- * @param query
163
- * @throws {Error} For the errors readExpression and readCondition describe, and `or where` after another clause.
158
+ * @param body
159
+ * @returns The rest of the body, after the expression.
160
+ * @throws {Error} For the errors readExpression and readCondition describe.
164
161
  */
165
- const findConditions = (parsed, query) => {
166
- const expressions = [];
167
- for (let found = query.match(whereMatch); found; found = query.match(whereMatch)) {
168
- if ((found[3] || '').toLowerCase() === 'or') {
169
- throw new Error('`or where` must follow the conditions it joins: put them in one where clause');
170
- }
171
- const whereStart = found.index + found[1].length;
172
- const {
173
- node,
174
- end
175
- } = readExpression(query, whereStart + found[2].length);
176
- expressions.push(node);
177
- query = `${query.slice(0, whereStart)} ${query.slice(end)}`.replace(/\s+/g, ' ').trim();
178
- }
179
- parsed['where'] = expressions.length ? join('and', expressions) : (0, _conditionTree.allOf)([]);
180
- return query;
162
+ const findConditions = (parsed, body) => {
163
+ const {
164
+ node,
165
+ end
166
+ } = readExpression(body);
167
+ parsed['where'] = node;
168
+ return body.slice(end).trim();
181
169
  };
182
170
  exports.findConditions = findConditions;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findConditions=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.flat-map.js");var _parseLiteralValue=require("./parseLiteralValue"),_conditionTree=require("../conditionTree"),_maskStrings=require("./maskStrings");const wordEnd="(?![a-z0-9_.-])",keyword=e=>new RegExp(`^(?:${e})${wordEnd}`,"i"),conditionMatch=new RegExp(`^([a-z0-9_-]+\\.?[a-z0-9_-]*)\\s+(?:(is\\s+not\\s+null|is\\s+null|exists|does\\s+not\\s+exist)${wordEnd}|(<>|!=|>=|<=|=|>|<|in|between|like)\\s+(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false)))`,"i"),expressionEnd=keyword("sort|group|limit|offset|select|merge|on|where|and\\s+on"),whereMatch=new RegExp(`(^|[^a-z0-9._-])((?:(and|or)\\s+)?where)${wordEnd}`,"i"),readCondition=e=>{const r=e[1];if(void 0!==e[2])return{property:r,comparator:e[2].toLowerCase().replace(/\s+/g," "),value:null};const n=e[3].toLowerCase(),t=(0,_parseLiteralValue.parseLiteralValue)(e,5);if(null===t)throw new Error(`\`${r} ${n} null\` never matches (null equals nothing, as in SQL): use \`${r} is null\` or \`${r} is not null\``);if("between"===n&&Array.isArray(t)&&t.includes(null))throw new Error(`\`${r} between\` can't have a null bound: it would never match`);return{property:r,comparator:n,value:t}},join=(e,r)=>{const n=r.flatMap((r=>!(0,_conditionTree.isCondition)(r)&&e in r?r[e]:[r]));return 1===n.length?n[0]:"and"===e?{and:n}:{or:n}},readExpression=(e,r)=>{let n=r;const t=()=>e.slice(n),o=()=>{n+=t().length-t().trimStart().length},s=e=>{o();const r=t().match(e);if(r)return n+=r[0].length,r[0]},i=()=>(0,_maskStrings.unmaskStrings)(t().trim(),"'"),a=e=>{o();const r=t().match(conditionMatch);if(r)return n+=r[0].length,readCondition(r);if(s(keyword("not")))return{not:a("not")};if(s(/^\(/)){if(o(),t().startsWith(")"))throw new Error("Empty brackets in a where clause");const e=d("(");if(!s(/^\)/))throw new Error("Missing ) "+(t().trim()?`before: ${i()}`:"at the end of the where clause"));return e}throw new Error(`Missing a condition after ${e}${t().trim()?`: ${i()}`:""}`)},l=e=>{const r=[a(e)];for(;o(),!keyword("and\\s+on").test(t());){const e=s(keyword("and(?:\\s+where)?"));if(!e)break;r.push(a(e.toLowerCase().replace(/\s+/g," ")))}return join("and",r)},d=e=>{const r=[l(e)];for(;;){const e=s(keyword("or(?:\\s+where)?"));if(!e)break;r.push(l(e.toLowerCase().replace(/\s+/g," ")))}return join("or",r)},c=d("where");if(o(),t().startsWith(")"))throw new Error(`Unexpected ) in a where clause: ${i()}`);if(t()&&!expressionEnd.test(t())&&conditionMatch.test(t()))throw new Error(`Missing and/or before: ${i()}`);return{node:c,end:n}},findConditions=(e,r)=>{const n=[];for(let e=r.match(whereMatch);e;e=r.match(whereMatch)){if("or"===(e[3]||"").toLowerCase())throw new Error("`or where` must follow the conditions it joins: put them in one where clause");const t=e.index+e[1].length,{node:o,end:s}=readExpression(r,t+e[2].length);n.push(o),r=`${r.slice(0,t)} ${r.slice(s)}`.replace(/\s+/g," ").trim()}return e.where=n.length?join("and",n):(0,_conditionTree.allOf)([]),r};exports.findConditions=findConditions;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findConditions=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.flat-map.js");var _parseLiteralValue=require("./parseLiteralValue"),_conditionTree=require("../conditionTree"),_maskStrings=require("./maskStrings"),_splitClauses=require("./splitClauses");const wordEnd="(?![a-z0-9_.-])",keyword=e=>new RegExp(`^(?:${e})${wordEnd}`,"i"),conditionMatch=new RegExp(`^([a-z0-9_-]+\\.?[a-z0-9_-]*)\\s+(?:(is\\s+not\\s+null|is\\s+null|exists|does\\s+not\\s+exist)${wordEnd}|(<>|!=|>=|<=|=|>|<|in|between|like)\\s+(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false)))`,"i"),readCondition=e=>{const r=e[1];if(void 0!==e[2])return{property:r,comparator:e[2].toLowerCase().replace(/\s+/g," "),value:null};const n=e[3].toLowerCase(),t=(0,_parseLiteralValue.parseLiteralValue)(e,5);if(null===t)throw new Error(`\`${r} ${n} null\` never matches (null equals nothing, as in SQL): use \`${r} is null\` or \`${r} is not null\``);if("between"===n&&Array.isArray(t)&&t.includes(null))throw new Error(`\`${r} between\` can't have a null bound: it would never match`);return{property:r,comparator:n,value:t}},join=(e,r)=>{const n=r.flatMap((r=>!(0,_conditionTree.isCondition)(r)&&e in r?r[e]:[r]));return 1===n.length?n[0]:"and"===e?{and:n}:{or:n}},readExpression=e=>{let r=0;const n=()=>e.slice(r),t=()=>{r+=n().length-n().trimStart().length},o=e=>{t();const o=n().match(e);if(o)return r+=o[0].length,o[0]},s=()=>(0,_maskStrings.unmaskStrings)(n().trim(),"'"),i=e=>{t();const a=n().match(conditionMatch);if(a)return r+=a[0].length,readCondition(a);if(("and"===e||"or"===e)&&keyword("where").test(n()))throw new Error(`"${e} where" is no longer supported: ${_splitClauses.whereHint}`);if(o(keyword("not")))return{not:i("not")};if(o(/^\(/)){if(t(),n().startsWith(")"))throw new Error("Empty brackets in a where clause");const e=l("(");if(!o(/^\)/))throw new Error("Missing ) "+(n().trim()?`before: ${s()}`:"at the end of the where clause"));return e}throw new Error(`Missing a condition after ${e}${n().trim()?`: ${s()}`:""}`)},a=e=>{const r=[i(e)];for(;;){const e=o(keyword("and"));if(!e)break;r.push(i(e.toLowerCase().replace(/\s+/g," ")))}return join("and",r)},l=e=>{const r=[a(e)];for(;;){const e=o(keyword("or"));if(!e)break;r.push(a(e.toLowerCase().replace(/\s+/g," ")))}return join("or",r)},u=l("where");if(t(),n().startsWith(")"))throw new Error(`Unexpected ) in a where clause: ${s()}`);if(conditionMatch.test(n()))throw new Error(`Missing and/or before: ${s()}`);return{node:u,end:r}},findConditions=(e,r)=>{const{node:n,end:t}=readExpression(r);return e.where=n,r.slice(t).trim()};exports.findConditions=findConditions;
@@ -1,9 +1,11 @@
1
1
  import { ParsedQuery } from './parseQuery';
2
- export type ParsedGroupBy = string;
2
+ export type ParsedGroupBy = string[];
3
3
  /**
4
- * Extract the group by clauses from the query.
4
+ * Read a group by clause body (`a, b`, see splitClauses).
5
5
  * @param parsed
6
- * @param query
6
+ * @param body
7
+ * @returns The rest of the body, after the last field.
8
+ * @throws {Error} For a field named twice.
7
9
  */
8
- export declare const findGroupBy: (parsed: ParsedQuery, query: string) => string;
10
+ export declare const findGroupBy: (parsed: ParsedQuery, body: string) => string;
9
11
  //# sourceMappingURL=findGroupBy.d.ts.map
@@ -4,19 +4,27 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.findGroupBy = void 0;
7
- var _removeFromQuery = require("./removeFromQuery");
8
- const groupMatch = /group\s+by\s+(,?[a-z0-9._-]+)+/i;
7
+ require("core-js/modules/esnext.iterator.constructor.js");
8
+ require("core-js/modules/esnext.iterator.find.js");
9
+ const groupMatch = /^[a-z0-9._-]+(?:\s*,\s*[a-z0-9._-]+)*/i;
9
10
  /**
10
- * Extract the group by clauses from the query.
11
+ * Read a group by clause body (`a, b`, see splitClauses).
11
12
  * @param parsed
12
- * @param query
13
+ * @param body
14
+ * @returns The rest of the body, after the last field.
15
+ * @throws {Error} For a field named twice.
13
16
  */
14
- const findGroupBy = (parsed, query) => {
15
- const groupFound = query.match(groupMatch);
17
+ const findGroupBy = (parsed, body) => {
18
+ const groupFound = body.match(groupMatch);
16
19
  if (groupFound) {
17
- parsed['groupBy'] = groupFound ? groupFound[1] : undefined;
18
- return (0, _removeFromQuery.removeFromQuery)(groupFound[0], query);
20
+ const fields = groupFound[0].split(/\s*,\s*/);
21
+ const repeated = fields.find((field, index) => fields.indexOf(field) !== index);
22
+ if (repeated) {
23
+ throw new Error(`"${repeated}" is in group by twice`);
24
+ }
25
+ parsed['groupBy'] = fields;
26
+ return body.slice(groupFound[0].length).trim();
19
27
  }
20
- return query;
28
+ return body;
21
29
  };
22
30
  exports.findGroupBy = findGroupBy;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findGroupBy=void 0;var _removeFromQuery=require("./removeFromQuery");const groupMatch=/group\s+by\s+(,?[a-z0-9._-]+)+/i,findGroupBy=(r,e)=>{const o=e.match(groupMatch);return o?(r.groupBy=o?o[1]:void 0,(0,_removeFromQuery.removeFromQuery)(o[0],e)):e};exports.findGroupBy=findGroupBy;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findGroupBy=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.find.js");const groupMatch=/^[a-z0-9._-]+(?:\s*,\s*[a-z0-9._-]+)*/i,findGroupBy=(r,e)=>{const t=e.match(groupMatch);if(t){const o=t[0].split(/\s*,\s*/),s=o.find(((r,e)=>o.indexOf(r)!==e));if(s)throw new Error(`"${s}" is in group by twice`);return r.groupBy=o,e.slice(t[0].length).trim()}return e};exports.findGroupBy=findGroupBy;
@@ -3,11 +3,12 @@ export type ParsedInsertRow = {
3
3
  [field: string]: any;
4
4
  };
5
5
  /**
6
- * Extract an insert's "values field = value, field2 = value2" clause from the query, the same way
6
+ * Read an insert's values clause body (`field = value, field2 = value2`, see splitClauses), the same way
7
7
  * findUpdateValues() does for "set". insertEntity() accepts an array of rows for bulk inserts, but
8
8
  * a query string only describes a single row for now - wrapped in a one-element array to match.
9
9
  * @param parsed
10
- * @param query
10
+ * @param body
11
+ * @returns The rest of the body, after the last assignment.
11
12
  */
12
- export declare const findInsertValues: (parsed: ParsedQuery, query: string) => string;
13
+ export declare const findInsertValues: (parsed: ParsedQuery, body: string) => string;
13
14
  //# sourceMappingURL=findInsertValues.d.ts.map