diff --git a/.changeset/blue-peas-retire.md b/.changeset/blue-peas-retire.md new file mode 100644 index 00000000..259f92b2 --- /dev/null +++ b/.changeset/blue-peas-retire.md @@ -0,0 +1,5 @@ +--- +"alcaeus": minor +--- + +Added support for `hydra:memberAssertion` diff --git a/docs/latest/representations/collections.md b/docs/latest/representations/collections.md index 9d18030d..faea03b5 100644 --- a/docs/latest/representations/collections.md +++ b/docs/latest/representations/collections.md @@ -3,13 +3,13 @@ Hydra specifications defines an interface for collections. A basic collection is represented by an array of its items (`member`) and the total number of items. ```typescript -import { ManagesBlock, Resource, View } from '@rdfine/hydra' +import { MemberAssertion, Resource, View } from '@rdfine/hydra' interface Collection { readonly totalItems: number; readonly member: Resource[]; readonly views?: View[]; - readonly manages: ManagesBlock[]; + readonly manages: MemberAssertion[]; } ``` diff --git a/package.json b/package.json index cf9faae7..b625d345 100644 --- a/package.json +++ b/package.json @@ -56,12 +56,12 @@ "@rdf-esm/sink-map": "^0.5.0", "@rdf-esm/term-map": "^0.5.0", "@rdf-esm/term-set": "^0.5.0", - "@rdfine/hydra": "^0.6.6", + "@rdfine/hydra": "^0.7.1", "@rdfine/rdf": "^0.5.4", "@rdfjs/formats-common": "^2.1.0", "@rdfjs/types": "*", "@tpluscode/rdf-ns-builders": "^1.0.0", - "@tpluscode/rdfine": "^0.5.27", + "@tpluscode/rdfine": "^0.5.29", "clownface": "^1", "isomorphic-fetch": "^3.0.0", "isomorphic-form-data": "^2.0.0", diff --git a/src/Resources/CoreMixins/HydraResource.ts b/src/Resources/CoreMixins/HydraResource.ts index b3ed69ad..a92b9d58 100644 --- a/src/Resources/CoreMixins/HydraResource.ts +++ b/src/Resources/CoreMixins/HydraResource.ts @@ -5,7 +5,7 @@ import type { DatasetCore, Term } from 'rdf-js' import TermMap from '@rdf-esm/term-map' import { GraphPointer } from 'clownface' import type { HydraClient } from '../../alcaeus' -import type { ManagesBlockPattern } from '../Mixins/ManagesBlock' +import type { MemberAssertionPattern } from '../Mixins/MemberAssertion' import { RuntimeOperation, createMixin } from '../Operation' declare module '@tpluscode/rdfine' { @@ -30,7 +30,7 @@ declare module '@tpluscode/rdfine' { /** * Gets objects of hydra:collection property */ - getCollections(filter?: ManagesBlockPattern): RdfResource[] + getCollections(filter?: MemberAssertionPattern): RdfResource[] } } @@ -111,10 +111,15 @@ export function createHydraResourceMixin(alcaeus: () => HydraClient) { return [...map.values()] } - public getCollections(filter?: ManagesBlockPattern) { + public getCollections(filter?: MemberAssertionPattern) { if (filter) { - return this.collection.filter((c) => c.manages && - c.manages.find((managesBlock) => managesBlock.matches(filter))) + return this.collection.filter((c) => { + const memberAssertions = [ + ...c.memberAssertion || [], + ...c.manages || [], + ] + return memberAssertions.find((managesBlock) => managesBlock.matches(filter)) + }) } return this.collection diff --git a/src/Resources/Mixins/ManagesBlock.ts b/src/Resources/Mixins/MemberAssertion.ts similarity index 60% rename from src/Resources/Mixins/ManagesBlock.ts rename to src/Resources/Mixins/MemberAssertion.ts index f8e6334c..8976f1ee 100644 --- a/src/Resources/Mixins/ManagesBlock.ts +++ b/src/Resources/Mixins/MemberAssertion.ts @@ -1,24 +1,28 @@ -import type { Constructor, RdfResource, ResourceIdentifier } from '@tpluscode/rdfine' +import type { ExtendingConstructor, RdfResource, ResourceIdentifier } from '@tpluscode/rdfine' import { namespace } from '@tpluscode/rdfine' -import type { Class, ManagesBlock } from '@rdfine/hydra' +import type { Class, MemberAssertion } from '@rdfine/hydra' import type { Property } from '@rdfine/rdf' import type { MultiPointer } from 'clownface' import type { NamedNode } from 'rdf-js' import { hydra, rdf } from '@tpluscode/rdf-ns-builders' -export interface ManagesBlockPattern { +export interface MemberAssertionPattern { subject?: string | RdfResource | NamedNode predicate?: string | Property | NamedNode object?: string | Class | NamedNode } +interface MemberAssertionEx { + /** + * Checks if the current manages block matches the given pattern + * @param filter {MemberAssertionPattern} + */ + matches(filter: MemberAssertionPattern): boolean +} + declare module '@rdfine/hydra' { - export interface ManagesBlock { - /** - * Checks if the current manages block matches the given pattern - * @param filter {ManagesBlockPattern} - */ - matches(filter: ManagesBlockPattern): boolean + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface MemberAssertion extends MemberAssertionEx { } } @@ -34,10 +38,10 @@ function getUri(factory: MultiPointer, resource: string | RdfResource | NamedNod return resource } -export function ManagesBlockMixin>>(Base: TBase) { +export function MemberAssertionMixin>(Base: TBase) { @namespace(hydra) - class ManagesBlockClass extends Base implements ManagesBlock { - public matches({ subject = '', predicate = rdf.type, object = '' }: ManagesBlockPattern): boolean { + class MemberAssertionClass extends Base implements MemberAssertionEx { + public matches({ subject = '', predicate = rdf.type, object = '' }: MemberAssertionPattern): boolean { const predicateId = getUri(this.pointer, predicate) const objectId = getUri(this.pointer, object) const subjectId = getUri(this.pointer, subject) @@ -56,9 +60,9 @@ export function ManagesBlockMixin { - return res.pointer.in(hydra.manages).terms.length > 0 +MemberAssertionMixin.shouldApply = (res: RdfResource) => { + return res.pointer.in([hydra.manages, hydra.memberAssertion]).terms.length > 0 } diff --git a/src/Resources/Mixins/index.ts b/src/Resources/Mixins/index.ts index eb83551e..d708c107 100644 --- a/src/Resources/Mixins/index.ts +++ b/src/Resources/Mixins/index.ts @@ -1,16 +1,15 @@ import './Class' import './Operation' -import './SupportedProperty' import './PartialCollectionView' import './ApiDocumentation' import './DocumentedResource' -import './ManagesBlock' +import './MemberAssertion' import './RdfProperty' export { ApiDocumentationMixin } from './ApiDocumentation' export { ClassMixin } from './Class' export { DocumentedResourceMixin } from './DocumentedResource' -export { ManagesBlockMixin } from './ManagesBlock' +export { MemberAssertionMixin } from './MemberAssertion' export { PartialCollectionViewMixin } from './PartialCollectionView' export { RdfPropertyMixin } from './RdfProperty' export { OperationMixin } from './Operation' diff --git a/src/inferences/index.ts b/src/inferences/index.ts index 4cc12620..c700b444 100644 --- a/src/inferences/index.ts +++ b/src/inferences/index.ts @@ -1,2 +1,2 @@ export { inferTypesFromPropertyRanges } from './propertyTypes' -export { addExplicitStatementsInferredFromManagesBlock } from './managesBlock' +export { addExplicitStatementsInferredFromMemberAssertion } from './memberAssertion' diff --git a/src/inferences/managesBlock.ts b/src/inferences/memberAssertion.ts similarity index 81% rename from src/inferences/managesBlock.ts rename to src/inferences/memberAssertion.ts index 83a03d65..16949207 100644 --- a/src/inferences/managesBlock.ts +++ b/src/inferences/memberAssertion.ts @@ -3,9 +3,9 @@ import * as RDF from '@rdf-esm/data-model' import { BaseQuad, DatasetCore } from 'rdf-js' import { hydra } from '@tpluscode/rdf-ns-builders' -export function * addExplicitStatementsInferredFromManagesBlock(dataset: DatasetCore): Iterable { - for (const collection of cf({ dataset }).has(hydra.manages).toArray()) { - const managesBlocks = collection.out(hydra.manages).toArray() +export function * addExplicitStatementsInferredFromMemberAssertion(dataset: DatasetCore): Iterable { + for (const collection of cf({ dataset }).has([hydra.manages, hydra.memberAssertion]).toArray()) { + const managesBlocks = collection.out([hydra.manages, hydra.memberAssertion]).toArray() for (const member of collection.out(hydra.member).toArray()) { for (const managesBlock of managesBlocks) { diff --git a/tests/Resources/HydraResource-spec.ts b/tests/Resources/HydraResource-spec.ts index 574f3ba4..56dba960 100644 --- a/tests/Resources/HydraResource-spec.ts +++ b/tests/Resources/HydraResource-spec.ts @@ -391,7 +391,37 @@ describe('HydraResource', () => { , . - ${hydra.manages} [ + a ${hydra.Collection} ; ${hydra.manages} [ + ${hydra.object} ; + ${hydra.property} ${rdf.type} + ] . + `) + const resource = new HydraResource(cf({ + dataset: await $rdf.dataset().import(resourceGraph), + term: namedNode('http://example.com/'), + })) + + // when + const collections = resource.getCollections({ + object: 'http://example.org/Class', + }) + + // then + expect(collections.length).toBe(1) + expect(collections[0].id.value).toBe('http://example.com/collection1') + }) + + it('returns collections matching member assertion Class given by id', async () => { + // given + const resourceGraph = parse( + turtle` + ${hydra.collection} + , + , + , + . + + a ${hydra.Collection} ; ${hydra.memberAssertion} [ ${hydra.object} ; ${hydra.property} ${rdf.type} ] . diff --git a/tests/Resources/Mixins/ManagesBlock-spec.ts b/tests/Resources/Mixins/MemberAssertion-spec.ts similarity index 90% rename from tests/Resources/Mixins/ManagesBlock-spec.ts rename to tests/Resources/Mixins/MemberAssertion-spec.ts index 9dfb8ffc..ddc08953 100644 --- a/tests/Resources/Mixins/ManagesBlock-spec.ts +++ b/tests/Resources/Mixins/MemberAssertion-spec.ts @@ -4,21 +4,21 @@ import * as Hydra from '@rdfine/hydra' import cf, { GraphPointer } from 'clownface' import $rdf from 'rdf-ext' import { BlankNode, DatasetCore } from 'rdf-js' -import { ManagesBlockMixin } from '../../../src/Resources/Mixins/ManagesBlock' +import { MemberAssertionMixin } from '../../../src/Resources/Mixins/MemberAssertion' import { foaf, hydra, rdf } from '@tpluscode/rdf-ns-builders' -class ManagesBlock extends ManagesBlockMixin(Hydra.ManagesBlockMixin(Resource)) {} +class MemberAssertion extends MemberAssertionMixin(Hydra.MemberAssertionMixin(Resource)) {} -describe('ManagesBlock', () => { +describe('MemberAssertion', () => { let dataset: DatasetCore let node: GraphPointer - let managesBlock: ManagesBlock + let managesBlock: MemberAssertion beforeEach(() => { dataset = $rdf.dataset() node = cf({ dataset }).blankNode() - managesBlock = new ManagesBlock(node) + managesBlock = new MemberAssertion(node) }) describe('shouldApply', () => { @@ -27,7 +27,18 @@ describe('ManagesBlock', () => { node.addIn(hydra.manages, node.blankNode()) // when - const result = ManagesBlockMixin.shouldApply(managesBlock) + const result = MemberAssertionMixin.shouldApply(managesBlock) + + // then + expect(result).toBeTruthy() + }) + + it('should return true if resource is object of hydra:memberAssertion property', () => { + // given + node.addIn(hydra.memberAssertion, node.blankNode()) + + // when + const result = MemberAssertionMixin.shouldApply(managesBlock) // then expect(result).toBeTruthy() diff --git a/tests/Resources/_TestResource.ts b/tests/Resources/_TestResource.ts index 61054c7f..0fcf6e82 100644 --- a/tests/Resources/_TestResource.ts +++ b/tests/Resources/_TestResource.ts @@ -1,7 +1,7 @@ import RdfResource from '@tpluscode/rdfine' import * as Hydra from '@rdfine/hydra' import { Criteria, RecursiveStopConditions } from '../../src/Resources/CoreMixins/OperationFinder' -import { ManagesBlockPattern } from '../../src/Resources/Mixins/ManagesBlock' +import { MemberAssertionPattern } from '../../src/Resources/Mixins/MemberAssertion' import { RuntimeOperation } from '../../src/Resources/Operation' export class Resource extends RdfResource implements Hydra.Resource { @@ -17,7 +17,7 @@ export class Resource extends RdfResource implements Hydra.Resource { return [] } - public getCollections(filter?: ManagesBlockPattern): Resource[] { + public getCollections(filter?: MemberAssertionPattern): Resource[] { return [] } diff --git a/tests/inferences/managesBlock-spec-graphs.ts b/tests/inferences/managesBlock-spec-graphs.ts index 2a5942e2..d663ae01 100644 --- a/tests/inferences/managesBlock-spec-graphs.ts +++ b/tests/inferences/managesBlock-spec-graphs.ts @@ -10,7 +10,7 @@ const managesWithTypeGraph = ` hydra:member , . ` -const multipleManagesBlocksGraph = ` +const multipleMemberAssertionsGraph = ` a hydra:Collection ; hydra:manages [ @@ -23,7 +23,7 @@ const multipleManagesBlocksGraph = ` hydra:member , . ` -const incompleteManagesBlocksGraph = ` +const incompleteMemberAssertionsGraph = ` a hydra:Collection ; hydra:manages [ @@ -36,5 +36,5 @@ const incompleteManagesBlocksGraph = ` hydra:member , .` export const managesWithType = createGraph(managesWithTypeGraph) -export const multipleManagesBlocks = createGraph(multipleManagesBlocksGraph) -export const incompleteManagesBlocks = createGraph(incompleteManagesBlocksGraph) +export const multipleMemberAssertions = createGraph(multipleMemberAssertionsGraph) +export const incompleteMemberAssertions = createGraph(incompleteMemberAssertionsGraph) diff --git a/tests/inferences/managesBlock-spec.ts b/tests/inferences/managesBlock-spec.ts index 8f099228..612a7a27 100644 --- a/tests/inferences/managesBlock-spec.ts +++ b/tests/inferences/managesBlock-spec.ts @@ -1,4 +1,4 @@ -import { addExplicitStatementsInferredFromManagesBlock } from '../../src/inferences' +import { addExplicitStatementsInferredFromMemberAssertion } from '../../src/inferences' import * as specGraphs from './managesBlock-spec-graphs' describe('manages block inference', () => { @@ -7,7 +7,7 @@ describe('manages block inference', () => { const dataset = await specGraphs.managesWithType() // when - dataset.addAll([...addExplicitStatementsInferredFromManagesBlock(dataset)]) + dataset.addAll([...addExplicitStatementsInferredFromMemberAssertion(dataset)]) // then expect(dataset.toCanonical()).toMatchSnapshot() @@ -15,10 +15,10 @@ describe('manages block inference', () => { it('adds triples for multiple manages blocks', async () => { // given - const dataset = await specGraphs.multipleManagesBlocks() + const dataset = await specGraphs.multipleMemberAssertions() // when - dataset.addAll([...addExplicitStatementsInferredFromManagesBlock(dataset)]) + dataset.addAll([...addExplicitStatementsInferredFromMemberAssertion(dataset)]) // then expect(dataset.toCanonical()).toMatchSnapshot() @@ -26,10 +26,10 @@ describe('manages block inference', () => { it('ignores malformed manages blocks', async () => { // given - const dataset = await specGraphs.incompleteManagesBlocks() + const dataset = await specGraphs.incompleteMemberAssertions() // when - dataset.addAll([...addExplicitStatementsInferredFromManagesBlock(dataset)]) + dataset.addAll([...addExplicitStatementsInferredFromMemberAssertion(dataset)]) // then expect(dataset.toCanonical()).toMatchSnapshot() diff --git a/yarn.lock b/yarn.lock index b093cb59..18b1cf41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1464,16 +1464,16 @@ resolved "https://registry.yarnpkg.com/@rdf-esm/to-ntriples/-/to-ntriples-0.5.0.tgz#4a2c294e6d740509571e3b0efde8277c4824b928" integrity sha512-VIcqRv68V/s0NS6bFy58CcsHwV0UCM/DHhAc1MYLB/yue1nyhKsX4uyu/SB5gbbY2r4BIH4G6O+arxf59KzgwQ== -"@rdfine/hydra@^0.6.6": - version "0.6.6" - resolved "https://registry.yarnpkg.com/@rdfine/hydra/-/hydra-0.6.6.tgz#de3174cffb3a8d72d134b39c31e3b3c7d2babce2" - integrity sha512-lKL8H4si9qrdRVP7DXD8FIIwuPElO0Dk4OlWn3WNGBmV3DRKpt1lbOte8DyEmFc+Gfl4zX6s4uLK3llGNomwKw== +"@rdfine/hydra@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@rdfine/hydra/-/hydra-0.7.1.tgz#961382ac0d73ef39ed7faf92974e1727830c4dac" + integrity sha512-7/DZt7O+MuY4KPFGKm3jcWk3/zDexSA9X9VC+B5Amr5lQdh2eJGRupk/Xoz09lLEGnzWK8t2bpLU71kYyTx1Xw== dependencies: "@rdf-esm/data-model" "^0.5.3" "@rdfine/rdf" "^0.5.4" - "@rdfine/rdfs" "^0.6.5" + "@rdfine/rdfs" "^0.6.6" "@tpluscode/rdf-ns-builders" "^1.0.0" - "@tpluscode/rdfine" "^0.5.27" + "@tpluscode/rdfine" "^0.5.29" es6-url-template "^3.0.2" "@rdfine/rdf@^0.5.4": @@ -1485,15 +1485,15 @@ "@tpluscode/rdf-ns-builders" "^1.0.0" "@tpluscode/rdfine" "^0.5.27" -"@rdfine/rdfs@^0.6.5": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@rdfine/rdfs/-/rdfs-0.6.5.tgz#f308f4807a351478a76ee64d34f4602ab0eb31eb" - integrity sha512-CAiMfpqzV1GLm0Le/43sY/S7wxZRFbNNkKw2DmSxAupCDFW5ZBRrnKCqMnO8UhG860uLUwmvk2NIUYhkxMcU9w== +"@rdfine/rdfs@^0.6.6": + version "0.6.6" + resolved "https://registry.yarnpkg.com/@rdfine/rdfs/-/rdfs-0.6.6.tgz#c4f6f9bc801a4a4432e19447e98cf9b17119898f" + integrity sha512-nyOXHOH2dlksApPhtRHYCGRp6r9HM4FdZCLn60VfP9O+6nN5a00k/VteHeax6QT/nkKhiVJk2uNY1CZiymc0Ag== dependencies: "@rdf-esm/data-model" "^0.5.3" "@rdfine/rdf" "^0.5.4" "@tpluscode/rdf-ns-builders" "^1.0.0" - "@tpluscode/rdfine" "^0.5.27" + "@tpluscode/rdfine" "^0.5.29" "@rdfjs/data-model@^1.0.1", "@rdfjs/data-model@^1.1.0", "@rdfjs/data-model@^1.1.1", "@rdfjs/data-model@^1.1.2", "@rdfjs/data-model@^1.2": version "1.2.0" @@ -1671,10 +1671,10 @@ "@tpluscode/rdf-ns-builders" "^1" "@zazuko/rdf-vocabularies" "*" -"@tpluscode/rdfine@^0.5.27": - version "0.5.27" - resolved "https://registry.yarnpkg.com/@tpluscode/rdfine/-/rdfine-0.5.27.tgz#27f9a1630d905bdecb65f79f608424cdb0d87a92" - integrity sha512-Ah/xM1QDMlGtfygtWOYj5wyovfE3Cn8f5LzHtNQz+qMu69XilZMGPnAOY6c/M1dNHGocnq8QPbXwfcnUcxu97A== +"@tpluscode/rdfine@^0.5.27", "@tpluscode/rdfine@^0.5.29": + version "0.5.29" + resolved "https://registry.yarnpkg.com/@tpluscode/rdfine/-/rdfine-0.5.29.tgz#9eec1e36925abc61f18bb537143c3b3d019ae394" + integrity sha512-CKudIrhJ2JmnW2gTmVZOrcjN4niKYV9DxEFoo9hndsMary8CdsYT5avzbXtc2B9YcW62lYBMl609TEETTx64Fw== dependencies: "@rdf-esm/data-model" "^0.5.3" "@rdf-esm/namespace" "^0.5.2"