@lumjs/core 1.33.0 → 1.35.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/traits.js CHANGED
@@ -10,13 +10,18 @@ const getProp = require('./obj/getproperty');
10
10
  const
11
11
  {
12
12
  def,F,B,S,
13
- isObj,isArray,isConstructor,isProperty,
13
+ isObj,isArray,isConstructor,isProperty,isClassObject,
14
14
  needObj,needType,
15
15
  } = require('./types');
16
16
 
17
17
  // Symbol for private storage of composed traits.
18
18
  const COMPOSED_TRAITS = Symbol.for('@lumjs/core/traits~ComposedTraits');
19
19
 
20
+ const FLAG_ET = 1; // Flag for ensure target
21
+ const FLAG_ES = 2; // Flag for ensure source
22
+ const FLAGS_S = 0; // Default flags for static mode
23
+ const FLAGS_O = 3; // Default flags for object mode
24
+
20
25
  /**
21
26
  * For when we need a prototype object exclusively.
22
27
  *
@@ -30,7 +35,7 @@ const COMPOSED_TRAITS = Symbol.for('@lumjs/core/traits~ComposedTraits');
30
35
  * If `from` is a `function` then `from.prototype` will be returned.
31
36
  *
32
37
  * @throws {TypeError} If `from` was not a valid value;
33
- * including if it is a `function` but does not have a valid
38
+ * including if it is a function but does not have a valid
34
39
  * prototype object to return.
35
40
  *
36
41
  * @alias module:@lumjs/core/traits.ensureProto
@@ -47,7 +52,42 @@ function ensureProto(from)
47
52
  }
48
53
  else
49
54
  {
50
- throw new TypeError("Invalid class constructor or object instance");
55
+ throw new TypeError("Invalid class constructor or instance object");
56
+ }
57
+ }
58
+
59
+ /**
60
+ * For when we need a constructor function exclusively.
61
+ *
62
+ * @param {(function|object)} from - Target
63
+ *
64
+ * May either be a class constructor `function`, or an `object` instance.
65
+ *
66
+ * @returns {function}
67
+ *
68
+ * If `from` is a constructor function it will be returned as-is.
69
+ * If `from` is an object instance (with a constructor other than `Object`),
70
+ * then the `from.constructor` value will be returned.
71
+ *
72
+ * @throws {TypeError} If `from` was not a valid value;
73
+ * including if it was a function without a prototype,
74
+ * or an object with a constructor of `Object`.
75
+ *
76
+ * @alias module:@lumjs/core/traits.ensureConstructor
77
+ */
78
+ function ensureConstructor(from)
79
+ {
80
+ if (isConstructor(from))
81
+ { // Good to go
82
+ return from;
83
+ }
84
+ else if (isClassObject(from))
85
+ { // An instance of a class (other than `Object` itself)
86
+ return from.constructor;
87
+ }
88
+ else
89
+ {
90
+ throw new TypeError("Invalid class constructor or instance object");
51
91
  }
52
92
  }
53
93
 
@@ -125,13 +165,16 @@ function getComposed(target, source, tryClass=true)
125
165
  composed = target[COMPOSED_TRAITS];
126
166
  }
127
167
 
128
- if (composed && source && composed.has(source))
129
- { // Let's get definitions for a specific trait.
130
- return composed.get(source);
131
- }
132
- else if (composed && !source)
133
- { // No trait specified, but compose rules found.
134
- return composed;
168
+ if (composed)
169
+ {
170
+ if (!source)
171
+ {
172
+ return composed;
173
+ }
174
+ if (composed.has(source))
175
+ { // Let's get definitions for a specific trait.
176
+ return composed.get(source);
177
+ }
135
178
  }
136
179
 
137
180
  if (tryClass && isObj(target))
@@ -224,9 +267,27 @@ function mapComposed(target, source)
224
267
  * or use the {@link module:@lumjs/core/traits.composeFully} function.
225
268
  *
226
269
  * Only useful if `source` is a class constructor (always recommended).
270
+ * The exact behaviour may be customized further with the `.flags` option.
227
271
  *
228
272
  * Default is `false`.
229
273
  *
274
+ * @param {number} [opts.flags] Binary flags for advanced behaviours
275
+ *
276
+ * The default `.flags` value, and the _ensure function_ used depends
277
+ * on the value of the `.static` option:
278
+ *
279
+ * | .static | Default | Ensure function |
280
+ * | ------- | ------- | --------------------------------------------------- |
281
+ * | `false` | `3` | {@link module:@lumjs/core/traits.ensureProto} |
282
+ * | `true` | `0` | {@link module:@lumjs/core/traits.ensureConstructor} |
283
+ *
284
+ * The actual flags are fairly straightforward:
285
+ *
286
+ * | Flag | Description |
287
+ * | ---- | ---------------------------------------------------------------- |
288
+ * | `1` | Pass `target` through _ensure function_ |
289
+ * | `2` | Pass `source` through _ensure function_ |
290
+ *
230
291
  * @param {boolean} [opts.symbols=false] Auto-compose `Symbol` properties?
231
292
  *
232
293
  * Calls `Object.getOwnPropertySymbols()` when generating a list of
@@ -260,8 +321,7 @@ function compose(specTarget, specSource, opts={})
260
321
  = getComposed(specTarget, specSource)
261
322
  ?? mapComposed(specTarget, specSource);
262
323
 
263
- const isStatic = opts.static ?? false;
264
-
324
+ const isStatic = opts.static ?? false;
265
325
  const mapId = isStatic ? 'static' : 'proto';
266
326
 
267
327
  if (composedMaps[mapId])
@@ -270,8 +330,11 @@ function compose(specTarget, specSource, opts={})
270
330
  return composedMaps;
271
331
  }
272
332
 
273
- const target = isStatic ? specTarget : ensureProto(specTarget);
274
- const source = isStatic ? specSource : ensureProto(specSource);
333
+ const flags = parseInt(opts.flags) ?? (isStatic ? FLAGS_S : FLAGS_O);
334
+ const ensure = isStatic ? ensureConstructor : ensureProto;
335
+
336
+ const target = (flags & FLAG_ET) ? specTarget : ensure(specTarget);
337
+ const source = (flags & FLAG_ES) ? specSource : ensure(specSource);
275
338
 
276
339
  let props = opts.props;
277
340
 
@@ -382,7 +445,6 @@ function compose(specTarget, specSource, opts={})
382
445
  composed.set(tprop, cdef);
383
446
  }
384
447
 
385
- composedMaps[mapId] = composed;
386
448
  return composedMaps;
387
449
  }
388
450
 
@@ -642,9 +704,10 @@ class CoreTrait
642
704
  * @param {boolean} info.ok - Will always be `true` initially.
643
705
  *
644
706
  * If an overridden `removeTrait()` method sets this to `false`,
645
- * then the `decomposeFrom()` operation will be cancelled before
646
- * decomposing the trait.
707
+ * then the decomposeFrom() operation will skip the step of
708
+ * actually decomposing the trait.
647
709
  *
710
+ * @returns {*} Return value is not used.
648
711
  */
649
712
  static removeTrait(info)
650
713
  {
@@ -714,7 +777,7 @@ class CoreTrait
714
777
  * @returns {function} The `registerTrait()` function created above.
715
778
  * @alias module:@lumjs/core/traits.makeRegistry
716
779
  */
717
- function makeTraitRegistry(registry)
780
+ function makeTraitRegistry(registry={})
718
781
  {
719
782
  needObj(registry, false, 'invalid trait registry object');
720
783
 
@@ -749,56 +812,10 @@ function makeTraitRegistry(registry)
749
812
  module.exports =
750
813
  {
751
814
  compose, composeFully, getComposed, decompose,
752
- Trait: CoreTrait, IGNORE_STATIC, ensureProto,
815
+ Trait: CoreTrait, IGNORE_STATIC,
816
+ ensureProto, ensureConstructor,
753
817
  makeRegistry: makeTraitRegistry,
754
818
 
755
819
  // Undocumented:
756
820
  hasOwn,
757
821
  }
758
-
759
- /**
760
- * Composed property maps.
761
- *
762
- * @typedef {object} module:@lumjs/core/traits~Composed
763
- *
764
- * @prop {?Map} proto - Map of composed *prototype* properties.
765
- *
766
- * Keys are the `string` or `symbol` for each composed property.
767
- * Values are {@link module:@lumjs/core/traits~ComposedProperty} objects.
768
- *
769
- * If no applicable trait properties are currently composed,
770
- * this will be `null`.
771
- *
772
- * @prop {?Map} static - Map of composed *static* properties.
773
- *
774
- * Exactly the same description as `proto`, but for static properties.
775
- *
776
- * @prop {boolean} forClass - Are these property maps from a class?
777
- *
778
- * Will be `true` if the original target was a `function`, or `false`
779
- * otherwise. Only really useful when trait functions need to be called
780
- * differently depending on if the trait was applied to an individual
781
- * instance or a class constructor.
782
- *
783
- */
784
-
785
- /**
786
- * Composed property definitions.
787
- *
788
- * @typedef {object} module:@lumjs/core/traits~ComposedProperty
789
- *
790
- * @prop {(string|Symbol)} id - The property key on the `target`
791
- * @prop {(string|Symbol)} [srcKey] The property key from the `source`;
792
- * only included if different from `propKey`.
793
- *
794
- * @prop {?object} newDescriptor - The property descriptor to be added;
795
- * will be `null` if the property did not exist.
796
- *
797
- * @prop {object} [oldDescriptor] The replaced property descriptor;
798
- * only included if there was an existing property in the `target`
799
- * and the `opts.overwrite` option was `true`.
800
- *
801
- * @prop {boolean} found - Was the property found in the `source` ?
802
- * @prop {boolean} added - Was the property added to the `target` ?
803
- *
804
- */
@@ -144,6 +144,9 @@ function isIterable(v)
144
144
  /**
145
145
  * Does a value appear to be a class constructor?
146
146
  *
147
+ * For the purposes of this test, a class constructor is
148
+ * any Function that has a `prototype` Object property.
149
+ *
147
150
  * @param {*} v - Value to test
148
151
  * @returns {boolean}
149
152
  * @alias module:@lumjs/core/types/basics.isConstructor
@@ -153,6 +156,35 @@ function isConstructor(v)
153
156
  return (typeof v === F && isObj(v.prototype));
154
157
  }
155
158
 
159
+ /**
160
+ * Is a value a _plain_ object?
161
+ *
162
+ * A plain object is one which has a `constructor` property
163
+ * that **IS** the `globalThis.Object` function.
164
+ *
165
+ * @param {*} v - Value to test
166
+ * @returns {boolean}
167
+ */
168
+ function isPlainObject(v)
169
+ {
170
+ return (isObj(v) && v.constructor === Object);
171
+ }
172
+
173
+ /**
174
+ * Is a value a class instance object?
175
+ *
176
+ * This is a counterpart to isPlainObject() that only returns
177
+ * true if the `constructor` property exists, but is **NOT**
178
+ * the `globalThis.Object` function.
179
+ *
180
+ * @param {*} v - Value to test
181
+ * @returns {boolean}
182
+ */
183
+ function isClassObject(v)
184
+ {
185
+ return (isObj(v) && typeof v.constructor === F && v.constructor !== Object);
186
+ }
187
+
156
188
  /**
157
189
  * See if an object can be used as a valid *descriptor*.
158
190
  *
@@ -246,7 +278,8 @@ module.exports =
246
278
  {
247
279
  isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
248
280
  isIterable, nonEmptyArray, isArguments, isProperty, isConstructor,
249
- doesDescriptor, doesDescriptorTemplate, JS,
281
+ isPlainObject, isClassObject, doesDescriptor, doesDescriptorTemplate,
282
+ JS,
250
283
  }
251
284
 
252
285
  JS.addTo(module.exports);
@@ -16,11 +16,11 @@ Object.assign(exports,
16
16
  require('./basics'),
17
17
  require('./root'),
18
18
  require('./isa'),
19
+ require('./stringify'),
19
20
  require('./needs'),
20
21
  { // A few standalone exports.
21
22
  def, lazy,
22
23
  TYPES: require('./typelist'),
23
- stringify: require('./stringify'),
24
24
  ownCount: require('./owncount'),
25
25
  },
26
26
  );
@@ -1,142 +1,301 @@
1
1
  // Get the extended type list.
2
2
  const TYPES = require('./typelist');
3
- const {isObj, isArray, isTypedArray} = require('./basics');
3
+ const {F, S, N, B, isObj, isArray, isTypedArray} = require('./basics');
4
4
  const def = require('./def');
5
5
 
6
- const TOSTRING_TYPES = [TYPES.F, TYPES.SY];
6
+ const TOSTRING_TYPES = [TYPES.SY];
7
7
  const TOSTRING_INSTANCES = [RegExp];
8
8
  const CUSTOM = [];
9
+ const DEF =
10
+ {
11
+ MAXD: 1,
12
+ NEWD: 0,
13
+ }
9
14
 
10
15
  /**
11
16
  * Stringify a Javascript value.
12
17
  *
13
- * We typically just use `JSON.stringify()` but that doesn't work on
14
- * some types. So this function adds string formats for:
18
+ * Creates a new `Stringify` instance, then passes the
19
+ * first argument to it's `stringify()` method.
20
+ *
21
+ * This is the primary way to use the Stringify class
22
+ * rather than manually creating an instance of it.
23
+ *
24
+ * @param {*} what - Passed to `instance.stringify()`
25
+ * @param {(object|number)} [opts] Options for Stringify constructor;
26
+ *
27
+ * If this is a `number` it's the `opts.maxDepth` option value.
28
+ *
29
+ * @param {(number|boolean)} [addNew] The `opts.newDepth` option value;
30
+ *
31
+ * For compatibility with old API. Boolean values are shortcuts:
32
+ * - `false` = `0`
33
+ * - `true` = `1`
34
+ *
35
+ * @returns {string} The stringified value
36
+ *
37
+ * @alias module:@lumjs/core/types.stringify
38
+ * @see module:@lumjs/core/types.Stringify
39
+ * @see module:@lumjs/core/types.Stringify#stringify
40
+ */
41
+ function stringify (what, opts={}, addNew=null)
42
+ {
43
+ if (typeof opts === N)
44
+ {
45
+ opts = {maxDepth: opts}
46
+ }
47
+
48
+ if (typeof addNew === N)
49
+ {
50
+ opts.newDepth = addNew;
51
+ }
52
+ else if (typeof addNew === B)
53
+ {
54
+ opts.newDepth = addNew ? 1 : 0;
55
+ }
56
+
57
+ const si = new Stringify(opts);
58
+ return si.stringify(what);
59
+ }
60
+
61
+ /**
62
+ * A class to stringify Javascript values for testing and debugging.
63
+ *
64
+ * This is NOT meant for serializing data, the output is in a format
65
+ * that's meant to be read by a developer, not parsed by a machine.
66
+ *
67
+ * Currently directly supports:
68
+ *
15
69
  * - `function`
16
70
  * - `symbol`
17
71
  * - `TypedArray`
18
72
  * - `Map`
19
73
  * - `Set`
20
74
  * - `Error`
21
- * I may add even more extended types in the future, but that's enough
22
- * for now.
75
+ * - `RegExp`
76
+ * - `Object`
23
77
  *
24
- * This is NOT meant for serializing data, and does not use a JSON-friendly
25
- * output format. I'm writing a different library for that.
78
+ * Any other JS value types (`number`,`boolean`, `string`, etc.) will
79
+ * be serialized using JSON.
26
80
  *
27
- * @param {*} what - The value to stringify.
28
- * @param {integer} [recurse=1] Recurse objects to this depth.
29
- * @param {boolean} [addNew=false] Use 'new Class()' instead of 'Class()'.
30
- * @returns {string} The stringified value.
31
- * @alias module:@lumjs/core/types.stringify
81
+ * @alias module:@lumjs/core/types.Stringify
32
82
  */
33
- function stringify (what, recurse=1, addNew=false)
83
+ class Stringify
34
84
  {
35
- const whatType = typeof what;
36
-
37
- for (const test of CUSTOM)
38
- { // If there are custom extensions, we check them first.
39
- const ret = test.call({stringify}, what, recurse, addNew);
40
- if (typeof ret === TYPES.S)
41
- { // The extension processed the item.
42
- return ret;
43
- }
44
- }
85
+ /**
86
+ * Create a new Stringify instance
87
+ * @param {object} [opts] Options; saved to `this.options`
88
+ * @param {number} [opts.maxDepth=1] Max depth to recurse?
89
+ * @param {number} [opts.newDepth=0] Depth to prepend 'new' to strings?
90
+ * @param {boolean} [opts.fnString=false] Fully stringify functions?
91
+ * @param {(Array|function)} [opts.setCustom] Set custom stringifiers;
92
+ * used *INSTEAD* of the globally registered ones.
93
+ * @param {(Array|function)} [opts.addCustom] Add custom stringifiers;
94
+ * used *in addition* to the globally registered ones.
95
+ */
96
+ constructor(opts={})
97
+ {
98
+ this.options = opts;
99
+ this.maxDepth = opts.maxDepth ?? DEF.MAXD;
100
+ this.newDepth = opts.newDepth ?? DEF.NEWD;
101
+ this.seenObjects = new Map(); // object => description
102
+ this.seenDescrip = new Map(); // description => count
103
+ this.strClass = TOSTRING_INSTANCES.slice();
104
+ this.strTypes = TOSTRING_TYPES.slice();
45
105
 
46
- // A few types we simply stringify right now.
47
- if (TOSTRING_TYPES.includes(whatType)) return what.toString();
106
+ if (opts.fnString)
107
+ {
108
+ this.strTypes.push(F);
109
+ }
48
110
 
49
- if (isObj(what))
50
- { // We support a few kinds of objects.
111
+ if (opts.errString)
112
+ {
113
+ this.strClass.push(Error);
114
+ }
51
115
 
52
- // Any class instance that we can simply call `toString()` on, let's do that.
53
- for (const aClass of TOSTRING_INSTANCES)
116
+ if (Array.isArray(opts.setCustom))
117
+ {
118
+ this.customFn = opts.setCustom.slice();
119
+ }
120
+ else if (typeof opts.setCustom === F)
54
121
  {
55
- if (what instanceof aClass)
122
+ this.customFn = [];
123
+ opts.setCustom.call(this, this.customFn);
124
+ }
125
+ else
126
+ { // Start out with the global custom tests.
127
+ this.customFn = CUSTOM.slice();
128
+ if (Array.isArray(opts.addCustom))
129
+ {
130
+ this.customFn.push(...opts.addCustom);
131
+ }
132
+ else if (typeof opts.addCustom === F)
56
133
  {
57
- return what.toString();
134
+ opts.addCustom.call(this, this.customFn);
58
135
  }
59
136
  }
60
137
 
61
- // A few formatting helpers used below.
62
- const classname = () => (addNew ? 'new ' : '') + what.constructor.name;
63
- const construct = val => `${classname()}(${val})`;
64
- const reconstruct = val => construct(stringify(val,recurse,addNew));
65
- const arrayish = vals => reconstruct(Array.from(vals));
138
+ }
66
139
 
67
- if (isTypedArray(what))
68
- { // This one is pretty simple.
69
- return construct(what.toString());
140
+ /**
141
+ * Stringify a JS value
142
+ * @param {*} what - Value to stringify
143
+ * @returns {string} A friendly representation of `what`
144
+ */
145
+ stringify(what, curd=0)
146
+ {
147
+ if (this.seenObjects.has(what))
148
+ { // Cached string for this subject already found.
149
+ return this.seenObjects.get(what);
70
150
  }
151
+
152
+ const recurse = curd < this.maxDepth;
153
+ const addNew = curd < this.newDepth;
154
+ const whatType = typeof what;
155
+ const strFor = (str) => this._stringFor(what, str);
71
156
 
72
- if (what instanceof Map)
73
- {
74
- return arrayish(what.entries());
157
+ for (const test of this.customFn)
158
+ { // If there are custom extensions, we check them first.
159
+ const ret = test.call(this, what, curd);
160
+ if (typeof ret === S)
161
+ { // The extension processed the item.
162
+ return strFor(ret);
163
+ }
75
164
  }
76
-
77
- if (what instanceof Set)
165
+
166
+ // A few types we simply stringify right now.
167
+ if (this.strTypes.includes(whatType))
78
168
  {
79
- return arrayish(what.values());
169
+ return strFor(what.toString());
80
170
  }
81
171
 
82
- if (what instanceof Error)
83
- {
84
- return `${what.name}(${JSON.stringify(what.message)})`;
172
+ if (!this.options.fnString && typeof what === F)
173
+ { // Simple format for functions
174
+ return strFor(what.name+'()');
85
175
  }
86
176
 
87
- if (recurse)
88
- { // Recursion mode enabled.
89
- let out = '';
90
- if (isArray(what))
91
- { // Stringify an array.
92
- out = '[';
93
- out += what.map(item => stringify(item, recurse-1, addNew)).join(',');
94
- out += ']';
95
- }
96
- else
97
- { // Stringify a plain object.
98
- out = '{';
99
- function add(key, pre='')
177
+ if (isObj(what))
178
+ { // We support a few kinds of objects.
179
+
180
+ // Any class instance that we can simply call `toString()` on, let's do that.
181
+ for (const aClass of this.strClass)
182
+ {
183
+ if (what instanceof aClass)
100
184
  {
101
- out += `${pre}${key}:${stringify(what[key], recurse-1, addNew)}`
185
+ return strFor(what.toString());
102
186
  }
103
- const keys = Object.keys(what);
104
- //console.debug("keys!", keys);
105
- if (keys.length > 0)
106
- { // Let's add the first key, then all subsequent keys.
107
- add(keys.shift());
108
- for (const key of keys)
187
+ }
188
+
189
+ // A few formatting helpers used below.
190
+ const nameFor = (name=what.constructor.name) =>
191
+ (addNew ? 'new ' : '') + strFor(name);
192
+
193
+ const container = (content, ps='[', pe=']') =>
194
+ {
195
+ let cstr = nameFor();
196
+ if (recurse)
197
+ {
198
+ cstr += ps;
199
+ if (!Array.isArray(content))
109
200
  {
110
- add(key, ',');
201
+ content = Array.from(content);
111
202
  }
203
+ cstr += (content
204
+ .map(item => this.stringify(item, curd+1))
205
+ .join(','));
206
+ cstr += pe;
207
+ }
208
+ return cstr;
209
+ }
210
+
211
+ if (isTypedArray(what))
212
+ { // This one is pretty simple.
213
+ if (this.options.typedContent)
214
+ {
215
+ return container(what.toString());
112
216
  }
113
- out += '}';
217
+ return nameFor();
218
+ }
219
+
220
+ if (what instanceof Map)
221
+ {
222
+ return container(what.entries());
223
+ }
224
+
225
+ if (what instanceof Set)
226
+ {
227
+ return container(what.values());
228
+ }
229
+
230
+ if (isArray(what))
231
+ {
232
+ return container(what);
233
+ }
234
+
235
+ if (!this.options.errString && what instanceof Error)
236
+ {
237
+ return nameFor(what.name)+'('+JSON.stringify(what.message)+')';
114
238
  }
115
- return out;
239
+
240
+ // If we reached here, it's another kind of object entirely.
241
+ return container(Object.keys(what), '{', '}');
242
+
243
+ } // if isObj
244
+
245
+ // If we reached here, there's no special methods, use JSON.
246
+ return JSON.stringify(what);
247
+ }
248
+
249
+ _stringFor(obj, str)
250
+ {
251
+ let count = 0;
252
+ if (this.seenDescrip.has(str))
253
+ {
254
+ count = this.seenDescrip.get(str);
116
255
  }
256
+ this.seenDescrip.set(str, ++count);
257
+ str += '#' + count.toString(16).padStart(3, '0');
258
+ this.seenObjects.set(obj, str);
259
+ return str;
260
+ }
117
261
 
118
- } // if isObj
119
-
120
- // If we reached here, there's no special methods, use JSON.
121
- return JSON.stringify(what);
122
- }
262
+ static addCustoms(...testFns)
263
+ {
264
+ for (let fn of testFns)
265
+ {
266
+ if (typeof fn === F)
267
+ {
268
+ CUSTOM.push(fn);
269
+ }
270
+ else
271
+ {
272
+ console.debug({fn, testFns});
273
+ throw new TypeError("Invalid custom stringify function");
274
+ }
275
+ }
276
+ }
123
277
 
124
- // Add a custom extension.
125
- def(stringify, '$extend',
126
- function(func, registration=false)
127
- {
128
- if (typeof func === TYPES.F)
278
+ static registerCustoms(registerFn)
129
279
  {
130
- if (registration)
131
- { // Using the function to register custom behaviour.
132
- func.call({stringify, TOSTRING_INSTANCES, TOSTRING_TYPES}, CUSTOM);
280
+ if (typeof registerFn === F)
281
+ {
282
+ registerFn.call(this, this);
133
283
  }
134
- else
135
- { // The function is a custom test.
136
- CUSTOM.push(func);
284
+ else
285
+ {
286
+ console.debug({registerFn});
287
+ throw new TypeError("Invalid custom registration function");
137
288
  }
138
289
  }
139
- });
290
+
291
+ } // Stringify
292
+
293
+ // Add some static properties for registerCustoms to use
294
+ def(Stringify)
295
+ ('strClass', {value: TOSTRING_INSTANCES})
296
+ ('strTypes', {value: TOSTRING_TYPES})
297
+ ('customFn', {value: CUSTOM})
298
+ ('DEFAULTS', {value: DEF})
140
299
 
141
300
  // Export it.
142
- module.exports = stringify;
301
+ module.exports = {stringify, Stringify};