epos-unit 1.16.0 → 1.18.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.
@@ -1,15 +1,22 @@
1
- import * as mobx from 'mobx';
2
- import { Cls, Obj, Arr } from 'dropcap/types';
1
+ import type { Arr, Cls, Obj } from 'dropcap/types';
3
2
  import { createLog } from 'dropcap/utils';
4
-
5
- declare const _root_: unique symbol;
6
- declare const _parent_: unique symbol;
7
- declare const _attached_: unique symbol;
8
- declare const _disposers_: unique symbol;
9
- declare const _ancestors_: unique symbol;
10
- declare const _pendingAttachFns_: unique symbol;
11
- type Node<T> = Unit<T> | Obj | Arr;
12
- declare class Unit<TRoot = unknown> {
3
+ import 'epos';
4
+ declare global {
5
+ interface ErrorConstructor {
6
+ captureStackTrace(targetObject: object, constructorOpt?: Function): void;
7
+ }
8
+ }
9
+ export declare const _root_: unique symbol;
10
+ export declare const _parent_: unique symbol;
11
+ export declare const _attached_: unique symbol;
12
+ export declare const _disposers_: unique symbol;
13
+ export declare const _ancestors_: unique symbol;
14
+ export declare const _attachQueue_: unique symbol;
15
+ export type Node<T> = Unit<T> | Obj | Arr;
16
+ export type Versioner<T> = {
17
+ [version: number]: (this: T) => void;
18
+ };
19
+ export declare class Unit<TRoot = unknown> {
13
20
  /**
14
21
  * Lifecycle method called when the unit is attached to the state tree.
15
22
  */
@@ -19,6 +26,7 @@ declare class Unit<TRoot = unknown> {
19
26
  */
20
27
  [epos.state.DETACH]: () => void;
21
28
  '@': string;
29
+ id: string;
22
30
  log: ReturnType<typeof createLog>;
23
31
  [':version']?: number;
24
32
  [_root_]?: TRoot;
@@ -26,42 +34,37 @@ declare class Unit<TRoot = unknown> {
26
34
  [_attached_]?: boolean;
27
35
  [_disposers_]?: Set<() => void>;
28
36
  [_ancestors_]?: Map<Cls, unknown>;
29
- [_pendingAttachFns_]?: (() => void)[];
37
+ [_attachQueue_]?: (() => void)[];
38
+ static defineVersioner<T extends Unit>(this: Cls<T>, versioner: Versioner<T>): Versioner<T>;
30
39
  constructor(parent: Unit<TRoot> | null);
31
40
  /**
32
- * Gets the root unit of the current unit's tree.
41
+ * Get the root unit of the current unit's tree.
33
42
  * The result is cached for subsequent calls.
34
43
  */
35
44
  get $(): TRoot | (undefined & TRoot);
36
45
  /**
37
- * Finds the closest ancestor unit of a given type.
38
- * The result is cached for subsequent calls.
46
+ * A wrapper around MobX's `autorun` that automatically disposes the reaction when the unit is detached.
39
47
  */
40
- closest<T extends Unit>(Ancestor: Cls<T>): T | null;
48
+ autorun(...args: Parameters<typeof epos.libs.mobx.autorun>): import("mobx").IReactionDisposer;
41
49
  /**
42
- * A wrapper around MobX's `autorun` that automatically disposes
43
- * the reaction when the unit is detached.
50
+ * A wrapper around MobX's `reaction` that automatically disposes the reaction when the unit is detached.
44
51
  */
45
- autorun(...args: Parameters<typeof epos.libs.mobx.autorun>): mobx.IReactionDisposer;
52
+ reaction(...args: Parameters<typeof epos.libs.mobx.reaction>): import("mobx").IReactionDisposer;
46
53
  /**
47
- * A wrapper around MobX's `reaction` that automatically disposes
48
- * the reaction when the unit is detached.
54
+ * A wrapper around `setTimeout` that automatically clears the timeout when the unit is detached.
49
55
  */
50
- reaction(...args: Parameters<typeof epos.libs.mobx.reaction>): mobx.IReactionDisposer;
56
+ setTimeout(...args: Parameters<typeof setTimeout>): NodeJS.Timeout;
51
57
  /**
52
- * A wrapper around `setTimeout` that automatically clears the timeout
53
- * when the unit is detached.
58
+ * A wrapper around `setInterval` that automatically clears the interval when the unit is detached.
54
59
  */
55
- setTimeout(...args: Parameters<typeof self.setTimeout>): number;
60
+ setInterval(...args: Parameters<typeof setInterval>): NodeJS.Timeout;
56
61
  /**
57
- * A wrapper around `setInterval` that automatically clears the interval
58
- * when the unit is detached.
62
+ * Create an error for code paths that are logically unreachable.
59
63
  */
60
- setInterval(...args: Parameters<typeof self.setInterval>): number;
64
+ never(message?: string): Error;
61
65
  /**
62
- * Creates an error for an unreachable code path.
66
+ * Find the closest ancestor unit of a given type.
67
+ * The result is cached for subsequent calls.
63
68
  */
64
- never(message?: string): Error;
69
+ closest<T extends Unit>(Ancestor: Cls<T>): T | null;
65
70
  }
66
-
67
- export { type Node, Unit, _ancestors_, _attached_, _disposers_, _parent_, _pendingAttachFns_, _root_ };
package/dist/epos-unit.js CHANGED
@@ -1,215 +1,313 @@
1
- // src/epos-unit.ts
2
- import { createLog, is } from "dropcap/utils";
3
- import "epos";
4
- var _root_ = /* @__PURE__ */ Symbol("root");
5
- var _parent_ = /* @__PURE__ */ Symbol("parent");
6
- var _attached_ = /* @__PURE__ */ Symbol("attached");
7
- var _disposers_ = /* @__PURE__ */ Symbol("disposers");
8
- var _ancestors_ = /* @__PURE__ */ Symbol("ancestors");
9
- var _pendingAttachFns_ = /* @__PURE__ */ Symbol("pendingAttachFns");
10
- var Unit = class {
11
- constructor(parent) {
12
- this[_parent_] = parent;
13
- const versions = getVersions(this);
14
- if (versions.length > 0) this[":version"] = versions.at(-1);
15
- }
16
- /**
17
- * Lifecycle method called when the unit is attached to the state tree.
18
- */
19
- [epos.state.ATTACH]() {
20
- setProperty(this, "log", createLog(this["@"]));
21
- epos.state.transaction(() => {
22
- const versioner = getVersioner(this);
23
- const versions = getVersions(this);
24
- for (const version of versions) {
25
- if (is.number(this[":version"]) && this[":version"] >= version) continue;
26
- const versionFn = versioner[version];
27
- if (!is.function(versionFn)) continue;
28
- versionFn.call(this, this);
29
- this[":version"] = version;
30
- }
31
- });
32
- for (const prototype of getPrototypes(this)) {
33
- const descriptors = Object.getOwnPropertyDescriptors(prototype);
34
- for (const [key, descriptor] of Object.entries(descriptors)) {
35
- if (key === "constructor") continue;
36
- if (this.hasOwnProperty(key)) continue;
37
- if (descriptor.get || descriptor.set) continue;
38
- if (!is.function(descriptor.value)) continue;
39
- const fn = descriptor.value.bind(this);
40
- if (key.endsWith("View")) {
41
- let Component = epos.component(fn);
42
- Component.displayName = `${this.constructor.name}.${key}`;
43
- setProperty(this, key, Component);
44
- } else {
45
- setProperty(this, key, fn);
1
+ import { createLog, is } from 'dropcap/utils';
2
+ import 'epos';
3
+ import { customAlphabet } from 'nanoid';
4
+ export const _root_ = Symbol('root');
5
+ export const _parent_ = Symbol('parent');
6
+ export const _attached_ = Symbol('attached');
7
+ export const _disposers_ = Symbol('disposers');
8
+ export const _ancestors_ = Symbol('ancestors');
9
+ export const _attachQueue_ = Symbol('pendingAttachHooks');
10
+ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 10);
11
+ export class Unit {
12
+ static defineVersioner(versioner) {
13
+ return versioner;
14
+ }
15
+ constructor(parent) {
16
+ this.id = nanoid();
17
+ this[_parent_] = parent;
18
+ }
19
+ // ---------------------------------------------------------------------------
20
+ // ATTACH
21
+ // ---------------------------------------------------------------------------
22
+ /**
23
+ * Lifecycle method called when the unit is attached to the state tree.
24
+ */
25
+ [epos.state.ATTACH]() {
26
+ // Setup logger
27
+ let log = createLog(this['@']);
28
+ Reflect.defineProperty(this, 'log', { configurable: true, get: () => log, set: v => (log = v) });
29
+ // Apply versioner
30
+ void (() => {
31
+ const versioner = Reflect.get(this.constructor, 'versioner');
32
+ if (!is.object(versioner))
33
+ return;
34
+ const asc = (v1, v2) => v1 - v2;
35
+ const versions = Object.keys(versioner).filter(is.numeric).map(Number).sort(asc);
36
+ if (versions.length === 0)
37
+ return;
38
+ epos.state.transaction(() => {
39
+ for (const version of versions) {
40
+ if (is.number(this[':version']) && this[':version'] >= version)
41
+ continue;
42
+ const versionFn = versioner[version];
43
+ if (!is.function(versionFn))
44
+ continue;
45
+ versionFn.call(this);
46
+ this[':version'] = version;
47
+ }
48
+ });
49
+ })();
50
+ // Setup state
51
+ void (() => {
52
+ const descriptor = Reflect.getOwnPropertyDescriptor(this.constructor.prototype, 'state');
53
+ if (!descriptor || !descriptor.get)
54
+ return;
55
+ const value = descriptor.get.call(this);
56
+ if (!is.object(value))
57
+ throw new Error(`'state' getter return an object`);
58
+ const state = epos.state.create(value);
59
+ Reflect.defineProperty(state, epos.state.PARENT, { configurable: true, value: this });
60
+ Reflect.defineProperty(this, 'state', { enumerable: true, get: () => state });
61
+ })();
62
+ // Setup inert
63
+ void (() => {
64
+ const descriptor = Reflect.getOwnPropertyDescriptor(this.constructor.prototype, 'inert');
65
+ if (!descriptor || !descriptor.get)
66
+ return;
67
+ const value = descriptor.get.call(this);
68
+ if (!is.object(value))
69
+ throw new Error(`'inert' getter return an object`);
70
+ Reflect.defineProperty(this, 'inert', { enumerable: true, get: () => value });
71
+ })();
72
+ // Prepare properties for the whole prototype chain:
73
+ // - Create components for methods ending with `View`
74
+ // - Bind all other methods to the unit instance
75
+ // - Turn getters into MobX computed properties
76
+ for (const prototype of getPrototypes(this)) {
77
+ const descriptors = Object.getOwnPropertyDescriptors(prototype);
78
+ for (const [key, descriptor] of Object.entries(descriptors)) {
79
+ // Skip constructor and already defined properties
80
+ if (key === 'constructor')
81
+ continue;
82
+ if (this.hasOwnProperty(key))
83
+ continue;
84
+ // Create components for methods ending with `View`
85
+ if (is.function(descriptor.value) && key.endsWith('View')) {
86
+ let View = createView(this, key, descriptor.value.bind(this));
87
+ Reflect.defineProperty(this, key, { configurable: true, get: () => View, set: v => (View = v) });
88
+ }
89
+ // Bind all other methods to the unit instance
90
+ else if (is.function(descriptor.value)) {
91
+ let method = descriptor.value.bind(this);
92
+ Reflect.defineProperty(this, key, {
93
+ configurable: true,
94
+ get: () => method,
95
+ set: v => (method = v),
96
+ });
97
+ }
98
+ // Turn getters into MobX computed properties
99
+ else if (descriptor.get) {
100
+ const getter = descriptor.get;
101
+ const computed = epos.libs.mobx.computed(() => getter.call(this));
102
+ Reflect.defineProperty(this, key, {
103
+ configurable: true,
104
+ get: () => computed.get(),
105
+ set: descriptor.set,
106
+ });
107
+ }
108
+ }
109
+ }
110
+ // Queue attach method.
111
+ // Do not execute `attach` methods immediately, but rather queue them on the highest unattached ancestor.
112
+ // This way `attach` methods are called after all versioners have been applied in the entire subtree.
113
+ const attach = Reflect.get(this, 'attach');
114
+ if (is.function(attach)) {
115
+ const head = findUnattachedRoot(this);
116
+ if (!head)
117
+ throw this.never();
118
+ ensure(head, _attachQueue_, () => []);
119
+ head[_attachQueue_].push(() => attach.call(this));
120
+ }
121
+ // Release attach queue
122
+ if (this[_attachQueue_]) {
123
+ this[_attachQueue_].forEach(attach => attach());
124
+ delete this[_attachQueue_];
46
125
  }
47
- }
126
+ // Mark as attached
127
+ Reflect.defineProperty(this, _attached_, { configurable: true, get: () => true });
48
128
  }
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 }));
129
+ // ---------------------------------------------------------------------------
130
+ // DETACH
131
+ // ---------------------------------------------------------------------------
132
+ /**
133
+ * Lifecycle method called when the unit is detached from the state tree.
134
+ */
135
+ [epos.state.DETACH]() {
136
+ // Run and clear disposers
137
+ if (this[_disposers_]) {
138
+ this[_disposers_].forEach(disposer => disposer());
139
+ this[_disposers_].clear();
140
+ }
141
+ // Clear ancestors cache
142
+ if (this[_ancestors_]) {
143
+ this[_ancestors_].clear();
144
+ }
145
+ // Call detach method
146
+ const detach = Reflect.get(this, 'detach');
147
+ if (is.function(detach))
148
+ detach();
53
149
  }
54
- const attach = Reflect.get(this, "attach");
55
- if (is.function(attach)) {
56
- const unattachedRoot = findUnattachedRoot(this);
57
- if (!unattachedRoot) throw this.never();
58
- ensureProperty(unattachedRoot, _pendingAttachFns_, () => []);
59
- unattachedRoot[_pendingAttachFns_].push(() => attach());
150
+ // ---------------------------------------------------------------------------
151
+ // ROOT GETTER
152
+ // ---------------------------------------------------------------------------
153
+ /**
154
+ * Get the root unit of the current unit's tree.
155
+ * The result is cached for subsequent calls.
156
+ */
157
+ get $() {
158
+ ensure(this, _root_, () => findRoot(this));
159
+ return this[_root_];
60
160
  }
61
- if (this[_pendingAttachFns_]) {
62
- this[_pendingAttachFns_].forEach((attach2) => attach2());
63
- delete this[_pendingAttachFns_];
161
+ // ---------------------------------------------------------------------------
162
+ // METHODS
163
+ // ---------------------------------------------------------------------------
164
+ /**
165
+ * A wrapper around MobX's `autorun` that automatically disposes the reaction when the unit is detached.
166
+ */
167
+ autorun(...args) {
168
+ const disposer = epos.libs.mobx.autorun(...args);
169
+ ensure(this, _disposers_, () => new Set());
170
+ this[_disposers_].add(disposer);
171
+ return disposer;
64
172
  }
65
- setProperty(this, _attached_, true);
66
- }
67
- /**
68
- * Lifecycle method called when the unit is detached from the state tree.
69
- */
70
- [epos.state.DETACH]() {
71
- if (this[_disposers_]) {
72
- this[_disposers_].forEach((disposer) => disposer());
73
- this[_disposers_].clear();
173
+ /**
174
+ * A wrapper around MobX's `reaction` that automatically disposes the reaction when the unit is detached.
175
+ */
176
+ reaction(...args) {
177
+ const disposer = epos.libs.mobx.reaction(...args);
178
+ ensure(this, _disposers_, () => new Set());
179
+ this[_disposers_].add(disposer);
180
+ return disposer;
74
181
  }
75
- const detach = Reflect.get(this, "detach");
76
- if (is.function(detach)) detach();
77
- delete this[_root_];
78
- delete this[_attached_];
79
- delete this[_ancestors_];
80
- delete this[_disposers_];
81
- }
82
- /**
83
- * Gets the root unit of the current unit's tree.
84
- * The result is cached for subsequent calls.
85
- */
86
- get $() {
87
- ensureProperty(this, _root_, () => findRoot(this));
88
- return this[_root_];
89
- }
90
- /**
91
- * Finds the closest ancestor unit of a given type.
92
- * The result is cached for subsequent calls.
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);
182
+ /**
183
+ * A wrapper around `setTimeout` that automatically clears the timeout when the unit is detached.
184
+ */
185
+ setTimeout(...args) {
186
+ const id = setTimeout(...args);
187
+ ensure(this, _disposers_, () => new Set());
188
+ this[_disposers_].add(() => clearTimeout(id));
189
+ return id;
190
+ }
191
+ /**
192
+ * A wrapper around `setInterval` that automatically clears the interval when the unit is detached.
193
+ */
194
+ setInterval(...args) {
195
+ const id = setInterval(...args);
196
+ ensure(this, _disposers_, () => new Set());
197
+ this[_disposers_].add(() => clearInterval(id));
198
+ return id;
199
+ }
200
+ /**
201
+ * Create an error for code paths that are logically unreachable.
202
+ */
203
+ never(message = 'This should never happen') {
204
+ const details = message ? `: ${message}` : '';
205
+ const error = new Error(`[${this['@']}] This should never happen${details}`);
206
+ Error.captureStackTrace(error, this.never);
207
+ return error;
208
+ }
209
+ /**
210
+ * Find the closest ancestor unit of a given type.
211
+ * The result is cached for subsequent calls.
212
+ */
213
+ closest(Ancestor) {
214
+ // Has cached value? -> Return it
215
+ ensure(this, _ancestors_, () => new Map());
216
+ if (this[_ancestors_].has(Ancestor))
217
+ return this[_ancestors_].get(Ancestor);
218
+ // Find the closest ancestor and cache it
219
+ let cursor = this;
220
+ while (cursor) {
221
+ if (cursor instanceof Ancestor) {
222
+ this[_ancestors_].set(Ancestor, cursor);
223
+ return cursor;
224
+ }
225
+ cursor = getParent(cursor);
226
+ }
227
+ return null;
104
228
  }
105
- return null;
106
- }
107
- /**
108
- * A wrapper around MobX's `autorun` that automatically disposes
109
- * the reaction when the unit is detached.
110
- */
111
- autorun(...args) {
112
- const disposer = epos.libs.mobx.autorun(...args);
113
- ensureProperty(this, _disposers_, () => /* @__PURE__ */ new Set());
114
- this[_disposers_].add(disposer);
115
- return disposer;
116
- }
117
- /**
118
- * A wrapper around MobX's `reaction` that automatically disposes
119
- * the reaction when the unit is detached.
120
- */
121
- reaction(...args) {
122
- const disposer = epos.libs.mobx.reaction(...args);
123
- ensureProperty(this, _disposers_, () => /* @__PURE__ */ new Set());
124
- this[_disposers_].add(disposer);
125
- return disposer;
126
- }
127
- /**
128
- * A wrapper around `setTimeout` that automatically clears the timeout
129
- * when the unit is detached.
130
- */
131
- setTimeout(...args) {
132
- const id = self.setTimeout(...args);
133
- ensureProperty(this, _disposers_, () => /* @__PURE__ */ new Set());
134
- this[_disposers_].add(() => self.clearTimeout(id));
135
- return id;
136
- }
137
- /**
138
- * A wrapper around `setInterval` that automatically clears the interval
139
- * when the unit is detached.
140
- */
141
- setInterval(...args) {
142
- const id = self.setInterval(...args);
143
- ensureProperty(this, _disposers_, () => /* @__PURE__ */ new Set());
144
- this[_disposers_].add(() => self.clearInterval(id));
145
- return id;
146
- }
147
- /**
148
- * Creates an error for an unreachable code path.
149
- */
150
- never(message = "This should never happen") {
151
- const details = message ? `: ${message}` : "";
152
- const error = new Error(`[${this.constructor.name}] This should never happen${details}`);
153
- Error.captureStackTrace(error, this.never);
154
- return error;
155
- }
156
- };
157
- function setProperty(object, key, value) {
158
- Reflect.defineProperty(object, key, {
159
- configurable: true,
160
- get: () => value,
161
- set: (v) => value = v
162
- });
163
229
  }
164
- function ensureProperty(object, key, getInitialValue) {
165
- if (key in object) return;
166
- const value = getInitialValue();
167
- Reflect.defineProperty(object, key, { configurable: true, get: () => value });
230
+ // ---------------------------------------------------------------------------
231
+ // HELPERS
232
+ // ---------------------------------------------------------------------------
233
+ /**
234
+ * Ensure a property exists on an object, initialize it if it doesn't.
235
+ */
236
+ function ensure(object, key, getInitialValue) {
237
+ if (key in object)
238
+ return;
239
+ const value = getInitialValue();
240
+ Reflect.defineProperty(object, key, { configurable: true, get: () => value });
168
241
  }
242
+ /**
243
+ * Get all prototypes of an object up to `Object.prototype`.
244
+ */
169
245
  function getPrototypes(object) {
170
- const prototype = Reflect.getPrototypeOf(object);
171
- if (!prototype || prototype === Object.prototype) return [];
172
- return [prototype, ...getPrototypes(prototype)];
246
+ const prototype = Reflect.getPrototypeOf(object);
247
+ if (!prototype || prototype === Object.prototype)
248
+ return [];
249
+ return [prototype, ...getPrototypes(prototype)];
173
250
  }
251
+ /**
252
+ * Find the root `Unit` in the hierarchy for a given unit.
253
+ */
174
254
  function findRoot(unit) {
175
- let root = null;
176
- let cursor = unit;
177
- while (cursor) {
178
- if (cursor instanceof Unit) root = cursor;
179
- cursor = getParent(cursor);
180
- }
181
- return root;
255
+ let root = null;
256
+ let cursor = unit;
257
+ while (cursor) {
258
+ if (cursor instanceof Unit)
259
+ root = cursor;
260
+ cursor = getParent(cursor);
261
+ }
262
+ return root;
182
263
  }
264
+ /**
265
+ * Find the highest unattached `Unit` in the hierarchy for a given unit.
266
+ */
183
267
  function findUnattachedRoot(unit) {
184
- let unattachedRoot = null;
185
- let cursor = unit;
186
- while (cursor) {
187
- if (cursor instanceof Unit && !cursor[_attached_]) unattachedRoot = cursor;
188
- cursor = getParent(cursor);
189
- }
190
- return unattachedRoot;
268
+ let unattachedRoot = null;
269
+ let cursor = unit;
270
+ while (cursor) {
271
+ if (cursor instanceof Unit && !cursor[_attached_])
272
+ unattachedRoot = cursor;
273
+ cursor = getParent(cursor);
274
+ }
275
+ return unattachedRoot;
191
276
  }
277
+ /**
278
+ * Get the parent of a node, which can be a `Unit`, an object, or an array.
279
+ */
192
280
  function getParent(node) {
193
- const parent = Reflect.get(node, _parent_) ?? Reflect.get(node, epos.state.PARENT) ?? null;
194
- return parent;
195
- }
196
- function getVersioner(unit) {
197
- const versioner = Reflect.get(unit.constructor, "versioner");
198
- if (!is.object(versioner)) return {};
199
- return versioner;
281
+ const parent = Reflect.get(node, _parent_) ?? Reflect.get(node, epos.state.PARENT) ?? null;
282
+ return parent;
200
283
  }
201
- function getVersions(unit) {
202
- const versioner = getVersioner(unit);
203
- const numericKeys = Object.keys(versioner).filter((key) => is.numeric(key));
204
- return numericKeys.map(Number).sort((v1, v2) => v1 - v2);
284
+ /**
285
+ * Create view component for the unit.
286
+ */
287
+ function createView(unit, name, render) {
288
+ const fullName = `${unit['@']}.${name}`;
289
+ const View = epos.component((props) => {
290
+ try {
291
+ return render(props);
292
+ }
293
+ catch (error) {
294
+ unit.log.error(error);
295
+ const message = is.error(error) ? error.message : String(error);
296
+ return epos.libs.reactJsxRuntime.jsx('div', {
297
+ children: `[${fullName}] ${message}`,
298
+ style: {
299
+ width: 'fit-content',
300
+ padding: '4px 6px 4px 4px',
301
+ color: '#f00',
302
+ border: '1px solid #f00',
303
+ background: 'rgba(255, 0, 0, 0.1)',
304
+ fontSize: 12,
305
+ fontWeight: 400,
306
+ },
307
+ });
308
+ }
309
+ });
310
+ View.displayName = fullName;
311
+ return View;
205
312
  }
206
- export {
207
- Unit,
208
- _ancestors_,
209
- _attached_,
210
- _disposers_,
211
- _parent_,
212
- _pendingAttachFns_,
213
- _root_
214
- };
215
313
  //# sourceMappingURL=epos-unit.js.map
@@ -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,"file":"epos-unit.js","sourceRoot":"","sources":["../src/epos-unit.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "epos-unit",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "imkost",
7
7
  "description": "",
8
8
  "keywords": [],
9
9
  "scripts": {
10
- "dev": "tsup --config ../../tsup.config.ts --watch",
11
- "build": "tsup --config ../../tsup.config.ts",
12
- "lint": "tsc --noEmit",
10
+ "dev": "rimraf dist && tsgo --watch",
11
+ "build": "rimraf dist && tsgo",
12
+ "lint": "tsgo --noEmit",
13
13
  "release": "sh -c 'npm version ${1:-minor} && npm run build && npm publish' --"
14
14
  },
15
15
  "exports": {
@@ -20,7 +20,8 @@
20
20
  "src"
21
21
  ],
22
22
  "dependencies": {
23
- "dropcap": "^1.4.0",
24
- "epos": "^1.32.0"
23
+ "dropcap": "^1.5.0",
24
+ "epos": "^1.35.0",
25
+ "nanoid": "^5.1.6"
25
26
  }
26
27
  }
package/src/epos-unit.ts CHANGED
@@ -1,18 +1,29 @@
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'
5
+ import type { FC } from 'react'
6
+
7
+ declare global {
8
+ interface ErrorConstructor {
9
+ captureStackTrace(targetObject: object, constructorOpt?: Function): void
10
+ }
11
+ }
4
12
 
5
13
  export const _root_ = Symbol('root')
6
14
  export const _parent_ = Symbol('parent')
7
15
  export const _attached_ = Symbol('attached')
8
16
  export const _disposers_ = Symbol('disposers')
9
17
  export const _ancestors_ = Symbol('ancestors')
10
- export const _pendingAttachFns_ = Symbol('pendingAttachFns')
18
+ export const _attachQueue_ = Symbol('pendingAttachHooks')
19
+ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 10)
11
20
 
12
21
  export type Node<T> = Unit<T> | Obj | Arr
22
+ export type Versioner<T> = { [version: number]: (this: T) => void }
13
23
 
14
24
  export class Unit<TRoot = unknown> {
15
25
  declare '@': string
26
+ declare id: string
16
27
  declare log: ReturnType<typeof createLog>;
17
28
  declare [':version']?: number;
18
29
  declare [_root_]?: TRoot;
@@ -20,192 +31,242 @@ export class Unit<TRoot = unknown> {
20
31
  declare [_attached_]?: boolean;
21
32
  declare [_disposers_]?: Set<() => void>;
22
33
  declare [_ancestors_]?: Map<Cls, unknown>;
23
- declare [_pendingAttachFns_]?: (() => void)[]
34
+ declare [_attachQueue_]?: (() => void)[]
35
+
36
+ static defineVersioner<T extends Unit>(this: Cls<T>, versioner: Versioner<T>) {
37
+ return versioner
38
+ }
24
39
 
25
40
  constructor(parent: Unit<TRoot> | null) {
41
+ this.id = nanoid()
26
42
  this[_parent_] = parent
27
- const versions = getVersions(this)
28
- if (versions.length > 0) this[':version'] = versions.at(-1)!
29
43
  }
30
44
 
45
+ // ---------------------------------------------------------------------------
46
+ // ATTACH
47
+ // ---------------------------------------------------------------------------
48
+
31
49
  /**
32
50
  * Lifecycle method called when the unit is attached to the state tree.
33
51
  */
34
52
  [epos.state.ATTACH]() {
35
53
  // Setup logger
36
- setProperty(this, 'log', createLog(this['@']))
54
+ let log = createLog(this['@'])
55
+ Reflect.defineProperty(this, 'log', { configurable: true, get: () => log, set: v => (log = v) })
37
56
 
38
57
  // Apply versioner
39
- epos.state.transaction(() => {
40
- const versioner = getVersioner(this)
41
- const versions = getVersions(this)
42
- for (const version of versions) {
43
- if (is.number(this[':version']) && this[':version'] >= version) continue
44
- const versionFn = versioner[version]
45
- if (!is.function(versionFn)) continue
46
- versionFn.call(this, this)
47
- this[':version'] = version
48
- }
49
- })
58
+ void (() => {
59
+ const versioner: unknown = Reflect.get(this.constructor, 'versioner')
60
+ if (!is.object(versioner)) return
61
+
62
+ const asc = (v1: number, v2: number) => v1 - v2
63
+ const versions = Object.keys(versioner).filter(is.numeric).map(Number).sort(asc)
64
+ if (versions.length === 0) return
65
+
66
+ epos.state.transaction(() => {
67
+ for (const version of versions) {
68
+ if (is.number(this[':version']) && this[':version'] >= version) continue
69
+ const versionFn = versioner[version]
70
+ if (!is.function(versionFn)) continue
71
+ versionFn.call(this)
72
+ this[':version'] = version
73
+ }
74
+ })
75
+ })()
50
76
 
51
- // Prepare methods:
77
+ // Setup state
78
+ void (() => {
79
+ const descriptor = Reflect.getOwnPropertyDescriptor(this.constructor.prototype, 'state')
80
+ if (!descriptor || !descriptor.get) return
81
+ const value: unknown = descriptor.get.call(this)
82
+ if (!is.object(value)) throw new Error(`'state' getter return an object`)
83
+ const state = epos.state.create(value)
84
+ Reflect.defineProperty(state, epos.state.PARENT, { configurable: true, value: this })
85
+ Reflect.defineProperty(this, 'state', { enumerable: true, get: () => state })
86
+ })()
87
+
88
+ // Setup inert
89
+ void (() => {
90
+ const descriptor = Reflect.getOwnPropertyDescriptor(this.constructor.prototype, 'inert')
91
+ if (!descriptor || !descriptor.get) return
92
+ const value: unknown = descriptor.get.call(this)
93
+ if (!is.object(value)) throw new Error(`'inert' getter return an object`)
94
+ Reflect.defineProperty(this, 'inert', { enumerable: true, get: () => value })
95
+ })()
96
+
97
+ // Prepare properties for the whole prototype chain:
52
98
  // - Create components for methods ending with `View`
53
99
  // - Bind all other methods to the unit instance
100
+ // - Turn getters into MobX computed properties
54
101
  for (const prototype of getPrototypes(this)) {
55
102
  const descriptors = Object.getOwnPropertyDescriptors(prototype)
56
-
57
103
  for (const [key, descriptor] of Object.entries(descriptors)) {
104
+ // Skip constructor and already defined properties
58
105
  if (key === 'constructor') continue
59
106
  if (this.hasOwnProperty(key)) continue
60
107
 
61
- if (descriptor.get || descriptor.set) continue
62
- if (!is.function(descriptor.value)) continue
63
- const fn = descriptor.value.bind(this)
108
+ // Create components for methods ending with `View`
109
+ if (is.function(descriptor.value) && key.endsWith('View')) {
110
+ let View = createView(this, key, descriptor.value.bind(this) as FC<unknown>)
111
+ Reflect.defineProperty(this, key, { configurable: true, get: () => View, set: v => (View = v) })
112
+ }
64
113
 
65
- if (key.endsWith('View')) {
66
- let Component = epos.component(fn)
67
- Component.displayName = `${this.constructor.name}.${key}`
68
- setProperty(this, key, Component)
69
- } else {
70
- setProperty(this, key, fn)
114
+ // Bind all other methods to the unit instance
115
+ else if (is.function(descriptor.value)) {
116
+ let method = descriptor.value.bind(this)
117
+ Reflect.defineProperty(this, key, {
118
+ configurable: true,
119
+ get: () => method,
120
+ set: v => (method = v),
121
+ })
71
122
  }
72
- }
73
- }
74
123
 
75
- // Setup state
76
- const stateDescriptor = Reflect.getOwnPropertyDescriptor(this.constructor.prototype, 'state')
77
- if (stateDescriptor && stateDescriptor.get) {
78
- const state = stateDescriptor.get.call(this)
79
- setProperty(this, 'state', epos.libs.mobx.observable.object(state, {}, { deep: false }))
124
+ // Turn getters into MobX computed properties
125
+ else if (descriptor.get) {
126
+ const getter = descriptor.get
127
+ const computed = epos.libs.mobx.computed(() => getter.call(this))
128
+ Reflect.defineProperty(this, key, {
129
+ configurable: true,
130
+ get: () => computed.get(),
131
+ set: descriptor.set,
132
+ })
133
+ }
134
+ }
80
135
  }
81
136
 
82
- // Process attach queue.
83
- // We do not execute `attach` methods immediately, but rather queue them
84
- // on the highest unattached ancestor. This way we ensure that `attach`
85
- // methods are called after all versioners have been applied in the entire subtree.
137
+ // Queue attach method.
138
+ // Do not execute `attach` methods immediately, but rather queue them on the highest unattached ancestor.
139
+ // This way `attach` methods are called after all versioners have been applied in the entire subtree.
86
140
  const attach = Reflect.get(this, 'attach')
87
141
  if (is.function(attach)) {
88
- const unattachedRoot = findUnattachedRoot(this)
89
- if (!unattachedRoot) throw this.never()
90
-
91
- ensureProperty(unattachedRoot, _pendingAttachFns_, () => [])
92
- unattachedRoot[_pendingAttachFns_].push(() => attach())
142
+ const head = findUnattachedRoot(this)
143
+ if (!head) throw this.never()
144
+ ensure(head, _attachQueue_, () => [])
145
+ head[_attachQueue_].push(() => attach.call(this))
93
146
  }
94
147
 
95
- if (this[_pendingAttachFns_]) {
96
- this[_pendingAttachFns_].forEach(attach => attach())
97
- delete this[_pendingAttachFns_]
148
+ // Release attach queue
149
+ if (this[_attachQueue_]) {
150
+ this[_attachQueue_].forEach(attach => attach())
151
+ delete this[_attachQueue_]
98
152
  }
99
153
 
100
154
  // Mark as attached
101
- setProperty(this, _attached_, true)
155
+ Reflect.defineProperty(this, _attached_, { configurable: true, get: () => true })
102
156
  }
103
157
 
158
+ // ---------------------------------------------------------------------------
159
+ // DETACH
160
+ // ---------------------------------------------------------------------------
161
+
104
162
  /**
105
163
  * Lifecycle method called when the unit is detached from the state tree.
106
164
  */
107
165
  [epos.state.DETACH]() {
108
- // 1. Run disposers
166
+ // Run and clear disposers
109
167
  if (this[_disposers_]) {
110
168
  this[_disposers_].forEach(disposer => disposer())
111
169
  this[_disposers_].clear()
112
170
  }
113
171
 
114
- // 2. Call detach method
172
+ // Clear ancestors cache
173
+ if (this[_ancestors_]) {
174
+ this[_ancestors_].clear()
175
+ }
176
+
177
+ // Call detach method
115
178
  const detach = Reflect.get(this, 'detach')
116
179
  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
180
  }
124
181
 
182
+ // ---------------------------------------------------------------------------
183
+ // ROOT GETTER
184
+ // ---------------------------------------------------------------------------
185
+
125
186
  /**
126
- * Gets the root unit of the current unit's tree.
187
+ * Get the root unit of the current unit's tree.
127
188
  * The result is cached for subsequent calls.
128
189
  */
129
190
  get $() {
130
- ensureProperty(this, _root_, () => findRoot(this))
191
+ ensure(this, _root_, () => findRoot(this))
131
192
  return this[_root_]
132
193
  }
133
194
 
134
- /**
135
- * Finds the closest ancestor unit of a given type.
136
- * The result is cached for subsequent calls.
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
- }
195
+ // ---------------------------------------------------------------------------
196
+ // METHODS
197
+ // ---------------------------------------------------------------------------
155
198
 
156
199
  /**
157
- * A wrapper around MobX's `autorun` that automatically disposes
158
- * the reaction when the unit is detached.
200
+ * A wrapper around MobX's `autorun` that automatically disposes the reaction when the unit is detached.
159
201
  */
160
202
  autorun(...args: Parameters<typeof epos.libs.mobx.autorun>) {
161
203
  const disposer = epos.libs.mobx.autorun(...args)
162
- ensureProperty(this, _disposers_, () => new Set())
204
+ ensure(this, _disposers_, () => new Set())
163
205
  this[_disposers_].add(disposer)
164
206
  return disposer
165
207
  }
166
208
 
167
209
  /**
168
- * A wrapper around MobX's `reaction` that automatically disposes
169
- * the reaction when the unit is detached.
210
+ * A wrapper around MobX's `reaction` that automatically disposes the reaction when the unit is detached.
170
211
  */
171
212
  reaction(...args: Parameters<typeof epos.libs.mobx.reaction>) {
172
213
  const disposer = epos.libs.mobx.reaction(...args)
173
- ensureProperty(this, _disposers_, () => new Set())
214
+ ensure(this, _disposers_, () => new Set())
174
215
  this[_disposers_].add(disposer)
175
216
  return disposer
176
217
  }
177
218
 
178
219
  /**
179
- * A wrapper around `setTimeout` that automatically clears the timeout
180
- * when the unit is detached.
220
+ * A wrapper around `setTimeout` that automatically clears the timeout when the unit is detached.
181
221
  */
182
- setTimeout(...args: Parameters<typeof self.setTimeout>) {
183
- const id = self.setTimeout(...args)
184
- ensureProperty(this, _disposers_, () => new Set())
185
- this[_disposers_].add(() => self.clearTimeout(id))
222
+ setTimeout(...args: Parameters<typeof setTimeout>) {
223
+ const id = setTimeout(...args)
224
+ ensure(this, _disposers_, () => new Set())
225
+ this[_disposers_].add(() => clearTimeout(id))
186
226
  return id
187
227
  }
188
228
 
189
229
  /**
190
- * A wrapper around `setInterval` that automatically clears the interval
191
- * when the unit is detached.
230
+ * A wrapper around `setInterval` that automatically clears the interval when the unit is detached.
192
231
  */
193
- setInterval(...args: Parameters<typeof self.setInterval>) {
194
- const id = self.setInterval(...args)
195
- ensureProperty(this, _disposers_, () => new Set())
196
- this[_disposers_].add(() => self.clearInterval(id))
232
+ setInterval(...args: Parameters<typeof setInterval>) {
233
+ const id = setInterval(...args)
234
+ ensure(this, _disposers_, () => new Set())
235
+ this[_disposers_].add(() => clearInterval(id))
197
236
  return id
198
237
  }
199
238
 
200
239
  /**
201
- * Creates an error for an unreachable code path.
240
+ * Create an error for code paths that are logically unreachable.
202
241
  */
203
242
  never(message = 'This should never happen') {
204
243
  const details = message ? `: ${message}` : ''
205
- const error = new Error(`[${this.constructor.name}] This should never happen${details}`)
244
+ const error = new Error(`[${this['@']}] This should never happen${details}`)
206
245
  Error.captureStackTrace(error, this.never)
207
246
  return error
208
247
  }
248
+
249
+ /**
250
+ * Find the closest ancestor unit of a given type.
251
+ * The result is cached for subsequent calls.
252
+ */
253
+ closest<T extends Unit>(Ancestor: Cls<T>) {
254
+ // Has cached value? -> Return it
255
+ ensure(this, _ancestors_, () => new Map())
256
+ if (this[_ancestors_].has(Ancestor)) return this[_ancestors_].get(Ancestor) as T
257
+
258
+ // Find the closest ancestor and cache it
259
+ let cursor: Node<TRoot> | null = this
260
+ while (cursor) {
261
+ if (cursor instanceof Ancestor) {
262
+ this[_ancestors_].set(Ancestor, cursor)
263
+ return cursor
264
+ }
265
+ cursor = getParent(cursor)
266
+ }
267
+
268
+ return null
269
+ }
209
270
  }
210
271
 
211
272
  // ---------------------------------------------------------------------------
@@ -213,20 +274,9 @@ export class Unit<TRoot = unknown> {
213
274
  // ---------------------------------------------------------------------------
214
275
 
215
276
  /**
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
- /**
227
- * Ensures a property exists on an object, initializing it if it doesn't.
277
+ * Ensure a property exists on an object, initialize it if it doesn't.
228
278
  */
229
- function ensureProperty<T extends object, K extends PropertyKey, V>(
279
+ function ensure<T extends object, K extends PropertyKey, V>(
230
280
  object: T,
231
281
  key: K,
232
282
  getInitialValue: () => V,
@@ -237,7 +287,7 @@ function ensureProperty<T extends object, K extends PropertyKey, V>(
237
287
  }
238
288
 
239
289
  /**
240
- * Gets all prototypes of an object up to `Object.prototype`.
290
+ * Get all prototypes of an object up to `Object.prototype`.
241
291
  */
242
292
  function getPrototypes(object: object): object[] {
243
293
  const prototype = Reflect.getPrototypeOf(object)
@@ -246,7 +296,7 @@ function getPrototypes(object: object): object[] {
246
296
  }
247
297
 
248
298
  /**
249
- * Finds the root `Unit` in the hierarchy for a given unit.
299
+ * Find the root `Unit` in the hierarchy for a given unit.
250
300
  */
251
301
  function findRoot<T>(unit: Unit<T>) {
252
302
  let root: Unit<T> | null = null
@@ -261,7 +311,7 @@ function findRoot<T>(unit: Unit<T>) {
261
311
  }
262
312
 
263
313
  /**
264
- * Finds the highest unattached `Unit` in the hierarchy for a given unit.
314
+ * Find the highest unattached `Unit` in the hierarchy for a given unit.
265
315
  */
266
316
  function findUnattachedRoot<T>(unit: Unit<T>) {
267
317
  let unattachedRoot: Unit<T> | null = null
@@ -276,7 +326,7 @@ function findUnattachedRoot<T>(unit: Unit<T>) {
276
326
  }
277
327
 
278
328
  /**
279
- * Gets the parent of a node, which can be a `Unit`, an object, or an array.
329
+ * Get the parent of a node, which can be a `Unit`, an object, or an array.
280
330
  */
281
331
  function getParent<T>(node: Node<T>) {
282
332
  const parent: Node<T> | null = Reflect.get(node, _parent_) ?? Reflect.get(node, epos.state.PARENT) ?? null
@@ -284,19 +334,33 @@ function getParent<T>(node: Node<T>) {
284
334
  }
285
335
 
286
336
  /**
287
- * Gets the versioner object from a unit's constructor.
337
+ * Create view component for the unit.
288
338
  */
289
- function getVersioner<T>(unit: Unit<T>) {
290
- const versioner: unknown = Reflect.get(unit.constructor, 'versioner')
291
- if (!is.object(versioner)) return {}
292
- return versioner
293
- }
339
+ function createView<T>(unit: Unit<T>, name: string, render: FC<unknown>) {
340
+ const fullName = `${unit['@']}.${name}`
341
+
342
+ const View = epos.component((props: unknown) => {
343
+ try {
344
+ return render(props)
345
+ } catch (error) {
346
+ unit.log.error(error)
347
+ const message = is.error(error) ? error.message : String(error)
348
+ return epos.libs.reactJsxRuntime.jsx('div', {
349
+ children: `[${fullName}] ${message}`,
350
+ style: {
351
+ width: 'fit-content',
352
+ padding: '4px 6px 4px 4px',
353
+ color: '#f00',
354
+ border: '1px solid #f00',
355
+ background: 'rgba(255, 0, 0, 0.1)',
356
+ fontSize: 12,
357
+ fontWeight: 400,
358
+ },
359
+ })
360
+ }
361
+ })
294
362
 
295
- /**
296
- * Gets a sorted list of numeric version keys from a unit's versioner.
297
- */
298
- function getVersions<T>(unit: Unit<T>) {
299
- const versioner = getVersioner(unit)
300
- const numericKeys = Object.keys(versioner).filter(key => is.numeric(key))
301
- return numericKeys.map(Number).sort((v1, v2) => v1 - v2)
363
+ View.displayName = fullName
364
+
365
+ return View
302
366
  }