Skip to content

Commit

Permalink
implement memory adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
0x8890 committed Aug 15, 2015
1 parent ab93fda commit 8d53039
Show file tree
Hide file tree
Showing 21 changed files with 247 additions and 400 deletions.
1 change: 0 additions & 1 deletion .gitignore
@@ -1,5 +1,4 @@
node_modules/
coverage/
dist/
*.db
*.log
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -59,4 +59,4 @@ See the [plugins page](http://fortunejs.com/plugins/) for more details.

## License

Fortune is licensed under the [MIT license](https://raw.githubusercontent.com/fortunejs/fortune/master/LICENSE).
This software is licensed under the [MIT license](https://raw.githubusercontent.com/fortunejs/fortune/master/LICENSE).
6 changes: 6 additions & 0 deletions doc/CHANGELOG.md
@@ -1,6 +1,12 @@
# Changelog


##### 1.0.0-rc.14 (2015-08-15)
- Implemented memory adapter, which replaces NeDB as the default adapter.
- Breaking change: removed NeDB adapter, now belongs to a separate module: `fortune-nedb`.
- Breaking change: browser build now defaults to memory adapter.


##### 1.0.0-rc.13 (2015-08-15)
- Breaking change: remove Micro API and JSON API serializers from this package, they are now external modules: `fortune-micro-api` and `fortune-json-api`.

Expand Down
3 changes: 2 additions & 1 deletion doc/PLUGINS.md
Expand Up @@ -9,11 +9,12 @@ Adapters must subclass and implement the Adapter class. The adapter could be bac

| Adapter | Description |
|:-----------------|:---------------------------------------------------------|
| [NeDB](https://github.com/louischatriot/nedb) (included, default) | Embedded document data store with an API that is mostly compatible with MongoDB. |
| Memory (included, default) | In-memory adapter, does not persist to disk. |
| IndexedDB (included) | Data storage adapter that works in modern browsers. |
| Web Storage (included) | Data storage adapter that works in most browsers. |
| [MongoDB](https://github.com/fortunejs/fortune-mongodb) | Document data store. MongoDB is [web scale](http://www.mongodb-is-web-scale.com/). |
| [Postgres](https://github.com/fortunejs/fortune-postgres) | Relational database adapter, translates adapter method inputs to SQL. |
| [NeDB](https://github.com/fortunejs/fortune-nedb) | Embedded document data store with an API that is mostly compatible with MongoDB. |


### Serializers
Expand Down
2 changes: 1 addition & 1 deletion doc/TODO.md
Expand Up @@ -2,7 +2,7 @@

*This file is marked for deletion after 1.0.*

- Implement memory adapter, move NeDB, IndexedDB, and Web Storage adapters out of core.
- Implement memory adapter, move NeDB adapter out of core.
- Consider using ES7 features (?)
- 100% test coverage.

Expand Down
7 changes: 7 additions & 0 deletions lib/adapter/adapters/common.js
Expand Up @@ -101,3 +101,10 @@ function sortByField (fieldDefinition, field, isAscending, a, b) {

return isAscending ? result : -result
}


// Browser-safe ID generation.
export function generateId () {
return ('00000000' + Math.floor(Math.random() * Math.pow(2, 32))
.toString(16)).slice(-8)
}
35 changes: 15 additions & 20 deletions lib/adapter/adapters/indexeddb/helpers.js
@@ -1,3 +1,5 @@
import clone from 'clone'
import { generateId } from '../common'
import { toBuffer, toArrayBuffer } from 'array-buffer'


Expand All @@ -13,47 +15,47 @@ export const delimiter = '__'

export function inputRecord (type, record) {
const { recordTypes, keys } = this
const clone = {}
const result = {}
const fields = recordTypes[type]

// ID business.
clone[keys.primary] = type + delimiter + (keys.primary in record ?
result[keys.primary] = type + delimiter + (keys.primary in record ?
record[keys.primary] : generateId())

for (let field of Object.getOwnPropertyNames(fields)) {
const fieldType = fields[field][keys.type]
const fieldIsArray = fields[field][keys.isArray]

if (!(field in record)) {
clone[field] = fieldIsArray ? [] : null
result[field] = fieldIsArray ? [] : null
continue
}

const value = record[field]

// Cast Buffer to ArrayBuffer.
if (fieldType === Buffer && value) {
clone[field] = fieldIsArray ?
result[field] = fieldIsArray ?
value.map(toArrayBuffer) : toArrayBuffer(value)
continue
}

clone[field] = value
result[field] = clone(value)
}

return clone
return result
}


export function outputRecord (type, record) {
const { recordTypes, keys } = this
const clone = {}
const result = {}
const fields = recordTypes[type]

// ID business.
const id = record[keys.primary].split(delimiter)[1]
const float = Number.parseFloat(id)
clone[keys.primary] = id - float + 1 >= 0 ? float : id
result[keys.primary] = id - float + 1 >= 0 ? float : id

for (let field in record) {
if (!(field in fields)) continue
Expand All @@ -65,28 +67,21 @@ export function outputRecord (type, record) {

// Cast ArrayBuffer to Buffer.
if (fieldType === Buffer && record[field]) {
clone[field] = fieldIsArray ?
result[field] = fieldIsArray ?
value.map(toBuffer) : toBuffer(value)
continue
}

// Do not enumerate denormalized fields.
if (fieldIsDenormalized) {
Object.defineProperty(clone, field, {
configurable: true, writable: true, value
Object.defineProperty(result, field, {
configurable: true, writable: true, value: clone(value)
})
continue
}

clone[field] = record[field]
result[field] = clone(value)
}

return clone
}


// Hopefully we don't need to do this.
function generateId () {
return ('00000000' + Math.floor(Math.random() * Math.pow(2, 32))
.toString(16)).slice(-8)
return result
}
52 changes: 52 additions & 0 deletions lib/adapter/adapters/memory/helpers.js
@@ -0,0 +1,52 @@
import clone from 'clone'
import { generateId } from '../common'


export function inputRecord (type, record) {
const { recordTypes, keys } = this
const result = {}
const fields = recordTypes[type]

// ID business.
result[keys.primary] = keys.primary in record ?
record[keys.primary] : generateId()

for (let field of Object.getOwnPropertyNames(fields)) {
if (!(field in record)) {
result[field] = fields[field][keys.isArray] ? [] : null
continue
}

result[field] = clone(record[field])
}

return result
}


export function outputRecord (type, record) {
const { recordTypes, keys } = this
const result = {}
const fields = recordTypes[type]

// ID business.
result[keys.primary] = record[keys.primary]

for (let field in record) {
if (!(field in fields)) continue

const value = clone(record[field])

// Do not enumerate denormalized fields.
if (fields[field][keys.denormalizedInverse]) {
Object.defineProperty(result, field, {
configurable: true, writable: true, value
})
continue
}

result[field] = value
}

return result
}
120 changes: 120 additions & 0 deletions lib/adapter/adapters/memory/index.js
@@ -0,0 +1,120 @@
import { applyOptions } from '../common'
import applyUpdate from '../../../common/apply_update'
import { inputRecord, outputRecord } from './helpers'


/**
* Memory adapter.
*/
export default Adapter => class MemoryAdapter extends Adapter {

connect () {
this.db = {}

const { recordTypes } = this

for (let type in recordTypes)
this.db[type] = {}

return Promise.resolve()
}


disconnect () {
delete this.db
return Promise.resolve()
}


find (type, ids, options = {}) {
if (ids && !ids.length) return super.find()

const { db, recordTypes } = this
const fields = recordTypes[type]
const collection = db[type]
let records = []
let count = 0

if (ids) for (let id of ids) {
const record = collection[id]
if (record) {
count++
records.push(outputRecord.call(this, type, record))
}
}
else for (let id in collection) {
count++
records.push(outputRecord.call(this, type, collection[id]))
}

return Promise.resolve(applyOptions(count, fields, records, options))
}


create (type, records) {
records = records.map(inputRecord.bind(this, type))

const { db, keys, errors: { ConflictError } } = this
const collection = db[type]

for (let record of records) {
const id = record[keys.primary]

if (id in collection)
return Promise.reject(new ConflictError(
`Record with ID "${id}" already exists.`))
}

for (let record of records) collection[record[keys.primary]] = record

return Promise.resolve(records.map(outputRecord.bind(this, type)))
}


update (type, updates) {
if (!updates.length) return super.update()

const { db, keys } = this
const collection = db[type]
let count = 0

for (let update of updates) {
const id = update[keys.primary]
let record = collection[id]

if (!record) continue

count++
record = outputRecord.call(this, type, record)

applyUpdate(record, update)

collection[id] = inputRecord.call(this, type, record)
}

return Promise.resolve(count)
}


delete (type, ids) {
if (ids && !ids.length) return super.delete()

const { db } = this
const collection = db[type]
let count = 0

if (ids) for (let id of ids) {
if (id in collection) {
count++
delete collection[id]
}
}
else for (let id in collection) {
count++
delete collection[id]
}

return Promise.resolve(count)
}

}

0 comments on commit 8d53039

Please sign in to comment.