Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/solid/ContainerDataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,24 @@ import { Container } from "./Container.js"
import { LDP } from "../vocabulary/mod.js"

export class ContainerDataset extends DatasetWrapper {
// TODO: Consider that this might be undefined if there are no contained resources. We might need different matching.

get container(): Container | undefined {
// Return the first container in the dataset
for (const s of this.subjectsOf(LDP.contains, Container)) {
return s
}

// If the container is empty, then the only instance of `ldp:Container`
// and `ldp:BasicContainer` in this dataset should be the container
// itself.
for (const s of this.instancesOf(LDP.Container, Container)) {
return s
}

for (const s of this.instancesOf(LDP.BasicContainer, Container)) {
return s
}

return
}
}
2 changes: 2 additions & 0 deletions src/vocabulary/ldp.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const LDP = {
contains: "http://www.w3.org/ns/ldp#contains",
Container: "http://www.w3.org/ns/ldp#Container",
BasicContainer: "http://www.w3.org/ns/ldp#BasicContainer",
} as const;
50 changes: 50 additions & 0 deletions test/unit/container.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { DataFactory, Parser, Store } from "n3";
import assert from "node:assert";
import { describe, it } from "node:test";
import { ContainerDataset } from "@solid/object";

describe("ContainerDataset", () => {
const emptyContainerRDF = `
@prefix dc: <http://purl.org/dc/terms/>.
@prefix ldp: <http://www.w3.org/ns/ldp#>.
@prefix posix: <http://www.w3.org/ns/posix/stat#>.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
<> a ldp:Container, ldp:BasicContainer, ldp:Resource;
dc:modified "2026-07-23T10:41:12.846Z"^^xsd:dateTime;
posix:mtime 1784803272.
`;

const nonEmptyContainerRDF = `
@prefix ldp: <http://www.w3.org/ns/ldp#>.
<https://pod.example/container/>
a ldp:Container;
ldp:contains <https://pod.example/container/file.txt> .
<https://pod.example/container/file.txt>
a ldp:Resource .
`;

it("resolves an empty container via rdf:type when ldp:contains is absent", () => {
const store = new Store();
const parser = new Parser({ baseIRI: "https://pod.example/empty/" });
store.addQuads(parser.parse(emptyContainerRDF));
const dataset = new ContainerDataset(store, DataFactory);
const container = dataset.container;
assert.ok(container !== undefined);
assert.equal(container.id, "https://pod.example/empty/");
assert.equal(container.contains.size, 0);
});

it("still resolves a non-empty container via ldp:contains", () => {
const store = new Store();
store.addQuads(new Parser().parse(nonEmptyContainerRDF));
const dataset = new ContainerDataset(store, DataFactory);
const container = dataset.container;
assert.ok(container !== undefined);
assert.equal(container.id, "https://pod.example/container/");
assert.equal(container.contains.size, 1);
assert.equal(
container.contains.values().next().value?.id,
"https://pod.example/container/file.txt",
);
});
});