@noeldemartin/solid-utils 0.5.0 → 0.6.0-next.3e3ceb79b047f4ec87a416c2f920a13eda7a0df1

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.
Files changed (55) hide show
  1. package/dist/io-CMHtz5bu.js +401 -0
  2. package/dist/io-CMHtz5bu.js.map +1 -0
  3. package/dist/noeldemartin-solid-utils.d.ts +13 -65
  4. package/dist/noeldemartin-solid-utils.js +224 -0
  5. package/dist/noeldemartin-solid-utils.js.map +1 -0
  6. package/dist/testing.d.ts +45 -0
  7. package/dist/testing.js +157 -0
  8. package/dist/testing.js.map +1 -0
  9. package/package.json +61 -63
  10. package/src/errors/UnauthorizedError.ts +1 -3
  11. package/src/errors/UnsuccessfulNetworkRequestError.ts +2 -2
  12. package/src/helpers/auth.test.ts +221 -0
  13. package/src/helpers/auth.ts +28 -27
  14. package/src/helpers/identifiers.test.ts +76 -0
  15. package/src/helpers/identifiers.ts +14 -17
  16. package/src/helpers/index.ts +0 -1
  17. package/src/helpers/interop.ts +23 -16
  18. package/src/helpers/io.test.ts +228 -0
  19. package/src/helpers/io.ts +57 -77
  20. package/src/helpers/jsonld.ts +6 -6
  21. package/src/helpers/vocabs.ts +3 -6
  22. package/src/helpers/wac.test.ts +64 -0
  23. package/src/helpers/wac.ts +10 -6
  24. package/src/index.ts +3 -0
  25. package/src/models/SolidDocument.test.ts +77 -0
  26. package/src/models/SolidDocument.ts +14 -18
  27. package/src/models/SolidStore.ts +22 -12
  28. package/src/models/SolidThing.ts +5 -7
  29. package/src/models/index.ts +2 -0
  30. package/src/{helpers/testing.ts → testing/helpers.ts} +24 -27
  31. package/src/testing/hepers.test.ts +329 -0
  32. package/src/testing/index.ts +2 -2
  33. package/src/testing/vitest/index.ts +13 -0
  34. package/src/testing/vitest/matchers.ts +68 -0
  35. package/src/types/n3.d.ts +0 -2
  36. package/.github/workflows/ci.yml +0 -16
  37. package/.nvmrc +0 -1
  38. package/CHANGELOG.md +0 -70
  39. package/dist/noeldemartin-solid-utils.cjs.js +0 -2
  40. package/dist/noeldemartin-solid-utils.cjs.js.map +0 -1
  41. package/dist/noeldemartin-solid-utils.esm.js +0 -2
  42. package/dist/noeldemartin-solid-utils.esm.js.map +0 -1
  43. package/dist/noeldemartin-solid-utils.umd.js +0 -90
  44. package/dist/noeldemartin-solid-utils.umd.js.map +0 -1
  45. package/noeldemartin.config.js +0 -9
  46. package/src/main.ts +0 -5
  47. package/src/plugins/chai/assertions.ts +0 -40
  48. package/src/plugins/chai/index.ts +0 -5
  49. package/src/plugins/cypress/types.d.ts +0 -15
  50. package/src/plugins/index.ts +0 -2
  51. package/src/plugins/jest/index.ts +0 -5
  52. package/src/plugins/jest/matchers.ts +0 -65
  53. package/src/plugins/jest/types.d.ts +0 -14
  54. package/src/testing/ResponseStub.ts +0 -46
  55. package/src/testing/mocking.ts +0 -33
@@ -0,0 +1,228 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { jsonldToQuads, normalizeSparql, quadsToJsonLD, sparqlToQuadsSync, turtleToQuadsSync } from './io';
4
+ import type { Quad } from '@rdfjs/types';
5
+
6
+ describe('IO', () => {
7
+
8
+ it('parses jsonld', async () => {
9
+ // Arrange
10
+ const jsonld = {
11
+ '@context': { '@vocab': 'https://schema.org/' },
12
+ '@type': 'Movie',
13
+ 'name': 'Spirited Away',
14
+ };
15
+
16
+ // Act
17
+ const quads = (await jsonldToQuads(jsonld)) as [Quad, Quad];
18
+
19
+ // Assert
20
+ expect(quads).toHaveLength(2);
21
+
22
+ expect(quads[0].subject.termType).toEqual('BlankNode');
23
+ expect(quads[0].predicate.termType).toEqual('NamedNode');
24
+ expect(quads[0].predicate.value).toEqual('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
25
+ expect(quads[0].object.termType).toEqual('NamedNode');
26
+ expect(quads[0].object.value).toEqual('https://schema.org/Movie');
27
+
28
+ expect(quads[1].subject.termType).toEqual('BlankNode');
29
+ expect(quads[1].predicate.termType).toEqual('NamedNode');
30
+ expect(quads[1].predicate.value).toEqual('https://schema.org/name');
31
+ expect(quads[1].object.termType).toEqual('Literal');
32
+ expect(quads[1].object.value).toEqual('Spirited Away');
33
+ });
34
+
35
+ it('parses jsonld with anonymous subjects', async () => {
36
+ // Arrange
37
+ const jsonld = {
38
+ '@context': { '@vocab': 'https://schema.org/' },
39
+ '@id': '#it',
40
+ '@type': 'Movie',
41
+ 'name': 'Spirited Away',
42
+ };
43
+
44
+ // Act
45
+ const quads = (await jsonldToQuads(jsonld)) as [Quad, Quad];
46
+
47
+ // Assert
48
+ expect(quads).toHaveLength(2);
49
+
50
+ expect(quads[0].subject.termType).toEqual('NamedNode');
51
+ expect(quads[0].subject.value).toEqual('#it');
52
+ expect(quads[0].predicate.termType).toEqual('NamedNode');
53
+ expect(quads[0].predicate.value).toEqual('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
54
+ expect(quads[0].object.termType).toEqual('NamedNode');
55
+ expect(quads[0].object.value).toEqual('https://schema.org/Movie');
56
+
57
+ expect(quads[1].subject.termType).toEqual('NamedNode');
58
+ expect(quads[1].subject.value).toEqual('#it');
59
+ expect(quads[1].predicate.termType).toEqual('NamedNode');
60
+ expect(quads[1].predicate.value).toEqual('https://schema.org/name');
61
+ expect(quads[1].object.termType).toEqual('Literal');
62
+ expect(quads[1].object.value).toEqual('Spirited Away');
63
+ });
64
+
65
+ it('parses jsonld graphs', async () => {
66
+ // Arrange
67
+ const jsonld = {
68
+ '@graph': [
69
+ {
70
+ '@context': { '@vocab': 'https://schema.org/' },
71
+ '@id': 'solid://movies/spirited-away',
72
+ '@type': 'Movie',
73
+ 'name': 'Spirited Away',
74
+ },
75
+ {
76
+ '@context': { '@vocab': 'https://schema.org/' },
77
+ '@id': 'solid://movies/spirited-away',
78
+ '@type': 'Movie',
79
+ 'name': 'Spirited Away',
80
+ },
81
+ ],
82
+ };
83
+
84
+ // Act
85
+ const quads = await jsonldToQuads(jsonld);
86
+
87
+ // Assert
88
+ expect(quads).toHaveLength(4);
89
+
90
+ [0, 2].forEach((index) => {
91
+ expect(quads[index]?.subject.termType).toEqual('NamedNode');
92
+ expect(quads[index]?.subject.value).toEqual('solid://movies/spirited-away');
93
+ expect(quads[index]?.predicate.termType).toEqual('NamedNode');
94
+ expect(quads[index]?.predicate.value).toEqual('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
95
+ expect(quads[index]?.object.termType).toEqual('NamedNode');
96
+ expect(quads[index]?.object.value).toEqual('https://schema.org/Movie');
97
+ });
98
+
99
+ [1, 3].forEach((index) => {
100
+ expect(quads[index]?.subject.termType).toEqual('NamedNode');
101
+ expect(quads[index]?.subject.value).toEqual('solid://movies/spirited-away');
102
+ expect(quads[index]?.predicate.termType).toEqual('NamedNode');
103
+ expect(quads[index]?.predicate.value).toEqual('https://schema.org/name');
104
+ expect(quads[index]?.object.termType).toEqual('Literal');
105
+ expect(quads[index]?.object.value).toEqual('Spirited Away');
106
+ });
107
+ });
108
+
109
+ it('normalizes sparql', () => {
110
+ // Arrange
111
+ const insertTurtle = `
112
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
113
+
114
+ <#me>
115
+ foaf:name "Amy" ;
116
+ foaf:lastName "Doe" .
117
+ `;
118
+ const deleteTurtle = `
119
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
120
+
121
+ <#me> foaf:name "John Doe" .
122
+ `;
123
+ const sparql = `
124
+ INSERT DATA { ${insertTurtle} } ;
125
+ DELETE DATA { ${deleteTurtle} }
126
+ `;
127
+
128
+ // Act
129
+ const normalized = normalizeSparql(sparql);
130
+
131
+ // Assert
132
+ expect(normalized).toEqual(
133
+ [
134
+ 'INSERT DATA {',
135
+ ' <#me> <http://xmlns.com/foaf/0.1/lastName> "Doe" .',
136
+ ' <#me> <http://xmlns.com/foaf/0.1/name> "Amy" .',
137
+ '} ;',
138
+ 'DELETE DATA {',
139
+ ' <#me> <http://xmlns.com/foaf/0.1/name> "John Doe" .',
140
+ '}',
141
+ ].join('\n'),
142
+ );
143
+ });
144
+
145
+ it('parses sparql', () => {
146
+ // Arrange
147
+ const insertTurtle = `
148
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
149
+
150
+ <#me> foaf:name "Amy Doe" .
151
+ `;
152
+ const deleteTurtle = `
153
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
154
+
155
+ <#me> foaf:name "John Doe" .
156
+ `;
157
+ const sparql = `
158
+ INSERT DATA { ${insertTurtle} } ;
159
+ DELETE DATA { ${deleteTurtle} }
160
+ `;
161
+
162
+ // Act
163
+ const quads = sparqlToQuadsSync(sparql);
164
+
165
+ // Assert
166
+ expect(Object.keys(quads)).toHaveLength(2);
167
+
168
+ expect(quads.insert).toEqual(turtleToQuadsSync(insertTurtle));
169
+ expect(quads.delete).toEqual(turtleToQuadsSync(deleteTurtle));
170
+ });
171
+
172
+ it('parses turtle synchronously', () => {
173
+ // Arrange
174
+ const turtle = `
175
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
176
+
177
+ <#me>
178
+ a foaf:Person ;
179
+ foaf:name "John Doe" .
180
+ `;
181
+
182
+ // Act
183
+ const quads = turtleToQuadsSync(turtle) as [Quad, Quad];
184
+
185
+ // Assert
186
+ expect(quads).toHaveLength(2);
187
+
188
+ expect(quads[0].subject.termType).toEqual('NamedNode');
189
+ expect(quads[0].subject.value).toEqual('#me');
190
+ expect(quads[0].predicate.termType).toEqual('NamedNode');
191
+ expect(quads[0].predicate.value).toEqual('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
192
+ expect(quads[0].object.termType).toEqual('NamedNode');
193
+ expect(quads[0].object.value).toEqual('http://xmlns.com/foaf/0.1/Person');
194
+
195
+ expect(quads[1].subject.termType).toEqual('NamedNode');
196
+ expect(quads[1].subject.value).toEqual('#me');
197
+ expect(quads[1].predicate.termType).toEqual('NamedNode');
198
+ expect(quads[1].predicate.value).toEqual('http://xmlns.com/foaf/0.1/name');
199
+ expect(quads[1].object.termType).toEqual('Literal');
200
+ expect(quads[1].object.value).toEqual('John Doe');
201
+ });
202
+
203
+ it('converts quads to jsonld', async () => {
204
+ // Arrange
205
+ const quads = turtleToQuadsSync(`
206
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
207
+
208
+ <#me>
209
+ a foaf:Person ;
210
+ foaf:name "John Doe" .
211
+ `);
212
+
213
+ // Act
214
+ const jsonld = await quadsToJsonLD(quads);
215
+
216
+ // Assert
217
+ expect(jsonld).toEqual({
218
+ '@graph': [
219
+ {
220
+ '@id': '#me',
221
+ '@type': ['http://xmlns.com/foaf/0.1/Person'],
222
+ 'http://xmlns.com/foaf/0.1/name': [{ '@value': 'John Doe' }],
223
+ },
224
+ ],
225
+ });
226
+ });
227
+
228
+ });
package/src/helpers/io.ts CHANGED
@@ -1,24 +1,20 @@
1
+ import jsonld from 'jsonld';
1
2
  import md5 from 'md5';
2
- import {
3
- TurtleParser,
4
- TurtleWriter,
5
- createBlankNode,
6
- createQuad,
7
- jsonLDFromRDF,
8
- jsonLDToRDF,
9
- } from '@noeldemartin/solid-utils-external';
10
3
  import { arr, arrayFilter, arrayReplace, objectWithoutEmpty, stringMatchAll, tap } from '@noeldemartin/utils';
11
- import type { Quad } from 'rdf-js';
12
- import type { JsonLdDocument, Term } from '@noeldemartin/solid-utils-external';
4
+ import { BlankNode as N3BlankNode, Quad as N3Quad, Parser, Writer } from 'n3';
5
+ import type { JsonLdDocument } from 'jsonld';
6
+ import type { Quad } from '@rdfjs/types';
7
+ import type { Term } from 'n3';
13
8
 
14
- import SolidDocument from '@/models/SolidDocument';
9
+ import SolidDocument from '@noeldemartin/solid-utils/models/SolidDocument';
15
10
 
16
- import MalformedSolidDocumentError, { SolidDocumentFormat } from '@/errors/MalformedSolidDocumentError';
17
- import NetworkRequestError from '@/errors/NetworkRequestError';
18
- import NotFoundError from '@/errors/NotFoundError';
19
- import UnauthorizedError from '@/errors/UnauthorizedError';
20
- import { isJsonLDGraph } from '@/helpers/jsonld';
21
- import type { JsonLD, JsonLDGraph, JsonLDResource } from '@/helpers/jsonld';
11
+ // eslint-disable-next-line max-len
12
+ import MalformedSolidDocumentError, { SolidDocumentFormat } from '@noeldemartin/solid-utils/errors/MalformedSolidDocumentError';
13
+ import NetworkRequestError from '@noeldemartin/solid-utils/errors/NetworkRequestError';
14
+ import NotFoundError from '@noeldemartin/solid-utils/errors/NotFoundError';
15
+ import UnauthorizedError from '@noeldemartin/solid-utils/errors/UnauthorizedError';
16
+ import { isJsonLDGraph } from '@noeldemartin/solid-utils/helpers/jsonld';
17
+ import type { JsonLD, JsonLDGraph, JsonLDResource } from '@noeldemartin/solid-utils/helpers/jsonld';
22
18
 
23
19
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
20
  export declare type AnyFetch = (input: any, options?: any) => Promise<Response>;
@@ -44,11 +40,9 @@ async function fetchRawSolidDocument(
44
40
  const fetch = options?.fetch ?? window.fetch;
45
41
  const response = await fetch(url, requestOptions);
46
42
 
47
- if (response.status === 404)
48
- throw new NotFoundError(url);
43
+ if (response.status === 404) throw new NotFoundError(url);
49
44
 
50
- if ([401, 403].includes(response.status))
51
- throw new UnauthorizedError(url, response.status);
45
+ if ([401, 403].includes(response.status)) throw new UnauthorizedError(url, response.status);
52
46
 
53
47
  const body = await response.text();
54
48
 
@@ -57,11 +51,9 @@ async function fetchRawSolidDocument(
57
51
  headers: response.headers,
58
52
  };
59
53
  } catch (error) {
60
- if (error instanceof UnauthorizedError)
61
- throw error;
54
+ if (error instanceof UnauthorizedError) throw error;
62
55
 
63
- if (error instanceof NotFoundError)
64
- throw error;
56
+ if (error instanceof NotFoundError) throw error;
65
57
 
66
58
  throw new NetworkRequestError(url, { cause: error });
67
59
  }
@@ -71,15 +63,14 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
71
63
  const normalizedQuads = quads.slice(0);
72
64
  const quadsIndexes: Record<string, Set<number>> = {};
73
65
  const blankNodeIds = arr(quads)
74
- .flatMap(
75
- (quad, index) => tap(
66
+ .flatMap((quad, index) =>
67
+ tap(
76
68
  arrayFilter([
77
69
  quad.object.termType === 'BlankNode' ? quad.object.value : null,
78
70
  quad.subject.termType === 'BlankNode' ? quad.subject.value : null,
79
71
  ]),
80
- ids => ids.forEach(id => (quadsIndexes[id] ??= new Set()).add(index)),
81
- ),
82
- )
72
+ (ids) => ids.forEach((id) => (quadsIndexes[id] ??= new Set()).add(index)),
73
+ ))
83
74
  .filter()
84
75
  .unique();
85
76
 
@@ -87,13 +78,10 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
87
78
  const quadIndexes = quadsIndexes[originalId] as Set<number>;
88
79
  const normalizedId = md5(
89
80
  arr(quadIndexes)
90
- .map(index => quads[index] as Quad)
81
+ .map((index) => quads[index] as Quad)
91
82
  .filter(({ subject: { termType, value } }) => termType === 'BlankNode' && value === originalId)
92
- .map(
93
- ({ predicate, object }) => object.termType === 'BlankNode'
94
- ? predicate.value
95
- : predicate.value + object.value,
96
- )
83
+ .map(({ predicate, object }) =>
84
+ object.termType === 'BlankNode' ? predicate.value : predicate.value + object.value)
97
85
  .sorted()
98
86
  .join(),
99
87
  );
@@ -106,20 +94,15 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
106
94
  };
107
95
 
108
96
  for (const [termName, termValue] of Object.entries(terms)) {
109
- if (termValue.termType !== 'BlankNode' || termValue.value !== originalId)
110
- continue;
97
+ if (termValue.termType !== 'BlankNode' || termValue.value !== originalId) continue;
111
98
 
112
- terms[termName] = createBlankNode(normalizedId) as Term;
99
+ terms[termName] = new N3BlankNode(normalizedId) as Term;
113
100
  }
114
101
 
115
102
  arrayReplace(
116
103
  normalizedQuads,
117
104
  quad,
118
- createQuad(
119
- terms.subject as Term,
120
- quad.predicate as Term,
121
- terms.object as Term,
122
- ),
105
+ new N3Quad(terms.subject as Term, quad.predicate as Term, terms.object as Term),
123
106
  );
124
107
  }
125
108
  }
@@ -128,15 +111,18 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
128
111
  }
129
112
 
130
113
  function normalizeQuads(quads: Quad[]): string {
131
- return quads.map(quad => ' ' + quadToTurtle(quad)).sort().join('\n');
114
+ return quads
115
+ .map((quad) => ' ' + quadToTurtle(quad))
116
+ .sort()
117
+ .join('\n');
132
118
  }
133
119
 
134
- function preprocessSubjects(jsonld: JsonLD): void {
135
- if (!jsonld['@id']?.startsWith('#')) {
120
+ function preprocessSubjects(json: JsonLD): void {
121
+ if (!json['@id']?.startsWith('#')) {
136
122
  return;
137
123
  }
138
124
 
139
- jsonld['@id'] = ANONYMOUS_PREFIX + jsonld['@id'];
125
+ json['@id'] = ANONYMOUS_PREFIX + json['@id'];
140
126
  }
141
127
 
142
128
  function postprocessSubjects(quads: Quad[]): void {
@@ -194,23 +180,22 @@ export async function fetchSolidDocumentIfFound(
194
180
 
195
181
  return document;
196
182
  } catch (error) {
197
- if (!(error instanceof NotFoundError))
198
- throw error;
183
+ if (!(error instanceof NotFoundError)) throw error;
199
184
 
200
185
  return null;
201
186
  }
202
187
  }
203
188
 
204
- export async function jsonldToQuads(jsonld: JsonLD, baseIRI?: string): Promise<Quad[]> {
205
- if (isJsonLDGraph(jsonld)) {
206
- const graphQuads = await Promise.all(jsonld['@graph'].map(resource => jsonldToQuads(resource, baseIRI)));
189
+ export async function jsonldToQuads(json: JsonLD, baseIRI?: string): Promise<Quad[]> {
190
+ if (isJsonLDGraph(json)) {
191
+ const graphQuads = await Promise.all(json['@graph'].map((resource) => jsonldToQuads(resource, baseIRI)));
207
192
 
208
193
  return graphQuads.flat();
209
194
  }
210
195
 
211
- preprocessSubjects(jsonld);
196
+ preprocessSubjects(json);
212
197
 
213
- const quads = await (jsonLDToRDF(jsonld as JsonLdDocument, { base: baseIRI }) as Promise<Quad[]>);
198
+ const quads = await (jsonld.toRDF(json as JsonLdDocument, { base: baseIRI }) as Promise<Quad[]>);
214
199
 
215
200
  postprocessSubjects(quads);
216
201
 
@@ -220,10 +205,9 @@ export async function jsonldToQuads(jsonld: JsonLD, baseIRI?: string): Promise<Q
220
205
  export function normalizeSparql(sparql: string): string {
221
206
  const quads = sparqlToQuadsSync(sparql);
222
207
 
223
- return Object
224
- .entries(quads)
225
- .reduce((normalizedOperations, [operation, quads]) => {
226
- const normalizedQuads = normalizeQuads(quads);
208
+ return Object.entries(quads)
209
+ .reduce((normalizedOperations, [operation, _quads]) => {
210
+ const normalizedQuads = normalizeQuads(_quads);
227
211
 
228
212
  return normalizedOperations.concat(`${operation.toUpperCase()} DATA {\n${normalizedQuads}\n}`);
229
213
  }, [] as string[])
@@ -238,7 +222,7 @@ export function normalizeTurtle(sparql: string): string {
238
222
 
239
223
  export function parseTurtle(turtle: string, options: Partial<ParsingOptions> = {}): Promise<RDFGraphData> {
240
224
  const parserOptions = objectWithoutEmpty({ baseIRI: options.baseIRI });
241
- const parser = new TurtleParser(parserOptions);
225
+ const parser = new Parser(parserOptions);
242
226
  const data: RDFGraphData = {
243
227
  quads: [],
244
228
  containsRelativeIRIs: false,
@@ -257,11 +241,7 @@ export function parseTurtle(turtle: string, options: Partial<ParsingOptions> = {
257
241
  parser.parse(turtle, (error, quad) => {
258
242
  if (error) {
259
243
  reject(
260
- new MalformedSolidDocumentError(
261
- options.baseIRI ?? null,
262
- SolidDocumentFormat.Turtle,
263
- error.message,
264
- ),
244
+ new MalformedSolidDocumentError(options.baseIRI ?? null, SolidDocumentFormat.Turtle, error.message),
265
245
  );
266
246
 
267
247
  return;
@@ -283,7 +263,7 @@ export function parseTurtle(turtle: string, options: Partial<ParsingOptions> = {
283
263
  }
284
264
 
285
265
  export async function quadsToJsonLD(quads: Quad[]): Promise<JsonLDGraph> {
286
- const graph = await jsonLDFromRDF(quads);
266
+ const graph = await jsonld.fromRDF(quads);
287
267
 
288
268
  return {
289
269
  '@graph': graph as JsonLDResource[],
@@ -291,13 +271,13 @@ export async function quadsToJsonLD(quads: Quad[]): Promise<JsonLDGraph> {
291
271
  }
292
272
 
293
273
  export function quadsToTurtle(quads: Quad[]): string {
294
- const writer = new TurtleWriter;
274
+ const writer = new Writer();
295
275
 
296
276
  return writer.quadsToString(quads);
297
277
  }
298
278
 
299
279
  export function quadToTurtle(quad: Quad): string {
300
- const writer = new TurtleWriter;
280
+ const writer = new Writer();
301
281
 
302
282
  return writer.quadsToString([quad]).slice(0, -1);
303
283
  }
@@ -319,12 +299,14 @@ export async function sparqlToQuads(
319
299
  const operations = stringMatchAll<3>(sparql, /(\w+) DATA {([^}]+)}/g);
320
300
  const quads: Record<string, Quad[]> = {};
321
301
 
322
- await Promise.all([...operations].map(async operation => {
323
- const operationName = operation[1].toLowerCase();
324
- const operationBody = operation[2];
302
+ await Promise.all(
303
+ [...operations].map(async (operation) => {
304
+ const operationName = operation[1].toLowerCase();
305
+ const operationBody = operation[2];
325
306
 
326
- quads[operationName] = await turtleToQuads(operationBody, options);
327
- }));
307
+ quads[operationName] = await turtleToQuads(operationBody, options);
308
+ }),
309
+ );
328
310
 
329
311
  return quads;
330
312
  }
@@ -351,14 +333,12 @@ export async function turtleToQuads(turtle: string, options: Partial<ParsingOpti
351
333
 
352
334
  export function turtleToQuadsSync(turtle: string, options: Partial<ParsingOptions> = {}): Quad[] {
353
335
  const parserOptions = objectWithoutEmpty({ baseIRI: options.baseIRI });
354
- const parser = new TurtleParser(parserOptions);
336
+ const parser = new Parser(parserOptions);
355
337
 
356
338
  try {
357
339
  const quads = parser.parse(turtle);
358
340
 
359
- return options.normalizeBlankNodes
360
- ? normalizeBlankNodes(quads)
361
- : quads;
341
+ return options.normalizeBlankNodes ? normalizeBlankNodes(quads) : quads;
362
342
  } catch (error) {
363
343
  throw new MalformedSolidDocumentError(
364
344
  options.baseIRI ?? null,
@@ -1,5 +1,5 @@
1
- import { compactJsonLD } from '@noeldemartin/solid-utils-external';
2
- import type { JsonLdDocument } from '@noeldemartin/solid-utils-external';
1
+ import jsonld from 'jsonld';
2
+ import type { JsonLdDocument } from 'jsonld';
3
3
 
4
4
  export type JsonLD = Partial<{
5
5
  '@context': Record<string, unknown>;
@@ -13,8 +13,8 @@ export type JsonLDGraph = {
13
13
  '@graph': JsonLDResource[];
14
14
  };
15
15
 
16
- export async function compactJsonLDGraph(jsonld: JsonLDGraph): Promise<JsonLDGraph> {
17
- const compactedJsonLD = await compactJsonLD(jsonld as JsonLdDocument, {});
16
+ export async function compactJsonLDGraph(json: JsonLDGraph): Promise<JsonLDGraph> {
17
+ const compactedJsonLD = await jsonld.compact(json as JsonLdDocument, {});
18
18
 
19
19
  if ('@graph' in compactedJsonLD) {
20
20
  return compactedJsonLD as JsonLDGraph;
@@ -27,6 +27,6 @@ export async function compactJsonLDGraph(jsonld: JsonLDGraph): Promise<JsonLDGra
27
27
  return { '@graph': [] };
28
28
  }
29
29
 
30
- export function isJsonLDGraph(jsonld: JsonLD): jsonld is JsonLDGraph {
31
- return '@graph' in jsonld;
30
+ export function isJsonLDGraph(json: JsonLD): json is JsonLDGraph {
31
+ return '@graph' in json;
32
32
  }
@@ -23,22 +23,19 @@ export function defineIRIPrefix(name: string, value: string): void {
23
23
  }
24
24
 
25
25
  export function expandIRI(iri: string, options: Partial<ExpandIRIOptions> = {}): string {
26
- if (iri.startsWith('http'))
27
- return iri;
26
+ if (iri.startsWith('http')) return iri;
28
27
 
29
28
  const [prefix, name] = iri.split(':');
30
29
 
31
30
  if (prefix && name) {
32
31
  const expandedPrefix = knownPrefixes[prefix] ?? options.extraContext?.[prefix] ?? null;
33
32
 
34
- if (!expandedPrefix)
35
- throw new Error(`Can't expand IRI with unknown prefix: '${iri}'`);
33
+ if (!expandedPrefix) throw new Error(`Can't expand IRI with unknown prefix: '${iri}'`);
36
34
 
37
35
  return expandedPrefix + name;
38
36
  }
39
37
 
40
- if (!options.defaultPrefix)
41
- throw new Error(`Can't expand IRI without a default prefix: '${iri}'`);
38
+ if (!options.defaultPrefix) throw new Error(`Can't expand IRI without a default prefix: '${iri}'`);
42
39
 
43
40
  return options.defaultPrefix + prefix;
44
41
  }
@@ -0,0 +1,64 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { FakeResponse, FakeServer } from '@noeldemartin/testing';
3
+
4
+ // eslint-disable-next-line max-len
5
+ import UnsupportedAuthorizationProtocolError from '@noeldemartin/solid-utils/errors/UnsupportedAuthorizationProtocolError';
6
+
7
+ import { fetchSolidDocumentACL } from './wac';
8
+
9
+ describe('WAC helpers', () => {
10
+
11
+ it('resolves relative ACL urls', async () => {
12
+ // Arrange
13
+ const server = new FakeServer();
14
+ const documentUrl = 'https://example.com/alice/movies/my-favorite-movie';
15
+
16
+ server.respondOnce(documentUrl, FakeResponse.success(undefined, { Link: '<my-favorite-movie.acl>;rel="acl"' }));
17
+ server.respondOnce(
18
+ `${documentUrl}.acl`,
19
+ FakeResponse.success(`
20
+ @prefix acl: <http://www.w3.org/ns/auth/acl#>.
21
+ @prefix foaf: <http://xmlns.com/foaf/0.1/>.
22
+
23
+ <#owner>
24
+ a acl:Authorization;
25
+ acl:agent <https://example.com/alice/profile/card#me>;
26
+ acl:accessTo <./my-favorite-movie> ;
27
+ acl:mode acl:Read, acl:Write, acl:Control .
28
+ `),
29
+ );
30
+
31
+ // Act
32
+ const { url, effectiveUrl, document } = await fetchSolidDocumentACL(documentUrl, server.fetch);
33
+
34
+ // Assert
35
+ expect(url).toEqual(`${documentUrl}.acl`);
36
+ expect(effectiveUrl).toEqual(url);
37
+ expect(document.contains(`${documentUrl}.acl#owner`, 'rdf:type', 'acl:Authorization')).toBe(true);
38
+
39
+ expect(server.getRequests()).toHaveLength(2);
40
+ expect(server.getRequest(documentUrl)?.method).toEqual('HEAD');
41
+ expect(server.getRequest(url)).not.toBeNull();
42
+ });
43
+
44
+ it('fails with ACP resources', async () => {
45
+ // Arrange
46
+ const server = new FakeServer();
47
+ const documentUrl = 'https://example.com/alice/movies/my-favorite-movie';
48
+
49
+ server.respondOnce(documentUrl, FakeResponse.success(undefined, { Link: '<my-favorite-movie.acl>;rel="acl"' }));
50
+ server.respondOnce(
51
+ `${documentUrl}.acl`,
52
+ FakeResponse.success(undefined, {
53
+ Link: '<http://www.w3.org/ns/solid/acp#AccessControlResource>; rel="type"',
54
+ }),
55
+ );
56
+
57
+ // Act
58
+ const promisedDocument = fetchSolidDocumentACL(documentUrl, server.fetch);
59
+
60
+ // Assert
61
+ await expect(promisedDocument).rejects.toBeInstanceOf(UnsupportedAuthorizationProtocolError);
62
+ });
63
+
64
+ });
@@ -1,9 +1,10 @@
1
1
  import { objectWithoutEmpty, requireUrlParentDirectory, urlResolve } from '@noeldemartin/utils';
2
2
 
3
- import UnsupportedAuthorizationProtocolError from '@/errors/UnsupportedAuthorizationProtocolError';
4
- import { fetchSolidDocumentIfFound } from '@/helpers/io';
5
- import type SolidDocument from '@/models/SolidDocument';
6
- import type { Fetch } from '@/helpers/io';
3
+ // eslint-disable-next-line max-len
4
+ import UnsupportedAuthorizationProtocolError from '@noeldemartin/solid-utils/errors/UnsupportedAuthorizationProtocolError';
5
+ import { fetchSolidDocumentIfFound } from '@noeldemartin/solid-utils/helpers/io';
6
+ import type SolidDocument from '@noeldemartin/solid-utils/models/SolidDocument';
7
+ import type { Fetch } from '@noeldemartin/solid-utils/helpers/io';
7
8
 
8
9
  async function fetchACLResourceUrl(resourceUrl: string, fetch: Fetch): Promise<string> {
9
10
  fetch = fetch ?? window.fetch.bind(window);
@@ -24,7 +25,7 @@ async function fetchEffectiveACL(
24
25
  fetch: Fetch,
25
26
  aclResourceUrl?: string | null,
26
27
  ): Promise<SolidDocument> {
27
- aclResourceUrl = aclResourceUrl ?? await fetchACLResourceUrl(resourceUrl, fetch);
28
+ aclResourceUrl = aclResourceUrl ?? (await fetchACLResourceUrl(resourceUrl, fetch));
28
29
 
29
30
  const aclDocument = await fetchSolidDocumentIfFound(aclResourceUrl ?? '', { fetch });
30
31
 
@@ -39,7 +40,10 @@ async function fetchEffectiveACL(
39
40
  return aclDocument;
40
41
  }
41
42
 
42
- export async function fetchSolidDocumentACL(documentUrl: string, fetch: Fetch): Promise<{
43
+ export async function fetchSolidDocumentACL(
44
+ documentUrl: string,
45
+ fetch: Fetch,
46
+ ): Promise<{
43
47
  url: string;
44
48
  effectiveUrl: string;
45
49
  document: SolidDocument;
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './errors';
2
+ export * from './helpers';
3
+ export * from './models';