@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.
@@ -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 | HeadsTable;
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 === 1;
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
- "head"
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
  };
@@ -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();
@@ -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: () => string;
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: string) => boolean;
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: string) => number | null;
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: string) => string | null;
30
+ export declare const getTimeIdUniquePart: (id: TimeId) => string | null;
@@ -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", "head"];
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.73",
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.10.1",
24
- "@typescript-eslint/eslint-plugin": "^8.48.1",
25
- "@typescript-eslint/parser": "^8.48.1",
26
- "@vitest/coverage-v8": "^4.0.15",
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.1",
29
- "eslint-plugin-jsdoc": "^61.4.1",
28
+ "eslint": "^9.39.2",
29
+ "eslint-plugin-jsdoc": "^62.5.0",
30
30
  "eslint-plugin-tsdoc": "^0.5.0",
31
- "globals": "^16.5.0",
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.48.1",
36
- "vite": "^7.2.6",
37
- "vite-node": "^5.2.0",
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": "^5.1.4",
40
- "vitest": "^4.0.15",
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.17",
44
+ "@rljson/hash": "^0.0.18",
45
45
  "@rljson/json": "^0.0.23",
46
46
  "nanoid": "^5.1.6"
47
47
  },
@@ -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;