@rljson/db 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/db.d.ts +27 -3
  2. package/dist/db.js +622 -205
  3. package/package.json +13 -13
package/dist/db.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Io } from '@rljson/io';
2
2
  import { Json } from '@rljson/json';
3
- import { Insert, InsertHistoryRow, InsertHistoryTimeId, Ref, Rljson, Route } from '@rljson/rljson';
3
+ import { Insert, InsertHistoryRow, InsertHistoryTimeId, Ref, Rljson, Route, TableKey } from '@rljson/rljson';
4
4
  import { Controller, ControllerRefs } from './controller/controller.ts';
5
5
  import { Core } from './core.ts';
6
6
  import { Join } from './join/join.ts';
@@ -33,7 +33,21 @@ export declare class Db {
33
33
  * @throws {Error} If the route is not valid or if any controller cannot be created
34
34
  */
35
35
  get(route: Route, where: string | Json): Promise<Rljson>;
36
- _get(route: Route, where: string | Json, controllers: Record<string, Controller<any, any>>, segmentLevel?: number): Promise<Rljson>;
36
+ /**
37
+ * Resolves the route and returns corresponding data for any segment of the route,
38
+ * matching recursive filters and where clauses
39
+ *
40
+ * @param route - The route to get data from
41
+ * @param where - The recursive filtering key/value pairs to apply to the data
42
+ * @param controllers - The controllers to use for fetching data
43
+ * @param filter - Optional filter to apply to the data at the current route segment
44
+ * @returns - An Rljson object matching the route and filters
45
+ */
46
+ _get(route: Route, where: string | Json, controllers: Record<string, Controller<any, any>>, filter?: Array<{
47
+ tableKey: TableKey;
48
+ columnKey?: string;
49
+ ref: Ref;
50
+ }>): Promise<Rljson>;
37
51
  /**
38
52
  * Get the reference (hash) of a route segment, considering default refs and insertHistory refs
39
53
  * @param segment - The route segment to get the reference for
@@ -45,6 +59,15 @@ export declare class Db {
45
59
  * @param rljson - The Rljson to join data for
46
60
  */
47
61
  join(columnSelection: ColumnSelection, cakeKey: string, cakeRef: Ref): Promise<Join>;
62
+ private _resolveComponentProperties;
63
+ /**
64
+ * Resolve a component's columns, including referenced components
65
+ *
66
+ * @param baseRoute - The base route for the component
67
+ * @param componentKey - The component's table key
68
+ * @returns - The resolved column configurations, column infos, and object map
69
+ */
70
+ private _resolveComponent;
48
71
  /**
49
72
  * Fetches data for the given ColumnSelection
50
73
  * @param columnSelection - The ColumnSelection to fetch data for
@@ -58,7 +81,8 @@ export declare class Db {
58
81
  */
59
82
  insert(insert: Insert<any>, options?: {
60
83
  skipNotification?: boolean;
61
- }): Promise<InsertHistoryRow<any>>;
84
+ skipHistory?: boolean;
85
+ }): Promise<InsertHistoryRow<any>[]>;
62
86
  /**
63
87
  * Recursively runs controllers based on the route of the Insert
64
88
  * @param insert - The Insert to run
package/dist/db.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { equals, merge } from "@rljson/json";
2
2
  import { timeId, createInsertHistoryTableCfg, Validate, BaseValidator, Route, validateInsert, isTimeId } from "@rljson/rljson";
3
+ import { traverse } from "object-traversal";
3
4
  import { rmhsh, hsh, Hash, hip } from "@rljson/hash";
4
5
  import { IoMem } from "@rljson/io";
5
6
  import { compileExpression } from "filtrex";
@@ -87,7 +88,7 @@ class CakeController extends BaseController {
87
88
  if (this._table._type !== "cakes") {
88
89
  throw new Error(`Table ${this._tableKey} is not of type cakes.`);
89
90
  }
90
- if (this._refs && this._refs.base) {
91
+ if (this._refs && this._refs.base && this._refs.base.length > 0) {
91
92
  const {
92
93
  [this._tableKey]: { _data: baseCakes }
93
94
  } = await this._core.readRow(this._tableKey, this._refs.base);
@@ -102,6 +103,7 @@ class CakeController extends BaseController {
102
103
  sliceIdsTable: cake.sliceIdsTable,
103
104
  sliceIdsRow: cake.sliceIdsRow
104
105
  };
106
+ this._baseLayers = rmhsh(cake.layers);
105
107
  }
106
108
  }
107
109
  async getChildRefs(where, filter) {
@@ -126,8 +128,20 @@ class CakeController extends BaseController {
126
128
  if (!command.startsWith("add")) {
127
129
  throw new Error(`Command ${command} is not supported by CakeController.`);
128
130
  }
131
+ if (this._refs?.base) delete this._refs.base;
132
+ const normalizedValue = {};
133
+ for (const [layerTable, layerRef] of Object.entries(
134
+ value
135
+ )) {
136
+ if (Array.isArray(layerRef) && layerRef.length > 1) {
137
+ throw new Error(
138
+ `CakeController insert: Layer ref for table ${layerTable} cannot be an array of size > 1. No 1:n relations supported.`
139
+ );
140
+ }
141
+ normalizedValue[layerTable] = Array.isArray(layerRef) ? layerRef[0] : layerRef;
142
+ }
129
143
  const cake = {
130
- layers: { ...this._baseLayers, ...value },
144
+ layers: { ...this._baseLayers, ...normalizedValue },
131
145
  ...refs || this._refs
132
146
  };
133
147
  const rlJson = { [this._tableKey]: { _data: [cake] } };
@@ -141,7 +155,7 @@ class CakeController extends BaseController {
141
155
  //Unique id/timestamp
142
156
  timeId: timeId()
143
157
  };
144
- return result;
158
+ return [result];
145
159
  }
146
160
  async get(where, filter) {
147
161
  if (typeof where === "string") {
@@ -196,18 +210,49 @@ class ComponentController extends BaseController {
196
210
  if (!!refs) {
197
211
  throw new Error(`Refs are not supported on ComponentController.`);
198
212
  }
199
- const component = value;
200
- const rlJson = { [this._tableKey]: { _data: [component] } };
201
- await this._core.import(rlJson);
202
- return {
203
- //Ref to component
204
- [this._tableKey + "Ref"]: hsh(component)._hash,
205
- //Data from edit
206
- route: "",
207
- origin,
208
- //Unique id/timestamp
209
- timeId: timeId()
210
- };
213
+ const values = [];
214
+ const referencedValues = /* @__PURE__ */ new Map();
215
+ for (const [k, v] of Object.entries(value)) {
216
+ if (Array.isArray(v)) {
217
+ for (const possibleRef of v) {
218
+ if (typeof possibleRef === "object" && possibleRef.hasOwnProperty("_ref") && possibleRef.hasOwnProperty("_value")) {
219
+ const ref = possibleRef._ref;
220
+ const val = possibleRef._value;
221
+ if (!referencedValues.has(ref))
222
+ referencedValues.set(ref, { [k]: val });
223
+ else {
224
+ const existing = referencedValues.get(ref);
225
+ referencedValues.set(ref, { ...{ [k]: val }, ...existing });
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+ if (referencedValues.size > 0) {
232
+ for (const refValue of referencedValues.values()) {
233
+ values.push({ ...value, ...refValue });
234
+ }
235
+ } else {
236
+ values.push(value);
237
+ }
238
+ const components = values;
239
+ const results = [];
240
+ for (const component of components) {
241
+ delete component._somethingToInsert;
242
+ const rlJson = { [this._tableKey]: { _data: [component] } };
243
+ await this._core.import(rlJson);
244
+ const result = {
245
+ //Ref to component
246
+ [this._tableKey + "Ref"]: hsh(component)._hash,
247
+ //Data from edit
248
+ route: "",
249
+ origin,
250
+ //Unique id/timestamp
251
+ timeId: timeId()
252
+ };
253
+ results.push(result);
254
+ }
255
+ return results;
211
256
  }
212
257
  // ...........................................................................
213
258
  /**
@@ -536,12 +581,23 @@ class LayerController extends BaseController {
536
581
  `Command ${command} is not supported by LayerController.`
537
582
  );
538
583
  }
584
+ const normalizedValue = {};
585
+ for (const [sliceId, compRef] of Object.entries(
586
+ value
587
+ )) {
588
+ if (Array.isArray(compRef) && compRef.length > 1) {
589
+ throw new Error(
590
+ `LayerController insert: Component ref for slice ${sliceId} cannot be an array of size > 1. No 1:n relations supported.`
591
+ );
592
+ }
593
+ normalizedValue[sliceId] = Array.isArray(compRef) ? compRef[0] : compRef;
594
+ }
539
595
  const layer = command.startsWith("add") === true ? {
540
- add: value,
596
+ add: normalizedValue,
541
597
  ...refs || this._refs
542
598
  } : {
543
599
  add: {},
544
- remove: value,
600
+ remove: normalizedValue,
545
601
  ...refs || this._refs
546
602
  };
547
603
  const rlJson = { [this._tableKey]: { _data: [layer] } };
@@ -555,7 +611,7 @@ class LayerController extends BaseController {
555
611
  //Unique id/timestamp
556
612
  timeId: timeId()
557
613
  };
558
- return result;
614
+ return [result];
559
615
  }
560
616
  async get(where, filter) {
561
617
  if (typeof where === "string") {
@@ -975,44 +1031,134 @@ class ColumnSelection {
975
1031
  static exampleCarsColumnSelection() {
976
1032
  return new ColumnSelection([
977
1033
  {
978
- route: "carCake/carGeneralLayer/carGeneral/brand",
979
1034
  key: "brand",
1035
+ route: "carCake/carGeneralLayer/carGeneral/brand",
980
1036
  alias: "brand",
981
- titleShort: "Brand",
982
1037
  titleLong: "Car Brand",
983
- type: "string"
1038
+ titleShort: "Brand",
1039
+ type: "string",
1040
+ _hash: ""
984
1041
  },
985
1042
  {
986
- route: "carCake/carGeneralLayer/carGeneral/type",
987
1043
  key: "type",
1044
+ route: "carCake/carGeneralLayer/carGeneral/type",
988
1045
  alias: "type",
989
- titleShort: "Type",
990
1046
  titleLong: "Car Type",
991
- type: "string"
1047
+ titleShort: "Type",
1048
+ type: "string",
1049
+ _hash: ""
1050
+ },
1051
+ {
1052
+ key: "serviceIntervals",
1053
+ route: "carCake/carGeneralLayer/carGeneral/serviceIntervals",
1054
+ alias: "serviceIntervals",
1055
+ titleLong: "Car Service Intervals",
1056
+ titleShort: "Service Intervals",
1057
+ type: "jsonValue",
1058
+ _hash: ""
992
1059
  },
993
1060
  {
994
- route: "carCake/carGeneralLayer/carGeneral/isElectric",
995
1061
  key: "isElectric",
1062
+ route: "carCake/carGeneralLayer/carGeneral/isElectric",
996
1063
  alias: "isElectric",
997
- titleShort: "Is Electric",
998
- titleLong: "This Car is Electric",
999
- type: "boolean"
1064
+ titleLong: "Is Electric Car",
1065
+ titleShort: "Electric",
1066
+ type: "boolean",
1067
+ _hash: ""
1000
1068
  },
1001
1069
  {
1002
- route: "carCake/carTechnicalLayer/carTechnical/transmission",
1003
- key: "transmission",
1004
- alias: "transmission",
1005
- titleShort: "Transmission",
1006
- titleLong: "Type of Transmission",
1007
- type: "string"
1070
+ key: "carDimensions/height",
1071
+ route: "carCake/carTechnicalLayer/carTechnical/carDimensions/height",
1072
+ alias: "height",
1073
+ titleLong: "Car Height",
1074
+ titleShort: "Height",
1075
+ type: "number",
1076
+ _hash: ""
1008
1077
  },
1009
1078
  {
1010
- route: "carCake/carColorLayer/carColor/sides",
1011
- key: "sides",
1012
- alias: "sides",
1013
- titleShort: "Sides",
1014
- titleLong: "Car Sides Color",
1015
- type: "string"
1079
+ key: "carDimensions/width",
1080
+ route: "carCake/carTechnicalLayer/carTechnical/carDimensions/width",
1081
+ alias: "width",
1082
+ titleLong: "Car Width",
1083
+ titleShort: "Width",
1084
+ type: "number",
1085
+ _hash: ""
1086
+ },
1087
+ {
1088
+ key: "carDimensions/length",
1089
+ route: "carCake/carTechnicalLayer/carTechnical/carDimensions/length",
1090
+ alias: "length",
1091
+ titleLong: "Car Length",
1092
+ titleShort: "Length",
1093
+ type: "number",
1094
+ _hash: ""
1095
+ },
1096
+ {
1097
+ key: "engine",
1098
+ route: "carCake/carTechnicalLayer/carTechnical/engine",
1099
+ alias: "engine",
1100
+ titleLong: "Car Engine",
1101
+ titleShort: "Engine",
1102
+ type: "string",
1103
+ _hash: ""
1104
+ },
1105
+ {
1106
+ key: "repairedByWorkshop",
1107
+ route: "carCake/carTechnicalLayer/carTechnical/repairedByWorkshop",
1108
+ alias: "repairedByWorkshop",
1109
+ titleLong: "Was Repaired By Workshop",
1110
+ titleShort: "Repaired By Workshop",
1111
+ type: "boolean",
1112
+ _hash: ""
1113
+ }
1114
+ ]);
1115
+ }
1116
+ static exampleCarsColumnSelectionOnlySomeColumns() {
1117
+ return new ColumnSelection([
1118
+ {
1119
+ key: "brand",
1120
+ route: "carCake/carGeneralLayer/carGeneral/brand",
1121
+ alias: "brand",
1122
+ titleLong: "Car Brand",
1123
+ titleShort: "Brand",
1124
+ type: "string",
1125
+ _hash: ""
1126
+ },
1127
+ {
1128
+ key: "type",
1129
+ route: "carCake/carGeneralLayer/carGeneral/type",
1130
+ alias: "type",
1131
+ titleLong: "Car Type",
1132
+ titleShort: "Type",
1133
+ type: "string",
1134
+ _hash: ""
1135
+ },
1136
+ {
1137
+ key: "serviceIntervals",
1138
+ route: "carCake/carGeneralLayer/carGeneral/serviceIntervals",
1139
+ alias: "serviceIntervals",
1140
+ titleLong: "Car Service Intervals",
1141
+ titleShort: "Service Intervals",
1142
+ type: "jsonValue",
1143
+ _hash: ""
1144
+ },
1145
+ {
1146
+ key: "isElectric",
1147
+ route: "carCake/carGeneralLayer/carGeneral/isElectric",
1148
+ alias: "isElectric",
1149
+ titleLong: "Is Electric Car",
1150
+ titleShort: "Electric",
1151
+ type: "boolean",
1152
+ _hash: ""
1153
+ },
1154
+ {
1155
+ key: "carDimensions/length",
1156
+ route: "carCake/carTechnicalLayer/carTechnical/carDimensions/length",
1157
+ alias: "length",
1158
+ titleLong: "Car Length",
1159
+ titleShort: "Length",
1160
+ type: "number",
1161
+ _hash: ""
1016
1162
  }
1017
1163
  ]);
1018
1164
  }
@@ -1493,8 +1639,25 @@ class RowFilterProcessor {
1493
1639
  }
1494
1640
  for (const i of remainingIndices) {
1495
1641
  const cellValue = join.value(i, columnIndex);
1496
- if (filter.matches(cellValue)) {
1497
- result.push(i);
1642
+ if (Array.isArray(cellValue)) {
1643
+ for (const v of cellValue) {
1644
+ if (typeof v === "object" && v !== null && v.hasOwnProperty("_value")) {
1645
+ const matchValue = v._value;
1646
+ if (filter.matches(matchValue)) {
1647
+ result.push(i);
1648
+ break;
1649
+ }
1650
+ } else {
1651
+ if (filter.matches(v)) {
1652
+ result.push(i);
1653
+ break;
1654
+ }
1655
+ }
1656
+ }
1657
+ } else {
1658
+ if (filter.matches(cellValue)) {
1659
+ result.push(i);
1660
+ }
1498
1661
  }
1499
1662
  }
1500
1663
  return result;
@@ -1545,7 +1708,7 @@ class RowFilterProcessor {
1545
1708
  const route = item.route;
1546
1709
  if (availableRoutes.includes(route) === false) {
1547
1710
  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.
1711
+ `RowFilterProcessor: Error while applying filter to join: There is a column filter for route "${route}", but the join does not have a column with this route.
1549
1712
 
1550
1713
  Available routes:
1551
1714
  ${availableRoutes.map((a) => `- ${a}`).join("\n")}`
@@ -1555,8 +1718,9 @@ ${availableRoutes.map((a) => `- ${a}`).join("\n")}`
1555
1718
  }
1556
1719
  }
1557
1720
  class Join {
1558
- constructor(baseRows, _baseColumnSelection) {
1721
+ constructor(baseRows, _baseColumnSelection, _objectMap) {
1559
1722
  this._baseColumnSelection = _baseColumnSelection;
1723
+ this._objectMap = _objectMap;
1560
1724
  this._base = this._hashedRows(baseRows);
1561
1725
  }
1562
1726
  _base = {};
@@ -1686,16 +1850,30 @@ class Join {
1686
1850
  * Returns insert Object of the join
1687
1851
  */
1688
1852
  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;
1853
+ const cakeInserts = this._insertCakeObjects(this.data);
1854
+ const cakeInsertsMergedOfLayerRoutes = /* @__PURE__ */ new Map();
1855
+ for (const { [this.cakeRoute.root.tableKey]: cakeInsert } of cakeInserts) {
1856
+ const cakeInsertRoute = cakeInsert.route.flat;
1857
+ if (!cakeInsertsMergedOfLayerRoutes.has(cakeInsertRoute)) {
1858
+ cakeInsertsMergedOfLayerRoutes.set(cakeInsertRoute, cakeInsert.value);
1859
+ } else {
1860
+ const existingValue = cakeInsertsMergedOfLayerRoutes.get(
1861
+ cakeInsertRoute
1862
+ );
1863
+ const mergedValue = merge(existingValue, cakeInsert.value);
1864
+ cakeInsertsMergedOfLayerRoutes.set(cakeInsertRoute, mergedValue);
1865
+ }
1866
+ }
1867
+ const results = [];
1868
+ for (const [route, value] of cakeInsertsMergedOfLayerRoutes) {
1869
+ const insert = {
1870
+ command: "add",
1871
+ route,
1872
+ value
1873
+ };
1874
+ results.push(insert);
1875
+ }
1876
+ return results;
1699
1877
  }
1700
1878
  // ...........................................................................
1701
1879
  /**
@@ -1739,7 +1917,12 @@ class Join {
1739
1917
  */
1740
1918
  get layerRoutes() {
1741
1919
  return Array.from(
1742
- new Set(this.componentRoutes.map((r) => r.upper().flat))
1920
+ new Set(
1921
+ Object.values(this.columnSelection.columns).map((c) => [
1922
+ Route.fromFlat(c.route).top,
1923
+ Route.fromFlat(c.route).deeper(1).top
1924
+ ]).map((segments) => new Route(segments).flat)
1925
+ )
1743
1926
  ).map((r) => Route.fromFlat(r));
1744
1927
  }
1745
1928
  // ...........................................................................
@@ -1748,7 +1931,11 @@ class Join {
1748
1931
  */
1749
1932
  get cakeRoute() {
1750
1933
  const cakeRoute = Array.from(
1751
- new Set(this.layerRoutes.map((r) => r.upper().flat))
1934
+ new Set(
1935
+ Object.values(this.columnSelection.columns).map(
1936
+ (c) => Route.fromFlat(c.route).top.tableKey
1937
+ )
1938
+ )
1752
1939
  ).map((r) => Route.fromFlat(r));
1753
1940
  if (cakeRoute.length !== 1) {
1754
1941
  throw new Error(
@@ -1851,7 +2038,7 @@ class Join {
1851
2038
  * @param rows - The join rows
1852
2039
  * @returns The cake insert objects
1853
2040
  */
1854
- _insertCakeObject(rows) {
2041
+ _insertCakeObjects(rows) {
1855
2042
  const cakeInsertObjects = [];
1856
2043
  const cakeRoute = this.cakeRoute;
1857
2044
  for (const [sliceId, row] of Object.entries(rows)) {
@@ -1894,13 +2081,14 @@ class Join {
1894
2081
  for (const [compRouteFlat, compInsertObj] of Object.entries(
1895
2082
  insertComponentObjects
1896
2083
  )) {
2084
+ if (!compInsertObj._somethingToInsert) continue;
1897
2085
  const compRoute = Route.fromFlat(compRouteFlat);
1898
2086
  if (layerRoute.includes(compRoute)) {
1899
2087
  const layerInsertObj = {};
1900
2088
  layerInsertObj[layerRoute.root.tableKey] = {
1901
- route: compInsertObj.route,
2089
+ route: Route.fromFlat(compRouteFlat),
1902
2090
  value: {
1903
- [sliceId]: compInsertObj.value
2091
+ [sliceId]: compInsertObj
1904
2092
  }
1905
2093
  };
1906
2094
  layerInsertObjects.push(layerInsertObj);
@@ -1918,28 +2106,60 @@ class Join {
1918
2106
  * @returns
1919
2107
  */
1920
2108
  _insertComponentObjects(sliceId, insertColumns) {
1921
- const componentRoutes = this.componentRoutes;
1922
2109
  const columns = this._mergeInsertRow(sliceId, insertColumns);
2110
+ return this._denormalizeComponentInserts(columns, this._objectMap || {});
2111
+ }
2112
+ _denormalizeComponentInserts(columns, objectMap, refTableKey) {
1923
2113
  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;
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
+ };
1937
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
+ };
1938
2162
  }
1939
- result[compRoute.flat] = {
1940
- route: compRoute,
1941
- value: compChanged ? compInsert : null
1942
- };
1943
2163
  }
1944
2164
  return result;
1945
2165
  }
@@ -2097,70 +2317,114 @@ class Db {
2097
2317
  return data;
2098
2318
  }
2099
2319
  }
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
- {}
2320
+ // ...........................................................................
2321
+ /**
2322
+ * Resolves the route and returns corresponding data for any segment of the route,
2323
+ * matching recursive filters and where clauses
2324
+ *
2325
+ * @param route - The route to get data from
2326
+ * @param where - The recursive filtering key/value pairs to apply to the data
2327
+ * @param controllers - The controllers to use for fetching data
2328
+ * @param filter - Optional filter to apply to the data at the current route segment
2329
+ * @returns - An Rljson object matching the route and filters
2330
+ */
2331
+ async _get(route, where, controllers, filter) {
2332
+ const nodeTableKey = route.top.tableKey;
2333
+ const nodeRoute = route;
2334
+ const nodeRouteRef = await this._getReferenceOfRouteSegment(nodeRoute.top);
2335
+ const nodeController = controllers[nodeTableKey];
2336
+ let nodeWhere = typeof where === "object" ? { ...where } : where;
2337
+ if (!route.isRoot && typeof nodeWhere === "object") {
2338
+ delete nodeWhere[nodeRoute.deeper().top.tableKey];
2339
+ }
2340
+ nodeWhere = nodeWhere ? typeof nodeWhere === "string" ? { _hash: nodeWhere } : nodeWhere : {};
2341
+ if (nodeRouteRef && nodeRouteRef.length > 0)
2342
+ nodeWhere = { ...nodeWhere, ...{ _hash: nodeRouteRef } };
2343
+ delete nodeWhere["_through"];
2344
+ delete nodeWhere["_tableKey"];
2345
+ const {
2346
+ [nodeTableKey]: { _data: nodeRows, _type: nodeType, _hash: nodeHash }
2347
+ } = await nodeController.get(nodeWhere);
2348
+ const nodeRowsFiltered = [];
2349
+ if (filter && filter.length > 0) {
2350
+ for (const f of filter) {
2351
+ if (f.tableKey !== nodeTableKey) continue;
2352
+ for (const nodeRow of nodeRows) {
2353
+ if (nodeRow._hash === f.ref) {
2354
+ nodeRowsFiltered.push(nodeRow);
2355
+ }
2356
+ }
2357
+ }
2358
+ } else {
2359
+ nodeRowsFiltered.push(...nodeRows);
2360
+ }
2361
+ const node = {
2362
+ [nodeTableKey]: {
2363
+ _data: nodeRowsFiltered,
2364
+ _type: nodeType,
2365
+ _hash: nodeHash
2366
+ }
2367
+ };
2368
+ if (route.isRoot) {
2369
+ if (route.hasPropertyKey) {
2370
+ return this.isolatePropertyFromComponents(node, route.propertyKey);
2371
+ }
2372
+ return node;
2373
+ }
2374
+ const childrenRoute = route.deeper();
2375
+ const childrenTableKey = childrenRoute.top.tableKey;
2376
+ const childrenWhere = typeof where === "object" ? where[childrenTableKey] ?? {} : {};
2377
+ const childrenThroughProperty = childrenWhere?._through;
2378
+ const nodeChildrenArray = [];
2379
+ const nodeRowsMatchingChildrenRefs = /* @__PURE__ */ new Map();
2380
+ for (let i = 0; i < nodeRowsFiltered.length; i++) {
2381
+ const nodeRow = nodeRowsFiltered[i];
2382
+ const childrenRefs = await nodeController.getChildRefs(
2383
+ nodeRow._hash
2124
2384
  );
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
2385
+ const rowChildren = await this._get(
2386
+ childrenRoute,
2387
+ childrenWhere,
2388
+ controllers,
2389
+ childrenRefs
2390
+ );
2391
+ if (rowChildren[childrenTableKey]._data.length === 0) continue;
2392
+ nodeChildrenArray.push(rowChildren);
2393
+ if (childrenThroughProperty) {
2394
+ const childrenHashes = rowChildren[childrenTableKey]._data.map(
2395
+ (rc) => rc._hash
2132
2396
  );
2133
- children.push(child);
2134
- for (const childObjs of child[tableKey]._data) {
2135
- const childRef = childObjs["_hash"];
2136
- for (const row of parent[segment.tableKey]._data) {
2137
- if (filteredParentRows.has(row["_hash"]))
2138
- continue;
2139
- const includesChild = segmentController.filterRow(
2140
- row,
2141
- columnKey ?? tableKey,
2142
- childRef
2143
- );
2144
- if (includesChild) {
2145
- filteredParentRows.set(
2146
- row["_hash"],
2147
- row
2148
- );
2397
+ for (const nr of nodeRowsFiltered) {
2398
+ {
2399
+ const throughHashesInRowCouldBeArray = nr[childrenThroughProperty];
2400
+ const throughHashesInRow = Array.isArray(
2401
+ throughHashesInRowCouldBeArray
2402
+ ) ? throughHashesInRowCouldBeArray : [throughHashesInRowCouldBeArray];
2403
+ for (const th of throughHashesInRow) {
2404
+ if (childrenHashes.includes(th)) {
2405
+ nodeRowsMatchingChildrenRefs.set(nr._hash, nr);
2406
+ }
2149
2407
  }
2150
2408
  }
2151
2409
  }
2152
2410
  }
2153
- const parentWithFilteredRows = {
2154
- [segment.tableKey]: {
2155
- ...parent[segment.tableKey],
2156
- ...{
2157
- _data: Array.from(filteredParentRows.values())
2411
+ }
2412
+ const nodeChildren = merge(...nodeChildrenArray);
2413
+ if (childrenThroughProperty) {
2414
+ const matchedNodeRows = Array.from(nodeRowsMatchingChildrenRefs.values());
2415
+ return {
2416
+ ...node,
2417
+ ...{
2418
+ [nodeTableKey]: {
2419
+ _data: matchedNodeRows,
2420
+ _type: nodeType,
2421
+ _hash: nodeHash
2158
2422
  }
2159
- }
2423
+ },
2424
+ ...nodeChildren
2160
2425
  };
2161
- return merge(parentWithFilteredRows, ...children);
2162
2426
  }
2163
- return parent;
2427
+ return { ...node, ...nodeChildren };
2164
2428
  }
2165
2429
  // ...........................................................................
2166
2430
  /**
@@ -2204,6 +2468,11 @@ class Db {
2204
2468
  );
2205
2469
  layers.set(layerKey, layer);
2206
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
+ }
2207
2476
  const mergedSliceIds = /* @__PURE__ */ new Set();
2208
2477
  for (const layer of layers.values()) {
2209
2478
  const sliceIdsTable = layer.sliceIdsTable;
@@ -2219,52 +2488,59 @@ class Db {
2219
2488
  }
2220
2489
  const columnCfgs = /* @__PURE__ */ new Map();
2221
2490
  const columnInfos = /* @__PURE__ */ new Map();
2491
+ let objectMap = {};
2222
2492
  for (const [layerKey, layer] of layers.entries()) {
2223
2493
  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);
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);
2243
2501
  }
2244
2502
  const rowMap = /* @__PURE__ */ new Map();
2503
+ const joinColumnInfos = /* @__PURE__ */ new Map();
2245
2504
  for (const sliceId of mergedSliceIds) {
2246
- let sliceIdRow = [];
2505
+ const sliceIdRow = [];
2247
2506
  for (const [layerKey, layer] of layers.entries()) {
2248
2507
  const layerRef = layer._hash;
2249
2508
  const componentKey = layer.componentsTable;
2250
2509
  const componentRef = layer.add[sliceId];
2251
2510
  const componentsTable = data[componentKey];
2252
- const component = componentsTable._data.find(
2511
+ const rowComponentProperties = componentsTable._data.find(
2253
2512
  (r) => r._hash === componentRef
2254
2513
  );
2255
- const colCfgs = columnCfgs.get(componentKey);
2514
+ const resolvedProperties = this._resolveComponentProperties(
2515
+ rowComponentProperties,
2516
+ objectMap,
2517
+ data
2518
+ );
2256
2519
  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
- });
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
+ }
2266
2542
  }
2267
- sliceIdRow = [...sliceIdRow, ...joinColumns];
2543
+ sliceIdRow.push(...joinColumns);
2268
2544
  }
2269
2545
  rowMap.set(sliceId, sliceIdRow);
2270
2546
  }
@@ -2274,9 +2550,111 @@ class Db {
2274
2550
  [sliceId]: joinColumns
2275
2551
  });
2276
2552
  }
2277
- const joinColumnInfos = Array.from(columnInfos.values()).flat();
2278
- const joinColumnSelection = new ColumnSelection(joinColumnInfos);
2279
- return new Join(joinRows, joinColumnSelection).select(columnSelection);
2553
+ return new Join(
2554
+ joinRows,
2555
+ new ColumnSelection(Array.from(joinColumnInfos.values())),
2556
+ objectMap
2557
+ ).select(columnSelection);
2558
+ }
2559
+ _resolveComponentProperties(componentData, objectMapOrRoute, baseData) {
2560
+ let result = {};
2561
+ for (const [propertyKey, propertyObjectMap] of Object.entries(
2562
+ objectMapOrRoute
2563
+ )) {
2564
+ if (propertyKey === "_tableKey") continue;
2565
+ if (typeof propertyObjectMap === "object") {
2566
+ const refs = Array.isArray(componentData[propertyKey]) ? componentData[propertyKey] : [componentData[propertyKey]];
2567
+ const refTableKey = propertyObjectMap["_tableKey"];
2568
+ const refCompTable = baseData[refTableKey];
2569
+ if (!refCompTable) continue;
2570
+ const prefixedResolvedRefCompData = {};
2571
+ for (const ref of refs) {
2572
+ const refCompData = refCompTable._data.find((r) => r._hash === ref);
2573
+ if (refCompData) {
2574
+ const resolvedRefCompData = this._resolveComponentProperties(
2575
+ refCompData,
2576
+ propertyObjectMap,
2577
+ baseData
2578
+ );
2579
+ for (const [refPropKey, value] of Object.entries(
2580
+ resolvedRefCompData
2581
+ )) {
2582
+ if (!prefixedResolvedRefCompData[`${refTableKey}/${refPropKey}`]) {
2583
+ prefixedResolvedRefCompData[`${refTableKey}/${refPropKey}`] = [];
2584
+ }
2585
+ prefixedResolvedRefCompData[`${refTableKey}/${refPropKey}`].push({
2586
+ _ref: ref,
2587
+ _value: value
2588
+ });
2589
+ }
2590
+ }
2591
+ result = {
2592
+ ...result,
2593
+ ...prefixedResolvedRefCompData
2594
+ };
2595
+ }
2596
+ }
2597
+ result = {
2598
+ ...result,
2599
+ ...{ [propertyKey]: componentData[propertyKey] }
2600
+ };
2601
+ }
2602
+ return result;
2603
+ }
2604
+ // ...........................................................................
2605
+ /**
2606
+ * Resolve a component's columns, including referenced components
2607
+ *
2608
+ * @param baseRoute - The base route for the component
2609
+ * @param componentKey - The component's table key
2610
+ * @returns - The resolved column configurations, column infos, and object map
2611
+ */
2612
+ async _resolveComponent(baseRoute, componentKey) {
2613
+ const { columns: colCfgs } = await this.core.tableCfg(componentKey);
2614
+ const objectMap = {};
2615
+ const columnCfgs = [];
2616
+ const columnInfos = [];
2617
+ for (let i = 0; i < colCfgs.length; i++) {
2618
+ if (colCfgs[i].key === "_hash") continue;
2619
+ const colCfg = colCfgs[i];
2620
+ if (colCfg.ref) {
2621
+ const columnCfgsAndInfosForRef = await this._resolveComponent(
2622
+ baseRoute + `/${componentKey}`,
2623
+ colCfg.ref.tableKey
2624
+ );
2625
+ objectMap[colCfg.key] = {
2626
+ _tableKey: colCfg.ref.tableKey,
2627
+ ...columnCfgsAndInfosForRef.objectMap
2628
+ };
2629
+ const columnCfgsForRef = columnCfgsAndInfosForRef.columnCfgs.map(
2630
+ (cc) => ({
2631
+ ...cc,
2632
+ key: colCfg.ref.tableKey + "/" + cc.key
2633
+ })
2634
+ );
2635
+ const columnInfosForRef = columnCfgsAndInfosForRef.columnInfos.map(
2636
+ (cc) => ({
2637
+ ...cc,
2638
+ key: colCfg.ref.tableKey + "/" + cc.key
2639
+ })
2640
+ );
2641
+ columnCfgs.push(...columnCfgsForRef);
2642
+ columnInfos.push(...columnInfosForRef);
2643
+ }
2644
+ const columnRoute = Route.fromFlat(
2645
+ baseRoute.length > 0 ? `${baseRoute}/${componentKey}/${colCfg.key}` : `/${componentKey}/${colCfg.key}`
2646
+ ).flat.slice(1);
2647
+ if (!objectMap[colCfg.key]) objectMap[colCfg.key] = columnRoute;
2648
+ columnCfgs.push(colCfg);
2649
+ columnInfos.push({
2650
+ ...colCfg,
2651
+ alias: `${colCfg.key}`,
2652
+ route: columnRoute,
2653
+ titleShort: colCfg.key,
2654
+ titleLong: colCfg.key
2655
+ });
2656
+ }
2657
+ return { columnCfgs, columnInfos, objectMap };
2280
2658
  }
2281
2659
  // ...........................................................................
2282
2660
  /**
@@ -2286,8 +2664,13 @@ class Db {
2286
2664
  async _getBaseDataForColumnSelection(columnSelection) {
2287
2665
  const uniqueComponentRoutes = /* @__PURE__ */ new Set();
2288
2666
  for (const colInfo of columnSelection.columns) {
2289
- const route = Route.fromFlat(colInfo.route).toRouteWithProperty();
2290
- uniqueComponentRoutes.add(route.toRouteWithoutProperty().flat);
2667
+ const componentRoute = Route.fromFlat(colInfo.route);
2668
+ const isolatedComponentRoute = await this.isolatePropertyKeyFromRoute(
2669
+ componentRoute
2670
+ );
2671
+ uniqueComponentRoutes.add(
2672
+ isolatedComponentRoute.toRouteWithoutProperty().flat
2673
+ );
2291
2674
  }
2292
2675
  const data = {};
2293
2676
  for (const compRouteFlat of uniqueComponentRoutes) {
@@ -2326,7 +2709,7 @@ ${JSON.stringify(errors, null, 2)}`
2326
2709
  * @throws {Error} If the route is not valid or if any controller cannot be created
2327
2710
  */
2328
2711
  async _insert(insert, route, runFns, options) {
2329
- let result;
2712
+ let results;
2330
2713
  let tableKey;
2331
2714
  const segment = route.segment(0);
2332
2715
  tableKey = segment.tableKey;
@@ -2351,36 +2734,63 @@ ${JSON.stringify(errors, null, 2)}`
2351
2734
  for (const k of childKeys) {
2352
2735
  const childValue = insert.value[k];
2353
2736
  const childInsert = { ...insert, value: childValue };
2354
- const childResult = await this._insert(childInsert, childRoute, runFns);
2737
+ const childResults = await this._insert(
2738
+ childInsert,
2739
+ childRoute,
2740
+ runFns
2741
+ );
2355
2742
  const childRefKey = childRoute.top.tableKey + "Ref";
2356
- const childRef = childResult[childRefKey];
2357
- childRefs[k] = childRef;
2743
+ const childRefArray = childResults.map(
2744
+ (childResult) => childResult[childRefKey]
2745
+ );
2746
+ childRefs[k] = childRefArray;
2358
2747
  }
2359
2748
  const runFn = runFns[tableKey];
2360
- result = {
2361
- ...await runFn(
2749
+ results = [
2750
+ ...(await runFn(
2362
2751
  insert.command,
2363
2752
  {
2364
2753
  ...insert.value,
2365
2754
  ...childRefs
2366
2755
  },
2367
2756
  insert.origin
2368
- ),
2369
- previous
2370
- };
2757
+ )).map((r) => ({ ...r, ...{ previous } }))
2758
+ ];
2371
2759
  } else {
2372
2760
  tableKey = route.root.tableKey;
2373
2761
  const runFn = runFns[tableKey];
2374
- result = {
2375
- ...await runFn(insert.command, insert.value, insert.origin),
2376
- previous
2377
- };
2762
+ const insertValue = insert.value;
2763
+ for (const [propertyKey, propertyValue] of Object.entries(insert.value)) {
2764
+ if (propertyValue && typeof propertyValue === "object" && !!propertyValue._tableKey) {
2765
+ const referenceRoute = propertyValue._tableKey;
2766
+ delete propertyValue._tableKey;
2767
+ const referenceInsert = {
2768
+ command: insert.command,
2769
+ route: referenceRoute,
2770
+ value: propertyValue
2771
+ };
2772
+ const referencesWritten = (await this._insert(
2773
+ referenceInsert,
2774
+ Route.fromFlat(referenceRoute),
2775
+ runFns
2776
+ )).map((h) => h[referenceRoute + "Ref"]);
2777
+ insertValue[propertyKey] = referencesWritten.length === 1 ? referencesWritten[0] : referencesWritten;
2778
+ }
2779
+ }
2780
+ results = [
2781
+ ...(await runFn(insert.command, insertValue, insert.origin)).map(
2782
+ (r) => ({ ...r, previous })
2783
+ )
2784
+ ];
2378
2785
  }
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;
2786
+ for (const result of results) {
2787
+ result.route = insert.route;
2788
+ if (!options?.skipHistory)
2789
+ await this._writeInsertHistory(tableKey, result);
2790
+ if (!options?.skipNotification)
2791
+ this.notify.notify(Route.fromFlat(insert.route), result);
2792
+ }
2793
+ return results;
2384
2794
  }
2385
2795
  // ...........................................................................
2386
2796
  /**
@@ -2411,6 +2821,14 @@ ${JSON.stringify(errors, null, 2)}`
2411
2821
  const controllers = await this._indexedControllers(
2412
2822
  Route.fromFlat(Insert2.route)
2413
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
+ }
2414
2832
  const runFns = {};
2415
2833
  for (const tableKey of Object.keys(controllers)) {
2416
2834
  runFns[tableKey] = controllers[tableKey].insert.bind(
@@ -2500,15 +2918,9 @@ ${JSON.stringify(errors, null, 2)}`
2500
2918
  if (options.sorted) {
2501
2919
  const dumpedTable = await this.core.dumpTable(insertHistoryTable);
2502
2920
  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
- });
2921
+ tableData.sort(
2922
+ (a, b) => options.ascending ? a.timeId.localeCompare(b.timeId) : b.timeId.localeCompare(a.timeId)
2923
+ );
2512
2924
  return {
2513
2925
  [insertHistoryTable]: { _data: tableData, _type: "insertHistory" }
2514
2926
  };
@@ -2591,15 +3003,20 @@ ${JSON.stringify(errors, null, 2)}`
2591
3003
  * @returns A route with extracted property key
2592
3004
  */
2593
3005
  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;
3006
+ const segmentLength = route.segments.length;
3007
+ let propertyKey = "";
3008
+ let result = route;
3009
+ for (let i = segmentLength; i > 0; i--) {
3010
+ const segment = route.segments[i - 1];
3011
+ const tableKey = segment.tableKey;
3012
+ const tableExists = await this._io.tableExists(tableKey);
3013
+ if (!tableExists) {
3014
+ propertyKey = propertyKey.length > 0 ? segment.tableKey + "/" + propertyKey : segment.tableKey;
3015
+ result = result.upper();
3016
+ result.propertyKey = propertyKey;
3017
+ }
3018
+ }
3019
+ return result;
2603
3020
  }
2604
3021
  // ...........................................................................
2605
3022
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/db",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "A high level interface to read and write RLJSON data from and into a database",
5
5
  "homepage": "https://github.com/rljson/db",
6
6
  "bugs": "https://github.com/rljson/db/issues",
@@ -20,31 +20,31 @@
20
20
  ],
21
21
  "type": "module",
22
22
  "devDependencies": {
23
- "@types/node": "^24.10.0",
24
- "@typescript-eslint/eslint-plugin": "^8.46.3",
25
- "@typescript-eslint/parser": "^8.46.3",
26
- "@vitest/coverage-v8": "^4.0.7",
23
+ "@types/node": "^24.10.1",
24
+ "@typescript-eslint/eslint-plugin": "^8.46.4",
25
+ "@typescript-eslint/parser": "^8.46.4",
26
+ "@vitest/coverage-v8": "^4.0.8",
27
27
  "cross-env": "^10.1.0",
28
28
  "eslint": "^9.39.1",
29
29
  "eslint-plugin-jsdoc": "^61.1.12",
30
- "eslint-plugin-tsdoc": "^0.4.0",
30
+ "eslint-plugin-tsdoc": "^0.5.0",
31
31
  "globals": "^16.5.0",
32
32
  "jsdoc": "^4.0.5",
33
- "read-pkg": "^9.0.1",
33
+ "read-pkg": "^10.0.0",
34
34
  "typescript": "~5.9.3",
35
- "typescript-eslint": "^8.46.3",
36
- "vite": "^7.2.0",
37
- "vite-node": "^3.2.4",
35
+ "typescript-eslint": "^8.46.4",
36
+ "vite": "^7.2.2",
37
+ "vite-node": "^5.0.0",
38
38
  "vite-plugin-dts": "^4.5.4",
39
39
  "vite-tsconfig-paths": "^5.1.4",
40
- "vitest": "^4.0.7",
40
+ "vitest": "^4.0.8",
41
41
  "vitest-dom": "^0.1.1"
42
42
  },
43
43
  "dependencies": {
44
44
  "@rljson/hash": "^0.0.17",
45
- "@rljson/io": "^0.0.53",
45
+ "@rljson/io": "^0.0.55",
46
46
  "@rljson/json": "^0.0.23",
47
- "@rljson/rljson": "^0.0.67",
47
+ "@rljson/rljson": "^0.0.69",
48
48
  "@rljson/validate": "^0.0.11",
49
49
  "filtrex": "^3.1.0",
50
50
  "object-traversal": "^1.0.1",