@noeldemartin/solid-utils 0.4.0-next.852c9f9e65275fc2a2e67a9750784fb43a0fd64b → 0.4.0-next.c9d36a1c12735b50a915e6ffeed60e60569706c0
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.cjs.js +1 -1
- package/dist/noeldemartin-solid-utils.cjs.js.map +1 -1
- package/dist/noeldemartin-solid-utils.d.ts +9 -22
- 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 +2 -3
- package/src/helpers/auth.ts +14 -8
- package/src/helpers/interop.ts +2 -4
- package/src/helpers/io.ts +40 -2
- 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/src/testing/faking.ts +0 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noeldemartin/solid-utils",
|
|
3
|
-
"version": "0.4.0-next.
|
|
3
|
+
"version": "0.4.0-next.c9d36a1c12735b50a915e6ffeed60e60569706c0",
|
|
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
35
|
"@noeldemartin/utils": "^0.5.1",
|
|
37
|
-
"@types
|
|
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
|
@@ -80,22 +80,22 @@ 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
84
|
const documentUrl = urlRoute(webId);
|
|
85
|
-
const document = await fetchSolidDocument(documentUrl, fetch);
|
|
85
|
+
const document = await fetchSolidDocument(documentUrl, options.fetch);
|
|
86
86
|
|
|
87
87
|
if (!document.isPersonalProfile() && !document.contains(webId, 'solid:oidcIssuer')) {
|
|
88
88
|
throw new Error(`${webId} is not a valid webId.`);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
const { store, writableProfileUrl, cloaked } = await fetchExtendedUserProfile(document, fetch);
|
|
91
|
+
const { store, writableProfileUrl, cloaked } = await fetchExtendedUserProfile(document, options.fetch);
|
|
92
92
|
const storageUrls = store.statements(webId, 'pim:storage').map(storage => storage.object.value);
|
|
93
93
|
const publicTypeIndex = store.statement(webId, 'solid:publicTypeIndex');
|
|
94
94
|
const privateTypeIndex = store.statement(webId, 'solid:privateTypeIndex');
|
|
95
95
|
|
|
96
96
|
let parentUrl = urlParentDirectory(documentUrl);
|
|
97
97
|
while (parentUrl && storageUrls.length === 0) {
|
|
98
|
-
const parentDocument = await silenced(fetchSolidDocument(parentUrl, fetch));
|
|
98
|
+
const parentDocument = await silenced(fetchSolidDocument(parentUrl, options.fetch));
|
|
99
99
|
|
|
100
100
|
if (parentDocument?.isStorage()) {
|
|
101
101
|
storageUrls.push(parentUrl);
|
|
@@ -110,6 +110,8 @@ async function fetchUserProfile(webId: string, fetch?: Fetch): Promise<SolidUser
|
|
|
110
110
|
throw new Error(`Could not find any storage for ${webId}.`);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
await options.onLoaded?.(new SolidStore(store.statements(webId)));
|
|
114
|
+
|
|
113
115
|
return {
|
|
114
116
|
webId,
|
|
115
117
|
cloaked,
|
|
@@ -129,9 +131,13 @@ async function fetchUserProfile(webId: string, fetch?: Fetch): Promise<SolidUser
|
|
|
129
131
|
};
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
export interface
|
|
133
|
-
required?: boolean;
|
|
134
|
+
export interface FetchUserProfileOptions {
|
|
134
135
|
fetch?: Fetch;
|
|
136
|
+
onLoaded?(store: SolidStore): Promise<unknown> | unknown;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface FetchLoginUserProfileOptions extends FetchUserProfileOptions {
|
|
140
|
+
required?: boolean;
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
export async function fetchLoginUserProfile(
|
|
@@ -139,10 +145,10 @@ export async function fetchLoginUserProfile(
|
|
|
139
145
|
options: FetchLoginUserProfileOptions = {},
|
|
140
146
|
): Promise<SolidUserProfile | null> {
|
|
141
147
|
if (options.required) {
|
|
142
|
-
return fetchUserProfile(loginUrl, options
|
|
148
|
+
return fetchUserProfile(loginUrl, options);
|
|
143
149
|
}
|
|
144
150
|
|
|
145
|
-
const fetchProfile = silenced(url => fetchUserProfile(url, options
|
|
151
|
+
const fetchProfile = silenced(url => fetchUserProfile(url, options));
|
|
146
152
|
|
|
147
153
|
return await fetchProfile(loginUrl)
|
|
148
154
|
?? await fetchProfile(loginUrl.replace(/\/$/, '').concat('/profile/card#me'))
|
package/src/helpers/interop.ts
CHANGED
|
@@ -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.
|
package/src/helpers/io.ts
CHANGED
|
@@ -25,6 +25,9 @@ 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
|
+
const ANONYMOUS_PREFIX = 'anonymous://';
|
|
29
|
+
const ANONYMOUS_PREFIX_LENGTH = ANONYMOUS_PREFIX.length;
|
|
30
|
+
|
|
28
31
|
async function fetchRawSolidDocument(url: string, fetch: Fetch): Promise<{ body: string; headers: Headers }> {
|
|
29
32
|
const options = {
|
|
30
33
|
headers: { Accept: 'text/turtle' },
|
|
@@ -116,6 +119,28 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
|
|
|
116
119
|
return normalizedQuads;
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
function normalizeQuads(quads: Quad[]): string {
|
|
123
|
+
return quads.map(quad => ' ' + quadToTurtle(quad)).sort().join('\n');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function preprocessSubjects(jsonld: JsonLD): void {
|
|
127
|
+
if (!jsonld['@id']?.startsWith('#')) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
jsonld['@id'] = ANONYMOUS_PREFIX + jsonld['@id'];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function postprocessSubjects(quads: Quad[]): void {
|
|
135
|
+
for (const quad of quads) {
|
|
136
|
+
if (!quad.subject.value.startsWith(ANONYMOUS_PREFIX)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
quad.subject.value = quad.subject.value.slice(ANONYMOUS_PREFIX_LENGTH);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
119
144
|
export interface ParsingOptions {
|
|
120
145
|
baseIRI: string;
|
|
121
146
|
normalizeBlankNodes: boolean;
|
|
@@ -126,6 +151,7 @@ export interface RDFGraphData {
|
|
|
126
151
|
containsRelativeIRIs: boolean;
|
|
127
152
|
}
|
|
128
153
|
|
|
154
|
+
|
|
129
155
|
export async function createSolidDocument(url: string, body: string, fetch?: Fetch): Promise<SolidDocument> {
|
|
130
156
|
fetch = fetch ?? window.fetch.bind(window);
|
|
131
157
|
|
|
@@ -167,7 +193,13 @@ export async function jsonldToQuads(jsonld: JsonLD, baseIRI?: string): Promise<Q
|
|
|
167
193
|
return graphQuads.flat();
|
|
168
194
|
}
|
|
169
195
|
|
|
170
|
-
|
|
196
|
+
preprocessSubjects(jsonld);
|
|
197
|
+
|
|
198
|
+
const quads = await (jsonLDToRDF(jsonld as JsonLdDocument, { base: baseIRI }) as Promise<Quad[]>);
|
|
199
|
+
|
|
200
|
+
postprocessSubjects(quads);
|
|
201
|
+
|
|
202
|
+
return quads;
|
|
171
203
|
}
|
|
172
204
|
|
|
173
205
|
export function normalizeSparql(sparql: string): string {
|
|
@@ -176,13 +208,19 @@ export function normalizeSparql(sparql: string): string {
|
|
|
176
208
|
return Object
|
|
177
209
|
.entries(quads)
|
|
178
210
|
.reduce((normalizedOperations, [operation, quads]) => {
|
|
179
|
-
const normalizedQuads = quads
|
|
211
|
+
const normalizedQuads = normalizeQuads(quads);
|
|
180
212
|
|
|
181
213
|
return normalizedOperations.concat(`${operation.toUpperCase()} DATA {\n${normalizedQuads}\n}`);
|
|
182
214
|
}, [] as string[])
|
|
183
215
|
.join(' ;\n');
|
|
184
216
|
}
|
|
185
217
|
|
|
218
|
+
export function normalizeTurtle(sparql: string): string {
|
|
219
|
+
const quads = turtleToQuadsSync(sparql);
|
|
220
|
+
|
|
221
|
+
return normalizeQuads(quads);
|
|
222
|
+
}
|
|
223
|
+
|
|
186
224
|
export function parseTurtle(turtle: string, options: Partial<ParsingOptions> = {}): Promise<RDFGraphData> {
|
|
187
225
|
const parserOptions = objectWithoutEmpty({ baseIRI: options.baseIRI });
|
|
188
226
|
const parser = new TurtleParser(parserOptions);
|
|
@@ -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/src/testing/faking.ts
DELETED
|
@@ -1,38 +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 containerSlug = stringToSlug(faker.random.word());
|
|
20
|
-
const baseUrl = options.baseUrl ?? faker.internet.url();
|
|
21
|
-
const parentContainerUrl = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';
|
|
22
|
-
|
|
23
|
-
return parentContainerUrl + containerSlug + '/';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function fakeDocumentUrl(options: Partial<DocumentOptions> = {}): string {
|
|
27
|
-
const containerUrl = options.containerUrl ?? fakeContainerUrl(options);
|
|
28
|
-
const name = options.name ?? faker.random.word();
|
|
29
|
-
|
|
30
|
-
return containerUrl + stringToSlug(name);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function fakeResourceUrl(options: Partial<ResourceOptions> = {}): string {
|
|
34
|
-
const documentUrl = options.documentUrl ?? fakeDocumentUrl(options);
|
|
35
|
-
const hash = options.hash ?? 'it';
|
|
36
|
-
|
|
37
|
-
return documentUrl + '#' + hash;
|
|
38
|
-
}
|