@lumjs/core 1.21.0 → 1.23.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/types/isa.js CHANGED
@@ -1,9 +1,13 @@
1
+ "use strict";
2
+
1
3
  const {O, F, S} = require('./js');
2
- const {isObj,isArray} = require('./basics');
4
+ const {isObj,isArray,isIterable,notNil} = require('./basics');
3
5
  const TYPES = require('./typelist');
6
+ const def = require('./def');
4
7
 
5
8
  /**
6
9
  * See if a value is an instance of a class.
10
+ * @deprecated Just use `instanceof` directly, this function is unnecessary.
7
11
  * @param {*} v - The value we're testing.
8
12
  * @param {function} f - The constructor/class we want.
9
13
  * @param {boolean} [needProto=false] If true, the `v` must have a `prototype`.
@@ -23,7 +27,7 @@ function isInstance(v, f, needProto=false)
23
27
  // Everything passed.
24
28
  return true;
25
29
  }
26
-
30
+
27
31
  exports.isInstance = isInstance;
28
32
 
29
33
  /**
@@ -38,6 +42,7 @@ exports.isInstance = isInstance;
38
42
  * this function does not count `null` as a valid `object`.
39
43
  *
40
44
  * @param {*} v - The value we're testing.
45
+ *
41
46
  * @returns {boolean} If the value was of the desired type.
42
47
  * @alias module:@lumjs/core/types.isType
43
48
  */
@@ -52,12 +57,12 @@ function isType(type, v)
52
57
  { // A type-specific test.
53
58
  return TYPES.tests[type](v);
54
59
  }
55
- else
60
+ else
56
61
  { // No type-specific tests.
57
62
  return (typeof v === type);
58
63
  }
59
64
  }
60
-
65
+
61
66
  exports.isType = isType;
62
67
 
63
68
  // Default options parser.
@@ -127,7 +132,8 @@ function processOptions(type, v)
127
132
  * @param {...any} types - The types the value should be one of.
128
133
  *
129
134
  * For each of the `types`, by default if it is a `string`
130
- * we test with `isType()`, if it is a `function` we test with `isInstance()`.
135
+ * we test with `isType()`, if it is a `function` we see if `v`
136
+ * is either an instance of the type or a sub-class of it.
131
137
  *
132
138
  * If it is an `object` and has an `is()` method, use that as the test.
133
139
  *
@@ -138,9 +144,9 @@ function processOptions(type, v)
138
144
  * - `needProto: boolean`, Change the `needProto` option for `isInstance()`
139
145
  * - `parsers: function`, Add another options parser function.
140
146
  * - `process: function`, A one-time set-up function.
141
- * - `test: function`, Pass the `v` to this test and return `true` if it passes.
142
- * - `typeof: boolean`, If `true` use `typeof` instead of `isType()` for tests.
143
- * - `instanceof: boolean`, If `true` use `instanceof` instead of `isInstance()`.
147
+ * - `test: function`, Pass the `v` to this and return `true` if it passes.
148
+ * - `instanceof: boolean`, If `true` use `instanceof` *instead of* `isInstance()`.
149
+ * As of version `1.22`, this defaults to `true`.
144
150
  * - Anything else will be set as an option that may be used by other parsers.
145
151
  *
146
152
  * Any other type value will only match if `v === type`
@@ -156,8 +162,7 @@ function isa(v, ...types)
156
162
  needProto: false,
157
163
  parsers: [DEFAULT_ISA_PARSER],
158
164
  process: processOptions,
159
- typeof: false,
160
- instanceof: false,
165
+ instanceof: true,
161
166
  }
162
167
 
163
168
  for (const type of types)
@@ -168,13 +173,25 @@ function isa(v, ...types)
168
173
  // With that out of the way, let's go!
169
174
  if (typeof type === S)
170
175
  { // A string is passed to isType()
171
- if (opts.typeof && typeof v === type) return true;
172
176
  if (isType(type, v)) return true;
173
177
  }
174
178
  else if (typeof type === F)
175
179
  { // A function is passed to isInstance()
176
- if (opts.instanceof && v instanceof type) return true;
177
- if (isInstance(v, type, opts.needProto)) return true;
180
+ if (typeof v === F)
181
+ { // See if it is a sub-class.
182
+ if (type.isPrototypeOf(v)) return true;
183
+ }
184
+ else
185
+ { // See if it is an instance.
186
+ if (opts.instanceof)
187
+ { // Simple test, is default now.
188
+ if (v instanceof type) return true;
189
+ }
190
+ else
191
+ { // Old test, will be removed in the future.
192
+ if (isInstance(v, type, opts.needProto)) return true;
193
+ }
194
+ }
178
195
  }
179
196
  else if (isObj(type))
180
197
  { // Objects can be additional tests, or options.
@@ -189,128 +206,583 @@ function isa(v, ...types)
189
206
  exports.isa = isa;
190
207
 
191
208
  /**
192
- * Extended return value from `isArrayOf()`.
209
+ * Extended return value from any test powered by the `OfTest` class.
193
210
  *
194
- * Used if the `opts.details` option was `true`.
211
+ * Used if `rules.details` was set to `true`.
195
212
  *
196
- * @typedef module:@lumjs/core/types~IsArrayOfResult
213
+ * @typedef module:@lumjs/core/types.OfTestResult
197
214
  *
198
- * @prop {boolean} pass - Did the `isArrayOf()` test pass?
199
- * @prop {boolean} empty - Was the array empty?
215
+ * @prop {boolean} pass - Did the test pass?
216
+ * @prop {boolean} empty - Was the list empty?
200
217
  *
201
218
  * @prop {object} [failed] Failure information;
202
- * Only addedd if `pass` is false.
219
+ * Only addedd if `pass` is `false`.
203
220
  *
204
- * @prop {number} failed.index - Index of item that caused the failure.
205
- * Will be set to `-1` if the `opts.value` argument is not an Array,
206
- * or if the array is empty and `opts.empty` was not set to `true`.
207
- *
208
- * @prop {mixed} failed.value - The item that caused the failure.
209
- * Will be the `opts.value` itself if `failed.index` is `-1`.
221
+ * Will be the {@link module:@lumjs/core/types.OfTest.state.at}
222
+ * property from the underlying `OfTest` instance.
223
+ * See the documentation for that property for further details.
210
224
  *
211
225
  */
212
226
 
213
227
  /**
214
- * See if every item in an Array passes an `isa()` test.
228
+ * A special class used for testing the contents of containers.
215
229
  *
216
- * @param {(object|Array)} opts - Options for this test function.
230
+ * Used by the `is[List|Array|Map]Of()` methods, this class
231
+ * abstracts out most of the common functionality of those tests.
232
+ *
233
+ * @alias module:@lumjs/core/types.OfTest
217
234
  *
218
- * If this argument is an `Array` it is assumed to be the
219
- * `opts.value` named option.
235
+ * @prop {boolean} valid - Does `rules.value` pass the validity test?
236
+ * @prop {object} rules - Rules passed to the constructor.
237
+ * @prop {object} state - Contains the current state of the tests.
238
+ * @prop {Array} vtype - Value type tests.
239
+ * @prop {?Array} ktype - Key/index type tests; `null` if unused.
220
240
  *
221
- * @param {Array} opts.value - The actual Array value to test.
222
- * @param {boolean} [opts.details=false] Return detailed test results?
223
- * @param {boolean} [opts.empty=false] Does an empty array pass the test?
241
+ * @prop {mixed} target - An alias to the `rules.value` property.
224
242
  *
225
- * @param {...any} types - See {@link module:@lumjs/core/types.isa}.
243
+ * @prop {mixed} rules.value - Should be the container we are testing.
244
+ * May be `null` if no valid `rules` were passed.
226
245
  *
227
- * All arguments other than `opts` are passed to `isa()` with each item
228
- * from the `opts.value` array as the subject of the test.
246
+ * @prop {boolean} [rules.details=false] Use detailed result objects?
229
247
  *
230
- * @returns {(boolean|object)} Results of the test.
248
+ * If `true` the `pass` and `fail` dynamic result properties will return
249
+ * {@link module:@lumjs/core/types.OfTestResult} objects.
250
+ *
251
+ * If `false` (default), the results will be simple `boolean` values.
252
+ *
253
+ * @prop {boolean} [rules.empty=false] Are empty containers valid?
231
254
  *
232
- * If `opts.details` was `true` this will be a
233
- * {@link module:@lumjs/core/types~IsArrayOfResult} object.
255
+ * Determines the return value of the `empty()` method.
256
+ *
257
+ * Should be used by the calling test function to determine if the
258
+ * test should return a `pass` or `fail` value on an empty container.
234
259
  *
235
- * Otherwise it will be a simple `boolean` value indicating
236
- * if the test passed or failed.
260
+ * @prop {boolean} state.empty - Has the container been marked empty?
237
261
  *
238
- * @alias module:@lumjs/core/types.isArrayOf
262
+ * @prop {object} state.at - Info about the last value tested.
263
+ * Will be used as the `failed` property in the `OfTestResult` object.
264
+ *
265
+ * @prop {mixed} state.at.index - Index/Key of the last value tested.
266
+ *
267
+ * For `isArrayOf()` and `isListOf()` this will always be a `number`.
268
+ *
269
+ * For `isMapOf()` this will be set to the _key_ of each item in the map,
270
+ * and so may be any type of value.
271
+ *
272
+ * This will be set to `-1` if no value has been tested yet,
273
+ * which will always be the case for empty or invalid containers.
274
+ *
275
+ * @prop {mixed} state.at.value - The last value that was tested.
276
+ *
277
+ * Will be the `rules.value` itself if no value has been tested yet,
278
+ * which will always be the case for empty or invalid containers.
279
+ *
280
+ * @prop {boolean} [state.at.key] Was it the key test that failed?
281
+ * Only included if `ktype` is not `null`.
239
282
  */
240
- function isArrayOf(opts, ...types)
283
+ class OfTest
241
284
  {
242
- if (!isObj(opts)) return false; // Failure right off the bat!
285
+ /**
286
+ * Create an `OfTest` instance.
287
+ *
288
+ * Unless you are creating a custom test function similar to the built-in
289
+ * `is*Of()` methods you'll never need to create an instance of this.
290
+ * It's documented here for the potential of advanced tests.
291
+ *
292
+ * @param {function} valid - A validity test for the `rules.value`.
293
+ *
294
+ * This will be used to set the value of the `this.valid` property.
295
+ *
296
+ * @param {object} rules - Object to set as the `this.rules` property.
297
+ *
298
+ * If `valid(rules)` returns `true` then the `rules` will be
299
+ * changed to `{value: rules}`. This is a short-cut so that
300
+ * the containers can be passed directly if default rules are okay.
301
+ *
302
+ * If this is NOT an object, then the `rules` will be changed to
303
+ * `{value: null}`, which should ensure that `this.valid` is `false`.
304
+ *
305
+ * See the class property description for the supported optional values.
306
+ *
307
+ * @param {object} rules.value - The container being tested.
308
+ *
309
+ * @param {?Array} valueTypes - An array of type tests for the values.
310
+ *
311
+ * Will be set as the `this.vtype` property.
312
+ *
313
+ * If `null` (only applicable if `keyTypes` is NOT `null`), then the
314
+ * value type tests will be skipped.
315
+ *
316
+ * See {@link module:@lumjs/core/types.isa} for details on the tests.
317
+ *
318
+ * @param {?Array} [keyTypes=null] Array of type tests for the _key/index_.
319
+ *
320
+ * Will be set as the `this.ktype` property.
321
+ *
322
+ * Uses `isa()` just like `valueTypes`. Is only needed in very specific
323
+ * cases, such as `isMapOf()` as `Map` objects may have any kind of key.
324
+ * If `null` (default), no tests against the keys will be done.
325
+ *
326
+ * @throws {TypeError} If any of the parameters is not valid.
327
+ * @throws {RangeError} If both `valueTypes` and `keyTypes` are `null`.
328
+ *
329
+ */
330
+ constructor(valid, rules, valueTypes, keyTypes=null)
331
+ {
332
+ if (typeof valid !== F)
333
+ {
334
+ throw new TypeError("Invalid OfTest validator");
335
+ }
243
336
 
244
- if (Array.isArray(opts))
245
- { // The array subject was passed.
246
- opts = {value: opts};
247
- }
337
+ if (valueTypes !== null && !isArray(valueTypes))
338
+ {
339
+ throw new TypeError("Invalid valueTypes array");
340
+ }
248
341
 
249
- let res; // Format depends on the options.
342
+ if (keyTypes !== null && !isArray(keyTypes))
343
+ {
344
+ throw new TypeError("Invalid keyTypes array");
345
+ }
250
346
 
251
- if (opts.details)
252
- {
253
- res =
347
+ if (valueTypes === null && keyTypes === null)
348
+ {
349
+ throw new RangeError("Both valueTypes and keyTypes are null");
350
+ }
351
+
352
+ if (valid(rules))
353
+ { // The container was sent instead of rules.
354
+ rules = {value: rules};
355
+ this.valid = true;
356
+ }
357
+ else if (isObj(rules))
358
+ {
359
+ this.valid = valid(rules.valid);
360
+ }
361
+ else
362
+ { // That's gonna be a no from me.
363
+ rules = {value: null};
364
+ this.valid = false;
365
+ }
366
+
367
+ this.vtype = valueTypes;
368
+ this.ktype = keyTypes;
369
+ this.rules = rules;
370
+ this.state =
254
371
  {
255
- pass: false,
256
372
  empty: false,
257
- failed:
373
+ at:
258
374
  {
259
375
  index: -1,
260
- value: val,
376
+ value: rules.value,
377
+ },
378
+ }
379
+
380
+ if (keyTypes !== null)
381
+ {
382
+ this.state.at.key = false;
383
+ }
384
+
385
+ // A shortcut for tests.
386
+ this.target = rules.value;
387
+
388
+ } // constructor()
389
+
390
+ /**
391
+ * Run `isa()` tests on the next item.
392
+ *
393
+ * @param {*} key - `state.at.index` will be set to this
394
+ *
395
+ * If `this.ktype` was set (via the `keyTypes` constructor argument),
396
+ * then the `key` will be tested for validity against those tests using
397
+ * the `isa()` function.
398
+ *
399
+ * @param {*} val - `state.at.value` will be set to this
400
+ *
401
+ * The `val` will be tested for validity against the `this.vtype` tests
402
+ * (via the `valueTests` constructor argument) using the `isa()` function.
403
+ *
404
+ * @returns {boolean} Was the `val` (and the `key` if applicable) valid?
405
+ */
406
+ test(key, val)
407
+ {
408
+ this.state.at.index = key;
409
+ this.state.at.value = val;
410
+
411
+ if (this.ktype)
412
+ {
413
+ const keyOk = isa(key, ...this.ktype);
414
+ if (!keyOk)
415
+ {
416
+ this.state.at.key = true;
417
+ return false;
261
418
  }
262
419
  }
420
+
421
+ return isa(val, ...this.vtype);
263
422
  }
264
- else
423
+
424
+ /**
425
+ * Mark `this.state.empty` as `true`
426
+ *
427
+ * @returns {boolean} `this.rules.empty`;
428
+ * determines if the test should be considered a `pass` or `fail`
429
+ * upon being marked `empty`.
430
+ */
431
+ empty()
265
432
  {
266
- res = false;
433
+ this.state.empty = true;
434
+ return !!this.rules.empty;
267
435
  }
268
436
 
269
- if (!isArray(opts.value)) return res;
437
+ /**
438
+ * A dynamic getter property that returns a _passed_ test result.
439
+ * @returns {(object|boolean)} Depends on `this.rules.details` value.
440
+ */
441
+ get pass()
442
+ {
443
+ if (this.rules.details)
444
+ {
445
+ const empty = this.state.empty;
446
+ return {pass: true, empty};
447
+ }
448
+
449
+ return true;
450
+ }
270
451
 
271
- if (opts.value.length === 0)
272
- { // An empty array.
273
- if (opts.details)
452
+ /**
453
+ * A dynamic getter property that returns a _failed_ test result.
454
+ * @returns {(object|boolean)} Depends on `this.rules.details` value.
455
+ */
456
+ get fail()
457
+ {
458
+ if (this.rules.details)
274
459
  {
275
- res.empty = true;
460
+ const {empty, at: failed} = this.state;
461
+ return {pass: false, empty, failed};
276
462
  }
277
463
 
278
- if (!opts.empty)
279
- { // Empty arrays are failure unless `opts.empty` is true.
280
- return res;
464
+ return false;
465
+ }
466
+
467
+ }
468
+
469
+ exports.OfTest = OfTest;
470
+
471
+ /**
472
+ * A nested class representing an explicit set of rules for `OfTest`.
473
+ *
474
+ * Not really needed for anything except for `isPlainObjectOf()` function.
475
+ *
476
+ * @alias module:@lumjs/core/types.OfTest.Rules
477
+ */
478
+ class OfTestRules
479
+ {
480
+ /**
481
+ * Define the Rules
482
+ *
483
+ * @param {mixed} value - Sets `rules.value`
484
+ * @param {object} [opts] Optional rules
485
+ * @param {boolean} [opts.empty=false] Sets `rules.empty`
486
+ * @param {boolean} [opts.details=false] Sets `rules.details`
487
+ *
488
+ */
489
+ constructor(value, opts)
490
+ {
491
+ this.value = value;
492
+ this.empty = opts.empty ?? false;
493
+ this.details = opts.details ?? false;
494
+ }
495
+ }
496
+
497
+ def(OfTest, 'Rules', OfTestRules);
498
+
499
+ /**
500
+ * See if every item in an `Array` passes an `isa()` test.
501
+ *
502
+ * Uses the {@link module:@lumjs/core/types.OfTest} class.
503
+ * This implementation works explicitly with `Array` objects only.
504
+ *
505
+ * @param {(object|Array)} rules - Rules for this test function.
506
+ *
507
+ * If this argument is an `Array` it is assumed to be the
508
+ * `rules.value` named option.
509
+ *
510
+ * @param {Array} rules.value - The actual Array object to test.
511
+ *
512
+ * @param {...any} types - See {@link module:@lumjs/core/types.isa}.
513
+ *
514
+ * All arguments other than `rules` are passed to `isa()` with each item
515
+ * from the `rules.value` object as the subject of the test.
516
+ *
517
+ * @returns {(boolean|object)} Results of the test.
518
+ *
519
+ * If `rules.details` was `true` this will be a
520
+ * {@link module:@lumjs/core/types~OfTestResult} object.
521
+ *
522
+ * Otherwise it will be a simple `boolean` value indicating
523
+ * if the test passed or failed.
524
+ *
525
+ * @alias module:@lumjs/core/types.isArrayOf
526
+ */
527
+ function isArrayOf(rules, ...types)
528
+ {
529
+ // Build an OfTest instance.
530
+ const test = new OfTest(isArray, rules, types);
531
+
532
+ if (!test.valid)
533
+ { // Test was invalid right off the bat.
534
+ return test.fail;
535
+ }
536
+
537
+ const target = test.target;
538
+
539
+ if (target.length === 0)
540
+ { // Array is empty.
541
+ if (!test.empty())
542
+ { // `opts.empty` was not `true`
543
+ return test.fail;
281
544
  }
282
545
  }
283
546
  else
284
547
  { // Run the tests on each item.
285
- for (let i=0; i < opts.value.length; i++)
548
+ for (let i=0; i < target.length; i++)
286
549
  {
287
- const vi = opts.value[i];
288
-
289
- if (opts.details)
290
- {
291
- res.failed.index = i;
292
- res.failed.value = vi;
293
- }
294
-
295
- if (!isa(vi, ...types))
296
- { // An item did not pass the test.
297
- return res;
550
+ if (!test.test(i, target[i]))
551
+ { // The test failed.
552
+ return test.fail;
298
553
  }
299
554
  }
300
555
  }
301
556
 
302
557
  // If we made it here, we passed.
303
- if (opts.details)
558
+ return test.pass;
559
+ }
560
+
561
+ exports.isArrayOf = isArrayOf;
562
+
563
+ /**
564
+ * See if every item in an Iterable list passes an `isa()` test.
565
+ *
566
+ * Uses the {@link module:@lumjs/core/types.OfTest} class.
567
+ * This implementation works with any kind of `Iterable` object.
568
+ *
569
+ * @param {(object|Iterable)} rules - Rules for this test function.
570
+ *
571
+ * If this argument is an `Iterable` object it is assumed to be the
572
+ * `rules.value` named option.
573
+ *
574
+ * @param {Iterable} rules.value - The actual list object for the tests.
575
+ *
576
+ * @param {...any} types - See {@link module:@lumjs/core/types.isa}.
577
+ *
578
+ * All arguments other than `rules` are passed to `isa()` with each item
579
+ * from the `rules.value` object as the subject of the test.
580
+ *
581
+ * @returns {(boolean|object)} Results of the test.
582
+ *
583
+ * If `rules.details` was `true` this will be a
584
+ * {@link module:@lumjs/core/types~OfTestResult} object.
585
+ *
586
+ * Otherwise it will be a simple `boolean` value indicating
587
+ * if the test passed or failed.
588
+ *
589
+ * @alias module:@lumjs/core/types.isListOf
590
+ */
591
+ function isListOf(rules, ...types)
592
+ {
593
+ // Build an OfTest instance.
594
+ const test = new OfTest(isIterable, rules, types);
595
+
596
+ if (!test.valid)
597
+ { // Test was invalid right off the bat.
598
+ return test.fail;
599
+ }
600
+
601
+ let i = 0;
602
+ for (const val of test.target)
304
603
  {
305
- res.pass = true;
306
- delete res.failed;
604
+ if (!test.test(i++, val))
605
+ { // The test failed.
606
+ return test.fail;
607
+ }
608
+ }
609
+
610
+ if (i === 0)
611
+ { // List was empty.
612
+ if (!test.empty())
613
+ { // `opts.empty` was not `true`
614
+ return test.fail;
615
+ }
616
+ }
617
+
618
+ // If we made it here, we passed.
619
+ return test.pass;
620
+ }
621
+
622
+ exports.isListOf = isListOf;
623
+
624
+ const isMap = v => (v instanceof Map);
625
+
626
+ /**
627
+ * See if every key/value pair in a Map passes an `isa()` test.
628
+ *
629
+ * Uses the {@link module:@lumjs/core/types.OfTest} class.
630
+ * This implementation works with only `Map` objects.
631
+ *
632
+ * Unlike `isArrayOf()` and `isListOf()`, this function does not
633
+ * have a variable argument list. It has only **3** arguments,
634
+ * and all of them are mandatory!
635
+ *
636
+ * @param {(object|Map)} rules - Rules for this test function.
637
+ *
638
+ * If this argument is a `Map` object it is assumed to be the
639
+ * `rules.value` named option.
640
+ *
641
+ * @param {Map} rules.value - The actual list object for the tests.
642
+ *
643
+ * @param {?Array} keyTypes - Types the _keys_ must be one of.
644
+ *
645
+ * If anything other than an `Array` or `null` is passed here,
646
+ * it will be wrapped in an `Array`.
647
+ *
648
+ * @param {?Array} valTypes - Types the _values_ must be one of.
649
+ *
650
+ * If anything other than an `Array` or `null` is passed here,
651
+ * it will be wrapped in an `Array`.
652
+ *
653
+ * @returns {(boolean|object)} Results of the test.
654
+ *
655
+ * If `rules.details` was `true` this will be a
656
+ * {@link module:@lumjs/core/types~OfTestResult} object.
657
+ *
658
+ * Otherwise it will be a simple `boolean` value indicating
659
+ * if the test passed or failed.
660
+ *
661
+ * @alias module:@lumjs/core/types.isListOf
662
+ */
663
+ function isMapOf(rules, keyTypes, valTypes)
664
+ {
665
+ if (notNil(keyTypes) && !isArray(keyTypes))
666
+ {
667
+ keyTypes = [keyTypes];
668
+ }
669
+
670
+ if (notNil(valTypes) && !isArray(valTypes))
671
+ {
672
+ valTypes = [valTypes];
673
+ }
674
+
675
+ const test = new OfTest(isMap, rules, valTypes, keyTypes);
676
+
677
+ if (!test.valid)
678
+ { // Test was invalid right off the bat.
679
+ return test.fail;
680
+ }
681
+
682
+ if (test.target.size === 0)
683
+ { // Map is empty.
684
+ if (!test.empty())
685
+ { // `opts.empty` was not `true`
686
+ return test.fail;
687
+ }
307
688
  }
308
689
  else
309
690
  {
310
- res = true;
691
+ for (const [key,val] of test.target)
692
+ {
693
+ if (!test.test(key,val))
694
+ {
695
+ return test.fail;
696
+ }
697
+ }
311
698
  }
312
699
 
313
- return res;
700
+ // We made it, yay!
701
+ return test.pass;
314
702
  }
315
703
 
316
- exports.isArrayOf = isArrayOf;
704
+ exports.isMapOf = isMapOf;
705
+
706
+ const isntRules = v => (isObj(v) && !(v instanceof OfTestRules));
707
+
708
+ /**
709
+ * See if all enumerable property values in an `object` pass an `isa()` test.
710
+ *
711
+ * Uses the {@link module:@lumjs/core/types.OfTest} class.
712
+ * This implementation works with any _plain_ objects.
713
+ *
714
+ * There is a special function called `rules(value, opts)` defined on this
715
+ * test function that will return an instance of the `OfTest.Rules` class,
716
+ * passing all parameters along to the constructor.
717
+ *
718
+ * @param {object} rules - Rules for this test function.
719
+ *
720
+ * If this argument is anything other than a `OfTest.Rules` instance,
721
+ * it is assumed to be the `rules.value` named option.
722
+ *
723
+ * If you want to set any of the optional rules, you can use the `rules()`
724
+ * helper function mentioned above to return a `OfTest.Rules` instance:
725
+ *
726
+ * ```js
727
+ * const opts = {details: true, empty: true}; // Example rules.
728
+ * const isValid = isObjOf(isObjOf.rules(value, opts), type1, type2, ...);
729
+ * ```
730
+ *
731
+ * @param {Iterable} rules.value - The actual list object for the tests.
732
+ *
733
+ * @param {...any} types - See {@link module:@lumjs/core/types.isa}.
734
+ *
735
+ * All arguments other than `rules` are passed to `isa()` with each item
736
+ * from the `rules.value` object as the subject of the test.
737
+ *
738
+ * @returns {(boolean|object)} Results of the test.
739
+ *
740
+ * If `rules.details` was `true` this will be a
741
+ * {@link module:@lumjs/core/types~OfTestResult} object.
742
+ *
743
+ * Otherwise it will be a simple `boolean` value indicating
744
+ * if the test passed or failed.
745
+ *
746
+ * @alias module:@lumjs/core/types.isObjOf
747
+ */
748
+ function isPlainObjectOf(rules, ...types)
749
+ {
750
+ // Build an OfTest instance.
751
+ const test = new OfTest(isntRules, rules, types);
752
+
753
+ if (!test.valid)
754
+ { // Test was invalid right off the bat.
755
+ return test.fail;
756
+ }
757
+
758
+ const target = test.target;
759
+ const keys = Object.keys(target);
760
+
761
+ if (keys.length === 0)
762
+ { // Object had no enumerable properties.
763
+ if (!test.empty())
764
+ { // `opts.empty` was not `true`
765
+ return test.fail;
766
+ }
767
+ }
768
+ else
769
+ { // There are properties to check.
770
+ for (const key of keys)
771
+ {
772
+ if (!test.test(key, target[key]))
773
+ { // The test failed.
774
+ return test.fail;
775
+ }
776
+ }
777
+ }
778
+
779
+ // If we made it here, we passed.
780
+ return test.pass;
781
+ }
782
+
783
+ def(isPlainObjectOf, 'rules', function()
784
+ {
785
+ return new OfTestRules(...arguments);
786
+ });
787
+
788
+ exports.isObjOf = isPlainObjectOf;