json-fs-query 3.2.0 → 3.3.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 (121) hide show
  1. package/README.md +257 -15
  2. package/browser/jsonFsQuery.js +2253 -293
  3. package/browser/jsonFsQuery.min.js +1 -1
  4. package/dist/data/introspect/alter.d.ts +12 -0
  5. package/dist/data/introspect/alter.js +304 -0
  6. package/dist/data/introspect/alter.min.js +1 -0
  7. package/dist/data/introspect/create.d.ts +10 -0
  8. package/dist/data/introspect/create.js +2 -1
  9. package/dist/data/introspect/create.min.js +1 -1
  10. package/dist/data/introspect/drop.js +2 -0
  11. package/dist/data/introspect/drop.min.js +1 -1
  12. package/dist/data/introspect/formatDatabase.d.ts +17 -0
  13. package/dist/data/introspect/formatDatabase.js +56 -0
  14. package/dist/data/introspect/formatDatabase.min.js +1 -0
  15. package/dist/data/introspect/index.d.ts +9 -0
  16. package/dist/data/introspect/index.js +18 -5
  17. package/dist/data/introspect/index.min.js +1 -1
  18. package/dist/data/introspect/packDatabase.d.ts +27 -0
  19. package/dist/data/introspect/packDatabase.js +113 -0
  20. package/dist/data/introspect/packDatabase.min.js +1 -0
  21. package/dist/data/queries/databaseStatements.d.ts +31 -0
  22. package/dist/data/queries/databaseStatements.js +97 -0
  23. package/dist/data/queries/databaseStatements.min.js +1 -0
  24. package/dist/data/queries/deleteEntity.js +6 -1
  25. package/dist/data/queries/deleteEntity.min.js +1 -1
  26. package/dist/data/queries/insertEntity.d.ts +8 -0
  27. package/dist/data/queries/insertEntity.js +338 -33
  28. package/dist/data/queries/insertEntity.min.js +1 -1
  29. package/dist/data/queries/query.d.ts +2 -1
  30. package/dist/data/queries/query.js +5 -2
  31. package/dist/data/queries/query.min.js +1 -1
  32. package/dist/data/queries/selectFields.d.ts +5 -4
  33. package/dist/data/queries/selectFields.js +25 -11
  34. package/dist/data/queries/selectFields.min.js +1 -1
  35. package/dist/data/queries/updateEntity.js +39 -5
  36. package/dist/data/queries/updateEntity.min.js +1 -1
  37. package/dist/data/storage/databases.d.ts +32 -0
  38. package/dist/data/storage/databases.js +93 -0
  39. package/dist/data/storage/databases.min.js +1 -0
  40. package/dist/data/storage/fileExists.d.ts +2 -1
  41. package/dist/data/storage/fileExists.js +9 -2
  42. package/dist/data/storage/fileExists.min.js +1 -1
  43. package/dist/data/storage/index.d.ts +5 -2
  44. package/dist/data/storage/index.js +64 -1
  45. package/dist/data/storage/index.min.js +1 -1
  46. package/dist/data/storage/listFiles.d.ts +8 -0
  47. package/dist/data/storage/listFiles.js +52 -0
  48. package/dist/data/storage/listFiles.min.js +1 -0
  49. package/dist/data/storage/makeDirectory.d.ts +2 -1
  50. package/dist/data/storage/makeDirectory.js +8 -2
  51. package/dist/data/storage/makeDirectory.min.js +1 -1
  52. package/dist/data/storage/readJson.d.ts +9 -1
  53. package/dist/data/storage/readJson.js +20 -7
  54. package/dist/data/storage/readJson.min.js +1 -1
  55. package/dist/data/storage/removeDirectory.d.ts +3 -1
  56. package/dist/data/storage/removeDirectory.js +7 -1
  57. package/dist/data/storage/removeDirectory.min.js +1 -1
  58. package/dist/data/storage/removeFile.d.ts +2 -1
  59. package/dist/data/storage/removeFile.js +10 -1
  60. package/dist/data/storage/removeFile.min.js +1 -1
  61. package/dist/data/storage/storagePath.js +1 -1
  62. package/dist/data/storage/storagePath.min.js +1 -1
  63. package/dist/data/storage/writeJson.d.ts +12 -3
  64. package/dist/data/storage/writeJson.js +21 -7
  65. package/dist/data/storage/writeJson.min.js +1 -1
  66. package/dist/data/storage/writeSet.d.ts +64 -0
  67. package/dist/data/storage/writeSet.js +306 -0
  68. package/dist/data/storage/writeSet.min.js +1 -0
  69. package/dist/data/utilities/config.d.ts +37 -0
  70. package/dist/data/utilities/config.js +77 -5
  71. package/dist/data/utilities/config.min.js +1 -1
  72. package/dist/data/utilities/databaseLock.d.ts +5 -0
  73. package/dist/data/utilities/databaseLock.js +15 -3
  74. package/dist/data/utilities/databaseLock.min.js +1 -1
  75. package/dist/data/utilities/getConditionIndexedList.js +3 -4
  76. package/dist/data/utilities/getConditionIndexedList.min.js +1 -1
  77. package/dist/data/utilities/indexBuffer.d.ts +35 -0
  78. package/dist/data/utilities/indexBuffer.js +139 -0
  79. package/dist/data/utilities/indexBuffer.min.js +1 -0
  80. package/dist/data/utilities/joinBases.js +2 -10
  81. package/dist/data/utilities/joinBases.min.js +1 -1
  82. package/dist/data/utilities/knownFields.d.ts +26 -0
  83. package/dist/data/utilities/knownFields.js +128 -0
  84. package/dist/data/utilities/knownFields.min.js +1 -0
  85. package/dist/data/utilities/packedTable.d.ts +34 -0
  86. package/dist/data/utilities/packedTable.js +60 -0
  87. package/dist/data/utilities/packedTable.min.js +1 -0
  88. package/dist/data/utilities/parsers/findInsertValues.d.ts +3 -3
  89. package/dist/data/utilities/parsers/findInsertValues.js +28 -12
  90. package/dist/data/utilities/parsers/findInsertValues.min.js +1 -1
  91. package/dist/data/utilities/parsers/maskStrings.js +11 -2
  92. package/dist/data/utilities/parsers/maskStrings.min.js +1 -1
  93. package/dist/data/utilities/parsers/parseAlter.d.ts +33 -0
  94. package/dist/data/utilities/parsers/parseAlter.js +84 -0
  95. package/dist/data/utilities/parsers/parseAlter.min.js +1 -0
  96. package/dist/data/utilities/parsers/parseCreate.d.ts +14 -0
  97. package/dist/data/utilities/parsers/parseCreate.js +13 -5
  98. package/dist/data/utilities/parsers/parseCreate.min.js +1 -1
  99. package/dist/data/utilities/parsers/parseDatabaseStatement.d.ts +17 -0
  100. package/dist/data/utilities/parsers/parseDatabaseStatement.js +82 -0
  101. package/dist/data/utilities/parsers/parseDatabaseStatement.min.js +1 -0
  102. package/dist/data/utilities/parsers/parseQuery.d.ts +3 -0
  103. package/dist/data/utilities/parsers/parseQuery.js +24 -0
  104. package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
  105. package/dist/data/utilities/parsers/startsQuery.js +1 -1
  106. package/dist/data/utilities/parsers/startsQuery.min.js +1 -1
  107. package/dist/data/utilities/retrieveFiles.d.ts +15 -0
  108. package/dist/data/utilities/retrieveFiles.js +48 -0
  109. package/dist/data/utilities/retrieveFiles.min.js +1 -0
  110. package/dist/data/utilities/retrieveRecord.d.ts +6 -0
  111. package/dist/data/utilities/retrieveRecord.js +24 -1
  112. package/dist/data/utilities/retrieveRecord.min.js +1 -1
  113. package/dist/data/utilities/retrieveRecords.d.ts +22 -0
  114. package/dist/data/utilities/retrieveRecords.js +60 -10
  115. package/dist/data/utilities/retrieveRecords.min.js +1 -1
  116. package/dist/data/utilities/runQuery.js +27 -2
  117. package/dist/data/utilities/runQuery.min.js +1 -1
  118. package/dist/index.d.ts +1 -1
  119. package/dist/index.js +24 -0
  120. package/dist/index.min.js +1 -1
  121. package/package.json +7 -3
@@ -4,13 +4,44 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.insertEntity = void 0;
7
+ require("core-js/modules/es.array.includes.js");
8
+ require("core-js/modules/es.json.stringify.js");
7
9
  require("core-js/modules/esnext.iterator.constructor.js");
10
+ require("core-js/modules/esnext.iterator.every.js");
8
11
  require("core-js/modules/esnext.iterator.find.js");
12
+ require("core-js/modules/esnext.iterator.for-each.js");
9
13
  require("core-js/modules/esnext.iterator.map.js");
10
14
  require("core-js/modules/esnext.iterator.some.js");
15
+ require("core-js/modules/esnext.map.delete-all.js");
16
+ require("core-js/modules/esnext.map.every.js");
17
+ require("core-js/modules/esnext.map.filter.js");
18
+ require("core-js/modules/esnext.map.find.js");
19
+ require("core-js/modules/esnext.map.find-key.js");
20
+ require("core-js/modules/esnext.map.includes.js");
21
+ require("core-js/modules/esnext.map.key-of.js");
22
+ require("core-js/modules/esnext.map.map-keys.js");
23
+ require("core-js/modules/esnext.map.map-values.js");
24
+ require("core-js/modules/esnext.map.merge.js");
25
+ require("core-js/modules/esnext.map.reduce.js");
26
+ require("core-js/modules/esnext.map.some.js");
27
+ require("core-js/modules/esnext.map.update.js");
28
+ require("core-js/modules/esnext.set.add-all.js");
29
+ require("core-js/modules/esnext.set.delete-all.js");
30
+ require("core-js/modules/esnext.set.difference.js");
31
+ require("core-js/modules/esnext.set.every.js");
32
+ require("core-js/modules/esnext.set.filter.js");
33
+ require("core-js/modules/esnext.set.find.js");
34
+ require("core-js/modules/esnext.set.intersection.js");
35
+ require("core-js/modules/esnext.set.is-disjoint-from.js");
36
+ require("core-js/modules/esnext.set.is-subset-of.js");
37
+ require("core-js/modules/esnext.set.is-superset-of.js");
38
+ require("core-js/modules/esnext.set.join.js");
39
+ require("core-js/modules/esnext.set.map.js");
40
+ require("core-js/modules/esnext.set.reduce.js");
41
+ require("core-js/modules/esnext.set.some.js");
42
+ require("core-js/modules/esnext.set.symmetric-difference.js");
43
+ require("core-js/modules/esnext.set.union.js");
11
44
  var _retrieveRecord = require("../utilities/retrieveRecord");
12
- var _updateIndex = require("../utilities/updateIndex");
13
- var _keyGenerate = require("../introspect/policies/keyGenerate");
14
45
  var _keyUnique = require("../introspect/policies/keyUnique");
15
46
  var _useDefault = require("../introspect/policies/useDefault");
16
47
  var _typeCheck = require("../introspect/policies/typeCheck");
@@ -18,26 +49,290 @@ var _foreignKeyExists = require("../introspect/policies/foreignKeyExists");
18
49
  var _splitEntityProperty = require("../utilities/parsers/splitEntityProperty");
19
50
  var _computeKeyValue = require("../utilities/computeKeyValue");
20
51
  var _fieldType = require("../utilities/fieldType");
52
+ var _compareAs = require("../utilities/compareAs");
53
+ var _retrieveRecords = require("../utilities/retrieveRecords");
54
+ var _isSingleFieldKey = require("../utilities/isSingleFieldKey");
55
+ var _indexBuffer = require("../utilities/indexBuffer");
21
56
  var _storage = require("../storage");
57
+ var _packedTable = require("../utilities/packedTable");
58
+ var _knownFields = require("../utilities/knownFields");
59
+ /**
60
+ * The rows of a record that hold each combination of values for some fields, compared field by field by type, as
61
+ * where's = compares them: built from the rows in storage once per statement, for a key no index can answer (several
62
+ * fields, or no index file), instead of looking at every row in storage once per inserted row.
63
+ */
64
+ class RowValues {
65
+ constructor(record, fields) {
66
+ this.seen = new Set();
67
+ this.fields = fields;
68
+ this.types = fields.map(field => (0, _fieldType.fieldType)(record, field));
69
+ }
70
+ /**
71
+ * Whether these fields' values can be compared this way: every field of a comparable type (see compareAs).
72
+ * @param record
73
+ * @param fields
74
+ */
75
+ static usable(record, fields) {
76
+ return fields.every(field => _compareAs.comparableTypes.includes((0, _fieldType.fieldType)(record, field)));
77
+ }
78
+ /**
79
+ * The values' key, or undefined when one is null or missing (where's = never matches those) or compareAs leaves it an
80
+ * object (compared by identity, so not by a key).
81
+ * @param row
82
+ */
83
+ keyOf(row) {
84
+ const typed = [];
85
+ for (let position = 0; position < this.fields.length; position++) {
86
+ const value = row[this.fields[position]];
87
+ if (value === null || typeof value === 'undefined') {
88
+ return undefined;
89
+ }
90
+ const key = (0, _compareAs.compareAs)(this.types[position], value);
91
+ if (typeof key === 'object') {
92
+ return undefined;
93
+ }
94
+ typed.push([typeof key, key]);
95
+ }
96
+ return JSON.stringify(typed);
97
+ }
98
+ add(row) {
99
+ const key = this.keyOf(row);
100
+ if (typeof key !== 'undefined') {
101
+ this.seen.add(key);
102
+ }
103
+ }
104
+ has(row) {
105
+ const key = this.keyOf(row);
106
+ return typeof key !== 'undefined' && this.seen.has(key);
107
+ }
108
+ }
109
+ /**
110
+ * One insert statement's view of the keys it checks, so that each index is read and parsed once for all its rows
111
+ * instead of once per row, and the record file and its indexes are written once at the end instead of once per row.
112
+ *
113
+ * It answers every check exactly as reading the files after each row would (as inserts did one row at a time): the
114
+ * record's own indexes are held in memory (see IndexBuffer) and gain each row as it's added, so a row is checked
115
+ * against the rows before it in the statement too; another record's index is read once, since an insert never changes
116
+ * another record. Where a check can't use an index it looks at every row in storage as before, plus this statement's
117
+ * rows, which aren't in storage until the end.
118
+ */
119
+ class InsertStatement {
120
+ constructor(entity, record) {
121
+ this.rows = [];
122
+ this.buffers = new Map();
123
+ this.unreadable = new Set();
124
+ this.otherLookups = new Map();
125
+ this.scans = new Map();
126
+ this.entity = entity;
127
+ this.record = record;
128
+ this.hadEntries = (record.entries || []).length > 0;
129
+ }
130
+ /**
131
+ * Read the record's index files, once. An index that can't be read starts empty (as updateIndex starts one) and its
132
+ * key is checked by looking at every row.
133
+ */
134
+ async load() {
135
+ for (const key of this.record.keys) {
136
+ if (!key.lookup) {
137
+ continue;
138
+ }
139
+ let entries = [];
140
+ try {
141
+ entries = await (0, _storage.readJson)(`__indexes/${this.record.path}/${key.lookup}`);
142
+ } catch (error) {
143
+ this.unreadable.add(key);
144
+ }
145
+ this.buffers.set(key, new _indexBuffer.IndexBuffer(Array.isArray(entries) ? entries : [], (0, _fieldType.keyValueType)(this.record, key)));
146
+ }
147
+ }
148
+ /**
149
+ * How to look up a non-null value of `property` in the record being inserted into: through its index buffer, which
150
+ * holds the statement's rows so far. indexHas reads a record with no entries as holding nothing, whatever its index
151
+ * file says, so until this statement adds a row to a record that had none, nothing is found.
152
+ * @param property
153
+ */
154
+ ownLookup(property) {
155
+ const key = this.record.keys.find(key => (0, _isSingleFieldKey.isSingleFieldKey)(key, property) && key.lookup);
156
+ if (!key || this.unreadable.has(key)) {
157
+ return () => undefined;
158
+ }
159
+ const buffer = this.buffers.get(key);
160
+ return value => (this.hadEntries || this.rows.length > 0) && buffer.has(value);
161
+ }
162
+ /**
163
+ * How to look up a non-null value of `property` in another record, read once (an insert doesn't change it).
164
+ * @param entity
165
+ * @param property
166
+ */
167
+ otherLookup(entity, property) {
168
+ const name = `${entity}\u0000${property}`;
169
+ if (!this.otherLookups.has(name)) {
170
+ this.otherLookups.set(name, (async () => {
171
+ const record = await (0, _retrieveRecord.retrieveRecord)(entity);
172
+ const key = record.keys.find(key => (0, _isSingleFieldKey.isSingleFieldKey)(key, property) && key.lookup);
173
+ if (!key) {
174
+ return () => undefined;
175
+ }
176
+ if (!(record.entries || []).length) {
177
+ return () => false;
178
+ }
179
+ let entries;
180
+ try {
181
+ entries = await (0, _storage.readJson)(`__indexes/${record.path}/${key.lookup}`);
182
+ } catch (error) {
183
+ return () => undefined;
184
+ }
185
+ const buffer = new _indexBuffer.IndexBuffer(Array.isArray(entries) ? entries : [], (0, _fieldType.fieldType)(record, property));
186
+ return value => buffer.has(value);
187
+ })());
188
+ }
189
+ return this.otherLookups.get(name);
190
+ }
191
+ /**
192
+ * The values of these fields in every row of `entity` (read once per statement), plus, for the record being inserted
193
+ * into, every row this statement adds; undefined when a field's type can't be compared by key.
194
+ * @param entity
195
+ * @param fields
196
+ */
197
+ scan(entity, fields) {
198
+ const name = `${entity}\u0000${fields.join('\u0000')}`;
199
+ if (!this.scans.has(name)) {
200
+ this.scans.set(name, (async () => {
201
+ const record = entity === this.entity ? this.record : await (0, _retrieveRecord.retrieveRecord)(entity);
202
+ if (!RowValues.usable(record, fields)) {
203
+ return undefined;
204
+ }
205
+ const values = new RowValues(record, fields);
206
+ // The record's rows in storage: this statement's own rows aren't there until the end, and are added as they come
207
+ for (const row of await (0, _retrieveRecords.retrieveRecords)(entity)) {
208
+ values.add(row);
209
+ }
210
+ if (entity === this.entity) {
211
+ this.rows.forEach(row => values.add(row));
212
+ }
213
+ return values;
214
+ })());
215
+ }
216
+ return this.scans.get(name);
217
+ }
218
+ /**
219
+ * Whether one of this statement's rows already holds these values in every one of these fields (none of them null).
220
+ * @param fields
221
+ * @param values
222
+ */
223
+ claimed(fields, values) {
224
+ return this.rows.some(row => fields.every(field => row[field] !== null && typeof row[field] !== 'undefined' && (0, _compareAs.sameValue)((0, _fieldType.fieldType)(this.record, field), row[field], values[field])));
225
+ }
226
+ /**
227
+ * Whether no row (in storage or earlier in this statement) holds these values for these fields, as keyUnique answers
228
+ * it after each earlier row is written.
229
+ * @param fields
230
+ * @param values
231
+ */
232
+ async isUnique(fields, values) {
233
+ if (fields.length === 1) {
234
+ const found = this.ownLookup(fields[0])(values[fields[0]]);
235
+ if (typeof found !== 'undefined') {
236
+ return !found;
237
+ }
238
+ }
239
+ const scanned = await this.scan(this.entity, fields);
240
+ if (scanned) {
241
+ return !scanned.has(values);
242
+ }
243
+ return (await (0, _keyUnique.keyUnique)(this.entity, fields, fields.map(field => values[field]))) && !this.claimed(fields, values);
244
+ }
245
+ /**
246
+ * Whether `entity` holds `value` in `property`, as foreignKeyExists answers it after each earlier row is written: a
247
+ * row may reference one added earlier in the same statement when the key points at its own record.
248
+ * @param entity
249
+ * @param property
250
+ * @param value
251
+ */
252
+ async exists(entity, property, value) {
253
+ const own = entity === this.entity;
254
+ const found = own ? this.ownLookup(property)(value) : (await this.otherLookup(entity, property))(value);
255
+ if (typeof found !== 'undefined') {
256
+ return found;
257
+ }
258
+ const scanned = await this.scan(entity, [property]);
259
+ if (scanned) {
260
+ return scanned.has({
261
+ [property]: value
262
+ });
263
+ }
264
+ return (await (0, _foreignKeyExists.foreignKeyExists)(entity, property, value)) || own && this.claimed([property], {
265
+ [property]: value
266
+ });
267
+ }
268
+ /**
269
+ * Note a row added to the statement, filed under `fileName`, in the record's entries and every index buffer.
270
+ * @param entityValues
271
+ * @param fileName
272
+ */
273
+ async add(entityValues, fileName) {
274
+ this.rows.push(entityValues);
275
+ // Scans already built for this record hold the rows in storage; a row added now joins them
276
+ for (const [name, scanned] of this.scans) {
277
+ if (name.startsWith(`${this.entity}\u0000`)) {
278
+ (await scanned)?.add(entityValues);
279
+ }
280
+ }
281
+ this.record.entries.push(fileName);
282
+ for (const [key, buffer] of this.buffers) {
283
+ buffer.add((0, _computeKeyValue.computeKeyValue)(key.fields, entityValues), fileName);
284
+ }
285
+ }
286
+ /**
287
+ * Write the record file and each index once, with every row the statement added.
288
+ */
289
+ async write() {
290
+ await (0, _storage.writeJson)(`${this.record.path}.json`, this.record);
291
+ if (this.buffers.size) {
292
+ await (0, _storage.makeDirectory)(`__indexes/${this.record.path}`);
293
+ }
294
+ for (const [key, buffer] of this.buffers) {
295
+ await (0, _storage.writeJson)(`__indexes/${this.record.path}/${key.lookup}`, buffer.entries);
296
+ }
297
+ }
298
+ }
299
+ /**
300
+ * A new id for an autoGenerate field that no row holds yet, in storage or earlier in the statement.
301
+ * @param statement
302
+ * @param field
303
+ */
304
+ const generateKey = async (statement, field) => {
305
+ let uuid;
306
+ do {
307
+ uuid = crypto.randomUUID();
308
+ } while (!(await statement.isUnique([field], {
309
+ [field]: uuid
310
+ })));
311
+ return uuid;
312
+ };
22
313
  /**
23
314
  * Build one entity's stored values from the raw input values, generating any autoGenerate
24
- * fields and applying field defaults, then throw if a required field still has no value.
25
- * @param entity
26
- * @param record
315
+ * fields and applying field defaults, then throw if a required field still has no value. A null
316
+ * in an optional field that isn't nullable means the same as leaving it out (the field can't
317
+ * hold null, so nothing is lost): it isn't stored, and a default fills it in as for a field left out.
318
+ * @param statement
27
319
  * @param rowValues
28
320
  */
29
- const buildEntityValues = async (entity, record, rowValues = {}) => {
321
+ const buildEntityValues = async (statement, rowValues = {}) => {
30
322
  const entityValues = {};
31
- for (const field of record.definition) {
323
+ for (const field of statement.record.definition) {
32
324
  let value = rowValues[field.name];
325
+ if (value === null && field.optional && !field.nullable) {
326
+ value = undefined;
327
+ }
33
328
  if (field.autoGenerate) {
34
- value = await (0, _keyGenerate.keyGenerate)(entity, [field.name]);
329
+ value = await generateKey(statement, field.name);
35
330
  } else if (Object.prototype.hasOwnProperty.call(field, 'default')) {
36
331
  value = (0, _useDefault.useDefault)(field['default'], value);
37
332
  }
38
333
  if (typeof value === 'undefined') {
39
334
  if (!field.optional) {
40
- throw new Error(`Missing required field "${field.name}" for ${entity}`);
335
+ throw new Error(`Missing required field "${field.name}" for ${statement.entity}`);
41
336
  }
42
337
  continue;
43
338
  }
@@ -49,34 +344,30 @@ const buildEntityValues = async (entity, record, rowValues = {}) => {
49
344
  * Reject the insert if it collides with an existing primary or unique key value - a single field,
50
345
  * or a composite of several for a multi-field key. As in SQL, a value with a null in it is never a
51
346
  * duplicate, so a nullable unique field can hold null in any number of rows.
52
- * @param entity
53
- * @param record
347
+ * @param statement
54
348
  * @param entityValues
55
349
  */
56
- const assertUnique = async (entity, record, entityValues) => {
57
- for (const key of record.keys) {
350
+ const assertUnique = async (statement, entityValues) => {
351
+ for (const key of statement.record.keys) {
58
352
  if (key.type !== 'primary' && key.type !== 'unique') {
59
353
  continue;
60
354
  }
61
- const checkKeys = key.fields.map(field => entityValues[field]);
62
- if (checkKeys.some(value => value === null)) {
355
+ if (key.fields.some(field => entityValues[field] === null)) {
63
356
  continue;
64
357
  }
65
- const isUnique = await (0, _keyUnique.keyUnique)(entity, key.fields, checkKeys);
66
- if (!isUnique) {
67
- throw new Error(`Duplicate value for ${key.type} key "${key.fields.join(', ')}" on ${entity}`);
358
+ if (!(await statement.isUnique(key.fields, entityValues))) {
359
+ throw new Error(`Duplicate value for ${key.type} key "${key.fields.join(', ')}" on ${statement.entity}`);
68
360
  }
69
361
  }
70
362
  };
71
363
  /**
72
364
  * Reject the insert if a single-field foreign key's value doesn't exist in the record it
73
365
  * references. A null references nothing, so it isn't checked (as in SQL).
74
- * @param entity
75
- * @param record
366
+ * @param statement
76
367
  * @param entityValues
77
368
  */
78
- const assertForeignKeys = async (entity, record, entityValues) => {
79
- for (const key of record.keys) {
369
+ const assertForeignKeys = async (statement, entityValues) => {
370
+ for (const key of statement.record.keys) {
80
371
  if (key.type !== 'foreign' || key.fields.length !== 1 || !key.references || key.references.length !== 1) {
81
372
  continue;
82
373
  }
@@ -88,12 +379,19 @@ const assertForeignKeys = async (entity, record, entityValues) => {
88
379
  entity: refEntity,
89
380
  property: refProperty
90
381
  } = (0, _splitEntityProperty.splitEntityProperty)(key.references[0].fields[0]);
91
- const exists = await (0, _foreignKeyExists.foreignKeyExists)(refEntity, refProperty, entityValues[field]);
92
- if (!exists) {
93
- throw new Error(`Foreign key "${field}" on ${entity} references a nonexistent ${refEntity}.${refProperty} = ${entityValues[field]}`);
382
+ if (!(await statement.exists(refEntity, refProperty, entityValues[field]))) {
383
+ throw new Error(`Foreign key "${field}" on ${statement.entity} references a nonexistent ${refEntity}.${refProperty} = ${entityValues[field]}`);
94
384
  }
95
385
  }
96
386
  };
387
+ /**
388
+ * Insert rows into a record, all in one statement: every row is checked (against the rows in storage and the rows
389
+ * before it in the statement) and its entry file written, then the record file and each index are written once.
390
+ * Inside a write set (as every query runs, see withWriteSet) a row that fails leaves none of them written.
391
+ * @param entity
392
+ * @param values - The rows, as field -> value.
393
+ * @param dataSet
394
+ */
97
395
  const insertEntity = async (entity = '', values = [], dataSet = {}) => {
98
396
  const record = await (0, _retrieveRecord.retrieveRecord)(entity);
99
397
  const primaryKey = record.keys.find(key => key.type === 'primary' && key.fields.length === 1);
@@ -103,19 +401,26 @@ const insertEntity = async (entity = '', values = [], dataSet = {}) => {
103
401
  // The record's folder can be missing even though the record exists: git doesn't keep empty folders, so a database
104
402
  // committed before its first insert comes back from a fresh clone without it
105
403
  await (0, _storage.makeDirectory)(record.path);
404
+ if (!values.length) {
405
+ return dataSet;
406
+ }
407
+ // A field the record doesn't define would otherwise be dropped without a word
106
408
  for (const rowValues of values) {
107
- const entityValues = await buildEntityValues(entity, record, rowValues);
108
- await assertUnique(entity, record, entityValues);
109
- await assertForeignKeys(entity, record, entityValues);
409
+ (0, _knownFields.assertFieldsDefined)(entity, record, Object.keys(rowValues || {}), 'insert');
410
+ }
411
+ await (0, _packedTable.discardPacked)(record.path);
412
+ const statement = new InsertStatement(entity, record);
413
+ await statement.load();
414
+ for (const rowValues of values) {
415
+ const entityValues = await buildEntityValues(statement, rowValues);
416
+ await assertUnique(statement, entityValues);
417
+ await assertForeignKeys(statement, entityValues);
110
418
  const fileName = `${primaryKey ? entityValues[primaryKey.fields[0]] : crypto.randomUUID()}.json`;
111
419
  await (0, _storage.writeJson)(`${record.path}/${fileName}`, entityValues);
112
- record.entries.push(fileName);
113
- await (0, _storage.writeJson)(`${record.path}.json`, record);
114
- for (const key of record.keys) {
115
- await (0, _updateIndex.updateIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, entityValues), fileName, (0, _fieldType.keyValueType)(record, key));
116
- }
420
+ await statement.add(entityValues, fileName);
117
421
  dataSet[entity].push(entityValues);
118
422
  }
423
+ await statement.write();
119
424
  return dataSet;
120
425
  };
121
426
  exports.insertEntity = insertEntity;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.insertEntity=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js");var _retrieveRecord=require("../utilities/retrieveRecord"),_updateIndex=require("../utilities/updateIndex"),_keyGenerate=require("../introspect/policies/keyGenerate"),_keyUnique=require("../introspect/policies/keyUnique"),_useDefault=require("../introspect/policies/useDefault"),_typeCheck=require("../introspect/policies/typeCheck"),_foreignKeyExists=require("../introspect/policies/foreignKeyExists"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_computeKeyValue=require("../utilities/computeKeyValue"),_fieldType=require("../utilities/fieldType"),_storage=require("../storage");const buildEntityValues=async(e,t,r={})=>{const i={};for(const s of t.definition){let t=r[s.name];if(s.autoGenerate?t=await(0,_keyGenerate.keyGenerate)(e,[s.name]):Object.prototype.hasOwnProperty.call(s,"default")&&(t=(0,_useDefault.useDefault)(s.default,t)),void 0!==t)i[s.name]=(0,_typeCheck.typeCheck)(s.type,t,s.nullable);else if(!s.optional)throw new Error(`Missing required field "${s.name}" for ${e}`)}return i},assertUnique=async(e,t,r)=>{for(const i of t.keys){if("primary"!==i.type&&"unique"!==i.type)continue;const t=i.fields.map((e=>r[e]));if(t.some((e=>null===e)))continue;if(!await(0,_keyUnique.keyUnique)(e,i.fields,t))throw new Error(`Duplicate value for ${i.type} key "${i.fields.join(", ")}" on ${e}`)}},assertForeignKeys=async(e,t,r)=>{for(const i of t.keys){if("foreign"!==i.type||1!==i.fields.length||!i.references||1!==i.references.length)continue;const t=i.fields[0];if(!Object.prototype.hasOwnProperty.call(r,t)||null===r[t])continue;const{entity:s,property:o}=(0,_splitEntityProperty.splitEntityProperty)(i.references[0].fields[0]);if(!await(0,_foreignKeyExists.foreignKeyExists)(s,o,r[t]))throw new Error(`Foreign key "${t}" on ${e} references a nonexistent ${s}.${o} = ${r[t]}`)}},insertEntity=async(e="",t=[],r={})=>{const i=await(0,_retrieveRecord.retrieveRecord)(e),s=i.keys.find((e=>"primary"===e.type&&1===e.fields.length));Object.prototype.hasOwnProperty.call(r,e)||(r[e]=[]),await(0,_storage.makeDirectory)(i.path);for(const o of t){const t=await buildEntityValues(e,i,o);await assertUnique(e,i,t),await assertForeignKeys(e,i,t);const n=`${s?t[s.fields[0]]:crypto.randomUUID()}.json`;await(0,_storage.writeJson)(`${i.path}/${n}`,t),i.entries.push(n),await(0,_storage.writeJson)(`${i.path}.json`,i);for(const e of i.keys)await(0,_updateIndex.updateIndex)(i.path,e,(0,_computeKeyValue.computeKeyValue)(e.fields,t),n,(0,_fieldType.keyValueType)(i,e));r[e].push(t)}return r};exports.insertEntity=insertEntity;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.insertEntity=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.every.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.for-each.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.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"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var _retrieveRecord=require("../utilities/retrieveRecord"),_keyUnique=require("../introspect/policies/keyUnique"),_useDefault=require("../introspect/policies/useDefault"),_typeCheck=require("../introspect/policies/typeCheck"),_foreignKeyExists=require("../introspect/policies/foreignKeyExists"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_computeKeyValue=require("../utilities/computeKeyValue"),_fieldType=require("../utilities/fieldType"),_compareAs=require("../utilities/compareAs"),_retrieveRecords=require("../utilities/retrieveRecords"),_isSingleFieldKey=require("../utilities/isSingleFieldKey"),_indexBuffer=require("../utilities/indexBuffer"),_storage=require("../storage"),_packedTable=require("../utilities/packedTable"),_knownFields=require("../utilities/knownFields");class RowValues{constructor(e,s){this.seen=new Set,this.fields=s,this.types=s.map((s=>(0,_fieldType.fieldType)(e,s)))}static usable(e,s){return s.every((s=>_compareAs.comparableTypes.includes((0,_fieldType.fieldType)(e,s))))}keyOf(e){const s=[];for(let t=0;t<this.fields.length;t++){const r=e[this.fields[t]];if(null==r)return;const i=(0,_compareAs.compareAs)(this.types[t],r);if("object"==typeof i)return;s.push([typeof i,i])}return JSON.stringify(s)}add(e){const s=this.keyOf(e);void 0!==s&&this.seen.add(s)}has(e){const s=this.keyOf(e);return void 0!==s&&this.seen.has(s)}}class InsertStatement{constructor(e,s){this.rows=[],this.buffers=new Map,this.unreadable=new Set,this.otherLookups=new Map,this.scans=new Map,this.entity=e,this.record=s,this.hadEntries=(s.entries||[]).length>0}async load(){for(const e of this.record.keys){if(!e.lookup)continue;let s=[];try{s=await(0,_storage.readJson)(`__indexes/${this.record.path}/${e.lookup}`)}catch(s){this.unreadable.add(e)}this.buffers.set(e,new _indexBuffer.IndexBuffer(Array.isArray(s)?s:[],(0,_fieldType.keyValueType)(this.record,e)))}}ownLookup(e){const s=this.record.keys.find((s=>(0,_isSingleFieldKey.isSingleFieldKey)(s,e)&&s.lookup));if(!s||this.unreadable.has(s))return()=>{};const t=this.buffers.get(s);return e=>(this.hadEntries||this.rows.length>0)&&t.has(e)}otherLookup(e,s){const t=`${e}\0${s}`;return this.otherLookups.has(t)||this.otherLookups.set(t,(async()=>{const t=await(0,_retrieveRecord.retrieveRecord)(e),r=t.keys.find((e=>(0,_isSingleFieldKey.isSingleFieldKey)(e,s)&&e.lookup));if(!r)return()=>{};if(!(t.entries||[]).length)return()=>!1;let i;try{i=await(0,_storage.readJson)(`__indexes/${t.path}/${r.lookup}`)}catch(e){return()=>{}}const o=new _indexBuffer.IndexBuffer(Array.isArray(i)?i:[],(0,_fieldType.fieldType)(t,s));return e=>o.has(e)})()),this.otherLookups.get(t)}scan(e,s){const t=`${e}\0${s.join("\0")}`;return this.scans.has(t)||this.scans.set(t,(async()=>{const t=e===this.entity?this.record:await(0,_retrieveRecord.retrieveRecord)(e);if(!RowValues.usable(t,s))return;const r=new RowValues(t,s);for(const s of await(0,_retrieveRecords.retrieveRecords)(e))r.add(s);return e===this.entity&&this.rows.forEach((e=>r.add(e))),r})()),this.scans.get(t)}claimed(e,s){return this.rows.some((t=>e.every((e=>null!==t[e]&&void 0!==t[e]&&(0,_compareAs.sameValue)((0,_fieldType.fieldType)(this.record,e),t[e],s[e])))))}async isUnique(e,s){if(1===e.length){const t=this.ownLookup(e[0])(s[e[0]]);if(void 0!==t)return!t}const t=await this.scan(this.entity,e);return t?!t.has(s):await(0,_keyUnique.keyUnique)(this.entity,e,e.map((e=>s[e])))&&!this.claimed(e,s)}async exists(e,s,t){const r=e===this.entity,i=r?this.ownLookup(s)(t):(await this.otherLookup(e,s))(t);if(void 0!==i)return i;const o=await this.scan(e,[s]);return o?o.has({[s]:t}):await(0,_foreignKeyExists.foreignKeyExists)(e,s,t)||r&&this.claimed([s],{[s]:t})}async add(e,s){this.rows.push(e);for(const[s,t]of this.scans)s.startsWith(`${this.entity}\0`)&&(await t)?.add(e);this.record.entries.push(s);for(const[t,r]of this.buffers)r.add((0,_computeKeyValue.computeKeyValue)(t.fields,e),s)}async write(){await(0,_storage.writeJson)(`${this.record.path}.json`,this.record),this.buffers.size&&await(0,_storage.makeDirectory)(`__indexes/${this.record.path}`);for(const[e,s]of this.buffers)await(0,_storage.writeJson)(`__indexes/${this.record.path}/${e.lookup}`,s.entries)}}const generateKey=async(e,s)=>{let t;do{t=crypto.randomUUID()}while(!await e.isUnique([s],{[s]:t}));return t},buildEntityValues=async(e,s={})=>{const t={};for(const r of e.record.definition){let i=s[r.name];if(null===i&&r.optional&&!r.nullable&&(i=void 0),r.autoGenerate?i=await generateKey(e,r.name):Object.prototype.hasOwnProperty.call(r,"default")&&(i=(0,_useDefault.useDefault)(r.default,i)),void 0!==i)t[r.name]=(0,_typeCheck.typeCheck)(r.type,i,r.nullable);else if(!r.optional)throw new Error(`Missing required field "${r.name}" for ${e.entity}`)}return t},assertUnique=async(e,s)=>{for(const t of e.record.keys)if(!("primary"!==t.type&&"unique"!==t.type||t.fields.some((e=>null===s[e]))||await e.isUnique(t.fields,s)))throw new Error(`Duplicate value for ${t.type} key "${t.fields.join(", ")}" on ${e.entity}`)},assertForeignKeys=async(e,s)=>{for(const t of e.record.keys){if("foreign"!==t.type||1!==t.fields.length||!t.references||1!==t.references.length)continue;const r=t.fields[0];if(!Object.prototype.hasOwnProperty.call(s,r)||null===s[r])continue;const{entity:i,property:o}=(0,_splitEntityProperty.splitEntityProperty)(t.references[0].fields[0]);if(!await e.exists(i,o,s[r]))throw new Error(`Foreign key "${r}" on ${e.entity} references a nonexistent ${i}.${o} = ${s[r]}`)}},insertEntity=async(e="",s=[],t={})=>{const r=await(0,_retrieveRecord.retrieveRecord)(e),i=r.keys.find((e=>"primary"===e.type&&1===e.fields.length));if(Object.prototype.hasOwnProperty.call(t,e)||(t[e]=[]),await(0,_storage.makeDirectory)(r.path),!s.length)return t;for(const t of s)(0,_knownFields.assertFieldsDefined)(e,r,Object.keys(t||{}),"insert");await(0,_packedTable.discardPacked)(r.path);const o=new InsertStatement(e,r);await o.load();for(const n of s){const s=await buildEntityValues(o,n);await assertUnique(o,s),await assertForeignKeys(o,s);const a=`${i?s[i.fields[0]]:crypto.randomUUID()}.json`;await(0,_storage.writeJson)(`${r.path}/${a}`,s),await o.add(s,a),t[e].push(s)}return await o.write(),t};exports.insertEntity=insertEntity;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Parse a query string (one query, or several separated by `;`) and run each query in turn, resolving to one result
3
3
  * per query. The queries of one call run together, under the database lock: no other call's queries run between
4
- * them (see withDatabaseLock).
4
+ * them (see withDatabaseLock). Each query is all or nothing: one that fails changes nothing, and the queries before it
5
+ * keep their changes (see withWriteSet).
5
6
  * @param queryString
6
7
  */
7
8
  export declare const query: (queryString?: string) => Promise<any[]>;
@@ -7,10 +7,12 @@ exports.query = void 0;
7
7
  var _parser = require("./parser");
8
8
  var _runQuery = require("../utilities/runQuery");
9
9
  var _databaseLock = require("../utilities/databaseLock");
10
+ var _storage = require("../storage");
10
11
  /**
11
12
  * Parse a query string (one query, or several separated by `;`) and run each query in turn, resolving to one result
12
13
  * per query. The queries of one call run together, under the database lock: no other call's queries run between
13
- * them (see withDatabaseLock).
14
+ * them (see withDatabaseLock). Each query is all or nothing: one that fails changes nothing, and the queries before it
15
+ * keep their changes (see withWriteSet).
14
16
  * @param queryString
15
17
  */
16
18
  const query = async (queryString = '') => {
@@ -18,7 +20,8 @@ const query = async (queryString = '') => {
18
20
  return (0, _databaseLock.withDatabaseLock)(async () => {
19
21
  const queryResults = [];
20
22
  for (const parsed of parsedQueries) {
21
- const dataSet = await (0, _runQuery.runQuery)(parsed);
23
+ // Each query's changes reach storage together, or not at all if it fails (see withWriteSet)
24
+ const dataSet = await (0, _storage.withWriteSet)(() => (0, _runQuery.runQuery)(parsed));
22
25
  queryResults.push(dataSet[parsed.entity]);
23
26
  }
24
27
  return queryResults;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.query=void 0;var _parser=require("./parser"),_runQuery=require("../utilities/runQuery"),_databaseLock=require("../utilities/databaseLock");const query=async(e="")=>{const r=(0,_parser.parser)(e);return(0,_databaseLock.withDatabaseLock)((async()=>{const e=[];for(const t of r){const r=await(0,_runQuery.runQuery)(t);e.push(r[t.entity])}return e}))};exports.query=query;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.query=void 0;var _parser=require("./parser"),_runQuery=require("../utilities/runQuery"),_databaseLock=require("../utilities/databaseLock"),_storage=require("../storage");const query=async(e="")=>{const r=(0,_parser.parser)(e);return(0,_databaseLock.withDatabaseLock)((async()=>{const e=[];for(const t of r){const r=await(0,_storage.withWriteSet)((()=>(0,_runQuery.runQuery)(t)));e.push(r[t.entity])}return e}))};exports.query=query;
@@ -1,12 +1,13 @@
1
1
  import { ParsedSelect } from '../utilities/parsers/findSelects';
2
2
  import { Group } from './groupBy';
3
3
  /**
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.
4
+ * Shape each row down to only the selected properties (aliased, when given, otherwise named as written). dataSet is
5
+ * either a list of rows, or (once groupBy has run) a list of groups, whose rows are shaped the same way at any depth;
6
+ * the groups keep their field and value, selected or not.
7
7
  * @param dataSet
8
8
  * @param selectClauses
9
9
  * @param grouped Whether dataSet is a list of groups (see groupBy)
10
+ * @param entity The queried record's name, so `<record>.<field>` picks the row's own field
10
11
  */
11
- export declare const selectFields: (dataSet?: Object[] | Group[], selectClauses?: ParsedSelect[], grouped?: boolean) => Object[] | Group[];
12
+ export declare const selectFields: (dataSet?: Object[] | Group[], selectClauses?: ParsedSelect[], grouped?: boolean, entity?: string) => Object[] | Group[];
12
13
  //# sourceMappingURL=selectFields.d.ts.map
@@ -7,35 +7,49 @@ exports.selectFields = void 0;
7
7
  require("core-js/modules/esnext.iterator.constructor.js");
8
8
  require("core-js/modules/esnext.iterator.map.js");
9
9
  require("core-js/modules/esnext.iterator.reduce.js");
10
- const shapeRow = (row, selectClauses) => selectClauses.reduce((shaped, {
10
+ /**
11
+ * The value a select name picks from a row: the row's own key (a field, a merged record's `<record>.<field>`, or a
12
+ * joined record's rows), or for `<queried record>.<field>`, the row's own field.
13
+ * @param row
14
+ * @param property
15
+ * @param entity - The queried record's name.
16
+ */
17
+ const pick = (row, property, entity) => {
18
+ if (Object.prototype.hasOwnProperty.call(row, property) || typeof entity === 'undefined' || !property.startsWith(`${entity}.`)) {
19
+ return row[property];
20
+ }
21
+ return row[property.slice(entity.length + 1)];
22
+ };
23
+ const shapeRow = (row, selectClauses, entity) => selectClauses.reduce((shaped, {
11
24
  property,
12
25
  alias
13
26
  }) => {
14
- shaped[alias || property] = row[property];
27
+ shaped[alias || property] = pick(row, property, entity);
15
28
  return shaped;
16
29
  }, {});
17
- const shapeGroup = (group, selectClauses) => group.groups ? {
30
+ const shapeGroup = (group, selectClauses, entity) => group.groups ? {
18
31
  ...group,
19
- groups: group.groups.map(inner => shapeGroup(inner, selectClauses))
32
+ groups: group.groups.map(inner => shapeGroup(inner, selectClauses, entity))
20
33
  } : {
21
34
  ...group,
22
- rows: group.rows.map(row => shapeRow(row, selectClauses))
35
+ rows: group.rows.map(row => shapeRow(row, selectClauses, entity))
23
36
  };
24
37
  /**
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.
38
+ * Shape each row down to only the selected properties (aliased, when given, otherwise named as written). dataSet is
39
+ * either a list of rows, or (once groupBy has run) a list of groups, whose rows are shaped the same way at any depth;
40
+ * the groups keep their field and value, selected or not.
28
41
  * @param dataSet
29
42
  * @param selectClauses
30
43
  * @param grouped Whether dataSet is a list of groups (see groupBy)
44
+ * @param entity The queried record's name, so `<record>.<field>` picks the row's own field
31
45
  */
32
- const selectFields = (dataSet = [], selectClauses = [], grouped = false) => {
46
+ const selectFields = (dataSet = [], selectClauses = [], grouped = false, entity) => {
33
47
  if (!selectClauses.length) {
34
48
  return dataSet;
35
49
  }
36
50
  if (grouped) {
37
- return dataSet.map(group => shapeGroup(group, selectClauses));
51
+ return dataSet.map(group => shapeGroup(group, selectClauses, entity));
38
52
  }
39
- return dataSet.map(row => shapeRow(row, selectClauses));
53
+ return dataSet.map(row => shapeRow(row, selectClauses, entity));
40
54
  };
41
55
  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: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;
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 pick=(e,s,r)=>Object.prototype.hasOwnProperty.call(e,s)||void 0===r||!s.startsWith(`${r}.`)?e[s]:e[s.slice(r.length+1)],shapeRow=(e,s,r)=>s.reduce(((s,{property:o,alias:t})=>(s[t||o]=pick(e,o,r),s)),{}),shapeGroup=(e,s,r)=>e.groups?{...e,groups:e.groups.map((e=>shapeGroup(e,s,r)))}:{...e,rows:e.rows.map((e=>shapeRow(e,s,r)))},selectFields=(e=[],s=[],r=!1,o)=>s.length?r?e.map((e=>shapeGroup(e,s,o))):e.map((e=>shapeRow(e,s,o))):e;exports.selectFields=selectFields;