@lumjs/core 1.4.0 → 1.5.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.
@@ -6,6 +6,23 @@ See [Changelogs](index.md) for more information on the changelogs.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.5.1] - 2022-09-29
10
+ ### Changed
11
+ - Removed `constructor()` from `AbstractClass`.
12
+ - Added `$needs()` to `AbstractClass`.
13
+ ### Fixed
14
+ - `AbstractClass` actually works properly now.
15
+
16
+ ## [1.5.0] - 2022-09-27
17
+ ### Changed
18
+ - Updated a few DocBlocks.
19
+ - Added a couple explicitly named options to `def()`
20
+ - Rewrote the default module to use `def()` to define its exported properties.
21
+ - Made `arrays`, `strings`, `flags`, `opt`, `modules`, and `observable` use `lazy()`.
22
+ ### Fixed
23
+ - Fixed `def()` so that the descriptor defaults are applied properly.
24
+ - Fixed `lazy()` so it returns a proper value and not a descriptor on first access.
25
+
9
26
  ## [1.4.0] - 2022-09-26
10
27
  ### Changed
11
28
  - Moved `lazy` into `types` module by default (leaving an alias in `core`).
@@ -81,7 +98,9 @@ See [Changelogs](index.md) for more information on the changelogs.
81
98
  - See [1.0-beta.md](1.0-beta.md) for the beta versions of `1.0`
82
99
  - See [lum.js](https://github.com/supernovus/lum.js) for the original library set this is replacing.
83
100
 
84
- [Unreleased]: https://github.com/supernovus/lum.core.js/compare/v1.4.0...HEAD
101
+ [Unreleased]: https://github.com/supernovus/lum.core.js/compare/v1.5.1...HEAD
102
+ [1.5.1]: https://github.com/supernovus/lum.core.js/compare/v1.5.0...v1.5.1
103
+ [1.5.0]: https://github.com/supernovus/lum.core.js/compare/v1.4.0...v1.5.0
85
104
  [1.4.0]: https://github.com/supernovus/lum.core.js/compare/v1.3.1...v1.4.0
86
105
  [1.3.1]: https://github.com/supernovus/lum.core.js/compare/v1.3.0...v1.3.1
87
106
  [1.3.0]: https://github.com/supernovus/lum.core.js/compare/v1.2.1...v1.3.0
package/lib/index.js CHANGED
@@ -4,89 +4,161 @@
4
4
  * This is the foundation upon which all the rest of my JS libraries
5
5
  * are built. It provides fundamental helper functions and classes.
6
6
  *
7
+ * Properties marked as «Lazy» will be loaded on-demand the first time
8
+ * the property is accessed. All other properties are always loaded.
9
+ *
7
10
  * @module @lumjs/core
8
11
  */
9
12
 
10
13
  /**
11
- * Constants and functions for basic type checking.
14
+ * Constants and functions for type checks
15
+ * and other fundamental functions
16
+ *
12
17
  * @alias module:@lumjs/core.types
13
18
  * @see module:@lumjs/core/types
14
19
  */
15
20
  const types = require('./types');
16
21
 
17
22
  /**
18
- * Array utility functions.
19
- * @alias module:@lumjs/core.arrays
20
- * @see module:@lumjs/core/arrays
23
+ * Define properties on an object or function
24
+ * @name module:@lumjs/core.def
25
+ * @function
26
+ * @see module:@lumjs/core/types.def
27
+ */
28
+
29
+ /**
30
+ * Define properties on an object or function
31
+ * @name module:@lumjs/core.lazy
32
+ * @function
33
+ * @see module:@lumjs/core/types.lazy
34
+ */
35
+
36
+ // Get a descriptor for one of our sub-modules.
37
+ function lib(name, def={})
38
+ {
39
+ let value = def.value;
40
+
41
+ if (value === undefined)
42
+ {
43
+ const module = def.module ?? name;
44
+ value = require('./'+module);
45
+
46
+ if (value && def.prop && value[def.prop] !== undefined)
47
+ {
48
+ value = value[def.prop];
49
+ }
50
+ }
51
+
52
+ const desc =
53
+ {
54
+ configurable: false,
55
+ enumerable: true,
56
+ writable: false,
57
+ value,
58
+ }
59
+
60
+ return desc;
61
+ }
62
+
63
+ // Export one of our always loaded properties.
64
+ function has(name, def)
65
+ {
66
+ types.def(exports, name, lib(name, def));
67
+ }
68
+
69
+ // Export one of our lazy loaded properties.
70
+ function can(name, def)
71
+ {
72
+ types.lazy(exports, name, () => lib(name, def), {enumerable: true});
73
+ }
74
+
75
+ // Export a set of always loaded properties from a sub-module
76
+ function from(modname, ...libs)
77
+ {
78
+ for (const lib of libs)
79
+ {
80
+ has(lib, {module: modname, prop: lib});
81
+ }
82
+ }
83
+
84
+ // Our fundamental bits.
85
+ has('types', {value: types});
86
+ has('def', {value: types.def});
87
+ has('lazy', {value: types.lazy});
88
+
89
+ /**
90
+ * Array utility functions «Lazy»
91
+ * @name module:@lumjs/core.arrays
92
+ * @type {module:@lumjs/core/arrays}
21
93
  */
22
- const arrays = require('./arrays');
94
+ can('arrays');
23
95
 
24
96
  /**
25
- * Information about the JS context we're running in.
26
- * @alias module:@lumjs/core.context
27
- * @see module:@lumjs/core/context
97
+ * Information about the JS context we're running in
98
+ * @name module:@lumjs/core.context
99
+ * @type {module:@lumjs/core/context}
28
100
  */
29
- const context = require('./context');
101
+ has('context');
30
102
 
31
103
  /**
32
- * Functions for working with strings and locales.
33
- * @alias module:@lumjs/core.strings
34
- * @see module:@lumjs/core/strings
104
+ * Functions for working with strings and locales «Lazy»
105
+ * @name module:@lumjs/core.strings
106
+ * @type {module:@lumjs/core/strings}
35
107
  */
36
- const strings = require('./strings');
108
+ can('strings');
37
109
 
38
110
  /**
39
- * Functions for working with binary flags.
40
- * @alias module:@lumjs/core.flags
41
- * @see module:@lumjs/core/flags
111
+ * Functions for working with binary flags «Lazy»
112
+ * @name module:@lumjs/core.flags
113
+ * @type {module:@lumjs/core/flags}
42
114
  */
43
- const flags = require('./flags');
115
+ can('flags');
44
116
 
45
117
  /**
46
- * Functions for manipulating objects.
47
- * @alis module:@lumjs/core.obj
48
- * @see module:@lumjs/core/obj
118
+ * Functions for manipulating objects
119
+ * @name module:@lumjs/core.obj
120
+ * @type {module:@lumjs/core/obj}
49
121
  */
50
- const obj = require('./obj');
122
+ has('obj');
51
123
 
52
124
  /**
53
- * Functions for getting values and properties with fallback defaults.
54
- * @alias module:@lumjs/core.opt
55
- * @see module:@lumjs/core/opt
125
+ * Functions for getting values and properties with fallback defaults «Lazy»
126
+ * @name module:@lumjs/core.opt
127
+ * @type {module:@lumjs/core/opt}
56
128
  */
57
- const opt = require('./opt');
129
+ can('opt');
58
130
 
59
131
  /**
60
- * Meta functions related to JS modules.
132
+ * Meta functions related to JS modules «Lazy»
61
133
  * @alias module:@lumjs/core.modules
62
134
  * @see module:@lumjs/core/modules
63
135
  */
64
- const modules = require('./modules');
136
+ can('modules');
65
137
 
66
138
  // ObjectID stuff is imported directly without registering a sub-module.
67
- const {randomNumber, InternalObjectId} = require('./objectid');
139
+ from('objectid', 'randomNumber', 'InternalObjectId');
68
140
 
69
141
  /**
70
- * Get a simplistic debugging stacktrace.
142
+ * Get a simplistic debugging stacktrace
71
143
  * @name module:@lumjs/core.stacktrace
72
144
  * @function
73
145
  * @see module:@lumjs/core/meta.stacktrace
74
146
  */
75
147
 
76
148
  /**
77
- * A simple base class for making *abstract* classes.
149
+ * A simple base class for making *abstract* classes
78
150
  * @name module:@lumjs/core.AbstractClass
79
151
  * @see module:@lumjs/core/meta.AbstractClass
80
152
  */
81
153
 
82
154
  /**
83
- * A *factory* for special types of JS `function` constructors.
155
+ * A *factory* for special types of JS `function` constructors
84
156
  * @name module:@lumjs/core.Functions
85
157
  * @see module:@lumjs/core/meta.Functions
86
158
  */
87
159
 
88
160
  /**
89
- * Throw an `Error` saying that a feature is not yet implemented.
161
+ * Throw an `Error` saying that a feature is not yet implemented
90
162
  * @name module:@lumjs/core.NYI
91
163
  * @function
92
164
  * @see module:@lumjs/core/meta.NYI
@@ -94,43 +166,20 @@ const {randomNumber, InternalObjectId} = require('./objectid');
94
166
 
95
167
  // These are exported directly, but a meta sub-module also exists.
96
168
  // Unlike most sub-modules there is no `meta` property in the main library.
97
- const {stacktrace,AbstractClass,Functions,NYI} = require('./meta');
169
+ from('meta', 'stacktrace', 'AbstractClass', 'Functions', 'NYI');
98
170
 
99
171
  /**
100
- * Create a magic *Enum* object.
101
- * @alias module:@lumjs/core.Enum
172
+ * Create a magic *Enum* object
173
+ * @name module:@lumjs/core.Enum
102
174
  * @function
103
175
  * @see module:@lumjs/core/enum
104
176
  */
105
- const Enum = require('./enum');
177
+ has('Enum', {module: 'enum'});
106
178
 
107
179
  /**
108
- * Make an object support the *Observable* API.
109
- * @alias module:@lumjs/core.observable
180
+ * Make an object support the *Observable* API «Lazy»
181
+ * @name module:@lumjs/core.observable
110
182
  * @function
111
183
  * @see module:@lumjs/core/observable
112
184
  */
113
- const observable = require('./observable');
114
-
115
- /**
116
- * Define properties on an object or function.
117
- * @alias module:@lumjs/core.def
118
- * @function
119
- * @see module:@lumjs/core/types.def
120
- */
121
- const def = types.def;
122
-
123
- /**
124
- * Define properties on an object or function.
125
- * @alias module:@lumjs/core.lazy
126
- * @function
127
- * @see module:@lumjs/core/types.lazy
128
- */
129
- const lazy = types.lazy;
130
-
131
- module.exports =
132
- {
133
- types, context, flags, obj, opt, modules, arrays, strings,
134
- def, randomNumber, InternalObjectId, Enum, lazy, observable,
135
- stacktrace, AbstractClass, Functions, NYI,
136
- }
185
+ can('observable');
package/lib/meta.js CHANGED
@@ -3,6 +3,8 @@
3
3
  * @module @lumjs/core/meta
4
4
  */
5
5
 
6
+ const {F,S,isArray,isa} = require('./types');
7
+
6
8
  /**
7
9
  * Get a stacktrace. Differs from browser to browser.
8
10
  *
@@ -30,20 +32,71 @@ exports.stacktrace = stacktrace;
30
32
  class AbstractClass
31
33
  {
32
34
  /**
33
- * You must override the constructor.
35
+ * If you want to mark a method as abstract use this.
34
36
  */
35
- constructor()
37
+ $abstract(name)
36
38
  {
37
- const name = this.constructor.name;
38
- throw new Error(`Cannot create instance of abstract class ${name}`);
39
+ if (name.indexOf('(') === -1)
40
+ { // Add empty method signature.
41
+ name += '()';
42
+ }
43
+ throw new Error(`Abstract method ${name} was not implemented`);
39
44
  }
40
45
 
41
46
  /**
42
- * If you want to mark a method as abstract use this.
47
+ * Check for required properties
48
+ *
49
+ * @param {...(string|Array)} needs - What is needed
50
+ *
51
+ * If this is a `string` it should be in a format like:
52
+ *
53
+ * - `methodName(arg1,arg2,arg3)`
54
+ * - `anotherMethod(number, string, object) : boolean`
55
+ * - `yetAnother (className) : resultClass`
56
+ *
57
+ * The names are case sensitive, and we'll look for the method after
58
+ * stripping off anything from the first *non-word* character.
59
+ *
60
+ * If this is an `Array`, the first item must be the name of a property,
61
+ * and each other item should be a type checking value, or array of type
62
+ * checking values from the [TYPES]{@link module:@lumjs/core/types.TYPES}
63
+ * object, as used by [isa()]{@link module:@lumjs/core/types.isa}.
64
+ *
65
+ * If you are calling this in an abstract class constructor, likely only
66
+ * the method checks will be useful, as the `super()` call must be done
67
+ * *before* any instance property assignments.
68
+ *
43
69
  */
44
- $abstract(name)
70
+ $needs(...needs)
45
71
  {
46
- throw new Error(`Abstract method ${name}() was not implemented`);
72
+ const className = this.constructor.name;
73
+
74
+ const getName = fullName => fullName.replace(/\W.*/, '');
75
+ const missing = propName =>
76
+ {
77
+ throw new Error(`${className} is missing ${propName}`);
78
+ }
79
+
80
+ for (const need of needs)
81
+ {
82
+ if (typeof need === S)
83
+ { // A simple method
84
+ const meth = getName(need);
85
+ if (typeof this[meth] !== F)
86
+ {
87
+ missing(need);
88
+ }
89
+ }
90
+ else if (isArray(need))
91
+ {
92
+ const prop = getName(need[0]);
93
+ const types = need.slice(1);
94
+ if (!isa(this[prop], ...types))
95
+ {
96
+ missing(need);
97
+ }
98
+ }
99
+ }
47
100
  }
48
101
 
49
102
  }
package/lib/types/def.js CHANGED
@@ -14,60 +14,83 @@ const clone = obj => copy({}, obj);
14
14
  * It replaces the `prop()` method from the old Lum.js v4.
15
15
  *
16
16
  * @param {(object|function)} obj - The object to add a property to.
17
- * @param {?(string|boolean|object)} name - If a `string`, the property name.
18
- * Property names may also be `symbol` values.
17
+ * @param {?(string|symbol|boolean|object)} name
18
+ * If a `string` or `symbol`, it's the property name.
19
19
  *
20
- * If this is `null` or `undefined` then the `value` is ignored entirely,
21
- * and instead a bound version of this function is created with the
22
- * `obj` already passed as the first parameter, and any of the options from
23
- * `opts` added to a new default set of options bound to the function.
24
- * Can be useful if you need to add a lot of properties to the same object.
20
+ * If this is `null` or `undefined` then the `value` is ignored entirely,
21
+ * and instead a bound version of this function is created with the
22
+ * `obj` already passed as the first parameter, and any of the options from
23
+ * `opts` added to a new default set of options bound to the function.
24
+ * Can be useful if you need to add a lot of properties to the same object.
25
25
  *
26
- * If this is a `boolean`, then the same logic as if it was `null` or
27
- * `undefined` will apply, except that an `enumerable` property with this
28
- * value will also be added to the descriptors.
26
+ * If this is a `boolean`, then the same logic as if it was `null` or
27
+ * `undefined` will apply, except that an `enumerable` property with this
28
+ * value will also be added to the descriptors.
29
29
  *
30
- * If this is an `object`, we also ignore `value` entirely, as each of
31
- * the keys of this object will be used as the name of a property,
32
- * and the value associated with the key will be the value to assign it.
30
+ * If this is an `object`, we also ignore `value` entirely, as each of
31
+ * the keys of this object will be used as the name of a property,
32
+ * and the value associated with the key will be the value to assign it.
33
33
  *
34
34
  * @param {*} value - Used to determine the value of the property.
35
35
  *
36
- * If it is an a valid descriptor object (as per `doesDescriptor()`),
37
- * it will be used as the descriptor. If it has no `configurable`
38
- * property defined, one will be added, and will be set to `true`.
39
- * This behaviour may be overridden by the `opts` parameter.
36
+ * If it is an a valid descriptor object (as per `doesDescriptor()`),
37
+ * it will be used as the descriptor. If it has no `configurable`
38
+ * property defined, one will be added, and will be set to `true`.
39
+ * This behaviour may be overridden by the `opts` parameter.
40
40
  *
41
- * If this and `opts` are both `function` values, then this will
42
- * be used as a *getter*, and `opts` will be used a *setter*.
41
+ * If this and `opts` are both `function` values, then this will
42
+ * be used as a *getter*, and `opts` will be used a *setter*.
43
43
  *
44
- * Any other value passed here will be used as the `value` in a
45
- * pre-canned descriptor, also with `configurable` set to `true`.
44
+ * Any other value passed here will be used as the `value` in a
45
+ * pre-canned descriptor, also with `configurable` set to `true`.
46
46
  *
47
47
  * @param {*} [opts] - A multi-purpose option.
48
48
  *
49
- * If this is an `object` then it is reserved for named options.
50
- * The named options `configurable`, `enumerable`, and `writable`
51
- * can be used to define default values to the descriptor properties
52
- * of the same name.
49
+ * If this is an `object` then it is reserved for named options.
53
50
  *
54
- * If `value` and this are both `function` values, then `value` will
55
- * be used as a *getter* and this will be used as a *setter*.
51
+ * The named options `configurable`, `enumerable`, and `writable`
52
+ * can be used to define default values to the descriptor properties
53
+ * of the same name.
56
54
  *
57
- * If `value` is a valid descriptor object, then setting this to `false`
58
- * will disable the assumption that it is the descriptor to set.
59
- * Setting this to `true` on will instruct the function to make a clone
60
- * of the descriptor object before modifying any properties in it.
55
+ * If `value` and this are both `function` values, then `value` will
56
+ * be used as a *getter* and this will be used as a *setter*.
61
57
  *
62
- * This defaults to `undefined`, except if
58
+ * If this is `true`, it sets `opts.cloneDesc` to `true`.
59
+ *
60
+ * If this is `false`, it sets `opts.autoDesc` to `false`.
61
+ *
62
+ * This defaults to `undefined`, except in bound functions.
63
+ *
64
+ * @param {boolean} [opts.autoDesc=true] Automatically detect descriptors?
65
+ *
66
+ * If `true` (the default value) then the automatic descriptor detection
67
+ * is used any time the `value` is an `object`.
68
+ *
69
+ * If `false` the descriptor detection is disabled, and we'll always
70
+ * wrap all values in a new internal descriptor.
71
+ *
72
+ * @param {boolean} [opts.cloneDesc=false] Clone descriptors?
73
+ *
74
+ * If `true` then we'll clone detected descriptors before making
75
+ * any changes to them.
76
+ *
77
+ * If `false` (the default value), we'll use passed descriptors
78
+ * directly, so any changes will be made on the original.
79
+ *
80
+ * @param {boolean} [opts.configurable=true] Default `configurable` value
81
+ * @param {boolean} [opts.enumerable=false] Default `enumerable` value
82
+ * @param {boolean} [opts.writable=false] Default `writable` value
83
+ *
84
+ * Only used with *data descriptors* and will be ignored with
85
+ * *accessor* descriptors.
63
86
  *
64
87
  * @returns {*} Normally the `obj` argument with new property added.
65
88
  *
66
- * The exception is if this is a bound copy of the function created
67
- * using the syntax described in the `name` parameter documentation.
68
- * In that case the return value is the bound copy itself.
69
- * While that might seem strange, it allows for chaining in a way
70
- * that otherwise would not be possible, take this example:
89
+ * The exception is if this is a bound copy of the function created
90
+ * using the syntax described in the `name` parameter documentation.
91
+ * In that case the return value is the bound copy itself.
92
+ * While that might seem strange, it allows for chaining in a way
93
+ * that otherwise would not be possible, take this example:
71
94
  *
72
95
  * ```
73
96
  * def(myObj)('name', "Bob")('age', 42);
@@ -136,11 +159,31 @@ function def(obj, name, value, opts)
136
159
  throw new TypeError("Property name must be a string or a Symbol");
137
160
  }
138
161
 
162
+ function getOpt(name, defVal)
163
+ {
164
+ const revVal = !defVal;
165
+ if (isObj(opts) && typeof opts[name] === B)
166
+ { // Found the option.
167
+ return opts[name];
168
+ }
169
+ else if (opts === revVal)
170
+ { // A special default rule was found.
171
+ return revVal;
172
+ }
173
+ else
174
+ { // Return the default.
175
+ return defVal;
176
+ }
177
+ }
178
+
179
+ const autoDesc = getOpt('autoDesc', true);
180
+ const cloneDesc = getOpt('cloneDesc', false);
181
+
139
182
  let desc;
140
183
 
141
- if (opts !== false && doesDescriptor(value))
184
+ if (autoDesc && doesDescriptor(value))
142
185
  { // The value is a descriptor, let's use it.
143
- desc = (opts === true) ? clone(value) : value;
186
+ desc = cloneDesc ? clone(value) : value;
144
187
  }
145
188
  else if (typeof value === F && typeof opts === F)
146
189
  { // A getter and setter were both specified.
@@ -151,27 +194,28 @@ function def(obj, name, value, opts)
151
194
  desc = {value};
152
195
  }
153
196
 
154
- if (isObj(opts))
155
- { // If opts is an object, let's look for some supported defaults.
156
- const cd = (opt, defval) =>
157
- {
158
- if (typeof opts[opt] === B && typeof desc[opt] !== B)
159
- { // Default descriptor option specified in `opts`
160
- desc[opt] = opts[opt];
161
- }
162
- else if (typeof defval === B)
163
- { // A fallback default.
164
- desc[opt] = defval;
165
- }
197
+ const cd = (opt, defval) =>
198
+ {
199
+ if (typeof desc[opt] === B)
200
+ { // Property was set explicitly.
201
+ return;
166
202
  }
167
-
168
- cd('configurable', true);
169
- cd('enumerable');
170
- if (desc.get === undefined && desc.set === undefined)
171
- { // Only look for this one on data descriptors.
172
- cd('writable');
203
+ else if (isObj(opts) && typeof opts[opt] === B)
204
+ { // Default descriptor option specified in `opts`
205
+ desc[opt] = opts[opt];
206
+ }
207
+ else if (typeof defval === B)
208
+ { // A fallback default.
209
+ desc[opt] = defval;
173
210
  }
174
- } // if isObj(opts)
211
+ }
212
+
213
+ cd('configurable', true);
214
+ cd('enumerable');
215
+ if (desc.get === undefined && desc.set === undefined)
216
+ { // Only look for this one on data descriptors.
217
+ cd('writable');
218
+ }
175
219
 
176
220
  // Now after all that, let's actually define the property!
177
221
  Object.defineProperty(obj, name, desc);
package/lib/types/lazy.js CHANGED
@@ -2,6 +2,7 @@ const def = require('./def');
2
2
  const {S,F} = require('./js');
3
3
  const {COMPLEX} = require('./typelist');
4
4
  const {needType,needObj} = require('./needs');
5
+ const {doesDescriptor} = require('./basics');
5
6
 
6
7
  /**
7
8
  * Metadata for the property definition.
@@ -169,8 +170,24 @@ function lazy(target, name, initfunc, opts={})
169
170
  if (assign)
170
171
  { // Replace the lazy accessor with the returned value.
171
172
  def(target, name, value, context.def);
173
+ // Now return the newly assigned value.
174
+ return target[name];
175
+ }
176
+ else if (doesDescriptor(value))
177
+ { // A descriptor was returned, extract the real value.
178
+ if (typeof value.get === F)
179
+ {
180
+ return value.get();
181
+ }
182
+ else
183
+ {
184
+ return value.value;
185
+ }
186
+ }
187
+ else
188
+ { // Just return the original value.
189
+ return value;
172
190
  }
173
- return value;
174
191
  }
175
192
 
176
193
  desc.get = function()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {