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

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.
@@ -1,4 +1,20 @@
1
- /// Type checking and other features common to everything else.
1
+ /**
2
+ * Fundamental Types sub-module.
3
+ *
4
+ * As `@lumjs/core` is the foundation for all my JS libraries,
5
+ * this sub-module is the foundation for `@lumjs/core`.
6
+ * Everything else is built upon this.
7
+ *
8
+ * @module @lumjs/core/types
9
+ * @property {string} O - "object"
10
+ * @property {string} F - "function"
11
+ * @property {string} S - "string"
12
+ * @property {string} B - "binary"
13
+ * @property {string} N - "number"
14
+ * @property {string} U - "undefined"
15
+ * @property {string} SY - "symbol"
16
+ * @property {string} BI - "bigint"
17
+ */
2
18
 
3
19
  // Constants representing core Javascript types.
4
20
  const {O, F, S, B, N, U, SY, BI} = require('./js');
@@ -23,6 +39,7 @@ const {needObj, needType, needs} = require('./needs');
23
39
 
24
40
  const def = require('./def');
25
41
  const TYPES = require('./typelist');
42
+ const stringify = require('./stringify');
26
43
 
27
44
  // Okay, add all those to our exports.
28
45
  // Further tests can be added by `TYPES.add()` later.
@@ -31,7 +48,7 @@ module.exports =
31
48
  O, F, S, B, N, U, SY, BI, TYPES, root, unbound, def,
32
49
  isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
33
50
  nonEmptyArray, isArguments, isProperty, doesDescriptor,
34
- isInstance, isType, isa, needObj, needType, needs,
51
+ isInstance, isType, isa, needObj, needType, needs, stringify,
35
52
  }
36
53
 
37
54
  // Last but not least, this will be the module for TYPES.add()
package/lib/types/isa.js CHANGED
@@ -8,6 +8,7 @@ const TYPES = require('./typelist');
8
8
  * @param {function} f - The constructor/class we want.
9
9
  * @param {boolean} [needProto=false] If true, the `v` must have a `prototype`.
10
10
  * @returns {boolean}
11
+ * @alias module:@lumjs/core/types.isInstance
11
12
  */
12
13
  function isInstance(v, f, needProto=false)
13
14
  {
@@ -30,24 +31,15 @@ const TYPES = require('./typelist');
30
31
  *
31
32
  * @param {string} type - The type we're checking for.
32
33
  *
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`.
34
+ * This supports all the same type names as `typeof` plus any of
35
+ * the properties defined in the `TYPES` object.
44
36
  *
45
37
  * One other thing, while `typeof` reports `null` as being an `object`,
46
38
  * this function does not count `null` as a valid `object`.
47
39
  *
48
40
  * @param {*} v - The value we're testing.
49
- *
50
41
  * @returns {boolean} If the value was of the desired type.
42
+ * @alias module:@lumjs/core/types.isType
51
43
  */
52
44
  function isType(type, v)
53
45
  {
@@ -138,6 +130,7 @@ const TYPES = require('./typelist');
138
130
  * Any other type value will only match if `v === type`
139
131
  *
140
132
  * @returns {boolean} Was the value one of the desired types?
133
+ * @alias module:@lumjs/core/types.isa
141
134
  */
142
135
  function isa(v, ...types)
143
136
  {
@@ -9,6 +9,7 @@ const {isObj, isComplex} = require('./basics');
9
9
  * @param {boolean} [allowFunc=false] - Also accept functions?
10
10
  * @param {string} [msg] A custom error message.
11
11
  * @throws {TypeError} If the type check failed.
12
+ * @alias module:@lumjs/core/types.needObj
12
13
  */
13
14
  function needObj (v, allowFunc=false, msg=null)
14
15
  {
@@ -33,6 +34,7 @@ exports.needObj = needObj;
33
34
  * @param {*} v - The value we're testing.
34
35
  * @param {string} [msg] A custom error message.
35
36
  * @throws {TypeError} If the type check failed.
37
+ * @alias module:@lumjs/core/types.needType
36
38
  */
37
39
  function needType (type, v, msg, unused)
38
40
  {
@@ -77,7 +79,10 @@ const NEEDS_PARSER = function(type, v)
77
79
  * look for an `object` with a single property named `error`
78
80
  * which can be either a `string` or any subclass of `Error`.
79
81
  * If specified, it will override the error message that will be thrown.
80
- *
82
+ *
83
+ * @throws {TypeError} If the type check failed.
84
+ * @throws {Error} If a custom error was specified.
85
+ * @alias module:@lumjs/core/types.needs
81
86
  */
82
87
  function needs(v, ...types)
83
88
  {
package/lib/types/root.js CHANGED
@@ -9,6 +9,7 @@ function no_root()
9
9
 
10
10
  /**
11
11
  * The global root object. Usually `globalThis` these days.
12
+ * @alias module:@lumjs/core/types.root
12
13
  */
13
14
  const root = typeof globalThis !== U ? globalThis
14
15
  : typeof global !== U ? global
@@ -32,6 +33,7 @@ const unboundObjects = [];
32
33
  * If the is `true` we use an global list that can register special
33
34
  * internal objects. Otherwise an `Array` of unbound objects may be used.
34
35
  * @returns {boolean}
36
+ * @alias module:@lumjs/core/types.unbound
35
37
  */
36
38
  function unbound(whatIsThis, rootIsUnbound=true, areUnbound=false)
37
39
  {
@@ -56,13 +58,11 @@ const def = require('./def');
56
58
  /**
57
59
  * Add an item to the unbound global objects list.
58
60
  *
59
- * @method unbound.add
60
- *
61
+ * @function
61
62
  * @param {(object|function)} obj - The object to be considered unbound.
62
- *
63
63
  * @returns {boolean} Will be `false` if `obj` is already unbound.
64
- *
65
64
  * @throws {TypeError} If `obj` was neither an `object` nor a `function`.
65
+ * @alias module:@lumjs/core/types.unbound.add
66
66
  */
67
67
  def(unbound, 'add', function (obj)
68
68
  {
@@ -79,13 +79,11 @@ def(unbound, 'add', function (obj)
79
79
  /**
80
80
  * Remove an item from the unbound global objects list.
81
81
  *
82
- * @method unbound.remove
83
- *
82
+ * @function
84
83
  * @param {(object|function)} obj - The object to be removed.
85
- *
86
84
  * @returns {boolean} Will be `false` if the item was not in the list.
87
- *
88
85
  * @throws {TypeError} If `obj` was neither an `object` nor a `function`.
86
+ * @alias module:@lumjs/core/types.unbound.remove
89
87
  */
90
88
  def(unbound, 'remove', function(obj)
91
89
  {
@@ -0,0 +1,98 @@
1
+ // Get the extended type list.
2
+ const TYPES = require('./typelist');
3
+ const {isObj, isArray, isTypedArray} = require('./basics');
4
+
5
+ const TOSTRING = [TYPES.F, TYPES.SY];
6
+
7
+ /**
8
+ * Stringify a Javascript value.
9
+ *
10
+ * We typically just use `JSON.stringify()` but that doesn't work on
11
+ * some types. So this function adds string formats for:
12
+ * - `function`
13
+ * - `symbol`
14
+ * - `TypedArray`
15
+ * - `Map`
16
+ * - `Set`
17
+ * - `Error`
18
+ * I may add even more extended types in the future, but that's enough
19
+ * for now.
20
+ *
21
+ * This is NOT meant for serializing data, and does not use a JSON-friendly
22
+ * output format. I'm writing a different library for that.
23
+ *
24
+ * @param {*} what - The value to stringify.
25
+ * @param {integer} [recurse=1] Recurse objects to this depth.
26
+ * @param {boolean} [addNew=false] Use 'new Class()' instead of 'Class()'.
27
+ * @returns {string} The stringified value.
28
+ * @alias module:@lumjs/core/types.stringify
29
+ */
30
+ function stringify (what, recurse=1, addNew=false)
31
+ {
32
+ const whatType = typeof what;
33
+ if (TOSTRING.includes(whatType)) return what.toString();
34
+
35
+ // A few formatting helpers used below.
36
+
37
+ const classname = () => (addNew ? 'new ' : '') + what.constructor.name;
38
+ const construct = val => `${classname()}(${val})`;
39
+ const reconstruct = val => construct(stringify(val,recurse,addNew));
40
+ const arrayish = vals => reconstruct(Array.from(vals));
41
+
42
+ if (isTypedArray(what))
43
+ { // This one is pretty simple.
44
+ return construct(what.toString());
45
+ }
46
+
47
+ if (what instanceof Map)
48
+ {
49
+ return arrayish(what.entries());
50
+ }
51
+
52
+ if (what instanceof Set)
53
+ {
54
+ return arrayish(what.values());
55
+ }
56
+
57
+ if (what instanceof Error)
58
+ {
59
+ return construct(what.message);
60
+ }
61
+
62
+ if (recurse && isObj(what))
63
+ { // Recursion mode enabled.
64
+ let out = '';
65
+ if (isArray(what))
66
+ { // Stringify an array.
67
+ out = '[';
68
+ out += what.map(item => stringify(item, recurse-1, addNew)).join(',');
69
+ out += ']';
70
+ }
71
+ else
72
+ { // Stringify a plain object.
73
+ out = '{';
74
+ function add(key, pre='')
75
+ {
76
+ out += `${pre}${key}:${stringify(what[key], recurse-1, addNew)}`
77
+ }
78
+ const keys = Object.keys(what);
79
+ //console.debug("keys!", keys);
80
+ if (keys.length > 0)
81
+ { // Let's add the first key, then all subsequent keys.
82
+ add(keys.shift());
83
+ for (const key of keys)
84
+ {
85
+ add(key, ',');
86
+ }
87
+ }
88
+ out += '}';
89
+ }
90
+ return out;
91
+ }
92
+ else
93
+ { // If we reached here, there's no special methods, use JSON.
94
+ return JSON.stringify(what);
95
+ }
96
+ }
97
+
98
+ module.exports = stringify;
@@ -7,12 +7,76 @@ const
7
7
  } = require('./basics');
8
8
 
9
9
  /**
10
- * A map of type names including special and union types.
10
+ * A map of **Types**, including *special* and *union* types.
11
+ *
12
+ * Contains the same `O, F, S, B, N, U, SY, BI` properties as also
13
+ * found in the top-level {@link module:@lumjs/core/types} module.
14
+ * While most of the core JS types simply use `typeof` as their
15
+ * test, this maps the `O` (`object`) type to the `isObj` test.
16
+ *
11
17
  * Will also contain a few helper functions, and a map of tests
12
- * for any types that require tests other than `typeof v === 'typename'`
18
+ * that are used by `isType`, `isa`, `needType`, and `needs`.
19
+ * Any one of these properties may be passed to those functions as
20
+ * the desired *type* a desired value must be.
21
+ *
22
+ * We will list the types added by the `types` module in
23
+ * the *Properties* table, and any types added by other *core modules*
24
+ * in the *Members* list, prior to listing the *Methods*.
25
+ *
26
+ * @namespace module:@lumjs/core/types.TYPES
27
+ * @property {string} NULL - Represents `null` values.
28
+ * @property {string} ARGS - Represents an *argument* object.
29
+ * @property {string} PROP - A `string` or a `symbol`.
30
+ * @property {string} ARRAY - An `Array` object.
31
+ * @property {string} TYPEDARRAY - A `TypedArray` object.
32
+ * @property {string} DESCRIPTOR - A *Descriptor* object (Data or Accessor).
33
+ * @property {string} COMPLEX - A `function` or an `object`.
34
+ * @property {string} SCALAR - A non-null value that is **not** *complex*.
35
+ * @property {string} NIL - Either `null` or `undefined`.
36
+ * @property {string} NOTNIL - Anything other than `null` or `undefined`.
37
+ * @property {string} MAP - A `Map` object.
38
+ * @property {object} tests - A map of tests for the above types.
13
39
  */
14
40
  const TYPES = {};
15
41
 
42
+ /**
43
+ * Add a new type to the `TYPES`.
44
+ * @name module:@lumjs/core/types.TYPES.add
45
+ * @function
46
+ * @param {(string|object)} name - If a `string` the property name to use.
47
+ * When adding the property the string will be forced to uppercase.
48
+ *
49
+ * If an `object` then its a shortcut for adding a bunch of types at once.
50
+ * Each key will be the `name`, and the type of the *value* can be one of:
51
+ * - `string` → Use as the `ident` parameter.
52
+ * - `function` → Use as the `test` parameter.
53
+ * - `object` → Supports `id`, `test`, and `export` parameters.
54
+ *
55
+ * @param {?string} [ident] The identifier string for the type.
56
+ * If not specified or `null`, it will default to a completely lowercase
57
+ * version of the `name` parameter.
58
+ * @param {function} [test] A type check test.
59
+ * Must accept a single value to test, must return a boolean.
60
+ * @param {string} [exportTest] A name to export the test as.
61
+ * The test will be added to the `types` module with this name.
62
+ *
63
+ * @returns {void}
64
+ */
65
+
66
+ /**
67
+ * Get a list of type properties available in `TYPES`.
68
+ * @name module:@lumjs/core/types.TYPES.keys
69
+ * @function
70
+ * @returns {string[]}
71
+ */
72
+
73
+ /**
74
+ * Get a list of type identifier values available in `TYPES`.
75
+ * @name module:@lumjs/core/types.TYPES.list
76
+ * @function
77
+ * @returns {string[]}
78
+ */
79
+
16
80
  // Let's setup the TYPES with its magic functions.
17
81
  const dt = def(TYPES);
18
82
  dt('tests', {})
package/package.json CHANGED
@@ -1,7 +1,23 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "main": "lib/index.js",
5
+ "exports":
6
+ {
7
+ ".": "./lib/index.js",
8
+ "./types": "./lib/types/index.js",
9
+ "./arrays": "./lib/arrays.js",
10
+ "./context": "./lib/context.js",
11
+ "./strings": "./lib/strings.js",
12
+ "./flags": "./lib/flags.js",
13
+ "./obj": "./lib/obj/index.js",
14
+ "./opt": "./lib/opt.js",
15
+ "./modules": "./lib/moduless.js",
16
+ "./meta": "./lib/meta.js",
17
+ "./enum": "./lib/enum.js",
18
+ "./observable": "./lib/observable.js",
19
+ "./package.json": "./package.json"
20
+ },
5
21
  "license": "MIT",
6
22
  "repository":
7
23
  {
@@ -10,11 +26,13 @@
10
26
  },
11
27
  "devDependencies":
12
28
  {
13
- "@lumjs/tests": "^1.0.0"
29
+ "@lumjs/tests": "^1.0.0",
30
+ "jsdoc": "^3.6.10"
14
31
  },
15
32
  "scripts":
16
33
  {
17
34
  "-TODO-1": "Use `lumtest` once its available",
18
- "test": "prove -e node --ext js ./test"
35
+ "test": "prove -e node --ext js ./test",
36
+ "build-docs": "jsdoc -c ./jsdoc.json"
19
37
  }
20
38
  }
package/test/types.js CHANGED
@@ -1,20 +1,14 @@
1
1
  // Current test count.
2
- const plan = 85;
2
+ const plan = 120;
3
3
  // A new test instance.
4
4
  const t = require('@lumjs/tests').new({module, plan});
5
5
  // The types core module
6
6
  const types = require('../lib/types');
7
7
 
8
- // And a quick reference to the type names.
8
+ // A quick reference to the type names.
9
9
  const TYP = types.TYPES;
10
-
11
- // Helper function.
12
- function stringify (what)
13
- {
14
- if (typeof what === TYP.F) return what.toString();
15
- if (typeof what === TYP.SY) return what.constructor.toString();
16
- return JSON.stringify(what);
17
- }
10
+ // And the stringify function.
11
+ const stringify = types.stringify;
18
12
 
19
13
  // Now for some further basics.
20
14
  t.ok(types.isObj({}), 'isObj({})');
@@ -55,10 +49,16 @@ class SubtypeClass extends TypeClass {}
55
49
  const typesInstance = new TypeClass();
56
50
  const subtypeInstance = new SubtypeClass();
57
51
 
52
+ class DifferentClass {}
53
+
54
+ const differentInstance = new DifferentClass();
55
+
58
56
  t.ok(types.isInstance(typesInstance, TypeClass), 'isInstance(typeInstance,TypeClass)');
59
57
  t.ok(types.isInstance(subtypeInstance, SubtypeClass), 'isInstance(subtypeInstance, SubtypeClass)');
60
58
  t.ok(types.isInstance(subtypeInstance, TypeClass), 'isInstance(subtypeInstance, TypeClass)');
61
- t.ok(!types.isInstance(typesInstance, SubtypeClass), '!isInstance(typeInstance, SubtypeClass');
59
+ t.ok(!types.isInstance(typesInstance, SubtypeClass), '!isInstance(typeInstance, SubtypeClass)');
60
+ t.ok(!types.isInstance(differentInstance, TypeClass), '!isInstance(differentInstance, TypeClass)');
61
+ t.ok(!types.isInstance(typesInstance, DifferentClass), '!isInstance(typesInstance, DifferentClass)');
62
62
 
63
63
  function doesDesc (tests, not=false)
64
64
  {
@@ -126,7 +126,7 @@ testIsType(
126
126
  [TYP.SCALAR, true],
127
127
  [TYP.SCALAR, 'hi'],
128
128
  [TYP.PROP, 'woah'],
129
- [TYP.PROP, Symbol('woah'), TYP.PROP+',Symbol'],
129
+ [TYP.PROP, Symbol('woah')],
130
130
  ]);
131
131
 
132
132
  testIsType(
@@ -161,18 +161,107 @@ t.dies(function(){types.needObj(null); return true}, '!needObj(null)');
161
161
  t.ok((function(){types.needType(TYP.S, 'hi'); return true})(), "needType('string','hi')");
162
162
  t.dies(function(){types.needType(TYP.O, null); return true}, "!needType('object',null)");
163
163
 
164
+ { // Tests of isa() method.
165
+ let wants = [TYP.S, TYP.N];
166
+ t.ok(types.isa('hi', ...wants), 'isa(val, ...types)');
167
+ t.isa('hello', wants, ' ^ using Test.isa()');
168
+ t.isa(42, wants, ' ^ with second type');
169
+ t.ok(!types.isa({}, ...wants), '!isa(val, ...types)');
170
+ t.nota({}, wants, ' ^ using Test.nota()');
171
+
172
+ wants = [SubtypeClass, DifferentClass];
173
+ t.isa(subtypeInstance, wants, 'isa(val, ...classes)');
174
+ t.isa(differentInstance, wants, ' ^ with second class');
175
+ t.nota(typesInstance, wants, 'nota(val, ...classes)');
176
+
177
+ wants = [TYP.B, TypeClass];
178
+ t.isa(true, wants, 'isa() → with mixed types/classes');
179
+ t.isa(subtypeInstance, wants, ' ^ with second type/class');
180
+ }
181
+
182
+ { // Tests of needs() method.
183
+ let needs = [TYP.S, TYP.N];
184
+ t.lives(() => types.needs('hi', ...needs), 'needs(val, ...types)');
185
+ t.lives(() => types.needs(42, ...needs), ' ^ with second type');
186
+ t.dies(() => types.needs({}, ...needs), ' ^ throws on failure');
187
+
188
+ needs = [SubtypeClass, DifferentClass];
189
+ t.lives(() => types.needs(subtypeInstance, ...needs), 'needs(val, ...classes)');
190
+ t.lives(() => types.needs(differentInstance, ...needs), ' ^ with second class');
191
+ t.dies(() => types.needs(typesInstance, ...needs), ' ^ throws on failure');
192
+
193
+ needs = [TYP.B, TypeClass];
194
+ t.lives(() => types.needs(true, ...needs), 'needs() → with mixed types/classes');
195
+ t.lives(() => types.needs(subtypeInstance, ...needs), ' ^ with second type/class');
196
+ }
197
+
164
198
  { // Try a few versions of 'def'
165
199
  const obj = {};
166
200
  types.def(obj, 'test1', 'Test 1');
167
201
  t.is(obj.test1, 'Test 1', 'def(obj, name, value)');
168
202
  types.def(obj)('test2', 'Test 2');
169
203
  t.is(obj.test2, 'Test 2', 'def(obj)(name, value)');
204
+ obj.test2 = '2 Test';
205
+ t.is(obj.test2, 'Test 2', 'def() is read-only by default');
206
+
207
+ types.def(obj, true)('test3', 'Test 3');
208
+ t.is(Object.keys(obj).length, 1, 'def(obj, true)(...)');
209
+
210
+ types.def(obj, 'a1', function()
211
+ { // Returning a different property.
212
+ return this.test2;
213
+ },
214
+ function(val)
215
+ { // Assigning to a different property.
216
+ this.$$ = val;
217
+ });
218
+
219
+ const gs = 'def(obj, name, getter, setter)';
220
+ t.is(obj.a1, 'Test 2', gs+'~getter');
221
+ obj.a1 = 'A1->$$';
222
+ t.is(obj.$$, 'A1->$$', gs+'~setter');
223
+
224
+ types.def(obj, 'test4', 'Test 4', {writable: true});
225
+ t.is(obj.test4, 'Test 4', 'def(..., {writable}) → added property');
226
+ obj.test4 = '4 Test';
227
+ t.is(obj.test4, '4 Test', ' ^ and it was writable');
228
+
229
+ const td = types.def(obj);
230
+ td('getOnly', {get: function() { return 'GETTER'; }});
231
+ obj.getOnly = 'blah blah blah';
232
+ t.is(obj.getOnly, 'GETTER', 'def(..., {get: getter}) → getter worked');
233
+ td('setOnly', {set: function(val) { this.__ = val; }});
234
+ obj.setOnly = 'SETTER';
235
+ t.is(obj.__, 'SETTER', 'def(..., {set: setter}) → setter worked');
236
+ t.is(obj.setOnly, undefined, ' ^ get is undefined');
237
+
238
+ td('foobar', {value: 'FOO BAR'});
239
+ t.is(obj.foobar, 'FOO BAR', 'def(..., {value})');
240
+
241
+ let anObj = {value: 'BAR FOO'};
242
+ td('barfoo', anObj, false);
243
+ t.is(obj.barfoo, anObj, 'def(..., descriptor, false) → assigned object as value');
170
244
 
171
- // TODO: new accessor assignment options.
245
+ td('barfoo2', anObj);
246
+ t.is(anObj.configurable, true, 'def(..., descriptor) → descriptor is a reference');
247
+
248
+ anObj = {value: 'new test'};
249
+ td('barfoo3', anObj, true);
250
+
251
+ t.is(anObj.configurable, undefined, 'def(..., descriptor, true) → cloned descriptor');
252
+ t.is(obj.barfoo3, 'new test', ' ^ value was correct')
253
+
254
+ td(
255
+ {
256
+ hello: 'World',
257
+ goodbye: 'Universe',
258
+ });
172
259
 
260
+ t.ok((obj.hello === 'World' && obj.goodbye === 'Universe'), 'def(obj, {prop1: value1, prop2: value2})')
173
261
  }
174
262
 
175
263
  // TODO: isa() and needs()
264
+ // TODO: stringify()
176
265
 
177
266
  // All done.
178
267
  t.output();