Initial UniqueIndex.
This commit is contained in:
parent
0880787d68
commit
e5f3ca1623
123
packages/xo-collection/src/unique-index.js
Normal file
123
packages/xo-collection/src/unique-index.js
Normal file
@ -0,0 +1,123 @@
|
||||
import bind from 'lodash.bind'
|
||||
import callback from 'lodash.callback'
|
||||
|
||||
import clearObject from './clear-object'
|
||||
import NotImplemented from './not-implemented'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export default class UniqueIndex {
|
||||
constructor (computeHash) {
|
||||
if (computeHash) {
|
||||
this.computeHash = callback(computeHash)
|
||||
}
|
||||
|
||||
this._itemByHash = Object.create(null)
|
||||
this._keysToHash = Object.create(null)
|
||||
|
||||
// Bound versions of listeners.
|
||||
this._onAdd = bind(this._onAdd, this)
|
||||
this._onUpdate = bind(this._onUpdate, this)
|
||||
this._onRemove = bind(this._onRemove, this)
|
||||
}
|
||||
|
||||
// This method is used to compute the hash under which an item must
|
||||
// be saved.
|
||||
computeHash (value, key) {
|
||||
throw new NotImplemented('this method must be overridden')
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
get items () {
|
||||
return this._itemByHash
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
_attachCollection (collection) {
|
||||
// Add existing entries.
|
||||
//
|
||||
// FIXME: I think there may be a race condition if the `add` event
|
||||
// has not been emitted yet.
|
||||
this._onAdd(collection.all)
|
||||
|
||||
collection.on('add', this._onAdd)
|
||||
collection.on('update', this._onUpdate)
|
||||
collection.on('remove', this._onRemove)
|
||||
}
|
||||
|
||||
_detachCollection (collection) {
|
||||
collection.removeListener('add', this._onAdd)
|
||||
collection.removeListener('update', this._onUpdate)
|
||||
collection.removeListener('remove', this._onRemove)
|
||||
|
||||
clearObject(this._itemByHash)
|
||||
clearObject(this._keysToHash)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
_onAdd (items) {
|
||||
const {
|
||||
computeHash,
|
||||
_itemByHash: itemByHash,
|
||||
_keysToHash: keysToHash
|
||||
} = this
|
||||
|
||||
for (let key in items) {
|
||||
const value = items[key]
|
||||
|
||||
const hash = computeHash(value, key)
|
||||
|
||||
if (hash != null) {
|
||||
itemByHash[hash] = {key, value}
|
||||
keysToHash[key] = hash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onUpdate (items) {
|
||||
const {
|
||||
computeHash,
|
||||
_itemByHash: itemByHash,
|
||||
_keysToHash: keysToHash
|
||||
} = this
|
||||
|
||||
for (let key in items) {
|
||||
const value = items[key]
|
||||
|
||||
const prev = keysToHash[key]
|
||||
const hash = computeHash(value, key)
|
||||
|
||||
// Same hash, nothing to do.
|
||||
if (hash === prev) continue
|
||||
|
||||
// Removes item from the previous hash's list if any.
|
||||
if (prev != null) delete itemByHash[prev]
|
||||
|
||||
// Inserts item into the new hash's list if any.
|
||||
if (hash != null) {
|
||||
keysToHash[key] = hash
|
||||
itemByHash[hash] = {key, value}
|
||||
} else {
|
||||
delete keysToHash[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onRemove (items) {
|
||||
const {
|
||||
_itemByHash: itemByHash,
|
||||
_keysToHash: keysToHash
|
||||
} = this
|
||||
|
||||
for (let key in items) {
|
||||
const prev = keysToHash[key]
|
||||
if (prev != null) {
|
||||
delete keysToHash[key]
|
||||
delete itemByHash[prev]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
144
packages/xo-collection/src/unique-index.spec.js
Normal file
144
packages/xo-collection/src/unique-index.spec.js
Normal file
@ -0,0 +1,144 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import chai, {expect} from 'chai'
|
||||
import dirtyChai from 'dirty-chai'
|
||||
chai.use(dirtyChai)
|
||||
|
||||
import sourceMapSupport from 'source-map-support'
|
||||
sourceMapSupport.install()
|
||||
|
||||
import forEach from 'lodash.foreach'
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
import Collection from '..'
|
||||
import Index from '../unique-index'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const waitTicks = (n = 1) => {
|
||||
const {nextTick} = process
|
||||
|
||||
return new Promise(resolve => {
|
||||
(function waitNextTick () {
|
||||
// The first tick is handled by Promise#then()
|
||||
if (--n) {
|
||||
nextTick(waitNextTick)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
describe('UniqueIndex', function () {
|
||||
let col, byKey
|
||||
const item1 = {
|
||||
id: '2ccb8a72-dc65-48e4-88fe-45ef541f2cba',
|
||||
key: '036dee1b-9a3b-4fb5-be8a-4f535b355581'
|
||||
}
|
||||
const item2 = {
|
||||
id: '7d21dc51-4da8-4538-a2e9-dd6f4784eb76',
|
||||
key: '103cd893-d2cc-4d37-96fd-c259ad04c0d4'
|
||||
}
|
||||
const item3 = {
|
||||
id: '668c1274-4442-44a6-b99a-512188e0bb09'
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
col = new Collection()
|
||||
forEach([item1, item2, item3], item => {
|
||||
col.add(item)
|
||||
})
|
||||
|
||||
byKey = new Index('key')
|
||||
|
||||
col.createIndex('byKey', byKey)
|
||||
|
||||
return waitTicks()
|
||||
})
|
||||
|
||||
it('works with existing items', function () {
|
||||
expect(col.indexes).to.eql({
|
||||
byKey: {
|
||||
[item1.key]: {
|
||||
key: item1.id,
|
||||
value: item1
|
||||
},
|
||||
[item2.key]: {
|
||||
key: item2.id,
|
||||
value: item2
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('works with added items', function () {
|
||||
const item4 = {
|
||||
id: '823b56c4-4b96-4f3a-9533-5d08177167ac',
|
||||
key: '1437af14-429a-40db-8a51-8a2f5ed03201'
|
||||
}
|
||||
|
||||
col.add(item4)
|
||||
|
||||
return waitTicks(2).then(() => {
|
||||
expect(col.indexes).to.eql({
|
||||
byKey: {
|
||||
[item1.key]: {
|
||||
key: item1.id,
|
||||
value: item1
|
||||
},
|
||||
[item2.key]: {
|
||||
key: item2.id,
|
||||
value: item2
|
||||
},
|
||||
[item4.key]: {
|
||||
key: item4.id,
|
||||
value: item4
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('works with updated items', function () {
|
||||
const item1bis = {
|
||||
id: item1.id,
|
||||
key: 'e03d4a3a-0331-4aca-97a2-016bbd43a29b'
|
||||
}
|
||||
|
||||
col.update(item1bis)
|
||||
|
||||
return waitTicks(2).then(() => {
|
||||
expect(col.indexes).to.eql({
|
||||
byKey: {
|
||||
[item1bis.key]: {
|
||||
key: item1.id,
|
||||
value: item1bis
|
||||
},
|
||||
[item2.key]: {
|
||||
key: item2.id,
|
||||
value: item2
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('works with removed items', function () {
|
||||
col.remove(item2)
|
||||
|
||||
return waitTicks(2).then(() => {
|
||||
expect(col.indexes).to.eql({
|
||||
byKey: {
|
||||
[item1.key]: {
|
||||
key: item1.id,
|
||||
value: item1
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
1
packages/xo-collection/unique-index.js
Normal file
1
packages/xo-collection/unique-index.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/unique-index')
|
Loading…
Reference in New Issue
Block a user