@lumjs/core 1.38.1 → 1.38.3

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,7 +1,10 @@
1
1
  "use strict";
2
2
 
3
- const {dfa} = require('./df');
4
- const {isObj,isNil} = require('../types');
3
+ const {df,dfa} = require('./df');
4
+ const {isObj,isIterable,isNil} = require('../types');
5
+ const cp = Object.assign;
6
+ const ASSIGND_OPTS = Symbol('@lumjs/core/obj.assignd~opts');
7
+ const DEF_OPTS = {force: 'configurable'};
5
8
 
6
9
  /**
7
10
  * Copy properties into a target object.
@@ -13,27 +16,71 @@ const {isObj,isNil} = require('../types');
13
16
  * Basically a super simple, non-recursive replacement for my copyProps()
14
17
  * and cp() functions that were overly complex and tried to do way too much.
15
18
  *
16
- * @param {object} target - The target object to copy properties into
17
- * @param {...object} sources - Source objects to copy properties from
19
+ * The default options used by this function when calling dfa() are:
20
+ *
21
+ * `{configurable: true, force: 'configurable'}`
22
+ *
23
+ * Which simply means that by default the `configurable` descriptor property
24
+ * will be forced to `true` on the properties assigned, allowing them to be
25
+ * re-assigned later. You can override the defaults using special properties
26
+ * in either the target or source objects.
27
+ *
28
+ * @param {object} target - The target object to copy properties into.
29
+ *
30
+ * If you create a `target[assignd.OPTS]` property as an object, it may be
31
+ * used to set any of the options supported by the dfa() function.
32
+ * The options specified here take priority over the defaults shown above,
33
+ * and will be used for all sources.
34
+ *
35
+ * In addition to the options supported by dfa(), an additional `skip`
36
+ * option may be specified, it should be an array of property keys that
37
+ * SHOULD NOT be overwritten in the target. The `assignd.OPTS` property
38
+ * is always implicitly in the skip list and will never be overridden.
39
+ *
40
+ * @param {...object} sources - Source objects to copy properties from.
41
+ *
42
+ * If you create a `source[assignd.OPTS]` property as an object, it may be
43
+ * used to set any of the options supported by the dfa() function.
44
+ * The options specified here take priority over any set in the `target`,
45
+ * and only apply to that specific source object.
46
+ *
47
+ * The options assigned to source objects do NOT support the `skip` option.
48
+ *
18
49
  * @returns {object} the `target` object
19
50
  *
20
51
  * @alias module:@lumjs/core/obj.assignd
21
52
  */
22
53
  function assignd (target, ...sources)
23
54
  {
55
+ const skips = [ASSIGND_OPTS];
56
+ const topts = cp({}, DEF_OPTS, target[ASSIGND_OPTS]);
57
+
58
+ if (isIterable(topts.skip))
59
+ {
60
+ skips.push(...topts.skip);
61
+ }
62
+
24
63
  for (const src of sources)
25
64
  {
26
65
  if (isObj(src))
27
66
  {
67
+ const sopts = cp({}, topts, src[ASSIGND_OPTS]);
28
68
  const descs = Object.getOwnPropertyDescriptors(src);
29
- dfa(target, descs);
69
+ for (let skip of skips)
70
+ {
71
+ delete descs[skip];
72
+ }
73
+ dfa(target, descs, sopts);
30
74
  }
31
75
  else if (!isNil(src))
32
76
  {
33
77
  console.error("Invalid assignd source", {src, sources, target});
34
78
  }
35
79
  }
80
+
36
81
  return target;
37
82
  }
38
83
 
84
+ df(assignd, 'OPTS', {value: ASSIGND_OPTS});
85
+
39
86
  module.exports = assignd;
package/lib/obj/df.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- const {doesDescriptor,needObj,needType,lazy,B,TYPES} = require('../types');
3
+ const {B,F,S,doesDescriptor} = require('../types/basics');
4
+ const {needObj,needType} = require('../types/needs');
5
+ const TYPES = require('../types/typelist');
4
6
 
5
7
  const DEF_DESC = {configurable: true}
6
8
 
@@ -11,8 +13,8 @@ const dps = Object.defineProperties;
11
13
  /**
12
14
  * Handle populating descriptor objects for df() and family.
13
15
  *
14
- * Exported as `obj.df.descriptor`, but since jsdoc doesn't like
15
- * properties attached to functions, I've listed it as internal.
16
+ * Alias: `df.descriptor()`
17
+ * @alias module:@lumjs/core/obj~descriptor
16
18
  *
17
19
  * @param {object} desc - Descriptor object from df()
18
20
  * @param {object} opts - Options from df();
@@ -30,8 +32,17 @@ const dps = Object.defineProperties;
30
32
  * only applies to data descriptors (ones with a `value` property),
31
33
  * NOT to accessor descriptors.
32
34
  *
35
+ * @param {(boolean|string|string[])} [opts.force=false] Force `opts` values?
36
+ *
37
+ * If this is set to true, then any descriptor property specified in `opts`
38
+ * will be set on `desc` regardless as to if it already has that property.
39
+ * That includes the fallback `configurable` property value.
40
+ *
41
+ * You may also set this to the name(s) of the properties which should
42
+ * be forced (any not in the list will only be assigned if not in the
43
+ * `desc` object already).
44
+ *
33
45
  * @returns {object} `desc`
34
- * @alias module:@lumjs/core/obj~descriptor
35
46
  */
36
47
  function descriptor(desc, opts)
37
48
  {
@@ -40,9 +51,24 @@ function descriptor(desc, opts)
40
51
  const dps = ['configurable', 'enumerable'];
41
52
  if (desc.value) dps.push('writable');
42
53
 
54
+ let force;
55
+ if (!opts.force)
56
+ { // force NONE
57
+ force = [];
58
+ }
59
+ else if (opts.force === true)
60
+ { // force ALL
61
+ force = dps;
62
+ }
63
+ else
64
+ { // force selected
65
+ force = Array.isArray(opts.force) ? opts.force : [opts.force];
66
+ }
67
+
43
68
  for (const dp of dps)
44
69
  {
45
- if (typeof desc[dp] !== B && typeof opts[dp] === B)
70
+ if (typeof opts[dp] === B
71
+ && (force.includes(dp) || typeof desc[dp] !== B))
46
72
  {
47
73
  desc[dp] = opts[dp];
48
74
  }
@@ -52,14 +78,10 @@ function descriptor(desc, opts)
52
78
  }
53
79
 
54
80
  /**
55
- * A replacement for `types.def()`, but MUCH simpler.
56
- *
57
- * Instead of one function where every argument has several different
58
- * purposes depending on the type of value, there are now a bunch
59
- * of functions, each one with a single purpose, and a single signature.
81
+ * A wrapper around Object.defineProperty() which simplifies things a bit.
60
82
  *
61
- * This is the main one that all the rest use and is just a wrapper
62
- * around Object.defineProperty() that simplifies its use a bit.
83
+ * Alias: `df.one`
84
+ * @alias module:@lumjs/core/obj.df
63
85
  *
64
86
  * @param {(object|function)} obj - Target to define property in
65
87
  * @param {(string|symbol)} name - Property identifier
@@ -68,9 +90,15 @@ function descriptor(desc, opts)
68
90
  * See `opts.useDesc` for how this value is handled in regards to
69
91
  * whether it is treated as a property descriptor or a plain data value.
70
92
  *
71
- * @param {object} [opts] Options
93
+ * @param {(object|function|boolean)} [opts] Options
72
94
  *
73
- * See {@link module:@lumjs/core/obj~descriptor} for options that
95
+ * If this is a boolean, it will be used as `opts.useDesc`.
96
+ *
97
+ * If both `value` and this are functions, then they will be used
98
+ * as the `get` and `set` descriptor values and `opts.useDesc` will
99
+ * be set to `true`.
100
+ *
101
+ * See {@link module:@lumjs/core/obj~descriptor} for more options that
74
102
  * are used as descriptor defaults when assigning the property.
75
103
  *
76
104
  * @param {?boolean} [opts.useDesc=null] Treat `value` is a descriptor?
@@ -86,8 +114,7 @@ function descriptor(desc, opts)
86
114
  * [doesDescriptor()]{@link module:@lumjs/core/types.doesDescriptor},
87
115
  * and that will determine if the value is treated as a descriptor or not.
88
116
  *
89
- * @returns {object} `obj`
90
- * @alias module:@lumjs/core/obj.df
117
+ * @returns {(object|function)} `obj`
91
118
  */
92
119
  function df(obj, name, value, opts={})
93
120
  {
@@ -95,26 +122,37 @@ function df(obj, name, value, opts={})
95
122
  needObj(obj, {log, fun: true});
96
123
  needType(TYPES.PROP, name, {log});
97
124
 
125
+ if (typeof opts === B)
126
+ {
127
+ opts = {useDesc: opts};
128
+ }
129
+ else if (typeof opts === F && typeof value === F)
130
+ {
131
+ value = {get: value, set: opts};
132
+ opts = {useDesc: true};
133
+ }
134
+
98
135
  const useDesc = opts.useDesc ?? doesDescriptor(value);
99
136
  const desc = descriptor((useDesc ? value : {value}), opts);
100
137
 
101
138
  return Object.defineProperty(obj, name, desc);
102
139
  }
103
140
 
104
- // Sub-export
141
+ df(df, 'one', df);
105
142
  df(df, 'descriptor', descriptor);
106
- df(df, 'lazy', lazy);
107
143
 
108
144
  /**
109
145
  * A wrapper around df() that defines multiple properties at once.
146
+ *
147
+ * Alias: `df.all`
148
+ * @alias module:@lumjs/core/obj.dfa
110
149
  *
111
150
  * @param {(object|function)} obj - Target to define property in
112
151
  * @param {object} values
113
152
  * All string-keyed enumerable properties will be passed to df()
114
- * @param {object} [opts] See df() for details
153
+ * @param {(object|boolean)} [opts] See df() for details
115
154
  *
116
- * @returns {object} `obj`
117
- * @alias module:@lumjs/core/obj.dfa
155
+ * @returns {(object|function)} `obj`
118
156
  */
119
157
  function dfa(obj, values, opts)
120
158
  {
@@ -132,55 +170,38 @@ function dfa(obj, values, opts)
132
170
  df(df, 'all', dfa);
133
171
 
134
172
  /**
135
- * A wrapper around df() that defines an *accessor property* with
136
- * BOTH a getter and a setter specified explicitly.
137
- *
138
- * @param {(object|function)} obj - Target to define property in
139
- * @param {(string|symbol)} name - Property identifier
140
- * @param {function} getter - Getter function
141
- * @param {function} setter - Setter function
142
- * @param {object} [opts] Options
143
- *
144
- * The actual options passed to df() will be a clone, with the
145
- * `useDesc` option explicitly forced to `true`.
146
- *
147
- */
148
- function dfb(obj, name, getter, setter, opts)
149
- {
150
- const log = {obj,name,getter,setter,opts};
151
- opts = clone(opts, {useDesc: true});
152
- return df(obj, name, {get: getter, set: setter}, opts);
153
- }
154
-
155
- df(df, 'both', dfb);
156
-
157
- /**
158
- * Build magic closures to define properties using a chained syntax.
173
+ * Build closures to define properties using a chained syntax.
159
174
  *
160
- * This actually creates closures for df(), dfa(), dfb(), and lazy().
161
- * You can switch between them using magic properties.
175
+ * This creates closures for df(), dfa(), and lazy().
176
+ * You can switch between them using special properties.
162
177
  *
163
178
  * The closures when called will pass their arguments to the function
164
179
  * they are wrapping, and in a bit of brain-melting recursion, the
165
180
  * return value from each closure is the closure itself.
166
181
  *
167
- * Magic Properties that are added to both closures:
182
+ * Special Properties that are added to all of the closures:
168
183
  *
169
- * - `one(name, value)` → A closure that wraps df()
170
- * - `all(values)` A closure that wraps dfa()
171
- * - `both(name, g, s)` → A closure that wraps dfb()
172
- * - `lazy(name, func)` → A closure that wraps lazy()
173
- * - `update(opts)` → Update the options used by the closures.
184
+ * - `one(name, val, [set])` → A closure that wraps df();
185
+ * `set` (setter function) is only used if `val` is a getter function.
186
+ * - `all(values)` → A closure that wraps dfa()
187
+ * - `lazy(name, func)` → A closure that wraps lazy()
188
+ * - `update(opts)` → Update the options used by the closures.
174
189
  * Uses Object.assign() to copy options from the specied object
175
- * to the internal options object from the original dfc() call.
190
+ * to the internal options object from the original dfor() call.
176
191
  * This is a method call, NOT a closure function.
177
- * - `opts` → The actual options object used by the closures.
178
- * - `obj` → The target object used by the closures.
179
- * - `for(obj)` → A wrapper method that will call dfc().
192
+ * - `opts` → The options object used by the closures.
193
+ * - `obj` → The target object used by the closures.
194
+ * - `for(obj)` → A wrapper method that will call dfor().
180
195
  * When using this method, a *new set of closures* will be created,
181
196
  * with the current options, but a new target object.
182
197
  * If you use this in a chained statement, any calls after this
183
198
  * will be using the new closures rather than the original ones.
199
+ * - `prev` → The previous closure(s) from which
200
+ * the chained `for()` method was called. If there is no previous
201
+ * set of closures, then this will return the current closure.
202
+ *
203
+ * Alias: `df.for()`
204
+ * @alias module:@lumjs/core/obj.dfor
184
205
  *
185
206
  * @param {(object|function)} obj - Target to define property in;
186
207
  * available as the `obj` magic property on the closures.
@@ -188,12 +209,22 @@ df(df, 'both', dfb);
188
209
  * @param {object} [opts] Options - see df() for details;
189
210
  * available as the `opts` magic property on the closures.
190
211
  *
212
+ * @param {?function} [prev=null] INTERNAL USE ONLY;
213
+ * if the `for()` wrapper method is used in a closure,
214
+ * that closure will be passed here, which makes returning
215
+ * to the previous closure possible.
216
+ *
191
217
  * @returns {function} The `one` closure.
192
218
  */
193
- function dfc(obj, opts={})
219
+ function dfor(obj, opts={}, prev=null)
194
220
  {
195
- const fnOne = function (name, value)
221
+ const fnOne = function (name, value, setter)
196
222
  {
223
+ if (typeof setter === F && typeof value === F)
224
+ {
225
+ value = {get: value, set: setter}
226
+ opts = clone(opts, {useDesc: true});
227
+ }
197
228
  df(obj, name, value, opts);
198
229
  return fnOne;
199
230
  }
@@ -204,12 +235,6 @@ function dfc(obj, opts={})
204
235
  return fnAll;
205
236
  }
206
237
 
207
- const fnBoth = function (name, getter, setter)
208
- {
209
- dfb(obj, name, getter, setter, opts);
210
- return fnBoth;
211
- }
212
-
213
238
  const fnLazy = function (name, initfunc)
214
239
  {
215
240
  lazy(obj, name, initfunc, opts);
@@ -222,8 +247,8 @@ function dfc(obj, opts={})
222
247
  opts: {value: opts},
223
248
  one: {value: fnOne},
224
249
  all: {value: fnAll},
225
- both: {value: fnBoth},
226
250
  lazy: {value: fnLazy},
251
+ prev: {get() { return prev ?? this }},
227
252
  update:
228
253
  {
229
254
  value()
@@ -236,25 +261,147 @@ function dfc(obj, opts={})
236
261
  {
237
262
  value(newobj)
238
263
  {
239
- return dfc(newobj, opts);
264
+
265
+ return dfor(newobj, opts);
240
266
  },
241
267
  },
242
268
  }
243
269
 
244
- // Quick alias
245
- ctx.next = ctx.for;
246
-
247
270
  dps(fnOne, ctx);
248
271
  dps(fnAll, ctx);
249
- dps(fnBoth, ctx);
250
272
  dps(fnLazy, ctx);
251
273
 
252
274
  return fnOne;
253
275
  }
254
276
 
255
- df(df, 'for', dfc);
277
+ df(df, 'for', dfor);
256
278
 
257
- module.exports =
279
+ /**
280
+ * Build a lazy initializer property.
281
+ *
282
+ * This builds an *accessor* property that will replace itself
283
+ * with a initialized property, the value of which is obtained from
284
+ * a supplied initialization function. Any subsequent use of the
285
+ * property will obviously be using the initialized property directly.
286
+ *
287
+ * The general purpose is if there is a property that requires
288
+ * a fairly intensive load time, or requires a large library that
289
+ * you may not always *need* for basic things, you can use this
290
+ * to ensure that the property is only initialized when needed.
291
+ *
292
+ * Alias: `df.lazy`
293
+ * @alias module:@lumjs/core/obj.lazy
294
+ *
295
+ * @param {(object|function)} target - The object to add the property to.
296
+ *
297
+ * @param {string} name - The name of the property to add.
298
+ *
299
+ * @param {module:@lumjs/core/obj~LazyGetter} initfunc
300
+ * The function to initialize the property.
301
+ *
302
+ * This will normally only ever be called the first time the property
303
+ * is accessed in a *read* operation. The return value from this
304
+ * will be assigned to the property using `def()`, replacing the
305
+ * lazy accessor property.
306
+ *
307
+ * @param {object} [opts] Options to customize behavior.
308
+ *
309
+ * @param {module:@lumjs/core/obj~LazySetter} [opts.set]
310
+ * A function to handle assignment attempts.
311
+ *
312
+ * This obviously only applies to the *lazy accessor* property,
313
+ * and will have no affect once the property has been replaced
314
+ * by its initialized value.
315
+ *
316
+ * @param {boolean} [opts.enumerable=false] Is the property enumerable?
317
+ *
318
+ * This applies to the *lazy accessor* property only.
319
+ * You can set custom descriptor rules in the `initfunc`
320
+ * if you need them on the initialized property.
321
+ *
322
+ * @param {?boolean} [opts.assign] The *default* value for the `assign` property
323
+ * in the [LazyDef]{@link module:@lumjs/core/obj~LazyDef} object.
324
+ *
325
+ * @param {*} [opts.df] The *default* value for the `df`/`def` property in
326
+ * the [LazyDef]{@link module:@lumjs/core/obj~LazyDef} object.
327
+ *
328
+ * @param {*} [opts.def] Alias of `opts.df` for compatibility.
329
+ *
330
+ * @returns {(object|function)} The `target` argument
331
+ */
332
+ function lazy(target, name, initfunc, opts={})
258
333
  {
259
- df, dfa, dfb, dfc, lazy,
334
+ let log = {target, name, initfunc, opts};
335
+ needObj(target, {log, fun: true});
336
+ needType(TYPES.PROP, name, {log});
337
+ needType(F, initfunc, {log});
338
+ needObj(opts, {log});
339
+
340
+ // The `this` object for the functions.
341
+ const ctx =
342
+ {
343
+ name,
344
+ target,
345
+ opts,
346
+ arguments,
347
+ assign: opts.assign,
348
+ df: opts.df ?? opts.def ?? {},
349
+ }
350
+ ctx.def = ctx.df;
351
+
352
+ // The descriptor for the lazy accessor.
353
+ const desc =
354
+ {
355
+ configurable: true,
356
+ enumerable: opts.enumerable ?? false,
357
+ };
358
+
359
+ // Replace the property if rules are correct.
360
+ const defval = function(value)
361
+ {
362
+ if (ctx.assign ?? value !== undefined)
363
+ { // Replace the lazy accessor with the returned value.
364
+ df(target, name, value, ctx.df);
365
+ // Now return the newly assigned value.
366
+ return target[name];
367
+ }
368
+ else if (doesDescriptor(value))
369
+ { // A descriptor was returned, extract the real value.
370
+ if (typeof value.get === F)
371
+ {
372
+ return value.get();
373
+ }
374
+ else
375
+ {
376
+ return value.value;
377
+ }
378
+ }
379
+ else
380
+ { // Just return the original value.
381
+ return value;
382
+ }
383
+ }
384
+
385
+ desc.get = function()
386
+ {
387
+ return defval(initfunc.call(ctx,ctx));
388
+ }
389
+
390
+ if (typeof opts.set === F)
391
+ { // We want to assign a lazy setter as well.
392
+ desc.set = function(value)
393
+ {
394
+ defval(opts.set.call(ctx,value,ctx));
395
+ }
396
+ }
397
+
398
+ // Assign the lazy accessor property.
399
+ return df(target, name, desc);
260
400
  }
401
+
402
+ df(df, 'lazy', lazy);
403
+ df(lazy, 'df', df);
404
+ // TODO: remove this for v2.x
405
+ lazy(lazy, 'def', () => require('../types/def'));
406
+
407
+ module.exports = {descriptor, df, dfa, dfor, lazy}
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ function getPrototypesOf(obj)
4
+ {
5
+ let chain = [];
6
+ while (true)
7
+ {
8
+ if (obj && typeof obj === 'object')
9
+ {
10
+ let objClass = obj.constructor;
11
+ if (!chain.includes(objClass))
12
+ {
13
+ chain.push(objClass);
14
+ }
15
+ obj = getProto(obj);
16
+ }
17
+ else
18
+ {
19
+ break;
20
+ }
21
+ }
22
+ return chain;
23
+ }
24
+
25
+ module.exports = getPrototypesOf;
package/lib/obj/index.js CHANGED
@@ -8,10 +8,11 @@ const assignd = require('./assignd');
8
8
  const {copyAll,duplicateOne,duplicateAll} = require('./copyall');
9
9
  const copyProps = require('./copyprops');
10
10
  const {CLONE,clone,addClone,cloneIfLocked} = require('./clone');
11
- const {df,dfa,dfb,dfc,lazy} = require('./df');
11
+ const {df,dfa,dfor,lazy} = require('./df');
12
12
  const {flip, flipKeyVal, flipMap} = require('./flip');
13
13
  const {getMethods,signatureOf,MethodFilter} = require('./getmethods');
14
14
  const getProperty = require('./getproperty');
15
+ const getPrototypesOf = require('./getprotos');
15
16
  const {lock,addLock} = require('./lock');
16
17
  const {mergeNested,syncNested} = require('./merge');
17
18
  const ns = require('./ns');
@@ -20,16 +21,35 @@ const unlocked = require('./unlocked');
20
21
 
21
22
  const
22
23
  {
23
- getObjectPath,setObjectPath,
24
- getNamespace,setNamespace,
24
+ getObjectPath,setObjectPath,delObjectPath,
25
+ getNamespace,setNamespace,nsFactory,
25
26
  } = ns;
26
27
 
28
+ /**
29
+ * Create a new object with a `null` prototype.
30
+ *
31
+ * @alias module:@lumjs/core/obj.discrete
32
+ *
33
+ * Unlike regular objects, discrete objects won't have any
34
+ * of the usual meta-programming methods or properties.
35
+ * Not sure why you'd want that, but anyway, here you go.
36
+ *
37
+ * @param {object} [props] Optional properties to define in
38
+ * the new object. Uses the same format as Object.defineProperties()
39
+ *
40
+ * @returns {object}
41
+ */
42
+ const discrete = (props) => Object.create(null, props);
43
+
44
+ // TODO: reorder the following alphabetically, for sanity sake...
27
45
  module.exports =
28
46
  {
29
47
  assignd, cp, CLONE, clone, addClone, cloneIfLocked, lock, addLock,
30
- df, dfa, dfb, dfc, lazy,
31
- mergeNested, syncNested, copyProps, copyAll, ns,
32
- getObjectPath, setObjectPath, getNamespace, setNamespace,
48
+ df, dfa, dfor, lazy, discrete,
49
+ mergeNested, syncNested, copyProps, copyAll, ns, nsFactory,
50
+ getObjectPath, setObjectPath, delObjectPath, getNamespace, setNamespace,
33
51
  getProperty, duplicateAll, duplicateOne, getMethods, signatureOf,
34
52
  MethodFilter, apply, flip, flipKeyVal, flipMap, unlocked,
53
+ getPrototypesOf,
35
54
  }
55
+