@noeldemartin/solid-utils 0.1.1 → 0.2.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/.nvmrc +1 -0
- package/.semaphore/semaphore.yml +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 +94 -16
- 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 +90 -0
- package/dist/noeldemartin-solid-utils.umd.js.map +1 -0
- package/noeldemartin.config.js +4 -2
- package/package.json +11 -11
- 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/UnsupportedAuthorizationProtocolError.ts +16 -0
- package/src/errors/index.ts +2 -0
- package/src/helpers/auth.ts +91 -18
- 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 +147 -60
- package/src/helpers/jsonld.ts +25 -1
- package/src/helpers/testing.ts +103 -28
- package/src/helpers/vocabs.ts +4 -2
- package/src/helpers/wac.ts +55 -0
- package/src/models/SolidDocument.ts +41 -35
- package/src/models/SolidStore.ts +61 -0
- package/src/models/index.ts +2 -1
- package/src/plugins/chai/assertions.ts +11 -2
- package/src/plugins/cypress/types.d.ts +1 -0
- package/src/plugins/jest/matchers.ts +45 -32
- package/src/plugins/jest/types.d.ts +1 -0
- package/src/types/n3.d.ts +9 -0
package/noeldemartin.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noeldemartin/solid-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|
|
7
|
+
"browser": "dist/noeldemartin-solid-utils.umd.js",
|
|
7
8
|
"types": "dist/noeldemartin-solid-utils.d.ts",
|
|
8
9
|
"scripts": {
|
|
9
10
|
"build": "rm dist -rf && npm run build:js && npm run build:types",
|
|
10
11
|
"build:js": "noeldemartin-build-javascript",
|
|
11
12
|
"build:types": "noeldemartin-build-types",
|
|
12
|
-
"lint": "
|
|
13
|
+
"lint": "noeldemartin-lint src",
|
|
13
14
|
"publish-next": "noeldemartin-publish-next",
|
|
14
15
|
"test": "jest --verbose",
|
|
15
16
|
"test:coverage": "jest --coverage"
|
|
16
17
|
},
|
|
17
|
-
"engines": {
|
|
18
|
-
"node": ">=14.x"
|
|
19
|
-
},
|
|
20
18
|
"repository": {
|
|
21
19
|
"type": "git",
|
|
22
20
|
"url": "git+https://github.com/noeldemartin/solid-utils.git"
|
|
23
21
|
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=14.x"
|
|
24
|
+
},
|
|
24
25
|
"author": "Noel De Martin",
|
|
25
26
|
"license": "MIT",
|
|
26
27
|
"bugs": {
|
|
@@ -29,12 +30,12 @@
|
|
|
29
30
|
"homepage": "https://github.com/noeldemartin/solid-utils",
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@babel/runtime": "^7.14.0",
|
|
32
|
-
"@noeldemartin/utils": "^0.
|
|
33
|
+
"@noeldemartin/solid-utils-external": "^0.1.0",
|
|
34
|
+
"@noeldemartin/utils": "^0.3.0",
|
|
33
35
|
"@types/rdf-js": "^4.0.1",
|
|
34
36
|
"core-js": "^3.12.1",
|
|
35
37
|
"jest-diff": "^26.6.2",
|
|
36
|
-
"md5": "^2.3.0"
|
|
37
|
-
"n3": "^1.10.0"
|
|
38
|
+
"md5": "^2.3.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@babel/core": "^7.14.3",
|
|
@@ -43,8 +44,8 @@
|
|
|
43
44
|
"@babel/preset-env": "^7.14.2",
|
|
44
45
|
"@babel/preset-typescript": "^7.13.0",
|
|
45
46
|
"@microsoft/api-extractor": "^7.15.2",
|
|
46
|
-
"@noeldemartin/eslint-config-typescript": "^0.1.
|
|
47
|
-
"@noeldemartin/scripts": "^0.1.
|
|
47
|
+
"@noeldemartin/eslint-config-typescript": "^0.1.1",
|
|
48
|
+
"@noeldemartin/scripts": "^0.1.2",
|
|
48
49
|
"@rollup/plugin-alias": "^3.1.2",
|
|
49
50
|
"@rollup/plugin-babel": "^5.3.0",
|
|
50
51
|
"@rollup/plugin-commonjs": "^19.0.0",
|
|
@@ -54,7 +55,6 @@
|
|
|
54
55
|
"@types/jest": "^26.0.23",
|
|
55
56
|
"@types/jest-diff": "^24.3.0",
|
|
56
57
|
"@types/md5": "^2.3.0",
|
|
57
|
-
"@types/n3": "^1.8.0",
|
|
58
58
|
"babel-plugin-transform-remove-imports": "^1.5.4",
|
|
59
59
|
"eslint": "^7.26.0",
|
|
60
60
|
"jest": "^26.6.3",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSError } from '@noeldemartin/utils';
|
|
2
2
|
|
|
3
3
|
function errorMessage(
|
|
4
4
|
documentUrl: string | null,
|
|
@@ -14,7 +14,7 @@ export enum SolidDocumentFormat {
|
|
|
14
14
|
Turtle = 'Turtle',
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export default class MalformedSolidDocumentError extends
|
|
17
|
+
export default class MalformedSolidDocumentError extends JSError {
|
|
18
18
|
|
|
19
19
|
public readonly documentUrl: string | null;
|
|
20
20
|
public readonly documentFormat: SolidDocumentFormat;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSError } from '@noeldemartin/utils';
|
|
2
|
+
import type { JSErrorOptions } from '@noeldemartin/utils';
|
|
2
3
|
|
|
3
|
-
export default class NetworkRequestError extends
|
|
4
|
+
export default class NetworkRequestError extends JSError {
|
|
4
5
|
|
|
5
6
|
public readonly url: string;
|
|
6
7
|
|
|
7
|
-
constructor(url: string) {
|
|
8
|
-
super(`Request failed trying to fetch ${url}
|
|
8
|
+
constructor(url: string, options?: JSErrorOptions) {
|
|
9
|
+
super(`Request failed trying to fetch ${url}`, options);
|
|
9
10
|
|
|
10
11
|
this.url = url;
|
|
11
12
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSError } from '@noeldemartin/utils';
|
|
2
2
|
|
|
3
3
|
function errorMessage(url: string, responseStatus?: number): string {
|
|
4
4
|
const typeInfo = responseStatus === 403 ? ' (Forbidden)' : '';
|
|
@@ -6,7 +6,7 @@ function errorMessage(url: string, responseStatus?: number): string {
|
|
|
6
6
|
return `Unauthorized${typeInfo}: ${url}`;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export default class UnauthorizedError extends
|
|
9
|
+
export default class UnauthorizedError extends JSError {
|
|
10
10
|
|
|
11
11
|
public readonly url: string;
|
|
12
12
|
public readonly responseStatus?: number;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { JSError } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
function getErrorMessage(messageOrResponse: string | Response, response?: Response): string {
|
|
4
|
+
response = response ?? messageOrResponse as Response;
|
|
5
|
+
|
|
6
|
+
return typeof messageOrResponse === 'string'
|
|
7
|
+
? `${messageOrResponse} (returned ${response.status} status code)`
|
|
8
|
+
: `Request to ${response.url} returned ${response.status} status code`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default class UnsuccessfulRequestError extends JSError {
|
|
12
|
+
|
|
13
|
+
public response: Response;
|
|
14
|
+
|
|
15
|
+
constructor(response: Response);
|
|
16
|
+
constructor(message: string, response: Response);
|
|
17
|
+
constructor(messageOrResponse: string | Response, response?: Response) {
|
|
18
|
+
super(getErrorMessage(messageOrResponse, response));
|
|
19
|
+
|
|
20
|
+
this.response = response ?? messageOrResponse as Response;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { JSError } from '@noeldemartin/utils';
|
|
2
|
+
import type { JSErrorOptions } from '@noeldemartin/utils';
|
|
3
|
+
|
|
4
|
+
export default class UnsupportedAuthorizationProtocolError extends JSError {
|
|
5
|
+
|
|
6
|
+
public readonly url: string;
|
|
7
|
+
public readonly protocol: string;
|
|
8
|
+
|
|
9
|
+
constructor(url: string, protocol: string, options?: JSErrorOptions) {
|
|
10
|
+
super(`The resource at ${url} is using an unsupported authorization protocol (${protocol})`, options);
|
|
11
|
+
|
|
12
|
+
this.url = url;
|
|
13
|
+
this.protocol = protocol;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
package/src/errors/index.ts
CHANGED
|
@@ -2,3 +2,5 @@ export { default as MalformedSolidDocumentError, SolidDocumentFormat } from './M
|
|
|
2
2
|
export { default as NetworkRequestError } from './NetworkRequestError';
|
|
3
3
|
export { default as NotFoundError } from './NotFoundError';
|
|
4
4
|
export { default as UnauthorizedError } from './UnauthorizedError';
|
|
5
|
+
export { default as UnsuccessfulNetworkRequestError } from './UnsuccessfulNetworkRequestError';
|
|
6
|
+
export { default as UnsupportedAuthorizationProtocolError } from './UnsupportedAuthorizationProtocolError';
|
package/src/helpers/auth.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { objectWithoutEmpty, silenced, urlParentDirectory, urlRoot, urlRoute } from '@noeldemartin/utils';
|
|
1
|
+
import { arrayUnique, objectWithoutEmpty, silenced, urlParentDirectory, urlRoot, urlRoute } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
import SolidStore from '../models/SolidStore';
|
|
4
|
+
import UnauthorizedError from '../errors/UnauthorizedError';
|
|
5
|
+
import type SolidDocument from '../models/SolidDocument';
|
|
2
6
|
|
|
3
7
|
import { fetchSolidDocument } from './io';
|
|
4
8
|
import type { Fetch } from './io';
|
|
@@ -6,6 +10,8 @@ import type { Fetch } from './io';
|
|
|
6
10
|
export interface SolidUserProfile {
|
|
7
11
|
webId: string;
|
|
8
12
|
storageUrls: string[];
|
|
13
|
+
cloaked: boolean;
|
|
14
|
+
writableProfileUrl: string | null;
|
|
9
15
|
name?: string;
|
|
10
16
|
avatarUrl?: string;
|
|
11
17
|
oidcIssuerUrl?: string;
|
|
@@ -13,16 +19,79 @@ export interface SolidUserProfile {
|
|
|
13
19
|
privateTypeIndexUrl?: string;
|
|
14
20
|
}
|
|
15
21
|
|
|
22
|
+
async function fetchExtendedUserProfile(webIdDocument: SolidDocument, fetch?: Fetch): Promise<{
|
|
23
|
+
store: SolidStore;
|
|
24
|
+
cloaked: boolean;
|
|
25
|
+
writableProfileUrl: string | null;
|
|
26
|
+
}> {
|
|
27
|
+
const store = new SolidStore(webIdDocument.getQuads());
|
|
28
|
+
const documents: Record<string, SolidDocument | false | null> = { [webIdDocument.url]: webIdDocument };
|
|
29
|
+
const addReferencedDocumentUrls = (document: SolidDocument) => {
|
|
30
|
+
document
|
|
31
|
+
.statements(undefined, 'foaf:isPrimaryTopicOf')
|
|
32
|
+
.map(quad => quad.object.value)
|
|
33
|
+
.forEach(profileDocumentUrl => documents[profileDocumentUrl] = documents[profileDocumentUrl] ?? null);
|
|
34
|
+
document
|
|
35
|
+
.statements(undefined, 'foaf:primaryTopic')
|
|
36
|
+
.map(quad => quad.subject.value)
|
|
37
|
+
.forEach(profileDocumentUrl => documents[profileDocumentUrl] = documents[profileDocumentUrl] ?? null);
|
|
38
|
+
};
|
|
39
|
+
const loadProfileDocuments = async (): Promise<void> => {
|
|
40
|
+
for (const [url, document] of Object.entries(documents)) {
|
|
41
|
+
if (document !== null) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const document = await fetchSolidDocument(url, fetch);
|
|
47
|
+
|
|
48
|
+
documents[url] = document;
|
|
49
|
+
store.addQuads(document.getQuads());
|
|
50
|
+
|
|
51
|
+
addReferencedDocumentUrls(document);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof UnauthorizedError) {
|
|
54
|
+
documents[url] = false;
|
|
55
|
+
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
addReferencedDocumentUrls(webIdDocument);
|
|
65
|
+
|
|
66
|
+
do {
|
|
67
|
+
await loadProfileDocuments();
|
|
68
|
+
} while (Object.values(documents).some(document => document === null));
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
store,
|
|
72
|
+
cloaked: Object.values(documents).some(document => document === false),
|
|
73
|
+
writableProfileUrl:
|
|
74
|
+
webIdDocument.isUserWritable()
|
|
75
|
+
? webIdDocument.url
|
|
76
|
+
: Object
|
|
77
|
+
.values(documents)
|
|
78
|
+
.find((document): document is SolidDocument => !!document && document.isUserWritable())
|
|
79
|
+
?.url ?? null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
16
83
|
async function fetchUserProfile(webId: string, fetch?: Fetch): Promise<SolidUserProfile> {
|
|
17
84
|
const documentUrl = urlRoute(webId);
|
|
18
85
|
const document = await fetchSolidDocument(documentUrl, fetch);
|
|
19
86
|
|
|
20
|
-
if (!document.isPersonalProfile())
|
|
21
|
-
throw new Error(
|
|
87
|
+
if (!document.isPersonalProfile() && !document.contains(webId, 'solid:oidcIssuer')) {
|
|
88
|
+
throw new Error(`${webId} is not a valid webId.`);
|
|
89
|
+
}
|
|
22
90
|
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
91
|
+
const { store, writableProfileUrl, cloaked } = await fetchExtendedUserProfile(document, fetch);
|
|
92
|
+
const storageUrls = store.statements(webId, 'pim:storage').map(storage => storage.object.value);
|
|
93
|
+
const publicTypeIndex = store.statement(webId, 'solid:publicTypeIndex');
|
|
94
|
+
const privateTypeIndex = store.statement(webId, 'solid:privateTypeIndex');
|
|
26
95
|
|
|
27
96
|
let parentUrl = urlParentDirectory(documentUrl);
|
|
28
97
|
while (parentUrl && storageUrls.length === 0) {
|
|
@@ -37,19 +106,23 @@ async function fetchUserProfile(webId: string, fetch?: Fetch): Promise<SolidUser
|
|
|
37
106
|
parentUrl = urlParentDirectory(parentUrl);
|
|
38
107
|
}
|
|
39
108
|
|
|
40
|
-
return
|
|
109
|
+
return {
|
|
41
110
|
webId,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
111
|
+
cloaked,
|
|
112
|
+
writableProfileUrl,
|
|
113
|
+
storageUrls: arrayUnique(storageUrls),
|
|
114
|
+
...objectWithoutEmpty({
|
|
115
|
+
name:
|
|
116
|
+
store.statement(webId, 'vcard:fn')?.object.value ??
|
|
117
|
+
store.statement(webId, 'foaf:name')?.object.value,
|
|
118
|
+
avatarUrl:
|
|
119
|
+
store.statement(webId, 'vcard:hasPhoto')?.object.value ??
|
|
120
|
+
store.statement(webId, 'foaf:img')?.object.value,
|
|
121
|
+
oidcIssuerUrl: store.statement(webId, 'solid:oidcIssuer')?.object.value,
|
|
122
|
+
publicTypeIndexUrl: publicTypeIndex?.object.value,
|
|
123
|
+
privateTypeIndexUrl: privateTypeIndex?.object.value,
|
|
124
|
+
}),
|
|
125
|
+
};
|
|
53
126
|
}
|
|
54
127
|
|
|
55
128
|
export async function fetchLoginUserProfile(loginUrl: string, fetch?: Fetch): Promise<SolidUserProfile | null> {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { arr, isArray, isObject, objectDeepClone, objectWithoutEmpty, tap, urlParse, uuid } from '@noeldemartin/utils';
|
|
2
|
+
import type { UrlParts } from '@noeldemartin/utils';
|
|
3
|
+
import type { JsonLD, JsonLDResource } from '@/helpers';
|
|
4
|
+
|
|
5
|
+
export interface SubjectParts {
|
|
6
|
+
containerUrl?: string;
|
|
7
|
+
documentName?: string;
|
|
8
|
+
resourceHash?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getContainerPath(parts: UrlParts): string | null {
|
|
12
|
+
if (!parts.path || !parts.path.startsWith('/'))
|
|
13
|
+
return null;
|
|
14
|
+
|
|
15
|
+
if (parts.path.match(/^\/[^/]*$/))
|
|
16
|
+
return '/';
|
|
17
|
+
|
|
18
|
+
return `/${arr(parts.path.split('/')).filter().slice(0, -1).join('/')}/`.replace('//', '/');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getContainerUrl(parts: UrlParts): string | null {
|
|
22
|
+
const containerPath = getContainerPath(parts);
|
|
23
|
+
|
|
24
|
+
return parts.protocol && parts.domain
|
|
25
|
+
? `${parts.protocol}://${parts.domain}${containerPath ?? '/'}`
|
|
26
|
+
: containerPath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function __mintJsonLDIdentifiers(jsonld: JsonLD): void {
|
|
30
|
+
if (!('@type' in jsonld) || '@value' in jsonld)
|
|
31
|
+
return;
|
|
32
|
+
|
|
33
|
+
jsonld['@id'] = jsonld['@id'] ?? uuid();
|
|
34
|
+
|
|
35
|
+
for (const propertyValue of Object.values(jsonld)) {
|
|
36
|
+
if (isObject(propertyValue))
|
|
37
|
+
__mintJsonLDIdentifiers(propertyValue);
|
|
38
|
+
|
|
39
|
+
if (isArray(propertyValue))
|
|
40
|
+
propertyValue.forEach(value => isObject(value) && __mintJsonLDIdentifiers(value));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function mintJsonLDIdentifiers(jsonld: JsonLD): JsonLDResource {
|
|
45
|
+
return tap(objectDeepClone(jsonld) as JsonLDResource, clone => __mintJsonLDIdentifiers(clone));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function parseResourceSubject(subject: string): SubjectParts {
|
|
49
|
+
const parts = urlParse(subject);
|
|
50
|
+
|
|
51
|
+
return !parts ? {} : objectWithoutEmpty({
|
|
52
|
+
containerUrl: getContainerUrl(parts),
|
|
53
|
+
documentName: parts.path ? parts.path.split('/').pop() : null,
|
|
54
|
+
resourceHash: parts.fragment,
|
|
55
|
+
});
|
|
56
|
+
}
|
package/src/helpers/index.ts
CHANGED
package/src/helpers/interop.ts
CHANGED
|
@@ -1,57 +1,95 @@
|
|
|
1
1
|
import { uuid } from '@noeldemartin/utils';
|
|
2
2
|
|
|
3
3
|
import { createSolidDocument, fetchSolidDocument, solidDocumentExists, updateSolidDocument } from '@/helpers/io';
|
|
4
|
-
import type SolidThing from '@/models/SolidThing';
|
|
5
4
|
import type { Fetch } from '@/helpers/io';
|
|
6
5
|
import type { SolidUserProfile } from '@/helpers/auth';
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
type TypeIndexType = 'public' | 'private';
|
|
8
|
+
|
|
9
|
+
async function mintTypeIndexUrl(user: SolidUserProfile, type: TypeIndexType, fetch?: Fetch): Promise<string> {
|
|
10
|
+
fetch = fetch ?? window.fetch.bind(fetch);
|
|
10
11
|
|
|
11
12
|
const storageUrl = user.storageUrls[0];
|
|
12
|
-
const typeIndexUrl = `${storageUrl}settings
|
|
13
|
+
const typeIndexUrl = `${storageUrl}settings/${type}TypeIndex`;
|
|
13
14
|
|
|
14
15
|
return await solidDocumentExists(typeIndexUrl, fetch)
|
|
15
|
-
? `${storageUrl}settings
|
|
16
|
+
? `${storageUrl}settings/${type}TypeIndex-${uuid()}`
|
|
16
17
|
: typeIndexUrl;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
async function createTypeIndex(user: SolidUserProfile, type: TypeIndexType, fetch?: Fetch) {
|
|
21
|
+
if (user.writableProfileUrl === null) {
|
|
22
|
+
throw new Error('Can\'t create type index without a writable profile document');
|
|
23
|
+
}
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
fetch = fetch ?? window.fetch.bind(fetch);
|
|
26
|
+
|
|
27
|
+
const typeIndexUrl = await mintTypeIndexUrl(user, type, fetch);
|
|
28
|
+
const typeIndexBody = type === 'public'
|
|
29
|
+
? '<> a <http://www.w3.org/ns/solid/terms#TypeIndex> .'
|
|
30
|
+
: `
|
|
31
|
+
<> a
|
|
32
|
+
<http://www.w3.org/ns/solid/terms#TypeIndex>,
|
|
33
|
+
<http://www.w3.org/ns/solid/terms#UnlistedDocument> .
|
|
34
|
+
`;
|
|
28
35
|
const profileUpdateBody = `
|
|
29
36
|
INSERT DATA {
|
|
30
|
-
<${user.webId}> <http://www.w3.org/ns/solid/terms
|
|
37
|
+
<${user.webId}> <http://www.w3.org/ns/solid/terms#${type}TypeIndex> <${typeIndexUrl}> .
|
|
31
38
|
}
|
|
32
39
|
`;
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
await Promise.all([
|
|
42
|
+
createSolidDocument(typeIndexUrl, typeIndexBody, fetch),
|
|
43
|
+
updateSolidDocument(user.writableProfileUrl, profileUpdateBody, fetch),
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
if (type === 'public') {
|
|
47
|
+
// TODO Implement updating ACLs for the listing itself to public
|
|
48
|
+
}
|
|
36
49
|
|
|
37
50
|
return typeIndexUrl;
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
async function findRegistrations(
|
|
41
54
|
typeIndexUrl: string,
|
|
42
|
-
|
|
55
|
+
type: string | string[],
|
|
56
|
+
predicate: string,
|
|
43
57
|
fetch?: Fetch,
|
|
44
|
-
): Promise<
|
|
58
|
+
): Promise<string[]> {
|
|
45
59
|
const typeIndex = await fetchSolidDocument(typeIndexUrl, fetch);
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
const types = Array.isArray(type) ? type : [type];
|
|
61
|
+
|
|
62
|
+
return types.map(
|
|
63
|
+
type => typeIndex
|
|
64
|
+
.statements(undefined, 'rdf:type', 'solid:TypeRegistration')
|
|
65
|
+
.filter(statement => typeIndex.contains(statement.subject.value, 'solid:forClass', type))
|
|
66
|
+
.map(statement => typeIndex.statements(statement.subject.value, predicate))
|
|
67
|
+
.flat()
|
|
68
|
+
.map(statement => statement.object.value)
|
|
69
|
+
.filter(url => !!url),
|
|
70
|
+
).flat();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function createPublicTypeIndex(user: SolidUserProfile, fetch?: Fetch): Promise<string> {
|
|
74
|
+
return createTypeIndex(user, 'public', fetch);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function createPrivateTypeIndex(user: SolidUserProfile, fetch?: Fetch): Promise<string> {
|
|
78
|
+
return createTypeIndex(user, 'private', fetch);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function findContainerRegistrations(
|
|
82
|
+
typeIndexUrl: string,
|
|
83
|
+
type: string | string[],
|
|
84
|
+
fetch?: Fetch,
|
|
85
|
+
): Promise<string[]> {
|
|
86
|
+
return findRegistrations(typeIndexUrl, type, 'solid:instanceContainer', fetch);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function findInstanceRegistrations(
|
|
90
|
+
typeIndexUrl: string,
|
|
91
|
+
type: string | string[],
|
|
92
|
+
fetch?: Fetch,
|
|
93
|
+
): Promise<string[]> {
|
|
94
|
+
return findRegistrations(typeIndexUrl, type, 'solid:instance', fetch);
|
|
57
95
|
}
|