@rljson/db 0.0.7 → 0.0.9

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,14 +1,16 @@
1
- import { equals, merge } from "@rljson/json";
2
- import { timeId, createInsertHistoryTableCfg, Validate, BaseValidator, Route, validateInsert, isTimeId } from "@rljson/rljson";
3
- import { traverse } from "object-traversal";
4
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";
5
4
  import { IoMem } from "@rljson/io";
5
+ import { traverse } from "object-traversal";
6
6
  import { compileExpression } from "filtrex";
7
7
  class BaseController {
8
8
  constructor(_core, _tableKey) {
9
9
  this._core = _core;
10
10
  this._tableKey = _tableKey;
11
11
  }
12
+ _contentType;
13
+ _tableCfg;
12
14
  // ...........................................................................
13
15
  /**
14
16
  * Retrieves the current state of the table.
@@ -67,6 +69,28 @@ class BaseController {
67
69
  });
68
70
  return rows;
69
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
+ }
70
94
  }
71
95
  class CakeController extends BaseController {
72
96
  constructor(_core, _tableKey, _refs) {
@@ -74,6 +98,7 @@ class CakeController extends BaseController {
74
98
  this._core = _core;
75
99
  this._tableKey = _tableKey;
76
100
  this._refs = _refs;
101
+ this._contentType = "cakes";
77
102
  }
78
103
  _table = null;
79
104
  _baseLayers = {};
@@ -88,7 +113,8 @@ class CakeController extends BaseController {
88
113
  if (this._table._type !== "cakes") {
89
114
  throw new Error(`Table ${this._tableKey} is not of type cakes.`);
90
115
  }
91
- if (this._refs && this._refs.base && this._refs.base.length > 0) {
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) {
92
118
  const {
93
119
  [this._tableKey]: { _data: baseCakes }
94
120
  } = await this._core.readRow(this._tableKey, this._refs.base);
@@ -99,11 +125,13 @@ class CakeController extends BaseController {
99
125
  this._baseLayers = rmhsh(baseCake.layers);
100
126
  } else {
101
127
  const cake = this._table._data[0];
102
- this._refs = {
103
- sliceIdsTable: cake.sliceIdsTable,
104
- sliceIdsRow: cake.sliceIdsRow
105
- };
106
- this._baseLayers = rmhsh(cake.layers);
128
+ if (!!cake) {
129
+ this._refs = {
130
+ sliceIdsTable: cake.sliceIdsTable,
131
+ sliceIdsRow: cake.sliceIdsRow
132
+ };
133
+ this._baseLayers = rmhsh(cake.layers);
134
+ }
107
135
  }
108
136
  }
109
137
  async getChildRefs(where, filter) {
@@ -131,7 +159,7 @@ class CakeController extends BaseController {
131
159
  if (this._refs?.base) delete this._refs.base;
132
160
  const normalizedValue = {};
133
161
  for (const [layerTable, layerRef] of Object.entries(
134
- value
162
+ value.layers
135
163
  )) {
136
164
  if (Array.isArray(layerRef) && layerRef.length > 1) {
137
165
  throw new Error(
@@ -141,6 +169,7 @@ class CakeController extends BaseController {
141
169
  normalizedValue[layerTable] = Array.isArray(layerRef) ? layerRef[0] : layerRef;
142
170
  }
143
171
  const cake = {
172
+ ...value,
144
173
  layers: { ...this._baseLayers, ...normalizedValue },
145
174
  ...refs || this._refs
146
175
  };
@@ -166,7 +195,7 @@ class CakeController extends BaseController {
166
195
  return Promise.resolve({});
167
196
  }
168
197
  }
169
- filterRow(row, key, value) {
198
+ async filterRow(row, key, value) {
170
199
  const cake = row;
171
200
  for (const [layerKey, layerRef] of Object.entries(cake.layers)) {
172
201
  if (layerKey === key && layerRef === value) {
@@ -182,8 +211,15 @@ class ComponentController extends BaseController {
182
211
  this._core = _core;
183
212
  this._tableKey = _tableKey;
184
213
  this._refs = _refs;
185
- }
186
- _tableCfg = null;
214
+ this._contentType = "components";
215
+ }
216
+ _allowedContentTypes = [
217
+ "components",
218
+ "edits",
219
+ "editHistory",
220
+ "multiEdits",
221
+ "head"
222
+ ];
187
223
  _resolvedColumns = null;
188
224
  _refTableKeyToColumnKeyMap = null;
189
225
  async init() {
@@ -192,7 +228,7 @@ class ComponentController extends BaseController {
192
228
  }
193
229
  const rljson = await this._core.dumpTable(this._tableKey);
194
230
  const table = rljson[this._tableKey];
195
- if (table._type !== "components") {
231
+ if (this._allowedContentTypes.indexOf(table._type) === -1) {
196
232
  throw new Error(`Table ${this._tableKey} is not of type components.`);
197
233
  }
198
234
  this._tableCfg = await this._core.tableCfg(this._tableKey);
@@ -210,38 +246,12 @@ class ComponentController extends BaseController {
210
246
  if (!!refs) {
211
247
  throw new Error(`Refs are not supported on ComponentController.`);
212
248
  }
213
- const values = [];
214
- const referencedValues = /* @__PURE__ */ new Map();
215
- for (const [k, v] of Object.entries(value)) {
216
- if (Array.isArray(v)) {
217
- for (const possibleRef of v) {
218
- if (typeof possibleRef === "object" && possibleRef.hasOwnProperty("_ref") && possibleRef.hasOwnProperty("_value")) {
219
- const ref = possibleRef._ref;
220
- const val = possibleRef._value;
221
- if (!referencedValues.has(ref))
222
- referencedValues.set(ref, { [k]: val });
223
- else {
224
- const existing = referencedValues.get(ref);
225
- referencedValues.set(ref, { ...{ [k]: val }, ...existing });
226
- }
227
- }
228
- }
229
- }
230
- }
231
- if (referencedValues.size > 0) {
232
- for (const refValue of referencedValues.values()) {
233
- values.push({ ...value, ...refValue });
234
- }
235
- } else {
236
- values.push(value);
237
- }
238
- const components = values;
239
- const results = [];
240
- for (const component of components) {
241
- delete component._somethingToInsert;
242
- const rlJson = { [this._tableKey]: { _data: [component] } };
243
- await this._core.import(rlJson);
244
- const result = {
249
+ const component = value;
250
+ delete component._somethingToInsert;
251
+ const rlJson = { [this._tableKey]: { _data: [component] } };
252
+ await this._core.import(rlJson);
253
+ return [
254
+ {
245
255
  //Ref to component
246
256
  [this._tableKey + "Ref"]: hsh(component)._hash,
247
257
  //Data from edit
@@ -249,10 +259,8 @@ class ComponentController extends BaseController {
249
259
  origin,
250
260
  //Unique id/timestamp
251
261
  timeId: timeId()
252
- };
253
- results.push(result);
254
- }
255
- return results;
262
+ }
263
+ ];
256
264
  }
257
265
  // ...........................................................................
258
266
  /**
@@ -287,6 +295,20 @@ class ComponentController extends BaseController {
287
295
  columnKey: propertyKey,
288
296
  ref: refItem
289
297
  });
298
+ continue;
299
+ }
300
+ if (typeof refItem === "object" && refItem !== null) {
301
+ const cakeReference = refItem;
302
+ childRefs.set(
303
+ `${childRefTableKey}|${propertyKey}|${cakeReference.ref}|${cakeReference.sliceIds?.join(",")}`,
304
+ {
305
+ tableKey: childRefTableKey,
306
+ columnKey: propertyKey,
307
+ ref: cakeReference.ref,
308
+ sliceIds: cakeReference.sliceIds
309
+ }
310
+ );
311
+ continue;
290
312
  }
291
313
  }
292
314
  continue;
@@ -319,7 +341,7 @@ class ComponentController extends BaseController {
319
341
  const column = Object.keys(refWhere)[0];
320
342
  const refValue = refWhere[column];
321
343
  for (const row of tableData) {
322
- if (this.filterRow(row, column, refValue)) {
344
+ if (await this.filterRow(row, column, refValue)) {
323
345
  consolidatedRows.set(row._hash, row);
324
346
  }
325
347
  }
@@ -518,7 +540,7 @@ class ComponentController extends BaseController {
518
540
  return this._core.readRows(table, where);
519
541
  }
520
542
  }
521
- filterRow(row, key, value) {
543
+ async filterRow(row, key, value) {
522
544
  for (const [propertyKey, propertyValue] of Object.entries(row)) {
523
545
  if (propertyKey === key && equals(propertyValue, value)) {
524
546
  return true;
@@ -533,12 +555,131 @@ class ComponentController extends BaseController {
533
555
  return false;
534
556
  }
535
557
  }
558
+ class SliceIdController extends BaseController {
559
+ constructor(_core, _tableKey, _refs) {
560
+ super(_core, _tableKey);
561
+ this._core = _core;
562
+ this._tableKey = _tableKey;
563
+ this._refs = _refs;
564
+ this._contentType = "sliceIds";
565
+ }
566
+ async init() {
567
+ if (this._tableKey.endsWith("SliceId") === false) {
568
+ throw new Error(
569
+ `Table ${this._tableKey} is not supported by SliceIdController.`
570
+ );
571
+ }
572
+ const rljson = await this._core.dumpTable(this._tableKey);
573
+ const table = rljson[this._tableKey];
574
+ if (table._type !== "sliceIds") {
575
+ throw new Error(`Table ${this._tableKey} is not of type sliceIds.`);
576
+ }
577
+ this._tableCfg = await this._core.tableCfg(this._tableKey);
578
+ if (this._refs && this._refs.base) {
579
+ const {
580
+ [this._tableKey]: { _data: SliceIds2 }
581
+ } = await this._core.readRow(this._tableKey, this._refs.base);
582
+ if (SliceIds2.length === 0) {
583
+ throw new Error(`Base sliceId ${this._refs.base} does not exist.`);
584
+ }
585
+ } else {
586
+ const sliceId = table._data[0];
587
+ if (!!sliceId) {
588
+ this._refs = {
589
+ base: sliceId.base
590
+ };
591
+ }
592
+ }
593
+ }
594
+ async insert(command, value, origin, refs) {
595
+ if (!command.startsWith("add") && !command.startsWith("remove")) {
596
+ throw new Error(
597
+ `Command ${command} is not supported by SliceIdController.`
598
+ );
599
+ }
600
+ const sliceIds = command.startsWith("add") === true ? {
601
+ add: value,
602
+ ...refs || this._refs
603
+ } : {
604
+ add: [],
605
+ remove: value,
606
+ ...refs || this._refs
607
+ };
608
+ const rlJson = { [this._tableKey]: { _data: [sliceIds] } };
609
+ await this._core.import(rlJson);
610
+ const result = {
611
+ //Ref to component
612
+ [this._tableKey + "Ref"]: hsh(sliceIds)._hash,
613
+ //Data from edit
614
+ route: "",
615
+ origin,
616
+ //Unique id/timestamp
617
+ timeId: timeId()
618
+ };
619
+ return [result];
620
+ }
621
+ async get(where, filter) {
622
+ if (typeof where === "string") {
623
+ return this._getByHash(where, filter);
624
+ } else {
625
+ return this._getByWhere(where, filter);
626
+ }
627
+ }
628
+ async resolveBaseSliceIds(sliceIds) {
629
+ const add = /* @__PURE__ */ new Set();
630
+ const remove = /* @__PURE__ */ new Set();
631
+ if (!!sliceIds.base) {
632
+ const baseSliceIds = await this.get(sliceIds.base);
633
+ if (!baseSliceIds[this._tableKey]?._data?.[0]) {
634
+ throw new Error(`Base sliceIds ${sliceIds.base} does not exist.`);
635
+ }
636
+ if (baseSliceIds[this._tableKey]._data.length > 1) {
637
+ throw new Error(
638
+ `Base sliceIds ${sliceIds.base} has more than one entry.`
639
+ );
640
+ }
641
+ const baseSliceId = baseSliceIds[this._tableKey]._data[0];
642
+ const resolvedBaseSliceIds = await this.resolveBaseSliceIds(baseSliceId);
643
+ for (const sliceId of resolvedBaseSliceIds.add) {
644
+ add.add(sliceId);
645
+ }
646
+ }
647
+ for (const sliceId of sliceIds.add) {
648
+ add.add(sliceId);
649
+ }
650
+ if (!!sliceIds.remove)
651
+ for (const sliceId of sliceIds.remove) {
652
+ remove.add(sliceId);
653
+ }
654
+ for (const sliceId of remove.values()) {
655
+ if (add.has(sliceId)) {
656
+ add.delete(sliceId);
657
+ }
658
+ }
659
+ return { add: Array.from(add) };
660
+ }
661
+ /* v8 ignore next -- @preserve */
662
+ async getChildRefs() {
663
+ return [];
664
+ }
665
+ async filterRow(row, _, value) {
666
+ const sliceIds = row;
667
+ const sliceId = value;
668
+ for (const sId of Object.values(sliceIds.add)) {
669
+ if (sliceId === sId) {
670
+ return true;
671
+ }
672
+ }
673
+ return false;
674
+ }
675
+ }
536
676
  class LayerController extends BaseController {
537
677
  constructor(_core, _tableKey, _refs) {
538
678
  super(_core, _tableKey);
539
679
  this._core = _core;
540
680
  this._tableKey = _tableKey;
541
681
  this._refs = _refs;
682
+ this._contentType = "layers";
542
683
  }
543
684
  async init() {
544
685
  if (this._tableKey.endsWith("Layer") === false) {
@@ -551,6 +692,7 @@ class LayerController extends BaseController {
551
692
  if (table._type !== "layers") {
552
693
  throw new Error(`Table ${this._tableKey} is not of type layers.`);
553
694
  }
695
+ this._tableCfg = await this._core.tableCfg(this._tableKey);
554
696
  if (this._refs && this._refs.base) {
555
697
  const {
556
698
  [this._tableKey]: { _data: baseLayers }
@@ -568,11 +710,13 @@ class LayerController extends BaseController {
568
710
  }
569
711
  } else {
570
712
  const layer = table._data[0];
571
- this._refs = {
572
- sliceIdsTable: layer.sliceIdsTable,
573
- sliceIdsTableRow: layer.sliceIdsTableRow,
574
- componentsTable: layer.componentsTable
575
- };
713
+ if (!!layer) {
714
+ this._refs = {
715
+ sliceIdsTable: layer.sliceIdsTable,
716
+ sliceIdsTableRow: layer.sliceIdsTableRow,
717
+ componentsTable: layer.componentsTable
718
+ };
719
+ }
576
720
  }
577
721
  }
578
722
  async insert(command, value, origin, refs) {
@@ -581,10 +725,9 @@ class LayerController extends BaseController {
581
725
  `Command ${command} is not supported by LayerController.`
582
726
  );
583
727
  }
728
+ const isAdd = command.startsWith("add");
584
729
  const normalizedValue = {};
585
- for (const [sliceId, compRef] of Object.entries(
586
- value
587
- )) {
730
+ for (const [sliceId, compRef] of isAdd ? Object.entries(value.add) : Object.entries(value.remove)) {
588
731
  if (Array.isArray(compRef) && compRef.length > 1) {
589
732
  throw new Error(
590
733
  `LayerController insert: Component ref for slice ${sliceId} cannot be an array of size > 1. No 1:n relations supported.`
@@ -592,13 +735,20 @@ class LayerController extends BaseController {
592
735
  }
593
736
  normalizedValue[sliceId] = Array.isArray(compRef) ? compRef[0] : compRef;
594
737
  }
595
- const layer = command.startsWith("add") === true ? {
596
- add: normalizedValue,
597
- ...refs || this._refs
738
+ const layer = isAdd ? {
739
+ ...value,
740
+ ...{
741
+ add: normalizedValue,
742
+ remove: {}
743
+ },
744
+ ...refs
598
745
  } : {
599
- add: {},
600
- remove: normalizedValue,
601
- ...refs || this._refs
746
+ ...value,
747
+ ...{
748
+ remove: normalizedValue,
749
+ add: {}
750
+ },
751
+ ...refs
602
752
  };
603
753
  const rlJson = { [this._tableKey]: { _data: [layer] } };
604
754
  await this._core.import(rlJson);
@@ -620,25 +770,108 @@ class LayerController extends BaseController {
620
770
  return this._getByWhere(where, filter);
621
771
  }
622
772
  }
773
+ async resolveBaseLayer(layer) {
774
+ const add = /* @__PURE__ */ new Map();
775
+ const sliceIds = /* @__PURE__ */ new Set();
776
+ if (!!layer.base) {
777
+ const baseLayer = await this.get(layer.base);
778
+ if (!baseLayer[this._tableKey]?._data?.[0]) {
779
+ throw new Error(`Base layer ${layer.base} does not exist.`);
780
+ }
781
+ if (baseLayer[this._tableKey]._data.length > 1) {
782
+ throw new Error(
783
+ `Base layer ${layer.base} resolving not possible. Not unique.`
784
+ );
785
+ }
786
+ const baseLayerData = rmhsh(baseLayer[this._tableKey]._data[0]);
787
+ const baseLayerResolved = await this.resolveBaseLayer(baseLayerData);
788
+ for (const [sliceId, compRef] of Object.entries(baseLayerResolved.add)) {
789
+ if (sliceId.startsWith("_")) continue;
790
+ add.set(sliceId, compRef);
791
+ }
792
+ for (const sliceId of baseLayerResolved.sliceIds) {
793
+ sliceIds.add(sliceId);
794
+ }
795
+ const baseLayerSliceIdsTable = baseLayerData.sliceIdsTable;
796
+ const baseLayerSliceIdsRow = baseLayerData.sliceIdsTableRow;
797
+ const {
798
+ [baseLayerSliceIdsTable]: { _data: baseLayerSliceIds }
799
+ } = await this._core.readRow(
800
+ baseLayerSliceIdsTable,
801
+ baseLayerSliceIdsRow
802
+ );
803
+ for (const sIds of baseLayerSliceIds) {
804
+ const sliceIdController = new SliceIdController(
805
+ this._core,
806
+ baseLayerSliceIdsTable
807
+ );
808
+ const resolvedSliceIds = await sliceIdController.resolveBaseSliceIds(
809
+ sIds
810
+ );
811
+ for (const sId of resolvedSliceIds.add) {
812
+ if (sId.startsWith("_")) continue;
813
+ sliceIds.add(sId);
814
+ }
815
+ }
816
+ }
817
+ const {
818
+ [layer.sliceIdsTable]: { _data: layerSliceIds }
819
+ } = await this._core.readRow(layer.sliceIdsTable, layer.sliceIdsTableRow);
820
+ if (!layerSliceIds || layerSliceIds.length === 0) {
821
+ throw new Error(
822
+ `Layer sliceIds ${layer.sliceIdsTableRow} does not exist.`
823
+ );
824
+ }
825
+ if (layerSliceIds.length > 1) {
826
+ throw new Error(
827
+ `Layer sliceIds ${layer.sliceIdsTableRow} has more than one entry.`
828
+ );
829
+ }
830
+ const layerSliceId = layerSliceIds[0];
831
+ for (const sId of layerSliceId.add) {
832
+ if (sId.startsWith("_")) continue;
833
+ sliceIds.add(sId);
834
+ }
835
+ if (!!layerSliceId.remove)
836
+ for (const sId of Object.keys(layerSliceId.remove)) {
837
+ if (sliceIds.has(sId)) {
838
+ sliceIds.delete(sId);
839
+ }
840
+ }
841
+ for (const [sliceId, compRef] of Object.entries(layer.add)) {
842
+ if (sliceId.startsWith("_")) continue;
843
+ add.set(sliceId, compRef);
844
+ }
845
+ if (!!layer.remove)
846
+ for (const sliceId of Object.keys(layer.remove)) {
847
+ if (add.has(sliceId)) {
848
+ add.delete(sliceId);
849
+ }
850
+ }
851
+ return { add: Object.fromEntries(add), sliceIds: Array.from(sliceIds) };
852
+ }
623
853
  async getChildRefs(where, filter) {
624
854
  const { [this._tableKey]: table } = await this.get(where, filter);
625
855
  const childRefs = [];
626
856
  for (const row of table._data) {
627
857
  const layer = row;
628
- for (const [sliceId, compRef] of Object.entries(layer.add)) {
858
+ const resolvedLayer = await this.resolveBaseLayer(layer);
859
+ for (const [sliceId, ref] of Object.entries(resolvedLayer.add)) {
629
860
  if (sliceId.startsWith("_")) continue;
630
861
  childRefs.push({
631
862
  tableKey: layer.componentsTable,
632
- ref: compRef
863
+ ref,
864
+ sliceIds: [sliceId]
633
865
  });
634
866
  }
635
867
  }
636
868
  return childRefs;
637
869
  }
638
- filterRow(row, _, value) {
870
+ async filterRow(row, _, value) {
639
871
  const layer = row;
640
872
  const compRef = value;
641
- for (const componentRef of Object.values(layer.add)) {
873
+ const resolvedLayer = await this.resolveBaseLayer(layer);
874
+ for (const componentRef of Object.values(resolvedLayer.add)) {
642
875
  if (componentRef === compRef) {
643
876
  return true;
644
877
  }
@@ -653,11 +886,22 @@ const createController = async (type, core, tableKey, refs) => {
653
886
  ctrl = new LayerController(core, tableKey, refs);
654
887
  break;
655
888
  case "components":
889
+ case "edits":
890
+ case "editHistory":
891
+ case "multiEdits":
892
+ case "head":
656
893
  ctrl = new ComponentController(core, tableKey, refs);
657
894
  break;
658
895
  case "cakes":
659
896
  ctrl = new CakeController(core, tableKey, refs);
660
897
  break;
898
+ case "sliceIds":
899
+ ctrl = new SliceIdController(
900
+ core,
901
+ tableKey,
902
+ refs
903
+ );
904
+ break;
661
905
  default:
662
906
  throw new Error(`Controller for type ${type} is not implemented yet.`);
663
907
  }
@@ -721,7 +965,7 @@ class Core {
721
965
  const validate = new Validate();
722
966
  validate.addValidator(new BaseValidator());
723
967
  const result = await validate.run(data);
724
- if ((result.hasErrors || result.base && result.base.hasErrors) && !result.base.refsNotFound) {
968
+ if ((result.hasErrors || result.base && result.base.hasErrors) && !result.base.refsNotFound && !result.base.layerBasesNotFound) {
725
969
  throw new Error(
726
970
  "The imported rljson data is not valid:\n" + JSON.stringify(result, null, 2)
727
971
  );
@@ -767,6 +1011,152 @@ class Core {
767
1011
  return this._io.readRows({ table, where });
768
1012
  }
769
1013
  }
1014
+ const inject = (tree, path, value) => {
1015
+ for (let i = 0; i < path.length; i++) {
1016
+ const segment = path[i];
1017
+ if (i === path.length - 1) {
1018
+ tree[segment] = value;
1019
+ delete tree["_hash"];
1020
+ } else {
1021
+ if (!tree[segment]) {
1022
+ tree[segment] = {};
1023
+ }
1024
+ tree = tree[segment];
1025
+ }
1026
+ }
1027
+ };
1028
+ const isolate = (tree, path, preservedKeys = []) => {
1029
+ if (path.length === 0) {
1030
+ return Array.isArray(tree) ? [] : {};
1031
+ }
1032
+ if (tree == null) {
1033
+ return null;
1034
+ }
1035
+ const [currentKey, ...remainingPath] = path;
1036
+ const result = Array.isArray(tree) ? [] : {};
1037
+ if (!Array.isArray(tree)) {
1038
+ for (const key in tree) {
1039
+ if (typeof key === "string" && key.startsWith("_") || preservedKeys.includes(key)) {
1040
+ result[key] = tree[key];
1041
+ }
1042
+ }
1043
+ }
1044
+ if (!(currentKey in tree)) {
1045
+ return result;
1046
+ }
1047
+ const currentValue = tree[currentKey];
1048
+ if (remainingPath.length === 0) {
1049
+ if (Array.isArray(result)) {
1050
+ result[currentKey] = currentValue;
1051
+ } else {
1052
+ result[currentKey] = currentValue;
1053
+ }
1054
+ } else {
1055
+ const isolatedChild = isolate(currentValue, remainingPath, preservedKeys);
1056
+ const hasContent = Array.isArray(isolatedChild) ? isolatedChild.length > 0 : Object.keys(isolatedChild).length > 0;
1057
+ if (hasContent || isolatedChild === null) {
1058
+ if (Array.isArray(result)) {
1059
+ result[currentKey] = isolatedChild;
1060
+ } else {
1061
+ result[currentKey] = isolatedChild;
1062
+ }
1063
+ }
1064
+ }
1065
+ return result;
1066
+ };
1067
+ const mergeTrees = (trees) => {
1068
+ if (!trees || trees.length === 0) {
1069
+ return {};
1070
+ }
1071
+ let result = {};
1072
+ for (const { tree } of trees) {
1073
+ if (tree != null) {
1074
+ result = mergeStructures(result, tree);
1075
+ }
1076
+ }
1077
+ const pathValues = [];
1078
+ for (const { tree, path } of trees) {
1079
+ if (tree == null) continue;
1080
+ let current = tree;
1081
+ let pathExists = true;
1082
+ for (const key of path) {
1083
+ if (current == null || !(key in current)) {
1084
+ pathExists = false;
1085
+ break;
1086
+ }
1087
+ current = current[key];
1088
+ }
1089
+ if (pathExists && current != null) {
1090
+ pathValues.push({ path, value: current });
1091
+ }
1092
+ }
1093
+ const pathGroups = /* @__PURE__ */ new Map();
1094
+ for (const { path, value } of pathValues) {
1095
+ const pathKey = JSON.stringify(path);
1096
+ if (!pathGroups.has(pathKey)) {
1097
+ pathGroups.set(pathKey, []);
1098
+ }
1099
+ pathGroups.get(pathKey).push(value);
1100
+ }
1101
+ for (const [pathKey, values] of pathGroups) {
1102
+ const path = JSON.parse(pathKey);
1103
+ let mergedValue = void 0;
1104
+ for (const value of values) {
1105
+ if (value == null) continue;
1106
+ if (mergedValue === void 0) {
1107
+ mergedValue = value;
1108
+ continue;
1109
+ }
1110
+ if (Array.isArray(mergedValue) && Array.isArray(value)) {
1111
+ mergedValue = [...mergedValue, ...value];
1112
+ } else if (!Array.isArray(mergedValue) && !Array.isArray(value)) {
1113
+ mergedValue = {
1114
+ ...mergedValue,
1115
+ ...value
1116
+ };
1117
+ }
1118
+ }
1119
+ let current = result;
1120
+ for (let i = 0; i < path.length - 1; i++) {
1121
+ const key = path[i];
1122
+ if (current == null || !(key in current)) {
1123
+ current[key] = typeof path[i + 1] === "number" ? [] : {};
1124
+ }
1125
+ current = current[key];
1126
+ }
1127
+ if (path.length > 0) {
1128
+ current[path[path.length - 1]] = mergedValue;
1129
+ }
1130
+ }
1131
+ return result;
1132
+ };
1133
+ function mergeStructures(target, source) {
1134
+ if (source == null) return target;
1135
+ if (target == null) return source;
1136
+ if (Array.isArray(target) && Array.isArray(source)) {
1137
+ const result = [...target];
1138
+ for (let i = 0; i < source.length; i++) {
1139
+ if (result[i] === void 0) {
1140
+ result[i] = source[i];
1141
+ } else {
1142
+ result[i] = mergeStructures(result[i], source[i]);
1143
+ }
1144
+ }
1145
+ return result;
1146
+ }
1147
+ if (typeof target === "object" && typeof source === "object" && !Array.isArray(target) && !Array.isArray(source) && target !== null && source !== null) {
1148
+ const result = { ...target };
1149
+ for (const key in source) {
1150
+ if (key in result) {
1151
+ result[key] = mergeStructures(result[key], source[key]);
1152
+ } else {
1153
+ result[key] = source[key];
1154
+ }
1155
+ }
1156
+ return result;
1157
+ }
1158
+ return source;
1159
+ }
770
1160
  class ColumnSelection {
771
1161
  constructor(columns) {
772
1162
  this._throwOnWrongAlias(columns);
@@ -1113,6 +1503,19 @@ class ColumnSelection {
1113
1503
  }
1114
1504
  ]);
1115
1505
  }
1506
+ static exampleCarsDeeplyNestedColumnSelection() {
1507
+ return new ColumnSelection([
1508
+ {
1509
+ key: "brand",
1510
+ route: "catalogCake/catalogSeriesLayer/catalogSeries/seriesCake/seriesCarsLayer/seriesCars/carCake/carGeneralLayer/carGeneral/brand",
1511
+ alias: "brand",
1512
+ titleLong: "Car Brand",
1513
+ titleShort: "Brand",
1514
+ type: "string",
1515
+ _hash: ""
1516
+ }
1517
+ ]);
1518
+ }
1116
1519
  static exampleCarsColumnSelectionOnlySomeColumns() {
1117
1520
  return new ColumnSelection([
1118
1521
  {
@@ -1638,23 +2041,8 @@ class RowFilterProcessor {
1638
2041
  return remainingIndices;
1639
2042
  }
1640
2043
  for (const i of remainingIndices) {
1641
- const cellValue = join.value(i, columnIndex);
1642
- if (Array.isArray(cellValue)) {
1643
- for (const v of cellValue) {
1644
- if (typeof v === "object" && v !== null && v.hasOwnProperty("_value")) {
1645
- const matchValue = v._value;
1646
- if (filter.matches(matchValue)) {
1647
- result.push(i);
1648
- break;
1649
- }
1650
- } else {
1651
- if (filter.matches(v)) {
1652
- result.push(i);
1653
- break;
1654
- }
1655
- }
1656
- }
1657
- } else {
2044
+ const cellValues = join.value(i, columnIndex);
2045
+ for (const cellValue of cellValues) {
1658
2046
  if (filter.matches(cellValue)) {
1659
2047
  result.push(i);
1660
2048
  }
@@ -1695,9 +2083,11 @@ class RowFilterProcessor {
1695
2083
  if (applyTo[r]) {
1696
2084
  continue;
1697
2085
  }
1698
- const cellValue = join.value(r, columnIndex);
1699
- if (filter.matches(cellValue)) {
1700
- applyTo[r] = true;
2086
+ const cellValues = join.value(r, columnIndex);
2087
+ for (const cellValue of cellValues) {
2088
+ if (filter.matches(cellValue)) {
2089
+ applyTo[r] = true;
2090
+ }
1701
2091
  }
1702
2092
  }
1703
2093
  }
@@ -1717,14 +2107,22 @@ ${availableRoutes.map((a) => `- ${a}`).join("\n")}`
1717
2107
  }
1718
2108
  }
1719
2109
  }
2110
+ const joinPreserveKeys = [
2111
+ "sliceIdsTable",
2112
+ "sliceIdsRow",
2113
+ /*'base',*/
2114
+ "sliceIdsTable",
2115
+ "sliceIdsTableRow",
2116
+ "componentsTable"
2117
+ ];
1720
2118
  class Join {
1721
- constructor(baseRows, _baseColumnSelection, _objectMap) {
1722
- this._baseColumnSelection = _baseColumnSelection;
1723
- this._objectMap = _objectMap;
1724
- this._base = this._hashedRows(baseRows);
1725
- }
1726
2119
  _base = {};
2120
+ _baseColumnSelection;
1727
2121
  _processes = [];
2122
+ constructor(rows, columnSelection) {
2123
+ this._base = this._hashedRows(rows);
2124
+ this._baseColumnSelection = columnSelection;
2125
+ }
1728
2126
  // ...........................................................................
1729
2127
  /**
1730
2128
  * Applies a filter to the join and returns the filtered view
@@ -1753,14 +2151,62 @@ class Join {
1753
2151
  const data = {};
1754
2152
  for (const [sliceId, joinRowH] of Object.entries(this.data)) {
1755
2153
  const cols = [...joinRowH.columns];
2154
+ const insertCols = [];
1756
2155
  for (const col of cols) {
1757
- if (Route.fromFlat(setValue.route).equalsWithoutRefs(col.route))
1758
- col.insert = setValue.value;
1759
- else continue;
2156
+ const insertCol = {
2157
+ ...col
2158
+ //inserts: col.inserts ? [...col.inserts] : [],
2159
+ };
2160
+ if (Route.fromFlat(setValue.route).equalsWithoutRefs(col.route)) {
2161
+ for (const cell of col.value.cell) {
2162
+ if (cell.path.length === 0) {
2163
+ throw new Error(
2164
+ `Join: Error while applying SetValue: Cannot set value for column without paths. Route: ${setValue.route.toString()}.`
2165
+ );
2166
+ }
2167
+ if (cell.path.length > 1) {
2168
+ throw new Error(
2169
+ `Join: Error while applying SetValue: Cannot set value for multiple paths in one cell. Found paths: [${cell.path.join(", ")}] for route: ${setValue.route.toString()}.`
2170
+ );
2171
+ }
2172
+ const cellInsertTree = isolate(
2173
+ { ...col.value.tree },
2174
+ cell.path[0],
2175
+ joinPreserveKeys
2176
+ );
2177
+ inject(cellInsertTree, cell.path[0], setValue.value);
2178
+ const propertyKey = cell.path[0].slice(-1)[0];
2179
+ const insert = {
2180
+ cell: [
2181
+ {
2182
+ ...cell,
2183
+ ...{ value: setValue.value },
2184
+ ...{
2185
+ row: {
2186
+ ...cell.row,
2187
+ ...{ [propertyKey]: setValue.value }
2188
+ }
2189
+ }
2190
+ }
2191
+ ],
2192
+ tree: cellInsertTree,
2193
+ rljson: col.value.rljson
2194
+ };
2195
+ if (insert) {
2196
+ if (insertCol.inserts) insertCol.inserts.push(insert);
2197
+ else insertCol.inserts = [insert];
2198
+ }
2199
+ }
2200
+ }
2201
+ insertCols.push(insertCol);
1760
2202
  }
1761
2203
  data[sliceId] = {
1762
- rowHash: Hash.default.calcHash(cols.map((c) => c.value)),
1763
- columns: cols
2204
+ rowHash: Hash.default.calcHash(
2205
+ insertCols.map(
2206
+ (col) => col.value.cell.flatMap((c) => c.value)
2207
+ )
2208
+ ),
2209
+ columns: insertCols
1764
2210
  };
1765
2211
  }
1766
2212
  const process = {
@@ -1803,15 +2249,15 @@ class Join {
1803
2249
  const data = {};
1804
2250
  for (let i2 = 0; i2 < this.rowCount; i2++) {
1805
2251
  const [sliceId, row] = Object.entries(this.data)[i2];
1806
- const selectedColumns = [];
2252
+ const cols = [];
1807
2253
  for (let j = 0; j < masterColumnIndices.length; j++) {
1808
- selectedColumns.push(row.columns[masterColumnIndices[j]]);
2254
+ cols.push(row.columns[masterColumnIndices[j]]);
1809
2255
  }
1810
2256
  data[sliceId] = {
1811
2257
  rowHash: Hash.default.calcHash(
1812
- selectedColumns.map((c) => c.value)
2258
+ cols.map((col) => col.value.cell.flatMap((c) => c.value))
1813
2259
  ),
1814
- columns: selectedColumns
2260
+ columns: cols
1815
2261
  };
1816
2262
  }
1817
2263
  const process = {
@@ -1850,30 +2296,51 @@ class Join {
1850
2296
  * Returns insert Object of the join
1851
2297
  */
1852
2298
  insert() {
1853
- const cakeInserts = this._insertCakeObjects(this.data);
1854
- const cakeInsertsMergedOfLayerRoutes = /* @__PURE__ */ new Map();
1855
- for (const { [this.cakeRoute.root.tableKey]: cakeInsert } of cakeInserts) {
1856
- const cakeInsertRoute = cakeInsert.route.flat;
1857
- if (!cakeInsertsMergedOfLayerRoutes.has(cakeInsertRoute)) {
1858
- cakeInsertsMergedOfLayerRoutes.set(cakeInsertRoute, cakeInsert.value);
1859
- } else {
1860
- const existingValue = cakeInsertsMergedOfLayerRoutes.get(
1861
- cakeInsertRoute
2299
+ const inserts = [];
2300
+ for (let i = 0; i < this.columnCount; i++) {
2301
+ const colInserts = [];
2302
+ for (const row of Object.values(this.data)) {
2303
+ const col = row.columns[i];
2304
+ if (col.inserts && col.inserts.length > 0) {
2305
+ for (const insert of col.inserts) {
2306
+ for (const cell of insert.cell) {
2307
+ const tree = insert.tree;
2308
+ const path = cell.path;
2309
+ inject(tree, path[0].slice(0, -1), cell.row);
2310
+ colInserts.push({
2311
+ route: col.route,
2312
+ tree,
2313
+ path: path[0]
2314
+ });
2315
+ }
2316
+ }
2317
+ }
2318
+ }
2319
+ if (colInserts.length === 0) continue;
2320
+ const routes = colInserts.map((ins) => ins.route.flat);
2321
+ const uniqueRoute = Array.from(new Set(routes));
2322
+ if (uniqueRoute.length > 1) {
2323
+ throw new Error(
2324
+ `Join: Error while generating insert: Multiple different routes found in inserts: ${uniqueRoute.map((r) => r.toString()).join(", ")}. Cannot generate single insert object.`
1862
2325
  );
1863
- const mergedValue = merge(existingValue, cakeInsert.value);
1864
- cakeInsertsMergedOfLayerRoutes.set(cakeInsertRoute, mergedValue);
1865
2326
  }
2327
+ const merged = mergeTrees(
2328
+ colInserts.map((ins) => ({
2329
+ tree: ins.tree,
2330
+ path: ins.path.slice(0, -1)
2331
+ }))
2332
+ );
2333
+ traverse(merged, ({ parent, key, value }) => {
2334
+ if (key == "_data" && Array.isArray(value) && value.length > 0) {
2335
+ parent[key] = value.filter((v) => !!v);
2336
+ }
2337
+ });
2338
+ inserts.push({
2339
+ route: Route.fromFlat(uniqueRoute[0]).toRouteWithProperty(),
2340
+ tree: merged
2341
+ });
1866
2342
  }
1867
- const results = [];
1868
- for (const [route, value] of cakeInsertsMergedOfLayerRoutes) {
1869
- const insert = {
1870
- command: "add",
1871
- route,
1872
- value
1873
- };
1874
- results.push(insert);
1875
- }
1876
- return results;
2343
+ return inserts;
1877
2344
  }
1878
2345
  // ...........................................................................
1879
2346
  /**
@@ -2020,7 +2487,11 @@ class Join {
2020
2487
  const dataColRoute = dataCol.route;
2021
2488
  return colInfoRoute.equalsWithoutRefs(dataColRoute);
2022
2489
  });
2023
- row.push(joinCol ? joinCol.insert ?? joinCol.value : null);
2490
+ const insertValue = joinCol && joinCol.inserts ? joinCol.inserts.flatMap(
2491
+ (con) => con.cell.flatMap((c) => c.value)
2492
+ ) ?? null : null;
2493
+ const baseValue = joinCol && joinCol.value.cell ? joinCol.value.cell.flatMap((c) => c.value) ?? null : null;
2494
+ row.push(insertValue ?? baseValue);
2024
2495
  }
2025
2496
  result.push(row);
2026
2497
  }
@@ -2029,184 +2500,26 @@ class Join {
2029
2500
  static empty() {
2030
2501
  return new Join({}, ColumnSelection.empty());
2031
2502
  }
2032
- //#############################################################
2033
- // ############# Private Methods ##############################
2034
- //#############################################################
2035
2503
  // ...........................................................................
2036
2504
  /**
2037
- * Builds cake insert objects from the given join rows
2038
- * @param rows - The join rows
2039
- * @returns The cake insert objects
2505
+ * Hashes the given join rows. If insert value is present, it is used for hashing.
2506
+ *
2507
+ * @param rows The join rows to hash
2508
+ * @returns The hashed join rows
2040
2509
  */
2041
- _insertCakeObjects(rows) {
2042
- const cakeInsertObjects = [];
2043
- const cakeRoute = this.cakeRoute;
2044
- for (const [sliceId, row] of Object.entries(rows)) {
2045
- const layerInsertObjectList = this._insertLayerObjects(
2046
- sliceId,
2047
- row.columns
2048
- );
2049
- for (const layerInsertObject of layerInsertObjectList) {
2050
- const cakeInsertObject = {};
2051
- for (const [layerRoute, layerInsertObj] of Object.entries(
2052
- layerInsertObject
2053
- )) {
2054
- cakeInsertObject[cakeRoute.root.tableKey] = {
2055
- route: layerInsertObj.route,
2056
- value: {
2057
- [layerRoute]: layerInsertObj.value
2058
- }
2059
- };
2060
- }
2061
- cakeInsertObjects.push(cakeInsertObject);
2062
- }
2063
- }
2064
- return cakeInsertObjects;
2065
- }
2066
- // ...........................................................................
2067
- /**
2068
- * Wraps component insert objects into layer insert objects
2069
- * @param sliceId - The slice id
2070
- * @param componentInsertObjects - The component insert objects
2071
- * @returns
2072
- */
2073
- _insertLayerObjects(sliceId, insertRow) {
2074
- const layerRoutes = this.layerRoutes;
2075
- const layerInsertObjects = [];
2076
- const insertComponentObjects = this._insertComponentObjects(
2077
- sliceId,
2078
- insertRow
2079
- );
2080
- for (const layerRoute of layerRoutes) {
2081
- for (const [compRouteFlat, compInsertObj] of Object.entries(
2082
- insertComponentObjects
2083
- )) {
2084
- if (!compInsertObj._somethingToInsert) continue;
2085
- const compRoute = Route.fromFlat(compRouteFlat);
2086
- if (layerRoute.includes(compRoute)) {
2087
- const layerInsertObj = {};
2088
- layerInsertObj[layerRoute.root.tableKey] = {
2089
- route: Route.fromFlat(compRouteFlat),
2090
- value: {
2091
- [sliceId]: compInsertObj
2092
- }
2093
- };
2094
- layerInsertObjects.push(layerInsertObj);
2095
- } else {
2096
- continue;
2097
- }
2098
- }
2099
- }
2100
- return layerInsertObjects;
2101
- }
2102
- // ...........................................................................
2103
- /**
2104
- * Merges columns into component insert objects
2105
- * @param insertColumns - The columns to merge
2106
- * @returns
2107
- */
2108
- _insertComponentObjects(sliceId, insertColumns) {
2109
- const columns = this._mergeInsertRow(sliceId, insertColumns);
2110
- return this._denormalizeComponentInserts(columns, this._objectMap || {});
2111
- }
2112
- _denormalizeComponentInserts(columns, objectMap, refTableKey) {
2113
- const result = {};
2114
- for (const [propertyKey, propertyValue] of Object.entries(objectMap)) {
2115
- if (typeof propertyValue === "object") {
2116
- const refObjectMap = {};
2117
- const refTableKey2 = propertyValue._tableKey;
2118
- for (const [refKey, refValue] of Object.entries(
2119
- propertyValue
2120
- )) {
2121
- if (refKey === "_tableKey") continue;
2122
- refObjectMap[refTableKey2 + "/" + refKey] = refValue;
2123
- }
2124
- const refInsert = this._denormalizeComponentInserts(
2125
- columns,
2126
- refObjectMap,
2127
- refTableKey2
2128
- );
2129
- for (const [refRoute, refObject] of Object.entries(refInsert)) {
2130
- const insertObj = { [propertyKey]: refObject };
2131
- result[refRoute] = {
2132
- ...result[refRoute],
2133
- ...insertObj,
2134
- ...{
2135
- _somethingToInsert: result[refRoute]._somethingToInsert || refObject._somethingToInsert
2136
- }
2137
- };
2138
- }
2139
- } else {
2140
- let compKey = Route.fromFlat(propertyValue).upper().flat;
2141
- compKey = refTableKey ? compKey.replace(`/${refTableKey}`, "") : compKey;
2142
- const refPropertyKey = refTableKey ? propertyKey.replace(`${refTableKey}/`, "") : propertyKey;
2143
- if (!result[compKey]) result[compKey] = {};
2144
- if (refTableKey && !result[compKey]._tableKey)
2145
- result[compKey]._tableKey = refTableKey;
2146
- const propValue = columns.find((col) => {
2147
- return col.route.propertyKey === propertyKey;
2148
- });
2149
- if (!propValue) {
2150
- throw new Error(
2151
- `Join._denormalizeComponentInserts: Could not find column value for property key "${propertyKey}".`
2152
- );
2153
- }
2154
- const somethingToInsert = !!propValue.insert;
2155
- result[compKey] = {
2156
- ...result[compKey],
2157
- [refPropertyKey]: propValue.insert ?? propValue.value,
2158
- ...{
2159
- _somethingToInsert: result[compKey]._somethingToInsert || somethingToInsert
2160
- }
2161
- };
2162
- }
2163
- }
2164
- return result;
2165
- }
2166
- // ...........................................................................
2167
- /**
2168
- * Merges the insert values into the base row
2169
- * @param sliceId - The slice id
2170
- * @param insertRow - The insert row
2171
- * @returns The merged join row
2172
- */
2173
- _mergeInsertRow(sliceId, insertRow) {
2174
- const baseColumns = this._base[sliceId].columns;
2175
- const mergedRow = [];
2176
- for (const baseCol of baseColumns) {
2177
- const insertCol = insertRow.find(
2178
- (col) => col.route.equalsWithoutRefs(baseCol.route)
2179
- );
2180
- if (insertCol) {
2181
- mergedRow.push({
2182
- route: baseCol.route,
2183
- value: baseCol.value,
2184
- insert: insertCol.insert
2185
- });
2186
- } else {
2187
- mergedRow.push(baseCol);
2188
- }
2189
- }
2190
- return mergedRow;
2191
- }
2192
- // ...........................................................................
2193
- /**
2194
- * Hashes the given join rows. If insert value is present, it is used for hashing.
2195
- *
2196
- * @param rows The join rows to hash
2197
- * @returns The hashed join rows
2198
- */
2199
- _hashedRows(rows) {
2200
- const sliceIds = Object.keys(rows);
2201
- const hashedRows = {};
2202
- for (const sliceId of sliceIds) {
2203
- const columns = rows[sliceId];
2204
- const rowHash = Hash.default.calcHash(
2205
- columns.map((col) => col.insert ?? col.value)
2510
+ _hashedRows(rows) {
2511
+ const sliceIds = Object.keys(rows);
2512
+ const hashedRows = {};
2513
+ for (const sliceId of sliceIds) {
2514
+ const cols = rows[sliceId];
2515
+ const rowHash = Hash.default.calcHash(
2516
+ cols.map(
2517
+ (col) => col.inserts?.flatMap((con) => con.cell.flatMap((c) => c.value)) ?? col.value.cell ? col.value.cell.flatMap((c) => c.value) : []
2518
+ )
2206
2519
  );
2207
2520
  hashedRows[sliceId] = {
2208
2521
  rowHash,
2209
- columns
2522
+ columns: cols
2210
2523
  };
2211
2524
  }
2212
2525
  return hashedRows;
@@ -2253,9 +2566,12 @@ class Notify {
2253
2566
  notify(route, insertHistoryRow) {
2254
2567
  const callbacks = this._callbacks.get(route.flat);
2255
2568
  if (callbacks) {
2256
- for (const cb of callbacks) {
2257
- cb(insertHistoryRow);
2258
- }
2569
+ Promise.all(callbacks.map((cb) => cb(insertHistoryRow))).catch((err) => {
2570
+ console.error(
2571
+ `Error notifying callbacks for route ${route.flat}:`,
2572
+ err
2573
+ );
2574
+ });
2259
2575
  }
2260
2576
  }
2261
2577
  // ...........................................................................
@@ -2276,6 +2592,25 @@ class Notify {
2276
2592
  return this._callbacks.get(route.flat) || [];
2277
2593
  }
2278
2594
  }
2595
+ const makeUniqueArrayByHash = (arr) => {
2596
+ const seen = /* @__PURE__ */ new Map();
2597
+ const result = [];
2598
+ for (const item of arr) {
2599
+ if (!seen.has(item._hash)) {
2600
+ seen.set(item._hash, item);
2601
+ result.push(item);
2602
+ }
2603
+ }
2604
+ return result;
2605
+ };
2606
+ const makeUnique = (rljson) => {
2607
+ traverse(rljson, ({ parent, key, value }) => {
2608
+ if (key == "_data" && Array.isArray(value)) {
2609
+ parent[key] = makeUniqueArrayByHash(value);
2610
+ }
2611
+ });
2612
+ return rljson;
2613
+ };
2279
2614
  class Db {
2280
2615
  /**
2281
2616
  * Constructor
@@ -2300,22 +2635,27 @@ class Db {
2300
2635
  * Get data from a route with optional filtering
2301
2636
  * @param route - The route to get data from
2302
2637
  * @param where - Optional filter to apply to the data
2638
+ * @param filter - Optional filter to apply to child entries in related tables
2639
+ * @param sliceIds - Optional slice IDs to filter the data
2303
2640
  * @returns An array of Rljson objects matching the route and filter
2304
2641
  * @throws {Error} If the route is not valid or if any controller cannot be created
2305
2642
  */
2306
- async get(route, where) {
2643
+ async get(route, where, filter, sliceIds) {
2307
2644
  if (!route.isValid) throw new Error(`Route ${route.flat} is not valid.`);
2308
2645
  const isolatedRoute = await this.isolatePropertyKeyFromRoute(route);
2309
- const cacheHash = `${isolatedRoute.flat}|${JSON.stringify(where)}`;
2310
- const isCached = this._cache.has(cacheHash);
2311
- if (isCached) {
2312
- return this._cache.get(cacheHash);
2313
- } else {
2314
- const controllers = await this._indexedControllers(isolatedRoute);
2315
- const data = await this._get(isolatedRoute, where, controllers);
2316
- this._cache.set(cacheHash, data);
2317
- return data;
2318
- }
2646
+ const controllers = await this.indexedControllers(isolatedRoute);
2647
+ const data = await this._get(
2648
+ isolatedRoute,
2649
+ where,
2650
+ controllers,
2651
+ filter,
2652
+ sliceIds
2653
+ );
2654
+ const dataWithControllers = {
2655
+ ...data,
2656
+ ...{ controllers }
2657
+ };
2658
+ return dataWithControllers;
2319
2659
  }
2320
2660
  // ...........................................................................
2321
2661
  /**
@@ -2326,37 +2666,108 @@ class Db {
2326
2666
  * @param where - The recursive filtering key/value pairs to apply to the data
2327
2667
  * @param controllers - The controllers to use for fetching data
2328
2668
  * @param filter - Optional filter to apply to the data at the current route segment
2669
+ * @param sliceIds - Optional slice IDs to filter the data at the current route segment
2329
2670
  * @returns - An Rljson object matching the route and filters
2330
2671
  */
2331
- async _get(route, where, controllers, filter) {
2672
+ async _get(route, where, controllers, filter, sliceIds, routeAccumulator) {
2673
+ const params = {
2674
+ route: route.flat,
2675
+ where,
2676
+ filter,
2677
+ sliceIds,
2678
+ routeAccumulator: routeAccumulator ? routeAccumulator.flat : ""
2679
+ };
2680
+ const cacheHash = hsh(rmhsh(params))._hash;
2681
+ const isCached = this._cache.has(cacheHash);
2682
+ if (isCached) {
2683
+ return this._cache.get(cacheHash);
2684
+ }
2332
2685
  const nodeTableKey = route.top.tableKey;
2333
2686
  const nodeRoute = route;
2334
2687
  const nodeRouteRef = await this._getReferenceOfRouteSegment(nodeRoute.top);
2335
2688
  const nodeController = controllers[nodeTableKey];
2689
+ const nodeSliceIds = nodeRoute.top.sliceIds ?? sliceIds;
2336
2690
  let nodeWhere = typeof where === "object" ? { ...where } : where;
2337
2691
  if (!route.isRoot && typeof nodeWhere === "object") {
2338
2692
  delete nodeWhere[nodeRoute.deeper().top.tableKey];
2339
2693
  }
2340
2694
  nodeWhere = nodeWhere ? typeof nodeWhere === "string" ? { _hash: nodeWhere } : nodeWhere : {};
2341
2695
  if (nodeRouteRef && nodeRouteRef.length > 0)
2342
- nodeWhere = { ...nodeWhere, ...{ _hash: nodeRouteRef } };
2696
+ nodeWhere = { _hash: nodeRouteRef };
2343
2697
  delete nodeWhere["_through"];
2344
2698
  delete nodeWhere["_tableKey"];
2345
2699
  const {
2346
2700
  [nodeTableKey]: { _data: nodeRows, _type: nodeType, _hash: nodeHash }
2347
2701
  } = await nodeController.get(nodeWhere);
2702
+ const nodeColumnCfgs = nodeController.tableCfg().columns;
2348
2703
  const nodeRowsFiltered = [];
2349
- if (filter && filter.length > 0) {
2350
- for (const f of filter) {
2351
- if (f.tableKey !== nodeTableKey) continue;
2352
- for (const nodeRow of nodeRows) {
2704
+ for (const nodeRow of nodeRows) {
2705
+ const filterActive = filter && filter.length > 0;
2706
+ const sliceIdActive = nodeSliceIds && nodeSliceIds.length > 0;
2707
+ if (!filterActive && !sliceIdActive) {
2708
+ nodeRowsFiltered.push(nodeRow);
2709
+ continue;
2710
+ }
2711
+ let filterResult = false;
2712
+ const filterProperties = [];
2713
+ if (filterActive) {
2714
+ for (const f of filter) {
2715
+ if (f.tableKey !== nodeTableKey) continue;
2353
2716
  if (nodeRow._hash === f.ref) {
2354
- nodeRowsFiltered.push(nodeRow);
2717
+ filterProperties.push(f);
2718
+ filterResult = true;
2355
2719
  }
2356
2720
  }
2721
+ } else {
2722
+ filterResult = true;
2723
+ }
2724
+ let sliceIdResult = false;
2725
+ if (sliceIdActive) {
2726
+ switch (nodeType) {
2727
+ case "cakes":
2728
+ const cake = nodeRow;
2729
+ const cakeSliceIds = await this._resolveSliceIds(
2730
+ cake.sliceIdsTable,
2731
+ cake.sliceIdsRow
2732
+ );
2733
+ const cakeMatchesSliceIds = nodeSliceIds.filter(
2734
+ (sId) => cakeSliceIds.includes(sId)
2735
+ );
2736
+ if (cakeMatchesSliceIds.length > 0) sliceIdResult = true;
2737
+ break;
2738
+ case "layers":
2739
+ const layer = nodeRow;
2740
+ const layerSliceIds = await this._resolveSliceIds(
2741
+ layer.sliceIdsTable,
2742
+ layer.sliceIdsTableRow
2743
+ );
2744
+ const layerMatchesSliceIds = nodeSliceIds.filter(
2745
+ (sId) => layerSliceIds.includes(sId)
2746
+ );
2747
+ if (layerMatchesSliceIds.length > 0) sliceIdResult = true;
2748
+ break;
2749
+ case "components":
2750
+ if (filterProperties.length > 0) {
2751
+ const componentSliceIds = filterProperties.flatMap(
2752
+ (f) => f.sliceIds
2753
+ );
2754
+ const componentMatchesSliceIds = nodeSliceIds.filter(
2755
+ (sId) => componentSliceIds.includes(sId)
2756
+ );
2757
+ if (componentMatchesSliceIds.length > 0) {
2758
+ sliceIdResult = true;
2759
+ }
2760
+ }
2761
+ break;
2762
+ /* v8 ignore next -- @preserve */
2763
+ default:
2764
+ sliceIdResult = true;
2765
+ break;
2766
+ }
2767
+ } else {
2768
+ sliceIdResult = true;
2357
2769
  }
2358
- } else {
2359
- nodeRowsFiltered.push(...nodeRows);
2770
+ if (filterResult && sliceIdResult) nodeRowsFiltered.push(nodeRow);
2360
2771
  }
2361
2772
  const node = {
2362
2773
  [nodeTableKey]: {
@@ -2365,11 +2776,48 @@ class Db {
2365
2776
  _hash: nodeHash
2366
2777
  }
2367
2778
  };
2779
+ const nodeValue = node[nodeTableKey]._data.filter(
2780
+ (v) => v !== void 0 && v !== null
2781
+ );
2368
2782
  if (route.isRoot) {
2369
2783
  if (route.hasPropertyKey) {
2370
- return this.isolatePropertyFromComponents(node, route.propertyKey);
2371
- }
2372
- return node;
2784
+ const isolatedNode = this.isolatePropertyFromComponents(
2785
+ node,
2786
+ route.propertyKey
2787
+ );
2788
+ const result3 = {
2789
+ rljson: isolatedNode,
2790
+ tree: { [nodeTableKey]: node[nodeTableKey] },
2791
+ cell: nodeValue.map(
2792
+ (v, idx) => ({
2793
+ value: v[route.propertyKey] ?? null,
2794
+ row: v,
2795
+ route: Route.fromFlat(
2796
+ (routeAccumulator ? routeAccumulator.flat : nodeTableKey) + (nodeHash ? `@${nodeHash}` : "") + `/${route.propertyKey}`
2797
+ ).toRouteWithProperty(),
2798
+ path: [[nodeTableKey, "_data", idx, route.propertyKey]]
2799
+ })
2800
+ )
2801
+ };
2802
+ this._cache.set(cacheHash, result3);
2803
+ return result3;
2804
+ }
2805
+ const result2 = {
2806
+ rljson: node,
2807
+ tree: { [nodeTableKey]: node[nodeTableKey] },
2808
+ cell: nodeValue.map(
2809
+ (v, idx) => ({
2810
+ value: v[route.propertyKey] ?? null,
2811
+ row: v,
2812
+ route: Route.fromFlat(
2813
+ (routeAccumulator ? routeAccumulator.flat : nodeTableKey) + (nodeHash ? `@${nodeHash}` : "")
2814
+ ),
2815
+ path: [[nodeTableKey, "_data", idx]]
2816
+ })
2817
+ )
2818
+ };
2819
+ this._cache.set(cacheHash, result2);
2820
+ return result2;
2373
2821
  }
2374
2822
  const childrenRoute = route.deeper();
2375
2823
  const childrenTableKey = childrenRoute.top.tableKey;
@@ -2379,21 +2827,60 @@ class Db {
2379
2827
  const nodeRowsMatchingChildrenRefs = /* @__PURE__ */ new Map();
2380
2828
  for (let i = 0; i < nodeRowsFiltered.length; i++) {
2381
2829
  const nodeRow = nodeRowsFiltered[i];
2830
+ const nodeRowObj = { ...{}, ...nodeRow };
2382
2831
  const childrenRefs = await nodeController.getChildRefs(
2383
2832
  nodeRow._hash
2384
2833
  );
2385
- const rowChildren = await this._get(
2834
+ const childrenRefTypes = /* @__PURE__ */ new Map();
2835
+ const childrenRefSliceIds = /* @__PURE__ */ new Set();
2836
+ for (const cr of childrenRefs) {
2837
+ if (!!cr.columnKey) {
2838
+ const childrenRefColumnCfg = nodeColumnCfgs.find(
2839
+ (c) => c.key === cr.columnKey
2840
+ );
2841
+ if (childrenRefColumnCfg) {
2842
+ childrenRefTypes.set(
2843
+ childrenRefColumnCfg.key,
2844
+ childrenRefColumnCfg.ref?.type ?? ""
2845
+ );
2846
+ }
2847
+ if (cr.sliceIds && cr.sliceIds.length > 0) {
2848
+ for (const sId of cr.sliceIds) {
2849
+ childrenRefSliceIds.add(sId);
2850
+ }
2851
+ }
2852
+ }
2853
+ }
2854
+ if (childrenRefTypes.size > 1) {
2855
+ throw new Error(
2856
+ `Db._get: Multiple reference types found for children of table ${nodeTableKey}.`
2857
+ );
2858
+ }
2859
+ const cakeIsReferenced = childrenRefTypes.size === 1 && [...childrenRefTypes.values()][0] === "cakes";
2860
+ const componentIsReferenced = childrenRefTypes.size === 1 && nodeType === "components" && [...childrenRefTypes.values()][0] === "components";
2861
+ const childrenSliceIds = cakeIsReferenced ? [...childrenRefSliceIds] : componentIsReferenced ? void 0 : nodeSliceIds;
2862
+ const {
2863
+ rljson: rowChildrenRljson,
2864
+ tree: rowChildrenTree,
2865
+ cell: rowChildrenCell
2866
+ } = await this._get(
2386
2867
  childrenRoute,
2387
2868
  childrenWhere,
2388
2869
  controllers,
2389
- childrenRefs
2870
+ childrenRefs,
2871
+ childrenSliceIds,
2872
+ Route.fromFlat(
2873
+ (routeAccumulator ? routeAccumulator.flat : nodeTableKey) + (nodeHash ? `@${nodeHash}` : "") + "/" + childrenTableKey
2874
+ )
2390
2875
  );
2391
- if (rowChildren[childrenTableKey]._data.length === 0) continue;
2392
- nodeChildrenArray.push(rowChildren);
2876
+ if (cakeIsReferenced) {
2877
+ const refKey = [...childrenRefTypes.keys()][0];
2878
+ nodeRowObj[refKey] = rowChildrenTree;
2879
+ }
2880
+ if (rowChildrenRljson[childrenTableKey]._data.length === 0) continue;
2881
+ nodeChildrenArray.push(rowChildrenRljson);
2393
2882
  if (childrenThroughProperty) {
2394
- const childrenHashes = rowChildren[childrenTableKey]._data.map(
2395
- (rc) => rc._hash
2396
- );
2883
+ const resolvedChildrenHashes = rowChildrenRljson[childrenTableKey]._data.map((rc) => rc._hash);
2397
2884
  for (const nr of nodeRowsFiltered) {
2398
2885
  {
2399
2886
  const throughHashesInRowCouldBeArray = nr[childrenThroughProperty];
@@ -2401,30 +2888,191 @@ class Db {
2401
2888
  throughHashesInRowCouldBeArray
2402
2889
  ) ? throughHashesInRowCouldBeArray : [throughHashesInRowCouldBeArray];
2403
2890
  for (const th of throughHashesInRow) {
2404
- if (childrenHashes.includes(th)) {
2405
- nodeRowsMatchingChildrenRefs.set(nr._hash, nr);
2891
+ if (resolvedChildrenHashes.includes(th)) {
2892
+ nodeRowObj[childrenThroughProperty] = {
2893
+ ...rowChildrenTree[childrenTableKey],
2894
+ ...{ _tableKey: childrenTableKey }
2895
+ };
2896
+ }
2897
+ }
2898
+ }
2899
+ }
2900
+ }
2901
+ const resolvedChildren = rowChildrenRljson[childrenTableKey]._data;
2902
+ const childrenRefsOfRow = childrenRefs.filter(
2903
+ (cr) => cr.tableKey == childrenTableKey
2904
+ );
2905
+ const matchingChildrenRefs = childrenRefsOfRow.filter(
2906
+ (cr) => !!resolvedChildren.find((ch) => cr.ref === ch._hash)
2907
+ );
2908
+ if (nodeType === "layers") {
2909
+ const compChildrenTrees = rowChildrenTree[childrenTableKey]._data;
2910
+ const compChildrenPaths = rowChildrenCell.map((c) => c.path);
2911
+ const components = compChildrenTrees.map((c, idx) => {
2912
+ return {
2913
+ tree: c,
2914
+ path: compChildrenPaths.filter((p) => p[0][2] == idx)
2915
+ };
2916
+ });
2917
+ const layerTreesAndPaths = components.map(({ tree: comp, path }) => {
2918
+ const sliceIds2 = matchingChildrenRefs.find(
2919
+ (cr) => cr.ref === comp._hash
2920
+ )?.sliceIds;
2921
+ if (!sliceIds2 || sliceIds2.length === 0) {
2922
+ throw new Error(
2923
+ `Db._get: No sliceIds found for component ${comp._hash} of layer ${nodeRow._hash}.`
2924
+ );
2925
+ }
2926
+ if (sliceIds2.length > 1) {
2927
+ throw new Error(
2928
+ `Db._get: Multiple sliceIds found for component ${comp._hash} of layer ${nodeRow._hash}.`
2929
+ );
2930
+ }
2931
+ const sliceId = sliceIds2[0];
2932
+ const pathsForSliceId = [
2933
+ ...path.map((p) => p[0]).map((p) => {
2934
+ const newPath = [...p];
2935
+ newPath[2] = 0;
2936
+ return ["add", sliceId, ...newPath];
2937
+ })
2938
+ ];
2939
+ return {
2940
+ [sliceId]: {
2941
+ tree: {
2942
+ [childrenTableKey]: {
2943
+ ...{ _data: [comp] },
2944
+ ...{ _type: "components" }
2945
+ }
2946
+ },
2947
+ path: pathsForSliceId
2948
+ }
2949
+ };
2950
+ });
2951
+ const layer = layerTreesAndPaths.flatMap(
2952
+ (ltap) => Object.entries(ltap).map(([sliceId, { tree }]) => ({
2953
+ [sliceId]: tree
2954
+ }))
2955
+ ).reduce((a, b) => ({ ...a, ...b }), {});
2956
+ const paths = layerTreesAndPaths.map(
2957
+ (ltap) => Object.values(ltap)[0].path
2958
+ );
2959
+ nodeRowsMatchingChildrenRefs.set(nodeRow._hash, {
2960
+ rljson: nodeRow,
2961
+ tree: {
2962
+ ...nodeRowObj,
2963
+ add: { ...nodeRowObj.add, ...layer }
2964
+ },
2965
+ cell: rowChildrenCell.map(
2966
+ (c, idx) => ({
2967
+ ...c,
2968
+ ...{
2969
+ path: [paths.flat()[idx]]
2406
2970
  }
2971
+ })
2972
+ )
2973
+ });
2974
+ } else if (nodeType === "cakes") {
2975
+ nodeRowsMatchingChildrenRefs.set(nodeRow._hash, {
2976
+ rljson: nodeRow,
2977
+ tree: {
2978
+ ...nodeRowObj,
2979
+ layers: { ...nodeRowObj.layers, ...rowChildrenTree }
2980
+ },
2981
+ cell: rowChildrenCell.map((c) => ({
2982
+ ...c,
2983
+ ...{
2984
+ path: c.path.map((p) => ["layers", ...p])
2407
2985
  }
2986
+ }))
2987
+ });
2988
+ } else if (nodeType === "components") {
2989
+ if (rowChildrenTree && Object.keys(rowChildrenTree).length > 0) {
2990
+ const columnReferenceMap = nodeColumnCfgs.filter(
2991
+ (c) => c.ref && ["components", "cakes"].includes(c.ref.type)
2992
+ ).reduce((acc, curr) => {
2993
+ acc.set(curr.key, curr.ref.tableKey);
2994
+ return acc;
2995
+ }, /* @__PURE__ */ new Map());
2996
+ const resolvedRefs = {};
2997
+ for (const [colKey, childTableKey] of columnReferenceMap) {
2998
+ const tree = {
2999
+ ...rowChildrenTree[childTableKey],
3000
+ ...{ _tableKey: childTableKey }
3001
+ };
3002
+ const cell = rowChildrenCell.map((c) => ({
3003
+ ...c,
3004
+ ...{
3005
+ path: c.path.filter((p) => p[0] === childTableKey).map((p) => [colKey, ...p.slice(1)])
3006
+ }
3007
+ }));
3008
+ resolvedRefs[colKey] = { tree, cell };
2408
3009
  }
3010
+ const resolvedProperties = Object.entries(resolvedRefs).map(([colKey, { tree }]) => ({
3011
+ [colKey]: tree
3012
+ })).reduce((a, b) => ({ ...a, ...b }), {});
3013
+ const resolvedTree = {
3014
+ ...nodeRowObj,
3015
+ ...resolvedProperties
3016
+ };
3017
+ const resolvedCell = Object.values(resolvedRefs).map((r) => r.cell).flat();
3018
+ nodeRowsMatchingChildrenRefs.set(nodeRow._hash, {
3019
+ rljson: nodeRow,
3020
+ tree: resolvedTree,
3021
+ cell: resolvedCell
3022
+ });
3023
+ } else {
3024
+ nodeRowsMatchingChildrenRefs.set(nodeRow._hash, {
3025
+ rljson: nodeRow,
3026
+ tree: { ...nodeRowObj },
3027
+ cell: rowChildrenCell
3028
+ });
2409
3029
  }
3030
+ } else {
3031
+ throw new Error(
3032
+ `Db._get: Unsupported node type ${nodeType} for getting children.`
3033
+ );
2410
3034
  }
2411
3035
  }
2412
- const nodeChildren = merge(...nodeChildrenArray);
2413
- if (childrenThroughProperty) {
2414
- const matchedNodeRows = Array.from(nodeRowsMatchingChildrenRefs.values());
2415
- return {
3036
+ const nodeChildren = makeUnique(
3037
+ merge(...nodeChildrenArray)
3038
+ );
3039
+ const matchedNodeRows = Array.from(nodeRowsMatchingChildrenRefs.values());
3040
+ const result = {
3041
+ rljson: {
2416
3042
  ...node,
2417
3043
  ...{
2418
3044
  [nodeTableKey]: {
2419
- _data: matchedNodeRows,
2420
- _type: nodeType,
2421
- _hash: nodeHash
3045
+ ...{
3046
+ _data: matchedNodeRows.map((mr) => mr.rljson),
3047
+ _type: nodeType
3048
+ },
3049
+ ...{
3050
+ ...nodeHash ? { _hash: nodeHash } : {}
3051
+ }
2422
3052
  }
2423
3053
  },
2424
3054
  ...nodeChildren
2425
- };
2426
- }
2427
- return { ...node, ...nodeChildren };
3055
+ },
3056
+ tree: {
3057
+ [nodeTableKey]: {
3058
+ ...{
3059
+ _data: matchedNodeRows.map((mr) => mr.tree),
3060
+ _type: nodeType
3061
+ },
3062
+ ...{
3063
+ ...nodeHash ? { _hash: nodeHash } : {}
3064
+ }
3065
+ }
3066
+ },
3067
+ cell: matchedNodeRows.map(
3068
+ (mr, idx) => mr.cell.map((c) => ({
3069
+ ...c,
3070
+ ...{ path: c.path.map((p) => [nodeTableKey, "_data", idx, ...p]) }
3071
+ }))
3072
+ ).flat()
3073
+ };
3074
+ this._cache.set(cacheHash, result);
3075
+ return result;
2428
3076
  }
2429
3077
  // ...........................................................................
2430
3078
  /**
@@ -2451,234 +3099,66 @@ class Db {
2451
3099
  * @param rljson - The Rljson to join data for
2452
3100
  */
2453
3101
  async join(columnSelection, cakeKey, cakeRef) {
2454
- const data = await this._getBaseDataForColumnSelection(columnSelection);
2455
- const cakesTable = data[cakeKey];
2456
- const cake = cakesTable._data.find((c) => c._hash === cakeRef);
2457
- if (!cake) {
3102
+ const {
3103
+ tree: { [cakeKey]: cakesTable }
3104
+ } = await this.get(Route.fromFlat(`${cakeKey}@${cakeRef}`), {});
3105
+ const cakes = cakesTable._data;
3106
+ if (cakes.length === 0) {
2458
3107
  throw new Error(
2459
3108
  `Db.join: Cake with ref "${cakeRef}" not found in cake table "${cakeKey}".`
2460
3109
  );
2461
3110
  }
2462
- const layers = /* @__PURE__ */ new Map();
2463
- for (const layerKey of Object.keys(cake.layers)) {
2464
- if (!data[layerKey]) continue;
2465
- const layersTable = data[layerKey];
2466
- const layer = layersTable._data.find(
2467
- (l) => l._hash === cake.layers[layerKey]
3111
+ if (cakes.length > 1) {
3112
+ throw new Error(
3113
+ `Db.join: Multiple cakes with ref "${cakeRef}" found in cake table "${cakeKey}".`
2468
3114
  );
2469
- layers.set(layerKey, layer);
2470
- }
2471
- const components = /* @__PURE__ */ new Map();
2472
- for (const [tableKey, table] of Object.entries(data)) {
2473
- if (table._type !== "components") continue;
2474
- components.set(tableKey, table);
2475
3115
  }
2476
- const mergedSliceIds = /* @__PURE__ */ new Set();
2477
- for (const layer of layers.values()) {
2478
- const sliceIdsTable = layer.sliceIdsTable;
2479
- const sliceIdsTableRow = layer.sliceIdsTableRow;
2480
- const {
2481
- [sliceIdsTable]: { _data: sliceIds }
2482
- } = await this.core.readRows(sliceIdsTable, { _hash: sliceIdsTableRow });
2483
- for (const sid of sliceIds) {
2484
- for (const s of sid.add) {
2485
- mergedSliceIds.add(s);
2486
- }
2487
- }
2488
- }
2489
- const columnCfgs = /* @__PURE__ */ new Map();
2490
- const columnInfos = /* @__PURE__ */ new Map();
2491
- let objectMap = {};
2492
- for (const [layerKey, layer] of layers.entries()) {
2493
- const componentKey = layer.componentsTable;
2494
- const componentResolved = await this._resolveComponent(
2495
- `${cakeKey}/${layerKey}/`,
2496
- componentKey
2497
- );
2498
- objectMap = { ...objectMap, ...componentResolved.objectMap };
2499
- columnInfos.set(componentKey, componentResolved.columnInfos);
2500
- columnCfgs.set(componentKey, componentResolved.columnCfgs);
2501
- }
2502
- const rowMap = /* @__PURE__ */ new Map();
2503
- const joinColumnInfos = /* @__PURE__ */ new Map();
2504
- for (const sliceId of mergedSliceIds) {
2505
- const sliceIdRow = [];
2506
- for (const [layerKey, layer] of layers.entries()) {
2507
- const layerRef = layer._hash;
2508
- const componentKey = layer.componentsTable;
2509
- const componentRef = layer.add[sliceId];
2510
- const componentsTable = data[componentKey];
2511
- const rowComponentProperties = componentsTable._data.find(
2512
- (r) => r._hash === componentRef
2513
- );
2514
- const resolvedProperties = this._resolveComponentProperties(
2515
- rowComponentProperties,
2516
- objectMap,
2517
- data
2518
- );
2519
- const joinColumns = [];
2520
- for (const [
2521
- resolvedPropertyKey,
2522
- resolvedPropertyValue
2523
- ] of Object.entries(resolvedProperties)) {
2524
- const propertyRoute = cakeKey + "/" + layerKey + "/" + componentKey + "/" + resolvedPropertyKey;
2525
- const propertyColumnInfo = columnInfos.get(componentKey).find((cI) => cI.route === propertyRoute);
2526
- if (!!propertyColumnInfo) {
2527
- const joinColumnRoute = Route.fromFlat(
2528
- `${cakeKey}@${cakeRef}/${layerKey}@${layerRef}/${componentKey}@${componentRef}`
2529
- );
2530
- joinColumnRoute.propertyKey = resolvedPropertyKey;
2531
- joinColumnInfos.set(resolvedPropertyKey, {
2532
- ...propertyColumnInfo,
2533
- key: resolvedPropertyKey,
2534
- route: joinColumnRoute.flatWithoutRefs.slice(1)
2535
- });
2536
- joinColumns.push({
2537
- route: joinColumnRoute,
2538
- value: resolvedPropertyValue ?? null,
2539
- insert: null
2540
- });
2541
- }
2542
- }
2543
- sliceIdRow.push(...joinColumns);
2544
- }
2545
- rowMap.set(sliceId, sliceIdRow);
2546
- }
2547
- const joinRows = {};
2548
- for (const [sliceId, joinColumns] of rowMap.entries()) {
2549
- Object.assign(joinRows, {
2550
- [sliceId]: joinColumns
2551
- });
2552
- }
2553
- return new Join(
2554
- joinRows,
2555
- new ColumnSelection(Array.from(joinColumnInfos.values())),
2556
- objectMap
2557
- ).select(columnSelection);
2558
- }
2559
- _resolveComponentProperties(componentData, objectMapOrRoute, baseData) {
2560
- let result = {};
2561
- for (const [propertyKey, propertyObjectMap] of Object.entries(
2562
- objectMapOrRoute
2563
- )) {
2564
- if (propertyKey === "_tableKey") continue;
2565
- if (typeof propertyObjectMap === "object") {
2566
- const refs = Array.isArray(componentData[propertyKey]) ? componentData[propertyKey] : [componentData[propertyKey]];
2567
- const refTableKey = propertyObjectMap["_tableKey"];
2568
- const refCompTable = baseData[refTableKey];
2569
- if (!refCompTable) continue;
2570
- const prefixedResolvedRefCompData = {};
2571
- for (const ref of refs) {
2572
- const refCompData = refCompTable._data.find((r) => r._hash === ref);
2573
- if (refCompData) {
2574
- const resolvedRefCompData = this._resolveComponentProperties(
2575
- refCompData,
2576
- propertyObjectMap,
2577
- baseData
2578
- );
2579
- for (const [refPropKey, value] of Object.entries(
2580
- resolvedRefCompData
2581
- )) {
2582
- if (!prefixedResolvedRefCompData[`${refTableKey}/${refPropKey}`]) {
2583
- prefixedResolvedRefCompData[`${refTableKey}/${refPropKey}`] = [];
2584
- }
2585
- prefixedResolvedRefCompData[`${refTableKey}/${refPropKey}`].push({
2586
- _ref: ref,
2587
- _value: value
2588
- });
2589
- }
2590
- }
2591
- result = {
2592
- ...result,
2593
- ...prefixedResolvedRefCompData
2594
- };
2595
- }
2596
- }
2597
- result = {
2598
- ...result,
2599
- ...{ [propertyKey]: componentData[propertyKey] }
2600
- };
2601
- }
2602
- return result;
2603
- }
2604
- // ...........................................................................
2605
- /**
2606
- * Resolve a component's columns, including referenced components
2607
- *
2608
- * @param baseRoute - The base route for the component
2609
- * @param componentKey - The component's table key
2610
- * @returns - The resolved column configurations, column infos, and object map
2611
- */
2612
- async _resolveComponent(baseRoute, componentKey) {
2613
- const { columns: colCfgs } = await this.core.tableCfg(componentKey);
2614
- const objectMap = {};
2615
- const columnCfgs = [];
2616
- const columnInfos = [];
2617
- for (let i = 0; i < colCfgs.length; i++) {
2618
- if (colCfgs[i].key === "_hash") continue;
2619
- const colCfg = colCfgs[i];
2620
- if (colCfg.ref) {
2621
- const columnCfgsAndInfosForRef = await this._resolveComponent(
2622
- baseRoute + `/${componentKey}`,
2623
- colCfg.ref.tableKey
3116
+ const cake = cakes[0];
3117
+ const sliceIds = await this._resolveSliceIds(
3118
+ cake.sliceIdsTable,
3119
+ cake.sliceIdsRow
3120
+ );
3121
+ const rows = {};
3122
+ for (const sliceId of sliceIds) {
3123
+ const row = [];
3124
+ for (const columnInfo of columnSelection.columns) {
3125
+ const columnRoute = Route.fromFlat(
3126
+ columnInfo.route
3127
+ ).toRouteWithProperty();
3128
+ const columnContainer = await this.get(
3129
+ columnRoute,
3130
+ cakeRef,
3131
+ void 0,
3132
+ [sliceId]
2624
3133
  );
2625
- objectMap[colCfg.key] = {
2626
- _tableKey: colCfg.ref.tableKey,
2627
- ...columnCfgsAndInfosForRef.objectMap
3134
+ const column = {
3135
+ route: columnRoute,
3136
+ value: columnContainer,
3137
+ inserts: null
2628
3138
  };
2629
- const columnCfgsForRef = columnCfgsAndInfosForRef.columnCfgs.map(
2630
- (cc) => ({
2631
- ...cc,
2632
- key: colCfg.ref.tableKey + "/" + cc.key
2633
- })
2634
- );
2635
- const columnInfosForRef = columnCfgsAndInfosForRef.columnInfos.map(
2636
- (cc) => ({
2637
- ...cc,
2638
- key: colCfg.ref.tableKey + "/" + cc.key
2639
- })
2640
- );
2641
- columnCfgs.push(...columnCfgsForRef);
2642
- columnInfos.push(...columnInfosForRef);
2643
- }
2644
- const columnRoute = Route.fromFlat(
2645
- baseRoute.length > 0 ? `${baseRoute}/${componentKey}/${colCfg.key}` : `/${componentKey}/${colCfg.key}`
2646
- ).flat.slice(1);
2647
- if (!objectMap[colCfg.key]) objectMap[colCfg.key] = columnRoute;
2648
- columnCfgs.push(colCfg);
2649
- columnInfos.push({
2650
- ...colCfg,
2651
- alias: `${colCfg.key}`,
2652
- route: columnRoute,
2653
- titleShort: colCfg.key,
2654
- titleLong: colCfg.key
2655
- });
3139
+ row.push(column);
3140
+ }
3141
+ rows[sliceId] = row;
2656
3142
  }
2657
- return { columnCfgs, columnInfos, objectMap };
3143
+ return new Join(rows, columnSelection);
2658
3144
  }
2659
3145
  // ...........................................................................
2660
- /**
2661
- * Fetches data for the given ColumnSelection
2662
- * @param columnSelection - The ColumnSelection to fetch data for
2663
- */
2664
- async _getBaseDataForColumnSelection(columnSelection) {
2665
- const uniqueComponentRoutes = /* @__PURE__ */ new Set();
2666
- for (const colInfo of columnSelection.columns) {
2667
- const componentRoute = Route.fromFlat(colInfo.route);
2668
- const isolatedComponentRoute = await this.isolatePropertyKeyFromRoute(
2669
- componentRoute
2670
- );
2671
- uniqueComponentRoutes.add(
2672
- isolatedComponentRoute.toRouteWithoutProperty().flat
3146
+ async _resolveSliceIds(sliceIdTable, sliceIdRow) {
3147
+ const sliceIdController = new SliceIdController(this.core, sliceIdTable);
3148
+ sliceIdController.init();
3149
+ const resolvedSliceIds = /* @__PURE__ */ new Set();
3150
+ const {
3151
+ [sliceIdTable]: { _data: sliceIds }
3152
+ } = await sliceIdController.get(sliceIdRow);
3153
+ for (const sliceId of sliceIds) {
3154
+ const baseSliceIds = await sliceIdController.resolveBaseSliceIds(
3155
+ sliceId
2673
3156
  );
3157
+ for (const sId of baseSliceIds.add) {
3158
+ resolvedSliceIds.add(sId);
3159
+ }
2674
3160
  }
2675
- const data = {};
2676
- for (const compRouteFlat of uniqueComponentRoutes) {
2677
- const uniqueComponentRoute = Route.fromFlat(compRouteFlat);
2678
- const componentData = await this.get(uniqueComponentRoute, {});
2679
- Object.assign(data, componentData);
2680
- }
2681
- return data;
3161
+ return Array.from(resolvedSliceIds);
2682
3162
  }
2683
3163
  // ...........................................................................
2684
3164
  /**
@@ -2687,17 +3167,15 @@ class Db {
2687
3167
  * @returns The result of the Insert as an InsertHistoryRow
2688
3168
  * @throws {Error} If the Insert is not valid or if any controller cannot be created
2689
3169
  */
2690
- async insert(insert, options) {
2691
- const initialRoute = Route.fromFlat(insert.route);
2692
- const runs = await this._resolveInsert(insert);
2693
- const errors = validateInsert(insert);
2694
- if (!!errors.hasErrors) {
2695
- throw new Error(
2696
- `Db.insert: Insert is not valid:
2697
- ${JSON.stringify(errors, null, 2)}`
2698
- );
3170
+ async insert(route, tree, options) {
3171
+ const controllers = await this.indexedControllers(
3172
+ Route.fromFlat(route.flatWithoutRefs)
3173
+ );
3174
+ const runFns = {};
3175
+ for (const [tableKey, controller] of Object.entries(controllers)) {
3176
+ runFns[tableKey] = controller.insert.bind(controller);
2699
3177
  }
2700
- return this._insert(insert, initialRoute, runs, options);
3178
+ return this._insert(route, tree, runFns, options);
2701
3179
  }
2702
3180
  // ...........................................................................
2703
3181
  /**
@@ -2708,87 +3186,198 @@ ${JSON.stringify(errors, null, 2)}`
2708
3186
  * @returns The result of the Insert
2709
3187
  * @throws {Error} If the route is not valid or if any controller cannot be created
2710
3188
  */
2711
- async _insert(insert, route, runFns, options) {
2712
- let results;
2713
- let tableKey;
2714
- const segment = route.segment(0);
2715
- tableKey = segment.tableKey;
2716
- let previous = [];
2717
- if (Route.segmentHasRef(segment)) {
2718
- const routeRef = Route.segmentRef(segment);
2719
- if (Route.segmentHasInsertHistoryRef(segment)) {
2720
- previous = [...previous, routeRef];
2721
- }
2722
- if (Route.segmentHasDefaultRef(segment)) {
2723
- const timeIds = await this.getTimeIdsForRef(
2724
- tableKey,
2725
- Route.segmentRef(segment)
2726
- );
2727
- previous = [...previous, ...timeIds];
2728
- }
3189
+ async _insert(route, tree, runFns, options) {
3190
+ const results = [];
3191
+ const nodeRoute = route;
3192
+ const nodeSegment = nodeRoute.segment(0);
3193
+ const nodeTableKey = nodeSegment.tableKey;
3194
+ const nodeTree = tree[nodeTableKey];
3195
+ const nodeType = nodeTree._type;
3196
+ if (nodeTree._data.length === 0) {
3197
+ throw new Error(
3198
+ `Db._insert: No data found for table "${nodeTableKey}" in route "${route.flat}".`
3199
+ );
2729
3200
  }
2730
- if (!route.isRoot) {
2731
- const childRoute = route.deeper(1);
2732
- const childKeys = this._childKeys(insert.value);
2733
- const childRefs = {};
2734
- for (const k of childKeys) {
2735
- const childValue = insert.value[k];
2736
- const childInsert = { ...insert, value: childValue };
3201
+ const previousHash = nodeSegment[nodeTableKey + "Ref"] ?? null;
3202
+ const previousTimeId = nodeSegment[nodeTableKey + "InsertHistoryRef"] ?? null;
3203
+ const previous = previousHash ? await this.getTimeIdsForRef(nodeTableKey, previousHash) : previousTimeId ? [previousTimeId] : [];
3204
+ if (!nodeRoute.isRoot) {
3205
+ const childRoute = nodeRoute.deeper(1);
3206
+ const childTableKey = childRoute.top.tableKey;
3207
+ if (nodeType === "cakes") {
3208
+ const cakes = nodeTree._data;
3209
+ if (cakes.length > 1) ;
3210
+ const cake = cakes[0];
3211
+ const childTree = cake.layers[childTableKey];
2737
3212
  const childResults = await this._insert(
2738
- childInsert,
2739
3213
  childRoute,
3214
+ { [childTableKey]: childTree },
2740
3215
  runFns
2741
3216
  );
2742
- const childRefKey = childRoute.top.tableKey + "Ref";
2743
- const childRefArray = childResults.map(
2744
- (childResult) => childResult[childRefKey]
3217
+ if (childResults.length > 1) {
3218
+ throw new Error(
3219
+ `Db._insert: Multiple inserts returned for child table "${childTableKey}" when inserting into cake table "${nodeTableKey}". Only single child inserts are supported.`
3220
+ );
3221
+ }
3222
+ const childResult = childResults[0];
3223
+ const insertValue = {
3224
+ ...cake,
3225
+ ...{
3226
+ layers: {
3227
+ ...cake.layers,
3228
+ ...{
3229
+ [childTableKey]: childResult[childTableKey + "Ref"]
3230
+ }
3231
+ }
3232
+ }
3233
+ };
3234
+ const runFn = runFns[nodeTableKey];
3235
+ const result = await runFn("add", rmhsh(insertValue), "db.insert");
3236
+ results.push(
3237
+ ...result.map((r) => ({
3238
+ ...r,
3239
+ ...{ previous },
3240
+ ...{ route: route.flat }
3241
+ }))
2745
3242
  );
2746
- childRefs[k] = childRefArray;
2747
3243
  }
2748
- const runFn = runFns[tableKey];
2749
- results = [
2750
- ...(await runFn(
2751
- insert.command,
2752
- {
2753
- ...insert.value,
2754
- ...childRefs
2755
- },
2756
- insert.origin
2757
- )).map((r) => ({ ...r, ...{ previous } }))
2758
- ];
3244
+ if (nodeType === "layers") {
3245
+ const layers = nodeTree._data;
3246
+ for (const layer of layers) {
3247
+ const layerInsert = {};
3248
+ for (const [sliceId, componentTree] of Object.entries(layer.add)) {
3249
+ if (sliceId === "_hash") continue;
3250
+ const writtenComponents = await this._insert(
3251
+ childRoute,
3252
+ componentTree,
3253
+ runFns
3254
+ );
3255
+ if (writtenComponents.length > 1) {
3256
+ throw new Error(
3257
+ `Db._insert: Multiple components written for layer "${layer._hash}" and sliceId "${sliceId}" is currently not supported.`
3258
+ );
3259
+ }
3260
+ const writtenComponent = writtenComponents[0];
3261
+ if (!writtenComponent || !writtenComponent[childTableKey + "Ref"]) {
3262
+ throw new Error(
3263
+ `Db._insert: No component reference returned for layer "${layer._hash}" and sliceId "${sliceId}".`
3264
+ );
3265
+ }
3266
+ layerInsert[sliceId] = writtenComponent[childTableKey + "Ref"];
3267
+ }
3268
+ const runFn = runFns[nodeTableKey];
3269
+ const result = await runFn(
3270
+ "add",
3271
+ rmhsh({
3272
+ ...layer,
3273
+ ...{ add: layerInsert }
3274
+ }),
3275
+ "db.insert"
3276
+ );
3277
+ results.push(
3278
+ ...result.map((r) => ({
3279
+ ...r,
3280
+ ...{ previous },
3281
+ ...{ route: route.flat }
3282
+ }))
3283
+ );
3284
+ }
3285
+ }
3286
+ if ([
3287
+ "components",
3288
+ "edits",
3289
+ "multiEdits",
3290
+ "editHistory",
3291
+ "head"
3292
+ ].includes(nodeType)) {
3293
+ const runFn = runFns[nodeTableKey];
3294
+ const components = nodeTree._data;
3295
+ for (const component of components) {
3296
+ const resolvedComponent = { ...component };
3297
+ for (const [property, value] of Object.entries(component)) {
3298
+ if (value.hasOwnProperty("_tableKey") && value._tableKey === childTableKey) {
3299
+ const writtenReferences = await this._insert(
3300
+ childRoute,
3301
+ { [childTableKey]: value },
3302
+ runFns
3303
+ );
3304
+ resolvedComponent[property] = writtenReferences.map(
3305
+ (wr) => wr[childTableKey + "Ref"]
3306
+ );
3307
+ }
3308
+ }
3309
+ const result = await runFn(
3310
+ "add",
3311
+ rmhsh(resolvedComponent),
3312
+ "db.insert"
3313
+ );
3314
+ results.push(
3315
+ ...result.map((r) => ({
3316
+ ...r,
3317
+ ...{ previous },
3318
+ ...{ route: route.flat }
3319
+ }))
3320
+ );
3321
+ }
3322
+ }
2759
3323
  } else {
2760
- tableKey = route.root.tableKey;
2761
- const runFn = runFns[tableKey];
2762
- const insertValue = insert.value;
2763
- for (const [propertyKey, propertyValue] of Object.entries(insert.value)) {
2764
- if (propertyValue && typeof propertyValue === "object" && !!propertyValue._tableKey) {
2765
- const referenceRoute = propertyValue._tableKey;
2766
- delete propertyValue._tableKey;
2767
- const referenceInsert = {
2768
- command: insert.command,
2769
- route: referenceRoute,
2770
- value: propertyValue
2771
- };
2772
- const referencesWritten = (await this._insert(
2773
- referenceInsert,
2774
- Route.fromFlat(referenceRoute),
2775
- runFns
2776
- )).map((h) => h[referenceRoute + "Ref"]);
2777
- insertValue[propertyKey] = referencesWritten.length === 1 ? referencesWritten[0] : referencesWritten;
3324
+ const runFn = runFns[nodeTableKey];
3325
+ if ([
3326
+ "components",
3327
+ "edits",
3328
+ "multiEdits",
3329
+ "editHistory",
3330
+ "head"
3331
+ ].includes(nodeType)) {
3332
+ const components = rmhsh(
3333
+ tree[nodeTableKey]
3334
+ );
3335
+ for (const component of components._data) {
3336
+ if (!component) continue;
3337
+ delete component._tableKey;
3338
+ delete component._type;
3339
+ const result = await runFn("add", component, "db.insert");
3340
+ results.push(
3341
+ ...result.map((r) => ({
3342
+ ...r,
3343
+ ...{ previous },
3344
+ ...{ route: route.flat }
3345
+ }))
3346
+ );
3347
+ }
3348
+ }
3349
+ if (nodeType === "layers") {
3350
+ const layers = rmhsh(tree[nodeTableKey]);
3351
+ for (const layer of layers._data) {
3352
+ const result = await runFn("add", layer, "db.insert");
3353
+ results.push(
3354
+ ...result.map((r) => ({
3355
+ ...r,
3356
+ ...{ previous },
3357
+ ...{ route: route.flat }
3358
+ }))
3359
+ );
3360
+ }
3361
+ }
3362
+ if (nodeType === "cakes") {
3363
+ const cakes = rmhsh(tree[nodeTableKey]);
3364
+ for (const cake of cakes._data) {
3365
+ const result = await runFn("add", cake, "db.insert");
3366
+ results.push(
3367
+ ...result.map((r) => ({
3368
+ ...r,
3369
+ ...{ previous },
3370
+ ...{ route: route.flat }
3371
+ }))
3372
+ );
2778
3373
  }
2779
3374
  }
2780
- results = [
2781
- ...(await runFn(insert.command, insertValue, insert.origin)).map(
2782
- (r) => ({ ...r, previous })
2783
- )
2784
- ];
2785
3375
  }
2786
3376
  for (const result of results) {
2787
- result.route = insert.route;
2788
3377
  if (!options?.skipHistory)
2789
- await this._writeInsertHistory(tableKey, result);
3378
+ await this._writeInsertHistory(nodeTableKey, result);
2790
3379
  if (!options?.skipNotification)
2791
- this.notify.notify(Route.fromFlat(insert.route), result);
3380
+ this.notify.notify(Route.fromFlat(result.route), result);
2792
3381
  }
2793
3382
  return results;
2794
3383
  }
@@ -2811,48 +3400,6 @@ ${JSON.stringify(errors, null, 2)}`
2811
3400
  this.notify.unregister(route, callback);
2812
3401
  }
2813
3402
  // ...........................................................................
2814
- /**
2815
- * Resolves an Insert by returning the run functions of all controllers involved in the Insert's route
2816
- * @param Insert - The Insert to resolve
2817
- * @returns A record of controller run functions, keyed by table name
2818
- * @throws {Error} If the route is not valid or if any controller cannot be created
2819
- */
2820
- async _resolveInsert(Insert2) {
2821
- const controllers = await this._indexedControllers(
2822
- Route.fromFlat(Insert2.route)
2823
- );
2824
- const referencedComponentTableKeys = /* @__PURE__ */ new Set();
2825
- traverse(Insert2.value, ({ key, parent }) => {
2826
- if (key == "_tableKey")
2827
- referencedComponentTableKeys.add(parent[key]);
2828
- });
2829
- for (const tableKey of referencedComponentTableKeys) {
2830
- controllers[tableKey] ??= await this.getController(tableKey);
2831
- }
2832
- const runFns = {};
2833
- for (const tableKey of Object.keys(controllers)) {
2834
- runFns[tableKey] = controllers[tableKey].insert.bind(
2835
- controllers[tableKey]
2836
- );
2837
- }
2838
- return runFns;
2839
- }
2840
- // ...........................................................................
2841
- /**
2842
- * Returns the keys of child refs in a value based on a route
2843
- * @param value - The value to check
2844
- * @returns An array of keys of child refs in the value
2845
- */
2846
- _childKeys(value) {
2847
- const keys = Object.keys(value);
2848
- const childKeys = [];
2849
- for (const k of keys) {
2850
- if (typeof value[k] !== "object") continue;
2851
- childKeys.push(k);
2852
- }
2853
- return childKeys;
2854
- }
2855
- // ...........................................................................
2856
3403
  /**
2857
3404
  * Get a controller for a specific table
2858
3405
  * @param tableKey - The key of the table to get the controller for
@@ -2869,7 +3416,7 @@ ${JSON.stringify(errors, null, 2)}`
2869
3416
  return createController(contentType, this.core, tableKey, refs);
2870
3417
  }
2871
3418
  // ...........................................................................
2872
- async _indexedControllers(route) {
3419
+ async indexedControllers(route) {
2873
3420
  const controllers = {};
2874
3421
  const isolatedRoute = await this.isolatePropertyKeyFromRoute(route);
2875
3422
  for (let i = 0; i < isolatedRoute.segments.length; i++) {
@@ -2901,6 +3448,114 @@ ${JSON.stringify(errors, null, 2)}`
2901
3448
  });
2902
3449
  }
2903
3450
  // ...........................................................................
3451
+ /**
3452
+ * Add a head revision for a cake
3453
+ * @param cakeKey - The cake table key
3454
+ * @param cakeRef - The cake reference
3455
+ */
3456
+ async addHeadRevision(cakeKey, cakeRef) {
3457
+ const cakeHeadKey = cakeKey + "Heads";
3458
+ const cakeHeadController = await this.getController(cakeHeadKey);
3459
+ return await cakeHeadController.insert("add", {
3460
+ cakeRef,
3461
+ timeId: timeId(),
3462
+ _hash: ""
3463
+ });
3464
+ }
3465
+ // ...........................................................................
3466
+ /**
3467
+ * Add a multiEdit
3468
+ * @param cakeKey - The cake table key
3469
+ * @param multiEdit - The multiEdit to add
3470
+ */
3471
+ async addMultiEdit(cakeKey, multiEdit) {
3472
+ return this.insert(
3473
+ Route.fromFlat(cakeKey + "MultiEdits"),
3474
+ {
3475
+ [cakeKey + "MultiEdits"]: {
3476
+ _data: [multiEdit],
3477
+ _type: "multiEdits"
3478
+ }
3479
+ },
3480
+ { skipHistory: true }
3481
+ );
3482
+ }
3483
+ // ...........................................................................
3484
+ /**
3485
+ * Get multiEdits
3486
+ * @param cakeKey - The cake table key
3487
+ * @param where - The where clause to filter multiEdits
3488
+ */
3489
+ async getMultiEdits(cakeKey, where) {
3490
+ const multiEditController = await this.getController(
3491
+ cakeKey + "MultiEdits"
3492
+ );
3493
+ const { [cakeKey + "MultiEdits"]: result } = await multiEditController.get(
3494
+ where
3495
+ );
3496
+ return result._data;
3497
+ }
3498
+ // ...........................................................................
3499
+ /**
3500
+ * Add an edit
3501
+ * @param cakeKey - The cake table key
3502
+ * @param edit - The edit to add
3503
+ */
3504
+ async addEdit(cakeKey, edit) {
3505
+ return this.insert(
3506
+ Route.fromFlat(cakeKey + "Edits"),
3507
+ {
3508
+ [cakeKey + "Edits"]: {
3509
+ _data: [edit],
3510
+ _type: "edits"
3511
+ }
3512
+ },
3513
+ { skipHistory: true }
3514
+ );
3515
+ }
3516
+ // ...........................................................................
3517
+ /**
3518
+ * Get edits
3519
+ * @param cakeKey - The cake table key
3520
+ * @param where - The where clause to filter edits
3521
+ */
3522
+ async getEdits(cakeKey, where) {
3523
+ const editController = await this.getController(cakeKey + "Edits");
3524
+ const { [cakeKey + "Edits"]: result } = await editController.get(where);
3525
+ return result._data;
3526
+ }
3527
+ // ...........................................................................
3528
+ /**
3529
+ * Add an edit history entry
3530
+ * @param cakeKey - The cake table key
3531
+ * @param editHistory - The edit history entry to add
3532
+ */
3533
+ async addEditHistory(cakeKey, editHistory) {
3534
+ return this.insert(
3535
+ Route.fromFlat(cakeKey + "EditHistory"),
3536
+ {
3537
+ [cakeKey + "EditHistory"]: {
3538
+ _data: [editHistory],
3539
+ _type: "editHistory"
3540
+ }
3541
+ },
3542
+ { skipHistory: true }
3543
+ );
3544
+ }
3545
+ // ...........................................................................
3546
+ /**
3547
+ * Get edit history entries
3548
+ * @param cakeKey - The cake table key
3549
+ * @param where - The where clause to filter edit history entries
3550
+ */
3551
+ async getEditHistories(cakeKey, where) {
3552
+ const editHistoryController = await this.getController(
3553
+ cakeKey + "EditHistory"
3554
+ );
3555
+ const { [cakeKey + "EditHistory"]: result } = await editHistoryController.get(where);
3556
+ return result._data;
3557
+ }
3558
+ // ...........................................................................
2904
3559
  /**
2905
3560
  * Get the InsertHistory of a table
2906
3561
  * @param table - The table to get the InsertHistory for