UNPKG

27.7 kBJavaScriptView Raw
1/* eslint-disable new-cap */
2'use strict';
3
4function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; }
5
6var Search = require('scour-search');
7var assign = require('object-assign');
8var buildExtensions = require('./build_extensions');
9var normalizeKeypath = require('../utilities/normalize_keypath');
10var utils = require('../utilities');
11var negate = require('./negate');
12var sortValues = require('../utilities/sort_values');
13var toFunction = require('to-function');
14
15/**
16 * scour : scour(object)
17 * Returns a scour instance wrapping `object`.
18 *
19 * scour(obj)
20 *
21 * It can be called on any Object or Array. (In fact, it can be called on
22 * anything, but is only generally useful for Objects and Arrays.)
23 *
24 * data = { menu: { visible: true, position: 'left' } }
25 * scour(data).get('menu.visible')
26 *
27 * list = [ { id: 2 }, { id: 5 }, { id: 12 } ]
28 * scour(list).get('0.id')
29 *
30 * __Chaining__:
31 * You can use it to start method chains. In fact, the intended use is to keep
32 * your root [scour] object around, and chain from this.
33 *
34 * db = scour({ menu: { visible: true, position: 'left' } })
35 *
36 * // Elsewhere:
37 * menu = db.go('menu')
38 * menu.get('visible')
39 *
40 * __Properties__:
41 * It the [root], [value] and [keypath] properties.
42 *
43 * s = scour(obj)
44 * s.root // => [scour object]
45 * s.value // => raw data (that is, `obj`)
46 * s.keypath // => string array
47 *
48 * __Accessing the value:__
49 * You can access the raw data using [value].
50 *
51 * db = scour(data)
52 * db.value // => same as `data`
53 * db.go('users').value // => same as `data.users`
54 */
55
56function scour(value, options) {
57 if (!(this instanceof scour)) return new scour(value, options);
58 this.value = value;
59
60 this.root = options && options.root || this;
61 this.keypath = options && options.keypath || [];
62 this.extensions = options && options.extensions || [];
63
64 // Apply any property extensions
65 if (this.extensions.length) this.applyExtensions();
66}
67
68scour.prototype = {
69 /**
70 * Chaining methods:
71 * (Section) These methods are used to traverse nested structures. All these
72 * methods return [scour] instances, making them suitable for chaining.
73 *
74 * #### On null values
75 * Note that `undefined`, `false` and `null` values are still [scour]-wrapped
76 * when returned from [go()], [at()] and [find()].
77 *
78 * list = [ { name: 'Homer' }, { name: 'Bart' } ]
79 *
80 * scour(list).at(4) // => [ scour undefined ]
81 * scour(list).at(4).value // => undefined
82 *
83 * This is done so that you can chain methods safely even when something is null.
84 * This behavior is consistent with what you'd expect with jQuery.
85 *
86 * data = { users: { ... } }
87 * db = scour(data)
88 *
89 * db.go('blogposts').map((post) => post.get('title'))
90 * // => []
91 */
92
93 /**
94 * go : go(keypath...)
95 * Navigates down to a given `keypath`. Always returns a [scour] instance.
96 * Rules [on null values] apply.
97 *
98 * data =
99 * { users:
100 * { 12: { name: 'steve', last: 'jobs' },
101 * 23: { name: 'bill', last: 'gates' } } }
102 *
103 * scour(data).go('users') // => [scour (users)]
104 * scour(data).go('users', '12') // => [scour (name, last)]
105 * scour(data).go('users', '12').get('name') // => 'steve'
106 *
107 * __Dot notation:__
108 * Keypaths can be given in dot notation or as an array. These statements are
109 * equivalent.
110 *
111 * scour(data).go('users.12')
112 * scour(data).go('users', '12')
113 * scour(data).go(['users', '12'])
114 *
115 * __Non-objects:__
116 * If you use it on a non-object or non-array value, it will still be
117 * returned as a [scour] instance. This is not likely what you want; use
118 * [get()] instead.
119 *
120 * attr = scour(data).go('users', '12', 'name')
121 * attr // => [scour object]
122 * attr.value // => 'steve'
123 * attr.keypath // => ['users', '12', 'name']
124 */
125
126 go: function go() {
127 var keypath = normalizeKeypath(arguments, true);
128 var result = this.get.apply(this, keypath);
129 return this._get(result, keypath);
130 },
131
132 /**
133 * Internal: gathers multiple keys; not used yet
134 */
135
136 gather: function gather(keypaths) {
137 var _this = this;
138
139 var result;
140 if (Array.isArray(this.value)) {
141 result = keypaths.map(function (key, val) {
142 return _this.get(key);
143 });
144 } else {
145 result = utils.indexedMap(keypaths, function (key) {
146 return [key, _this.get(key)];
147 });
148 }
149 return this.reset(result);
150 },
151
152 /**
153 * Returns the item at `index`. This differs from `go` as this searches by
154 * index, not by key. This returns a the raw value, unlike [getAt()]. Rules
155 * [on null values] apply.
156 *
157 * users =
158 * { 12: { name: 'steve' },
159 * 23: { name: 'bill' } }
160 *
161 * scour(users).at(0) // => [scour { name: 'steve' }]
162 * scour(users).get(12) // => [scour { name: 'steve' }]
163 */
164
165 at: function at(index) {
166 if (Array.isArray(this.value)) {
167 return this._get(this.value[index], ['' + index]);
168 }
169
170 var key = this.keys()[index];
171 return this._get(key && this.value[key], ['' + key]);
172 },
173
174 /**
175 * Returns the item at `index`. This differs from `get` as this searches by
176 * index, not by key. This returns a the raw value, unlike [at()].
177 *
178 * users =
179 * { 12: { name: 'steve' },
180 * 23: { name: 'bill' } }
181 *
182 * scour(users).at(0) // => [scour { name: 'steve' }]
183 * scour(users).getAt(0) // => { name: 'steve' }
184 */
185
186 getAt: function getAt(index) {
187 if (Array.isArray(this.value)) return this.value[index];
188 var key = this.keys()[index];
189 return key && this.value[key];
190 },
191
192 /**
193 * Sifts through the values and returns a set that matches given
194 * `conditions`. Supports simple objects, MongoDB-style
195 * queries, and functions.
196 *
197 * scour(data).filter({ name: 'Moe' })
198 * scour(data).filter({ name: { $in: ['Larry', 'Curly'] })
199 * scour(data).filter((item) => item.get('name') === 'Moe')
200 *
201 * __Filter by object:__
202 * If you pass an object as a condition, `filter()` will check if that object
203 * coincides with the objects in the collection.
204 *
205 * scour(data).filter({ name: 'Moe' })
206 *
207 * __Filter by function:__
208 * You may pass a function as a parameter. In this case, the `item` being
209 * passed to the callback will be a [scour]-wrapped object. The result
210 * will also be a [scour]-wrapped object, making it chainable.
211 *
212 * scour(data)
213 * .filter((item, key) => +item.get('price') > 200)
214 * .sortBy('price')
215 * .first()
216 *
217 * __Advanced queries:__
218 * MongoDB-style queries are supported as provided by [sift.js]. For
219 * reference, see [MongoDB Query Operators][query-ops].
220 *
221 * scour(products).filter({ price: { $gt: 200 })
222 * scour(articles).filter({ published_at: { $not: null }})
223 *
224 * __Arrays or objects:__
225 * Both arrays and array-like objects are supported. In this example below,
226 * an object will be used as the input.
227 *
228 * devices =
229 * { 1: { id: 1, name: 'Phone', mobile: true },
230 * 2: { id: 2, name: 'Tablet', mobile: true },
231 * 3: { id: 3, name: 'Desktop', mobile: false } }
232 *
233 * scour(devices).filter({ mobile: true }).len()
234 * // => 2
235 *
236 * Also see [scour.filter()] for the unwrapped version.
237 *
238 * [query-ops]: https://docs.mongodb.org/manual/reference/operator/query/
239 */
240
241 filter: function filter(conditions) {
242 if (!this.value) return this.reset([]);
243 if (typeof conditions === 'function') {
244 return this.filterByFunction(conditions);
245 }
246 return this.reset(Search(this.value).filter(conditions));
247 },
248 filterByFunction: function filterByFunction(fn) {
249 var isArray = Array.isArray(this.value);
250 var result;
251
252 if (isArray) {
253 result = [];
254 this.each(function (val, key) {
255 return fn(val, key) && result.push(val.value);
256 });
257 } else {
258 result = {};
259 this.each(function (val, key) {
260 if (fn(val, key)) result[key] = val.value;
261 });
262 }
263
264 return this.reset(result);
265 },
266
267 /**
268 * Inverse of [filter()] -- see `filter()` documentation for details.
269 */
270
271 reject: function reject(conditions) {
272 if (!this.value) return this.reset([]);
273 if (typeof conditions === 'function') {
274 return this.filterByFunction(negate(conditions));
275 } else {
276 return this.filter({ $not: conditions });
277 }
278 },
279
280 /**
281 * Returns the first value that matches `conditions`. Supports MongoDB-style
282 * queries. For reference, see [MongoDB Query Operators][query-ops]. Also
283 * see [filter()], as this is functionally-equivalent to the first result of
284 * `filter()`. Rules [on null values] apply.
285 *
286 * [query-ops]: https://docs.mongodb.org/manual/reference/operator/query/
287 *
288 * scour(data).find({ name: 'john' })
289 * scour(data).find({ name: { $in: ['moe', 'larry'] })
290 */
291
292 find: function find(conditions) {
293 var value = this.value;
294 var key = Search(value).indexOf(conditions);
295 if (key === -1) return;
296 return this._get(value[key], [key]);
297 },
298
299 /**
300 * Returns the first result as a [scour]-wrapped object. This is equivalent
301 * to [at(0)](#at).
302 */
303
304 first: function first() {
305 return this.at(0);
306 },
307
308 /**
309 * Returns the first result as a [scour]-wrapped object. This is equivalent
310 * to `at(len() - 1)`: see [at()] and [len()].
311 */
312
313 last: function last() {
314 var len = this.len();
315 return this.at(len - 1);
316 },
317
318 /**
319 * Sorts a collection. Returns a [scour]-wrapped object suitable for
320 * chaining. Like other chainable methods, this works on arrays as well as
321 * objects.
322 *
323 * data =
324 * { 0: { name: 'Wilma' },
325 * 1: { name: 'Barney' },
326 * 2: { name: 'Fred' } }
327 *
328 * scour(data).sortBy('name').value
329 * // { 1: { name: 'Barney' },
330 * // 2: { name: 'Fred' },
331 * // 0: { name: 'Wilma' } }
332 *
333 * __Conditions:__
334 * The given condition can be a string or a function. When it's given as a
335 * function, the `item` being passed is a [scour]-wrapped object, just like
336 * in [forEach()] (et al). These two examples below are
337 * functionally-equivalent.
338 *
339 * scour(data).sortBy('name')
340 * scour(data).sortBy((item) => item.get('name'))
341 *
342 * You may also define nested keys in dot-notation:
343 *
344 * scour(data).sortBy('user.name')
345 */
346
347 sortBy: function sortBy(condition) {
348 if (!this.value) return this.reset([]);
349 var values;
350
351 if (typeof condition === 'string') {
352 var key = condition;
353 condition = toFunction(key);
354 // don't use `this.map` or `this.each` so we skip `new scour()`
355 values = utils.map(this.value, function (value, key, index) {
356 return {
357 key: key, value: value, criteria: condition(value, key), index: index
358 };
359 });
360 } else {
361 values = this.map(function (value, key, index) {
362 return {
363 key: key, value: value.value, criteria: condition(value, key), index: index
364 };
365 });
366 }
367
368 var sorted = sortValues(values, Array.isArray(this.value));
369 return this.reset(sorted);
370 },
371
372 /**
373 * Reading methods:
374 * (Section) For retrieving data.
375 */
376
377 /**
378 * get : get(keypath...)
379 * Returns data in a given `keypath`.
380 *
381 * data =
382 * { users:
383 * { 12: { name: 'steve' },
384 * 23: { name: 'bill' } } }
385 *
386 * scour(data).get('users') // => same as data.users
387 * scour(data).go('users').value // => same as data.users
388 *
389 * __Dot notation:__
390 * Like [go()], the `keypath` can be given in dot notation.
391 *
392 * scour(data).get('books.featured.name')
393 * scour(data).get('books', 'featured', 'name')
394 */
395
396 get: function get() {
397 if (!this.value) return;
398 var keypath = normalizeKeypath(arguments, true);
399 return utils.get(this.value, keypath);
400 },
401
402 /**
403 * Returns the length of the object or array. For objects, it returns the
404 * number of keys.
405 *
406 * users =
407 * { 12: { name: 'steve' },
408 * 23: { name: 'bill' } }
409 *
410 * names = scour(users).len() // => 2
411 */
412
413 len: function len() {
414 if (!this.value) return 0;
415 if (Array.isArray(this.value)) return this.value.length;
416 return this.keys().length;
417 },
418
419 /**
420 * Returns an array. If the the value is an object, it returns the values of
421 * that object. If the value is an array, it returns it as is. Also aliased
422 * as `values()`.
423 *
424 * users =
425 * { 12: { name: 'steve' },
426 * 23: { name: 'bill' } }
427 *
428 * names = scour(users).toArray()
429 * // => [ {name: 'steve'}, {name: 'bill'} ]
430 */
431
432 toArray: function toArray() {
433 if (Array.isArray(this.value)) return this.value;
434 return scour.map(this.value, function (val, key) {
435 return val;
436 });
437 },
438 values: function values() {
439 return this.toArray();
440 },
441
442 /**
443 * Returns keys. If the value is an array, this returns the array's indices.
444 * Also see [toArray()] to retrieve the values instead.
445 */
446
447 keys: function keys() {
448 if (!this.value) return [];
449 return Object.keys(this.value);
450 },
451
452 /**
453 * Writing methods:
454 * (Section) These are methods for modifying an object/array tree immutably.
455 * Note that all these functions are immutable--it will not modify existing
456 * data, but rather spawn new objects with the modifications done on them.
457 */
458
459 /**
460 * Sets values immutably. Returns a copy of the same object ([scour]-wrapped)
461 * with the modifications applied.
462 *
463 * data = { bob: { name: 'Bob' } }
464 * db = scour(data)
465 * db.set([ 'bob', 'name' ], 'Robert')
466 * // db.value == { bob: { name: 'Robert' } }
467 *
468 * __Immutability:__
469 * This is an immutable function, and will return a new object. It won't
470 * modify your original object.
471 *
472 * profile = scour({ name: 'John' })
473 * profile2 = profile.set('email', 'john@gmail.com')
474 *
475 * profile.value // => { name: 'John' }
476 * profile2.value // => { name: 'John', email: 'john@gmail.com' }
477 *
478 * __Using within a scope:__
479 * Be aware that using all writing methods ([set()], [del()], [extend()]) on
480 * scoped objects (ie, made with [go()]) will spawn a new [root] object. If
481 * you're keeping a reference to the root object, you'll need to update it
482 * accordingly.
483 *
484 * db = scour(data)
485 * book = db.go('book')
486 * book.root === db // correct so far
487 *
488 * book = book.set('title', 'IQ84')
489 * book = book.del('sale_price')
490 * book.root !== db // `root` has been updated
491 *
492 * __Dot notation:__
493 * Like [go()] and [get()], the keypath can be given in dot notation or an
494 * array.
495 *
496 * scour(data).set('menu.left.visible', true)
497 * scour(data).set(['menu', 'left', 'visible'], true)
498 */
499
500 set: function set(keypath, value) {
501 keypath = normalizeKeypath(keypath);
502
503 if (this.root !== this) {
504 return this.root.set(this.keypath.concat(keypath), value).go(this.keypath);
505 }
506
507 // use .valueOf() to denature any scour-wrapping or String() or whatnot
508 var result = scour.set(this.value || {}, keypath, value.valueOf());
509 return this.reset(result, { root: null });
510 },
511
512 /**
513 * Deletes values immutably. Returns a copy of the same object
514 * ([scour]-wrapped) with the modifications applied.
515 *
516 * Like [set()], the keypath can be given in dot notation or an
517 * array.
518 *
519 * scour(data).del('menu.left.visible')
520 * scour(data).del(['menu', 'left', 'visible'])
521 *
522 * See [set()] for more information on working with immutables.
523 */
524
525 del: function del(keypath) {
526 if (!this.value) return this;
527 keypath = normalizeKeypath(keypath);
528
529 if (this.root !== this) {
530 return this.root.del(this.keypath.concat(keypath)).go(this.keypath);
531 }
532
533 var result = scour.del(this.value, keypath);
534 return this.reset(result, { root: null });
535 },
536
537 /**
538 * extend : extend(objects...)
539 * Extends the data with more values. Returns a [scour]-wrapped object. Just
540 * like [Object.assign], you may pass multiple objects to the parameters.
541 *
542 * data = { a: 1, b: 2 }
543 * data2 = scour(data).extend({ c: 3 })
544 *
545 * data2 // => [scour { a: 1, b: 2, c: 3 }]
546 * data2.value // => { a: 1, b: 2, c: 3 }
547 *
548 * When used with anything non-object, it will be overridden.
549 *
550 * data = {}
551 * db = scour(data)
552 * db = db.go('state').extend({ pressed: true }).root
553 *
554 * db.value // => { state: { pressed: true } }
555 *
556 * See [set()] for more information on working with immutables.
557 */
558
559 extend: function extend() {
560 var result = {};
561 if (_typeof(this.value) === 'object' && !Array.isArray(this.value)) {
562 assign(result, this.value);
563 }
564
565 for (var i = 0, len = arguments.length; i < len; i++) {
566 if (_typeof(arguments[i]) !== 'object') return;
567 assign(result, arguments[i]);
568 }
569
570 if (this.root !== this) {
571 return this.root.set(this.keypath, result).go(this.keypath);
572 }
573
574 return this.reset(result, { root: false });
575 },
576
577 /**
578 * Utility methods:
579 * (Section) For stuff.
580 */
581
582 /**
583 * use : use(extensions)
584 * Extends functionality for certain keypaths with custom methods.
585 * See [Extensions example] for examples.
586 *
587 * data =
588 * { users:
589 * { 12: { name: 'steve', surname: 'jobs' },
590 * 23: { name: 'bill', surname: 'gates' } } }
591 *
592 * extensions = {
593 * 'users.*': {
594 * fullname () {
595 * return this.get('name') + ' ' + this.get('surname')
596 * }
597 * }
598 * }
599 *
600 * scour(data)
601 * .use(extensions)
602 * .get('users', 12)
603 * .fullname() // => 'bill gates'
604 *
605 * __Extensions format:__
606 * The parameter `extension` is an object, with keys being keypath globs, and
607 * values being properties to be extended.
608 *
609 * .use({
610 * 'books.*': { ... },
611 * 'authors.*': { ... },
612 * 'publishers.*': { ... }
613 * })
614 *
615 * __Extending root:__
616 * To bind properties to the root method, use an empty string as the keypath.
617 *
618 * .use({
619 * '': {
620 * users() { return this.go('users') },
621 * authors() { return this.go('authors') }
622 * }
623 * })
624 *
625 * __Keypath filtering:__
626 * You can use glob-like `*` and `**` to match parts of a keypath. A `*` will
627 * match any one segment, and `**` will match one or many segments. Here are
628 * some examples:
629 *
630 * - `users.*` - will match `users.1`, but not `users.1.photos`
631 * - `users.**` - will match `users.1.photos`
632 * - `users.*.photos` - will match `users.1.photos`
633 * - `**` will match anything
634 *
635 * __When using outside root:__
636 * Any extensions in a scoped object (ie, made with [go()]) will be used relative
637 * to it. For instance, if you define an extension to `admins.*` inside
638 * `.go('users')`, it will affect `users.
639 *
640 * data = { users: { john: { } }
641 * db = scour(data)
642 *
643 * users = db.go('users')
644 * .use({ '*': { hasName () { return !!this.get('name') } })
645 *
646 * users.go('john').hasName() // works
647 *
648 * While this is supported, it is *not* recommended: these extensions will not
649 * propagate back to the root, and any objects taken from the root will not
650 * have those extensions applied to them.
651 *
652 * users.go('john').hasName() // works
653 * db.go('users.john').hasName() // doesn't work
654 */
655
656 use: function use(spec) {
657 var extensions = buildExtensions(this.keypath, spec);
658 if (this.root === this) {
659 return this.reset(this.value, { extensions: extensions, root: null });
660 } else {
661 // Spawn a new `root` with the extensions applied
662 return this.root.reset(this.root.value, { extensions: extensions, root: null }).reset(this.value, { keypath: this.keypath });
663 }
664 },
665
666 /**
667 * Returns the value for serialization. This allows `JSON.stringify()` to
668 * work with `scour`-wrapped objects. The name of this method is a bit
669 * confusing, as it doesn't actually return a JSON string — but I'm afraid
670 * that it's the way that the JavaScript API for [JSON.stringify] works.
671 *
672 * [JSON.stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON%28%29_behavior
673 */
674
675 toJSON: function toJSON() {
676 return this.value;
677 },
678 valueOf: function valueOf() {
679 return this.value;
680 },
681
682 /**
683 * Checks for equality between two Scour-wrapped objects.
684 *
685 * a = scour(data)
686 * b = scour(data)
687 *
688 * a.equal(b) // => true
689 */
690
691 equal: function equal(other) {
692 return this.value === other.value && this.keypath.join('.') === other.keypath.join('.');
693 },
694 toString: function toString() {
695 return '[scour (' + this.keys().join(', ') + ')]';
696 },
697
698 /**
699 * Iteration methods:
700 * (Section) These methods are generally useful for collections. These
701 * methods can work with either arrays or array-like objects, such as
702 * below.
703 *
704 * subjects =
705 * { 1: { id: 1, title: 'Math', level: 101 },
706 * 2: { id: 2, title: 'Science', level: 103 },
707 * 3: { id: 3, title: 'History', level: 102 } }
708 *
709 * __Values:__
710 * For all these functions, The items passed onto the callbacks _is_ a
711 * [scour]-wrapped object. Use `item.value` or `this` to access the raw
712 * values.
713 *
714 * scour(subjects).forEach((subject, key) => {
715 * console.log(subject.get('title'))
716 * })
717 *
718 * __Return values:__
719 * For methods that return values (such as [map()], the returned results _is
720 * not_ a [scour]-wrapped object, and isn't suitable for chaining.
721 *
722 * scour(subjects).map((subject, key) => {
723 * return subject.get('title') + ' ' + subject.get('level')
724 * })
725 * // => [ 'Math 101', 'Science 103', 'History 102' ]
726 */
727
728 /**
729 * forEach : forEach(function(item, key, index))
730 * Loops through each item. Supports both arrays and objects.
731 * The rules specified in [Iteration methods] apply.
732 *
733 * users =
734 * { 12: { name: 'steve' },
735 * 23: { name: 'bill' } }
736 *
737 * scour(users).each((user, key) => {
738 * console.log(user.get('name'))
739 * })
740 *
741 * The values passed onto the function are:
742 *
743 * - `item` - the value; always a scour object.
744 * - `key` - the key.
745 * - `index` - the index.
746 */
747
748 forEach: function forEach(fn) {
749 var _this2 = this;
750
751 var index = 0;
752 scour.each(this.value, function (val, key) {
753 fn.call(val, _this2._get(val, [key]), key, index++);
754 });
755 return this;
756 },
757
758 /**
759 * Alias for [forEach](#foreach).
760 */
761
762 each: function each(fn) {
763 return this.forEach(fn);
764 },
765
766 /**
767 * map : map(function(item, key))
768 * Loops through each item and returns an array based on the iterator's
769 * return values. Supports both arrays and objects.
770 * The rules specified in [Iteration methods] apply.
771 *
772 * users =
773 * { 12: { name: 'Steve' },
774 * 23: { name: 'Bill' } }
775 *
776 * names = scour(users).map((user, key) => user.get('name'))
777 * // => [ 'Steve', 'Bill' ]
778 */
779
780 map: thisify(utils.map),
781
782 /**
783 * mapObject : mapObject(function(val, key))
784 * Creates a new `Object` with with the results of calling a provided function
785 * on every element in this array. Works like [Array#map], but also works on
786 * objects as well as arrays, and it returns an object instead.
787 * The rules specified in [Iteration methods] apply.
788 *
789 * See [scour.mapObject()] for details and the non-wrapped version.
790 */
791
792 mapObject: thisify(utils.mapObject),
793
794 /**
795 * indexedMap : indexedMap(function(val, key))
796 * Creates a new `Object` with with the results of calling a provided function
797 * returning the keys and values for the new object.
798 * The rules specified in [Iteration methods] apply.
799 *
800 * See [scour.indexedMap()] for details and the non-wrapped version.
801 */
802
803 indexedMap: thisify(utils.indexedMap),
804
805 /**
806 * Internal: spawns an instance with a given data and keypath.
807 */
808
809 _get: function _get(result, keypath) {
810 return this.reset(result, {
811 keypath: this.keypath.concat(keypath)
812 });
813 },
814
815 /**
816 * Returns a clone with the `value` replaced. The new instance will
817 * retain the same properties, so things like [use()] extensions are carried
818 * over.
819 *
820 * db = scour({ name: 'hello' })
821 * db.value //=> { name: 'hello' }
822 *
823 * db = db.reset({})
824 * db.value // => {}
825 *
826 * This is useful for, say, using Scour with [Redux] and implementing an
827 * action to reset the state back to empty.
828 */
829
830 reset: function reset(value, options) {
831 var op = options || {};
832 return new scour(value, {
833 root: typeof op.root !== 'undefined' ? op.root : this.root,
834 keypath: typeof op.keypath !== 'undefined' ? op.keypath : this.keypath,
835 extensions: typeof op.extensions !== 'undefined' ? this.extensions.concat(op.extensions) : this.extensions
836 });
837 },
838
839 /**
840 * Internal: applies extensions
841 */
842
843 applyExtensions: function applyExtensions() {
844 var _this3 = this;
845
846 var path = this.keypath.join('.');
847
848 this.extensions.forEach(function (extension) {
849 // extension is [ RegExp, properties object ]
850 if (extension[0].test(path)) assign(_this3, extension[1]);
851 });
852 }
853};
854
855/**
856 * Attributes:
857 * (Section) These attributes are available to [scour] instances.
858 */
859
860/**
861 * value : value
862 * The raw value being wrapped. You can use this to terminate a chained call.
863 *
864 * users =
865 * [ { name: 'john', admin: true },
866 * { name: 'kyle', admin: false } ]
867 *
868 * scour(users)
869 * .filter({ admin: true })
870 * .value
871 * // => [ { name: 'john', admin: true } ]
872 */
873
874/**
875 * root : root
876 * A reference to the root [scour] instance.
877 * Everytime you traverse using [go()], a new [scour] object is spawned that's
878 * scoped to a keypath. Each of these [scour] objects have a `root` attribute
879 * that's a reference to the top-level [scour] object.
880 *
881 * db = scour(...)
882 *
883 * photos = db.go('photos')
884 * photos.root // => same as `db`
885 *
886 * This allows you to return to the root when needed.
887 *
888 * db = scour(...)
889 * artist = db.go('artists', '9328')
890 * artist.root.go('albums').find({ artist_id: artist.get('id') })
891 */
892
893/**
894 * keypath : keypath
895 * An array of strings representing each step in how deep the current scope is
896 * relative to the root. Each time you traverse using [go()], a new [scour]
897 * object is spawned.
898 *
899 * db = scour(...)
900 *
901 * users = db.go('users')
902 * users.keypath // => ['users']
903 *
904 * admins = users.go('admins')
905 * admins.keypath // => ['users', 'admins']
906 *
907 * user = admins.go('23')
908 * user.keypath // => ['users', 'admins', '23']
909 */
910
911// Export utilities
912assign(scour, utils);
913
914/**
915 * Internal: decorates collection functions
916 */
917
918function thisify(fn) {
919 return function () {
920 return fn.bind(null, this.forEach.bind(this)).apply(this, arguments);
921 };
922}
923
924module.exports = scour;
\No newline at end of file