@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.
@@ -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 | 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,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
- "head"
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
  };
@@ -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.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.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.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.1",
29
- "eslint-plugin-jsdoc": "^61.4.1",
28
+ "eslint": "^9.39.2",
29
+ "eslint-plugin-jsdoc": "^62.2.0",
30
30
  "eslint-plugin-tsdoc": "^0.5.0",
31
- "globals": "^16.5.0",
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.48.1",
36
- "vite": "^7.2.6",
37
- "vite-node": "^5.2.0",
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": "^5.1.4",
40
- "vitest": "^4.0.15",
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.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;