@lumjs/compat 1.0.0 → 1.1.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/CHANGELOG.md CHANGED
@@ -6,10 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.1.0] - 2022-08-11
10
+ ### Changed
11
+ - Moved all *v4* stuff into a new `v4` sub-module.
12
+ - Kept `compat/v4-meta` as an alias to `compat/v4/meta`.
13
+ ### Added
14
+ - `v4/deprecated`
15
+ - `v4/loadtracker`
16
+ - `v4/object-helpers`
17
+ - `v4/promise`
18
+
9
19
  ## [1.0.0] - 2022-07-29
10
20
  ### Added
11
21
  - Initial release.
12
22
 
13
- [Unreleased]: https://github.com/supernovus/lum.compat.js/compare/v1.0.0...HEAD
23
+ [Unreleased]: https://github.com/supernovus/lum.compat.js/compare/v1.1.0...HEAD
24
+ [1.1.0]: https://github.com/supernovus/lum.compat.js/compare/v1.0.0...v1.1.0
14
25
  [1.0.0]: https://github.com/supernovus/lum.compat.js/releases/tag/v1.0.0
15
26
 
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Mark a function/method/property as deprecated.
3
+ *
4
+ * Adds a warning to the Console that the method is deprecated.
5
+ *
6
+ * It can also optionally give example replacement code, and run a function
7
+ * that will call the replacement code automatically.
8
+ *
9
+ * @param {string} name The name of the deprecated method/property/etc.
10
+ *
11
+ * This should actually be the full method signature, or at least the
12
+ * signature matching what a call to the deprecated method made.
13
+ *
14
+ * So rather than 'someOldMethod', use 'MyClass.someOldMethod(foo, bar)'
15
+ * as a more detailed name. This is only used in the Console warning.
16
+ *
17
+ * This is the only mandatory parameter.
18
+ *
19
+ * @param {mixed} [replace={}] Replacement options.
20
+ *
21
+ * If this is a {string}, it is the same as passing an {object} with
22
+ * the following options specified:
23
+ *
24
+ * ```{msg: replace}```
25
+ *
26
+ * If it is a {function}, it is the same as passing an {object} with
27
+ * the following options specified:
28
+ *
29
+ * ```{exec: replace, msg: true, strip: true}```
30
+ *
31
+ * If it is an {object}, then it will be a set of options:
32
+ *
33
+ * "exec" {function}
34
+ *
35
+ * If specified, this function will be called and the value returned.
36
+ * No paramters are passed to the function, so it should be a simple
37
+ * anonymous closure which simply calls the actual replacement code.
38
+ *
39
+ * "msg" {string|boolean}
40
+ *
41
+ * If this is a {string} it will be added to the warning output.
42
+ *
43
+ * If this is `true` and `exec` is set, we will extract the function
44
+ * text using `exec.toString()` and add it to the warning output.
45
+ *
46
+ * In any other cases, no replacement message will be appended.
47
+ *
48
+ * "strip" {boolean}
49
+ *
50
+ * If this is `true`, then we will strip `'function() { return '`
51
+ * from the start of the function text (whitespace ignored), as well
52
+ * as stripping '}' from the very end of the function text.
53
+ *
54
+ * This is only applicable if `exec` is set, and `msg` is `true`.
55
+ *
56
+ * If the `replace` value is none of the above, it will be ignored.
57
+ *
58
+ * @return {mixed} The output is dependent on the parameters passed.
59
+ *
60
+ * If `replace` is a function or an object with
61
+ *
62
+ * In any other case the return value will be undefined.
63
+ *
64
+ * @alias module:@lumjs/compat/v4/deprecated
65
+ */
66
+ module.exports = function (name, replace={})
67
+ {
68
+ const DEP_MSG = ':Deprecated =>';
69
+ const REP_MSG = ':replaceWith =>';
70
+
71
+ if (typeof replace === S)
72
+ { // A string replacement example only.
73
+ replace = {msg: replace};
74
+ }
75
+ else if (typeof replace === F)
76
+ { // A function replacement.
77
+ replace = {exec: replace, msg: true, strip: true};
78
+ }
79
+ else if (!is_obj(replace))
80
+ { // Not an object, that's not valid.
81
+ replace = {};
82
+ }
83
+
84
+ const msgs = [DEP_MSG, name];
85
+ const exec = (typeof replace.exec === F)
86
+ ? replace.exec
87
+ : null;
88
+
89
+ if (exec && replace.msg === true)
90
+ { // Extract the function text.
91
+ const strip = (typeof replace.strip === B)
92
+ ? replace.strip
93
+ : false;
94
+
95
+ let methtext = replace.exec.toString();
96
+
97
+ if (strip)
98
+ { // Remove wrapping anonymous closure function.
99
+ methtext = methtext.replace(/^function\(\)\s*\{\s*(return\s*)?/, '');
100
+ methtext = methtext.replace(/\s*\}$/, '');
101
+ // Also support arrow function version.
102
+ methtext = methtext.replace(/^\(\)\s*\=\>\s*/, '');
103
+ }
104
+
105
+ // Set the replacement msg to the method text.
106
+ replace.msg = methtext;
107
+ }
108
+
109
+ if (typeof replace.msg === 'string')
110
+ { // A replacement message.
111
+ msgs.push(REP_MSG, replace.msg);
112
+ }
113
+
114
+ // Show the messages.
115
+ console.warn(...msgs);
116
+
117
+ // Finally, call the replacement function if it was defined.
118
+ if (exec)
119
+ {
120
+ return exec();
121
+ }
122
+ }
File without changes
@@ -0,0 +1,509 @@
1
+
2
+ const core = require('@lumjs/core');
3
+ const {F,S,B,needObj,needType,def} = core.types
4
+
5
+ /**
6
+ * Track if libraries, plugins, or whatever are loaded.
7
+ *
8
+ * Automatically manages events that are trigged on load.
9
+ * Also will automatically run events assigned *after* the
10
+ * desired item has been loaded.
11
+ *
12
+ * Can handle custom tests in addition to the default
13
+ * test which simply see's if the `loadTracker.mark()`
14
+ * method has been called for the specified name.
15
+ *
16
+ * @class Lum.LoadTracker
17
+ */
18
+ class LoadTracker
19
+ {
20
+ /**
21
+ * Build a LoadTracker
22
+ *
23
+ * @param {object} [opts] - Named options for custom behaviours.
24
+ *
25
+ * @param {function} [opts.or] A custom test for `is()` method.
26
+ * If this returns true, `is()` will return true immediately.
27
+ * Mutually exclusive with the `opts.and` option.
28
+ *
29
+ * The function will have `this` set to the `LoadTracker` instance.
30
+ * It will be passed the same arguments sent to the `is()` method.
31
+ * The function must return a boolean value indicating if the item
32
+ * is considered *loaded* for the purposes of this loader instance.
33
+ *
34
+ * @param {function} [opts.and] A custom test for `is()` method.
35
+ * If this returns false, `is()` will return false immediately.
36
+ * Mutually exclusive with the `opts.or` option.
37
+ *
38
+ * The same function notes as `opts.or` apply to this as well.
39
+ *
40
+ * @param {boolean} [opts.before=false] When to run the custom test.
41
+ * If `true` the custom test is run before the standard test.
42
+ * If `false` the custom test is run after the standard test.
43
+ *
44
+ * If `opts.or` was used, and whichever test is ran first returns
45
+ * `true`, the other test won't be run at all.
46
+ *
47
+ * Likewise, if `opts.and` was used, and whichever test is ran first
48
+ * returns `false`, the other test won't be run at all.
49
+ *
50
+ * @param {function} [opts.check] A custom test for the `check*` methods.
51
+ * If specified, this will be ran *before* checking the rest of the
52
+ * arguments. The first parameter passed to the function is a boolean,
53
+ * indicating if only a single return value is expected; `checkOne()`
54
+ * passes `true` while `checkAll()` passes false. All subsequent
55
+ * parameters will be the arguments passed to the calling method.
56
+ * The function will have `this` set to the `LoadTracker` instance.
57
+ *
58
+ * When called from `checkOne()` if it returns a string, that will
59
+ * be returned as the missing item name. Return nil if no errors.
60
+ *
61
+ * When called from `checkAll()` if it returns an Array, all items
62
+ * from that array will be added to the list of missing items, and
63
+ * then the regular `checkAll()` logic will continue. If however it
64
+ * returns a string, then that will be returned as the sole item in
65
+ * the missing list without running any further `checkAll()` logic.
66
+ * Return nil or an *empty* array if no errors.
67
+ *
68
+ */
69
+ constructor(opts={})
70
+ {
71
+ needObj(opts);
72
+
73
+ def(this, '$loaded', {}); // List of loaded libraries.
74
+ def(this, '$onload', {}); // Events to trigger when libraries are loaded.
75
+
76
+ let isTest = false, testOr = false;
77
+ if (typeof opts.or === F)
78
+ { // A test that can override
79
+ isTest = opts.or;
80
+ testOr = true;
81
+ }
82
+ else if (typeof opts.and === F)
83
+ {
84
+ isTest = opts.and;
85
+ testOr = false;
86
+ }
87
+
88
+ def(this, '$isTest', isTest);
89
+ def(this, '$isOr', testOr);
90
+ def(this, '$is1st', opts.before ?? false);
91
+ def(this, '$check', opts.check ?? false);
92
+ def(this, '$typeOne', opts.type ?? 'item');
93
+ def(this, '$typeAll', opts.types ?? this.$typeOne + 's');
94
+
95
+ }
96
+
97
+ /**
98
+ * Assign a callback function to be called.
99
+ *
100
+ * All callbacks for a given `name` will be ran when
101
+ * that `name` has been passed to `mark()` or `call()`.
102
+ *
103
+ * @param {string} name - The name of the item to be loaded.
104
+ * @param {function} event - The callback function.
105
+ *
106
+ * @returns {boolean} - Is the method call deferred?
107
+ * If `true` the `name` has not been marked as loaded, so the
108
+ * method has been added to a queue that will be called
109
+ */
110
+ on(name, event)
111
+ {
112
+ needType(S, name, "Name must be a string");
113
+ needType(F, event, "Event must be a function");
114
+
115
+ if (!Array.isArray(this.$onload[name]))
116
+ { // Add an array of events.
117
+ def(this.$onload, name, []);
118
+ }
119
+
120
+ this.$onload[name].push(event);
121
+
122
+ if (this.is(name))
123
+ { // Execute the function right now.
124
+ event.call(Lum, name, false);
125
+ return false;
126
+ }
127
+
128
+ return true;
129
+ }
130
+
131
+ /**
132
+ * Mark an item as loaded.
133
+ *
134
+ * @param {string} name - Item being marked as loaded.
135
+ * @param {boolean} [call=true] Also call `call()` method?
136
+ * @param {boolean} [skipTest=true] Passed to `call()` method.
137
+ */
138
+ mark(name, call=true, skipTest=true)
139
+ {
140
+ def(this.$loaded, name, true);
141
+ if (call)
142
+ {
143
+ this.call(name, skipTest);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Call all of the callback function for an item.
149
+ *
150
+ * @param {string} name - The name of the item.
151
+ * @param {boolean} [skipTest=false] Skip the `is()` test?
152
+ * If `false` we call `this.is(name)` and will only continue
153
+ * if it returns a true value. If `true` we call without testing.
154
+ *
155
+ * @returns {boolean} - If the events were called or not.
156
+ */
157
+ call(name, skipTest=false)
158
+ {
159
+ if (!skipTest && !this.is(name))
160
+ { // Okay, we cannot call if the item isn't loaded.
161
+ console.error("Cannot call events if item is not loaded", name, this);
162
+ return false;
163
+ }
164
+
165
+ if (Array.isArray(this.$onload[name]))
166
+ {
167
+ for (const event of this.$onload[name])
168
+ {
169
+ event.call(Lum, name, true);
170
+ }
171
+ }
172
+
173
+ return true;
174
+ }
175
+
176
+ /**
177
+ * Check if the item is loaded.
178
+ *
179
+ * @param {string} name - The item to check.
180
+ * @returns {boolean}
181
+ */
182
+ is(name)
183
+ {
184
+ if (typeof name !== S)
185
+ {
186
+ console.error("Name must be a string", name);
187
+ return false;
188
+ }
189
+
190
+ let okay = false;
191
+
192
+ const hasTest = (typeof this.$isTest === F);
193
+ const test1st = this.$is1st;
194
+ const testOr = this.$isOr;
195
+
196
+ // A test that indicates we can return `okay` value.
197
+ const done = () => (!hasTest || (testOr && okay) || (!testOr && !okay));
198
+
199
+ if (hasTest && test1st)
200
+ { // A custom test that is called before the normal test.
201
+ okay = this.$isTest(...arguments);
202
+ console.debug("is:before", okay, this);
203
+ if (done()) return okay;
204
+ }
205
+
206
+ // Call the normal test, is the name marked?
207
+ okay = (this.$loaded[name] ?? false);
208
+ if (done()) return okay;
209
+
210
+ if (hasTest && !test1st)
211
+ { // A custom test that is called after the normal test.
212
+ okay = this.$isTest(...arguments);
213
+ console.debug("is:after", okay, this);
214
+ }
215
+
216
+ return okay;
217
+ }
218
+
219
+ /**
220
+ * Get a list of loaded items.
221
+ *
222
+ * This only includes items that have been marked using the
223
+ * `mark()` method.
224
+ *
225
+ * @param {(boolean|function)} [sort=false] Should we sort the results?
226
+ * If this is `true` we use the default sorting algorithm.
227
+ * If this is a function, it's passed to the `array.sort()` method.
228
+ * Any other value and the list will be in the order returned
229
+ * by the `Object.keys()` method.
230
+ *
231
+ * @returns {Array} The list of loaded items.
232
+ */
233
+ list(sort=false)
234
+ {
235
+ let list = Object.keys(this.$loaded);
236
+ if (sort === true)
237
+ { // If sort is boolean true, use default sorting algorithm.
238
+ list.sort();
239
+ }
240
+ else if (typeof sort === F)
241
+ { // A custom sort function.
242
+ list.sort(sort);
243
+ }
244
+ return list;
245
+ }
246
+
247
+ /**
248
+ * The same output as `list(false)` but as a readonly accessor property.
249
+ */
250
+ get keys()
251
+ {
252
+ return Object.keys(this.$loaded);
253
+ }
254
+
255
+ /**
256
+ * Return the first item that isn't loaded.
257
+ *
258
+ * @param {string} ...names - Any items you want to look for.
259
+ *
260
+ * @returns {string|undefined} If no items are missing, will be undefined.
261
+ */
262
+ checkOne()
263
+ {
264
+ if (typeof this.$check === F)
265
+ {
266
+ const check = this.$check(true, ...arguments);
267
+ console.debug("checkOne:$check", check, this);
268
+ if (typeof check === S && check.trim() !== '')
269
+ { // A non-empty string was passed.
270
+ return check;
271
+ }
272
+ }
273
+
274
+ for (const lib of arguments)
275
+ {
276
+ if (!this.is(lib))
277
+ {
278
+ return lib;
279
+ }
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Return a full list of any missing items.
285
+ *
286
+ * @param {string} ...names - Any items you want to look for.
287
+ *
288
+ * @returns {Array} A list of missing items.
289
+ */
290
+ checkAll()
291
+ {
292
+ const missing = [];
293
+
294
+ if (typeof this.$check === F)
295
+ {
296
+ const check = this.$check(false, ...arguments);
297
+ console.debug("checkAll:$check", check, this);
298
+ if (Array.isArray(check))
299
+ { // May have missing items, or be empty.
300
+ missing.push(...check);
301
+ }
302
+ else if (typeof check === S)
303
+ { // A string indicates we can continue no further.
304
+ missing.push(check);
305
+ return missing;
306
+ }
307
+ }
308
+
309
+ for (const lib of arguments)
310
+ {
311
+ if (!this.is(lib))
312
+ {
313
+ missing.push(lib);
314
+ }
315
+ }
316
+
317
+ return missing;
318
+ }
319
+
320
+ /**
321
+ * Setup a namespace object with wrapper methods.
322
+ *
323
+ * @param {object} ns - The namespace object, may also be a function.
324
+ * @param {object} [names] A map of alternate names for the methods.
325
+ *
326
+ * The default method names are:
327
+ *
328
+ * `mark` → Call `lt.mark`, returns `ourself()`.
329
+ * `has` → Proxy `lt.is`.
330
+ * `check` → Proxy `lt.checkOne`.
331
+ * `checkAll` → Proxy `lt.checkAll`.
332
+ * `list` → Proxy `lt.list`.
333
+ * `need` → Call `check`, if any missing, throw Error.
334
+ * `want` → Call `check`, return true if all are loaded, false if not.
335
+ * `onLoad` → Proxy `on`.
336
+ *
337
+ * If any of the method names are already present, they will be skipped.
338
+ *
339
+ */
340
+ setupNamespace(ns, names={})
341
+ {
342
+ needObj(ns, true, "Invalid namespace object");
343
+ needObj(names, false, "Names must be an object");
344
+
345
+ const thisLoad = this; // Contextual instance reference.
346
+ let propName; // Will be set by hasnt() closure.
347
+
348
+ const getname = (name) => names[name] ?? name;
349
+
350
+ const hasnt = (name) =>
351
+ {
352
+ propName = getname(name);
353
+ return (ns[propName] === undefined);
354
+ }
355
+
356
+ const addfunc = (func) => def(ns, propName, func);
357
+ const addgetter = (func) => def(ns, propName, {get: func});
358
+
359
+ // Options for need() and want().
360
+ const loadOpts = def(ns, '$loadOpts',
361
+ {
362
+ checkAll: false,
363
+ warnings: false,
364
+ }).$loadOpts;
365
+
366
+ if (hasnt('mark'))
367
+ {
368
+ addfunc(function()
369
+ {
370
+ thisLoad.mark(...arguments);
371
+ return ourself();
372
+ });
373
+ }
374
+
375
+ if (hasnt('has'))
376
+ {
377
+ addfunc(function()
378
+ {
379
+ return thisLoad.is(...arguments);
380
+ });
381
+ }
382
+
383
+ if (hasnt('check'))
384
+ {
385
+ addfunc(function()
386
+ {
387
+ return thisLoad.checkOne(...arguments);
388
+ })
389
+ }
390
+
391
+ if (hasnt('checkAll'))
392
+ {
393
+ addfunc(function()
394
+ {
395
+ return thisLoad.checkAll(...arguments);
396
+ });
397
+ }
398
+
399
+ if (hasnt('list'))
400
+ {
401
+ addfunc(function()
402
+ {
403
+ return thisLoad.list(...arguments);
404
+ });
405
+ }
406
+
407
+ if (hasnt('missing'))
408
+ {
409
+ addfunc(function(
410
+ {
411
+ fatal = false,
412
+ all = loadOpts.checkAll,
413
+ warn = loadOpts.warnings,
414
+ ok = this,
415
+ },
416
+ ...items)
417
+ {
418
+ const thisCheck = all ? getname('checkAll') : getname('check');
419
+ const result = ns[thisCheck](...items);
420
+
421
+ if (typeof result === S
422
+ || (all && Array.isArray(result) && result.length > 0))
423
+ { // There are missing libraries.
424
+ const typeName = all ? thisLoad.$typeAll : thisLoad.$typeOne;
425
+ const missing = fatal ? JSON.stringify(result) : result;
426
+
427
+ if (fatal)
428
+ {
429
+ throw new Error(`Missing required ${typeName}: ${missing}`);
430
+ }
431
+ else
432
+ {
433
+ if (warn)
434
+ {
435
+ console.warn("Missing", typeName, missing);
436
+ }
437
+ return false;
438
+ }
439
+ }
440
+
441
+ // If we reached here, nothing was reported as missing.
442
+ return ok;
443
+ });
444
+ }
445
+
446
+ if (hasnt('need'))
447
+ {
448
+ addfunc(function()
449
+ {
450
+ const missing = getname('missing');
451
+ return ns[missing]({fatal: true, ok: ourself()}, ...arguments);
452
+ });
453
+ }
454
+
455
+ if (hasnt('want'))
456
+ {
457
+ addfunc(function()
458
+ {
459
+ const missing = getname('missing');
460
+ return ns[missing]({fatal: false, ok: true}, ...arguments);
461
+ });
462
+ }
463
+
464
+ if (hasnt('all'))
465
+ {
466
+ addgetter(function()
467
+ {
468
+ loadOpts.checkAll = true;
469
+ return ns;
470
+ });
471
+ }
472
+
473
+ if (hasnt('one'))
474
+ {
475
+ addgetter(function()
476
+ {
477
+ loadOpts.checkAll = false;
478
+ return ns;
479
+ });
480
+ }
481
+
482
+ if (hasnt('showWarnings'))
483
+ {
484
+ addfunc(function(val)
485
+ {
486
+ if (typeof val === B)
487
+ {
488
+ loadOpts.warnings = val;
489
+ return this;
490
+ }
491
+ else
492
+ {
493
+ return loadOpts.warnings;
494
+ }
495
+ });
496
+ }
497
+
498
+ if (hasnt('onLoad'))
499
+ {
500
+ addfunc(function()
501
+ {
502
+ return thisLoad.on(...arguments);
503
+ });
504
+ }
505
+
506
+ }
507
+ } // LoadTracker class
508
+
509
+ module.exports = LoadTracker;
File without changes
@@ -0,0 +1,191 @@
1
+ const core = require('@lumjs/core');
2
+ const {O,F,S,B,isObj} = core.types;
3
+ const {clone,copyProps} = core.obj;
4
+
5
+ /**
6
+ * A way to handle Mixins/Traits.
7
+ *
8
+ * This is basically a magic wrapper around {@link Lum.obj.into} which we
9
+ * use instead of Object.assign() as we don't want to overwrite properties
10
+ * by default.
11
+ *
12
+ * As it's designed to extend the class prototype and only the prototype,
13
+ * it will see if anything passed to it is a function/class and if so, it
14
+ * will automatically use the prototype of the function/class. If you want
15
+ * to copy static class properties, use {@link Lum.obj.into} instead of this.
16
+ *
17
+ * @param {object|function} target - The target we are copying into.
18
+ * @param {...*} sources - The source traits we want to mix in.
19
+ *
20
+ * @return {object|function} The `target` will be returned.
21
+ *
22
+ * @alias module:@lumjs/compat/v4/object-helpers.mixin
23
+ */
24
+ exports.mixin = function (target, ...inSources)
25
+ {
26
+ var outSources = [];
27
+
28
+ function unwrap (what)
29
+ {
30
+ if (typeof what === F && typeof what.prototype === O)
31
+ {
32
+ return what.prototype;
33
+ }
34
+ else if (isObj(what))
35
+ {
36
+ return what;
37
+ }
38
+ else
39
+ {
40
+ throw new Error("Invalid function/object passed to addTraits()");
41
+ }
42
+ }
43
+
44
+ target = unwrap(target); // Ensure the target is an object.
45
+
46
+ for (var s in inSources)
47
+ {
48
+ var source = inSources[s];
49
+ if (typeof source === B || typeof source === S)
50
+ { // A special option statement, push it directly.
51
+ outSources.push(source);
52
+ }
53
+ else
54
+ { // Anything else needs to be unwrapped.
55
+ outSources.push(unwrap(source));
56
+ }
57
+ }
58
+
59
+ return exports.into(target, outSources);
60
+ }
61
+
62
+ /**
63
+ * Copy properties between objects. Can be used for mixins/traits.
64
+ *
65
+ * For each of the sources specified, this will call:
66
+ *
67
+ * ```Lum.obj.copy(source, target, opts)```
68
+ *
69
+ * The current `opts` can be changed dynamically using special statements.
70
+ * See below for details on the statements to make that work.
71
+ * The default `opts` is `{default: true, overwrite: false}`
72
+ *
73
+ * @param {object|function} target - The target we are copying into.
74
+ * @param {...*} sources - The sources to copy from, and options.
75
+ *
76
+ * If a source is a boolean, it will reset the `opts.overwrite` property.
77
+ *
78
+ * If a source is the string 'default' we set the `opts` to:
79
+ * `{default: true, overwrite: opts.overwrite}`
80
+ *
81
+ * If a source is the string 'all' we set the `opts` to:
82
+ * `{all: true, overwrite: opts.overwrite}`
83
+ *
84
+ * If a source is an object with a property of `__copy_opts` which is `true`
85
+ * then the `opts` will be set to the source itself.
86
+ *
87
+ * If a source is an object with a property of `__copy_opts` which is an
88
+ * `object` then the `opts` will be set to the object, and the rest of
89
+ * the properties from the source will be copied as usual.
90
+ *
91
+ * If the source is any other object or function, it will be considered a
92
+ * valid source to copy into the `target`.
93
+ *
94
+ * Anything else will be invalid and will throw an Error.
95
+ *
96
+ * @return {object|function} The `target` will be returned.
97
+ *
98
+ * @method Lum.obj.into
99
+ */
100
+ exports.into = function (target, ...sources)
101
+ {
102
+ let opts = {default: true, overwrite: false}; // default opts.
103
+
104
+ // console.debug("Lum.obj.copyInto()", target, sources);
105
+
106
+ for (let s in sources)
107
+ {
108
+ let source = sources[s];
109
+ const stype = typeof source;
110
+ // console.debug("source", source, stype);
111
+ if (stype === B)
112
+ {
113
+ opts.overwrite = source;
114
+ }
115
+ else if (stype === S)
116
+ {
117
+ if (source === 'default')
118
+ {
119
+ opts = {default: true, overwrite: opts.overwrite};
120
+ }
121
+ else if (source === 'all')
122
+ {
123
+ opts = {all: true, overwrite: opts.overwrite};
124
+ }
125
+ }
126
+ else if (stype === O || stype === F)
127
+ {
128
+ // console.debug("copying properties", source);
129
+ if (source.__copy_opts === true)
130
+ { // Source is copy options.
131
+ opts = source;
132
+ continue; // Nothing more to do here.
133
+ }
134
+ else if (isObj(source.__copy_opts))
135
+ { // Copy options included in source.
136
+ opts = source.__copy_opts;
137
+ source = clone(source); // Make a copy of the source.
138
+ delete(source.__copy_opts); // Remove the __copy_opts.
139
+ }
140
+
141
+ // Copy the properties.
142
+ copyProps(source, target, opts);
143
+ }
144
+ else
145
+ {
146
+ throw new Error("Invalid function/object passed to copyInto()");
147
+ }
148
+ }
149
+
150
+ return target;
151
+ }
152
+
153
+ /**
154
+ * Clone a simple object, using the {@link Lum._.clone} function.
155
+ *
156
+ * By default it uses `Lum._.CLONE.JSON` mode for cloning, but this can
157
+ * be adjusted as desired by passing a `cloneOpts.mode` option.
158
+ *
159
+ * Can also clone extended properties that aren't serialized in JSON.
160
+ *
161
+ * @param {object} object The object or function to clone.
162
+ *
163
+ * @param {object} [copyProperties] Use {@link Lum.copyProperties} as well.
164
+ *
165
+ * If copyProperties is defined, and is a non-false value, then we'll
166
+ * call {@link Lum.obj.copy} after cloning to copy 'special' properties.
167
+ *
168
+ * @param {object} [cloneOpts] Options to send to {@link Lum._clone}
169
+ *
170
+ * This can be any options supported by the {@link Lum._.clone} function.
171
+ *
172
+ * @return {object} A clone of the object.
173
+ *
174
+ */
175
+ exports.clone = function(object, copyProperties, cloneOpts)
176
+ {
177
+ if (!isObj(cloneOpts))
178
+ {
179
+ cloneOpts = {mode: clone.MODE.JSON};
180
+ }
181
+
182
+ if (copyProperties)
183
+ {
184
+ cloneOpts.copy = copyProperties;
185
+ }
186
+
187
+ //console.debug("Lum.obj.clone", object, copyProperties, cloneOpts);
188
+
189
+ return clone(object, cloneOpts);
190
+ }
191
+
@@ -0,0 +1,381 @@
1
+ const {B,O,F} = require('@lumjs/core').types;
2
+
3
+ /**
4
+ * @class LumPromise
5
+ *
6
+ * Build the Promise object.
7
+ *
8
+ * @param (object|boolean) options Options for the Promise object.
9
+ *
10
+ * If options is a boolean, it's assumed to be the 'jquery' option.
11
+ *
12
+ * Recognized properties if options is an object:
13
+ *
14
+ * jquery: (boolean) If true, run this._extendJQuery(options);
15
+ * If false, run this._extendInternal(options);
16
+ *
17
+ * @deprecated Use HTML 5 Promises, or the jQuery.Deferred API directly.
18
+ */
19
+ function LumPromise(options)
20
+ {
21
+ if (typeof options === B)
22
+ { // Assume the 'jquery' option was passed implicitly.
23
+ options = {jquery: options};
24
+ }
25
+ else if (typeof options !== O || options === null)
26
+ { // Ensure options is an object, and auto-select jQuery if it's loaded.
27
+ options = {jquery: (typeof jQuery === O)};
28
+ }
29
+
30
+ if (options.jquery)
31
+ {
32
+ this._extendJQuery(options)
33
+ }
34
+ else
35
+ {
36
+ this._extendInternal(options);
37
+ }
38
+ }
39
+
40
+ const lpp = LumPromise.prototype;
41
+
42
+ /**
43
+ * Add the methods from jQuery.Deferred to ourself.
44
+ *
45
+ * If jQuery is not loaded, this will throw an error.
46
+ */
47
+ lpp._extendJQuery = function (options)
48
+ {
49
+ if (jQuery === undefined)
50
+ {
51
+ throw new Error("Cannot use 'jquery' without jQuery loaded.");
52
+ }
53
+ var def = jQuery.Deferred();
54
+ for (var f in def)
55
+ {
56
+ this[f] = def[f];
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Create our own internal represenation of the Deferred API.
62
+ *
63
+ * Setters: done(), fail(), always(), progress()
64
+ * Promise chaining: then(), catch()
65
+ * Triggers: resolve(), reject(), notify()
66
+ * Targetted triggers: resolveWith(), rejectWith(), notifyWith()
67
+ * Info: state()
68
+ *
69
+ */
70
+ lpp._extendInternal = function (options)
71
+ {
72
+ // A copy of this for use in closures.
73
+ var self = this;
74
+
75
+ // Private storage for the state of the Promise.
76
+ var state = 'pending';
77
+
78
+ // Private storage for the arguments used to resolve/reject.
79
+ var final_args;
80
+
81
+ // Private storage for the 'this' object to use in callbacks.
82
+ var final_this = this;
83
+
84
+ var callbacks =
85
+ {
86
+ always: [],
87
+ done: [],
88
+ fail: [],
89
+ progress: [],
90
+ };
91
+
92
+ // Private function to run registered callbacks.
93
+ function apply_callbacks (name, args, current_this)
94
+ {
95
+ if (args === undefined)
96
+ {
97
+ args = final_args;
98
+ }
99
+ if (current_this === undefined)
100
+ {
101
+ current_this = final_this;
102
+ }
103
+ var cbs = callbacks[name];
104
+ if (cbs && cbs.length)
105
+ {
106
+ for (var i = 0; i < cbs.length; i++)
107
+ {
108
+ var cb = cbs[i];
109
+ cb.apply(current_this, args);
110
+ }
111
+ }
112
+ }
113
+
114
+ // Private function to create a listener.
115
+ function create_listener (name, validStates)
116
+ {
117
+ var listener = function ()
118
+ {
119
+ var args = Array.prototype.slice.call(arguments);
120
+ for (var i = 0; i < args.length; i++)
121
+ {
122
+ if (typeof args[i] === O && Array.isArray(args[i]))
123
+ { // An array of callbacks, recurse them.
124
+ listener.apply(self, args[i]);
125
+ }
126
+ else if (typeof args[i] === F)
127
+ {
128
+ if (state === 'pending')
129
+ { // Add the callback to the appropriate listener queue.
130
+ callbacks[name].push(args[i]);
131
+ }
132
+ else if (validStates.indexOf(state) != -1)
133
+ { // Execute the callback now.
134
+ args[i].apply(final_this, final_args);
135
+ }
136
+ }
137
+ else
138
+ {
139
+ console.warn("Unhandled parameter passed to "+name, args[i]);
140
+ }
141
+ }
142
+ return self;
143
+ }
144
+ return listener;
145
+ }
146
+
147
+ // Add our event assignment methods.
148
+ var meths =
149
+ {
150
+ done: ['resolved'],
151
+ fail: ['rejected'],
152
+ always: ['resolved', 'rejected'],
153
+ progress: [],
154
+ };
155
+ for (var mname in meths)
156
+ {
157
+ var mstate = meths[mname];
158
+ self[mname] = create_listener(mname, mstate);
159
+ }
160
+
161
+ // Add our trigger methods.
162
+ self.resolve = function ()
163
+ {
164
+ if (state === 'pending')
165
+ {
166
+ var args = Array.prototype.slice.call(arguments);
167
+ if (args.length === 1 && typeof args[0] === O &&
168
+ typeof args[0].then === F)
169
+ { // Passed a promise. We'll 'then' it, and resolve again from it.
170
+ args[0].then(function ()
171
+ {
172
+ self.resolve.apply(self, arguments);
173
+ });
174
+ }
175
+ else
176
+ { // Not a promise, let's do this.
177
+ state = 'resolved';
178
+ final_args = args;
179
+ apply_callbacks('always');
180
+ apply_callbacks('done');
181
+ }
182
+ }
183
+ }
184
+
185
+ self.resolveWith = function (withObj)
186
+ {
187
+ if (state === 'pending')
188
+ {
189
+ final_this = withObj;
190
+ var resolveArgs = Array.prototype.slice.call(arguments, 1);
191
+ self.resolve.apply(self, resolveArgs);
192
+ }
193
+ }
194
+
195
+ self.reject = function ()
196
+ {
197
+ if (state === 'pending')
198
+ {
199
+ var args = Array.prototype.slice.call(arguments);
200
+ if (args.length === 1 && typeof args[0] === O &&
201
+ typeof args[0].then === F)
202
+ { // Passed a promise. We'll 'then' it, and resolve again from it.
203
+ args[0].then(null, function ()
204
+ {
205
+ self.reject.apply(self, arguments);
206
+ });
207
+ }
208
+ else
209
+ { // Not a promise, let's do this.
210
+ state = 'rejected';
211
+ final_args = args;
212
+ apply_callbacks('always');
213
+ apply_callbacks('fail');
214
+ }
215
+ }
216
+ }
217
+
218
+ self.rejectWith = function (withObj)
219
+ {
220
+ if (state === 'pending')
221
+ {
222
+ final_this = withObj;
223
+ var rejectArgs = Array.prototype.slice.call(arguments, 1);
224
+ self.reject.apply(self, rejectArgs);
225
+ }
226
+ }
227
+
228
+ self.notify = function ()
229
+ {
230
+ if (state === 'pending')
231
+ {
232
+ var args = Array.prototype.slice.call(arguments);
233
+ if (args.length === 1 && typeof args[0] === O &&
234
+ typeof args[0].then === F)
235
+ { // Passed a promise. We'll 'then' it, and resolve again from it.
236
+ args[0].then(null, null, function ()
237
+ {
238
+ self.notify.apply(self, arguments);
239
+ });
240
+ }
241
+ else
242
+ { // Not a promise, let's do this.
243
+ apply_callbacks('progress', args);
244
+ }
245
+ }
246
+ }
247
+
248
+ self.notifyWith = function (withObj)
249
+ {
250
+ if (state === 'pending')
251
+ {
252
+ var args = Array.prototype.slice.call(arguments, 1);
253
+ if (args.length === 1 && typeof args[0] === O &&
254
+ typeof args[0].then === F)
255
+ { // Passed a promise. We'll 'then' it, and resolve again from it.
256
+ args[0].then(null, null, function ()
257
+ {
258
+ var subargs = Array.prototype.slice.call(arguments);
259
+ subargs.unshift(withObj);
260
+ self.notifyWith.apply(self, subargs);
261
+ });
262
+ }
263
+ else
264
+ { // Not a promise, let's do this.
265
+ apply_callbacks('progress', args, withObj);
266
+ }
267
+ }
268
+ }
269
+
270
+ self.catch = function (failFilter)
271
+ {
272
+ return self.then(null, failFilter);
273
+ }
274
+
275
+ self.then = function (doneFilter, failFilter, progressFilter)
276
+ {
277
+ var newPromise = new LumPromise(false);
278
+
279
+ function make_callback (filterSpec)
280
+ {
281
+ var callback = function ()
282
+ {
283
+ var useWith = (this !== self);
284
+ var args = Array.prototype.slice.call(arguments);
285
+ var result = filterSpec.filter.apply(this, args);
286
+ if (useWith)
287
+ {
288
+ newPromise[filterSpec.methodWith](this, result);
289
+ }
290
+ else
291
+ {
292
+ newPromise[filterSpec.methodName](result);
293
+ }
294
+ }
295
+ return callback;
296
+ }
297
+
298
+ var filterSpecs =
299
+ {
300
+ done:
301
+ {
302
+ filter: doneFilter,
303
+ methodName: 'resolve',
304
+ methodWith: 'resolveWith',
305
+ },
306
+ fail:
307
+ {
308
+ filter: failFilter,
309
+ methodName: 'reject',
310
+ methodWith: 'rejectWith',
311
+ },
312
+ progress:
313
+ {
314
+ filter: progressFilter,
315
+ methodName: 'notify',
316
+ methodWith: 'notifyWith',
317
+ },
318
+ };
319
+
320
+ for (var onName in filterSpecs)
321
+ {
322
+ var filterSpec = filterSpecs[onName];
323
+ if (typeof filterSpec.filter === F)
324
+ {
325
+ var callback = make_callback(filterSpec);
326
+ self[onName](callback);
327
+ }
328
+ }
329
+
330
+ return newPromise;
331
+ }
332
+
333
+ self.state = function ()
334
+ {
335
+ return state;
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Execute a delayed 'done' action.
341
+ *
342
+ * @param mixed obj The object to send to the done().
343
+ * @param str ts The textStatus to send to done().
344
+ * @param mixed xhr Object to use as XHR (default is this.)
345
+ * @param int timeout The timeout (in ms) defaults to 5.
346
+ */
347
+ lpp.deferDone = function (obj, ts, xhr, timeout)
348
+ {
349
+ var self = this;
350
+ if (timeout === undefined)
351
+ timeout = 5; // 5ms should be enough time to register .done events.
352
+ if (xhr === undefined)
353
+ xhr = self;
354
+ self.doneTimer = setTimeout(function()
355
+ {
356
+ self.resolve(obj, ts, xhr);
357
+ }, timeout);
358
+ }
359
+
360
+ /**
361
+ * Execute a delayed 'fail' action.
362
+ *
363
+ * @param mixed error The message, code, or object to send to fail().
364
+ * @param str ts The textStatus to send to fail().
365
+ * @param mixed xhr Object to use as XHR (default is this.)
366
+ * @param int timeout The timeout (in ms) defaults to 5.
367
+ */
368
+ lpp.deferFail = function (error, ts, xhr, timeout)
369
+ {
370
+ var self = this;
371
+ if (timeout === undefined)
372
+ timeout = 5;
373
+ if (xhr === undefined)
374
+ xhr = self;
375
+ self.failTimer = setTimeout(function ()
376
+ {
377
+ self.reject(xhr, ts, error);
378
+ }, timeout);
379
+ }
380
+
381
+ module.exports = LumPromise;
File without changes
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@lumjs/compat",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "main": "lib/index.js",
5
5
  "exports": {
6
6
  ".": "./lib/index.js",
7
- "./v4-meta": "./lib/v4-meta.js",
7
+ "./v4-meta": "./lib/v4/meta.js",
8
+ "./v4/*": "./lib/v4/*.js",
8
9
  "./package.json": "./package.json"
9
10
  },
10
11
  "license": "MIT",