@noeldemartin/solid-utils 0.5.0 → 0.6.0-next.cccdc9c7e033588e2df9d1887ceae49788344d84
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.
- package/dist/noeldemartin-solid-utils.d.ts +10 -62
- package/dist/noeldemartin-solid-utils.js +590 -0
- package/dist/noeldemartin-solid-utils.js.map +1 -0
- package/package.json +54 -63
- package/src/helpers/auth.test.ts +221 -0
- package/src/helpers/auth.ts +28 -27
- package/src/helpers/identifiers.test.ts +76 -0
- package/src/helpers/identifiers.ts +1 -1
- package/src/helpers/index.ts +0 -1
- package/src/helpers/interop.ts +23 -16
- package/src/helpers/io.test.ts +228 -0
- package/src/helpers/io.ts +53 -73
- package/src/helpers/jsonld.ts +3 -3
- package/src/helpers/wac.test.ts +64 -0
- package/src/helpers/wac.ts +10 -6
- package/src/index.ts +3 -0
- package/src/models/SolidDocument.test.ts +77 -0
- package/src/models/SolidDocument.ts +14 -18
- package/src/models/SolidStore.ts +22 -12
- package/src/models/SolidThing.ts +5 -7
- package/src/models/index.ts +2 -0
- package/.github/workflows/ci.yml +0 -16
- package/.nvmrc +0 -1
- package/CHANGELOG.md +0 -70
- package/dist/noeldemartin-solid-utils.cjs.js +0 -2
- package/dist/noeldemartin-solid-utils.cjs.js.map +0 -1
- package/dist/noeldemartin-solid-utils.esm.js +0 -2
- package/dist/noeldemartin-solid-utils.esm.js.map +0 -1
- package/dist/noeldemartin-solid-utils.umd.js +0 -90
- package/dist/noeldemartin-solid-utils.umd.js.map +0 -1
- package/noeldemartin.config.js +0 -9
- package/src/helpers/testing.ts +0 -190
- package/src/main.ts +0 -5
- package/src/plugins/chai/assertions.ts +0 -40
- package/src/plugins/chai/index.ts +0 -5
- package/src/plugins/cypress/types.d.ts +0 -15
- package/src/plugins/index.ts +0 -2
- package/src/plugins/jest/index.ts +0 -5
- package/src/plugins/jest/matchers.ts +0 -65
- package/src/plugins/jest/types.d.ts +0 -14
- package/src/testing/ResponseStub.ts +0 -46
- package/src/testing/index.ts +0 -2
- package/src/testing/mocking.ts +0 -33
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { mintJsonLDIdentifiers } from '@noeldemartin/solid-utils/helpers';
|
|
4
|
+
import { parseResourceSubject } from '@noeldemartin/solid-utils/helpers/identifiers';
|
|
5
|
+
import type { JsonLD } from '@noeldemartin/solid-utils/helpers';
|
|
6
|
+
|
|
7
|
+
describe('Identifiers helpers', () => {
|
|
8
|
+
|
|
9
|
+
it('mints JsonLD identifiers', () => {
|
|
10
|
+
// Arrange
|
|
11
|
+
const jsonld = {
|
|
12
|
+
'@context': { '@vocab': 'https://schema.org/' },
|
|
13
|
+
'@type': 'Recipe',
|
|
14
|
+
'name': 'Ramen',
|
|
15
|
+
'ingredients': ['Broth', 'Noodles'],
|
|
16
|
+
'instructions': [
|
|
17
|
+
{
|
|
18
|
+
'@type': 'HowToStep',
|
|
19
|
+
'text': 'Boil Noodles',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
'@type': 'HowToStep',
|
|
23
|
+
'text': 'Dip them into the broth',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
'http://purl.org/dc/terms/created': {
|
|
27
|
+
'@type': 'http://www.w3.org/2001/XMLSchema#dateTime',
|
|
28
|
+
'@value': '1997-07-21T23:42:00.000Z',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Act
|
|
33
|
+
const jsonldWithIds = mintJsonLDIdentifiers(jsonld);
|
|
34
|
+
|
|
35
|
+
// Assert
|
|
36
|
+
expect(jsonldWithIds['@id']).not.toBeUndefined();
|
|
37
|
+
|
|
38
|
+
const createdAt = jsonldWithIds['http://purl.org/dc/terms/created'] as Record<string, unknown>;
|
|
39
|
+
expect(createdAt['@id']).toBeUndefined();
|
|
40
|
+
|
|
41
|
+
const instructions = jsonldWithIds['instructions'] as [JsonLD, JsonLD];
|
|
42
|
+
expect(instructions[0]['@id']).not.toBeUndefined();
|
|
43
|
+
expect(instructions[1]['@id']).not.toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('parses subjects', () => {
|
|
47
|
+
expect(parseResourceSubject('https://my-pod.com/profile/card#me')).toEqual({
|
|
48
|
+
containerUrl: 'https://my-pod.com/profile/',
|
|
49
|
+
documentName: 'card',
|
|
50
|
+
resourceHash: 'me',
|
|
51
|
+
});
|
|
52
|
+
expect(parseResourceSubject('https://my-pod.com/about')).toEqual({
|
|
53
|
+
containerUrl: 'https://my-pod.com/',
|
|
54
|
+
documentName: 'about',
|
|
55
|
+
});
|
|
56
|
+
expect(parseResourceSubject('/profile/card#me')).toEqual({
|
|
57
|
+
containerUrl: '/profile/',
|
|
58
|
+
documentName: 'card',
|
|
59
|
+
resourceHash: 'me',
|
|
60
|
+
});
|
|
61
|
+
expect(parseResourceSubject('/about#sections')).toEqual({
|
|
62
|
+
containerUrl: '/',
|
|
63
|
+
documentName: 'about',
|
|
64
|
+
resourceHash: 'sections',
|
|
65
|
+
});
|
|
66
|
+
expect(parseResourceSubject('about#sections')).toEqual({
|
|
67
|
+
documentName: 'about',
|
|
68
|
+
resourceHash: 'sections',
|
|
69
|
+
});
|
|
70
|
+
expect(parseResourceSubject('about')).toEqual({
|
|
71
|
+
documentName: 'about',
|
|
72
|
+
});
|
|
73
|
+
expect(parseResourceSubject('')).toEqual({});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { arr, isArray, isObject, objectDeepClone, objectWithoutEmpty, tap, urlParse, uuid } from '@noeldemartin/utils';
|
|
2
2
|
import type { UrlParts } from '@noeldemartin/utils';
|
|
3
|
-
import type { JsonLD, JsonLDResource } from '
|
|
3
|
+
import type { JsonLD, JsonLDResource } from '@noeldemartin/solid-utils/helpers';
|
|
4
4
|
|
|
5
5
|
export interface SubjectParts {
|
|
6
6
|
containerUrl?: string;
|
package/src/helpers/index.ts
CHANGED
package/src/helpers/interop.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { uuid } from '@noeldemartin/utils';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
createSolidDocument,
|
|
5
|
+
fetchSolidDocument,
|
|
6
|
+
solidDocumentExists,
|
|
7
|
+
updateSolidDocument,
|
|
8
|
+
} from '@noeldemartin/solid-utils/helpers/io';
|
|
9
|
+
import type { Fetch } from '@noeldemartin/solid-utils/helpers/io';
|
|
10
|
+
import type { SolidUserProfile } from '@noeldemartin/solid-utils/helpers/auth';
|
|
6
11
|
|
|
7
12
|
type TypeIndexType = 'public' | 'private';
|
|
8
13
|
|
|
@@ -12,7 +17,7 @@ async function mintTypeIndexUrl(user: SolidUserProfile, type: TypeIndexType, fet
|
|
|
12
17
|
const storageUrl = user.storageUrls[0];
|
|
13
18
|
const typeIndexUrl = `${storageUrl}settings/${type}TypeIndex`;
|
|
14
19
|
|
|
15
|
-
return await solidDocumentExists(typeIndexUrl, { fetch })
|
|
20
|
+
return (await solidDocumentExists(typeIndexUrl, { fetch }))
|
|
16
21
|
? `${storageUrl}settings/${type}TypeIndex-${uuid()}`
|
|
17
22
|
: typeIndexUrl;
|
|
18
23
|
}
|
|
@@ -25,9 +30,10 @@ async function createTypeIndex(user: SolidUserProfile, type: TypeIndexType, fetc
|
|
|
25
30
|
fetch = fetch ?? window.fetch.bind(fetch);
|
|
26
31
|
|
|
27
32
|
const typeIndexUrl = await mintTypeIndexUrl(user, type, fetch);
|
|
28
|
-
const typeIndexBody =
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
const typeIndexBody =
|
|
34
|
+
type === 'public'
|
|
35
|
+
? '<> a <http://www.w3.org/ns/solid/terms#TypeIndex> .'
|
|
36
|
+
: `
|
|
31
37
|
<> a
|
|
32
38
|
<http://www.w3.org/ns/solid/terms#TypeIndex>,
|
|
33
39
|
<http://www.w3.org/ns/solid/terms#UnlistedDocument> .
|
|
@@ -60,15 +66,16 @@ async function findRegistrations(
|
|
|
60
66
|
const typeIndex = await fetchSolidDocument(typeIndexUrl, { fetch });
|
|
61
67
|
const types = Array.isArray(type) ? type : [type];
|
|
62
68
|
|
|
63
|
-
return types
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
return types
|
|
70
|
+
.map((_type) =>
|
|
71
|
+
typeIndex
|
|
72
|
+
.statements(undefined, 'rdf:type', 'solid:TypeRegistration')
|
|
73
|
+
.filter((statement) => typeIndex.contains(statement.subject.value, 'solid:forClass', _type))
|
|
74
|
+
.map((statement) => typeIndex.statements(statement.subject.value, predicate))
|
|
75
|
+
.flat()
|
|
76
|
+
.map((statement) => statement.object.value)
|
|
77
|
+
.filter((url) => !!url))
|
|
78
|
+
.flat();
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
/**
|
|
@@ -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
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';
|
|
10
2
|
import { arr, arrayFilter, arrayReplace, objectWithoutEmpty, stringMatchAll, tap } from '@noeldemartin/utils';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
3
|
+
import { BlankNode as N3BlankNode, Quad as N3Quad, Parser, Writer } from 'n3';
|
|
4
|
+
import { fromRDF, toRDF } from 'jsonld';
|
|
5
|
+
import type { JsonLdDocument } from 'jsonld';
|
|
6
|
+
import type { Quad } from '@rdfjs/types';
|
|
7
|
+
import type { Term } from 'n3';
|
|
8
|
+
|
|
9
|
+
import SolidDocument from '@noeldemartin/solid-utils/models/SolidDocument';
|
|
10
|
+
|
|
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
|
-
|
|
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
|
-
|
|
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] =
|
|
99
|
+
terms[termName] = new N3BlankNode(normalizedId) as Term;
|
|
113
100
|
}
|
|
114
101
|
|
|
115
102
|
arrayReplace(
|
|
116
103
|
normalizedQuads,
|
|
117
104
|
quad,
|
|
118
|
-
|
|
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,7 +111,10 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
|
|
|
128
111
|
}
|
|
129
112
|
|
|
130
113
|
function normalizeQuads(quads: Quad[]): string {
|
|
131
|
-
return quads
|
|
114
|
+
return quads
|
|
115
|
+
.map((quad) => ' ' + quadToTurtle(quad))
|
|
116
|
+
.sort()
|
|
117
|
+
.join('\n');
|
|
132
118
|
}
|
|
133
119
|
|
|
134
120
|
function preprocessSubjects(jsonld: JsonLD): void {
|
|
@@ -194,8 +180,7 @@ 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
|
}
|
|
@@ -203,14 +188,14 @@ export async function fetchSolidDocumentIfFound(
|
|
|
203
188
|
|
|
204
189
|
export async function jsonldToQuads(jsonld: JsonLD, baseIRI?: string): Promise<Quad[]> {
|
|
205
190
|
if (isJsonLDGraph(jsonld)) {
|
|
206
|
-
const graphQuads = await Promise.all(jsonld['@graph'].map(resource => jsonldToQuads(resource, baseIRI)));
|
|
191
|
+
const graphQuads = await Promise.all(jsonld['@graph'].map((resource) => jsonldToQuads(resource, baseIRI)));
|
|
207
192
|
|
|
208
193
|
return graphQuads.flat();
|
|
209
194
|
}
|
|
210
195
|
|
|
211
196
|
preprocessSubjects(jsonld);
|
|
212
197
|
|
|
213
|
-
const quads = await (
|
|
198
|
+
const quads = await (toRDF(jsonld 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
|
-
.
|
|
225
|
-
|
|
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
|
|
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
|
|
266
|
+
const graph = await 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
|
|
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
|
|
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(
|
|
323
|
-
|
|
324
|
-
|
|
302
|
+
await Promise.all(
|
|
303
|
+
[...operations].map(async (operation) => {
|
|
304
|
+
const operationName = operation[1].toLowerCase();
|
|
305
|
+
const operationBody = operation[2];
|
|
325
306
|
|
|
326
|
-
|
|
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
|
|
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,
|
package/src/helpers/jsonld.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { JsonLdDocument } from '
|
|
1
|
+
import { compact } from 'jsonld';
|
|
2
|
+
import type { JsonLdDocument } from 'jsonld';
|
|
3
3
|
|
|
4
4
|
export type JsonLD = Partial<{
|
|
5
5
|
'@context': Record<string, unknown>;
|
|
@@ -14,7 +14,7 @@ export type JsonLDGraph = {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export async function compactJsonLDGraph(jsonld: JsonLDGraph): Promise<JsonLDGraph> {
|
|
17
|
-
const compactedJsonLD = await
|
|
17
|
+
const compactedJsonLD = await compact(jsonld as JsonLdDocument, {});
|
|
18
18
|
|
|
19
19
|
if ('@graph' in compactedJsonLD) {
|
|
20
20
|
return compactedJsonLD as JsonLDGraph;
|