You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
3.2 KiB
152 lines
3.2 KiB
'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)); |
|
} |
|
} |
|
|
|
restore() { |
|
|
|
if (this.mainstay.shadow) { |
|
this.mainstay.shadow.override(this.path, this._snapshot); |
|
this._snapshot = undefined; |
|
} |
|
} |
|
}; |
|
|
|
|
|
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); |
|
} |
|
} |
|
};
|
|
|