@lumjs/core 1.0.0-beta.2 → 1.0.0-beta.4

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,180 @@
1
+ // Thanks to CJS `require()` rules, recursive dependencies are possible.
2
+ const unbound = require('./root').unbound;
3
+ const {F, B} = require('./js');
4
+ const {isObj, isNil, isProperty, doesDescriptor} = require('./basics');
5
+ const copy = require('../obj/copyall');
6
+
7
+ /**
8
+ * A wrapper around `Object.defineProperty()`.
9
+ *
10
+ * This has a few features that makes adding properties a lot nicer.
11
+ * It replaces the `prop()` method from the old Lum.js v4.
12
+ *
13
+ * @param {(object|function)} obj - The object to add a property to.
14
+ * @param {?(string|boolean|object)} name - If a `string`, the property name.
15
+ *
16
+ * If this is `null` or `undefined` then the `value` is ignored entirely,
17
+ * and instead a bound version of this function is created with the
18
+ * `obj` already passed as the first parameter. Can be useful if you
19
+ * need to add a lot of properties to the same object.
20
+ *
21
+ * If this is a `boolean`, then the same logic as if it was `null` or
22
+ * `undefined` will apply, except that an `enumerable` property with this
23
+ * value will also be added to the descriptors.
24
+ *
25
+ * @param {*} value - Used to determine the value of the property.
26
+ *
27
+ * If it is an a valid descriptor object (as per `doesDescriptor()`),
28
+ * it will be used as the descriptor. If it has no `configurable`
29
+ * property defined, one will be added, and will be set to `true`.
30
+ * This behaviour may be overridden by the `opts` parameter.
31
+ *
32
+ * If this is a `function` and `opts` is a `boolean` or `function`,
33
+ * then this will be used as a getter or setter (see `opts` for details.)
34
+ *
35
+ * Any other value passed here will be used as the `value` in a
36
+ * pre-canned descriptor, also with `configurable` set to `true`.
37
+ *
38
+ * @param {(object|function|boolean)} [opts] - A multi-purpose option.
39
+ *
40
+ * If this is an `object` then it is reserved for named options.
41
+ * Any other use of this is dependent on the `value` parameter.
42
+ *
43
+ * If `value` and this are both `function` values, then `value` will
44
+ * be used as a *getter* and this will be used as a *setter*.
45
+ *
46
+ * If `value` is a `function` and this is `true`, then `value` will
47
+ * be used as a *getter* and no *setter* will be assigned.
48
+ *
49
+ * If `value` is a `function` and this is `false`, then `value` will
50
+ * be used as a *setter* and no *getter* will be assigned.
51
+ *
52
+ * If `value` is a valid descriptor object, then setting this to `false`
53
+ * will disable the assumption that it is the descriptor to set.
54
+ * Setting this to `true` on will instruct the function to make a clone
55
+ * of the descriptor object before modifying any properties in it.
56
+ *
57
+ * @returns {*} Normally the `obj` argument with new property added.
58
+ *
59
+ * The exception is if this is a bound copy of the function created
60
+ * using the syntax described in the `name` parameter documentation.
61
+ * In that case the return value is the bound copy itself.
62
+ * While that might seem strange, it allows for chaining in a way
63
+ * that otherwise would not be possible, take this example:
64
+ *
65
+ * ```
66
+ * def(myObj)('name', "Bob")('age', 42);
67
+ * ```
68
+ *
69
+ */
70
+ function def(obj, name, value, opts)
71
+ {
72
+ const isBound // Is this a 'bound' def function?
73
+ = !unbound(this, true, true)
74
+ && typeof this.bound === F;
75
+
76
+ // When we're finished, return this value.
77
+ const done = () =>
78
+ {
79
+ if (isBound)
80
+ { // Bound version, returns itself recursively.
81
+ return this.bound;
82
+ }
83
+ else
84
+ { // Not bound, or doesn't have a reference to itself.
85
+ return obj;
86
+ }
87
+ }
88
+
89
+ if (isBound && isNil(opts))
90
+ { // We'll use `this` as the options.
91
+ opts = this;
92
+ }
93
+
94
+ if (isObj(name))
95
+ { // Assume it's a map of {name: value} entries.
96
+ for (const key in name)
97
+ {
98
+ def(obj, key, name[key], opts);
99
+ }
100
+ // Okay, we're done now.
101
+ return done();
102
+ }
103
+ else if (isNil(name) || typeof name === B)
104
+ { // Create a fresh binding context for the bound function.
105
+ const bind = {};
106
+
107
+ if (isObj(opts))
108
+ { // Copy our existing options as defaults.
109
+ copy(bind, opts);
110
+ }
111
+
112
+ if (typeof name === B)
113
+ { // A boolean `name` overrides the enumerable option.
114
+ bind.enumerable = name;
115
+ }
116
+
117
+ // Create a bound function.
118
+ const bound = def.bind(bind, obj);
119
+ // Add a reference to the function in the binding context.
120
+ bind.bound = bound;
121
+ // And a reference to the binding options from the function.
122
+ bound.$this = bind;
123
+
124
+ return bound;
125
+ }
126
+ else if (!isProperty(name))
127
+ { // That's not valid.
128
+ throw new TypeError("Property name must be a string or a Symbol");
129
+ }
130
+
131
+ let desc;
132
+
133
+ if (opts !== false && doesDescriptor(value))
134
+ { // The value is a descriptor, let's use it.
135
+ desc = (opts === true) ? copy(value) : value;
136
+ }
137
+ else if (typeof value === F && typeof opts === F)
138
+ { // A getter and setter were both specified.
139
+ desc = {get: value, set: opts};
140
+ }
141
+ else if (typeof value === F && typeof opts === B)
142
+ { // A function value with a boolean opts is a getter or setter.
143
+ const prop = opts ? 'get' : 'set';
144
+ desc = {[prop]: value};
145
+ }
146
+ else
147
+ { // The value is just a value, so let's assign it.
148
+ desc = {value};
149
+ }
150
+
151
+ if (isObj(opts))
152
+ { // If opts is an object, let's look for some supported defaults.
153
+ const cd = (opt, defval) =>
154
+ {
155
+ if (typeof opts[opt] === B && typeof desc[opt] !== B)
156
+ { // Default descriptor option specified in `opts`
157
+ desc[opt] = opts[opt];
158
+ }
159
+ else if (typeof defval === B)
160
+ { // A fallback default.
161
+ desc[opt] = defval;
162
+ }
163
+ }
164
+
165
+ cd('configurable', true);
166
+ cd('enumerable');
167
+ if (desc.get === undefined && desc.set === undefined)
168
+ { // Only look for this one on data descriptors.
169
+ cd('writable');
170
+ }
171
+ } // if isObj(opts)
172
+
173
+ // Now after all that, let's actually define the property!
174
+ Object.defineProperty(obj, name, desc);
175
+
176
+ return done();
177
+
178
+ } // def()
179
+
180
+ module.exports = def;
@@ -0,0 +1,38 @@
1
+ /// Type checking and other features common to everything else.
2
+
3
+ // Constants representing core Javascript types.
4
+ const {O, F, S, B, N, U, SY, BI} = require('./js');
5
+
6
+ // Basic type check functions.
7
+ const
8
+ {
9
+ isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
10
+ nonEmptyArray, isArguments, isProperty, doesDescriptor,
11
+ } = require('./basics');
12
+
13
+ // Root namespace helpers.
14
+ const {root, unbound} = require('./root');
15
+
16
+ // Advanced type checks.
17
+ const {isInstance, isType, isa} = require('./isa');
18
+
19
+ // Error-throwing type checks.
20
+ const {needObj, needType, needs} = require('./needs');
21
+
22
+ // A few standalone items.
23
+
24
+ const def = require('./def');
25
+ const TYPES = require('./typelist');
26
+
27
+ // Okay, add all those to our exports.
28
+ // Further tests can be added by `TYPES.add()` later.
29
+ module.exports =
30
+ {
31
+ O, F, S, B, N, U, SY, BI, TYPES, root, unbound, def,
32
+ isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
33
+ nonEmptyArray, isArguments, isProperty, doesDescriptor,
34
+ isInstance, isType, isa, needObj, needType, needs,
35
+ }
36
+
37
+ // Last but not least, this will be the module for TYPES.add()
38
+ def(TYPES, '$module', module);
@@ -0,0 +1,193 @@
1
+ const {O, F, S} = require('./js');
2
+ const {isObj} = require('./basics');
3
+ const TYPES = require('./typelist');
4
+
5
+ /**
6
+ * See if a value is an instance of a class.
7
+ * @param {*} v - The value we're testing.
8
+ * @param {function} f - The constructor/class we want.
9
+ * @param {boolean} [needProto=false] If true, the `v` must have a `prototype`.
10
+ * @returns {boolean}
11
+ */
12
+ function isInstance(v, f, needProto=false)
13
+ {
14
+ if (!isObj(v)) return false; // Not an object.
15
+ if (needProto && (typeof v.prototype !== O || v.prototype === null))
16
+ { // Has no prototype.
17
+ return false;
18
+ }
19
+
20
+ if (typeof f !== F || !(v instanceof f)) return false;
21
+
22
+ // Everything passed.
23
+ return true;
24
+ }
25
+
26
+ exports.isInstance = isInstance;
27
+
28
+ /**
29
+ * A smarter `typeof` function.
30
+ *
31
+ * @param {string} type - The type we're checking for.
32
+ *
33
+ * This supports all the same type names as `typeof` plus:
34
+ *
35
+ * - `arguments` → An arguments object inside a function.
36
+ * - `array` → An Array object.
37
+ * - `null` → A null value.
38
+ * - `typedarray` → One of the typed array objects.
39
+ * - `descriptor` → An object which is a valid descriptor.
40
+ * - `complex` → Either an `object` or a `function.
41
+ * - `scalar` → Anything other than an `object` or `function`.
42
+ * - `property` → A `string` or a `symbol`.
43
+ * - `nil` → Either `null` or `undefined`.
44
+ *
45
+ * One other thing, while `typeof` reports `null` as being an `object`,
46
+ * this function does not count `null` as a valid `object`.
47
+ *
48
+ * @param {*} v - The value we're testing.
49
+ *
50
+ * @returns {boolean} If the value was of the desired type.
51
+ */
52
+ function isType(type, v)
53
+ {
54
+ if (typeof type !== S || !TYPES.list.includes(type))
55
+ {
56
+ throw new TypeError(`Invalid type ${JSON.stringify(type)} specified`);
57
+ }
58
+
59
+ if (typeof TYPES.tests[type] === F)
60
+ { // A type-specific test.
61
+ return TYPES.tests[type](v);
62
+ }
63
+ else
64
+ { // No type-specific tests.
65
+ return (typeof v === type);
66
+ }
67
+ }
68
+
69
+ exports.isType = isType;
70
+
71
+ // Default options parser.
72
+ const DEFAULT_ISA_PARSER = function(type, v)
73
+ { // `this` is the options object itself.
74
+ if (typeof type.is === F)
75
+ { // We assume `is()` methods are the type check.
76
+ if (type.is(v)) return true;
77
+ }
78
+
79
+ if (typeof type.isa === O)
80
+ { // Process our known options.
81
+ const opts = type.isa;
82
+
83
+ if (typeof opts.process === F)
84
+ { // We'll pass the current `opts` as well as the `v` to this method.
85
+ // It doesn't return anything on its own, but can assign further tests,
86
+ // which by default only apply to `object`
87
+ opts.process(this, v);
88
+ }
89
+
90
+ if (typeof opts.parsers === F)
91
+ { // Assign another parser.
92
+ this.parsers.push(opts.parsers);
93
+ }
94
+
95
+ if (typeof opts.test === F)
96
+ { // A custom test, will be passed `v` and the current `opts`.
97
+ if (opts.test(v, this))
98
+ { // Mark this as having passed.
99
+ return true;
100
+ }
101
+ }
102
+
103
+ // Okay, now anything else gets set.
104
+ const RESERVED = ['parsers','process','test'];
105
+ for (const opt in opts)
106
+ {
107
+ if (RESERVED.includes(opt)) continue; // Skip it.
108
+ this[opt] = opts[opt];
109
+ }
110
+
111
+ }
112
+
113
+ // We should almost always return false.
114
+ return false;
115
+ }
116
+
117
+ /**
118
+ * Is value a certain type, or an instance of a certain class.
119
+ *
120
+ * @param {*} v - The value we're testing.
121
+ * @param {...any} types - The types the value should be one of.
122
+ *
123
+ * For each of the `types`, if it is a `string` we test with `isType()`,
124
+ * if it is a `function` we test with `isInstance()`.
125
+ *
126
+ * If it is an `object` and has an `is()` method, use that as the test.
127
+ *
128
+ * If it is an `object` it will be passed to the current options parsers.
129
+ * The default options parser looks for an `object` property called `isa`,
130
+ * which supports the following child properties:
131
+ *
132
+ * - `needProto: boolean`, Change the `needProto` option for `isInstance()`
133
+ * - `parsers: function`, Add another options parser function.
134
+ * - `process: function`, A one-time set-up function.
135
+ * - `test: function`, Pass the `v` to this test and return `true` if it passes.
136
+ * - Anything else will be set as an option.
137
+ *
138
+ * Any other type value will only match if `v === type`
139
+ *
140
+ * @returns {boolean} Was the value one of the desired types?
141
+ */
142
+ function isa(v, ...types)
143
+ {
144
+ // A special options object.
145
+ const opts =
146
+ {
147
+ needProto: false,
148
+ parsers:
149
+ [
150
+ DEFAULT_ISA_PARSER,
151
+ ],
152
+ process(type, v)
153
+ {
154
+ for (const parser of this.parsers)
155
+ {
156
+ if (typeof parser === F)
157
+ {
158
+ if (parser.call(this, type, v) === true)
159
+ { // Returning true means a custom test passed.
160
+ return true;
161
+ }
162
+ }
163
+ }
164
+ return false;
165
+ },
166
+ }
167
+
168
+ for (const type of types)
169
+ {
170
+ // First a quick test for absolute equality.
171
+ if (v === type) return true;
172
+
173
+ // With that out of the way, let's go!
174
+ if (typeof type === S)
175
+ { // A string is passed to isType()
176
+ if (isType(type, v)) return true;
177
+ }
178
+ else if (typeof type === F)
179
+ { // A function is passed to isInstance()
180
+ if (isInstance(v, type, opts.needProto)) return true;
181
+ }
182
+ else if (isObj(type))
183
+ { // Objects can be additional tests, or options.
184
+ if (opts.process(type, v)) return true;
185
+ }
186
+
187
+ }
188
+
189
+ // None of the tests passed. We have failed.
190
+ return false;
191
+ }
192
+
193
+ exports.isa = isa;
@@ -0,0 +1,12 @@
1
+ // Fundamental core types.
2
+ const O='object', F='function', S='string', B='boolean', N='number',
3
+ U='undefined', SY='symbol', BI='bigint';
4
+
5
+ /**
6
+ * One or two character identifiers for the core JS type names.
7
+ *
8
+ * These are only the strings returned by the `typeof` operator.
9
+ * See the `TYPES` object (defined in `typelist.js`) for a list
10
+ * that includes special types and compound pseudo-types, etc.
11
+ */
12
+ module.exports = {O, F, S, B, N, U, SY, BI};
@@ -0,0 +1,112 @@
1
+ const {F, S, B} = require('./js');
2
+ const {isType, isa} = require('./isa');
3
+ const {isObj, isComplex} = require('./basics');
4
+
5
+ /**
6
+ * If a value is not an object, throw an error.
7
+ *
8
+ * @param {*} v - The value we're testing.
9
+ * @param {boolean} [allowFunc=false] - Also accept functions?
10
+ * @param {string} [msg] A custom error message.
11
+ * @throws {TypeError} If the type check failed.
12
+ */
13
+ function needObj (v, allowFunc=false, msg=null)
14
+ {
15
+ if (allowFunc && isComplex(v)) return;
16
+ if (isObj(v)) return;
17
+
18
+ if (typeof msg !== S)
19
+ { // Use a default message.
20
+ msg = "Invalid object";
21
+ if (allowFunc)
22
+ msg += " or function";
23
+ }
24
+ throw new TypeError(msg);
25
+ }
26
+
27
+ exports.needObj = needObj;
28
+
29
+ /**
30
+ * If a value is not a certain type, throw an error.
31
+ *
32
+ * @param {string} type - The type name as per `isType()`.
33
+ * @param {*} v - The value we're testing.
34
+ * @param {string} [msg] A custom error message.
35
+ * @throws {TypeError} If the type check failed.
36
+ */
37
+ function needType (type, v, msg, unused)
38
+ {
39
+ if (!isType(type, v))
40
+ {
41
+ if (typeof msg === B)
42
+ {
43
+ console.warn("needType(): 'allowNull' is no longer supported");
44
+ if (typeof unused === S)
45
+ { // Compatibility with old code.
46
+ msg = unused;
47
+ }
48
+ }
49
+
50
+ if (typeof msg !== S)
51
+ { // Use a default message.
52
+ msg = `Invalid ${type} value`;
53
+ }
54
+
55
+ throw new TypeError(msg);
56
+ }
57
+ }
58
+
59
+ exports.needType = needType;
60
+
61
+ // Options parser for needs();
62
+ const NEEDS_PARSER = function(type, v)
63
+ { // `this` is the options object itself.
64
+ if (typeof type.is === F)
65
+ { // We assume `is()` methods are the type check.
66
+ if (type.is(v)) return true;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * A wrapper around `isa()` that will throw an error on failure.
72
+ *
73
+ * @param {*} v - The value we're testing.
74
+ * @param {...any} types - The types the value should be one of.
75
+ *
76
+ * In addition to the `types` supported by `isa()` this will also
77
+ * look for an `object` with a single property named `error`
78
+ * which can be either a `string` or any subclass of `Error`.
79
+ * If specified, it will override the error message that will be thrown.
80
+ *
81
+ */
82
+ function needs(v, ...types)
83
+ {
84
+ let error;
85
+
86
+ function parser(type, v)
87
+ {
88
+ // Only process objects with a single `error` property.
89
+ if ('error' in type && Object.keys(type).length === 1)
90
+ {
91
+ if (typeof type.error === 'string')
92
+ { // An error message.
93
+ error = new TypeError(type.error);
94
+ }
95
+ else if (type.error instanceof Error)
96
+ { // An error object.
97
+ error = type.error;
98
+ }
99
+ }
100
+ }
101
+
102
+ if (!isa(v, {isa:{parsers: parser}}, ...types))
103
+ {
104
+ if (!(error instanceof Error))
105
+ {
106
+ error = new TypeError("value did not pass needs check");
107
+ }
108
+ throw error;
109
+ }
110
+ }
111
+
112
+ exports.needs = needs;
@@ -0,0 +1,94 @@
1
+ const {U} = require('./js');
2
+ const {isNil,isArray} = require('./basics');
3
+
4
+ // «private»
5
+ function no_root()
6
+ {
7
+ throw new Error("Invalid JS environment, no root object found");
8
+ }
9
+
10
+ /**
11
+ * The global root object. Usually `globalThis` these days.
12
+ */
13
+ const root = typeof globalThis !== U ? globalThis
14
+ : typeof global !== U ? global
15
+ : typeof self !== U ? self
16
+ : typeof window !== U ? window
17
+ : no_root(); // Unlike the old way, we'll die if the environment is undetermined.
18
+
19
+ exports.root = root;
20
+
21
+ // A list of objects to be considered unbound globally.
22
+ const unboundObjects = [];
23
+
24
+ /**
25
+ * Pass `this` here to see if it is bound to an object.
26
+ *
27
+ * Always considers `null` and `undefined` as unbound.
28
+ *
29
+ * @param {*} whatIsThis - The `this` from any context.
30
+ * @param {boolean} [rootIsUnbound=true] The global root is unbound.
31
+ * @param {(boolean|Array)} [areUnbound=false] A list of unbound objects.
32
+ * If the is `true` we use an global list that can register special
33
+ * internal objects. Otherwise an `Array` of unbound objects may be used.
34
+ * @returns {boolean}
35
+ */
36
+ function unbound(whatIsThis, rootIsUnbound=true, areUnbound=false)
37
+ {
38
+ if (areUnbound === true)
39
+ { // If areUnbound is true, we use the unboundObjects
40
+ areUnbound = unboundObjects;
41
+ }
42
+
43
+ if (isNil(whatIsThis)) return true;
44
+ if (rootIsUnbound && whatIsThis === root) return true;
45
+ if (isArray(areUnbound) && areUnbound.includes(whatIsThis)) return true;
46
+
47
+ // Nothing considered unbound.
48
+ return false;
49
+ }
50
+
51
+ exports.unbound = unbound;
52
+
53
+ // Now that 'unbound' is exported, we can do some wibbly wobbly magic.
54
+ const def = require('./def');
55
+
56
+ /**
57
+ * Add an item to the unbound global objects list.
58
+ *
59
+ * @method unbound.add
60
+ *
61
+ * @param {(object|function)} obj - The object to be considered unbound.
62
+ *
63
+ * @returns {boolean} Will be `false` if `obj` is already unbound.
64
+ *
65
+ * @throws {TypeError} If `obj` was neither an `object` nor a `function`.
66
+ */
67
+ def(unbound, 'add', function (obj)
68
+ {
69
+ needObj(obj, true);
70
+ if (unbound(obj, true, true))
71
+ { // Item is already unbound.
72
+ return false;
73
+ }
74
+ // Add to list and we're done.
75
+ unboundObjects.push(obj);
76
+ return true;
77
+ });
78
+
79
+ /**
80
+ * Remove an item from the unbound global objects list.
81
+ *
82
+ * @method unbound.remove
83
+ *
84
+ * @param {(object|function)} obj - The object to be removed.
85
+ *
86
+ * @returns {boolean} Will be `false` if the item was not in the list.
87
+ *
88
+ * @throws {TypeError} If `obj` was neither an `object` nor a `function`.
89
+ */
90
+ def(unbound, 'remove', function(obj)
91
+ {
92
+ needObj(obj, true);
93
+ return (removeFromArray(unboundObjects, obj) > 0);
94
+ });