state.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. 'use strict';
  2. const Clone = require('@hapi/hoek/lib/clone');
  3. const Reach = require('@hapi/hoek/lib/reach');
  4. const Common = require('./common');
  5. const internals = {
  6. value: Symbol('value')
  7. };
  8. module.exports = internals.State = class {
  9. constructor(path, ancestors, state) {
  10. this.path = path;
  11. this.ancestors = ancestors; // [parent, ..., root]
  12. this.mainstay = state.mainstay;
  13. this.schemas = state.schemas; // [current, ..., root]
  14. this.debug = null;
  15. }
  16. localize(path, ancestors = null, schema = null) {
  17. const state = new internals.State(path, ancestors, this);
  18. if (schema &&
  19. state.schemas) {
  20. state.schemas = [internals.schemas(schema), ...state.schemas];
  21. }
  22. return state;
  23. }
  24. nest(schema, debug) {
  25. const state = new internals.State(this.path, this.ancestors, this);
  26. state.schemas = state.schemas && [internals.schemas(schema), ...state.schemas];
  27. state.debug = debug;
  28. return state;
  29. }
  30. shadow(value, reason) {
  31. this.mainstay.shadow = this.mainstay.shadow || new internals.Shadow();
  32. this.mainstay.shadow.set(this.path, value, reason);
  33. }
  34. snapshot() {
  35. if (this.mainstay.shadow) {
  36. this._snapshot = Clone(this.mainstay.shadow.node(this.path));
  37. }
  38. this.mainstay.snapshot();
  39. }
  40. restore() {
  41. if (this.mainstay.shadow) {
  42. this.mainstay.shadow.override(this.path, this._snapshot);
  43. this._snapshot = undefined;
  44. }
  45. this.mainstay.restore();
  46. }
  47. commit() {
  48. if (this.mainstay.shadow) {
  49. this.mainstay.shadow.override(this.path, this._snapshot);
  50. this._snapshot = undefined;
  51. }
  52. this.mainstay.commit();
  53. }
  54. };
  55. internals.schemas = function (schema) {
  56. if (Common.isSchema(schema)) {
  57. return { schema };
  58. }
  59. return schema;
  60. };
  61. internals.Shadow = class {
  62. constructor() {
  63. this._values = null;
  64. }
  65. set(path, value, reason) {
  66. if (!path.length) { // No need to store root value
  67. return;
  68. }
  69. if (reason === 'strip' &&
  70. typeof path[path.length - 1] === 'number') { // Cannot store stripped array values (due to shift)
  71. return;
  72. }
  73. this._values = this._values || new Map();
  74. let node = this._values;
  75. for (let i = 0; i < path.length; ++i) {
  76. const segment = path[i];
  77. let next = node.get(segment);
  78. if (!next) {
  79. next = new Map();
  80. node.set(segment, next);
  81. }
  82. node = next;
  83. }
  84. node[internals.value] = value;
  85. }
  86. get(path) {
  87. const node = this.node(path);
  88. if (node) {
  89. return node[internals.value];
  90. }
  91. }
  92. node(path) {
  93. if (!this._values) {
  94. return;
  95. }
  96. return Reach(this._values, path, { iterables: true });
  97. }
  98. override(path, node) {
  99. if (!this._values) {
  100. return;
  101. }
  102. const parents = path.slice(0, -1);
  103. const own = path[path.length - 1];
  104. const parent = Reach(this._values, parents, { iterables: true });
  105. if (node) {
  106. parent.set(own, node);
  107. return;
  108. }
  109. if (parent) {
  110. parent.delete(own);
  111. }
  112. }
  113. };