├── test ├── mocha.opts ├── fixture │ └── ratatat.json ├── search_spec.js ├── iri_spec.js ├── languagetags_spec.js ├── cut_spec.js ├── get_spec.js ├── del_spec.js ├── datatype_spec.js ├── helper.js └── put_spec.js ├── .zuul.yml ├── bower.json ├── .gitignore ├── browserify.sh ├── CHANGELOG.md ├── .jshintrc ├── .travis.yml ├── .github └── workflows │ └── nodejs.yml ├── package.json ├── README.md └── index.js /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --ui bdd 2 | --growl 3 | --colors 4 | --check-leaks 5 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: mocha-bdd 2 | browsers: 3 | - name: chrome 4 | version: 31..latest 5 | - name: firefox 6 | version: 28..latest 7 | - name: opera 8 | version: 17..latest 9 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "levelgraph-jsonld", 3 | "version": "0.4.0", 4 | "main": "build/levelgraph-jsonld.js", 5 | "dependencies": { 6 | "levelgraph": "^1.1.1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | .idea 17 | docs 18 | .DS_Store 19 | coverage/ 20 | package-lock.json 21 | -------------------------------------------------------------------------------- /browserify.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | rm -rf build 4 | mkdir build 5 | ./node_modules/.bin/browserify -s levelgraphJSONLD index.js > build/levelgraph-jsonld.js 6 | ./node_modules/.bin/uglifyjs build/levelgraph-jsonld.js > build/levelgraph-jsonld.min.js 7 | du -h build/* 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0.4.0 (2014-11-24) 3 | 4 | #### Breaking Changes 5 | 6 | * Changed internal representation of *Literals* (removing < >) 7 | ```"42"^^``` becomes 8 | ``` "42"^^http://www.w3.org/2001/XMLSchema#integer ``` 9 | 10 | If you need a script to migrate your data please [create new 11 | issue](https://github.com/mcollina/levelgraph-jsonld/issues) 12 | 13 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "es5": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": true, 10 | "noarg": true, 11 | "sub": true, 12 | "undef": true, 13 | "unused": false, 14 | "boss": true, 15 | "eqnull": true, 16 | "laxcomma": true, 17 | "quotmark": true, 18 | "globals": { 19 | "describe": false, 20 | "beforeEach": false, 21 | "afterEach": false, 22 | "expect": false, 23 | "it": false, 24 | "setImmediate": false, 25 | "sinon": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "5" 5 | - "6" 6 | - "7" 7 | - "8" 8 | script: 9 | - npm run coverage 10 | - test $SAUCE_USERNAME && npm run zuul || echo 'not running on saucelabs' 11 | after_script: 12 | - npm run publish-coverage 13 | env: 14 | global: 15 | - secure: HQYn8X9CAc9gtzHdmFErbS5yf2Ug63i/MUAV4jqI/+0v5b6m3zLbhQGSXlboQEk+uqtOebdAdGsTJpoLq1JghqcRdrUwHV+tdENJguc4fStF8q+u9+U8fw8eKiVwSbHKR66Z2uhyabI/AkZ4Lqmlsi7zNM7ESOFRuSyx5c4pUtE= 16 | - secure: JZ+sZY/ZDosFLnE6zPa2QFl8GBq144Iwi+LA2PgoYsPTu3H7ksIh3yvEVnahQ0N0jYIXvKc9HlUECwI8AREp8sszgrQzKKhGUEYlv3vIIz34f+3hV6ZaBt9Tho5LHOXe0HXWmFP74nFKj7qFXGc4VJ9igAOzaqVRV2n0H3qUTaM= 17 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'master' 7 | push: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | 13 | unit-tests: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node-version: [12.x, 14.x, 16.x] 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm install 25 | - run: npm run build --if-present 26 | - run: npm test 27 | env: 28 | CI: true 29 | 30 | coverage: 31 | runs-on: ubuntu-latest 32 | strategy: 33 | matrix: 34 | node-version: [16.x] 35 | steps: 36 | - uses: actions/checkout@v2 37 | - name: Use Node.js ${{ matrix.node-version }} 38 | uses: actions/setup-node@v2 39 | with: 40 | node-version: ${{ matrix.node-version }} 41 | - run: npm install 42 | - run: npm run build --if-present 43 | - run: npm run coverage 44 | env: 45 | CI: true 46 | -------------------------------------------------------------------------------- /test/fixture/ratatat.json: -------------------------------------------------------------------------------- 1 | { 2 | "@id": "http://dbpedia.org/resource/Ratatat", 3 | "@type" : 4 | [ 5 | "http://dbpedia.org/class/yago/Measure100033615" , 6 | "http://dbpedia.org/ontology/Band" , 7 | "http://dbpedia.org/ontology/Agent" , 8 | "http://dbpedia.org/class/yago/Digit113741022" , 9 | "http://dbpedia.org/ontology/Organisation" , 10 | "http://dbpedia.org/class/yago/AmericanHouseMusicGroups" , 11 | "http://dbpedia.org/class/yago/Onomatopoeia107104574" , 12 | "http://dbpedia.org/class/yago/Number113582013" , 13 | "http://dbpedia.org/class/yago/Couple113743605" , 14 | "http://dbpedia.org/class/yago/Two113743269" , 15 | "http://schema.org/Organization" , 16 | "http://dbpedia.org/class/yago/DefiniteQuantity113576101" , 17 | "http://dbpedia.org/class/yago/Integer113728499" , 18 | "http://dbpedia.org/class/yago/ExpressiveStyle107066659" , 19 | "http://schema.org/MusicGroup" , 20 | "http://dbpedia.org/class/yago/Group100031264" , 21 | "http://www.w3.org/2002/07/owl#Thing" , 22 | "http://dbpedia.org/class/yago/AmericanPost-rockGroups" , 23 | "http://dbpedia.org/class/yago/Communication100033020" , 24 | "http://dbpedia.org/class/yago/ElectronicMusicGroupsFromNewYork" , 25 | "http://dbpedia.org/class/yago/ElectronicMusicDuos" , 26 | "http://dbpedia.org/class/yago/Onomatopoeias" , 27 | "http://dbpedia.org/class/yago/Abstraction100002137" , 28 | "http://dbpedia.org/class/yago/Device107068844" , 29 | "http://dbpedia.org/class/yago/RhetoricalDevice107098193" 30 | ] 31 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "levelgraph-jsonld", 3 | "version": "2.0.0", 4 | "description": "The Object Document Mapper for LevelGraph based on JSON-LD", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/mocha --recursive test", 8 | "zuul": "zuul -- test", 9 | "zuul-local": "zuul --local -- test/*.js", 10 | "coverage": "rm -rf coverage; istanbul cover _mocha -- --recursive --reporter spec --bail", 11 | "publish-coverage": "cat coverage/lcov.info | coveralls" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/mcollina/levelgraph-jsonld.git" 16 | }, 17 | "bugs": { 18 | "url": "http://github.com/mcollina/levelgraph-jsonld/issues" 19 | }, 20 | "keywords": [ 21 | "object", 22 | "document", 23 | "mapper", 24 | "odm", 25 | "graph", 26 | "graph", 27 | "database", 28 | "json", 29 | "json-ld", 30 | "rdf", 31 | "linked data" 32 | ], 33 | "author": "Matteo Collina ", 34 | "license": "MIT", 35 | "dependencies": { 36 | "async": "^2.1.4", 37 | "jsonld": "0.4.9", 38 | "n3": "^0.10.0", 39 | "uuid": "^3.0.1" 40 | }, 41 | "peerDependencies": { 42 | "levelgraph": "^3.0.0" 43 | }, 44 | "devDependencies": { 45 | "browserify": "^14.0.0", 46 | "chai": "^4.1.2", 47 | "coveralls": "^3.0.0", 48 | "istanbul": "^0.4.2", 49 | "level-mem": "^6.0.1", 50 | "levelgraph": "^3.0.0", 51 | "lodash": "^4.17.4", 52 | "mocha": "^4.1.0", 53 | "uglify-js": "^3.0.0", 54 | "zuul": "^3.11.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/search_spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var helper = require('./helper'); 3 | 4 | describe('db.search', function() { 5 | 6 | var db, gang, manu; 7 | 8 | beforeEach(function() { 9 | db = helper.getDB(); 10 | manu = helper.getFixture('manu.json'); 11 | manu['@context']['knows'] = { "@type": "@id" }; 12 | manu['@context']['based_near'] = { "@type": "@id" }; 13 | manu['knows'] = [ 14 | { 15 | "@id": "https://my-profile.eu/people/deiu/card#me", 16 | "name": "Andrei Vlad Sambra", 17 | "based_near": "http://dbpedia.org/resource/Paris" 18 | }, { 19 | "@id": "http://melvincarvalho.com/#me", 20 | "name": "Melvin Carvalho", 21 | "based_near": "http://dbpedia.org/resource/Honolulu" 22 | }, { 23 | "@id": "http://bblfish.net/people/henry/card#me", 24 | "name": "Henry Story", 25 | "based_near": "http://dbpedia.org/resource/Paris" 26 | }, { 27 | "@id": "http://presbrey.mit.edu/foaf#presbrey", 28 | "name": "Joe Presbrey", 29 | "based_near": "http://dbpedia.org/resource/Cambridge" 30 | } 31 | ]; 32 | }); 33 | 34 | afterEach(function(done) { 35 | db.close(done); 36 | }); 37 | 38 | it('should find homies in Paris', function(done) { 39 | var paris = 'http://dbpedia.org/resource/Paris'; 40 | var parisians = [{ 41 | webid: 'http://bblfish.net/people/henry/card#me', 42 | name: '"Henry Story"' 43 | }, { 44 | webid: 'https://my-profile.eu/people/deiu/card#me', 45 | name: '"Andrei Vlad Sambra"' 46 | }]; 47 | 48 | db.jsonld.put(manu, function(){ 49 | db.search([{ 50 | subject: manu['@id'], 51 | predicate: 'http://xmlns.com/foaf/0.1/knows', 52 | object: db.v('webid') 53 | }, { 54 | subject: db.v('webid'), 55 | predicate: 'http://xmlns.com/foaf/0.1/based_near', 56 | object: paris 57 | }, { 58 | subject: db.v('webid'), 59 | predicate: 'http://xmlns.com/foaf/0.1/name', 60 | object: db.v('name') 61 | }], function(err, solution) { 62 | expect(solution).to.eql(parisians); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /test/iri_spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var helper = require('./helper'), 3 | _ = require('lodash'); 4 | 5 | describe('IRI', function() { 6 | 7 | var db, manu; 8 | 9 | beforeEach(function() { 10 | db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/get' } }); 11 | manu = helper.getFixture('manu.json'); 12 | }); 13 | 14 | afterEach(function(done) { 15 | db.close(done); 16 | }); 17 | 18 | it('keeps literals as literals', function(done) { 19 | var literal = 'http://dbpedia.org/resource/Honolulu'; 20 | manu['based_near'] = literal; 21 | 22 | db.jsonld.put(manu, function(){ 23 | db.jsonld.get(manu['@id'], { '@context': manu['@context'] }, function(err, obj) { 24 | expect(obj['based_near']).to.eql(literal); 25 | done(); 26 | }); 27 | }); 28 | }); 29 | 30 | it('keeps @id as IRI', function(done) { 31 | var id = { '@id': 'http://dbpedia.org/resource/Honolulu' }; 32 | manu['based_near'] = id; 33 | 34 | db.jsonld.put(manu, function(){ 35 | db.jsonld.get(manu['@id'], { '@context': manu['@context'] }, function(err, obj) { 36 | expect(obj['based_near']).to.eql(id); 37 | done(); 38 | }); 39 | }); 40 | }); 41 | 42 | it('keeps literal as IRI if defined as @id through @context', function(done) { 43 | var literal = 'http://dbpedia.org/resource/Honolulu'; 44 | manu['based_near'] = literal; 45 | 46 | var oldContext = _.cloneDeep(manu['@context']); 47 | manu['@context']['based_near'] = { '@type': '@id' }; 48 | 49 | var id = { '@id': 'http://dbpedia.org/resource/Honolulu' }; 50 | 51 | db.jsonld.put(manu, function(){ 52 | db.jsonld.get(manu['@id'], { '@context': oldContext }, function(err, obj) { 53 | expect(obj['based_near']).to.eql(id); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | 59 | it('keeps @id as IRI not only for http: scheme', function(done) { 60 | manu['@id'] = 'mailto:msporny@digitalbazar.com'; 61 | 62 | db.jsonld.put(manu, function(){ 63 | db.jsonld.get(manu['@id'], { '@context': manu['@context'] }, function(err, obj) { 64 | expect(manu).to.eql(manu); 65 | done(); 66 | }); 67 | }); 68 | }); 69 | }); -------------------------------------------------------------------------------- /test/languagetags_spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var helper = require('./helper'); 3 | 4 | describe('jsonld.put language tags', function() { 5 | 6 | var db, bbb; 7 | 8 | beforeEach(function() { 9 | db = helper.getDB(); 10 | bbb = helper.getFixture('bigbuckbunny.json'); 11 | }); 12 | 13 | it('default set in context', function(done) { 14 | bbb['@context']['@language'] = 'en'; 15 | db.jsonld.put(bbb, function() { 16 | db.get({ 17 | predicate: 'http://schema.org/name' 18 | }, function(err, triples) { 19 | expect(triples[0].object).to.equal('"Big Buck Bunny"@en'); 20 | done(); 21 | }); 22 | }); 23 | }); 24 | 25 | it('set for term in context', function(done) { 26 | bbb['@context']['name'] = { '@id': 'http://schema.org/name', '@language': 'en' }; 27 | db.jsonld.put(bbb, function() { 28 | db.get({ 29 | predicate: 'http://schema.org/name' 30 | }, function(err, triples) { 31 | expect(triples[0].object).to.equal('"Big Buck Bunny"@en'); 32 | done(); 33 | }); 34 | }); 35 | }); 36 | 37 | it('language map', function(done) { 38 | bbb['@context']['name'] = { '@id': 'http://schema.org/name', '@container': '@language' }; 39 | bbb.name = { 'en': 'Big Buck Bunny' }; 40 | db.jsonld.put(bbb, function() { 41 | db.get({ 42 | predicate: 'http://schema.org/name' 43 | }, function(err, triples) { 44 | expect(triples[0].object).to.equal('"Big Buck Bunny"@en'); 45 | done(); 46 | }); 47 | }); 48 | }); 49 | 50 | it('value object', function(done) { 51 | bbb.name = { '@language': 'en', '@value': 'Big Buck Bunny' }; 52 | db.jsonld.put(bbb, function() { 53 | db.get({ 54 | predicate: 'http://schema.org/name' 55 | }, function(err, triples) { 56 | expect(triples[0].object).to.equal('"Big Buck Bunny"@en'); 57 | done(); 58 | }); 59 | }); 60 | }); 61 | 62 | }); 63 | 64 | describe('jsonld.get language tags', function() { 65 | 66 | var db, bbb, triple; 67 | 68 | beforeEach(function() { 69 | db = helper.getDB(); 70 | bbb = helper.getFixture('bigbuckbunny.json'); 71 | }); 72 | 73 | it('recognizes', function(done) { 74 | delete bbb.name; 75 | var triple = { 76 | subject: bbb['@id'], 77 | predicate: 'http://schema.org/name', 78 | object: '"Big Buck Bunny"@en' 79 | }; 80 | 81 | db.jsonld.put(bbb, function() { 82 | db.put(triple, function() { 83 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 84 | expect(doc['name']['@language']).to.equal('en'); 85 | expect(doc['name']['@value']).to.equal('Big Buck Bunny'); 86 | done(); 87 | }); 88 | }); 89 | }); 90 | }); 91 | 92 | it('supports multiple language objects', function(done) { 93 | var en = { 94 | subject: bbb['@id'], 95 | predicate: 'http://schema.org/description', 96 | object: '"Big Buck Bunny"@en' 97 | }; 98 | 99 | var it = { 100 | subject: bbb['@id'], 101 | predicate: 'http://schema.org/description', 102 | object: '"Grande Coniglio Coniglietto"@it' 103 | }; 104 | bbb['@context'].description = { '@container': '@language' }; 105 | 106 | db.jsonld.put(bbb, function() { 107 | db.put([en, it], function() { 108 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 109 | expect(doc.description.en).to.equal('Big Buck Bunny'); 110 | expect(doc.description.it).to.equal('Grande Coniglio Coniglietto'); 111 | done(); 112 | }); 113 | }); 114 | }); 115 | }); 116 | 117 | }); 118 | -------------------------------------------------------------------------------- /test/cut_spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var helper = require('./helper'); 3 | 4 | describe('jsonld.cut', function() { 5 | 6 | var db, manu, tesla; 7 | 8 | beforeEach(function() { 9 | db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/' } }); 10 | manu = helper.getFixture('manu.json'); 11 | tesla = helper.getFixture('tesla.json'); 12 | }); 13 | 14 | afterEach(function(done) { 15 | db.close(done); 16 | }); 17 | 18 | it('should accept a done callback', function(done) { 19 | db.jsonld.cut(manu, done); 20 | }); 21 | 22 | it('should cut a basic object', function(done) { 23 | db.jsonld.put(manu, function() { 24 | db.jsonld.cut(manu, function() { 25 | db.get({}, function(err, triples) { 26 | // getting the full db 27 | expect(triples).to.be.empty; 28 | done(); 29 | }); 30 | }); 31 | }); 32 | }); 33 | 34 | it('should cut nothing', function(done) { 35 | db.jsonld.put(manu, function() { 36 | db.jsonld.cut({}, function() { 37 | db.get({}, function(err, triples) { 38 | // getting the full db 39 | expect(triples).to.have.length(2); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | }); 45 | 46 | it('should cut a complex object', function(done) { 47 | db.jsonld.put(tesla, function() { 48 | db.jsonld.cut(tesla, function() { 49 | db.get({}, function(err, triples) { 50 | // getting the full db 51 | expect(triples).to.have.length(0); 52 | done(); 53 | }); 54 | }); 55 | }); 56 | }); 57 | 58 | it('should del an iri with the cut option', function(done) { 59 | db.jsonld.put(manu, function() { 60 | db.jsonld.del(manu['@id'], function(err) { 61 | expect(err && err.message).to.equal("Passing an IRI to del is not supported anymore. Please pass a JSON-LD document.") 62 | db.get({}, function(err, triples) { 63 | // getting the full db 64 | expect(triples).to.have.length(2); 65 | done(); 66 | }); 67 | }); 68 | }); 69 | }); 70 | 71 | 72 | it('should del a single object leaving blank nodes', function(done) { 73 | db.jsonld.put(manu, function() { 74 | db.jsonld.put(tesla, function() { 75 | db.jsonld.del(tesla, function() { 76 | db.get({}, function(err, triples) { 77 | // getting the full db 78 | expect(triples).to.have.length(10); // 2 triples from Manu and 8 from tesla blanks. 79 | done(); 80 | }); 81 | }); 82 | }); 83 | }); 84 | }); 85 | 86 | it('should del a single object with the cut option leaving blank nodes', function(done) { 87 | // This should also be deprecated in favor of using a `cut` option or the `cut` function. 88 | db.jsonld.put(manu, function() { 89 | db.jsonld.put(tesla, function() { 90 | db.jsonld.del(tesla, {cut: true}, function() { 91 | db.get({}, function(err, triples) { 92 | // getting the full db 93 | expect(triples).to.have.length(2); // 2 triples from Manu. 94 | done(); 95 | }); 96 | }); 97 | }); 98 | }); 99 | }); 100 | 101 | it('should del a single object with no blank nodes completely', function(done) { 102 | var library = helper.getFixture('library_framed.json'); 103 | 104 | db.jsonld.put(manu, function() { 105 | db.jsonld.put(library, function() { 106 | db.jsonld.del(library, function() { 107 | db.get({}, function(err, triples) { 108 | // getting the full db 109 | expect(triples).to.have.length(2); 110 | done(); 111 | }); 112 | }); 113 | }); 114 | }); 115 | }); 116 | 117 | it('should del obj passed as stringified JSON', function(done) { 118 | var jld = {"@context": { "@vocab": "https://schema.org/"}, "name": "BigBlueHat"}; 119 | 120 | db.jsonld.put(JSON.stringify(jld), function() { 121 | db.jsonld.del(JSON.stringify(jld), function(err) { 122 | expect(err).to.not.exist; 123 | db.get({}, function(err, triples) { 124 | // getting the full db 125 | expect(triples).to.have.length(1); 126 | done(); 127 | }); 128 | }); 129 | }); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /test/get_spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var helper = require('./helper'); 3 | 4 | describe('jsonld.get', function() { 5 | 6 | var db, manu; 7 | 8 | beforeEach(function() { 9 | manu = helper.getFixture('manu.json'); 10 | db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/get' } }); 11 | }); 12 | 13 | afterEach(function(done) { 14 | db.close(done); 15 | }); 16 | 17 | it('should get no object', function(done) { 18 | db.jsonld.get('http://path/to/nowhere', { '@context': manu['@context'] }, function(err, obj) { 19 | expect(obj).to.be.null; 20 | done(); 21 | }); 22 | }); 23 | 24 | describe('with one object loaded', function() { 25 | beforeEach(function(done) { 26 | db.jsonld.put(manu, done); 27 | }); 28 | 29 | it('should load it', function(done) { 30 | db.jsonld.get(manu['@id'], { '@context': manu['@context'] }, function(err, obj) { 31 | expect(obj).to.eql(manu); 32 | done(); 33 | }); 34 | }); 35 | }); 36 | 37 | describe('with an object with blank nodes', function() { 38 | var tesla, annotation; 39 | 40 | beforeEach(function(done) { 41 | tesla = helper.getFixture('tesla.json'); 42 | annotation = helper.getFixture('annotation.json'); 43 | done(); 44 | }); 45 | 46 | it('should load it properly', function(done) { 47 | db.jsonld.put(tesla, function() { 48 | db.jsonld.get(tesla['@id'], { '@context': tesla['@context'] }, function(err, obj) { 49 | expect(obj).to.eql(tesla); 50 | done(); 51 | }); 52 | }); 53 | }); 54 | 55 | it('should load a context with mapped ids', function(done) { 56 | db.jsonld.put(annotation, function() { 57 | db.jsonld.get(annotation['id'], { '@context': annotation['@context'] }, function(err, obj) { 58 | expect(obj['body']).to.deep.have.members(annotation['body']); 59 | expect(obj['target']).to.deep.have.members(annotation['target']); 60 | done(); 61 | }); 62 | }); 63 | }); 64 | }); 65 | 66 | it('should support nested objects', function(done) { 67 | var nested = helper.getFixture('nested.json'); 68 | db.jsonld.put(nested, function(err, obj) { 69 | db.jsonld.get(obj['@id'], { '@context': obj['@context'] }, function(err, result) { 70 | delete result['knows'][0]['@id']; 71 | delete result['knows'][1]['@id']; 72 | expect(result).to.eql(nested); 73 | done(); 74 | }); 75 | }); 76 | }); 77 | 78 | it('with an object with multiple objects for same predicate' ,function(done){ 79 | var bbb = helper.getFixture('bigbuckbunny.json'); 80 | 81 | var act1 = { 82 | subject: bbb['@id'], 83 | predicate: 'http://schema.org/actor', 84 | object: 'http://example.net/act1' 85 | }; 86 | 87 | var act2 = { 88 | subject: bbb['@id'], 89 | predicate: 'http://schema.org/actor', 90 | object: 'http://example.net/act2' 91 | }; 92 | 93 | db.jsonld.put(bbb, function() { 94 | db.put([act1, act2], function() { 95 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 96 | expect(doc['actor']).to.be.an('array'); 97 | expect(doc['actor']).to.have.length(2); 98 | done(); 99 | }); 100 | }); 101 | }); 102 | }); 103 | 104 | describe('with an object with lists', function() { 105 | var listdoc, listcontext; 106 | 107 | beforeEach(function(done) { 108 | listdoc = helper.getFixture('list.json'); 109 | listcontext = helper.getFixture('listcontext.json'); 110 | done(); 111 | }); 112 | 113 | it('should load it properly', function(done) { 114 | db.jsonld.put(listdoc, function() { 115 | db.jsonld.get(listdoc['@id'], {}, function(err, obj) { 116 | expect(obj).to.eql(listdoc); 117 | done(); 118 | }); 119 | }); 120 | }); 121 | 122 | it('should load with a context with list containers', function(done) { 123 | db.jsonld.put(listcontext, function() { 124 | db.jsonld.get(listcontext['@id'], { '@context': listcontext['@context'] }, function(err, obj) { 125 | expect(obj).to.eql(listcontext); 126 | done(); 127 | }); 128 | }); 129 | }); 130 | }); 131 | 132 | it('should reconstitute a list into an array', function(done) { 133 | var listdoc = helper.getFixture('list.json'); 134 | 135 | db.jsonld.put(listdoc, function(err, obj) { 136 | db.jsonld.get(obj['@id'], {}, function (err, loaded) { 137 | expect(loaded['https://example.org/list']['@list']).to.have.length(2) 138 | expect(loaded['https://example.org/list']['@list'][0]['https://example.org/item']).to.equal("one") 139 | expect(loaded['https://example.org/list']['@list'][1]['https://example.org/item']).to.equal("two") 140 | done(); 141 | }); 142 | }); 143 | }); 144 | 145 | describe('with an object with an array for its ["@type"]', function() { 146 | var ratatat; 147 | 148 | beforeEach(function(done) { 149 | ratatat = helper.getFixture('ratatat.json'); 150 | db.jsonld.put(ratatat, done); 151 | }); 152 | 153 | it('should retrieve the object', function(done) { 154 | db.jsonld.get(ratatat['@id'], {}, function(err, obj) { 155 | expect(obj['@type']).to.have.members(ratatat['@type']); 156 | expect(obj['@id']).to.eql(ratatat['@id']); 157 | done(); 158 | }); 159 | }); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /test/del_spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var helper = require('./helper'); 3 | 4 | describe('jsonld.del', function() { 5 | 6 | var db, manu, tesla; 7 | 8 | beforeEach(function() { 9 | db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/' } }); 10 | manu = helper.getFixture('manu.json'); 11 | tesla = helper.getFixture('tesla.json'); 12 | }); 13 | 14 | afterEach(function(done) { 15 | db.close(done); 16 | }); 17 | 18 | it('should accept a done callback', function(done) { 19 | db.jsonld.put(manu, done); 20 | }); 21 | 22 | it('should del a basic object', function(done) { 23 | db.jsonld.put(manu, function() { 24 | db.jsonld.del(manu, function() { 25 | db.get({}, function(err, triples) { 26 | // getting the full db 27 | expect(triples).to.be.empty; 28 | done(); 29 | }); 30 | }); 31 | }); 32 | }); 33 | 34 | 35 | it('should del nothing', function(done) { 36 | db.jsonld.put(manu, function() { 37 | db.jsonld.del({}, function() { 38 | db.get({}, function(err, triples) { 39 | // getting the full db 40 | expect(triples).to.have.length(2); 41 | done(); 42 | }); 43 | }); 44 | }); 45 | }); 46 | 47 | it('should del a complex object', function(done) { 48 | db.jsonld.put(tesla, function() { 49 | db.jsonld.del(tesla, function() { 50 | db.get({}, function(err, triples) { 51 | // getting the full db 52 | expect(triples).to.have.length(8); 53 | done(); 54 | }); 55 | }); 56 | }); 57 | }); 58 | 59 | it('should del a complex object including blank nodes with the cut option set to true', function(done) { 60 | db.jsonld.put(tesla, function(err) { 61 | db.jsonld.del(tesla, { cut: true }, function() { 62 | db.get({}, function(err, triples) { 63 | // blank nodes are left. Consistent with https://www.w3.org/TR/ldpatch/#Delete-statement 64 | expect(triples).to.have.length(0); 65 | done(); 66 | }); 67 | }); 68 | }); 69 | }); 70 | 71 | it('should error when iri is passed', function(done) { 72 | db.jsonld.put(manu, function() { 73 | db.jsonld.del(manu['@id'], function(err) { 74 | expect(err && err.message).to.equal("Passing an IRI to del is not supported anymore. Please pass a JSON-LD document.") 75 | done(); 76 | }); 77 | }); 78 | }); 79 | 80 | it('should del an iri with the cut option', function(done) { 81 | db.jsonld.put(manu, function() { 82 | db.jsonld.del(manu['@id'], { cut: true }, function(err) { 83 | db.get({}, function(err, triples) { 84 | // getting the full db 85 | expect(triples).to.have.length(0); 86 | done(); 87 | }); 88 | }); 89 | }); 90 | }); 91 | 92 | 93 | it('should del a single object leaving blank nodes', function(done) { 94 | db.jsonld.put(manu, function() { 95 | db.jsonld.put(tesla, function() { 96 | db.jsonld.del(tesla, function() { 97 | db.get({}, function(err, triples) { 98 | // getting the full db 99 | expect(triples).to.have.length(10); // 2 triples from Manu and 8 from tesla blanks. 100 | done(); 101 | }); 102 | }); 103 | }); 104 | }); 105 | }); 106 | 107 | it('should del a single object with the cut option leaving blank nodes', function(done) { 108 | // This should also be deprecated in favor of using a `cut` option or the `cut` function. 109 | db.jsonld.put(manu, function() { 110 | db.jsonld.put(tesla, function() { 111 | db.jsonld.del(tesla, {cut: true}, function() { 112 | db.get({}, function(err, triples) { 113 | // getting the full db 114 | expect(triples).to.have.length(2); // 2 triples from Manu. 115 | done(); 116 | }); 117 | }); 118 | }); 119 | }); 120 | }); 121 | 122 | it('should del a single object with no blank nodes completely', function(done) { 123 | var library = helper.getFixture('library_framed.json'); 124 | 125 | db.jsonld.put(manu, function() { 126 | db.jsonld.put(library, function() { 127 | db.jsonld.del(library, function() { 128 | db.get({}, function(err, triples) { 129 | // getting the full db 130 | expect(triples).to.have.length(2); 131 | done(); 132 | }); 133 | }); 134 | }); 135 | }); 136 | }); 137 | 138 | it('should del obj passed as stringified JSON', function(done) { 139 | var jld = {"@context": { "@vocab": "https://schema.org/"}, "name": "BigBlueHat"}; 140 | 141 | db.jsonld.put(JSON.stringify(jld), function() { 142 | db.jsonld.del(JSON.stringify(jld), function(err) { 143 | expect(err).to.not.exist; 144 | db.get({}, function(err, triples) { 145 | // getting the full db 146 | expect(triples).to.have.length(1); 147 | done(); 148 | }); 149 | }); 150 | }); 151 | }); 152 | }); 153 | 154 | describe('jsonld.del with overwrite and cut set to true (backward compatibility)', function() { 155 | 156 | var db, manu, tesla; 157 | 158 | beforeEach(function() { 159 | db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/', overwrite: true, cut: true } }); 160 | manu = helper.getFixture('manu.json'); 161 | tesla = helper.getFixture('tesla.json'); 162 | }); 163 | 164 | afterEach(function(done) { 165 | db.close(done); 166 | }); 167 | 168 | it('should accept a done callback', function(done) { 169 | db.jsonld.put(manu, done); 170 | }); 171 | 172 | it('should del a basic object', function(done) { 173 | db.jsonld.put(manu, function() { 174 | db.jsonld.del(manu, function() { 175 | db.get({}, function(err, triples) { 176 | // getting the full db 177 | expect(triples).to.be.empty; 178 | done(); 179 | }); 180 | }); 181 | }); 182 | }); 183 | 184 | it('should del nothing', function(done) { 185 | db.jsonld.put(manu, function() { 186 | db.jsonld.del({}, function() { 187 | db.get({}, function(err, triples) { 188 | // getting the full db 189 | expect(triples).to.have.length(2); 190 | done(); 191 | }); 192 | }); 193 | }); 194 | }); 195 | 196 | it('should del nothing with a top blank node', function(done) { 197 | delete manu["@id"]; 198 | db.jsonld.put(manu, function() { 199 | db.jsonld.del({}, function() { 200 | db.get({}, function(err, triples) { 201 | // getting the full db 202 | expect(triples).to.have.length(2); 203 | done(); 204 | }); 205 | }); 206 | }); 207 | }); 208 | 209 | it('should del nothing with the recurse option set and a top blank node', function(done) { 210 | delete manu["@id"]; 211 | db.jsonld.put(manu, function() { 212 | db.jsonld.del({}, { recurse: true }, function() { 213 | db.get({}, function(err, triples) { 214 | // getting the full db 215 | expect(triples).to.have.length(2); 216 | done(); 217 | }); 218 | }); 219 | }); 220 | }); 221 | 222 | it('should del a complex object', function(done) { 223 | db.jsonld.put(tesla, function() { 224 | db.jsonld.del(tesla, function() { 225 | db.get({}, function(err, triples) { 226 | // getting the full db 227 | expect(triples).to.be.empty; 228 | done(); 229 | }); 230 | }); 231 | }); 232 | }); 233 | 234 | it('should del a complex object cutting blank nodes', function(done) { 235 | db.jsonld.put(tesla, function() { 236 | db.jsonld.del(tesla, function() { 237 | db.get({}, function(err, triples) { 238 | // blank nodes are cut. 239 | expect(triples).to.have.length(0); 240 | done(); 241 | }); 242 | }); 243 | }); 244 | }); 245 | 246 | it('should del a complex object with no blank nodes without recursing', function(done) { 247 | var library = helper.getFixture('library_framed.json'); 248 | 249 | db.jsonld.put(library, function() { 250 | db.jsonld.del(library, function() { 251 | db.get({}, function(err, triples) { 252 | // blank nodes are left. Consistent with https://www.w3.org/TR/ldpatch/#Delete-statement 253 | expect(triples).to.have.length(7); 254 | done(); 255 | }); 256 | }); 257 | }); 258 | }); 259 | 260 | it('should del a complex object with no blank nodes with the recurse option completely', function(done) { 261 | var library = helper.getFixture('library_framed.json'); 262 | 263 | db.jsonld.put(library, function() { 264 | db.jsonld.del(library, {recurse: true}, function() { 265 | db.get({}, function(err, triples) { 266 | // blank nodes are left. Consistent with https://www.w3.org/TR/ldpatch/#Delete-statement 267 | expect(triples).to.have.length(0); 268 | done(); 269 | }); 270 | }); 271 | }); 272 | }); 273 | 274 | it('should del an iri', function(done) { 275 | db.jsonld.put(manu, function() { 276 | db.jsonld.del(manu['@id'], function() { 277 | db.get({}, function(err, triples) { 278 | // getting the full db 279 | expect(triples).to.be.empty; 280 | done(); 281 | }); 282 | }); 283 | }); 284 | }); 285 | 286 | it('should del a single object leaving blank nodes', function(done) { 287 | db.jsonld.put(manu, function() { 288 | db.jsonld.put(tesla, function() { 289 | db.jsonld.del(tesla, function() { 290 | db.get({}, function(err, triples) { 291 | // getting the full db 292 | expect(triples).to.have.length(2); 293 | done(); 294 | }); 295 | }); 296 | }); 297 | }); 298 | }); 299 | 300 | it('should del a single object with cut set to false leaving blank nodes', function(done) { 301 | db.jsonld.put(manu, function() { 302 | db.jsonld.put(tesla, function() { 303 | db.jsonld.del(tesla, { cut: false }, function() { 304 | db.get({}, function(err, triples) { 305 | // getting the full db 306 | expect(triples).to.have.length(10); // 2 triples from Manu and 8 from tesla blanks. 307 | done(); 308 | }); 309 | }); 310 | }); 311 | }); 312 | }); 313 | 314 | it('should del a single object with no blank nodes completely with the recurse option', function(done) { 315 | var library = helper.getFixture('library_framed.json'); 316 | 317 | db.jsonld.put(manu, function(err) { 318 | db.jsonld.put(library, function(err) { 319 | db.jsonld.del(library, {recurse:true}, function(err) { 320 | db.get({}, function(err, triples) { 321 | // getting the full db 322 | expect(triples).to.have.length(2); 323 | done(); 324 | }); 325 | }); 326 | }); 327 | }); 328 | }); 329 | 330 | it('should del obj passed as stringified JSON', function(done) { 331 | var jld = {"@context": { "@vocab": "https://schema.org/"}, "name": "BigBlueHat"}; 332 | 333 | db.jsonld.put(JSON.stringify(jld), {preserve:true}, function() { 334 | db.jsonld.del(JSON.stringify(jld), {preserve:true}, function(err) { 335 | expect(err).to.not.exist; 336 | db.get({}, function(err, triples) { 337 | // getting the full db 338 | expect(triples).to.have.length(1); 339 | done(); 340 | }); 341 | }); 342 | }); 343 | }); 344 | 345 | }); 346 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LevelGraph-JSONLD 2 | =========== 3 | 4 | ![Logo](https://github.com/levelgraph/levelgraph/raw/master/logo.png) 5 | 6 | [![Build Status](https://travis-ci.org/levelgraph/levelgraph-jsonld.png)](https://travis-ci.org/levelgraph/levelgraph-jsonld) 7 | [![Coverage Status](https://coveralls.io/repos/levelgraph/levelgraph-jsonld/badge.png)](https://coveralls.io/r/levelgraph/levelgraph-jsonld) 8 | [![Dependency Status](https://david-dm.org/levelgraph/levelgraph-jsonld.png?theme=shields.io)](https://david-dm.org/levelgraph/levelgraph-jsonld) 9 | [![Sauce Labs Tests](https://saucelabs.com/browser-matrix/levelgraph-jsonld.svg)](https://saucelabs.com/u/levelgraph-jsonld) 10 | 11 | __LevelGraph-JSONLD__ is a plugin for 12 | [LevelGraph](http://github.com/levelgraph/levelgraph) that adds the 13 | ability to store, retrieve and delete JSON-LD objects. 14 | In fact, it is a full-blown Object-Document-Mapper (ODM) for 15 | __LevelGraph__. 16 | 17 | ## Install 18 | 19 | ### Node.js 20 | 21 | Adding support for JSON-LD to LevelGraph is easy: 22 | ```shell 23 | $ npm install level levelgraph levelgraph-jsonld --save 24 | ``` 25 | Then in your code: 26 | ```javascript 27 | var level = require('level'), 28 | yourDB = level('./yourdb'), 29 | levelgraph = require('levelgraph'), 30 | jsonld = require('levelgraph-jsonld'), 31 | db = jsonld(levelgraph(yourDB)); 32 | ``` 33 | 34 | At the moment it requires node v0.10.x, but the port to node v0.8.x 35 | should be straighforward. 36 | If you need it, just open a pull request. 37 | 38 | ## Browser 39 | 40 | If you use [browserify](http://browserify.org/) you can use this package 41 | in a browser just as in node.js. Please also take a look at [Browserify 42 | section in LevelGraph package](https://github.com/levelgraph/levelgraph#browserify) 43 | 44 | You can also use standalone browserified version from `./build` 45 | directory or use [bower](http://bower.io) 46 | 47 | ```shell 48 | $ bower install levelgraph-jsonld --save 49 | ``` 50 | It will also install its dependency levelgraph! Now you can simply: 51 | 52 | ```html 53 | 54 | 55 | 58 | ``` 59 | 60 | ## Usage 61 | 62 | We assume in following examples that you created database as explained 63 | above! 64 | ```js 65 | var level = require('level'), 66 | yourDB = level('./yourdb'), 67 | db = levelgraphJSONLD(levelgraph(yourDB)); 68 | ``` 69 | 70 | `'base'` can also be specified when you create the db: 71 | ```javascript 72 | var level = require('level'), 73 | yourDB = level('./yourdb'), 74 | levelgraph = require('levelgraph'), 75 | jsonld = require('levelgraph-jsonld'), 76 | opts = { base: 'http://matteocollina.com/base' }, 77 | db = jsonld(levelgraph(yourDB), opts); 78 | ``` 79 | 80 | > 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: 81 | > - overwrite all existing triples when using `put` 82 | > - delete all blank nodes recursively when using `del` (cf upcoming `cut` function) 83 | > This old api will be phased out. 84 | 85 | ### Put 86 | 87 | Please keep in mind that LevelGraph-JSONLD __doesn't store the original 88 | JSON-LD document but decomposes it into triples__! It stores literals 89 | double quoted with datatype if other then string. If you use plain 90 | LevelGraph methods, instead trying to match number `42` you need to try 91 | matching `"42"^^http://www.w3.org/2001/XMLSchema#integer` 92 | 93 | Storing triples from JSON-LD document is extremely easy: 94 | ```javascript 95 | var manu = { 96 | "@context": { 97 | "name": "http://xmlns.com/foaf/0.1/name", 98 | "homepage": { 99 | "@id": "http://xmlns.com/foaf/0.1/homepage", 100 | "@type": "@id" 101 | } 102 | }, 103 | "@id": "http://manu.sporny.org#person", 104 | "name": "Manu Sporny", 105 | "homepage": "http://manu.sporny.org/" 106 | }; 107 | 108 | db.jsonld.put(manu, function(err, obj) { 109 | // do something after the obj is inserted 110 | }); 111 | ``` 112 | 113 | if the top level objects have no `'@id'` key, one will be generated for 114 | each, using a UUID and the `'base'` argument, like so: 115 | ```javascript 116 | delete manu['@id']; 117 | db.jsonld.put(manu, { base: 'http://this/is/an/iri' }, function(err, obj) { 118 | // obj['@id'] will be something like 119 | // http://this/is/an/iri/b1e783b0-eda6-11e2-9540-d7575689f4bc 120 | }); 121 | ``` 122 | 123 | `'base'` can also be [specified when you create the db](#usage). 124 | 125 | __LevelGraph-JSONLD__ also support nested objects, like so: 126 | ```javascript 127 | var nested = { 128 | "@context": { 129 | "name": "http://xmlns.com/foaf/0.1/name", 130 | "knows": "http://xmlns.com/foaf/0.1/knows" 131 | }, 132 | "@id": "http://matteocollina.com", 133 | "name": "Matteo", 134 | "knows": [{ 135 | "name": "Daniele" 136 | }, { 137 | "name": "Lucio" 138 | }] 139 | }; 140 | 141 | db.jsonld.put(nested, function(err, obj) { 142 | // do something... 143 | }); 144 | ``` 145 | 146 | ### Get 147 | 148 | Retrieving a JSON-LD object from the store requires its `'@id'`: 149 | ```javascript 150 | db.jsonld.get(manu['@id'], { '@context': manu['@context'] }, function(err, obj) { 151 | // obj will be the very same of the manu object 152 | }); 153 | ``` 154 | 155 | The format of the loaded object is entirely specified by the 156 | `'@context'`, so have fun :). 157 | 158 | As with `'put'` it correctly support nested objects. If nested objects didn't originally include `'@id'` properties, now they will have them since `'put'` generates them by using UUID and formats 159 | them as *blank node identifiers*: 160 | ```javascript 161 | var nested = { 162 | "@context": { 163 | "name": "http://xmlns.com/foaf/0.1/name", 164 | "knows": "http://xmlns.com/foaf/0.1/knows" 165 | }, 166 | "@id": "http://matteocollina.com", 167 | "name": "Matteo", 168 | "knows": [{ 169 | "name": "Daniele" 170 | }, { 171 | "name": "Lucio" 172 | }] 173 | }; 174 | 175 | db.jsonld.put(nested, function(err, obj) { 176 | // obj will be 177 | // { 178 | // "@context": { 179 | // "name": "http://xmlns.com/foaf/0.1/name", 180 | // "knows": "http://xmlns.com/foaf/0.1/knows" 181 | // }, 182 | // "@id": "http://matteocollina.com", 183 | // "name": "Matteo", 184 | // "knows": [{ 185 | // "@id": "_:7053c150-5fea-11e3-a62e-adadc4e3df79", 186 | // "name": "Daniele" 187 | // }, { 188 | // "@id": "_:9d2bb59d-3baf-42ff-ba5d-9f8eab34ada5", 189 | // "name": "Lucio" 190 | // }] 191 | // } 192 | }); 193 | ``` 194 | 195 | ### Delete 196 | 197 | 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: 198 | ```javascript 199 | db.jsonld.del(manu, function(err) { 200 | // do something after it is deleted! 201 | }); 202 | ``` 203 | 204 | 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. 205 | 206 | > Note that since v1 `'del'` doesn't support passing an IRI anymore. 207 | 208 | ### Cut 209 | 210 | In order to delete the blank nodes object, you can just pass it's `'@id'` to the 211 | `'cut'` method: 212 | ```javascript 213 | db.jsonld.cut(manu['@id'], function(err) { 214 | // do something after it is cut! 215 | }); 216 | ``` 217 | 218 | 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. 219 | 220 | Using the `recurse` option you can follow all links and blank nodes (which might result in deleting more data than you expect) 221 | ```javascript 222 | db.jsonld.cut(manu['@id'], { recurse: true }, function(err) { 223 | // do something after it is cut! 224 | }); 225 | ``` 226 | 227 | ### Searching with LevelGraph 228 | 229 | __LevelGraph-JSONLD__ does not support searching for objects, because 230 | that problem is already solved by __LevelGraph__ itself. This example 231 | search finds friends living near Paris: 232 | ```javascript 233 | var manu = { 234 | "@context": { 235 | "@vocab": "http://xmlns.com/foaf/0.1/", 236 | "homepage": { "@type": "@id" }, 237 | "knows": { "@type": "@id" }, 238 | "based_near": { "@type": "@id" } 239 | }, 240 | "@id": "http://manu.sporny.org#person", 241 | "name": "Manu Sporny", 242 | "homepage": "http://manu.sporny.org/", 243 | "knows": [{ 244 | "@id": "https://my-profile.eu/people/deiu/card#me", 245 | "name": "Andrei Vlad Sambra", 246 | "based_near": "http://dbpedia.org/resource/Paris" 247 | }, { 248 | "@id": "http://melvincarvalho.com/#me", 249 | "name": "Melvin Carvalho", 250 | "based_near": "http://dbpedia.org/resource/Honolulu" 251 | }, { 252 | "@id": "http://bblfish.net/people/henry/card#me", 253 | "name": "Henry Story", 254 | "based_near": "http://dbpedia.org/resource/Paris" 255 | }, { 256 | "@id": "http://presbrey.mit.edu/foaf#presbrey", 257 | "name": "Joe Presbrey", 258 | "based_near": "http://dbpedia.org/resource/Cambridge" 259 | }] 260 | }; 261 | 262 | var paris = 'http://dbpedia.org/resource/Paris'; 263 | 264 | db.jsonld.put(manu, function(){ 265 | db.search([{ 266 | subject: manu['@id'], 267 | predicate: 'http://xmlns.com/foaf/0.1/knows', 268 | object: db.v('webid') 269 | }, { 270 | subject: db.v('webid'), 271 | predicate: 'http://xmlns.com/foaf/0.1/based_near', 272 | object: paris 273 | }, { 274 | subject: db.v('webid'), 275 | predicate: 'http://xmlns.com/foaf/0.1/name', 276 | object: db.v('name') 277 | } 278 | ], function(err, solution) { 279 | // solution contains 280 | // [{ 281 | // webid: 'http://bblfish.net/people/henry/card#me', 282 | // name: '"Henry Story"' 283 | // }, { 284 | // webid: 'https://my-profile.eu/people/deiu/card#me', 285 | // name: '"Andrei Vlad Sambra"' 286 | // }] 287 | }); 288 | }); 289 | ``` 290 | ## Changes 291 | 292 | [CHANGELOG.md](https://github.com/levelgraph/levelgraph-jsonld/blob/master/CHANGELOG.md) 293 | **including migration info for breaking changes** 294 | 295 | 296 | ## Contributing to LevelGraph-JSONLD 297 | 298 | * Check out the latest master to make sure the feature hasn't been 299 | implemented or the bug hasn't been fixed yet 300 | * Check out the issue tracker to make sure someone already hasn't 301 | requested it and/or contributed it 302 | * Fork the project 303 | * Start a feature/bugfix branch 304 | * Commit and push until you are happy with your contribution 305 | * Make sure to add tests for it. This is important so I don't break it 306 | in a future version unintentionally. 307 | * Please try not to mess with the Makefile and package.json. If you 308 | want to have your own version, or is otherwise necessary, that is 309 | fine, but please isolate to its own commit so I can cherry-pick around 310 | it. 311 | 312 | ## LICENSE - "MIT License" 313 | 314 | Copyright (c) 2013-2017 Matteo Collina and LevelGraph-JSONLD contributors 315 | 316 | Permission is hereby granted, free of charge, to any person 317 | obtaining a copy of this software and associated documentation 318 | files (the "Software"), to deal in the Software without 319 | restriction, including without limitation the rights to use, 320 | copy, modify, merge, publish, distribute, sublicense, and/or sell 321 | copies of the Software, and to permit persons to whom the 322 | Software is furnished to do so, subject to the following 323 | conditions: 324 | 325 | The above copyright notice and this permission notice shall be 326 | included in all copies or substantial portions of the Software. 327 | 328 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 329 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 330 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 331 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 332 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 333 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 334 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 335 | OTHER DEALINGS IN THE SOFTWARE. 336 | -------------------------------------------------------------------------------- /test/datatype_spec.js: -------------------------------------------------------------------------------- 1 | // http://json-ld.org/spec/latest/json-ld-api/#data-round-tripping 2 | var expect = require('chai').expect; 3 | var helper = require('./helper'); 4 | 5 | describe('jsonld.put data type', function() { 6 | 7 | var db, bbb; 8 | 9 | beforeEach(function() { 10 | db = helper.getDB(); 11 | bbb = helper.getFixture('bigbuckbunny.json'); 12 | }); 13 | 14 | describe('coerce', function() { 15 | 16 | it('preserves boolean true', function(done) { 17 | bbb.isFamilyFriendly = true; 18 | db.jsonld.put(bbb, function() { 19 | db.get({ 20 | predicate: 'http://schema.org/isFamilyFriendly' 21 | }, function(err, triples) { 22 | expect(triples[0].object).to.equal('"true"^^http://www.w3.org/2001/XMLSchema#boolean'); 23 | done(); 24 | }); 25 | }); 26 | }); 27 | 28 | it('preserves boolean false', function(done) { 29 | bbb.isFamilyFriendly = false; 30 | db.jsonld.put(bbb, function() { 31 | db.get({ 32 | predicate: 'http://schema.org/isFamilyFriendly' 33 | }, function(err, triples) { 34 | expect(triples[0].object).to.equal('"false"^^http://www.w3.org/2001/XMLSchema#boolean'); 35 | done(); 36 | }); 37 | }); 38 | }); 39 | 40 | it('preserves integer positive', function(done) { 41 | bbb.version = 2; 42 | db.jsonld.put(bbb, function() { 43 | db.get({ 44 | predicate: 'http://schema.org/version' 45 | }, function(err, triples) { 46 | expect(triples[0].object).to.equal('"2"^^http://www.w3.org/2001/XMLSchema#integer'); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | 52 | it('preserves integer negative', function(done) { 53 | bbb.version = -2; 54 | db.jsonld.put(bbb, function() { 55 | db.get({ 56 | predicate: 'http://schema.org/version' 57 | }, function(err, triples) { 58 | expect(triples[0].object).to.equal('"-2"^^http://www.w3.org/2001/XMLSchema#integer'); 59 | done(); 60 | }); 61 | }); 62 | }); 63 | 64 | it('preserves integer zero', function(done) { 65 | bbb.version = 0; 66 | db.jsonld.put(bbb, function() { 67 | db.get({ 68 | predicate: 'http://schema.org/version' 69 | }, function(err, triples) { 70 | expect(triples[0].object).to.equal('"0"^^http://www.w3.org/2001/XMLSchema#integer'); 71 | done(); 72 | }); 73 | }); 74 | }); 75 | 76 | it('preserves double positive', function(done) { 77 | bbb.version = 12.345; 78 | db.jsonld.put(bbb, function() { 79 | db.get({ 80 | predicate: 'http://schema.org/version' 81 | }, function(err, triples) { 82 | expect(triples[0].object).to.equal('"1.2345E1"^^http://www.w3.org/2001/XMLSchema#double'); 83 | done(); 84 | }); 85 | }); 86 | }); 87 | 88 | it('preserves double negative', function(done) { 89 | bbb.version = -12.345; 90 | db.jsonld.put(bbb, function() { 91 | db.get({ 92 | predicate: 'http://schema.org/version' 93 | }, function(err, triples) { 94 | expect(triples[0].object).to.equal('"-1.2345E1"^^http://www.w3.org/2001/XMLSchema#double'); 95 | done(); 96 | }); 97 | }); 98 | }); 99 | 100 | it('does not preserve string', function(done) { 101 | bbb.contentRating = 'MPAA PG-13'; 102 | db.jsonld.put(bbb, function() { 103 | db.get({ 104 | predicate: 'http://schema.org/contentRating' 105 | }, function(err, triples) { 106 | expect(triples[0].object).to.equal('"MPAA PG-13"'); 107 | done(); 108 | }); 109 | }); 110 | }); 111 | 112 | it('does preserve date type when defined in context', function(done) { 113 | var example = { 114 | "@context": { 115 | "@vocab": "http://schema.org/", 116 | "startTime": { "@type": "http://www.w3.org/2001/XMLSchema#dateTime" } 117 | }, 118 | "@id": "http://example.net/random-thing", 119 | "@type": "Action", 120 | "startTime": "2014-01-28T12:27:54" 121 | }; 122 | db.jsonld.put(example, function() { 123 | db.get({ 124 | predicate: 'http://schema.org/startTime' 125 | }, function(err, triples) { 126 | expect(triples[0].object).to.equal('"2014-01-28T12:27:54"^^http://www.w3.org/2001/XMLSchema#dateTime'); 127 | done(); 128 | }); 129 | 130 | }); 131 | }); 132 | 133 | it('does preserve date type when defined for given object', function(done) { 134 | var example = { 135 | "@context": { 136 | "@vocab": "http://schema.org/" 137 | }, 138 | "@id": "http://example.net/random-thing", 139 | "@type": "Action", 140 | "startTime": { 141 | "@value": "2014-01-28T12:27:54", 142 | "@type": "http://www.w3.org/2001/XMLSchema#dateTime" 143 | } 144 | }; 145 | db.jsonld.put(example, function() { 146 | db.get({ 147 | predicate: 'http://schema.org/startTime' 148 | }, function(err, triples) { 149 | expect(triples[0].object).to.equal('"2014-01-28T12:27:54"^^http://www.w3.org/2001/XMLSchema#dateTime'); 150 | done(); 151 | }); 152 | 153 | }); 154 | }); 155 | 156 | it('does preserve custom type when defined for given object', function(done) { 157 | var example = { 158 | "@context": { 159 | "@vocab": "http://example.com/" 160 | }, 161 | "@id": "http://example.com/123", 162 | "password": { 163 | "@value": "foo", 164 | "@type": "http://example.com/#password" 165 | } 166 | }; 167 | db.jsonld.put(example, function() { 168 | db.get({ 169 | predicate: 'http://example.com/password' 170 | }, function(err, triples) { 171 | expect(triples[0].object).to.equal('"foo"^^http://example.com/#password'); 172 | done(); 173 | }); 174 | 175 | }); 176 | }); 177 | 178 | }); 179 | }); 180 | describe('jsonld.get data type', function() { 181 | 182 | var db, bbb, triple; 183 | 184 | beforeEach(function() { 185 | db = helper.getDB(); 186 | bbb = helper.getFixture('bigbuckbunny.json'); 187 | triple = { 188 | subject: bbb['@id'], 189 | predicate: null, 190 | object: null 191 | }; 192 | }); 193 | 194 | describe('coerce', function() { 195 | 196 | it('preserves boolean true', function(done) { 197 | triple.predicate = 'http://schema.org/isFamilyFriendly'; 198 | triple.object = '"true"^^http://www.w3.org/2001/XMLSchema#boolean'; 199 | 200 | db.jsonld.put(bbb, function() { 201 | db.put(triple, function() { 202 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 203 | expect(doc['isFamilyFriendly']).to.be.true; 204 | done(); 205 | }); 206 | }); 207 | }); 208 | }); 209 | 210 | it('preserves boolean false', function(done) { 211 | triple.predicate = 'http://schema.org/isFamilyFriendly'; 212 | triple.object = '"false"^^http://www.w3.org/2001/XMLSchema#boolean'; 213 | 214 | db.jsonld.put(bbb, function() { 215 | db.put(triple, function() { 216 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 217 | expect(doc['isFamilyFriendly']).to.be.false; 218 | done(); 219 | }); 220 | }); 221 | }); 222 | }); 223 | 224 | it('preserves integer positive', function(done) { 225 | triple.predicate = 'http://schema.org/version'; 226 | triple.object = '"2"^^http://www.w3.org/2001/XMLSchema#integer'; 227 | 228 | db.jsonld.put(bbb, function() { 229 | db.put(triple, function() { 230 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 231 | expect(doc['version']).to.equal(2); 232 | done(); 233 | }); 234 | }); 235 | }); 236 | }); 237 | 238 | it('preserves integer negative', function(done) { 239 | triple.predicate = 'http://schema.org/version'; 240 | triple.object = '"-2"^^http://www.w3.org/2001/XMLSchema#integer'; 241 | 242 | db.jsonld.put(bbb, function() { 243 | db.put(triple, function() { 244 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 245 | expect(doc['version']).to.equal(-2); 246 | done(); 247 | }); 248 | }); 249 | }); 250 | }); 251 | 252 | it('preserves integer zero', function(done) { 253 | triple.predicate = 'http://schema.org/version'; 254 | triple.object = '"0"^^http://www.w3.org/2001/XMLSchema#integer'; 255 | 256 | db.jsonld.put(bbb, function() { 257 | db.put(triple, function() { 258 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 259 | expect(doc['version']).to.equal(0); 260 | done(); 261 | }); 262 | }); 263 | }); 264 | }); 265 | 266 | it('preserves double positive', function(done) { 267 | triple.predicate = 'http://schema.org/version'; 268 | triple.object = '"1.2345E1"^^http://www.w3.org/2001/XMLSchema#double'; 269 | 270 | db.jsonld.put(bbb, function() { 271 | db.put(triple, function() { 272 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 273 | expect(doc['version']).to.equal(12.345); 274 | done(); 275 | }); 276 | }); 277 | }); 278 | }); 279 | 280 | it('preserves double negative', function(done) { 281 | triple.predicate = 'http://schema.org/version'; 282 | triple.object = '"-1.2345E1"^^http://www.w3.org/2001/XMLSchema#double'; 283 | 284 | db.jsonld.put(bbb, function() { 285 | db.put(triple, function() { 286 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 287 | expect(doc['version']).to.equal(-12.345); 288 | done(); 289 | }); 290 | }); 291 | }); 292 | }); 293 | 294 | it('does not preserve string', function(done) { 295 | triple.predicate = 'http://schema.org/contentRating'; 296 | triple.object = '"MPAA PG-13"^^http://www.w3.org/2001/XMLSchema#string'; 297 | 298 | db.jsonld.put(bbb, function() { 299 | db.put(triple, function() { 300 | db.jsonld.get(bbb['@id'], bbb['@context'], function(err, doc) { 301 | expect(doc['contentRating']).to.equal('MPAA PG-13'); 302 | done(); 303 | }); 304 | }); 305 | }); 306 | }); 307 | 308 | it('does preserve date', function(done) { 309 | var example = { 310 | "@context": { 311 | "@vocab": "http://schema.org/", 312 | }, 313 | "@id": "http://example.net/random-thing", 314 | "@type": "Action" 315 | }; 316 | var triple = { 317 | subject: example['@id'], 318 | predicate: 'http://schema.org/startTime', 319 | object: '"2014-01-28T12:27:54"^^http://www.w3.org/2001/XMLSchema#dateTime' 320 | }; 321 | db.jsonld.put(example, function() { 322 | db.put(triple, function() { 323 | db.jsonld.get(example['@id'], example['@context'], function(err, doc) { 324 | expect(doc['startTime']['@type']).to.equal('http://www.w3.org/2001/XMLSchema#dateTime'); 325 | done(); 326 | }); 327 | }); 328 | }); 329 | }); 330 | 331 | //https://github.com/digitalbazaar/jsonld.js/issues/49#issuecomment-31614358 332 | it('compacts term if value matches type', function(done) { 333 | var example = { 334 | "@context": { 335 | "@vocab": "http://schema.org/", 336 | "startTime": { "@type": "http://www.w3.org/2001/XMLSchema#dateTime" } 337 | }, 338 | "@id": "http://example.net/random-thing", 339 | "@type": "Action" 340 | }; 341 | var triple = { 342 | subject: example['@id'], 343 | predicate: 'http://schema.org/startTime', 344 | object: '"2014-01-28T12:27:54"^^http://www.w3.org/2001/XMLSchema#dateTime' 345 | }; 346 | db.jsonld.put(example, function() { 347 | db.put(triple, function() { 348 | db.jsonld.get(example['@id'], example['@context'], function(err, doc) { 349 | expect(doc['startTime']).to.equal('2014-01-28T12:27:54'); 350 | done(); 351 | }); 352 | }); 353 | }); 354 | }); 355 | 356 | //https://github.com/digitalbazaar/jsonld.js/issues/49#issuecomment-31614358 357 | it('does not compact term when value does not have explicit type', function(done) { 358 | var example = { 359 | "@context": { 360 | "@vocab": "http://schema.org/", 361 | "startTime": { "@type": "http://www.w3.org/2001/XMLSchema#dateTime" } 362 | }, 363 | "@id": "http://example.net/random-thing", 364 | "@type": "Action" 365 | }; 366 | var triple = { 367 | subject: example['@id'], 368 | predicate: 'http://schema.org/startTime', 369 | object: '"2014-01-28T12:27:54"' 370 | }; 371 | db.jsonld.put(example, function() { 372 | db.put(triple, function() { 373 | db.jsonld.get(example['@id'], example['@context'], function(err, doc) { 374 | expect(doc['http://schema.org/startTime']).to.equal('2014-01-28T12:27:54'); 375 | done(); 376 | }); 377 | }); 378 | }); 379 | }); 380 | 381 | }); 382 | }); 383 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var jsonld = require('jsonld'), 2 | uuid = require('uuid'), 3 | RDFTYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 4 | RDFFIRST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 5 | RDFREST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 6 | RDFNIL = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 7 | RDFLANGSTRING = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString', 8 | XSDTYPE = 'http://www.w3.org/2001/XMLSchema#', 9 | async = require('async'), 10 | N3Util = require('n3/lib/N3Util'); // with browserify require('n3').Util would bundle more then needed! 11 | 12 | function levelgraphJSONLD(db, jsonldOpts) { 13 | 14 | if (db.jsonld) { 15 | return db; 16 | } 17 | 18 | var graphdb = Object.create(db); 19 | 20 | jsonldOpts = jsonldOpts || {}; 21 | jsonldOpts.base = jsonldOpts.base || ''; 22 | 23 | graphdb.jsonld = { 24 | options: jsonldOpts 25 | }; 26 | 27 | function doPut(obj, options, callback) { 28 | // holds accumulated blank node identifiers 29 | // example {"_:b0": "_:...uuid..."} 30 | var blanks = {}; 31 | // the inverse of the above object for speady look-up 32 | // example {"_:...uuid...": "_:b0"} 33 | var blanks_inverted = {}; 34 | 35 | jsonld.expand(obj, function(err, expanded) { 36 | if (err) { 37 | return callback && callback(err); 38 | } 39 | if (options.base) { 40 | if (expanded['@context']) { 41 | expanded['@context']['@base'] = options.base; 42 | } else { 43 | expanded['@context'] = { '@base' : options.base }; 44 | } 45 | } 46 | 47 | jsonld.toRDF(expanded, options, function(err, triples) { 48 | if (err || triples.length === 0) { 49 | return callback(err, null); 50 | } 51 | 52 | var stream = graphdb.putStream(); 53 | 54 | stream.on('error', callback); 55 | stream.on('close', function() { 56 | if (options.blank_ids) { 57 | // return rdf store scoped blank nodes 58 | 59 | var blank_keys = Object.keys(blanks); 60 | 61 | function framify(o) { 62 | if (Array.isArray(o)) { 63 | return o.map(framify) 64 | } else if (typeof o == 'object') { 65 | var clone_o = {} 66 | Object.keys(o).forEach(function(key) { 67 | if (Array.isArray(o[key]) && key != "@type") { 68 | clone_o[key] = framify(o[key][0]) 69 | } else if (!Array.isArray(o[key]) && typeof o[key] === "object") { 70 | clone_o[key] = framify(o[key]) 71 | } else { 72 | clone_o[key] = framify(o[key]) 73 | } 74 | }) 75 | return clone_o 76 | } else { 77 | return o 78 | } 79 | } 80 | 81 | var frame = framify(obj) 82 | 83 | if (blank_keys.length != 0) { 84 | jsonld.frame(obj, frame, function(err, framed) { 85 | if (err) { 86 | return callback(err, null); 87 | } 88 | var framed_string = JSON.stringify(framed); 89 | 90 | blank_keys.forEach(function(blank) { 91 | framed_string = framed_string.replace(blank,blanks[blank]) 92 | }) 93 | var ided = JSON.parse(framed_string); 94 | if (ided["@graph"].length == 1) { 95 | var clean_reframe = Object.assign({}, { "@context": ided["@context"]}, ided["@graph"][0]); 96 | return callback(null, clean_reframe); 97 | } else if (ided["@graph"].length > 1) { 98 | return callback(null, ided); 99 | } else { 100 | // Could not reframe the input, returning the original object 101 | return callback(null, obj); 102 | } 103 | }) 104 | } else { 105 | return callback(null, obj); 106 | } 107 | } else { 108 | return callback(null, obj); 109 | } 110 | }); 111 | 112 | triples['@default'].map(function(triple) { 113 | 114 | return ['subject', 'predicate', 'object'].reduce(function(acc, key) { 115 | var node = triple[key]; 116 | // generate UUID to identify blank nodes 117 | // uses type field set to 'blank node' by jsonld.js toRDF() 118 | if (node.type === 'blank node') { 119 | if (!(node.value in blanks) 120 | // avoid adding newly generated blank node identifiers 121 | && !(node.value in blanks_inverted)) { 122 | var bnode = '_:' + uuid.v1(); 123 | blanks[node.value] = bnode; 124 | blanks_inverted[bnode] = node.value; 125 | } 126 | if (!(node.value in blanks_inverted)) { 127 | // we've not seen this one before, so add it to the node 128 | node.value = blanks[node.value]; 129 | } 130 | } 131 | // preserve object data types using double quotation for literals 132 | // and don't keep data type for strings without defined language 133 | if(key === 'object' && triple.object.datatype){ 134 | if(triple.object.datatype.match(XSDTYPE)){ 135 | if(triple.object.datatype === 'http://www.w3.org/2001/XMLSchema#string'){ 136 | node.value = '"' + triple.object.value + '"'; 137 | } else { 138 | node.value = '"' + triple.object.value + '"^^' + triple.object.datatype; 139 | } 140 | } else if(triple.object.datatype.match(RDFLANGSTRING)){ 141 | node.value = '"' + triple.object.value + '"@' + triple.object.language; 142 | } else { 143 | node.value = '"' + triple.object.value + '"^^' + triple.object.datatype; 144 | } 145 | } 146 | acc[key] = node.value; 147 | return acc; 148 | }, {}); 149 | }).forEach(function(triple) { 150 | stream.write(triple); 151 | }); 152 | stream.end(); 153 | }); 154 | 155 | }); 156 | } 157 | 158 | function doDel(obj, options, callback) { 159 | var blanks = {}; 160 | jsonld.expand(obj, options, function(err, expanded) { 161 | if (err) { 162 | return callback && callback(err); 163 | } 164 | 165 | var stream = graphdb.delStream(); 166 | stream.on('close', callback); 167 | stream.on('error', callback); 168 | 169 | if (options.base) { 170 | if (expanded['@context']) { 171 | expanded['@context']['@base'] = options.base; 172 | } else { 173 | expanded['@context'] = { '@base' : options.base }; 174 | } 175 | } 176 | 177 | jsonld.toRDF(expanded, options, function(err, triples) { 178 | if (err || triples.length === 0) { 179 | return callback(err, null); 180 | } 181 | 182 | triples['@default'].map(function(triple) { 183 | 184 | return ['subject', 'predicate', 'object'].reduce(function(acc, key) { 185 | var node = triple[key]; 186 | // mark blank nodes to skip deletion as per https://www.w3.org/TR/ldpatch/#Delete-statement 187 | // uses type field set to 'blank node' by jsonld.js toRDF() 188 | if (node.type === 'blank node') { 189 | if (!blanks[node.value]) { 190 | blanks[node.value] = '_:'; 191 | } 192 | node.value = blanks[node.value]; 193 | } 194 | // preserve object data types using double quotation for literals 195 | // and don't keep data type for strings without defined language 196 | if(key === 'object' && triple.object.datatype){ 197 | if(triple.object.datatype.match(XSDTYPE)){ 198 | if(triple.object.datatype === 'http://www.w3.org/2001/XMLSchema#string'){ 199 | node.value = '"' + triple.object.value + '"'; 200 | } else { 201 | node.value = '"' + triple.object.value + '"^^' + triple.object.datatype; 202 | } 203 | } else if(triple.object.datatype.match(RDFLANGSTRING)){ 204 | node.value = '"' + triple.object.value + '"@' + triple.object.language; 205 | } else { 206 | node.value = '"' + triple.object.value + '"^^' + triple.object.datatype; 207 | } 208 | } 209 | acc[key] = node.value; 210 | return acc; 211 | }, {}); 212 | }).forEach(function(triple) { 213 | // Skip marked blank nodes. 214 | if (triple.subject.indexOf('_:') !== 0 && triple.object.indexOf('_:') !== 0) { 215 | stream.write(triple); 216 | } 217 | }); 218 | stream.end(); 219 | }); 220 | }) 221 | } 222 | 223 | function doCut(obj, options, callback) { 224 | var iri = obj; 225 | if (typeof obj !=='string') { 226 | iri = obj['@id']; 227 | } 228 | if (iri === undefined) { 229 | return callback && callback(null); 230 | } 231 | 232 | var stream = graphdb.delStream(); 233 | stream.on('close', callback); 234 | stream.on('error', callback); 235 | 236 | (function delAllTriples(iri, done) { 237 | graphdb.get({ subject: iri }, function(err, triples) { 238 | async.each(triples, function(triple, cb) { 239 | stream.write(triple); 240 | if (triple.object.indexOf('_:') === 0 || (options.recurse && N3Util.isIRI(triple.object))) { 241 | delAllTriples(triple.object, cb); 242 | } else { 243 | cb(); 244 | } 245 | }, done); 246 | }); 247 | })(iri, function(err) { 248 | if (err) { 249 | return callback(err); 250 | } 251 | stream.end(); 252 | }); 253 | } 254 | 255 | graphdb.jsonld.put = function(obj, options, callback) { 256 | 257 | if (typeof obj === 'string') { 258 | obj = JSON.parse(obj); 259 | } 260 | 261 | if (typeof options === 'function') { 262 | callback = options; 263 | options = {}; 264 | } 265 | 266 | options.base = options.base || this.options.base; 267 | options.overwrite = options.overwrite !== undefined ? options.overwrite : ( this.options.overwrite !== undefined ? this.options.overwrite : false ); 268 | 269 | if (!options.overwrite) { 270 | doPut(obj, options, callback); 271 | } else { 272 | graphdb.jsonld.del(obj, options, function(err) { 273 | if (err) { 274 | return callback && callback(err); 275 | } 276 | }); 277 | doPut(obj, options, callback); 278 | } 279 | }; 280 | 281 | graphdb.jsonld.del = function(obj, options, callback) { 282 | 283 | if (typeof options === 'function') { 284 | callback = options; 285 | options = {}; 286 | } 287 | 288 | options.cut = options.cut !== undefined ? options.cut : ( this.options.cut !== undefined ? this.options.cut : false ); 289 | options.recurse = options.recurse !== undefined ? options.recurse : ( this.options.recurse !== undefined ? this.options.recurse : false ); 290 | 291 | if (typeof obj === 'string') { 292 | try { 293 | obj = JSON.parse(obj); 294 | } catch (e) { 295 | if (N3Util.isIRI(obj) && !options.cut) { 296 | callback(new Error("Passing an IRI to del is not supported anymore. Please pass a JSON-LD document.")) 297 | } 298 | } 299 | } 300 | 301 | if (!options.cut) { 302 | doDel(obj, options, callback) 303 | } else { 304 | doCut(obj, options, callback) 305 | } 306 | }; 307 | 308 | 309 | graphdb.jsonld.cut = function(obj, options, callback) { 310 | 311 | if (typeof options === 'function') { 312 | callback = options; 313 | options = {}; 314 | } 315 | 316 | options.recurse = options.recurse || this.options.recurse || false; 317 | 318 | doCut(obj, options, callback); 319 | } 320 | 321 | // http://json-ld.org/spec/latest/json-ld-api/#data-round-tripping 322 | function getCoercedObject(object) { 323 | var TYPES = { 324 | PLAIN: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#PlainLiteral', 325 | BOOLEAN: XSDTYPE + 'boolean', 326 | INTEGER: XSDTYPE + 'integer', 327 | DOUBLE: XSDTYPE + 'double', 328 | STRING: XSDTYPE + 'string', 329 | }; 330 | var value = N3Util.getLiteralValue(object); 331 | var type = N3Util.getLiteralType(object); 332 | var coerced = {}; 333 | switch (type) { 334 | case TYPES.STRING: 335 | case TYPES.PLAIN: 336 | coerced['@value'] = value; 337 | break; 338 | case RDFLANGSTRING: 339 | coerced['@value'] = value; 340 | coerced['@language'] = N3Util.getLiteralLanguage(object); 341 | break; 342 | case TYPES.INTEGER: 343 | coerced['@value'] = parseInt(value, 10); 344 | break; 345 | case TYPES.DOUBLE: 346 | coerced['@value'] = parseFloat(value); 347 | break; 348 | case TYPES.BOOLEAN: 349 | if (value === 'true' || value === '1') { 350 | coerced['@value'] = true; 351 | } else if (value === 'false' || value === '0') { 352 | coerced['@value'] = false; 353 | } else { 354 | throw new Error('value not boolean!'); 355 | } 356 | break; 357 | default: 358 | coerced = { '@value': value, '@type': type }; 359 | } 360 | return coerced; 361 | } 362 | 363 | function fetchExpandedTriples(iri, memo, callback) { 364 | if (typeof memo === 'function') { 365 | callback = memo; 366 | memo = {}; 367 | } 368 | 369 | graphdb.get({ subject: iri }, function(err, triples) { 370 | if (err || triples.length === 0) { 371 | return callback(err, null); 372 | } 373 | 374 | async.reduce(triples, memo, function(acc, triple, cb) { 375 | var key; 376 | 377 | if (!acc[triple.subject] && !N3Util.isBlank(triple.subject)) { 378 | acc[triple.subject] = { '@id': triple.subject }; 379 | } else if (N3Util.isBlank(triple.subject) && !acc[triple.subject]) { 380 | acc[triple.subject] = {}; 381 | } 382 | if (triple.predicate === RDFTYPE) { 383 | if (acc[triple.subject]['@type']) { 384 | acc[triple.subject]['@type'].push(triple.object); 385 | } else { 386 | acc[triple.subject]['@type'] = [triple.object]; 387 | } 388 | return cb(null, acc); 389 | } else if (!N3Util.isBlank(triple.object)) { 390 | var object = {}; 391 | if (N3Util.isIRI(triple.object)) { 392 | object['@id'] = triple.object; 393 | } else if (N3Util.isLiteral(triple.object)) { 394 | object = getCoercedObject(triple.object); 395 | } 396 | if(object['@id']) { 397 | // expanding object iri 398 | fetchExpandedTriples(triple.object, function(err, expanded) { 399 | if (!acc[triple.subject][triple.predicate]) acc[triple.subject][triple.predicate] = []; 400 | if (expanded !== null) { 401 | acc[triple.subject][triple.predicate].push(expanded[triple.object]); 402 | } else { 403 | acc[triple.subject][triple.predicate].push(object); 404 | } 405 | return cb(err, acc); 406 | }); 407 | } 408 | else if (Array.isArray(acc[triple.subject][triple.predicate])){ 409 | acc[triple.subject][triple.predicate] = acc[triple.subject][triple.predicate].concat(object); 410 | return cb(err, acc); 411 | } else { 412 | acc[triple.subject][triple.predicate] = Array.isArray(object) ? object : [object]; 413 | return cb(err, acc); 414 | } 415 | } else { 416 | // deal with blanks 417 | fetchExpandedTriples(triple.object, function(err, expanded) { 418 | if (!acc[triple.subject][triple.predicate]) acc[triple.subject][triple.predicate] = []; 419 | if (expanded !== null) { 420 | acc[triple.subject][triple.predicate] = acc[triple.subject][triple.predicate].concat(expanded[triple.object]); 421 | } else { 422 | acc[triple.subject][triple.predicate] = acc[triple.subject][triple.predicate].concat(object); 423 | } 424 | return cb(err, acc); 425 | }); 426 | } 427 | }, function (err, nodes) { 428 | if (err) return callback(err) 429 | 430 | if (nodes[iri][RDFFIRST]) { 431 | 432 | var list = { '@list': gatherList(nodes[iri]) } 433 | nodes[iri] = list 434 | } 435 | 436 | callback(null, nodes) 437 | }) 438 | 439 | function gatherList(node) { 440 | var list = [] 441 | list = list.concat(node[RDFFIRST]) 442 | if (node[RDFREST]) { 443 | if (node[RDFREST][0] && node[RDFREST][0]['@list']) { 444 | list = list.concat(node[RDFREST][0]['@list']) 445 | } else if (!(node[RDFREST][0] && node[RDFREST][0]['@id'] && node[RDFREST][0]['@id'] == RDFNIL)) { 446 | list = list.concat(node[RDFREST]) 447 | } 448 | } 449 | return list 450 | } 451 | }); 452 | } 453 | 454 | graphdb.jsonld.get = function(iri, context, options, callback) { 455 | 456 | if (typeof options === 'function') { 457 | callback = options; 458 | options = {}; 459 | } 460 | 461 | fetchExpandedTriples(iri, function(err, expanded) { 462 | if (err || expanded === null) { 463 | return callback(err, expanded); 464 | } 465 | jsonld.compact(expanded[iri], context, options, callback); 466 | }); 467 | }; 468 | 469 | return graphdb; 470 | } 471 | 472 | module.exports = levelgraphJSONLD; 473 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | level = require('level-mem'), 3 | graph = require('levelgraph'), 4 | jsonld = require('../'), 5 | _ = require('lodash'); 6 | 7 | var getDB = function(opts) { 8 | opts = _.assign({ jsonld: {} }, opts); 9 | return jsonld(graph(level()), opts.jsonld); 10 | }; 11 | 12 | var getFixture = function(name) { 13 | var fixtures = { 14 | "bigbuckbunny.json": { 15 | "@context": { 16 | "@vocab": "http://schema.org/" 17 | }, 18 | "@id": "http://www.bigbuckbunny.org/", 19 | "name": "Big Buck Bunny" 20 | }, 21 | "john.json": { 22 | "@context": "http://json-ld.org/contexts/person.jsonld", 23 | "@id": "http://dbpedia.org/resource/John_Lennon", 24 | "name": "John Lennon", 25 | "born": "1940-10-09", 26 | "spouse": "http://dbpedia.org/resource/Cynthia_Lennon" 27 | }, 28 | "manu.json": { 29 | "@context": { 30 | "@vocab": "http://xmlns.com/foaf/0.1/", 31 | "homepage": { "@type": "@id" } 32 | }, 33 | "@id": "http://manu.sporny.org#person", 34 | "name": "Manu Sporny", 35 | "homepage": "http://manu.sporny.org/" 36 | }, 37 | "nested.json": { 38 | "@context": { 39 | "name": "http://xmlns.com/foaf/0.1/name", 40 | "knows": "http://xmlns.com/foaf/0.1/knows" 41 | }, 42 | "@id": "http://matteocollina.com", 43 | "name": "matteo", 44 | "knows": [{ 45 | "name": "daniele" 46 | }, { 47 | "name": "lucio" 48 | }] 49 | }, 50 | "person.json": { 51 | "@context": { 52 | "Person": "http://xmlns.com/foaf/0.1/Person", 53 | "xsd": "http://www.w3.org/2001/XMLSchema#", 54 | "name": "http://xmlns.com/foaf/0.1/name", 55 | "nickname": "http://xmlns.com/foaf/0.1/nick", 56 | "affiliation": "http://schema.org/affiliation", 57 | "depiction": { 58 | "@id": "http://xmlns.com/foaf/0.1/depiction", 59 | "@type": "@id" 60 | }, 61 | "image": { 62 | "@id": "http://xmlns.com/foaf/0.1/img", 63 | "@type": "@id" 64 | }, 65 | "born": { 66 | "@id": "http://schema.org/birthDate", 67 | "@type": "xsd:dateTime" 68 | }, 69 | "child": { 70 | "@id": "http://schema.org/children", 71 | "@type": "@id" 72 | }, 73 | "colleague": { 74 | "@id": "http://schema.org/colleagues", 75 | "@type": "@id" 76 | }, 77 | "knows": { 78 | "@id": "http://xmlns.com/foaf/0.1/knows", 79 | "@type": "@id" 80 | }, 81 | "died": { 82 | "@id": "http://schema.org/deathDate", 83 | "@type": "xsd:dateTime" 84 | }, 85 | "email": { 86 | "@id": "http://xmlns.com/foaf/0.1/mbox", 87 | "@type": "@id" 88 | }, 89 | "familyName": "http://xmlns.com/foaf/0.1/familyName", 90 | "givenName": "http://xmlns.com/foaf/0.1/givenName", 91 | "gender": "http://schema.org/gender", 92 | "homepage": { 93 | "@id": "http://xmlns.com/foaf/0.1/homepage", 94 | "@type": "@id" 95 | }, 96 | "honorificPrefix": "http://schema.org/honorificPrefix", 97 | "honorificSuffix": "http://schema.org/honorificSuffix", 98 | "jobTitle": "http://xmlns.com/foaf/0.1/title", 99 | "nationality": "http://schema.org/nationality", 100 | "parent": { 101 | "@id": "http://schema.org/parent", 102 | "@type": "@id" 103 | }, 104 | "sibling": { 105 | "@id": "http://schema.org/sibling", 106 | "@type": "@id" 107 | }, 108 | "spouse": { 109 | "@id": "http://schema.org/spouse", 110 | "@type": "@id" 111 | }, 112 | "telephone": "http://schema.org/telephone", 113 | "Address": "http://www.w3.org/2006/vcard/ns#Address", 114 | "address": "http://www.w3.org/2006/vcard/ns#address", 115 | "street": "http://www.w3.org/2006/vcard/ns#street-address", 116 | "locality": "http://www.w3.org/2006/vcard/ns#locality", 117 | "region": "http://www.w3.org/2006/vcard/ns#region", 118 | "country": "http://www.w3.org/2006/vcard/ns#country", 119 | "postalCode": "http://www.w3.org/2006/vcard/ns#postal-code" 120 | } 121 | }, 122 | "tesla.json": { 123 | "@context": { 124 | "gr": "http://purl.org/goodrelations/v1#", 125 | "pto": "http://www.productontology.org/id/", 126 | "foaf": "http://xmlns.com/foaf/0.1/", 127 | "xsd": "http://www.w3.org/2001/XMLSchema#", 128 | "foaf:page": { 129 | "@type": "@id" 130 | }, 131 | "gr:acceptedPaymentMethods": { 132 | "@type": "@id" 133 | }, 134 | "gr:hasBusinessFunction": { 135 | "@type": "@id" 136 | } 137 | }, 138 | "@id": "http://example.org/cars/for-sale#tesla", 139 | "@type": "gr:Offering", 140 | "gr:name": "Used Tesla Roadster", 141 | "gr:description": "Need to sell fast and furiously", 142 | "gr:hasBusinessFunction": "gr:Sell", 143 | "gr:acceptedPaymentMethods": "gr:Cash", 144 | "gr:hasPriceSpecification": { 145 | "gr:hasCurrencyValue": "85000", 146 | "gr:hasCurrency": "USD" 147 | }, 148 | "gr:includes": { 149 | "@type": ["gr:Individual", "pto:Vehicle"], 150 | "gr:name": "Tesla Roadster", 151 | "foaf:page": "http://www.teslamotors.com/roadster" 152 | } 153 | }, 154 | "ratatat.json": { 155 | "@id": "http://dbpedia.org/resource/Ratatat", 156 | "@type" : [ 157 | "http://dbpedia.org/class/yago/Measure100033615" , 158 | "http://dbpedia.org/ontology/Band" , 159 | "http://dbpedia.org/ontology/Agent" , 160 | "http://dbpedia.org/class/yago/Digit113741022" , 161 | "http://dbpedia.org/ontology/Organisation" , 162 | "http://dbpedia.org/class/yago/AmericanHouseMusicGroups" , 163 | "http://dbpedia.org/class/yago/Onomatopoeia107104574" , 164 | "http://dbpedia.org/class/yago/Number113582013" , 165 | "http://dbpedia.org/class/yago/Couple113743605" , 166 | "http://dbpedia.org/class/yago/Two113743269" , 167 | "http://schema.org/Organization" , 168 | "http://dbpedia.org/class/yago/DefiniteQuantity113576101" , 169 | "http://dbpedia.org/class/yago/Integer113728499" , 170 | "http://dbpedia.org/class/yago/ExpressiveStyle107066659" , 171 | "http://schema.org/MusicGroup" , 172 | "http://dbpedia.org/class/yago/Group100031264" , 173 | "http://www.w3.org/2002/07/owl#Thing" , 174 | "http://dbpedia.org/class/yago/AmericanPost-rockGroups" , 175 | "http://dbpedia.org/class/yago/Communication100033020" , 176 | "http://dbpedia.org/class/yago/ElectronicMusicGroupsFromNewYork" , 177 | "http://dbpedia.org/class/yago/ElectronicMusicDuos" , 178 | "http://dbpedia.org/class/yago/Onomatopoeias" , 179 | "http://dbpedia.org/class/yago/Abstraction100002137" , 180 | "http://dbpedia.org/class/yago/Device107068844" , 181 | "http://dbpedia.org/class/yago/RhetoricalDevice107098193" 182 | ] 183 | }, 184 | "chapter.json": { 185 | "@context": { 186 | "dc": "http://purl.org/dc/elements/1.1/", 187 | "ex": "http://example.org/vocab#" 188 | }, 189 | "@id": "http://example.org/library/the-republic#introduction", 190 | "@type": "ex:Chapter", 191 | "dc:title": "The Introduction" 192 | }, 193 | "chapterdescription.json": { 194 | "@context": { 195 | "dc": "http://purl.org/dc/elements/1.1/", 196 | "ex": "http://example.org/vocab#" 197 | }, 198 | "@id": "http://example.org/library/the-republic#introduction", 199 | "dc:description": "An introductory chapter on The Republic." 200 | }, 201 | "library_framed.json": { 202 | "@context": { 203 | "dc": "http://purl.org/dc/elements/1.1/", 204 | "ex": "http://example.org/vocab#", 205 | "xsd": "http://www.w3.org/2001/XMLSchema#", 206 | "ex:contains": { 207 | "@type": "@id" 208 | } 209 | }, 210 | "@id": "http://example.org/library", 211 | "@type": "ex:Library", 212 | "ex:contains": { 213 | "@id": "http://example.org/library/the-republic", 214 | "@type": "ex:Book", 215 | "ex:contains": { 216 | "@id": "http://example.org/library/the-republic#introduction", 217 | "@type": "ex:Chapter", 218 | "dc:description": "An introductory chapter on The Republic.", 219 | "dc:title": "The Introduction" 220 | }, 221 | "dc:creator": "Plato", 222 | "dc:title": "The Republic" 223 | } 224 | }, 225 | "library.json": { 226 | "@context": { 227 | "dc": "http://purl.org/dc/elements/1.1/", 228 | "ex": "http://example.org/vocab#", 229 | "xsd": "http://www.w3.org/2001/XMLSchema#", 230 | "ex:contains": { 231 | "@type": "@id" 232 | } 233 | }, 234 | "@graph": [ 235 | { 236 | "@id": "http://example.org/library", 237 | "@type": "ex:Library", 238 | "ex:contains": "http://example.org/library/the-republic" 239 | }, 240 | { 241 | "@id": "http://example.org/library/the-republic", 242 | "@type": "ex:Book", 243 | "dc:creator": "Plato", 244 | "dc:title": "The Republic", 245 | "ex:contains": "http://example.org/library/the-republic#introduction" 246 | }, 247 | { 248 | "@id": "http://example.org/library/the-republic#introduction", 249 | "@type": "ex:Chapter", 250 | "dc:description": "An introductory chapter on The Republic.", 251 | "dc:title": "The Introduction" 252 | } 253 | ] 254 | }, 255 | "mapped_id.json": { 256 | "@context": { 257 | "id": "@id", 258 | "@vocab": "http://xmlns.com/foaf/0.1/" 259 | }, 260 | "id": "http://bigbluehat.com/#", 261 | "name": "BigBlueHat", 262 | "knows": [ 263 | { 264 | "id": "http://manu.sporny.org#person", 265 | "name": "Manu Sporny", 266 | "homepage": "http://manu.sporny.org/" 267 | } 268 | ] 269 | }, 270 | "annotation_remote.json": { 271 | "@context": "http://www.w3.org/ns/anno.jsonld", 272 | "id": "http://example.org/anno9", 273 | "type": "Annotation", 274 | "body": [ 275 | "http://example.org/description1", 276 | { 277 | "type": "TextualBody", 278 | "value": "tag1" 279 | } 280 | ], 281 | "target": [ 282 | "http://example.org/image1", 283 | "http://example.org/image2", 284 | { 285 | "source": "http://example.org/" 286 | } 287 | ] 288 | }, 289 | "list.json": { 290 | "@id": "https://example.org/doc", 291 | "https://example.org/list": { "@list": [ { "https://example.org/item": "one" }, { "https://example.org/item": "two" } ] } 292 | }, 293 | "listcontext.json": { 294 | "@id": "https://example.org/doc", 295 | "@context": { 296 | "https://example.org/list": { "@container": "@list" } 297 | }, 298 | "https://example.org/list": [ { "https://example.org/item": "one" }, { "https://example.org/item": "two" } ] 299 | }, 300 | "annotation.json": { 301 | "@context": { 302 | "oa": "http://www.w3.org/ns/oa#", 303 | "dc": "http://purl.org/dc/elements/1.1/", 304 | "dcterms": "http://purl.org/dc/terms/", 305 | "dctypes": "http://purl.org/dc/dcmitype/", 306 | "foaf": "http://xmlns.com/foaf/0.1/", 307 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 308 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 309 | "skos": "http://www.w3.org/2004/02/skos/core#", 310 | "xsd": "http://www.w3.org/2001/XMLSchema#", 311 | "iana": "http://www.iana.org/assignments/relation/", 312 | "owl": "http://www.w3.org/2002/07/owl#", 313 | "as": "http://www.w3.org/ns/activitystreams#", 314 | "schema": "http://schema.org/", 315 | 316 | "id": {"@type": "@id", "@id": "@id"}, 317 | "type": {"@type": "@id", "@id": "@type"}, 318 | 319 | "Annotation": "oa:Annotation", 320 | "Dataset": "dctypes:Dataset", 321 | "Image": "dctypes:StillImage", 322 | "Video": "dctypes:MovingImage", 323 | "Audio": "dctypes:Sound", 324 | "Text": "dctypes:Text", 325 | "TextualBody": "oa:TextualBody", 326 | "ResourceSelection": "oa:ResourceSelection", 327 | "SpecificResource": "oa:SpecificResource", 328 | "FragmentSelector": "oa:FragmentSelector", 329 | "CssSelector": "oa:CssSelector", 330 | "XPathSelector": "oa:XPathSelector", 331 | "TextQuoteSelector": "oa:TextQuoteSelector", 332 | "TextPositionSelector": "oa:TextPositionSelector", 333 | "DataPositionSelector": "oa:DataPositionSelector", 334 | "SvgSelector": "oa:SvgSelector", 335 | "RangeSelector": "oa:RangeSelector", 336 | "TimeState": "oa:TimeState", 337 | "HttpRequestState": "oa:HttpRequestState", 338 | "CssStylesheet": "oa:CssStyle", 339 | "Choice": "oa:Choice", 340 | "Person": "foaf:Person", 341 | "Software": "as:Application", 342 | "Organization": "foaf:Organization", 343 | "AnnotationCollection": "as:OrderedCollection", 344 | "AnnotationPage": "as:OrderedCollectionPage", 345 | "Audience": "schema:Audience", 346 | 347 | "Motivation": "oa:Motivation", 348 | "bookmarking": "oa:bookmarking", 349 | "classifying": "oa:classifying", 350 | "commenting": "oa:commenting", 351 | "describing": "oa:describing", 352 | "editing": "oa:editing", 353 | "highlighting": "oa:highlighting", 354 | "identifying": "oa:identifying", 355 | "linking": "oa:linking", 356 | "moderating": "oa:moderating", 357 | "questioning": "oa:questioning", 358 | "replying": "oa:replying", 359 | "reviewing": "oa:reviewing", 360 | "tagging": "oa:tagging", 361 | 362 | "auto": "oa:autoDirection", 363 | "ltr": "oa:ltrDirection", 364 | "rtl": "oa:rtlDirection", 365 | 366 | "body": {"@type": "@id", "@id": "oa:hasBody"}, 367 | "target": {"@type": "@id", "@id": "oa:hasTarget"}, 368 | "source": {"@type": "@id", "@id": "oa:hasSource"}, 369 | "selector": {"@type": "@id", "@id": "oa:hasSelector"}, 370 | "state": {"@type": "@id", "@id": "oa:hasState"}, 371 | "scope": {"@type": "@id", "@id": "oa:hasScope"}, 372 | "refinedBy": {"@type": "@id", "@id": "oa:refinedBy"}, 373 | "startSelector": {"@type": "@id", "@id": "oa:hasStartSelector"}, 374 | "endSelector": {"@type": "@id", "@id": "oa:hasEndSelector"}, 375 | "renderedVia": {"@type": "@id", "@id": "oa:renderedVia"}, 376 | "creator": {"@type": "@id", "@id": "dcterms:creator"}, 377 | "generator": {"@type": "@id", "@id": "as:generator"}, 378 | "rights": {"@type": "@id", "@id": "dcterms:rights"}, 379 | "homepage": {"@type": "@id", "@id": "foaf:homepage"}, 380 | "via": {"@type": "@id", "@id": "oa:via"}, 381 | "canonical": {"@type": "@id", "@id": "oa:canonical"}, 382 | "stylesheet": {"@type": "@id", "@id": "oa:styledBy"}, 383 | "cached": {"@type": "@id", "@id": "oa:cachedSource"}, 384 | "conformsTo": {"@type": "@id", "@id": "dcterms:conformsTo"}, 385 | "items": {"@type": "@id", "@id": "as:items", "@container": "@list"}, 386 | "partOf": {"@type": "@id", "@id": "as:partOf"}, 387 | "first": {"@type": "@id", "@id": "as:first"}, 388 | "last": {"@type": "@id", "@id": "as:last"}, 389 | "next": {"@type": "@id", "@id": "as:next"}, 390 | "prev": {"@type": "@id", "@id": "as:prev"}, 391 | "audience": {"@type": "@id", "@id": "schema:audience"}, 392 | "motivation": {"@type": "@vocab", "@id": "oa:motivatedBy"}, 393 | "purpose": {"@type": "@vocab", "@id": "oa:hasPurpose"}, 394 | "textDirection": {"@type": "@vocab", "@id": "oa:textDirection"}, 395 | 396 | "accessibility": "schema:accessibilityFeature", 397 | "bodyValue": "oa:bodyValue", 398 | "format": "dc:format", 399 | "language": "dc:language", 400 | "processingLanguage": "oa:processingLanguage", 401 | "value": "rdf:value", 402 | "exact": "oa:exact", 403 | "prefix": "oa:prefix", 404 | "suffix": "oa:suffix", 405 | "styleClass": "oa:styleClass", 406 | "name": "foaf:name", 407 | "email": "foaf:mbox", 408 | "email_sha1": "foaf:mbox_sha1sum", 409 | "nickname": "foaf:nick", 410 | "label": "rdfs:label", 411 | 412 | "created": {"@id": "dcterms:created", "@type": "xsd:dateTime"}, 413 | "modified": {"@id": "dcterms:modified", "@type": "xsd:dateTime"}, 414 | "generated": {"@id": "dcterms:issued", "@type": "xsd:dateTime"}, 415 | "sourceDate": {"@id": "oa:sourceDate", "@type": "xsd:dateTime"}, 416 | "sourceDateStart": {"@id": "oa:sourceDateStart", "@type": "xsd:dateTime"}, 417 | "sourceDateEnd": {"@id": "oa:sourceDateEnd", "@type": "xsd:dateTime"}, 418 | 419 | "start": {"@id": "oa:start", "@type": "xsd:nonNegativeInteger"}, 420 | "end": {"@id": "oa:end", "@type": "xsd:nonNegativeInteger"}, 421 | "total": {"@id": "as:totalItems", "@type": "xsd:nonNegativeInteger"}, 422 | "startIndex": {"@id": "as:startIndex", "@type": "xsd:nonNegativeInteger"} 423 | }, 424 | "id": "http://example.org/anno9", 425 | "type": "Annotation", 426 | "body": [ 427 | "http://example.org/description1", 428 | { 429 | "type": "TextualBody", 430 | "value": "tag1" 431 | } 432 | ], 433 | "target": [ 434 | "http://example.org/image1", 435 | "http://example.org/image2", 436 | { 437 | "source": "http://example.org/" 438 | } 439 | ] 440 | } 441 | }; 442 | return fixtures[name]; 443 | }; 444 | 445 | 446 | module.exports.getFixture = getFixture; 447 | module.exports.getDB = getDB; 448 | -------------------------------------------------------------------------------- /test/put_spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var helper = require('./helper'); 3 | 4 | describe('jsonld.put', function() { 5 | 6 | var db, manu; 7 | 8 | beforeEach(function() { 9 | db = helper.getDB(); 10 | manu = helper.getFixture('manu.json'); 11 | }); 12 | 13 | afterEach(function(done) { 14 | db.close(done); 15 | }); 16 | 17 | it('should accept a done callback', function(done) { 18 | db.jsonld.put(manu, done); 19 | }); 20 | 21 | it('should store a triple', function(done) { 22 | db.jsonld.put(manu, function() { 23 | db.get({ 24 | subject: 'http://manu.sporny.org#person', 25 | predicate: 'http://xmlns.com/foaf/0.1/name' 26 | }, function(err, triples) { 27 | expect(triples).to.have.length(1); 28 | done(); 29 | }); 30 | }); 31 | }); 32 | 33 | it('should store two triples', function(done) { 34 | db.jsonld.put(manu, function() { 35 | db.get({ 36 | subject: 'http://manu.sporny.org#person' 37 | }, function(err, triples) { 38 | expect(triples).to.have.length(2); 39 | done(); 40 | }); 41 | }); 42 | }); 43 | 44 | it('should store a JSON file', function(done) { 45 | db.jsonld.put(JSON.stringify(manu), function() { 46 | db.get({ 47 | subject: 'http://manu.sporny.org#person' 48 | }, function(err, triples) { 49 | expect(triples).to.have.length(2); 50 | done(); 51 | }); 52 | }); 53 | }); 54 | 55 | it('should support a base IRI', function(done) { 56 | manu['@id'] = '42' 57 | db.jsonld.put(manu, { base: 'http://levelgraph.org/tests/' }, function() { 58 | db.get({ 59 | subject: 'http://levelgraph.org/tests/42', 60 | predicate: 'http://xmlns.com/foaf/0.1/name' 61 | }, function(err, triples) { 62 | expect(triples).to.have.length(1); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | 68 | it('should generate an @id for unknown objects', function(done) { 69 | delete manu['@id']; 70 | var baseString = 'http://levelgraph.org/tests/'; 71 | var baseRegEx = /^_\:/; 72 | 73 | db.jsonld.put(manu, { base: baseString }, function() { 74 | db.search({ 75 | subject: db.v('subject'), 76 | predicate: 'http://xmlns.com/foaf/0.1/name' 77 | }, function(err, solutions) { 78 | expect(solutions[0].subject).to.match(baseRegEx); 79 | done(); 80 | }); 81 | }); 82 | }); 83 | 84 | it('should generate an @id for all blank nodes in a complex object', function(done) { 85 | var tesla = helper.getFixture('tesla.json'); 86 | 87 | db.jsonld.put(tesla, { blank_ids: true }, function(err, obj) { 88 | expect(obj['gr:hasPriceSpecification']['@id']).to.match(/^_\:/); 89 | expect(obj['gr:includes']['@id']).to.match(/^_\:/); 90 | done(); 91 | }); 92 | }); 93 | 94 | it('should generate an @id for all blank nodes in a @list object', function(done) { 95 | var listdoc = helper.getFixture('list.json'); 96 | 97 | db.jsonld.put(listdoc, { blank_ids: true }, function(err, obj) { 98 | expect(obj['https://example.org/list']['@list']).to.have.length(2) 99 | obj['https://example.org/list']['@list'].forEach(function (e) { 100 | expect(e['@id']).to.match(/^_:/) 101 | }) 102 | db.jsonld.get(obj['@id'], {}, function (err, loaded) { 103 | expect(loaded['https://example.org/list']['@list']).to.have.length(2) 104 | done(); 105 | }); 106 | }); 107 | }); 108 | 109 | it('should pass the generated @id to callback', function(done) { 110 | delete manu['@id']; 111 | var baseString = 'http://levelgraph.org/tests/'; 112 | var baseRegEx = /^_\:/; 113 | 114 | db.jsonld.put(manu, { base: baseString, blank_ids: true }, function(err, obj) { 115 | expect(obj['@id']).to.match(baseRegEx); 116 | done(); 117 | }); 118 | }); 119 | 120 | it('should convert @type into http://www.w3.org/1999/02/22-rdf-syntax-ns#type', function(done) { 121 | db.jsonld.put(helper.getFixture('tesla.json'), function() { 122 | db.get({ 123 | subject: 'http://example.org/cars/for-sale#tesla', 124 | predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 125 | object: 'http://purl.org/goodrelations/v1#Offering' 126 | }, function(err, triples) { 127 | expect(triples).to.have.length(1); 128 | done(); 129 | }); 130 | }); 131 | }); 132 | 133 | it('should update a property', function(done) { 134 | db.jsonld.put(manu, function(err, instance) { 135 | instance.homepage = 'http://another/website'; 136 | db.jsonld.put(instance, function() { 137 | db.get({ 138 | subject: 'http://manu.sporny.org#person', 139 | predicate: 'http://xmlns.com/foaf/0.1/homepage', 140 | object: 'http://another/website' 141 | }, function(err, triples) { 142 | expect(triples).to.have.length(1); 143 | done(); 144 | }); 145 | }); 146 | }); 147 | }); 148 | 149 | it('should add a property', function(done) { 150 | db.jsonld.put(manu, function(err, instance) { 151 | instance.age = 42; 152 | instance['@context'].age = 'http://xmlns.com/foaf/0.1/age'; 153 | 154 | db.jsonld.put(instance, function() { 155 | db.get({ 156 | subject: 'http://manu.sporny.org#person', 157 | predicate: 'http://xmlns.com/foaf/0.1/age', 158 | object: '"42"^^http://www.w3.org/2001/XMLSchema#integer' 159 | }, function(err, triples) { 160 | expect(triples).to.have.length(1); 161 | done(); 162 | }); 163 | }); 164 | }); 165 | }); 166 | 167 | it('should delete a property', function(done) { 168 | db.jsonld.put(manu, function(err, instance) { 169 | delete instance.homepage 170 | 171 | db.jsonld.put(instance, function() { 172 | db.get({ 173 | subject: 'http://manu.sporny.org#person', 174 | predicate: 'http://xmlns.com/foaf/0.1/homepage' 175 | }, function(err, triples) { 176 | expect(triples).to.have.length(1); 177 | done(); 178 | }); 179 | }); 180 | }); 181 | }); 182 | 183 | 184 | it('should overwrite properties with the overwrite option', function(done) { 185 | db.jsonld.put(manu, function(err, instance) { 186 | delete instance.homepage 187 | 188 | db.jsonld.put(instance, { overwrite: true }, function() { 189 | db.get({ 190 | subject: 'http://manu.sporny.org#person', 191 | predicate: 'http://xmlns.com/foaf/0.1/homepage' 192 | }, function(err, triples) { 193 | expect(triples).to.have.length(1); 194 | done(); 195 | }); 196 | }); 197 | }); 198 | }); 199 | 200 | it('should delete a nested object', function(done) { 201 | db.jsonld.put(helper.getFixture('tesla.json'), function(err, instance) { 202 | delete instance['gr:hasPriceSpecification']; 203 | 204 | db.jsonld.put(instance, function() { 205 | db.get({ 206 | subject: 'http://example.org/cars/for-sale#tesla', 207 | predicate: 'http://purl.org/goodrelations/v1#' 208 | }, function(err, triples) { 209 | expect(triples).to.be.empty; 210 | done(); 211 | }); 212 | }); 213 | }); 214 | }); 215 | 216 | it('should receive error on invalid input', function(done) { 217 | var invalid = { 218 | "@context": { "@vocab": "http//example.com/" }, 219 | "test": { "@value": "foo", "bar": "oh yes" } 220 | } 221 | db.jsonld.put(invalid, function(err) { 222 | expect(err && err.name).to.equal('jsonld.SyntaxError'); 223 | expect(err && err.message).to.equal('Invalid JSON-LD syntax; the value of "@vocab" in a @context must be an absolute IRI.'); 224 | done(); 225 | }); 226 | }); 227 | 228 | it('should manage mapped @id', function(done) { 229 | var mapped_id = helper.getFixture('mapped_id.json') 230 | db.jsonld.put(mapped_id, {preserve: true}, function(err, obj) { 231 | expect(err && err.name).to.be.null; 232 | db.get({}, function(err, triples) { 233 | expect(triples).to.have.length(4); 234 | done(); 235 | }); 236 | }); 237 | }); 238 | 239 | it('should insert graphs', function(done) { 240 | var library = helper.getFixture('library.json'); 241 | 242 | db.jsonld.put(library, function() { 243 | db.get({}, function(err, triples) { 244 | expect(triples).to.have.length(9); 245 | done(); 246 | }); 247 | }); 248 | }); 249 | 250 | }); 251 | 252 | describe('jsonld.put with default base and overwrite and cut option set to true (backward compatibility)', function() { 253 | 254 | var db, manu; 255 | 256 | beforeEach(function() { 257 | db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/ahah/', overwrite: true, cut: true } }); 258 | manu = helper.getFixture('manu.json'); 259 | }); 260 | 261 | afterEach(function(done) { 262 | db.close(done); 263 | }); 264 | 265 | it('should use it', function(done) { 266 | manu['@id'] = '42' 267 | db.jsonld.put(manu, function() { 268 | db.get({ 269 | subject: 'http://levelgraph.io/ahah/42', 270 | predicate: 'http://xmlns.com/foaf/0.1/name' 271 | }, function(err, triples) { 272 | expect(triples).to.have.length(1); 273 | done(); 274 | }); 275 | }); 276 | }); 277 | 278 | it('should correctly generate blank nodes as subjects', function(done) { 279 | var tesla = helper.getFixture('tesla.json'); 280 | 281 | db.jsonld.put(tesla, function() { 282 | db.search([{ 283 | subject: 'http://example.org/cars/for-sale#tesla', 284 | predicate: 'http://purl.org/goodrelations/v1#hasPriceSpecification', 285 | object: db.v('bnode') 286 | }, { 287 | subject: db.v('bnode'), 288 | predicate: 'http://purl.org/goodrelations/v1#hasCurrency', 289 | object: '"USD"' 290 | }], function(err, solutions) { 291 | expect(solutions[0].bnode).to.exist; 292 | done(); 293 | }); 294 | }); 295 | }); 296 | 297 | it('should not store undefined objects', function(done) { 298 | var tesla = helper.getFixture('tesla.json'); 299 | 300 | db.jsonld.put(tesla, function() { 301 | db.get({}, function(err, triples) { 302 | triples.forEach(function(triple) { 303 | expect(triple.object).to.exist; 304 | }); 305 | done(); 306 | }); 307 | }); 308 | }); 309 | 310 | it('should support nested objects', function(done) { 311 | var nested = helper.getFixture('nested.json'); 312 | 313 | db.jsonld.put(nested, function() { 314 | db.get({}, function(err, triples) { 315 | expect(triples).to.have.length(5); 316 | done(); 317 | }); 318 | }); 319 | }); 320 | 321 | it('should break compatibility and not delete existing triples', function(done) { 322 | var chapter = helper.getFixture('chapter.json'); 323 | var description = helper.getFixture('chapterdescription.json'); 324 | 325 | db.jsonld.put(chapter, function() { 326 | db.jsonld.put(description, function() { 327 | db.get({}, function(err, triples) { 328 | expect(triples).to.have.length(3); 329 | done(); 330 | }); 331 | }); 332 | }); 333 | }); 334 | 335 | it('should not delete existing facts with the cut option set to false', function(done) { 336 | var chapter = helper.getFixture('chapter.json'); 337 | var description = helper.getFixture('chapterdescription.json'); 338 | 339 | db.jsonld.put(chapter, function() { 340 | db.jsonld.put(description, { cut: false }, function() { 341 | db.get({}, function(err, triples) { 342 | expect(triples).to.have.length(3); 343 | done(); 344 | }); 345 | }); 346 | }); 347 | }); 348 | 349 | it('should insert graphs', function(done) { 350 | var library = helper.getFixture('library.json'); 351 | 352 | db.jsonld.put(library, function() { 353 | db.get({}, function(err, triples) { 354 | expect(triples).to.have.length(9); 355 | done(); 356 | }); 357 | }); 358 | }); 359 | 360 | }); 361 | 362 | describe('jsonld.put with base', function() { 363 | 364 | var db, manu; 365 | 366 | beforeEach(function() { 367 | db = helper.getDB({ jsonld: { base: 'http://levelgraph.io/ahah/' } }); 368 | manu = helper.getFixture('manu.json'); 369 | }); 370 | 371 | afterEach(function(done) { 372 | db.close(done); 373 | }); 374 | 375 | 376 | it('should accept a done callback', function(done) { 377 | db.jsonld.put(manu, done); 378 | }); 379 | 380 | it('should store a triple', function(done) { 381 | db.jsonld.put(manu, function() { 382 | db.get({ 383 | subject: 'http://manu.sporny.org#person', 384 | predicate: 'http://xmlns.com/foaf/0.1/name' 385 | }, function(err, triples) { 386 | expect(triples).to.have.length(1); 387 | done(); 388 | }); 389 | }); 390 | }); 391 | 392 | it('should store two triples', function(done) { 393 | db.jsonld.put(manu, function() { 394 | db.get({ 395 | subject: 'http://manu.sporny.org#person' 396 | }, function(err, triples) { 397 | expect(triples).to.have.length(2); 398 | done(); 399 | }); 400 | }); 401 | }); 402 | 403 | it('should store a JSON file', function(done) { 404 | db.jsonld.put(JSON.stringify(manu), function() { 405 | db.get({ 406 | subject: 'http://manu.sporny.org#person' 407 | }, function(err, triples) { 408 | expect(triples).to.have.length(2); 409 | done(); 410 | }); 411 | }); 412 | }); 413 | 414 | it('should support a base IRI', function(done) { 415 | manu['@id'] = '42' 416 | db.jsonld.put(manu, { base: 'http://levelgraph.org/tests/' }, function() { 417 | db.get({ 418 | subject: 'http://levelgraph.org/tests/42', 419 | predicate: 'http://xmlns.com/foaf/0.1/name' 420 | }, function(err, triples) { 421 | expect(triples).to.have.length(1); 422 | done(); 423 | }); 424 | }); 425 | }); 426 | 427 | it('should generate an @id for unknown objects with the blank_ids option', function(done) { 428 | delete manu['@id']; 429 | var baseString = 'http://levelgraph.org/tests/'; 430 | var baseRegEx = /^_\:/; 431 | 432 | db.jsonld.put(manu, { base: baseString, blank_ids: true }, function() { 433 | db.search({ 434 | subject: db.v('subject'), 435 | predicate: 'http://xmlns.com/foaf/0.1/name' 436 | }, function(err, solutions) { 437 | expect(solutions[0].subject).to.match(baseRegEx); 438 | done(); 439 | }); 440 | }); 441 | }); 442 | 443 | it('should pass the generated @id to callback with the blank_ids option', function(done) { 444 | delete manu['@id']; 445 | var baseString = 'http://levelgraph.org/tests/'; 446 | var baseRegEx = /^_\:/; 447 | 448 | db.jsonld.put(manu, { base: baseString, blank_ids: true }, function(err, obj) { 449 | expect(obj['@id']).to.match(baseRegEx); 450 | done(); 451 | }); 452 | }); 453 | 454 | it('should convert @type into http://www.w3.org/1999/02/22-rdf-syntax-ns#type', function(done) { 455 | db.jsonld.put(helper.getFixture('tesla.json'), function() { 456 | db.get({ 457 | subject: 'http://example.org/cars/for-sale#tesla', 458 | predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 459 | object: 'http://purl.org/goodrelations/v1#Offering' 460 | }, function(err, triples) { 461 | expect(triples).to.have.length(1); 462 | done(); 463 | }); 464 | }); 465 | }); 466 | 467 | it('should update a property', function(done) { 468 | db.jsonld.put(manu, function(err, instance) { 469 | instance.homepage = 'http://another/website'; 470 | db.jsonld.put(instance, function() { 471 | db.get({ 472 | subject: 'http://manu.sporny.org#person', 473 | predicate: 'http://xmlns.com/foaf/0.1/homepage', 474 | object: 'http://another/website' 475 | }, function(err, triples) { 476 | expect(triples).to.have.length(1); 477 | done(); 478 | }); 479 | }); 480 | }); 481 | }); 482 | 483 | it('should add a property', function(done) { 484 | db.jsonld.put(manu, function(err, instance) { 485 | instance.age = 42; 486 | instance['@context'].age = 'http://xmlns.com/foaf/0.1/age'; 487 | 488 | db.jsonld.put(instance, function() { 489 | db.get({ 490 | subject: 'http://manu.sporny.org#person', 491 | predicate: 'http://xmlns.com/foaf/0.1/age', 492 | object: '"42"^^http://www.w3.org/2001/XMLSchema#integer' 493 | }, function(err, triples) { 494 | expect(triples).to.have.length(1); 495 | done(); 496 | }); 497 | }); 498 | }); 499 | }); 500 | 501 | it('should delete a property but preserve existing ones', function(done) { 502 | db.jsonld.put(manu, function(err, instance) { 503 | delete instance.homepage 504 | 505 | db.jsonld.put(instance, function() { 506 | db.get({ 507 | subject: 'http://manu.sporny.org#person', 508 | predicate: 'http://xmlns.com/foaf/0.1/homepage' 509 | }, function(err, triples) { 510 | expect(triples).to.have.length(1); 511 | done(); 512 | }); 513 | }); 514 | }); 515 | }); 516 | 517 | 518 | it('should preserve properties with the preserve option', function(done) { 519 | db.jsonld.put(manu, function(err, instance) { 520 | delete instance.homepage 521 | 522 | db.jsonld.put(instance, { preserve: true }, function() { 523 | db.get({ 524 | subject: 'http://manu.sporny.org#person', 525 | predicate: 'http://xmlns.com/foaf/0.1/homepage' 526 | }, function(err, triples) { 527 | expect(triples).to.have.length(1); 528 | done(); 529 | }); 530 | }); 531 | }); 532 | }); 533 | 534 | 535 | it('should use the base option', function(done) { 536 | manu['@id'] = '42' 537 | db.jsonld.put(manu, function() { 538 | db.get({ 539 | subject: 'http://levelgraph.io/ahah/42', 540 | predicate: 'http://xmlns.com/foaf/0.1/name' 541 | }, function(err, triples) { 542 | expect(triples).to.have.length(1); 543 | done(); 544 | }); 545 | }); 546 | }); 547 | 548 | it('should correctly generate blank nodes as subjects', function(done) { 549 | var tesla = helper.getFixture('tesla.json'); 550 | 551 | db.jsonld.put(tesla, function() { 552 | db.search([{ 553 | subject: 'http://example.org/cars/for-sale#tesla', 554 | predicate: 'http://purl.org/goodrelations/v1#hasPriceSpecification', 555 | object: db.v('bnode') 556 | }, { 557 | subject: db.v('bnode'), 558 | predicate: 'http://purl.org/goodrelations/v1#hasCurrency', 559 | object: '"USD"' 560 | }], function(err, solutions) { 561 | expect(solutions[0].bnode).to.exist; 562 | done(); 563 | }); 564 | }); 565 | }); 566 | 567 | it('should not store undefined objects', function(done) { 568 | var tesla = helper.getFixture('tesla.json'); 569 | 570 | db.jsonld.put(tesla, function() { 571 | db.get({}, function(err, triples) { 572 | triples.forEach(function(triple) { 573 | expect(triple.object).to.exist; 574 | }); 575 | done(); 576 | }); 577 | }); 578 | }); 579 | 580 | 581 | it('should delete a nested object', function(done) { 582 | db.jsonld.put(helper.getFixture('tesla.json'), function(err, instance) { 583 | delete instance['gr:hasPriceSpecification']; 584 | 585 | db.jsonld.put(instance, function() { 586 | db.get({ 587 | subject: 'http://example.org/cars/for-sale#tesla', 588 | predicate: 'http://purl.org/goodrelations/v1#' 589 | }, function(err, triples) { 590 | expect(triples).to.be.empty; 591 | done(); 592 | }); 593 | }); 594 | }); 595 | }); 596 | 597 | it('should receive error on invalid input', function(done) { 598 | var invalid = { 599 | "@context": { "@vocab": "http//example.com/" }, 600 | "test": { "@value": "foo", "bar": "oh yes" } 601 | } 602 | db.jsonld.put(invalid, function(err) { 603 | expect(err && err.name).to.equal('jsonld.SyntaxError'); 604 | expect(err && err.message).to.equal('Invalid JSON-LD syntax; the value of "@vocab" in a @context must be an absolute IRI.'); 605 | done(); 606 | }); 607 | }); 608 | 609 | it('should not overwrite existing facts', function(done) { 610 | var chapter = helper.getFixture('chapter.json'); 611 | var description = helper.getFixture('chapterdescription.json'); 612 | 613 | db.jsonld.put(chapter, function() { 614 | db.jsonld.put(description, function() { 615 | db.get({}, function(err, triples) { 616 | expect(triples).to.have.length(3); 617 | done(); 618 | }); 619 | }); 620 | }); 621 | }); 622 | 623 | it('should insert graphs', function(done) { 624 | var library = helper.getFixture('library.json'); 625 | 626 | db.jsonld.put(library, function() { 627 | db.get({}, function(err, triples) { 628 | expect(triples).to.have.length(9); 629 | done(); 630 | }); 631 | }); 632 | }); 633 | 634 | describe('jsonld.put with overwrite and cut set to true (backward compatibility)', function() { 635 | 636 | var db, manu; 637 | 638 | beforeEach(function() { 639 | db = helper.getDB({ jsonld: { overwrite: false, cut: true } }); 640 | manu = helper.getFixture('manu.json'); 641 | }); 642 | 643 | afterEach(function(done) { 644 | db.close(done); 645 | }); 646 | 647 | it('should accept a done callback', function(done) { 648 | db.jsonld.put(manu, done); 649 | }); 650 | 651 | it('should store a triple', function(done) { 652 | db.jsonld.put(manu, function() { 653 | db.get({ 654 | subject: 'http://manu.sporny.org#person', 655 | predicate: 'http://xmlns.com/foaf/0.1/name' 656 | }, function(err, triples) { 657 | expect(triples).to.have.length(1); 658 | done(); 659 | }); 660 | }); 661 | }); 662 | 663 | it('should store two triples', function(done) { 664 | db.jsonld.put(manu, function() { 665 | db.get({ 666 | subject: 'http://manu.sporny.org#person' 667 | }, function(err, triples) { 668 | expect(triples).to.have.length(2); 669 | done(); 670 | }); 671 | }); 672 | }); 673 | 674 | it('should store a JSON file', function(done) { 675 | db.jsonld.put(JSON.stringify(manu), function() { 676 | db.get({ 677 | subject: 'http://manu.sporny.org#person' 678 | }, function(err, triples) { 679 | expect(triples).to.have.length(2); 680 | done(); 681 | }); 682 | }); 683 | }); 684 | 685 | it('should support a base IRI', function(done) { 686 | manu['@id'] = '42' 687 | db.jsonld.put(manu, { base: 'http://levelgraph.org/tests/' }, function() { 688 | db.get({ 689 | subject: 'http://levelgraph.org/tests/42', 690 | predicate: 'http://xmlns.com/foaf/0.1/name' 691 | }, function(err, triples) { 692 | expect(triples).to.have.length(1); 693 | done(); 694 | }); 695 | }); 696 | }); 697 | 698 | it('should generate an @id for unknown objects', function(done) { 699 | delete manu['@id']; 700 | var baseString = 'http://levelgraph.org/tests/'; 701 | var baseRegEx = /^_\:/; 702 | 703 | db.jsonld.put(manu, { base: baseString, blank_ids: true }, function() { 704 | db.search({ 705 | subject: db.v('subject'), 706 | predicate: 'http://xmlns.com/foaf/0.1/name' 707 | }, function(err, solutions) { 708 | expect(solutions[0].subject).to.match(baseRegEx); 709 | done(); 710 | }); 711 | }); 712 | }); 713 | 714 | it('should generate an @id for all blank nodes in a complex object', function(done) { 715 | var tesla = helper.getFixture('tesla.json'); 716 | 717 | db.jsonld.put(tesla, { blank_ids: true }, function(err, obj) { 718 | expect(obj['gr:hasPriceSpecification']['@id']).to.match(/^_\:/); 719 | expect(obj['gr:includes']['@id']).to.match(/^_\:/); 720 | done(); 721 | }); 722 | }); 723 | 724 | it('should pass the generated @id to callback', function(done) { 725 | delete manu['@id']; 726 | var baseString = 'http://levelgraph.org/tests/'; 727 | var baseRegEx = /^_\:/; 728 | 729 | db.jsonld.put(manu, { base: baseString, blank_ids: true }, function(err, obj) { 730 | expect(obj['@id']).to.match(baseRegEx); 731 | done(); 732 | }); 733 | }); 734 | 735 | it('should convert @type into http://www.w3.org/1999/02/22-rdf-syntax-ns#type', function(done) { 736 | db.jsonld.put(helper.getFixture('tesla.json'), function() { 737 | db.get({ 738 | subject: 'http://example.org/cars/for-sale#tesla', 739 | predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 740 | object: 'http://purl.org/goodrelations/v1#Offering' 741 | }, function(err, triples) { 742 | expect(triples).to.have.length(1); 743 | done(); 744 | }); 745 | }); 746 | }); 747 | 748 | it('should update a property', function(done) { 749 | db.jsonld.put(manu, function(err, instance) { 750 | instance.homepage = 'http://another/website'; 751 | db.jsonld.put(instance, function() { 752 | db.get({ 753 | subject: 'http://manu.sporny.org#person', 754 | predicate: 'http://xmlns.com/foaf/0.1/homepage', 755 | object: 'http://another/website' 756 | }, function(err, triples) { 757 | expect(triples).to.have.length(1); 758 | done(); 759 | }); 760 | }); 761 | }); 762 | }); 763 | 764 | it('should add a property', function(done) { 765 | db.jsonld.put(manu, function(err, instance) { 766 | instance.age = 42; 767 | instance['@context'].age = 'http://xmlns.com/foaf/0.1/age'; 768 | 769 | db.jsonld.put(instance, function() { 770 | db.get({ 771 | subject: 'http://manu.sporny.org#person', 772 | predicate: 'http://xmlns.com/foaf/0.1/age', 773 | object: '"42"^^http://www.w3.org/2001/XMLSchema#integer' 774 | }, function(err, triples) { 775 | expect(triples).to.have.length(1); 776 | done(); 777 | }); 778 | }); 779 | }); 780 | }); 781 | 782 | it('should break compatibility and keep an existing property', function(done) { 783 | db.jsonld.put(manu, function(err, instance) { 784 | delete instance.homepage 785 | 786 | db.jsonld.put(instance, function() { 787 | db.get({ 788 | subject: 'http://manu.sporny.org#person', 789 | predicate: 'http://xmlns.com/foaf/0.1/homepage' 790 | }, function(err, triples) { 791 | expect(triples).to.have.length(1); 792 | done(); 793 | }); 794 | }); 795 | }); 796 | }); 797 | 798 | 799 | it('should preserve properties with the overwrite option set to false', function(done) { 800 | db.jsonld.put(manu, function(err, instance) { 801 | delete instance.homepage 802 | 803 | db.jsonld.put(instance, { overwrite: false }, function() { 804 | db.get({ 805 | subject: 'http://manu.sporny.org#person', 806 | predicate: 'http://xmlns.com/foaf/0.1/homepage' 807 | }, function(err, triples) { 808 | expect(triples).to.have.length(1); 809 | done(); 810 | }); 811 | }); 812 | }); 813 | }); 814 | 815 | it('should delete a nested object', function(done) { 816 | db.jsonld.put(helper.getFixture('tesla.json'), function(err, instance) { 817 | delete instance['gr:hasPriceSpecification']; 818 | 819 | db.jsonld.put(instance, function() { 820 | db.get({ 821 | subject: 'http://example.org/cars/for-sale#tesla', 822 | predicate: 'http://purl.org/goodrelations/v1#' 823 | }, function(err, triples) { 824 | expect(triples).to.be.empty; 825 | done(); 826 | }); 827 | }); 828 | }); 829 | }); 830 | 831 | it('should receive error on invalid input', function(done) { 832 | var invalid = { 833 | "@context": { "@vocab": "http//example.com/" }, 834 | "test": { "@value": "foo", "bar": "oh yes" } 835 | } 836 | db.jsonld.put(invalid, function(err) { 837 | expect(err && err.name).to.equal('jsonld.SyntaxError'); 838 | expect(err && err.message).to.equal('Invalid JSON-LD syntax; the value of "@vocab" in a @context must be an absolute IRI.'); 839 | done(); 840 | }); 841 | }); 842 | 843 | it('should manage mapped @id', function(done) { 844 | var mapped_id = helper.getFixture('mapped_id.json') 845 | db.jsonld.put(mapped_id, {preserve: true}, function(err) { 846 | expect(err && err.name).to.be.null; 847 | db.jsonld.get(mapped_id["id"], { "@context": mapped_id["@context"]}, function(err, obj) { 848 | db.get({}, function(err, triples) { 849 | expect(triples).to.have.length(4); 850 | done(); 851 | }); 852 | }); 853 | }); 854 | }); 855 | 856 | it('should insert graphs', function(done) { 857 | var library = helper.getFixture('library.json'); 858 | 859 | db.jsonld.put(library, function() { 860 | db.get({}, function(err, triples) { 861 | expect(triples).to.have.length(9); 862 | done(); 863 | }); 864 | }); 865 | }); 866 | 867 | }); 868 | 869 | }); 870 | --------------------------------------------------------------------------------