@noeldemartin/solid-utils 0.1.1-next.8c064afc67573f7cf20fa4cbeecfad10a949a9ef → 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.
- package/.nvmrc +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 +78 -17
- package/dist/noeldemartin-solid-utils.esm.js +1 -1
- package/dist/noeldemartin-solid-utils.esm.js.map +1 -1
- package/package.json +10 -13
- package/src/errors/MalformedSolidDocumentError.ts +2 -2
- package/src/errors/NetworkRequestError.ts +5 -4
- package/src/errors/NotFoundError.ts +2 -2
- package/src/errors/UnauthorizedError.ts +2 -2
- package/src/errors/UnsuccessfulNetworkRequestError.ts +23 -0
- package/src/errors/index.ts +1 -0
- package/src/helpers/auth.ts +82 -16
- package/src/helpers/identifiers.ts +56 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/interop.ts +68 -30
- package/src/helpers/io.ts +133 -66
- package/src/helpers/jsonld.ts +17 -0
- package/src/helpers/testing.ts +69 -33
- package/src/helpers/vocabs.ts +4 -2
- package/src/helpers/wac.ts +45 -0
- package/src/models/SolidDocument.ts +38 -37
- package/src/models/SolidStore.ts +61 -0
- package/src/models/index.ts +2 -1
- package/src/plugins/jest/matchers.ts +5 -7
- package/src/types/n3.d.ts +9 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { objectWithoutEmpty, requireUrlParentDirectory, urlResolve } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
import { fetchSolidDocumentIfFound } from '@/helpers/io';
|
|
4
|
+
import type SolidDocument from '@/models/SolidDocument';
|
|
5
|
+
import type { Fetch } from '@/helpers/io';
|
|
6
|
+
|
|
7
|
+
async function fetchACLResourceUrl(resourceUrl: string, fetch: Fetch): Promise<string> {
|
|
8
|
+
fetch = fetch ?? window.fetch.bind(window);
|
|
9
|
+
|
|
10
|
+
const resourceHead = await fetch(resourceUrl, { method: 'HEAD' });
|
|
11
|
+
const linkHeader = resourceHead.headers.get('Link') ?? '';
|
|
12
|
+
const url = linkHeader.match(/<([^>]+)>;\s*rel="acl"/)?.[1] ?? null;
|
|
13
|
+
|
|
14
|
+
if (!url) {
|
|
15
|
+
throw new Error(`Could not find ACL Resource for '${resourceUrl}'`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return urlResolve(requireUrlParentDirectory(resourceUrl), url);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function fetchEffectiveACL(
|
|
22
|
+
resourceUrl: string,
|
|
23
|
+
fetch: Fetch,
|
|
24
|
+
aclResourceUrl?: string | null,
|
|
25
|
+
): Promise<SolidDocument> {
|
|
26
|
+
aclResourceUrl = aclResourceUrl ?? await fetchACLResourceUrl(resourceUrl, fetch);
|
|
27
|
+
|
|
28
|
+
return await fetchSolidDocumentIfFound(aclResourceUrl ?? '', fetch)
|
|
29
|
+
?? await fetchEffectiveACL(requireUrlParentDirectory(resourceUrl), fetch);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function fetchSolidDocumentACL(documentUrl: string, fetch: Fetch): Promise<{
|
|
33
|
+
url: string;
|
|
34
|
+
effectiveUrl: string;
|
|
35
|
+
document: SolidDocument;
|
|
36
|
+
}> {
|
|
37
|
+
const url = await fetchACLResourceUrl(documentUrl, fetch);
|
|
38
|
+
const document = await fetchEffectiveACL(documentUrl, fetch, url);
|
|
39
|
+
|
|
40
|
+
return objectWithoutEmpty({
|
|
41
|
+
url,
|
|
42
|
+
effectiveUrl: document.url,
|
|
43
|
+
document,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -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
|
|
6
|
+
import SolidStore from './SolidStore';
|
|
7
7
|
|
|
8
|
-
export
|
|
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('
|
|
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
|
|
37
|
-
return
|
|
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
|
|
44
|
-
return this.
|
|
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
|
|
53
|
-
|
|
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
|
|
64
|
-
return this.
|
|
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
|
-
|
|
68
|
-
|
|
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
|
+
}
|
package/src/models/index.ts
CHANGED
|
@@ -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
|
}
|