@noeldemartin/solid-utils 0.1.1-next.8dbff68470e9068fdba6a336302f07e0df1dcd79 → 0.1.1-next.9e1ba757a71d124d509b14f4ed0de95a13e0c196

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,59 +1,95 @@
1
1
  import { uuid } from '@noeldemartin/utils';
2
2
 
3
3
  import { createSolidDocument, fetchSolidDocument, solidDocumentExists, updateSolidDocument } from '@/helpers/io';
4
- import type SolidThing from '@/models/SolidThing';
5
4
  import type { Fetch } from '@/helpers/io';
6
5
  import type { SolidUserProfile } from '@/helpers/auth';
7
6
 
8
- async function mintPrivateTypeIndexUrl(user: SolidUserProfile, fetch?: Fetch): Promise<string> {
7
+ type TypeIndexType = 'public' | 'private';
8
+
9
+ async function mintTypeIndexUrl(user: SolidUserProfile, type: TypeIndexType, fetch?: Fetch): Promise<string> {
9
10
  fetch = fetch ?? window.fetch.bind(fetch);
10
11
 
11
12
  const storageUrl = user.storageUrls[0];
12
- const typeIndexUrl = `${storageUrl}settings/privateTypeIndex`;
13
+ const typeIndexUrl = `${storageUrl}settings/${type}TypeIndex`;
13
14
 
14
15
  return await solidDocumentExists(typeIndexUrl, fetch)
15
- ? `${storageUrl}settings/privateTypeIndex-${uuid()}`
16
+ ? `${storageUrl}settings/${type}TypeIndex-${uuid()}`
16
17
  : typeIndexUrl;
17
18
  }
18
19
 
19
- export async function createPrivateTypeIndex(user: SolidUserProfile, fetch?: Fetch): Promise<string> {
20
+ async function createTypeIndex(user: SolidUserProfile, type: TypeIndexType, fetch?: Fetch) {
21
+ if (user.writableProfileUrl === null) {
22
+ throw new Error('Can\'t create type index without a writable profile document');
23
+ }
24
+
20
25
  fetch = fetch ?? window.fetch.bind(fetch);
21
26
 
22
- const typeIndexUrl = await mintPrivateTypeIndexUrl(user, fetch);
23
- const typeIndexBody = `
24
- <> a
25
- <http://www.w3.org/ns/solid/terms#TypeIndex>,
26
- <http://www.w3.org/ns/solid/terms#UnlistedDocument> .
27
- `;
27
+ const typeIndexUrl = await mintTypeIndexUrl(user, type, fetch);
28
+ const typeIndexBody = type === 'public'
29
+ ? '<> a <http://www.w3.org/ns/solid/terms#TypeIndex> .'
30
+ : `
31
+ <> a
32
+ <http://www.w3.org/ns/solid/terms#TypeIndex>,
33
+ <http://www.w3.org/ns/solid/terms#UnlistedDocument> .
34
+ `;
28
35
  const profileUpdateBody = `
29
36
  INSERT DATA {
30
- <${user.webId}> <http://www.w3.org/ns/solid/terms#privateTypeIndex> <${typeIndexUrl}> .
37
+ <${user.webId}> <http://www.w3.org/ns/solid/terms#${type}TypeIndex> <${typeIndexUrl}> .
31
38
  }
32
39
  `;
33
40
 
34
41
  await Promise.all([
35
42
  createSolidDocument(typeIndexUrl, typeIndexBody, fetch),
36
- updateSolidDocument(user.webId, profileUpdateBody, fetch),
43
+ updateSolidDocument(user.writableProfileUrl, profileUpdateBody, fetch),
37
44
  ]);
38
45
 
46
+ if (type === 'public') {
47
+ // TODO Implement updating ACLs for the listing itself to public
48
+ }
49
+
39
50
  return typeIndexUrl;
40
51
  }
41
52
 
42
- export async function findContainerRegistration(
53
+ async function findRegistrations(
43
54
  typeIndexUrl: string,
44
- childrenType: string,
55
+ type: string | string[],
56
+ predicate: string,
45
57
  fetch?: Fetch,
46
- ): Promise<SolidThing | null> {
58
+ ): Promise<string[]> {
47
59
  const typeIndex = await fetchSolidDocument(typeIndexUrl, fetch);
48
- const containerQuad = typeIndex
49
- .statements(undefined, 'rdfs:type', 'solid:TypeRegistration')
50
- .find(
51
- statement =>
52
- typeIndex.contains(statement.subject.value, 'solid:forClass', childrenType) &&
53
- typeIndex.contains(statement.subject.value, 'solid:instanceContainer'),
54
- );
55
-
56
- return containerQuad
57
- ? typeIndex.getThing(containerQuad.subject.value) ?? null
58
- : null;
60
+ const types = Array.isArray(type) ? type : [type];
61
+
62
+ return types.map(
63
+ type => typeIndex
64
+ .statements(undefined, 'rdf:type', 'solid:TypeRegistration')
65
+ .filter(statement => typeIndex.contains(statement.subject.value, 'solid:forClass', type))
66
+ .map(statement => typeIndex.statements(statement.subject.value, predicate))
67
+ .flat()
68
+ .map(statement => statement.object.value)
69
+ .filter(url => !!url),
70
+ ).flat();
71
+ }
72
+
73
+ export async function createPublicTypeIndex(user: SolidUserProfile, fetch?: Fetch): Promise<string> {
74
+ return createTypeIndex(user, 'public', fetch);
75
+ }
76
+
77
+ export async function createPrivateTypeIndex(user: SolidUserProfile, fetch?: Fetch): Promise<string> {
78
+ return createTypeIndex(user, 'private', fetch);
79
+ }
80
+
81
+ export async function findContainerRegistrations(
82
+ typeIndexUrl: string,
83
+ type: string | string[],
84
+ fetch?: Fetch,
85
+ ): Promise<string[]> {
86
+ return findRegistrations(typeIndexUrl, type, 'solid:instanceContainer', fetch);
87
+ }
88
+
89
+ export async function findInstanceRegistrations(
90
+ typeIndexUrl: string,
91
+ type: string | string[],
92
+ fetch?: Fetch,
93
+ ): Promise<string[]> {
94
+ return findRegistrations(typeIndexUrl, type, 'solid:instance', fetch);
59
95
  }
package/src/helpers/io.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  import md5 from 'md5';
2
+ import {
3
+ TurtleParser,
4
+ TurtleWriter,
5
+ createBlankNode,
6
+ createQuad,
7
+ jsonLDFromRDF,
8
+ jsonLDToRDF,
9
+ } from '@noeldemartin/solid-utils-external';
2
10
  import { arr, arrayFilter, arrayReplace, objectWithoutEmpty, stringMatchAll, tap } from '@noeldemartin/utils';
3
- import { BlankNode as N3BlankNode, Quad as N3Quad, Parser as TurtleParser, Writer as TurtleWriter } from 'n3';
4
- import { fromRDF, toRDF } from 'jsonld';
5
- import type { JsonLdDocument } from 'jsonld';
6
11
  import type { Quad } from 'rdf-js';
7
- import type { Term as N3Term } from 'n3';
12
+ import type { JsonLdDocument, Term } from '@noeldemartin/solid-utils-external';
8
13
 
9
14
  import SolidDocument from '@/models/SolidDocument';
10
15
 
@@ -84,25 +89,25 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
84
89
 
85
90
  for (const index of quadIndexes) {
86
91
  const quad = normalizedQuads[index] as Quad;
87
- const terms: Record<string, N3Term> = {
88
- subject: quad.subject as N3Term,
89
- object: quad.object as N3Term,
92
+ const terms: Record<string, Term> = {
93
+ subject: quad.subject as Term,
94
+ object: quad.object as Term,
90
95
  };
91
96
 
92
97
  for (const [termName, termValue] of Object.entries(terms)) {
93
98
  if (termValue.termType !== 'BlankNode' || termValue.value !== originalId)
94
99
  continue;
95
100
 
96
- terms[termName] = new N3BlankNode(normalizedId);
101
+ terms[termName] = createBlankNode(normalizedId) as Term;
97
102
  }
98
103
 
99
104
  arrayReplace(
100
105
  normalizedQuads,
101
106
  quad,
102
- new N3Quad(
103
- terms.subject as N3Term,
104
- quad.predicate as N3Term,
105
- terms.object as N3Term,
107
+ createQuad(
108
+ terms.subject as Term,
109
+ quad.predicate as Term,
110
+ terms.object as Term,
106
111
  ),
107
112
  );
108
113
  }
@@ -112,10 +117,15 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
112
117
  }
113
118
 
114
119
  export interface ParsingOptions {
115
- documentUrl: string;
120
+ baseIRI: string;
116
121
  normalizeBlankNodes: boolean;
117
122
  }
118
123
 
124
+ export interface RDFGraphData {
125
+ quads: Quad[];
126
+ containsRelativeIRIs: boolean;
127
+ }
128
+
119
129
  export async function createSolidDocument(url: string, body: string, fetch?: Fetch): Promise<SolidDocument> {
120
130
  fetch = fetch ?? window.fetch.bind(window);
121
131
 
@@ -132,7 +142,7 @@ export async function createSolidDocument(url: string, body: string, fetch?: Fet
132
142
 
133
143
  export async function fetchSolidDocument(url: string, fetch?: Fetch): Promise<SolidDocument> {
134
144
  const { body: data, headers } = await fetchRawSolidDocument(url, fetch ?? window.fetch);
135
- const statements = await turtleToQuads(data, { documentUrl: url });
145
+ const statements = await turtleToQuads(data, { baseIRI: url });
136
146
 
137
147
  return new SolidDocument(url, statements, headers);
138
148
  }
@@ -150,14 +160,14 @@ export async function fetchSolidDocumentIfFound(url: string, fetch?: Fetch): Pro
150
160
  }
151
161
  }
152
162
 
153
- export async function jsonldToQuads(jsonld: JsonLD): Promise<Quad[]> {
163
+ export async function jsonldToQuads(jsonld: JsonLD, baseIRI?: string): Promise<Quad[]> {
154
164
  if (isJsonLDGraph(jsonld)) {
155
- const graphQuads = await Promise.all(jsonld['@graph'].map(jsonldToQuads));
165
+ const graphQuads = await Promise.all(jsonld['@graph'].map(resource => jsonldToQuads(resource, baseIRI)));
156
166
 
157
167
  return graphQuads.flat();
158
168
  }
159
169
 
160
- return toRDF(jsonld as JsonLdDocument) as Promise<Quad[]>;
170
+ return jsonLDToRDF(jsonld as JsonLdDocument, { base: baseIRI }) as Promise<Quad[]>;
161
171
  }
162
172
 
163
173
  export function normalizeSparql(sparql: string): string {
@@ -173,8 +183,54 @@ export function normalizeSparql(sparql: string): string {
173
183
  .join(' ;\n');
174
184
  }
175
185
 
186
+ export function parseTurtle(turtle: string, options: Partial<ParsingOptions> = {}): Promise<RDFGraphData> {
187
+ const parserOptions = objectWithoutEmpty({ baseIRI: options.baseIRI });
188
+ const parser = new TurtleParser(parserOptions);
189
+ const data: RDFGraphData = {
190
+ quads: [],
191
+ containsRelativeIRIs: false,
192
+ };
193
+
194
+ return new Promise((resolve, reject) => {
195
+ const resolveRelativeIRI = parser._resolveRelativeIRI;
196
+
197
+ parser._resolveRelativeIRI = (...args) => {
198
+ data.containsRelativeIRIs = true;
199
+ parser._resolveRelativeIRI = resolveRelativeIRI;
200
+
201
+ return parser._resolveRelativeIRI(...args);
202
+ };
203
+
204
+ parser.parse(turtle, (error, quad) => {
205
+ if (error) {
206
+ reject(
207
+ new MalformedSolidDocumentError(
208
+ options.baseIRI ?? null,
209
+ SolidDocumentFormat.Turtle,
210
+ error.message,
211
+ ),
212
+ );
213
+
214
+ return;
215
+ }
216
+
217
+ if (!quad) {
218
+ // data.quads = options.normalizeBlankNodes
219
+ // ? normalizeBlankNodes(data.quads)
220
+ // : data.quads;
221
+
222
+ resolve(data);
223
+
224
+ return;
225
+ }
226
+
227
+ data.quads.push(quad);
228
+ });
229
+ });
230
+ }
231
+
176
232
  export async function quadsToJsonLD(quads: Quad[]): Promise<JsonLDGraph> {
177
- const graph = await fromRDF(quads);
233
+ const graph = await jsonLDFromRDF(quads);
178
234
 
179
235
  return {
180
236
  '@graph': graph as JsonLDResource[],
@@ -235,38 +291,13 @@ export function sparqlToQuadsSync(sparql: string, options: Partial<ParsingOption
235
291
  }
236
292
 
237
293
  export async function turtleToQuads(turtle: string, options: Partial<ParsingOptions> = {}): Promise<Quad[]> {
238
- const parserOptions = objectWithoutEmpty({ baseIRI: options.documentUrl });
239
- const parser = new TurtleParser(parserOptions);
240
- const quads: Quad[] = [];
241
-
242
- return new Promise((resolve, reject) => {
243
- parser.parse(turtle, (error, quad) => {
244
- if (error) {
245
- reject(
246
- new MalformedSolidDocumentError(
247
- options.documentUrl ?? null,
248
- SolidDocumentFormat.Turtle,
249
- error.message,
250
- ),
251
- );
252
- return;
253
- }
254
-
255
- if (!quad) {
256
- options.normalizeBlankNodes
257
- ? resolve(normalizeBlankNodes(quads))
258
- : resolve(quads);
294
+ const { quads } = await parseTurtle(turtle, options);
259
295
 
260
- return;
261
- }
262
-
263
- quads.push(quad);
264
- });
265
- });
296
+ return quads;
266
297
  }
267
298
 
268
299
  export function turtleToQuadsSync(turtle: string, options: Partial<ParsingOptions> = {}): Quad[] {
269
- const parserOptions = objectWithoutEmpty({ baseIRI: options.documentUrl });
300
+ const parserOptions = objectWithoutEmpty({ baseIRI: options.baseIRI });
270
301
  const parser = new TurtleParser(parserOptions);
271
302
 
272
303
  try {
@@ -277,7 +308,7 @@ export function turtleToQuadsSync(turtle: string, options: Partial<ParsingOption
277
308
  : quads;
278
309
  } catch (error) {
279
310
  throw new MalformedSolidDocumentError(
280
- options.documentUrl ?? null,
311
+ options.baseIRI ?? null,
281
312
  SolidDocumentFormat.Turtle,
282
313
  (error as Error).message ?? '',
283
314
  );
@@ -1,3 +1,6 @@
1
+ import { compactJsonLD } from '@noeldemartin/solid-utils-external';
2
+ import type { JsonLdDocument } from '@noeldemartin/solid-utils-external';
3
+
1
4
  export type JsonLD = Partial<{
2
5
  '@context': Record<string, unknown>;
3
6
  '@id': string;
@@ -7,6 +10,20 @@ export type JsonLD = Partial<{
7
10
  export type JsonLDResource = Omit<JsonLD, '@id'> & { '@id': string };
8
11
  export type JsonLDGraph = { '@graph': JsonLDResource[] };
9
12
 
13
+ export async function compactJsonLDGraph(jsonld: JsonLDGraph): Promise<JsonLDGraph> {
14
+ const compactedJsonLD = await compactJsonLD(jsonld as JsonLdDocument, {});
15
+
16
+ if ('@graph' in compactedJsonLD) {
17
+ return compactedJsonLD as JsonLDGraph;
18
+ }
19
+
20
+ if ('@id' in compactedJsonLD) {
21
+ return { '@graph': [compactedJsonLD] } as JsonLDGraph;
22
+ }
23
+
24
+ return { '@graph': [] };
25
+ }
26
+
10
27
  export function isJsonLDGraph(jsonld: JsonLD): jsonld is JsonLDGraph {
11
28
  return '@graph' in jsonld;
12
29
  }
@@ -1,4 +1,4 @@
1
- import { Error, arrayRemove, pull, stringMatchAll } from '@noeldemartin/utils';
1
+ import { JSError, arrayRemove, pull, stringMatchAll } from '@noeldemartin/utils';
2
2
  import type { JsonLD } from '@/helpers/jsonld';
3
3
  import type { Quad, Quad_Object } from 'rdf-js';
4
4
 
@@ -9,7 +9,7 @@ const builtInPatterns: Record<string, string> = {
9
9
  '%uuid%': '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
10
10
  };
11
11
 
12
- class ExpectedQuadAssertionError extends Error {
12
+ class ExpectedQuadAssertionError extends JSError {
13
13
 
14
14
  constructor(public readonly expectedQuad: Quad) {
15
15
  super(`Couldn't find the following triple: ${quadToTurtle(expectedQuad)}`);
@@ -10,7 +10,8 @@ const knownPrefixes: RDFContext = {
10
10
  foaf: 'http://xmlns.com/foaf/0.1/',
11
11
  pim: 'http://www.w3.org/ns/pim/space#',
12
12
  purl: 'http://purl.org/dc/terms/',
13
- rdfs: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
13
+ rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
14
+ rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
14
15
  schema: 'https://schema.org/',
15
16
  solid: 'http://www.w3.org/ns/solid/terms#',
16
17
  vcard: 'http://www.w3.org/2006/vcard/ns#',
@@ -1,4 +1,4 @@
1
- import { objectWithoutEmpty, requireUrlParentDirectory } from '@noeldemartin/utils';
1
+ import { objectWithoutEmpty, requireUrlParentDirectory, urlResolve } from '@noeldemartin/utils';
2
2
 
3
3
  import { fetchSolidDocumentIfFound } from '@/helpers/io';
4
4
  import type SolidDocument from '@/models/SolidDocument';
@@ -11,7 +11,11 @@ async function fetchACLResourceUrl(resourceUrl: string, fetch: Fetch): Promise<s
11
11
  const linkHeader = resourceHead.headers.get('Link') ?? '';
12
12
  const url = linkHeader.match(/<([^>]+)>;\s*rel="acl"/)?.[1] ?? null;
13
13
 
14
- return url ?? fail(`Could not find ACL Resource for '${resourceUrl}'`);
14
+ if (!url) {
15
+ throw new Error(`Could not find ACL Resource for '${resourceUrl}'`);
16
+ }
17
+
18
+ return urlResolve(requireUrlParentDirectory(resourceUrl), url);
15
19
  }
16
20
 
17
21
  async function fetchEffectiveACL(
@@ -1,30 +1,33 @@
1
- import { parseDate } from '@noeldemartin/utils';
1
+ import { arrayFilter, parseDate, stringMatch } from '@noeldemartin/utils';
2
2
  import type { Quad } from 'rdf-js';
3
3
 
4
4
  import { expandIRI } from '@/helpers/vocabs';
5
5
 
6
- import SolidThing from './SolidThing';
6
+ import SolidStore from './SolidStore';
7
7
 
8
- export default class SolidDocument {
8
+ export enum SolidDocumentPermission {
9
+ Read = 'read',
10
+ Write = 'write',
11
+ Append = 'append',
12
+ Control = 'control',
13
+ }
14
+
15
+ export default class SolidDocument extends SolidStore {
9
16
 
10
17
  public readonly url: string;
11
18
  public readonly headers: Headers;
12
- private quads: Quad[];
13
19
 
14
20
  public constructor(url: string, quads: Quad[], headers: Headers) {
21
+ super(quads);
22
+
15
23
  this.url = url;
16
- this.quads = quads;
17
24
  this.headers = headers;
18
25
  }
19
26
 
20
- public isEmpty(): boolean {
21
- return this.statements.length === 0;
22
- }
23
-
24
27
  public isPersonalProfile(): boolean {
25
28
  return !!this.statement(
26
29
  this.url,
27
- expandIRI('rdfs:type'),
30
+ expandIRI('rdf:type'),
28
31
  expandIRI('foaf:PersonalProfileDocument'),
29
32
  );
30
33
  }
@@ -33,41 +36,27 @@ export default class SolidDocument {
33
36
  return !!this.headers.get('Link')?.match(/<http:\/\/www\.w3\.org\/ns\/pim\/space#Storage>;[^,]+rel="type"/);
34
37
  }
35
38
 
36
- public getLastModified(): Date | null {
37
- return parseDate(this.headers.get('last-modified'))
38
- ?? parseDate(this.statement(this.url, 'purl:modified')?.object.value)
39
- ?? this.getLatestDocumentDate()
40
- ?? null;
39
+ public isUserWritable(): boolean {
40
+ return this.getUserPermissions().includes(SolidDocumentPermission.Write);
41
41
  }
42
42
 
43
- public statements(subject?: string, predicate?: string, object?: string): Quad[] {
44
- return this.quads.filter(
45
- statement =>
46
- (!object || statement.object.value === expandIRI(object, { defaultPrefix: this.url })) &&
47
- (!subject || statement.subject.value === expandIRI(subject, { defaultPrefix: this.url })) &&
48
- (!predicate || statement.predicate.value === expandIRI(predicate, { defaultPrefix: this.url })),
49
- );
43
+ public getUserPermissions(): SolidDocumentPermission[] {
44
+ return this.getPermissionsFromWAC('user');
50
45
  }
51
46
 
52
- public statement(subject?: string, predicate?: string, object?: string): Quad | null {
53
- const statement = this.quads.find(
54
- statement =>
55
- (!object || statement.object.value === expandIRI(object, { defaultPrefix: this.url })) &&
56
- (!subject || statement.subject.value === expandIRI(subject, { defaultPrefix: this.url })) &&
57
- (!predicate || statement.predicate.value === expandIRI(predicate, { defaultPrefix: this.url })),
58
- );
59
-
60
- return statement ?? null;
47
+ public getPublicPermissions(): SolidDocumentPermission[] {
48
+ return this.getPermissionsFromWAC('public');
61
49
  }
62
50
 
63
- public contains(subject: string, predicate?: string, object?: string): boolean {
64
- return this.statement(subject, predicate, object) !== null;
51
+ public getLastModified(): Date | null {
52
+ return parseDate(this.headers.get('last-modified'))
53
+ ?? parseDate(this.statement(this.url, 'purl:modified')?.object.value)
54
+ ?? this.getLatestDocumentDate()
55
+ ?? null;
65
56
  }
66
57
 
67
- public getThing(subject: string): SolidThing {
68
- const statements = this.statements(subject);
69
-
70
- return new SolidThing(subject, statements);
58
+ protected expandIRI(iri: string): string {
59
+ return expandIRI(iri, { defaultPrefix: this.url });
71
60
  }
72
61
 
73
62
  private getLatestDocumentDate(): Date | null {
@@ -81,4 +70,16 @@ export default class SolidDocument {
81
70
  return dates.length > 0 ? dates.reduce((a, b) => a > b ? a : b) : null;
82
71
  }
83
72
 
73
+ private getPermissionsFromWAC(type: string): SolidDocumentPermission[] {
74
+ const wacAllow = this.headers.get('WAC-Allow') ?? '';
75
+ const publicModes = stringMatch<2>(wacAllow, new RegExp(`${type}="([^"]+)"`))?.[1] ?? '';
76
+
77
+ return arrayFilter([
78
+ publicModes.includes('read') && SolidDocumentPermission.Read,
79
+ publicModes.includes('write') && SolidDocumentPermission.Write,
80
+ publicModes.includes('append') && SolidDocumentPermission.Append,
81
+ publicModes.includes('control') && SolidDocumentPermission.Control,
82
+ ]);
83
+ }
84
+
84
85
  }
@@ -0,0 +1,61 @@
1
+ import type { Quad } from 'rdf-js';
2
+
3
+ import { expandIRI } from '@/helpers/vocabs';
4
+
5
+ import SolidThing from './SolidThing';
6
+
7
+ export default class SolidStore {
8
+
9
+ private quads: Quad[];
10
+
11
+ public constructor(quads: Quad[] = []) {
12
+ this.quads = quads;
13
+ }
14
+
15
+ public isEmpty(): boolean {
16
+ return this.statements.length === 0;
17
+ }
18
+
19
+ public getQuads(): Quad[] {
20
+ return this.quads.slice(0);
21
+ }
22
+
23
+ public addQuads(quads: Quad[]): void {
24
+ this.quads.push(...quads);
25
+ }
26
+
27
+ public statements(subject?: string, predicate?: string, object?: string): Quad[] {
28
+ return this.quads.filter(
29
+ statement =>
30
+ (!object || statement.object.value === this.expandIRI(object)) &&
31
+ (!subject || statement.subject.value === this.expandIRI(subject)) &&
32
+ (!predicate || statement.predicate.value === this.expandIRI(predicate)),
33
+ );
34
+ }
35
+
36
+ public statement(subject?: string, predicate?: string, object?: string): Quad | null {
37
+ const statement = this.quads.find(
38
+ statement =>
39
+ (!object || statement.object.value === this.expandIRI(object)) &&
40
+ (!subject || statement.subject.value === this.expandIRI(subject)) &&
41
+ (!predicate || statement.predicate.value === this.expandIRI(predicate)),
42
+ );
43
+
44
+ return statement ?? null;
45
+ }
46
+
47
+ public contains(subject: string, predicate?: string, object?: string): boolean {
48
+ return this.statement(subject, predicate, object) !== null;
49
+ }
50
+
51
+ public getThing(subject: string): SolidThing {
52
+ const statements = this.statements(subject);
53
+
54
+ return new SolidThing(subject, statements);
55
+ }
56
+
57
+ protected expandIRI(iri: string): string {
58
+ return expandIRI(iri);
59
+ }
60
+
61
+ }
@@ -1,2 +1,3 @@
1
- export { default as SolidDocument } from './SolidDocument';
1
+ export { default as SolidDocument, SolidDocumentPermission } from './SolidDocument';
2
+ export { default as SolidStore } from './SolidStore';
2
3
  export { default as SolidThing } from './SolidThing';
@@ -16,17 +16,15 @@ function formatResult(result: EqualityResult, options: FormatResultOptions) {
16
16
  ? () => [
17
17
  result.message,
18
18
  utils.matcherHint(options.hint),
19
+ ].join('\n\n')
20
+ : () => [
21
+ result.message,
22
+ utils.matcherHint(options.hint),
19
23
  [
20
24
  `Expected: not ${utils.printExpected(options.expected)}`,
21
25
  `Received: ${utils.printReceived(options.received)}`,
22
26
  ].join('\n'),
23
- ].join('\n\n')
24
- : () => {
25
- return [
26
- result.message,
27
- utils.matcherHint(options.hint),
28
- ].join('\n\n');
29
- };
27
+ ].join('\n\n');
30
28
 
31
29
  return { pass, message };
32
30
  }
@@ -0,0 +1,9 @@
1
+ import '@types/n3';
2
+
3
+ declare module 'n3' {
4
+
5
+ interface Parser {
6
+ _resolveRelativeIRI(iri: string): string;
7
+ }
8
+
9
+ }