167 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| const Clone = require('@hapi/hoek/lib/clone');
 | |
| const Reach = require('@hapi/hoek/lib/reach');
 | |
| 
 | |
| const Common = require('./common');
 | |
| 
 | |
| 
 | |
| const internals = {
 | |
|     value: Symbol('value')
 | |
| };
 | |
| 
 | |
| 
 | |
| module.exports = internals.State = class {
 | |
| 
 | |
|     constructor(path, ancestors, state) {
 | |
| 
 | |
|         this.path = path;
 | |
|         this.ancestors = ancestors;                 // [parent, ..., root]
 | |
| 
 | |
|         this.mainstay = state.mainstay;
 | |
|         this.schemas = state.schemas;               // [current, ..., root]
 | |
|         this.debug = null;
 | |
|     }
 | |
| 
 | |
|     localize(path, ancestors = null, schema = null) {
 | |
| 
 | |
|         const state = new internals.State(path, ancestors, this);
 | |
| 
 | |
|         if (schema &&
 | |
|             state.schemas) {
 | |
| 
 | |
|             state.schemas = [internals.schemas(schema), ...state.schemas];
 | |
|         }
 | |
| 
 | |
|         return state;
 | |
|     }
 | |
| 
 | |
|     nest(schema, debug) {
 | |
| 
 | |
|         const state = new internals.State(this.path, this.ancestors, this);
 | |
|         state.schemas = state.schemas && [internals.schemas(schema), ...state.schemas];
 | |
|         state.debug = debug;
 | |
|         return state;
 | |
|     }
 | |
| 
 | |
|     shadow(value, reason) {
 | |
| 
 | |
|         this.mainstay.shadow = this.mainstay.shadow || new internals.Shadow();
 | |
|         this.mainstay.shadow.set(this.path, value, reason);
 | |
|     }
 | |
| 
 | |
|     snapshot() {
 | |
| 
 | |
|         if (this.mainstay.shadow) {
 | |
|             this._snapshot = Clone(this.mainstay.shadow.node(this.path));
 | |
|         }
 | |
| 
 | |
|         this.mainstay.snapshot();
 | |
|     }
 | |
| 
 | |
|     restore() {
 | |
| 
 | |
|         if (this.mainstay.shadow) {
 | |
|             this.mainstay.shadow.override(this.path, this._snapshot);
 | |
|             this._snapshot = undefined;
 | |
|         }
 | |
| 
 | |
|         this.mainstay.restore();
 | |
|     }
 | |
| 
 | |
|     commit() {
 | |
| 
 | |
|         if (this.mainstay.shadow) {
 | |
|             this.mainstay.shadow.override(this.path, this._snapshot);
 | |
|             this._snapshot = undefined;
 | |
|         }
 | |
| 
 | |
|         this.mainstay.commit();
 | |
|     }
 | |
| };
 | |
| 
 | |
| 
 | |
| internals.schemas = function (schema) {
 | |
| 
 | |
|     if (Common.isSchema(schema)) {
 | |
|         return { schema };
 | |
|     }
 | |
| 
 | |
|     return schema;
 | |
| };
 | |
| 
 | |
| 
 | |
| internals.Shadow = class {
 | |
| 
 | |
|     constructor() {
 | |
| 
 | |
|         this._values = null;
 | |
|     }
 | |
| 
 | |
|     set(path, value, reason) {
 | |
| 
 | |
|         if (!path.length) {                                     // No need to store root value
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (reason === 'strip' &&
 | |
|             typeof path[path.length - 1] === 'number') {        // Cannot store stripped array values (due to shift)
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         this._values = this._values || new Map();
 | |
| 
 | |
|         let node = this._values;
 | |
|         for (let i = 0; i < path.length; ++i) {
 | |
|             const segment = path[i];
 | |
|             let next = node.get(segment);
 | |
|             if (!next) {
 | |
|                 next = new Map();
 | |
|                 node.set(segment, next);
 | |
|             }
 | |
| 
 | |
|             node = next;
 | |
|         }
 | |
| 
 | |
|         node[internals.value] = value;
 | |
|     }
 | |
| 
 | |
|     get(path) {
 | |
| 
 | |
|         const node = this.node(path);
 | |
|         if (node) {
 | |
|             return node[internals.value];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     node(path) {
 | |
| 
 | |
|         if (!this._values) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         return Reach(this._values, path, { iterables: true });
 | |
|     }
 | |
| 
 | |
|     override(path, node) {
 | |
| 
 | |
|         if (!this._values) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         const parents = path.slice(0, -1);
 | |
|         const own = path[path.length - 1];
 | |
|         const parent = Reach(this._values, parents, { iterables: true });
 | |
| 
 | |
|         if (node) {
 | |
|             parent.set(own, node);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (parent) {
 | |
|             parent.delete(own);
 | |
|         }
 | |
|     }
 | |
| };
 |