@rljson/db 0.0.3 → 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,22 +1,644 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
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";
4
4
  import { IoMem } from "@rljson/io";
5
- import { validate } from "@rljson/validate";
6
- // @license
7
- const _Core = class _Core {
5
+ import { compileExpression } from "filtrex";
6
+ class BaseController {
7
+ constructor(_core, _tableKey) {
8
+ this._core = _core;
9
+ this._tableKey = _tableKey;
10
+ }
11
+ // ...........................................................................
12
+ /**
13
+ * Retrieves the current state of the table.
14
+ * @returns A promise that resolves to the current state of the table.
15
+ */
16
+ async table() {
17
+ const rljson = await this._core.dumpTable(this._tableKey);
18
+ return rljson[this._tableKey];
19
+ }
20
+ // ...........................................................................
21
+ /**
22
+ * Fetches a specific entry from the table by its reference or by a partial match.
23
+ * @param where A string representing the reference of the entry to fetch, or an object representing a partial match.
24
+ * @returns A promise that resolves to an array of entries matching the criteria, or null if no entries are found.
25
+ */
26
+ async get(where, filter) {
27
+ if (typeof where === "string") {
28
+ return this._getByHash(where, filter);
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
+ }
33
+ return this._getByWhere(where, filter);
34
+ } else {
35
+ return Promise.resolve({});
36
+ }
37
+ }
38
+ // ...........................................................................
39
+ /**
40
+ * Fetches a specific entry from the table by its reference.
41
+ * @param hash A string representing the reference of the entry to fetch.
42
+ * @returns A promise that resolves to the entry matching the reference, or null if no entry is found.
43
+ */
44
+ async _getByHash(hash, filter) {
45
+ let result = {};
46
+ if (!filter || equals(filter, {})) {
47
+ result = await this._core.readRow(this._tableKey, hash);
48
+ } else {
49
+ result = await this._core.readRows(this._tableKey, {
50
+ _hash: hash,
51
+ ...filter
52
+ });
53
+ }
54
+ return result;
55
+ }
56
+ // ...........................................................................
57
+ /**
58
+ * Fetches entries from the table that match the specified criteria.
59
+ * @param where An object representing the criteria to match.
60
+ * @returns A promise that resolves to an array of entries matching the criteria, or null if no entries are found.
61
+ */
62
+ async _getByWhere(where, filter) {
63
+ const rows = await this._core.readRows(this._tableKey, {
64
+ ...where,
65
+ ...filter
66
+ });
67
+ return rows;
68
+ }
69
+ }
70
+ class CakeController extends BaseController {
71
+ constructor(_core, _tableKey, _refs) {
72
+ super(_core, _tableKey);
73
+ this._core = _core;
74
+ this._tableKey = _tableKey;
75
+ this._refs = _refs;
76
+ }
77
+ _table = null;
78
+ _baseLayers = {};
79
+ async init() {
80
+ if (this._tableKey.endsWith("Cake") === false) {
81
+ throw new Error(
82
+ `Table ${this._tableKey} is not supported by CakeController.`
83
+ );
84
+ }
85
+ const rljson = await this._core.dumpTable(this._tableKey);
86
+ this._table = rljson[this._tableKey];
87
+ if (this._table._type !== "cakes") {
88
+ throw new Error(`Table ${this._tableKey} is not of type cakes.`);
89
+ }
90
+ if (this._refs && this._refs.base) {
91
+ const {
92
+ [this._tableKey]: { _data: baseCakes }
93
+ } = await this._core.readRow(this._tableKey, this._refs.base);
94
+ if (baseCakes.length === 0) {
95
+ throw new Error(`Base cake ${this._refs.base} does not exist.`);
96
+ }
97
+ const baseCake = baseCakes[0];
98
+ this._baseLayers = rmhsh(baseCake.layers);
99
+ } else {
100
+ const cake = this._table._data[0];
101
+ this._refs = {
102
+ sliceIdsTable: cake.sliceIdsTable,
103
+ sliceIdsRow: cake.sliceIdsRow
104
+ };
105
+ }
106
+ }
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) {
126
+ if (!command.startsWith("add")) {
127
+ throw new Error(`Command ${command} is not supported by CakeController.`);
128
+ }
129
+ const cake = {
130
+ layers: { ...this._baseLayers, ...value },
131
+ ...refs || this._refs
132
+ };
133
+ const rlJson = { [this._tableKey]: { _data: [cake] } };
134
+ await this._core.import(rlJson);
135
+ const result = {
136
+ //Ref to component
137
+ [this._tableKey + "Ref"]: hsh(cake)._hash,
138
+ //Data from edit
139
+ route: "",
140
+ origin,
141
+ //Unique id/timestamp
142
+ timeId: timeId()
143
+ };
144
+ return result;
145
+ }
146
+ async get(where, filter) {
147
+ if (typeof where === "string") {
148
+ return this._getByHash(where, filter);
149
+ } else if (typeof where === "object" && where !== null) {
150
+ return this._getByWhere(where, filter);
151
+ } else {
152
+ return Promise.resolve({});
153
+ }
154
+ }
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;
160
+ }
161
+ }
162
+ return false;
163
+ }
164
+ }
165
+ class ComponentController extends BaseController {
166
+ constructor(_core, _tableKey, _refs) {
167
+ super(_core, _tableKey);
168
+ this._core = _core;
169
+ this._tableKey = _tableKey;
170
+ this._refs = _refs;
171
+ }
172
+ _tableCfg = null;
173
+ _resolvedColumns = null;
174
+ _refTableKeyToColumnKeyMap = null;
175
+ async init() {
176
+ if (!!this._refs && !this._refs.base) {
177
+ throw new Error(`Refs are not required on ComponentController.`);
178
+ }
179
+ const rljson = await this._core.dumpTable(this._tableKey);
180
+ const table = rljson[this._tableKey];
181
+ if (table._type !== "components") {
182
+ throw new Error(`Table ${this._tableKey} is not of type components.`);
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();
189
+ }
190
+ async insert(command, value, origin, refs) {
191
+ if (!command.startsWith("add")) {
192
+ throw new Error(
193
+ `Command ${command} is not supported by ComponentController.`
194
+ );
195
+ }
196
+ if (!!refs) {
197
+ throw new Error(`Refs are not supported on ComponentController.`);
198
+ }
199
+ const component = value;
200
+ const rlJson = { [this._tableKey]: { _data: [component] } };
201
+ await this._core.import(rlJson);
202
+ return {
203
+ //Ref to component
204
+ [this._tableKey + "Ref"]: hsh(component)._hash,
205
+ //Data from edit
206
+ route: "",
207
+ origin,
208
+ //Unique id/timestamp
209
+ timeId: timeId()
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
+ }
490
+ }
491
+ class LayerController extends BaseController {
492
+ constructor(_core, _tableKey, _refs) {
493
+ super(_core, _tableKey);
494
+ this._core = _core;
495
+ this._tableKey = _tableKey;
496
+ this._refs = _refs;
497
+ }
498
+ async init() {
499
+ if (this._tableKey.endsWith("Layer") === false) {
500
+ throw new Error(
501
+ `Table ${this._tableKey} is not supported by LayerController.`
502
+ );
503
+ }
504
+ const rljson = await this._core.dumpTable(this._tableKey);
505
+ const table = rljson[this._tableKey];
506
+ if (table._type !== "layers") {
507
+ throw new Error(`Table ${this._tableKey} is not of type layers.`);
508
+ }
509
+ if (this._refs && this._refs.base) {
510
+ const {
511
+ [this._tableKey]: { _data: baseLayers }
512
+ } = await this._core.readRow(this._tableKey, this._refs.base);
513
+ if (baseLayers.length === 0) {
514
+ throw new Error(`Base layer ${this._refs.base} does not exist.`);
515
+ }
516
+ const baseLayer = baseLayers[0];
517
+ if (!this._refs.sliceIdsTable || !this._refs.sliceIdsRow || !this._refs.componentsTable) {
518
+ this._refs = {
519
+ sliceIdsTable: baseLayer.sliceIdsTable,
520
+ sliceIdsTableRow: baseLayer.sliceIdsTableRow,
521
+ componentsTable: baseLayer.componentsTable
522
+ };
523
+ }
524
+ } else {
525
+ const layer = table._data[0];
526
+ this._refs = {
527
+ sliceIdsTable: layer.sliceIdsTable,
528
+ sliceIdsTableRow: layer.sliceIdsTableRow,
529
+ componentsTable: layer.componentsTable
530
+ };
531
+ }
532
+ }
533
+ async insert(command, value, origin, refs) {
534
+ if (!command.startsWith("add") && !command.startsWith("remove")) {
535
+ throw new Error(
536
+ `Command ${command} is not supported by LayerController.`
537
+ );
538
+ }
539
+ const layer = command.startsWith("add") === true ? {
540
+ add: value,
541
+ ...refs || this._refs
542
+ } : {
543
+ add: {},
544
+ remove: value,
545
+ ...refs || this._refs
546
+ };
547
+ const rlJson = { [this._tableKey]: { _data: [layer] } };
548
+ await this._core.import(rlJson);
549
+ const result = {
550
+ //Ref to component
551
+ [this._tableKey + "Ref"]: hsh(layer)._hash,
552
+ //Data from edit
553
+ route: "",
554
+ origin,
555
+ //Unique id/timestamp
556
+ timeId: timeId()
557
+ };
558
+ return result;
559
+ }
560
+ async get(where, filter) {
561
+ if (typeof where === "string") {
562
+ return this._getByHash(where, filter);
563
+ } else {
564
+ return this._getByWhere(where, filter);
565
+ }
566
+ }
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) {
571
+ const layer = row;
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
+ });
578
+ }
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;
588
+ }
589
+ }
590
+ return false;
591
+ }
592
+ }
593
+ const createController = async (type, core, tableKey, refs) => {
594
+ let ctrl;
595
+ switch (type) {
596
+ case "layers":
597
+ ctrl = new LayerController(core, tableKey, refs);
598
+ break;
599
+ case "components":
600
+ ctrl = new ComponentController(core, tableKey, refs);
601
+ break;
602
+ case "cakes":
603
+ ctrl = new CakeController(core, tableKey, refs);
604
+ break;
605
+ default:
606
+ throw new Error(`Controller for type ${type} is not implemented yet.`);
607
+ }
608
+ await ctrl.init();
609
+ return ctrl;
610
+ };
611
+ class Core {
8
612
  // ...........................................................................
9
613
  constructor(_io) {
10
614
  this._io = _io;
11
615
  }
616
+ static example = async () => {
617
+ return new Core(await IoMem.example());
618
+ };
12
619
  // ...........................................................................
620
+ /**
621
+ * Creates a table and an insertHistory for the table
622
+ * @param tableCfg TableCfg of table to create
623
+ */
624
+ async createTableWithInsertHistory(tableCfg) {
625
+ await this.createTable(tableCfg);
626
+ await this.createInsertHistory(tableCfg);
627
+ }
13
628
  /**
14
629
  * Creates a table
15
- * @param name The name of the table.
16
- * @param type The type of the table.
630
+ * @param tableCfg TableCfg of table to create
631
+ */
632
+ async createTable(tableCfg) {
633
+ return this._io.createOrExtendTable({ tableCfg });
634
+ }
635
+ /**
636
+ * Creates an insertHistory table for a given table
637
+ * @param tableCfg TableCfg of table
17
638
  */
18
- async createTable(name, type) {
19
- return this._io.createTable({ name, type });
639
+ async createInsertHistory(tableCfg) {
640
+ const cfg = createInsertHistoryTableCfg(tableCfg);
641
+ await this.createTable(cfg);
20
642
  }
21
643
  // ...........................................................................
22
644
  /**
@@ -30,17 +652,20 @@ const _Core = class _Core {
30
652
  * @returns a dump of a table.
31
653
  * @throws when table name does not exist
32
654
  */
33
- dumpTable(name) {
34
- return this._io.dumpTable({ name });
655
+ dumpTable(table) {
656
+ return this._io.dumpTable({ table });
35
657
  }
36
658
  // ...........................................................................
37
659
  /**
38
660
  * Imports data into the memory.
661
+ * @param data - The rljson data to import.
39
662
  * @throws {Error} If the data is invalid.
40
663
  */
41
664
  async import(data) {
42
- const result = validate(data);
43
- if (result.hasErrors) {
665
+ const validate = new Validate();
666
+ validate.addValidator(new BaseValidator());
667
+ const result = await validate.run(data);
668
+ if ((result.hasErrors || result.base && result.base.hasErrors) && !result.base.refsNotFound) {
44
669
  throw new Error(
45
670
  "The imported rljson data is not valid:\n" + JSON.stringify(result, null, 2)
46
671
  );
@@ -49,51 +674,1969 @@ const _Core = class _Core {
49
674
  }
50
675
  // ...........................................................................
51
676
  async tables() {
52
- return this._io.tables();
677
+ return await this._io.dump();
53
678
  }
54
679
  // ...........................................................................
55
680
  async hasTable(table) {
56
- const tables = await this._io.tables();
57
- return tables.includes(table);
681
+ return await this._io.tableExists(table);
682
+ }
683
+ // ...........................................................................
684
+ async contentType(table) {
685
+ const t = await this._io.dumpTable({ table });
686
+ const contentType = t[table]?._type;
687
+ return contentType;
688
+ }
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;
58
703
  }
59
704
  // ...........................................................................
60
705
  /** Reads a specific row from a database table */
61
706
  readRow(table, rowHash) {
62
- return this._io.readRow({ table, rowHash });
707
+ return this._io.readRows({ table, where: { _hash: rowHash } });
63
708
  }
64
709
  // ...........................................................................
65
710
  readRows(table, where) {
66
711
  return this._io.readRows({ table, where });
67
712
  }
68
- };
69
- __publicField(_Core, "example", async () => {
70
- return new _Core(IoMem.example());
71
- });
72
- let Core = _Core;
73
- // @license
74
- const _Db = class _Db {
713
+ }
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);
722
+ }
723
+ // ...........................................................................
75
724
  /**
76
- * Constructor
77
- * @param _io - The Io instance used to read and write data
725
+ * Returns unique routes from a list of routes
726
+ * @param routes - The list of routes
727
+ * @returns Unique routes
78
728
  */
79
- constructor(_io) {
80
- /**
81
- * Core functionalities like importing data, setting and getting tables
82
- */
83
- __publicField(this, "core");
84
- this._io = _io;
85
- this.core = new Core(this._io);
729
+ static uniqueRoutes(routes) {
730
+ return Array.from(new Set(routes.map((r) => r.flat))).map(
731
+ (flat) => Route.fromFlat(flat)
732
+ );
733
+ }
734
+ // ...........................................................................
735
+ /**
736
+ * Returns a ColumnSelection from a list of route segments
737
+ * @param routeSegmentsList - A list of route segments
738
+ * @returns A ColumnSelection object
739
+ */
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
+ });
759
+ }
760
+ return new ColumnSelection(definition);
761
+ }
762
+ // ...........................................................................
763
+ columns;
764
+ routes;
765
+ aliases;
766
+ routeHashes;
767
+ metadata(key) {
768
+ return this.columns.map((column) => column[key]);
769
+ }
770
+ // ...........................................................................
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
+ );
777
+ }
778
+ // ...........................................................................
779
+ static calcHash(str) {
780
+ return Hash.default.calcHash(str);
781
+ }
782
+ route(aliasRouteOrHash) {
783
+ return this.column(aliasRouteOrHash).route;
784
+ }
785
+ // ...........................................................................
786
+ alias(aliasRouteOrHash) {
787
+ return this.column(aliasRouteOrHash).alias;
788
+ }
789
+ // ...........................................................................
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}`);
807
+ }
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
+ );
838
+ }
839
+ const validValueRegex = /^[a-zA-Z0-9/]*$/;
840
+ const invalidValues = routes.filter(
841
+ (value) => !validValueRegex.test(value)
842
+ );
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)
851
+ );
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.`
865
+ );
866
+ }
867
+ }
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);
878
+ }
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
+ ]);
1018
+ }
1019
+ // ...........................................................................
1020
+ static empty() {
1021
+ return new ColumnSelection([]);
86
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;
87
1054
  };
88
- /**
89
- * Example
90
- * @returns A new Db instance for test purposes
91
- */
92
- __publicField(_Db, "example", async () => {
93
- const io = new IoMem();
94
- return new _Db(io);
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: ""
95
1208
  });
96
- let Db = _Db;
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
1978
+ */
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) {
2329
+ let result;
2330
+ let tableKey;
2331
+ const segment = route.segment(0);
2332
+ tableKey = segment.tableKey;
2333
+ let previous = [];
2334
+ if (Route.segmentHasRef(segment)) {
2335
+ const routeRef = Route.segmentRef(segment);
2336
+ if (Route.segmentHasInsertHistoryRef(segment)) {
2337
+ previous = [...previous, routeRef];
2338
+ }
2339
+ if (Route.segmentHasDefaultRef(segment)) {
2340
+ const timeIds = await this.getTimeIdsForRef(
2341
+ tableKey,
2342
+ Route.segmentRef(segment)
2343
+ );
2344
+ previous = [...previous, ...timeIds];
2345
+ }
2346
+ }
2347
+ if (!route.isRoot) {
2348
+ const childRoute = route.deeper(1);
2349
+ const childKeys = this._childKeys(insert.value);
2350
+ const childRefs = {};
2351
+ for (const k of childKeys) {
2352
+ const childValue = insert.value[k];
2353
+ const childInsert = { ...insert, value: childValue };
2354
+ const childResult = await this._insert(childInsert, childRoute, runFns);
2355
+ const childRefKey = childRoute.top.tableKey + "Ref";
2356
+ const childRef = childResult[childRefKey];
2357
+ childRefs[k] = childRef;
2358
+ }
2359
+ const runFn = runFns[tableKey];
2360
+ result = {
2361
+ ...await runFn(
2362
+ insert.command,
2363
+ {
2364
+ ...insert.value,
2365
+ ...childRefs
2366
+ },
2367
+ insert.origin
2368
+ ),
2369
+ previous
2370
+ };
2371
+ } else {
2372
+ tableKey = route.root.tableKey;
2373
+ const runFn = runFns[tableKey];
2374
+ result = {
2375
+ ...await runFn(insert.command, insert.value, insert.origin),
2376
+ previous
2377
+ };
2378
+ }
2379
+ result.route = insert.route;
2380
+ await this._writeInsertHistory(tableKey, result);
2381
+ if (!options?.skipNotification)
2382
+ this.notify.notify(Route.fromFlat(insert.route), result);
2383
+ return result;
2384
+ }
2385
+ // ...........................................................................
2386
+ /**
2387
+ * Registers a callback to be called when an Insert is made on the given route
2388
+ * @param route - The route to register the callback on
2389
+ * @param callback - The callback to be called when an Insert is made
2390
+ */
2391
+ registerObserver(route, callback) {
2392
+ this.notify.register(route, callback);
2393
+ }
2394
+ // ...........................................................................
2395
+ /**
2396
+ * Unregisters a callback from the given route
2397
+ * @param route - The route to unregister the callback from
2398
+ * @param callback - The callback to be unregistered
2399
+ */
2400
+ unregisterObserver(route, callback) {
2401
+ this.notify.unregister(route, callback);
2402
+ }
2403
+ // ...........................................................................
2404
+ /**
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
2407
+ * @returns A record of controller run functions, keyed by table name
2408
+ * @throws {Error} If the route is not valid or if any controller cannot be created
2409
+ */
2410
+ async _resolveInsert(Insert2) {
2411
+ const controllers = await this._indexedControllers(
2412
+ Route.fromFlat(Insert2.route)
2413
+ );
2414
+ const runFns = {};
2415
+ for (const tableKey of Object.keys(controllers)) {
2416
+ runFns[tableKey] = controllers[tableKey].insert.bind(
2417
+ controllers[tableKey]
2418
+ );
2419
+ }
2420
+ return runFns;
2421
+ }
2422
+ // ...........................................................................
2423
+ /**
2424
+ * Returns the keys of child refs in a value based on a route
2425
+ * @param value - The value to check
2426
+ * @returns An array of keys of child refs in the value
2427
+ */
2428
+ _childKeys(value) {
2429
+ const keys = Object.keys(value);
2430
+ const childKeys = [];
2431
+ for (const k of keys) {
2432
+ if (typeof value[k] !== "object") continue;
2433
+ childKeys.push(k);
2434
+ }
2435
+ return childKeys;
2436
+ }
2437
+ // ...........................................................................
2438
+ /**
2439
+ * Get a controller for a specific table
2440
+ * @param tableKey - The key of the table to get the controller for
2441
+ * @param refs - Optional references required by some controllers
2442
+ * @returns A controller for the specified table
2443
+ * @throws {Error} If the table does not exist or if the table type is not supported
2444
+ */
2445
+ async getController(tableKey, refs) {
2446
+ const hasTable = await this.core.hasTable(tableKey);
2447
+ if (!hasTable) {
2448
+ throw new Error(`Db.getController: Table ${tableKey} does not exist.`);
2449
+ }
2450
+ const contentType = await this.core.contentType(tableKey);
2451
+ return createController(contentType, this.core, tableKey, refs);
2452
+ }
2453
+ // ...........................................................................
2454
+ async _indexedControllers(route) {
2455
+ const controllers = {};
2456
+ const isolatedRoute = await this.isolatePropertyKeyFromRoute(route);
2457
+ for (let i = 0; i < isolatedRoute.segments.length; i++) {
2458
+ const segment = isolatedRoute.segments[i];
2459
+ const tableKey = segment.tableKey;
2460
+ const segmentRef = Route.segmentRef(segment);
2461
+ const base = segmentRef ? isTimeId(segmentRef) ? await this.getRefOfTimeId(tableKey, segmentRef) : segmentRef : null;
2462
+ controllers[tableKey] ??= await this.getController(
2463
+ tableKey,
2464
+ base ? { base } : void 0
2465
+ );
2466
+ }
2467
+ return controllers;
2468
+ }
2469
+ // ...........................................................................
2470
+ /**
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
2475
+ */
2476
+ async _writeInsertHistory(table, insertHistoryRow) {
2477
+ const insertHistoryTable = table + "InsertHistory";
2478
+ await this.core.import({
2479
+ [insertHistoryTable]: {
2480
+ _data: [insertHistoryRow],
2481
+ _type: "insertHistory"
2482
+ }
2483
+ });
2484
+ }
2485
+ // ...........................................................................
2486
+ /**
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
2490
+ */
2491
+ async getInsertHistory(table, options) {
2492
+ const insertHistoryTable = table + "InsertHistory";
2493
+ const hasTable = await this.core.hasTable(insertHistoryTable);
2494
+ if (!hasTable) {
2495
+ throw new Error(`Db.getInsertHistory: Table ${table} does not exist`);
2496
+ }
2497
+ if (options === void 0) {
2498
+ options = { sorted: false, ascending: true };
2499
+ }
2500
+ if (options.sorted) {
2501
+ const dumpedTable = await this.core.dumpTable(insertHistoryTable);
2502
+ const tableData = dumpedTable[insertHistoryTable]._data;
2503
+ tableData.sort((a, b) => {
2504
+ const aTime = a.timeId.split(":")[1];
2505
+ const bTime = b.timeId.split(":")[1];
2506
+ if (options.ascending) {
2507
+ return aTime.localeCompare(bTime);
2508
+ } else {
2509
+ return bTime.localeCompare(aTime);
2510
+ }
2511
+ });
2512
+ return {
2513
+ [insertHistoryTable]: { _data: tableData, _type: "insertHistory" }
2514
+ };
2515
+ }
2516
+ return this.core.dumpTable(insertHistoryTable);
2517
+ }
2518
+ // ...........................................................................
2519
+ /**
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
2525
+ */
2526
+ async getInsertHistoryRowsByRef(table, ref) {
2527
+ const insertHistoryTable = table + "InsertHistory";
2528
+ const {
2529
+ [insertHistoryTable]: { _data: insertHistory }
2530
+ } = await this.core.readRows(insertHistoryTable, { [table + "Ref"]: ref });
2531
+ return insertHistory;
2532
+ }
2533
+ // ...........................................................................
2534
+ /**
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
2540
+ */
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
+ }
2548
+ );
2549
+ return result._data?.[0];
2550
+ }
2551
+ // ...........................................................................
2552
+ /**
2553
+ * Get all timeIds for a specific ref in a table
2554
+ * @param table - The table to get the timeIds from
2555
+ * @param ref - The reference to get the timeIds for
2556
+ * @returns An array of timeIds
2557
+ * @throws {Error} If the Inserts table does not exist
2558
+ */
2559
+ async getTimeIdsForRef(table, ref) {
2560
+ const insertHistoryTable = table + "InsertHistory";
2561
+ const { [insertHistoryTable]: result } = await this.core.readRows(
2562
+ insertHistoryTable,
2563
+ {
2564
+ [table + "Ref"]: ref
2565
+ }
2566
+ );
2567
+ return result._data?.map((r) => r.timeId);
2568
+ }
2569
+ // ...........................................................................
2570
+ /**
2571
+ * Get the ref for a specific timeId in a table
2572
+ * @param table - The table to get the ref from
2573
+ * @param timeId - The timeId to get the ref for
2574
+ * @returns The ref or null if it does not exist
2575
+ * @throws {Error} If the Inserts table does not exist
2576
+ */
2577
+ async getRefOfTimeId(table, timeId2) {
2578
+ const insertHistoryTable = table + "InsertHistory";
2579
+ const { [insertHistoryTable]: result } = await this.core.readRows(
2580
+ insertHistoryTable,
2581
+ {
2582
+ timeId: timeId2
2583
+ }
2584
+ );
2585
+ return result._data?.[0]?.[table + "Ref"];
2586
+ }
2587
+ // ...........................................................................
2588
+ /**
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
2592
+ */
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
+ }
2639
+ }
97
2640
  export {
98
2641
  Db as RljsonDb
99
2642
  };