diff --git a/.changeset/chilly-jars-drive.md b/.changeset/chilly-jars-drive.md new file mode 100644 index 00000000..2c946063 --- /dev/null +++ b/.changeset/chilly-jars-drive.md @@ -0,0 +1,5 @@ +--- +"@hydrofoil/knossos": patch +--- + +Ignore member assertions where object is blank node diff --git a/.changeset/seven-buttons-cheat.md b/.changeset/seven-buttons-cheat.md new file mode 100644 index 00000000..2d2be4ad --- /dev/null +++ b/.changeset/seven-buttons-cheat.md @@ -0,0 +1,5 @@ +--- +"@hydrofoil/labyrinth": patch +--- + +Support shapes in member assertions diff --git a/.changeset/small-kangaroos-drop.md b/.changeset/small-kangaroos-drop.md new file mode 100644 index 00000000..991284d6 --- /dev/null +++ b/.changeset/small-kangaroos-drop.md @@ -0,0 +1,5 @@ +--- +"@labyrinth/testing": patch +--- + +Helper to parse graph from turtle template diff --git a/.changeset/yellow-lamps-sit.md b/.changeset/yellow-lamps-sit.md new file mode 100644 index 00000000..edd85373 --- /dev/null +++ b/.changeset/yellow-lamps-sit.md @@ -0,0 +1,5 @@ +--- +"@hydrofoil/shape-to-query": minor +--- + +First version. Minimal support only for `sh:targetClass` diff --git a/docs/knossos/collections.md b/docs/knossos/collections.md index b9b48c00..bbe3e7f9 100644 --- a/docs/knossos/collections.md +++ b/docs/knossos/collections.md @@ -192,6 +192,42 @@ This will wrap such a member assertion in a `GRAPH ?member` pattern > [!TIP] > This technique is useful to exclude inferred terms from matching the query. Only the resources self-asserted properties will be matched. +## Advanced member assertions + +A more advanced feature allows blank nodes to be used with member assertions. At the time of writing they can represent +[SHACL NodeShapes][node-shape], which will be used to add complex static filters to collections. + +For example, the following collection will return `lexvo:Language` resources but only those which are used as objects +of `bibo:Book` resources. + +```turtle +PREFIX bibo: +PREFIX sh: +PREFIX dcterms: +PREFIX lexvo: +PREFIX rdf: +PREFIX hydra: + + + a hydra:Collection ; + hydra:memberAssertion + [ + hydra:property rdf:type ; + hydra:object lexvo:Language ; + ], + [ + hydra:property dcterms:language ; + hydra:subject + [ + a sh:NodeShape ; + sh:targetClass bibo:Book ; + ]; + ] ; +. +``` + +[node-shape]: https://www.w3.org/TR/shacl/#node-shapes + ## Queries Collections can also be queries dynamically using `GET` requests with query strings. The variables passed by the client need to be mapped to URI Template variables which gets reconstructed into an RDF graph of filters on the server. The filters are then transformed into SPARQL query patterns using JS code. @@ -388,6 +424,10 @@ To enable this feature, the collection class has to support the `POST` operation Member assertions which have `hydra:predicate` and `hydra:object` will be implicitly added to the newly created resource. Other member assertions will be ignored. +> [!NOTE] +> For a member assertion to be applied to a new member, the `hydra:property` MUST be an IRI and `hydra:object` MUST be +> an IRI or Literal + ```turtle prefix rdfs: prefix schema: diff --git a/packages/knossos/collection.ts b/packages/knossos/collection.ts index 4379b6f2..36c015de 100644 --- a/packages/knossos/collection.ts +++ b/packages/knossos/collection.ts @@ -16,6 +16,7 @@ import * as rdfRequest from 'express-rdf-request' import { preprocessMiddleware, sendResponse } from '@hydrofoil/labyrinth/lib/middleware' import { getPayload } from '@hydrofoil/labyrinth/lib/request' import TermSet from '@rdfjs/term-set' +import { isBlankNode, isNamedNode } from 'is-graph-pointer' import { payloadTypes, shaclValidate } from './shacl' import { save } from './lib/resource' import { applyTransformations, hasAllRequiredVariables } from './lib/template' @@ -64,9 +65,9 @@ const assertMemberAssertions = asyncMiddleware(async (req, res: CreateMemberResp const member = res.locals.member! for (const assertion of res.locals.memberAssertions!.toArray()) { - const predicate = assertion.out(hydra.property).term - const object = assertion.out(hydra.object).term - if (predicate && object) { + const predicate = assertion.out(hydra.property) + const object = assertion.out(hydra.object) + if (isNamedNode(predicate) && !isBlankNode(object)) { member.addOut(predicate, object) } } diff --git a/packages/knossos/test/collection.test.ts b/packages/knossos/test/collection.test.ts index 27c8b896..48f52bfc 100644 --- a/packages/knossos/test/collection.test.ts +++ b/packages/knossos/test/collection.test.ts @@ -379,6 +379,72 @@ describe('@hydrofoil/knossos/collection', () => { })) }) + it('ignores member assertions with blank nodes', async () => { + // given + app.use(async (req, res, next) => { + const collection = await req.hydra.resource.clownface() + collection.addOut(rdf.type, ex.Collection) + clownface(req.hydra.api) + .node(ex.Collection) + .addOut(hydra.memberAssertion, assert => { + assert.addOut(hydra.property, rdf.type) + assert.addOut(hydra.object, foaf.Person) + }) + collection.addOut(hydra.memberAssertion, assert => { + assert.addOut(hydra.property, foaf.knows) + assert.addOut(hydra.object, collection.blankNode()) + }) + next() + }) + app.post('/collection', CreateMember) + + // when + await request(app) + .post('/collection') + .send(turtle`<> ${schema.name} "john" .`.toString()) + .set('content-type', 'text/turtle') + .set('host', 'example.com') + + // then + expect(knossos.store.save).to.have.been.calledWith(sinon.match((value: GraphPointer) => { + expect(value.out(foaf.knows).terms).to.be.empty + return true + })) + }) + + it('asserts member assertions with literal object', async () => { + // given + app.use(async (req, res, next) => { + const collection = await req.hydra.resource.clownface() + collection.addOut(rdf.type, ex.Collection) + clownface(req.hydra.api) + .node(ex.Collection) + .addOut(hydra.memberAssertion, assert => { + assert.addOut(hydra.property, rdf.type) + assert.addOut(hydra.object, foaf.Person) + }) + collection.addOut(hydra.memberAssertion, assert => { + assert.addOut(hydra.property, foaf.gender) + assert.addOut(hydra.object, 'M') + }) + next() + }) + app.post('/collection', CreateMember) + + // when + await request(app) + .post('/collection') + .send(turtle`<> ${schema.name} "john" .`.toString()) + .set('content-type', 'text/turtle') + .set('host', 'example.com') + + // then + expect(knossos.store.save).to.have.been.calledWith(sinon.match((value: GraphPointer) => { + expect(value.out(foaf.gender).term).to.deep.eq($rdf.literal('M')) + return true + })) + }) + it('does not mistake self reference in member assertion for new item id', async () => { // given app.use(async (req, res, next) => { diff --git a/packages/labyrinth/lib/query/dynamicCollection.ts b/packages/labyrinth/lib/query/dynamicCollection.ts index 26552886..444b96d2 100644 --- a/packages/labyrinth/lib/query/dynamicCollection.ts +++ b/packages/labyrinth/lib/query/dynamicCollection.ts @@ -6,17 +6,16 @@ import cf, { AnyPointer, GraphPointer, MultiPointer } from 'clownface' import { sparql, SparqlTemplateResult } from '@tpluscode/rdf-string' import { IriTemplate, IriTemplateMapping } from '@rdfine/hydra' import { Api } from 'hydra-box/Api' -import { hyper_query, knossos } from '@hydrofoil/vocabularies/builders' +import { hyper_query } from '@hydrofoil/vocabularies/builders' import type { StreamClient } from 'sparql-http-client/StreamClient' import toArray from 'stream-to-array' import { toSparql } from 'clownface-shacl-path' -import { toRdf } from 'rdf-literal' -import TermSet from '@rdfjs/term-set' import { loadImplementations } from '../code' -import { exactMatch } from '../query/filters' -import { Filter } from '../query' import { log, warn } from '../logger' -import { loadResourceWithLinks } from '../query/eagerLinks' +import { exactMatch } from './filters' +import { loadResourceWithLinks } from './eagerLinks' +import { memberAssertionPatterns } from './memberAssertion' +import { Filter } from '.' function createTemplateVariablePatterns(subject: Variable, queryPointer: AnyPointer, api: Api) { return async (mapping: IriTemplateMapping, index: number): Promise => { @@ -64,48 +63,6 @@ function createTemplateVariablePatterns(subject: Variable, queryPointer: AnyPoin } } -function * createPatterns(subs: Term[], preds: Term[], objs: Term[], { graph }: { graph?: Variable }) { - for (const subject of subs) { - for (const predicate of preds) { - for (const object of objs) { - const pattern = sparql`${subject} ${predicate} ${object} .` - - yield graph ? sparql`GRAPH ${graph} { ${pattern} }` : pattern - } - } - } -} - -function toSparqlPattern(member: Variable) { - const seen = new TermSet() - - return function (previous: SparqlTemplateResult[], memberAssertion: GraphPointer): SparqlTemplateResult[] { - if (seen.has(memberAssertion.term)) { - return previous - } - - seen.add(memberAssertion.term) - const subject = memberAssertion.out(hydra.subject).terms - const predicate = memberAssertion.out(hydra.property).terms - const object = memberAssertion.out(hydra.object).terms - const graph = memberAssertion.out(knossos.ownGraphOnly).term?.equals(toRdf(true)) ? member : undefined - - if (subject.length && predicate.length && !object.length) { - return [...previous, ...createPatterns(subject, predicate, [member], { graph })] - } - if (subject.length && object.length && !predicate.length) { - return [...previous, ...createPatterns(subject, [member], object, { graph })] - } - if (predicate.length && object.length && !subject.length) { - return [...previous, ...createPatterns([member], predicate, object, { graph })] - } - - log('Skipping invalid member assertion') - - return previous - } -} - type SelectBuilder = ReturnType function createOrdering(collectionTypes: MultiPointer, collection: GraphPointer, subject: Variable): { patterns: SparqlTemplateResult; addClauses(q: SelectBuilder): SelectBuilder } { @@ -170,8 +127,8 @@ export default async function ({ api, collection, client, pageSize, query, varia const collectionTypes = apiPointer.node(collection.out(rdf.type)) const memberAssertions = [ - ...collectionTypes.out(memberAssertionPredicates).toArray().reduce(toSparqlPattern(subject), []), - ...collection.out(memberAssertionPredicates).toArray().reduce(toSparqlPattern(subject), []), + ...memberAssertionPatterns(collectionTypes.out(memberAssertionPredicates), subject), + ...memberAssertionPatterns(collection.out(memberAssertionPredicates), subject), ] const managesBlockPatterns = memberAssertions.reduce((combined, next) => sparql`${combined}\n${next}`, sparql``) diff --git a/packages/labyrinth/lib/query/memberAssertion.ts b/packages/labyrinth/lib/query/memberAssertion.ts new file mode 100644 index 00000000..1d2c88c6 --- /dev/null +++ b/packages/labyrinth/lib/query/memberAssertion.ts @@ -0,0 +1,83 @@ +import { Term, Variable } from 'rdf-js' +import { sparql, SparqlTemplateResult } from '@tpluscode/rdf-string' +import TermSet from '@rdfjs/term-set' +import { GraphPointer, MultiPointer } from 'clownface' +import { hydra, rdf, sh } from '@tpluscode/rdf-ns-builders' +import { knossos } from '@hydrofoil/vocabularies/builders' +import { toRdf } from 'rdf-literal' +import { isBlankNode } from 'is-graph-pointer' +import { shapeToPatterns } from '@hydrofoil/shape-to-query' +import $rdf from 'rdf-ext' +import { log } from '../../lib/logger' + +export function memberAssertionPatterns(memberAssertions: MultiPointer, subject: Variable): SparqlTemplateResult[] { + return memberAssertions.toArray().reduce(toSparqlPattern(subject), []) +} + +function toSparqlPattern(member: Variable) { + const seen = new TermSet() + + return function (previous: SparqlTemplateResult[], memberAssertion: GraphPointer): SparqlTemplateResult[] { + if (seen.has(memberAssertion.term)) { + return previous + } + + seen.add(memberAssertion.term) + const subject = memberAssertion.out(hydra.subject) + const predicate = memberAssertion.out(hydra.property) + const object = memberAssertion.out(hydra.object) + const graph = memberAssertion.out(knossos.ownGraphOnly).term?.equals(toRdf(true)) ? member : undefined + const memberPointer = memberAssertion.node(member) + + if (subject.values.length && predicate.values.length && !object.values.length) { + return [...previous, ...createPatterns(subject, predicate, memberPointer, { graph })] + } + if (subject.values.length && object.values.length && !predicate.values.length) { + return [...previous, ...createPatterns(subject, memberPointer, object, { graph })] + } + if (predicate.values.length && object.values.length && !subject.values.length) { + return [...previous, ...createPatterns(memberPointer, predicate, object, { graph })] + } + + log('Skipping invalid member assertion') + + return previous + } +} + +function * createPatterns(subs: MultiPointer, preds: MultiPointer, objs: MultiPointer, { graph }: { graph?: Variable }) { + for (const [subject, subjectPatterns] of subs.map(createPatternValue('ma_s'))) { + for (const [predicate, predicatePatterns] of preds.map(createPatternValue('ma_p'))) { + for (const [object, objectPatterns] of objs.map(createPatternValue('ma_o'))) { + const patterns = sparql` + ${subject} ${predicate} ${object} . + ${subjectPatterns} + ${predicatePatterns} + ${objectPatterns} + ` + + yield graph ? sparql`GRAPH ${graph} { ${patterns} }` : patterns + } + } + } +} + +function createPatternValue(variable: string) { + return (ptr: GraphPointer, index: number): [Term | null, SparqlTemplateResult] | [Term | null] => { + if (isBlankNode(ptr)) { + if (isNodeShape(ptr)) { + const variableName = `${variable}${index}` + + return [$rdf.variable(variableName), shapeToPatterns(ptr, variableName)] + } + + return [null] + } + + return [ptr.term] + } +} + +function isNodeShape(pointer: GraphPointer) { + return pointer.has(rdf.type, sh.NodeShape).terms.length > 0 +} diff --git a/packages/labyrinth/package.json b/packages/labyrinth/package.json index 674f5ef4..97897de4 100644 --- a/packages/labyrinth/package.json +++ b/packages/labyrinth/package.json @@ -20,6 +20,7 @@ "directory": "packages/labyrinth" }, "dependencies": { + "@hydrofoil/shape-to-query": "^0.0.0", "@hydrofoil/vocabularies": "^0.3.2", "@rdfine/hydra": "^0.8.4", "@rdfjs/data-model": "^1.2", diff --git a/packages/labyrinth/test/lib/query/memberAssertion.test.ts b/packages/labyrinth/test/lib/query/memberAssertion.test.ts new file mode 100644 index 00000000..68614172 --- /dev/null +++ b/packages/labyrinth/test/lib/query/memberAssertion.test.ts @@ -0,0 +1,60 @@ +import { parse } from '@labyrinth/testing/nodeFactory' +import { hydra, sh } from '@tpluscode/rdf-ns-builders' +import { expect } from 'chai' +import $rdf from 'rdf-ext' +import { ex } from '@labyrinth/testing/namespace' +import { sparql } from '@tpluscode/rdf-string' +import { SELECT } from '@tpluscode/sparql-builder' +import { memberAssertionPatterns } from '../../../lib/query/memberAssertion' +import '@labyrinth/testing/sparql' + +describe('@hydrofoil/labyrinth/lib/query/memberAssertion', () => { + async function testData(...args: Parameters) { + const ptr = await parse(...args) + + return ptr.any().has([hydra.subject, hydra.property, hydra.object]) + } + + it('ignores member assertion which is a blank node and has no type', async () => { + // given + const assertions = await testData` + [ + ${hydra.object} ${ex.foo} ; ${hydra.subject} [] ; + ] . + [ + ${hydra.property} ${ex.foo} ; ${hydra.subject} [] ; + ] . + [ + ${hydra.property} ${ex.foo} ; ${hydra.object} [] ; + ] . + ` + + // when + const patterns = memberAssertionPatterns(assertions, $rdf.variable('member')) + + // then + expect(patterns).to.be.empty + }) + + it('generates shape for Node Shape', async () => { + // given + const assertions = await testData` + [ + ${hydra.property} ${ex.foo} ; + ${hydra.object} [ + a ${sh.NodeShape} ; + ${sh.targetClass} ${ex.Bar} ; + ] ; + ] . + ` + + // when + const patterns = SELECT.ALL.WHERE`${memberAssertionPatterns(assertions, $rdf.variable('member'))}` + + // then + await expect(patterns).to.be.a.query(sparql`SELECT * { + ?member ${ex.foo} ?ma_o0 . + ?ma_o0 a ${ex.Bar} . + }`) + }) +}) diff --git a/packages/shape-to-query/LICENSE b/packages/shape-to-query/LICENSE new file mode 100644 index 00000000..e1c2979e --- /dev/null +++ b/packages/shape-to-query/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 hypermedia.app + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/shape-to-query/index.ts b/packages/shape-to-query/index.ts new file mode 100644 index 00000000..c06a009f --- /dev/null +++ b/packages/shape-to-query/index.ts @@ -0,0 +1,8 @@ +import type { GraphPointer } from 'clownface' +import { sparql, SparqlTemplateResult } from '@tpluscode/rdf-string' +import { sh } from '@tpluscode/rdf-ns-builders' + +export function shapeToPatterns(shape: GraphPointer, variablePrefix: string): SparqlTemplateResult { + const targetClass = shape.out(sh.targetClass).term + return sparql`?${variablePrefix} a ${targetClass}` +} diff --git a/packages/shape-to-query/package.json b/packages/shape-to-query/package.json new file mode 100644 index 00000000..8d65514e --- /dev/null +++ b/packages/shape-to-query/package.json @@ -0,0 +1,16 @@ +{ + "name": "@hydrofoil/shape-to-query", + "version": "0.0.0", + "main": "index.js", + "dependencies": { + "@tpluscode/rdf-ns-builders": "^2.0.1", + "@tpluscode/rdf-string": "^0.2.26", + "@tpluscode/sparql-builder": "^0.3" + }, + "devDependencies": { + "@labyrinth/testing": "^0.0.17", + "@types/sparqljs": "^3.1.3", + "chai": "^4.3.6", + "sparqljs": "^3.6.1" + } +} diff --git a/packages/shape-to-query/test/index.test.ts b/packages/shape-to-query/test/index.test.ts new file mode 100644 index 00000000..c24b6e77 --- /dev/null +++ b/packages/shape-to-query/test/index.test.ts @@ -0,0 +1,33 @@ +import { parse } from '@labyrinth/testing/nodeFactory' +import { foaf, sh } from '@tpluscode/rdf-ns-builders' +import { SELECT } from '@tpluscode/sparql-builder' +import { expect } from 'chai' +import { sparql } from '@tpluscode/rdf-string' +import { shapeToPatterns } from '..' +import '@labyrinth/testing/sparql' + +describe('@hydrofoil/shape-to-query', () => { + describe('shapeToPatterns', () => { + context('targets', () => { + context('class target', () => { + it('creates an rdf:type pattern', async () => { + // given + const shape = await parse` + <> + a ${sh.NodeShape} ; + ${sh.targetClass} ${foaf.Person} . + ` + + // when + const patterns = shapeToPatterns(shape, 'node') + const query = SELECT.ALL.WHERE`${patterns}`.build() + + // then + expect(query).to.be.a.query(sparql`SELECT * WHERE { + ?node a ${foaf.Person} + }`) + }) + }) + }) + }) +}) diff --git a/packages/testing/nodeFactory.ts b/packages/testing/nodeFactory.ts index 77ec9ca3..c4c2dd0d 100644 --- a/packages/testing/nodeFactory.ts +++ b/packages/testing/nodeFactory.ts @@ -2,6 +2,9 @@ import { BlankNode, NamedNode } from 'rdf-js' import clownface, { GraphPointer } from 'clownface' import DatasetExt from 'rdf-ext/lib/Dataset' import $rdf from 'rdf-ext' +import toStream from 'string-to-stream' +import { turtle } from '@tpluscode/rdf-string' +import { StreamParser } from 'n3' export function namedNode(term: Iri | NamedNode): GraphPointer { return clownface({ dataset: $rdf.dataset() }).namedNode(term) @@ -10,3 +13,11 @@ export function namedNode(term: Iri | NamedNode { return clownface({ dataset: $rdf.dataset() }).blankNode(label) } + +export async function parse(...[strings, ...values]: Parameters): Promise { + const turtleStream = toStream(turtle(strings, ...values).toString()) + const quadStream = turtleStream.pipe(new StreamParser()) + const dataset = await $rdf.dataset().import(quadStream) + + return clownface({ dataset }).namedNode('') +} diff --git a/packages/testing/package.json b/packages/testing/package.json index ea9d6149..19eca792 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -15,12 +15,17 @@ "debug": "^4.3.4", "express": "^4.18.1", "hydra-box": "^0.6.6", + "n3": "^1.8.1", "rdf-ext": "^1.3.5", "rdf-loader-code": "^0.3.2", "rdf-loaders-registry": "^0.3.0", "set-link": "^1.0.0", "sinon": "^14.0.0", "sparqljs": "^3.5.2", - "sparql-http-client": "^2.4.1" + "sparql-http-client": "^2.4.1", + "string-to-stream": "^3.0.1" + }, + "devDependencies": { + "@types/n3": "^1.10.4" } } diff --git a/yarn.lock b/yarn.lock index 58f29b53..4d464a31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1756,10 +1756,10 @@ clownface "^1" once "^1.4.0" -"@tpluscode/sparql-builder@^0.3.22", "@tpluscode/sparql-builder@^0.3.23": - version "0.3.23" - resolved "https://registry.yarnpkg.com/@tpluscode/sparql-builder/-/sparql-builder-0.3.23.tgz#a1c0a2dd0f3d42c04742f417a56f2837435e9561" - integrity sha512-qJYMAn5MNUcna+8fwloZTVbI7BZ1KjPLhuGluddC0GvcKo3VDqFFD3AwNCKsKdw9MHu5j2kiUufcZJpejOyBRw== +"@tpluscode/sparql-builder@^0.3", "@tpluscode/sparql-builder@^0.3.22", "@tpluscode/sparql-builder@^0.3.23": + version "0.3.24" + resolved "https://registry.yarnpkg.com/@tpluscode/sparql-builder/-/sparql-builder-0.3.24.tgz#8fccd8f1323597ca3ad11b7a86c469529ed774f6" + integrity sha512-5DjQafLbdAOn3BwHv6eianSfO8jV54IJDy8usY6rO8Rz81e+BkpdUcqzx9gHoghISVgffgq2hwSLL44Fa/PG+Q== dependencies: "@rdf-esm/data-model" "^0.5.4" "@rdf-esm/term-set" "^0.5.0" @@ -1998,6 +1998,14 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/n3@^1.10.4": + version "1.10.4" + resolved "https://registry.yarnpkg.com/@types/n3/-/n3-1.10.4.tgz#fd23d15fd7e47cf6d199d1f44ac5d6930cc50905" + integrity sha512-FfRTwcbXcScVHuAjIASveRWL6Fi6fPALl1Ge8tMESYLqU7R42LJvtdBpUi+f9YK0oQPqIN+zFFgMDFJfLMx0bg== + dependencies: + "@types/node" "*" + rdf-js "^4.0.2" + "@types/nanoid@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/nanoid/-/nanoid-2.1.0.tgz#41edfda78986e9127d0dc14de982de766f994020" @@ -2199,7 +2207,7 @@ dependencies: rdf-js "^4.0.2" -"@types/sparqljs@^3.0.1": +"@types/sparqljs@^3.0.1", "@types/sparqljs@^3.1.3": version "3.1.3" resolved "https://registry.yarnpkg.com/@types/sparqljs/-/sparqljs-3.1.3.tgz#e4b9a2511bc2f14f564559ed6cf567835791a7e9" integrity sha512-nmFgmR6ns4i8sg9fYu+293H+PMLKmDOZy34sgwgAeUEEiIqSs4guj5aCZRt3gq1g0yuKXkqrxLDq/684g7pGtQ== @@ -5749,10 +5757,10 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -n3@^1.3.5: - version "1.8.1" - resolved "https://registry.yarnpkg.com/n3/-/n3-1.8.1.tgz#7ad887f6aba108344e827d7722e01c51b5c194fa" - integrity sha512-oS0XBNhHH+1TFByvJs84q2lsy3MqpkIeLNT8r9OPyblcvcQbZHcKspwdVcWWzNrlMpSHmbOG1KO5AbXy2++Csw== +n3@^1.3.5, n3@^1.8.1: + version "1.16.2" + resolved "https://registry.yarnpkg.com/n3/-/n3-1.16.2.tgz#ef86b4bf48b556505da1cc3062a3c2ef35f02976" + integrity sha512-5vYa2HuNEJ+a26FEs4FGgfFLgaPOODaZpJlc7FS0eUjDumc4uK0cvx216PjKXBkLzmAsSqGgQPwqztcLLvwDsw== dependencies: queue-microtask "^1.1.2" readable-stream "^3.6.0" @@ -6474,10 +6482,10 @@ rdf-canonize@^3.0.0: dependencies: setimmediate "^1.0.5" -rdf-data-factory@^1.0.4, rdf-data-factory@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/rdf-data-factory/-/rdf-data-factory-1.1.0.tgz#d0510b9f100dd79e94f29559a12d4a5a585054d6" - integrity sha512-g8feOVZ/KL1OK2Pco/jDBDFh4m29QDsOOD+rWloG9qFvIzRFchGy2CviLUX491E0ByewXxMpaq/A3zsWHQA16A== +rdf-data-factory@^1.1.0, rdf-data-factory@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/rdf-data-factory/-/rdf-data-factory-1.1.1.tgz#370142794e2299846896e9c0fafd35e5128c8e5f" + integrity sha512-0HoLx7lbBlNd2YTmNKin0txgiYmAV56eVU823at8cG2+iD0Ia5kcRNDpzZy6I/HCtFTymHvTfdhHTzm3ak3Jpw== dependencies: "@rdfjs/types" "*" @@ -7139,12 +7147,12 @@ sparql-http-client@^2.4.1: readable-stream "^3.5.0" separate-stream "^1.0.0" -sparqljs@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/sparqljs/-/sparqljs-3.5.2.tgz#033db2d2cb7b0e7b7e32bb32dee39ac9953e9901" - integrity sha512-HPe2H6WcEJyZ5sRYRdFE7bWCGqtVf8BZJRD7P1uBCeoYtGPDs7Kcn+BZQ7NLVd6L5OfaP2XFuD1X/xER+hF0uQ== +sparqljs@^3.5.2, sparqljs@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/sparqljs/-/sparqljs-3.6.1.tgz#9aa6d07afbce2b95edfcd83403fac09b3ab51f73" + integrity sha512-4QoI3cMywOio8mtTLa3Rl85XI7UBvQRm1CbzbHEQ7C6AN6ldBFsSS96vkhcKCQKPl0eDTmCXsi+50234+1cOpA== dependencies: - rdf-data-factory "^1.0.4" + rdf-data-factory "^1.1.1" spawndamnit@^2.0.0: version "2.0.0"