@lumjs/core 1.21.0 → 1.22.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/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
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.
package/lib/types/isa.js CHANGED
@@ -4,6 +4,7 @@ const TYPES = require('./typelist');
4
4
 
5
5
  /**
6
6
  * See if a value is an instance of a class.
7
+ * @deprecated Just use `instanceof` directly, this function is unnecessary.
7
8
  * @param {*} v - The value we're testing.
8
9
  * @param {function} f - The constructor/class we want.
9
10
  * @param {boolean} [needProto=false] If true, the `v` must have a `prototype`.
@@ -23,7 +24,7 @@ function isInstance(v, f, needProto=false)
23
24
  // Everything passed.
24
25
  return true;
25
26
  }
26
-
27
+
27
28
  exports.isInstance = isInstance;
28
29
 
29
30
  /**
@@ -38,6 +39,7 @@ exports.isInstance = isInstance;
38
39
  * this function does not count `null` as a valid `object`.
39
40
  *
40
41
  * @param {*} v - The value we're testing.
42
+ *
41
43
  * @returns {boolean} If the value was of the desired type.
42
44
  * @alias module:@lumjs/core/types.isType
43
45
  */
@@ -52,12 +54,12 @@ function isType(type, v)
52
54
  { // A type-specific test.
53
55
  return TYPES.tests[type](v);
54
56
  }
55
- else
57
+ else
56
58
  { // No type-specific tests.
57
59
  return (typeof v === type);
58
60
  }
59
61
  }
60
-
62
+
61
63
  exports.isType = isType;
62
64
 
63
65
  // Default options parser.
@@ -127,7 +129,8 @@ function processOptions(type, v)
127
129
  * @param {...any} types - The types the value should be one of.
128
130
  *
129
131
  * For each of the `types`, by default if it is a `string`
130
- * we test with `isType()`, if it is a `function` we test with `isInstance()`.
132
+ * we test with `isType()`, if it is a `function` we see if `v`
133
+ * is either an instance of the type or a sub-class of it.
131
134
  *
132
135
  * If it is an `object` and has an `is()` method, use that as the test.
133
136
  *
@@ -138,9 +141,9 @@ function processOptions(type, v)
138
141
  * - `needProto: boolean`, Change the `needProto` option for `isInstance()`
139
142
  * - `parsers: function`, Add another options parser function.
140
143
  * - `process: function`, A one-time set-up function.
141
- * - `test: function`, Pass the `v` to this test and return `true` if it passes.
142
- * - `typeof: boolean`, If `true` use `typeof` instead of `isType()` for tests.
143
- * - `instanceof: boolean`, If `true` use `instanceof` instead of `isInstance()`.
144
+ * - `test: function`, Pass the `v` to this and return `true` if it passes.
145
+ * - `instanceof: boolean`, If `true` use `instanceof` *instead of* `isInstance()`.
146
+ * As of version `1.22`, this defaults to `true`.
144
147
  * - Anything else will be set as an option that may be used by other parsers.
145
148
  *
146
149
  * Any other type value will only match if `v === type`
@@ -156,8 +159,7 @@ function isa(v, ...types)
156
159
  needProto: false,
157
160
  parsers: [DEFAULT_ISA_PARSER],
158
161
  process: processOptions,
159
- typeof: false,
160
- instanceof: false,
162
+ instanceof: true,
161
163
  }
162
164
 
163
165
  for (const type of types)
@@ -168,13 +170,25 @@ function isa(v, ...types)
168
170
  // With that out of the way, let's go!
169
171
  if (typeof type === S)
170
172
  { // A string is passed to isType()
171
- if (opts.typeof && typeof v === type) return true;
172
173
  if (isType(type, v)) return true;
173
174
  }
174
175
  else if (typeof type === F)
175
176
  { // A function is passed to isInstance()
176
- if (opts.instanceof && v instanceof type) return true;
177
- if (isInstance(v, type, opts.needProto)) return true;
177
+ if (typeof v === F)
178
+ { // See if it is a sub-class.
179
+ if (type.isPrototypeOf(v)) return true;
180
+ }
181
+ else
182
+ { // See if it is an instance.
183
+ if (opts.instanceof)
184
+ { // Simple test, is default now.
185
+ if (v instanceof type) return true;
186
+ }
187
+ else
188
+ { // Old test, will be removed in the future.
189
+ if (isInstance(v, type, opts.needProto)) return true;
190
+ }
191
+ }
178
192
  }
179
193
  else if (isObj(type))
180
194
  { // Objects can be additional tests, or options.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {