@rljson/db 0.0.6 → 0.0.8

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,13 +1,16 @@
1
- import { equals, merge } from "@rljson/json";
2
- import { timeId, createInsertHistoryTableCfg, Validate, BaseValidator, Route, validateInsert, isTimeId } from "@rljson/rljson";
3
1
  import { rmhsh, hsh, Hash, hip } from "@rljson/hash";
2
+ import { equals, merge } from "@rljson/json";
3
+ import { timeId, createInsertHistoryTableCfg, Validate, BaseValidator, Route, isTimeId } from "@rljson/rljson";
4
4
  import { IoMem } from "@rljson/io";
5
+ import { traverse } from "object-traversal";
5
6
  import { compileExpression } from "filtrex";
6
7
  class BaseController {
7
8
  constructor(_core, _tableKey) {
8
9
  this._core = _core;
9
10
  this._tableKey = _tableKey;
10
11
  }
12
+ _contentType;
13
+ _tableCfg;
11
14
  // ...........................................................................
12
15
  /**
13
16
  * Retrieves the current state of the table.
@@ -66,6 +69,28 @@ class BaseController {
66
69
  });
67
70
  return rows;
68
71
  }
72
+ // ...........................................................................
73
+ /**
74
+ * Gets the content type of the controller.
75
+ * @returns The content type managed by the controller.
76
+ */
77
+ /* v8 ignore next -- @preserve */
78
+ contentType() {
79
+ return this._contentType ?? "components";
80
+ }
81
+ // ...........................................................................
82
+ /**
83
+ * Gets the table configuration of the controller.
84
+ * @returns The table configuration managed by the controller.
85
+ */
86
+ tableCfg() {
87
+ if (!this._tableCfg) {
88
+ throw new Error(
89
+ `TableCfg for controller ${this._tableKey} is not initialized.`
90
+ );
91
+ }
92
+ return this._tableCfg;
93
+ }
69
94
  }
70
95
  class CakeController extends BaseController {
71
96
  constructor(_core, _tableKey, _refs) {
@@ -73,6 +98,7 @@ class CakeController extends BaseController {
73
98
  this._core = _core;
74
99
  this._tableKey = _tableKey;
75
100
  this._refs = _refs;
101
+ this._contentType = "cakes";
76
102
  }
77
103
  _table = null;
78
104
  _baseLayers = {};
@@ -87,7 +113,8 @@ class CakeController extends BaseController {
87
113
  if (this._table._type !== "cakes") {
88
114
  throw new Error(`Table ${this._tableKey} is not of type cakes.`);
89
115
  }
90
- if (this._refs && this._refs.base) {
116
+ this._tableCfg = await this._core.tableCfg(this._tableKey);
117
+ if (this._refs && this._refs.base && this._refs.base !== void 0 && this._refs.base.length > 0) {
91
118
  const {
92
119
  [this._tableKey]: { _data: baseCakes }
93
120
  } = await this._core.readRow(this._tableKey, this._refs.base);
@@ -98,10 +125,13 @@ class CakeController extends BaseController {
98
125
  this._baseLayers = rmhsh(baseCake.layers);
99
126
  } else {
100
127
  const cake = this._table._data[0];
101
- this._refs = {
102
- sliceIdsTable: cake.sliceIdsTable,
103
- sliceIdsRow: cake.sliceIdsRow
104
- };
128
+ if (!!cake) {
129
+ this._refs = {
130
+ sliceIdsTable: cake.sliceIdsTable,
131
+ sliceIdsRow: cake.sliceIdsRow
132
+ };
133
+ this._baseLayers = rmhsh(cake.layers);
134
+ }
105
135
  }
106
136
  }
107
137
  async getChildRefs(where, filter) {
@@ -126,8 +156,21 @@ class CakeController extends BaseController {
126
156
  if (!command.startsWith("add")) {
127
157
  throw new Error(`Command ${command} is not supported by CakeController.`);
128
158
  }
159
+ if (this._refs?.base) delete this._refs.base;
160
+ const normalizedValue = {};
161
+ for (const [layerTable, layerRef] of Object.entries(
162
+ value.layers
163
+ )) {
164
+ if (Array.isArray(layerRef) && layerRef.length > 1) {
165
+ throw new Error(
166
+ `CakeController insert: Layer ref for table ${layerTable} cannot be an array of size > 1. No 1:n relations supported.`
167
+ );
168
+ }
169
+ normalizedValue[layerTable] = Array.isArray(layerRef) ? layerRef[0] : layerRef;
170
+ }
129
171
  const cake = {
130
- layers: { ...this._baseLayers, ...value },
172
+ ...value,
173
+ layers: { ...this._baseLayers, ...normalizedValue },
131
174
  ...refs || this._refs
132
175
  };
133
176
  const rlJson = { [this._tableKey]: { _data: [cake] } };
@@ -141,7 +184,7 @@ class CakeController extends BaseController {
141
184
  //Unique id/timestamp
142
185
  timeId: timeId()
143
186
  };
144
- return result;
187
+ return [result];
145
188
  }
146
189
  async get(where, filter) {
147
190
  if (typeof where === "string") {
@@ -152,7 +195,7 @@ class CakeController extends BaseController {
152
195
  return Promise.resolve({});
153
196
  }
154
197
  }
155
- filterRow(row, key, value) {
198
+ async filterRow(row, key, value) {
156
199
  const cake = row;
157
200
  for (const [layerKey, layerRef] of Object.entries(cake.layers)) {
158
201
  if (layerKey === key && layerRef === value) {
@@ -168,8 +211,14 @@ class ComponentController extends BaseController {
168
211
  this._core = _core;
169
212
  this._tableKey = _tableKey;
170
213
  this._refs = _refs;
214
+ this._contentType = "components";
171
215
  }
172
- _tableCfg = null;
216
+ _allowedContentTypes = [
217
+ "components",
218
+ "edits",
219
+ "editHistory",
220
+ "multiEdits"
221
+ ];
173
222
  _resolvedColumns = null;
174
223
  _refTableKeyToColumnKeyMap = null;
175
224
  async init() {
@@ -178,7 +227,7 @@ class ComponentController extends BaseController {
178
227
  }
179
228
  const rljson = await this._core.dumpTable(this._tableKey);
180
229
  const table = rljson[this._tableKey];
181
- if (table._type !== "components") {
230
+ if (this._allowedContentTypes.indexOf(table._type) === -1) {
182
231
  throw new Error(`Table ${this._tableKey} is not of type components.`);
183
232
  }
184
233
  this._tableCfg = await this._core.tableCfg(this._tableKey);
@@ -197,17 +246,20 @@ class ComponentController extends BaseController {
197
246
  throw new Error(`Refs are not supported on ComponentController.`);
198
247
  }
199
248
  const component = value;
249
+ delete component._somethingToInsert;
200
250
  const rlJson = { [this._tableKey]: { _data: [component] } };
201
251
  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
- };
252
+ return [
253
+ {
254
+ //Ref to component
255
+ [this._tableKey + "Ref"]: hsh(component)._hash,
256
+ //Data from edit
257
+ route: "",
258
+ origin,
259
+ //Unique id/timestamp
260
+ timeId: timeId()
261
+ }
262
+ ];
211
263
  }
212
264
  // ...........................................................................
213
265
  /**
@@ -242,6 +294,20 @@ class ComponentController extends BaseController {
242
294
  columnKey: propertyKey,
243
295
  ref: refItem
244
296
  });
297
+ continue;
298
+ }
299
+ if (typeof refItem === "object" && refItem !== null) {
300
+ const cakeReference = refItem;
301
+ childRefs.set(
302
+ `${childRefTableKey}|${propertyKey}|${cakeReference.ref}|${cakeReference.sliceIds?.join(",")}`,
303
+ {
304
+ tableKey: childRefTableKey,
305
+ columnKey: propertyKey,
306
+ ref: cakeReference.ref,
307
+ sliceIds: cakeReference.sliceIds
308
+ }
309
+ );
310
+ continue;
245
311
  }
246
312
  }
247
313
  continue;
@@ -274,7 +340,7 @@ class ComponentController extends BaseController {
274
340
  const column = Object.keys(refWhere)[0];
275
341
  const refValue = refWhere[column];
276
342
  for (const row of tableData) {
277
- if (this.filterRow(row, column, refValue)) {
343
+ if (await this.filterRow(row, column, refValue)) {
278
344
  consolidatedRows.set(row._hash, row);
279
345
  }
280
346
  }
@@ -473,7 +539,7 @@ class ComponentController extends BaseController {
473
539
  return this._core.readRows(table, where);
474
540
  }
475
541
  }
476
- filterRow(row, key, value) {
542
+ async filterRow(row, key, value) {
477
543
  for (const [propertyKey, propertyValue] of Object.entries(row)) {
478
544
  if (propertyKey === key && equals(propertyValue, value)) {
479
545
  return true;
@@ -488,12 +554,131 @@ class ComponentController extends BaseController {
488
554
  return false;
489
555
  }
490
556
  }
557
+ class SliceIdController extends BaseController {
558
+ constructor(_core, _tableKey, _refs) {
559
+ super(_core, _tableKey);
560
+ this._core = _core;
561
+ this._tableKey = _tableKey;
562
+ this._refs = _refs;
563
+ this._contentType = "sliceIds";
564
+ }
565
+ async init() {
566
+ if (this._tableKey.endsWith("SliceId") === false) {
567
+ throw new Error(
568
+ `Table ${this._tableKey} is not supported by SliceIdController.`
569
+ );
570
+ }
571
+ const rljson = await this._core.dumpTable(this._tableKey);
572
+ const table = rljson[this._tableKey];
573
+ if (table._type !== "sliceIds") {
574
+ throw new Error(`Table ${this._tableKey} is not of type sliceIds.`);
575
+ }
576
+ this._tableCfg = await this._core.tableCfg(this._tableKey);
577
+ if (this._refs && this._refs.base) {
578
+ const {
579
+ [this._tableKey]: { _data: SliceIds2 }
580
+ } = await this._core.readRow(this._tableKey, this._refs.base);
581
+ if (SliceIds2.length === 0) {
582
+ throw new Error(`Base sliceId ${this._refs.base} does not exist.`);
583
+ }
584
+ } else {
585
+ const sliceId = table._data[0];
586
+ if (!!sliceId) {
587
+ this._refs = {
588
+ base: sliceId.base
589
+ };
590
+ }
591
+ }
592
+ }
593
+ async insert(command, value, origin, refs) {
594
+ if (!command.startsWith("add") && !command.startsWith("remove")) {
595
+ throw new Error(
596
+ `Command ${command} is not supported by SliceIdController.`
597
+ );
598
+ }
599
+ const sliceIds = command.startsWith("add") === true ? {
600
+ add: value,
601
+ ...refs || this._refs
602
+ } : {
603
+ add: [],
604
+ remove: value,
605
+ ...refs || this._refs
606
+ };
607
+ const rlJson = { [this._tableKey]: { _data: [sliceIds] } };
608
+ await this._core.import(rlJson);
609
+ const result = {
610
+ //Ref to component
611
+ [this._tableKey + "Ref"]: hsh(sliceIds)._hash,
612
+ //Data from edit
613
+ route: "",
614
+ origin,
615
+ //Unique id/timestamp
616
+ timeId: timeId()
617
+ };
618
+ return [result];
619
+ }
620
+ async get(where, filter) {
621
+ if (typeof where === "string") {
622
+ return this._getByHash(where, filter);
623
+ } else {
624
+ return this._getByWhere(where, filter);
625
+ }
626
+ }
627
+ async resolveBaseSliceIds(sliceIds) {
628
+ const add = /* @__PURE__ */ new Set();
629
+ const remove = /* @__PURE__ */ new Set();
630
+ if (!!sliceIds.base) {
631
+ const baseSliceIds = await this.get(sliceIds.base);
632
+ if (!baseSliceIds[this._tableKey]?._data?.[0]) {
633
+ throw new Error(`Base sliceIds ${sliceIds.base} does not exist.`);
634
+ }
635
+ if (baseSliceIds[this._tableKey]._data.length > 1) {
636
+ throw new Error(
637
+ `Base sliceIds ${sliceIds.base} has more than one entry.`
638
+ );
639
+ }
640
+ const baseSliceId = baseSliceIds[this._tableKey]._data[0];
641
+ const resolvedBaseSliceIds = await this.resolveBaseSliceIds(baseSliceId);
642
+ for (const sliceId of resolvedBaseSliceIds.add) {
643
+ add.add(sliceId);
644
+ }
645
+ }
646
+ for (const sliceId of sliceIds.add) {
647
+ add.add(sliceId);
648
+ }
649
+ if (!!sliceIds.remove)
650
+ for (const sliceId of sliceIds.remove) {
651
+ remove.add(sliceId);
652
+ }
653
+ for (const sliceId of remove.values()) {
654
+ if (add.has(sliceId)) {
655
+ add.delete(sliceId);
656
+ }
657
+ }
658
+ return { add: Array.from(add) };
659
+ }
660
+ /* v8 ignore next -- @preserve */
661
+ async getChildRefs() {
662
+ return [];
663
+ }
664
+ async filterRow(row, _, value) {
665
+ const sliceIds = row;
666
+ const sliceId = value;
667
+ for (const sId of Object.values(sliceIds.add)) {
668
+ if (sliceId === sId) {
669
+ return true;
670
+ }
671
+ }
672
+ return false;
673
+ }
674
+ }
491
675
  class LayerController extends BaseController {
492
676
  constructor(_core, _tableKey, _refs) {
493
677
  super(_core, _tableKey);
494
678
  this._core = _core;
495
679
  this._tableKey = _tableKey;
496
680
  this._refs = _refs;
681
+ this._contentType = "layers";
497
682
  }
498
683
  async init() {
499
684
  if (this._tableKey.endsWith("Layer") === false) {
@@ -506,6 +691,7 @@ class LayerController extends BaseController {
506
691
  if (table._type !== "layers") {
507
692
  throw new Error(`Table ${this._tableKey} is not of type layers.`);
508
693
  }
694
+ this._tableCfg = await this._core.tableCfg(this._tableKey);
509
695
  if (this._refs && this._refs.base) {
510
696
  const {
511
697
  [this._tableKey]: { _data: baseLayers }
@@ -523,11 +709,13 @@ class LayerController extends BaseController {
523
709
  }
524
710
  } else {
525
711
  const layer = table._data[0];
526
- this._refs = {
527
- sliceIdsTable: layer.sliceIdsTable,
528
- sliceIdsTableRow: layer.sliceIdsTableRow,
529
- componentsTable: layer.componentsTable
530
- };
712
+ if (!!layer) {
713
+ this._refs = {
714
+ sliceIdsTable: layer.sliceIdsTable,
715
+ sliceIdsTableRow: layer.sliceIdsTableRow,
716
+ componentsTable: layer.componentsTable
717
+ };
718
+ }
531
719
  }
532
720
  }
533
721
  async insert(command, value, origin, refs) {
@@ -536,13 +724,30 @@ class LayerController extends BaseController {
536
724
  `Command ${command} is not supported by LayerController.`
537
725
  );
538
726
  }
539
- const layer = command.startsWith("add") === true ? {
540
- add: value,
541
- ...refs || this._refs
727
+ const isAdd = command.startsWith("add");
728
+ const normalizedValue = {};
729
+ for (const [sliceId, compRef] of isAdd ? Object.entries(value.add) : Object.entries(value.remove)) {
730
+ if (Array.isArray(compRef) && compRef.length > 1) {
731
+ throw new Error(
732
+ `LayerController insert: Component ref for slice ${sliceId} cannot be an array of size > 1. No 1:n relations supported.`
733
+ );
734
+ }
735
+ normalizedValue[sliceId] = Array.isArray(compRef) ? compRef[0] : compRef;
736
+ }
737
+ const layer = isAdd ? {
738
+ ...value,
739
+ ...{
740
+ add: normalizedValue,
741
+ remove: {}
742
+ },
743
+ ...refs
542
744
  } : {
543
- add: {},
544
- remove: value,
545
- ...refs || this._refs
745
+ ...value,
746
+ ...{
747
+ remove: normalizedValue,
748
+ add: {}
749
+ },
750
+ ...refs
546
751
  };
547
752
  const rlJson = { [this._tableKey]: { _data: [layer] } };
548
753
  await this._core.import(rlJson);
@@ -555,7 +760,7 @@ class LayerController extends BaseController {
555
760
  //Unique id/timestamp
556
761
  timeId: timeId()
557
762
  };
558
- return result;
763
+ return [result];
559
764
  }
560
765
  async get(where, filter) {
561
766
  if (typeof where === "string") {
@@ -564,25 +769,108 @@ class LayerController extends BaseController {
564
769
  return this._getByWhere(where, filter);
565
770
  }
566
771
  }
772
+ async resolveBaseLayer(layer) {
773
+ const add = /* @__PURE__ */ new Map();
774
+ const sliceIds = /* @__PURE__ */ new Set();
775
+ if (!!layer.base) {
776
+ const baseLayer = await this.get(layer.base);
777
+ if (!baseLayer[this._tableKey]?._data?.[0]) {
778
+ throw new Error(`Base layer ${layer.base} does not exist.`);
779
+ }
780
+ if (baseLayer[this._tableKey]._data.length > 1) {
781
+ throw new Error(
782
+ `Base layer ${layer.base} resolving not possible. Not unique.`
783
+ );
784
+ }
785
+ const baseLayerData = rmhsh(baseLayer[this._tableKey]._data[0]);
786
+ const baseLayerResolved = await this.resolveBaseLayer(baseLayerData);
787
+ for (const [sliceId, compRef] of Object.entries(baseLayerResolved.add)) {
788
+ if (sliceId.startsWith("_")) continue;
789
+ add.set(sliceId, compRef);
790
+ }
791
+ for (const sliceId of baseLayerResolved.sliceIds) {
792
+ sliceIds.add(sliceId);
793
+ }
794
+ const baseLayerSliceIdsTable = baseLayerData.sliceIdsTable;
795
+ const baseLayerSliceIdsRow = baseLayerData.sliceIdsTableRow;
796
+ const {
797
+ [baseLayerSliceIdsTable]: { _data: baseLayerSliceIds }
798
+ } = await this._core.readRow(
799
+ baseLayerSliceIdsTable,
800
+ baseLayerSliceIdsRow
801
+ );
802
+ for (const sIds of baseLayerSliceIds) {
803
+ const sliceIdController = new SliceIdController(
804
+ this._core,
805
+ baseLayerSliceIdsTable
806
+ );
807
+ const resolvedSliceIds = await sliceIdController.resolveBaseSliceIds(
808
+ sIds
809
+ );
810
+ for (const sId of resolvedSliceIds.add) {
811
+ if (sId.startsWith("_")) continue;
812
+ sliceIds.add(sId);
813
+ }
814
+ }
815
+ }
816
+ const {
817
+ [layer.sliceIdsTable]: { _data: layerSliceIds }
818
+ } = await this._core.readRow(layer.sliceIdsTable, layer.sliceIdsTableRow);
819
+ if (!layerSliceIds || layerSliceIds.length === 0) {
820
+ throw new Error(
821
+ `Layer sliceIds ${layer.sliceIdsTableRow} does not exist.`
822
+ );
823
+ }
824
+ if (layerSliceIds.length > 1) {
825
+ throw new Error(
826
+ `Layer sliceIds ${layer.sliceIdsTableRow} has more than one entry.`
827
+ );
828
+ }
829
+ const layerSliceId = layerSliceIds[0];
830
+ for (const sId of layerSliceId.add) {
831
+ if (sId.startsWith("_")) continue;
832
+ sliceIds.add(sId);
833
+ }
834
+ if (!!layerSliceId.remove)
835
+ for (const sId of Object.keys(layerSliceId.remove)) {
836
+ if (sliceIds.has(sId)) {
837
+ sliceIds.delete(sId);
838
+ }
839
+ }
840
+ for (const [sliceId, compRef] of Object.entries(layer.add)) {
841
+ if (sliceId.startsWith("_")) continue;
842
+ add.set(sliceId, compRef);
843
+ }
844
+ if (!!layer.remove)
845
+ for (const sliceId of Object.keys(layer.remove)) {
846
+ if (add.has(sliceId)) {
847
+ add.delete(sliceId);
848
+ }
849
+ }
850
+ return { add: Object.fromEntries(add), sliceIds: Array.from(sliceIds) };
851
+ }
567
852
  async getChildRefs(where, filter) {
568
853
  const { [this._tableKey]: table } = await this.get(where, filter);
569
854
  const childRefs = [];
570
855
  for (const row of table._data) {
571
856
  const layer = row;
572
- for (const [sliceId, compRef] of Object.entries(layer.add)) {
857
+ const resolvedLayer = await this.resolveBaseLayer(layer);
858
+ for (const [sliceId, ref] of Object.entries(resolvedLayer.add)) {
573
859
  if (sliceId.startsWith("_")) continue;
574
860
  childRefs.push({
575
861
  tableKey: layer.componentsTable,
576
- ref: compRef
862
+ ref,
863
+ sliceIds: [sliceId]
577
864
  });
578
865
  }
579
866
  }
580
867
  return childRefs;
581
868
  }
582
- filterRow(row, _, value) {
869
+ async filterRow(row, _, value) {
583
870
  const layer = row;
584
871
  const compRef = value;
585
- for (const componentRef of Object.values(layer.add)) {
872
+ const resolvedLayer = await this.resolveBaseLayer(layer);
873
+ for (const componentRef of Object.values(resolvedLayer.add)) {
586
874
  if (componentRef === compRef) {
587
875
  return true;
588
876
  }
@@ -597,11 +885,21 @@ const createController = async (type, core, tableKey, refs) => {
597
885
  ctrl = new LayerController(core, tableKey, refs);
598
886
  break;
599
887
  case "components":
888
+ case "edits":
889
+ case "editHistory":
890
+ case "multiEdits":
600
891
  ctrl = new ComponentController(core, tableKey, refs);
601
892
  break;
602
893
  case "cakes":
603
894
  ctrl = new CakeController(core, tableKey, refs);
604
895
  break;
896
+ case "sliceIds":
897
+ ctrl = new SliceIdController(
898
+ core,
899
+ tableKey,
900
+ refs
901
+ );
902
+ break;
605
903
  default:
606
904
  throw new Error(`Controller for type ${type} is not implemented yet.`);
607
905
  }
@@ -665,7 +963,7 @@ class Core {
665
963
  const validate = new Validate();
666
964
  validate.addValidator(new BaseValidator());
667
965
  const result = await validate.run(data);
668
- if ((result.hasErrors || result.base && result.base.hasErrors) && !result.base.refsNotFound) {
966
+ if ((result.hasErrors || result.base && result.base.hasErrors) && !result.base.refsNotFound && !result.base.layerBasesNotFound) {
669
967
  throw new Error(
670
968
  "The imported rljson data is not valid:\n" + JSON.stringify(result, null, 2)
671
969
  );
@@ -711,6 +1009,152 @@ class Core {
711
1009
  return this._io.readRows({ table, where });
712
1010
  }
713
1011
  }
1012
+ const inject = (tree, path, value) => {
1013
+ for (let i = 0; i < path.length; i++) {
1014
+ const segment = path[i];
1015
+ if (i === path.length - 1) {
1016
+ tree[segment] = value;
1017
+ delete tree["_hash"];
1018
+ } else {
1019
+ if (!tree[segment]) {
1020
+ tree[segment] = {};
1021
+ }
1022
+ tree = tree[segment];
1023
+ }
1024
+ }
1025
+ };
1026
+ const isolate = (tree, path, preservedKeys = []) => {
1027
+ if (path.length === 0) {
1028
+ return Array.isArray(tree) ? [] : {};
1029
+ }
1030
+ if (tree == null) {
1031
+ return null;
1032
+ }
1033
+ const [currentKey, ...remainingPath] = path;
1034
+ const result = Array.isArray(tree) ? [] : {};
1035
+ if (!Array.isArray(tree)) {
1036
+ for (const key in tree) {
1037
+ if (typeof key === "string" && key.startsWith("_") || preservedKeys.includes(key)) {
1038
+ result[key] = tree[key];
1039
+ }
1040
+ }
1041
+ }
1042
+ if (!(currentKey in tree)) {
1043
+ return result;
1044
+ }
1045
+ const currentValue = tree[currentKey];
1046
+ if (remainingPath.length === 0) {
1047
+ if (Array.isArray(result)) {
1048
+ result[currentKey] = currentValue;
1049
+ } else {
1050
+ result[currentKey] = currentValue;
1051
+ }
1052
+ } else {
1053
+ const isolatedChild = isolate(currentValue, remainingPath, preservedKeys);
1054
+ const hasContent = Array.isArray(isolatedChild) ? isolatedChild.length > 0 : Object.keys(isolatedChild).length > 0;
1055
+ if (hasContent || isolatedChild === null) {
1056
+ if (Array.isArray(result)) {
1057
+ result[currentKey] = isolatedChild;
1058
+ } else {
1059
+ result[currentKey] = isolatedChild;
1060
+ }
1061
+ }
1062
+ }
1063
+ return result;
1064
+ };
1065
+ const mergeTrees = (trees) => {
1066
+ if (!trees || trees.length === 0) {
1067
+ return {};
1068
+ }
1069
+ let result = {};
1070
+ for (const { tree } of trees) {
1071
+ if (tree != null) {
1072
+ result = mergeStructures(result, tree);
1073
+ }
1074
+ }
1075
+ const pathValues = [];
1076
+ for (const { tree, path } of trees) {
1077
+ if (tree == null) continue;
1078
+ let current = tree;
1079
+ let pathExists = true;
1080
+ for (const key of path) {
1081
+ if (current == null || !(key in current)) {
1082
+ pathExists = false;
1083
+ break;
1084
+ }
1085
+ current = current[key];
1086
+ }
1087
+ if (pathExists && current != null) {
1088
+ pathValues.push({ path, value: current });
1089
+ }
1090
+ }
1091
+ const pathGroups = /* @__PURE__ */ new Map();
1092
+ for (const { path, value } of pathValues) {
1093
+ const pathKey = JSON.stringify(path);
1094
+ if (!pathGroups.has(pathKey)) {
1095
+ pathGroups.set(pathKey, []);
1096
+ }
1097
+ pathGroups.get(pathKey).push(value);
1098
+ }
1099
+ for (const [pathKey, values] of pathGroups) {
1100
+ const path = JSON.parse(pathKey);
1101
+ let mergedValue = void 0;
1102
+ for (const value of values) {
1103
+ if (value == null) continue;
1104
+ if (mergedValue === void 0) {
1105
+ mergedValue = value;
1106
+ continue;
1107
+ }
1108
+ if (Array.isArray(mergedValue) && Array.isArray(value)) {
1109
+ mergedValue = [...mergedValue, ...value];
1110
+ } else if (!Array.isArray(mergedValue) && !Array.isArray(value)) {
1111
+ mergedValue = {
1112
+ ...mergedValue,
1113
+ ...value
1114
+ };
1115
+ }
1116
+ }
1117
+ let current = result;
1118
+ for (let i = 0; i < path.length - 1; i++) {
1119
+ const key = path[i];
1120
+ if (current == null || !(key in current)) {
1121
+ current[key] = typeof path[i + 1] === "number" ? [] : {};
1122
+ }
1123
+ current = current[key];
1124
+ }
1125
+ if (path.length > 0) {
1126
+ current[path[path.length - 1]] = mergedValue;
1127
+ }
1128
+ }
1129
+ return result;
1130
+ };
1131
+ function mergeStructures(target, source) {
1132
+ if (source == null) return target;
1133
+ if (target == null) return source;
1134
+ if (Array.isArray(target) && Array.isArray(source)) {
1135
+ const result = [...target];
1136
+ for (let i = 0; i < source.length; i++) {
1137
+ if (result[i] === void 0) {
1138
+ result[i] = source[i];
1139
+ } else {
1140
+ result[i] = mergeStructures(result[i], source[i]);
1141
+ }
1142
+ }
1143
+ return result;
1144
+ }
1145
+ if (typeof target === "object" && typeof source === "object" && !Array.isArray(target) && !Array.isArray(source) && target !== null && source !== null) {
1146
+ const result = { ...target };
1147
+ for (const key in source) {
1148
+ if (key in result) {
1149
+ result[key] = mergeStructures(result[key], source[key]);
1150
+ } else {
1151
+ result[key] = source[key];
1152
+ }
1153
+ }
1154
+ return result;
1155
+ }
1156
+ return source;
1157
+ }
714
1158
  class ColumnSelection {
715
1159
  constructor(columns) {
716
1160
  this._throwOnWrongAlias(columns);
@@ -975,53 +1419,156 @@ class ColumnSelection {
975
1419
  static exampleCarsColumnSelection() {
976
1420
  return new ColumnSelection([
977
1421
  {
978
- route: "carCake/carGeneralLayer/carGeneral/brand",
979
1422
  key: "brand",
1423
+ route: "carCake/carGeneralLayer/carGeneral/brand",
980
1424
  alias: "brand",
981
- titleShort: "Brand",
982
1425
  titleLong: "Car Brand",
983
- type: "string"
1426
+ titleShort: "Brand",
1427
+ type: "string",
1428
+ _hash: ""
984
1429
  },
985
1430
  {
986
- route: "carCake/carGeneralLayer/carGeneral/type",
987
1431
  key: "type",
1432
+ route: "carCake/carGeneralLayer/carGeneral/type",
988
1433
  alias: "type",
989
- titleShort: "Type",
990
1434
  titleLong: "Car Type",
991
- type: "string"
1435
+ titleShort: "Type",
1436
+ type: "string",
1437
+ _hash: ""
1438
+ },
1439
+ {
1440
+ key: "serviceIntervals",
1441
+ route: "carCake/carGeneralLayer/carGeneral/serviceIntervals",
1442
+ alias: "serviceIntervals",
1443
+ titleLong: "Car Service Intervals",
1444
+ titleShort: "Service Intervals",
1445
+ type: "jsonValue",
1446
+ _hash: ""
992
1447
  },
993
1448
  {
994
- route: "carCake/carGeneralLayer/carGeneral/isElectric",
995
1449
  key: "isElectric",
1450
+ route: "carCake/carGeneralLayer/carGeneral/isElectric",
996
1451
  alias: "isElectric",
997
- titleShort: "Is Electric",
998
- titleLong: "This Car is Electric",
999
- type: "boolean"
1452
+ titleLong: "Is Electric Car",
1453
+ titleShort: "Electric",
1454
+ type: "boolean",
1455
+ _hash: ""
1456
+ },
1457
+ {
1458
+ key: "carDimensions/height",
1459
+ route: "carCake/carTechnicalLayer/carTechnical/carDimensions/height",
1460
+ alias: "height",
1461
+ titleLong: "Car Height",
1462
+ titleShort: "Height",
1463
+ type: "number",
1464
+ _hash: ""
1465
+ },
1466
+ {
1467
+ key: "carDimensions/width",
1468
+ route: "carCake/carTechnicalLayer/carTechnical/carDimensions/width",
1469
+ alias: "width",
1470
+ titleLong: "Car Width",
1471
+ titleShort: "Width",
1472
+ type: "number",
1473
+ _hash: ""
1474
+ },
1475
+ {
1476
+ key: "carDimensions/length",
1477
+ route: "carCake/carTechnicalLayer/carTechnical/carDimensions/length",
1478
+ alias: "length",
1479
+ titleLong: "Car Length",
1480
+ titleShort: "Length",
1481
+ type: "number",
1482
+ _hash: ""
1000
1483
  },
1001
1484
  {
1002
- route: "carCake/carTechnicalLayer/carTechnical/transmission",
1003
- key: "transmission",
1004
- alias: "transmission",
1005
- titleShort: "Transmission",
1006
- titleLong: "Type of Transmission",
1007
- type: "string"
1485
+ key: "engine",
1486
+ route: "carCake/carTechnicalLayer/carTechnical/engine",
1487
+ alias: "engine",
1488
+ titleLong: "Car Engine",
1489
+ titleShort: "Engine",
1490
+ type: "string",
1491
+ _hash: ""
1008
1492
  },
1009
1493
  {
1010
- route: "carCake/carColorLayer/carColor/sides",
1011
- key: "sides",
1012
- alias: "sides",
1013
- titleShort: "Sides",
1014
- titleLong: "Car Sides Color",
1015
- type: "string"
1494
+ key: "repairedByWorkshop",
1495
+ route: "carCake/carTechnicalLayer/carTechnical/repairedByWorkshop",
1496
+ alias: "repairedByWorkshop",
1497
+ titleLong: "Was Repaired By Workshop",
1498
+ titleShort: "Repaired By Workshop",
1499
+ type: "boolean",
1500
+ _hash: ""
1016
1501
  }
1017
1502
  ]);
1018
1503
  }
1019
- // ...........................................................................
1020
- static empty() {
1021
- return new ColumnSelection([]);
1504
+ static exampleCarsDeeplyNestedColumnSelection() {
1505
+ return new ColumnSelection([
1506
+ {
1507
+ key: "brand",
1508
+ route: "catalogCake/catalogSeriesLayer/catalogSeries/seriesCake/seriesCarsLayer/seriesCars/carCake/carGeneralLayer/carGeneral/brand",
1509
+ alias: "brand",
1510
+ titleLong: "Car Brand",
1511
+ titleShort: "Brand",
1512
+ type: "string",
1513
+ _hash: ""
1514
+ }
1515
+ ]);
1022
1516
  }
1023
- }
1024
- const trueValues = ["t", "j", "y"];
1517
+ static exampleCarsColumnSelectionOnlySomeColumns() {
1518
+ return new ColumnSelection([
1519
+ {
1520
+ key: "brand",
1521
+ route: "carCake/carGeneralLayer/carGeneral/brand",
1522
+ alias: "brand",
1523
+ titleLong: "Car Brand",
1524
+ titleShort: "Brand",
1525
+ type: "string",
1526
+ _hash: ""
1527
+ },
1528
+ {
1529
+ key: "type",
1530
+ route: "carCake/carGeneralLayer/carGeneral/type",
1531
+ alias: "type",
1532
+ titleLong: "Car Type",
1533
+ titleShort: "Type",
1534
+ type: "string",
1535
+ _hash: ""
1536
+ },
1537
+ {
1538
+ key: "serviceIntervals",
1539
+ route: "carCake/carGeneralLayer/carGeneral/serviceIntervals",
1540
+ alias: "serviceIntervals",
1541
+ titleLong: "Car Service Intervals",
1542
+ titleShort: "Service Intervals",
1543
+ type: "jsonValue",
1544
+ _hash: ""
1545
+ },
1546
+ {
1547
+ key: "isElectric",
1548
+ route: "carCake/carGeneralLayer/carGeneral/isElectric",
1549
+ alias: "isElectric",
1550
+ titleLong: "Is Electric Car",
1551
+ titleShort: "Electric",
1552
+ type: "boolean",
1553
+ _hash: ""
1554
+ },
1555
+ {
1556
+ key: "carDimensions/length",
1557
+ route: "carCake/carTechnicalLayer/carTechnical/carDimensions/length",
1558
+ alias: "length",
1559
+ titleLong: "Car Length",
1560
+ titleShort: "Length",
1561
+ type: "number",
1562
+ _hash: ""
1563
+ }
1564
+ ]);
1565
+ }
1566
+ // ...........................................................................
1567
+ static empty() {
1568
+ return new ColumnSelection([]);
1569
+ }
1570
+ }
1571
+ const trueValues = ["t", "j", "y"];
1025
1572
  const falseValues = ["n", "f"];
1026
1573
  const parseBooleanSearch = (search) => {
1027
1574
  if (typeof search == "undefined" || search == null) {
@@ -1492,9 +2039,11 @@ class RowFilterProcessor {
1492
2039
  return remainingIndices;
1493
2040
  }
1494
2041
  for (const i of remainingIndices) {
1495
- const cellValue = join.value(i, columnIndex);
1496
- if (filter.matches(cellValue)) {
1497
- result.push(i);
2042
+ const cellValues = join.value(i, columnIndex);
2043
+ for (const cellValue of cellValues) {
2044
+ if (filter.matches(cellValue)) {
2045
+ result.push(i);
2046
+ }
1498
2047
  }
1499
2048
  }
1500
2049
  return result;
@@ -1532,9 +2081,11 @@ class RowFilterProcessor {
1532
2081
  if (applyTo[r]) {
1533
2082
  continue;
1534
2083
  }
1535
- const cellValue = join.value(r, columnIndex);
1536
- if (filter.matches(cellValue)) {
1537
- applyTo[r] = true;
2084
+ const cellValues = join.value(r, columnIndex);
2085
+ for (const cellValue of cellValues) {
2086
+ if (filter.matches(cellValue)) {
2087
+ applyTo[r] = true;
2088
+ }
1538
2089
  }
1539
2090
  }
1540
2091
  }
@@ -1545,7 +2096,7 @@ class RowFilterProcessor {
1545
2096
  const route = item.route;
1546
2097
  if (availableRoutes.includes(route) === false) {
1547
2098
  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.
2099
+ `RowFilterProcessor: Error while applying filter to join: There is a column filter for route "${route}", but the join does not have a column with this route.
1549
2100
 
1550
2101
  Available routes:
1551
2102
  ${availableRoutes.map((a) => `- ${a}`).join("\n")}`
@@ -1554,13 +2105,22 @@ ${availableRoutes.map((a) => `- ${a}`).join("\n")}`
1554
2105
  }
1555
2106
  }
1556
2107
  }
2108
+ const joinPreserveKeys = [
2109
+ "sliceIdsTable",
2110
+ "sliceIdsRow",
2111
+ /*'base',*/
2112
+ "sliceIdsTable",
2113
+ "sliceIdsTableRow",
2114
+ "componentsTable"
2115
+ ];
1557
2116
  class Join {
1558
- constructor(baseRows, _baseColumnSelection) {
1559
- this._baseColumnSelection = _baseColumnSelection;
1560
- this._base = this._hashedRows(baseRows);
1561
- }
1562
2117
  _base = {};
2118
+ _baseColumnSelection;
1563
2119
  _processes = [];
2120
+ constructor(rows, columnSelection) {
2121
+ this._base = this._hashedRows(rows);
2122
+ this._baseColumnSelection = columnSelection;
2123
+ }
1564
2124
  // ...........................................................................
1565
2125
  /**
1566
2126
  * Applies a filter to the join and returns the filtered view
@@ -1589,14 +2149,62 @@ class Join {
1589
2149
  const data = {};
1590
2150
  for (const [sliceId, joinRowH] of Object.entries(this.data)) {
1591
2151
  const cols = [...joinRowH.columns];
2152
+ const insertCols = [];
1592
2153
  for (const col of cols) {
1593
- if (Route.fromFlat(setValue.route).equalsWithoutRefs(col.route))
1594
- col.insert = setValue.value;
1595
- else continue;
2154
+ const insertCol = {
2155
+ ...col
2156
+ //inserts: col.inserts ? [...col.inserts] : [],
2157
+ };
2158
+ if (Route.fromFlat(setValue.route).equalsWithoutRefs(col.route)) {
2159
+ for (const cell of col.value.cell) {
2160
+ if (cell.path.length === 0) {
2161
+ throw new Error(
2162
+ `Join: Error while applying SetValue: Cannot set value for column without paths. Route: ${setValue.route.toString()}.`
2163
+ );
2164
+ }
2165
+ if (cell.path.length > 1) {
2166
+ throw new Error(
2167
+ `Join: Error while applying SetValue: Cannot set value for multiple paths in one cell. Found paths: [${cell.path.join(", ")}] for route: ${setValue.route.toString()}.`
2168
+ );
2169
+ }
2170
+ const cellInsertTree = isolate(
2171
+ { ...col.value.tree },
2172
+ cell.path[0],
2173
+ joinPreserveKeys
2174
+ );
2175
+ inject(cellInsertTree, cell.path[0], setValue.value);
2176
+ const propertyKey = cell.path[0].slice(-1)[0];
2177
+ const insert = {
2178
+ cell: [
2179
+ {
2180
+ ...cell,
2181
+ ...{ value: setValue.value },
2182
+ ...{
2183
+ row: {
2184
+ ...cell.row,
2185
+ ...{ [propertyKey]: setValue.value }
2186
+ }
2187
+ }
2188
+ }
2189
+ ],
2190
+ tree: cellInsertTree,
2191
+ rljson: col.value.rljson
2192
+ };
2193
+ if (insert) {
2194
+ if (insertCol.inserts) insertCol.inserts.push(insert);
2195
+ else insertCol.inserts = [insert];
2196
+ }
2197
+ }
2198
+ }
2199
+ insertCols.push(insertCol);
1596
2200
  }
1597
2201
  data[sliceId] = {
1598
- rowHash: Hash.default.calcHash(cols.map((c) => c.value)),
1599
- columns: cols
2202
+ rowHash: Hash.default.calcHash(
2203
+ insertCols.map(
2204
+ (col) => col.value.cell.flatMap((c) => c.value)
2205
+ )
2206
+ ),
2207
+ columns: insertCols
1600
2208
  };
1601
2209
  }
1602
2210
  const process = {
@@ -1639,15 +2247,15 @@ class Join {
1639
2247
  const data = {};
1640
2248
  for (let i2 = 0; i2 < this.rowCount; i2++) {
1641
2249
  const [sliceId, row] = Object.entries(this.data)[i2];
1642
- const selectedColumns = [];
2250
+ const cols = [];
1643
2251
  for (let j = 0; j < masterColumnIndices.length; j++) {
1644
- selectedColumns.push(row.columns[masterColumnIndices[j]]);
2252
+ cols.push(row.columns[masterColumnIndices[j]]);
1645
2253
  }
1646
2254
  data[sliceId] = {
1647
2255
  rowHash: Hash.default.calcHash(
1648
- selectedColumns.map((c) => c.value)
2256
+ cols.map((col) => col.value.cell.flatMap((c) => c.value))
1649
2257
  ),
1650
- columns: selectedColumns
2258
+ columns: cols
1651
2259
  };
1652
2260
  }
1653
2261
  const process = {
@@ -1686,16 +2294,51 @@ class Join {
1686
2294
  * Returns insert Object of the join
1687
2295
  */
1688
2296
  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;
2297
+ const inserts = [];
2298
+ for (let i = 0; i < this.columnCount; i++) {
2299
+ const colInserts = [];
2300
+ for (const row of Object.values(this.data)) {
2301
+ const col = row.columns[i];
2302
+ if (col.inserts && col.inserts.length > 0) {
2303
+ for (const insert of col.inserts) {
2304
+ for (const cell of insert.cell) {
2305
+ const tree = insert.tree;
2306
+ const path = cell.path;
2307
+ inject(tree, path[0].slice(0, -1), cell.row);
2308
+ colInserts.push({
2309
+ route: col.route,
2310
+ tree,
2311
+ path: path[0]
2312
+ });
2313
+ }
2314
+ }
2315
+ }
2316
+ }
2317
+ if (colInserts.length === 0) continue;
2318
+ const routes = colInserts.map((ins) => ins.route.flat);
2319
+ const uniqueRoute = Array.from(new Set(routes));
2320
+ if (uniqueRoute.length > 1) {
2321
+ throw new Error(
2322
+ `Join: Error while generating insert: Multiple different routes found in inserts: ${uniqueRoute.map((r) => r.toString()).join(", ")}. Cannot generate single insert object.`
2323
+ );
2324
+ }
2325
+ const merged = mergeTrees(
2326
+ colInserts.map((ins) => ({
2327
+ tree: ins.tree,
2328
+ path: ins.path.slice(0, -1)
2329
+ }))
2330
+ );
2331
+ traverse(merged, ({ parent, key, value }) => {
2332
+ if (key == "_data" && Array.isArray(value) && value.length > 0) {
2333
+ parent[key] = value.filter((v) => !!v);
2334
+ }
2335
+ });
2336
+ inserts.push({
2337
+ route: Route.fromFlat(uniqueRoute[0]).toRouteWithProperty(),
2338
+ tree: merged
2339
+ });
2340
+ }
2341
+ return inserts;
1699
2342
  }
1700
2343
  // ...........................................................................
1701
2344
  /**
@@ -1739,7 +2382,12 @@ class Join {
1739
2382
  */
1740
2383
  get layerRoutes() {
1741
2384
  return Array.from(
1742
- new Set(this.componentRoutes.map((r) => r.upper().flat))
2385
+ new Set(
2386
+ Object.values(this.columnSelection.columns).map((c) => [
2387
+ Route.fromFlat(c.route).top,
2388
+ Route.fromFlat(c.route).deeper(1).top
2389
+ ]).map((segments) => new Route(segments).flat)
2390
+ )
1743
2391
  ).map((r) => Route.fromFlat(r));
1744
2392
  }
1745
2393
  // ...........................................................................
@@ -1748,7 +2396,11 @@ class Join {
1748
2396
  */
1749
2397
  get cakeRoute() {
1750
2398
  const cakeRoute = Array.from(
1751
- new Set(this.layerRoutes.map((r) => r.upper().flat))
2399
+ new Set(
2400
+ Object.values(this.columnSelection.columns).map(
2401
+ (c) => Route.fromFlat(c.route).top.tableKey
2402
+ )
2403
+ )
1752
2404
  ).map((r) => Route.fromFlat(r));
1753
2405
  if (cakeRoute.length !== 1) {
1754
2406
  throw new Error(
@@ -1833,7 +2485,11 @@ class Join {
1833
2485
  const dataColRoute = dataCol.route;
1834
2486
  return colInfoRoute.equalsWithoutRefs(dataColRoute);
1835
2487
  });
1836
- row.push(joinCol ? joinCol.insert ?? joinCol.value : null);
2488
+ const insertValue = joinCol && joinCol.inserts ? joinCol.inserts.flatMap(
2489
+ (con) => con.cell.flatMap((c) => c.value)
2490
+ ) ?? null : null;
2491
+ const baseValue = joinCol && joinCol.value.cell ? joinCol.value.cell.flatMap((c) => c.value) ?? null : null;
2492
+ row.push(insertValue ?? baseValue);
1837
2493
  }
1838
2494
  result.push(row);
1839
2495
  }
@@ -1842,133 +2498,6 @@ class Join {
1842
2498
  static empty() {
1843
2499
  return new Join({}, ColumnSelection.empty());
1844
2500
  }
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
2501
  // ...........................................................................
1973
2502
  /**
1974
2503
  * Hashes the given join rows. If insert value is present, it is used for hashing.
@@ -1980,13 +2509,15 @@ class Join {
1980
2509
  const sliceIds = Object.keys(rows);
1981
2510
  const hashedRows = {};
1982
2511
  for (const sliceId of sliceIds) {
1983
- const columns = rows[sliceId];
2512
+ const cols = rows[sliceId];
1984
2513
  const rowHash = Hash.default.calcHash(
1985
- columns.map((col) => col.insert ?? col.value)
2514
+ cols.map(
2515
+ (col) => col.inserts?.flatMap((con) => con.cell.flatMap((c) => c.value)) ?? col.value.cell ? col.value.cell.flatMap((c) => c.value) : []
2516
+ )
1986
2517
  );
1987
2518
  hashedRows[sliceId] = {
1988
2519
  rowHash,
1989
- columns
2520
+ columns: cols
1990
2521
  };
1991
2522
  }
1992
2523
  return hashedRows;
@@ -2056,6 +2587,25 @@ class Notify {
2056
2587
  return this._callbacks.get(route.flat) || [];
2057
2588
  }
2058
2589
  }
2590
+ const makeUniqueArrayByHash = (arr) => {
2591
+ const seen = /* @__PURE__ */ new Map();
2592
+ const result = [];
2593
+ for (const item of arr) {
2594
+ if (!seen.has(item._hash)) {
2595
+ seen.set(item._hash, item);
2596
+ result.push(item);
2597
+ }
2598
+ }
2599
+ return result;
2600
+ };
2601
+ const makeUnique = (rljson) => {
2602
+ traverse(rljson, ({ parent, key, value }) => {
2603
+ if (key == "_data" && Array.isArray(value)) {
2604
+ parent[key] = makeUniqueArrayByHash(value);
2605
+ }
2606
+ });
2607
+ return rljson;
2608
+ };
2059
2609
  class Db {
2060
2610
  /**
2061
2611
  * Constructor
@@ -2080,87 +2630,444 @@ class Db {
2080
2630
  * Get data from a route with optional filtering
2081
2631
  * @param route - The route to get data from
2082
2632
  * @param where - Optional filter to apply to the data
2633
+ * @param filter - Optional filter to apply to child entries in related tables
2634
+ * @param sliceIds - Optional slice IDs to filter the data
2083
2635
  * @returns An array of Rljson objects matching the route and filter
2084
2636
  * @throws {Error} If the route is not valid or if any controller cannot be created
2085
2637
  */
2086
- async get(route, where) {
2638
+ async get(route, where, filter, sliceIds) {
2087
2639
  if (!route.isValid) throw new Error(`Route ${route.flat} is not valid.`);
2088
2640
  const isolatedRoute = await this.isolatePropertyKeyFromRoute(route);
2089
- const cacheHash = `${isolatedRoute.flat}|${JSON.stringify(where)}`;
2641
+ const controllers = await this.indexedControllers(isolatedRoute);
2642
+ const data = await this._get(
2643
+ isolatedRoute,
2644
+ where,
2645
+ controllers,
2646
+ filter,
2647
+ sliceIds
2648
+ );
2649
+ const dataWithControllers = {
2650
+ ...data,
2651
+ ...{ controllers }
2652
+ };
2653
+ return dataWithControllers;
2654
+ }
2655
+ // ...........................................................................
2656
+ /**
2657
+ * Resolves the route and returns corresponding data for any segment of the route,
2658
+ * matching recursive filters and where clauses
2659
+ *
2660
+ * @param route - The route to get data from
2661
+ * @param where - The recursive filtering key/value pairs to apply to the data
2662
+ * @param controllers - The controllers to use for fetching data
2663
+ * @param filter - Optional filter to apply to the data at the current route segment
2664
+ * @param sliceIds - Optional slice IDs to filter the data at the current route segment
2665
+ * @returns - An Rljson object matching the route and filters
2666
+ */
2667
+ async _get(route, where, controllers, filter, sliceIds, routeAccumulator) {
2668
+ const params = {
2669
+ route: route.flat,
2670
+ where,
2671
+ filter,
2672
+ sliceIds,
2673
+ routeAccumulator: routeAccumulator ? routeAccumulator.flat : ""
2674
+ };
2675
+ const cacheHash = hsh(rmhsh(params))._hash;
2090
2676
  const isCached = this._cache.has(cacheHash);
2091
2677
  if (isCached) {
2092
2678
  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
- {}
2679
+ }
2680
+ const nodeTableKey = route.top.tableKey;
2681
+ const nodeRoute = route;
2682
+ const nodeRouteRef = await this._getReferenceOfRouteSegment(nodeRoute.top);
2683
+ const nodeController = controllers[nodeTableKey];
2684
+ const nodeSliceIds = nodeRoute.top.sliceIds ?? sliceIds;
2685
+ let nodeWhere = typeof where === "object" ? { ...where } : where;
2686
+ if (!route.isRoot && typeof nodeWhere === "object") {
2687
+ delete nodeWhere[nodeRoute.deeper().top.tableKey];
2688
+ }
2689
+ nodeWhere = nodeWhere ? typeof nodeWhere === "string" ? { _hash: nodeWhere } : nodeWhere : {};
2690
+ if (nodeRouteRef && nodeRouteRef.length > 0)
2691
+ nodeWhere = { _hash: nodeRouteRef };
2692
+ delete nodeWhere["_through"];
2693
+ delete nodeWhere["_tableKey"];
2694
+ const {
2695
+ [nodeTableKey]: { _data: nodeRows, _type: nodeType, _hash: nodeHash }
2696
+ } = await nodeController.get(nodeWhere);
2697
+ const nodeColumnCfgs = nodeController.tableCfg().columns;
2698
+ const nodeRowsFiltered = [];
2699
+ for (const nodeRow of nodeRows) {
2700
+ const filterActive = filter && filter.length > 0;
2701
+ const sliceIdActive = nodeSliceIds && nodeSliceIds.length > 0;
2702
+ if (!filterActive && !sliceIdActive) {
2703
+ nodeRowsFiltered.push(nodeRow);
2704
+ continue;
2705
+ }
2706
+ let filterResult = false;
2707
+ const filterProperties = [];
2708
+ if (filterActive) {
2709
+ for (const f of filter) {
2710
+ if (f.tableKey !== nodeTableKey) continue;
2711
+ if (nodeRow._hash === f.ref) {
2712
+ filterProperties.push(f);
2713
+ filterResult = true;
2714
+ }
2715
+ }
2716
+ } else {
2717
+ filterResult = true;
2718
+ }
2719
+ let sliceIdResult = false;
2720
+ if (sliceIdActive) {
2721
+ switch (nodeType) {
2722
+ case "cakes":
2723
+ const cake = nodeRow;
2724
+ const cakeSliceIds = await this._resolveSliceIds(
2725
+ cake.sliceIdsTable,
2726
+ cake.sliceIdsRow
2727
+ );
2728
+ const cakeMatchesSliceIds = nodeSliceIds.filter(
2729
+ (sId) => cakeSliceIds.includes(sId)
2730
+ );
2731
+ if (cakeMatchesSliceIds.length > 0) sliceIdResult = true;
2732
+ break;
2733
+ case "layers":
2734
+ const layer = nodeRow;
2735
+ const layerSliceIds = await this._resolveSliceIds(
2736
+ layer.sliceIdsTable,
2737
+ layer.sliceIdsTableRow
2738
+ );
2739
+ const layerMatchesSliceIds = nodeSliceIds.filter(
2740
+ (sId) => layerSliceIds.includes(sId)
2741
+ );
2742
+ if (layerMatchesSliceIds.length > 0) sliceIdResult = true;
2743
+ break;
2744
+ case "components":
2745
+ if (filterProperties.length > 0) {
2746
+ const componentSliceIds = filterProperties.flatMap(
2747
+ (f) => f.sliceIds
2748
+ );
2749
+ const componentMatchesSliceIds = nodeSliceIds.filter(
2750
+ (sId) => componentSliceIds.includes(sId)
2751
+ );
2752
+ if (componentMatchesSliceIds.length > 0) {
2753
+ sliceIdResult = true;
2754
+ }
2755
+ }
2756
+ break;
2757
+ /* v8 ignore next -- @preserve */
2758
+ default:
2759
+ sliceIdResult = true;
2760
+ break;
2761
+ }
2762
+ } else {
2763
+ sliceIdResult = true;
2764
+ }
2765
+ if (filterResult && sliceIdResult) nodeRowsFiltered.push(nodeRow);
2766
+ }
2767
+ const node = {
2768
+ [nodeTableKey]: {
2769
+ _data: nodeRowsFiltered,
2770
+ _type: nodeType,
2771
+ _hash: nodeHash
2772
+ }
2773
+ };
2774
+ const nodeValue = node[nodeTableKey]._data.filter(
2775
+ (v) => v !== void 0 && v !== null
2776
+ );
2777
+ if (route.isRoot) {
2778
+ if (route.hasPropertyKey) {
2779
+ const isolatedNode = this.isolatePropertyFromComponents(
2780
+ node,
2781
+ route.propertyKey
2782
+ );
2783
+ const result3 = {
2784
+ rljson: isolatedNode,
2785
+ tree: { [nodeTableKey]: node[nodeTableKey] },
2786
+ cell: nodeValue.map(
2787
+ (v, idx) => ({
2788
+ value: v[route.propertyKey] ?? null,
2789
+ row: v,
2790
+ route: Route.fromFlat(
2791
+ (routeAccumulator ? routeAccumulator.flat : nodeTableKey) + (nodeHash ? `@${nodeHash}` : "") + `/${route.propertyKey}`
2792
+ ).toRouteWithProperty(),
2793
+ path: [[nodeTableKey, "_data", idx, route.propertyKey]]
2794
+ })
2795
+ )
2796
+ };
2797
+ this._cache.set(cacheHash, result3);
2798
+ return result3;
2799
+ }
2800
+ const result2 = {
2801
+ rljson: node,
2802
+ tree: { [nodeTableKey]: node[nodeTableKey] },
2803
+ cell: nodeValue.map(
2804
+ (v, idx) => ({
2805
+ value: v[route.propertyKey] ?? null,
2806
+ row: v,
2807
+ route: Route.fromFlat(
2808
+ (routeAccumulator ? routeAccumulator.flat : nodeTableKey) + (nodeHash ? `@${nodeHash}` : "")
2809
+ ),
2810
+ path: [[nodeTableKey, "_data", idx]]
2811
+ })
2812
+ )
2813
+ };
2814
+ this._cache.set(cacheHash, result2);
2815
+ return result2;
2816
+ }
2817
+ const childrenRoute = route.deeper();
2818
+ const childrenTableKey = childrenRoute.top.tableKey;
2819
+ const childrenWhere = typeof where === "object" ? where[childrenTableKey] ?? {} : {};
2820
+ const childrenThroughProperty = childrenWhere?._through;
2821
+ const nodeChildrenArray = [];
2822
+ const nodeRowsMatchingChildrenRefs = /* @__PURE__ */ new Map();
2823
+ for (let i = 0; i < nodeRowsFiltered.length; i++) {
2824
+ const nodeRow = nodeRowsFiltered[i];
2825
+ const nodeRowObj = { ...{}, ...nodeRow };
2826
+ const childrenRefs = await nodeController.getChildRefs(
2827
+ nodeRow._hash
2124
2828
  );
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
2829
+ const childrenRefTypes = /* @__PURE__ */ new Map();
2830
+ const childrenRefSliceIds = /* @__PURE__ */ new Set();
2831
+ for (const cr of childrenRefs) {
2832
+ if (!!cr.columnKey) {
2833
+ const childrenRefColumnCfg = nodeColumnCfgs.find(
2834
+ (c) => c.key === cr.columnKey
2835
+ );
2836
+ if (childrenRefColumnCfg) {
2837
+ childrenRefTypes.set(
2838
+ childrenRefColumnCfg.key,
2839
+ childrenRefColumnCfg.ref?.type ?? ""
2840
+ );
2841
+ }
2842
+ if (cr.sliceIds && cr.sliceIds.length > 0) {
2843
+ for (const sId of cr.sliceIds) {
2844
+ childrenRefSliceIds.add(sId);
2845
+ }
2846
+ }
2847
+ }
2848
+ }
2849
+ if (childrenRefTypes.size > 1) {
2850
+ throw new Error(
2851
+ `Db._get: Multiple reference types found for children of table ${nodeTableKey}.`
2132
2852
  );
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
2853
+ }
2854
+ const cakeIsReferenced = childrenRefTypes.size === 1 && [...childrenRefTypes.values()][0] === "cakes";
2855
+ const componentIsReferenced = childrenRefTypes.size === 1 && nodeType === "components" && [...childrenRefTypes.values()][0] === "components";
2856
+ const childrenSliceIds = cakeIsReferenced ? [...childrenRefSliceIds] : componentIsReferenced ? void 0 : nodeSliceIds;
2857
+ const {
2858
+ rljson: rowChildrenRljson,
2859
+ tree: rowChildrenTree,
2860
+ cell: rowChildrenCell
2861
+ } = await this._get(
2862
+ childrenRoute,
2863
+ childrenWhere,
2864
+ controllers,
2865
+ childrenRefs,
2866
+ childrenSliceIds,
2867
+ Route.fromFlat(
2868
+ (routeAccumulator ? routeAccumulator.flat : nodeTableKey) + (nodeHash ? `@${nodeHash}` : "") + "/" + childrenTableKey
2869
+ )
2870
+ );
2871
+ if (cakeIsReferenced) {
2872
+ const refKey = [...childrenRefTypes.keys()][0];
2873
+ nodeRowObj[refKey] = rowChildrenTree;
2874
+ }
2875
+ if (rowChildrenRljson[childrenTableKey]._data.length === 0) continue;
2876
+ nodeChildrenArray.push(rowChildrenRljson);
2877
+ if (childrenThroughProperty) {
2878
+ const resolvedChildrenHashes = rowChildrenRljson[childrenTableKey]._data.map((rc) => rc._hash);
2879
+ for (const nr of nodeRowsFiltered) {
2880
+ {
2881
+ const throughHashesInRowCouldBeArray = nr[childrenThroughProperty];
2882
+ const throughHashesInRow = Array.isArray(
2883
+ throughHashesInRowCouldBeArray
2884
+ ) ? throughHashesInRowCouldBeArray : [throughHashesInRowCouldBeArray];
2885
+ for (const th of throughHashesInRow) {
2886
+ if (resolvedChildrenHashes.includes(th)) {
2887
+ nodeRowObj[childrenThroughProperty] = {
2888
+ ...rowChildrenTree[childrenTableKey],
2889
+ ...{ _tableKey: childrenTableKey }
2890
+ };
2891
+ }
2892
+ }
2893
+ }
2894
+ }
2895
+ }
2896
+ const resolvedChildren = rowChildrenRljson[childrenTableKey]._data;
2897
+ const childrenRefsOfRow = childrenRefs.filter(
2898
+ (cr) => cr.tableKey == childrenTableKey
2899
+ );
2900
+ const matchingChildrenRefs = childrenRefsOfRow.filter(
2901
+ (cr) => !!resolvedChildren.find((ch) => cr.ref === ch._hash)
2902
+ );
2903
+ if (nodeType === "layers") {
2904
+ const compChildrenTrees = rowChildrenTree[childrenTableKey]._data;
2905
+ const compChildrenPaths = rowChildrenCell.map((c) => c.path);
2906
+ const components = compChildrenTrees.map((c, idx) => {
2907
+ return {
2908
+ tree: c,
2909
+ path: compChildrenPaths.filter((p) => p[0][2] == idx)
2910
+ };
2911
+ });
2912
+ const layerTreesAndPaths = components.map(({ tree: comp, path }) => {
2913
+ const sliceIds2 = matchingChildrenRefs.find(
2914
+ (cr) => cr.ref === comp._hash
2915
+ )?.sliceIds;
2916
+ if (!sliceIds2 || sliceIds2.length === 0) {
2917
+ throw new Error(
2918
+ `Db._get: No sliceIds found for component ${comp._hash} of layer ${nodeRow._hash}.`
2143
2919
  );
2144
- if (includesChild) {
2145
- filteredParentRows.set(
2146
- row["_hash"],
2147
- row
2148
- );
2920
+ }
2921
+ if (sliceIds2.length > 1) {
2922
+ throw new Error(
2923
+ `Db._get: Multiple sliceIds found for component ${comp._hash} of layer ${nodeRow._hash}.`
2924
+ );
2925
+ }
2926
+ const sliceId = sliceIds2[0];
2927
+ const pathsForSliceId = [
2928
+ ...path.map((p) => p[0]).map((p) => {
2929
+ const newPath = [...p];
2930
+ newPath[2] = 0;
2931
+ return ["add", sliceId, ...newPath];
2932
+ })
2933
+ ];
2934
+ return {
2935
+ [sliceId]: {
2936
+ tree: {
2937
+ [childrenTableKey]: {
2938
+ ...{ _data: [comp] },
2939
+ ...{ _type: "components" }
2940
+ }
2941
+ },
2942
+ path: pathsForSliceId
2149
2943
  }
2944
+ };
2945
+ });
2946
+ const layer = layerTreesAndPaths.flatMap(
2947
+ (ltap) => Object.entries(ltap).map(([sliceId, { tree }]) => ({
2948
+ [sliceId]: tree
2949
+ }))
2950
+ ).reduce((a, b) => ({ ...a, ...b }), {});
2951
+ const paths = layerTreesAndPaths.map(
2952
+ (ltap) => Object.values(ltap)[0].path
2953
+ );
2954
+ nodeRowsMatchingChildrenRefs.set(nodeRow._hash, {
2955
+ rljson: nodeRow,
2956
+ tree: {
2957
+ ...nodeRowObj,
2958
+ add: { ...nodeRowObj.add, ...layer }
2959
+ },
2960
+ cell: rowChildrenCell.map(
2961
+ (c, idx) => ({
2962
+ ...c,
2963
+ ...{
2964
+ path: [paths.flat()[idx]]
2965
+ }
2966
+ })
2967
+ )
2968
+ });
2969
+ } else if (nodeType === "cakes") {
2970
+ nodeRowsMatchingChildrenRefs.set(nodeRow._hash, {
2971
+ rljson: nodeRow,
2972
+ tree: {
2973
+ ...nodeRowObj,
2974
+ layers: { ...nodeRowObj.layers, ...rowChildrenTree }
2975
+ },
2976
+ cell: rowChildrenCell.map((c) => ({
2977
+ ...c,
2978
+ ...{
2979
+ path: c.path.map((p) => ["layers", ...p])
2980
+ }
2981
+ }))
2982
+ });
2983
+ } else if (nodeType === "components") {
2984
+ if (rowChildrenTree && Object.keys(rowChildrenTree).length > 0) {
2985
+ const columnReferenceMap = nodeColumnCfgs.filter(
2986
+ (c) => c.ref && ["components", "cakes"].includes(c.ref.type)
2987
+ ).reduce((acc, curr) => {
2988
+ acc.set(curr.key, curr.ref.tableKey);
2989
+ return acc;
2990
+ }, /* @__PURE__ */ new Map());
2991
+ const resolvedRefs = {};
2992
+ for (const [colKey, childTableKey] of columnReferenceMap) {
2993
+ const tree = {
2994
+ ...rowChildrenTree[childTableKey],
2995
+ ...{ _tableKey: childTableKey }
2996
+ };
2997
+ const cell = rowChildrenCell.map((c) => ({
2998
+ ...c,
2999
+ ...{
3000
+ path: c.path.filter((p) => p[0] === childTableKey).map((p) => [colKey, ...p.slice(1)])
3001
+ }
3002
+ }));
3003
+ resolvedRefs[colKey] = { tree, cell };
2150
3004
  }
3005
+ const resolvedProperties = Object.entries(resolvedRefs).map(([colKey, { tree }]) => ({
3006
+ [colKey]: tree
3007
+ })).reduce((a, b) => ({ ...a, ...b }), {});
3008
+ const resolvedTree = {
3009
+ ...nodeRowObj,
3010
+ ...resolvedProperties
3011
+ };
3012
+ const resolvedCell = Object.values(resolvedRefs).map((r) => r.cell).flat();
3013
+ nodeRowsMatchingChildrenRefs.set(nodeRow._hash, {
3014
+ rljson: nodeRow,
3015
+ tree: resolvedTree,
3016
+ cell: resolvedCell
3017
+ });
3018
+ } else {
3019
+ nodeRowsMatchingChildrenRefs.set(nodeRow._hash, {
3020
+ rljson: nodeRow,
3021
+ tree: { ...nodeRowObj },
3022
+ cell: rowChildrenCell
3023
+ });
2151
3024
  }
3025
+ } else {
3026
+ throw new Error(
3027
+ `Db._get: Unsupported node type ${nodeType} for getting children.`
3028
+ );
2152
3029
  }
2153
- const parentWithFilteredRows = {
2154
- [segment.tableKey]: {
2155
- ...parent[segment.tableKey],
3030
+ }
3031
+ const nodeChildren = makeUnique(
3032
+ merge(...nodeChildrenArray)
3033
+ );
3034
+ const matchedNodeRows = Array.from(nodeRowsMatchingChildrenRefs.values());
3035
+ const result = {
3036
+ rljson: {
3037
+ ...node,
3038
+ ...{
3039
+ [nodeTableKey]: {
3040
+ ...{
3041
+ _data: matchedNodeRows.map((mr) => mr.rljson),
3042
+ _type: nodeType
3043
+ },
3044
+ ...{
3045
+ ...nodeHash ? { _hash: nodeHash } : {}
3046
+ }
3047
+ }
3048
+ },
3049
+ ...nodeChildren
3050
+ },
3051
+ tree: {
3052
+ [nodeTableKey]: {
3053
+ ...{
3054
+ _data: matchedNodeRows.map((mr) => mr.tree),
3055
+ _type: nodeType
3056
+ },
2156
3057
  ...{
2157
- _data: Array.from(filteredParentRows.values())
3058
+ ...nodeHash ? { _hash: nodeHash } : {}
2158
3059
  }
2159
3060
  }
2160
- };
2161
- return merge(parentWithFilteredRows, ...children);
2162
- }
2163
- return parent;
3061
+ },
3062
+ cell: matchedNodeRows.map(
3063
+ (mr, idx) => mr.cell.map((c) => ({
3064
+ ...c,
3065
+ ...{ path: c.path.map((p) => [nodeTableKey, "_data", idx, ...p]) }
3066
+ }))
3067
+ ).flat()
3068
+ };
3069
+ this._cache.set(cacheHash, result);
3070
+ return result;
2164
3071
  }
2165
3072
  // ...........................................................................
2166
3073
  /**
@@ -2187,115 +3094,66 @@ class Db {
2187
3094
  * @param rljson - The Rljson to join data for
2188
3095
  */
2189
3096
  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) {
3097
+ const {
3098
+ tree: { [cakeKey]: cakesTable }
3099
+ } = await this.get(Route.fromFlat(`${cakeKey}@${cakeRef}`), {});
3100
+ const cakes = cakesTable._data;
3101
+ if (cakes.length === 0) {
2194
3102
  throw new Error(
2195
3103
  `Db.join: Cake with ref "${cakeRef}" not found in cake table "${cakeKey}".`
2196
3104
  );
2197
3105
  }
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]
3106
+ if (cakes.length > 1) {
3107
+ throw new Error(
3108
+ `Db.join: Multiple cakes with ref "${cakeRef}" found in cake table "${cakeKey}".`
2204
3109
  );
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
3110
  }
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
3111
+ const cake = cakes[0];
3112
+ const sliceIds = await this._resolveSliceIds(
3113
+ cake.sliceIdsTable,
3114
+ cake.sliceIdsRow
3115
+ );
3116
+ const rows = {};
3117
+ for (const sliceId of sliceIds) {
3118
+ const row = [];
3119
+ for (const columnInfo of columnSelection.columns) {
3120
+ const columnRoute = Route.fromFlat(
3121
+ columnInfo.route
3122
+ ).toRouteWithProperty();
3123
+ const columnContainer = await this.get(
3124
+ columnRoute,
3125
+ cakeRef,
3126
+ void 0,
3127
+ [sliceId]
2254
3128
  );
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];
3129
+ const column = {
3130
+ route: columnRoute,
3131
+ value: columnContainer,
3132
+ inserts: null
3133
+ };
3134
+ row.push(column);
2268
3135
  }
2269
- rowMap.set(sliceId, sliceIdRow);
3136
+ rows[sliceId] = row;
2270
3137
  }
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);
3138
+ return new Join(rows, columnSelection);
2280
3139
  }
2281
3140
  // ...........................................................................
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);
3141
+ async _resolveSliceIds(sliceIdTable, sliceIdRow) {
3142
+ const sliceIdController = new SliceIdController(this.core, sliceIdTable);
3143
+ sliceIdController.init();
3144
+ const resolvedSliceIds = /* @__PURE__ */ new Set();
3145
+ const {
3146
+ [sliceIdTable]: { _data: sliceIds }
3147
+ } = await sliceIdController.get(sliceIdRow);
3148
+ for (const sliceId of sliceIds) {
3149
+ const baseSliceIds = await sliceIdController.resolveBaseSliceIds(
3150
+ sliceId
3151
+ );
3152
+ for (const sId of baseSliceIds.add) {
3153
+ resolvedSliceIds.add(sId);
3154
+ }
2297
3155
  }
2298
- return data;
3156
+ return Array.from(resolvedSliceIds);
2299
3157
  }
2300
3158
  // ...........................................................................
2301
3159
  /**
@@ -2304,17 +3162,15 @@ class Db {
2304
3162
  * @returns The result of the Insert as an InsertHistoryRow
2305
3163
  * @throws {Error} If the Insert is not valid or if any controller cannot be created
2306
3164
  */
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
- );
3165
+ async insert(route, tree, options) {
3166
+ const controllers = await this.indexedControllers(
3167
+ Route.fromFlat(route.flatWithoutRefs)
3168
+ );
3169
+ const runFns = {};
3170
+ for (const [tableKey, controller] of Object.entries(controllers)) {
3171
+ runFns[tableKey] = controller.insert.bind(controller);
2316
3172
  }
2317
- return this._insert(insert, initialRoute, runs, options);
3173
+ return this._insert(route, tree, runFns, options);
2318
3174
  }
2319
3175
  // ...........................................................................
2320
3176
  /**
@@ -2325,62 +3181,192 @@ ${JSON.stringify(errors, null, 2)}`
2325
3181
  * @returns The result of the Insert
2326
3182
  * @throws {Error} If the route is not valid or if any controller cannot be created
2327
3183
  */
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)
3184
+ async _insert(route, tree, runFns, options) {
3185
+ const results = [];
3186
+ const nodeRoute = route;
3187
+ const nodeSegment = nodeRoute.segment(0);
3188
+ const nodeTableKey = nodeSegment.tableKey;
3189
+ const nodeTree = tree[nodeTableKey];
3190
+ const nodeType = nodeTree._type;
3191
+ if (nodeTree._data.length === 0) {
3192
+ throw new Error(
3193
+ `Db._insert: No data found for table "${nodeTableKey}" in route "${route.flat}".`
3194
+ );
3195
+ }
3196
+ const previousHash = nodeSegment[nodeTableKey + "Ref"] ?? null;
3197
+ const previousTimeId = nodeSegment[nodeTableKey + "InsertHistoryRef"] ?? null;
3198
+ const previous = previousHash ? await this.getTimeIdsForRef(nodeTableKey, previousHash) : previousTimeId ? [previousTimeId] : [];
3199
+ if (!nodeRoute.isRoot) {
3200
+ const childRoute = nodeRoute.deeper(1);
3201
+ const childTableKey = childRoute.top.tableKey;
3202
+ if (nodeType === "cakes") {
3203
+ const cakes = nodeTree._data;
3204
+ if (cakes.length > 1) ;
3205
+ const cake = cakes[0];
3206
+ const childTree = cake.layers[childTableKey];
3207
+ const childResults = await this._insert(
3208
+ childRoute,
3209
+ { [childTableKey]: childTree },
3210
+ runFns
2343
3211
  );
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
- };
3212
+ if (childResults.length > 1) {
3213
+ throw new Error(
3214
+ `Db._insert: Multiple inserts returned for child table "${childTableKey}" when inserting into cake table "${nodeTableKey}". Only single child inserts are supported.`
3215
+ );
3216
+ }
3217
+ const childResult = childResults[0];
3218
+ const insertValue = {
3219
+ ...cake,
3220
+ ...{
3221
+ layers: {
3222
+ ...cake.layers,
3223
+ ...{
3224
+ [childTableKey]: childResult[childTableKey + "Ref"]
3225
+ }
3226
+ }
3227
+ }
3228
+ };
3229
+ const runFn = runFns[nodeTableKey];
3230
+ const result = await runFn("add", rmhsh(insertValue), "db.insert");
3231
+ results.push(
3232
+ ...result.map((r) => ({
3233
+ ...r,
3234
+ ...{ previous },
3235
+ ...{ route: route.flat }
3236
+ }))
3237
+ );
3238
+ }
3239
+ if (nodeType === "layers") {
3240
+ const layers = nodeTree._data;
3241
+ for (const layer of layers) {
3242
+ const layerInsert = {};
3243
+ for (const [sliceId, componentTree] of Object.entries(layer.add)) {
3244
+ if (sliceId === "_hash") continue;
3245
+ const writtenComponents = await this._insert(
3246
+ childRoute,
3247
+ componentTree,
3248
+ runFns
3249
+ );
3250
+ if (writtenComponents.length > 1) {
3251
+ throw new Error(
3252
+ `Db._insert: Multiple components written for layer "${layer._hash}" and sliceId "${sliceId}" is currently not supported.`
3253
+ );
3254
+ }
3255
+ const writtenComponent = writtenComponents[0];
3256
+ if (!writtenComponent || !writtenComponent[childTableKey + "Ref"]) {
3257
+ throw new Error(
3258
+ `Db._insert: No component reference returned for layer "${layer._hash}" and sliceId "${sliceId}".`
3259
+ );
3260
+ }
3261
+ layerInsert[sliceId] = writtenComponent[childTableKey + "Ref"];
3262
+ }
3263
+ const runFn = runFns[nodeTableKey];
3264
+ const result = await runFn(
3265
+ "add",
3266
+ rmhsh({
3267
+ ...layer,
3268
+ ...{ add: layerInsert }
3269
+ }),
3270
+ "db.insert"
3271
+ );
3272
+ results.push(
3273
+ ...result.map((r) => ({
3274
+ ...r,
3275
+ ...{ previous },
3276
+ ...{ route: route.flat }
3277
+ }))
3278
+ );
3279
+ }
3280
+ }
3281
+ if (["components", "edits", "multiEdits"].includes(
3282
+ nodeType
3283
+ )) {
3284
+ const runFn = runFns[nodeTableKey];
3285
+ const components = nodeTree._data;
3286
+ for (const component of components) {
3287
+ const resolvedComponent = { ...component };
3288
+ for (const [property, value] of Object.entries(component)) {
3289
+ if (value.hasOwnProperty("_tableKey") && value._tableKey === childTableKey) {
3290
+ const writtenReferences = await this._insert(
3291
+ childRoute,
3292
+ { [childTableKey]: value },
3293
+ runFns
3294
+ );
3295
+ resolvedComponent[property] = writtenReferences.map(
3296
+ (wr) => wr[childTableKey + "Ref"]
3297
+ );
3298
+ }
3299
+ }
3300
+ const result = await runFn(
3301
+ "add",
3302
+ rmhsh(resolvedComponent),
3303
+ "db.insert"
3304
+ );
3305
+ results.push(
3306
+ ...result.map((r) => ({
3307
+ ...r,
3308
+ ...{ previous },
3309
+ ...{ route: route.flat }
3310
+ }))
3311
+ );
3312
+ }
3313
+ }
2371
3314
  } 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
- };
3315
+ const runFn = runFns[nodeTableKey];
3316
+ if (["components", "edits", "multiEdits"].includes(
3317
+ nodeType
3318
+ )) {
3319
+ const components = rmhsh(
3320
+ tree[nodeTableKey]
3321
+ );
3322
+ for (const component of components._data) {
3323
+ if (!component) continue;
3324
+ delete component._tableKey;
3325
+ delete component._type;
3326
+ const result = await runFn("add", component, "db.insert");
3327
+ results.push(
3328
+ ...result.map((r) => ({
3329
+ ...r,
3330
+ ...{ previous },
3331
+ ...{ route: route.flat }
3332
+ }))
3333
+ );
3334
+ }
3335
+ }
3336
+ if (nodeType === "layers") {
3337
+ const layers = rmhsh(tree[nodeTableKey]);
3338
+ for (const layer of layers._data) {
3339
+ const result = await runFn("add", layer, "db.insert");
3340
+ results.push(
3341
+ ...result.map((r) => ({
3342
+ ...r,
3343
+ ...{ previous },
3344
+ ...{ route: route.flat }
3345
+ }))
3346
+ );
3347
+ }
3348
+ }
3349
+ if (nodeType === "cakes") {
3350
+ const cakes = rmhsh(tree[nodeTableKey]);
3351
+ for (const cake of cakes._data) {
3352
+ const result = await runFn("add", cake, "db.insert");
3353
+ results.push(
3354
+ ...result.map((r) => ({
3355
+ ...r,
3356
+ ...{ previous },
3357
+ ...{ route: route.flat }
3358
+ }))
3359
+ );
3360
+ }
3361
+ }
2378
3362
  }
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;
3363
+ for (const result of results) {
3364
+ if (!options?.skipHistory)
3365
+ await this._writeInsertHistory(nodeTableKey, result);
3366
+ if (!options?.skipNotification)
3367
+ this.notify.notify(Route.fromFlat(result.route), result);
3368
+ }
3369
+ return results;
2384
3370
  }
2385
3371
  // ...........................................................................
2386
3372
  /**
@@ -2401,40 +3387,6 @@ ${JSON.stringify(errors, null, 2)}`
2401
3387
  this.notify.unregister(route, callback);
2402
3388
  }
2403
3389
  // ...........................................................................
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
3390
  /**
2439
3391
  * Get a controller for a specific table
2440
3392
  * @param tableKey - The key of the table to get the controller for
@@ -2451,7 +3403,7 @@ ${JSON.stringify(errors, null, 2)}`
2451
3403
  return createController(contentType, this.core, tableKey, refs);
2452
3404
  }
2453
3405
  // ...........................................................................
2454
- async _indexedControllers(route) {
3406
+ async indexedControllers(route) {
2455
3407
  const controllers = {};
2456
3408
  const isolatedRoute = await this.isolatePropertyKeyFromRoute(route);
2457
3409
  for (let i = 0; i < isolatedRoute.segments.length; i++) {
@@ -2500,15 +3452,9 @@ ${JSON.stringify(errors, null, 2)}`
2500
3452
  if (options.sorted) {
2501
3453
  const dumpedTable = await this.core.dumpTable(insertHistoryTable);
2502
3454
  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
- });
3455
+ tableData.sort(
3456
+ (a, b) => options.ascending ? a.timeId.localeCompare(b.timeId) : b.timeId.localeCompare(a.timeId)
3457
+ );
2512
3458
  return {
2513
3459
  [insertHistoryTable]: { _data: tableData, _type: "insertHistory" }
2514
3460
  };
@@ -2591,15 +3537,20 @@ ${JSON.stringify(errors, null, 2)}`
2591
3537
  * @returns A route with extracted property key
2592
3538
  */
2593
3539
  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;
3540
+ const segmentLength = route.segments.length;
3541
+ let propertyKey = "";
3542
+ let result = route;
3543
+ for (let i = segmentLength; i > 0; i--) {
3544
+ const segment = route.segments[i - 1];
3545
+ const tableKey = segment.tableKey;
3546
+ const tableExists = await this._io.tableExists(tableKey);
3547
+ if (!tableExists) {
3548
+ propertyKey = propertyKey.length > 0 ? segment.tableKey + "/" + propertyKey : segment.tableKey;
3549
+ result = result.upper();
3550
+ result.propertyKey = propertyKey;
3551
+ }
3552
+ }
3553
+ return result;
2603
3554
  }
2604
3555
  // ...........................................................................
2605
3556
  /**