@lumjs/core 1.37.2 → 1.38.1

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.
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ const {dfa} = require('./df');
4
+ const {isObj,isNil} = require('../types');
5
+
6
+ /**
7
+ * Copy properties into a target object.
8
+ *
9
+ * Like Object.assign(), but it uses getOwnPropertyDescriptors() to get
10
+ * the properties to copy, and [dfa()]{@link module:@lumjs/core/obj.dfa}
11
+ * to assign them.
12
+ *
13
+ * Basically a super simple, non-recursive replacement for my copyProps()
14
+ * and cp() functions that were overly complex and tried to do way too much.
15
+ *
16
+ * @param {object} target - The target object to copy properties into
17
+ * @param {...object} sources - Source objects to copy properties from
18
+ * @returns {object} the `target` object
19
+ *
20
+ * @alias module:@lumjs/core/obj.assignd
21
+ */
22
+ function assignd (target, ...sources)
23
+ {
24
+ for (const src of sources)
25
+ {
26
+ if (isObj(src))
27
+ {
28
+ const descs = Object.getOwnPropertyDescriptors(src);
29
+ dfa(target, descs);
30
+ }
31
+ else if (!isNil(src))
32
+ {
33
+ console.error("Invalid assignd source", {src, sources, target});
34
+ }
35
+ }
36
+ return target;
37
+ }
38
+
39
+ module.exports = assignd;
package/lib/obj/df.js ADDED
@@ -0,0 +1,260 @@
1
+ "use strict";
2
+
3
+ const {doesDescriptor,needObj,needType,lazy,B,TYPES} = require('../types');
4
+
5
+ const DEF_DESC = {configurable: true}
6
+
7
+ const cp = Object.assign;
8
+ const clone = (...args) => cp({}, ...args);
9
+ const dps = Object.defineProperties;
10
+
11
+ /**
12
+ * Handle populating descriptor objects for df() and family.
13
+ *
14
+ * Exported as `obj.df.descriptor`, but since jsdoc doesn't like
15
+ * properties attached to functions, I've listed it as internal.
16
+ *
17
+ * @param {object} desc - Descriptor object from df()
18
+ * @param {object} opts - Options from df();
19
+ *
20
+ * Will be checked for default descriptor options in the
21
+ * case where those options were not explicitly defined
22
+ * in the descriptor object.
23
+ *
24
+ * @param {boolean} [opts.configurable=true] configurable rule;
25
+ * this is the only option with a fallback default value.
26
+ *
27
+ * @param {boolean} [opts.enumerable] enumerable rule
28
+ *
29
+ * @param {boolean} [opts.writable] writable rule;
30
+ * only applies to data descriptors (ones with a `value` property),
31
+ * NOT to accessor descriptors.
32
+ *
33
+ * @returns {object} `desc`
34
+ * @alias module:@lumjs/core/obj~descriptor
35
+ */
36
+ function descriptor(desc, opts)
37
+ {
38
+ opts = clone(DEF_DESC, opts);
39
+
40
+ const dps = ['configurable', 'enumerable'];
41
+ if (desc.value) dps.push('writable');
42
+
43
+ for (const dp of dps)
44
+ {
45
+ if (typeof desc[dp] !== B && typeof opts[dp] === B)
46
+ {
47
+ desc[dp] = opts[dp];
48
+ }
49
+ }
50
+
51
+ return desc;
52
+ }
53
+
54
+ /**
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.
60
+ *
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.
63
+ *
64
+ * @param {(object|function)} obj - Target to define property in
65
+ * @param {(string|symbol)} name - Property identifier
66
+ * @param {*} value - Value to assign
67
+ *
68
+ * See `opts.useDesc` for how this value is handled in regards to
69
+ * whether it is treated as a property descriptor or a plain data value.
70
+ *
71
+ * @param {object} [opts] Options
72
+ *
73
+ * See {@link module:@lumjs/core/obj~descriptor} for options that
74
+ * are used as descriptor defaults when assigning the property.
75
+ *
76
+ * @param {?boolean} [opts.useDesc=null] Treat `value` is a descriptor?
77
+ *
78
+ * If this is set to `true` then the `value` will be assumed to be a
79
+ * valid descriptor object; no tests will be done to confirm, so be sure!
80
+ *
81
+ * If this is set to `false` then a descriptor of `{value}` will be created.
82
+ * Again, no tests will be done, so this is a way to assign properties that
83
+ * have `value`, `get`, or `set` properties in them.
84
+ *
85
+ * If this is `null` (the default), then the `value` will be tested with
86
+ * [doesDescriptor()]{@link module:@lumjs/core/types.doesDescriptor},
87
+ * and that will determine if the value is treated as a descriptor or not.
88
+ *
89
+ * @returns {object} `obj`
90
+ * @alias module:@lumjs/core/obj.df
91
+ */
92
+ function df(obj, name, value, opts={})
93
+ {
94
+ const log = {obj,name,value,opts};
95
+ needObj(obj, {log, fun: true});
96
+ needType(TYPES.PROP, name, {log});
97
+
98
+ const useDesc = opts.useDesc ?? doesDescriptor(value);
99
+ const desc = descriptor((useDesc ? value : {value}), opts);
100
+
101
+ return Object.defineProperty(obj, name, desc);
102
+ }
103
+
104
+ // Sub-export
105
+ df(df, 'descriptor', descriptor);
106
+ df(df, 'lazy', lazy);
107
+
108
+ /**
109
+ * A wrapper around df() that defines multiple properties at once.
110
+ *
111
+ * @param {(object|function)} obj - Target to define property in
112
+ * @param {object} values
113
+ * All string-keyed enumerable properties will be passed to df()
114
+ * @param {object} [opts] See df() for details
115
+ *
116
+ * @returns {object} `obj`
117
+ * @alias module:@lumjs/core/obj.dfa
118
+ */
119
+ function dfa(obj, values, opts)
120
+ {
121
+ const log = {obj,values,opts};
122
+ needObj(values, {log});
123
+
124
+ for (const key in values)
125
+ {
126
+ df(obj, key, values[key], opts);
127
+ }
128
+
129
+ return obj;
130
+ }
131
+
132
+ df(df, 'all', dfa);
133
+
134
+ /**
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.
159
+ *
160
+ * This actually creates closures for df(), dfa(), dfb(), and lazy().
161
+ * You can switch between them using magic properties.
162
+ *
163
+ * The closures when called will pass their arguments to the function
164
+ * they are wrapping, and in a bit of brain-melting recursion, the
165
+ * return value from each closure is the closure itself.
166
+ *
167
+ * Magic Properties that are added to both closures:
168
+ *
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.
174
+ * Uses Object.assign() to copy options from the specied object
175
+ * to the internal options object from the original dfc() call.
176
+ * 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().
180
+ * When using this method, a *new set of closures* will be created,
181
+ * with the current options, but a new target object.
182
+ * If you use this in a chained statement, any calls after this
183
+ * will be using the new closures rather than the original ones.
184
+ *
185
+ * @param {(object|function)} obj - Target to define property in;
186
+ * available as the `obj` magic property on the closures.
187
+ *
188
+ * @param {object} [opts] Options - see df() for details;
189
+ * available as the `opts` magic property on the closures.
190
+ *
191
+ * @returns {function} The `one` closure.
192
+ */
193
+ function dfc(obj, opts={})
194
+ {
195
+ const fnOne = function (name, value)
196
+ {
197
+ df(obj, name, value, opts);
198
+ return fnOne;
199
+ }
200
+
201
+ const fnAll = function (values)
202
+ {
203
+ dfa(obj, values, opts);
204
+ return fnAll;
205
+ }
206
+
207
+ const fnBoth = function (name, getter, setter)
208
+ {
209
+ dfb(obj, name, getter, setter, opts);
210
+ return fnBoth;
211
+ }
212
+
213
+ const fnLazy = function (name, initfunc)
214
+ {
215
+ lazy(obj, name, initfunc, opts);
216
+ return fnLazy;
217
+ }
218
+
219
+ const ctx =
220
+ {
221
+ obj: {value: obj},
222
+ opts: {value: opts},
223
+ one: {value: fnOne},
224
+ all: {value: fnAll},
225
+ both: {value: fnBoth},
226
+ lazy: {value: fnLazy},
227
+ update:
228
+ {
229
+ value()
230
+ {
231
+ cp(opts, ...arguments);
232
+ return this;
233
+ },
234
+ },
235
+ for:
236
+ {
237
+ value(newobj)
238
+ {
239
+ return dfc(newobj, opts);
240
+ },
241
+ },
242
+ }
243
+
244
+ // Quick alias
245
+ ctx.next = ctx.for;
246
+
247
+ dps(fnOne, ctx);
248
+ dps(fnAll, ctx);
249
+ dps(fnBoth, ctx);
250
+ dps(fnLazy, ctx);
251
+
252
+ return fnOne;
253
+ }
254
+
255
+ df(df, 'for', dfc);
256
+
257
+ module.exports =
258
+ {
259
+ df, dfa, dfb, dfc, lazy,
260
+ }
package/lib/obj/index.js CHANGED
@@ -4,9 +4,11 @@
4
4
  */
5
5
 
6
6
  const apply = require('./apply');
7
+ const assignd = require('./assignd');
7
8
  const {copyAll,duplicateOne,duplicateAll} = require('./copyall');
8
9
  const copyProps = require('./copyprops');
9
10
  const {CLONE,clone,addClone,cloneIfLocked} = require('./clone');
11
+ const {df,dfa,dfb,dfc,lazy} = require('./df');
10
12
  const {flip, flipKeyVal, flipMap} = require('./flip');
11
13
  const {getMethods,signatureOf,MethodFilter} = require('./getmethods');
12
14
  const getProperty = require('./getproperty');
@@ -24,7 +26,8 @@ const
24
26
 
25
27
  module.exports =
26
28
  {
27
- cp, CLONE, clone, addClone, cloneIfLocked, lock, addLock,
29
+ assignd, cp, CLONE, clone, addClone, cloneIfLocked, lock, addLock,
30
+ df, dfa, dfb, dfc, lazy,
28
31
  mergeNested, syncNested, copyProps, copyAll, ns,
29
32
  getObjectPath, setObjectPath, getNamespace, setNamespace,
30
33
  getProperty, duplicateAll, duplicateOne, getMethods, signatureOf,
package/lib/types/def.js CHANGED
@@ -7,6 +7,14 @@ const clone = (...args) => Object.assign({}, ...args);
7
7
  /**
8
8
  * A wrapper around `Object.defineProperty()` with added flair!
9
9
  *
10
+ * **FUTURE DEPRECATION**: this function will be deprecated in v1.19.0!
11
+ * It's gotten almost as convoluted as the old prop() method it replaced
12
+ * from my original Nano.js libraries.
13
+ *
14
+ * I have written new `obj.{df,dfa,dfc}` functions that will replace this
15
+ * function going forward. The related `types.lazy()` function will also
16
+ * be refactored to use the new functions, and moved to the `obj` module.
17
+ *
10
18
  * @param {(object|function)} obj - The object to add a property to.
11
19
  *
12
20
  * @param {?(string|symbol|boolean|object)} name
@@ -73,8 +81,8 @@ const clone = (...args) => Object.assign({}, ...args);
73
81
  * directly, so any changes will be made on the original.
74
82
  *
75
83
  * @param {boolean} [opts.configurable=true] Default `configurable` value
76
- * @param {boolean} [opts.enumerable=false] Default `enumerable` value
77
- * @param {boolean} [opts.writable=false] Default `writable` value
84
+ * @param {boolean} [opts.enumerable] Default `enumerable` value
85
+ * @param {boolean} [opts.writable] Default `writable` value
78
86
  *
79
87
  * Only used with *data descriptors* and will be ignored with
80
88
  * *accessor* descriptors.
package/lib/types/lazy.js CHANGED
@@ -100,6 +100,12 @@ const {doesDescriptor} = require('./basics');
100
100
  *
101
101
  * This is an extension of the [def()]{@link module:@lumjs/core/types.def}
102
102
  * method, and indeed an alias called `def.lazy()` is also available.
103
+ *
104
+ * IMPORTANT: In a later v1.18.x release, this will be refactored to use the
105
+ * [obj.df()]{@link module:@lumjs/core/obj.df} function that will replace
106
+ * def(). There's already an alias called `df.lazy()` available now.
107
+ * This will also be moved to the `obj` module, with a deprecated alias left
108
+ * in the `types` module for the duration of the 1.x lifecycle.
103
109
  *
104
110
  * @param {(object|function)} target - The object to add the property to.
105
111
  *
@@ -2,44 +2,103 @@ const {F, S, B} = require('./js');
2
2
  const {isType, isa} = require('./isa');
3
3
  const {isObj, isComplex} = require('./basics');
4
4
 
5
+ // Internal function used by both needObj and needType
6
+ function needOpts(opts, fn, bopt, ons)
7
+ {
8
+ if (isObj(opts))
9
+ {
10
+ if (opts.log)
11
+ { // Make a closure to handle the logging
12
+ const info = (opts.log === true) ? [opts]
13
+ : (Array.isArray(opts.log) ? opts.log : [opts.log]);
14
+ opts.$log = () => console.error(...info);
15
+ }
16
+ }
17
+ else if (typeof opts === S)
18
+ { // A message was passed.
19
+ opts =
20
+ {
21
+ msg: opts,
22
+ }
23
+ if (ons) ons(opts);
24
+ }
25
+ else if (typeof opts === B)
26
+ {
27
+ opts = {[bopt]: opts}
28
+ }
29
+ else
30
+ {
31
+ console.error('invalid options', {fn, opts});
32
+ opts = {};
33
+ }
34
+ return opts;
35
+ }
36
+
37
+ exports._needOptions = needOpts;
38
+
5
39
  /**
6
40
  * If a value is not an object, throw an error.
7
41
  *
8
42
  * @param {*} v - The value we're testing.
9
- * @param {(boolean|string)} [allowFunc=false] - Also accept `function`?
43
+ *
44
+ * @param {(object|boolean|string)} [opts] Options
45
+ *
46
+ * - If this is a string, it will be used as `opts.msg`,
47
+ * and `opts.fun` will be determined by the presence
48
+ * of the word 'function' (case-insensitive) in the message.
49
+ * - If this is a boolean, it will be used as `opts.fun`,
50
+ * and the default message will be used.
51
+ *
52
+ * @param {boolean} [opts.fun=false] Also accept `function`?
10
53
  *
11
54
  * By default this function uses `isObj()` to perform the
12
- * test. If `allowFunc` is `true` then it'll use `isComplex()` instead.
55
+ * test. If `opts.fun` is `true` then it'll use `isComplex()` instead.
56
+ *
57
+ * @param {(object|string|true)} [opts.log] Optional debugging information.
58
+ *
59
+ * This is a way to specify arguments to be sent to the
60
+ * console.error() method before the TypeError is thrown.
61
+ *
62
+ * If this is an `Array` object, it will be used as positional arguments.
13
63
  *
14
- * If this is a `string` then it'll be assigned to the `msg`
15
- * parameter, and `allowFunc` will be `true` if the word
16
- * `function` is found in the message, or `false` otherwise.
64
+ * If it is boolean `true`, the `opts` object itself will be used.
17
65
  *
18
- * @param {string} [msg] A custom error message.
66
+ * Any other value will be used as the sole argument.
19
67
  *
20
- * If not specified, a generic one will be used.
68
+ * @param {string} [opts.msg] The error message to use on failure.
69
+ *
70
+ * If not specified, a simple default message will be used.
71
+ *
72
+ * @param {?string} [msg=null] A positional version of `opts.msg`.
73
+ *
74
+ * Setting `opts.msg` as a named option will take precedence
75
+ * over this positional argument.
21
76
  *
22
77
  * @throws {TypeError} If the type check failed.
23
78
  * @alias module:@lumjs/core/types.needObj
24
79
  */
25
- function needObj (v, allowFunc=false, msg=null)
80
+ function needObj (v, opts={}, msg=null)
26
81
  {
27
- if (typeof allowFunc === S)
28
- { // A message was passed.
29
- msg = allowFunc;
30
- allowFunc = msg.toLowerCase().includes(F);
31
- }
82
+ opts = needOpts(opts, 'needObj', 'fun',
83
+ (o) => o.fun = o.msg.toLowerCase().includes(F));
32
84
 
33
- const ok = allowFunc ? isComplex(v) : isObj(v);
85
+ const ok = opts.fun ? isComplex(v) : isObj(v);
34
86
 
35
87
  if (!ok)
36
88
  { // Did not pass the test.
37
- if (typeof msg !== S)
89
+ if (typeof opts.msg === S)
90
+ {
91
+ msg = opts.msg;
92
+ }
93
+ else if (typeof msg !== S)
38
94
  { // Use a default message.
39
95
  msg = "Invalid object";
40
- if (allowFunc)
96
+ if (opts.fun)
41
97
  msg += " or function";
42
98
  }
99
+
100
+ if (opts.$log) opts.$log();
101
+
43
102
  throw new TypeError(msg);
44
103
  }
45
104
  }
@@ -51,28 +110,42 @@ exports.needObj = needObj;
51
110
  *
52
111
  * @param {string} type - The type name as per `isType()`.
53
112
  * @param {*} v - The value we're testing.
54
- * @param {string} [msg] A custom error message.
113
+ *
114
+ * @param {(object|string)} [opts] Options
115
+ *
116
+ * If this is a string, it will be used as `opts.msg`.
117
+ *
118
+ * @param {string} [opts.msg] See needObj() for details.
119
+ * @param {(object|string|true)} [opts.log] See needObj() for details.
120
+ *
121
+ * @param {?string} [msg=null] A positional version of `opts.msg`;
122
+ * handled the same was as needObj().
123
+ *
55
124
  * @throws {TypeError} If the type check failed.
56
125
  * @alias module:@lumjs/core/types.needType
57
126
  */
58
- function needType (type, v, msg, unused)
127
+ function needType (type, v, opts={}, msg=null)
59
128
  {
129
+ opts = needOpts(opts, 'needType', 'null');
130
+
131
+ if (typeof opts.null === B)
132
+ {
133
+ console.warn("needType(): 'allowNull' is no longer supported");
134
+ }
135
+
60
136
  if (!isType(type, v))
61
137
  {
62
- if (typeof msg === B)
138
+ if (typeof opts.msg === S)
63
139
  {
64
- console.warn("needType(): 'allowNull' is no longer supported");
65
- if (typeof unused === S)
66
- { // Compatibility with old code.
67
- msg = unused;
68
- }
140
+ msg = opts.msg;
69
141
  }
70
-
71
- if (typeof msg !== S)
142
+ else if (typeof msg !== S)
72
143
  { // Use a default message.
73
144
  msg = `Invalid ${type} value`;
74
145
  }
75
146
 
147
+ if (opts.$log) opts.$log();
148
+
76
149
  throw new TypeError(msg);
77
150
  }
78
151
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.37.2",
3
+ "version": "1.38.1",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {
@@ -17,6 +17,7 @@
17
17
  "./modules": "./lib/modules.js",
18
18
  "./obj": "./lib/obj/index.js",
19
19
  "./obj/cp": "./lib/obj/cp.js",
20
+ "./obj/df": "./lib/obj/df.js",
20
21
  "./observable": "./lib/observable.js",
21
22
  "./opt": "./lib/opt/index.js",
22
23
  "./opt/args": "./lib/opt/args.js",