@lumjs/core 1.33.0 → 1.34.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/console.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const def = require('./types/def');
2
- const {F} = require('./types/js');
2
+ const {F,isObj} = require('./types/basics');
3
3
 
4
4
  /**
5
5
  * A super simple singleton wrapper around the Javascript console.
@@ -36,7 +36,21 @@ exports = module.exports =
36
36
  */
37
37
  extend(target, opts)
38
38
  {
39
- exports.register(target, opts);
39
+ exports.register([target], opts);
40
40
  return target;
41
41
  },
42
+
43
+ /**
44
+ * Apply the compatibility version of the observable API;
45
+ * will load the events/observable sub-module on demand.
46
+ * @param {object} target
47
+ * @param {object} [opts]
48
+ * @returns {object} `target`
49
+ * @see module:@lumjs/core/events/observable.makeObservable
50
+ */
51
+ observable(target, opts)
52
+ {
53
+ const {makeObservable} = require('./observable');
54
+ return makeObservable(target, opts);
55
+ },
42
56
  }
@@ -77,6 +77,12 @@ class LumEventListener
77
77
  this.registry = registry;
78
78
  this.options = makeOpts(spec);
79
79
  this.eventNames = registry.getEventNames(spec.eventNames);
80
+
81
+ const setup = this.options.setupListener ?? registry.options.setupListener;
82
+ if (typeof setup === F)
83
+ {
84
+ setup.call(registry, this);
85
+ }
80
86
  }
81
87
 
82
88
  /**
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Observable Compatibility API
3
+ * @module @lumjs/core/events/observable
4
+ */
5
+ "use strict";
6
+
7
+ const {B,F,S,def} = require('../types');
8
+ const lock = Object.freeze;
9
+ const copy = Object.assign;
10
+ const Registry = require('./registry');
11
+
12
+ const ISOB = 'isObservable';
13
+
14
+ const OBS_EXTENDS =
15
+ {
16
+ registry: '$events',
17
+ listen: 'on',
18
+ emit: 'trigger',
19
+ remove: 'off',
20
+ once: 'one',
21
+ }
22
+
23
+ const OBS_OPTIONS =
24
+ {
25
+ wrapargs: false,
26
+ wrapthis: false,
27
+ wraplock: true,
28
+ wrapsetup: null,
29
+ addme: null,
30
+ // addname: !(wrapargs || wrapthis)
31
+ // addis: (wrapargs || wrapthis)
32
+ }
33
+
34
+ /**
35
+ * A version of the observable API using the events module.
36
+ *
37
+ * It's obviously not going to be 100% identical due to
38
+ * the very different nature of the backend engine, but it'll
39
+ * try its best to make existing code work.
40
+ *
41
+ * A few obscure features that I don't think are particularly
42
+ * important and won't affect *most* exising uses will be dropped.
43
+ *
44
+ * @param {(object|function)} el - The target we're making observable
45
+ * @param {object} [oo]
46
+ * {@link module:@lumjs/core/observable Observable} options;
47
+ *
48
+ * The `addre` and `reinherit` options are not supported.
49
+ * All the other ones should try to work mostly the same.
50
+ *
51
+ * @param {boolean} [oo.addwrap] If this is `true`,
52
+ * and `oo.wrapargs` is `false`, then the `event` object
53
+ * will be appended to the arguments sent to the handler.
54
+ *
55
+ * The default value is: `!oo.wrapthis`
56
+ *
57
+ * @param {object} [ro]
58
+ * {@link module:@lumjs/core/events.Registry Registry} options;
59
+ *
60
+ * The `setupEvent` and `setupListener` options cannot be specified,
61
+ * as the versions from this module will always be used.
62
+ *
63
+ * The `extend` defaults are changed to match those used
64
+ * by the observable API (`on`,`trigger`,`off`,`one`),
65
+ * and `extend.registry` is set to `$events` by default.
66
+ *
67
+ * Setting the `extend` option to `false` will have no effect
68
+ * in this implementation.
69
+ *
70
+ * @param {boolean} [wantReg=false] Return the Registry?
71
+ * @returns {object} normally `el`, unless `wantReg` was `true`
72
+ * @alias module:@lumjs/core/events/observable.makeObservable
73
+ */
74
+ function makeObservable(el, oo, ro, wantReg=false)
75
+ {
76
+ ro = copy({}, ro);
77
+ ro.extend = copy({}, OBS_EXTENDS, ro.extend);
78
+ ro.setupEvent = setupEvent;
79
+ ro.setupListener = setupListener;
80
+ oo = ro.observable = copy({}, OBS_OPTIONS, oo);
81
+
82
+ let wrapped = (oo.wrapargs || oo.wrapthis);
83
+ if (!wrapped && typeof oo.wrapsetup === F)
84
+ {
85
+ wrapped = oo.wrapargs = true;
86
+ }
87
+
88
+ if (typeof oo.wildcard === S)
89
+ { // oo.wildcard takes precedence
90
+ ro.wildcard = oo.wildcard;
91
+ }
92
+
93
+ const setIf = (opt, get) =>
94
+ {
95
+ if (typeof oo[opt] !== B)
96
+ {
97
+ oo[opt] = get();
98
+ }
99
+ }
100
+
101
+ setIf('addname', () => !wrapped);
102
+ setIf('addis', () => wrapped);
103
+ setIf('addwrap', () => !oo.wrapthis);
104
+
105
+ const reg = new Registry([el], ro);
106
+
107
+ if (oo.addis)
108
+ {
109
+ def(el, ISOB, lock({event: false, target: true}));
110
+ }
111
+
112
+ if (typeof oo.addme === S)
113
+ {
114
+ def(el, oo.addme, function(el2, oo2, ro2)
115
+ {
116
+ return makeObservable(el2,
117
+ copy({}, oo, oo2),
118
+ copy({}, ro, ro2)
119
+ );
120
+ });
121
+ }
122
+
123
+ return wantReg ? reg : el;
124
+ }
125
+
126
+ /**
127
+ * Event setup for observable compatibility
128
+ * @type {module:@lumjs/core/events~SetupEvent}
129
+ */
130
+ function setupEvent(ev)
131
+ {
132
+ const ro = this.registry.options;
133
+ const oo = ro.observable;
134
+ ev.wildcard = this.eventNames.has(ro.wildcard);
135
+ ev.self = ev.target;
136
+ ev.type = ev.name;
137
+ def(ev, ISOB, lock({event: true, target: false}));
138
+
139
+ if (typeof oo.wrapsetup === F)
140
+ {
141
+ oo.wrapsetup.call(ev.target, ev);
142
+ }
143
+
144
+ if (oo.wraplock)
145
+ {
146
+ lock(ev);
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Listener setup for observable compatibility
152
+ * @type {module:@lumjs/core/events~SetupListener}
153
+ */
154
+ function setupListener(ln)
155
+ {
156
+ const oo = this.options.observable;
157
+ const oh = ln.observableHandler = ln.handler;
158
+ const go = (ev, args) =>
159
+ {
160
+ if (typeof oh === F)
161
+ {
162
+ const thisTarget = oo.wrapthis ? ev : ev.target;
163
+ oh.apply(thisTarget, args);
164
+ }
165
+ else
166
+ {
167
+ oh.handleEvent(...args);
168
+ }
169
+ }
170
+
171
+ if (!oo.wrapargs)
172
+ { // Not wrapping args, we need to wrap the handler.
173
+ const ec = ln.eventNames.size;
174
+ ln.handler = function(ev)
175
+ {
176
+ const args = ev.args;
177
+ if (ec > 1 && oo.addname)
178
+ {
179
+ args.unshift(ev.name);
180
+ }
181
+ if (oo.addwrap)
182
+ {
183
+ args.push(ev);
184
+ }
185
+
186
+ go(ev, args);
187
+ }
188
+ }
189
+ else if (oo.wrapthis)
190
+ { // Both wrapargs and wrapthis are in use, woah!
191
+ ln.handler = function(ev)
192
+ {
193
+ go(ev, [ev]);
194
+ }
195
+ }
196
+ }
197
+
198
+ module.exports =
199
+ {
200
+ makeObservable, setupEvent, setupListener,
201
+ }
@@ -95,6 +95,9 @@ class LumEventRegistry
95
95
  * If this is a `function`, it will be called to dynamically get a
96
96
  * list of target objects whenever an event is triggered.
97
97
  *
98
+ * If you want to use a `function` as an actual target, you'll need to
99
+ * wrap it in an array or other iterable object.
100
+ *
98
101
  * @param {object} [opts] Options (saved to `options` property).
99
102
  *
100
103
  * @param {(RegExp|string)} [opts.delimiter=/\s+/] Used to split event names
@@ -135,12 +138,17 @@ class LumEventRegistry
135
138
  * If `true` then when adding wrapper methods, the properties from
136
139
  * `opts.extend` will replace any existing ones in each target.
137
140
  *
138
- * @param {function} [opts.setupEvent] Initialize each Event object?
141
+ * @param {module:@lumjs/core/events~SetupEvent} [opts.setupEvent]
139
142
  *
140
143
  * If this is specified (either here or in individual listeners),
141
144
  * it will be called and passed the Event object at the very end of
142
145
  * its constructor.
143
146
  *
147
+ * @param {module:@lumjs/core/events~SetupListener} [opts.setupListener]
148
+ *
149
+ * If this is specified, it will be called and passed the Listener
150
+ * object at the very end of its constructor.
151
+ *
144
152
  * @param {string} [opts.wildcard='*'] Wildcard event name.
145
153
  *
146
154
  * - If you use this in `listen()` the handler will be used regardless
@@ -248,7 +256,7 @@ class LumEventRegistry
248
256
  * it will be used as the `spec`, and the `spec.eventNames`
249
257
  * and `spec.listener` properties will become mandatory.
250
258
  *
251
- * If it's a string or there is more than one argument, this
259
+ * If it's a string *OR* there is more than one argument, this
252
260
  * will be used as the `spec.eventNames` property.
253
261
  *
254
262
  * @param {module:@lumjs/core/events~Handler} [handler]
@@ -277,7 +285,8 @@ class LumEventRegistry
277
285
  * names `listener`, `handler` or `eventNames` as option properties,
278
286
  * and if found, they will be removed.
279
287
  *
280
- * You may also override the `setupEvent` registry option here.
288
+ * You may also override the `setupEvent` and `setupListener` registry
289
+ * options here if needed.
281
290
  *
282
291
  * @param {boolean} [spec.options.once=false] Only use the listener once?
283
292
  *
package/lib/obj/cp.js CHANGED
@@ -1235,7 +1235,7 @@ cp.from = function()
1235
1235
  * @param {?object} [opts] Options
1236
1236
  *
1237
1237
  * If this is an `object`, the clone call will be:
1238
- * `cp.with(opts).into(obj).ow.clone();`
1238
+ * `cp.with(opts).from(obj).ow.clone();`
1239
1239
  *
1240
1240
  * If this is `null` or `undefined`, the call will be:
1241
1241
  * `cp(obj);`
@@ -1251,7 +1251,7 @@ cp.clone = function(obj, opts)
1251
1251
  }
1252
1252
  else
1253
1253
  {
1254
- return cp.with(opts).into(obj).ow.clone();
1254
+ return cp.with(opts).from(obj).ow.clone();
1255
1255
  }
1256
1256
  }
1257
1257
 
package/lib/observable.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Defining these after observable.
2
2
  const {B,F,S,def,isObj,isComplex,TYPES} = require('./types');
3
- const {duplicateAll: clone} = require('./obj/copyall');
4
3
  const lock = Object.freeze;
4
+ const copy = Object.assign;
5
5
 
6
6
  /**
7
7
  * Make a target object (or function) support the *Observable* API.
@@ -346,9 +346,9 @@ function observable (el={}, opts={}, redefine=false)
346
346
 
347
347
  if (addme)
348
348
  { // Add a wrapper for observable() that sets new default options.
349
- add(addme, function (obj=null, mopts={})
349
+ add(addme, function (obj=null, mopts)
350
350
  {
351
- return observable(obj, clone(opts, mopts));
351
+ return observable(obj, copy({}, opts, mopts));
352
352
  });
353
353
  }
354
354
 
@@ -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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.33.0",
3
+ "version": "1.34.0",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {
@@ -10,6 +10,7 @@
10
10
  "./context": "./lib/context.js",
11
11
  "./enum": "./lib/enum.js",
12
12
  "./events": "./lib/events/index.js",
13
+ "./events/observable": "./lib/events/observable.js",
13
14
  "./flags": "./lib/flags.js",
14
15
  "./maps": "./lib/maps.js",
15
16
  "./meta": "./lib/meta.js",