Beginnings of new js sdk

This commit is contained in:
Rafael Weinstein
2015-10-23 12:55:03 -07:00
parent fa3a0c4c8c
commit bf7cdbf5b4
9 changed files with 351 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
[ignore]
[include]
[libs]
[options]
unsafe.enable_getters_and_setters=true
+2
View File
@@ -0,0 +1,2 @@
node_modules
dist
+17
View File
@@ -0,0 +1,17 @@
{
"name": "newjs",
"main": "dist/noms.js",
"dependencies": {
"rusha": "^0.8.3"
},
"devDependencies": {
"babel": "^5.6.23",
"chai": "^3.2.0",
"mocha": "^2.3.0"
},
"scripts": {
"start": "babel -w src/ -d dist/",
"build": "babel src/ -d dist/",
"test": "babel src/ -d dist/; flow; mocha --ui tdd dist/"
}
}
+25
View File
@@ -0,0 +1,25 @@
/* @flow */
'use strict';
const Ref = require('./ref.js');
class Chunk {
ref: Ref;
data: string;
constructor(data: string = '', ref: ?Ref) {
this.data = data;
this.ref = ref ? ref : Ref.fromData(data);
}
isEmpty(): boolean {
return this.data.length === 0;
}
static emptyChunk: Chunk;
}
Chunk.emptyChunk = new Chunk();
module.exports = Chunk;
+35
View File
@@ -0,0 +1,35 @@
/* @flow */
'use strict';
const {suite, test} = require('mocha');
const {assert} = require('chai');
const Chunk = require('./chunk.js');
const Ref = require('./ref.js');
suite('Chunk', () => {
test('construct', () => {
let c = new Chunk('abc');
assert.strictEqual(c.data, 'abc');
assert.isTrue(c.ref.equals(Ref.parse('sha1-a9993e364706816aba3e25717850c26c9cd0d89d')));
assert.isFalse(c.isEmpty());
});
test('construct with ref', () => {
let ref = Ref.parse('sha1-0000000000000000000000000000000000000001');
let c = new Chunk('abc', ref);
assert.strictEqual(c.data, 'abc');
assert.isTrue(c.ref.equals(Ref.parse('sha1-0000000000000000000000000000000000000001')))
assert.isFalse(c.isEmpty());
});
test('isEmpty', () => {
function assertChunkIsEmpty(c: Chunk) {
assert.strictEqual(c.data.length, 0);
assert.isTrue(c.isEmpty());
}
assertChunkIsEmpty(new Chunk());
assertChunkIsEmpty(new Chunk(''));
});
});
+53
View File
@@ -0,0 +1,53 @@
/* @flow */
'use strict';
const Ref = require('./ref.js');
const Chunk = require('./chunk.js');
class MemoryStore {
_data: { [key: string]: Chunk };
_root: Ref;
constructor() {
this._data = Object.create(null);
this._root = new Ref();
}
get root(): Ref {
return this._root;
}
updateRoot(current: Ref, last: Ref): boolean {
if (!this._root.equals(last)) {
return false
}
this._root = current;
return true;
}
get(ref: Ref): Chunk {
var c = this._data[ref.toString()];
if (c == null) {
c = Chunk.emptyChunk;
}
return c;
}
has(ref: Ref): boolean {
return this._data[ref.toString()] == null;
}
put(c: Chunk) {
this._data[c.ref.toString()] = c;
}
get size(): number {
return Object.keys(this._data).length;
}
close() {}
}
module.exports = MemoryStore;
+60
View File
@@ -0,0 +1,60 @@
/* @flow */
'use strict';
const {suite, test} = require('mocha');
const {assert} = require('chai');
const Chunk = require('./chunk.js');
const Ref = require('./ref.js');
const MemoryStore = require('./memory_store.js');
suite('MemoryStore', () => {
function assertInputInStore(input: string, ref: Ref, ms: MemoryStore) {
let chunk = ms.get(ref);
assert.isFalse(chunk.isEmpty());
assert.strictEqual(input, chunk.data);
}
test('put', () => {
let ms = new MemoryStore();
let input = 'abc';
let c = new Chunk(input);
ms.put(c);
// See http://www.di-mgt.com.au/sha_testvectors.html
assert.strictEqual('sha1-a9993e364706816aba3e25717850c26c9cd0d89d', c.ref.toString());
ms.updateRoot(c.ref, ms.root);
assertInputInStore(input, c.ref, ms);
// Re-writing the same data should be idempotent and should not result in a second put
c = new Chunk(input);
ms.put(c);
assertInputInStore(input, c.ref, ms);
});
test('updateRoot', () => {
let ms = new MemoryStore();
let oldRoot = ms.root;
assert.isTrue(oldRoot.isEmpty());
let bogusRoot = Ref.parse('sha1-81c870618113ba29b6f2b396ea3a69c6f1d626c5'); // sha1("Bogus, Dude")
let newRoot = Ref.parse('sha1-907d14fb3af2b0d4f18c2d46abe8aedce17367bd'); // sha1("Hello, World")
// Try to update root with bogus oldRoot
let result = ms.updateRoot(newRoot, bogusRoot);
assert.isFalse(result);
// Now do a valid root update
result = ms.updateRoot(newRoot, oldRoot);
assert.isTrue(result);
});
test('get non-existing', () => {
let ms = new MemoryStore();
let ref = Ref.parse('sha1-1111111111111111111111111111111111111111');
let c = ms.get(ref);
assert.isTrue(c.isEmpty());
});
});
+81
View File
@@ -0,0 +1,81 @@
/* @flow */
'use strict';
const Rusha = require('rusha');
const r = new Rusha();
const sha1Size = 20;
const pattern = /^sha1-([0-9a-f]{40})$/;
function uint8ArrayToHex(a: Uint8Array): string {
let hex = '';
for (let i = 0; i < a.length; i++) {
let v = a[i].toString(16);
if (v.length == 1) {
hex += '0' + v;
} else {
hex += v;
}
}
return hex;
}
function hexToUint8(s: string): Uint8Array {
let digest = new Uint8Array(sha1Size);
for (let i = 0; i < sha1Size; i++) {
let ch = s.substring(i*2, i*2 + 2);
digest[i] = parseInt(ch, 16)
}
return digest;
}
class Ref {
digest: Uint8Array;
constructor(digest: Uint8Array = new Uint8Array(sha1Size)) {
this.digest = digest;
}
isEmpty(): boolean {
for (let i = 0; i < sha1Size; i++) {
if (this.digest[i] != 0) {
return false;
}
}
return true;
}
equals(other: Ref): boolean {
for (let i = 0; i < sha1Size; i++) {
if (this.digest[i] != other.digest[i]) {
return false;
}
}
return true;
}
toString(): string {
return 'sha1-' + uint8ArrayToHex(this.digest);
}
static parse(s: string): Ref {
let m = s.match(pattern);
if (m === null) {
throw Error('Could not parse ref: ' + s);
}
return new Ref(hexToUint8(m[1]));
}
static fromData(data: string): Ref {
let digest = r.rawDigest(data);
return new Ref(new Uint8Array(digest.buffer));
}
}
module.exports = Ref;
+70
View File
@@ -0,0 +1,70 @@
/* @flow */
'use strict';
const {suite, test} = require('mocha');
const {assert} = require('chai');
const Ref = require('./ref.js');
suite('Ref', () => {
test('parse', () => {
function assertParseError(s) {
assert.throws(() => {
Ref.parse(s);
});
}
assertParseError('foo');
assertParseError('sha1');
assertParseError('sha1-0');
// too many digits
assertParseError('sha1-00000000000000000000000000000000000000000');
// 'g' not valid hex
assertParseError('sha1- 000000000000000000000000000000000000000g');
// sha2 not supported
assertParseError('sha2-0000000000000000000000000000000000000000');
let r = Ref.parse('sha1-0000000000000000000000000000000000000000');
assert.isNotNull(r);
});
test('equals', () => {
let r0 = Ref.parse('sha1-0000000000000000000000000000000000000000');
let r01 = Ref.parse('sha1-0000000000000000000000000000000000000000');
let r1 = Ref.parse('sha1-0000000000000000000000000000000000000001');
assert.isTrue(r0.equals(r01));
assert.isTrue(r01.equals(r0));
assert.isFalse(r0.equals(r1));
assert.isFalse(r1.equals(r0));
});
test('toString', () => {
let s = 'sha1-0123456789abcdef0123456789abcdef01234567';
let r = Ref.parse(s);
assert.strictEqual(s, r.toString());
});
test('fromData', () => {
let r = Ref.fromData('abc');
assert.strictEqual('sha1-a9993e364706816aba3e25717850c26c9cd0d89d', r.toString());
});
test('isEmpty', () => {
let digest = new Uint8Array(20);
let r = new Ref(digest);
assert.isTrue(r.isEmpty());
digest[0] = 10;
r = new Ref(digest);
assert.isFalse(r.isEmpty());
r = new Ref();
assert.isTrue(r.isEmpty());
});
});