@noeldemartin/solid-utils 0.4.0 → 0.5.0
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/.github/workflows/ci.yml +16 -0
- package/CHANGELOG.md +21 -0
- package/README.md +1 -1
- package/dist/noeldemartin-solid-utils.cjs.js +1 -1
- package/dist/noeldemartin-solid-utils.cjs.js.map +1 -1
- package/dist/noeldemartin-solid-utils.d.ts +17 -25
- package/dist/noeldemartin-solid-utils.esm.js +1 -1
- package/dist/noeldemartin-solid-utils.esm.js.map +1 -1
- package/dist/noeldemartin-solid-utils.umd.js +16 -16
- package/dist/noeldemartin-solid-utils.umd.js.map +1 -1
- package/package.json +3 -4
- package/src/helpers/auth.ts +25 -11
- package/src/helpers/interop.ts +4 -6
- package/src/helpers/io.ts +64 -11
- package/src/helpers/vocabs.ts +1 -0
- package/src/helpers/wac.ts +1 -1
- package/src/plugins/jest/matchers.ts +12 -2
- package/src/plugins/jest/types.d.ts +1 -0
- package/src/testing/index.ts +0 -1
- package/.semaphore/semaphore.yml +0 -26
- package/src/testing/faking.ts +0 -36
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noeldemartin/solid-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "My JavaScript utilities for Solid",
|
|
5
5
|
"main": "dist/noeldemartin-solid-utils.cjs.js",
|
|
6
6
|
"module": "dist/noeldemartin-solid-utils.esm.js",
|
|
@@ -31,10 +31,9 @@
|
|
|
31
31
|
"homepage": "https://github.com/noeldemartin/solid-utils",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@babel/runtime": "^7.14.0",
|
|
34
|
-
"@noeldemartin/faker": "^7.6.0",
|
|
35
34
|
"@noeldemartin/solid-utils-external": "^0.1.1",
|
|
36
|
-
"@noeldemartin/utils": "^0.
|
|
37
|
-
"@types
|
|
35
|
+
"@noeldemartin/utils": "^0.6.0",
|
|
36
|
+
"@rdfjs/types": "^1.1.0",
|
|
38
37
|
"core-js": "^3.12.1",
|
|
39
38
|
"md5": "^2.3.0"
|
|
40
39
|
},
|
package/src/helpers/auth.ts
CHANGED
|
@@ -5,7 +5,7 @@ import UnauthorizedError from '../errors/UnauthorizedError';
|
|
|
5
5
|
import type SolidDocument from '../models/SolidDocument';
|
|
6
6
|
|
|
7
7
|
import { fetchSolidDocument } from './io';
|
|
8
|
-
import type { Fetch } from './io';
|
|
8
|
+
import type { Fetch, FetchSolidDocumentOptions } from './io';
|
|
9
9
|
|
|
10
10
|
export interface SolidUserProfile {
|
|
11
11
|
webId: string;
|
|
@@ -19,7 +19,7 @@ export interface SolidUserProfile {
|
|
|
19
19
|
privateTypeIndexUrl?: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
async function fetchExtendedUserProfile(webIdDocument: SolidDocument,
|
|
22
|
+
async function fetchExtendedUserProfile(webIdDocument: SolidDocument, options?: FetchSolidDocumentOptions): Promise<{
|
|
23
23
|
store: SolidStore;
|
|
24
24
|
cloaked: boolean;
|
|
25
25
|
writableProfileUrl: string | null;
|
|
@@ -43,7 +43,7 @@ async function fetchExtendedUserProfile(webIdDocument: SolidDocument, fetch?: Fe
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
try {
|
|
46
|
-
const document = await fetchSolidDocument(url,
|
|
46
|
+
const document = await fetchSolidDocument(url, options);
|
|
47
47
|
|
|
48
48
|
documents[url] = document;
|
|
49
49
|
store.addQuads(document.getQuads());
|
|
@@ -80,22 +80,30 @@ async function fetchExtendedUserProfile(webIdDocument: SolidDocument, fetch?: Fe
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
async function fetchUserProfile(webId: string,
|
|
83
|
+
async function fetchUserProfile(webId: string, options: FetchUserProfileOptions = {}): Promise<SolidUserProfile> {
|
|
84
|
+
const requestOptions: FetchSolidDocumentOptions = {
|
|
85
|
+
fetch: options.fetch,
|
|
86
|
+
|
|
87
|
+
// Needed for CSS v7.1.3.
|
|
88
|
+
// See https://github.com/CommunitySolidServer/CommunitySolidServer/issues/1972
|
|
89
|
+
cache: 'no-store',
|
|
90
|
+
};
|
|
91
|
+
|
|
84
92
|
const documentUrl = urlRoute(webId);
|
|
85
|
-
const document = await fetchSolidDocument(documentUrl,
|
|
93
|
+
const document = await fetchSolidDocument(documentUrl, requestOptions);
|
|
86
94
|
|
|
87
95
|
if (!document.isPersonalProfile() && !document.contains(webId, 'solid:oidcIssuer')) {
|
|
88
96
|
throw new Error(`${webId} is not a valid webId.`);
|
|
89
97
|
}
|
|
90
98
|
|
|
91
|
-
const { store, writableProfileUrl, cloaked } = await fetchExtendedUserProfile(document,
|
|
99
|
+
const { store, writableProfileUrl, cloaked } = await fetchExtendedUserProfile(document, options);
|
|
92
100
|
const storageUrls = store.statements(webId, 'pim:storage').map(storage => storage.object.value);
|
|
93
101
|
const publicTypeIndex = store.statement(webId, 'solid:publicTypeIndex');
|
|
94
102
|
const privateTypeIndex = store.statement(webId, 'solid:privateTypeIndex');
|
|
95
103
|
|
|
96
104
|
let parentUrl = urlParentDirectory(documentUrl);
|
|
97
105
|
while (parentUrl && storageUrls.length === 0) {
|
|
98
|
-
const parentDocument = await silenced(fetchSolidDocument(parentUrl,
|
|
106
|
+
const parentDocument = await silenced(fetchSolidDocument(parentUrl, requestOptions));
|
|
99
107
|
|
|
100
108
|
if (parentDocument?.isStorage()) {
|
|
101
109
|
storageUrls.push(parentUrl);
|
|
@@ -110,6 +118,8 @@ async function fetchUserProfile(webId: string, fetch?: Fetch): Promise<SolidUser
|
|
|
110
118
|
throw new Error(`Could not find any storage for ${webId}.`);
|
|
111
119
|
}
|
|
112
120
|
|
|
121
|
+
await options.onLoaded?.(new SolidStore(store.statements(webId)));
|
|
122
|
+
|
|
113
123
|
return {
|
|
114
124
|
webId,
|
|
115
125
|
cloaked,
|
|
@@ -129,9 +139,13 @@ async function fetchUserProfile(webId: string, fetch?: Fetch): Promise<SolidUser
|
|
|
129
139
|
};
|
|
130
140
|
}
|
|
131
141
|
|
|
132
|
-
export interface
|
|
133
|
-
required?: boolean;
|
|
142
|
+
export interface FetchUserProfileOptions {
|
|
134
143
|
fetch?: Fetch;
|
|
144
|
+
onLoaded?(store: SolidStore): Promise<unknown> | unknown;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface FetchLoginUserProfileOptions extends FetchUserProfileOptions {
|
|
148
|
+
required?: boolean;
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
export async function fetchLoginUserProfile(
|
|
@@ -139,10 +153,10 @@ export async function fetchLoginUserProfile(
|
|
|
139
153
|
options: FetchLoginUserProfileOptions = {},
|
|
140
154
|
): Promise<SolidUserProfile | null> {
|
|
141
155
|
if (options.required) {
|
|
142
|
-
return fetchUserProfile(loginUrl, options
|
|
156
|
+
return fetchUserProfile(loginUrl, options);
|
|
143
157
|
}
|
|
144
158
|
|
|
145
|
-
const fetchProfile = silenced(url => fetchUserProfile(url, options
|
|
159
|
+
const fetchProfile = silenced(url => fetchUserProfile(url, options));
|
|
146
160
|
|
|
147
161
|
return await fetchProfile(loginUrl)
|
|
148
162
|
?? await fetchProfile(loginUrl.replace(/\/$/, '').concat('/profile/card#me'))
|
package/src/helpers/interop.ts
CHANGED
|
@@ -12,7 +12,7 @@ async function mintTypeIndexUrl(user: SolidUserProfile, type: TypeIndexType, fet
|
|
|
12
12
|
const storageUrl = user.storageUrls[0];
|
|
13
13
|
const typeIndexUrl = `${storageUrl}settings/${type}TypeIndex`;
|
|
14
14
|
|
|
15
|
-
return await solidDocumentExists(typeIndexUrl, fetch)
|
|
15
|
+
return await solidDocumentExists(typeIndexUrl, { fetch })
|
|
16
16
|
? `${storageUrl}settings/${type}TypeIndex-${uuid()}`
|
|
17
17
|
: typeIndexUrl;
|
|
18
18
|
}
|
|
@@ -38,10 +38,8 @@ async function createTypeIndex(user: SolidUserProfile, type: TypeIndexType, fetc
|
|
|
38
38
|
}
|
|
39
39
|
`;
|
|
40
40
|
|
|
41
|
-
await
|
|
42
|
-
|
|
43
|
-
updateSolidDocument(user.writableProfileUrl, profileUpdateBody, fetch),
|
|
44
|
-
]);
|
|
41
|
+
await createSolidDocument(typeIndexUrl, typeIndexBody, fetch);
|
|
42
|
+
await updateSolidDocument(user.writableProfileUrl, profileUpdateBody, fetch);
|
|
45
43
|
|
|
46
44
|
if (type === 'public') {
|
|
47
45
|
// TODO This is currently implemented in soukai-solid.
|
|
@@ -59,7 +57,7 @@ async function findRegistrations(
|
|
|
59
57
|
predicate: string,
|
|
60
58
|
fetch?: Fetch,
|
|
61
59
|
): Promise<string[]> {
|
|
62
|
-
const typeIndex = await fetchSolidDocument(typeIndexUrl, fetch);
|
|
60
|
+
const typeIndex = await fetchSolidDocument(typeIndexUrl, { fetch });
|
|
63
61
|
const types = Array.isArray(type) ? type : [type];
|
|
64
62
|
|
|
65
63
|
return types.map(
|
package/src/helpers/io.ts
CHANGED
|
@@ -25,13 +25,24 @@ export declare type AnyFetch = (input: any, options?: any) => Promise<Response>;
|
|
|
25
25
|
export declare type TypedFetch = (input: RequestInfo, options?: RequestInit) => Promise<Response>;
|
|
26
26
|
export declare type Fetch = TypedFetch | AnyFetch;
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const ANONYMOUS_PREFIX = 'anonymous://';
|
|
29
|
+
const ANONYMOUS_PREFIX_LENGTH = ANONYMOUS_PREFIX.length;
|
|
30
|
+
|
|
31
|
+
async function fetchRawSolidDocument(
|
|
32
|
+
url: string,
|
|
33
|
+
options?: FetchSolidDocumentOptions,
|
|
34
|
+
): Promise<{ body: string; headers: Headers }> {
|
|
35
|
+
const requestOptions: RequestInit = {
|
|
30
36
|
headers: { Accept: 'text/turtle' },
|
|
31
37
|
};
|
|
32
38
|
|
|
39
|
+
if (options?.cache) {
|
|
40
|
+
requestOptions.cache = options.cache;
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
try {
|
|
34
|
-
const
|
|
44
|
+
const fetch = options?.fetch ?? window.fetch;
|
|
45
|
+
const response = await fetch(url, requestOptions);
|
|
35
46
|
|
|
36
47
|
if (response.status === 404)
|
|
37
48
|
throw new NotFoundError(url);
|
|
@@ -116,6 +127,33 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
|
|
|
116
127
|
return normalizedQuads;
|
|
117
128
|
}
|
|
118
129
|
|
|
130
|
+
function normalizeQuads(quads: Quad[]): string {
|
|
131
|
+
return quads.map(quad => ' ' + quadToTurtle(quad)).sort().join('\n');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function preprocessSubjects(jsonld: JsonLD): void {
|
|
135
|
+
if (!jsonld['@id']?.startsWith('#')) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
jsonld['@id'] = ANONYMOUS_PREFIX + jsonld['@id'];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function postprocessSubjects(quads: Quad[]): void {
|
|
143
|
+
for (const quad of quads) {
|
|
144
|
+
if (!quad.subject.value.startsWith(ANONYMOUS_PREFIX)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
quad.subject.value = quad.subject.value.slice(ANONYMOUS_PREFIX_LENGTH);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface FetchSolidDocumentOptions {
|
|
153
|
+
fetch?: Fetch;
|
|
154
|
+
cache?: RequestCache;
|
|
155
|
+
}
|
|
156
|
+
|
|
119
157
|
export interface ParsingOptions {
|
|
120
158
|
baseIRI: string;
|
|
121
159
|
normalizeBlankNodes: boolean;
|
|
@@ -140,16 +178,19 @@ export async function createSolidDocument(url: string, body: string, fetch?: Fet
|
|
|
140
178
|
return new SolidDocument(url, statements, new Headers({}));
|
|
141
179
|
}
|
|
142
180
|
|
|
143
|
-
export async function fetchSolidDocument(url: string,
|
|
144
|
-
const { body: data, headers } = await fetchRawSolidDocument(url,
|
|
181
|
+
export async function fetchSolidDocument(url: string, options?: FetchSolidDocumentOptions): Promise<SolidDocument> {
|
|
182
|
+
const { body: data, headers } = await fetchRawSolidDocument(url, options);
|
|
145
183
|
const statements = await turtleToQuads(data, { baseIRI: url });
|
|
146
184
|
|
|
147
185
|
return new SolidDocument(url, statements, headers);
|
|
148
186
|
}
|
|
149
187
|
|
|
150
|
-
export async function fetchSolidDocumentIfFound(
|
|
188
|
+
export async function fetchSolidDocumentIfFound(
|
|
189
|
+
url: string,
|
|
190
|
+
options?: FetchSolidDocumentOptions,
|
|
191
|
+
): Promise<SolidDocument | null> {
|
|
151
192
|
try {
|
|
152
|
-
const document = await fetchSolidDocument(url,
|
|
193
|
+
const document = await fetchSolidDocument(url, options);
|
|
153
194
|
|
|
154
195
|
return document;
|
|
155
196
|
} catch (error) {
|
|
@@ -167,7 +208,13 @@ export async function jsonldToQuads(jsonld: JsonLD, baseIRI?: string): Promise<Q
|
|
|
167
208
|
return graphQuads.flat();
|
|
168
209
|
}
|
|
169
210
|
|
|
170
|
-
|
|
211
|
+
preprocessSubjects(jsonld);
|
|
212
|
+
|
|
213
|
+
const quads = await (jsonLDToRDF(jsonld as JsonLdDocument, { base: baseIRI }) as Promise<Quad[]>);
|
|
214
|
+
|
|
215
|
+
postprocessSubjects(quads);
|
|
216
|
+
|
|
217
|
+
return quads;
|
|
171
218
|
}
|
|
172
219
|
|
|
173
220
|
export function normalizeSparql(sparql: string): string {
|
|
@@ -176,13 +223,19 @@ export function normalizeSparql(sparql: string): string {
|
|
|
176
223
|
return Object
|
|
177
224
|
.entries(quads)
|
|
178
225
|
.reduce((normalizedOperations, [operation, quads]) => {
|
|
179
|
-
const normalizedQuads = quads
|
|
226
|
+
const normalizedQuads = normalizeQuads(quads);
|
|
180
227
|
|
|
181
228
|
return normalizedOperations.concat(`${operation.toUpperCase()} DATA {\n${normalizedQuads}\n}`);
|
|
182
229
|
}, [] as string[])
|
|
183
230
|
.join(' ;\n');
|
|
184
231
|
}
|
|
185
232
|
|
|
233
|
+
export function normalizeTurtle(sparql: string): string {
|
|
234
|
+
const quads = turtleToQuadsSync(sparql);
|
|
235
|
+
|
|
236
|
+
return normalizeQuads(quads);
|
|
237
|
+
}
|
|
238
|
+
|
|
186
239
|
export function parseTurtle(turtle: string, options: Partial<ParsingOptions> = {}): Promise<RDFGraphData> {
|
|
187
240
|
const parserOptions = objectWithoutEmpty({ baseIRI: options.baseIRI });
|
|
188
241
|
const parser = new TurtleParser(parserOptions);
|
|
@@ -249,9 +302,9 @@ export function quadToTurtle(quad: Quad): string {
|
|
|
249
302
|
return writer.quadsToString([quad]).slice(0, -1);
|
|
250
303
|
}
|
|
251
304
|
|
|
252
|
-
export async function solidDocumentExists(url: string,
|
|
305
|
+
export async function solidDocumentExists(url: string, options?: FetchSolidDocumentOptions): Promise<boolean> {
|
|
253
306
|
try {
|
|
254
|
-
const document = await fetchSolidDocument(url,
|
|
307
|
+
const document = await fetchSolidDocument(url, options);
|
|
255
308
|
|
|
256
309
|
return !document.isEmpty();
|
|
257
310
|
} catch (error) {
|
package/src/helpers/vocabs.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface ExpandIRIOptions {
|
|
|
8
8
|
const knownPrefixes: RDFContext = {
|
|
9
9
|
acl: 'http://www.w3.org/ns/auth/acl#',
|
|
10
10
|
foaf: 'http://xmlns.com/foaf/0.1/',
|
|
11
|
+
ldp: 'http://www.w3.org/ns/ldp#',
|
|
11
12
|
pim: 'http://www.w3.org/ns/pim/space#',
|
|
12
13
|
purl: 'http://purl.org/dc/terms/',
|
|
13
14
|
rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
package/src/helpers/wac.ts
CHANGED
|
@@ -26,7 +26,7 @@ async function fetchEffectiveACL(
|
|
|
26
26
|
): Promise<SolidDocument> {
|
|
27
27
|
aclResourceUrl = aclResourceUrl ?? await fetchACLResourceUrl(resourceUrl, fetch);
|
|
28
28
|
|
|
29
|
-
const aclDocument = await fetchSolidDocumentIfFound(aclResourceUrl ?? '', fetch);
|
|
29
|
+
const aclDocument = await fetchSolidDocumentIfFound(aclResourceUrl ?? '', { fetch });
|
|
30
30
|
|
|
31
31
|
if (!aclDocument) {
|
|
32
32
|
return fetchEffectiveACL(requireUrlParentDirectory(resourceUrl), fetch);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { normalizeSparql } from '@/helpers/io';
|
|
2
|
-
import { jsonldEquals, sparqlEquals } from '@/helpers/testing';
|
|
1
|
+
import { normalizeSparql, normalizeTurtle } from '@/helpers/io';
|
|
2
|
+
import { jsonldEquals, sparqlEquals, turtleEquals } from '@/helpers/testing';
|
|
3
3
|
import type { EqualityResult } from '@/helpers/testing';
|
|
4
4
|
|
|
5
5
|
interface FormatResultOptions {
|
|
@@ -50,6 +50,16 @@ const matchers: jest.ExpectExtendMap = {
|
|
|
50
50
|
received: normalizeSparql(received),
|
|
51
51
|
});
|
|
52
52
|
},
|
|
53
|
+
toEqualTurtle(received, expected) {
|
|
54
|
+
const result = turtleEquals(expected, received);
|
|
55
|
+
|
|
56
|
+
return formatResult(result, {
|
|
57
|
+
context: this,
|
|
58
|
+
hint: 'toEqualTurtle',
|
|
59
|
+
expected: normalizeTurtle(expected),
|
|
60
|
+
received: normalizeTurtle(received),
|
|
61
|
+
});
|
|
62
|
+
},
|
|
53
63
|
};
|
|
54
64
|
|
|
55
65
|
export default matchers;
|
package/src/testing/index.ts
CHANGED
package/.semaphore/semaphore.yml
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
version: v1.0
|
|
2
|
-
name: Solid Utils
|
|
3
|
-
agent:
|
|
4
|
-
machine:
|
|
5
|
-
type: e1-standard-2
|
|
6
|
-
os_image: ubuntu1804
|
|
7
|
-
blocks:
|
|
8
|
-
- name: CI
|
|
9
|
-
task:
|
|
10
|
-
prologue:
|
|
11
|
-
commands:
|
|
12
|
-
- checkout
|
|
13
|
-
- nvm install
|
|
14
|
-
- cache restore
|
|
15
|
-
- npm ci
|
|
16
|
-
- cache store
|
|
17
|
-
jobs:
|
|
18
|
-
- name: Linting
|
|
19
|
-
commands:
|
|
20
|
-
- npm run lint
|
|
21
|
-
- name: Tests
|
|
22
|
-
commands:
|
|
23
|
-
- npm test
|
|
24
|
-
- name: Build
|
|
25
|
-
commands:
|
|
26
|
-
- npm run build
|
package/src/testing/faking.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { faker } from '@noeldemartin/faker';
|
|
2
|
-
import { stringToSlug } from '@noeldemartin/utils';
|
|
3
|
-
|
|
4
|
-
export interface ContainerOptions {
|
|
5
|
-
baseUrl: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface DocumentOptions extends ContainerOptions {
|
|
9
|
-
containerUrl: string;
|
|
10
|
-
name: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ResourceOptions extends DocumentOptions {
|
|
14
|
-
documentUrl: string;
|
|
15
|
-
hash: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function fakeContainerUrl(options: Partial<ContainerOptions> = {}): string {
|
|
19
|
-
const baseUrl = options.baseUrl ?? faker.internet.url();
|
|
20
|
-
|
|
21
|
-
return baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function fakeDocumentUrl(options: Partial<DocumentOptions> = {}): string {
|
|
25
|
-
const containerUrl = options.containerUrl ?? fakeContainerUrl(options);
|
|
26
|
-
const name = options.name ?? faker.random.word();
|
|
27
|
-
|
|
28
|
-
return containerUrl + stringToSlug(name);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function fakeResourceUrl(options: Partial<ResourceOptions> = {}): string {
|
|
32
|
-
const documentUrl = options.documentUrl ?? fakeDocumentUrl(options);
|
|
33
|
-
const hash = options.hash ?? 'it';
|
|
34
|
-
|
|
35
|
-
return documentUrl + '#' + hash;
|
|
36
|
-
}
|