UNPKG

27.3 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. Only
540 * supports objects; arrays and non-objects will return undefined. Just like
541 * [Object.assign], you may pass multiple objects to the parameters.
542 *
543 * data = { a: 1, b: 2 }
544 * data2 = scour(data).extend({ c: 3 })
545 *
546 * data2 // => [scour { a: 1, b: 2, c: 3 }]
547 * data2.value // => { a: 1, b: 2, c: 3 }
548 *
549 * See [set()] for more information on working with immutables.
550 */
551
552 extend: function extend() {
553 if (_typeof(this.value) !== 'object' || Array.isArray(this.value)) return;
554 var result = {};
555 assign(result, this.value);
556 for (var i = 0, len = arguments.length; i < len; i++) {
557 if (_typeof(arguments[i]) !== 'object') return;
558 assign(result, arguments[i]);
559 }
560
561 if (this.root !== this) {
562 return this.root.set(this.keypath, result).go(this.keypath);
563 }
564
565 return this.reset(result, { root: false });
566 },
567
568 /**
569 * Utility methods:
570 * (Section) For stuff.
571 */
572
573 /**
574 * use : use(extensions)
575 * Extends functionality for certain keypaths with custom methods.
576 * See [Extensions example] for examples.
577 *
578 * data =
579 * { users:
580 * { 12: { name: 'steve', surname: 'jobs' },
581 * 23: { name: 'bill', surname: 'gates' } } }
582 *
583 * extensions = {
584 * 'users.*': {
585 * fullname () {
586 * return this.get('name') + ' ' + this.get('surname')
587 * }
588 * }
589 * }
590 *
591 * scour(data)
592 * .use(extensions)
593 * .get('users', 12)
594 * .fullname() // => 'bill gates'
595 *
596 * __Extensions format:__
597 * The parameter `extension` is an object, with keys being keypath globs, and
598 * values being properties to be extended.
599 *
600 * .use({
601 * 'books.*': { ... },
602 * 'authors.*': { ... },
603 * 'publishers.*': { ... }
604 * })
605 *
606 * __Extending root:__
607 * To bind properties to the root method, use an empty string as the keypath.
608 *
609 * .use({
610 * '': {
611 * users() { return this.go('users') },
612 * authors() { return this.go('authors') }
613 * }
614 * })
615 *
616 * __Keypath filtering:__
617 * You can use glob-like `*` and `**` to match parts of a keypath. A `*` will
618 * match any one segment, and `**` will match one or many segments. Here are
619 * some examples:
620 *
621 * - `users.*` - will match `users.1`, but not `users.1.photos`
622 * - `users.**` - will match `users.1.photos`
623 * - `users.*.photos` - will match `users.1.photos`
624 * - `**` will match anything
625 *
626 * __When using outside root:__
627 * Any extensions in a scoped object (ie, made with [go()]) will be used relative
628 * to it. For instance, if you define an extension to `admins.*` inside
629 * `.go('users')`, it will affect `users.
630 *
631 * data = { users: { john: { } }
632 * db = scour(data)
633 *
634 * users = db.go('users')
635 * .use({ '*': { hasName () { return !!this.get('name') } })
636 *
637 * users.go('john').hasName() // works
638 *
639 * While this is supported, it is *not* recommended: these extensions will not
640 * propagate back to the root, and any objects taken from the root will not
641 * have those extensions applied to them.
642 *
643 * users.go('john').hasName() // works
644 * db.go('users.john').hasName() // doesn't work
645 */
646
647 use: function use(spec) {
648 var extensions = buildExtensions(this.keypath, spec);
649 if (this.root === this) {
650 return this.reset(this.value, { extensions: extensions, root: null });
651 } else {
652 // Spawn a new `root` with the extensions applied
653 return this.root.reset(this.root.value, { extensions: extensions, root: null }).reset(this.value, { keypath: this.keypath });
654 }
655 },
656
657 /**
658 * Returns the value for serialization. This allows `JSON.stringify()` to
659 * work with `scour`-wrapped objects. The name of this method is a bit
660 * confusing, as it doesn't actually return a JSON string — but I'm afraid
661 * that it's the way that the JavaScript API for [JSON.stringify] works.
662 *
663 * [JSON.stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON%28%29_behavior
664 */
665
666 toJSON: function toJSON() {
667 return this.value;
668 },
669 valueOf: function valueOf() {
670 return this.value;
671 },
672 toString: function toString() {
673 return '[scour (' + this.keys().join(', ') + ')]';
674 },
675
676 /**
677 * Iteration methods:
678 * (Section) These methods are generally useful for collections. These
679 * methods can work with either arrays or array-like objects, such as
680 * below.
681 *
682 * subjects =
683 * { 1: { id: 1, title: 'Math', level: 101 },
684 * 2: { id: 2, title: 'Science', level: 103 },
685 * 3: { id: 3, title: 'History', level: 102 } }
686 *
687 * __Values:__
688 * For all these functions, The items passed onto the callbacks _is_ a
689 * [scour]-wrapped object. Use `item.value` or `this` to access the raw
690 * values.
691 *
692 * scour(subjects).forEach((subject, key) => {
693 * console.log(subject.get('title'))
694 * })
695 *
696 * __Return values:__
697 * For methods that return values (such as [map()], the returned results _is
698 * not_ a [scour]-wrapped object, and isn't suitable for chaining.
699 *
700 * scour(subjects).map((subject, key) => {
701 * return subject.get('title') + ' ' + subject.get('level')
702 * })
703 * // => [ 'Math 101', 'Science 103', 'History 102' ]
704 */
705
706 /**
707 * forEach : forEach(function(item, key, index))
708 * Loops through each item. Supports both arrays and objects.
709 * The rules specified in [Iteration methods] apply.
710 *
711 * users =
712 * { 12: { name: 'steve' },
713 * 23: { name: 'bill' } }
714 *
715 * scour(users).each((user, key) => {
716 * console.log(user.get('name'))
717 * })
718 *
719 * The values passed onto the function are:
720 *
721 * - `item` - the value; always a scour object.
722 * - `key` - the key.
723 * - `index` - the index.
724 */
725
726 forEach: function forEach(fn) {
727 var _this2 = this;
728
729 var index = 0;
730 scour.each(this.value, function (val, key) {
731 fn.call(val, _this2._get(val, [key]), key, index++);
732 });
733 return this;
734 },
735
736 /**
737 * Alias for [forEach](#foreach).
738 */
739
740 each: function each(fn) {
741 return this.forEach(fn);
742 },
743
744 /**
745 * map : map(function(item, key))
746 * Loops through each item and returns an array based on the iterator's
747 * return values. Supports both arrays and objects.
748 * The rules specified in [Iteration methods] apply.
749 *
750 * users =
751 * { 12: { name: 'Steve' },
752 * 23: { name: 'Bill' } }
753 *
754 * names = scour(users).map((user, key) => user.get('name'))
755 * // => [ 'Steve', 'Bill' ]
756 */
757
758 map: thisify(utils.map),
759
760 /**
761 * mapObject : mapObject(function(val, key))
762 * Creates a new `Object` with with the results of calling a provided function
763 * on every element in this array. Works like [Array#map], but also works on
764 * objects as well as arrays, and it returns an object instead.
765 * The rules specified in [Iteration methods] apply.
766 *
767 * See [scour.mapObject()] for details and the non-wrapped version.
768 */
769
770 mapObject: thisify(utils.mapObject),
771
772 /**
773 * indexedMap : indexedMap(function(val, key))
774 * Creates a new `Object` with with the results of calling a provided function
775 * returning the keys and values for the new object.
776 * The rules specified in [Iteration methods] apply.
777 *
778 * See [scour.indexedMap()] for details and the non-wrapped version.
779 */
780
781 indexedMap: thisify(utils.indexedMap),
782
783 /**
784 * Internal: spawns an instance with a given data and keypath.
785 */
786
787 _get: function _get(result, keypath) {
788 return this.reset(result, {
789 keypath: this.keypath.concat(keypath)
790 });
791 },
792
793 /**
794 * Returns a clone with the `value` replaced. The new instance will
795 * retain the same properties, so things like [use()] extensions are carried
796 * over.
797 *
798 * db = scour({ name: 'hello' })
799 * db.value //=> { name: 'hello' }
800 *
801 * db = db.reset({})
802 * db.value // => {}
803 *
804 * This is useful for, say, using Scour with [Redux] and implementing an
805 * action to reset the state back to empty.
806 */
807
808 reset: function reset(value, options) {
809 var op = options || {};
810 return new scour(value, {
811 root: typeof op.root !== 'undefined' ? op.root : this.root,
812 keypath: typeof op.keypath !== 'undefined' ? op.keypath : this.keypath,
813 extensions: typeof op.extensions !== 'undefined' ? this.extensions.concat(op.extensions) : this.extensions
814 });
815 },
816
817 /**
818 * Internal: applies extensions
819 */
820
821 applyExtensions: function applyExtensions() {
822 var _this3 = this;
823
824 var path = this.keypath.join('.');
825
826 this.extensions.forEach(function (extension) {
827 // extension is [ RegExp, properties object ]
828 if (extension[0].test(path)) assign(_this3, extension[1]);
829 });
830 }
831};
832
833/**
834 * Attributes:
835 * (Section) These attributes are available to [scour] instances.
836 */
837
838/**
839 * value : value
840 * The raw value being wrapped. You can use this to terminate a chained call.
841 *
842 * users =
843 * [ { name: 'john', admin: true },
844 * { name: 'kyle', admin: false } ]
845 *
846 * scour(users)
847 * .filter({ admin: true })
848 * .value
849 * // => [ { name: 'john', admin: true } ]
850 */
851
852/**
853 * root : root
854 * A reference to the root [scour] instance.
855 * Everytime you traverse using [go()], a new [scour] object is spawned that's
856 * scoped to a keypath. Each of these [scour] objects have a `root` attribute
857 * that's a reference to the top-level [scour] object.
858 *
859 * db = scour(...)
860 *
861 * photos = db.go('photos')
862 * photos.root // => same as `db`
863 *
864 * This allows you to return to the root when needed.
865 *
866 * db = scour(...)
867 * artist = db.go('artists', '9328')
868 * artist.root.go('albums').find({ artist_id: artist.get('id') })
869 */
870
871/**
872 * keypath : keypath
873 * An array of strings representing each step in how deep the current scope is
874 * relative to the root. Each time you traverse using [go()], a new [scour]
875 * object is spawned.
876 *
877 * db = scour(...)
878 *
879 * users = db.go('users')
880 * users.keypath // => ['users']
881 *
882 * admins = users.go('admins')
883 * admins.keypath // => ['users', 'admins']
884 *
885 * user = admins.go('23')
886 * user.keypath // => ['users', 'admins', '23']
887 */
888
889// Export utilities
890assign(scour, utils);
891
892/**
893 * Internal: decorates collection functions
894 */
895
896function thisify(fn) {
897 return function () {
898 return fn.bind(null, this.forEach.bind(this)).apply(this, arguments);
899 };
900}
901
902module.exports = scour;
\No newline at end of file