json-fs-query 3.1.4 → 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 (187) hide show
  1. package/README.md +257 -15
  2. package/browser/jsonFsQuery.js +3884 -5057
  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 +9 -23
  9. package/dist/data/introspect/create.min.js +1 -1
  10. package/dist/data/introspect/drop.js +7 -15
  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/introspect/policies/foreignKeyExists.d.ts +2 -0
  22. package/dist/data/introspect/policies/foreignKeyExists.js +9 -3
  23. package/dist/data/introspect/policies/foreignKeyExists.min.js +1 -1
  24. package/dist/data/introspect/policies/hasDependents.d.ts +3 -3
  25. package/dist/data/introspect/policies/hasDependents.js +11 -3
  26. package/dist/data/introspect/policies/hasDependents.min.js +1 -1
  27. package/dist/data/introspect/policies/keyUnique.d.ts +3 -0
  28. package/dist/data/introspect/policies/keyUnique.js +10 -0
  29. package/dist/data/introspect/policies/keyUnique.min.js +1 -1
  30. package/dist/data/queries/databaseStatements.d.ts +31 -0
  31. package/dist/data/queries/databaseStatements.js +97 -0
  32. package/dist/data/queries/databaseStatements.min.js +1 -0
  33. package/dist/data/queries/deleteEntity.js +35 -19
  34. package/dist/data/queries/deleteEntity.min.js +1 -1
  35. package/dist/data/queries/insertEntity.d.ts +8 -0
  36. package/dist/data/queries/insertEntity.js +340 -43
  37. package/dist/data/queries/insertEntity.min.js +1 -1
  38. package/dist/data/queries/joinEntity.d.ts +15 -0
  39. package/dist/data/queries/joinEntity.js +91 -15
  40. package/dist/data/queries/joinEntity.min.js +1 -1
  41. package/dist/data/queries/query.d.ts +2 -1
  42. package/dist/data/queries/query.js +5 -2
  43. package/dist/data/queries/query.min.js +1 -1
  44. package/dist/data/queries/selectFields.d.ts +5 -4
  45. package/dist/data/queries/selectFields.js +25 -11
  46. package/dist/data/queries/selectFields.min.js +1 -1
  47. package/dist/data/queries/updateEntity.js +54 -15
  48. package/dist/data/queries/updateEntity.min.js +1 -1
  49. package/dist/data/storage/databases.d.ts +32 -0
  50. package/dist/data/storage/databases.js +93 -0
  51. package/dist/data/storage/databases.min.js +1 -0
  52. package/dist/data/storage/fileExists.d.ts +7 -0
  53. package/dist/data/storage/fileExists.js +25 -0
  54. package/dist/data/storage/fileExists.min.js +1 -0
  55. package/dist/data/storage/index.d.ts +19 -0
  56. package/dist/data/storage/index.js +124 -0
  57. package/dist/data/storage/index.min.js +1 -0
  58. package/dist/data/storage/listFiles.d.ts +8 -0
  59. package/dist/data/storage/listFiles.js +52 -0
  60. package/dist/data/storage/listFiles.min.js +1 -0
  61. package/dist/data/storage/makeDirectory.d.ts +7 -0
  62. package/dist/data/storage/makeDirectory.js +24 -0
  63. package/dist/data/storage/makeDirectory.min.js +1 -0
  64. package/dist/data/storage/nodeFiles.d.ts +6 -0
  65. package/dist/data/storage/nodeFiles.js +12 -0
  66. package/dist/data/storage/nodeFiles.min.js +1 -0
  67. package/dist/data/storage/readCache.d.ts +37 -0
  68. package/dist/data/storage/readCache.js +117 -0
  69. package/dist/data/storage/readCache.min.js +1 -0
  70. package/dist/data/storage/readJson.d.ts +16 -0
  71. package/dist/data/storage/readJson.js +38 -0
  72. package/dist/data/storage/readJson.min.js +1 -0
  73. package/dist/data/storage/removeDirectory.d.ts +8 -0
  74. package/dist/data/storage/removeDirectory.js +30 -0
  75. package/dist/data/storage/removeDirectory.min.js +1 -0
  76. package/dist/data/storage/removeFile.d.ts +8 -0
  77. package/dist/data/storage/removeFile.js +31 -0
  78. package/dist/data/storage/removeFile.min.js +1 -0
  79. package/dist/data/storage/storagePath.d.ts +11 -0
  80. package/dist/data/storage/storagePath.js +25 -0
  81. package/dist/data/storage/storagePath.min.js +1 -0
  82. package/dist/data/storage/writeJson.d.ts +17 -0
  83. package/dist/data/storage/writeJson.js +37 -0
  84. package/dist/data/storage/writeJson.min.js +1 -0
  85. package/dist/data/storage/writeSet.d.ts +64 -0
  86. package/dist/data/storage/writeSet.js +306 -0
  87. package/dist/data/storage/writeSet.min.js +1 -0
  88. package/dist/data/utilities/compareAs.d.ts +1 -0
  89. package/dist/data/utilities/compareAs.js +2 -2
  90. package/dist/data/utilities/compareAs.min.js +1 -1
  91. package/dist/data/utilities/config.d.ts +37 -0
  92. package/dist/data/utilities/config.js +77 -5
  93. package/dist/data/utilities/config.min.js +1 -1
  94. package/dist/data/utilities/databaseLock.d.ts +7 -0
  95. package/dist/data/utilities/databaseLock.js +19 -4
  96. package/dist/data/utilities/databaseLock.min.js +1 -1
  97. package/dist/data/utilities/deregisterRecord.js +4 -10
  98. package/dist/data/utilities/deregisterRecord.min.js +1 -1
  99. package/dist/data/utilities/getConditionIndexedList.js +3 -4
  100. package/dist/data/utilities/getConditionIndexedList.min.js +1 -1
  101. package/dist/data/utilities/indexBuffer.d.ts +35 -0
  102. package/dist/data/utilities/indexBuffer.js +139 -0
  103. package/dist/data/utilities/indexBuffer.min.js +1 -0
  104. package/dist/data/utilities/indexHas.d.ts +13 -0
  105. package/dist/data/utilities/indexHas.js +48 -0
  106. package/dist/data/utilities/indexHas.min.js +1 -0
  107. package/dist/data/utilities/joinBases.d.ts +29 -0
  108. package/dist/data/utilities/joinBases.js +128 -0
  109. package/dist/data/utilities/joinBases.min.js +1 -0
  110. package/dist/data/utilities/joinKey.d.ts +17 -0
  111. package/dist/data/utilities/joinKey.js +31 -0
  112. package/dist/data/utilities/joinKey.min.js +1 -0
  113. package/dist/data/utilities/keyIndexFiles.d.ts +16 -0
  114. package/dist/data/utilities/keyIndexFiles.js +60 -0
  115. package/dist/data/utilities/keyIndexFiles.min.js +1 -0
  116. package/dist/data/utilities/knownFields.d.ts +26 -0
  117. package/dist/data/utilities/knownFields.js +128 -0
  118. package/dist/data/utilities/knownFields.min.js +1 -0
  119. package/dist/data/utilities/packedTable.d.ts +34 -0
  120. package/dist/data/utilities/packedTable.js +60 -0
  121. package/dist/data/utilities/packedTable.min.js +1 -0
  122. package/dist/data/utilities/parsers/findInsertValues.d.ts +3 -3
  123. package/dist/data/utilities/parsers/findInsertValues.js +28 -12
  124. package/dist/data/utilities/parsers/findInsertValues.min.js +1 -1
  125. package/dist/data/utilities/parsers/maskStrings.js +11 -2
  126. package/dist/data/utilities/parsers/maskStrings.min.js +1 -1
  127. package/dist/data/utilities/parsers/parseAlter.d.ts +33 -0
  128. package/dist/data/utilities/parsers/parseAlter.js +84 -0
  129. package/dist/data/utilities/parsers/parseAlter.min.js +1 -0
  130. package/dist/data/utilities/parsers/parseCreate.d.ts +14 -0
  131. package/dist/data/utilities/parsers/parseCreate.js +13 -5
  132. package/dist/data/utilities/parsers/parseCreate.min.js +1 -1
  133. package/dist/data/utilities/parsers/parseDatabaseStatement.d.ts +17 -0
  134. package/dist/data/utilities/parsers/parseDatabaseStatement.js +82 -0
  135. package/dist/data/utilities/parsers/parseDatabaseStatement.min.js +1 -0
  136. package/dist/data/utilities/parsers/parseQuery.d.ts +3 -0
  137. package/dist/data/utilities/parsers/parseQuery.js +24 -0
  138. package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
  139. package/dist/data/utilities/parsers/startsQuery.js +1 -1
  140. package/dist/data/utilities/parsers/startsQuery.min.js +1 -1
  141. package/dist/data/utilities/recordExists.js +2 -7
  142. package/dist/data/utilities/recordExists.min.js +1 -1
  143. package/dist/data/utilities/registerRecord.js +4 -10
  144. package/dist/data/utilities/registerRecord.min.js +1 -1
  145. package/dist/data/utilities/removeFromIndex.js +4 -10
  146. package/dist/data/utilities/removeFromIndex.min.js +1 -1
  147. package/dist/data/utilities/retrieveFile.d.ts +5 -0
  148. package/dist/data/utilities/retrieveFile.js +7 -16
  149. package/dist/data/utilities/retrieveFile.min.js +1 -1
  150. package/dist/data/utilities/retrieveFiles.d.ts +15 -0
  151. package/dist/data/utilities/retrieveFiles.js +48 -0
  152. package/dist/data/utilities/retrieveFiles.min.js +1 -0
  153. package/dist/data/utilities/retrieveRecord.d.ts +6 -0
  154. package/dist/data/utilities/retrieveRecord.js +24 -1
  155. package/dist/data/utilities/retrieveRecord.min.js +1 -1
  156. package/dist/data/utilities/retrieveRecords.d.ts +22 -0
  157. package/dist/data/utilities/retrieveRecords.js +60 -10
  158. package/dist/data/utilities/retrieveRecords.min.js +1 -1
  159. package/dist/data/utilities/rowLookup.d.ts +38 -0
  160. package/dist/data/utilities/rowLookup.js +99 -0
  161. package/dist/data/utilities/rowLookup.min.js +1 -0
  162. package/dist/data/utilities/runQuery.js +28 -4
  163. package/dist/data/utilities/runQuery.min.js +1 -1
  164. package/dist/data/utilities/semiJoin.d.ts +27 -0
  165. package/dist/data/utilities/semiJoin.js +97 -0
  166. package/dist/data/utilities/semiJoin.min.js +1 -0
  167. package/dist/data/utilities/updateIndex.js +5 -14
  168. package/dist/data/utilities/updateIndex.min.js +1 -1
  169. package/dist/index.d.ts +1 -1
  170. package/dist/index.js +24 -0
  171. package/dist/index.min.js +1 -1
  172. package/package.json +10 -4
  173. package/dist/data/utilities/getJoinIndexedList.d.ts +0 -3
  174. package/dist/data/utilities/getJoinIndexedList.js +0 -76
  175. package/dist/data/utilities/getJoinIndexedList.min.js +0 -1
  176. package/dist/data/utilities/getMatchingIndexes.d.ts +0 -2
  177. package/dist/data/utilities/getMatchingIndexes.js +0 -28
  178. package/dist/data/utilities/getMatchingIndexes.min.js +0 -1
  179. package/dist/data/utilities/matchIndexedJoins.d.ts +0 -2
  180. package/dist/data/utilities/matchIndexedJoins.js +0 -63
  181. package/dist/data/utilities/matchIndexedJoins.min.js +0 -1
  182. package/dist/data/utilities/restrictIndexedList.d.ts +0 -2
  183. package/dist/data/utilities/restrictIndexedList.js +0 -45
  184. package/dist/data/utilities/restrictIndexedList.min.js +0 -1
  185. package/dist/data/utilities/useJoinClause.d.ts +0 -6
  186. package/dist/data/utilities/useJoinClause.js +0 -57
  187. package/dist/data/utilities/useJoinClause.min.js +0 -1
@@ -4,14 +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");
7
8
  require("core-js/modules/es.json.stringify.js");
8
9
  require("core-js/modules/esnext.iterator.constructor.js");
10
+ require("core-js/modules/esnext.iterator.every.js");
9
11
  require("core-js/modules/esnext.iterator.find.js");
12
+ require("core-js/modules/esnext.iterator.for-each.js");
10
13
  require("core-js/modules/esnext.iterator.map.js");
11
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");
12
44
  var _retrieveRecord = require("../utilities/retrieveRecord");
13
- var _updateIndex = require("../utilities/updateIndex");
14
- var _keyGenerate = require("../introspect/policies/keyGenerate");
15
45
  var _keyUnique = require("../introspect/policies/keyUnique");
16
46
  var _useDefault = require("../introspect/policies/useDefault");
17
47
  var _typeCheck = require("../introspect/policies/typeCheck");
@@ -19,30 +49,290 @@ var _foreignKeyExists = require("../introspect/policies/foreignKeyExists");
19
49
  var _splitEntityProperty = require("../utilities/parsers/splitEntityProperty");
20
50
  var _computeKeyValue = require("../utilities/computeKeyValue");
21
51
  var _fieldType = require("../utilities/fieldType");
22
- var _config = require("../utilities/config");
23
- const {
24
- writeFile,
25
- mkdir
26
- } = require('fs/promises');
52
+ var _compareAs = require("../utilities/compareAs");
53
+ var _retrieveRecords = require("../utilities/retrieveRecords");
54
+ var _isSingleFieldKey = require("../utilities/isSingleFieldKey");
55
+ var _indexBuffer = require("../utilities/indexBuffer");
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
+ };
27
313
  /**
28
314
  * Build one entity's stored values from the raw input values, generating any autoGenerate
29
- * fields and applying field defaults, then throw if a required field still has no value.
30
- * @param entity
31
- * @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
32
319
  * @param rowValues
33
320
  */
34
- const buildEntityValues = async (entity, record, rowValues = {}) => {
321
+ const buildEntityValues = async (statement, rowValues = {}) => {
35
322
  const entityValues = {};
36
- for (const field of record.definition) {
323
+ for (const field of statement.record.definition) {
37
324
  let value = rowValues[field.name];
325
+ if (value === null && field.optional && !field.nullable) {
326
+ value = undefined;
327
+ }
38
328
  if (field.autoGenerate) {
39
- value = await (0, _keyGenerate.keyGenerate)(entity, [field.name]);
329
+ value = await generateKey(statement, field.name);
40
330
  } else if (Object.prototype.hasOwnProperty.call(field, 'default')) {
41
331
  value = (0, _useDefault.useDefault)(field['default'], value);
42
332
  }
43
333
  if (typeof value === 'undefined') {
44
334
  if (!field.optional) {
45
- throw new Error(`Missing required field "${field.name}" for ${entity}`);
335
+ throw new Error(`Missing required field "${field.name}" for ${statement.entity}`);
46
336
  }
47
337
  continue;
48
338
  }
@@ -54,34 +344,30 @@ const buildEntityValues = async (entity, record, rowValues = {}) => {
54
344
  * Reject the insert if it collides with an existing primary or unique key value - a single field,
55
345
  * or a composite of several for a multi-field key. As in SQL, a value with a null in it is never a
56
346
  * duplicate, so a nullable unique field can hold null in any number of rows.
57
- * @param entity
58
- * @param record
347
+ * @param statement
59
348
  * @param entityValues
60
349
  */
61
- const assertUnique = async (entity, record, entityValues) => {
62
- for (const key of record.keys) {
350
+ const assertUnique = async (statement, entityValues) => {
351
+ for (const key of statement.record.keys) {
63
352
  if (key.type !== 'primary' && key.type !== 'unique') {
64
353
  continue;
65
354
  }
66
- const checkKeys = key.fields.map(field => entityValues[field]);
67
- if (checkKeys.some(value => value === null)) {
355
+ if (key.fields.some(field => entityValues[field] === null)) {
68
356
  continue;
69
357
  }
70
- const isUnique = await (0, _keyUnique.keyUnique)(entity, key.fields, checkKeys);
71
- if (!isUnique) {
72
- 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}`);
73
360
  }
74
361
  }
75
362
  };
76
363
  /**
77
364
  * Reject the insert if a single-field foreign key's value doesn't exist in the record it
78
365
  * references. A null references nothing, so it isn't checked (as in SQL).
79
- * @param entity
80
- * @param record
366
+ * @param statement
81
367
  * @param entityValues
82
368
  */
83
- const assertForeignKeys = async (entity, record, entityValues) => {
84
- for (const key of record.keys) {
369
+ const assertForeignKeys = async (statement, entityValues) => {
370
+ for (const key of statement.record.keys) {
85
371
  if (key.type !== 'foreign' || key.fields.length !== 1 || !key.references || key.references.length !== 1) {
86
372
  continue;
87
373
  }
@@ -93,14 +379,20 @@ const assertForeignKeys = async (entity, record, entityValues) => {
93
379
  entity: refEntity,
94
380
  property: refProperty
95
381
  } = (0, _splitEntityProperty.splitEntityProperty)(key.references[0].fields[0]);
96
- const exists = await (0, _foreignKeyExists.foreignKeyExists)(refEntity, refProperty, entityValues[field]);
97
- if (!exists) {
98
- 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]}`);
99
384
  }
100
385
  }
101
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
+ */
102
395
  const insertEntity = async (entity = '', values = [], dataSet = {}) => {
103
- const databasePath = (0, _config.getSetting)('databasePath', 'database/');
104
396
  const record = await (0, _retrieveRecord.retrieveRecord)(entity);
105
397
  const primaryKey = record.keys.find(key => key.type === 'primary' && key.fields.length === 1);
106
398
  if (!Object.prototype.hasOwnProperty.call(dataSet, entity)) {
@@ -108,22 +400,27 @@ const insertEntity = async (entity = '', values = [], dataSet = {}) => {
108
400
  }
109
401
  // The record's folder can be missing even though the record exists: git doesn't keep empty folders, so a database
110
402
  // committed before its first insert comes back from a fresh clone without it
111
- await mkdir(`${databasePath}${record.path}`, {
112
- recursive: true
113
- });
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
114
408
  for (const rowValues of values) {
115
- const entityValues = await buildEntityValues(entity, record, rowValues);
116
- await assertUnique(entity, record, entityValues);
117
- 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);
118
418
  const fileName = `${primaryKey ? entityValues[primaryKey.fields[0]] : crypto.randomUUID()}.json`;
119
- await writeFile(`${databasePath}${record.path}/${fileName}`, JSON.stringify(entityValues, null, 2));
120
- record.entries.push(fileName);
121
- await writeFile(`${databasePath}${record.path}.json`, JSON.stringify(record, null, 2));
122
- for (const key of record.keys) {
123
- await (0, _updateIndex.updateIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, entityValues), fileName, (0, _fieldType.keyValueType)(record, key));
124
- }
419
+ await (0, _storage.writeJson)(`${record.path}/${fileName}`, entityValues);
420
+ await statement.add(entityValues, fileName);
125
421
  dataSet[entity].push(entityValues);
126
422
  }
423
+ await statement.write();
127
424
  return dataSet;
128
425
  };
129
426
  exports.insertEntity = insertEntity;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.insertEntity=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.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"),_config=require("../utilities/config");const{writeFile:writeFile,mkdir:mkdir}=require("fs/promises"),buildEntityValues=async(e,t,i={})=>{const r={};for(const s of t.definition){let t=i[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)r[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 r},assertUnique=async(e,t,i)=>{for(const r of t.keys){if("primary"!==r.type&&"unique"!==r.type)continue;const t=r.fields.map((e=>i[e]));if(t.some((e=>null===e)))continue;if(!await(0,_keyUnique.keyUnique)(e,r.fields,t))throw new Error(`Duplicate value for ${r.type} key "${r.fields.join(", ")}" on ${e}`)}},assertForeignKeys=async(e,t,i)=>{for(const r of t.keys){if("foreign"!==r.type||1!==r.fields.length||!r.references||1!==r.references.length)continue;const t=r.fields[0];if(!Object.prototype.hasOwnProperty.call(i,t)||null===i[t])continue;const{entity:s,property:n}=(0,_splitEntityProperty.splitEntityProperty)(r.references[0].fields[0]);if(!await(0,_foreignKeyExists.foreignKeyExists)(s,n,i[t]))throw new Error(`Foreign key "${t}" on ${e} references a nonexistent ${s}.${n} = ${i[t]}`)}},insertEntity=async(e="",t=[],i={})=>{const r=(0,_config.getSetting)("databasePath","database/"),s=await(0,_retrieveRecord.retrieveRecord)(e),n=s.keys.find((e=>"primary"===e.type&&1===e.fields.length));Object.prototype.hasOwnProperty.call(i,e)||(i[e]=[]),await mkdir(`${r}${s.path}`,{recursive:!0});for(const o of t){const t=await buildEntityValues(e,s,o);await assertUnique(e,s,t),await assertForeignKeys(e,s,t);const a=`${n?t[n.fields[0]]:crypto.randomUUID()}.json`;await writeFile(`${r}${s.path}/${a}`,JSON.stringify(t,null,2)),s.entries.push(a),await writeFile(`${r}${s.path}.json`,JSON.stringify(s,null,2));for(const e of s.keys)await(0,_updateIndex.updateIndex)(s.path,e,(0,_computeKeyValue.computeKeyValue)(e.fields,t),a,(0,_fieldType.keyValueType)(s,e));i[e].push(t)}return i};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,2 +1,17 @@
1
+ /**
2
+ * Attach to each row of `entity` the rows of `joinEntity` its join reaches, through the `on` clauses in order.
3
+ *
4
+ * Each clause narrows both its records to the rows that match the other side: first the rows of the left one that
5
+ * match a right one, then the right ones that match those (see semiJoin for the order). A row's join starts from the
6
+ * row alone and from the rows read for every other record (see joinBases), so each record is read once for the whole
7
+ * join and each lookup is built once, rather than every loaded row being copied for each row joined.
8
+ * @param entity
9
+ * @param joinEntity
10
+ * @param dataSets
11
+ * @param joinClauses
12
+ * @param entityAlreadyResolved - Whether the where clause already narrowed entity's rows (then they aren't read again:
13
+ * an empty list reads as "not loaded yet", see readEntity).
14
+ * @param joinAlreadyResolved - The same for joinEntity.
15
+ */
1
16
  export declare const joinEntity: (entity: any, joinEntity: any, dataSets?: {}, joinClauses?: any[], entityAlreadyResolved?: boolean, joinAlreadyResolved?: boolean) => Promise<{}>;
2
17
  //# sourceMappingURL=joinEntity.d.ts.map
@@ -4,31 +4,107 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.joinEntity = void 0;
7
- var _siFunciona = _interopRequireDefault(require("si-funciona"));
7
+ require("core-js/modules/esnext.iterator.constructor.js");
8
+ require("core-js/modules/esnext.iterator.map.js");
9
+ require("core-js/modules/esnext.map.delete-all.js");
10
+ require("core-js/modules/esnext.map.every.js");
11
+ require("core-js/modules/esnext.map.filter.js");
12
+ require("core-js/modules/esnext.map.find.js");
13
+ require("core-js/modules/esnext.map.find-key.js");
14
+ require("core-js/modules/esnext.map.includes.js");
15
+ require("core-js/modules/esnext.map.key-of.js");
16
+ require("core-js/modules/esnext.map.map-keys.js");
17
+ require("core-js/modules/esnext.map.map-values.js");
18
+ require("core-js/modules/esnext.map.merge.js");
19
+ require("core-js/modules/esnext.map.reduce.js");
20
+ require("core-js/modules/esnext.map.some.js");
21
+ require("core-js/modules/esnext.map.update.js");
8
22
  var _readEntity = require("./readEntity");
9
- var _useJoinClause = require("../utilities/useJoinClause");
10
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
23
+ var _retrieveRecord = require("../utilities/retrieveRecord");
24
+ var _splitEntityProperty = require("../utilities/parsers/splitEntityProperty");
25
+ var _fieldType = require("../utilities/fieldType");
26
+ var _semiJoin = require("../utilities/semiJoin");
27
+ var _joinBases = require("../utilities/joinBases");
28
+ /**
29
+ * Each `on` clause with the types its two sides are compared by. Each record is read here, before its type is looked
30
+ * up, so a record that doesn't exist fails with "Could not read <record>.json", as it always has.
31
+ * @param joinClauses
32
+ */
33
+ const joinSteps = async joinClauses => {
34
+ const steps = [];
35
+ for (const {
36
+ propertyA,
37
+ comparator,
38
+ propertyB
39
+ } of joinClauses) {
40
+ const {
41
+ entity: entityA,
42
+ property: a
43
+ } = (0, _splitEntityProperty.splitEntityProperty)(propertyA);
44
+ const {
45
+ entity: entityB,
46
+ property: b
47
+ } = (0, _splitEntityProperty.splitEntityProperty)(propertyB);
48
+ steps.push({
49
+ entityA,
50
+ propertyA: a,
51
+ typeA: (0, _fieldType.fieldType)(await (0, _retrieveRecord.retrieveRecord)(entityA), a),
52
+ comparator,
53
+ entityB,
54
+ propertyB: b,
55
+ typeB: (0, _fieldType.fieldType)(await (0, _retrieveRecord.retrieveRecord)(entityB), b)
56
+ });
57
+ }
58
+ return steps;
59
+ };
60
+ /**
61
+ * What a row gets for its joined rows. Today every row is kept with the list of rows its join found (none is an empty
62
+ * list), and a condition on the joined record's fields then drops rows with none (whereTree): an inner join. This is
63
+ * the one place a join type (left, right, outer) would decide differently.
64
+ * @param matches - The joined rows the row's join found, shared with other rows' joins until copied here.
65
+ */
66
+ const joinedRows = matches => matches.map(match => structuredClone(match));
67
+ /**
68
+ * Attach to each row of `entity` the rows of `joinEntity` its join reaches, through the `on` clauses in order.
69
+ *
70
+ * Each clause narrows both its records to the rows that match the other side: first the rows of the left one that
71
+ * match a right one, then the right ones that match those (see semiJoin for the order). A row's join starts from the
72
+ * row alone and from the rows read for every other record (see joinBases), so each record is read once for the whole
73
+ * join and each lookup is built once, rather than every loaded row being copied for each row joined.
74
+ * @param entity
75
+ * @param joinEntity
76
+ * @param dataSets
77
+ * @param joinClauses
78
+ * @param entityAlreadyResolved - Whether the where clause already narrowed entity's rows (then they aren't read again:
79
+ * an empty list reads as "not loaded yet", see readEntity).
80
+ * @param joinAlreadyResolved - The same for joinEntity.
81
+ */
11
82
  const joinEntity = async (entity, joinEntity, dataSets = {}, joinClauses = [], entityAlreadyResolved = false, joinAlreadyResolved = false) => {
12
- // Same ambiguity readEntity() always has (can't tell "never loaded" from "loaded, zero rows
13
- // matched") - runQuery already resolved entity's rows via an indexed condition in that second
14
- // case, so entityAlreadyResolved tells this call not to reload and silently discard that result.
15
83
  if (!entityAlreadyResolved) {
16
84
  dataSets = await (0, _readEntity.readEntity)(entity, dataSets);
17
85
  }
18
86
  if (!joinClauses.length) {
19
87
  return dataSets;
20
88
  }
21
- // The same for joinEntity: an indexed condition on its field may have resolved its rows to none
22
- if (!joinAlreadyResolved) {
23
- dataSets = await (0, _readEntity.readEntity)(joinEntity, dataSets);
89
+ const steps = await joinSteps(joinClauses);
90
+ if (!dataSets[entity].length) {
91
+ return dataSets;
24
92
  }
25
- for (const anEntity in dataSets[entity]) {
26
- let filterJoins = _siFunciona.default.cloneObject(dataSets);
27
- filterJoins[entity] = [dataSets[entity][anEntity]];
28
- for (const joinClause of joinClauses) {
29
- filterJoins = await (0, _useJoinClause.useJoinClause)(joinClause, dataSets, filterJoins);
93
+ const bases = await (0, _joinBases.joinBases)(entity, joinEntity, dataSets, steps, joinAlreadyResolved);
94
+ for (const row of dataSets[entity]) {
95
+ const sides = new Map(bases);
96
+ sides.set(entity, {
97
+ rows: [row]
98
+ });
99
+ for (const step of steps) {
100
+ sides.set(step.entityA, {
101
+ rows: (0, _semiJoin.semiJoin)(sides.get(step.entityA), sides.get(step.entityB), step.propertyA, step.propertyB, step.comparator, step.typeA)
102
+ });
103
+ sides.set(step.entityB, {
104
+ rows: (0, _semiJoin.semiJoin)(sides.get(step.entityB), sides.get(step.entityA), step.propertyB, step.propertyA, step.comparator, step.typeB)
105
+ });
30
106
  }
31
- dataSets[entity][anEntity][joinEntity] = filterJoins[joinEntity];
107
+ row[joinEntity] = joinedRows(sides.get(joinEntity).rows);
32
108
  }
33
109
  return dataSets;
34
110
  };
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.joinEntity=void 0;var _siFunciona=_interopRequireDefault(require("si-funciona")),_readEntity=require("./readEntity"),_useJoinClause=require("../utilities/useJoinClause");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const joinEntity=async(e,t,i={},n=[],r=!1,u=!1)=>{if(r||(i=await(0,_readEntity.readEntity)(e,i)),!n.length)return i;u||(i=await(0,_readEntity.readEntity)(t,i));for(const r in i[e]){let u=_siFunciona.default.cloneObject(i);u[e]=[i[e][r]];for(const e of n)u=await(0,_useJoinClause.useJoinClause)(e,i,u);i[e][r][t]=u[t]}return i};exports.joinEntity=joinEntity;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.joinEntity=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.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");var _readEntity=require("./readEntity"),_retrieveRecord=require("../utilities/retrieveRecord"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_fieldType=require("../utilities/fieldType"),_semiJoin=require("../utilities/semiJoin"),_joinBases=require("../utilities/joinBases");const joinSteps=async e=>{const t=[];for(const{propertyA:r,comparator:s,propertyB:i}of e){const{entity:e,property:o}=(0,_splitEntityProperty.splitEntityProperty)(r),{entity:n,property:p}=(0,_splitEntityProperty.splitEntityProperty)(i);t.push({entityA:e,propertyA:o,typeA:(0,_fieldType.fieldType)(await(0,_retrieveRecord.retrieveRecord)(e),o),comparator:s,entityB:n,propertyB:p,typeB:(0,_fieldType.fieldType)(await(0,_retrieveRecord.retrieveRecord)(n),p)})}return t},joinedRows=e=>e.map((e=>structuredClone(e))),joinEntity=async(e,t,r={},s=[],i=!1,o=!1)=>{if(i||(r=await(0,_readEntity.readEntity)(e,r)),!s.length)return r;const n=await joinSteps(s);if(!r[e].length)return r;const p=await(0,_joinBases.joinBases)(e,t,r,n,o);for(const s of r[e]){const r=new Map(p);r.set(e,{rows:[s]});for(const e of n)r.set(e.entityA,{rows:(0,_semiJoin.semiJoin)(r.get(e.entityA),r.get(e.entityB),e.propertyA,e.propertyB,e.comparator,e.typeA)}),r.set(e.entityB,{rows:(0,_semiJoin.semiJoin)(r.get(e.entityB),r.get(e.entityA),e.propertyB,e.propertyA,e.comparator,e.typeB)});s[t]=joinedRows(r.get(t).rows)}return r};exports.joinEntity=joinEntity;
@@ -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[]>;