@lumjs/core 1.36.0 → 1.37.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/lib/events/listener.js +5 -1
- package/lib/events/registry.js +136 -36
- package/lib/obj/clone.js +3 -0
- package/lib/obj/index.js +2 -1
- package/lib/obj/unlocked.js +87 -0
- package/package.json +1 -1
package/lib/events/listener.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const {F,isObj} = require('../types');
|
|
3
|
+
const {F,isObj,def} = require('../types');
|
|
4
4
|
const Event = require('./event');
|
|
5
5
|
|
|
6
6
|
const REMOVE_OPTS = ['listener','handler','eventNames'];
|
|
@@ -127,6 +127,10 @@ class LumEventListener
|
|
|
127
127
|
return event;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
static get classProps()
|
|
131
|
+
{
|
|
132
|
+
return Object.getOwnPropertyNames(this.prototype);
|
|
133
|
+
}
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
LumEventListener.isListener = isListener;
|
package/lib/events/registry.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const {S,F,isObj,def,isIterable} = require('../types');
|
|
4
4
|
const Listener = require('./listener');
|
|
5
5
|
const RegSym = Symbol('@lumjs/core/events:registry');
|
|
6
|
+
const cp = Object.assign;
|
|
6
7
|
|
|
7
8
|
const DEF_EXTENDS =
|
|
8
9
|
{
|
|
@@ -13,6 +14,13 @@ const DEF_EXTENDS =
|
|
|
13
14
|
once: null,
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
const INT_EXTENDS =
|
|
18
|
+
{
|
|
19
|
+
listeners: true,
|
|
20
|
+
results: true,
|
|
21
|
+
onDemand: false,
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
const DEF_OPTIONS =
|
|
17
25
|
{
|
|
18
26
|
delimiter: /\s+/,
|
|
@@ -20,6 +28,12 @@ const DEF_OPTIONS =
|
|
|
20
28
|
wildcard: '*',
|
|
21
29
|
}
|
|
22
30
|
|
|
31
|
+
const RES_PROPS =
|
|
32
|
+
[
|
|
33
|
+
'eventNames', 'targets', 'multiMatch', 'onceRemoved', 'stopEmitting',
|
|
34
|
+
'emitted', 'targetListeners', 'registry',
|
|
35
|
+
]
|
|
36
|
+
|
|
23
37
|
/**
|
|
24
38
|
* Has a target object been registered with an event registry?
|
|
25
39
|
* @param {object} target
|
|
@@ -98,29 +112,53 @@ class LumEventRegistry
|
|
|
98
112
|
* If you want to use a `function` as an actual target, you'll need to
|
|
99
113
|
* wrap it in an array or other iterable object.
|
|
100
114
|
*
|
|
101
|
-
* @param {object} [opts] Options
|
|
115
|
+
* @param {object} [opts] Options
|
|
116
|
+
*
|
|
117
|
+
* A _compiled_ version is saved to the `options` property.
|
|
118
|
+
* The compiled version includes a bunch of defaults, and various
|
|
119
|
+
* compose rules (mostly for the `.extend` nested options).
|
|
102
120
|
*
|
|
103
121
|
* @param {(RegExp|string)} [opts.delimiter=/\s+/] Used to split event names
|
|
104
122
|
*
|
|
105
|
-
* @param {
|
|
106
|
-
*
|
|
107
|
-
*
|
|
123
|
+
* @param {object} [opts.extend] Options for wrapper methods/properties
|
|
124
|
+
*
|
|
125
|
+
* The `boolean` options determine if extension methods will be added to
|
|
126
|
+
* certain types of objects (and in some cases, when to do so).
|
|
108
127
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
128
|
+
* The `?string` options are each the names of properties/methods in the
|
|
129
|
+
* Registry class. If they are set to a `string` then that will be the
|
|
130
|
+
* name used for the wrapper property/method added to objects. If it
|
|
131
|
+
* is explicitly set to `null` it means skip adding a wrapper for that
|
|
132
|
+
* method/property. If it is omitted entirely the default will be used.
|
|
111
133
|
*
|
|
112
|
-
*
|
|
113
|
-
* it disables adding extension properties entirely.
|
|
134
|
+
* @param {boolean} [opts.extend.targets] Extend target objects?
|
|
114
135
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
136
|
+
* The default will be `true` when `targets` is an `object`, or `false`
|
|
137
|
+
* when `targets` is a `function`.
|
|
138
|
+
*
|
|
139
|
+
* @param {boolean} [opts.extend.listeners=true] Extend Listener instances?
|
|
140
|
+
* As returned by `makeListener()`, `listen()`, and `once()`
|
|
141
|
+
* @param {boolean} [opts.extend.results=true] Extend `emit()` results?
|
|
142
|
+
* @param {boolean} [opts.extend.onDemand=false] On-demand target setup
|
|
143
|
+
*
|
|
144
|
+
* If `targets` was a `function` and this is set to `true`, then
|
|
145
|
+
* we'll perform the target setup on every emit() call. The setup process
|
|
146
|
+
* is skipped on any targets that have already been set up, so this
|
|
147
|
+
* is meant for dynamic targets that may change on every call.
|
|
148
|
+
*
|
|
149
|
+
* @param {?string} [opts.extend.registry="events"] Registry property;
|
|
150
|
+
* only added to `targets`, never to listeners or results which have
|
|
151
|
+
* their own inherent `registry` property already.
|
|
117
152
|
*
|
|
118
|
-
* @param {?string} [opts.extend.registry="events"] Registry property
|
|
119
153
|
* @param {?string} [opts.extend.emit="emit"] `emit()` proxy method
|
|
120
154
|
* @param {?string} [opts.extend.listen="on"] `listen()` proxy method
|
|
121
155
|
* @param {?string} [opts.extend.once=null] `once()` proxy method
|
|
122
156
|
* @param {?string} [opts.extend.remove=null] `remove()` proxy method
|
|
123
157
|
*
|
|
158
|
+
* The `remove` wrapper method added to Listener instances is slightly
|
|
159
|
+
* different than the one added to other objects, as if you call it
|
|
160
|
+
* with no arguments, it will pass the Listener itself as the argument.
|
|
161
|
+
*
|
|
124
162
|
* @param {boolean} [opts.multiMatch=false]
|
|
125
163
|
* If a registered listener has multiple event names, and a call
|
|
126
164
|
* to `emit()` also has multiple event names, the value of this
|
|
@@ -159,7 +197,7 @@ class LumEventRegistry
|
|
|
159
197
|
*/
|
|
160
198
|
constructor(targets, opts={})
|
|
161
199
|
{
|
|
162
|
-
let defExt; // Default opts.extend value
|
|
200
|
+
let defExt; // Default opts.extend.targets value
|
|
163
201
|
if (typeof targets === F)
|
|
164
202
|
{ // A dynamic getter method
|
|
165
203
|
this.funTargets = true;
|
|
@@ -181,47 +219,53 @@ class LumEventRegistry
|
|
|
181
219
|
defExt = true;
|
|
182
220
|
}
|
|
183
221
|
|
|
184
|
-
|
|
222
|
+
// Build composite extend rules
|
|
223
|
+
const extend = cp(
|
|
224
|
+
{targets: defExt},
|
|
225
|
+
INT_EXTENDS,
|
|
226
|
+
DEF_EXTENDS,
|
|
227
|
+
opts.extend);
|
|
228
|
+
|
|
229
|
+
// Now compile the final options
|
|
230
|
+
this.options = cp({}, DEF_OPTIONS, opts, {extend});
|
|
185
231
|
|
|
186
232
|
this.allListeners = new Set();
|
|
187
233
|
this.listenersFor = new Map();
|
|
188
234
|
|
|
189
|
-
this.
|
|
235
|
+
this.setupTargets(targets);
|
|
190
236
|
} // constructor()
|
|
191
237
|
|
|
192
238
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
239
|
+
* Set up target objects
|
|
240
|
+
*
|
|
241
|
+
* Always sets the necessary metadata on each target.
|
|
242
|
+
* May also extend the targets with wrapper properties and methods
|
|
243
|
+
* depending on the `options.extend` values set.
|
|
244
|
+
*
|
|
245
|
+
* Not meant to be called from outside code.
|
|
196
246
|
* @private
|
|
197
247
|
* @param {Iterable} targets - Targets to extend
|
|
198
248
|
* @returns {module:@lumjs/core/events.Registry} `this`
|
|
199
249
|
*/
|
|
200
|
-
|
|
250
|
+
setupTargets(targets)
|
|
201
251
|
{
|
|
202
252
|
const opts = this.options;
|
|
203
253
|
const extOpts = opts.extend;
|
|
204
|
-
|
|
205
|
-
let intNames = null, extNames = null;
|
|
206
|
-
|
|
207
|
-
if (extOpts)
|
|
208
|
-
{
|
|
209
|
-
intNames = Object.keys(DEF_EXTENDS);
|
|
210
|
-
extNames = Object.assign({}, DEF_EXTENDS, extOpts);
|
|
211
|
-
}
|
|
254
|
+
const intNames = extOpts.targets ? Object.keys(DEF_EXTENDS) : null;
|
|
212
255
|
|
|
213
256
|
for (const target of targets)
|
|
214
257
|
{
|
|
215
258
|
const tps = {}, tpm = getMetadata(target, true);
|
|
259
|
+
if (tpm.r.has(this)) continue; // Already set up with this registry.
|
|
216
260
|
tpm.r.set(this, tps);
|
|
217
261
|
|
|
218
|
-
if (extOpts)
|
|
262
|
+
if (extOpts.targets)
|
|
219
263
|
{
|
|
220
264
|
for (const iname of intNames)
|
|
221
265
|
{
|
|
222
|
-
if (typeof
|
|
266
|
+
if (typeof extOpts[iname] === S && extOpts[iname].trim() !== '')
|
|
223
267
|
{
|
|
224
|
-
const ename =
|
|
268
|
+
const ename = extOpts[iname];
|
|
225
269
|
const value = iname === 'registry'
|
|
226
270
|
? this // The registry instance itself
|
|
227
271
|
: (...args) => this[iname](...args) // A proxy method
|
|
@@ -240,6 +284,48 @@ class LumEventRegistry
|
|
|
240
284
|
}
|
|
241
285
|
}
|
|
242
286
|
}
|
|
287
|
+
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Add extension methods to internal objects;
|
|
293
|
+
* currently supports Listener instances and result/status objects.
|
|
294
|
+
* Not meant to be called from outside code.
|
|
295
|
+
* @private
|
|
296
|
+
* @param {object} obj - Internal object to extend
|
|
297
|
+
* @returns {module:@lumjs/core/events.Registry} `this`
|
|
298
|
+
*/
|
|
299
|
+
extendInternal(obj)
|
|
300
|
+
{
|
|
301
|
+
const opts = this.options;
|
|
302
|
+
const extOpts = opts.extend;
|
|
303
|
+
const intNames = Object.keys(DEF_EXTENDS);
|
|
304
|
+
const isLs = (obj instanceof Listener);
|
|
305
|
+
const reserved = isLs ? Listener.classProps : RES_PROPS;
|
|
306
|
+
|
|
307
|
+
for (const iname of intNames)
|
|
308
|
+
{
|
|
309
|
+
if (iname === 'registry') continue; // skip the registry property
|
|
310
|
+
if (typeof extOpts[iname] === S && extOpts[iname].trim() !== '')
|
|
311
|
+
{
|
|
312
|
+
const ename = extOpts[iname];
|
|
313
|
+
if (reserved.includes(ename))
|
|
314
|
+
{ // Skip reserved names
|
|
315
|
+
console.warn("reserved property", {ename, obj, registry: this});
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const value = (isLs && iname === 'remove')
|
|
320
|
+
? (what=obj) => this.remove(what) // special remove wrapper
|
|
321
|
+
: (...args) => this[iname](...args) // regular wrapper method
|
|
322
|
+
if (opts.overwrite || obj[ename] === undefined)
|
|
323
|
+
{
|
|
324
|
+
def(obj, ename, {value});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
243
329
|
return this;
|
|
244
330
|
}
|
|
245
331
|
|
|
@@ -305,16 +391,21 @@ class LumEventRegistry
|
|
|
305
391
|
}
|
|
306
392
|
else if (args.length === 1 && isObj(args[0]))
|
|
307
393
|
{ // listen(spec)
|
|
308
|
-
spec =
|
|
394
|
+
spec = cp({}, args[0]);
|
|
309
395
|
}
|
|
310
396
|
else
|
|
311
397
|
{ // listen(eventNames, listener, [spec])
|
|
312
|
-
spec =
|
|
398
|
+
spec = cp({}, args[2]);
|
|
313
399
|
spec.eventNames = args[0];
|
|
314
400
|
spec.handler = args[1];
|
|
315
401
|
}
|
|
316
402
|
|
|
317
|
-
|
|
403
|
+
const lsnr = new Listener(this, spec);
|
|
404
|
+
if (this.options.extend.listeners)
|
|
405
|
+
{
|
|
406
|
+
this.extendInternal(lsnr);
|
|
407
|
+
}
|
|
408
|
+
return lsnr;
|
|
318
409
|
}
|
|
319
410
|
|
|
320
411
|
/**
|
|
@@ -524,6 +615,7 @@ class LumEventRegistry
|
|
|
524
615
|
*/
|
|
525
616
|
emit(eventNames, ...args)
|
|
526
617
|
{
|
|
618
|
+
const extOpts = this.options.extend;
|
|
527
619
|
const sti =
|
|
528
620
|
{
|
|
529
621
|
eventNames: this.getEventNames(eventNames),
|
|
@@ -531,11 +623,21 @@ class LumEventRegistry
|
|
|
531
623
|
onceRemoved: new Set(),
|
|
532
624
|
stopEmitting: false,
|
|
533
625
|
emitted: [],
|
|
626
|
+
registry: this,
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (extOpts.results)
|
|
630
|
+
{
|
|
631
|
+
this.extendInternal(sti);
|
|
534
632
|
}
|
|
535
633
|
|
|
536
634
|
{ // Get the targets.
|
|
537
635
|
const tgs = this.getTargets(sti);
|
|
538
636
|
sti.targets = (tgs instanceof Set) ? tgs : new Set(tgs);
|
|
637
|
+
if (this.funTargets && extOpts.onDemand)
|
|
638
|
+
{
|
|
639
|
+
this.setupTargets(sti.targets);
|
|
640
|
+
}
|
|
539
641
|
}
|
|
540
642
|
|
|
541
643
|
const wilds = this.listenersFor.get(this.options.wildcard);
|
|
@@ -579,8 +681,6 @@ class LumEventRegistry
|
|
|
579
681
|
return sti;
|
|
580
682
|
}
|
|
581
683
|
|
|
582
|
-
|
|
583
|
-
|
|
584
684
|
/**
|
|
585
685
|
* Register additional target objects
|
|
586
686
|
* @param {...object} addTargets - Target objects to register
|
|
@@ -617,7 +717,7 @@ class LumEventRegistry
|
|
|
617
717
|
}
|
|
618
718
|
}
|
|
619
719
|
|
|
620
|
-
this.
|
|
720
|
+
this.setupTargets(addTargets);
|
|
621
721
|
return this;
|
|
622
722
|
}
|
|
623
723
|
|
|
@@ -735,7 +835,7 @@ class LumEventRegistry
|
|
|
735
835
|
|
|
736
836
|
}
|
|
737
837
|
|
|
738
|
-
|
|
838
|
+
cp(LumEventRegistry,
|
|
739
839
|
{
|
|
740
840
|
isRegistered, getMetadata, targetsAre,
|
|
741
841
|
});
|
package/lib/obj/clone.js
CHANGED
|
@@ -296,6 +296,9 @@ exports.addClone = addClone;
|
|
|
296
296
|
*
|
|
297
297
|
* If not, if the object has a `clone()` method it will be used.
|
|
298
298
|
* Otherwise use our `clone()` function.
|
|
299
|
+
*
|
|
300
|
+
* **NOTE**: A newer replacement for this function exists, see
|
|
301
|
+
* {@link module:@lumjs/core/obj.unlocked} for details.
|
|
299
302
|
*
|
|
300
303
|
* @param {object} obj - The object to clone if needed.
|
|
301
304
|
* @param {object} [opts] - Options to pass to `clone()` method.
|
package/lib/obj/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const {lock,addLock} = require('./lock');
|
|
|
14
14
|
const {mergeNested,syncNested} = require('./merge');
|
|
15
15
|
const ns = require('./ns');
|
|
16
16
|
const cp = require('./cp');
|
|
17
|
+
const unlocked = require('./unlocked');
|
|
17
18
|
|
|
18
19
|
const
|
|
19
20
|
{
|
|
@@ -27,5 +28,5 @@ module.exports =
|
|
|
27
28
|
mergeNested, syncNested, copyProps, copyAll, ns,
|
|
28
29
|
getObjectPath, setObjectPath, getNamespace, setNamespace,
|
|
29
30
|
getProperty, duplicateAll, duplicateOne, getMethods, signatureOf,
|
|
30
|
-
MethodFilter, apply, flip, flipKeyVal, flipMap,
|
|
31
|
+
MethodFilter, apply, flip, flipKeyVal, flipMap, unlocked,
|
|
31
32
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {F,S,isObj,needObj} = require('../types');
|
|
4
|
+
|
|
5
|
+
const CLONE = 'clone';
|
|
6
|
+
const clone = obj => Object.assign({}, obj);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get an unlocked object
|
|
10
|
+
*
|
|
11
|
+
* @param {object} obj - The target object;
|
|
12
|
+
*
|
|
13
|
+
* If the object is extensible it will be returned _as is_.
|
|
14
|
+
*
|
|
15
|
+
* If the object is frozen, sealed, or otherwise non-extensible,
|
|
16
|
+
* a cloning function will be used to make an unlocked copy.
|
|
17
|
+
*
|
|
18
|
+
* @param {(object|function|string)} [opts] Options to customize the behaviour
|
|
19
|
+
*
|
|
20
|
+
* - If this is a `function` it will be used as the `opts.fn` value.
|
|
21
|
+
* - If this is a `string` it will be used as the `opts.method` value.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} [opts.method='clone'] Object method to use
|
|
24
|
+
*
|
|
25
|
+
* Set to an empty string `""` to skip checking for an object method.
|
|
26
|
+
*
|
|
27
|
+
* If the method exists on the target object, it will be called to
|
|
28
|
+
* perform the cloning procedure, otherwise `opts.fn` will be used.
|
|
29
|
+
*
|
|
30
|
+
* @param {Array} [opts.args] Arguments for `opts.method` method
|
|
31
|
+
*
|
|
32
|
+
* If not specified, the default value is: `[opts]`
|
|
33
|
+
*
|
|
34
|
+
* @param {function} [opts.fn] A function to perform the clone operation
|
|
35
|
+
*
|
|
36
|
+
* Must take only a single parameter, which is the object to clone.
|
|
37
|
+
* Must return an extensible clone of the object.
|
|
38
|
+
*
|
|
39
|
+
* If for whatever reason you need the `opts` in the function,
|
|
40
|
+
* then use a unbound function, and `opts` will be available
|
|
41
|
+
* as `this` in the function body. Arrow functions (closures) are
|
|
42
|
+
* always considered bound and cannot have a `this` value assigned.
|
|
43
|
+
*
|
|
44
|
+
* The default value is a closure: `obj => Object.assign({}, obj)`;
|
|
45
|
+
*
|
|
46
|
+
* @return {object}
|
|
47
|
+
*
|
|
48
|
+
* @alias module:@lumjs/core/obj.unlocked
|
|
49
|
+
*/
|
|
50
|
+
function unlocked(obj, opts={})
|
|
51
|
+
{
|
|
52
|
+
needObj(obj, true, "invalid obj value");
|
|
53
|
+
|
|
54
|
+
if (Object.isExtensible(obj))
|
|
55
|
+
{ // We're done here.
|
|
56
|
+
return obj;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof opts === F)
|
|
60
|
+
{
|
|
61
|
+
opts = {fn: opts}
|
|
62
|
+
}
|
|
63
|
+
else if (typeof opts === S)
|
|
64
|
+
{
|
|
65
|
+
opts = {method: opts}
|
|
66
|
+
}
|
|
67
|
+
else if (!isObj(opts))
|
|
68
|
+
{
|
|
69
|
+
console.error({obj, opts});
|
|
70
|
+
throw new TypeError("invalid opts value");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const fn = (typeof opts.fn === F) ? opts.fn : clone;
|
|
74
|
+
const meth = (typeof opts.method === S) ? opts.method.trim() : CLONE;
|
|
75
|
+
const args = (Array.isArray(opts.args)) ? opts.args : [opts];
|
|
76
|
+
|
|
77
|
+
if (meth && typeof obj[meth] === F)
|
|
78
|
+
{ // Use a clone method
|
|
79
|
+
return obj[meth](...args);
|
|
80
|
+
}
|
|
81
|
+
else
|
|
82
|
+
{ // Use a clone function
|
|
83
|
+
return fn.call(opts, obj);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = unlocked;
|