@rljson/db 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cars-example.d.ts +5 -1
- package/dist/core.d.ts +6 -5
- package/dist/db.d.ts +102 -55
- package/dist/db.js +2552 -368
- package/dist/notify.d.ts +4 -4
- package/package.json +21 -19
package/dist/db.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { equals, merge } from "@rljson/json";
|
|
2
|
+
import { timeId, createInsertHistoryTableCfg, Validate, BaseValidator, Route, validateInsert, isTimeId } from "@rljson/rljson";
|
|
3
|
+
import { traverse } from "object-traversal";
|
|
4
|
+
import { rmhsh, hsh, Hash, hip } from "@rljson/hash";
|
|
1
5
|
import { IoMem } from "@rljson/io";
|
|
2
|
-
import {
|
|
3
|
-
import { rmhsh, hsh } from "@rljson/hash";
|
|
4
|
-
import { equals } from "@rljson/json";
|
|
5
|
-
// @license
|
|
6
|
+
import { compileExpression } from "filtrex";
|
|
6
7
|
class BaseController {
|
|
7
8
|
constructor(_core, _tableKey) {
|
|
8
9
|
this._core = _core;
|
|
@@ -27,6 +28,9 @@ class BaseController {
|
|
|
27
28
|
if (typeof where === "string") {
|
|
28
29
|
return this._getByHash(where, filter);
|
|
29
30
|
} else if (typeof where === "object" && where !== null) {
|
|
31
|
+
if (Object.keys(where).length === 1 && "_hash" in where && typeof where["_hash"] === "string") {
|
|
32
|
+
return this._getByHash(where["_hash"], filter);
|
|
33
|
+
}
|
|
30
34
|
return this._getByWhere(where, filter);
|
|
31
35
|
} else {
|
|
32
36
|
return Promise.resolve({});
|
|
@@ -64,7 +68,6 @@ class BaseController {
|
|
|
64
68
|
return rows;
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
|
-
// @license
|
|
68
71
|
class CakeController extends BaseController {
|
|
69
72
|
constructor(_core, _tableKey, _refs) {
|
|
70
73
|
super(_core, _tableKey);
|
|
@@ -72,6 +75,7 @@ class CakeController extends BaseController {
|
|
|
72
75
|
this._tableKey = _tableKey;
|
|
73
76
|
this._refs = _refs;
|
|
74
77
|
}
|
|
78
|
+
_table = null;
|
|
75
79
|
_baseLayers = {};
|
|
76
80
|
async init() {
|
|
77
81
|
if (this._tableKey.endsWith("Cake") === false) {
|
|
@@ -80,11 +84,11 @@ class CakeController extends BaseController {
|
|
|
80
84
|
);
|
|
81
85
|
}
|
|
82
86
|
const rljson = await this._core.dumpTable(this._tableKey);
|
|
83
|
-
|
|
84
|
-
if (
|
|
87
|
+
this._table = rljson[this._tableKey];
|
|
88
|
+
if (this._table._type !== "cakes") {
|
|
85
89
|
throw new Error(`Table ${this._tableKey} is not of type cakes.`);
|
|
86
90
|
}
|
|
87
|
-
if (this._refs && this._refs.base) {
|
|
91
|
+
if (this._refs && this._refs.base && this._refs.base.length > 0) {
|
|
88
92
|
const {
|
|
89
93
|
[this._tableKey]: { _data: baseCakes }
|
|
90
94
|
} = await this._core.readRow(this._tableKey, this._refs.base);
|
|
@@ -93,26 +97,51 @@ class CakeController extends BaseController {
|
|
|
93
97
|
}
|
|
94
98
|
const baseCake = baseCakes[0];
|
|
95
99
|
this._baseLayers = rmhsh(baseCake.layers);
|
|
96
|
-
if (!this._refs.sliceIdsTable || !this._refs.sliceIdsRow) {
|
|
97
|
-
this._refs = {
|
|
98
|
-
sliceIdsTable: baseCake.sliceIdsTable,
|
|
99
|
-
sliceIdsRow: baseCake.sliceIdsRow
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
100
|
} else {
|
|
103
|
-
const cake =
|
|
101
|
+
const cake = this._table._data[0];
|
|
104
102
|
this._refs = {
|
|
105
103
|
sliceIdsTable: cake.sliceIdsTable,
|
|
106
104
|
sliceIdsRow: cake.sliceIdsRow
|
|
107
105
|
};
|
|
106
|
+
this._baseLayers = rmhsh(cake.layers);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async getChildRefs(where, filter) {
|
|
110
|
+
if (!this._table) {
|
|
111
|
+
throw new Error(`Controller not initialized.`);
|
|
108
112
|
}
|
|
113
|
+
const childRefs = [];
|
|
114
|
+
const { [this._tableKey]: table } = await this.get(where, filter);
|
|
115
|
+
const cakes = table._data;
|
|
116
|
+
for (const cake of cakes) {
|
|
117
|
+
for (const layerTable of Object.keys(cake.layers)) {
|
|
118
|
+
if (layerTable.startsWith("_")) continue;
|
|
119
|
+
childRefs.push({
|
|
120
|
+
tableKey: layerTable,
|
|
121
|
+
ref: cake.layers[layerTable]
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return childRefs;
|
|
109
126
|
}
|
|
110
|
-
async
|
|
127
|
+
async insert(command, value, origin, refs) {
|
|
111
128
|
if (!command.startsWith("add")) {
|
|
112
129
|
throw new Error(`Command ${command} is not supported by CakeController.`);
|
|
113
130
|
}
|
|
131
|
+
if (this._refs?.base) delete this._refs.base;
|
|
132
|
+
const normalizedValue = {};
|
|
133
|
+
for (const [layerTable, layerRef] of Object.entries(
|
|
134
|
+
value
|
|
135
|
+
)) {
|
|
136
|
+
if (Array.isArray(layerRef) && layerRef.length > 1) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`CakeController insert: Layer ref for table ${layerTable} cannot be an array of size > 1. No 1:n relations supported.`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
normalizedValue[layerTable] = Array.isArray(layerRef) ? layerRef[0] : layerRef;
|
|
142
|
+
}
|
|
114
143
|
const cake = {
|
|
115
|
-
layers: { ...this._baseLayers, ...
|
|
144
|
+
layers: { ...this._baseLayers, ...normalizedValue },
|
|
116
145
|
...refs || this._refs
|
|
117
146
|
};
|
|
118
147
|
const rlJson = { [this._tableKey]: { _data: [cake] } };
|
|
@@ -126,49 +155,27 @@ class CakeController extends BaseController {
|
|
|
126
155
|
//Unique id/timestamp
|
|
127
156
|
timeId: timeId()
|
|
128
157
|
};
|
|
129
|
-
return result;
|
|
158
|
+
return [result];
|
|
130
159
|
}
|
|
131
160
|
async get(where, filter) {
|
|
132
161
|
if (typeof where === "string") {
|
|
133
162
|
return this._getByHash(where, filter);
|
|
134
163
|
} else if (typeof where === "object" && where !== null) {
|
|
135
|
-
|
|
136
|
-
if (keys.length === 1 && keys[0].endsWith("Ref")) {
|
|
137
|
-
const tableKey = keys[0].replace("Ref", "");
|
|
138
|
-
return this._getByRef(tableKey, where[keys[0]], filter);
|
|
139
|
-
} else {
|
|
140
|
-
return this._getByWhere(where, filter);
|
|
141
|
-
}
|
|
164
|
+
return this._getByWhere(where, filter);
|
|
142
165
|
} else {
|
|
143
166
|
return Promise.resolve({});
|
|
144
167
|
}
|
|
145
168
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
filter
|
|
152
|
-
);
|
|
153
|
-
} else {
|
|
154
|
-
table = await this._core.dumpTable(this._tableKey);
|
|
155
|
-
}
|
|
156
|
-
const cakes = [];
|
|
157
|
-
for (const row of table[this._tableKey]._data) {
|
|
158
|
-
const cake = row;
|
|
159
|
-
const layers = cake.layers;
|
|
160
|
-
for (const layerTable of Object.keys(layers)) {
|
|
161
|
-
if (layerTable === tableKey && layers[layerTable] === ref) {
|
|
162
|
-
cakes.push(cake);
|
|
163
|
-
}
|
|
169
|
+
filterRow(row, key, value) {
|
|
170
|
+
const cake = row;
|
|
171
|
+
for (const [layerKey, layerRef] of Object.entries(cake.layers)) {
|
|
172
|
+
if (layerKey === key && layerRef === value) {
|
|
173
|
+
return true;
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
|
-
return
|
|
167
|
-
[this._tableKey]: { _data: cakes, _type: "cakes" }
|
|
168
|
-
};
|
|
176
|
+
return false;
|
|
169
177
|
}
|
|
170
178
|
}
|
|
171
|
-
// @license
|
|
172
179
|
class ComponentController extends BaseController {
|
|
173
180
|
constructor(_core, _tableKey, _refs) {
|
|
174
181
|
super(_core, _tableKey);
|
|
@@ -176,6 +183,9 @@ class ComponentController extends BaseController {
|
|
|
176
183
|
this._tableKey = _tableKey;
|
|
177
184
|
this._refs = _refs;
|
|
178
185
|
}
|
|
186
|
+
_tableCfg = null;
|
|
187
|
+
_resolvedColumns = null;
|
|
188
|
+
_refTableKeyToColumnKeyMap = null;
|
|
179
189
|
async init() {
|
|
180
190
|
if (!!this._refs && !this._refs.base) {
|
|
181
191
|
throw new Error(`Refs are not required on ComponentController.`);
|
|
@@ -185,8 +195,13 @@ class ComponentController extends BaseController {
|
|
|
185
195
|
if (table._type !== "components") {
|
|
186
196
|
throw new Error(`Table ${this._tableKey} is not of type components.`);
|
|
187
197
|
}
|
|
198
|
+
this._tableCfg = await this._core.tableCfg(this._tableKey);
|
|
199
|
+
this._resolvedColumns = await this._resolveReferenceColumns({
|
|
200
|
+
base: this._tableCfg.columns
|
|
201
|
+
});
|
|
202
|
+
this._refTableKeyToColumnKeyMap = this._createRefTableKeyToColumnKeyMap();
|
|
188
203
|
}
|
|
189
|
-
async
|
|
204
|
+
async insert(command, value, origin, refs) {
|
|
190
205
|
if (!command.startsWith("add")) {
|
|
191
206
|
throw new Error(
|
|
192
207
|
`Command ${command} is not supported by ComponentController.`
|
|
@@ -195,21 +210,329 @@ class ComponentController extends BaseController {
|
|
|
195
210
|
if (!!refs) {
|
|
196
211
|
throw new Error(`Refs are not supported on ComponentController.`);
|
|
197
212
|
}
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
|
|
213
|
+
const values = [];
|
|
214
|
+
const referencedValues = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const [k, v] of Object.entries(value)) {
|
|
216
|
+
if (Array.isArray(v)) {
|
|
217
|
+
for (const possibleRef of v) {
|
|
218
|
+
if (typeof possibleRef === "object" && possibleRef.hasOwnProperty("_ref") && possibleRef.hasOwnProperty("_value")) {
|
|
219
|
+
const ref = possibleRef._ref;
|
|
220
|
+
const val = possibleRef._value;
|
|
221
|
+
if (!referencedValues.has(ref))
|
|
222
|
+
referencedValues.set(ref, { [k]: val });
|
|
223
|
+
else {
|
|
224
|
+
const existing = referencedValues.get(ref);
|
|
225
|
+
referencedValues.set(ref, { ...{ [k]: val }, ...existing });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (referencedValues.size > 0) {
|
|
232
|
+
for (const refValue of referencedValues.values()) {
|
|
233
|
+
values.push({ ...value, ...refValue });
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
values.push(value);
|
|
237
|
+
}
|
|
238
|
+
const components = values;
|
|
239
|
+
const results = [];
|
|
240
|
+
for (const component of components) {
|
|
241
|
+
delete component._somethingToInsert;
|
|
242
|
+
const rlJson = { [this._tableKey]: { _data: [component] } };
|
|
243
|
+
await this._core.import(rlJson);
|
|
244
|
+
const result = {
|
|
245
|
+
//Ref to component
|
|
246
|
+
[this._tableKey + "Ref"]: hsh(component)._hash,
|
|
247
|
+
//Data from edit
|
|
248
|
+
route: "",
|
|
249
|
+
origin,
|
|
250
|
+
//Unique id/timestamp
|
|
251
|
+
timeId: timeId()
|
|
252
|
+
};
|
|
253
|
+
results.push(result);
|
|
254
|
+
}
|
|
255
|
+
return results;
|
|
256
|
+
}
|
|
257
|
+
// ...........................................................................
|
|
258
|
+
/**
|
|
259
|
+
* Retrieves references to child entries in related tables based on a condition.
|
|
260
|
+
* @param where - The condition to filter the data.
|
|
261
|
+
* @param filter - Optional filter to apply to the data.
|
|
262
|
+
* @returns
|
|
263
|
+
*/
|
|
264
|
+
async getChildRefs(where, filter) {
|
|
265
|
+
const { [this._tableKey]: table } = await this.get(where, filter);
|
|
266
|
+
const { columns } = await this._core.tableCfg(this._tableKey);
|
|
267
|
+
const childRefs = /* @__PURE__ */ new Map();
|
|
268
|
+
for (const colCfg of columns) {
|
|
269
|
+
if (!colCfg.ref || colCfg.ref === void 0) continue;
|
|
270
|
+
const propertyKey = colCfg.key;
|
|
271
|
+
const childRefTableKey = colCfg.ref.tableKey;
|
|
272
|
+
for (const row of table._data) {
|
|
273
|
+
const refValue = row[propertyKey];
|
|
274
|
+
if (typeof refValue === "string") {
|
|
275
|
+
childRefs.set(`${childRefTableKey}|${propertyKey}|${refValue}`, {
|
|
276
|
+
tableKey: childRefTableKey,
|
|
277
|
+
columnKey: propertyKey,
|
|
278
|
+
ref: refValue
|
|
279
|
+
});
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (Array.isArray(refValue)) {
|
|
283
|
+
for (const refItem of refValue) {
|
|
284
|
+
if (typeof refItem === "string") {
|
|
285
|
+
childRefs.set(`${childRefTableKey}|${propertyKey}|${refItem}`, {
|
|
286
|
+
tableKey: childRefTableKey,
|
|
287
|
+
columnKey: propertyKey,
|
|
288
|
+
ref: refItem
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return Array.from(childRefs.values());
|
|
297
|
+
}
|
|
298
|
+
// ...........................................................................
|
|
299
|
+
/**
|
|
300
|
+
* Fetches a specific entry from the table by a partial match. Resolves references as needed.
|
|
301
|
+
* @param where - An object representing a partial match.
|
|
302
|
+
* @returns A promise that resolves to an array of entries matching the criteria, or null if no entries are found.
|
|
303
|
+
*/
|
|
304
|
+
async _getByWhere(where, filter) {
|
|
305
|
+
const consolidatedWheres = [];
|
|
306
|
+
const consolidatedRows = /* @__PURE__ */ new Map();
|
|
307
|
+
const hasReferenceColumns = this._hasReferenceColumns(where);
|
|
308
|
+
if (hasReferenceColumns) {
|
|
309
|
+
const resolvedReferences = await this._resolveReferences(this._getWhereReferences(where));
|
|
310
|
+
const refWhereClauses = this._referencesToWhereClauses(resolvedReferences);
|
|
311
|
+
for (const refWhere of refWhereClauses) {
|
|
312
|
+
consolidatedWheres.push({
|
|
313
|
+
...this._getWhereBase(where),
|
|
314
|
+
...refWhere
|
|
315
|
+
});
|
|
316
|
+
const {
|
|
317
|
+
[this._tableKey]: { _data: tableData }
|
|
318
|
+
} = await this._core.dumpTable(this._tableKey);
|
|
319
|
+
const column = Object.keys(refWhere)[0];
|
|
320
|
+
const refValue = refWhere[column];
|
|
321
|
+
for (const row of tableData) {
|
|
322
|
+
if (this.filterRow(row, column, refValue)) {
|
|
323
|
+
consolidatedRows.set(row._hash, row);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
const {
|
|
329
|
+
[this._tableKey]: { _data: rows }
|
|
330
|
+
} = await this._core.readRows(this._tableKey, {
|
|
331
|
+
...where,
|
|
332
|
+
...filter
|
|
333
|
+
});
|
|
334
|
+
for (const row of rows) {
|
|
335
|
+
consolidatedRows.set(row._hash, row);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
201
338
|
return {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
origin,
|
|
207
|
-
//Unique id/timestamp
|
|
208
|
-
timeId: timeId()
|
|
339
|
+
[this._tableKey]: {
|
|
340
|
+
_data: Array.from(consolidatedRows.values()),
|
|
341
|
+
_type: "components"
|
|
342
|
+
}
|
|
209
343
|
};
|
|
210
344
|
}
|
|
345
|
+
_referencesToWhereClauses(references) {
|
|
346
|
+
const whereClauses = [];
|
|
347
|
+
for (const [tableKey, refs] of Object.entries(references)) {
|
|
348
|
+
const wherePropertyKeys = this._refTableKeyToColumnKeyMap?.[tableKey];
|
|
349
|
+
for (const propKey of wherePropertyKeys) {
|
|
350
|
+
for (const ref of refs) {
|
|
351
|
+
whereClauses.push({ [propKey]: ref });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return whereClauses;
|
|
356
|
+
}
|
|
357
|
+
_createRefTableKeyToColumnKeyMap() {
|
|
358
|
+
const map = {};
|
|
359
|
+
const columns = this._tableCfg?.columns;
|
|
360
|
+
for (const colCfg of columns) {
|
|
361
|
+
if (colCfg.ref) {
|
|
362
|
+
const tableKey = colCfg.ref.tableKey;
|
|
363
|
+
if (!map[tableKey]) {
|
|
364
|
+
map[tableKey] = [];
|
|
365
|
+
}
|
|
366
|
+
map[tableKey].push(colCfg.key);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return map;
|
|
370
|
+
}
|
|
371
|
+
// ...........................................................................
|
|
372
|
+
/**
|
|
373
|
+
* Extracts reference columns from the where clause.
|
|
374
|
+
* @param where - The condition to filter the data.
|
|
375
|
+
* @returns An object representing only the reference columns in the where clause.
|
|
376
|
+
*/
|
|
377
|
+
_getWhereReferences(where) {
|
|
378
|
+
const whereRefs = {};
|
|
379
|
+
for (const colCfg of this._referenceColumns) {
|
|
380
|
+
if (colCfg.key in where) {
|
|
381
|
+
whereRefs[colCfg.key] = where[colCfg.key];
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return whereRefs;
|
|
385
|
+
}
|
|
386
|
+
// ...........................................................................
|
|
387
|
+
/**
|
|
388
|
+
* Removes reference columns from the where clause.
|
|
389
|
+
* @param where - The condition to filter the data.
|
|
390
|
+
* @returns An object representing the where clause without reference columns.
|
|
391
|
+
*/
|
|
392
|
+
_getWhereBase(where) {
|
|
393
|
+
const whereWithoutRefs = { ...where };
|
|
394
|
+
for (const colCfg of this._referenceColumns) {
|
|
395
|
+
if (colCfg.key in whereWithoutRefs) {
|
|
396
|
+
delete whereWithoutRefs[colCfg.key];
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return whereWithoutRefs;
|
|
400
|
+
}
|
|
401
|
+
// ...........................................................................
|
|
402
|
+
/**
|
|
403
|
+
* Retrieves all reference columns from the resolved columns.
|
|
404
|
+
* @returns An array of ColumnCfg representing the reference columns.
|
|
405
|
+
*/
|
|
406
|
+
get _referenceColumns() {
|
|
407
|
+
if (!this._resolvedColumns) {
|
|
408
|
+
throw new Error(
|
|
409
|
+
`Resolved columns are not available for table ${this._tableKey}. You must call init() first.`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
const references = [];
|
|
413
|
+
for (const refCols of Object.values(this._resolvedColumns.references)) {
|
|
414
|
+
references.push(...refCols);
|
|
415
|
+
}
|
|
416
|
+
return references;
|
|
417
|
+
}
|
|
418
|
+
// ...........................................................................
|
|
419
|
+
/**
|
|
420
|
+
* Checks if the where clause contains any reference columns.
|
|
421
|
+
* @param where - The condition to filter the data.
|
|
422
|
+
* @returns A promise that resolves to true if reference columns are present, false otherwise.
|
|
423
|
+
*/
|
|
424
|
+
_hasReferenceColumns(where) {
|
|
425
|
+
if (!this._resolvedColumns) {
|
|
426
|
+
throw new Error(
|
|
427
|
+
`Resolved columns are not available for table ${this._tableKey}. You must call init() first.`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
for (const colCfg of this._referenceColumns) {
|
|
431
|
+
if (colCfg.key in where) {
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
// ...........................................................................
|
|
438
|
+
/**
|
|
439
|
+
* Resolves reference columns in the where clause.
|
|
440
|
+
* @param columns - The columns to resolve.
|
|
441
|
+
* @returns A promise that resolves to an object containing base and reference columns.
|
|
442
|
+
*/
|
|
443
|
+
async _resolveReferenceColumns(columns) {
|
|
444
|
+
const base = [];
|
|
445
|
+
const references = {};
|
|
446
|
+
for (const col of columns.base) {
|
|
447
|
+
if (!!col.ref) {
|
|
448
|
+
const refTableKey = col.ref.tableKey;
|
|
449
|
+
const { columns: refColumns } = await this._core.tableCfg(refTableKey);
|
|
450
|
+
if (!references[refTableKey]) {
|
|
451
|
+
references[refTableKey] = [];
|
|
452
|
+
}
|
|
453
|
+
const refsHaveRefs = refColumns.some((c) => !!c.ref);
|
|
454
|
+
if (refsHaveRefs) {
|
|
455
|
+
const resolvedRefColumns = await this._resolveReferenceColumns({
|
|
456
|
+
base: refColumns
|
|
457
|
+
});
|
|
458
|
+
references[refTableKey].push(...resolvedRefColumns.base);
|
|
459
|
+
}
|
|
460
|
+
references[refTableKey].push(...refColumns);
|
|
461
|
+
} else {
|
|
462
|
+
base.push(col);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return { base, references };
|
|
466
|
+
}
|
|
467
|
+
// ...........................................................................
|
|
468
|
+
/**
|
|
469
|
+
* Resolves references based on the where clause.
|
|
470
|
+
* @param where - The condition to filter the data.
|
|
471
|
+
* @returns - A promise that resolves to an object containing resolved references.
|
|
472
|
+
*/
|
|
473
|
+
async _resolveReferences(where) {
|
|
474
|
+
if (!this._resolvedColumns) {
|
|
475
|
+
throw new Error(
|
|
476
|
+
`Resolved columns are not available for table ${this._tableKey}. You must call init() first.`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
const resolvedReferences = {};
|
|
480
|
+
const references = this._resolvedColumns.references;
|
|
481
|
+
for (const [tableKey, refColumns] of Object.entries(references)) {
|
|
482
|
+
const whereForTable = {};
|
|
483
|
+
for (const colCfg of refColumns) {
|
|
484
|
+
if (colCfg.key in where) {
|
|
485
|
+
whereForTable[colCfg.key] = where[colCfg.key];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (Object.keys(whereForTable).length === 0) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const refRows = await this._readRowsWithReferences(
|
|
492
|
+
tableKey,
|
|
493
|
+
whereForTable
|
|
494
|
+
);
|
|
495
|
+
const refs = refRows[tableKey]._data.map(
|
|
496
|
+
(r) => r._hash
|
|
497
|
+
);
|
|
498
|
+
resolvedReferences[tableKey] = refs;
|
|
499
|
+
}
|
|
500
|
+
return resolvedReferences;
|
|
501
|
+
}
|
|
502
|
+
async _readRowsWithReferences(table, where) {
|
|
503
|
+
const splitted = [];
|
|
504
|
+
for (const [key, value] of Object.entries(where)) {
|
|
505
|
+
if (Array.isArray(value)) {
|
|
506
|
+
for (const v of value) {
|
|
507
|
+
splitted.push({ ...where, ...{ [key]: v } });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (splitted.length > 0) {
|
|
512
|
+
const results = [];
|
|
513
|
+
for (const s of splitted) {
|
|
514
|
+
results.push(await this._core.readRows(table, s));
|
|
515
|
+
}
|
|
516
|
+
return merge(...results);
|
|
517
|
+
} else {
|
|
518
|
+
return this._core.readRows(table, where);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
filterRow(row, key, value) {
|
|
522
|
+
for (const [propertyKey, propertyValue] of Object.entries(row)) {
|
|
523
|
+
if (propertyKey === key && equals(propertyValue, value)) {
|
|
524
|
+
return true;
|
|
525
|
+
} else if (Array.isArray(propertyValue)) {
|
|
526
|
+
for (const item of propertyValue) {
|
|
527
|
+
if (equals(item, value)) {
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
211
535
|
}
|
|
212
|
-
// @license
|
|
213
536
|
class LayerController extends BaseController {
|
|
214
537
|
constructor(_core, _tableKey, _refs) {
|
|
215
538
|
super(_core, _tableKey);
|
|
@@ -236,10 +559,7 @@ class LayerController extends BaseController {
|
|
|
236
559
|
throw new Error(`Base layer ${this._refs.base} does not exist.`);
|
|
237
560
|
}
|
|
238
561
|
const baseLayer = baseLayers[0];
|
|
239
|
-
if (
|
|
240
|
-
/* v8 ignore start */
|
|
241
|
-
!this._refs.sliceIdsTable || !this._refs.sliceIdsRow || !this._refs.componentsTable
|
|
242
|
-
) {
|
|
562
|
+
if (!this._refs.sliceIdsTable || !this._refs.sliceIdsRow || !this._refs.componentsTable) {
|
|
243
563
|
this._refs = {
|
|
244
564
|
sliceIdsTable: baseLayer.sliceIdsTable,
|
|
245
565
|
sliceIdsTableRow: baseLayer.sliceIdsTableRow,
|
|
@@ -255,18 +575,29 @@ class LayerController extends BaseController {
|
|
|
255
575
|
};
|
|
256
576
|
}
|
|
257
577
|
}
|
|
258
|
-
async
|
|
578
|
+
async insert(command, value, origin, refs) {
|
|
259
579
|
if (!command.startsWith("add") && !command.startsWith("remove")) {
|
|
260
580
|
throw new Error(
|
|
261
581
|
`Command ${command} is not supported by LayerController.`
|
|
262
582
|
);
|
|
263
583
|
}
|
|
584
|
+
const normalizedValue = {};
|
|
585
|
+
for (const [sliceId, compRef] of Object.entries(
|
|
586
|
+
value
|
|
587
|
+
)) {
|
|
588
|
+
if (Array.isArray(compRef) && compRef.length > 1) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
`LayerController insert: Component ref for slice ${sliceId} cannot be an array of size > 1. No 1:n relations supported.`
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
normalizedValue[sliceId] = Array.isArray(compRef) ? compRef[0] : compRef;
|
|
594
|
+
}
|
|
264
595
|
const layer = command.startsWith("add") === true ? {
|
|
265
|
-
add:
|
|
596
|
+
add: normalizedValue,
|
|
266
597
|
...refs || this._refs
|
|
267
598
|
} : {
|
|
268
599
|
add: {},
|
|
269
|
-
remove:
|
|
600
|
+
remove: normalizedValue,
|
|
270
601
|
...refs || this._refs
|
|
271
602
|
};
|
|
272
603
|
const rlJson = { [this._tableKey]: { _data: [layer] } };
|
|
@@ -280,50 +611,41 @@ class LayerController extends BaseController {
|
|
|
280
611
|
//Unique id/timestamp
|
|
281
612
|
timeId: timeId()
|
|
282
613
|
};
|
|
283
|
-
return result;
|
|
614
|
+
return [result];
|
|
284
615
|
}
|
|
285
616
|
async get(where, filter) {
|
|
286
617
|
if (typeof where === "string") {
|
|
287
618
|
return this._getByHash(where, filter);
|
|
288
|
-
} else if (typeof where === "object" && where !== null) {
|
|
289
|
-
const keys = Object.keys(where);
|
|
290
|
-
if (keys.length === 1 && keys[0].endsWith("Ref")) {
|
|
291
|
-
const tableKey = keys[0].replace("Ref", "");
|
|
292
|
-
return this._getByRef(tableKey, where[keys[0]], filter);
|
|
293
|
-
} else {
|
|
294
|
-
return this._getByWhere(where, filter);
|
|
295
|
-
}
|
|
296
619
|
} else {
|
|
297
|
-
return
|
|
620
|
+
return this._getByWhere(where, filter);
|
|
298
621
|
}
|
|
299
622
|
}
|
|
300
|
-
async
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
...filter
|
|
305
|
-
});
|
|
306
|
-
} else {
|
|
307
|
-
table = await this._core.dumpTable(this._tableKey);
|
|
308
|
-
}
|
|
309
|
-
const layers = [];
|
|
310
|
-
for (const row of table[this._tableKey]._data) {
|
|
623
|
+
async getChildRefs(where, filter) {
|
|
624
|
+
const { [this._tableKey]: table } = await this.get(where, filter);
|
|
625
|
+
const childRefs = [];
|
|
626
|
+
for (const row of table._data) {
|
|
311
627
|
const layer = row;
|
|
312
|
-
|
|
313
|
-
continue;
|
|
628
|
+
for (const [sliceId, compRef] of Object.entries(layer.add)) {
|
|
629
|
+
if (sliceId.startsWith("_")) continue;
|
|
630
|
+
childRefs.push({
|
|
631
|
+
tableKey: layer.componentsTable,
|
|
632
|
+
ref: compRef
|
|
633
|
+
});
|
|
314
634
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
635
|
+
}
|
|
636
|
+
return childRefs;
|
|
637
|
+
}
|
|
638
|
+
filterRow(row, _, value) {
|
|
639
|
+
const layer = row;
|
|
640
|
+
const compRef = value;
|
|
641
|
+
for (const componentRef of Object.values(layer.add)) {
|
|
642
|
+
if (componentRef === compRef) {
|
|
643
|
+
return true;
|
|
319
644
|
}
|
|
320
645
|
}
|
|
321
|
-
return
|
|
322
|
-
[this._tableKey]: { _data: layers, _type: "layers" }
|
|
323
|
-
};
|
|
646
|
+
return false;
|
|
324
647
|
}
|
|
325
648
|
}
|
|
326
|
-
// @license
|
|
327
649
|
const createController = async (type, core, tableKey, refs) => {
|
|
328
650
|
let ctrl;
|
|
329
651
|
switch (type) {
|
|
@@ -342,7 +664,6 @@ const createController = async (type, core, tableKey, refs) => {
|
|
|
342
664
|
await ctrl.init();
|
|
343
665
|
return ctrl;
|
|
344
666
|
};
|
|
345
|
-
// @license
|
|
346
667
|
class Core {
|
|
347
668
|
// ...........................................................................
|
|
348
669
|
constructor(_io) {
|
|
@@ -353,12 +674,12 @@ class Core {
|
|
|
353
674
|
};
|
|
354
675
|
// ...........................................................................
|
|
355
676
|
/**
|
|
356
|
-
* Creates a table and an
|
|
677
|
+
* Creates a table and an insertHistory for the table
|
|
357
678
|
* @param tableCfg TableCfg of table to create
|
|
358
679
|
*/
|
|
359
|
-
async
|
|
680
|
+
async createTableWithInsertHistory(tableCfg) {
|
|
360
681
|
await this.createTable(tableCfg);
|
|
361
|
-
await this.
|
|
682
|
+
await this.createInsertHistory(tableCfg);
|
|
362
683
|
}
|
|
363
684
|
/**
|
|
364
685
|
* Creates a table
|
|
@@ -368,11 +689,11 @@ class Core {
|
|
|
368
689
|
return this._io.createOrExtendTable({ tableCfg });
|
|
369
690
|
}
|
|
370
691
|
/**
|
|
371
|
-
* Creates an
|
|
692
|
+
* Creates an insertHistory table for a given table
|
|
372
693
|
* @param tableCfg TableCfg of table
|
|
373
694
|
*/
|
|
374
|
-
async
|
|
375
|
-
const cfg =
|
|
695
|
+
async createInsertHistory(tableCfg) {
|
|
696
|
+
const cfg = createInsertHistoryTableCfg(tableCfg);
|
|
376
697
|
await this.createTable(cfg);
|
|
377
698
|
}
|
|
378
699
|
// ...........................................................................
|
|
@@ -422,6 +743,21 @@ class Core {
|
|
|
422
743
|
return contentType;
|
|
423
744
|
}
|
|
424
745
|
// ...........................................................................
|
|
746
|
+
async tableCfg(table) {
|
|
747
|
+
const { [table]: dump } = await this._io.dumpTable({ table });
|
|
748
|
+
const tableCfgRef = dump._tableCfg;
|
|
749
|
+
const tableCfgs = await this._io.rawTableCfgs();
|
|
750
|
+
let tableCfg;
|
|
751
|
+
if (!tableCfgRef) {
|
|
752
|
+
tableCfg = tableCfgs.find((tc) => tc.key === table);
|
|
753
|
+
} else {
|
|
754
|
+
tableCfg = tableCfgs.find(
|
|
755
|
+
(tc) => tc.key === table && tc._hash === tableCfgRef
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
return tableCfg;
|
|
759
|
+
}
|
|
760
|
+
// ...........................................................................
|
|
425
761
|
/** Reads a specific row from a database table */
|
|
426
762
|
readRow(table, rowHash) {
|
|
427
763
|
return this._io.readRows({ table, where: { _hash: rowHash } });
|
|
@@ -431,184 +767,1956 @@ class Core {
|
|
|
431
767
|
return this._io.readRows({ table, where });
|
|
432
768
|
}
|
|
433
769
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
770
|
+
class ColumnSelection {
|
|
771
|
+
constructor(columns) {
|
|
772
|
+
this._throwOnWrongAlias(columns);
|
|
773
|
+
this.routes = columns.map((column) => column.route);
|
|
774
|
+
this.routeHashes = this.routes.map(ColumnSelection.calcHash);
|
|
775
|
+
this.aliases = columns.map((column) => column.alias);
|
|
776
|
+
this.columns = this._initColumns(columns);
|
|
777
|
+
ColumnSelection.check(this.aliases, this.routes);
|
|
439
778
|
}
|
|
440
779
|
// ...........................................................................
|
|
441
780
|
/**
|
|
442
|
-
*
|
|
443
|
-
* @param
|
|
444
|
-
* @
|
|
781
|
+
* Returns unique routes from a list of routes
|
|
782
|
+
* @param routes - The list of routes
|
|
783
|
+
* @returns Unique routes
|
|
445
784
|
*/
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
]);
|
|
785
|
+
static uniqueRoutes(routes) {
|
|
786
|
+
return Array.from(new Set(routes.map((r) => r.flat))).map(
|
|
787
|
+
(flat) => Route.fromFlat(flat)
|
|
788
|
+
);
|
|
451
789
|
}
|
|
452
790
|
// ...........................................................................
|
|
453
791
|
/**
|
|
454
|
-
*
|
|
455
|
-
* @param
|
|
456
|
-
* @
|
|
792
|
+
* Returns a ColumnSelection from a list of route segments
|
|
793
|
+
* @param routeSegmentsList - A list of route segments
|
|
794
|
+
* @returns A ColumnSelection object
|
|
457
795
|
*/
|
|
458
|
-
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
796
|
+
static fromRoutes(routes) {
|
|
797
|
+
const definition = [];
|
|
798
|
+
const aliasCountMap = {};
|
|
799
|
+
for (const route of this.uniqueRoutes(routes)) {
|
|
800
|
+
const alias = route.root.tableKey;
|
|
801
|
+
let uniqueAlias = alias;
|
|
802
|
+
const aliasCount = aliasCountMap[alias] ?? 0;
|
|
803
|
+
if (aliasCount > 0) {
|
|
804
|
+
uniqueAlias = `${alias}${aliasCount}`;
|
|
805
|
+
}
|
|
806
|
+
aliasCountMap[alias] = aliasCount + 1;
|
|
807
|
+
definition.push({
|
|
808
|
+
key: uniqueAlias,
|
|
809
|
+
type: "jsonValue",
|
|
810
|
+
alias: uniqueAlias,
|
|
811
|
+
route: route.flat.slice(1),
|
|
812
|
+
titleLong: "",
|
|
813
|
+
titleShort: ""
|
|
814
|
+
});
|
|
465
815
|
}
|
|
816
|
+
return new ColumnSelection(definition);
|
|
466
817
|
}
|
|
467
818
|
// ...........................................................................
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
const callbacks = this._callbacks.get(route.flat);
|
|
475
|
-
if (callbacks) {
|
|
476
|
-
for (const cb of callbacks) {
|
|
477
|
-
cb(editProtocolRow);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
819
|
+
columns;
|
|
820
|
+
routes;
|
|
821
|
+
aliases;
|
|
822
|
+
routeHashes;
|
|
823
|
+
metadata(key) {
|
|
824
|
+
return this.columns.map((column) => column[key]);
|
|
480
825
|
}
|
|
481
826
|
// ...........................................................................
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
827
|
+
static merge(columnSelections) {
|
|
828
|
+
const routes = columnSelections.map((selection) => selection.routes).flat();
|
|
829
|
+
const routesWithoutDuplicates = Array.from(new Set(routes));
|
|
830
|
+
return ColumnSelection.fromRoutes(
|
|
831
|
+
routesWithoutDuplicates.map((route) => Route.fromFlat(route))
|
|
832
|
+
);
|
|
488
833
|
}
|
|
489
834
|
// ...........................................................................
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
* @param route The route to get callbacks for.
|
|
493
|
-
* @returns An array of callback functions registered for the specified route.
|
|
494
|
-
*/
|
|
495
|
-
getCallBacksForRoute(route) {
|
|
496
|
-
return this._callbacks.get(route.flat) || [];
|
|
835
|
+
static calcHash(str) {
|
|
836
|
+
return Hash.default.calcHash(str);
|
|
497
837
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
class Db {
|
|
501
|
-
/**
|
|
502
|
-
* Constructor
|
|
503
|
-
* @param _io - The Io instance used to read and write data
|
|
504
|
-
*/
|
|
505
|
-
constructor(_io) {
|
|
506
|
-
this._io = _io;
|
|
507
|
-
this.core = new Core(this._io);
|
|
508
|
-
this.notify = new Notify();
|
|
838
|
+
route(aliasRouteOrHash) {
|
|
839
|
+
return this.column(aliasRouteOrHash).route;
|
|
509
840
|
}
|
|
510
|
-
/**
|
|
511
|
-
* Core functionalities like importing data, setting and getting tables
|
|
512
|
-
*/
|
|
513
|
-
core;
|
|
514
|
-
/**
|
|
515
|
-
* Notification system to register callbacks on data changes
|
|
516
|
-
*/
|
|
517
|
-
notify;
|
|
518
841
|
// ...........................................................................
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
* @param route - The route to get data from
|
|
522
|
-
* @param where - Optional filter to apply to the data
|
|
523
|
-
* @returns An array of Rljson objects matching the route and filter
|
|
524
|
-
* @throws {Error} If the route is not valid or if any controller cannot be created
|
|
525
|
-
*/
|
|
526
|
-
async get(route, where) {
|
|
527
|
-
if (!route.isValid) throw new Error(`Route ${route.flat} is not valid.`);
|
|
528
|
-
const controllers = await this._indexedControllers(route);
|
|
529
|
-
return this._get(route, where, controllers);
|
|
842
|
+
alias(aliasRouteOrHash) {
|
|
843
|
+
return this.column(aliasRouteOrHash).alias;
|
|
530
844
|
}
|
|
531
845
|
// ...........................................................................
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
* @param where - The filter to apply to the root table
|
|
536
|
-
* @param controllers - A record of controllers keyed by table name
|
|
537
|
-
* @returns An array of Rljson objects matching the route and filter
|
|
538
|
-
*/
|
|
539
|
-
async _get(route, where, controllers) {
|
|
540
|
-
let filter = {};
|
|
541
|
-
if (Route.segmentHasRef(route.root)) {
|
|
542
|
-
let revision = "";
|
|
543
|
-
if (Route.segmentHasDefaultRef(route.root)) {
|
|
544
|
-
revision = Route.segmentRef(route.root);
|
|
545
|
-
} else {
|
|
546
|
-
revision = await this.getRefOfTimeId(
|
|
547
|
-
route.root.tableKey,
|
|
548
|
-
Route.segmentRef(route.root)
|
|
549
|
-
);
|
|
550
|
-
}
|
|
551
|
-
filter = { _hash: revision };
|
|
846
|
+
columnIndex(hashAliasOrRoute, throwIfNotExisting = true) {
|
|
847
|
+
if (typeof hashAliasOrRoute === "number") {
|
|
848
|
+
return hashAliasOrRoute;
|
|
552
849
|
}
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const rootRefs = rootRljson[route.root.tableKey]._data.map(
|
|
558
|
-
(r) => r._hash
|
|
559
|
-
);
|
|
560
|
-
if (route.segments.length === 1) {
|
|
561
|
-
return [rootRljson];
|
|
850
|
+
const str = Array.isArray(hashAliasOrRoute) ? hashAliasOrRoute.join("/") : hashAliasOrRoute;
|
|
851
|
+
const hashIndex = this.routeHashes.indexOf(str);
|
|
852
|
+
if (hashIndex >= 0) {
|
|
853
|
+
return hashIndex;
|
|
562
854
|
}
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const res = await this._get(
|
|
567
|
-
superiorRoute,
|
|
568
|
-
{
|
|
569
|
-
[route.root.tableKey + "Ref"]: ref
|
|
570
|
-
},
|
|
571
|
-
controllers
|
|
572
|
-
);
|
|
573
|
-
results.push(...res);
|
|
855
|
+
const aliasIndex = this.aliases.indexOf(str);
|
|
856
|
+
if (aliasIndex >= 0) {
|
|
857
|
+
return aliasIndex;
|
|
574
858
|
}
|
|
575
|
-
|
|
859
|
+
const routeIndex = this.routes.indexOf(str);
|
|
860
|
+
if (routeIndex < 0) {
|
|
861
|
+
if (throwIfNotExisting) {
|
|
862
|
+
throw new Error(`Unknown column alias or route: ${str}`);
|
|
863
|
+
}
|
|
864
|
+
return -1;
|
|
865
|
+
}
|
|
866
|
+
return routeIndex;
|
|
576
867
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
* Runs an edit by executing the appropriate controller(s) based on the edit's route
|
|
580
|
-
* @param edit - The edit to run
|
|
581
|
-
* @returns The result of the edit as an EditProtocolRow
|
|
582
|
-
* @throws {Error} If the edit is not valid or if any controller cannot be created
|
|
868
|
+
/***
|
|
869
|
+
* Returns the column config for a specific alias, route or hash.
|
|
583
870
|
*/
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
const errors = validateEdit(edit);
|
|
588
|
-
if (!!errors.hasErrors) {
|
|
589
|
-
throw new Error(`Edit is not valid:
|
|
590
|
-
${JSON.stringify(errors, null, 2)}`);
|
|
591
|
-
}
|
|
592
|
-
return this._run(edit, initialRoute, runs, options);
|
|
871
|
+
column(aliasRouteOrHash) {
|
|
872
|
+
const index = this.columnIndex(aliasRouteOrHash);
|
|
873
|
+
return this.columns[index];
|
|
593
874
|
}
|
|
594
875
|
// ...........................................................................
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
876
|
+
get count() {
|
|
877
|
+
return this.aliases.length;
|
|
878
|
+
}
|
|
879
|
+
// ...........................................................................
|
|
880
|
+
addedColumns(columnSelection) {
|
|
881
|
+
const a = this.routes.filter(
|
|
882
|
+
(route) => !columnSelection.routes.includes(route)
|
|
883
|
+
);
|
|
884
|
+
return a;
|
|
885
|
+
}
|
|
886
|
+
// ...........................................................................
|
|
887
|
+
static check(aliases, routes) {
|
|
888
|
+
const camelCaseRegex = /^[a-z][a-zA-Z0-9]*$/;
|
|
889
|
+
const invalidKeys = aliases.filter((key) => !camelCaseRegex.test(key));
|
|
890
|
+
if (invalidKeys.length > 0) {
|
|
891
|
+
throw new Error(
|
|
892
|
+
`Invalid alias "${invalidKeys[0]}". Aliases must be lower camel case.`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
const validValueRegex = /^[a-zA-Z0-9/]*$/;
|
|
896
|
+
const invalidValues = routes.filter(
|
|
897
|
+
(value) => !validValueRegex.test(value)
|
|
898
|
+
);
|
|
899
|
+
if (invalidValues.length > 0) {
|
|
900
|
+
throw new Error(
|
|
901
|
+
`Invalid route "${invalidValues}". Routes must only contain letters, numbers and slashes.`
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
const pathParts = routes.map((value) => value.split("/")).flat();
|
|
905
|
+
const invalidPathParts = pathParts.filter(
|
|
906
|
+
(part) => !camelCaseRegex.test(part)
|
|
907
|
+
);
|
|
908
|
+
if (invalidPathParts.length > 0) {
|
|
909
|
+
throw new Error(
|
|
910
|
+
`Invalid route segment "${invalidPathParts[0]}". Route segments must be lower camel case.`
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
const routeCountMap = {};
|
|
914
|
+
routes.forEach((value) => {
|
|
915
|
+
routeCountMap[value] = (routeCountMap[value] ?? 0) + 1;
|
|
916
|
+
});
|
|
917
|
+
const duplicateRoutes = Object.entries(routeCountMap).filter(([, count]) => count > 1).map(([route]) => route);
|
|
918
|
+
if (duplicateRoutes.length > 0) {
|
|
919
|
+
throw new Error(
|
|
920
|
+
`Duplicate route ${duplicateRoutes[0]}. A column must only occur once.`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
// ######################
|
|
925
|
+
// Private
|
|
926
|
+
// ######################
|
|
927
|
+
_throwOnWrongAlias(columns) {
|
|
928
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
929
|
+
for (const column of columns) {
|
|
930
|
+
if (aliases.has(column.alias)) {
|
|
931
|
+
throw new Error(`Duplicate alias: ${column.alias}`);
|
|
932
|
+
}
|
|
933
|
+
aliases.add(column.alias);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
_initColumns(columns) {
|
|
937
|
+
let i = 0;
|
|
938
|
+
return columns.map(
|
|
939
|
+
(column) => hip({
|
|
940
|
+
...column,
|
|
941
|
+
routeHash: this.routeHashes[i],
|
|
942
|
+
index: i++,
|
|
943
|
+
_hash: ""
|
|
944
|
+
})
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
// ######################
|
|
948
|
+
// Example
|
|
949
|
+
// ######################
|
|
950
|
+
static example() {
|
|
951
|
+
return new ColumnSelection([
|
|
952
|
+
{
|
|
953
|
+
key: "stringCol",
|
|
954
|
+
alias: "stringCol",
|
|
955
|
+
route: "basicTypes/stringsRef/value",
|
|
956
|
+
type: "string",
|
|
957
|
+
titleLong: "String values",
|
|
958
|
+
titleShort: "Strings"
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
key: "intCol",
|
|
962
|
+
alias: "intCol",
|
|
963
|
+
route: "basicTypes/numbersRef/intsRef/value",
|
|
964
|
+
type: "number",
|
|
965
|
+
titleLong: "Int values",
|
|
966
|
+
titleShort: "Ints"
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
key: "floatCol",
|
|
970
|
+
alias: "floatCol",
|
|
971
|
+
route: "basicTypes/numbersRef/floatsRef/value",
|
|
972
|
+
type: "number",
|
|
973
|
+
titleLong: "Float values",
|
|
974
|
+
titleShort: "Floats"
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
key: "booleanCol",
|
|
978
|
+
alias: "booleanCol",
|
|
979
|
+
route: "basicTypes/booleansRef/value",
|
|
980
|
+
type: "boolean",
|
|
981
|
+
titleLong: "Boolean values",
|
|
982
|
+
titleShort: "Booleans"
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
key: "jsonObjectCol",
|
|
986
|
+
alias: "jsonObjectCol",
|
|
987
|
+
route: "complexTypes/jsonObjectsRef/value",
|
|
988
|
+
type: "json",
|
|
989
|
+
titleLong: "Json objects",
|
|
990
|
+
titleShort: "JO"
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
key: "jsonArrayCol",
|
|
994
|
+
alias: "jsonArrayCol",
|
|
995
|
+
route: "complexTypes/jsonArraysRef/value",
|
|
996
|
+
type: "jsonArray",
|
|
997
|
+
titleLong: "Array values",
|
|
998
|
+
titleShort: "JA"
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
key: "jsonValueCol",
|
|
1002
|
+
alias: "jsonValueCol",
|
|
1003
|
+
route: "complexTypes/jsonValuesRef/value",
|
|
1004
|
+
type: "jsonValue",
|
|
1005
|
+
titleLong: "Json values",
|
|
1006
|
+
titleShort: "JV"
|
|
1007
|
+
}
|
|
1008
|
+
]);
|
|
1009
|
+
}
|
|
1010
|
+
static exampleBroken() {
|
|
1011
|
+
return [
|
|
1012
|
+
{
|
|
1013
|
+
key: "stringCol",
|
|
1014
|
+
alias: "stringCol",
|
|
1015
|
+
route: "basicTypes/stringsRef/value",
|
|
1016
|
+
type: "string",
|
|
1017
|
+
titleLong: "String values",
|
|
1018
|
+
titleShort: "Strings"
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
key: "stringCol2",
|
|
1022
|
+
alias: "stringCol",
|
|
1023
|
+
// ⚠️ Duplicate alias
|
|
1024
|
+
route: "basicTypes/stringsRef/value",
|
|
1025
|
+
type: "string",
|
|
1026
|
+
titleLong: "String values",
|
|
1027
|
+
titleShort: "Strings"
|
|
1028
|
+
}
|
|
1029
|
+
];
|
|
1030
|
+
}
|
|
1031
|
+
static exampleCarsColumnSelection() {
|
|
1032
|
+
return new ColumnSelection([
|
|
1033
|
+
{
|
|
1034
|
+
key: "brand",
|
|
1035
|
+
route: "carCake/carGeneralLayer/carGeneral/brand",
|
|
1036
|
+
alias: "brand",
|
|
1037
|
+
titleLong: "Car Brand",
|
|
1038
|
+
titleShort: "Brand",
|
|
1039
|
+
type: "string",
|
|
1040
|
+
_hash: ""
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
key: "type",
|
|
1044
|
+
route: "carCake/carGeneralLayer/carGeneral/type",
|
|
1045
|
+
alias: "type",
|
|
1046
|
+
titleLong: "Car Type",
|
|
1047
|
+
titleShort: "Type",
|
|
1048
|
+
type: "string",
|
|
1049
|
+
_hash: ""
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
key: "serviceIntervals",
|
|
1053
|
+
route: "carCake/carGeneralLayer/carGeneral/serviceIntervals",
|
|
1054
|
+
alias: "serviceIntervals",
|
|
1055
|
+
titleLong: "Car Service Intervals",
|
|
1056
|
+
titleShort: "Service Intervals",
|
|
1057
|
+
type: "jsonValue",
|
|
1058
|
+
_hash: ""
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
key: "isElectric",
|
|
1062
|
+
route: "carCake/carGeneralLayer/carGeneral/isElectric",
|
|
1063
|
+
alias: "isElectric",
|
|
1064
|
+
titleLong: "Is Electric Car",
|
|
1065
|
+
titleShort: "Electric",
|
|
1066
|
+
type: "boolean",
|
|
1067
|
+
_hash: ""
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
key: "carDimensions/height",
|
|
1071
|
+
route: "carCake/carTechnicalLayer/carTechnical/carDimensions/height",
|
|
1072
|
+
alias: "height",
|
|
1073
|
+
titleLong: "Car Height",
|
|
1074
|
+
titleShort: "Height",
|
|
1075
|
+
type: "number",
|
|
1076
|
+
_hash: ""
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
key: "carDimensions/width",
|
|
1080
|
+
route: "carCake/carTechnicalLayer/carTechnical/carDimensions/width",
|
|
1081
|
+
alias: "width",
|
|
1082
|
+
titleLong: "Car Width",
|
|
1083
|
+
titleShort: "Width",
|
|
1084
|
+
type: "number",
|
|
1085
|
+
_hash: ""
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
key: "carDimensions/length",
|
|
1089
|
+
route: "carCake/carTechnicalLayer/carTechnical/carDimensions/length",
|
|
1090
|
+
alias: "length",
|
|
1091
|
+
titleLong: "Car Length",
|
|
1092
|
+
titleShort: "Length",
|
|
1093
|
+
type: "number",
|
|
1094
|
+
_hash: ""
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
key: "engine",
|
|
1098
|
+
route: "carCake/carTechnicalLayer/carTechnical/engine",
|
|
1099
|
+
alias: "engine",
|
|
1100
|
+
titleLong: "Car Engine",
|
|
1101
|
+
titleShort: "Engine",
|
|
1102
|
+
type: "string",
|
|
1103
|
+
_hash: ""
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
key: "repairedByWorkshop",
|
|
1107
|
+
route: "carCake/carTechnicalLayer/carTechnical/repairedByWorkshop",
|
|
1108
|
+
alias: "repairedByWorkshop",
|
|
1109
|
+
titleLong: "Was Repaired By Workshop",
|
|
1110
|
+
titleShort: "Repaired By Workshop",
|
|
1111
|
+
type: "boolean",
|
|
1112
|
+
_hash: ""
|
|
1113
|
+
}
|
|
1114
|
+
]);
|
|
1115
|
+
}
|
|
1116
|
+
static exampleCarsColumnSelectionOnlySomeColumns() {
|
|
1117
|
+
return new ColumnSelection([
|
|
1118
|
+
{
|
|
1119
|
+
key: "brand",
|
|
1120
|
+
route: "carCake/carGeneralLayer/carGeneral/brand",
|
|
1121
|
+
alias: "brand",
|
|
1122
|
+
titleLong: "Car Brand",
|
|
1123
|
+
titleShort: "Brand",
|
|
1124
|
+
type: "string",
|
|
1125
|
+
_hash: ""
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
key: "type",
|
|
1129
|
+
route: "carCake/carGeneralLayer/carGeneral/type",
|
|
1130
|
+
alias: "type",
|
|
1131
|
+
titleLong: "Car Type",
|
|
1132
|
+
titleShort: "Type",
|
|
1133
|
+
type: "string",
|
|
1134
|
+
_hash: ""
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
key: "serviceIntervals",
|
|
1138
|
+
route: "carCake/carGeneralLayer/carGeneral/serviceIntervals",
|
|
1139
|
+
alias: "serviceIntervals",
|
|
1140
|
+
titleLong: "Car Service Intervals",
|
|
1141
|
+
titleShort: "Service Intervals",
|
|
1142
|
+
type: "jsonValue",
|
|
1143
|
+
_hash: ""
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
key: "isElectric",
|
|
1147
|
+
route: "carCake/carGeneralLayer/carGeneral/isElectric",
|
|
1148
|
+
alias: "isElectric",
|
|
1149
|
+
titleLong: "Is Electric Car",
|
|
1150
|
+
titleShort: "Electric",
|
|
1151
|
+
type: "boolean",
|
|
1152
|
+
_hash: ""
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
key: "carDimensions/length",
|
|
1156
|
+
route: "carCake/carTechnicalLayer/carTechnical/carDimensions/length",
|
|
1157
|
+
alias: "length",
|
|
1158
|
+
titleLong: "Car Length",
|
|
1159
|
+
titleShort: "Length",
|
|
1160
|
+
type: "number",
|
|
1161
|
+
_hash: ""
|
|
1162
|
+
}
|
|
1163
|
+
]);
|
|
1164
|
+
}
|
|
1165
|
+
// ...........................................................................
|
|
1166
|
+
static empty() {
|
|
1167
|
+
return new ColumnSelection([]);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
const trueValues = ["t", "j", "y"];
|
|
1171
|
+
const falseValues = ["n", "f"];
|
|
1172
|
+
const parseBooleanSearch = (search) => {
|
|
1173
|
+
if (typeof search == "undefined" || search == null) {
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
if (typeof search == "boolean") {
|
|
1177
|
+
return search;
|
|
1178
|
+
}
|
|
1179
|
+
if (typeof search == "number") {
|
|
1180
|
+
return search != 0;
|
|
1181
|
+
}
|
|
1182
|
+
if (typeof search == "string") {
|
|
1183
|
+
const val = search.toLowerCase();
|
|
1184
|
+
for (const trueValue of trueValues) {
|
|
1185
|
+
if (val.startsWith(trueValue)) {
|
|
1186
|
+
return true;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
for (const falseValue of falseValues) {
|
|
1190
|
+
if (val.startsWith(falseValue)) {
|
|
1191
|
+
return false;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
const containsOnlyNumbers = /^\d+$/.test(search);
|
|
1195
|
+
if (containsOnlyNumbers) {
|
|
1196
|
+
return parseInt(search) != 0;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return false;
|
|
1200
|
+
};
|
|
1201
|
+
const exampleBooleanFilter = () => hip({
|
|
1202
|
+
column: "basicTypes/booleansRef/value",
|
|
1203
|
+
operator: "equals",
|
|
1204
|
+
type: "boolean",
|
|
1205
|
+
search: true,
|
|
1206
|
+
_hash: ""
|
|
1207
|
+
});
|
|
1208
|
+
class BooleanFilterProcessor {
|
|
1209
|
+
constructor(operator, search) {
|
|
1210
|
+
this.operator = operator;
|
|
1211
|
+
this.search = search === null ? null : typeof search === "boolean" ? search : parseBooleanSearch(search);
|
|
1212
|
+
}
|
|
1213
|
+
search;
|
|
1214
|
+
// ...........................................................................
|
|
1215
|
+
static fromModel(model) {
|
|
1216
|
+
return new BooleanFilterProcessor(model.operator, model.search);
|
|
1217
|
+
}
|
|
1218
|
+
// ...........................................................................
|
|
1219
|
+
equals(other) {
|
|
1220
|
+
return other instanceof BooleanFilterProcessor && this.operator === other.operator && this.search === other.search;
|
|
1221
|
+
}
|
|
1222
|
+
// ...........................................................................
|
|
1223
|
+
static allOperators = ["equals", "notEquals"];
|
|
1224
|
+
// ...........................................................................
|
|
1225
|
+
matches(cellValue) {
|
|
1226
|
+
if (this.search === null || this.search === void 0) {
|
|
1227
|
+
return true;
|
|
1228
|
+
}
|
|
1229
|
+
if (cellValue === null || cellValue === void 0) {
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
if (typeof cellValue == "number") {
|
|
1233
|
+
cellValue = cellValue != 0;
|
|
1234
|
+
}
|
|
1235
|
+
if (typeof cellValue == "string") {
|
|
1236
|
+
const val = cellValue.toLowerCase();
|
|
1237
|
+
cellValue = val === "true" || val === "yes" || "1";
|
|
1238
|
+
}
|
|
1239
|
+
switch (this.operator) {
|
|
1240
|
+
case "equals":
|
|
1241
|
+
return cellValue === this.search;
|
|
1242
|
+
case "notEquals":
|
|
1243
|
+
return cellValue !== this.search;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
// ...........................................................................
|
|
1247
|
+
static get example() {
|
|
1248
|
+
const model = exampleBooleanFilter();
|
|
1249
|
+
const filterProcessor = BooleanFilterProcessor.fromModel(model);
|
|
1250
|
+
return filterProcessor;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
const exampleNumberFilter = () => hip({
|
|
1254
|
+
column: "basicTypes/numbersRef/intsRef/value",
|
|
1255
|
+
operator: "greaterThanOrEquals",
|
|
1256
|
+
type: "number",
|
|
1257
|
+
search: 1e3,
|
|
1258
|
+
_hash: ""
|
|
1259
|
+
});
|
|
1260
|
+
class NumberFilterProcessor {
|
|
1261
|
+
constructor(operator, search) {
|
|
1262
|
+
this.operator = operator;
|
|
1263
|
+
if (operator === "filtrex") {
|
|
1264
|
+
this.search = search;
|
|
1265
|
+
this._initFiltrex();
|
|
1266
|
+
} else {
|
|
1267
|
+
this.search = typeof search == "string" ? parseFloat(search) : search;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
// ...........................................................................
|
|
1271
|
+
static fromModel(model) {
|
|
1272
|
+
return new NumberFilterProcessor(model.operator, model.search);
|
|
1273
|
+
}
|
|
1274
|
+
equals(other) {
|
|
1275
|
+
return other instanceof NumberFilterProcessor && this.operator === other.operator && this.search === other.search;
|
|
1276
|
+
}
|
|
1277
|
+
// ...........................................................................
|
|
1278
|
+
static allOperators = [
|
|
1279
|
+
"equals",
|
|
1280
|
+
"notEquals",
|
|
1281
|
+
"greaterThan",
|
|
1282
|
+
"greaterThanOrEquals",
|
|
1283
|
+
"lessThan",
|
|
1284
|
+
"lessThanOrEquals",
|
|
1285
|
+
"filtrex"
|
|
1286
|
+
];
|
|
1287
|
+
// ...........................................................................
|
|
1288
|
+
matches(cellValue) {
|
|
1289
|
+
if (!this.search && this.search !== 0) {
|
|
1290
|
+
return true;
|
|
1291
|
+
}
|
|
1292
|
+
if (cellValue === null || cellValue === void 0) {
|
|
1293
|
+
return false;
|
|
1294
|
+
}
|
|
1295
|
+
switch (this.operator) {
|
|
1296
|
+
case "equals":
|
|
1297
|
+
return cellValue === this.search;
|
|
1298
|
+
case "notEquals":
|
|
1299
|
+
return cellValue !== this.search;
|
|
1300
|
+
case "greaterThan":
|
|
1301
|
+
return cellValue > this.search;
|
|
1302
|
+
case "lessThan":
|
|
1303
|
+
return cellValue < this.search;
|
|
1304
|
+
case "greaterThanOrEquals":
|
|
1305
|
+
return cellValue >= this.search;
|
|
1306
|
+
case "lessThanOrEquals":
|
|
1307
|
+
return cellValue <= this.search;
|
|
1308
|
+
case "filtrex":
|
|
1309
|
+
return this?._evalExpression(cellValue);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
search = "";
|
|
1313
|
+
static get example() {
|
|
1314
|
+
return NumberFilterProcessor.fromModel(exampleNumberFilter());
|
|
1315
|
+
}
|
|
1316
|
+
// ######################
|
|
1317
|
+
// Private
|
|
1318
|
+
// ######################
|
|
1319
|
+
_expression = null;
|
|
1320
|
+
// ..........................................................................
|
|
1321
|
+
_initFiltrex() {
|
|
1322
|
+
if (this.search === "") {
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
if (typeof this.search === "number") {
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
const isNumber = /^\d+$/.test(this.search);
|
|
1329
|
+
if (isNumber) {
|
|
1330
|
+
this.search = parseInt(this.search);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
try {
|
|
1334
|
+
this._expression = compileExpression(this.search);
|
|
1335
|
+
} catch (_) {
|
|
1336
|
+
this._expression = "";
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
// ...........................................................................
|
|
1340
|
+
_evalExpression(cellValue) {
|
|
1341
|
+
return this._expression ? (
|
|
1342
|
+
// result can also contain errors which could be sent to the outside here
|
|
1343
|
+
this._expression({ v: cellValue }) == true
|
|
1344
|
+
) : cellValue == this.search;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
const exampleStringFilter = () => hip({
|
|
1348
|
+
type: "string",
|
|
1349
|
+
column: "basicTypes/stringsRef/value",
|
|
1350
|
+
operator: "startsWith",
|
|
1351
|
+
search: "t",
|
|
1352
|
+
matchCase: false,
|
|
1353
|
+
_hash: ""
|
|
1354
|
+
});
|
|
1355
|
+
class StringFilterProcessor {
|
|
1356
|
+
constructor(operator, search, matchCase = false) {
|
|
1357
|
+
this.operator = operator;
|
|
1358
|
+
this.matchCase = matchCase;
|
|
1359
|
+
this.search = (matchCase ? search : search.toLowerCase()).replaceAll(
|
|
1360
|
+
" ",
|
|
1361
|
+
""
|
|
1362
|
+
);
|
|
1363
|
+
try {
|
|
1364
|
+
this.regExp = operator === "regExp" ? new RegExp(search) : null;
|
|
1365
|
+
} catch {
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
// ...........................................................................
|
|
1369
|
+
static fromModel(model) {
|
|
1370
|
+
return new StringFilterProcessor(
|
|
1371
|
+
model.operator,
|
|
1372
|
+
model.search,
|
|
1373
|
+
model.matchCase
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
// ...........................................................................
|
|
1377
|
+
search;
|
|
1378
|
+
regExp = null;
|
|
1379
|
+
// ...........................................................................
|
|
1380
|
+
equals(other) {
|
|
1381
|
+
return other instanceof StringFilterProcessor && this.operator === other.operator && this.search === other.search && this.matchCase === other.matchCase;
|
|
1382
|
+
}
|
|
1383
|
+
// ...........................................................................
|
|
1384
|
+
static allOperators = [
|
|
1385
|
+
"contains",
|
|
1386
|
+
"equals",
|
|
1387
|
+
"notEquals",
|
|
1388
|
+
"startsWith",
|
|
1389
|
+
"notContains",
|
|
1390
|
+
"endsWith",
|
|
1391
|
+
"regExp"
|
|
1392
|
+
];
|
|
1393
|
+
// ...........................................................................
|
|
1394
|
+
matches(cellValue) {
|
|
1395
|
+
if (!this.search) {
|
|
1396
|
+
return true;
|
|
1397
|
+
}
|
|
1398
|
+
if (cellValue === null || cellValue === void 0) {
|
|
1399
|
+
return false;
|
|
1400
|
+
}
|
|
1401
|
+
if (typeof cellValue !== "string") {
|
|
1402
|
+
cellValue = `${cellValue}`;
|
|
1403
|
+
}
|
|
1404
|
+
if (!this.matchCase) {
|
|
1405
|
+
cellValue = cellValue.toLowerCase();
|
|
1406
|
+
}
|
|
1407
|
+
cellValue = cellValue.replaceAll(" ", "");
|
|
1408
|
+
switch (this.operator) {
|
|
1409
|
+
case "equals":
|
|
1410
|
+
return cellValue === this.search;
|
|
1411
|
+
case "notEquals":
|
|
1412
|
+
return cellValue !== this.search;
|
|
1413
|
+
case "startsWith":
|
|
1414
|
+
return cellValue.startsWith(this.search);
|
|
1415
|
+
case "contains":
|
|
1416
|
+
return cellValue.includes(this.search);
|
|
1417
|
+
case "endsWith":
|
|
1418
|
+
return cellValue.endsWith(this.search);
|
|
1419
|
+
/* v8 ignore next -- @preserve */
|
|
1420
|
+
case "regExp":
|
|
1421
|
+
return this.regExp?.test(cellValue) ?? false;
|
|
1422
|
+
case "notContains":
|
|
1423
|
+
return !cellValue.includes(this.search);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
// ...........................................................................
|
|
1427
|
+
static get example() {
|
|
1428
|
+
const result = StringFilterProcessor.fromModel(exampleStringFilter());
|
|
1429
|
+
return result;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
class ColumnFilterProcessor {
|
|
1433
|
+
/* v8 ignore next -- @preserve */
|
|
1434
|
+
matches(_cellValue) {
|
|
1435
|
+
return true;
|
|
1436
|
+
}
|
|
1437
|
+
equals(_other) {
|
|
1438
|
+
return this === _other;
|
|
1439
|
+
}
|
|
1440
|
+
// ...........................................................................
|
|
1441
|
+
static fromModel(model) {
|
|
1442
|
+
switch (model.type) {
|
|
1443
|
+
case "string":
|
|
1444
|
+
return StringFilterProcessor.fromModel(model);
|
|
1445
|
+
case "number":
|
|
1446
|
+
return NumberFilterProcessor.fromModel(model);
|
|
1447
|
+
case "boolean":
|
|
1448
|
+
return BooleanFilterProcessor.fromModel(model);
|
|
1449
|
+
/* v8 ignore next -- @preserve */
|
|
1450
|
+
default:
|
|
1451
|
+
return StringFilterProcessor.fromModel(model);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
// ...........................................................................
|
|
1455
|
+
/* v8 ignore stop */
|
|
1456
|
+
static operatorsForType(type) {
|
|
1457
|
+
switch (type) {
|
|
1458
|
+
case "string":
|
|
1459
|
+
return StringFilterProcessor.allOperators;
|
|
1460
|
+
case "number":
|
|
1461
|
+
return NumberFilterProcessor.allOperators;
|
|
1462
|
+
case "boolean":
|
|
1463
|
+
return BooleanFilterProcessor.allOperators;
|
|
1464
|
+
default:
|
|
1465
|
+
return StringFilterProcessor.allOperators;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
static translationsForType(type, language) {
|
|
1469
|
+
const operators = ColumnFilterProcessor.operatorsForType(type);
|
|
1470
|
+
const translations = [];
|
|
1471
|
+
for (const operator of operators) {
|
|
1472
|
+
translations.push(
|
|
1473
|
+
ColumnFilterProcessor.translateOperator(operator, language)
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
return translations;
|
|
1477
|
+
}
|
|
1478
|
+
static translateOperator(operator, language) {
|
|
1479
|
+
const translations = {
|
|
1480
|
+
equals: { en: "Equals", de: "Gleich" },
|
|
1481
|
+
notEquals: { en: "Not equals", de: "Ungleich" },
|
|
1482
|
+
greaterThan: { en: "Greater than", de: "Größer" },
|
|
1483
|
+
greaterThanOrEquals: {
|
|
1484
|
+
en: "Greater than or Equals",
|
|
1485
|
+
de: "Größer oder gleich"
|
|
1486
|
+
},
|
|
1487
|
+
lessThan: { en: "Less than", de: "Kleiner als" },
|
|
1488
|
+
lessThanOrEquals: {
|
|
1489
|
+
en: "Less than or equals",
|
|
1490
|
+
de: "Kleiner gleich"
|
|
1491
|
+
},
|
|
1492
|
+
startsWith: { en: "Starts with", de: "Beginnt mit" },
|
|
1493
|
+
contains: { en: "Contains", de: "Enthält" },
|
|
1494
|
+
notContains: { en: "Not contains", de: "Enthält nicht" },
|
|
1495
|
+
endsWith: { en: "Ends with", de: "Endet mit" },
|
|
1496
|
+
regExp: { en: "Regular expression", de: "Regulärer Ausdruck" },
|
|
1497
|
+
filtrex: { en: "Expression", de: "Ausdruck" }
|
|
1498
|
+
};
|
|
1499
|
+
return translations[operator][language];
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
class RowFilterProcessor {
|
|
1503
|
+
// ...........................................................................
|
|
1504
|
+
constructor(columnFilters, operator = "and") {
|
|
1505
|
+
this.operator = operator;
|
|
1506
|
+
this._columnFilters = this._initColumnFilters(columnFilters);
|
|
1507
|
+
}
|
|
1508
|
+
// ...........................................................................
|
|
1509
|
+
static fromModel(model) {
|
|
1510
|
+
const operator = model.operator;
|
|
1511
|
+
const columnFilters = {};
|
|
1512
|
+
for (const columnFilter of model.columnFilters) {
|
|
1513
|
+
const key = columnFilter.column;
|
|
1514
|
+
const processor = ColumnFilterProcessor.fromModel(columnFilter);
|
|
1515
|
+
columnFilters[key] = processor;
|
|
1516
|
+
}
|
|
1517
|
+
return new RowFilterProcessor(columnFilters, operator);
|
|
1518
|
+
}
|
|
1519
|
+
// ...........................................................................
|
|
1520
|
+
get processors() {
|
|
1521
|
+
return Object.values(this._columnFilters).map((item) => item.processor);
|
|
1522
|
+
}
|
|
1523
|
+
// ...........................................................................
|
|
1524
|
+
/// Returns an empty filter
|
|
1525
|
+
static get empty() {
|
|
1526
|
+
return new RowFilterProcessor({}, "and");
|
|
1527
|
+
}
|
|
1528
|
+
// ...........................................................................
|
|
1529
|
+
/// Checks if two filters are equal
|
|
1530
|
+
equals(other) {
|
|
1531
|
+
if (this.operator !== other.operator) {
|
|
1532
|
+
return false;
|
|
1533
|
+
}
|
|
1534
|
+
const thisKeys = Object.keys(this._columnFilters);
|
|
1535
|
+
const otherKeys = Object.keys(other._columnFilters);
|
|
1536
|
+
if (thisKeys.length !== otherKeys.length) {
|
|
1537
|
+
return false;
|
|
1538
|
+
}
|
|
1539
|
+
for (const key of thisKeys) {
|
|
1540
|
+
const a = this._columnFilters[key];
|
|
1541
|
+
const b = other._columnFilters[key];
|
|
1542
|
+
if (a?.processor.equals(b?.processor) === false) {
|
|
1543
|
+
return false;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
return true;
|
|
1547
|
+
}
|
|
1548
|
+
// ...........................................................................
|
|
1549
|
+
applyTo(join) {
|
|
1550
|
+
if (join.rowCount === 0) {
|
|
1551
|
+
return join.data;
|
|
1552
|
+
}
|
|
1553
|
+
this._throwOnWrongRoutes(join.columnSelection);
|
|
1554
|
+
const columnCount = join.columnCount;
|
|
1555
|
+
const columnHashes = join.columnSelection.routeHashes;
|
|
1556
|
+
const filterArray = new Array(columnCount).fill(
|
|
1557
|
+
null
|
|
1558
|
+
);
|
|
1559
|
+
let hasFilters = false;
|
|
1560
|
+
for (let c = 0; c < columnCount; c++) {
|
|
1561
|
+
const hash = columnHashes[c];
|
|
1562
|
+
const filter = this._columnFilters[hash];
|
|
1563
|
+
if (filter) {
|
|
1564
|
+
filterArray[c] = filter.processor;
|
|
1565
|
+
hasFilters = true;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
if (!hasFilters) {
|
|
1569
|
+
return join.data;
|
|
1570
|
+
}
|
|
1571
|
+
let rowIndices = [];
|
|
1572
|
+
switch (this.operator) {
|
|
1573
|
+
case "and":
|
|
1574
|
+
rowIndices = this._filterRowsAnd(join, filterArray);
|
|
1575
|
+
break;
|
|
1576
|
+
case "or":
|
|
1577
|
+
rowIndices = this._filterRowsOr(join, filterArray);
|
|
1578
|
+
break;
|
|
1579
|
+
}
|
|
1580
|
+
const result = {};
|
|
1581
|
+
const rowIndexSet = new Set(rowIndices);
|
|
1582
|
+
let idx = 0;
|
|
1583
|
+
for (const [sliceId, row] of Object.entries(join.data)) {
|
|
1584
|
+
if (rowIndexSet.has(idx)) {
|
|
1585
|
+
result[sliceId] = row;
|
|
1586
|
+
}
|
|
1587
|
+
idx++;
|
|
1588
|
+
}
|
|
1589
|
+
return result;
|
|
1590
|
+
}
|
|
1591
|
+
// ######################
|
|
1592
|
+
// Private
|
|
1593
|
+
// ######################
|
|
1594
|
+
// ...........................................................................
|
|
1595
|
+
_columnFilters;
|
|
1596
|
+
// ...........................................................................
|
|
1597
|
+
_initColumnFilters(columnFilters) {
|
|
1598
|
+
const result = {};
|
|
1599
|
+
const columnKeys = Object.keys(columnFilters);
|
|
1600
|
+
const columnRoutes = columnKeys.map((k) => Route.fromFlat(k));
|
|
1601
|
+
const columnSelection = ColumnSelection.fromRoutes(columnRoutes);
|
|
1602
|
+
const { routeHashes, routes } = columnSelection;
|
|
1603
|
+
for (let i = 0; i < routeHashes.length; i++) {
|
|
1604
|
+
const routeHash = routeHashes[i];
|
|
1605
|
+
const route = routes[i];
|
|
1606
|
+
const processor = columnFilters[route];
|
|
1607
|
+
result[routeHash] = {
|
|
1608
|
+
processor,
|
|
1609
|
+
routeHash,
|
|
1610
|
+
route
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
return result;
|
|
1614
|
+
}
|
|
1615
|
+
// ...........................................................................
|
|
1616
|
+
_filterRowsAnd(join, filters) {
|
|
1617
|
+
const rowCount = join.rowCount;
|
|
1618
|
+
let remainingIndices = new Array(rowCount);
|
|
1619
|
+
for (let i = 0; i < rowCount; i++) {
|
|
1620
|
+
remainingIndices[i] = i;
|
|
1621
|
+
}
|
|
1622
|
+
const columnCount = join.columnCount;
|
|
1623
|
+
for (let c = 0; c < columnCount; c++) {
|
|
1624
|
+
remainingIndices = this._filterColumnAnd(
|
|
1625
|
+
join,
|
|
1626
|
+
c,
|
|
1627
|
+
remainingIndices,
|
|
1628
|
+
filters
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
return remainingIndices;
|
|
1632
|
+
}
|
|
1633
|
+
// ...........................................................................
|
|
1634
|
+
_filterColumnAnd(join, columnIndex, remainingIndices, filters) {
|
|
1635
|
+
const result = [];
|
|
1636
|
+
const filter = filters[columnIndex];
|
|
1637
|
+
if (filter == null) {
|
|
1638
|
+
return remainingIndices;
|
|
1639
|
+
}
|
|
1640
|
+
for (const i of remainingIndices) {
|
|
1641
|
+
const cellValue = join.value(i, columnIndex);
|
|
1642
|
+
if (Array.isArray(cellValue)) {
|
|
1643
|
+
for (const v of cellValue) {
|
|
1644
|
+
if (typeof v === "object" && v !== null && v.hasOwnProperty("_value")) {
|
|
1645
|
+
const matchValue = v._value;
|
|
1646
|
+
if (filter.matches(matchValue)) {
|
|
1647
|
+
result.push(i);
|
|
1648
|
+
break;
|
|
1649
|
+
}
|
|
1650
|
+
} else {
|
|
1651
|
+
if (filter.matches(v)) {
|
|
1652
|
+
result.push(i);
|
|
1653
|
+
break;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
} else {
|
|
1658
|
+
if (filter.matches(cellValue)) {
|
|
1659
|
+
result.push(i);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return result;
|
|
1664
|
+
}
|
|
1665
|
+
// ...........................................................................
|
|
1666
|
+
_filterRowsOr(join, filters) {
|
|
1667
|
+
const applyTo = new Array(join.rowCount).fill(false);
|
|
1668
|
+
const columnCount = join.columnCount;
|
|
1669
|
+
for (let c = 0; c < columnCount; c++) {
|
|
1670
|
+
this._filterColumnOr(join, c, applyTo, filters);
|
|
1671
|
+
}
|
|
1672
|
+
let rowCount = 0;
|
|
1673
|
+
for (let r = 0; r < applyTo.length; r++) {
|
|
1674
|
+
if (applyTo[r]) {
|
|
1675
|
+
rowCount++;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
const result = new Array(rowCount);
|
|
1679
|
+
let resultIndex = 0;
|
|
1680
|
+
for (let r = 0; r < applyTo.length; r++) {
|
|
1681
|
+
if (applyTo[r]) {
|
|
1682
|
+
result[resultIndex] = r;
|
|
1683
|
+
resultIndex++;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
return result;
|
|
1687
|
+
}
|
|
1688
|
+
// ...........................................................................
|
|
1689
|
+
_filterColumnOr(join, columnIndex, applyTo, filters) {
|
|
1690
|
+
const filter = filters[columnIndex];
|
|
1691
|
+
if (filter == null) {
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
for (let r = 0; r < join.rowCount; r++) {
|
|
1695
|
+
if (applyTo[r]) {
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
const cellValue = join.value(r, columnIndex);
|
|
1699
|
+
if (filter.matches(cellValue)) {
|
|
1700
|
+
applyTo[r] = true;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
// ...........................................................................
|
|
1705
|
+
_throwOnWrongRoutes(columnSelection) {
|
|
1706
|
+
const availableRoutes = columnSelection.routes;
|
|
1707
|
+
for (const item of Object.values(this._columnFilters)) {
|
|
1708
|
+
const route = item.route;
|
|
1709
|
+
if (availableRoutes.includes(route) === false) {
|
|
1710
|
+
throw new Error(
|
|
1711
|
+
`RowFilterProcessor: Error while applying filter to join: There is a column filter for route "${route}", but the join does not have a column with this route.
|
|
1712
|
+
|
|
1713
|
+
Available routes:
|
|
1714
|
+
${availableRoutes.map((a) => `- ${a}`).join("\n")}`
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
class Join {
|
|
1721
|
+
constructor(baseRows, _baseColumnSelection, _objectMap) {
|
|
1722
|
+
this._baseColumnSelection = _baseColumnSelection;
|
|
1723
|
+
this._objectMap = _objectMap;
|
|
1724
|
+
this._base = this._hashedRows(baseRows);
|
|
1725
|
+
}
|
|
1726
|
+
_base = {};
|
|
1727
|
+
_processes = [];
|
|
1728
|
+
// ...........................................................................
|
|
1729
|
+
/**
|
|
1730
|
+
* Applies a filter to the join and returns the filtered view
|
|
1731
|
+
*
|
|
1732
|
+
* @param filter The filter to apply
|
|
1733
|
+
*/
|
|
1734
|
+
filter(filter) {
|
|
1735
|
+
const proc = RowFilterProcessor.fromModel(filter);
|
|
1736
|
+
const data = proc.applyTo(this);
|
|
1737
|
+
const process = {
|
|
1738
|
+
type: "filter",
|
|
1739
|
+
instance: filter,
|
|
1740
|
+
data,
|
|
1741
|
+
columnSelection: this.columnSelection
|
|
1742
|
+
};
|
|
1743
|
+
this._processes.push(process);
|
|
1744
|
+
return this;
|
|
1745
|
+
}
|
|
1746
|
+
// ...........................................................................
|
|
1747
|
+
/**
|
|
1748
|
+
* Applies a set value action to the join and returns the edited join
|
|
1749
|
+
*
|
|
1750
|
+
* @param setValue The set value action to apply
|
|
1751
|
+
*/
|
|
1752
|
+
setValue(setValue) {
|
|
1753
|
+
const data = {};
|
|
1754
|
+
for (const [sliceId, joinRowH] of Object.entries(this.data)) {
|
|
1755
|
+
const cols = [...joinRowH.columns];
|
|
1756
|
+
for (const col of cols) {
|
|
1757
|
+
if (Route.fromFlat(setValue.route).equalsWithoutRefs(col.route))
|
|
1758
|
+
col.insert = setValue.value;
|
|
1759
|
+
else continue;
|
|
1760
|
+
}
|
|
1761
|
+
data[sliceId] = {
|
|
1762
|
+
rowHash: Hash.default.calcHash(cols.map((c) => c.value)),
|
|
1763
|
+
columns: cols
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
const process = {
|
|
1767
|
+
type: "setValue",
|
|
1768
|
+
instance: setValue,
|
|
1769
|
+
data,
|
|
1770
|
+
columnSelection: this.columnSelection
|
|
1771
|
+
};
|
|
1772
|
+
this._processes.push(process);
|
|
1773
|
+
return this;
|
|
1774
|
+
}
|
|
1775
|
+
// ...........................................................................
|
|
1776
|
+
/**
|
|
1777
|
+
* Applies multiple set value actions to the join and returns the edited join
|
|
1778
|
+
*
|
|
1779
|
+
* @param setValues The set value actions to apply
|
|
1780
|
+
*/
|
|
1781
|
+
setValues(setValues) {
|
|
1782
|
+
let result = this.clone();
|
|
1783
|
+
for (const setValue of setValues) {
|
|
1784
|
+
result = result.setValue(setValue);
|
|
1785
|
+
}
|
|
1786
|
+
return result;
|
|
1787
|
+
}
|
|
1788
|
+
// ...........................................................................
|
|
1789
|
+
/**
|
|
1790
|
+
* Selects columns from the join and returns the resulting join
|
|
1791
|
+
*
|
|
1792
|
+
* @param columnSelection The column selection to apply
|
|
1793
|
+
*/
|
|
1794
|
+
select(columnSelection) {
|
|
1795
|
+
const columnCount = columnSelection.count;
|
|
1796
|
+
const masterColumnIndices = new Array(columnCount);
|
|
1797
|
+
let i = 0;
|
|
1798
|
+
for (const hash of columnSelection.routeHashes) {
|
|
1799
|
+
const index = this.columnSelection.columnIndex(hash);
|
|
1800
|
+
masterColumnIndices[i] = index;
|
|
1801
|
+
i++;
|
|
1802
|
+
}
|
|
1803
|
+
const data = {};
|
|
1804
|
+
for (let i2 = 0; i2 < this.rowCount; i2++) {
|
|
1805
|
+
const [sliceId, row] = Object.entries(this.data)[i2];
|
|
1806
|
+
const selectedColumns = [];
|
|
1807
|
+
for (let j = 0; j < masterColumnIndices.length; j++) {
|
|
1808
|
+
selectedColumns.push(row.columns[masterColumnIndices[j]]);
|
|
1809
|
+
}
|
|
1810
|
+
data[sliceId] = {
|
|
1811
|
+
rowHash: Hash.default.calcHash(
|
|
1812
|
+
selectedColumns.map((c) => c.value)
|
|
1813
|
+
),
|
|
1814
|
+
columns: selectedColumns
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
const process = {
|
|
1818
|
+
type: "selection",
|
|
1819
|
+
instance: columnSelection,
|
|
1820
|
+
data,
|
|
1821
|
+
columnSelection
|
|
1822
|
+
};
|
|
1823
|
+
this._processes.push(process);
|
|
1824
|
+
return this;
|
|
1825
|
+
}
|
|
1826
|
+
// ...........................................................................
|
|
1827
|
+
/**
|
|
1828
|
+
* Sorts the join rows and returns the sorted join
|
|
1829
|
+
*
|
|
1830
|
+
* @param rowSort The row sort to apply
|
|
1831
|
+
*/
|
|
1832
|
+
sort(rowSort) {
|
|
1833
|
+
const sortedIndices = rowSort.applyTo(this);
|
|
1834
|
+
const data = {};
|
|
1835
|
+
for (let i = 0; i < sortedIndices.length; i++) {
|
|
1836
|
+
const sliceId = sortedIndices[i];
|
|
1837
|
+
data[sliceId] = this.data[sliceId];
|
|
1838
|
+
}
|
|
1839
|
+
const process = {
|
|
1840
|
+
type: "sort",
|
|
1841
|
+
instance: rowSort,
|
|
1842
|
+
data,
|
|
1843
|
+
columnSelection: this.columnSelection
|
|
1844
|
+
};
|
|
1845
|
+
this._processes.push(process);
|
|
1846
|
+
return this;
|
|
1847
|
+
}
|
|
1848
|
+
// ...........................................................................
|
|
1849
|
+
/**
|
|
1850
|
+
* Returns insert Object of the join
|
|
1851
|
+
*/
|
|
1852
|
+
insert() {
|
|
1853
|
+
const cakeInserts = this._insertCakeObjects(this.data);
|
|
1854
|
+
const cakeInsertsMergedOfLayerRoutes = /* @__PURE__ */ new Map();
|
|
1855
|
+
for (const { [this.cakeRoute.root.tableKey]: cakeInsert } of cakeInserts) {
|
|
1856
|
+
const cakeInsertRoute = cakeInsert.route.flat;
|
|
1857
|
+
if (!cakeInsertsMergedOfLayerRoutes.has(cakeInsertRoute)) {
|
|
1858
|
+
cakeInsertsMergedOfLayerRoutes.set(cakeInsertRoute, cakeInsert.value);
|
|
1859
|
+
} else {
|
|
1860
|
+
const existingValue = cakeInsertsMergedOfLayerRoutes.get(
|
|
1861
|
+
cakeInsertRoute
|
|
1862
|
+
);
|
|
1863
|
+
const mergedValue = merge(existingValue, cakeInsert.value);
|
|
1864
|
+
cakeInsertsMergedOfLayerRoutes.set(cakeInsertRoute, mergedValue);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
const results = [];
|
|
1868
|
+
for (const [route, value] of cakeInsertsMergedOfLayerRoutes) {
|
|
1869
|
+
const insert = {
|
|
1870
|
+
command: "add",
|
|
1871
|
+
route,
|
|
1872
|
+
value
|
|
1873
|
+
};
|
|
1874
|
+
results.push(insert);
|
|
1875
|
+
}
|
|
1876
|
+
return results;
|
|
1877
|
+
}
|
|
1878
|
+
// ...........................................................................
|
|
1879
|
+
/**
|
|
1880
|
+
* Returns the value at the given row and column index
|
|
1881
|
+
*
|
|
1882
|
+
* @param row The row index
|
|
1883
|
+
* @param column The column index
|
|
1884
|
+
* @returns The value at the given row and column
|
|
1885
|
+
*/
|
|
1886
|
+
value(row, column) {
|
|
1887
|
+
return this.rows[row][column];
|
|
1888
|
+
}
|
|
1889
|
+
// ...........................................................................
|
|
1890
|
+
/**
|
|
1891
|
+
* Clones the join
|
|
1892
|
+
*
|
|
1893
|
+
* @returns The cloned join
|
|
1894
|
+
*/
|
|
1895
|
+
clone() {
|
|
1896
|
+
const cloned = Object.create(this);
|
|
1897
|
+
cloned._data = this._base;
|
|
1898
|
+
cloned._processes = [...this._processes];
|
|
1899
|
+
return cloned;
|
|
1900
|
+
}
|
|
1901
|
+
// ...........................................................................
|
|
1902
|
+
/**
|
|
1903
|
+
* Returns all component routes of the join
|
|
1904
|
+
*/
|
|
1905
|
+
get componentRoutes() {
|
|
1906
|
+
return Array.from(
|
|
1907
|
+
new Set(
|
|
1908
|
+
Object.values(this.columnSelection.columns).map(
|
|
1909
|
+
(c) => Route.fromFlat(c.route).upper().flatWithoutRefs
|
|
1910
|
+
)
|
|
1911
|
+
)
|
|
1912
|
+
).map((r) => Route.fromFlat(r));
|
|
1913
|
+
}
|
|
1914
|
+
// ...........................................................................
|
|
1915
|
+
/**
|
|
1916
|
+
* Returns all layer routes of the join
|
|
1917
|
+
*/
|
|
1918
|
+
get layerRoutes() {
|
|
1919
|
+
return Array.from(
|
|
1920
|
+
new Set(
|
|
1921
|
+
Object.values(this.columnSelection.columns).map((c) => [
|
|
1922
|
+
Route.fromFlat(c.route).top,
|
|
1923
|
+
Route.fromFlat(c.route).deeper(1).top
|
|
1924
|
+
]).map((segments) => new Route(segments).flat)
|
|
1925
|
+
)
|
|
1926
|
+
).map((r) => Route.fromFlat(r));
|
|
1927
|
+
}
|
|
1928
|
+
// ...........................................................................
|
|
1929
|
+
/**
|
|
1930
|
+
* Returns the cake route of the join
|
|
1931
|
+
*/
|
|
1932
|
+
get cakeRoute() {
|
|
1933
|
+
const cakeRoute = Array.from(
|
|
1934
|
+
new Set(
|
|
1935
|
+
Object.values(this.columnSelection.columns).map(
|
|
1936
|
+
(c) => Route.fromFlat(c.route).top.tableKey
|
|
1937
|
+
)
|
|
1938
|
+
)
|
|
1939
|
+
).map((r) => Route.fromFlat(r));
|
|
1940
|
+
if (cakeRoute.length !== 1) {
|
|
1941
|
+
throw new Error(
|
|
1942
|
+
`Join: Error while getting cake route: The join has ${cakeRoute.length} different cake routes. Cannot determine a single cake route.`
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
return cakeRoute[0];
|
|
1946
|
+
}
|
|
1947
|
+
// ...........................................................................
|
|
1948
|
+
/**
|
|
1949
|
+
* Returns the number of rows in the join
|
|
1950
|
+
*/
|
|
1951
|
+
get rowCount() {
|
|
1952
|
+
return Object.keys(this.data).length;
|
|
1953
|
+
}
|
|
1954
|
+
// ...........................................................................
|
|
1955
|
+
/**
|
|
1956
|
+
* Returns the number of columns in the join
|
|
1957
|
+
*/
|
|
1958
|
+
get columnCount() {
|
|
1959
|
+
return this.columnSelection.count;
|
|
1960
|
+
}
|
|
1961
|
+
// ...........................................................................
|
|
1962
|
+
/**
|
|
1963
|
+
* Returns the row indices (sliceIds) of the join
|
|
1964
|
+
*/
|
|
1965
|
+
get rowIndices() {
|
|
1966
|
+
return Object.keys(this.data);
|
|
1967
|
+
}
|
|
1968
|
+
// ...........................................................................
|
|
1969
|
+
/**
|
|
1970
|
+
* Returns the join row for the given slice id
|
|
1971
|
+
*
|
|
1972
|
+
* @param sliceId - The slice id
|
|
1973
|
+
* @returns The join row
|
|
1974
|
+
*/
|
|
1975
|
+
row(sliceId) {
|
|
1976
|
+
return this.data[sliceId].columns;
|
|
1977
|
+
}
|
|
1978
|
+
// ...........................................................................
|
|
1979
|
+
/**
|
|
1980
|
+
* Returns the data of the join
|
|
1981
|
+
*/
|
|
1982
|
+
get data() {
|
|
1983
|
+
if (this._processes.length > 0) {
|
|
1984
|
+
return this._processes[this._processes.length - 1].data;
|
|
1985
|
+
}
|
|
1986
|
+
return this._base;
|
|
1987
|
+
}
|
|
1988
|
+
// ...........................................................................
|
|
1989
|
+
/**
|
|
1990
|
+
* Returns the column types of the join
|
|
1991
|
+
*/
|
|
1992
|
+
get columnTypes() {
|
|
1993
|
+
return this.columnSelection.columns.map((col) => col.type);
|
|
1994
|
+
}
|
|
1995
|
+
// ...........................................................................
|
|
1996
|
+
/**
|
|
1997
|
+
* Returns the column selection of the join
|
|
1998
|
+
*/
|
|
1999
|
+
get columnSelection() {
|
|
2000
|
+
if (this._processes.length > 0) {
|
|
2001
|
+
return this._processes[this._processes.length - 1].columnSelection;
|
|
2002
|
+
}
|
|
2003
|
+
return this._baseColumnSelection;
|
|
2004
|
+
}
|
|
2005
|
+
// ...........................................................................
|
|
2006
|
+
/**
|
|
2007
|
+
* Returns all rows of the join w/ nulled missing values
|
|
2008
|
+
*
|
|
2009
|
+
* @return The rows of the join
|
|
2010
|
+
*/
|
|
2011
|
+
get rows() {
|
|
2012
|
+
const result = [];
|
|
2013
|
+
const sliceIds = Object.keys(this.data);
|
|
2014
|
+
for (const sliceId of sliceIds) {
|
|
2015
|
+
const dataColumns = this.data[sliceId].columns;
|
|
2016
|
+
const row = [];
|
|
2017
|
+
for (const colInfo of this.columnSelection.columns) {
|
|
2018
|
+
const joinCol = dataColumns.find((dataCol) => {
|
|
2019
|
+
const colInfoRoute = Route.fromFlat(colInfo.route);
|
|
2020
|
+
const dataColRoute = dataCol.route;
|
|
2021
|
+
return colInfoRoute.equalsWithoutRefs(dataColRoute);
|
|
2022
|
+
});
|
|
2023
|
+
row.push(joinCol ? joinCol.insert ?? joinCol.value : null);
|
|
2024
|
+
}
|
|
2025
|
+
result.push(row);
|
|
2026
|
+
}
|
|
2027
|
+
return result;
|
|
2028
|
+
}
|
|
2029
|
+
static empty() {
|
|
2030
|
+
return new Join({}, ColumnSelection.empty());
|
|
2031
|
+
}
|
|
2032
|
+
//#############################################################
|
|
2033
|
+
// ############# Private Methods ##############################
|
|
2034
|
+
//#############################################################
|
|
2035
|
+
// ...........................................................................
|
|
2036
|
+
/**
|
|
2037
|
+
* Builds cake insert objects from the given join rows
|
|
2038
|
+
* @param rows - The join rows
|
|
2039
|
+
* @returns The cake insert objects
|
|
2040
|
+
*/
|
|
2041
|
+
_insertCakeObjects(rows) {
|
|
2042
|
+
const cakeInsertObjects = [];
|
|
2043
|
+
const cakeRoute = this.cakeRoute;
|
|
2044
|
+
for (const [sliceId, row] of Object.entries(rows)) {
|
|
2045
|
+
const layerInsertObjectList = this._insertLayerObjects(
|
|
2046
|
+
sliceId,
|
|
2047
|
+
row.columns
|
|
2048
|
+
);
|
|
2049
|
+
for (const layerInsertObject of layerInsertObjectList) {
|
|
2050
|
+
const cakeInsertObject = {};
|
|
2051
|
+
for (const [layerRoute, layerInsertObj] of Object.entries(
|
|
2052
|
+
layerInsertObject
|
|
2053
|
+
)) {
|
|
2054
|
+
cakeInsertObject[cakeRoute.root.tableKey] = {
|
|
2055
|
+
route: layerInsertObj.route,
|
|
2056
|
+
value: {
|
|
2057
|
+
[layerRoute]: layerInsertObj.value
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
cakeInsertObjects.push(cakeInsertObject);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
return cakeInsertObjects;
|
|
2065
|
+
}
|
|
2066
|
+
// ...........................................................................
|
|
2067
|
+
/**
|
|
2068
|
+
* Wraps component insert objects into layer insert objects
|
|
2069
|
+
* @param sliceId - The slice id
|
|
2070
|
+
* @param componentInsertObjects - The component insert objects
|
|
2071
|
+
* @returns
|
|
2072
|
+
*/
|
|
2073
|
+
_insertLayerObjects(sliceId, insertRow) {
|
|
2074
|
+
const layerRoutes = this.layerRoutes;
|
|
2075
|
+
const layerInsertObjects = [];
|
|
2076
|
+
const insertComponentObjects = this._insertComponentObjects(
|
|
2077
|
+
sliceId,
|
|
2078
|
+
insertRow
|
|
2079
|
+
);
|
|
2080
|
+
for (const layerRoute of layerRoutes) {
|
|
2081
|
+
for (const [compRouteFlat, compInsertObj] of Object.entries(
|
|
2082
|
+
insertComponentObjects
|
|
2083
|
+
)) {
|
|
2084
|
+
if (!compInsertObj._somethingToInsert) continue;
|
|
2085
|
+
const compRoute = Route.fromFlat(compRouteFlat);
|
|
2086
|
+
if (layerRoute.includes(compRoute)) {
|
|
2087
|
+
const layerInsertObj = {};
|
|
2088
|
+
layerInsertObj[layerRoute.root.tableKey] = {
|
|
2089
|
+
route: Route.fromFlat(compRouteFlat),
|
|
2090
|
+
value: {
|
|
2091
|
+
[sliceId]: compInsertObj
|
|
2092
|
+
}
|
|
2093
|
+
};
|
|
2094
|
+
layerInsertObjects.push(layerInsertObj);
|
|
2095
|
+
} else {
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
return layerInsertObjects;
|
|
2101
|
+
}
|
|
2102
|
+
// ...........................................................................
|
|
2103
|
+
/**
|
|
2104
|
+
* Merges columns into component insert objects
|
|
2105
|
+
* @param insertColumns - The columns to merge
|
|
2106
|
+
* @returns
|
|
2107
|
+
*/
|
|
2108
|
+
_insertComponentObjects(sliceId, insertColumns) {
|
|
2109
|
+
const columns = this._mergeInsertRow(sliceId, insertColumns);
|
|
2110
|
+
return this._denormalizeComponentInserts(columns, this._objectMap || {});
|
|
2111
|
+
}
|
|
2112
|
+
_denormalizeComponentInserts(columns, objectMap, refTableKey) {
|
|
2113
|
+
const result = {};
|
|
2114
|
+
for (const [propertyKey, propertyValue] of Object.entries(objectMap)) {
|
|
2115
|
+
if (typeof propertyValue === "object") {
|
|
2116
|
+
const refObjectMap = {};
|
|
2117
|
+
const refTableKey2 = propertyValue._tableKey;
|
|
2118
|
+
for (const [refKey, refValue] of Object.entries(
|
|
2119
|
+
propertyValue
|
|
2120
|
+
)) {
|
|
2121
|
+
if (refKey === "_tableKey") continue;
|
|
2122
|
+
refObjectMap[refTableKey2 + "/" + refKey] = refValue;
|
|
2123
|
+
}
|
|
2124
|
+
const refInsert = this._denormalizeComponentInserts(
|
|
2125
|
+
columns,
|
|
2126
|
+
refObjectMap,
|
|
2127
|
+
refTableKey2
|
|
2128
|
+
);
|
|
2129
|
+
for (const [refRoute, refObject] of Object.entries(refInsert)) {
|
|
2130
|
+
const insertObj = { [propertyKey]: refObject };
|
|
2131
|
+
result[refRoute] = {
|
|
2132
|
+
...result[refRoute],
|
|
2133
|
+
...insertObj,
|
|
2134
|
+
...{
|
|
2135
|
+
_somethingToInsert: result[refRoute]._somethingToInsert || refObject._somethingToInsert
|
|
2136
|
+
}
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
} else {
|
|
2140
|
+
let compKey = Route.fromFlat(propertyValue).upper().flat;
|
|
2141
|
+
compKey = refTableKey ? compKey.replace(`/${refTableKey}`, "") : compKey;
|
|
2142
|
+
const refPropertyKey = refTableKey ? propertyKey.replace(`${refTableKey}/`, "") : propertyKey;
|
|
2143
|
+
if (!result[compKey]) result[compKey] = {};
|
|
2144
|
+
if (refTableKey && !result[compKey]._tableKey)
|
|
2145
|
+
result[compKey]._tableKey = refTableKey;
|
|
2146
|
+
const propValue = columns.find((col) => {
|
|
2147
|
+
return col.route.propertyKey === propertyKey;
|
|
2148
|
+
});
|
|
2149
|
+
if (!propValue) {
|
|
2150
|
+
throw new Error(
|
|
2151
|
+
`Join._denormalizeComponentInserts: Could not find column value for property key "${propertyKey}".`
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
const somethingToInsert = !!propValue.insert;
|
|
2155
|
+
result[compKey] = {
|
|
2156
|
+
...result[compKey],
|
|
2157
|
+
[refPropertyKey]: propValue.insert ?? propValue.value,
|
|
2158
|
+
...{
|
|
2159
|
+
_somethingToInsert: result[compKey]._somethingToInsert || somethingToInsert
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
return result;
|
|
2165
|
+
}
|
|
2166
|
+
// ...........................................................................
|
|
2167
|
+
/**
|
|
2168
|
+
* Merges the insert values into the base row
|
|
2169
|
+
* @param sliceId - The slice id
|
|
2170
|
+
* @param insertRow - The insert row
|
|
2171
|
+
* @returns The merged join row
|
|
2172
|
+
*/
|
|
2173
|
+
_mergeInsertRow(sliceId, insertRow) {
|
|
2174
|
+
const baseColumns = this._base[sliceId].columns;
|
|
2175
|
+
const mergedRow = [];
|
|
2176
|
+
for (const baseCol of baseColumns) {
|
|
2177
|
+
const insertCol = insertRow.find(
|
|
2178
|
+
(col) => col.route.equalsWithoutRefs(baseCol.route)
|
|
2179
|
+
);
|
|
2180
|
+
if (insertCol) {
|
|
2181
|
+
mergedRow.push({
|
|
2182
|
+
route: baseCol.route,
|
|
2183
|
+
value: baseCol.value,
|
|
2184
|
+
insert: insertCol.insert
|
|
2185
|
+
});
|
|
2186
|
+
} else {
|
|
2187
|
+
mergedRow.push(baseCol);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
return mergedRow;
|
|
2191
|
+
}
|
|
2192
|
+
// ...........................................................................
|
|
2193
|
+
/**
|
|
2194
|
+
* Hashes the given join rows. If insert value is present, it is used for hashing.
|
|
2195
|
+
*
|
|
2196
|
+
* @param rows The join rows to hash
|
|
2197
|
+
* @returns The hashed join rows
|
|
2198
|
+
*/
|
|
2199
|
+
_hashedRows(rows) {
|
|
2200
|
+
const sliceIds = Object.keys(rows);
|
|
2201
|
+
const hashedRows = {};
|
|
2202
|
+
for (const sliceId of sliceIds) {
|
|
2203
|
+
const columns = rows[sliceId];
|
|
2204
|
+
const rowHash = Hash.default.calcHash(
|
|
2205
|
+
columns.map((col) => col.insert ?? col.value)
|
|
2206
|
+
);
|
|
2207
|
+
hashedRows[sliceId] = {
|
|
2208
|
+
rowHash,
|
|
2209
|
+
columns
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
return hashedRows;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
class Notify {
|
|
2216
|
+
_callbacks = /* @__PURE__ */ new Map();
|
|
2217
|
+
// ...........................................................................
|
|
2218
|
+
constructor() {
|
|
2219
|
+
}
|
|
2220
|
+
// ...........................................................................
|
|
2221
|
+
/**
|
|
2222
|
+
* Registers a callback for a specific route.
|
|
2223
|
+
* @param route The route to register the callback for.
|
|
2224
|
+
* @param callback The callback function to be invoked when a notification is sent.
|
|
2225
|
+
*/
|
|
2226
|
+
register(route, callback) {
|
|
2227
|
+
this._callbacks.set(route.flat, [
|
|
2228
|
+
...this._callbacks.get(route.flat) || [],
|
|
2229
|
+
callback
|
|
2230
|
+
]);
|
|
2231
|
+
}
|
|
2232
|
+
// ...........................................................................
|
|
2233
|
+
/**
|
|
2234
|
+
* Unregisters a callback for a specific route.
|
|
2235
|
+
* @param route The route to unregister the callback from.
|
|
2236
|
+
* @param callback The callback function to be removed.
|
|
2237
|
+
*/
|
|
2238
|
+
unregister(route, callback) {
|
|
2239
|
+
const callbacks = this._callbacks.get(route.flat);
|
|
2240
|
+
if (callbacks) {
|
|
2241
|
+
this._callbacks.set(
|
|
2242
|
+
route.flat,
|
|
2243
|
+
callbacks.filter((cb) => cb !== callback)
|
|
2244
|
+
);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
// ...........................................................................
|
|
2248
|
+
/**
|
|
2249
|
+
* Notifies all registered callbacks for a specific route with the provided edit protocol row.
|
|
2250
|
+
* @param route The route to notify callbacks for.
|
|
2251
|
+
* @param insertHistoryRow The edit protocol row to pass to the callbacks.
|
|
2252
|
+
*/
|
|
2253
|
+
notify(route, insertHistoryRow) {
|
|
2254
|
+
const callbacks = this._callbacks.get(route.flat);
|
|
2255
|
+
if (callbacks) {
|
|
2256
|
+
for (const cb of callbacks) {
|
|
2257
|
+
cb(insertHistoryRow);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
// ...........................................................................
|
|
2262
|
+
/**
|
|
2263
|
+
* Returns the current map of registered callbacks.
|
|
2264
|
+
* @returns A map where keys are route strings and values are arrays of callback functions.
|
|
2265
|
+
*/
|
|
2266
|
+
get callbacks() {
|
|
2267
|
+
return this._callbacks;
|
|
2268
|
+
}
|
|
2269
|
+
// ...........................................................................
|
|
2270
|
+
/**
|
|
2271
|
+
* Retrieves the list of callbacks registered for a specific route.
|
|
2272
|
+
* @param route The route to get callbacks for.
|
|
2273
|
+
* @returns An array of callback functions registered for the specified route.
|
|
2274
|
+
*/
|
|
2275
|
+
getCallBacksForRoute(route) {
|
|
2276
|
+
return this._callbacks.get(route.flat) || [];
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
class Db {
|
|
2280
|
+
/**
|
|
2281
|
+
* Constructor
|
|
2282
|
+
* @param _io - The Io instance used to read and write data
|
|
2283
|
+
*/
|
|
2284
|
+
constructor(_io) {
|
|
2285
|
+
this._io = _io;
|
|
2286
|
+
this.core = new Core(this._io);
|
|
2287
|
+
this.notify = new Notify();
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* Core functionalities like importing data, setting and getting tables
|
|
2291
|
+
*/
|
|
2292
|
+
core;
|
|
2293
|
+
/**
|
|
2294
|
+
* Notification system to register callbacks on data changes
|
|
2295
|
+
*/
|
|
2296
|
+
notify;
|
|
2297
|
+
_cache = /* @__PURE__ */ new Map();
|
|
2298
|
+
// ...........................................................................
|
|
2299
|
+
/**
|
|
2300
|
+
* Get data from a route with optional filtering
|
|
2301
|
+
* @param route - The route to get data from
|
|
2302
|
+
* @param where - Optional filter to apply to the data
|
|
2303
|
+
* @returns An array of Rljson objects matching the route and filter
|
|
2304
|
+
* @throws {Error} If the route is not valid or if any controller cannot be created
|
|
2305
|
+
*/
|
|
2306
|
+
async get(route, where) {
|
|
2307
|
+
if (!route.isValid) throw new Error(`Route ${route.flat} is not valid.`);
|
|
2308
|
+
const isolatedRoute = await this.isolatePropertyKeyFromRoute(route);
|
|
2309
|
+
const cacheHash = `${isolatedRoute.flat}|${JSON.stringify(where)}`;
|
|
2310
|
+
const isCached = this._cache.has(cacheHash);
|
|
2311
|
+
if (isCached) {
|
|
2312
|
+
return this._cache.get(cacheHash);
|
|
2313
|
+
} else {
|
|
2314
|
+
const controllers = await this._indexedControllers(isolatedRoute);
|
|
2315
|
+
const data = await this._get(isolatedRoute, where, controllers);
|
|
2316
|
+
this._cache.set(cacheHash, data);
|
|
2317
|
+
return data;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
// ...........................................................................
|
|
2321
|
+
/**
|
|
2322
|
+
* Resolves the route and returns corresponding data for any segment of the route,
|
|
2323
|
+
* matching recursive filters and where clauses
|
|
2324
|
+
*
|
|
2325
|
+
* @param route - The route to get data from
|
|
2326
|
+
* @param where - The recursive filtering key/value pairs to apply to the data
|
|
2327
|
+
* @param controllers - The controllers to use for fetching data
|
|
2328
|
+
* @param filter - Optional filter to apply to the data at the current route segment
|
|
2329
|
+
* @returns - An Rljson object matching the route and filters
|
|
2330
|
+
*/
|
|
2331
|
+
async _get(route, where, controllers, filter) {
|
|
2332
|
+
const nodeTableKey = route.top.tableKey;
|
|
2333
|
+
const nodeRoute = route;
|
|
2334
|
+
const nodeRouteRef = await this._getReferenceOfRouteSegment(nodeRoute.top);
|
|
2335
|
+
const nodeController = controllers[nodeTableKey];
|
|
2336
|
+
let nodeWhere = typeof where === "object" ? { ...where } : where;
|
|
2337
|
+
if (!route.isRoot && typeof nodeWhere === "object") {
|
|
2338
|
+
delete nodeWhere[nodeRoute.deeper().top.tableKey];
|
|
2339
|
+
}
|
|
2340
|
+
nodeWhere = nodeWhere ? typeof nodeWhere === "string" ? { _hash: nodeWhere } : nodeWhere : {};
|
|
2341
|
+
if (nodeRouteRef && nodeRouteRef.length > 0)
|
|
2342
|
+
nodeWhere = { ...nodeWhere, ...{ _hash: nodeRouteRef } };
|
|
2343
|
+
delete nodeWhere["_through"];
|
|
2344
|
+
delete nodeWhere["_tableKey"];
|
|
2345
|
+
const {
|
|
2346
|
+
[nodeTableKey]: { _data: nodeRows, _type: nodeType, _hash: nodeHash }
|
|
2347
|
+
} = await nodeController.get(nodeWhere);
|
|
2348
|
+
const nodeRowsFiltered = [];
|
|
2349
|
+
if (filter && filter.length > 0) {
|
|
2350
|
+
for (const f of filter) {
|
|
2351
|
+
if (f.tableKey !== nodeTableKey) continue;
|
|
2352
|
+
for (const nodeRow of nodeRows) {
|
|
2353
|
+
if (nodeRow._hash === f.ref) {
|
|
2354
|
+
nodeRowsFiltered.push(nodeRow);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
} else {
|
|
2359
|
+
nodeRowsFiltered.push(...nodeRows);
|
|
2360
|
+
}
|
|
2361
|
+
const node = {
|
|
2362
|
+
[nodeTableKey]: {
|
|
2363
|
+
_data: nodeRowsFiltered,
|
|
2364
|
+
_type: nodeType,
|
|
2365
|
+
_hash: nodeHash
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
if (route.isRoot) {
|
|
2369
|
+
if (route.hasPropertyKey) {
|
|
2370
|
+
return this.isolatePropertyFromComponents(node, route.propertyKey);
|
|
2371
|
+
}
|
|
2372
|
+
return node;
|
|
2373
|
+
}
|
|
2374
|
+
const childrenRoute = route.deeper();
|
|
2375
|
+
const childrenTableKey = childrenRoute.top.tableKey;
|
|
2376
|
+
const childrenWhere = typeof where === "object" ? where[childrenTableKey] ?? {} : {};
|
|
2377
|
+
const childrenThroughProperty = childrenWhere?._through;
|
|
2378
|
+
const nodeChildrenArray = [];
|
|
2379
|
+
const nodeRowsMatchingChildrenRefs = /* @__PURE__ */ new Map();
|
|
2380
|
+
for (let i = 0; i < nodeRowsFiltered.length; i++) {
|
|
2381
|
+
const nodeRow = nodeRowsFiltered[i];
|
|
2382
|
+
const childrenRefs = await nodeController.getChildRefs(
|
|
2383
|
+
nodeRow._hash
|
|
2384
|
+
);
|
|
2385
|
+
const rowChildren = await this._get(
|
|
2386
|
+
childrenRoute,
|
|
2387
|
+
childrenWhere,
|
|
2388
|
+
controllers,
|
|
2389
|
+
childrenRefs
|
|
2390
|
+
);
|
|
2391
|
+
if (rowChildren[childrenTableKey]._data.length === 0) continue;
|
|
2392
|
+
nodeChildrenArray.push(rowChildren);
|
|
2393
|
+
if (childrenThroughProperty) {
|
|
2394
|
+
const childrenHashes = rowChildren[childrenTableKey]._data.map(
|
|
2395
|
+
(rc) => rc._hash
|
|
2396
|
+
);
|
|
2397
|
+
for (const nr of nodeRowsFiltered) {
|
|
2398
|
+
{
|
|
2399
|
+
const throughHashesInRowCouldBeArray = nr[childrenThroughProperty];
|
|
2400
|
+
const throughHashesInRow = Array.isArray(
|
|
2401
|
+
throughHashesInRowCouldBeArray
|
|
2402
|
+
) ? throughHashesInRowCouldBeArray : [throughHashesInRowCouldBeArray];
|
|
2403
|
+
for (const th of throughHashesInRow) {
|
|
2404
|
+
if (childrenHashes.includes(th)) {
|
|
2405
|
+
nodeRowsMatchingChildrenRefs.set(nr._hash, nr);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
const nodeChildren = merge(...nodeChildrenArray);
|
|
2413
|
+
if (childrenThroughProperty) {
|
|
2414
|
+
const matchedNodeRows = Array.from(nodeRowsMatchingChildrenRefs.values());
|
|
2415
|
+
return {
|
|
2416
|
+
...node,
|
|
2417
|
+
...{
|
|
2418
|
+
[nodeTableKey]: {
|
|
2419
|
+
_data: matchedNodeRows,
|
|
2420
|
+
_type: nodeType,
|
|
2421
|
+
_hash: nodeHash
|
|
2422
|
+
}
|
|
2423
|
+
},
|
|
2424
|
+
...nodeChildren
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
return { ...node, ...nodeChildren };
|
|
2428
|
+
}
|
|
2429
|
+
// ...........................................................................
|
|
2430
|
+
/**
|
|
2431
|
+
* Get the reference (hash) of a route segment, considering default refs and insertHistory refs
|
|
2432
|
+
* @param segment - The route segment to get the reference for
|
|
2433
|
+
* @returns
|
|
2434
|
+
*/
|
|
2435
|
+
async _getReferenceOfRouteSegment(segment) {
|
|
2436
|
+
if (Route.segmentHasRef(segment)) {
|
|
2437
|
+
if (Route.segmentHasDefaultRef(segment)) {
|
|
2438
|
+
return Route.segmentRef(segment);
|
|
2439
|
+
} else {
|
|
2440
|
+
return await this.getRefOfTimeId(
|
|
2441
|
+
segment.tableKey,
|
|
2442
|
+
Route.segmentRef(segment)
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
return null;
|
|
2447
|
+
}
|
|
2448
|
+
// ...........................................................................
|
|
2449
|
+
/**
|
|
2450
|
+
* Joins data from layers in an Rljson into a single dataset
|
|
2451
|
+
* @param rljson - The Rljson to join data for
|
|
2452
|
+
*/
|
|
2453
|
+
async join(columnSelection, cakeKey, cakeRef) {
|
|
2454
|
+
const data = await this._getBaseDataForColumnSelection(columnSelection);
|
|
2455
|
+
const cakesTable = data[cakeKey];
|
|
2456
|
+
const cake = cakesTable._data.find((c) => c._hash === cakeRef);
|
|
2457
|
+
if (!cake) {
|
|
2458
|
+
throw new Error(
|
|
2459
|
+
`Db.join: Cake with ref "${cakeRef}" not found in cake table "${cakeKey}".`
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
const layers = /* @__PURE__ */ new Map();
|
|
2463
|
+
for (const layerKey of Object.keys(cake.layers)) {
|
|
2464
|
+
if (!data[layerKey]) continue;
|
|
2465
|
+
const layersTable = data[layerKey];
|
|
2466
|
+
const layer = layersTable._data.find(
|
|
2467
|
+
(l) => l._hash === cake.layers[layerKey]
|
|
2468
|
+
);
|
|
2469
|
+
layers.set(layerKey, layer);
|
|
2470
|
+
}
|
|
2471
|
+
const components = /* @__PURE__ */ new Map();
|
|
2472
|
+
for (const [tableKey, table] of Object.entries(data)) {
|
|
2473
|
+
if (table._type !== "components") continue;
|
|
2474
|
+
components.set(tableKey, table);
|
|
2475
|
+
}
|
|
2476
|
+
const mergedSliceIds = /* @__PURE__ */ new Set();
|
|
2477
|
+
for (const layer of layers.values()) {
|
|
2478
|
+
const sliceIdsTable = layer.sliceIdsTable;
|
|
2479
|
+
const sliceIdsTableRow = layer.sliceIdsTableRow;
|
|
2480
|
+
const {
|
|
2481
|
+
[sliceIdsTable]: { _data: sliceIds }
|
|
2482
|
+
} = await this.core.readRows(sliceIdsTable, { _hash: sliceIdsTableRow });
|
|
2483
|
+
for (const sid of sliceIds) {
|
|
2484
|
+
for (const s of sid.add) {
|
|
2485
|
+
mergedSliceIds.add(s);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
const columnCfgs = /* @__PURE__ */ new Map();
|
|
2490
|
+
const columnInfos = /* @__PURE__ */ new Map();
|
|
2491
|
+
let objectMap = {};
|
|
2492
|
+
for (const [layerKey, layer] of layers.entries()) {
|
|
2493
|
+
const componentKey = layer.componentsTable;
|
|
2494
|
+
const componentResolved = await this._resolveComponent(
|
|
2495
|
+
`${cakeKey}/${layerKey}/`,
|
|
2496
|
+
componentKey
|
|
2497
|
+
);
|
|
2498
|
+
objectMap = { ...objectMap, ...componentResolved.objectMap };
|
|
2499
|
+
columnInfos.set(componentKey, componentResolved.columnInfos);
|
|
2500
|
+
columnCfgs.set(componentKey, componentResolved.columnCfgs);
|
|
2501
|
+
}
|
|
2502
|
+
const rowMap = /* @__PURE__ */ new Map();
|
|
2503
|
+
const joinColumnInfos = /* @__PURE__ */ new Map();
|
|
2504
|
+
for (const sliceId of mergedSliceIds) {
|
|
2505
|
+
const sliceIdRow = [];
|
|
2506
|
+
for (const [layerKey, layer] of layers.entries()) {
|
|
2507
|
+
const layerRef = layer._hash;
|
|
2508
|
+
const componentKey = layer.componentsTable;
|
|
2509
|
+
const componentRef = layer.add[sliceId];
|
|
2510
|
+
const componentsTable = data[componentKey];
|
|
2511
|
+
const rowComponentProperties = componentsTable._data.find(
|
|
2512
|
+
(r) => r._hash === componentRef
|
|
2513
|
+
);
|
|
2514
|
+
const resolvedProperties = this._resolveComponentProperties(
|
|
2515
|
+
rowComponentProperties,
|
|
2516
|
+
objectMap,
|
|
2517
|
+
data
|
|
2518
|
+
);
|
|
2519
|
+
const joinColumns = [];
|
|
2520
|
+
for (const [
|
|
2521
|
+
resolvedPropertyKey,
|
|
2522
|
+
resolvedPropertyValue
|
|
2523
|
+
] of Object.entries(resolvedProperties)) {
|
|
2524
|
+
const propertyRoute = cakeKey + "/" + layerKey + "/" + componentKey + "/" + resolvedPropertyKey;
|
|
2525
|
+
const propertyColumnInfo = columnInfos.get(componentKey).find((cI) => cI.route === propertyRoute);
|
|
2526
|
+
if (!!propertyColumnInfo) {
|
|
2527
|
+
const joinColumnRoute = Route.fromFlat(
|
|
2528
|
+
`${cakeKey}@${cakeRef}/${layerKey}@${layerRef}/${componentKey}@${componentRef}`
|
|
2529
|
+
);
|
|
2530
|
+
joinColumnRoute.propertyKey = resolvedPropertyKey;
|
|
2531
|
+
joinColumnInfos.set(resolvedPropertyKey, {
|
|
2532
|
+
...propertyColumnInfo,
|
|
2533
|
+
key: resolvedPropertyKey,
|
|
2534
|
+
route: joinColumnRoute.flatWithoutRefs.slice(1)
|
|
2535
|
+
});
|
|
2536
|
+
joinColumns.push({
|
|
2537
|
+
route: joinColumnRoute,
|
|
2538
|
+
value: resolvedPropertyValue ?? null,
|
|
2539
|
+
insert: null
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
sliceIdRow.push(...joinColumns);
|
|
2544
|
+
}
|
|
2545
|
+
rowMap.set(sliceId, sliceIdRow);
|
|
2546
|
+
}
|
|
2547
|
+
const joinRows = {};
|
|
2548
|
+
for (const [sliceId, joinColumns] of rowMap.entries()) {
|
|
2549
|
+
Object.assign(joinRows, {
|
|
2550
|
+
[sliceId]: joinColumns
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
return new Join(
|
|
2554
|
+
joinRows,
|
|
2555
|
+
new ColumnSelection(Array.from(joinColumnInfos.values())),
|
|
2556
|
+
objectMap
|
|
2557
|
+
).select(columnSelection);
|
|
2558
|
+
}
|
|
2559
|
+
_resolveComponentProperties(componentData, objectMapOrRoute, baseData) {
|
|
2560
|
+
let result = {};
|
|
2561
|
+
for (const [propertyKey, propertyObjectMap] of Object.entries(
|
|
2562
|
+
objectMapOrRoute
|
|
2563
|
+
)) {
|
|
2564
|
+
if (propertyKey === "_tableKey") continue;
|
|
2565
|
+
if (typeof propertyObjectMap === "object") {
|
|
2566
|
+
const refs = Array.isArray(componentData[propertyKey]) ? componentData[propertyKey] : [componentData[propertyKey]];
|
|
2567
|
+
const refTableKey = propertyObjectMap["_tableKey"];
|
|
2568
|
+
const refCompTable = baseData[refTableKey];
|
|
2569
|
+
if (!refCompTable) continue;
|
|
2570
|
+
const prefixedResolvedRefCompData = {};
|
|
2571
|
+
for (const ref of refs) {
|
|
2572
|
+
const refCompData = refCompTable._data.find((r) => r._hash === ref);
|
|
2573
|
+
if (refCompData) {
|
|
2574
|
+
const resolvedRefCompData = this._resolveComponentProperties(
|
|
2575
|
+
refCompData,
|
|
2576
|
+
propertyObjectMap,
|
|
2577
|
+
baseData
|
|
2578
|
+
);
|
|
2579
|
+
for (const [refPropKey, value] of Object.entries(
|
|
2580
|
+
resolvedRefCompData
|
|
2581
|
+
)) {
|
|
2582
|
+
if (!prefixedResolvedRefCompData[`${refTableKey}/${refPropKey}`]) {
|
|
2583
|
+
prefixedResolvedRefCompData[`${refTableKey}/${refPropKey}`] = [];
|
|
2584
|
+
}
|
|
2585
|
+
prefixedResolvedRefCompData[`${refTableKey}/${refPropKey}`].push({
|
|
2586
|
+
_ref: ref,
|
|
2587
|
+
_value: value
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
result = {
|
|
2592
|
+
...result,
|
|
2593
|
+
...prefixedResolvedRefCompData
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
result = {
|
|
2598
|
+
...result,
|
|
2599
|
+
...{ [propertyKey]: componentData[propertyKey] }
|
|
2600
|
+
};
|
|
2601
|
+
}
|
|
2602
|
+
return result;
|
|
2603
|
+
}
|
|
2604
|
+
// ...........................................................................
|
|
2605
|
+
/**
|
|
2606
|
+
* Resolve a component's columns, including referenced components
|
|
2607
|
+
*
|
|
2608
|
+
* @param baseRoute - The base route for the component
|
|
2609
|
+
* @param componentKey - The component's table key
|
|
2610
|
+
* @returns - The resolved column configurations, column infos, and object map
|
|
2611
|
+
*/
|
|
2612
|
+
async _resolveComponent(baseRoute, componentKey) {
|
|
2613
|
+
const { columns: colCfgs } = await this.core.tableCfg(componentKey);
|
|
2614
|
+
const objectMap = {};
|
|
2615
|
+
const columnCfgs = [];
|
|
2616
|
+
const columnInfos = [];
|
|
2617
|
+
for (let i = 0; i < colCfgs.length; i++) {
|
|
2618
|
+
if (colCfgs[i].key === "_hash") continue;
|
|
2619
|
+
const colCfg = colCfgs[i];
|
|
2620
|
+
if (colCfg.ref) {
|
|
2621
|
+
const columnCfgsAndInfosForRef = await this._resolveComponent(
|
|
2622
|
+
baseRoute + `/${componentKey}`,
|
|
2623
|
+
colCfg.ref.tableKey
|
|
2624
|
+
);
|
|
2625
|
+
objectMap[colCfg.key] = {
|
|
2626
|
+
_tableKey: colCfg.ref.tableKey,
|
|
2627
|
+
...columnCfgsAndInfosForRef.objectMap
|
|
2628
|
+
};
|
|
2629
|
+
const columnCfgsForRef = columnCfgsAndInfosForRef.columnCfgs.map(
|
|
2630
|
+
(cc) => ({
|
|
2631
|
+
...cc,
|
|
2632
|
+
key: colCfg.ref.tableKey + "/" + cc.key
|
|
2633
|
+
})
|
|
2634
|
+
);
|
|
2635
|
+
const columnInfosForRef = columnCfgsAndInfosForRef.columnInfos.map(
|
|
2636
|
+
(cc) => ({
|
|
2637
|
+
...cc,
|
|
2638
|
+
key: colCfg.ref.tableKey + "/" + cc.key
|
|
2639
|
+
})
|
|
2640
|
+
);
|
|
2641
|
+
columnCfgs.push(...columnCfgsForRef);
|
|
2642
|
+
columnInfos.push(...columnInfosForRef);
|
|
2643
|
+
}
|
|
2644
|
+
const columnRoute = Route.fromFlat(
|
|
2645
|
+
baseRoute.length > 0 ? `${baseRoute}/${componentKey}/${colCfg.key}` : `/${componentKey}/${colCfg.key}`
|
|
2646
|
+
).flat.slice(1);
|
|
2647
|
+
if (!objectMap[colCfg.key]) objectMap[colCfg.key] = columnRoute;
|
|
2648
|
+
columnCfgs.push(colCfg);
|
|
2649
|
+
columnInfos.push({
|
|
2650
|
+
...colCfg,
|
|
2651
|
+
alias: `${colCfg.key}`,
|
|
2652
|
+
route: columnRoute,
|
|
2653
|
+
titleShort: colCfg.key,
|
|
2654
|
+
titleLong: colCfg.key
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
return { columnCfgs, columnInfos, objectMap };
|
|
2658
|
+
}
|
|
2659
|
+
// ...........................................................................
|
|
2660
|
+
/**
|
|
2661
|
+
* Fetches data for the given ColumnSelection
|
|
2662
|
+
* @param columnSelection - The ColumnSelection to fetch data for
|
|
2663
|
+
*/
|
|
2664
|
+
async _getBaseDataForColumnSelection(columnSelection) {
|
|
2665
|
+
const uniqueComponentRoutes = /* @__PURE__ */ new Set();
|
|
2666
|
+
for (const colInfo of columnSelection.columns) {
|
|
2667
|
+
const componentRoute = Route.fromFlat(colInfo.route);
|
|
2668
|
+
const isolatedComponentRoute = await this.isolatePropertyKeyFromRoute(
|
|
2669
|
+
componentRoute
|
|
2670
|
+
);
|
|
2671
|
+
uniqueComponentRoutes.add(
|
|
2672
|
+
isolatedComponentRoute.toRouteWithoutProperty().flat
|
|
2673
|
+
);
|
|
2674
|
+
}
|
|
2675
|
+
const data = {};
|
|
2676
|
+
for (const compRouteFlat of uniqueComponentRoutes) {
|
|
2677
|
+
const uniqueComponentRoute = Route.fromFlat(compRouteFlat);
|
|
2678
|
+
const componentData = await this.get(uniqueComponentRoute, {});
|
|
2679
|
+
Object.assign(data, componentData);
|
|
2680
|
+
}
|
|
2681
|
+
return data;
|
|
2682
|
+
}
|
|
2683
|
+
// ...........................................................................
|
|
2684
|
+
/**
|
|
2685
|
+
* Runs an Insert by executing the appropriate controller(s) based on the Insert's route
|
|
2686
|
+
* @param Insert - The Insert to run
|
|
2687
|
+
* @returns The result of the Insert as an InsertHistoryRow
|
|
2688
|
+
* @throws {Error} If the Insert is not valid or if any controller cannot be created
|
|
2689
|
+
*/
|
|
2690
|
+
async insert(insert, options) {
|
|
2691
|
+
const initialRoute = Route.fromFlat(insert.route);
|
|
2692
|
+
const runs = await this._resolveInsert(insert);
|
|
2693
|
+
const errors = validateInsert(insert);
|
|
2694
|
+
if (!!errors.hasErrors) {
|
|
2695
|
+
throw new Error(
|
|
2696
|
+
`Db.insert: Insert is not valid:
|
|
2697
|
+
${JSON.stringify(errors, null, 2)}`
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
return this._insert(insert, initialRoute, runs, options);
|
|
2701
|
+
}
|
|
2702
|
+
// ...........................................................................
|
|
2703
|
+
/**
|
|
2704
|
+
* Recursively runs controllers based on the route of the Insert
|
|
2705
|
+
* @param insert - The Insert to run
|
|
2706
|
+
* @param route - The route of the Insert
|
|
2707
|
+
* @param runFns - A record of controller run functions, keyed by table name
|
|
2708
|
+
* @returns The result of the Insert
|
|
2709
|
+
* @throws {Error} If the route is not valid or if any controller cannot be created
|
|
2710
|
+
*/
|
|
2711
|
+
async _insert(insert, route, runFns, options) {
|
|
2712
|
+
let results;
|
|
2713
|
+
let tableKey;
|
|
2714
|
+
const segment = route.segment(0);
|
|
607
2715
|
tableKey = segment.tableKey;
|
|
608
2716
|
let previous = [];
|
|
609
2717
|
if (Route.segmentHasRef(segment)) {
|
|
610
2718
|
const routeRef = Route.segmentRef(segment);
|
|
611
|
-
if (Route.
|
|
2719
|
+
if (Route.segmentHasInsertHistoryRef(segment)) {
|
|
612
2720
|
previous = [...previous, routeRef];
|
|
613
2721
|
}
|
|
614
2722
|
if (Route.segmentHasDefaultRef(segment)) {
|
|
@@ -621,47 +2729,74 @@ ${JSON.stringify(errors, null, 2)}`);
|
|
|
621
2729
|
}
|
|
622
2730
|
if (!route.isRoot) {
|
|
623
2731
|
const childRoute = route.deeper(1);
|
|
624
|
-
const childKeys = this._childKeys(
|
|
2732
|
+
const childKeys = this._childKeys(insert.value);
|
|
625
2733
|
const childRefs = {};
|
|
626
2734
|
for (const k of childKeys) {
|
|
627
|
-
const childValue =
|
|
628
|
-
const
|
|
629
|
-
const
|
|
2735
|
+
const childValue = insert.value[k];
|
|
2736
|
+
const childInsert = { ...insert, value: childValue };
|
|
2737
|
+
const childResults = await this._insert(
|
|
2738
|
+
childInsert,
|
|
2739
|
+
childRoute,
|
|
2740
|
+
runFns
|
|
2741
|
+
);
|
|
630
2742
|
const childRefKey = childRoute.top.tableKey + "Ref";
|
|
631
|
-
const
|
|
632
|
-
|
|
2743
|
+
const childRefArray = childResults.map(
|
|
2744
|
+
(childResult) => childResult[childRefKey]
|
|
2745
|
+
);
|
|
2746
|
+
childRefs[k] = childRefArray;
|
|
633
2747
|
}
|
|
634
2748
|
const runFn = runFns[tableKey];
|
|
635
|
-
|
|
636
|
-
...await runFn(
|
|
637
|
-
|
|
2749
|
+
results = [
|
|
2750
|
+
...(await runFn(
|
|
2751
|
+
insert.command,
|
|
638
2752
|
{
|
|
639
|
-
...
|
|
2753
|
+
...insert.value,
|
|
640
2754
|
...childRefs
|
|
641
2755
|
},
|
|
642
|
-
|
|
643
|
-
),
|
|
644
|
-
|
|
645
|
-
};
|
|
2756
|
+
insert.origin
|
|
2757
|
+
)).map((r) => ({ ...r, ...{ previous } }))
|
|
2758
|
+
];
|
|
646
2759
|
} else {
|
|
647
2760
|
tableKey = route.root.tableKey;
|
|
648
2761
|
const runFn = runFns[tableKey];
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
2762
|
+
const insertValue = insert.value;
|
|
2763
|
+
for (const [propertyKey, propertyValue] of Object.entries(insert.value)) {
|
|
2764
|
+
if (propertyValue && typeof propertyValue === "object" && !!propertyValue._tableKey) {
|
|
2765
|
+
const referenceRoute = propertyValue._tableKey;
|
|
2766
|
+
delete propertyValue._tableKey;
|
|
2767
|
+
const referenceInsert = {
|
|
2768
|
+
command: insert.command,
|
|
2769
|
+
route: referenceRoute,
|
|
2770
|
+
value: propertyValue
|
|
2771
|
+
};
|
|
2772
|
+
const referencesWritten = (await this._insert(
|
|
2773
|
+
referenceInsert,
|
|
2774
|
+
Route.fromFlat(referenceRoute),
|
|
2775
|
+
runFns
|
|
2776
|
+
)).map((h) => h[referenceRoute + "Ref"]);
|
|
2777
|
+
insertValue[propertyKey] = referencesWritten.length === 1 ? referencesWritten[0] : referencesWritten;
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
results = [
|
|
2781
|
+
...(await runFn(insert.command, insertValue, insert.origin)).map(
|
|
2782
|
+
(r) => ({ ...r, previous })
|
|
2783
|
+
)
|
|
2784
|
+
];
|
|
653
2785
|
}
|
|
654
|
-
result
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
2786
|
+
for (const result of results) {
|
|
2787
|
+
result.route = insert.route;
|
|
2788
|
+
if (!options?.skipHistory)
|
|
2789
|
+
await this._writeInsertHistory(tableKey, result);
|
|
2790
|
+
if (!options?.skipNotification)
|
|
2791
|
+
this.notify.notify(Route.fromFlat(insert.route), result);
|
|
2792
|
+
}
|
|
2793
|
+
return results;
|
|
659
2794
|
}
|
|
660
2795
|
// ...........................................................................
|
|
661
2796
|
/**
|
|
662
|
-
* Registers a callback to be called when an
|
|
2797
|
+
* Registers a callback to be called when an Insert is made on the given route
|
|
663
2798
|
* @param route - The route to register the callback on
|
|
664
|
-
* @param callback - The callback to be called when an
|
|
2799
|
+
* @param callback - The callback to be called when an Insert is made
|
|
665
2800
|
*/
|
|
666
2801
|
registerObserver(route, callback) {
|
|
667
2802
|
this.notify.register(route, callback);
|
|
@@ -677,34 +2812,42 @@ ${JSON.stringify(errors, null, 2)}`);
|
|
|
677
2812
|
}
|
|
678
2813
|
// ...........................................................................
|
|
679
2814
|
/**
|
|
680
|
-
* Resolves an
|
|
681
|
-
* @param
|
|
2815
|
+
* Resolves an Insert by returning the run functions of all controllers involved in the Insert's route
|
|
2816
|
+
* @param Insert - The Insert to resolve
|
|
682
2817
|
* @returns A record of controller run functions, keyed by table name
|
|
683
2818
|
* @throws {Error} If the route is not valid or if any controller cannot be created
|
|
684
2819
|
*/
|
|
685
|
-
async
|
|
2820
|
+
async _resolveInsert(Insert2) {
|
|
686
2821
|
const controllers = await this._indexedControllers(
|
|
687
|
-
Route.fromFlat(
|
|
2822
|
+
Route.fromFlat(Insert2.route)
|
|
688
2823
|
);
|
|
2824
|
+
const referencedComponentTableKeys = /* @__PURE__ */ new Set();
|
|
2825
|
+
traverse(Insert2.value, ({ key, parent }) => {
|
|
2826
|
+
if (key == "_tableKey")
|
|
2827
|
+
referencedComponentTableKeys.add(parent[key]);
|
|
2828
|
+
});
|
|
2829
|
+
for (const tableKey of referencedComponentTableKeys) {
|
|
2830
|
+
controllers[tableKey] ??= await this.getController(tableKey);
|
|
2831
|
+
}
|
|
689
2832
|
const runFns = {};
|
|
690
2833
|
for (const tableKey of Object.keys(controllers)) {
|
|
691
|
-
runFns[tableKey] = controllers[tableKey].
|
|
2834
|
+
runFns[tableKey] = controllers[tableKey].insert.bind(
|
|
2835
|
+
controllers[tableKey]
|
|
2836
|
+
);
|
|
692
2837
|
}
|
|
693
2838
|
return runFns;
|
|
694
2839
|
}
|
|
695
2840
|
// ...........................................................................
|
|
696
2841
|
/**
|
|
697
2842
|
* Returns the keys of child refs in a value based on a route
|
|
698
|
-
* @param route - The route to check
|
|
699
2843
|
* @param value - The value to check
|
|
700
2844
|
* @returns An array of keys of child refs in the value
|
|
701
2845
|
*/
|
|
702
|
-
_childKeys(
|
|
2846
|
+
_childKeys(value) {
|
|
703
2847
|
const keys = Object.keys(value);
|
|
704
2848
|
const childKeys = [];
|
|
705
2849
|
for (const k of keys) {
|
|
706
2850
|
if (typeof value[k] !== "object") continue;
|
|
707
|
-
if (k.endsWith("Ref") && route.next?.tableKey + "Ref" !== k) continue;
|
|
708
2851
|
childKeys.push(k);
|
|
709
2852
|
}
|
|
710
2853
|
return childKeys;
|
|
@@ -718,25 +2861,19 @@ ${JSON.stringify(errors, null, 2)}`);
|
|
|
718
2861
|
* @throws {Error} If the table does not exist or if the table type is not supported
|
|
719
2862
|
*/
|
|
720
2863
|
async getController(tableKey, refs) {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
const tableExists = this.core.hasTable(tableKey);
|
|
725
|
-
if (!tableExists) {
|
|
726
|
-
throw new Error(`Table ${tableKey} does not exist.`);
|
|
2864
|
+
const hasTable = await this.core.hasTable(tableKey);
|
|
2865
|
+
if (!hasTable) {
|
|
2866
|
+
throw new Error(`Db.getController: Table ${tableKey} does not exist.`);
|
|
727
2867
|
}
|
|
728
2868
|
const contentType = await this.core.contentType(tableKey);
|
|
729
|
-
if (!contentType) {
|
|
730
|
-
throw new Error(`Table ${tableKey} does not have a valid content type.`);
|
|
731
|
-
}
|
|
732
2869
|
return createController(contentType, this.core, tableKey, refs);
|
|
733
2870
|
}
|
|
734
2871
|
// ...........................................................................
|
|
735
2872
|
async _indexedControllers(route) {
|
|
736
|
-
if (!route.isValid) throw new Error(`Route ${route.flat} is not valid.`);
|
|
737
2873
|
const controllers = {};
|
|
738
|
-
|
|
739
|
-
|
|
2874
|
+
const isolatedRoute = await this.isolatePropertyKeyFromRoute(route);
|
|
2875
|
+
for (let i = 0; i < isolatedRoute.segments.length; i++) {
|
|
2876
|
+
const segment = isolatedRoute.segments[i];
|
|
740
2877
|
const tableKey = segment.tableKey;
|
|
741
2878
|
const segmentRef = Route.segmentRef(segment);
|
|
742
2879
|
const base = segmentRef ? isTimeId(segmentRef) ? await this.getRefOfTimeId(tableKey, segmentRef) : segmentRef : null;
|
|
@@ -749,85 +2886,79 @@ ${JSON.stringify(errors, null, 2)}`);
|
|
|
749
2886
|
}
|
|
750
2887
|
// ...........................................................................
|
|
751
2888
|
/**
|
|
752
|
-
* Adds an
|
|
753
|
-
* @param table - The table the
|
|
754
|
-
* @param
|
|
755
|
-
* @throws {Error} If the
|
|
2889
|
+
* Adds an InsertHistory row to the InsertHistory table of a table
|
|
2890
|
+
* @param table - The table the Insert was made on
|
|
2891
|
+
* @param InsertHistoryRow - The InsertHistory row to add
|
|
2892
|
+
* @throws {Error} If the InsertHistory table does not exist
|
|
756
2893
|
*/
|
|
757
|
-
async
|
|
758
|
-
const
|
|
759
|
-
const hasTable = await this.core.hasTable(protocolTable);
|
|
760
|
-
if (!hasTable) {
|
|
761
|
-
throw new Error(`Table ${table} does not exist`);
|
|
762
|
-
}
|
|
2894
|
+
async _writeInsertHistory(table, insertHistoryRow) {
|
|
2895
|
+
const insertHistoryTable = table + "InsertHistory";
|
|
763
2896
|
await this.core.import({
|
|
764
|
-
[
|
|
765
|
-
_data: [
|
|
766
|
-
_type: "
|
|
2897
|
+
[insertHistoryTable]: {
|
|
2898
|
+
_data: [insertHistoryRow],
|
|
2899
|
+
_type: "insertHistory"
|
|
767
2900
|
}
|
|
768
2901
|
});
|
|
769
2902
|
}
|
|
770
2903
|
// ...........................................................................
|
|
771
2904
|
/**
|
|
772
|
-
* Get the
|
|
773
|
-
* @param table - The table to get the
|
|
774
|
-
* @throws {Error} If the
|
|
2905
|
+
* Get the InsertHistory of a table
|
|
2906
|
+
* @param table - The table to get the InsertHistory for
|
|
2907
|
+
* @throws {Error} If the InsertHistory table does not exist
|
|
775
2908
|
*/
|
|
776
|
-
async
|
|
777
|
-
const
|
|
778
|
-
const hasTable = await this.core.hasTable(
|
|
2909
|
+
async getInsertHistory(table, options) {
|
|
2910
|
+
const insertHistoryTable = table + "InsertHistory";
|
|
2911
|
+
const hasTable = await this.core.hasTable(insertHistoryTable);
|
|
779
2912
|
if (!hasTable) {
|
|
780
|
-
throw new Error(`Table ${table} does not exist`);
|
|
2913
|
+
throw new Error(`Db.getInsertHistory: Table ${table} does not exist`);
|
|
781
2914
|
}
|
|
782
2915
|
if (options === void 0) {
|
|
783
2916
|
options = { sorted: false, ascending: true };
|
|
784
2917
|
}
|
|
785
2918
|
if (options.sorted) {
|
|
786
|
-
const dumpedTable = await this.core.dumpTable(
|
|
787
|
-
const tableData = dumpedTable[
|
|
788
|
-
tableData.sort(
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
return bTime.localeCompare(aTime);
|
|
795
|
-
}
|
|
796
|
-
});
|
|
797
|
-
return { [protocolTable]: { _data: tableData, _type: "edits" } };
|
|
2919
|
+
const dumpedTable = await this.core.dumpTable(insertHistoryTable);
|
|
2920
|
+
const tableData = dumpedTable[insertHistoryTable]._data;
|
|
2921
|
+
tableData.sort(
|
|
2922
|
+
(a, b) => options.ascending ? a.timeId.localeCompare(b.timeId) : b.timeId.localeCompare(a.timeId)
|
|
2923
|
+
);
|
|
2924
|
+
return {
|
|
2925
|
+
[insertHistoryTable]: { _data: tableData, _type: "insertHistory" }
|
|
2926
|
+
};
|
|
798
2927
|
}
|
|
799
|
-
return this.core.dumpTable(
|
|
2928
|
+
return this.core.dumpTable(insertHistoryTable);
|
|
800
2929
|
}
|
|
801
2930
|
// ...........................................................................
|
|
802
2931
|
/**
|
|
803
|
-
* Get a specific
|
|
804
|
-
* @param table - The table to get the
|
|
805
|
-
* @param ref - The reference of the
|
|
806
|
-
* @returns The
|
|
807
|
-
* @throws {Error} If the
|
|
2932
|
+
* Get a specific InsertHistory row from a table
|
|
2933
|
+
* @param table - The table to get the InsertHistory row from
|
|
2934
|
+
* @param ref - The reference of the InsertHistory row to get
|
|
2935
|
+
* @returns The InsertHistory row or null if it does not exist
|
|
2936
|
+
* @throws {Error} If the Inserts table does not exist
|
|
808
2937
|
*/
|
|
809
|
-
async
|
|
810
|
-
const
|
|
2938
|
+
async getInsertHistoryRowsByRef(table, ref) {
|
|
2939
|
+
const insertHistoryTable = table + "InsertHistory";
|
|
811
2940
|
const {
|
|
812
|
-
[
|
|
813
|
-
} = await this.core.readRows(
|
|
814
|
-
return
|
|
2941
|
+
[insertHistoryTable]: { _data: insertHistory }
|
|
2942
|
+
} = await this.core.readRows(insertHistoryTable, { [table + "Ref"]: ref });
|
|
2943
|
+
return insertHistory;
|
|
815
2944
|
}
|
|
816
2945
|
// ...........................................................................
|
|
817
2946
|
/**
|
|
818
|
-
* Get a specific
|
|
819
|
-
* @param table - The table to get the
|
|
820
|
-
* @param timeId - The timeId of the
|
|
821
|
-
* @returns The
|
|
822
|
-
* @throws {Error} If the
|
|
2947
|
+
* Get a specific InsertHistory row from a table by its timeId
|
|
2948
|
+
* @param table - The table to get the InsertHistory row from
|
|
2949
|
+
* @param timeId - The timeId of the InsertHistory row to get
|
|
2950
|
+
* @returns The InsertHistory row or null if it does not exist
|
|
2951
|
+
* @throws {Error} If the Inserts table does not exist
|
|
823
2952
|
*/
|
|
824
|
-
async
|
|
825
|
-
const
|
|
826
|
-
const { [
|
|
827
|
-
|
|
828
|
-
{
|
|
2953
|
+
async getInsertHistoryRowByTimeId(table, timeId2) {
|
|
2954
|
+
const insertHistoryTable = table + "InsertHistory";
|
|
2955
|
+
const { [insertHistoryTable]: result } = await this.core.readRows(
|
|
2956
|
+
insertHistoryTable,
|
|
2957
|
+
{
|
|
2958
|
+
timeId: timeId2
|
|
2959
|
+
}
|
|
829
2960
|
);
|
|
830
|
-
return result._data?.[0]
|
|
2961
|
+
return result._data?.[0];
|
|
831
2962
|
}
|
|
832
2963
|
// ...........................................................................
|
|
833
2964
|
/**
|
|
@@ -835,15 +2966,17 @@ ${JSON.stringify(errors, null, 2)}`);
|
|
|
835
2966
|
* @param table - The table to get the timeIds from
|
|
836
2967
|
* @param ref - The reference to get the timeIds for
|
|
837
2968
|
* @returns An array of timeIds
|
|
838
|
-
* @throws {Error} If the
|
|
2969
|
+
* @throws {Error} If the Inserts table does not exist
|
|
839
2970
|
*/
|
|
840
2971
|
async getTimeIdsForRef(table, ref) {
|
|
841
|
-
const
|
|
842
|
-
const { [
|
|
843
|
-
|
|
844
|
-
{
|
|
2972
|
+
const insertHistoryTable = table + "InsertHistory";
|
|
2973
|
+
const { [insertHistoryTable]: result } = await this.core.readRows(
|
|
2974
|
+
insertHistoryTable,
|
|
2975
|
+
{
|
|
2976
|
+
[table + "Ref"]: ref
|
|
2977
|
+
}
|
|
845
2978
|
);
|
|
846
|
-
return result._data?.map((r) => r.timeId)
|
|
2979
|
+
return result._data?.map((r) => r.timeId);
|
|
847
2980
|
}
|
|
848
2981
|
// ...........................................................................
|
|
849
2982
|
/**
|
|
@@ -851,24 +2984,75 @@ ${JSON.stringify(errors, null, 2)}`);
|
|
|
851
2984
|
* @param table - The table to get the ref from
|
|
852
2985
|
* @param timeId - The timeId to get the ref for
|
|
853
2986
|
* @returns The ref or null if it does not exist
|
|
854
|
-
* @throws {Error} If the
|
|
2987
|
+
* @throws {Error} If the Inserts table does not exist
|
|
855
2988
|
*/
|
|
856
2989
|
async getRefOfTimeId(table, timeId2) {
|
|
857
|
-
const
|
|
858
|
-
const { [
|
|
859
|
-
|
|
860
|
-
{
|
|
2990
|
+
const insertHistoryTable = table + "InsertHistory";
|
|
2991
|
+
const { [insertHistoryTable]: result } = await this.core.readRows(
|
|
2992
|
+
insertHistoryTable,
|
|
2993
|
+
{
|
|
2994
|
+
timeId: timeId2
|
|
2995
|
+
}
|
|
861
2996
|
);
|
|
862
|
-
return result._data?.[0]?.[table + "Ref"]
|
|
2997
|
+
return result._data?.[0]?.[table + "Ref"];
|
|
863
2998
|
}
|
|
2999
|
+
// ...........................................................................
|
|
864
3000
|
/**
|
|
865
|
-
*
|
|
866
|
-
* @
|
|
3001
|
+
* Isolates the property key from the last segment of a route if it is a property of a component
|
|
3002
|
+
* @param route - The route to extract property key from
|
|
3003
|
+
* @returns A route with extracted property key
|
|
867
3004
|
*/
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
3005
|
+
async isolatePropertyKeyFromRoute(route) {
|
|
3006
|
+
const segmentLength = route.segments.length;
|
|
3007
|
+
let propertyKey = "";
|
|
3008
|
+
let result = route;
|
|
3009
|
+
for (let i = segmentLength; i > 0; i--) {
|
|
3010
|
+
const segment = route.segments[i - 1];
|
|
3011
|
+
const tableKey = segment.tableKey;
|
|
3012
|
+
const tableExists = await this._io.tableExists(tableKey);
|
|
3013
|
+
if (!tableExists) {
|
|
3014
|
+
propertyKey = propertyKey.length > 0 ? segment.tableKey + "/" + propertyKey : segment.tableKey;
|
|
3015
|
+
result = result.upper();
|
|
3016
|
+
result.propertyKey = propertyKey;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
return result;
|
|
3020
|
+
}
|
|
3021
|
+
// ...........................................................................
|
|
3022
|
+
/**
|
|
3023
|
+
* Isolates a property from all components in an Rljson
|
|
3024
|
+
* @param rljson - The Rljson to isolate the property from
|
|
3025
|
+
* @param propertyKey - The property key to isolate
|
|
3026
|
+
* @returns A new Rljson with only the isolated property
|
|
3027
|
+
*/
|
|
3028
|
+
isolatePropertyFromComponents(rljson, propertyKey) {
|
|
3029
|
+
const result = {};
|
|
3030
|
+
for (const tableKey of Object.keys(rljson)) {
|
|
3031
|
+
const table = rljson[tableKey];
|
|
3032
|
+
const newData = table._data.map((row) => {
|
|
3033
|
+
if (row.hasOwnProperty(propertyKey)) {
|
|
3034
|
+
return {
|
|
3035
|
+
[propertyKey]: row[propertyKey],
|
|
3036
|
+
_hash: row._hash
|
|
3037
|
+
};
|
|
3038
|
+
} else {
|
|
3039
|
+
return row;
|
|
3040
|
+
}
|
|
3041
|
+
});
|
|
3042
|
+
result[tableKey] = {
|
|
3043
|
+
_type: table._type,
|
|
3044
|
+
_data: newData
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
return result;
|
|
3048
|
+
}
|
|
3049
|
+
// ...........................................................................
|
|
3050
|
+
/**
|
|
3051
|
+
* Get the current cache of the Db instance
|
|
3052
|
+
*/
|
|
3053
|
+
get cache() {
|
|
3054
|
+
return this._cache;
|
|
3055
|
+
}
|
|
872
3056
|
}
|
|
873
3057
|
export {
|
|
874
3058
|
Db as RljsonDb
|