Skip to content

Commit

Permalink
Implementing PATCH requests, moving "index" methods into utility fu…
Browse files Browse the repository at this point in the history
…nction file & taking them off the prototype, refactoring `reindex()` to iterate the datastore once & the indices array many times (switched)
  • Loading branch information
avoidwork committed Jul 3, 2015
1 parent 3a03a6d commit b4b168d
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 260 deletions.
33 changes: 24 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ _Map_
Map of indexes, which are Sets containing Map keys.
**patch**
_Boolean_
Set from the success handler of `sync()`, infers `PATCH` requests are supported by the API collection.
**registry**
_Array_
Expand All @@ -178,7 +183,7 @@ Total records in the DataStore.
**uri**
_String_
API collection URI the DataStore is wired to, in a feedback loop (do not modify, use `setUri()`). Setting the value creates an implicit relationship with records, e.g. setting `/users` would an implicit URI structure of `/users/{key}`
API collection URI the DataStore is wired to, in a feedback loop (do not modify, use `setUri()`). Setting the value creates an implicit relationship with records, e.g. setting `/users` would imply a URI structure of `/users/{key}`. Trailing slashes may be stripped.
**versions**
_Map_
Expand All @@ -189,7 +194,10 @@ _Map_
**batch( array, type )**
_Promise_
The first argument must be an `Array`, and the second argument must be `del` or `set`.
The first argument must be an `Array`, and the second argument must be `del` or `set`. Batch operations with a DataStore
that is wired to an API with pagination enabled & `PATCH` support may create erroneous operations, such as `add` where
`replace` is appropriate; this will happen because the DataStore will not have the entire data set to generate it's
[JSONPatch](http://jsonpatchjs.com/) request.
```javascript
var haro = require('haro'),
Expand Down Expand Up @@ -246,7 +254,8 @@ store.set(null, {abc: true}).then(function (rec) {
**entries()**
_MapIterator_
Returns returns a new `Iterator` object that contains an array of `[key, value]` for each element in the `Map` object in insertion order.
Returns returns a new `Iterator` object that contains an array of `[key, value]` for each element in the `Map` object in
insertion order.
Example of deleting a record:
```javascript
Expand All @@ -267,7 +276,8 @@ do {
**filter( callbackFn )**
_Tuple_
Returns a `Tuple` of double `Tuples` with the shape `[key, value]` for records which returned `true` to `callbackFn(value, key)`.
Returns a `Tuple` of double `Tuples` with the shape `[key, value]` for records which returned `true` to
`callbackFn(value, key)`.
Example of filtering a DataStore:
```javascript
Expand Down Expand Up @@ -297,7 +307,8 @@ store.find({field1: 'some value'});
**forEach( callbackFn[, thisArg] )**
_Undefined_
Calls `callbackFn` once for each key-value pair present in the `Map` object, in insertion order. If a `thisArg` parameter is provided to `forEach`, it will be used as the this value for each callback.
Calls `callbackFn` once for each key-value pair present in the `Map` object, in insertion order. If a `thisArg`
parameter is provided to `forEach`, it will be used as the this value for each callback.
Example of deleting a record:
```javascript
Expand Down Expand Up @@ -421,7 +432,8 @@ store.request('https://somedomain.com/api').then(function (arg) {
_Tuple_

Returns a `Tuple` of double `Tuples` with the shape `[key, value]` of records found matching `arg`.
If `arg` is a `Function` a match is made if the result is `true`, if `arg` is a `RegExp` the field value must `.test()` as `true`, else the value must be an equality match.
If `arg` is a `Function` a match is made if the result is `true`, if `arg` is a `RegExp` the field value must `.test()`
as `true`, else the value must be an equality match.

Example of searching with a predicate function:
```javascript
Expand All @@ -440,7 +452,8 @@ store.batch(data, 'set').then(function (records) {
**set( key, data, batch=false, override=false )**
_Promise_

Returns a `Promise` for setting/amending a record in the DataStore, if `key` is `false` a version 4 `UUID` will be generated.
Returns a `Promise` for setting/amending a record in the DataStore, if `key` is `false` a version 4 `UUID` will be
generated.

If `override` is `true`, the existing record will be replaced instead of amended.

Expand All @@ -461,8 +474,10 @@ _Promise_
Returns a `Promise` for wiring the DataStore to an API, with the retrieved record set as the `resolve()` argument. This
creates an implicit mapping of `$uri/{key}` for records.

Pagination can be implemented by conditionally supplying `true` as the second argument. Doing so will `clear()` the DataStore
prior to a batch insertion.
Pagination can be implemented by conditionally supplying `true` as the second argument. Doing so will `clear()` the
DataStore prior to a batch insertion.

If `PATCH` requests are supported by the collection `batch()`, `del()` & `set()` will make `JSONPatch` requests.

Example setting the URI of the DataStore:
```javascript
Expand Down
130 changes: 64 additions & 66 deletions lib/haro.es6.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Map = global.Map || require("es6-map");
const Set = global.Set || require("es6-set");
const fetch = global.fetch || require("node-fetch");
const tuple = global.tuple || require("tiny-tuple");
const r = [8, 9, "a", "b"];
const regex = {
querystring: /\?.*/,
endslash: /\/$/
Expand All @@ -39,16 +40,6 @@ function deferred () {
return {resolve: resolver, reject: rejecter, promise: promise};
}

function iterate (obj, fn) {
if (obj instanceof Object) {
Object.keys(obj).forEach(function (i) {
fn.call(obj, obj[i], i);
});
} else {
obj.forEach(fn);
}
}

function keyIndex (key, data, delimiter) {
let keys = key.split(delimiter).sort(),
result;
Expand All @@ -64,6 +55,27 @@ function keyIndex (key, data, delimiter) {
return result;
}

function delIndex (index, indexes, delimiter, key, data) {
index.forEach(function (i) {
let idx = indexes.get(i),
value = keyIndex(i, data, delimiter);

if (idx.has(value)) {
idx.get(value).delete(key);
}
});
}

function iterate (obj, fn) {
if (obj instanceof Object) {
Object.keys(obj).forEach(function (i) {
fn.call(obj, obj[i], i);
});
} else {
obj.forEach(fn);
}
}

function merge (a, b) {
let c = clone(a),
d = clone(b);
Expand All @@ -87,34 +99,50 @@ function merge (a, b) {
return c;
}

function patch (ogdata = {}, data = {}, overwrite = false) {
function patch (ogdata = {}, data = {}, key = "", overwrite = false) {
let result = [];

if (overwrite) {
iterate(ogdata, (value, key) => {
if (key !== this.key && data[key] === undefined) {
result.push({op: "remove", path: "/" + key});
iterate(ogdata, function (v, k) {
if (k !== key && data[k] === undefined) {
result.push({op: "remove", path: "/" + k});
}
});
}

iterate(data, (value, key) => {
if (key !== this.key && ogdata[key] === undefined) {
result.push({op: "add", path: "/" + key, value: value});
} else if (JSON.stringify(ogdata[key]) !== JSON.stringify(value)) {
result.push({op: "replace", path: "/" + key, value: value});
iterate(data, function (v, k) {
if (k !== key && ogdata[k] === undefined) {
result.push({op: "add", path: "/" + k, value: v});
} else if (JSON.stringify(ogdata[k]) !== JSON.stringify(v)) {
result.push({op: "replace", path: "/" + k, value: v});
}
});

return result;
}

const r = [8, 9, "a", "b"];

function s () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}

function setIndexValue (index, key, value) {
if (!index.has(key)) {
index.set(key, new Set());
}

index.get(key).add(value);
}

function setIndex (index, indexes, delimiter, key, data, indice) {
if (!indice) {
index.forEach(function (i) {
setIndexValue(indexes.get(i), keyIndex(i, data, delimiter), key);
});
} else {
setIndexValue(indexes.get(indice), keyIndex(indice, data, delimiter), key);
}
}

function uuid () {
return (s() + s() + "-" + s() + "-4" + s().substr(0, 3) + "-" + r[Math.floor(Math.random() * 4)] + s().substr(0, 3) + "-" + s() + s() + s());
}
Expand Down Expand Up @@ -180,7 +208,7 @@ class Haro {
if (del) {
data = patch(this.toArray().map(i => {
return i[this.key];
}), args, true);
}), args, this.key, true);
} else {
data = [];
hash = {};
Expand All @@ -193,7 +221,7 @@ class Haro {
data.push({op: "add", path: "/", value: i});
}
});
data = data.concat(patch(this.toObject(), hash, true));
data = data.concat(patch(this.toObject(), hash, this.key, true));
}

if (data.length > 0) {
Expand Down Expand Up @@ -226,9 +254,10 @@ class Haro {
}

del (key, batch = false) {
let defer = deferred();
let defer = deferred(),
next;

let next = () => {
next = () => {
let index = this.registry.indexOf(key);

if (index > -1) {
Expand All @@ -240,7 +269,7 @@ class Haro {
this.registry.splice(index, 1);
}

this.delIndex(key, this.data.get(key));
delIndex(this.index, this.indexes, this.delimiter, key, this.data.get(key));
this.data.delete(key);
--this.total;

Expand All @@ -255,10 +284,9 @@ class Haro {
if (this.data.has(key)) {
if (!batch && this.uri) {
if (this.patch) {
// @todo implement this!
this.request(concatURI(this.uri, null), {
method: "patch",
body: null
body: JSON.stringify([{op: "remove", path: "/" + key}])
}).then(next, function (e) {
defer.reject(e[0] || e);
});
Expand All @@ -279,17 +307,6 @@ class Haro {
return defer.promise;
}

delIndex (key, data) {
this.index.forEach(i => {
let idx = this.indexes.get(i),
value = keyIndex(i, data, this.delimiter);

if (idx.has(value)) {
idx.get(value).delete(key);
}
});
}

entries () {
return this.data.entries();
}
Expand Down Expand Up @@ -386,14 +403,16 @@ class Haro {
this.indexes.clear();
this.index.forEach(i => {
this.indexes.set(i, new Map());
this.forEach((data, key) => {
this.setIndex(key, data, i);
});
this.forEach((data, key) => {
this.index.forEach(i => {
setIndex(this.index, this.indexes, this.delimiter, key, data, i);
});
});
} else {
this.indexes.set(index, new Map());
this.forEach((data, key) => {
this.setIndex(key, data, index);
setIndex(this.index, this.indexes, this.delimiter, key, data, index);
});
}

Expand Down Expand Up @@ -471,11 +490,11 @@ class Haro {
this.versions.get(lkey).add(tuple(ogdata));
}

this.delIndex(lkey, ogdata);
delIndex(this.index, this.indexes, this.delimiter, lkey, ogdata);
}

this.data.set(lkey, ldata);
this.setIndex(lkey, ldata);
setIndex(this.index, this.indexes, this.delimiter, lkey, ldata);
defer.resolve(this.get(lkey));
};

Expand All @@ -491,10 +510,9 @@ class Haro {

if (!batch && this.uri) {
if (this.patch) {
// @todo implement this!
this.request(concatURI(this.uri, null), {
method: "patch",
body: JSON.stringify(ldata)
body: JSON.stringify([{op: method === "post" ? "add" : "replace", path: "/" + lkey, value: ldata}])
}).then(next, function (e) {
defer.reject(e[0] || e);
});
Expand All @@ -513,26 +531,6 @@ class Haro {
return defer.promise;
}

setIndex (key, data, index) {
if (!index) {
this.index.forEach(i => {
this.setIndexValue(this.indexes.get(i), keyIndex(i, data, this.delimiter), key);
});
} else {
this.setIndexValue(this.indexes.get(index), keyIndex(index, data, this.delimiter), key);
}

return this;
}

setIndexValue (index, key, value) {
if (!index.has(key)) {
index.set(key, new Set());
}

index.get(key).add(value);
}

setUri (uri, clear = false) {
let defer = deferred();

Expand Down
Loading

0 comments on commit b4b168d

Please sign in to comment.