@rljson/rljson 0.0.73 → 0.0.75
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/content/tree.d.ts +53 -0
- package/dist/example/bakery-example.d.ts +2 -0
- package/dist/example.d.ts +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/rljson.d.ts +2 -2
- package/dist/rljson.js +311 -35
- package/dist/src/example.ts +57 -0
- package/dist/tools/time-id.d.ts +5 -4
- package/dist/typedefs.d.ts +1 -1
- package/dist/validate/base-validator.d.ts +3 -0
- package/package.json +14 -14
- package/dist/content/head.d.ts +0 -10
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Json } from '@rljson/json';
|
|
2
|
+
import { RljsonTable } from '../rljson.ts';
|
|
3
|
+
import { Ref } from '../typedefs.ts';
|
|
4
|
+
import { TableCfg } from './table-cfg.ts';
|
|
5
|
+
/**
|
|
6
|
+
* A TreeRef is a hash pointing to another hash in the tree
|
|
7
|
+
*/
|
|
8
|
+
export type TreeRef = Ref;
|
|
9
|
+
/**
|
|
10
|
+
* A Tree is a hierarchical structure of Trees
|
|
11
|
+
*/
|
|
12
|
+
export interface Tree extends Json {
|
|
13
|
+
/**
|
|
14
|
+
* `id` identifies the tree node, it has to be unique among sibling nodes
|
|
15
|
+
*/
|
|
16
|
+
id?: string;
|
|
17
|
+
/**
|
|
18
|
+
* If `isParent` is true, this node is a parent node and can have children
|
|
19
|
+
*/
|
|
20
|
+
isParent: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Optional meta information about this tree node
|
|
23
|
+
*/
|
|
24
|
+
meta: Json | null;
|
|
25
|
+
/**
|
|
26
|
+
* The children of this tree node
|
|
27
|
+
*/
|
|
28
|
+
children: Array<TreeRef> | null;
|
|
29
|
+
}
|
|
30
|
+
export type TreeWithHash = Tree & {
|
|
31
|
+
_hash: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* A table containing trees
|
|
35
|
+
*/
|
|
36
|
+
export type TreesTable = RljsonTable<Tree, 'trees'>;
|
|
37
|
+
/**
|
|
38
|
+
* Creates a TableCfg for Trees tables
|
|
39
|
+
* @param treesTableKey - The table key of the trees table
|
|
40
|
+
* @returns A TableCfg for Trees tables
|
|
41
|
+
*/
|
|
42
|
+
export declare const createTreesTableCfg: (treesTableKey: string) => TableCfg;
|
|
43
|
+
/**
|
|
44
|
+
* Provides an example treesTable for test purposes
|
|
45
|
+
*/
|
|
46
|
+
export declare const exampleTreesTable: () => TreesTable;
|
|
47
|
+
/**
|
|
48
|
+
* Converts a plain object into a tree structure
|
|
49
|
+
* @param obj - The plain object to convert
|
|
50
|
+
* @param skipRootCreation - If true, skips creating an automatic root node (default: false)
|
|
51
|
+
* @returns An array of Tree nodes representing the tree structure
|
|
52
|
+
*/
|
|
53
|
+
export declare const treeFromObject: (obj: any, skipRootCreation?: boolean) => TreeWithHash[];
|
|
@@ -4,6 +4,7 @@ import { CakesTable } from '../content/cake.ts';
|
|
|
4
4
|
import { ComponentsTable } from '../content/components.ts';
|
|
5
5
|
import { LayersTable } from '../content/layer.ts';
|
|
6
6
|
import { SliceIdsTable } from '../content/slice-ids.ts';
|
|
7
|
+
import { TreesTable } from '../content/tree.ts';
|
|
7
8
|
import { InsertHistoryTable } from '../insertHistory/insertHistory.ts';
|
|
8
9
|
import { Rljson } from '../rljson.ts';
|
|
9
10
|
import { Ref } from '../typedefs.ts';
|
|
@@ -33,5 +34,6 @@ export interface Bakery extends Rljson {
|
|
|
33
34
|
ingredients: ComponentsTable<Ingredient>;
|
|
34
35
|
nutritionalValues: ComponentsTable<NutritionalValues>;
|
|
35
36
|
ingredientsInsertHistory: InsertHistoryTable<'Ingredients'>;
|
|
37
|
+
recipesTreeTable: TreesTable;
|
|
36
38
|
}
|
|
37
39
|
export declare const bakeryExample: () => Bakery;
|
package/dist/example.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare class Example {
|
|
|
10
10
|
multiRef: () => Rljson;
|
|
11
11
|
singleSliceIdRef: () => Rljson;
|
|
12
12
|
multiSliceIdRef: () => Rljson;
|
|
13
|
+
tree: () => Rljson;
|
|
13
14
|
complete: () => Rljson;
|
|
14
15
|
};
|
|
15
16
|
static readonly broken: {
|
|
@@ -30,6 +31,12 @@ export declare class Example {
|
|
|
30
31
|
tableCfg: {
|
|
31
32
|
wrongType: () => Rljson;
|
|
32
33
|
};
|
|
34
|
+
trees: {
|
|
35
|
+
missingChildNodes: () => Rljson;
|
|
36
|
+
cyclicTree: () => Rljson;
|
|
37
|
+
duplicateChildNodeIds: () => Rljson;
|
|
38
|
+
nonParentWithChildren: () => Rljson;
|
|
39
|
+
};
|
|
33
40
|
layers: {
|
|
34
41
|
missingBase: () => Rljson;
|
|
35
42
|
missingSliceIdSet: () => Rljson;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export * from './content/buffet.ts';
|
|
2
2
|
export * from './content/cake.ts';
|
|
3
3
|
export * from './content/components.ts';
|
|
4
|
-
export * from './content/head.ts';
|
|
5
4
|
export * from './content/layer.ts';
|
|
6
5
|
export * from './content/revision.ts';
|
|
7
6
|
export * from './content/slice-ids.ts';
|
|
8
7
|
export * from './content/table-cfg.ts';
|
|
8
|
+
export * from './content/tree.ts';
|
|
9
9
|
export * from './edit/edit-history.ts';
|
|
10
10
|
export * from './edit/edit.ts';
|
|
11
11
|
export * from './edit/multi-edit.ts';
|
package/dist/rljson.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { Json } from '@rljson/json';
|
|
|
2
2
|
import { BuffetsTable } from './content/buffet.ts';
|
|
3
3
|
import { CakesTable } from './content/cake.ts';
|
|
4
4
|
import { ComponentsTable } from './content/components.ts';
|
|
5
|
-
import { HeadsTable } from './content/head.ts';
|
|
6
5
|
import { LayersTable } from './content/layer.ts';
|
|
7
6
|
import { RevisionsTable } from './content/revision.ts';
|
|
8
7
|
import { SliceIdsTable } from './content/slice-ids.ts';
|
|
9
8
|
import { TableCfgRef, TablesCfgTable } from './content/table-cfg.ts';
|
|
9
|
+
import { TreesTable } from './content/tree.ts';
|
|
10
10
|
import { EditHistoryTable } from './edit/edit-history.ts';
|
|
11
11
|
import { EditsTable } from './edit/edit.ts';
|
|
12
12
|
import { MultiEditsTable } from './edit/multi-edit.ts';
|
|
@@ -17,7 +17,7 @@ export declare const reservedTableKeys: string[];
|
|
|
17
17
|
/**
|
|
18
18
|
* One of the supported Rljson table types
|
|
19
19
|
*/
|
|
20
|
-
export type TableType = BuffetsTable | ComponentsTable<any> | LayersTable | SliceIdsTable | CakesTable | RevisionsTable | TablesCfgTable | InsertHistoryTable<any> | EditsTable | MultiEditsTable | EditHistoryTable |
|
|
20
|
+
export type TableType = BuffetsTable | ComponentsTable<any> | LayersTable | SliceIdsTable | CakesTable | RevisionsTable | TablesCfgTable | InsertHistoryTable<any> | EditsTable | MultiEditsTable | EditHistoryTable | TreesTable;
|
|
21
21
|
/** The rljson data format */
|
|
22
22
|
export interface Rljson extends Json {
|
|
23
23
|
[tableId: TableKey]: TableType;
|
package/dist/rljson.js
CHANGED
|
@@ -154,7 +154,7 @@ class Route {
|
|
|
154
154
|
* @returns True if the current route is the root route, false otherwise
|
|
155
155
|
*/
|
|
156
156
|
get isRoot() {
|
|
157
|
-
return this._segments.length
|
|
157
|
+
return this._segments.length <= 1;
|
|
158
158
|
}
|
|
159
159
|
// .............................................................................
|
|
160
160
|
/**
|
|
@@ -486,6 +486,23 @@ const bakeryExample = () => {
|
|
|
486
486
|
],
|
|
487
487
|
_hash: ""
|
|
488
488
|
});
|
|
489
|
+
const recipesTreeChildren = hip({
|
|
490
|
+
id: "tastyCake",
|
|
491
|
+
isParent: false,
|
|
492
|
+
meta: { description: "A tasty cake recipe" },
|
|
493
|
+
children: null
|
|
494
|
+
});
|
|
495
|
+
const recipesTreeRoot = hip({
|
|
496
|
+
id: "root",
|
|
497
|
+
isParent: true,
|
|
498
|
+
meta: { description: "Root of the recipes tree" },
|
|
499
|
+
children: [recipesTreeChildren._hash]
|
|
500
|
+
});
|
|
501
|
+
const recipesTreeTable = hip({
|
|
502
|
+
_type: "trees",
|
|
503
|
+
_data: [recipesTreeRoot, recipesTreeChildren],
|
|
504
|
+
_hash: ""
|
|
505
|
+
});
|
|
489
506
|
const result = {
|
|
490
507
|
buffets,
|
|
491
508
|
cakes,
|
|
@@ -496,7 +513,8 @@ const bakeryExample = () => {
|
|
|
496
513
|
recipeIngredients,
|
|
497
514
|
ingredients,
|
|
498
515
|
nutritionalValues,
|
|
499
|
-
ingredientsInsertHistory
|
|
516
|
+
ingredientsInsertHistory,
|
|
517
|
+
recipesTreeTable
|
|
500
518
|
};
|
|
501
519
|
return result;
|
|
502
520
|
};
|
|
@@ -527,36 +545,6 @@ const createCakeTableCfg = (cakeKey) => ({
|
|
|
527
545
|
});
|
|
528
546
|
const exampleCakesTable = () => bakeryExample().cakes;
|
|
529
547
|
const exampleComponentsTable = () => bakeryExample().nutritionalValues;
|
|
530
|
-
const createHeadsTableCfg = (cakeKey) => ({
|
|
531
|
-
key: `${cakeKey}Heads`,
|
|
532
|
-
type: "head",
|
|
533
|
-
columns: [
|
|
534
|
-
{
|
|
535
|
-
key: "_hash",
|
|
536
|
-
type: "string",
|
|
537
|
-
titleLong: "Hash",
|
|
538
|
-
titleShort: "Hash"
|
|
539
|
-
},
|
|
540
|
-
{
|
|
541
|
-
key: "timeId",
|
|
542
|
-
type: "string",
|
|
543
|
-
titleLong: "Time Identifier",
|
|
544
|
-
titleShort: "Time ID"
|
|
545
|
-
},
|
|
546
|
-
{
|
|
547
|
-
key: "cakeRef",
|
|
548
|
-
type: "string",
|
|
549
|
-
titleLong: "Cake Reference",
|
|
550
|
-
titleShort: "Cake Ref",
|
|
551
|
-
ref: {
|
|
552
|
-
tableKey: `${cakeKey}`
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
],
|
|
556
|
-
isHead: false,
|
|
557
|
-
isRoot: false,
|
|
558
|
-
isShared: true
|
|
559
|
-
});
|
|
560
548
|
const createLayerTableCfg = (layerKey) => ({
|
|
561
549
|
key: layerKey,
|
|
562
550
|
type: "layers",
|
|
@@ -625,6 +613,140 @@ const createSliceIdsTableCfg = (tableKey) => ({
|
|
|
625
613
|
isRoot: false,
|
|
626
614
|
isShared: true
|
|
627
615
|
});
|
|
616
|
+
const createTreesTableCfg = (treesTableKey) => ({
|
|
617
|
+
key: treesTableKey,
|
|
618
|
+
type: "trees",
|
|
619
|
+
columns: [
|
|
620
|
+
{ key: "_hash", type: "string", titleLong: "Hash", titleShort: "Hash" },
|
|
621
|
+
{ key: "id", type: "string", titleLong: "Identifier", titleShort: "Id" },
|
|
622
|
+
{
|
|
623
|
+
key: "isParent",
|
|
624
|
+
type: "boolean",
|
|
625
|
+
titleLong: "Is Parent",
|
|
626
|
+
titleShort: "Is Parent"
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
key: "meta",
|
|
630
|
+
type: "json",
|
|
631
|
+
titleLong: "Meta Information",
|
|
632
|
+
titleShort: "Meta"
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
key: "children",
|
|
636
|
+
type: "jsonArray",
|
|
637
|
+
titleLong: "Children",
|
|
638
|
+
titleShort: "Children"
|
|
639
|
+
}
|
|
640
|
+
],
|
|
641
|
+
isHead: false,
|
|
642
|
+
isRoot: false,
|
|
643
|
+
isShared: true
|
|
644
|
+
});
|
|
645
|
+
const exampleTreesTable = () => bakeryExample().recipesTreeTable;
|
|
646
|
+
const treeFromObject = (obj, skipRootCreation = false) => {
|
|
647
|
+
const result = [];
|
|
648
|
+
const processedIds = /* @__PURE__ */ new Set();
|
|
649
|
+
const idToHashMap = /* @__PURE__ */ new Map();
|
|
650
|
+
const processNode = (value, nodeId) => {
|
|
651
|
+
if (processedIds.has(nodeId)) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
processedIds.add(nodeId);
|
|
655
|
+
const childIds = [];
|
|
656
|
+
if (Array.isArray(value)) {
|
|
657
|
+
const isChildrenArray = value.length === 0 || value.every(
|
|
658
|
+
(item) => item !== null && typeof item === "object" && !Array.isArray(item) && Object.keys(item).length === 1
|
|
659
|
+
);
|
|
660
|
+
if (isChildrenArray) {
|
|
661
|
+
for (const item of value) {
|
|
662
|
+
const keys = Object.keys(item);
|
|
663
|
+
const childId = keys[0];
|
|
664
|
+
childIds.push(childId);
|
|
665
|
+
processNode(item[childId], childId);
|
|
666
|
+
}
|
|
667
|
+
const treeNode = {
|
|
668
|
+
id: nodeId,
|
|
669
|
+
isParent: true,
|
|
670
|
+
meta: null,
|
|
671
|
+
children: childIds.length > 0 ? childIds.map((id) => idToHashMap.get(id)) : null
|
|
672
|
+
};
|
|
673
|
+
const hashedNode = hip(treeNode);
|
|
674
|
+
idToHashMap.set(nodeId, hashedNode._hash);
|
|
675
|
+
result.push(hashedNode);
|
|
676
|
+
} else {
|
|
677
|
+
const treeNode = {
|
|
678
|
+
id: nodeId,
|
|
679
|
+
isParent: false,
|
|
680
|
+
meta: { value },
|
|
681
|
+
children: null
|
|
682
|
+
};
|
|
683
|
+
const hashedNode = hip(treeNode);
|
|
684
|
+
idToHashMap.set(nodeId, hashedNode._hash);
|
|
685
|
+
result.push(hashedNode);
|
|
686
|
+
}
|
|
687
|
+
} else if (value !== null && typeof value === "object") {
|
|
688
|
+
const keys = Object.keys(value);
|
|
689
|
+
if (keys.includes("meta") || keys.includes("isParent") || keys.includes("children")) {
|
|
690
|
+
const treeNode = {
|
|
691
|
+
id: nodeId,
|
|
692
|
+
isParent: false,
|
|
693
|
+
meta: value.meta,
|
|
694
|
+
children: null
|
|
695
|
+
};
|
|
696
|
+
const hashedNode = hip(treeNode);
|
|
697
|
+
idToHashMap.set(nodeId, hashedNode._hash);
|
|
698
|
+
result.push(hashedNode);
|
|
699
|
+
} else {
|
|
700
|
+
for (const key in value) {
|
|
701
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
702
|
+
childIds.push(key);
|
|
703
|
+
processNode(value[key], key);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const treeNode = {
|
|
707
|
+
id: nodeId,
|
|
708
|
+
isParent: true,
|
|
709
|
+
meta: null,
|
|
710
|
+
children: childIds.length > 0 ? childIds.map((id) => idToHashMap.get(id)) : null
|
|
711
|
+
};
|
|
712
|
+
const hashedNode = hip(treeNode);
|
|
713
|
+
idToHashMap.set(nodeId, hashedNode._hash);
|
|
714
|
+
result.push(hashedNode);
|
|
715
|
+
}
|
|
716
|
+
} else {
|
|
717
|
+
const treeNode = {
|
|
718
|
+
id: nodeId,
|
|
719
|
+
isParent: false,
|
|
720
|
+
meta: { value },
|
|
721
|
+
children: null
|
|
722
|
+
};
|
|
723
|
+
const hashedNode = hip(treeNode);
|
|
724
|
+
idToHashMap.set(nodeId, hashedNode._hash);
|
|
725
|
+
result.push(hashedNode);
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const topLevelIds = [];
|
|
729
|
+
if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
|
|
730
|
+
for (const key in obj) {
|
|
731
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
732
|
+
topLevelIds.push(key);
|
|
733
|
+
processNode(obj[key], key);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (!skipRootCreation && topLevelIds.length > 0) {
|
|
738
|
+
const rootNode = {
|
|
739
|
+
id: "root",
|
|
740
|
+
isParent: true,
|
|
741
|
+
meta: null,
|
|
742
|
+
children: topLevelIds.map((id) => idToHashMap.get(id))
|
|
743
|
+
};
|
|
744
|
+
const hashedRootNode = hip(rootNode);
|
|
745
|
+
idToHashMap.set("root", hashedRootNode._hash);
|
|
746
|
+
result.push(hashedRootNode);
|
|
747
|
+
}
|
|
748
|
+
return result;
|
|
749
|
+
};
|
|
628
750
|
class Example {
|
|
629
751
|
static ok = {
|
|
630
752
|
bakery: () => bakeryExample(),
|
|
@@ -966,6 +1088,11 @@ class Example {
|
|
|
966
1088
|
}
|
|
967
1089
|
};
|
|
968
1090
|
},
|
|
1091
|
+
tree: () => {
|
|
1092
|
+
return {
|
|
1093
|
+
recipesTreeTable: exampleTreesTable()
|
|
1094
|
+
};
|
|
1095
|
+
},
|
|
969
1096
|
complete: () => {
|
|
970
1097
|
const sliceIds = hip({
|
|
971
1098
|
_type: "sliceIds",
|
|
@@ -1324,6 +1451,42 @@ class Example {
|
|
|
1324
1451
|
});
|
|
1325
1452
|
}
|
|
1326
1453
|
},
|
|
1454
|
+
trees: {
|
|
1455
|
+
missingChildNodes: () => {
|
|
1456
|
+
const result = Example.ok.tree();
|
|
1457
|
+
const treeTable = result.recipesTreeTable;
|
|
1458
|
+
treeTable._data.pop();
|
|
1459
|
+
return hip(result, {
|
|
1460
|
+
updateExistingHashes: true,
|
|
1461
|
+
throwOnWrongHashes: false
|
|
1462
|
+
});
|
|
1463
|
+
},
|
|
1464
|
+
cyclicTree: () => {
|
|
1465
|
+
const result = Example.ok.tree();
|
|
1466
|
+
const treeTable = result.recipesTreeTable;
|
|
1467
|
+
treeTable._data[0].children = [treeTable._data[0]._hash];
|
|
1468
|
+
return { recipesTreeTable: treeTable };
|
|
1469
|
+
},
|
|
1470
|
+
duplicateChildNodeIds: () => {
|
|
1471
|
+
const result = Example.ok.tree();
|
|
1472
|
+
const treeTable = result.recipesTreeTable;
|
|
1473
|
+
const firstChildHash = treeTable._data[1]._hash;
|
|
1474
|
+
treeTable._data[0].children = [firstChildHash, firstChildHash];
|
|
1475
|
+
return hip(result, {
|
|
1476
|
+
updateExistingHashes: true,
|
|
1477
|
+
throwOnWrongHashes: false
|
|
1478
|
+
});
|
|
1479
|
+
},
|
|
1480
|
+
nonParentWithChildren: () => {
|
|
1481
|
+
const result = Example.ok.tree();
|
|
1482
|
+
const treeTable = result.recipesTreeTable;
|
|
1483
|
+
treeTable._data[0].isParent = false;
|
|
1484
|
+
return hip(result, {
|
|
1485
|
+
updateExistingHashes: true,
|
|
1486
|
+
throwOnWrongHashes: false
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
},
|
|
1327
1490
|
layers: {
|
|
1328
1491
|
missingBase: () => {
|
|
1329
1492
|
const result = Example.ok.complete();
|
|
@@ -1745,7 +1908,11 @@ const createInsertHistoryTableCfg = (tableCfg) => ({
|
|
|
1745
1908
|
key: `${tableCfg.key}Ref`,
|
|
1746
1909
|
type: "string",
|
|
1747
1910
|
titleLong: "Reference",
|
|
1748
|
-
titleShort: "Ref"
|
|
1911
|
+
titleShort: "Ref",
|
|
1912
|
+
ref: {
|
|
1913
|
+
tableKey: `${tableCfg.key}MultiEdits`,
|
|
1914
|
+
type: tableCfg.type
|
|
1915
|
+
}
|
|
1749
1916
|
},
|
|
1750
1917
|
{ key: "route", type: "string", titleLong: "Route", titleShort: "Route" },
|
|
1751
1918
|
{
|
|
@@ -1849,7 +2016,7 @@ const contentTypes = [
|
|
|
1849
2016
|
"edits",
|
|
1850
2017
|
"multiEdits",
|
|
1851
2018
|
"editHistory",
|
|
1852
|
-
"
|
|
2019
|
+
"trees"
|
|
1853
2020
|
];
|
|
1854
2021
|
const exampleTypedefs = () => {
|
|
1855
2022
|
return {
|
|
@@ -1924,6 +2091,10 @@ class _BaseValidator {
|
|
|
1924
2091
|
() => this._rootOrHeadTableHasNoIdColumn(),
|
|
1925
2092
|
// Check references
|
|
1926
2093
|
() => this._refsNotFound(),
|
|
2094
|
+
// Check trees
|
|
2095
|
+
() => this._treeChildNodesNotFound(),
|
|
2096
|
+
() => this._treeDuplicateNodeIdsAsSibling(),
|
|
2097
|
+
() => this._treeIsNotParentButHasChildren(),
|
|
1927
2098
|
// Check layers
|
|
1928
2099
|
() => this._layerBasesNotFound(),
|
|
1929
2100
|
() => this._layerSliceIdsTableNotFound(),
|
|
@@ -2345,9 +2516,112 @@ class _BaseValidator {
|
|
|
2345
2516
|
}
|
|
2346
2517
|
}
|
|
2347
2518
|
// ...........................................................................
|
|
2519
|
+
_treeChildNodesNotFound() {
|
|
2520
|
+
const brokenTrees = [];
|
|
2521
|
+
iterateTablesSync(this.rljson, (tableKey, table) => {
|
|
2522
|
+
if (table._type !== "trees") {
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
const treesTable = table;
|
|
2526
|
+
for (const tree of treesTable._data) {
|
|
2527
|
+
const childIds = tree.children;
|
|
2528
|
+
if (!childIds) {
|
|
2529
|
+
continue;
|
|
2530
|
+
}
|
|
2531
|
+
for (const childId of childIds) {
|
|
2532
|
+
const childNode = treesTable._data.find(
|
|
2533
|
+
(n) => n._hash === childId
|
|
2534
|
+
);
|
|
2535
|
+
if (!childNode) {
|
|
2536
|
+
brokenTrees.push({
|
|
2537
|
+
treesTable: tableKey,
|
|
2538
|
+
brokenTree: tree._hash,
|
|
2539
|
+
missingChildNode: childId
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
});
|
|
2545
|
+
if (brokenTrees.length > 0) {
|
|
2546
|
+
this.errors.treeChildNodesNotFound = {
|
|
2547
|
+
error: "Child nodes are missing",
|
|
2548
|
+
brokenTrees
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
// ...........................................................................
|
|
2553
|
+
_treeDuplicateNodeIdsAsSibling() {
|
|
2554
|
+
const treesWithDuplicateSiblingIds = [];
|
|
2555
|
+
iterateTablesSync(this.rljson, (tableKey, table) => {
|
|
2556
|
+
if (table._type !== "trees") {
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
const treesTable = table;
|
|
2560
|
+
for (const tree of treesTable._data) {
|
|
2561
|
+
const childIds = tree.children;
|
|
2562
|
+
if (!childIds) {
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
2566
|
+
const duplicateIds = /* @__PURE__ */ new Set();
|
|
2567
|
+
for (const childId of childIds) {
|
|
2568
|
+
if (seenIds.has(childId)) {
|
|
2569
|
+
duplicateIds.add(childId);
|
|
2570
|
+
} else {
|
|
2571
|
+
seenIds.add(childId);
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
if (duplicateIds.size > 0) {
|
|
2575
|
+
treesWithDuplicateSiblingIds.push({
|
|
2576
|
+
treesTable: tableKey,
|
|
2577
|
+
tree: tree._hash,
|
|
2578
|
+
duplicateChildIds: Array.from(duplicateIds)
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
});
|
|
2583
|
+
if (treesWithDuplicateSiblingIds.length > 0) {
|
|
2584
|
+
this.errors.treeDuplicateNodeIdsAsSibling = {
|
|
2585
|
+
error: "Trees have duplicate sibling node IDs",
|
|
2586
|
+
trees: treesWithDuplicateSiblingIds
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
// ...........................................................................
|
|
2591
|
+
_treeIsNotParentButHasChildren() {
|
|
2592
|
+
const invalidTrees = [];
|
|
2593
|
+
iterateTablesSync(this.rljson, (tableKey, table) => {
|
|
2594
|
+
if (table._type !== "trees") {
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
const treesTable = table;
|
|
2598
|
+
for (const tree of treesTable._data) {
|
|
2599
|
+
const isParent = tree.isParent;
|
|
2600
|
+
const childIds = tree.children;
|
|
2601
|
+
if (!isParent && childIds && childIds.length > 0) {
|
|
2602
|
+
invalidTrees.push({
|
|
2603
|
+
treesTable: tableKey,
|
|
2604
|
+
tree: tree._hash,
|
|
2605
|
+
isParent,
|
|
2606
|
+
children: childIds
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
});
|
|
2611
|
+
if (invalidTrees.length > 0) {
|
|
2612
|
+
this.errors.treeIsNotParentButHasChildren = {
|
|
2613
|
+
error: "Trees marked as non-parents have children",
|
|
2614
|
+
trees: invalidTrees
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
// ...........................................................................
|
|
2348
2619
|
_layerBasesNotFound() {
|
|
2349
2620
|
const brokenLayers = [];
|
|
2350
2621
|
iterateTablesSync(this.rljson, (tableKey, table) => {
|
|
2622
|
+
if (table._type !== "layers") {
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2351
2625
|
const layersIndexed = this.rljsonIndexed[tableKey];
|
|
2352
2626
|
const layersTable = table;
|
|
2353
2627
|
for (const layer of layersTable._data) {
|
|
@@ -2703,11 +2977,11 @@ export {
|
|
|
2703
2977
|
createCakeTableCfg,
|
|
2704
2978
|
createEditHistoryTableCfg,
|
|
2705
2979
|
createEditTableCfg,
|
|
2706
|
-
createHeadsTableCfg,
|
|
2707
2980
|
createInsertHistoryTableCfg,
|
|
2708
2981
|
createLayerTableCfg,
|
|
2709
2982
|
createMultiEditTableCfg,
|
|
2710
2983
|
createSliceIdsTableCfg,
|
|
2984
|
+
createTreesTableCfg,
|
|
2711
2985
|
exampleBuffetsTable,
|
|
2712
2986
|
exampleCakesTable,
|
|
2713
2987
|
exampleComponentsTable,
|
|
@@ -2719,6 +2993,7 @@ export {
|
|
|
2719
2993
|
exampleSliceIdsTable,
|
|
2720
2994
|
exampleTableCfg,
|
|
2721
2995
|
exampleTableCfgTable,
|
|
2996
|
+
exampleTreesTable,
|
|
2722
2997
|
exampleTypedefs,
|
|
2723
2998
|
getTimeIdTimestamp,
|
|
2724
2999
|
getTimeIdUniquePart,
|
|
@@ -2734,6 +3009,7 @@ export {
|
|
|
2734
3009
|
routeSliceIdSeperator,
|
|
2735
3010
|
throwOnInvalidTableCfg,
|
|
2736
3011
|
timeId,
|
|
3012
|
+
treeFromObject,
|
|
2737
3013
|
validateInsert,
|
|
2738
3014
|
validateRljsonAgainstTableCfg
|
|
2739
3015
|
};
|
package/dist/src/example.ts
CHANGED
|
@@ -13,9 +13,11 @@ import { ComponentsTable } from './content/components.ts';
|
|
|
13
13
|
import { Layer, LayersTable } from './content/layer.ts';
|
|
14
14
|
import { SliceIdsTable } from './content/slice-ids.ts';
|
|
15
15
|
import { ColumnCfg, TablesCfgTable } from './content/table-cfg.ts';
|
|
16
|
+
import { exampleTreesTable, TreesTable } from './content/tree.ts';
|
|
16
17
|
import { bakeryExample } from './example/bakery-example.ts';
|
|
17
18
|
import { Rljson } from './rljson.ts';
|
|
18
19
|
|
|
20
|
+
|
|
19
21
|
export class Example {
|
|
20
22
|
static readonly ok = {
|
|
21
23
|
bakery: (): Rljson => bakeryExample(),
|
|
@@ -371,6 +373,11 @@ export class Example {
|
|
|
371
373
|
} as ComponentsTable<Json>,
|
|
372
374
|
};
|
|
373
375
|
},
|
|
376
|
+
tree: (): Rljson => {
|
|
377
|
+
return {
|
|
378
|
+
recipesTreeTable: exampleTreesTable(),
|
|
379
|
+
};
|
|
380
|
+
},
|
|
374
381
|
complete: (): Rljson => {
|
|
375
382
|
const sliceIds = hip<SliceIdsTable>({
|
|
376
383
|
_type: 'sliceIds',
|
|
@@ -751,6 +758,56 @@ export class Example {
|
|
|
751
758
|
},
|
|
752
759
|
},
|
|
753
760
|
|
|
761
|
+
trees: {
|
|
762
|
+
missingChildNodes: (): Rljson => {
|
|
763
|
+
const result = Example.ok.tree();
|
|
764
|
+
const treeTable = result.recipesTreeTable as TreesTable;
|
|
765
|
+
|
|
766
|
+
treeTable._data.pop(); // Remove child node from _data array
|
|
767
|
+
|
|
768
|
+
return hip(result, {
|
|
769
|
+
updateExistingHashes: true,
|
|
770
|
+
throwOnWrongHashes: false,
|
|
771
|
+
});
|
|
772
|
+
},
|
|
773
|
+
|
|
774
|
+
cyclicTree: (): Rljson => {
|
|
775
|
+
const result = Example.ok.tree();
|
|
776
|
+
const treeTable = result.recipesTreeTable as TreesTable;
|
|
777
|
+
|
|
778
|
+
// Introduce a cycle
|
|
779
|
+
treeTable._data[0].children = [treeTable._data[0]._hash as string];
|
|
780
|
+
|
|
781
|
+
return { recipesTreeTable: treeTable } as Rljson;
|
|
782
|
+
},
|
|
783
|
+
|
|
784
|
+
duplicateChildNodeIds: (): Rljson => {
|
|
785
|
+
const result = Example.ok.tree();
|
|
786
|
+
const treeTable = result.recipesTreeTable as TreesTable;
|
|
787
|
+
|
|
788
|
+
// Introduce duplicate child node ids
|
|
789
|
+
const firstChildHash = treeTable._data[1]._hash as string;
|
|
790
|
+
treeTable._data[0].children = [firstChildHash, firstChildHash];
|
|
791
|
+
|
|
792
|
+
return hip(result, {
|
|
793
|
+
updateExistingHashes: true,
|
|
794
|
+
throwOnWrongHashes: false,
|
|
795
|
+
});
|
|
796
|
+
},
|
|
797
|
+
|
|
798
|
+
nonParentWithChildren: (): Rljson => {
|
|
799
|
+
const result = Example.ok.tree();
|
|
800
|
+
const treeTable = result.recipesTreeTable as TreesTable;
|
|
801
|
+
|
|
802
|
+
// Make a non-parent have children
|
|
803
|
+
treeTable._data[0].isParent = false;
|
|
804
|
+
|
|
805
|
+
return hip(result, {
|
|
806
|
+
updateExistingHashes: true,
|
|
807
|
+
throwOnWrongHashes: false,
|
|
808
|
+
});
|
|
809
|
+
},
|
|
810
|
+
},
|
|
754
811
|
layers: {
|
|
755
812
|
missingBase: (): Rljson => {
|
|
756
813
|
const result = Example.ok.complete();
|
package/dist/tools/time-id.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type TimeId = string;
|
|
1
2
|
/**
|
|
2
3
|
* Generates a new TimeId.
|
|
3
4
|
* A TimeId has the format "timestamp:xxxx" where:
|
|
@@ -5,7 +6,7 @@
|
|
|
5
6
|
* - "xxxx" is a 4-character unique identifier
|
|
6
7
|
* @returns A new TimeId string
|
|
7
8
|
*/
|
|
8
|
-
export declare const timeId: () =>
|
|
9
|
+
export declare const timeId: () => TimeId;
|
|
9
10
|
/**
|
|
10
11
|
* Checks if a given id is a valid TimeId.
|
|
11
12
|
* A TimeId has the format "timestamp:xxxx" where:
|
|
@@ -14,16 +15,16 @@ export declare const timeId: () => string;
|
|
|
14
15
|
* @param id - The id to check
|
|
15
16
|
* @returns True if the id is a valid TimeId, false otherwise
|
|
16
17
|
*/
|
|
17
|
-
export declare const isTimeId: (id:
|
|
18
|
+
export declare const isTimeId: (id: TimeId) => boolean;
|
|
18
19
|
/**
|
|
19
20
|
* Extracts the timestamp from a TimeId.
|
|
20
21
|
* @param id - The TimeId string
|
|
21
22
|
* @returns The timestamp in milliseconds since epoch, or null if the id is not a valid TimeId
|
|
22
23
|
*/
|
|
23
|
-
export declare const getTimeIdTimestamp: (id:
|
|
24
|
+
export declare const getTimeIdTimestamp: (id: TimeId) => number | null;
|
|
24
25
|
/**
|
|
25
26
|
* Extracts the unique part from a TimeId.
|
|
26
27
|
* @param id - The TimeId string
|
|
27
28
|
* @returns The unique identifier part, or null if the id is not a valid TimeId
|
|
28
29
|
*/
|
|
29
|
-
export declare const getTimeIdUniquePart: (id:
|
|
30
|
+
export declare const getTimeIdUniquePart: (id: TimeId) => string | null;
|
package/dist/typedefs.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type ColumnKey = JsonKey;
|
|
|
26
26
|
* - `ids` Tables containing slice ids
|
|
27
27
|
* - `components` Tables containing slice components
|
|
28
28
|
*/
|
|
29
|
-
export declare const contentTypes: readonly ["buffets", "cakes", "layers", "sliceIds", "components", "revisions", "tableCfgs", "insertHistory", "edits", "multiEdits", "editHistory", "
|
|
29
|
+
export declare const contentTypes: readonly ["buffets", "cakes", "layers", "sliceIds", "components", "revisions", "tableCfgs", "insertHistory", "edits", "multiEdits", "editHistory", "trees"];
|
|
30
30
|
export type ContentType = (typeof contentTypes)[number];
|
|
31
31
|
/**
|
|
32
32
|
* An example object using the typedefs
|
|
@@ -18,6 +18,9 @@ export interface BaseErrors extends Errors {
|
|
|
18
18
|
rootOrHeadTableHasNoIdColumn?: Json;
|
|
19
19
|
tableCfgHasRootHeadSharedError?: Json;
|
|
20
20
|
refsNotFound?: Json;
|
|
21
|
+
treeChildNodesNotFound?: Json;
|
|
22
|
+
treeDuplicateNodeIdsAsSibling?: Json;
|
|
23
|
+
treeIsNotParentButHasChildren?: Json;
|
|
21
24
|
layerBasesNotFound?: Json;
|
|
22
25
|
layerSliceIdsGivenButNoTable?: Json;
|
|
23
26
|
layerSliceIdsTableNotFound?: Json;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rljson/rljson",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.75",
|
|
4
4
|
"description": "The RLJSON data format specification",
|
|
5
5
|
"homepage": "https://github.com/rljson/rljson",
|
|
6
6
|
"bugs": "https://github.com/rljson/rljson/issues",
|
|
@@ -20,28 +20,28 @@
|
|
|
20
20
|
],
|
|
21
21
|
"type": "module",
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@types/node": "^
|
|
24
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
25
|
-
"@typescript-eslint/parser": "^8.
|
|
26
|
-
"@vitest/coverage-v8": "^4.0.
|
|
23
|
+
"@types/node": "^25.1.0",
|
|
24
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
25
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
26
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
27
27
|
"cross-env": "^10.1.0",
|
|
28
|
-
"eslint": "^9.39.
|
|
29
|
-
"eslint-plugin-jsdoc": "^
|
|
28
|
+
"eslint": "^9.39.2",
|
|
29
|
+
"eslint-plugin-jsdoc": "^62.5.0",
|
|
30
30
|
"eslint-plugin-tsdoc": "^0.5.0",
|
|
31
|
-
"globals": "^
|
|
31
|
+
"globals": "^17.2.0",
|
|
32
32
|
"jsdoc": "^4.0.5",
|
|
33
33
|
"read-pkg": "^10.0.0",
|
|
34
34
|
"typescript": "~5.9.3",
|
|
35
|
-
"typescript-eslint": "^8.
|
|
36
|
-
"vite": "^7.
|
|
37
|
-
"vite-node": "^5.
|
|
35
|
+
"typescript-eslint": "^8.54.0",
|
|
36
|
+
"vite": "^7.3.1",
|
|
37
|
+
"vite-node": "^5.3.0",
|
|
38
38
|
"vite-plugin-dts": "^4.5.4",
|
|
39
|
-
"vite-tsconfig-paths": "^
|
|
40
|
-
"vitest": "^4.0.
|
|
39
|
+
"vite-tsconfig-paths": "^6.0.5",
|
|
40
|
+
"vitest": "^4.0.18",
|
|
41
41
|
"vitest-dom": "^0.1.1"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@rljson/hash": "^0.0.
|
|
44
|
+
"@rljson/hash": "^0.0.18",
|
|
45
45
|
"@rljson/json": "^0.0.23",
|
|
46
46
|
"nanoid": "^5.1.6"
|
|
47
47
|
},
|
package/dist/content/head.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Json } from '@rljson/json';
|
|
2
|
-
import { RljsonTable } from '../rljson.ts';
|
|
3
|
-
import { TableCfg } from './table-cfg.ts';
|
|
4
|
-
export interface Head extends Json {
|
|
5
|
-
timeId: string;
|
|
6
|
-
cakeRef: string;
|
|
7
|
-
_hash: string;
|
|
8
|
-
}
|
|
9
|
-
export type HeadsTable = RljsonTable<Head, 'head'>;
|
|
10
|
-
export declare const createHeadsTableCfg: (cakeKey: string) => TableCfg;
|