@rljson/rljson 0.0.73 → 0.0.74
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 +52 -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 +298 -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,52 @@
|
|
|
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
|
+
* @returns An array of Tree nodes representing the tree structure
|
|
51
|
+
*/
|
|
52
|
+
export declare const treeFromObject: (obj: any) => 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,127 @@ 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) => {
|
|
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
|
+
if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
|
|
729
|
+
for (const key in obj) {
|
|
730
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
731
|
+
processNode(obj[key], key);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return result;
|
|
736
|
+
};
|
|
628
737
|
class Example {
|
|
629
738
|
static ok = {
|
|
630
739
|
bakery: () => bakeryExample(),
|
|
@@ -966,6 +1075,11 @@ class Example {
|
|
|
966
1075
|
}
|
|
967
1076
|
};
|
|
968
1077
|
},
|
|
1078
|
+
tree: () => {
|
|
1079
|
+
return {
|
|
1080
|
+
recipesTreeTable: exampleTreesTable()
|
|
1081
|
+
};
|
|
1082
|
+
},
|
|
969
1083
|
complete: () => {
|
|
970
1084
|
const sliceIds = hip({
|
|
971
1085
|
_type: "sliceIds",
|
|
@@ -1324,6 +1438,42 @@ class Example {
|
|
|
1324
1438
|
});
|
|
1325
1439
|
}
|
|
1326
1440
|
},
|
|
1441
|
+
trees: {
|
|
1442
|
+
missingChildNodes: () => {
|
|
1443
|
+
const result = Example.ok.tree();
|
|
1444
|
+
const treeTable = result.recipesTreeTable;
|
|
1445
|
+
treeTable._data.pop();
|
|
1446
|
+
return hip(result, {
|
|
1447
|
+
updateExistingHashes: true,
|
|
1448
|
+
throwOnWrongHashes: false
|
|
1449
|
+
});
|
|
1450
|
+
},
|
|
1451
|
+
cyclicTree: () => {
|
|
1452
|
+
const result = Example.ok.tree();
|
|
1453
|
+
const treeTable = result.recipesTreeTable;
|
|
1454
|
+
treeTable._data[0].children = [treeTable._data[0]._hash];
|
|
1455
|
+
return { recipesTreeTable: treeTable };
|
|
1456
|
+
},
|
|
1457
|
+
duplicateChildNodeIds: () => {
|
|
1458
|
+
const result = Example.ok.tree();
|
|
1459
|
+
const treeTable = result.recipesTreeTable;
|
|
1460
|
+
const firstChildHash = treeTable._data[1]._hash;
|
|
1461
|
+
treeTable._data[0].children = [firstChildHash, firstChildHash];
|
|
1462
|
+
return hip(result, {
|
|
1463
|
+
updateExistingHashes: true,
|
|
1464
|
+
throwOnWrongHashes: false
|
|
1465
|
+
});
|
|
1466
|
+
},
|
|
1467
|
+
nonParentWithChildren: () => {
|
|
1468
|
+
const result = Example.ok.tree();
|
|
1469
|
+
const treeTable = result.recipesTreeTable;
|
|
1470
|
+
treeTable._data[0].isParent = false;
|
|
1471
|
+
return hip(result, {
|
|
1472
|
+
updateExistingHashes: true,
|
|
1473
|
+
throwOnWrongHashes: false
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
},
|
|
1327
1477
|
layers: {
|
|
1328
1478
|
missingBase: () => {
|
|
1329
1479
|
const result = Example.ok.complete();
|
|
@@ -1745,7 +1895,11 @@ const createInsertHistoryTableCfg = (tableCfg) => ({
|
|
|
1745
1895
|
key: `${tableCfg.key}Ref`,
|
|
1746
1896
|
type: "string",
|
|
1747
1897
|
titleLong: "Reference",
|
|
1748
|
-
titleShort: "Ref"
|
|
1898
|
+
titleShort: "Ref",
|
|
1899
|
+
ref: {
|
|
1900
|
+
tableKey: `${tableCfg.key}MultiEdits`,
|
|
1901
|
+
type: tableCfg.type
|
|
1902
|
+
}
|
|
1749
1903
|
},
|
|
1750
1904
|
{ key: "route", type: "string", titleLong: "Route", titleShort: "Route" },
|
|
1751
1905
|
{
|
|
@@ -1849,7 +2003,7 @@ const contentTypes = [
|
|
|
1849
2003
|
"edits",
|
|
1850
2004
|
"multiEdits",
|
|
1851
2005
|
"editHistory",
|
|
1852
|
-
"
|
|
2006
|
+
"trees"
|
|
1853
2007
|
];
|
|
1854
2008
|
const exampleTypedefs = () => {
|
|
1855
2009
|
return {
|
|
@@ -1924,6 +2078,10 @@ class _BaseValidator {
|
|
|
1924
2078
|
() => this._rootOrHeadTableHasNoIdColumn(),
|
|
1925
2079
|
// Check references
|
|
1926
2080
|
() => this._refsNotFound(),
|
|
2081
|
+
// Check trees
|
|
2082
|
+
() => this._treeChildNodesNotFound(),
|
|
2083
|
+
() => this._treeDuplicateNodeIdsAsSibling(),
|
|
2084
|
+
() => this._treeIsNotParentButHasChildren(),
|
|
1927
2085
|
// Check layers
|
|
1928
2086
|
() => this._layerBasesNotFound(),
|
|
1929
2087
|
() => this._layerSliceIdsTableNotFound(),
|
|
@@ -2345,9 +2503,112 @@ class _BaseValidator {
|
|
|
2345
2503
|
}
|
|
2346
2504
|
}
|
|
2347
2505
|
// ...........................................................................
|
|
2506
|
+
_treeChildNodesNotFound() {
|
|
2507
|
+
const brokenTrees = [];
|
|
2508
|
+
iterateTablesSync(this.rljson, (tableKey, table) => {
|
|
2509
|
+
if (table._type !== "trees") {
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
const treesTable = table;
|
|
2513
|
+
for (const tree of treesTable._data) {
|
|
2514
|
+
const childIds = tree.children;
|
|
2515
|
+
if (!childIds) {
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
for (const childId of childIds) {
|
|
2519
|
+
const childNode = treesTable._data.find(
|
|
2520
|
+
(n) => n._hash === childId
|
|
2521
|
+
);
|
|
2522
|
+
if (!childNode) {
|
|
2523
|
+
brokenTrees.push({
|
|
2524
|
+
treesTable: tableKey,
|
|
2525
|
+
brokenTree: tree._hash,
|
|
2526
|
+
missingChildNode: childId
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
});
|
|
2532
|
+
if (brokenTrees.length > 0) {
|
|
2533
|
+
this.errors.treeChildNodesNotFound = {
|
|
2534
|
+
error: "Child nodes are missing",
|
|
2535
|
+
brokenTrees
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
// ...........................................................................
|
|
2540
|
+
_treeDuplicateNodeIdsAsSibling() {
|
|
2541
|
+
const treesWithDuplicateSiblingIds = [];
|
|
2542
|
+
iterateTablesSync(this.rljson, (tableKey, table) => {
|
|
2543
|
+
if (table._type !== "trees") {
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2546
|
+
const treesTable = table;
|
|
2547
|
+
for (const tree of treesTable._data) {
|
|
2548
|
+
const childIds = tree.children;
|
|
2549
|
+
if (!childIds) {
|
|
2550
|
+
continue;
|
|
2551
|
+
}
|
|
2552
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
2553
|
+
const duplicateIds = /* @__PURE__ */ new Set();
|
|
2554
|
+
for (const childId of childIds) {
|
|
2555
|
+
if (seenIds.has(childId)) {
|
|
2556
|
+
duplicateIds.add(childId);
|
|
2557
|
+
} else {
|
|
2558
|
+
seenIds.add(childId);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (duplicateIds.size > 0) {
|
|
2562
|
+
treesWithDuplicateSiblingIds.push({
|
|
2563
|
+
treesTable: tableKey,
|
|
2564
|
+
tree: tree._hash,
|
|
2565
|
+
duplicateChildIds: Array.from(duplicateIds)
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
if (treesWithDuplicateSiblingIds.length > 0) {
|
|
2571
|
+
this.errors.treeDuplicateNodeIdsAsSibling = {
|
|
2572
|
+
error: "Trees have duplicate sibling node IDs",
|
|
2573
|
+
trees: treesWithDuplicateSiblingIds
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
// ...........................................................................
|
|
2578
|
+
_treeIsNotParentButHasChildren() {
|
|
2579
|
+
const invalidTrees = [];
|
|
2580
|
+
iterateTablesSync(this.rljson, (tableKey, table) => {
|
|
2581
|
+
if (table._type !== "trees") {
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
const treesTable = table;
|
|
2585
|
+
for (const tree of treesTable._data) {
|
|
2586
|
+
const isParent = tree.isParent;
|
|
2587
|
+
const childIds = tree.children;
|
|
2588
|
+
if (!isParent && childIds && childIds.length > 0) {
|
|
2589
|
+
invalidTrees.push({
|
|
2590
|
+
treesTable: tableKey,
|
|
2591
|
+
tree: tree._hash,
|
|
2592
|
+
isParent,
|
|
2593
|
+
children: childIds
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
if (invalidTrees.length > 0) {
|
|
2599
|
+
this.errors.treeIsNotParentButHasChildren = {
|
|
2600
|
+
error: "Trees marked as non-parents have children",
|
|
2601
|
+
trees: invalidTrees
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
// ...........................................................................
|
|
2348
2606
|
_layerBasesNotFound() {
|
|
2349
2607
|
const brokenLayers = [];
|
|
2350
2608
|
iterateTablesSync(this.rljson, (tableKey, table) => {
|
|
2609
|
+
if (table._type !== "layers") {
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2351
2612
|
const layersIndexed = this.rljsonIndexed[tableKey];
|
|
2352
2613
|
const layersTable = table;
|
|
2353
2614
|
for (const layer of layersTable._data) {
|
|
@@ -2703,11 +2964,11 @@ export {
|
|
|
2703
2964
|
createCakeTableCfg,
|
|
2704
2965
|
createEditHistoryTableCfg,
|
|
2705
2966
|
createEditTableCfg,
|
|
2706
|
-
createHeadsTableCfg,
|
|
2707
2967
|
createInsertHistoryTableCfg,
|
|
2708
2968
|
createLayerTableCfg,
|
|
2709
2969
|
createMultiEditTableCfg,
|
|
2710
2970
|
createSliceIdsTableCfg,
|
|
2971
|
+
createTreesTableCfg,
|
|
2711
2972
|
exampleBuffetsTable,
|
|
2712
2973
|
exampleCakesTable,
|
|
2713
2974
|
exampleComponentsTable,
|
|
@@ -2719,6 +2980,7 @@ export {
|
|
|
2719
2980
|
exampleSliceIdsTable,
|
|
2720
2981
|
exampleTableCfg,
|
|
2721
2982
|
exampleTableCfgTable,
|
|
2983
|
+
exampleTreesTable,
|
|
2722
2984
|
exampleTypedefs,
|
|
2723
2985
|
getTimeIdTimestamp,
|
|
2724
2986
|
getTimeIdUniquePart,
|
|
@@ -2734,6 +2996,7 @@ export {
|
|
|
2734
2996
|
routeSliceIdSeperator,
|
|
2735
2997
|
throwOnInvalidTableCfg,
|
|
2736
2998
|
timeId,
|
|
2999
|
+
treeFromObject,
|
|
2737
3000
|
validateInsert,
|
|
2738
3001
|
validateRljsonAgainstTableCfg
|
|
2739
3002
|
};
|
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.74",
|
|
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.0.9",
|
|
24
|
+
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
|
25
|
+
"@typescript-eslint/parser": "^8.53.1",
|
|
26
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
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.2.0",
|
|
30
30
|
"eslint-plugin-tsdoc": "^0.5.0",
|
|
31
|
-
"globals": "^
|
|
31
|
+
"globals": "^17.0.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.53.1",
|
|
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.4",
|
|
40
|
+
"vitest": "^4.0.17",
|
|
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;
|