diff --git a/README.md b/README.md index 9cbfef0..b8f687d 100644 --- a/README.md +++ b/README.md @@ -77,20 +77,10 @@ var levelup = require("levelup"), db = jsonld(levelgraph(yourDB), opts); ``` -By default `update`s (and `delete`s) **delete all triples associated to the `@id` of the document before updating it**, the `'preserve'` option will ensure that all `put`s and `delete`s only update the triples that are mentioned in the document: - - -```javascript -var levelup = require("levelup"), - yourDB = levelup("./yourdb"), - levelgraph = require('levelgraph'), - jsonld = require('levelgraph-jsonld'), - opts = { - base: 'http://matteocollina.com/base', - preserve: true - }, - db = jsonld(levelgraph(yourDB), opts); -``` +> From v1, overwriting and deleting is more conservative. If you rely on the previous behavior you can set the `overwrite` option to `true` (when creating the db or as options to `put` and `del`) to: +> - overwrite all existing triples when using `put` +> - delete all blank nodes recursively when using `del` (cf upcoming `cut` function) +> This old api will be phased out. ### Put @@ -153,14 +143,6 @@ db.jsonld.put(nested, function(err, obj) { }); ``` -By default, `put` **deletes all triples associated to the `@id` of the document before updating it**. If you want to instead only update the triples that are part of the document you can use the `{ preserve : true }` option (you can also [set it globally when you create the DB](#usage)): - -```javascript -db.jsonld.put(nested, { preserve : true }, function(err, obj) { - // do something... -}); -``` - ### Get Retrieving a JSON-LD object from the store requires its `'@id'`: @@ -212,19 +194,33 @@ db.jsonld.put(nested, function(err, obj) { ### Delete -In order to delete an object, you can just pass it's `'@id'` to the -`'@del'` method: +In order to delete an object, you need to pass the document to the `'del'` method which will delete only the properties specified in the document: ```javascript -db.jsonld.del(manu['@id'], function(err) { +db.jsonld.del(manu, function(err) { // do something after it is deleted! }); ``` -By default, `del` **deletes all triples associated to the `@id`**. If you want to instead only delete the triples that are part of the document, you can use the `{ preserve : true }` option (you can also [set it globally when you create the DB](#usage)): +Note that blank nodes are ignored, so to delete blank nodes you need to pass the `cut: true` option (you can also add the `recurse: true`option) or use the `'cut'` method below. +> Note that since v1 `'del'` doesn't support passing an IRI anymore. + +### Cut + +In order to delete the blank nodes object, you can just pass it's `'@id'` to the +`'cut'` method: ```javascript -db.jsonld.del(nested, { preserve : true }, function(err) { - // do something... +db.jsonld.cut(manu['@id'], function(err) { + // do something after it is cut! +}); +``` + +You can also pass an object, but in this case the properties are not used to determine which triples will be deleted and only the `@id`s are considered. + +Using the `recurse` option you can follow all links and blank nodes (which might result in deleting more data than you expect) +```javascript +db.jsonld.cut(manu['@id'], { recurse: true }, function(err) { + // do something after it is cut! }); ``` diff --git a/index.js b/index.js index a548c08..0c8287b 100644 --- a/index.js +++ b/index.js @@ -127,7 +127,6 @@ function levelgraphJSONLD(db, jsonldOpts) { function doDel(obj, options, callback) { var blanks = {}; - jsonld.expand(obj, options, function(err, expanded) { if (err) { return callback && callback(err); @@ -136,6 +135,7 @@ function levelgraphJSONLD(db, jsonldOpts) { var stream = graphdb.delStream(); stream.on('close', callback); stream.on('error', callback); + if (options.base) { if (expanded['@context']) { expanded['@context']['@base'] = options.base; @@ -190,6 +190,38 @@ function levelgraphJSONLD(db, jsonldOpts) { }) } + function doCut(obj, options, callback) { + var iri = obj; + if (typeof obj !=='string') { + iri = obj['@id']; + } + if (iri === undefined) { + return callback && callback(null); + } + + var stream = graphdb.delStream(); + stream.on('close', callback); + stream.on('error', callback); + + (function delAllTriples(iri, done) { + graphdb.get({ subject: iri }, function(err, triples) { + async.each(triples, function(triple, cb) { + stream.write(triple); + if (triple.object.indexOf('_:') === 0 || (options.recurse && N3Util.isIRI(triple.object))) { + delAllTriples(triple.object, cb); + } else { + cb(); + } + }, done); + }); + })(iri, function(err) { + if (err) { + return callback(err); + } + stream.end(); + }); + } + graphdb.jsonld.put = function(obj, options, callback) { if (typeof obj === 'string') { @@ -202,68 +234,59 @@ function levelgraphJSONLD(db, jsonldOpts) { } options.base = options.base || this.options.base; - options.preserve = options.preserve || this.options.preserve || false; + options.overwrite = options.overwrite !== undefined ? options.overwrite : ( this.options.overwrite !== undefined ? this.options.overwrite : false ); - graphdb.jsonld.del(obj, options, function(err) { - if (err) { - return callback && callback(err); - } + if (!options.overwrite) { doPut(obj, options, callback); - }); + } else { + graphdb.jsonld.del(obj, options, function(err) { + if (err) { + return callback && callback(err); + } + }); + doPut(obj, options, callback); + } }; graphdb.jsonld.del = function(obj, options, callback) { - var blanks = {}; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + options.cut = options.cut !== undefined ? options.cut : ( this.options.cut !== undefined ? this.options.cut : false ); + options.recurse = options.recurse !== undefined ? options.recurse : ( this.options.recurse !== undefined ? this.options.recurse : false ); if (typeof obj === 'string') { try { obj = JSON.parse(obj); } catch (e) { - // Handle case where we're trying to delete by passing an IRI - if (!N3Util.isIRI(obj)) { - throw e + if (N3Util.isIRI(obj) && !options.cut) { + callback(new Error("Passing an IRI to del is not supported anymore. Please pass a JSON-LD document.")) } } } + if (!options.cut) { + doDel(obj, options, callback) + } else { + doCut(obj, options, callback) + } + }; + + + graphdb.jsonld.cut = function(obj, options, callback) { + if (typeof options === 'function') { callback = options; options = {}; } - options.preserve = options.preserve || this.options.preserve || false; - - if (options.preserve === false) { - var iri = obj; - if (typeof obj !=='string') { - iri = obj['@id']; - } - - var stream = graphdb.delStream(); - stream.on('close', callback); - stream.on('error', callback); + options.recurse = options.recurse || this.options.recurse || false; - (function delAllTriples(iri, done) { - graphdb.get({ subject: iri }, function(err, triples) { - async.each(triples, function(triple, cb) { - stream.write(triple); - if (triple.object.indexOf('_:') === 0) { - delAllTriples(triple.object, cb); - } else { - cb(); - } - }, done); - }); - })(iri, function(err) { - if (err) { - return callback(err); - } - stream.end(); - }); - } else { - doDel(obj, options, callback) - } - }; + doCut(obj, options, callback); + } // http://json-ld.org/spec/latest/json-ld-api/#data-round-tripping function getCoercedObject(object) { diff --git a/package.json b/package.json index 134c3c6..0318474 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "async": "^2.1.4", "jsonld": "~0.4.6", "memdb": "^1.3.1", - "n3": "^0.9.0", + "n3": "iilab/N3.js#fix/object-is-not-IRI", "uuid": "^3.0.1" }, "peerDependencies": { diff --git a/test/cut_spec.js b/test/cut_spec.js new file mode 100644 index 0000000..f961052 --- /dev/null +++ b/test/cut_spec.js @@ -0,0 +1,131 @@ +var expect = require('chai').expect; +var helper = require('./helper'); + +describe('jsonld.cut', function() { + + var db, manu, tesla; + + beforeEach(function() { + db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/' } }); + manu = helper.getFixture('manu.json'); + tesla = helper.getFixture('tesla.json'); + }); + + afterEach(function(done) { + db.close(done); + }); + + it('should accept a done callback', function(done) { + db.jsonld.cut(manu, done); + }); + + it('should cut a basic object', function(done) { + db.jsonld.put(manu, function() { + db.jsonld.cut(manu, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.be.empty; + done(); + }); + }); + }); + }); + + it('should cut nothing', function(done) { + db.jsonld.put(manu, function() { + db.jsonld.cut({}, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(2); + done(); + }); + }); + }); + }); + + it('should cut a complex object', function(done) { + db.jsonld.put(tesla, function() { + db.jsonld.cut(tesla, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(0); + done(); + }); + }); + }); + }); + + it('should del an iri with the cut option', function(done) { + db.jsonld.put(manu, function() { + db.jsonld.del(manu['@id'], function(err) { + expect(err && err.message).to.equal("Passing an IRI to del is not supported anymore. Please pass a JSON-LD document.") + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(2); + done(); + }); + }); + }); + }); + + + it('should del a single object leaving blank nodes', function(done) { + db.jsonld.put(manu, function() { + db.jsonld.put(tesla, function() { + db.jsonld.del(tesla, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(10); // 2 triples from Manu and 8 from tesla blanks. + done(); + }); + }); + }); + }); + }); + + it('should del a single object with the cut option leaving blank nodes', function(done) { + // This should also be deprecated in favor of using a `cut` option or the `cut` function. + db.jsonld.put(manu, function() { + db.jsonld.put(tesla, function() { + db.jsonld.del(tesla, {cut: true}, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(2); // 2 triples from Manu. + done(); + }); + }); + }); + }); + }); + + it('should del a single object with no blank nodes completely', function(done) { + var library = helper.getFixture('library_framed.json'); + + db.jsonld.put(manu, function() { + db.jsonld.put(library, function() { + db.jsonld.del(library, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(2); + done(); + }); + }); + }); + }); + }); + + it('should del obj passed as stringified JSON', function(done) { + var jld = {"@context": { "@vocab": "https://schema.org/"}, "name": "BigBlueHat"}; + + db.jsonld.put(JSON.stringify(jld), function() { + db.jsonld.del(JSON.stringify(jld), function(err) { + expect(err).to.not.exist; + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(1); + done(); + }); + }); + }); + }); +}); diff --git a/test/del_spec.js b/test/del_spec.js index 8355716..71f67fb 100644 --- a/test/del_spec.js +++ b/test/del_spec.js @@ -31,9 +31,147 @@ describe('jsonld.del', function() { }); }); + + it('should del nothing', function(done) { + db.jsonld.put(manu, function() { + db.jsonld.del({}, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(2); + done(); + }); + }); + }); + }); + it('should del a complex object', function(done) { db.jsonld.put(tesla, function() { db.jsonld.del(tesla, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(8); + done(); + }); + }); + }); + }); + + it('should del a complex object including blank nodes with the cut option set to true', function(done) { + db.jsonld.put(tesla, function(err) { + db.jsonld.del(tesla, { cut: true }, function() { + db.get({}, function(err, triples) { + // blank nodes are left. Consistent with https://www.w3.org/TR/ldpatch/#Delete-statement + expect(triples).to.have.length(0); + done(); + }); + }); + }); + }); + + it('should error when iri is passed', function(done) { + db.jsonld.put(manu, function() { + db.jsonld.del(manu['@id'], function(err) { + expect(err && err.message).to.equal("Passing an IRI to del is not supported anymore. Please pass a JSON-LD document.") + done(); + }); + }); + }); + + it('should del an iri with the cut option', function(done) { + db.jsonld.put(manu, function() { + db.jsonld.del(manu['@id'], { cut: true }, function(err) { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(0); + done(); + }); + }); + }); + }); + + + it('should del a single object leaving blank nodes', function(done) { + db.jsonld.put(manu, function() { + db.jsonld.put(tesla, function() { + db.jsonld.del(tesla, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(10); // 2 triples from Manu and 8 from tesla blanks. + done(); + }); + }); + }); + }); + }); + + it('should del a single object with the cut option leaving blank nodes', function(done) { + // This should also be deprecated in favor of using a `cut` option or the `cut` function. + db.jsonld.put(manu, function() { + db.jsonld.put(tesla, function() { + db.jsonld.del(tesla, {cut: true}, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(2); // 2 triples from Manu. + done(); + }); + }); + }); + }); + }); + + it('should del a single object with no blank nodes completely', function(done) { + var library = helper.getFixture('library_framed.json'); + + db.jsonld.put(manu, function() { + db.jsonld.put(library, function() { + db.jsonld.del(library, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(2); + done(); + }); + }); + }); + }); + }); + + it('should del obj passed as stringified JSON', function(done) { + var jld = {"@context": { "@vocab": "https://schema.org/"}, "name": "BigBlueHat"}; + + db.jsonld.put(JSON.stringify(jld), function() { + db.jsonld.del(JSON.stringify(jld), function(err) { + expect(err).to.not.exist; + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(1); + done(); + }); + }); + }); + }); +}); + +describe('jsonld.del with overwrite and cut set to true (backward compatibility)', function() { + + var db, manu, tesla; + + beforeEach(function() { + db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/', overwrite: true, cut: true } }); + manu = helper.getFixture('manu.json'); + tesla = helper.getFixture('tesla.json'); + }); + + afterEach(function(done) { + db.close(done); + }); + + it('should accept a done callback', function(done) { + db.jsonld.put(manu, done); + }); + + it('should del a basic object', function(done) { + db.jsonld.put(manu, function() { + db.jsonld.del(manu, function() { db.get({}, function(err, triples) { // getting the full db expect(triples).to.be.empty; @@ -43,23 +181,87 @@ describe('jsonld.del', function() { }); }); - it('should del a complex object with the preserve option leaving blank nodes', function(done) { + it('should del nothing', function(done) { + db.jsonld.put(manu, function() { + db.jsonld.del({}, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(2); + done(); + }); + }); + }); + }); + + it('should del nothing with a top blank node', function(done) { + delete manu["@id"]; + db.jsonld.put(manu, function() { + db.jsonld.del({}, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(2); + done(); + }); + }); + }); + }); + + it('should del nothing with the recurse option set and a top blank node', function(done) { + delete manu["@id"]; + db.jsonld.put(manu, function() { + db.jsonld.del({}, { recurse: true }, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.have.length(2); + done(); + }); + }); + }); + }); + + it('should del a complex object', function(done) { + db.jsonld.put(tesla, function() { + db.jsonld.del(tesla, function() { + db.get({}, function(err, triples) { + // getting the full db + expect(triples).to.be.empty; + done(); + }); + }); + }); + }); + + it('should del a complex object cutting blank nodes', function(done) { db.jsonld.put(tesla, function() { - db.jsonld.del(tesla, { preserve: true }, function() { + db.jsonld.del(tesla, function() { + db.get({}, function(err, triples) { + // blank nodes are cut. + expect(triples).to.have.length(0); + done(); + }); + }); + }); + }); + + it('should del a complex object with no blank nodes without recursing', function(done) { + var library = helper.getFixture('library_framed.json'); + + db.jsonld.put(library, function() { + db.jsonld.del(library, function() { db.get({}, function(err, triples) { // blank nodes are left. Consistent with https://www.w3.org/TR/ldpatch/#Delete-statement - expect(triples).to.have.length(8); + expect(triples).to.have.length(7); done(); }); }); }); }); - it('should del a complex object with the preserve option with no blank nodes completely', function(done) { + it('should del a complex object with no blank nodes with the recurse option completely', function(done) { var library = helper.getFixture('library_framed.json'); db.jsonld.put(library, function() { - db.jsonld.del(library, { preserve: true }, function() { + db.jsonld.del(library, {recurse: true}, function() { db.get({}, function(err, triples) { // blank nodes are left. Consistent with https://www.w3.org/TR/ldpatch/#Delete-statement expect(triples).to.have.length(0); @@ -69,7 +271,6 @@ describe('jsonld.del', function() { }); }); - it('should del an iri', function(done) { db.jsonld.put(manu, function() { db.jsonld.del(manu['@id'], function() { @@ -96,10 +297,10 @@ describe('jsonld.del', function() { }); }); - it('should del a single object with the preserve option leaving blank nodes', function(done) { + it('should del a single object with cut set to false leaving blank nodes', function(done) { db.jsonld.put(manu, function() { db.jsonld.put(tesla, function() { - db.jsonld.del(tesla, { preserve: true }, function() { + db.jsonld.del(tesla, { cut: false }, function() { db.get({}, function(err, triples) { // getting the full db expect(triples).to.have.length(10); // 2 triples from Manu and 8 from tesla blanks. @@ -110,12 +311,12 @@ describe('jsonld.del', function() { }); }); - it('should del a single object with the preserve option with no blank nodes completely', function(done) { + it('should del a single object with no blank nodes completely with the recurse option', function(done) { var library = helper.getFixture('library_framed.json'); - db.jsonld.put(manu, function() { - db.jsonld.put(library, function() { - db.jsonld.del(library, { preserve: true }, function() { + db.jsonld.put(manu, function(err) { + db.jsonld.put(library, function(err) { + db.jsonld.del(library, {recurse:true}, function(err) { db.get({}, function(err, triples) { // getting the full db expect(triples).to.have.length(2); diff --git a/test/put_spec.js b/test/put_spec.js index 148f886..b952bb9 100644 --- a/test/put_spec.js +++ b/test/put_spec.js @@ -158,7 +158,7 @@ describe('jsonld.put', function() { subject: 'http://manu.sporny.org#person', predicate: 'http://xmlns.com/foaf/0.1/homepage' }, function(err, triples) { - expect(triples).to.have.length(0); + expect(triples).to.have.length(1); done(); }); }); @@ -166,11 +166,11 @@ describe('jsonld.put', function() { }); - it('should preserve properties with the preserve option', function(done) { + it('should overwrite properties with the overwrite option', function(done) { db.jsonld.put(manu, function(err, instance) { delete instance.homepage - db.jsonld.put(instance, { preserve: true }, function() { + db.jsonld.put(instance, { overwrite: true }, function() { db.get({ subject: 'http://manu.sporny.org#person', predicate: 'http://xmlns.com/foaf/0.1/homepage' @@ -234,12 +234,12 @@ describe('jsonld.put', function() { }); -describe('jsonld.put with default base', function() { +describe('jsonld.put with default base and overwrite and cut option set to true (backward compatibility)', function() { var db, manu; beforeEach(function() { - db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/ahah/' } }); + db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/ahah/', overwrite: true, cut: true } }); manu = helper.getFixture('manu.json'); }); @@ -303,26 +303,26 @@ describe('jsonld.put with default base', function() { }); }); - it('should keep the default behavior without the preserve option', function(done) { + it('should break compatibility and not delete existing triples', function(done) { var chapter = helper.getFixture('chapter.json'); var description = helper.getFixture('chapterdescription.json'); db.jsonld.put(chapter, function() { db.jsonld.put(description, function() { db.get({}, function(err, triples) { - expect(triples).to.have.length(1); + expect(triples).to.have.length(3); done(); }); }); }); }); - it('should not overwrite existing facts with the preserve option', function(done) { + it('should not delete existing facts with the cut option set to false', function(done) { var chapter = helper.getFixture('chapter.json'); var description = helper.getFixture('chapterdescription.json'); db.jsonld.put(chapter, function() { - db.jsonld.put(description, { preserve: true }, function() { + db.jsonld.put(description, { cut: false }, function() { db.get({}, function(err, triples) { expect(triples).to.have.length(3); done(); @@ -344,12 +344,12 @@ describe('jsonld.put with default base', function() { }); -describe('jsonld.put with base and preserve option', function() { +describe('jsonld.put with base', function() { var db, manu; beforeEach(function() { - db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/ahah/', preserve: true } }); + db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/ahah/' } }); manu = helper.getFixture('manu.json'); }); @@ -616,4 +616,237 @@ describe('jsonld.put with base and preserve option', function() { }); }); + describe('jsonld.put with overwrite and cut set to true (backward compatibility)', function() { + + var db, manu; + + beforeEach(function() { + db = helper.getDB({ jsonld: { overwrite: false, cut: true } }); + manu = helper.getFixture('manu.json'); + }); + + afterEach(function(done) { + db.close(done); + }); + + it('should accept a done callback', function(done) { + db.jsonld.put(manu, done); + }); + + it('should store a triple', function(done) { + db.jsonld.put(manu, function() { + db.get({ + subject: 'http://manu.sporny.org#person', + predicate: 'http://xmlns.com/foaf/0.1/name' + }, function(err, triples) { + expect(triples).to.have.length(1); + done(); + }); + }); + }); + + it('should store two triples', function(done) { + db.jsonld.put(manu, function() { + db.get({ + subject: 'http://manu.sporny.org#person' + }, function(err, triples) { + expect(triples).to.have.length(2); + done(); + }); + }); + }); + + it('should store a JSON file', function(done) { + db.jsonld.put(JSON.stringify(manu), function() { + db.get({ + subject: 'http://manu.sporny.org#person' + }, function(err, triples) { + expect(triples).to.have.length(2); + done(); + }); + }); + }); + + it('should support a base IRI', function(done) { + manu['@id'] = '42' + db.jsonld.put(manu, { base: 'http://levelgraph.org/tests/' }, function() { + db.get({ + subject: 'http://levelgraph.org/tests/42', + predicate: 'http://xmlns.com/foaf/0.1/name' + }, function(err, triples) { + expect(triples).to.have.length(1); + done(); + }); + }); + }); + + it('should generate an @id for unknown objects', function(done) { + delete manu['@id']; + var baseString = 'http://levelgraph.org/tests/'; + var baseRegEx = /^_\:/; + + db.jsonld.put(manu, { base: baseString }, function() { + db.search({ + subject: db.v('subject'), + predicate: 'http://xmlns.com/foaf/0.1/name' + }, function(err, solutions) { + expect(solutions[0].subject).to.match(baseRegEx); + done(); + }); + }); + }); + + it('should generate an @id for all blank nodes in a complex object', function(done) { + var tesla = helper.getFixture('tesla.json'); + + db.jsonld.put(tesla, function(err, obj) { + expect(obj['gr:hasPriceSpecification']['@id']).to.match(/^_\:/); + expect(obj['gr:includes']['@id']).to.match(/^_\:/); + done(); + }); + }); + + it('should pass the generated @id to callback', function(done) { + delete manu['@id']; + var baseString = 'http://levelgraph.org/tests/'; + var baseRegEx = /^_\:/; + + db.jsonld.put(manu, { base: baseString }, function(err, obj) { + expect(obj['@id']).to.match(baseRegEx); + done(); + }); + }); + + it('should convert @type into http://www.w3.org/1999/02/22-rdf-syntax-ns#type', function(done) { + db.jsonld.put(helper.getFixture('tesla.json'), function() { + db.get({ + subject: 'http://example.org/cars/for-sale#tesla', + predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', + object: 'http://purl.org/goodrelations/v1#Offering' + }, function(err, triples) { + expect(triples).to.have.length(1); + done(); + }); + }); + }); + + it('should update a property', function(done) { + db.jsonld.put(manu, function(err, instance) { + instance.homepage = 'http://another/website'; + db.jsonld.put(instance, function() { + db.get({ + subject: 'http://manu.sporny.org#person', + predicate: 'http://xmlns.com/foaf/0.1/homepage', + object: 'http://another/website' + }, function(err, triples) { + expect(triples).to.have.length(1); + done(); + }); + }); + }); + }); + + it('should add a property', function(done) { + db.jsonld.put(manu, function(err, instance) { + instance.age = 42; + instance['@context'].age = 'http://xmlns.com/foaf/0.1/age'; + + db.jsonld.put(instance, function() { + db.get({ + subject: 'http://manu.sporny.org#person', + predicate: 'http://xmlns.com/foaf/0.1/age', + object: '"42"^^http://www.w3.org/2001/XMLSchema#integer' + }, function(err, triples) { + expect(triples).to.have.length(1); + done(); + }); + }); + }); + }); + + it('should break compatibility and keep an existing property', function(done) { + db.jsonld.put(manu, function(err, instance) { + delete instance.homepage + + db.jsonld.put(instance, function() { + db.get({ + subject: 'http://manu.sporny.org#person', + predicate: 'http://xmlns.com/foaf/0.1/homepage' + }, function(err, triples) { + expect(triples).to.have.length(1); + done(); + }); + }); + }); + }); + + + it('should preserve properties with the overwrite option set to false', function(done) { + db.jsonld.put(manu, function(err, instance) { + delete instance.homepage + + db.jsonld.put(instance, { overwrite: false }, function() { + db.get({ + subject: 'http://manu.sporny.org#person', + predicate: 'http://xmlns.com/foaf/0.1/homepage' + }, function(err, triples) { + expect(triples).to.have.length(1); + done(); + }); + }); + }); + }); + + it('should delete a nested object', function(done) { + db.jsonld.put(helper.getFixture('tesla.json'), function(err, instance) { + delete instance['gr:hasPriceSpecification']; + + db.jsonld.put(instance, function() { + db.get({ + subject: 'http://example.org/cars/for-sale#tesla', + predicate: 'http://purl.org/goodrelations/v1#' + }, function(err, triples) { + expect(triples).to.be.empty; + done(); + }); + }); + }); + }); + + it('should receive error on invalid input', function(done) { + var invalid = { + "@context": { "@vocab": "http//example.com/" }, + "test": { "@value": "foo", "bar": "oh yes" } + } + db.jsonld.put(invalid, function(err) { + expect(err && err.name).to.equal('jsonld.SyntaxError'); + expect(err && err.message).to.equal('Invalid JSON-LD syntax; the value of "@vocab" in a @context must be an absolute IRI.'); + done(); + }); + }); + + it('should manage mapped @id', function(done) { + var mapped_id = helper.getFixture('mapped_id.json') + db.jsonld.put(mapped_id, {preserve: true}, function(err, obj) { + expect(err && err.name).to.be.null; + db.get({}, function(err, triples) { + expect(triples).to.have.length(4); + done(); + }); + }); + }); + + it('should insert graphs', function(done) { + var library = helper.getFixture('library.json'); + + db.jsonld.put(library, function() { + db.get({}, function(err, triples) { + expect(triples).to.have.length(9); + done(); + }); + }); + }); + + }); + });