1 | /* eslint-disable new-cap */
|
2 |
|
3 |
|
4 | const sift = require('sift')
|
5 | const assign = require('object-assign')
|
6 | const buildExtensions = require('./lib/build_extensions')
|
7 | const normalizeKeypath = require('./lib/normalize_keypath')
|
8 |
|
9 | /**
|
10 | * scour : scour(object)
|
11 | * Returns a scour instance wrapping `object`.
|
12 | *
|
13 | * scour(obj)
|
14 | *
|
15 | * It can be called on any Object or Array. (In fact, it can be called on
|
16 | * anything, but is only generally useful for Objects and Arrays.)
|
17 | *
|
18 | * data = { menu: { visible: true, position: 'left' } }
|
19 | * scour(data).get('menu.visible')
|
20 | *
|
21 | * list = [ { id: 2 }, { id: 5 }, { id: 12 } ]
|
22 | * scour(list).get('0.id')
|
23 | *
|
24 | * __Chaining__:
|
25 | * You can use it to start method chains. In fact, the intended use is to keep
|
26 | * your root [scour] object around, and chain from this.
|
27 | *
|
28 | * db = scour({ menu: { visible: true, position: 'left' } })
|
29 | *
|
30 | * // Elsewhere:
|
31 | * menu = db.go('menu')
|
32 | * menu.get('visible')
|
33 | *
|
34 | * __Properties__:
|
35 | * It the [root], [value] and [keypath] properties.
|
36 | *
|
37 | * s = scour(obj)
|
38 | * s.root // => [scour object]
|
39 | * s.value // => raw data (that is, `obj`)
|
40 | * s.keypath // => string array
|
41 | *
|
42 | * __Accessing the value:__
|
43 | * You can access the raw data using [value].
|
44 | *
|
45 | * db = scour(data)
|
46 | * db.value // => same as `data`
|
47 | * db.go('users').value // => same as `data.users`
|
48 | */
|
49 |
|
50 | function scour (value, options) {
|
51 | if (!(this instanceof scour)) return new scour(value, options)
|
52 | this.value = value
|
53 |
|
54 | this.root = options && options.root || this
|
55 | this.keypath = options && options.keypath || []
|
56 | this.extensions = options && options.extensions || []
|
57 |
|
58 | // Apply any property extensions
|
59 | if (this.extensions.length) this.applyExtensions()
|
60 | }
|
61 |
|
62 | /**
|
63 | * Attributes:
|
64 | * (Section) These attributes are available to [scour] instances.
|
65 | */
|
66 |
|
67 | /**
|
68 | * value : value
|
69 | * The raw value being wrapped. You can use this to terminate a chained call.
|
70 | *
|
71 | * users =
|
72 | * [ { name: 'john', admin: true },
|
73 | * { name: 'kyle', admin: false } ]
|
74 | *
|
75 | * scour(users)
|
76 | * .filter({ admin: true })
|
77 | * .value
|
78 | * // => [ { name: 'john', admin: true } ]
|
79 | */
|
80 |
|
81 | /**
|
82 | * root : root
|
83 | * A reference to the root [scour] instance.
|
84 | * Everytime you traverse using [go()], a new [scour] object is spawned that's
|
85 | * scoped to a keypath. Each of these [scour] objects have a `root` attribute
|
86 | * that's a reference to the top-level [scour] object.
|
87 | *
|
88 | * db = scour(...)
|
89 | *
|
90 | * photos = db.go('photos')
|
91 | * photos.root // => same as `db`
|
92 | *
|
93 | * This allows you to return to the root when needed.
|
94 | *
|
95 | * db = scour(...)
|
96 | * artist = db.go('artists', '9328')
|
97 | * artist.root.go('albums').find({ artist_id: artist.get('id') })
|
98 | */
|
99 |
|
100 | /**
|
101 | * keypath : keypath
|
102 | * An array of strings representing each step in how deep the current scope is
|
103 | * relative to the root. Each time you traverse using [go()], a new [scour]
|
104 | * object is spawned.
|
105 | *
|
106 | * db = scour(...)
|
107 | *
|
108 | * users = db.go('users')
|
109 | * users.keypath // => ['users']
|
110 | *
|
111 | * admins = users.go('admins')
|
112 | * admins.keypath // => ['users', 'admins']
|
113 | *
|
114 | * user = admins.go('23')
|
115 | * user.keypath // => ['users', 'admins', '23']
|
116 | */
|
117 |
|
118 | scour.prototype = {
|
119 | /**
|
120 | * Traversal methods:
|
121 | * (Section) For traversing. All these methods return [scour] instances,
|
122 | * making them suitable for chaining.
|
123 | */
|
124 |
|
125 | /**
|
126 | * go : go(keypath...)
|
127 | * Navigates down to a given `keypath`. Always returns a [scour] instance.
|
128 | *
|
129 | * data =
|
130 | * { users:
|
131 | * { 12: { name: 'steve', last: 'jobs' },
|
132 | * 23: { name: 'bill', last: 'gates' } } }
|
133 | *
|
134 | * scour(data).go('users') // => [scour (users)]
|
135 | * scour(data).go('users', '12') // => [scour (name, last)]
|
136 | * scour(data).go('users', '12').get('name') // => 'steve'
|
137 | *
|
138 | * __Dot notation:__
|
139 | * Keypaths can be given in dot notation or as an array. These statements are
|
140 | * equivalent.
|
141 | *
|
142 | * scour(data).go('users.12')
|
143 | * scour(data).go('users', '12')
|
144 | * scour(data).go(['users', '12'])
|
145 | *
|
146 | * __Non-objects:__
|
147 | * If you use it on a non-object or non-array value, it will still be
|
148 | * returned as a [scour] instance. This is not likely what you want; use
|
149 | * [get()] instead.
|
150 | *
|
151 | * attr = scour(data).go('users', '12', 'name')
|
152 | * attr // => [scour object]
|
153 | * attr.value // => 'steve'
|
154 | * attr.keypath // => ['users', '12', 'name']
|
155 | */
|
156 |
|
157 | go () {
|
158 | const keypath = normalizeKeypath(arguments, true)
|
159 | const result = this.get.apply(this, keypath)
|
160 | return this._get(result, keypath, true)
|
161 | },
|
162 |
|
163 | /**
|
164 | * Returns the item at `index`. This differs from `go` as this searches by
|
165 | * index, not by key.
|
166 | *
|
167 | * users =
|
168 | * { 12: { name: 'steve' },
|
169 | * 23: { name: 'bill' } }
|
170 | *
|
171 | * scour(users).at(0) // => [scour { name: 'steve' }]
|
172 | * scour(users).get(12) // => [scour { name: 'steve' }]
|
173 | */
|
174 |
|
175 | at (index) {
|
176 | if (Array.isArray(this.value)) {
|
177 | return this._get(this.value[index], [ '' + index ])
|
178 | }
|
179 |
|
180 | const key = this.keys()[index]
|
181 | return this._get(this.value[key], [ '' + key ])
|
182 | },
|
183 |
|
184 | /**
|
185 | * Sifts through the values and returns a set that matches given
|
186 | * `conditions`. Supports functions, simple objects, and MongoDB-style
|
187 | * queries.
|
188 | *
|
189 | * [query-ops]: https://docs.mongodb.org/manual/reference/operator/query/
|
190 | *
|
191 | * scour(data).filter({ name: 'john' })
|
192 | * scour(data).filter({ name: { $in: ['moe', 'larry'] })
|
193 | *
|
194 | * MongoDB-style queries are supported as provided by [sift.js]. For
|
195 | * reference, see [MongoDB Query Operators][query-ops].
|
196 | *
|
197 | * scour(products).filter({ price: { $gt: 200 })
|
198 | * scour(articles).filter({ published_at: { $not: null }})
|
199 | */
|
200 |
|
201 | filter (conditions) {
|
202 | const results = sift(conditions, this.value)
|
203 | return this._get(results, [])
|
204 | },
|
205 |
|
206 | /**
|
207 | * Returns the first value that matches `conditions`. Supports MongoDB-style
|
208 | * queries. For reference, see [MongoDB Query Operators][query-ops]. Also
|
209 | * see [filter()], as this is functionally-equivalent to the first result of
|
210 | * `filter()`.
|
211 | *
|
212 | * [query-ops]: https://docs.mongodb.org/manual/reference/operator/query/
|
213 | *
|
214 | * scour(data).find({ name: 'john' })
|
215 | * scour(data).find({ name: { $in: ['moe', 'larry'] })
|
216 | */
|
217 |
|
218 | find (conditions) {
|
219 | const key = sift.keyOf(conditions, this.value)
|
220 | if (typeof key === 'undefined') return
|
221 | return this._get(this.value[key], [key])
|
222 | },
|
223 |
|
224 | /**
|
225 | * Returns the first result as a [scour]-wrapped object. This is equivalent
|
226 | * to [at(0)](#at).
|
227 | */
|
228 |
|
229 | first () {
|
230 | return this.at(0)
|
231 | },
|
232 |
|
233 | /**
|
234 | * Returns the first result as a [scour]-wrapped object. This is equivalent
|
235 | * to `at(len() - 1)`: see [at()] and [len()].
|
236 | */
|
237 |
|
238 | last () {
|
239 | return this.at(this.len() - 1)
|
240 | },
|
241 |
|
242 | /**
|
243 | * Reading methods:
|
244 | * (Section) For retrieving data.
|
245 | */
|
246 |
|
247 | /**
|
248 | * get : get(keypath...)
|
249 | * Returns data in a given `keypath`.
|
250 | *
|
251 | * data =
|
252 | * { users:
|
253 | * { 12: { name: 'steve' },
|
254 | * 23: { name: 'bill' } } }
|
255 | *
|
256 | * scour(data).get('users') // => same as data.users
|
257 | * scour(data).go('users').value // => same as data.users
|
258 | *
|
259 | * __Dot notation:__
|
260 | * Like [go()], the `keypath` can be given in dot notation.
|
261 | *
|
262 | * scour(data).get('books.featured.name')
|
263 | * scour(data).get('books', 'featured', 'name')
|
264 | */
|
265 |
|
266 | get () {
|
267 | let result = this.value
|
268 | const keypath = normalizeKeypath(arguments, true)
|
269 |
|
270 | for (let i = 0, len = keypath.length; i < len; i++) {
|
271 | result = result[keypath[i]]
|
272 | if (!result) return
|
273 | }
|
274 |
|
275 | return result
|
276 | },
|
277 |
|
278 | /**
|
279 | * Returns the length of the object or array. For objects, it returns the
|
280 | * number of keys.
|
281 | *
|
282 | * users =
|
283 | * { 12: { name: 'steve' },
|
284 | * 23: { name: 'bill' } }
|
285 | *
|
286 | * names = scour(users).len() // => 2
|
287 | */
|
288 |
|
289 | len () {
|
290 | if (!this.value) return 0
|
291 | if (Array.isArray(this.value)) return this.value.length
|
292 | return this.keys().length
|
293 | },
|
294 |
|
295 | /**
|
296 | * Returns an array. If the the value is an object, it returns the values of
|
297 | * that object.
|
298 | *
|
299 | * users =
|
300 | * { 12: { name: 'steve' },
|
301 | * 23: { name: 'bill' } }
|
302 | *
|
303 | * names = scour(users).toArray()
|
304 | * // => [ {name: 'steve'}, {name: 'bill'} ]
|
305 | */
|
306 |
|
307 | toArray () {
|
308 | if (Array.isArray(this.value)) return this.value
|
309 | var result = []
|
310 | scour.each(this.value, (val, key) => result.push(val))
|
311 | return result
|
312 | },
|
313 |
|
314 | /**
|
315 | * Alias for `toArray()`.
|
316 | */
|
317 |
|
318 | values () {
|
319 | return this.toArray()
|
320 | },
|
321 |
|
322 | /**
|
323 | * Returns keys. If the value is an array, this returns the array's indices.
|
324 | */
|
325 |
|
326 | keys () {
|
327 | return Object.keys(this.value)
|
328 | },
|
329 |
|
330 | /**
|
331 | * Writing methods:
|
332 | * (Section) These are methods for modifying an object/array tree immutably.
|
333 | * Note that all these functions are immutable--it will not modify existing
|
334 | * data, but rather spawn new objects with the modifications done on them.
|
335 | */
|
336 |
|
337 | /**
|
338 | * Sets values immutably. Returns a copy of the same object ([scour]-wrapped)
|
339 | * with the modifications applied.
|
340 | *
|
341 | * data = { bob: { name: 'Bob' } }
|
342 | * db = scour(data)
|
343 | * db.set([ 'bob', 'name' ], 'Robert')
|
344 | * // db.value == { bob: { name: 'Robert' } }
|
345 | *
|
346 | * __Immutability:__
|
347 | * This is an immutable function, and will return a new object. It won't
|
348 | * modify your original object.
|
349 | *
|
350 | * profile = scour({ name: 'John' })
|
351 | * profile2 = profile.set('email', 'john@gmail.com')
|
352 | *
|
353 | * profile.value // => { name: 'John' }
|
354 | * profile2.value // => { name: 'John', email: 'john@gmail.com' }
|
355 | *
|
356 | * __Using within a scope:__
|
357 | * Be aware that using all writing methods ([set()], [del()], [extend()]) on
|
358 | * scoped objects (ie, made with [go()]) will spawn a new [root] object. If
|
359 | * you're keeping a reference to the root object, you'll need to update it
|
360 | * accordingly.
|
361 | *
|
362 | * db = scour(data)
|
363 | * book = db.go('book')
|
364 | * book.root === db // correct so far
|
365 | *
|
366 | * book = book.set('title', 'IQ84')
|
367 | * book = book.del('sale_price')
|
368 | * book.root !== db // `root` has been updated
|
369 | *
|
370 | * __Dot notation:__
|
371 | * Like [go()] and [get()], the keypath can be given in dot notation or an
|
372 | * array.
|
373 | *
|
374 | * scour(data).set('menu.left.visible', true)
|
375 | * scour(data).set(['menu', 'left', 'visible'], true)
|
376 | */
|
377 |
|
378 | set (keypath, value) {
|
379 | keypath = normalizeKeypath(keypath)
|
380 |
|
381 | if (this.root !== this) {
|
382 | return this.root
|
383 | .set(this.keypath.concat(keypath), value).go(this.keypath)
|
384 | }
|
385 |
|
386 | const result = scour.set(this.value, keypath, value)
|
387 | return this.replace(result, { root: null })
|
388 | },
|
389 |
|
390 | /**
|
391 | * Deletes values immutably. Returns a copy of the same object
|
392 | * ([scour]-wrapped) with the modifications applied.
|
393 | *
|
394 | * Like [set()], the keypath can be given in dot notation or an
|
395 | * array.
|
396 | *
|
397 | * scour(data).del('menu.left.visible')
|
398 | * scour(data).del(['menu', 'left', 'visible'])
|
399 | *
|
400 | * See [set()] for more information on working with immutables.
|
401 | */
|
402 |
|
403 | del (keypath) {
|
404 | keypath = normalizeKeypath(keypath)
|
405 |
|
406 | if (this.root !== this) {
|
407 | return this.root.del(this.keypath.concat(keypath)).go(this.keypath)
|
408 | }
|
409 |
|
410 | const result = scour.del(this.value, keypath)
|
411 | return this.replace(result, { root: null })
|
412 | },
|
413 |
|
414 | /**
|
415 | * extend : extend(objects...)
|
416 | * Extends the data with more values. Returns a [scour]-wrapped object. Only
|
417 | * supports objects; arrays and non-objects will return undefined. Just like
|
418 | * [Object.assign], you may pass multiple objects to the parameters.
|
419 | *
|
420 | * data = { a: 1, b: 2 }
|
421 | * data2 = scour(data).extend({ c: 3 })
|
422 | *
|
423 | * data2 // => [scour { a: 1, b: 2, c: 3 }]
|
424 | * data2.value // => { a: 1, b: 2, c: 3 }
|
425 | *
|
426 | * See [set()] for more information on working with immutables.
|
427 | */
|
428 |
|
429 | extend () {
|
430 | if (typeof this.value !== 'object' || Array.isArray(this.value)) return
|
431 | var result = {}
|
432 | assign(result, this.value)
|
433 | for (var i = 0, len = arguments.length; i < len; i++) {
|
434 | if (typeof arguments[i] !== 'object') return
|
435 | assign(result, arguments[i])
|
436 | }
|
437 |
|
438 | if (this.root !== this) {
|
439 | return this.root.set(this.keypath, result).go(this.keypath)
|
440 | }
|
441 |
|
442 | return this.replace(result)
|
443 | },
|
444 |
|
445 | /**
|
446 | * Utility methods:
|
447 | * (Section) For stuff.
|
448 | */
|
449 |
|
450 | /**
|
451 | * use : use(extensions)
|
452 | * Extends functionality for certain keypaths with custom methods.
|
453 | * See [Extensions example] for examples.
|
454 | *
|
455 | * data =
|
456 | * { users:
|
457 | * { 12: { name: 'steve', surname: 'jobs' },
|
458 | * 23: { name: 'bill', surname: 'gates' } } }
|
459 | *
|
460 | * extensions = {
|
461 | * 'users.*': {
|
462 | * fullname () {
|
463 | * return this.get('name') + ' ' + this.get('surname')
|
464 | * }
|
465 | * }
|
466 | * }
|
467 | *
|
468 | * scour(data)
|
469 | * .use(extensions)
|
470 | * .get('users', 12)
|
471 | * .fullname() // => 'bill gates'
|
472 | *
|
473 | * __Extensions format:__
|
474 | * The parameter `extension` is an object, with keys being keypath globs, and
|
475 | * values being properties to be extended.
|
476 | *
|
477 | * .use({
|
478 | * 'books.*': { ... },
|
479 | * 'authors.*': { ... },
|
480 | * 'publishers.*': { ... }
|
481 | * })
|
482 | *
|
483 | * __Extending root:__
|
484 | * To bind properties to the root method, use an empty string as the keypath.
|
485 | *
|
486 | * .use({
|
487 | * '': {
|
488 | * users() { return this.go('users') },
|
489 | * authors() { return this.go('authors') }
|
490 | * }
|
491 | * })
|
492 | *
|
493 | * __Keypath filtering:__
|
494 | * You can use glob-like `*` and `**` to match parts of a keypath. A `*` will
|
495 | * match any one segment, and `**` will match one or many segments. Here are
|
496 | * some examples:
|
497 | *
|
498 | * - `users.*` - will match `users.1`, but not `users.1.photos`
|
499 | * - `users.**` - will match `users.1.photos`
|
500 | * - `users.*.photos` - will match `users.1.photos`
|
501 | * - `**` will match anything
|
502 | *
|
503 | * __When using outside root:__
|
504 | * Any extensions in a scoped object (ie, made with [go()]) will be used relative
|
505 | * to it. For instance, if you define an extension to `admins.*` inside
|
506 | * `.go('users')`, it will affect `users.
|
507 | *
|
508 | * data = { users: { john: { } }
|
509 | * db = scour(data)
|
510 | *
|
511 | * users = db.go('users')
|
512 | * .use({ '*': { hasName () { return !!this.get('name') } })
|
513 | *
|
514 | * users.go('john').hasName() // works
|
515 | *
|
516 | * While this is supported, it is *not* recommended: these extensions will not
|
517 | * propagate back to the root, and any objects taken from the root will not
|
518 | * have those extensions applied to them.
|
519 | *
|
520 | * users.go('john').hasName() // works
|
521 | * db.go('users.john').hasName() // doesn't work
|
522 | */
|
523 |
|
524 | use (spec) {
|
525 | const extensions = buildExtensions(this.keypath, spec)
|
526 | if (this.root === this) {
|
527 | return this.replace(this.value, { extensions, root: null })
|
528 | } else {
|
529 | // Spawn a new `root` with the extensions applied
|
530 | return this.root
|
531 | .replace(this.root.value, { extensions, root: null })
|
532 | .replace(this.value, { keypath: this.keypath })
|
533 | }
|
534 | },
|
535 |
|
536 | /**
|
537 | * Returns the value for serialization. This allows `JSON.stringify()` to
|
538 | * work with `scour`-wrapped objects. The name of this method is a bit
|
539 | * confusing, as it doesn't actually return a JSON string — but I'm afraid
|
540 | * that it's the way that the JavaScript API for [JSON.stringify] works.
|
541 | *
|
542 | * [JSON.stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON%28%29_behavior
|
543 | */
|
544 |
|
545 | toJSON () {
|
546 | return this.value
|
547 | },
|
548 |
|
549 | valueOf () {
|
550 | return this.value
|
551 | },
|
552 |
|
553 | toString () {
|
554 | return `[scour (${this.keys().join(', ')})]`
|
555 | },
|
556 |
|
557 | /**
|
558 | * Iteration methods:
|
559 | * (Section) For traversing.
|
560 | */
|
561 |
|
562 | /**
|
563 | * forEach : forEach(function(item, key))
|
564 | * Loops through each item. Supports both arrays and objects. The `item`s
|
565 | * passed to the function will be returned as a [scour] instance.
|
566 | *
|
567 | * users =
|
568 | * { 12: { name: 'steve' },
|
569 | * 23: { name: 'bill' } }
|
570 | *
|
571 | * scour(users).each((user, key) => {
|
572 | * console.log(user.get('name'))
|
573 | * })
|
574 | *
|
575 | * The values passed onto the function are:
|
576 | *
|
577 | * - `item` - the value; always a scour object.
|
578 | * - `key` - the key.
|
579 | *
|
580 | * The value being passed onto the function is going to be a [scour] object.
|
581 | * Use `item.value` or `this` to access the raw values.
|
582 | */
|
583 |
|
584 | forEach (fn) {
|
585 | scour.each(this.value, (val, key) => {
|
586 | fn.call(val, this._get(val, [key]), key)
|
587 | })
|
588 | return this
|
589 | },
|
590 |
|
591 | /**
|
592 | * Alias for [forEach](#foreach).
|
593 | */
|
594 |
|
595 | each (fn) {
|
596 | return this.forEach(fn)
|
597 | },
|
598 |
|
599 | /**
|
600 | * map : map(function(item, key))
|
601 | * Loops through each item and returns an array based on the iterator's
|
602 | * return values. Supports both arrays and objects. The `item`s passed to
|
603 | * the function will be returned as a [scour] instance.
|
604 | *
|
605 | * users =
|
606 | * { 12: { name: 'steve' },
|
607 | * 23: { name: 'bill' } }
|
608 | *
|
609 | * names = scour(users).map((user, key) => user.get('name'))
|
610 | * // => [ 'steve', 'bill' ]
|
611 | */
|
612 |
|
613 | map: thisify(require('./utilities/map')),
|
614 |
|
615 | /**
|
616 | * Internal: spawns an instance with a given data and keypath.
|
617 | */
|
618 |
|
619 | _get (result, keypath) {
|
620 | if (typeof result === 'undefined' || result === null) return result
|
621 | return this.replace(result, {
|
622 | keypath: this.keypath.concat(keypath)
|
623 | })
|
624 | },
|
625 |
|
626 | /**
|
627 | * Internal: Returns a clone with the `value` replaced. The new instance will
|
628 | * retain the same properties, so things like [use()] extensions are carried
|
629 | * over. You may pass additional `options`.
|
630 | *
|
631 | * db = scour(data)
|
632 | * db = db.replace(newData)
|
633 | *
|
634 | * I don't think this will be useful outside internal use.
|
635 | */
|
636 |
|
637 | replace (value, options) {
|
638 | const op = options || {}
|
639 | return new scour(value || this.value, {
|
640 | root:
|
641 | typeof op.root !== 'undefined' ? op.root : this.root,
|
642 | keypath:
|
643 | typeof op.keypath !== 'undefined' ? op.keypath : this.keypath,
|
644 | extensions: typeof op.extensions !== 'undefined'
|
645 | ? this.extensions.concat(op.extensions)
|
646 | : this.extensions
|
647 | })
|
648 | },
|
649 |
|
650 | /**
|
651 | * Internal: applies extensions
|
652 | */
|
653 |
|
654 | applyExtensions () {
|
655 | var path = this.keypath.join('.')
|
656 |
|
657 | this.extensions.forEach((extension) => {
|
658 | // extension is [ RegExp, properties object ]
|
659 | if (extension[0].test(path)) assign(this, extension[1])
|
660 | })
|
661 | }
|
662 | }
|
663 |
|
664 | /**
|
665 | * Utility functions:
|
666 | * (Section) These are utilities that don't need a wrapped object.
|
667 | */
|
668 |
|
669 | /**
|
670 | * scour.set : scour.set(object, keypath, value)
|
671 | * Sets a `keypath` into an `object` immutably.
|
672 | *
|
673 | * data = { users: { bob: { name: 'john' } } }
|
674 | *
|
675 | * result = set(data, ['users', 'bob', 'name'], 'robert')
|
676 | * // => { users: { bob: { name: 'robert' } } }
|
677 | *
|
678 | * This is also available as `require('scourjs/utilities/set')`.
|
679 | */
|
680 |
|
681 | scour.set = require('./utilities/set')
|
682 |
|
683 | /**
|
684 | * scour.del : scour.del(object, keypath)
|
685 | * Deletes a `keypath` from an `object` immutably.
|
686 | *
|
687 | * data = { users: { bob: { name: 'robert' } } }
|
688 | * result = del(data, ['users', 'bob', 'name'])
|
689 | *
|
690 | * // => { users: { bob: {} } }
|
691 | *
|
692 | * This is also available as `require('scourjs/utilities/del')`.
|
693 | */
|
694 |
|
695 | scour.del = require('./utilities/del')
|
696 |
|
697 | /**
|
698 | * scour.each : scour.each(iterable, fn)
|
699 | * Iterates through `iterable`, either an object or an array. This is an
|
700 | * implementation of `Array.forEach` that also works for objects.
|
701 | *
|
702 | * This is also available as `require('scourjs/utilities/each')`.
|
703 | */
|
704 |
|
705 | scour.each = require('./utilities/each')
|
706 |
|
707 | /**
|
708 | * scour.map : scour.map(iterable, fn)
|
709 | * Works like Array#map, but also works on objects.
|
710 | *
|
711 | * This is also available as `require('scourjs/utilities/map')`.
|
712 | */
|
713 |
|
714 | scour.map = require('./utilities/map')
|
715 |
|
716 | /**
|
717 | * Internal: decorates collection functions
|
718 | */
|
719 |
|
720 | function thisify (fn) {
|
721 | return function () {
|
722 | return fn.bind(null, this.forEach.bind(this)).apply(this, arguments)
|
723 | }
|
724 | }
|
725 |
|
726 | module.exports = scour
|