epos-unit 1.16.0 → 1.17.0
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.
- package/dist/epos-unit.d.ts +9 -8
- package/dist/epos-unit.js +84 -60
- package/dist/epos-unit.js.map +1 -1
- package/package.json +3 -2
- package/src/epos-unit.ts +114 -84
package/dist/epos-unit.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ declare const _parent_: unique symbol;
|
|
|
7
7
|
declare const _attached_: unique symbol;
|
|
8
8
|
declare const _disposers_: unique symbol;
|
|
9
9
|
declare const _ancestors_: unique symbol;
|
|
10
|
-
declare const
|
|
10
|
+
declare const _pendingAttachHooks_: unique symbol;
|
|
11
11
|
type Node<T> = Unit<T> | Obj | Arr;
|
|
12
12
|
declare class Unit<TRoot = unknown> {
|
|
13
13
|
/**
|
|
@@ -19,6 +19,7 @@ declare class Unit<TRoot = unknown> {
|
|
|
19
19
|
*/
|
|
20
20
|
[epos.state.DETACH]: () => void;
|
|
21
21
|
'@': string;
|
|
22
|
+
id: string;
|
|
22
23
|
log: ReturnType<typeof createLog>;
|
|
23
24
|
[':version']?: number;
|
|
24
25
|
[_root_]?: TRoot;
|
|
@@ -26,18 +27,13 @@ declare class Unit<TRoot = unknown> {
|
|
|
26
27
|
[_attached_]?: boolean;
|
|
27
28
|
[_disposers_]?: Set<() => void>;
|
|
28
29
|
[_ancestors_]?: Map<Cls, unknown>;
|
|
29
|
-
[
|
|
30
|
+
[_pendingAttachHooks_]?: (() => void)[];
|
|
30
31
|
constructor(parent: Unit<TRoot> | null);
|
|
31
32
|
/**
|
|
32
33
|
* Gets the root unit of the current unit's tree.
|
|
33
34
|
* The result is cached for subsequent calls.
|
|
34
35
|
*/
|
|
35
36
|
get $(): TRoot | (undefined & TRoot);
|
|
36
|
-
/**
|
|
37
|
-
* Finds the closest ancestor unit of a given type.
|
|
38
|
-
* The result is cached for subsequent calls.
|
|
39
|
-
*/
|
|
40
|
-
closest<T extends Unit>(Ancestor: Cls<T>): T | null;
|
|
41
37
|
/**
|
|
42
38
|
* A wrapper around MobX's `autorun` that automatically disposes
|
|
43
39
|
* the reaction when the unit is detached.
|
|
@@ -62,6 +58,11 @@ declare class Unit<TRoot = unknown> {
|
|
|
62
58
|
* Creates an error for an unreachable code path.
|
|
63
59
|
*/
|
|
64
60
|
never(message?: string): Error;
|
|
61
|
+
/**
|
|
62
|
+
* Finds the closest ancestor unit of a given type.
|
|
63
|
+
* The result is cached for subsequent calls.
|
|
64
|
+
*/
|
|
65
|
+
closest<T extends Unit>(Ancestor: Cls<T>): T | null;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
export { type Node, Unit, _ancestors_, _attached_, _disposers_, _parent_,
|
|
68
|
+
export { type Node, Unit, _ancestors_, _attached_, _disposers_, _parent_, _pendingAttachHooks_, _root_ };
|
package/dist/epos-unit.js
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
// src/epos-unit.ts
|
|
2
2
|
import { createLog, is } from "dropcap/utils";
|
|
3
3
|
import "epos";
|
|
4
|
+
import { customAlphabet } from "nanoid";
|
|
4
5
|
var _root_ = /* @__PURE__ */ Symbol("root");
|
|
5
6
|
var _parent_ = /* @__PURE__ */ Symbol("parent");
|
|
6
7
|
var _attached_ = /* @__PURE__ */ Symbol("attached");
|
|
7
8
|
var _disposers_ = /* @__PURE__ */ Symbol("disposers");
|
|
8
9
|
var _ancestors_ = /* @__PURE__ */ Symbol("ancestors");
|
|
9
|
-
var
|
|
10
|
+
var _pendingAttachHooks_ = /* @__PURE__ */ Symbol("pendingAttachHooks");
|
|
11
|
+
var nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 8);
|
|
10
12
|
var Unit = class {
|
|
11
13
|
constructor(parent) {
|
|
14
|
+
this.id = nanoid();
|
|
12
15
|
this[_parent_] = parent;
|
|
13
|
-
const
|
|
16
|
+
const versioner = getVersioner(this);
|
|
17
|
+
const versions = getVersions(versioner);
|
|
14
18
|
if (versions.length > 0) this[":version"] = versions.at(-1);
|
|
15
19
|
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// ATTACH / DETACH
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
16
23
|
/**
|
|
17
24
|
* Lifecycle method called when the unit is attached to the state tree.
|
|
18
25
|
*/
|
|
19
26
|
[epos.state.ATTACH]() {
|
|
20
|
-
setProperty(this, "log", createLog(this["@"]));
|
|
21
27
|
epos.state.transaction(() => {
|
|
22
28
|
const versioner = getVersioner(this);
|
|
23
|
-
const versions = getVersions(
|
|
29
|
+
const versions = getVersions(versioner);
|
|
24
30
|
for (const version of versions) {
|
|
25
31
|
if (is.number(this[":version"]) && this[":version"] >= version) continue;
|
|
26
32
|
const versionFn = versioner[version];
|
|
@@ -29,40 +35,61 @@ var Unit = class {
|
|
|
29
35
|
this[":version"] = version;
|
|
30
36
|
}
|
|
31
37
|
});
|
|
38
|
+
let log = createLog(this["@"]);
|
|
39
|
+
Reflect.defineProperty(this, "log", {
|
|
40
|
+
configurable: true,
|
|
41
|
+
get: () => log,
|
|
42
|
+
set: (v) => log = v
|
|
43
|
+
});
|
|
44
|
+
const stateDescriptor = Reflect.getOwnPropertyDescriptor(this.constructor.prototype, "state");
|
|
45
|
+
if (stateDescriptor && stateDescriptor.get) {
|
|
46
|
+
const value = stateDescriptor.get.call(this);
|
|
47
|
+
const state = epos.state.local(value, { deep: false });
|
|
48
|
+
Reflect.defineProperty(this, "state", { get: () => state });
|
|
49
|
+
}
|
|
32
50
|
for (const prototype of getPrototypes(this)) {
|
|
33
51
|
const descriptors = Object.getOwnPropertyDescriptors(prototype);
|
|
34
52
|
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
35
53
|
if (key === "constructor") continue;
|
|
36
54
|
if (this.hasOwnProperty(key)) continue;
|
|
37
|
-
if (descriptor.
|
|
38
|
-
|
|
39
|
-
const fn = descriptor.value.bind(this);
|
|
40
|
-
if (key.endsWith("View")) {
|
|
41
|
-
let Component = epos.component(fn);
|
|
55
|
+
if (is.function(descriptor.value) && key.endsWith("View")) {
|
|
56
|
+
let Component = epos.component(descriptor.value.bind(this));
|
|
42
57
|
Component.displayName = `${this.constructor.name}.${key}`;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
Reflect.defineProperty(this, key, {
|
|
59
|
+
configurable: true,
|
|
60
|
+
get: () => Component,
|
|
61
|
+
set: (v) => Component = v
|
|
62
|
+
});
|
|
63
|
+
} else if (is.function(descriptor.value)) {
|
|
64
|
+
let method = descriptor.value.bind(this);
|
|
65
|
+
Reflect.defineProperty(this, key, {
|
|
66
|
+
configurable: true,
|
|
67
|
+
get: () => method,
|
|
68
|
+
set: (v) => method = v
|
|
69
|
+
});
|
|
70
|
+
} else if (descriptor.get) {
|
|
71
|
+
const getter = descriptor.get;
|
|
72
|
+
const computed = epos.libs.mobx.computed(() => getter.call(this));
|
|
73
|
+
Reflect.defineProperty(this, key, {
|
|
74
|
+
configurable: true,
|
|
75
|
+
get: () => computed.get(),
|
|
76
|
+
set: descriptor.set
|
|
77
|
+
});
|
|
46
78
|
}
|
|
47
79
|
}
|
|
48
80
|
}
|
|
49
|
-
const stateDescriptor = Reflect.getOwnPropertyDescriptor(this.constructor.prototype, "state");
|
|
50
|
-
if (stateDescriptor && stateDescriptor.get) {
|
|
51
|
-
const state = stateDescriptor.get.call(this);
|
|
52
|
-
setProperty(this, "state", epos.libs.mobx.observable.object(state, {}, { deep: false }));
|
|
53
|
-
}
|
|
54
81
|
const attach = Reflect.get(this, "attach");
|
|
55
82
|
if (is.function(attach)) {
|
|
56
83
|
const unattachedRoot = findUnattachedRoot(this);
|
|
57
84
|
if (!unattachedRoot) throw this.never();
|
|
58
|
-
|
|
59
|
-
unattachedRoot[
|
|
85
|
+
ensure(unattachedRoot, _pendingAttachHooks_, () => []);
|
|
86
|
+
unattachedRoot[_pendingAttachHooks_].push(() => attach());
|
|
60
87
|
}
|
|
61
|
-
if (this[
|
|
62
|
-
this[
|
|
63
|
-
delete this[
|
|
88
|
+
if (this[_pendingAttachHooks_]) {
|
|
89
|
+
this[_pendingAttachHooks_].forEach((attach2) => attach2());
|
|
90
|
+
delete this[_pendingAttachHooks_];
|
|
64
91
|
}
|
|
65
|
-
|
|
92
|
+
Reflect.defineProperty(this, _attached_, { configurable: true, get: () => true });
|
|
66
93
|
}
|
|
67
94
|
/**
|
|
68
95
|
* Lifecycle method called when the unit is detached from the state tree.
|
|
@@ -72,45 +99,33 @@ var Unit = class {
|
|
|
72
99
|
this[_disposers_].forEach((disposer) => disposer());
|
|
73
100
|
this[_disposers_].clear();
|
|
74
101
|
}
|
|
102
|
+
if (this[_ancestors_]) {
|
|
103
|
+
this[_ancestors_].clear();
|
|
104
|
+
}
|
|
75
105
|
const detach = Reflect.get(this, "detach");
|
|
76
106
|
if (is.function(detach)) detach();
|
|
77
|
-
delete this[_root_];
|
|
78
|
-
delete this[_attached_];
|
|
79
|
-
delete this[_ancestors_];
|
|
80
|
-
delete this[_disposers_];
|
|
81
107
|
}
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// ROOT GETTER
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
82
111
|
/**
|
|
83
112
|
* Gets the root unit of the current unit's tree.
|
|
84
113
|
* The result is cached for subsequent calls.
|
|
85
114
|
*/
|
|
86
115
|
get $() {
|
|
87
|
-
|
|
116
|
+
ensure(this, _root_, () => findRoot(this));
|
|
88
117
|
return this[_root_];
|
|
89
118
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
*/
|
|
94
|
-
closest(Ancestor) {
|
|
95
|
-
ensureProperty(this, _ancestors_, () => /* @__PURE__ */ new Map());
|
|
96
|
-
if (this[_ancestors_].has(Ancestor)) return this[_ancestors_].get(Ancestor);
|
|
97
|
-
let cursor = this;
|
|
98
|
-
while (cursor) {
|
|
99
|
-
if (cursor instanceof Ancestor) {
|
|
100
|
-
this[_ancestors_].set(Ancestor, cursor);
|
|
101
|
-
return cursor;
|
|
102
|
-
}
|
|
103
|
-
cursor = getParent(cursor);
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// METHODS
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
107
122
|
/**
|
|
108
123
|
* A wrapper around MobX's `autorun` that automatically disposes
|
|
109
124
|
* the reaction when the unit is detached.
|
|
110
125
|
*/
|
|
111
126
|
autorun(...args) {
|
|
112
127
|
const disposer = epos.libs.mobx.autorun(...args);
|
|
113
|
-
|
|
128
|
+
ensure(this, _disposers_, () => /* @__PURE__ */ new Set());
|
|
114
129
|
this[_disposers_].add(disposer);
|
|
115
130
|
return disposer;
|
|
116
131
|
}
|
|
@@ -120,7 +135,7 @@ var Unit = class {
|
|
|
120
135
|
*/
|
|
121
136
|
reaction(...args) {
|
|
122
137
|
const disposer = epos.libs.mobx.reaction(...args);
|
|
123
|
-
|
|
138
|
+
ensure(this, _disposers_, () => /* @__PURE__ */ new Set());
|
|
124
139
|
this[_disposers_].add(disposer);
|
|
125
140
|
return disposer;
|
|
126
141
|
}
|
|
@@ -130,7 +145,7 @@ var Unit = class {
|
|
|
130
145
|
*/
|
|
131
146
|
setTimeout(...args) {
|
|
132
147
|
const id = self.setTimeout(...args);
|
|
133
|
-
|
|
148
|
+
ensure(this, _disposers_, () => /* @__PURE__ */ new Set());
|
|
134
149
|
this[_disposers_].add(() => self.clearTimeout(id));
|
|
135
150
|
return id;
|
|
136
151
|
}
|
|
@@ -140,7 +155,7 @@ var Unit = class {
|
|
|
140
155
|
*/
|
|
141
156
|
setInterval(...args) {
|
|
142
157
|
const id = self.setInterval(...args);
|
|
143
|
-
|
|
158
|
+
ensure(this, _disposers_, () => /* @__PURE__ */ new Set());
|
|
144
159
|
this[_disposers_].add(() => self.clearInterval(id));
|
|
145
160
|
return id;
|
|
146
161
|
}
|
|
@@ -153,15 +168,25 @@ var Unit = class {
|
|
|
153
168
|
Error.captureStackTrace(error, this.never);
|
|
154
169
|
return error;
|
|
155
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Finds the closest ancestor unit of a given type.
|
|
173
|
+
* The result is cached for subsequent calls.
|
|
174
|
+
*/
|
|
175
|
+
closest(Ancestor) {
|
|
176
|
+
ensure(this, _ancestors_, () => /* @__PURE__ */ new Map());
|
|
177
|
+
if (this[_ancestors_].has(Ancestor)) return this[_ancestors_].get(Ancestor);
|
|
178
|
+
let cursor = this;
|
|
179
|
+
while (cursor) {
|
|
180
|
+
if (cursor instanceof Ancestor) {
|
|
181
|
+
this[_ancestors_].set(Ancestor, cursor);
|
|
182
|
+
return cursor;
|
|
183
|
+
}
|
|
184
|
+
cursor = getParent(cursor);
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
156
188
|
};
|
|
157
|
-
function
|
|
158
|
-
Reflect.defineProperty(object, key, {
|
|
159
|
-
configurable: true,
|
|
160
|
-
get: () => value,
|
|
161
|
-
set: (v) => value = v
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
function ensureProperty(object, key, getInitialValue) {
|
|
189
|
+
function ensure(object, key, getInitialValue) {
|
|
165
190
|
if (key in object) return;
|
|
166
191
|
const value = getInitialValue();
|
|
167
192
|
Reflect.defineProperty(object, key, { configurable: true, get: () => value });
|
|
@@ -198,8 +223,7 @@ function getVersioner(unit) {
|
|
|
198
223
|
if (!is.object(versioner)) return {};
|
|
199
224
|
return versioner;
|
|
200
225
|
}
|
|
201
|
-
function getVersions(
|
|
202
|
-
const versioner = getVersioner(unit);
|
|
226
|
+
function getVersions(versioner) {
|
|
203
227
|
const numericKeys = Object.keys(versioner).filter((key) => is.numeric(key));
|
|
204
228
|
return numericKeys.map(Number).sort((v1, v2) => v1 - v2);
|
|
205
229
|
}
|
|
@@ -209,7 +233,7 @@ export {
|
|
|
209
233
|
_attached_,
|
|
210
234
|
_disposers_,
|
|
211
235
|
_parent_,
|
|
212
|
-
|
|
236
|
+
_pendingAttachHooks_,
|
|
213
237
|
_root_
|
|
214
238
|
};
|
|
215
239
|
//# sourceMappingURL=epos-unit.js.map
|
package/dist/epos-unit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/epos-unit.ts"],"sourcesContent":["import type { Arr, Cls, Obj } from 'dropcap/types'\nimport { createLog, is } from 'dropcap/utils'\nimport 'epos'\n\nexport const _root_ = Symbol('root')\nexport const _parent_ = Symbol('parent')\nexport const _attached_ = Symbol('attached')\nexport const _disposers_ = Symbol('disposers')\nexport const _ancestors_ = Symbol('ancestors')\nexport const _pendingAttachFns_ = Symbol('pendingAttachFns')\n\nexport type Node<T> = Unit<T> | Obj | Arr\n\nexport class Unit<TRoot = unknown> {\n declare '@': string\n declare log: ReturnType<typeof createLog>;\n declare [':version']?: number;\n declare [_root_]?: TRoot;\n declare [_parent_]?: Unit<TRoot> | null; // Parent reference for a not-yet-attached units\n declare [_attached_]?: boolean;\n declare [_disposers_]?: Set<() => void>;\n declare [_ancestors_]?: Map<Cls, unknown>;\n declare [_pendingAttachFns_]?: (() => void)[]\n\n constructor(parent: Unit<TRoot> | null) {\n this[_parent_] = parent\n const versions = getVersions(this)\n if (versions.length > 0) this[':version'] = versions.at(-1)!\n }\n\n /**\n * Lifecycle method called when the unit is attached to the state tree.\n */\n [epos.state.ATTACH]() {\n // Setup logger\n setProperty(this, 'log', createLog(this['@']))\n\n // Apply versioner\n epos.state.transaction(() => {\n const versioner = getVersioner(this)\n const versions = getVersions(this)\n for (const version of versions) {\n if (is.number(this[':version']) && this[':version'] >= version) continue\n const versionFn = versioner[version]\n if (!is.function(versionFn)) continue\n versionFn.call(this, this)\n this[':version'] = version\n }\n })\n\n // Prepare methods:\n // - Create components for methods ending with `View`\n // - Bind all other methods to the unit instance\n for (const prototype of getPrototypes(this)) {\n const descriptors = Object.getOwnPropertyDescriptors(prototype)\n\n for (const [key, descriptor] of Object.entries(descriptors)) {\n if (key === 'constructor') continue\n if (this.hasOwnProperty(key)) continue\n\n if (descriptor.get || descriptor.set) continue\n if (!is.function(descriptor.value)) continue\n const fn = descriptor.value.bind(this)\n\n if (key.endsWith('View')) {\n let Component = epos.component(fn)\n Component.displayName = `${this.constructor.name}.${key}`\n setProperty(this, key, Component)\n } else {\n setProperty(this, key, fn)\n }\n }\n }\n\n // Setup state\n const stateDescriptor = Reflect.getOwnPropertyDescriptor(this.constructor.prototype, 'state')\n if (stateDescriptor && stateDescriptor.get) {\n const state = stateDescriptor.get.call(this)\n setProperty(this, 'state', epos.libs.mobx.observable.object(state, {}, { deep: false }))\n }\n\n // Process attach queue.\n // We do not execute `attach` methods immediately, but rather queue them\n // on the highest unattached ancestor. This way we ensure that `attach`\n // methods are called after all versioners have been applied in the entire subtree.\n const attach = Reflect.get(this, 'attach')\n if (is.function(attach)) {\n const unattachedRoot = findUnattachedRoot(this)\n if (!unattachedRoot) throw this.never()\n\n ensureProperty(unattachedRoot, _pendingAttachFns_, () => [])\n unattachedRoot[_pendingAttachFns_].push(() => attach())\n }\n\n if (this[_pendingAttachFns_]) {\n this[_pendingAttachFns_].forEach(attach => attach())\n delete this[_pendingAttachFns_]\n }\n\n // Mark as attached\n setProperty(this, _attached_, true)\n }\n\n /**\n * Lifecycle method called when the unit is detached from the state tree.\n */\n [epos.state.DETACH]() {\n // 1. Run disposers\n if (this[_disposers_]) {\n this[_disposers_].forEach(disposer => disposer())\n this[_disposers_].clear()\n }\n\n // 2. Call detach method\n const detach = Reflect.get(this, 'detach')\n if (is.function(detach)) detach()\n\n // 3. Clean up internal properties\n delete this[_root_]\n delete this[_attached_]\n delete this[_ancestors_]\n delete this[_disposers_]\n }\n\n /**\n * Gets the root unit of the current unit's tree.\n * The result is cached for subsequent calls.\n */\n get $() {\n ensureProperty(this, _root_, () => findRoot(this))\n return this[_root_]\n }\n\n /**\n * Finds the closest ancestor unit of a given type.\n * The result is cached for subsequent calls.\n */\n closest<T extends Unit>(Ancestor: Cls<T>) {\n // Has cached value? -> Return it\n ensureProperty(this, _ancestors_, () => new Map())\n if (this[_ancestors_].has(Ancestor)) return this[_ancestors_].get(Ancestor) as T\n\n // Find the closest ancestor and cache it\n let cursor: Node<TRoot> | null = this\n while (cursor) {\n if (cursor instanceof Ancestor) {\n this[_ancestors_].set(Ancestor, cursor)\n return cursor\n }\n cursor = getParent(cursor)\n }\n\n return null\n }\n\n /**\n * A wrapper around MobX's `autorun` that automatically disposes\n * the reaction when the unit is detached.\n */\n autorun(...args: Parameters<typeof epos.libs.mobx.autorun>) {\n const disposer = epos.libs.mobx.autorun(...args)\n ensureProperty(this, _disposers_, () => new Set())\n this[_disposers_].add(disposer)\n return disposer\n }\n\n /**\n * A wrapper around MobX's `reaction` that automatically disposes\n * the reaction when the unit is detached.\n */\n reaction(...args: Parameters<typeof epos.libs.mobx.reaction>) {\n const disposer = epos.libs.mobx.reaction(...args)\n ensureProperty(this, _disposers_, () => new Set())\n this[_disposers_].add(disposer)\n return disposer\n }\n\n /**\n * A wrapper around `setTimeout` that automatically clears the timeout\n * when the unit is detached.\n */\n setTimeout(...args: Parameters<typeof self.setTimeout>) {\n const id = self.setTimeout(...args)\n ensureProperty(this, _disposers_, () => new Set())\n this[_disposers_].add(() => self.clearTimeout(id))\n return id\n }\n\n /**\n * A wrapper around `setInterval` that automatically clears the interval\n * when the unit is detached.\n */\n setInterval(...args: Parameters<typeof self.setInterval>) {\n const id = self.setInterval(...args)\n ensureProperty(this, _disposers_, () => new Set())\n this[_disposers_].add(() => self.clearInterval(id))\n return id\n }\n\n /**\n * Creates an error for an unreachable code path.\n */\n never(message = 'This should never happen') {\n const details = message ? `: ${message}` : ''\n const error = new Error(`[${this.constructor.name}] This should never happen${details}`)\n Error.captureStackTrace(error, this.never)\n return error\n }\n}\n\n// ---------------------------------------------------------------------------\n// HELPERS\n// ---------------------------------------------------------------------------\n\n/**\n * Defines a configurable property on an object.\n */\nfunction setProperty(object: object, key: PropertyKey, value: unknown) {\n Reflect.defineProperty(object, key, {\n configurable: true,\n get: () => value,\n set: v => (value = v),\n })\n}\n\n/**\n * Ensures a property exists on an object, initializing it if it doesn't.\n */\nfunction ensureProperty<T extends object, K extends PropertyKey, V>(\n object: T,\n key: K,\n getInitialValue: () => V,\n): asserts object is T & { [key in K]: V } {\n if (key in object) return\n const value = getInitialValue()\n Reflect.defineProperty(object, key, { configurable: true, get: () => value })\n}\n\n/**\n * Gets all prototypes of an object up to `Object.prototype`.\n */\nfunction getPrototypes(object: object): object[] {\n const prototype = Reflect.getPrototypeOf(object)\n if (!prototype || prototype === Object.prototype) return []\n return [prototype, ...getPrototypes(prototype)]\n}\n\n/**\n * Finds the root `Unit` in the hierarchy for a given unit.\n */\nfunction findRoot<T>(unit: Unit<T>) {\n let root: Unit<T> | null = null\n let cursor: Node<T> | null = unit\n\n while (cursor) {\n if (cursor instanceof Unit) root = cursor\n cursor = getParent(cursor)\n }\n\n return root as T\n}\n\n/**\n * Finds the highest unattached `Unit` in the hierarchy for a given unit.\n */\nfunction findUnattachedRoot<T>(unit: Unit<T>) {\n let unattachedRoot: Unit<T> | null = null\n let cursor: Node<T> | null = unit\n\n while (cursor) {\n if (cursor instanceof Unit && !cursor[_attached_]) unattachedRoot = cursor\n cursor = getParent(cursor)\n }\n\n return unattachedRoot\n}\n\n/**\n * Gets the parent of a node, which can be a `Unit`, an object, or an array.\n */\nfunction getParent<T>(node: Node<T>) {\n const parent: Node<T> | null = Reflect.get(node, _parent_) ?? Reflect.get(node, epos.state.PARENT) ?? null\n return parent\n}\n\n/**\n * Gets the versioner object from a unit's constructor.\n */\nfunction getVersioner<T>(unit: Unit<T>) {\n const versioner: unknown = Reflect.get(unit.constructor, 'versioner')\n if (!is.object(versioner)) return {}\n return versioner\n}\n\n/**\n * Gets a sorted list of numeric version keys from a unit's versioner.\n */\nfunction getVersions<T>(unit: Unit<T>) {\n const versioner = getVersioner(unit)\n const numericKeys = Object.keys(versioner).filter(key => is.numeric(key))\n return numericKeys.map(Number).sort((v1, v2) => v1 - v2)\n}\n"],"mappings":"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","names":["attach"]}
|
|
1
|
+
{"version":3,"sources":["../src/epos-unit.ts"],"sourcesContent":["import type { Arr, Cls, Obj } from 'dropcap/types'\nimport { createLog, is } from 'dropcap/utils'\nimport 'epos'\nimport { customAlphabet } from 'nanoid'\n\nexport const _root_ = Symbol('root')\nexport const _parent_ = Symbol('parent')\nexport const _attached_ = Symbol('attached')\nexport const _disposers_ = Symbol('disposers')\nexport const _ancestors_ = Symbol('ancestors')\nexport const _pendingAttachHooks_ = Symbol('pendingAttachHooks')\nconst nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 8)\n\nexport type Node<T> = Unit<T> | Obj | Arr\n\nexport class Unit<TRoot = unknown> {\n declare '@': string\n declare id: string\n declare log: ReturnType<typeof createLog>;\n declare [':version']?: number;\n declare [_root_]?: TRoot;\n declare [_parent_]?: Unit<TRoot> | null; // Parent reference for a not-yet-attached units\n declare [_attached_]?: boolean;\n declare [_disposers_]?: Set<() => void>;\n declare [_ancestors_]?: Map<Cls, unknown>;\n declare [_pendingAttachHooks_]?: (() => void)[]\n\n constructor(parent: Unit<TRoot> | null) {\n this.id = nanoid()\n this[_parent_] = parent\n const versioner = getVersioner(this)\n const versions = getVersions(versioner)\n if (versions.length > 0) this[':version'] = versions.at(-1)!\n }\n\n // ---------------------------------------------------------------------------\n // ATTACH / DETACH\n // ---------------------------------------------------------------------------\n\n /**\n * Lifecycle method called when the unit is attached to the state tree.\n */\n [epos.state.ATTACH]() {\n // Apply versioner\n epos.state.transaction(() => {\n const versioner = getVersioner(this)\n const versions = getVersions(versioner)\n for (const version of versions) {\n if (is.number(this[':version']) && this[':version'] >= version) continue\n const versionFn = versioner[version]\n if (!is.function(versionFn)) continue\n versionFn.call(this, this)\n this[':version'] = version\n }\n })\n\n // Setup logger\n let log = createLog(this['@'])\n Reflect.defineProperty(this, 'log', {\n configurable: true,\n get: () => log,\n set: v => (log = v),\n })\n\n // Setup state\n const stateDescriptor = Reflect.getOwnPropertyDescriptor(this.constructor.prototype, 'state')\n if (stateDescriptor && stateDescriptor.get) {\n const value = stateDescriptor.get.call(this)\n const state = epos.state.local(value, { deep: false })\n Reflect.defineProperty(this, 'state', { get: () => state })\n }\n\n // Prepare properties for the whole prototype chain:\n // - Create components for methods ending with `View`\n // - Bind all other methods to the unit instance\n // - Turn getters into MobX computed properties\n for (const prototype of getPrototypes(this)) {\n const descriptors = Object.getOwnPropertyDescriptors(prototype)\n for (const [key, descriptor] of Object.entries(descriptors)) {\n // Skip constructor and already defined properties\n if (key === 'constructor') continue\n if (this.hasOwnProperty(key)) continue\n\n // Create components for methods ending with `View`\n if (is.function(descriptor.value) && key.endsWith('View')) {\n let Component = epos.component(descriptor.value.bind(this))\n Component.displayName = `${this.constructor.name}.${key}`\n Reflect.defineProperty(this, key, {\n configurable: true,\n get: () => Component,\n set: v => (Component = v),\n })\n }\n\n // Bind all other methods to the unit instance\n else if (is.function(descriptor.value)) {\n let method = descriptor.value.bind(this)\n Reflect.defineProperty(this, key, {\n configurable: true,\n get: () => method,\n set: v => (method = v),\n })\n }\n\n // Turn getters into MobX computed properties\n else if (descriptor.get) {\n const getter = descriptor.get\n const computed = epos.libs.mobx.computed(() => getter.call(this))\n Reflect.defineProperty(this, key, {\n configurable: true,\n get: () => computed.get(),\n set: descriptor.set,\n })\n }\n }\n }\n\n // Queue attach hook.\n // Do not execute `attach` hooks immediately, but rather queue them on the highest unattached ancestor.\n // This way `attach` hooks are called after all versioners have been applied in the entire subtree.\n const attach = Reflect.get(this, 'attach')\n if (is.function(attach)) {\n const unattachedRoot = findUnattachedRoot(this)\n if (!unattachedRoot) throw this.never()\n ensure(unattachedRoot, _pendingAttachHooks_, () => [])\n unattachedRoot[_pendingAttachHooks_].push(() => attach())\n }\n\n // Release attach hooks\n if (this[_pendingAttachHooks_]) {\n this[_pendingAttachHooks_].forEach(attach => attach())\n delete this[_pendingAttachHooks_]\n }\n\n // Mark as attached\n Reflect.defineProperty(this, _attached_, { configurable: true, get: () => true })\n }\n\n /**\n * Lifecycle method called when the unit is detached from the state tree.\n */\n [epos.state.DETACH]() {\n // Run and clear disposers\n if (this[_disposers_]) {\n this[_disposers_].forEach(disposer => disposer())\n this[_disposers_].clear()\n }\n\n // Clear ancestors cache\n if (this[_ancestors_]) {\n this[_ancestors_].clear()\n }\n\n // Call detach method\n const detach = Reflect.get(this, 'detach')\n if (is.function(detach)) detach()\n }\n\n // ---------------------------------------------------------------------------\n // ROOT GETTER\n // ---------------------------------------------------------------------------\n\n /**\n * Gets the root unit of the current unit's tree.\n * The result is cached for subsequent calls.\n */\n get $() {\n ensure(this, _root_, () => findRoot(this))\n return this[_root_]\n }\n\n // ---------------------------------------------------------------------------\n // METHODS\n // ---------------------------------------------------------------------------\n\n /**\n * A wrapper around MobX's `autorun` that automatically disposes\n * the reaction when the unit is detached.\n */\n autorun(...args: Parameters<typeof epos.libs.mobx.autorun>) {\n const disposer = epos.libs.mobx.autorun(...args)\n ensure(this, _disposers_, () => new Set())\n this[_disposers_].add(disposer)\n return disposer\n }\n\n /**\n * A wrapper around MobX's `reaction` that automatically disposes\n * the reaction when the unit is detached.\n */\n reaction(...args: Parameters<typeof epos.libs.mobx.reaction>) {\n const disposer = epos.libs.mobx.reaction(...args)\n ensure(this, _disposers_, () => new Set())\n this[_disposers_].add(disposer)\n return disposer\n }\n\n /**\n * A wrapper around `setTimeout` that automatically clears the timeout\n * when the unit is detached.\n */\n setTimeout(...args: Parameters<typeof self.setTimeout>) {\n const id = self.setTimeout(...args)\n ensure(this, _disposers_, () => new Set())\n this[_disposers_].add(() => self.clearTimeout(id))\n return id\n }\n\n /**\n * A wrapper around `setInterval` that automatically clears the interval\n * when the unit is detached.\n */\n setInterval(...args: Parameters<typeof self.setInterval>) {\n const id = self.setInterval(...args)\n ensure(this, _disposers_, () => new Set())\n this[_disposers_].add(() => self.clearInterval(id))\n return id\n }\n\n /**\n * Creates an error for an unreachable code path.\n */\n never(message = 'This should never happen') {\n const details = message ? `: ${message}` : ''\n const error = new Error(`[${this.constructor.name}] This should never happen${details}`)\n Error.captureStackTrace(error, this.never)\n return error\n }\n\n /**\n * Finds the closest ancestor unit of a given type.\n * The result is cached for subsequent calls.\n */\n closest<T extends Unit>(Ancestor: Cls<T>) {\n // Has cached value? -> Return it\n ensure(this, _ancestors_, () => new Map())\n if (this[_ancestors_].has(Ancestor)) return this[_ancestors_].get(Ancestor) as T\n\n // Find the closest ancestor and cache it\n let cursor: Node<TRoot> | null = this\n while (cursor) {\n if (cursor instanceof Ancestor) {\n this[_ancestors_].set(Ancestor, cursor)\n return cursor\n }\n cursor = getParent(cursor)\n }\n\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// HELPERS\n// ---------------------------------------------------------------------------\n\n/**\n * Ensures a property exists on an object, initializing it if it doesn't.\n */\nfunction ensure<T extends object, K extends PropertyKey, V>(\n object: T,\n key: K,\n getInitialValue: () => V,\n): asserts object is T & { [key in K]: V } {\n if (key in object) return\n const value = getInitialValue()\n Reflect.defineProperty(object, key, { configurable: true, get: () => value })\n}\n\n/**\n * Gets all prototypes of an object up to `Object.prototype`.\n */\nfunction getPrototypes(object: object): object[] {\n const prototype = Reflect.getPrototypeOf(object)\n if (!prototype || prototype === Object.prototype) return []\n return [prototype, ...getPrototypes(prototype)]\n}\n\n/**\n * Finds the root `Unit` in the hierarchy for a given unit.\n */\nfunction findRoot<T>(unit: Unit<T>) {\n let root: Unit<T> | null = null\n let cursor: Node<T> | null = unit\n\n while (cursor) {\n if (cursor instanceof Unit) root = cursor\n cursor = getParent(cursor)\n }\n\n return root as T\n}\n\n/**\n * Finds the highest unattached `Unit` in the hierarchy for a given unit.\n */\nfunction findUnattachedRoot<T>(unit: Unit<T>) {\n let unattachedRoot: Unit<T> | null = null\n let cursor: Node<T> | null = unit\n\n while (cursor) {\n if (cursor instanceof Unit && !cursor[_attached_]) unattachedRoot = cursor\n cursor = getParent(cursor)\n }\n\n return unattachedRoot\n}\n\n/**\n * Gets the parent of a node, which can be a `Unit`, an object, or an array.\n */\nfunction getParent<T>(node: Node<T>) {\n const parent: Node<T> | null = Reflect.get(node, _parent_) ?? Reflect.get(node, epos.state.PARENT) ?? null\n return parent\n}\n\n/**\n * Gets the versioner object from a unit's constructor.\n */\nfunction getVersioner<T>(unit: Unit<T>) {\n const versioner: unknown = Reflect.get(unit.constructor, 'versioner')\n if (!is.object(versioner)) return {}\n return versioner\n}\n\n/**\n * Gets a sorted list of numeric version keys from a unit's versioner.\n */\nfunction getVersions(versioner: Obj) {\n const numericKeys = Object.keys(versioner).filter(key => is.numeric(key))\n return numericKeys.map(Number).sort((v1, v2) => v1 - v2)\n}\n"],"mappings":"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","names":["attach"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epos-unit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "imkost",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"dropcap": "^1.4.0",
|
|
24
|
-
"epos": "^1.32.0"
|
|
24
|
+
"epos": "^1.32.0",
|
|
25
|
+
"nanoid": "^3.3.11"
|
|
25
26
|
}
|
|
26
27
|
}
|
package/src/epos-unit.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import type { Arr, Cls, Obj } from 'dropcap/types'
|
|
2
2
|
import { createLog, is } from 'dropcap/utils'
|
|
3
3
|
import 'epos'
|
|
4
|
+
import { customAlphabet } from 'nanoid'
|
|
4
5
|
|
|
5
6
|
export const _root_ = Symbol('root')
|
|
6
7
|
export const _parent_ = Symbol('parent')
|
|
7
8
|
export const _attached_ = Symbol('attached')
|
|
8
9
|
export const _disposers_ = Symbol('disposers')
|
|
9
10
|
export const _ancestors_ = Symbol('ancestors')
|
|
10
|
-
export const
|
|
11
|
+
export const _pendingAttachHooks_ = Symbol('pendingAttachHooks')
|
|
12
|
+
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 8)
|
|
11
13
|
|
|
12
14
|
export type Node<T> = Unit<T> | Obj | Arr
|
|
13
15
|
|
|
14
16
|
export class Unit<TRoot = unknown> {
|
|
15
17
|
declare '@': string
|
|
18
|
+
declare id: string
|
|
16
19
|
declare log: ReturnType<typeof createLog>;
|
|
17
20
|
declare [':version']?: number;
|
|
18
21
|
declare [_root_]?: TRoot;
|
|
@@ -20,25 +23,28 @@ export class Unit<TRoot = unknown> {
|
|
|
20
23
|
declare [_attached_]?: boolean;
|
|
21
24
|
declare [_disposers_]?: Set<() => void>;
|
|
22
25
|
declare [_ancestors_]?: Map<Cls, unknown>;
|
|
23
|
-
declare [
|
|
26
|
+
declare [_pendingAttachHooks_]?: (() => void)[]
|
|
24
27
|
|
|
25
28
|
constructor(parent: Unit<TRoot> | null) {
|
|
29
|
+
this.id = nanoid()
|
|
26
30
|
this[_parent_] = parent
|
|
27
|
-
const
|
|
31
|
+
const versioner = getVersioner(this)
|
|
32
|
+
const versions = getVersions(versioner)
|
|
28
33
|
if (versions.length > 0) this[':version'] = versions.at(-1)!
|
|
29
34
|
}
|
|
30
35
|
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// ATTACH / DETACH
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
31
40
|
/**
|
|
32
41
|
* Lifecycle method called when the unit is attached to the state tree.
|
|
33
42
|
*/
|
|
34
43
|
[epos.state.ATTACH]() {
|
|
35
|
-
// Setup logger
|
|
36
|
-
setProperty(this, 'log', createLog(this['@']))
|
|
37
|
-
|
|
38
44
|
// Apply versioner
|
|
39
45
|
epos.state.transaction(() => {
|
|
40
46
|
const versioner = getVersioner(this)
|
|
41
|
-
const versions = getVersions(
|
|
47
|
+
const versions = getVersions(versioner)
|
|
42
48
|
for (const version of versions) {
|
|
43
49
|
if (is.number(this[':version']) && this[':version'] >= version) continue
|
|
44
50
|
const versionFn = versioner[version]
|
|
@@ -48,110 +54,124 @@ export class Unit<TRoot = unknown> {
|
|
|
48
54
|
}
|
|
49
55
|
})
|
|
50
56
|
|
|
51
|
-
//
|
|
57
|
+
// Setup logger
|
|
58
|
+
let log = createLog(this['@'])
|
|
59
|
+
Reflect.defineProperty(this, 'log', {
|
|
60
|
+
configurable: true,
|
|
61
|
+
get: () => log,
|
|
62
|
+
set: v => (log = v),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Setup state
|
|
66
|
+
const stateDescriptor = Reflect.getOwnPropertyDescriptor(this.constructor.prototype, 'state')
|
|
67
|
+
if (stateDescriptor && stateDescriptor.get) {
|
|
68
|
+
const value = stateDescriptor.get.call(this)
|
|
69
|
+
const state = epos.state.local(value, { deep: false })
|
|
70
|
+
Reflect.defineProperty(this, 'state', { get: () => state })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Prepare properties for the whole prototype chain:
|
|
52
74
|
// - Create components for methods ending with `View`
|
|
53
75
|
// - Bind all other methods to the unit instance
|
|
76
|
+
// - Turn getters into MobX computed properties
|
|
54
77
|
for (const prototype of getPrototypes(this)) {
|
|
55
78
|
const descriptors = Object.getOwnPropertyDescriptors(prototype)
|
|
56
|
-
|
|
57
79
|
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
80
|
+
// Skip constructor and already defined properties
|
|
58
81
|
if (key === 'constructor') continue
|
|
59
82
|
if (this.hasOwnProperty(key)) continue
|
|
60
83
|
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (key.endsWith('View')) {
|
|
66
|
-
let Component = epos.component(fn)
|
|
84
|
+
// Create components for methods ending with `View`
|
|
85
|
+
if (is.function(descriptor.value) && key.endsWith('View')) {
|
|
86
|
+
let Component = epos.component(descriptor.value.bind(this))
|
|
67
87
|
Component.displayName = `${this.constructor.name}.${key}`
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
88
|
+
Reflect.defineProperty(this, key, {
|
|
89
|
+
configurable: true,
|
|
90
|
+
get: () => Component,
|
|
91
|
+
set: v => (Component = v),
|
|
92
|
+
})
|
|
71
93
|
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
94
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
95
|
+
// Bind all other methods to the unit instance
|
|
96
|
+
else if (is.function(descriptor.value)) {
|
|
97
|
+
let method = descriptor.value.bind(this)
|
|
98
|
+
Reflect.defineProperty(this, key, {
|
|
99
|
+
configurable: true,
|
|
100
|
+
get: () => method,
|
|
101
|
+
set: v => (method = v),
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Turn getters into MobX computed properties
|
|
106
|
+
else if (descriptor.get) {
|
|
107
|
+
const getter = descriptor.get
|
|
108
|
+
const computed = epos.libs.mobx.computed(() => getter.call(this))
|
|
109
|
+
Reflect.defineProperty(this, key, {
|
|
110
|
+
configurable: true,
|
|
111
|
+
get: () => computed.get(),
|
|
112
|
+
set: descriptor.set,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
}
|
|
80
116
|
}
|
|
81
117
|
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// methods are called after all versioners have been applied in the entire subtree.
|
|
118
|
+
// Queue attach hook.
|
|
119
|
+
// Do not execute `attach` hooks immediately, but rather queue them on the highest unattached ancestor.
|
|
120
|
+
// This way `attach` hooks are called after all versioners have been applied in the entire subtree.
|
|
86
121
|
const attach = Reflect.get(this, 'attach')
|
|
87
122
|
if (is.function(attach)) {
|
|
88
123
|
const unattachedRoot = findUnattachedRoot(this)
|
|
89
124
|
if (!unattachedRoot) throw this.never()
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
unattachedRoot[_pendingAttachFns_].push(() => attach())
|
|
125
|
+
ensure(unattachedRoot, _pendingAttachHooks_, () => [])
|
|
126
|
+
unattachedRoot[_pendingAttachHooks_].push(() => attach())
|
|
93
127
|
}
|
|
94
128
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
129
|
+
// Release attach hooks
|
|
130
|
+
if (this[_pendingAttachHooks_]) {
|
|
131
|
+
this[_pendingAttachHooks_].forEach(attach => attach())
|
|
132
|
+
delete this[_pendingAttachHooks_]
|
|
98
133
|
}
|
|
99
134
|
|
|
100
135
|
// Mark as attached
|
|
101
|
-
|
|
136
|
+
Reflect.defineProperty(this, _attached_, { configurable: true, get: () => true })
|
|
102
137
|
}
|
|
103
138
|
|
|
104
139
|
/**
|
|
105
140
|
* Lifecycle method called when the unit is detached from the state tree.
|
|
106
141
|
*/
|
|
107
142
|
[epos.state.DETACH]() {
|
|
108
|
-
//
|
|
143
|
+
// Run and clear disposers
|
|
109
144
|
if (this[_disposers_]) {
|
|
110
145
|
this[_disposers_].forEach(disposer => disposer())
|
|
111
146
|
this[_disposers_].clear()
|
|
112
147
|
}
|
|
113
148
|
|
|
114
|
-
//
|
|
149
|
+
// Clear ancestors cache
|
|
150
|
+
if (this[_ancestors_]) {
|
|
151
|
+
this[_ancestors_].clear()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Call detach method
|
|
115
155
|
const detach = Reflect.get(this, 'detach')
|
|
116
156
|
if (is.function(detach)) detach()
|
|
117
|
-
|
|
118
|
-
// 3. Clean up internal properties
|
|
119
|
-
delete this[_root_]
|
|
120
|
-
delete this[_attached_]
|
|
121
|
-
delete this[_ancestors_]
|
|
122
|
-
delete this[_disposers_]
|
|
123
157
|
}
|
|
124
158
|
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// ROOT GETTER
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
125
163
|
/**
|
|
126
164
|
* Gets the root unit of the current unit's tree.
|
|
127
165
|
* The result is cached for subsequent calls.
|
|
128
166
|
*/
|
|
129
167
|
get $() {
|
|
130
|
-
|
|
168
|
+
ensure(this, _root_, () => findRoot(this))
|
|
131
169
|
return this[_root_]
|
|
132
170
|
}
|
|
133
171
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
*/
|
|
138
|
-
closest<T extends Unit>(Ancestor: Cls<T>) {
|
|
139
|
-
// Has cached value? -> Return it
|
|
140
|
-
ensureProperty(this, _ancestors_, () => new Map())
|
|
141
|
-
if (this[_ancestors_].has(Ancestor)) return this[_ancestors_].get(Ancestor) as T
|
|
142
|
-
|
|
143
|
-
// Find the closest ancestor and cache it
|
|
144
|
-
let cursor: Node<TRoot> | null = this
|
|
145
|
-
while (cursor) {
|
|
146
|
-
if (cursor instanceof Ancestor) {
|
|
147
|
-
this[_ancestors_].set(Ancestor, cursor)
|
|
148
|
-
return cursor
|
|
149
|
-
}
|
|
150
|
-
cursor = getParent(cursor)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return null
|
|
154
|
-
}
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// METHODS
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
155
175
|
|
|
156
176
|
/**
|
|
157
177
|
* A wrapper around MobX's `autorun` that automatically disposes
|
|
@@ -159,7 +179,7 @@ export class Unit<TRoot = unknown> {
|
|
|
159
179
|
*/
|
|
160
180
|
autorun(...args: Parameters<typeof epos.libs.mobx.autorun>) {
|
|
161
181
|
const disposer = epos.libs.mobx.autorun(...args)
|
|
162
|
-
|
|
182
|
+
ensure(this, _disposers_, () => new Set())
|
|
163
183
|
this[_disposers_].add(disposer)
|
|
164
184
|
return disposer
|
|
165
185
|
}
|
|
@@ -170,7 +190,7 @@ export class Unit<TRoot = unknown> {
|
|
|
170
190
|
*/
|
|
171
191
|
reaction(...args: Parameters<typeof epos.libs.mobx.reaction>) {
|
|
172
192
|
const disposer = epos.libs.mobx.reaction(...args)
|
|
173
|
-
|
|
193
|
+
ensure(this, _disposers_, () => new Set())
|
|
174
194
|
this[_disposers_].add(disposer)
|
|
175
195
|
return disposer
|
|
176
196
|
}
|
|
@@ -181,7 +201,7 @@ export class Unit<TRoot = unknown> {
|
|
|
181
201
|
*/
|
|
182
202
|
setTimeout(...args: Parameters<typeof self.setTimeout>) {
|
|
183
203
|
const id = self.setTimeout(...args)
|
|
184
|
-
|
|
204
|
+
ensure(this, _disposers_, () => new Set())
|
|
185
205
|
this[_disposers_].add(() => self.clearTimeout(id))
|
|
186
206
|
return id
|
|
187
207
|
}
|
|
@@ -192,7 +212,7 @@ export class Unit<TRoot = unknown> {
|
|
|
192
212
|
*/
|
|
193
213
|
setInterval(...args: Parameters<typeof self.setInterval>) {
|
|
194
214
|
const id = self.setInterval(...args)
|
|
195
|
-
|
|
215
|
+
ensure(this, _disposers_, () => new Set())
|
|
196
216
|
this[_disposers_].add(() => self.clearInterval(id))
|
|
197
217
|
return id
|
|
198
218
|
}
|
|
@@ -206,27 +226,38 @@ export class Unit<TRoot = unknown> {
|
|
|
206
226
|
Error.captureStackTrace(error, this.never)
|
|
207
227
|
return error
|
|
208
228
|
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Finds the closest ancestor unit of a given type.
|
|
232
|
+
* The result is cached for subsequent calls.
|
|
233
|
+
*/
|
|
234
|
+
closest<T extends Unit>(Ancestor: Cls<T>) {
|
|
235
|
+
// Has cached value? -> Return it
|
|
236
|
+
ensure(this, _ancestors_, () => new Map())
|
|
237
|
+
if (this[_ancestors_].has(Ancestor)) return this[_ancestors_].get(Ancestor) as T
|
|
238
|
+
|
|
239
|
+
// Find the closest ancestor and cache it
|
|
240
|
+
let cursor: Node<TRoot> | null = this
|
|
241
|
+
while (cursor) {
|
|
242
|
+
if (cursor instanceof Ancestor) {
|
|
243
|
+
this[_ancestors_].set(Ancestor, cursor)
|
|
244
|
+
return cursor
|
|
245
|
+
}
|
|
246
|
+
cursor = getParent(cursor)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
209
251
|
}
|
|
210
252
|
|
|
211
253
|
// ---------------------------------------------------------------------------
|
|
212
254
|
// HELPERS
|
|
213
255
|
// ---------------------------------------------------------------------------
|
|
214
256
|
|
|
215
|
-
/**
|
|
216
|
-
* Defines a configurable property on an object.
|
|
217
|
-
*/
|
|
218
|
-
function setProperty(object: object, key: PropertyKey, value: unknown) {
|
|
219
|
-
Reflect.defineProperty(object, key, {
|
|
220
|
-
configurable: true,
|
|
221
|
-
get: () => value,
|
|
222
|
-
set: v => (value = v),
|
|
223
|
-
})
|
|
224
|
-
}
|
|
225
|
-
|
|
226
257
|
/**
|
|
227
258
|
* Ensures a property exists on an object, initializing it if it doesn't.
|
|
228
259
|
*/
|
|
229
|
-
function
|
|
260
|
+
function ensure<T extends object, K extends PropertyKey, V>(
|
|
230
261
|
object: T,
|
|
231
262
|
key: K,
|
|
232
263
|
getInitialValue: () => V,
|
|
@@ -295,8 +326,7 @@ function getVersioner<T>(unit: Unit<T>) {
|
|
|
295
326
|
/**
|
|
296
327
|
* Gets a sorted list of numeric version keys from a unit's versioner.
|
|
297
328
|
*/
|
|
298
|
-
function getVersions
|
|
299
|
-
const versioner = getVersioner(unit)
|
|
329
|
+
function getVersions(versioner: Obj) {
|
|
300
330
|
const numericKeys = Object.keys(versioner).filter(key => is.numeric(key))
|
|
301
331
|
return numericKeys.map(Number).sort((v1, v2) => v1 - v2)
|
|
302
332
|
}
|