@lumjs/core 1.32.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 +1 -1
- package/lib/events/index.js +15 -1
- package/lib/events/listener.js +6 -0
- package/lib/events/observable.js +201 -0
- package/lib/events/registry.js +12 -3
- package/lib/index.js +18 -153
- package/lib/meta.js +9 -2
- package/lib/obj/cp.js +16 -17
- package/lib/obj/ns.js +43 -8
- package/lib/observable.js +3 -3
- package/lib/state.js +121 -8
- package/lib/types/index.js +1 -1
- package/lib/types/stringify.js +249 -90
- package/package.json +5 -2
package/lib/console.js
CHANGED
package/lib/events/index.js
CHANGED
|
@@ -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
|
}
|
package/lib/events/listener.js
CHANGED
|
@@ -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
|
+
}
|
package/lib/events/registry.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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`
|
|
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/index.js
CHANGED
|
@@ -10,100 +10,7 @@
|
|
|
10
10
|
* @module @lumjs/core
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
* Constants and functions for type checks
|
|
15
|
-
* and other fundamental functions
|
|
16
|
-
*
|
|
17
|
-
* @alias module:@lumjs/core.types
|
|
18
|
-
* @see module:@lumjs/core/types
|
|
19
|
-
*/
|
|
20
|
-
const types = require('./types');
|
|
21
|
-
|
|
22
|
-
/**
|
|
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
|
-
const def = types.def;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Define *lazy* properties on an object or function
|
|
32
|
-
* @alias module:@lumjs/core.lazy
|
|
33
|
-
* @function
|
|
34
|
-
* @see module:@lumjs/core/types.lazy
|
|
35
|
-
*/
|
|
36
|
-
const lazy = types.lazy;
|
|
37
|
-
|
|
38
|
-
def(exports, 'types', types);
|
|
39
|
-
def(exports, 'def', def);
|
|
40
|
-
def(exports, 'lazy', lazy);
|
|
41
|
-
|
|
42
|
-
// TODO: document
|
|
43
|
-
def(exports, 'state', require('./state'));
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Array utility functions «Lazy»
|
|
47
|
-
* @name module:@lumjs/core.arrays
|
|
48
|
-
* @type {module:@lumjs/core/arrays}
|
|
49
|
-
*/
|
|
50
|
-
lazy(exports, 'arrays', () => require('./arrays'));
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Map utility functions «Lazy»
|
|
54
|
-
* @name module:@lumjs/core.maps
|
|
55
|
-
* @type {module:@lumjs/core/maps}
|
|
56
|
-
*/
|
|
57
|
-
lazy(exports, 'maps', () => require('./maps'));
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Information about the JS context we're running in
|
|
61
|
-
* @name module:@lumjs/core.context
|
|
62
|
-
* @type {module:@lumjs/core/context}
|
|
63
|
-
*/
|
|
64
|
-
def(exports, 'context', require('./context'));
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Functions for working with strings and locales «Lazy»
|
|
68
|
-
* @name module:@lumjs/core.strings
|
|
69
|
-
* @type {module:@lumjs/core/strings}
|
|
70
|
-
*/
|
|
71
|
-
lazy(exports, 'strings', () => require('./strings'));
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Functions for working with binary flags «Lazy»
|
|
75
|
-
* @name module:@lumjs/core.flags
|
|
76
|
-
* @type {module:@lumjs/core/flags}
|
|
77
|
-
*/
|
|
78
|
-
lazy(exports, 'flags', () => require('./flags'));
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Functions for manipulating objects «Lazy»
|
|
82
|
-
* @name module:@lumjs/core.obj
|
|
83
|
-
* @type {module:@lumjs/core/obj}
|
|
84
|
-
*/
|
|
85
|
-
lazy(exports, 'obj', () => require('./obj'));
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* A wrapper around the Javascript console «Lazy»
|
|
89
|
-
* @name module:@lumjs/core.console
|
|
90
|
-
* @type {module:@lumjs/core/console}
|
|
91
|
-
*/
|
|
92
|
-
lazy(exports, 'console', () => require('./console'));
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* A simplistic implementation of class/object traits «Lazy»
|
|
96
|
-
* @name module:@lumjs/core.traits
|
|
97
|
-
* @type {module:@lumjs/core/traits}
|
|
98
|
-
*/
|
|
99
|
-
lazy(exports, 'traits', () => require('./traits'));
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Functions for getting values and properties with fallback defaults «Lazy»
|
|
103
|
-
* @name module:@lumjs/core.opt
|
|
104
|
-
* @type {module:@lumjs/core/opt}
|
|
105
|
-
*/
|
|
106
|
-
lazy(exports, 'opt', () => require('./opt'), {def:{autoDesc: false}});
|
|
13
|
+
/// see also: `docs/src/index.js`
|
|
107
14
|
|
|
108
15
|
// Get a bunch of properties from a submodule.
|
|
109
16
|
function from(submod)
|
|
@@ -114,49 +21,18 @@ function from(submod)
|
|
|
114
21
|
}
|
|
115
22
|
}
|
|
116
23
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Get a simplistic debugging stacktrace
|
|
122
|
-
* @name module:@lumjs/core.stacktrace
|
|
123
|
-
* @function
|
|
124
|
-
* @see module:@lumjs/core/meta.stacktrace
|
|
125
|
-
*/
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* A simple base class for making *abstract* classes
|
|
129
|
-
* @name module:@lumjs/core.AbstractClass
|
|
130
|
-
* @see module:@lumjs/core/meta.AbstractClass
|
|
131
|
-
*/
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* A *factory* for special types of JS `function` constructors
|
|
135
|
-
* @name module:@lumjs/core.Functions
|
|
136
|
-
* @see module:@lumjs/core/meta.Functions
|
|
137
|
-
*/
|
|
24
|
+
const types = require('./types');
|
|
25
|
+
const {def,lazy} = types;
|
|
138
26
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
* @function
|
|
143
|
-
* @see module:@lumjs/core/meta.NYI
|
|
144
|
-
*/
|
|
27
|
+
def(exports, 'types', types);
|
|
28
|
+
def(exports, 'def', def);
|
|
29
|
+
def(exports, 'lazy', lazy);
|
|
145
30
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
* @name module:@lumjs/core.deprecated
|
|
149
|
-
* @function
|
|
150
|
-
* @see module:@lumjs/core/meta.deprecated
|
|
151
|
-
*/
|
|
31
|
+
def(exports, 'context', require('./context'));
|
|
32
|
+
def(exports, 'state', {value: require('./state')});
|
|
152
33
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
* function with a specific message before returning the proxied value.
|
|
156
|
-
* @name module:@lumjs/core.wrapDepr
|
|
157
|
-
* @function
|
|
158
|
-
* @see module:@lumjs/core/meta.wrapDepre
|
|
159
|
-
*/
|
|
34
|
+
// ObjectID stuff is imported directly without registering a sub-module.
|
|
35
|
+
from(require('./objectid'));
|
|
160
36
|
|
|
161
37
|
// These are exported directly, but a meta sub-module also exists.
|
|
162
38
|
// Unlike most sub-modules there is no `meta` property in the main library.
|
|
@@ -167,25 +43,14 @@ def(exports, 'AbstractClass',
|
|
|
167
43
|
get() { return meta.AbstractClass; }
|
|
168
44
|
});
|
|
169
45
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
46
|
+
lazy(exports, 'arrays', () => require('./arrays'));
|
|
47
|
+
lazy(exports, 'maps', () => require('./maps'));
|
|
48
|
+
lazy(exports, 'strings', () => require('./strings'));
|
|
49
|
+
lazy(exports, 'flags', () => require('./flags'));
|
|
50
|
+
lazy(exports, 'obj', () => require('./obj'));
|
|
51
|
+
lazy(exports, 'console', () => require('./console'));
|
|
52
|
+
lazy(exports, 'traits', () => require('./traits'));
|
|
53
|
+
lazy(exports, 'opt', () => require('./opt'), {def:{autoDesc: false}});
|
|
176
54
|
lazy(exports, 'Enum', () => require('./enum'));
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Make an object support the *Observable* API «Lazy»
|
|
180
|
-
* @name module:@lumjs/core.observable
|
|
181
|
-
* @function
|
|
182
|
-
* @see module:@lumjs/core/observable
|
|
183
|
-
*/
|
|
184
55
|
lazy(exports, 'observable', () => require('./observable'));
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* The Events module «Lazy»
|
|
188
|
-
* @name module:@lumjs/core.events
|
|
189
|
-
* @see module:@lumjs/core/events
|
|
190
|
-
*/
|
|
191
56
|
lazy(exports, 'events', () => require('./events'));
|
package/lib/meta.js
CHANGED
|
@@ -135,7 +135,11 @@ function NYI(fatal=true, prefix='')
|
|
|
135
135
|
exports.NYI = NYI;
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
|
-
* Send a deprecation message
|
|
138
|
+
* Send a deprecation message to the console.
|
|
139
|
+
*
|
|
140
|
+
* Will default to using `console.warn()` to send the message,
|
|
141
|
+
* unless `core.state.get('core.traceDeprecated')` evaluates to true,
|
|
142
|
+
* in which case it will use `console.trace()` instead.
|
|
139
143
|
*
|
|
140
144
|
* @param {string} dep - Name of what is deprecated
|
|
141
145
|
* @param {(string|string[])} [rep] Replacement suggestion(s).
|
|
@@ -145,12 +149,15 @@ exports.NYI = NYI;
|
|
|
145
149
|
*/
|
|
146
150
|
function deprecated(dep, rep, ret)
|
|
147
151
|
{
|
|
152
|
+
const fn = require('./state').get('core.traceDeprecated')
|
|
153
|
+
? 'trace'
|
|
154
|
+
: 'warn';
|
|
148
155
|
const msgs = [dep,'is deprecated'];
|
|
149
156
|
if (rep)
|
|
150
157
|
{
|
|
151
158
|
msgs.push('replace with', rep);
|
|
152
159
|
}
|
|
153
|
-
console
|
|
160
|
+
console[fn](...msgs);
|
|
154
161
|
return ret;
|
|
155
162
|
}
|
|
156
163
|
|
package/lib/obj/cp.js
CHANGED
|
@@ -440,7 +440,7 @@ function cpArgs(subject, ...sources)
|
|
|
440
440
|
} // cpArgs()
|
|
441
441
|
|
|
442
442
|
/**
|
|
443
|
-
* Copy enumerable properties into a subject.
|
|
443
|
+
* Copy enumerable properties into a subject.
|
|
444
444
|
*
|
|
445
445
|
* This version overwrites any existing properties, with latter sources
|
|
446
446
|
* taking precedence over earlier ones.
|
|
@@ -448,6 +448,18 @@ function cpArgs(subject, ...sources)
|
|
|
448
448
|
* See {@link module:@lumjs/core/obj/cp.safe cp.safe()} for a version that
|
|
449
449
|
* only copies non-existent properties.
|
|
450
450
|
*
|
|
451
|
+
* **Note**: this is the actual `obj.cp` default export value!
|
|
452
|
+
*
|
|
453
|
+
* To keep JSDoc happy it is listed as a _named export_ inside
|
|
454
|
+
* the `cp` module, but in reality, it *is* the module.
|
|
455
|
+
*
|
|
456
|
+
* So when you do: `const cp = require('@lumjs/core/obj/cp');`
|
|
457
|
+
* or `const core = require('@lumjs/core), {cp} = core.obj;`
|
|
458
|
+
* the `cp` variable will be this function. All the other named
|
|
459
|
+
* exports are properties attached to the function object itself.
|
|
460
|
+
*
|
|
461
|
+
* The named export *does* exist as an alias to itself (`cp.cp = cp`).
|
|
462
|
+
*
|
|
451
463
|
* @param {object} subject - Subject of the copy operation
|
|
452
464
|
*
|
|
453
465
|
* If this is a {@link module:@lumjs/core/obj/cp.HandlerSet HandlerSet},
|
|
@@ -475,20 +487,7 @@ function cpArgs(subject, ...sources)
|
|
|
475
487
|
*
|
|
476
488
|
* @returns {object} Either `subject` or a clone of `subject`.
|
|
477
489
|
*
|
|
478
|
-
* @alias module:@lumjs/core/obj/cp.cp
|
|
479
|
-
*
|
|
480
|
-
* **Note**: this is the actual `obj.cp` default export value!
|
|
481
|
-
*
|
|
482
|
-
* To keep JSDoc happy it is listed as a _named export_ inside
|
|
483
|
-
* the `cp` module, but in reality, it *is* the module.
|
|
484
|
-
*
|
|
485
|
-
* So when you do: `const cp = require('@lumjs/core/obj/cp');`
|
|
486
|
-
* or `const core = require('@lumjs/core), {cp} = core.obj;`
|
|
487
|
-
* the `cp` variable will be this function. All the other named
|
|
488
|
-
* exports are properties attached to the function object itself.
|
|
489
|
-
*
|
|
490
|
-
* The named export *does* exist as an alias to itself (`cp.cp = cp`).
|
|
491
|
-
*
|
|
490
|
+
* @alias module:@lumjs/core/obj/cp.cp
|
|
492
491
|
*/
|
|
493
492
|
function cp()
|
|
494
493
|
{
|
|
@@ -1236,7 +1235,7 @@ cp.from = function()
|
|
|
1236
1235
|
* @param {?object} [opts] Options
|
|
1237
1236
|
*
|
|
1238
1237
|
* If this is an `object`, the clone call will be:
|
|
1239
|
-
* `cp.with(opts).
|
|
1238
|
+
* `cp.with(opts).from(obj).ow.clone();`
|
|
1240
1239
|
*
|
|
1241
1240
|
* If this is `null` or `undefined`, the call will be:
|
|
1242
1241
|
* `cp(obj);`
|
|
@@ -1252,7 +1251,7 @@ cp.clone = function(obj, opts)
|
|
|
1252
1251
|
}
|
|
1253
1252
|
else
|
|
1254
1253
|
{
|
|
1255
|
-
return cp.with(opts).
|
|
1254
|
+
return cp.with(opts).from(obj).ow.clone();
|
|
1256
1255
|
}
|
|
1257
1256
|
}
|
|
1258
1257
|
|
package/lib/obj/ns.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const
|
|
3
3
|
{
|
|
4
4
|
B, S,
|
|
5
|
-
root, isObj, needObj, def, nonEmptyArray, notNil,
|
|
5
|
+
root, isObj, needObj, def, nonEmptyArray, isNil, notNil,
|
|
6
6
|
doesDescriptorTemplate,
|
|
7
7
|
} = require('../types');
|
|
8
8
|
|
|
@@ -152,20 +152,20 @@ function setObjectPath(obj, proppath, opts={})
|
|
|
152
152
|
proppath = nsArray(proppath);
|
|
153
153
|
|
|
154
154
|
let assign;
|
|
155
|
-
if (
|
|
155
|
+
if (opts.assign)
|
|
156
|
+
{ // Use direct property assignment.
|
|
157
|
+
assign = (o,p,v={}) => o[p] = v;
|
|
158
|
+
}
|
|
159
|
+
else if (doesDescriptorTemplate(opts.desc))
|
|
156
160
|
{ // An explicit descriptor.
|
|
157
161
|
assign = (o,p,v={}) =>
|
|
158
162
|
{
|
|
159
|
-
const desc =
|
|
163
|
+
const desc = Object.assign({}, opts.desc);
|
|
160
164
|
desc.value = v;
|
|
161
165
|
def(o,p,desc);
|
|
162
166
|
}
|
|
163
167
|
}
|
|
164
|
-
else
|
|
165
|
-
{ // Use direct property assignment.
|
|
166
|
-
assign = (o,p,v={}) => o[p] = v;
|
|
167
|
-
}
|
|
168
|
-
else
|
|
168
|
+
else
|
|
169
169
|
{ // Use def with default descriptor.
|
|
170
170
|
assign = (o,p,v={}) => def(o,p,v);
|
|
171
171
|
}
|
|
@@ -216,6 +216,41 @@ function setObjectPath(obj, proppath, opts={})
|
|
|
216
216
|
|
|
217
217
|
exports.setObjectPath = setObjectPath;
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Delete a nested property from an object.
|
|
221
|
+
*
|
|
222
|
+
* @param {(object|function)} obj - Object we're operating on.
|
|
223
|
+
* @param {(string|Array)} proppath - Property path we want to remove.
|
|
224
|
+
*
|
|
225
|
+
* Generally a string of dot (`.`) separated nested property names.
|
|
226
|
+
*
|
|
227
|
+
* The last property name in the path will be the one that is deleted
|
|
228
|
+
* from its parent object.
|
|
229
|
+
*
|
|
230
|
+
* @param {object} [opts] Options for `getObjectPath()`
|
|
231
|
+
* @returns {*} The value that was removed; or `undefined` if the
|
|
232
|
+
* proppath didn't resolve to a defined value.
|
|
233
|
+
*/
|
|
234
|
+
function delObjectPath(obj, proppath, opts={})
|
|
235
|
+
{
|
|
236
|
+
proppath = nsArray(proppath);
|
|
237
|
+
const rmProp = proppath.pop();
|
|
238
|
+
const rmFrom = proppath.length
|
|
239
|
+
? getObjectPath(obj, proppath, opts)
|
|
240
|
+
: obj;
|
|
241
|
+
|
|
242
|
+
if (isNil(rmFrom))
|
|
243
|
+
{ // Nothing to remove from
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const removed = rmFrom[rmProp];
|
|
248
|
+
delete rmFrom[rmProp];
|
|
249
|
+
return removed;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
exports.delObjectPath = delObjectPath;
|
|
253
|
+
|
|
219
254
|
/**
|
|
220
255
|
* Get a global namespace path if it exists.
|
|
221
256
|
*
|
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,
|
|
351
|
+
return observable(obj, copy({}, opts, mopts));
|
|
352
352
|
});
|
|
353
353
|
}
|
|
354
354
|
|
package/lib/state.js
CHANGED
|
@@ -2,15 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
const {S,isObj} = require('./types')
|
|
4
4
|
const ctx = require('./context')
|
|
5
|
+
const ns = require('./obj/ns');
|
|
6
|
+
const cp = require('./obj/cp');
|
|
5
7
|
const stateSym = Symbol('@lumjs/core/state')
|
|
6
8
|
const stateData = {[stateSym]: false}
|
|
7
9
|
const stateKey = 'LUM_JS_STATE'
|
|
8
10
|
const stateOpts = {}
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
function getOpts(localOpts)
|
|
11
13
|
{
|
|
12
|
-
|
|
14
|
+
return Object.assign({}, stateOpts, localOpts);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A simple persistent state helper object.
|
|
19
|
+
*
|
|
20
|
+
* Will use `localStorage` in a browser environment,
|
|
21
|
+
* and `process.env` if running in Node.js.
|
|
22
|
+
*
|
|
23
|
+
* In both cases the key `LUM_JS_STATE` is used,
|
|
24
|
+
* and the value must be valid JSON data.
|
|
25
|
+
*
|
|
26
|
+
* @exports module:@lumjs/core/state
|
|
27
|
+
*/
|
|
28
|
+
exports = module.exports =
|
|
29
|
+
{
|
|
30
|
+
/**
|
|
31
|
+
* Load (retrieve) state data
|
|
32
|
+
* @param {object} [opts] Options
|
|
33
|
+
* @param {boolean} [opts.refresh=false] Refresh data from storage?
|
|
34
|
+
*
|
|
35
|
+
* If `true` this will force the data to be reloaded from storage.
|
|
36
|
+
* If `false` previously loaded data will be returned as is.
|
|
37
|
+
*
|
|
38
|
+
* @param {function} [opts.jsonRevive] JSON.parse() reviver
|
|
39
|
+
*
|
|
40
|
+
* @returns {object}
|
|
41
|
+
*/
|
|
42
|
+
load(opts)
|
|
13
43
|
{
|
|
44
|
+
opts = getOpts(opts);
|
|
45
|
+
|
|
14
46
|
if (opts.refresh || !stateData[stateSym])
|
|
15
47
|
{
|
|
16
48
|
let json;
|
|
@@ -25,24 +57,44 @@ module.exports =
|
|
|
25
57
|
|
|
26
58
|
if (typeof json === S)
|
|
27
59
|
{
|
|
28
|
-
const revive = opts.jsonRevive
|
|
60
|
+
const revive = opts.jsonRevive;
|
|
29
61
|
const storedData = JSON.parse(json, revive);
|
|
30
62
|
if (isObj(storedData))
|
|
31
63
|
{
|
|
32
64
|
Object.assign(stateData, storedData);
|
|
33
|
-
stateData[stateSym] = true;
|
|
34
65
|
}
|
|
35
66
|
}
|
|
67
|
+
|
|
68
|
+
stateData[stateSym] = true;
|
|
36
69
|
}
|
|
70
|
+
|
|
37
71
|
return stateData;
|
|
38
72
|
},
|
|
39
73
|
|
|
40
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Save current state data into persistent storage.
|
|
76
|
+
*
|
|
77
|
+
* In a web browser environment, this will save the data
|
|
78
|
+
* into the `localStorage` global object directly.
|
|
79
|
+
*
|
|
80
|
+
* In Node.js while this updates the `process.env` object,
|
|
81
|
+
* there's no way to export to the environment in a truly
|
|
82
|
+
* persistent way. So this also will output a shell statement
|
|
83
|
+
* via `console.log()` that can be exported into the environment
|
|
84
|
+
* (or added to a `.env` file, etc.)
|
|
85
|
+
*
|
|
86
|
+
* @param {object} [opts] Options
|
|
87
|
+
* @param {function} [opts.jsonReplace] JSON.stringify() replacer
|
|
88
|
+
* @returns {string} The JSON data
|
|
89
|
+
*/
|
|
90
|
+
save(opts)
|
|
41
91
|
{
|
|
42
92
|
if (!stateData[stateSym]) return false;
|
|
93
|
+
|
|
94
|
+
opts = getOpts(opts);
|
|
43
95
|
|
|
44
|
-
const replace = opts.jsonReplace
|
|
45
|
-
|
|
96
|
+
const replace = opts.jsonReplace;
|
|
97
|
+
let json = JSON.stringify(stateData, replace);
|
|
46
98
|
|
|
47
99
|
if (ctx.isBrowser)
|
|
48
100
|
{
|
|
@@ -50,10 +102,71 @@ module.exports =
|
|
|
50
102
|
}
|
|
51
103
|
else if (ctx.isNode)
|
|
52
104
|
{
|
|
53
|
-
|
|
105
|
+
process.env[stateKey] = json;
|
|
106
|
+
console.log(`${stateKey}='${json.replaceAll("'", "'\"'\"'")}'`);
|
|
54
107
|
}
|
|
108
|
+
|
|
109
|
+
return json;
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get a property value from the state data via a namespace path
|
|
114
|
+
* @param {(string|string[])} path - Namespace path to get
|
|
115
|
+
* @param {object} [opts] Options for `load()` and `getObjectPath()`
|
|
116
|
+
* @returns {*} The property value (or `undefined` if not found)
|
|
117
|
+
* @see module:@lumjs/core/obj.getObjectPath
|
|
118
|
+
*/
|
|
119
|
+
get(path, opts)
|
|
120
|
+
{
|
|
121
|
+
const data = exports.load(opts);
|
|
122
|
+
return ns.getObjectPath(data, path, opts);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Set a (nested) property value in the state data
|
|
127
|
+
* @param {(string|string[])} path - Namespace path to set
|
|
128
|
+
* @param {*} value - Value to set the property to
|
|
129
|
+
* @param {object} [opts] Options for `load()` and `setObjectPath()`
|
|
130
|
+
*
|
|
131
|
+
* The call to `setObjectPath()` has a few different defaults here:
|
|
132
|
+
* - `opts.assign` defaults to `true`
|
|
133
|
+
* - `opts.overwrite` defaults to `true`
|
|
134
|
+
* - `opts.value` is **forced** to the `value` argument
|
|
135
|
+
*
|
|
136
|
+
* @returns {*}
|
|
137
|
+
* @see module:@lumjs/core/obj.setObjectPath
|
|
138
|
+
*/
|
|
139
|
+
set(path, value, opts)
|
|
140
|
+
{
|
|
141
|
+
const loadOpts = cp({refresh: false}, opts);
|
|
142
|
+
const setOpts = cp({assign: true, overwrite: true}, opts, {value});
|
|
143
|
+
const data = exports.load(loadOpts);
|
|
144
|
+
return ns.setObjectPath(data, path, setOpts);
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get a property value from the state data via a namespace path
|
|
149
|
+
* @param {(string|string[])} path - Namespace path to delete
|
|
150
|
+
* @param {object} [opts] Options for `load()` and `delObjectPath()`
|
|
151
|
+
* @returns {*} The property value (or `undefined` if not found)
|
|
152
|
+
* @see module:@lumjs/core/obj.delObjectPath
|
|
153
|
+
*/
|
|
154
|
+
del(path, opts)
|
|
155
|
+
{
|
|
156
|
+
const data = exports.load(cp({refresh: false}, opts));
|
|
157
|
+
return ns.delObjectPath(data, path, opts);
|
|
55
158
|
},
|
|
56
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Default options for `load()` and `save()` methods.
|
|
162
|
+
*
|
|
163
|
+
* Any of the options supported by those methods can be set here.
|
|
164
|
+
*
|
|
165
|
+
* The `refresh` option will be ignored by the `set()` and `del()`
|
|
166
|
+
* methods if it's specified here.
|
|
167
|
+
*
|
|
168
|
+
* @type {object}
|
|
169
|
+
*/
|
|
57
170
|
opts: stateOpts,
|
|
58
171
|
|
|
59
172
|
[stateSym]: stateData,
|
package/lib/types/index.js
CHANGED
|
@@ -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
|
);
|
package/lib/types/stringify.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
14
|
-
*
|
|
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
|
-
*
|
|
22
|
-
*
|
|
75
|
+
* - `RegExp`
|
|
76
|
+
* - `Object`
|
|
23
77
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
78
|
+
* Any other JS value types (`number`,`boolean`, `string`, etc.) will
|
|
79
|
+
* be serialized using JSON.
|
|
26
80
|
*
|
|
27
|
-
* @
|
|
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
|
-
|
|
83
|
+
class Stringify
|
|
34
84
|
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
|
|
106
|
+
if (opts.fnString)
|
|
107
|
+
{
|
|
108
|
+
this.strTypes.push(F);
|
|
109
|
+
}
|
|
48
110
|
|
|
49
|
-
|
|
50
|
-
|
|
111
|
+
if (opts.errString)
|
|
112
|
+
{
|
|
113
|
+
this.strClass.push(Error);
|
|
114
|
+
}
|
|
51
115
|
|
|
52
|
-
|
|
53
|
-
|
|
116
|
+
if (Array.isArray(opts.setCustom))
|
|
117
|
+
{
|
|
118
|
+
this.customFn = opts.setCustom.slice();
|
|
119
|
+
}
|
|
120
|
+
else if (typeof opts.setCustom === F)
|
|
54
121
|
{
|
|
55
|
-
|
|
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
|
-
|
|
134
|
+
opts.addCustom.call(this, this.customFn);
|
|
58
135
|
}
|
|
59
136
|
}
|
|
60
137
|
|
|
61
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
{
|
|
74
|
-
|
|
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
|
-
|
|
165
|
+
|
|
166
|
+
// A few types we simply stringify right now.
|
|
167
|
+
if (this.strTypes.includes(whatType))
|
|
78
168
|
{
|
|
79
|
-
return
|
|
169
|
+
return strFor(what.toString());
|
|
80
170
|
}
|
|
81
171
|
|
|
82
|
-
if (what
|
|
83
|
-
{
|
|
84
|
-
return
|
|
172
|
+
if (!this.options.fnString && typeof what === F)
|
|
173
|
+
{ // Simple format for functions
|
|
174
|
+
return strFor(what.name+'()');
|
|
85
175
|
}
|
|
86
176
|
|
|
87
|
-
if (
|
|
88
|
-
{ //
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
185
|
+
return strFor(what.toString());
|
|
102
186
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
125
|
-
def(stringify, '$extend',
|
|
126
|
-
function(func, registration=false)
|
|
127
|
-
{
|
|
128
|
-
if (typeof func === TYPES.F)
|
|
278
|
+
static registerCustoms(registerFn)
|
|
129
279
|
{
|
|
130
|
-
if (
|
|
131
|
-
{
|
|
132
|
-
|
|
280
|
+
if (typeof registerFn === F)
|
|
281
|
+
{
|
|
282
|
+
registerFn.call(this, this);
|
|
133
283
|
}
|
|
134
|
-
else
|
|
135
|
-
{
|
|
136
|
-
|
|
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.
|
|
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",
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
"./observable": "./lib/observable.js",
|
|
20
21
|
"./opt": "./lib/opt/index.js",
|
|
21
22
|
"./opt/args": "./lib/opt/args.js",
|
|
23
|
+
"./state": "./lib/state.js",
|
|
22
24
|
"./strings": "./lib/strings.js",
|
|
23
25
|
"./traits": "./lib/traits.js",
|
|
24
26
|
"./types": "./lib/types/index.js",
|
|
@@ -40,7 +42,8 @@
|
|
|
40
42
|
"test": "lumtest.js",
|
|
41
43
|
"build-meta": "lum-build",
|
|
42
44
|
"build-jsdoc": "jsdoc -c ./jsdoc.js",
|
|
43
|
-
"build-docs": "npm run build-meta && npm run build-jsdoc"
|
|
45
|
+
"build-docs": "npm run build-meta && npm run build-jsdoc",
|
|
46
|
+
"build": "npm run build-docs"
|
|
44
47
|
},
|
|
45
48
|
"repository":
|
|
46
49
|
{
|