@lumjs/core 1.24.1 → 1.25.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/arrays/list.js +2 -3
- package/lib/index.js +7 -0
- package/lib/obj/getproperty.js +3 -2
- package/lib/traits.js +681 -0
- package/lib/types/basics.js +13 -1
- package/lib/types/index.js +6 -3
- package/lib/types/root.js +1 -41
- package/lib/types/unbound/extend.js +46 -0
- package/lib/types/unbound/objects.js +2 -0
- package/package.json +2 -1
package/lib/arrays/list.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
const {F,S} = require('../types');
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
2
|
* A wrapper class to abstract functionality of various kinds of lists.
|
|
6
3
|
*
|
|
@@ -313,3 +310,5 @@ function removeItems(list, ...items)
|
|
|
313
310
|
List.removeItems = removeItems;
|
|
314
311
|
|
|
315
312
|
module.exports = exports = List;
|
|
313
|
+
|
|
314
|
+
const {F,S} = require('../types');
|
package/lib/index.js
CHANGED
|
@@ -86,6 +86,13 @@ lazy(exports, 'obj', () => require('./obj'));
|
|
|
86
86
|
*/
|
|
87
87
|
lazy(exports, 'console', () => require('./console'));
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* A simplistic implementation of class/object traits «Lazy»
|
|
91
|
+
* @name module:@lumjs/core.traits
|
|
92
|
+
* @type {module:@lumjs/core/traits}
|
|
93
|
+
*/
|
|
94
|
+
lazy(exports, 'traits', () => require('./traits'));
|
|
95
|
+
|
|
89
96
|
/**
|
|
90
97
|
* Functions for getting values and properties with fallback defaults «Lazy»
|
|
91
98
|
* @name module:@lumjs/core.opt
|
package/lib/obj/getproperty.js
CHANGED
package/lib/traits.js
ADDED
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A very simplistic Trait system.
|
|
3
|
+
* @module @lumjs/core/traits
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
const getProp = require('./obj/getproperty');
|
|
9
|
+
|
|
10
|
+
const
|
|
11
|
+
{
|
|
12
|
+
def,F,B,
|
|
13
|
+
isObj,isArray,isConstructor,isProperty,
|
|
14
|
+
needObj,
|
|
15
|
+
} = require('./types');
|
|
16
|
+
|
|
17
|
+
// Symbol for private storage of composed traits.
|
|
18
|
+
const COMPOSED_TRAITS = Symbol.for('@lumjs/core/traits~ComposedTraits');
|
|
19
|
+
|
|
20
|
+
// Protected function to get a class prototype.
|
|
21
|
+
function ensureProto(from)
|
|
22
|
+
{
|
|
23
|
+
if (isObj(from))
|
|
24
|
+
{ // Assume an object is already a prototype.
|
|
25
|
+
return from;
|
|
26
|
+
}
|
|
27
|
+
else if (isConstructor(from))
|
|
28
|
+
{ // It's a constructor, return its prototype.
|
|
29
|
+
return from.prototype;
|
|
30
|
+
}
|
|
31
|
+
else
|
|
32
|
+
{
|
|
33
|
+
throw new TypeError("Invalid class constructor or object instance");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const hasOwn
|
|
38
|
+
= (typeof Object.hasOwn === F)
|
|
39
|
+
? Object.hasOwn
|
|
40
|
+
: (obj,prop) => obj.hasOwnProperty(prop);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Default list of properties to ignore if `opts.static` is `true`.
|
|
44
|
+
*
|
|
45
|
+
* If you specify your own `opts.filter` values, you should probably
|
|
46
|
+
* use this list as well.
|
|
47
|
+
*
|
|
48
|
+
* @see module:@lumjs/core/traits.compose
|
|
49
|
+
* @type {Array}
|
|
50
|
+
* @alias module:@lumjs/core/traits.IGNORE_STATIC
|
|
51
|
+
*/
|
|
52
|
+
const IGNORE_STATIC =
|
|
53
|
+
[
|
|
54
|
+
// Possible properties from `function` values.
|
|
55
|
+
'length', 'name', 'arguments', 'caller', 'prototype',
|
|
56
|
+
// Defined static methods from the Trait abstract class.
|
|
57
|
+
'composeInto', 'setupTrait', 'getComposed', 'decomposeFrom', 'removeTrait',
|
|
58
|
+
// Optional static getter properties for the Trait abstract class.
|
|
59
|
+
'composeOptions', 'staticOptions',
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get a composed definition list.
|
|
64
|
+
*
|
|
65
|
+
* @param {(function|object)} target - Target to look in.
|
|
66
|
+
* @param {(function|object)} [source] Source trait to get defs for.
|
|
67
|
+
*
|
|
68
|
+
* @returns {mixed} Return value depends on a few factors.
|
|
69
|
+
*
|
|
70
|
+
* If NO traits have been composed into the `target`, returns `null`.
|
|
71
|
+
*
|
|
72
|
+
* If NO `source` argument was passed, then this will return a `Map` where
|
|
73
|
+
* the keys will be a full set of `source` traits that have been composed,
|
|
74
|
+
* and each value will be a {@link module:@lumjs/core/traits~Composed} object.
|
|
75
|
+
*
|
|
76
|
+
* If a `source` argument was passed, and that trait has been composed by
|
|
77
|
+
* the `target` at least once (even if it has since been decomposed),
|
|
78
|
+
* the return value will be the {@link module:@lumjs/core/traits~Composed}
|
|
79
|
+
* object for the trait.
|
|
80
|
+
*
|
|
81
|
+
* If the specified `source` trait has *never* been composed, but at least
|
|
82
|
+
* one other trait has been, this will return `undefined`.
|
|
83
|
+
*
|
|
84
|
+
* @throws {TypeError} If `target` is not valid.
|
|
85
|
+
*
|
|
86
|
+
* @alias module:@lumjs/core/traits.getComposed
|
|
87
|
+
*/
|
|
88
|
+
function getComposed(target, source)
|
|
89
|
+
{
|
|
90
|
+
needObj(target, true, 'invalid target');
|
|
91
|
+
|
|
92
|
+
let composed = null;
|
|
93
|
+
|
|
94
|
+
if (target[COMPOSED_TRAITS] instanceof Map)
|
|
95
|
+
{
|
|
96
|
+
composed = target[COMPOSED_TRAITS];
|
|
97
|
+
}
|
|
98
|
+
else if (isObj(target))
|
|
99
|
+
{
|
|
100
|
+
return getComposed(target.constructor, source);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (composed && source)
|
|
104
|
+
{ // Let's get definitions for a specific trait.
|
|
105
|
+
return composed.get(source);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return composed;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Private function to initialize composed maps.
|
|
112
|
+
function mapComposed(target, source)
|
|
113
|
+
{
|
|
114
|
+
if (!(target[COMPOSED_TRAITS] instanceof Map))
|
|
115
|
+
{
|
|
116
|
+
def(target, COMPOSED_TRAITS, {value: new Map()});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const composed = {proto: null, static: null};
|
|
120
|
+
target[COMPOSED_TRAITS].set(source, composed);
|
|
121
|
+
return composed;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Compose a trait into a target.
|
|
126
|
+
*
|
|
127
|
+
* @param {(function|object)} target - Target to compose trait into.
|
|
128
|
+
*
|
|
129
|
+
* Pass a class constructor function if you want to add properties
|
|
130
|
+
* that will be available to every instance of the class.
|
|
131
|
+
*
|
|
132
|
+
* Pass an object instance of you only want the properties added to
|
|
133
|
+
* an individual object and not the class itself.
|
|
134
|
+
*
|
|
135
|
+
* @param {(function|object)} source - Source trait to compose from.
|
|
136
|
+
*
|
|
137
|
+
* Generally _should_ be the class constructor function of the Trait.
|
|
138
|
+
*
|
|
139
|
+
* This _may_ be an object instance or prototype object, but that usage
|
|
140
|
+
* is NOT generally recommended, and may lead to unforeseen side-effects.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} [opts] Options for what to compose, and how.
|
|
143
|
+
*
|
|
144
|
+
* @param {(Array|function)} [opts.filter] Filter property names/symbols.
|
|
145
|
+
*
|
|
146
|
+
* Only used if `.props` is NOT defined, this can be used to block
|
|
147
|
+
* specific properties from being detected automatically.
|
|
148
|
+
*
|
|
149
|
+
* Has absolutely no effect if the `.props` option is defined.
|
|
150
|
+
*
|
|
151
|
+
* If it is a `function` it will be used by `Array#filter()` as a test
|
|
152
|
+
* to determine the validity of property names/symbols.
|
|
153
|
+
*
|
|
154
|
+
* If it is an `Array` the property names/symbols in the array
|
|
155
|
+
* will **ignored/skipped** (i.e. a negative filter).
|
|
156
|
+
*
|
|
157
|
+
* If not specified, the defaults depend on the `.static` option:
|
|
158
|
+
* - If `.static` is `false` there is no default value.
|
|
159
|
+
* - If `.static` is `true`, the default is to ignore all of the
|
|
160
|
+
* built-in properties `Object.getOwnPropertyNames()` returns on
|
|
161
|
+
* `function` values (which class constructors are), plus any
|
|
162
|
+
* of the static methods and getters from the `Trait` abstract class.
|
|
163
|
+
*
|
|
164
|
+
* @param {object} [opts.props] A list of trait properties to compose.
|
|
165
|
+
*
|
|
166
|
+
* May be a flat `Array` or a `Set` in which case the same keys are used
|
|
167
|
+
* for the both the source and target properties.
|
|
168
|
+
*
|
|
169
|
+
* May be a `Map` or just a plain `object` whose enumerable properties
|
|
170
|
+
* will be used as keys/value, where the key is the property name in
|
|
171
|
+
* the source, and the value is the property name to use in the target.
|
|
172
|
+
*
|
|
173
|
+
* If the target property is not a `string` or `Symbol`, then the source
|
|
174
|
+
* property key will be used. This makes it easy to rename some properties
|
|
175
|
+
* but not others.
|
|
176
|
+
*
|
|
177
|
+
* If omitted or set to any non-object value, we will get a list of
|
|
178
|
+
* all properties defined on the `source`, by default using the
|
|
179
|
+
* `Object.getOwnPropertyNames()` method.
|
|
180
|
+
*
|
|
181
|
+
* @param {boolean} [opts.overwrite=false] Overwrite existing properties?
|
|
182
|
+
*
|
|
183
|
+
* @param {boolean} [opts.static=false] Compose static properties?
|
|
184
|
+
*
|
|
185
|
+
* - If `true` ONLY the *static* properties/methods will be composed.
|
|
186
|
+
* - If `false` ONLY the *prototype* properties/methods will be composed.
|
|
187
|
+
*
|
|
188
|
+
* In order to compose _both_ you'd need to call the function twice,
|
|
189
|
+
* or use the {@link module:@lumjs/core/traits.composeFully} function.
|
|
190
|
+
*
|
|
191
|
+
* Only useful if `source` is a class constructor (always recommended).
|
|
192
|
+
*
|
|
193
|
+
* Default is `false`.
|
|
194
|
+
*
|
|
195
|
+
* @param {boolean} [opts.symbols=false] Auto-compose `Symbol` properties?
|
|
196
|
+
*
|
|
197
|
+
* Calls `Object.getOwnPropertySymbols()` when generating a list of
|
|
198
|
+
* trait properties to compose.
|
|
199
|
+
*
|
|
200
|
+
* Only used if `.props` is NOT defined; has no effect if `.props` is set.
|
|
201
|
+
*
|
|
202
|
+
* Default is `false`.
|
|
203
|
+
*
|
|
204
|
+
* @prop {boolean} [opts.strings=true] Auto-compose `string` properties?
|
|
205
|
+
*
|
|
206
|
+
* Calls `Object.getOwnPropertyNames()` when generating a list of
|
|
207
|
+
* trait properties to compose.
|
|
208
|
+
*
|
|
209
|
+
* Only used if `.props` is NOT defined; has no effect if `.props` is set.
|
|
210
|
+
*
|
|
211
|
+
* Default is `true`.
|
|
212
|
+
*
|
|
213
|
+
* @returns {@link module:@lumjs/core/traits~Composed}
|
|
214
|
+
*
|
|
215
|
+
* @throws {TypeError} If any of the arguments are not valid types.
|
|
216
|
+
*
|
|
217
|
+
* @alias module:@lumjs/core/traits.compose
|
|
218
|
+
*/
|
|
219
|
+
function compose(specTarget, specSource, opts={})
|
|
220
|
+
{
|
|
221
|
+
needObj(specSource, true, 'invalid source trait');
|
|
222
|
+
needObj(opts, false, 'invalid options object');
|
|
223
|
+
|
|
224
|
+
const composedMaps
|
|
225
|
+
= getComposed(specTarget, specSource)
|
|
226
|
+
?? mapComposed(specTarget, specSource);
|
|
227
|
+
|
|
228
|
+
const isStatic = opts.static ?? false;
|
|
229
|
+
|
|
230
|
+
const mapId = isStatic ? 'static' : 'proto';
|
|
231
|
+
|
|
232
|
+
if (composedMaps[mapId])
|
|
233
|
+
{
|
|
234
|
+
console.warn(specSource, mapId, "already composed", specTarget, opts);
|
|
235
|
+
return composedMaps;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const target = isStatic ? specTarget : ensureProto(specTarget);
|
|
239
|
+
const source = isStatic ? specSource : ensureProto(specSource);
|
|
240
|
+
|
|
241
|
+
let props = opts.props;
|
|
242
|
+
|
|
243
|
+
if (!isObj(props))
|
|
244
|
+
{ // No valid props specified, use all 'own' properties.
|
|
245
|
+
const doStr = opts.strings ?? true,
|
|
246
|
+
doSym = opts.symbols ?? false;
|
|
247
|
+
|
|
248
|
+
props = doStr ? Object.getOwnPropertyNames(source) : [];
|
|
249
|
+
|
|
250
|
+
if (doSym)
|
|
251
|
+
{ // Compose symbols as well.
|
|
252
|
+
const symbols = Object.getOwnPropertySymbols(source);
|
|
253
|
+
props.push(...symbols);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let filter = opts.filter ?? (isStatic ? IGNORE_STATIC : null);
|
|
257
|
+
|
|
258
|
+
if (isArray(filter))
|
|
259
|
+
{ // Convert ignore into a filter function.
|
|
260
|
+
const ignoreList = filter;
|
|
261
|
+
filter = (p) => !ignoreList.includes(p);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (typeof filter === F)
|
|
265
|
+
{
|
|
266
|
+
props = props.filter(filter);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let sprops, tprops;
|
|
271
|
+
|
|
272
|
+
if (isArray(props))
|
|
273
|
+
{ // A flat list of properties, use the same names.
|
|
274
|
+
sprops = tprops = props;
|
|
275
|
+
}
|
|
276
|
+
else if (props instanceof Set)
|
|
277
|
+
{ // Almost identical to using an Array.
|
|
278
|
+
sprops = tprops = Array.from(props.values());
|
|
279
|
+
}
|
|
280
|
+
else if (props instanceof Map)
|
|
281
|
+
{ // A structured Map of source name to dest name.
|
|
282
|
+
sprops = Array.from(props.keys());
|
|
283
|
+
tprops = Array.from(props.values());
|
|
284
|
+
}
|
|
285
|
+
else
|
|
286
|
+
{ // A plain object mapping.
|
|
287
|
+
sprops = Object.keys(props);
|
|
288
|
+
tprops = Object.values(props);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// A map to store the composed property info in.
|
|
292
|
+
const composed = composedMaps[mapId] = new Map();
|
|
293
|
+
|
|
294
|
+
for (const i in sprops)
|
|
295
|
+
{
|
|
296
|
+
let sprop = sprops[i];
|
|
297
|
+
let tprop = tprops[i];
|
|
298
|
+
|
|
299
|
+
const cdef =
|
|
300
|
+
{
|
|
301
|
+
added: true,
|
|
302
|
+
found: true,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (!isProperty(sprop))
|
|
306
|
+
{ // Should never happen, but...
|
|
307
|
+
sprop = sprop.toString();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (tprop === '' || !isProperty(tprop))
|
|
311
|
+
{ // Use the source name as the target name.
|
|
312
|
+
tprop = sprop;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
cdef.id = tprop; // Ref to the target property here.
|
|
316
|
+
|
|
317
|
+
if (sprop !== tprop)
|
|
318
|
+
{ // Source key differs, add a reference to it.
|
|
319
|
+
cdef.srcKey = sprop;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const propDesc = getProp(source, sprop, null);
|
|
323
|
+
cdef.newDescriptor = propDesc;
|
|
324
|
+
|
|
325
|
+
if (!propDesc)
|
|
326
|
+
{ // No source property, that's weird.
|
|
327
|
+
cdef.added = false;
|
|
328
|
+
cdef.found = false;
|
|
329
|
+
}
|
|
330
|
+
else if (hasOwn(target, tprop))
|
|
331
|
+
{ // The target property already exists.
|
|
332
|
+
if (opts.overwrite)
|
|
333
|
+
{ // Save the overwritten property descriptor.
|
|
334
|
+
cdef.oldDescriptor = getProp(target, tprop);
|
|
335
|
+
}
|
|
336
|
+
else
|
|
337
|
+
{ // Not going to add it.
|
|
338
|
+
cdef.added = false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (cdef.added)
|
|
343
|
+
{ // (re-)define the target property.
|
|
344
|
+
def(target, tprop, propDesc);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
composed.set(tprop, cdef);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
composedMaps[mapId] = composed;
|
|
351
|
+
return composedMaps;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function getOpts(inOpts, isStatic)
|
|
355
|
+
{
|
|
356
|
+
return Object.assign({}, inOpts, {static: isStatic});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* A wrapper around `compose()` that calls it twice.
|
|
361
|
+
*
|
|
362
|
+
* - Once to compose *prototype* properties.
|
|
363
|
+
* - Then to compose *static* properties.
|
|
364
|
+
*
|
|
365
|
+
* While the `target` and `source` arguments _can_ be specified
|
|
366
|
+
* as `object` values, for the purposes of this function it makes
|
|
367
|
+
* the most sense to pass class constructor `function` values.
|
|
368
|
+
* The behaviour if `object` values are passed is *undefined* and
|
|
369
|
+
* *untested*, and the rest of the function documentation assumes
|
|
370
|
+
* only `function` values will be passed.
|
|
371
|
+
*
|
|
372
|
+
* @param {function} target - Target class
|
|
373
|
+
* @param {function} source - Source trait
|
|
374
|
+
*
|
|
375
|
+
* @param {object} [protoOpts] Options for the *prototype* call.
|
|
376
|
+
*
|
|
377
|
+
* Compiled options will have `static` option forced to `false`.
|
|
378
|
+
*
|
|
379
|
+
* @param {object} [staticOpts] Options for the *static* call.
|
|
380
|
+
*
|
|
381
|
+
* Compiled options will have `static` option forced to `true`.
|
|
382
|
+
*
|
|
383
|
+
* @param {boolean} [reverse=false] Reverse order of composing?
|
|
384
|
+
*
|
|
385
|
+
* If `true` then *static* properties will be composed first,
|
|
386
|
+
* followed by *prototype* properties.
|
|
387
|
+
*
|
|
388
|
+
* @returns {@link module:@lumjs/core/traits~Composed}
|
|
389
|
+
*
|
|
390
|
+
* Will have both `proto` and `static` properties set.
|
|
391
|
+
*
|
|
392
|
+
* @see module:@lumjs/core/traits.compose
|
|
393
|
+
* @alias module:@lumjs/core/traits.composeFully
|
|
394
|
+
*/
|
|
395
|
+
function composeFully(target, source, protoOpts, staticOpts, reverse=false)
|
|
396
|
+
{
|
|
397
|
+
const OPR = getOpts(protoOpts, false);
|
|
398
|
+
const OST = getOpts(staticOpts, true);
|
|
399
|
+
const ALL = reverse ? [OST,OPR] : [OPR,OST];
|
|
400
|
+
|
|
401
|
+
let composed;
|
|
402
|
+
|
|
403
|
+
for (const opts of ALL)
|
|
404
|
+
{
|
|
405
|
+
composed = compose(target, source, opts);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return composed;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Decompose (remove) a trait from a target.
|
|
413
|
+
*
|
|
414
|
+
* Applicable properties that were added to the `target` will be removed.
|
|
415
|
+
*
|
|
416
|
+
* If an existing property was overwritten when the trait was composed,
|
|
417
|
+
* the original property will be restored.
|
|
418
|
+
*
|
|
419
|
+
* @param {(function|object)} target - Target to decompose trait from.
|
|
420
|
+
* @param {(function|object)} source - Source trait we are decomposing.
|
|
421
|
+
*
|
|
422
|
+
* @param {object} [opts] Options
|
|
423
|
+
* @param {boolean} [opts.static] Decompose only a subset of properties?
|
|
424
|
+
*
|
|
425
|
+
* - If `true` ONLY *static* properties will be decomposed.
|
|
426
|
+
* - If `false` ONLY *prototype* properties will be decomposed.
|
|
427
|
+
* - If omitted **ALL** properties will be decomposed!
|
|
428
|
+
*
|
|
429
|
+
* @returns {number} Number of properties decomposed.
|
|
430
|
+
*/
|
|
431
|
+
function decompose(specTarget, specSource, opts={})
|
|
432
|
+
{
|
|
433
|
+
const isStatic = opts.static
|
|
434
|
+
|
|
435
|
+
let c = 0;
|
|
436
|
+
|
|
437
|
+
if (typeof isStatic !== B)
|
|
438
|
+
{ // The default is to remove all composed properties.
|
|
439
|
+
c += decompose(specTarget, specSource, getOpts(opts, true));
|
|
440
|
+
c += decompose(specTarget, specSource, getOpts(opts, false));
|
|
441
|
+
return c;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const composedMaps = getComposed(specTarget, specSource);
|
|
445
|
+
const mapId = isStatic ? 'static' : 'proto';
|
|
446
|
+
if (composedMaps && composedMaps[mapId] instanceof Map)
|
|
447
|
+
{ // Found a valid map.
|
|
448
|
+
const composed = composedMaps[mapId];
|
|
449
|
+
const target = isStatic ? specTarget : ensureProto(specTarget);
|
|
450
|
+
|
|
451
|
+
for (const [prop,spec] of composed)
|
|
452
|
+
{
|
|
453
|
+
if (spec.added)
|
|
454
|
+
{
|
|
455
|
+
if (spec.oldDescriptor)
|
|
456
|
+
{ // An old descriptor to restore.
|
|
457
|
+
def(target, prop, spec.oldDescriptor);
|
|
458
|
+
}
|
|
459
|
+
else
|
|
460
|
+
{ // Just delete the added property descriptor.
|
|
461
|
+
delete target[prop];
|
|
462
|
+
}
|
|
463
|
+
c++; // Increment the counter.
|
|
464
|
+
}
|
|
465
|
+
} // for composed
|
|
466
|
+
|
|
467
|
+
// Now remove the property map itself.
|
|
468
|
+
composedMaps[mapId] = null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return c;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* An abstract class for Traits.
|
|
476
|
+
*
|
|
477
|
+
* Simply offers a couple static methods and APIs that
|
|
478
|
+
* wrap the `compose()` and `composeFully()` functions,
|
|
479
|
+
* and makes it fairly simple to create Trait classes.
|
|
480
|
+
*
|
|
481
|
+
* @alias module:@lumjs/core/traits.Trait
|
|
482
|
+
*/
|
|
483
|
+
class CoreTrait
|
|
484
|
+
{
|
|
485
|
+
/**
|
|
486
|
+
* Extend another class or object instance with the methods and
|
|
487
|
+
* getter/setter properties from a Trait.
|
|
488
|
+
*
|
|
489
|
+
* The sub-class of Trait this static method is called on will always
|
|
490
|
+
* be the `source` argument.
|
|
491
|
+
*
|
|
492
|
+
* @param {(function|object)} target - Target class or instance.
|
|
493
|
+
*
|
|
494
|
+
* @param {object} [protoOpts] Options for `compose()` function.
|
|
495
|
+
*
|
|
496
|
+
* If this is not specified, or is any value other than an `object`,
|
|
497
|
+
* we will look for defaults in a `composeOptions` static property:
|
|
498
|
+
*
|
|
499
|
+
* ```js
|
|
500
|
+
* static get composeOptions() { return {your: default, options: here}; }
|
|
501
|
+
* ```
|
|
502
|
+
*
|
|
503
|
+
* @param {(object|true)} [staticOpts] Static options.
|
|
504
|
+
*
|
|
505
|
+
* If this is set we'll use `composeFully()` instead of using `compose()`.
|
|
506
|
+
*
|
|
507
|
+
* If this value is an `object` it will be used as the `staticOpts`.
|
|
508
|
+
*
|
|
509
|
+
* If this is the special value `true`, then we will look for the options
|
|
510
|
+
* in a `staticOptions` static property:
|
|
511
|
+
*
|
|
512
|
+
* ```js
|
|
513
|
+
* static get staticOptions() { return {your: static, options: here}; }
|
|
514
|
+
* ```
|
|
515
|
+
*
|
|
516
|
+
* If this any value other than an `object` or `true`, it will be ignored
|
|
517
|
+
* entirely, and the regular `compose()` call will be used.
|
|
518
|
+
*
|
|
519
|
+
* @returns {object} Return value from the `setupTrait()` static method.
|
|
520
|
+
*
|
|
521
|
+
*/
|
|
522
|
+
static composeInto(target, protoOpts, staticOpts)
|
|
523
|
+
{
|
|
524
|
+
if (!isObj(protoOpts))
|
|
525
|
+
{
|
|
526
|
+
protoOpts = this.composeOptions ?? {};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (staticOpts === true)
|
|
530
|
+
{
|
|
531
|
+
staticOpts = this.staticOptions ?? {};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
let composed;
|
|
535
|
+
|
|
536
|
+
if (isObj(staticOpts))
|
|
537
|
+
{
|
|
538
|
+
composed = composeFully(target, this, protoOpts, staticOpts);
|
|
539
|
+
}
|
|
540
|
+
else
|
|
541
|
+
{
|
|
542
|
+
composed = compose(target, this, protoOpts);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return this.setupTrait({target, protoOpts, staticOpts, composed});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* A static method called by `composeInto()`
|
|
550
|
+
* _after_ composing the trait properties into the target.
|
|
551
|
+
*
|
|
552
|
+
* @param {object} info - Metadata from `composeInto()`
|
|
553
|
+
* @param {(function|object)} info.target - The `target` argument
|
|
554
|
+
* @param {object} info.protoOpts - The `protoOpts` used
|
|
555
|
+
* @param {object} [info.staticOpts] The `staticOpts` if used
|
|
556
|
+
* @param {module:@lumjs/core/traits~Composed} info.composed
|
|
557
|
+
* The return value from `compose()` or `composeFully()`.
|
|
558
|
+
*
|
|
559
|
+
* @returns {object} The `info` object, with any changes made
|
|
560
|
+
* by an overridden `setupTrait()` method in the sub-class.
|
|
561
|
+
*
|
|
562
|
+
* The default implementation is a placeholder that returns the
|
|
563
|
+
* `info` object without making any changes.
|
|
564
|
+
*
|
|
565
|
+
*/
|
|
566
|
+
static setupTrait(info)
|
|
567
|
+
{
|
|
568
|
+
if (this.debug)
|
|
569
|
+
{
|
|
570
|
+
console.debug(this.name, "setupTrait()", info, this);
|
|
571
|
+
}
|
|
572
|
+
return info;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* A method wrapping {@link module:@lumjs/core/traits.decompose}
|
|
577
|
+
* where the `source` is always the Trait sub-class constructor.
|
|
578
|
+
*
|
|
579
|
+
* See the `decompose()` docs for descriptions of the other arguments.
|
|
580
|
+
*
|
|
581
|
+
* @param {(function|object)} target
|
|
582
|
+
* @param {object} [opts]
|
|
583
|
+
* @returns {object} Return value from the `removeTrait()` static method.
|
|
584
|
+
*/
|
|
585
|
+
static decomposeFrom(target, opts)
|
|
586
|
+
{
|
|
587
|
+
const info = {target};
|
|
588
|
+
info.composed = this.getComposed(target);
|
|
589
|
+
info.count = decompose(target, this, opts);
|
|
590
|
+
return this.removeTrait(info);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* A static method called by `decomposeFrom()`
|
|
595
|
+
* _after_ decomposing the trait properties from the target.
|
|
596
|
+
*
|
|
597
|
+
* @param {object} info - Metadata from `decomposeFrom()`
|
|
598
|
+
* @param {(function|object)} info.target - The `target` argument
|
|
599
|
+
* @param {module:@lumjs/core/traits~Composed} info.composed
|
|
600
|
+
* The property map that was previously composed.
|
|
601
|
+
* @param {number} info.count - The number of properties decomposed.
|
|
602
|
+
*
|
|
603
|
+
* @returns {object} The `info` object, with any changes made
|
|
604
|
+
* by an overridden `removeTrait()` method in the sub-class.
|
|
605
|
+
*
|
|
606
|
+
* The default implementation is a placeholder that returns the
|
|
607
|
+
* `info` object without making any changes.
|
|
608
|
+
*
|
|
609
|
+
*/
|
|
610
|
+
static removeTrait(info)
|
|
611
|
+
{
|
|
612
|
+
if (this.debug)
|
|
613
|
+
{
|
|
614
|
+
console.debug(this.name, "removeTrait()", info, this);
|
|
615
|
+
}
|
|
616
|
+
return info;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* A method wrapping {@link module:@lumjs/core/traits.getComposed}
|
|
621
|
+
* where the `source` is always the Trait sub-class constructor.
|
|
622
|
+
*
|
|
623
|
+
* @param {(function|object)} target
|
|
624
|
+
* @returns {mixed} Return value from `getComposed()`
|
|
625
|
+
*/
|
|
626
|
+
static getComposed(target)
|
|
627
|
+
{
|
|
628
|
+
return getComposed(target, this);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
} // CoreTrait class
|
|
632
|
+
|
|
633
|
+
module.exports =
|
|
634
|
+
{
|
|
635
|
+
// The main public exports.
|
|
636
|
+
compose, composeFully, getComposed, decompose,
|
|
637
|
+
Trait: CoreTrait, IGNORE_STATIC,
|
|
638
|
+
|
|
639
|
+
// Undocumented protected functions.
|
|
640
|
+
ensureProto, hasOwn,
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Composed property maps.
|
|
645
|
+
*
|
|
646
|
+
* @typedef {object} module:@lumjs/core/traits~Composed
|
|
647
|
+
*
|
|
648
|
+
* @prop {?Map} proto - Map of composed *prototype* properties.
|
|
649
|
+
*
|
|
650
|
+
* Keys are the `string` or `symbol` for each composed property.
|
|
651
|
+
* Values are {@link module:@lumjs/core/traits~ComposedProperty} objects.
|
|
652
|
+
*
|
|
653
|
+
* If no applicable trait properties are currently composed,
|
|
654
|
+
* this will be `null`.
|
|
655
|
+
*
|
|
656
|
+
* @prop {?Map} static - Map of composed *static* properties.
|
|
657
|
+
*
|
|
658
|
+
* Exactly the same description as `proto`, but for static properties.
|
|
659
|
+
*
|
|
660
|
+
*/
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Composed property definitions.
|
|
664
|
+
*
|
|
665
|
+
* @typedef {object} module:@lumjs/core/traits~ComposedProperty
|
|
666
|
+
*
|
|
667
|
+
* @prop {(string|Symbol)} id - The property key on the `target`
|
|
668
|
+
* @prop {(string|Symbol)} [srcKey] The property key from the `source`;
|
|
669
|
+
* only included if different from `propKey`.
|
|
670
|
+
*
|
|
671
|
+
* @prop {?object} newDescriptor - The property descriptor to be added;
|
|
672
|
+
* will be `null` if the property did not exist.
|
|
673
|
+
*
|
|
674
|
+
* @prop {object} [oldDescriptor] The replaced property descriptor;
|
|
675
|
+
* only included if there was an existing property in the `target`
|
|
676
|
+
* and the `opts.overwrite` option was `true`.
|
|
677
|
+
*
|
|
678
|
+
* @prop {boolean} found - Was the property found in the `source` ?
|
|
679
|
+
* @prop {boolean} added - Was the property added to the `target` ?
|
|
680
|
+
*
|
|
681
|
+
*/
|
package/lib/types/basics.js
CHANGED
|
@@ -124,6 +124,18 @@ function isIterable(v)
|
|
|
124
124
|
return (isObj(v) && typeof v[Symbol.iterator] === F);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Does a value appear to be a class constructor?
|
|
129
|
+
*
|
|
130
|
+
* @param {*} v - Value to test
|
|
131
|
+
* @returns {boolean}
|
|
132
|
+
* @alias module:@lumjs/core/types.isConstructor
|
|
133
|
+
*/
|
|
134
|
+
function isConstructor(v)
|
|
135
|
+
{
|
|
136
|
+
return (typeof v === F && isObj(v.prototype));
|
|
137
|
+
}
|
|
138
|
+
|
|
127
139
|
/**
|
|
128
140
|
* See if an object can be used as a valid *descriptor*.
|
|
129
141
|
*
|
|
@@ -216,6 +228,6 @@ function doesDescriptorTemplate(obj, accessor=false, strict=true)
|
|
|
216
228
|
module.exports =
|
|
217
229
|
{
|
|
218
230
|
isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
|
|
219
|
-
isIterable, nonEmptyArray, isArguments, isProperty,
|
|
231
|
+
isIterable, nonEmptyArray, isArguments, isProperty, isConstructor,
|
|
220
232
|
doesDescriptor, doesDescriptorTemplate,
|
|
221
233
|
}
|
package/lib/types/index.js
CHANGED
|
@@ -24,7 +24,7 @@ const
|
|
|
24
24
|
{
|
|
25
25
|
isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
|
|
26
26
|
nonEmptyArray, isArguments, isProperty, doesDescriptor, isIterable,
|
|
27
|
-
doesDescriptorTemplate,
|
|
27
|
+
isConstructor, doesDescriptorTemplate,
|
|
28
28
|
} = require('./basics');
|
|
29
29
|
|
|
30
30
|
// Root namespace helpers.
|
|
@@ -59,10 +59,13 @@ module.exports =
|
|
|
59
59
|
isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
|
|
60
60
|
nonEmptyArray, isArguments, isProperty, doesDescriptor,
|
|
61
61
|
isInstance, isType, isa, needObj, needType, needs, stringify,
|
|
62
|
-
doesDescriptorTemplate, ownCount, isIterable,
|
|
62
|
+
doesDescriptorTemplate, ownCount, isIterable, isConstructor,
|
|
63
63
|
isArrayOf, isListOf, isMapOf, isObjOf, OfTest,
|
|
64
64
|
console,
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
//
|
|
67
|
+
// This will be the module for TYPES.add()
|
|
68
68
|
def(TYPES, '$module', module);
|
|
69
|
+
|
|
70
|
+
// Extend `unbound` with add() and remove() methods.
|
|
71
|
+
require('./unbound/extend')(unbound);
|
package/lib/types/root.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const {U} = require('./js');
|
|
2
2
|
const {isNil,isArray} = require('./basics');
|
|
3
|
-
const removeFromArray = require('../arrays/list').removeItems;
|
|
4
3
|
|
|
5
4
|
// «private»
|
|
6
5
|
function no_root()
|
|
@@ -21,7 +20,7 @@ const root = typeof globalThis !== U ? globalThis
|
|
|
21
20
|
exports.root = root;
|
|
22
21
|
|
|
23
22
|
// A list of objects to be considered unbound globally.
|
|
24
|
-
const unboundObjects =
|
|
23
|
+
const unboundObjects = require('./unbound/objects');
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
26
|
* Pass `this` here to see if it is bound to an object.
|
|
@@ -52,42 +51,3 @@ function unbound(whatIsThis, rootIsUnbound=true, areUnbound=false)
|
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
exports.unbound = unbound;
|
|
55
|
-
|
|
56
|
-
// Now that 'unbound' is exported, we can do some wibbly wobbly magic.
|
|
57
|
-
const def = require('./def');
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Add an item to the unbound global objects list.
|
|
61
|
-
*
|
|
62
|
-
* @function
|
|
63
|
-
* @param {(object|function)} obj - The object to be considered unbound.
|
|
64
|
-
* @returns {boolean} Will be `false` if `obj` is already unbound.
|
|
65
|
-
* @throws {TypeError} If `obj` was neither an `object` nor a `function`.
|
|
66
|
-
* @alias module:@lumjs/core/types.unbound.add
|
|
67
|
-
*/
|
|
68
|
-
def(unbound, 'add', function (obj)
|
|
69
|
-
{
|
|
70
|
-
needObj(obj, true);
|
|
71
|
-
if (unbound(obj, true, true))
|
|
72
|
-
{ // Item is already unbound.
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
// Add to list and we're done.
|
|
76
|
-
unboundObjects.push(obj);
|
|
77
|
-
return true;
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Remove an item from the unbound global objects list.
|
|
82
|
-
*
|
|
83
|
-
* @function
|
|
84
|
-
* @param {(object|function)} obj - The object to be removed.
|
|
85
|
-
* @returns {boolean} Will be `false` if the item was not in the list.
|
|
86
|
-
* @throws {TypeError} If `obj` was neither an `object` nor a `function`.
|
|
87
|
-
* @alias module:@lumjs/core/types.unbound.remove
|
|
88
|
-
*/
|
|
89
|
-
def(unbound, 'remove', function(obj)
|
|
90
|
-
{
|
|
91
|
-
needObj(obj, true);
|
|
92
|
-
return (removeFromArray(unboundObjects, obj) > 0);
|
|
93
|
-
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const def = require('../def');
|
|
4
|
+
const {needObj} = require('../needs');
|
|
5
|
+
const removeFromArray = require('../../arrays/list').removeItems;
|
|
6
|
+
const unboundObjects = require('./objects');
|
|
7
|
+
|
|
8
|
+
// Adds a couple magic methods to the `unbound` function.
|
|
9
|
+
module.exports = function(unbound)
|
|
10
|
+
{
|
|
11
|
+
/**
|
|
12
|
+
* Add an item to the unbound global objects list.
|
|
13
|
+
*
|
|
14
|
+
* @function
|
|
15
|
+
* @param {(object|function)} obj - The object to be considered unbound.
|
|
16
|
+
* @returns {boolean} Will be `false` if `obj` is already unbound.
|
|
17
|
+
* @throws {TypeError} If `obj` was neither an `object` nor a `function`.
|
|
18
|
+
* @name module:@lumjs/core/types.unbound.add
|
|
19
|
+
*/
|
|
20
|
+
def(unbound, 'add', function (obj)
|
|
21
|
+
{
|
|
22
|
+
needObj(obj, true);
|
|
23
|
+
if (unbound(obj, true, true))
|
|
24
|
+
{ // Item is already unbound.
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
// Add to list and we're done.
|
|
28
|
+
unboundObjects.push(obj);
|
|
29
|
+
return true;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Remove an item from the unbound global objects list.
|
|
34
|
+
*
|
|
35
|
+
* @function
|
|
36
|
+
* @param {(object|function)} obj - The object to be removed.
|
|
37
|
+
* @returns {boolean} Will be `false` if the item was not in the list.
|
|
38
|
+
* @throws {TypeError} If `obj` was neither an `object` nor a `function`.
|
|
39
|
+
* @name module:@lumjs/core/types.unbound.remove
|
|
40
|
+
*/
|
|
41
|
+
def(unbound, 'remove', function(obj)
|
|
42
|
+
{
|
|
43
|
+
needObj(obj, true);
|
|
44
|
+
return (removeFromArray(unboundObjects, obj) > 0);
|
|
45
|
+
});
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumjs/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"exports":
|
|
6
6
|
{
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"./observable": "./lib/observable.js",
|
|
19
19
|
"./opt": "./lib/opt.js",
|
|
20
20
|
"./strings": "./lib/strings.js",
|
|
21
|
+
"./traits": "./lib/traits.js",
|
|
21
22
|
"./types": "./lib/types/index.js",
|
|
22
23
|
|
|
23
24
|
"./package.json": "./package.json"
|