@onehat/data 1.17.0 → 1.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/cypress/{integration/Config.spec.js → e2e/Config.cy.js} +1 -1
  2. package/cypress/{integration/Entity.spec.js → e2e/Entity.cy.js} +84 -2
  3. package/cypress/{integration/Repository/Ajax.spec.js → e2e/Repository/Ajax.cy.js} +1 -1
  4. package/cypress/{integration/Repository/Memory.spec.js → e2e/Repository/Memory.cy.js} +1 -1
  5. package/cypress/{integration/Repository/OneBuild.spec.js → e2e/Repository/OneBuild.cy.js} +12 -3
  6. package/cypress/{integration/Repository/Repository.spec.js → e2e/Repository/Repository.cy.js} +225 -4
  7. package/cypress/plugins/index.js +2 -32
  8. package/cypress.config.js +42 -0
  9. package/package.json +22 -22
  10. package/src/Entity/Entity.js +357 -3
  11. package/src/Repository/Ajax.js +13 -3
  12. package/src/Repository/Memory.js +2 -6
  13. package/src/Repository/OneBuild.js +80 -0
  14. package/src/Repository/Repository.js +126 -34
  15. package/src/Schema/Schema.js +33 -0
  16. package/cypress/cypress.json +0 -177
  17. package/cypress.json +0 -1
  18. package/src/Entity/TreeNode.js +0 -190
  19. /package/cypress/{integration/Async.spec.js → e2e/Async.cy.js} +0 -0
  20. /package/cypress/{integration/OneHatData.spec.js → e2e/OneHatData.cy.js} +0 -0
  21. /package/cypress/{integration/Property/Base64.spec.js → e2e/Property/Base64.cy.js} +0 -0
  22. /package/cypress/{integration/Property/Boolean.spec.js → e2e/Property/Boolean.cy.js} +0 -0
  23. /package/cypress/{integration/Property/Currency.spec.js → e2e/Property/Currency.cy.js} +0 -0
  24. /package/cypress/{integration/Property/Date.spec.js → e2e/Property/Date.cy.js} +0 -0
  25. /package/cypress/{integration/Property/DateTime.spec.js → e2e/Property/DateTime.cy.js} +0 -0
  26. /package/cypress/{integration/Property/Float.spec.js → e2e/Property/Float.cy.js} +0 -0
  27. /package/cypress/{integration/Property/Integer.spec.js → e2e/Property/Integer.cy.js} +0 -0
  28. /package/cypress/{integration/Property/Json.spec.js → e2e/Property/Json.cy.js} +0 -0
  29. /package/cypress/{integration/Property/Percent.spec.js → e2e/Property/Percent.cy.js} +0 -0
  30. /package/cypress/{integration/Property/PercentInt.spec.js → e2e/Property/PercentInt.cy.js} +0 -0
  31. /package/cypress/{integration/Property/Property.spec.js → e2e/Property/Property.cy.js} +0 -0
  32. /package/cypress/{integration/Property/String.spec.js → e2e/Property/String.cy.js} +0 -0
  33. /package/cypress/{integration/Property/Time.spec.js → e2e/Property/Time.cy.js} +0 -0
  34. /package/cypress/{integration/Property/Uuid.spec.js → e2e/Property/Uuid.cy.js} +0 -0
  35. /package/cypress/{integration/Repository/LocalFromRemote.spec.js → e2e/Repository/LocalFromRemote.cy.js} +0 -0
  36. /package/cypress/{integration/Schema.spec.js → e2e/Schema.cy.js} +0 -0
  37. /package/cypress/support/{index.js → e2e.js} +0 -0
@@ -104,9 +104,37 @@ class Entity extends EventEmitter {
104
104
  this.properties = [];
105
105
 
106
106
  /**
107
- * @member {boolean} isTreeNode - Whether this Entity is a TreeNode
107
+ * @member {boolean} isTree - Whether this Entity is a TreeNode
108
108
  */
109
- this.isTreeNode = false;
109
+ this.isTree = schema.model.isTree || false;
110
+
111
+ if (this.isTree && !schema.model.parentIdProperty) {
112
+ throw new Error('parentIdProperty cannot be empty for a TreeNode');
113
+ }
114
+ if (this.isTree && this.repository?.isClosureTable && !schema.model.depthProperty) {
115
+ throw new Error('depthProperty cannot be empty for a Closure Table TreeNode');
116
+ }
117
+ if (this.isTree && !schema.model.hasChildrenProperty) {
118
+ throw new Error('hasChildrenProperty cannot be empty for a TreeNode');
119
+ }
120
+
121
+ /**
122
+ * @member {TreeNode} parent - The parent TreeNode for this TreeNode
123
+ * For trees only
124
+ */
125
+ this.parent = null;
126
+
127
+ /**
128
+ * @member {array} children - Contains any children of this TreeNode
129
+ * For trees only
130
+ */
131
+ this.children = this._originalData.children && !_.isEmpty(this._originalData.children) ? this._originalData.children : [];
132
+
133
+ /**
134
+ * @member {boolean} isChildrenLoaded - Whether child TreeNodes have loaded for this TreeNode
135
+ * For trees only
136
+ */
137
+ this.isChildrenLoaded = this._originalData.isChildrenLoaded || false;
110
138
 
111
139
  /**
112
140
  * @member {boolean} isPersisted - Whether this object has been persisted in a storage medium
@@ -350,7 +378,7 @@ class Entity extends EventEmitter {
350
378
  * Assumes (and sets) isTempId === false.
351
379
  * @param {array} originalData - Raw data to load into entity.
352
380
  */
353
- loadOriginalData = (originalData) => {
381
+ loadOriginalData = (originalData, assembleTreeNodes = true) => {
354
382
  if (this.isDestroyed) {
355
383
  throw Error('this.loadOriginalData is no longer valid. Entity has been destroyed.');
356
384
  }
@@ -358,6 +386,10 @@ class Entity extends EventEmitter {
358
386
  this._originalData = originalData || {};
359
387
  this.reset();
360
388
  this.getIdProperty().isTempId = false;
389
+
390
+ if (this.isTree && this.repository && assembleTreeNodes) {
391
+ this.repository.assembleTreeNodes(); // rebuilds them all
392
+ }
361
393
  }
362
394
 
363
395
  /**
@@ -1432,6 +1464,328 @@ class Entity extends EventEmitter {
1432
1464
  }
1433
1465
 
1434
1466
 
1467
+
1468
+ // ______
1469
+ // /_ __/_______ ___ _____
1470
+ // / / / ___/ _ \/ _ \/ ___/
1471
+ // / / / / / __/ __(__ )
1472
+ // /_/ /_/ \___/\___/____/
1473
+
1474
+ /**
1475
+ * Gets the "parentId" Property object for this TreeNode.
1476
+ * This is the Property whose value represents the id for the parent TreeNode.
1477
+ * @return {Property} parentId Property
1478
+ */
1479
+ getParentIdProperty = () => {
1480
+ this.ensureTree();
1481
+ if (this.isDestroyed) {
1482
+ throw Error('this.getParentIdProperty is no longer valid. TreeNode has been destroyed.');
1483
+ }
1484
+
1485
+ const parentIdProperty = this.getSchema().model.parentIdProperty;
1486
+ return this.getProperty(parentIdProperty);
1487
+ }
1488
+
1489
+ /**
1490
+ * Gets the parentId for this TreeNode.
1491
+ * It does this by getting the parentId property's submitValue.
1492
+ * It doesn't look at some value created by client on the TreeNode.
1493
+ * @return {any} parentId - The parentId
1494
+ */
1495
+ getParentId = () => {
1496
+ this.ensureTree();
1497
+ if (this.isDestroyed) {
1498
+ throw Error('this.getParentId is no longer valid. TreeNode has been destroyed.');
1499
+ }
1500
+
1501
+ return this.getParentIdProperty().getSubmitValue();
1502
+ }
1503
+
1504
+ /**
1505
+ * Getter of parentId for this TreeNode.
1506
+ * @return {any} parentId - The parentId
1507
+ */
1508
+ get parentId() {
1509
+ return this.getParentId();
1510
+ }
1511
+
1512
+ /**
1513
+ * Getter of hasParent
1514
+ * Returns true if this node has a parentId
1515
+ * @return {boolean} hasParent
1516
+ */
1517
+ get hasParent() {
1518
+ this.ensureTree();
1519
+
1520
+ return !!this.parentId;
1521
+ }
1522
+
1523
+ /**
1524
+ * Getter of isRoot
1525
+ * Returns true if this node has no parent
1526
+ * @return {boolean} hasParent
1527
+ */
1528
+ get isRoot() {
1529
+ this.ensureTree();
1530
+
1531
+ return !this.hasParent;
1532
+ }
1533
+
1534
+ /**
1535
+ * Gets the "depth" Property object for this TreeNode.
1536
+ * This is the Property whose value represents the depth of the TreeNode.
1537
+ * @return {Property} parentId Property
1538
+ */
1539
+ getDepthProperty = () => {
1540
+ this.ensureTree();
1541
+ if (this.isDestroyed) {
1542
+ throw Error('this.getDepthProperty is no longer valid. TreeNode has been destroyed.');
1543
+ }
1544
+
1545
+ const depthProperty = this.getSchema().model.depthProperty;
1546
+ return this.getProperty(depthProperty);
1547
+ }
1548
+
1549
+ /**
1550
+ * Gets the depth for this TreeNode.
1551
+ * It does this by getting the depth property's submitValue.
1552
+ * @return {any} depth - The depth
1553
+ */
1554
+ getDepth = () => {
1555
+ this.ensureTree();
1556
+ if (this.isDestroyed) {
1557
+ throw Error('this.getDepth is no longer valid. TreeNode has been destroyed.');
1558
+ }
1559
+
1560
+ return this.getDepthProperty().getSubmitValue();
1561
+ }
1562
+
1563
+ /**
1564
+ * Getter of depth for this TreeNode.
1565
+ * @return {any} depth - The depth
1566
+ */
1567
+ get depth() {
1568
+ return this.getDepth();
1569
+ }
1570
+
1571
+ /**
1572
+ * Gets the "hasChildren" Property object for this TreeNode.
1573
+ * This is the Property whose value represents whether this TreeNode has any children.
1574
+ * @return {Property} parentId Property
1575
+ */
1576
+ getHasChildrenProperty = () => {
1577
+ this.ensureTree();
1578
+ if (this.isDestroyed) {
1579
+ throw Error('this.getHasChildrenProperty is no longer valid. TreeNode has been destroyed.');
1580
+ }
1581
+
1582
+ const hasChildrenProperty = this.getSchema().model.hasChildrenProperty;
1583
+ return this.getProperty(hasChildrenProperty);
1584
+ }
1585
+
1586
+ /**
1587
+ * Gets the hasChildren value for this TreeNode.
1588
+ * It does this by getting the hasChildren property's submitValue.
1589
+ * It doesn't look at some value created by client on the TreeNode.
1590
+ * @return {any} parentId - The parentId
1591
+ */
1592
+ getHasChildren = () => {
1593
+ this.ensureTree();
1594
+ if (this.isDestroyed) {
1595
+ throw Error('this.getHasChildren is no longer valid. TreeNode has been destroyed.');
1596
+ }
1597
+
1598
+ return this.getHasChildrenProperty().getSubmitValue();
1599
+ }
1600
+
1601
+ /**
1602
+ * Getter of hasChildren for this TreeNode.
1603
+ * @return {any} parentId - The parentId
1604
+ */
1605
+ get hasChildren() {
1606
+ return this.getHasChildren();
1607
+ }
1608
+
1609
+ /**
1610
+ * Getter of parent TreeNode for this TreeNode.
1611
+ * @return {TreeNode} parent - The parent TreeNode
1612
+ */
1613
+ getParent = () => {
1614
+ this.ensureTree();
1615
+ if (this.isDestroyed) {
1616
+ throw Error('this.getParent is no longer valid. TreeNode has been destroyed.');
1617
+ }
1618
+
1619
+ if (!this.hasParent) {
1620
+ return null;
1621
+ }
1622
+ return this.parent;
1623
+ }
1624
+
1625
+ /**
1626
+ * Getter of child TreeNodes for this TreeNode.
1627
+ * @return {array} children - The children
1628
+ */
1629
+ getChildren = async () => {
1630
+ this.ensureTree();
1631
+ if (this.isDestroyed) {
1632
+ throw Error('this.getChildren is no longer valid. TreeNode has been destroyed.');
1633
+ }
1634
+ if (!this.isChildrenLoaded) {
1635
+ await this.loadChildren();
1636
+ }
1637
+ return this.children;
1638
+ }
1639
+
1640
+ /**
1641
+ * Whether the supplied TreeNode is a child of this TreeNode.
1642
+ * @return {boolean} hasThisChild
1643
+ */
1644
+ hasThisChild = async (treeNode) => {
1645
+ this.ensureTree();
1646
+ if (this.isDestroyed) {
1647
+ throw Error('this.hasThisChild is no longer valid. TreeNode has been destroyed.');
1648
+ }
1649
+
1650
+ const children = await this.getChildren();
1651
+ return _.includes(children, treeNode);
1652
+ }
1653
+
1654
+ /**
1655
+ * Loads the children of this TreeNode from repository.
1656
+ */
1657
+ loadChildren = async (depth) => {
1658
+ this.ensureTree();
1659
+ if (this.isDestroyed) {
1660
+ throw Error('this.loadChildren is no longer valid. TreeNode has been destroyed.');
1661
+ }
1662
+ if (!this.repository?.loadChildren) {
1663
+ throw Error('repository.loadChildren is not defined.');
1664
+ }
1665
+
1666
+ this.children = await this.repository.loadChildren(this, depth); // populates the children with a reference to this in child.parent
1667
+ this.isChildrenLoaded = true;
1668
+ }
1669
+
1670
+ /**
1671
+ * Alias for loadChildren
1672
+ */
1673
+ reloadChildren = () => { // alias
1674
+ return this.loadChildren();
1675
+ }
1676
+
1677
+ /**
1678
+ * Gets the previous sibling of this TreeNode from repository.
1679
+ * @return {TreeNode} sibling
1680
+ */
1681
+ getPrevousSibling = async () => {
1682
+ this.ensureTree();
1683
+ if (this.isDestroyed) {
1684
+ throw Error('this.getPrevousSibling is no longer valid. TreeNode has been destroyed.');
1685
+ }
1686
+
1687
+ const
1688
+ parent = this.getParent(),
1689
+ siblings = await parent.getChildren();
1690
+ let previous = null;
1691
+ _.each(siblings, (treeNode) => {
1692
+ if (treeNode.id === this.id) {
1693
+ return false;
1694
+ }
1695
+ previous = treeNode;
1696
+ })
1697
+ return previous;
1698
+ }
1699
+
1700
+ /**
1701
+ * Gets the next sibling of this TreeNode from repository.
1702
+ * @return {TreeNode} sibling
1703
+ */
1704
+ getNextSibling = async () => {
1705
+ this.ensureTree();
1706
+ if (this.isDestroyed) {
1707
+ throw Error('this.getNextSibling is no longer valid. TreeNode has been destroyed.');
1708
+ }
1709
+
1710
+ const
1711
+ parent = this.getParent(),
1712
+ siblings = await parent.getChildren();
1713
+ let returnNext = false,
1714
+ next = null;
1715
+ _.each(siblings, (treeNode) => {
1716
+ if (returnNext) {
1717
+ next = treeNode;
1718
+ return false;
1719
+ }
1720
+ if (treeNode.id === this.id) {
1721
+ returnNext = true;
1722
+ }
1723
+ })
1724
+ return next;
1725
+ }
1726
+
1727
+ /**
1728
+ * Gets the child of this TreeNode at index ix from repository.
1729
+ * @return {TreeNode} child
1730
+ */
1731
+ getChildAt = (ix) => {
1732
+ this.ensureTree();
1733
+ if (this.isDestroyed) {
1734
+ throw Error('this.getChildAt is no longer valid. TreeNode has been destroyed.');
1735
+ }
1736
+
1737
+ if (!this.children[ix]) {
1738
+ return null;
1739
+ }
1740
+ return this.children[ix];
1741
+ }
1742
+
1743
+ /**
1744
+ * Gets the first child of this TreeNode from repository.
1745
+ * @return {TreeNode} child
1746
+ */
1747
+ getFirstChild = () => {
1748
+ this.ensureTree();
1749
+ if (this.isDestroyed) {
1750
+ throw Error('this.getFirstChild is no longer valid. TreeNode has been destroyed.');
1751
+ }
1752
+
1753
+ if (!this.children[0]) {
1754
+ return null;
1755
+ }
1756
+ return this.children[0];
1757
+ }
1758
+
1759
+ /**
1760
+ * Gets the last child of this TreeNode from repository.
1761
+ * @return {TreeNode} child
1762
+ */
1763
+ getLastChild = () => {
1764
+ this.ensureTree();
1765
+ if (this.isDestroyed) {
1766
+ throw Error('this.getLastChild is no longer valid. TreeNode has been destroyed.');
1767
+ }
1768
+
1769
+ const child = this.children.slice(-1)[0];
1770
+ if (!child) {
1771
+ return null;
1772
+ }
1773
+ return child;
1774
+ }
1775
+
1776
+ /**
1777
+ * Helper to make sure this Repository is a tree
1778
+ * @private
1779
+ */
1780
+ ensureTree = async () => {
1781
+ if (!this.isTree) {
1782
+ this.throwError('This Entity is not a tree!');
1783
+ return false;
1784
+ }
1785
+ return true;
1786
+ }
1787
+
1788
+
1435
1789
  /**
1436
1790
  * Destroy this object.
1437
1791
  * - Removes all circular references to parent objects
@@ -430,6 +430,10 @@ class AjaxRepository extends Repository {
430
430
  this._relayEntityEvents(entity);
431
431
  return entity;
432
432
  });
433
+
434
+ if (this.isTree) {
435
+ this.assembleTreeNodes();
436
+ }
433
437
 
434
438
  // Set the total records that pass filter
435
439
  this.total = total;
@@ -492,7 +496,7 @@ class AjaxRepository extends Repository {
492
496
  }
493
497
 
494
498
  const updatedData = root[0];
495
- entity.loadOriginalData(updatedData, true);
499
+ entity.loadOriginalData(updatedData);
496
500
  entity.emit('reload', entity);
497
501
 
498
502
  this.markLoaded();
@@ -638,11 +642,14 @@ class AjaxRepository extends Repository {
638
642
  // Reload each entity with new data
639
643
  // TODO: Check this
640
644
  _.each(entities, (entity, ix) => {
641
- entity.loadOriginalData(root[ix]);
645
+ entity.loadOriginalData(root[ix], false); // false to not assembleTreeNodes
642
646
  if (entity.isRemotePhantomMode) {
643
647
  entity.isRemotePhantom = true;
644
648
  }
645
649
  });
650
+ if (this.isTree) {
651
+ this.assembleTreeNodes();
652
+ }
646
653
  });
647
654
  }
648
655
 
@@ -748,11 +755,14 @@ class AjaxRepository extends Repository {
748
755
  // Reload each entity with new data
749
756
  // TODO: Check this
750
757
  _.each(entities, (entity, ix) => {
751
- entity.loadOriginalData(root[ix]);
758
+ entity.loadOriginalData(root[ix], false); // false to not assembleTreeNodes
752
759
  if (entity.isRemotePhantomMode && entity.isRemotePhantom) {
753
760
  entity.isRemotePhantom = false;
754
761
  }
755
762
  });
763
+ if (this.isTree) {
764
+ this.assembleTreeNodes();
765
+ }
756
766
  });
757
767
  }
758
768
 
@@ -511,13 +511,9 @@ class MemoryRepository extends Repository {
511
511
 
512
512
  super.removeEntity(entity);
513
513
 
514
- if (this.hasSorters) {
515
- this._applySorters();
516
- }
517
- if (this.hasFilters) {
518
- this._applyFilters();
519
- }
520
514
  delete this._keyedEntities[id];
515
+
516
+ this._recalculate();
521
517
  }
522
518
 
523
519
 
@@ -404,6 +404,86 @@ class OneBuildRepository extends AjaxRepository {
404
404
  });
405
405
  }
406
406
 
407
+
408
+ // ______
409
+ // /_ __/_______ ___ _____
410
+ // / / / ___/ _ \/ _ \/ ___/
411
+ // / / / / / __/ __(__ )
412
+ // /_/ /_/ \___/\___/____/
413
+
414
+ /**
415
+ * Gets the root nodes of this tree.
416
+ */
417
+ getRootNodes = async (getChildren = false, depth) => {
418
+ this.ensureTree();
419
+ if (this.isDestroyed) {
420
+ this.throwError('this.setRootNode is no longer valid. Repository has been destroyed.');
421
+ return;
422
+ }
423
+
424
+ // Clear all entities, if any exist
425
+ _.each(this.entities, (entity) => {
426
+ entity.destroy();
427
+ });
428
+ this.entities = [];
429
+
430
+ // TODO: Load root nodes (and possibly their children)
431
+
432
+
433
+ }
434
+
435
+ /**
436
+ * Loads the children of the supplied treeNode
437
+ */
438
+ loadChildren = async (treeNode, depth = 1) => {
439
+ this.ensureTree();
440
+ if (this.isDestroyed) {
441
+ this.throwError('this.setRootNode is no longer valid. Repository has been destroyed.');
442
+ return;
443
+ }
444
+
445
+
446
+ // If children already exist, remove them from the repository
447
+ // This way, we can reload just a portion of the tree
448
+ if (!_.isEmpty(treeNode.children)) {
449
+ const children = treeNode.children;
450
+ treeNode.children = [];
451
+
452
+ _.each(children, (child) => {
453
+ this.removeNode(child);
454
+ });
455
+ }
456
+
457
+
458
+ // TODO: load children here
459
+
460
+
461
+
462
+ }
463
+
464
+ /**
465
+ * Alias for loadChildren
466
+ */
467
+ reloadChildren = (treeNode, depth) => {
468
+ return this.loadChildren(treeNode, depth);
469
+ }
470
+
471
+ /**
472
+ * Moves the supplied treeNode to a new position on the tree
473
+ */
474
+ moveTreeNode = async (treeNode, newParentId) => {
475
+ this.ensureTree();
476
+ if (this.isDestroyed) {
477
+ this.throwError('this.moveTreeNode is no longer valid. Repository has been destroyed.');
478
+ return;
479
+ }
480
+
481
+ // TODO: move node here
482
+
483
+
484
+
485
+ }
486
+
407
487
  }
408
488
 
409
489