react-native version

pull/17/head
Timothée Rebours 3 years ago
parent e3f4ee8783
commit 44ae8d7029
  1. 4
      CHANGELOG.md
  2. 1
      __mocks__/@react-native-async-storage/async-storage.js
  3. 31031
      package-lock.json
  4. 16
      package.json
  5. 1
      react-native-version/lib/byline.js
  6. 66
      react-native-version/lib/customUtils.js
  7. 84
      react-native-version/lib/storage.js
  8. 108
      test/react-native/persistence.test.js
  9. 3
      test/react-native/resolver.js

@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Include a `"react-native"` version (heavily inspired from [react-native-local-mongdb](https://github.com/antoniopresto/react-native-local-mongodb)).
## [2.1.0] - 2021-10-21
Thanks to [@eliot-akira](https://github.com/eliot-akira) for the amazing work on file streaming.
### Changed

@ -0,0 +1 @@
module.exports = require('@react-native-async-storage/async-storage/jest/async-storage-mock')

31031
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -46,9 +46,13 @@
"localforage": "^1.9.0"
},
"devDependencies": {
"@react-native-async-storage/async-storage": "^1.15.9",
"@types/jest": "^27.0.2",
"browser-resolve": "^2.0.0",
"chai": "^4.3.4",
"commander": "^7.2.0",
"events": "^3.3.0",
"jest": "^27.3.1",
"jquery": "^3.6.0",
"karma": "^6.3.2",
"karma-chai": "^0.1.0",
@ -65,6 +69,7 @@
"standard": "^16.0.3",
"terser-webpack-plugin": "^5.1.2",
"timers-browserify": "^2.0.12",
"ts-jest": "^27.0.7",
"ts-node": "^10.3.0",
"typescript": "^4.4.4",
"webpack": "^5.37.0",
@ -77,6 +82,7 @@
"build:browser": "webpack && webpack --optimization-minimize",
"pretest:browser": "npm run build:browser",
"test:browser": "xvfb-maybe karma start karma.conf.local.js",
"test:react-native": "jest test/react-native",
"test:typings": "ts-node ./typings-tests.ts",
"prepublishOnly": "npm run build:browser"
},
@ -86,6 +92,11 @@
"./lib/storage.js": "./browser-version/lib/storage.js",
"./lib/byline.js": "./browser-version/lib/byline.js"
},
"react-native": {
"./lib/customUtils.js": "./react-native-version/lib/customUtils.js",
"./lib/storage.js": "./react-native-version/lib/storage.js",
"./lib/byline.js": "./react-native-version/lib/byline.js"
},
"license": "MIT",
"publishConfig": {
"access": "public"
@ -95,5 +106,10 @@
"browser-version/out",
"**/*.ts"
]
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"resolver": "<rootDir>/test/react-native/resolver.js"
}
}

@ -0,0 +1 @@
module.exports = {}

@ -0,0 +1,66 @@
/**
* Specific customUtils for the browser, where we don't have access to the Crypto and Buffer modules
*/
/**
* Taken from the crypto-browserify module
* https://github.com/dominictarr/crypto-browserify
* NOTE: Math.random() does not guarantee "cryptographic quality" but we actually don't need it
*/
const randomBytes = size => {
const bytes = new Array(size)
for (let i = 0, r; i < size; i++) {
if ((i & 0x03) === 0) r = Math.random() * 0x100000000
bytes[i] = r >>> ((i & 0x03) << 3) & 0xff
}
return bytes
}
/**
* Taken from the base64-js module
* https://github.com/beatgammit/base64-js/
*/
const byteArrayToBase64 = uint8 => {
const lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
const extraBytes = uint8.length % 3 // if we have 1 byte left, pad 2 bytes
let output = ''
let temp
const tripletToBase64 = num => lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
// go through the array every three bytes, we'll deal with trailing stuff later
for (let i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
output += tripletToBase64(temp)
}
// pad the end with zeros, but make sure to not forget the extra bytes
if (extraBytes === 1) {
temp = uint8[uint8.length - 1]
output += lookup[temp >> 2]
output += lookup[(temp << 4) & 0x3F]
output += '=='
} else if (extraBytes === 2) {
temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1])
output += lookup[temp >> 10]
output += lookup[(temp >> 4) & 0x3F]
output += lookup[(temp << 2) & 0x3F]
output += '='
}
return output
}
/**
* Return a random alphanumerical string of length len
* There is a very small probability (less than 1/1,000,000) for the length to be less than len
* (il the base64 conversion yields too many pluses and slashes) but
* that's not an issue here
* The probability of a collision is extremely small (need 3*10^12 documents to have one chance in a million of a collision)
* See http://en.wikipedia.org/wiki/Birthday_problem
*/
const uid = len => byteArrayToBase64(randomBytes(Math.ceil(Math.max(8, len * 2)))).replace(/[+/]/g, '').slice(0, len)
module.exports.uid = uid

@ -0,0 +1,84 @@
/**
* Way data is stored for this database
* For a Node.js/Node Webkit database it's the file system
* For a browser-side database it's localforage, which uses the best backend available (IndexedDB then WebSQL then localStorage)
* For a react-native database, we use @react-native-async-storage/async-storage
* This version is the react-native version
*/
const AsyncStorage = require('@react-native-async-storage/async-storage')
const exists = (filename, cback) => {
// eslint-disable-next-line node/handle-callback-err
AsyncStorage.getItem(filename, (err, value) => {
if (value !== null) {
return cback(true)
} else {
return cback(false)
}
})
}
const rename = (filename, newFilename, callback) => {
// eslint-disable-next-line node/handle-callback-err
AsyncStorage.getItem(filename, (err, value) => {
if (value === null) {
this.storage.removeItem(newFilename, callback)
} else {
this.storage.setItem(newFilename, value, () => {
this.storage.removeItem(filename, callback)
})
}
})
}
const writeFile = (filename, contents, options, callback) => {
// Options do not matter in browser setup
if (typeof options === 'function') { callback = options }
AsyncStorage.setItem(filename, contents, callback)
}
const appendFile = (filename, toAppend, options, callback) => {
// Options do not matter in browser setup
if (typeof options === 'function') { callback = options }
// eslint-disable-next-line node/handle-callback-err
AsyncStorage.getItem(filename, (err, contents) => {
contents = contents || ''
contents += toAppend
AsyncStorage.setItem(filename, contents, callback)
})
}
const readFile = (filename, options, callback) => {
// Options do not matter in browser setup
if (typeof options === 'function') { callback = options }
// eslint-disable-next-line node/handle-callback-err
AsyncStorage.getItem(filename, (err, contents) => {
return callback(null, contents || '')
})
}
const unlink = (filename, callback) => {
AsyncStorage.removeItem(filename, callback)
}
// Nothing to do, no directories will be used on the browser
const mkdir = (dir, options, callback) => callback()
// Nothing to do, no data corruption possible in the browser
const ensureDatafileIntegrity = (filename, callback) => callback(null)
const crashSafeWriteFileLines = (filename, lines, callback) => {
lines.push('') // Add final new line
writeFile(filename, lines.join('\n'), callback)
}
// Interface
module.exports.exists = exists
module.exports.rename = rename
module.exports.writeFile = writeFile
module.exports.crashSafeWriteFileLines = crashSafeWriteFileLines
module.exports.appendFile = appendFile
module.exports.readFile = readFile
module.exports.unlink = unlink
module.exports.mkdir = mkdir
module.exports.ensureDatafileIntegrity = ensureDatafileIntegrity

@ -0,0 +1,108 @@
/* eslint-env jest */
// Forked from https://github.com/antoniopresto/react-native-local-mongodb/blob/93acbc8a9aaca86aed1d632855cd8b984501147b/test/persistence.test.js
const { promisify } = require('util')
const AsyncStorage = require('@react-native-async-storage/async-storage')
const DataStore = require('../../')
const getDb = async () => {
await AsyncStorage.clear()
const db = new DataStore({ filename: 'foo' })
await promisify(db.loadDatabase.bind(db))()
return db
}
it('update', async () => {
const db = await getDb()
const items0 = await promisify(db.find.bind(db))({})
expect(AsyncStorage.getItem).toHaveBeenCalled()
await promisify(db.insert.bind(db))({ name: 'Maggie' })
await promisify(db.insert.bind(db))({ name: 'Bob' })
expect(AsyncStorage.setItem).toHaveBeenCalled()
const items = await promisify(db.find.bind(db))({})
const maggie1 = await promisify(db.findOne.bind(db))({ name: 'Maggie' })
const bob1 = await promisify(db.findOne.bind(db))({ name: 'Bob' })
const res = await promisify(db.update.bind(db))({ name: { $in: ['Maggie', 'Bob'] } }, { $set: { age: 1 } }, { multi: true })
const maggie2 = await promisify(db.findOne.bind(db))({ name: 'Maggie' })
const bob2 = await promisify(db.findOne.bind(db))({ name: 'Bob' })
expect(res).toEqual(2)
expect(items0).toHaveLength(0)
expect(items).toHaveLength(2)
expect(maggie1.age).toBeUndefined()
expect(bob1.age).toBeUndefined()
expect(bob2.age).toEqual(1)
expect(maggie2.age).toEqual(1)
})
it('remove', async () => {
const db = await getDb()
const items0 = await promisify(db.find.bind(db))({})
expect(AsyncStorage.getItem).toHaveBeenCalled()
await promisify(db.insert.bind(db))({ name: 'Maggie' })
await promisify(db.insert.bind(db))({ name: 'Bob' })
expect(AsyncStorage.setItem).toHaveBeenCalled()
const items = await promisify(db.find.bind(db))({})
const res = await promisify(db.remove.bind(db))({ name: { $in: ['Bob'] } }, { multi: true })
const bob2 = await promisify(db.findOne.bind(db))({ name: 'Bob' })
expect(res).toEqual(1)
expect(items0).toHaveLength(0)
expect(items).toHaveLength(2)
expect(bob2).toBeNull()
})
it('resolve remove nonexistent', async () => {
const db = await getDb()
const items0 = await promisify(db.find.bind(db))({})
expect(AsyncStorage.getItem).toHaveBeenCalled()
await promisify(db.insert.bind(db))({ name: 'Maggie' })
await promisify(db.insert.bind(db))({ name: 'Bob' })
expect(AsyncStorage.setItem).toHaveBeenCalled()
const items = await promisify(db.find.bind(db))({})
const res = await promisify(db.remove.bind(db))({ name: 'nonexistent' }, { multi: true })
const nonexistent = await promisify(db.findOne.bind(db))({ name: 'nonexistent' })
expect(res).toEqual(0)
expect(items0).toHaveLength(0)
expect(items).toHaveLength(2)
expect(nonexistent).toBeNull()
})
it('resolve findOne nonexistent', async () => {
const db = await getDb()
await promisify(db.insert.bind(db))({ name: 'Maggie' })
await promisify(db.insert.bind(db))({ name: 'Bob' })
expect(AsyncStorage.setItem).toHaveBeenCalled()
const items = await promisify(db.find.bind(db))({ name: 'nonexistent' })
const item = await promisify(db.findOne.bind(db))({ name: 'nonexistent' })
expect(item).toBeNull()
expect(items.length).toEqual(0)
})
it('should limit', async () => {
const db = await getDb()
await promisify(db.insert.bind(db))({ name: 'A' })
await promisify(db.insert.bind(db))({ name: 'B' })
await promisify(db.insert.bind(db))({ name: 'C' })
await promisify(db.insert.bind(db))({ name: 'D' })
expect(AsyncStorage.setItem).toHaveBeenCalled()
const cursor = db.find({}).sort({ name: 1 }).skip(1).limit(2)
const docs = await promisify(cursor.exec.bind(cursor))()
expect(docs.length).toEqual(2)
expect(docs[1].name).toEqual('C')
})

@ -0,0 +1,3 @@
const browserResolve = require('browser-resolve')
module.exports = (id, opts) => browserResolve.sync(id, { ...opts, browser: 'react-native' })
Loading…
Cancel
Save