@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/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 { timeId, createEditProtocolTableCfg, Validate, BaseValidator, Route, validateEdit, isTimeId } from "@rljson/rljson";
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
- const table = rljson[this._tableKey];
84
- if (table._type !== "cakes") {
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 = table._data[0];
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 run(command, value, origin, refs) {
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, ...value },
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
- const keys = Object.keys(where);
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
- async _getByRef(tableKey, ref, filter) {
147
- let table = {};
148
- if (!!filter && Object.keys(filter).length > 0) {
149
- table = await this._core.readRows(
150
- this._tableKey,
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 run(command, value, origin, refs) {
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 component = value;
199
- const rlJson = { [this._tableKey]: { _data: [component] } };
200
- await this._core.import(rlJson);
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
- //Ref to component
203
- [this._tableKey + "Ref"]: hsh(component)._hash,
204
- //Data from edit
205
- route: "",
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 run(command, value, origin, refs) {
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: value,
596
+ add: normalizedValue,
266
597
  ...refs || this._refs
267
598
  } : {
268
599
  add: {},
269
- remove: value,
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 Promise.resolve({});
620
+ return this._getByWhere(where, filter);
298
621
  }
299
622
  }
300
- async _getByRef(tableKey, ref, filter) {
301
- let table = {};
302
- if (!!filter && Object.keys(filter).length > 0) {
303
- table = await this._core.readRows(this._tableKey, {
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
- if (layer.componentsTable !== tableKey) {
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
- for (const layerRef of Object.values(layer.add)) {
316
- if (layerRef === ref) {
317
- layers.push(layer);
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 edit protocol for the table
677
+ * Creates a table and an insertHistory for the table
357
678
  * @param tableCfg TableCfg of table to create
358
679
  */
359
- async createEditable(tableCfg) {
680
+ async createTableWithInsertHistory(tableCfg) {
360
681
  await this.createTable(tableCfg);
361
- await this.createEditProtocol(tableCfg);
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 edit protocol table for a given table
692
+ * Creates an insertHistory table for a given table
372
693
  * @param tableCfg TableCfg of table
373
694
  */
374
- async createEditProtocol(tableCfg) {
375
- const cfg = createEditProtocolTableCfg(tableCfg);
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
- // @license
435
- class Notify {
436
- _callbacks = /* @__PURE__ */ new Map();
437
- // ...........................................................................
438
- constructor() {
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
- * Registers a callback for a specific route.
443
- * @param route The route to register the callback for.
444
- * @param callback The callback function to be invoked when a notification is sent.
781
+ * Returns unique routes from a list of routes
782
+ * @param routes - The list of routes
783
+ * @returns Unique routes
445
784
  */
446
- register(route, callback) {
447
- this._callbacks.set(route.flat, [
448
- ...this._callbacks.get(route.flat) || [],
449
- callback
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
- * Unregisters a callback for a specific route.
455
- * @param route The route to unregister the callback from.
456
- * @param callback The callback function to be removed.
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
- unregister(route, callback) {
459
- const callbacks = this._callbacks.get(route.flat);
460
- if (callbacks) {
461
- this._callbacks.set(
462
- route.flat,
463
- callbacks.filter((cb) => cb !== callback)
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
- * Notifies all registered callbacks for a specific route with the provided edit protocol row.
470
- * @param route The route to notify callbacks for.
471
- * @param editProtocolRow The edit protocol row to pass to the callbacks.
472
- */
473
- notify(route, editProtocolRow) {
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
- * Returns the current map of registered callbacks.
484
- * @returns A map where keys are route strings and values are arrays of callback functions.
485
- */
486
- get callbacks() {
487
- return this._callbacks;
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
- * Retrieves the list of callbacks registered for a specific route.
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
- // @license
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
- * Get data from a route with optional filtering
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
- * Recursively fetches data from the given route using the provided controllers
534
- * @param route - The route to fetch data from
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 rootRljson = await controllers[route.root.tableKey].get(
554
- where,
555
- filter
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 results = [];
564
- for (const ref of rootRefs) {
565
- const superiorRoute = new Route(route.segments.slice(0, -1));
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
- return results.map((r) => ({ ...r, ...rootRljson }));
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
- async run(edit, options) {
585
- const initialRoute = Route.fromFlat(edit.route);
586
- const runs = await this._resolveRuns(edit);
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
- * Recursively runs controllers based on the route of the edit
597
- * @param edit - The edit to run
598
- * @param route - The route of the edit
599
- * @param runFns - A record of controller run functions, keyed by table name
600
- * @returns The result of the edit
601
- * @throws {Error} If the route is not valid or if any controller cannot be created
602
- */
603
- async _run(edit, route, runFns, options) {
604
- let result;
605
- let tableKey;
606
- const segment = route.segment(0);
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.segmentHasProtocolRef(segment)) {
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(route, edit.value);
2732
+ const childKeys = this._childKeys(insert.value);
625
2733
  const childRefs = {};
626
2734
  for (const k of childKeys) {
627
- const childValue = edit.value[k];
628
- const childEdit = { ...edit, value: childValue };
629
- const childResult = await this._run(childEdit, childRoute, runFns);
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 childRef = childResult[childRefKey];
632
- childRefs[k] = childRef;
2743
+ const childRefArray = childResults.map(
2744
+ (childResult) => childResult[childRefKey]
2745
+ );
2746
+ childRefs[k] = childRefArray;
633
2747
  }
634
2748
  const runFn = runFns[tableKey];
635
- result = {
636
- ...await runFn(
637
- edit.command,
2749
+ results = [
2750
+ ...(await runFn(
2751
+ insert.command,
638
2752
  {
639
- ...edit.value,
2753
+ ...insert.value,
640
2754
  ...childRefs
641
2755
  },
642
- edit.origin
643
- ),
644
- previous
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
- result = {
650
- ...await runFn(edit.command, edit.value, edit.origin),
651
- previous
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.route = edit.route;
655
- await this._writeProtocol(tableKey, result);
656
- if (!options?.skipNotification)
657
- this.notify.notify(Route.fromFlat(edit.route), result);
658
- return result;
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 edit is made on the given route
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 edit is made
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 edit by returning the run functions of all controllers involved in the edit's route
681
- * @param edit - The edit to resolve
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 _resolveRuns(edit) {
2820
+ async _resolveInsert(Insert2) {
686
2821
  const controllers = await this._indexedControllers(
687
- Route.fromFlat(edit.route)
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].run.bind(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(route, value) {
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
- if (typeof tableKey !== "string" || tableKey.length === 0) {
722
- throw new Error("TableKey must be a non-empty string.");
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
- for (let i = 0; i < route.segments.length; i++) {
739
- const segment = route.segments[i];
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 edit protocol row to the edits table of a table
753
- * @param table - The table the edit was made on
754
- * @param editProtocolRow - The edit protocol row to add
755
- * @throws {Error} If the edits table does not exist
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 _writeProtocol(table, editProtocolRow) {
758
- const protocolTable = table + "Edits";
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
- [protocolTable]: {
765
- _data: [editProtocolRow],
766
- _type: "edits"
2897
+ [insertHistoryTable]: {
2898
+ _data: [insertHistoryRow],
2899
+ _type: "insertHistory"
767
2900
  }
768
2901
  });
769
2902
  }
770
2903
  // ...........................................................................
771
2904
  /**
772
- * Get the edit protocol of a table
773
- * @param table - The table to get the edit protocol for
774
- * @throws {Error} If the edits table does not exist
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 getProtocol(table, options) {
777
- const protocolTable = table + "Edits";
778
- const hasTable = await this.core.hasTable(protocolTable);
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(protocolTable);
787
- const tableData = dumpedTable[protocolTable]._data;
788
- tableData.sort((a, b) => {
789
- const aTime = a.timeId.split(":")[1];
790
- const bTime = b.timeId.split(":")[1];
791
- if (options.ascending) {
792
- return aTime.localeCompare(bTime);
793
- } else {
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(protocolTable);
2928
+ return this.core.dumpTable(insertHistoryTable);
800
2929
  }
801
2930
  // ...........................................................................
802
2931
  /**
803
- * Get a specific edit protocol row from a table
804
- * @param table - The table to get the edit protocol row from
805
- * @param ref - The reference of the edit protocol row to get
806
- * @returns The edit protocol row or null if it does not exist
807
- * @throws {Error} If the edits table does not exist
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 getProtocolRowsByRef(table, ref) {
810
- const protocolTable = table + "Edits";
2938
+ async getInsertHistoryRowsByRef(table, ref) {
2939
+ const insertHistoryTable = table + "InsertHistory";
811
2940
  const {
812
- [protocolTable]: { _data: protocol }
813
- } = await this.core.readRows(protocolTable, { [table + "Ref"]: ref });
814
- return protocol || null;
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 edit protocol row from a table by its timeId
819
- * @param table - The table to get the edit protocol row from
820
- * @param timeId - The timeId of the edit protocol row to get
821
- * @returns The edit protocol row or null if it does not exist
822
- * @throws {Error} If the edits table does not exist
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 getProtocolRowByTimeId(table, timeId2) {
825
- const protocolTable = table + "Edits";
826
- const { [protocolTable]: result } = await this.core.readRows(
827
- protocolTable,
828
- { timeId: timeId2 }
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] || null;
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 edits table does not exist
2969
+ * @throws {Error} If the Inserts table does not exist
839
2970
  */
840
2971
  async getTimeIdsForRef(table, ref) {
841
- const protocolTable = table + "Edits";
842
- const { [protocolTable]: result } = await this.core.readRows(
843
- protocolTable,
844
- { [table + "Ref"]: ref }
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 edits table does not exist
2987
+ * @throws {Error} If the Inserts table does not exist
855
2988
  */
856
2989
  async getRefOfTimeId(table, timeId2) {
857
- const protocolTable = table + "Edits";
858
- const { [protocolTable]: result } = await this.core.readRows(
859
- protocolTable,
860
- { timeId: timeId2 }
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"] || null;
2997
+ return result._data?.[0]?.[table + "Ref"];
863
2998
  }
2999
+ // ...........................................................................
864
3000
  /**
865
- * Example
866
- * @returns A new Db instance for test purposes
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
- static example = async () => {
869
- const io = new IoMem();
870
- return new Db(io);
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