@lumjs/core 1.0.0-beta.1

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/src/obj/ns.js ADDED
@@ -0,0 +1,194 @@
1
+ // Import required bits here.
2
+ const
3
+ {
4
+ B, root, isObj, needObj, def, nonEmptyArray, notNil
5
+ } = require('../types');
6
+
7
+ /**
8
+ * Internal: need a String or Array
9
+ */
10
+ function SOA(name, err=true)
11
+ {
12
+ const msg = (typeof name === S)
13
+ ? name + ' ' + this.message
14
+ : this.message;
15
+ return err ? (new TypeError(msg)) : msg;
16
+ }
17
+ SOA.message = "must be a string or non-empty array";
18
+ def(SOA, 'toString', function() { return this.message; });
19
+
20
+ exports.SOA = SOA;
21
+
22
+ function nsString(ns, name='Namespace')
23
+ {
24
+ if (nonEmptyArray(ns))
25
+ {
26
+ return ns.join('.');
27
+ }
28
+ else if (typeof ns !== S)
29
+ {
30
+ throw SOA(name);
31
+ }
32
+ return ns;
33
+ }
34
+
35
+ exports.nsString = nsString;
36
+
37
+ function nsArray(ns, name='Namespace')
38
+ {
39
+ if (typeof ns === S)
40
+ {
41
+ return ns.split('.');
42
+ }
43
+ else if (!nonEmptyArray(ns))
44
+ {
45
+ throw SOA(name);
46
+ }
47
+ return ns;
48
+ }
49
+
50
+ exports.nsArray = nsArray;
51
+
52
+ /**
53
+ * Get a (nested) property from an object with a given path.
54
+ *
55
+ * @param {object} obj - Object we're looking in.
56
+ * @param {(string|Array)} proppath - Property path we're looking for.
57
+ * Generally a string of dot (`.`) separated nested property names.
58
+ * @param {object} [opts] TBD.
59
+ * @return {*} The property if found, or `opts.default` if not.
60
+ */
61
+ function getObjectPath(obj, proppath, opts={})
62
+ {
63
+ needObj(obj);
64
+
65
+ if (typeof opts === B)
66
+ opts = {log: opts};
67
+ else if (!isObj(opts))
68
+ opts = {};
69
+
70
+ proppath = nsArray(proppath);
71
+
72
+ for (let p = 0; p < proppath.length; p++)
73
+ {
74
+ const propname = proppath[p];
75
+ if (obj[propname] === undefined)
76
+ { // End of search, sorry.
77
+ if (opts.log)
78
+ {
79
+ console.error("Object property path not found",
80
+ propname, p, proppath, obj);
81
+ }
82
+ return opts.default;
83
+ }
84
+ obj = obj[propname];
85
+ }
86
+
87
+ return obj;
88
+ }
89
+
90
+ exports.getObjectPath = getObjectPath;
91
+
92
+ /**
93
+ * Create a nested property path if it does not exist.
94
+ *
95
+ * @param {object} obj - Object the property path is for.
96
+ * @param {(string|Array)} proppath - Property path to create.
97
+ * @param {object} [opts] TBD.
98
+ * @return {*} Generally the last object in the nested path.
99
+ * However the output may vary depending on the options.
100
+ */
101
+ function setObjectPath(obj, proppath, opts={})
102
+ {
103
+ needObj(obj); needObj(opts);
104
+ proppath = nsArray(proppath);
105
+
106
+ let assign;
107
+ if (isObj(opts.desc))
108
+ { // An explicit descriptor.
109
+ assign = (o,p,v={}) =>
110
+ {
111
+ const desc = clone(opts.desc);
112
+ desc.value = v;
113
+ def(o,p,desc);
114
+ }
115
+ }
116
+ else if (opts.assign)
117
+ { // Use direct property assignment.
118
+ assign = (o,p,v={}) => o[p] = v;
119
+ }
120
+ else
121
+ { // Use def with default descriptor.
122
+ assign = (o,p,v={}) => def(o,p,v);
123
+ }
124
+
125
+ let cns = obj;
126
+ const nsc = proppath.length;
127
+ const lastns = nsc - 1;
128
+
129
+ console.debug("setObjectPath", obj, proppath, opts, nsc, arguments);
130
+
131
+ for (let n = 0; n < nsc; n++)
132
+ {
133
+ const ns = proppath[n];
134
+
135
+ if (cns[ns] === undefined)
136
+ { // Nothing currently here. Let's fix that.
137
+ if (n == lastns && notNil(opts.value))
138
+ { // We're at the end and have a value to assign.
139
+ assign(cns, ns, opts.value);
140
+ }
141
+ else
142
+ { // Create a new empty object.
143
+ assign(cns, ns);
144
+ }
145
+ }
146
+ else if (opts.overwrite && n == lastns && notNil(opts.value))
147
+ { // We have a value, and overwrite mode is on.
148
+ assign(cns, ns, opts.value);
149
+ }
150
+
151
+ cns = cns[ns];
152
+ }
153
+
154
+ if (opts.returnThis)
155
+ {
156
+ return this;
157
+ }
158
+ else if (opts.returnObj)
159
+ {
160
+ return obj;
161
+ }
162
+ else
163
+ { // Default is to return the last namespace object.
164
+ return cns;
165
+ }
166
+ }
167
+
168
+ exports.setObjectPath = setObjectPath;
169
+
170
+ /**
171
+ * Get a global namespace path if it exists.
172
+ *
173
+ * - TODO: document this.
174
+ * - TODO: rewrite `ns.get` to use this behind the scenes.
175
+ */
176
+ function getNamespace(namespaces, opts={})
177
+ {
178
+ return getObjectPath(root, namespaces, opts);
179
+ }
180
+
181
+ exports.getNamespace = getNamespace;
182
+
183
+ /**
184
+ * Create a global namespace path if it does not exist.
185
+ *
186
+ * - TODO: document this.
187
+ * - TODO: rewrite `ns.add` to use this behind the scenes.
188
+ */
189
+ function setNamespace(namespaces, opts={})
190
+ {
191
+ return setObjectPath(root, namespaces, opts);
192
+ }
193
+
194
+ exports.setNamespace = setNamespace;
@@ -0,0 +1,85 @@
1
+
2
+ const {notNil,def,isNil} = require('./types');
3
+
4
+ /**
5
+ * Generate a large random number.
6
+ *
7
+ * Takes advantage of
8
+ *
9
+ * @returns {number}
10
+ */
11
+ function randomNumber()
12
+ {
13
+ return Math.floor(Math.random() * Date.now());
14
+ }
15
+
16
+ exports.randomNumber = randomNumber;
17
+
18
+ /**
19
+ * A class for creating unique identifier objects.
20
+ * Generally only used by my own inernal libraries, thus the name.
21
+ */
22
+ class InternalObjectId
23
+ {
24
+ /**
25
+ * Build a unique InternalObjectId instance.
26
+ *
27
+ * @param {object} opts - Named options to change default behaviours.
28
+ * @param {string} [opts.name] A friendly name for diagnostics.
29
+ * @param {(string|number)} [opts.id] An internal id value.
30
+ * Default value is a large random number.
31
+ * @param {(string|Symbol)} [opts.property] The property name or Symbol.
32
+ * This is the property that will be set on objects that are tagged
33
+ * with this InternalObjectId. Default is `Symbol(this.id)`.
34
+ * @param {boolean} [opts.useInstance=true] Store object or id value?
35
+ * If this is `true` (now the default), we store the instance itself.
36
+ * If this is `false` (the old default), we store just the `id` value.
37
+ *
38
+ */
39
+ constructor(opts={})
40
+ {
41
+ this.name = opts.name;
42
+ this.id = opts.id ?? randomNumber();
43
+ this.property = opts.property ?? Symbol(this.id);
44
+ this.useInstance = opts.useInstance ?? true;
45
+ }
46
+
47
+ /**
48
+ * Tag an object with the ObjectId.
49
+ *
50
+ * @param {*} obj - The object to tag with the unique object id.
51
+ * @returns {*} `obj`
52
+ */
53
+ tag(obj)
54
+ {
55
+ if (isNil(obj)) throw new TypeError("Cannot tag a null or undefined value");
56
+ const val = this.useInstance ? this : this.id;
57
+ return def(obj, this.property, val);
58
+ }
59
+
60
+ /**
61
+ * Is the specified object tagged with this object id?
62
+ *
63
+ * @param {*} obj - The object to test.
64
+ * @returns {boolean} If it's tagged or not.
65
+ */
66
+ is(obj)
67
+ {
68
+ const want = this.useInstance ? this : this.id;
69
+ return (notNil(obj) && obj[this.property] === want);
70
+ }
71
+
72
+ /**
73
+ * Generate a function that calls `instance.is()`
74
+ *
75
+ * @returns {function} The wrapper function.
76
+ */
77
+ isFunction()
78
+ {
79
+ const oid = this;
80
+ return function(obj) { return oid.is(obj); }
81
+ }
82
+
83
+ }
84
+
85
+ exports.InternalObjectId = InternalObjectId;
@@ -0,0 +1,292 @@
1
+ // This library was original based on the observable library from riot.js,
2
+ // but has been refactored and expanded a lot since then.
3
+
4
+ const {B,F,S,def,isObj,isComplex} = require('./types');;
5
+
6
+ /**
7
+ * Make an object support the observable API.
8
+ *
9
+ * Adds `on()`, `off()`, `one()`, and `trigger()` methods.
10
+ *
11
+ * @param {object} el - The object we are making observable.
12
+ * @param {object} [opts] Options that define behaviours.
13
+ * @param {string} [opts.wildcard='*'] The event name used as a wildcard.
14
+ * @param {boolean} [opts.wrapthis=false] If `true`, `this` will be a wrapper.
15
+ *
16
+ * If 'wrapthis' is true, the function will be called with a wrapper object as
17
+ * the 'this' variable instead of the target object. The wrapper will be:
18
+ *
19
+ * ```js
20
+ * {
21
+ * self: el, // The target object.
22
+ * name: event, // The event name that was triggered.
23
+ * wildcard: bool, // Will be true if this was a wildcard event handler.
24
+ * func: function, // The function being called.
25
+ * }
26
+ * ```
27
+ *
28
+ * @param {boolean} [opts.addname=!opts.wrapthis] If `true` callbacks with
29
+ * multiple events will have the name of the triggered event added as
30
+ * the first parameter.
31
+ *
32
+ * @param {boolean} [opts.addis=opts.wrapthis] If `true` add immutable
33
+ * property named `isObservable` which will have a value of `true`.
34
+ * @param {string} [opts.addme] If set, add a method with this name
35
+ * to the object, which is a version of `observable()` with the
36
+ * default options being the same as the current `opts`.
37
+ * If `opts.wrapthis` is `true`, then this defaults to `'makeObservable'`.
38
+ * In any other case it defaults to `null`.
39
+ *
40
+ * @returns {object} el
41
+ */
42
+ function observable (el={}, opts={})
43
+ {
44
+ //console.debug("observable", el, opts);
45
+
46
+ if (!isComplex(el))
47
+ { // Don't know how to handle this, sorry.
48
+ throw new Error("non-object sent to observable()");
49
+ }
50
+
51
+ if (observable.is(el))
52
+ { // It's already observable.
53
+ return el;
54
+ }
55
+
56
+ if (typeof opts === B)
57
+ { // Assume it's the wrapthis option.
58
+ opts = {wrapthis: opts};
59
+ }
60
+ else if (!isObj(opts))
61
+ {
62
+ opts = {};
63
+ }
64
+
65
+ const noSpace = /^\S+$/;
66
+
67
+ const wildcard = (typeof opts.wildcard === S
68
+ && noSpace.test(opts.wildcard))
69
+ ? opts.wildcard
70
+ : '*';
71
+
72
+ const wrapthis = (typeof opts.wrapthis === 'boolean')
73
+ ? opts.wrapthis
74
+ : false;
75
+
76
+ const addname = (typeof opts.addname === 'boolean')
77
+ ? opts.addname
78
+ : !wrapthis;
79
+
80
+ const addis = (typeof opts.addis === 'boolean')
81
+ ? opts.addis
82
+ : wrapthis;
83
+
84
+ const validIdent = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
85
+
86
+ const addme = (typeof opts.addme === 'string'
87
+ && validIdent.test(opts.addme))
88
+ ? opts.addme
89
+ : (wrapthis ? 'makeObservable' : null);
90
+
91
+ const slice = Array.prototype.slice;
92
+
93
+ function onEachEvent (e, fn)
94
+ {
95
+ e.replace(/\S+/g, fn);
96
+ }
97
+
98
+ const add = def(el);
99
+
100
+ function runCallback (name, fn, args)
101
+ {
102
+ if (fn.busy) return;
103
+ fn.busy = 1;
104
+
105
+ let fthis;
106
+
107
+ if (wrapthis)
108
+ {
109
+ const isWild = (name === wildcard);
110
+ const fname = isWild ? (addname ? args[0] : args.shift()) : name;
111
+ fthis =
112
+ {
113
+ self: el,
114
+ name: fname,
115
+ func: fn,
116
+ wildcard: isWild,
117
+ };
118
+ }
119
+ else
120
+ {
121
+ fthis = el;
122
+ }
123
+
124
+ let fargs = (fn.typed && addname) ? [name].concat(args) : args;
125
+
126
+ fn.apply(fthis, fargs);
127
+
128
+ fn.busy = 0;
129
+ }
130
+
131
+ let callbacks = {};
132
+
133
+ /**
134
+ * Assign an event handler
135
+ *
136
+ * Listen to the given space separated list of `events` and execute
137
+ * the `callback` each time an event is triggered.
138
+ * @param {string} events - events ids
139
+ * @param {function} fn - callback function
140
+ * @returns {object} el
141
+ */
142
+ add('on', function(events, fn)
143
+ {
144
+ if (typeof fn !== F)
145
+ {
146
+ console.error("non-function passed to on()");
147
+ return el;
148
+ }
149
+
150
+ onEachEvent(events, function(name, pos)
151
+ {
152
+ (callbacks[name] = callbacks[name] || []).push(fn);
153
+ fn.typed = pos > 0;
154
+ });
155
+
156
+ return el;
157
+ });
158
+
159
+ /**
160
+ * Removes the given space separated list of `events` listeners
161
+ *
162
+ * @param {string} events - events ids
163
+ * @param {function} fn - callback function
164
+ * @returns {object} el
165
+ */
166
+ add('off', function(events, fn)
167
+ {
168
+ if (events === wildcard && !fn)
169
+ { // Clear all callbacks.
170
+ callbacks = {};
171
+ }
172
+ else
173
+ {
174
+ onEachEvent(events, function(name)
175
+ {
176
+ if (fn)
177
+ { // Find a specific callback to remove.
178
+ var arr = callbacks[name]
179
+ for (var i = 0, cb; cb = arr && arr[i]; ++i)
180
+ {
181
+ if (cb == fn) arr.splice(i--, 1);
182
+ }
183
+ }
184
+ else
185
+ { // Remove all callbacks for this event.
186
+ delete callbacks[name];
187
+ }
188
+ });
189
+ }
190
+ return el
191
+ });
192
+
193
+ /**
194
+ * Add a one-shot event handler.
195
+ *
196
+ * Listen to the given space separated list of `events` and execute
197
+ * the `callback` at most once.
198
+ *
199
+ * @param {string} events - events ids
200
+ * @param {function} fn - callback function
201
+ * @returns {object} el
202
+ */
203
+ add('one', function(events, fn)
204
+ {
205
+ function on()
206
+ {
207
+ el.off(events, on)
208
+ fn.apply(el, arguments)
209
+ }
210
+ return el.on(events, on);
211
+ });
212
+
213
+ /**
214
+ * Execute all callback functions that listen to the given space
215
+ * separated list of `events`
216
+ * @param {string} events - events ids
217
+ * @returns {object} el
218
+ */
219
+ add('trigger', function(events)
220
+ {
221
+ // getting the arguments
222
+ // skipping the first one
223
+ const args = slice.call(arguments, 1);
224
+
225
+ onEachEvent(events, function(name)
226
+ {
227
+ const fns = slice.call(callbacks[name] || [], 0);
228
+
229
+ for (var i = 0, fn; fn = fns[i]; ++i)
230
+ {
231
+ runCallback(name, fn, args);
232
+ if (fns[i] !== fn) { i-- }
233
+ }
234
+
235
+ if (callbacks[wildcard] && name != wildcard)
236
+ { // Trigger the wildcard.
237
+ el.trigger.apply(el, ['*', name].concat(args));
238
+ }
239
+
240
+ });
241
+
242
+ return el
243
+ });
244
+
245
+ if (addis)
246
+ {
247
+ add('isObservable', true);
248
+ }
249
+
250
+ if (addme)
251
+ { // Add a wrapper for observable() that sets new default options.
252
+ const ourProps = Object.keys(opts);
253
+ add(addme, function (obj=null, mopts={})
254
+ {
255
+ if (ourProps.length > 0)
256
+ {
257
+ for (const prop of ourProps)
258
+ {
259
+ if (mopts[prop] === undefined)
260
+ {
261
+ mopts[prop] = opts[prop];
262
+ }
263
+ }
264
+ }
265
+ return observable(obj, mopts);
266
+ });
267
+ }
268
+
269
+ return el
270
+
271
+ } // observable()
272
+
273
+ module.exports = observable;
274
+
275
+ /**
276
+ * See if an object appears to be observable.
277
+ *
278
+ * @param {object} obj
279
+ * @returns {boolean}
280
+ */
281
+ function isObservable(obj)
282
+ {
283
+ return (isObj(obj)
284
+ && typeof obj.trigger === F
285
+ && typeof obj.on === F);
286
+ }
287
+
288
+ // Add an 'is()' method to `observable` itself.
289
+ def(observable, 'is', isObservable);
290
+
291
+ // And make it available as `types.doesObservable`
292
+ require('./types').doesObservable = isObservable;
package/src/opt.js ADDED
@@ -0,0 +1,60 @@
1
+ // Simple option handling.
2
+
3
+ const {U,needObj,needType} = require('./types');
4
+
5
+ /**
6
+ * See if a value is *set*, and if not, return a default value.
7
+ *
8
+ * @param {*} opt - The value we are testing.
9
+ * @param {*} defvalue - The default value if opt was null or undefined.
10
+ *
11
+ * @param {boolean} [allowNull=false] If true, allow null to count as *set*.
12
+ * @param {boolean} [isLazy=false] If true, and `defvalue` is a function,
13
+ * use the value from the function as
14
+ * the default.
15
+ * @param {object} [lazyThis=null] If `isLazy` is true, this object will
16
+ * be used as `this` for the function.
17
+ *
18
+ * @return {*} Either the specified `opt` value or the default value.
19
+ */
20
+ function val(opt, defvalue, allowNull=false, isLazy=false, lazyThis=null)
21
+ {
22
+ if (typeof opt === U || (!allowNull && opt === null))
23
+ { // The defined value was not "set" as per our rules.
24
+ if (isLazy && typeof defvalue === F)
25
+ { // Get the default value from a passed in function.
26
+ return defvalue.call(lazyThis);
27
+ }
28
+ return defvalue;
29
+ }
30
+
31
+ return opt;
32
+ }
33
+
34
+ exports.val = val;
35
+
36
+ /**
37
+ * See if a property in an object is set.
38
+ *
39
+ * If it is, return the property, otherwise return a default value.
40
+ * This uses the `val()` method, and as such supports the same options.
41
+ * However read the parameters carefully, as the defaults may be different!
42
+ *
43
+ * @param {object} obj - An object to test for a property in.
44
+ * @param {string} optname - The property name we're checking for.
45
+ * @param {*} defvalue - The default value.
46
+ *
47
+ * @param {bool} [allowNull=true] Same as `val()`, but the default is `true`.
48
+ * @param {bool} [isLazy=false] Same as `val()`.
49
+ * @param {object} [lazyThis=opts] Same as `val()`.
50
+ *
51
+ * @return {*} Either the property value, or the default value.
52
+ */
53
+ function get(obj, optname, defvalue, allowNull=true, isLazy=false, lazyThis=obj)
54
+ {
55
+ needObj(obj);
56
+ needType(S, optname);
57
+ return val(obj[optname], defvalue, allowNull, isLazy, lazyThis);
58
+ }
59
+
60
+ exports.get = get;