├── 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 | 
5 |
6 | [](https://travis-ci.org/levelgraph/levelgraph-jsonld)
7 | [](https://coveralls.io/r/levelgraph/levelgraph-jsonld)
8 | [](https://david-dm.org/levelgraph/levelgraph-jsonld)
9 | [](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 |
--------------------------------------------------------------------------------