@platform/state
Version:
A small, simple, strongly typed, [rx/observable] state-machine.
313 lines (312 loc) • 12.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TreeState = void 0;
var tslib_1 = require("tslib");
var util_value_1 = require("@platform/util.value");
var rxjs_1 = require("rxjs");
var operators_1 = require("rxjs/operators");
var common_1 = require("../common");
var StateObject_1 = require("../StateObject");
var TreeIdentity_1 = require("../TreeIdentity");
var TreeQuery_1 = require("../TreeQuery");
var helpers_1 = require("./helpers");
var events = require("./TreeState.events");
var path = require("./TreeState.path");
var sync = require("./TreeState.sync");
var Identity = TreeIdentity_1.TreeIdentity;
var TreeState = (function () {
function TreeState(args) {
var _this = this;
this._children = [];
this._kind = 'TreeState';
this._dispose$ = new rxjs_1.Subject();
this.dispose$ = this._dispose$.pipe(operators_1.share());
this._event$ = new rxjs_1.Subject();
this.change = function (fn, options) { return _this._change(fn, options); };
this.add = function (args) {
if (TreeState.isInstance(args)) {
args = { parent: _this.id, root: args };
}
var self = _this;
var child = _this.getOrCreateInstance(args);
if (_this.childExists(child)) {
var err = "Cannot add child '" + child.id + "' as it already exists within the parent '" + _this.root.id + "'.";
throw new Error(err);
}
_this._children.push(child);
_this.change(function (draft, ctx) {
var root = args.parent && args.parent !== draft.id ? ctx.findById(args.parent) : draft;
if (!root) {
var err = "Cannot add child-state because the parent sub-node '" + args.parent + "' within '" + draft.id + "' does not exist.";
throw new Error(err);
}
TreeState.children(root).push(child.root);
});
_this.listen(child);
child.dispose$
.pipe(operators_1.take(1))
.pipe(operators_1.filter(function () { return _this.childExists(child); }))
.subscribe(function () { return _this.remove(child); });
_this.fire({ type: 'TreeState/child/added', payload: { parent: self, child: child } });
return child;
};
this.remove = function (input) {
var child = _this.child(input);
if (!child) {
var err = "Cannot remove child-state as it does not exist in the parent '" + _this.root.id + "'.";
throw new Error(err);
}
_this._children = _this._children.filter(function (item) { return item.root.id !== child.root.id; });
var self = _this;
_this.fire({ type: 'TreeState/child/removed', payload: { parent: self, child: child } });
return child;
};
this.clear = function () {
_this.children.forEach(function (child) { return _this.remove(child); });
return _this;
};
this.contains = function (match) {
return Boolean(_this.find(match));
};
this.find = function (input) {
if (!input) {
return undefined;
}
var match = typeof input === 'function'
? input
: function (e) {
var id = TreeIdentity_1.TreeIdentity.toNodeId(input);
return e.id === id ? true : Boolean(e.tree.query.findById(id));
};
var result;
_this.walkDown(function (e) {
if (e.level > 0) {
if (match(e) === true) {
e.stop();
result = e.tree;
}
}
});
return result;
};
this.walkDown = function (visit) {
var inner = function (level, index, tree, parent, state) {
if (state.stopped) {
return;
}
var skipped = false;
var args = {
level: level,
id: tree.id,
key: Identity.key(tree.id),
namespace: tree.namespace,
index: index,
tree: tree,
parent: parent,
stop: function () { return (state.stopped = true); },
skip: function () { return (skipped = true); },
toString: function () { return tree.id; },
};
visit(args);
if (state.stopped) {
return;
}
if (!skipped && tree.children.length) {
tree.children.forEach(function (child, i) {
inner(level + 1, i, child, tree, state);
});
}
};
return inner(0, -1, _this, undefined, {});
};
this.syncFrom = function (args) {
var until$ = args.until$;
var isObservable = common_1.is.observable(args.source.event$);
var source$ = isObservable
? args.source.event$
: args.source.event.$;
var parent = isObservable
? args.source.parent
: args.source.parent;
var initial = isObservable ? undefined : args.source.root;
return sync.syncFrom({ target: _this, parent: parent, initial: initial, source$: source$, until$: until$ });
};
this.fire = function (e) { return _this._event$.next(e); };
var root = (typeof args.root === 'string' ? { id: args.root } : args.root);
if (root.id.includes('/')) {
var err = "Tree node IDs cannot contain the \"/\" character";
throw new Error(err);
}
this.key = Identity.key(root.id);
this.namespace = Identity.namespace(root.id) || util_value_1.id.cuid();
this.parent = args.parent;
var store = (this._store = StateObject_1.StateObject.create(root));
this.event = events.create({
event$: this._event$,
until$: this.dispose$,
});
this._change(function (draft) { return helpers_1.helpers.ensureNamespace(draft, _this.namespace); }, {
ensureNamespace: false,
});
store.event.changed$.pipe(operators_1.takeUntil(this.dispose$)).subscribe(function (e) {
_this.fire({ type: 'TreeState/changed', payload: e });
});
store.event.patched$.pipe(operators_1.takeUntil(this.dispose$)).subscribe(function (e) {
_this.fire({ type: 'TreeState/patched', payload: e });
});
if (args.dispose$) {
args.dispose$.subscribe(function () { return _this.dispose(); });
}
}
TreeState.create = function (args) {
var root = (args === null || args === void 0 ? void 0 : args.root) || 'node';
var e = tslib_1.__assign(tslib_1.__assign({}, args), { root: root });
return new TreeState(e);
};
TreeState.prototype.dispose = function () {
if (!this.isDisposed) {
this.children.forEach(function (child) { return child.dispose(); });
this._store.dispose();
this.fire({
type: 'TreeState/disposed',
payload: { final: this.root },
});
this._dispose$.next();
this._dispose$.complete();
}
};
Object.defineProperty(TreeState.prototype, "isDisposed", {
get: function () {
return this._dispose$.isStopped;
},
enumerable: false,
configurable: true
});
Object.defineProperty(TreeState.prototype, "readonly", {
get: function () {
return this;
},
enumerable: false,
configurable: true
});
Object.defineProperty(TreeState.prototype, "store", {
get: function () {
return this._store;
},
enumerable: false,
configurable: true
});
Object.defineProperty(TreeState.prototype, "root", {
get: function () {
return this._store.state;
},
enumerable: false,
configurable: true
});
Object.defineProperty(TreeState.prototype, "id", {
get: function () {
return this.root.id;
},
enumerable: false,
configurable: true
});
Object.defineProperty(TreeState.prototype, "children", {
get: function () {
return this._children;
},
enumerable: false,
configurable: true
});
Object.defineProperty(TreeState.prototype, "query", {
get: function () {
var root = this.root;
var namespace = this.namespace;
return TreeQuery_1.TreeQuery.create({ root: root, namespace: namespace });
},
enumerable: false,
configurable: true
});
Object.defineProperty(TreeState.prototype, "path", {
get: function () {
return path.create(this);
},
enumerable: false,
configurable: true
});
TreeState.prototype._change = function (fn, options) {
var _this = this;
if (options === void 0) { options = {}; }
var action = options.action;
var res = this._store.change(function (draft) {
var ctx = _this.ctx(draft);
fn(draft, ctx);
if (options.ensureNamespace !== false) {
helpers_1.helpers.ensureNamespace(draft, _this.namespace);
}
}, { action: action });
return res;
};
TreeState.prototype.ctx = function (root) {
var namespace = this.namespace;
var query = TreeQuery_1.TreeQuery.create({ root: root, namespace: namespace });
return tslib_1.__assign(tslib_1.__assign({}, query), { props: TreeState.props, children: TreeState.children, toObject: function (draft) { return (draft ? StateObject_1.StateObject.toObject(draft) : undefined); }, query: function (node, namespace) {
return TreeQuery_1.TreeQuery.create({ root: node || root, namespace: namespace });
} });
};
TreeState.prototype.child = function (id) {
id = typeof id === 'string' ? id : id.root.id;
return this.children.find(function (item) { return item.root.id === id; });
};
TreeState.prototype.childExists = function (input) {
return Boolean(this.child(input));
};
TreeState.prototype.getOrCreateInstance = function (args) {
var root = (typeof args.root === 'string' ? { id: args.root } : args.root);
if (TreeState.isInstance(root)) {
return args.root;
}
var parent = Identity.toString(args.parent);
parent = parent ? parent : Identity.stripNamespace(this.id);
if (!this.query.exists(function (e) { return e.key === parent; })) {
var err = "Cannot add child-state because the parent node '" + parent + "' does not exist.";
throw new Error(err);
}
parent = Identity.format(this.namespace, parent);
return TreeState.create({ parent: parent, root: root });
};
TreeState.prototype.listen = function (child) {
var _this = this;
var removed$ = this.event
.payload('TreeState/child/removed')
.pipe(operators_1.filter(function (e) { return e.child.id === child.id; }));
removed$.subscribe(function (e) {
_this.change(function (draft, ctx) {
draft.children = TreeState.children(draft).filter(function (_a) {
var id = _a.id;
return id !== child.id;
});
});
});
child.event
.payload('TreeState/changed')
.pipe(operators_1.takeUntil(child.dispose$), operators_1.takeUntil(removed$))
.subscribe(function (e) {
_this.change(function (draft, ctx) {
var children = TreeState.children(draft);
var index = children.findIndex(function (_a) {
var id = _a.id;
return id === child.id;
});
if (index > -1) {
children[index] = e.to;
}
});
});
};
TreeState.identity = Identity;
TreeState.props = helpers_1.helpers.props;
TreeState.children = helpers_1.helpers.children;
TreeState.isInstance = helpers_1.helpers.isInstance;
return TreeState;
}());
exports.TreeState = TreeState;