@lumjs/core 1.21.0 → 1.23.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/PLANS.txt ADDED
@@ -0,0 +1,10 @@
1
+ # Future Plans
2
+
3
+ ## 2.x
4
+
5
+ I've done a lot of updates to this vanity library since writing it, and it's built up a
6
+ bit of cruft over time.
7
+
8
+ While it likely won't happen for a while yet, I do plan to have a clean-up version where
9
+ anything marked deprecated is removed, and any other cleanup that may break stuff can be
10
+ done at that time as well.
package/lib/index.js CHANGED
@@ -105,7 +105,10 @@ function from(submod, ...libs)
105
105
 
106
106
  // ObjectID stuff is imported directly without registering a sub-module.
107
107
  const objectid = require('./objectid');
108
- from(objectid, 'randomNumber', 'InternalObjectId');
108
+ from(objectid,
109
+ 'randomNumber',
110
+ 'UniqueObjectIds',
111
+ 'InternalObjectId');
109
112
 
110
113
  /**
111
114
  * Get a simplistic debugging stacktrace
@@ -136,7 +139,12 @@ from(objectid, 'randomNumber', 'InternalObjectId');
136
139
  // These are exported directly, but a meta sub-module also exists.
137
140
  // Unlike most sub-modules there is no `meta` property in the main library.
138
141
  const meta = require('./meta');
139
- from(meta, 'stacktrace', 'AbstractClass', 'Functions', 'NYI');
142
+ from(meta,
143
+ 'stacktrace',
144
+ 'AbstractClass',
145
+ 'AbstractError',
146
+ 'Functions',
147
+ 'NYI');
140
148
 
141
149
  /**
142
150
  * Create a magic *Enum* object «Lazy»
package/lib/meta.js CHANGED
@@ -27,6 +27,7 @@ exports.stacktrace = stacktrace;
27
27
 
28
28
  /**
29
29
  * Abstract classes for Javascript.
30
+ * @deprecated Just use `throw new AbstractError()` instead.
30
31
  * @alias module:@lumjs/core/meta.AbstractClass
31
32
  */
32
33
  class AbstractClass
@@ -103,6 +104,64 @@ class AbstractClass
103
104
 
104
105
  exports.AbstractClass = AbstractClass;
105
106
 
107
+ /**
108
+ * An Error that can be thrown from abstract methods.
109
+ *
110
+ * Example usage:
111
+ *
112
+ * ```js
113
+ * class MyAbstractClass
114
+ * {
115
+ * abstractMethod()
116
+ * {
117
+ * throw new AbstractError();
118
+ * // msg = "Abstract method not implemented"
119
+ * }
120
+ *
121
+ * namedMethod()
122
+ * {
123
+ * throw new AbstractError("namedMethod");
124
+ * // msg = "Abstract method 'namedMethod' not implemented"
125
+ * }
126
+ *
127
+ * get absProp()
128
+ * {
129
+ * throw new AbstractError("absProp", true);
130
+ * // msg = "Abstract getter 'absProp' not implemented"
131
+ * }
132
+ * }
133
+ * ```
134
+ *
135
+ * @alias module:@lumjs/core/meta.AbstractError
136
+ */
137
+ class AbstractError extends Error
138
+ {
139
+ /**
140
+ * Construct an AbstractError
141
+ *
142
+ * @param {string} [name] Name of abstract method/getter.
143
+ *
144
+ * If included will be included in error message.
145
+ *
146
+ * @param {boolean} [getter=false] Is a getter?
147
+ *
148
+ * This option literally just changes the phrasing of the
149
+ * error message to use 'getter' instead of 'method'.
150
+ *
151
+ */
152
+ constructor(name, getter=false)
153
+ {
154
+ let msg = "Abstract ";
155
+ msg += (getter ? 'getter ' : 'method ');
156
+ if (name) msg += `'${name}' `;
157
+ msg += 'not implemented';
158
+ super(msg);
159
+ this.name = 'AbstractError';
160
+ }
161
+ }
162
+
163
+ exports.AbstractError = AbstractError;
164
+
106
165
  /**
107
166
  * Function prototypes for async, generator, and async generator functions.
108
167
  * @namespace
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+
3
+ const {B,S,isObj,needObj,isMapOf,isa,isArray} = require('../types');
4
+
5
+ /**
6
+ * Flip an object's keys and values.
7
+ *
8
+ * Is a wrapper around the other `flip*` functions.
9
+ *
10
+ * @alias module:@lumjs/core/obj.flip
11
+ *
12
+ * @param {object} obj - Object to flip.
13
+ *
14
+ * If `obj` is a `Map`, passes it to `flipMap()`.
15
+ * Anything else will be passed to `flipKeyVal()`.
16
+ *
17
+ * Other supported types may be added in the future.
18
+ *
19
+ * @param {object} [opts] Any options supported by the
20
+ * underlying `flip*` functions that may be called.
21
+ *
22
+ * @returns {object} Object with flipped keys and values.
23
+ *
24
+ * @throws {Error} See the underlying functions for exact error types.
25
+ *
26
+ * @see {@link module:@lumjs/core/obj.flipKeyVal}
27
+ * @see {@link module:@lumjs/core/obj.flipMap}
28
+ */
29
+ function flip(obj, opts={})
30
+ {
31
+ if (obj instanceof Map)
32
+ {
33
+ return flipMap(obj, opts);
34
+ }
35
+ else
36
+ {
37
+ return flipKeyVal(obj, opts);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Flip a plain object's keys and values.
43
+ *
44
+ * Works for _enumerable_ properties with _non-duplicate_ `string` values.
45
+ *
46
+ * @param {object} inObj - The input object to flip.
47
+ *
48
+ * @param {object} [opts] Options
49
+ * @param {boolean} [opts.warnDup=true] Use `console` on duplicate values?
50
+ * @param {boolean} [opts.fatalDup=false] Throw Error on duplicate values?
51
+ * @param {boolean} [opts.warnStr=true] Use `console` on non-string values?
52
+ * @param {boolean} [opts.fatalStr=false] Throw Error on non-string values?
53
+ *
54
+ * @returns {object} A new `object` with flipped keys and values.
55
+ *
56
+ * @throws {TypeError} If `obj` was not a valid `object`,
57
+ * or if `fatalStr` is `true` and a non-string value is found.
58
+ *
59
+ * @throws {ReferenceError} If `fatalDup` is `true`
60
+ * and a duplicate value is found.
61
+ *
62
+ */
63
+ function flipKeyVal(inObj, opts={})
64
+ {
65
+ needObj(inObj);
66
+
67
+ const wdup = opts.warnDup ?? true,
68
+ fdup = opts.fatalDup ?? false,
69
+ wstr = opts.warnStr ?? true,
70
+ fstr = opts.fatalStr ?? false;
71
+
72
+ const outObj = {};
73
+
74
+ for (const key in inObj)
75
+ {
76
+ const val = inObj[key];
77
+ if (typeof val === S)
78
+ {
79
+ if (outObj[val] === undefined)
80
+ {
81
+ outObj[val] = key;
82
+ }
83
+ else
84
+ {
85
+ if (wdup) console.error({key, val, obj: inObj});
86
+ if (fdup) throw new ReferenceError("Duplicate value");
87
+ }
88
+ }
89
+ else
90
+ {
91
+ if (wstr) console.error({key, val, obj: inObj});
92
+ if (fstr) throw new TypeError("Non-string value");
93
+ }
94
+ }
95
+
96
+ return outObj;
97
+ }
98
+
99
+ /**
100
+ * Flip the keys and values of a `Map`.
101
+ *
102
+ * @param {Map} inMap - The input Map to flip.
103
+ *
104
+ * @param {object} [opts] Options
105
+ *
106
+ * @param {boolean} [opts.warn=true] Log warnings with `console`?
107
+ * @param {boolean} [opts.fatal=false] Throw Errors for invalid parameters?
108
+ *
109
+ * @param {object} [opts.valid] Validate the `inMap` keys/values?
110
+ * @param {?Array} [opts.valid.keys=null] Valid key type tests.
111
+ * @param {?Array} [opts.valid.values=null] Validate value type tests.
112
+ *
113
+ * @param {(boolean|object)} [opts.valid.map=false] Validate entire map?
114
+ *
115
+ * If this is `true` or an `object` then the entire `inMap` will be tested
116
+ * using `isMapOf()`. If an `object`, it will be used as the `rules`
117
+ * to pass to `isMapOf()` (the `inMap` will be assigned as `rules.value`).
118
+ * If the validation fails when this is `true`, no attempt to flip the
119
+ * map will be done.
120
+ *
121
+ * If this is `false`, but at least one of `.keys` or
122
+ * `.values` is set, then each key and/or value will be tested using
123
+ * the `isa()` function, and any invalid key-val pair will be skipped.
124
+ *
125
+ * If not specified, this will default to the value of the `.fatal` option.
126
+ *
127
+ * @param {boolean} [opts.valid.warn] Use `console` if validation failed?
128
+ *
129
+ * If `.map` is `true`, the console will only have one log entry added
130
+ * with the results of the `isMapOf()` test.
131
+ *
132
+ * If `.map` is `false`, the console will have an entry for every
133
+ * invalid key-val pair in the `inMap`.
134
+ *
135
+ * If not specified, this will default to the value of the `..warn` option.
136
+ *
137
+ * @param {boolean} [opts.valid.fatal] Throw Error on invalid `inMap`?
138
+ *
139
+ * Only applicable if `.map` is `true`.
140
+ * If not specified, this will default to the value of the `..fatal` option.
141
+ *
142
+ * @returns {?Map} A new Map with flipped keys and values.
143
+ *
144
+ * @throws {TypeError} Thrown in specific situations:
145
+ *
146
+ * - If `inMap` is not a valid `Map` and `opts.fatal` is `true`.
147
+ *
148
+ * - If both `opts.valid.map` and `opts.valid.fatal` are `true`,
149
+ * and the validation tests against the `inMap` object fail.
150
+ *
151
+ */
152
+ function flipMap(inMap, opts={})
153
+ {
154
+ const warn = opts.warn ?? true;
155
+ const fatal = opts.fatal ?? false;
156
+
157
+ if (!(inMap instanceof Map))
158
+ {
159
+ const errMsg = "Invalid Map instance";
160
+
161
+ if (warn)
162
+ {
163
+ const log = [{inMap, opts}];
164
+ if (!fatal) log.unshift(errMsg);
165
+ console.error(log);
166
+ }
167
+
168
+ if (fatal)
169
+ {
170
+ throw new TypeError(errMsg);
171
+ }
172
+ else
173
+ {
174
+ return null;
175
+ }
176
+ }
177
+
178
+ const vopts = opts.valid ?? {};
179
+ const vwarn = vopts.warn ?? warn;
180
+ const vfatal = vopts.fatal ?? fatal;
181
+ const vmap = vopts.map ?? vfatal;
182
+
183
+ const vkeys = isArray(vopts.keys) ? vopts.keys : null;
184
+ const vvals = isArray(vopts.values) ? vopts.values : null;
185
+
186
+ function validate(key, val)
187
+ {
188
+ if (vkeys && !isa(key, ...vkeys))
189
+ {
190
+ if (vwarn)
191
+ {
192
+ console.error("Invalid Key", {key, val});
193
+ }
194
+ return false;
195
+ }
196
+
197
+ if (vvals && !isa(val, ...vvals))
198
+ {
199
+ if (vwarn)
200
+ {
201
+ console.error("Invalid Value", {key, val});
202
+ }
203
+ return false;
204
+ }
205
+
206
+ return true;
207
+ }
208
+
209
+ if (vmap)
210
+ { // Validating the entire input Map.
211
+ const rules = isObj(vmap) ? vmap : {};
212
+ const valid = isMapOf(rules, vkeys, vvals);
213
+
214
+ let passed = false;
215
+ if (typeof valid === B)
216
+ {
217
+ passed = valid;
218
+ }
219
+ else if (isObj(valid) && typeof valid.pass === B)
220
+ {
221
+ passed = valid.pass;
222
+ }
223
+
224
+ if (!passed)
225
+ {
226
+ if (vwarn)
227
+ { // Log to the console.
228
+ console.error("Map validation failed", {inMap, opts, valid});
229
+ }
230
+ if (vfatal)
231
+ {
232
+ throw new TypeError("Invalid Map content");
233
+ }
234
+ else
235
+ { // We're done here.
236
+ return null;
237
+ }
238
+ }
239
+ }
240
+
241
+ const outMap = new Map();
242
+
243
+ for (const [key, val] of inMap)
244
+ {
245
+ if (!vmap && !validate(key, val))
246
+ { // Skip invalid key-val pair.
247
+ continue;
248
+ }
249
+ outMap.set(val, key);
250
+ }
251
+
252
+ return outMap;
253
+ }
254
+
255
+ module.exports =
256
+ {
257
+ flip, flipKeyVal, flipMap,
258
+ }
package/lib/obj/index.js CHANGED
@@ -7,11 +7,13 @@ const apply = require('./apply');
7
7
  const {copyAll,duplicateOne,duplicateAll} = require('./copyall');
8
8
  const copyProps = require('./copyprops');
9
9
  const {CLONE,clone,addClone,cloneIfLocked} = require('./clone');
10
+ const {flip, flipKeyVal, flipMap} = require('./flip');
11
+ const {getMethods,signatureOf,MethodFilter} = require('./getmethods');
12
+ const getProperty = require('./getproperty');
10
13
  const {lock,addLock} = require('./lock');
11
14
  const {mergeNested,syncNested} = require('./merge');
12
15
  const ns = require('./ns');
13
- const getProperty = require('./getproperty');
14
- const {getMethods,signatureOf,MethodFilter} = require('./getmethods');
16
+
15
17
  const
16
18
  {
17
19
  getObjectPath,setObjectPath,
@@ -24,5 +26,5 @@ module.exports =
24
26
  mergeNested, syncNested, copyProps, copyAll, ns,
25
27
  getObjectPath, setObjectPath, getNamespace, setNamespace,
26
28
  getProperty, duplicateAll, duplicateOne, getMethods, signatureOf,
27
- MethodFilter, apply,
29
+ MethodFilter, apply, flip, flipKeyVal, flipMap,
28
30
  }
package/lib/objectid.js CHANGED
@@ -1,19 +1,198 @@
1
1
 
2
- const {notNil,def,isNil} = require('./types');
2
+ const {notNil,def,isNil,F,N,S,SY} = require('./types');
3
3
 
4
4
  /**
5
5
  * Generate a large random number.
6
6
  *
7
+ * @param {number} [seed] A base number to use.
8
+ *
9
+ * The default is to use `Date.now()` as the `seed`.
10
+ *
7
11
  * @returns {number}
8
12
  * @alias module:@lumjs/core.randomNumber
9
13
  */
10
- function randomNumber()
14
+ function randomNumber(seed)
11
15
  {
12
- return Math.floor(Math.random() * Date.now());
16
+ if (typeof seed !== N) seed = Date.now();
17
+ return Math.floor(Math.random() * seed);
13
18
  }
14
19
 
15
20
  exports.randomNumber = randomNumber;
16
21
 
22
+ const validBase = base => (typeof base === N && base > 1 && base < 37);
23
+
24
+ class UniqueObjectIds
25
+ {
26
+ constructor(opts={})
27
+ {
28
+ def(this, '$options', {value: opts});
29
+
30
+ if (validBase(opts.random))
31
+ { // Use random ids.
32
+ def(this, '$randIds', {value: {}});
33
+ }
34
+ else if (validBase(opts.timestamp))
35
+ { // Use timestamp-based ids.
36
+ def(this, '$timeIds', {value: {}});
37
+ }
38
+ else
39
+ { // Use incremental ids.
40
+ def(this, '$incIds', {value: {}});
41
+ }
42
+
43
+ const propType = (typeof opts.idProperty);
44
+ const hasProp = (propType === S || propType === SY);
45
+ const useRegistry = opts.useRegistry ?? !hasProp;
46
+
47
+ if (useRegistry)
48
+ { // Using an internal registry.
49
+ def(this, '$registry', {value: new Map()});
50
+ }
51
+ else if (!hasProp)
52
+ { // At least ONE of them MUST be used!
53
+ throw new RangeError("Need one of 'useRegistry' or 'idProperty'");
54
+ }
55
+
56
+ if (hasProp)
57
+ { // Add a direct reference to the id property key.
58
+ def(this, '$idProp', {value: opts.idProperty});
59
+ }
60
+
61
+ }
62
+
63
+ id(obj)
64
+ {
65
+ const hasRegistry = (this.$registry instanceof Map);
66
+ if (hasRegistry && this.$registry.has(obj))
67
+ { // The object was found in the registry.
68
+ return this.$registry.get(obj);
69
+ }
70
+
71
+ const idProp = this.$idProp;
72
+
73
+ if (idProp && obj[idProp])
74
+ { // An existing object id was found in the object's id property.
75
+ return obj[idProp];
76
+ }
77
+
78
+ let cno = this.$options.className ?? {};
79
+ if (typeof cno === F) cno = {setup: cno};
80
+ else if (cno === 'lc') cno = {lowercase: true};
81
+ else if (cno === 'uc') cno = {uppercase: true};
82
+
83
+ let id = '', idNum = null;
84
+
85
+ if (typeof this.$options.prefix === S)
86
+ {
87
+ id += this.$options.prefix;
88
+ }
89
+
90
+ let className = obj.constructor.name;
91
+
92
+ if (typeof cno.setup === F)
93
+ { // Perform a transformation before any other changes.
94
+ className = cno.setup(className);
95
+ }
96
+
97
+ if (cno.lowercase)
98
+ { // Force to lowercase.
99
+ className = className.toLowerCase();
100
+ }
101
+ else if (cno.uppercase)
102
+ { // Force to uppercase.
103
+ className = className.toUpperCase();
104
+ }
105
+
106
+ id += className;
107
+
108
+ if (this.$incIds)
109
+ { // Auto-incrementing ids.
110
+ const ids = this.$incIds;
111
+ if (ids[className])
112
+ { // An existing value was found.
113
+ idNum = (++ids[className]).toString();
114
+ }
115
+ else
116
+ { // No existing values yet.
117
+ const start = this.$options.startAt ?? 1;
118
+ ids[className] = start;
119
+ if (!this.$options.skipFirst)
120
+ {
121
+ idNum = start;
122
+ }
123
+ }
124
+ }
125
+ else
126
+ { // Something other than auto-increment.
127
+ let ids, radix;
128
+ if (this.$randIds)
129
+ { // Using a random id.
130
+ ids = this.$randIds;
131
+ radix = this.$options.random;
132
+ }
133
+ else if (this.$timeIds)
134
+ { // Using timestamp ids.
135
+ ids = this.$timeIds;
136
+ radix = this.$options.timestamp;
137
+ }
138
+ else
139
+ { // Something went horribly wrong.
140
+ throw new Error("No id storage vars found");
141
+ }
142
+
143
+ const getId = () => (this.$randIds
144
+ ? randomNumber()
145
+ : Date.now())
146
+ .toString(radix);
147
+
148
+ if (ids[className])
149
+ { // Existing ids for this className have been set.
150
+ while (!idNum)
151
+ {
152
+ idNum = getId();
153
+ if (ids[className][idNum])
154
+ { // That id has already been used.
155
+ idNum = null;
156
+ }
157
+ else
158
+ { // Hasn't been used yet, yay!
159
+ ids[className][idNum] = true;
160
+ }
161
+ }
162
+ }
163
+ else
164
+ {
165
+ idNum = getId();
166
+ ids[className] = {};
167
+ ids[className][idNum] = true;
168
+ }
169
+ }
170
+
171
+ if (typeof idNum === S)
172
+ {
173
+ if (typeof this.$options.infix === S)
174
+ {
175
+ id += this.$options.infix;
176
+ }
177
+ id += idNum;
178
+ }
179
+
180
+ if (idProp)
181
+ {
182
+ def(obj, idProp, {value: id});
183
+ }
184
+
185
+ if (hasRegistry)
186
+ {
187
+ this.$registry.set(obj, id);
188
+ }
189
+
190
+ return id;
191
+ } // id()
192
+ } // UniqueObjectIds class
193
+
194
+ exports.UniqueObjectIds = UniqueObjectIds;
195
+
17
196
  /**
18
197
  * A class for creating unique identifier objects.
19
198
  * Generally only used by my own inernal libraries, thus the name.
@@ -103,6 +103,27 @@ function isProperty(v)
103
103
  return (t === S || t === SY);
104
104
  }
105
105
 
106
+ /**
107
+ * See if a value is an Iterable object.
108
+ *
109
+ * Objects with `[Symbol.iterator]` can be used in
110
+ * `for (foo of bar)` statements. This tests for that.
111
+ *
112
+ * @param {*} v - Value we're testing
113
+ *
114
+ * While `string` values implement the `Iterable` interface,
115
+ * they are not considered an `object` in this and thus will fail.
116
+ * Only actual `object` values that implement the `[Symbol.iterator]`
117
+ * function will pass the test.
118
+ *
119
+ * @returns {boolean}
120
+ * @alias module:@lumjs/core/types.isIterable
121
+ */
122
+ function isIterable(v)
123
+ {
124
+ return (isObj(v) && typeof v[Symbol.iterator] === F);
125
+ }
126
+
106
127
  /**
107
128
  * See if an object can be used as a valid *descriptor*.
108
129
  *
@@ -192,9 +213,9 @@ function doesDescriptorTemplate(obj, accessor=false, strict=true)
192
213
  return true;
193
214
  }
194
215
 
195
- // Now export those.
196
216
  module.exports =
197
217
  {
198
218
  isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
199
- nonEmptyArray, isArguments, isProperty, doesDescriptor, doesDescriptorTemplate,
219
+ isIterable, nonEmptyArray, isArguments, isProperty,
220
+ doesDescriptor, doesDescriptorTemplate,
200
221
  }
@@ -23,7 +23,7 @@ const {O, F, S, B, N, U, SY, BI} = require('./js');
23
23
  const
24
24
  {
25
25
  isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
26
- nonEmptyArray, isArguments, isProperty, doesDescriptor,
26
+ nonEmptyArray, isArguments, isProperty, doesDescriptor, isIterable,
27
27
  doesDescriptorTemplate,
28
28
  } = require('./basics');
29
29
 
@@ -31,7 +31,11 @@ const
31
31
  const {root, unbound} = require('./root');
32
32
 
33
33
  // Advanced type checks.
34
- const {isInstance, isType, isArrayOf, isa} = require('./isa');
34
+ const
35
+ {
36
+ isInstance, isType, isa,
37
+ isArrayOf, isListOf, isMapOf, isObjOf, OfTest,
38
+ } = require('./isa');
35
39
 
36
40
  // Error-throwing type checks.
37
41
  const {needObj, needType, needs} = require('./needs');
@@ -55,7 +59,8 @@ module.exports =
55
59
  isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
56
60
  nonEmptyArray, isArguments, isProperty, doesDescriptor,
57
61
  isInstance, isType, isa, needObj, needType, needs, stringify,
58
- doesDescriptorTemplate, ownCount, isArrayOf,
62
+ doesDescriptorTemplate, ownCount, isIterable,
63
+ isArrayOf, isListOf, isMapOf, isObjOf, OfTest,
59
64
  console,
60
65
  }
61
66