@rljson/db 0.0.5 → 0.0.6

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