@noeldemartin/solid-utils 0.5.0 → 0.6.0-next.3e3ceb79b047f4ec87a416c2f920a13eda7a0df1

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.
Files changed (55) hide show
  1. package/dist/io-CMHtz5bu.js +401 -0
  2. package/dist/io-CMHtz5bu.js.map +1 -0
  3. package/dist/noeldemartin-solid-utils.d.ts +13 -65
  4. package/dist/noeldemartin-solid-utils.js +224 -0
  5. package/dist/noeldemartin-solid-utils.js.map +1 -0
  6. package/dist/testing.d.ts +45 -0
  7. package/dist/testing.js +157 -0
  8. package/dist/testing.js.map +1 -0
  9. package/package.json +61 -63
  10. package/src/errors/UnauthorizedError.ts +1 -3
  11. package/src/errors/UnsuccessfulNetworkRequestError.ts +2 -2
  12. package/src/helpers/auth.test.ts +221 -0
  13. package/src/helpers/auth.ts +28 -27
  14. package/src/helpers/identifiers.test.ts +76 -0
  15. package/src/helpers/identifiers.ts +14 -17
  16. package/src/helpers/index.ts +0 -1
  17. package/src/helpers/interop.ts +23 -16
  18. package/src/helpers/io.test.ts +228 -0
  19. package/src/helpers/io.ts +57 -77
  20. package/src/helpers/jsonld.ts +6 -6
  21. package/src/helpers/vocabs.ts +3 -6
  22. package/src/helpers/wac.test.ts +64 -0
  23. package/src/helpers/wac.ts +10 -6
  24. package/src/index.ts +3 -0
  25. package/src/models/SolidDocument.test.ts +77 -0
  26. package/src/models/SolidDocument.ts +14 -18
  27. package/src/models/SolidStore.ts +22 -12
  28. package/src/models/SolidThing.ts +5 -7
  29. package/src/models/index.ts +2 -0
  30. package/src/{helpers/testing.ts → testing/helpers.ts} +24 -27
  31. package/src/testing/hepers.test.ts +329 -0
  32. package/src/testing/index.ts +2 -2
  33. package/src/testing/vitest/index.ts +13 -0
  34. package/src/testing/vitest/matchers.ts +68 -0
  35. package/src/types/n3.d.ts +0 -2
  36. package/.github/workflows/ci.yml +0 -16
  37. package/.nvmrc +0 -1
  38. package/CHANGELOG.md +0 -70
  39. package/dist/noeldemartin-solid-utils.cjs.js +0 -2
  40. package/dist/noeldemartin-solid-utils.cjs.js.map +0 -1
  41. package/dist/noeldemartin-solid-utils.esm.js +0 -2
  42. package/dist/noeldemartin-solid-utils.esm.js.map +0 -1
  43. package/dist/noeldemartin-solid-utils.umd.js +0 -90
  44. package/dist/noeldemartin-solid-utils.umd.js.map +0 -1
  45. package/noeldemartin.config.js +0 -9
  46. package/src/main.ts +0 -5
  47. package/src/plugins/chai/assertions.ts +0 -40
  48. package/src/plugins/chai/index.ts +0 -5
  49. package/src/plugins/cypress/types.d.ts +0 -15
  50. package/src/plugins/index.ts +0 -2
  51. package/src/plugins/jest/index.ts +0 -5
  52. package/src/plugins/jest/matchers.ts +0 -65
  53. package/src/plugins/jest/types.d.ts +0 -14
  54. package/src/testing/ResponseStub.ts +0 -46
  55. package/src/testing/mocking.ts +0 -33
@@ -19,9 +19,7 @@ export default class UnauthorizedError extends JSError {
19
19
  }
20
20
 
21
21
  public get forbidden(): boolean | undefined {
22
- return typeof this.responseStatus !== 'undefined'
23
- ? this.responseStatus === 403
24
- : undefined;
22
+ return typeof this.responseStatus !== 'undefined' ? this.responseStatus === 403 : undefined;
25
23
  }
26
24
 
27
25
  }
@@ -1,7 +1,7 @@
1
1
  import { JSError } from '@noeldemartin/utils';
2
2
 
3
3
  function getErrorMessage(messageOrResponse: string | Response, response?: Response): string {
4
- response = response ?? messageOrResponse as Response;
4
+ response = response ?? (messageOrResponse as Response);
5
5
 
6
6
  return typeof messageOrResponse === 'string'
7
7
  ? `${messageOrResponse} (returned ${response.status} status code)`
@@ -17,7 +17,7 @@ export default class UnsuccessfulRequestError extends JSError {
17
17
  constructor(messageOrResponse: string | Response, response?: Response) {
18
18
  super(getErrorMessage(messageOrResponse, response));
19
19
 
20
- this.response = response ?? messageOrResponse as Response;
20
+ this.response = response ?? (messageOrResponse as Response);
21
21
  }
22
22
 
23
23
  }
@@ -0,0 +1,221 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { FakeResponse, FakeServer } from '@noeldemartin/testing';
4
+
5
+ import { MalformedSolidDocumentError } from '@noeldemartin/solid-utils/errors';
6
+
7
+ import { fetchLoginUserProfile } from './auth';
8
+
9
+ describe('Auth helpers', () => {
10
+
11
+ it('reads NSS profiles', async () => {
12
+ // Arrange
13
+ const server = new FakeServer();
14
+ const webId = 'https://alice.solidcommunity.net/profile/card#me';
15
+
16
+ server.respondOnce(
17
+ 'https://alice.solidcommunity.net/profile/card',
18
+ FakeResponse.success(
19
+ `
20
+ @prefix foaf: <http://xmlns.com/foaf/0.1/>.
21
+ @prefix solid: <http://www.w3.org/ns/solid/terms#>.
22
+ @prefix pim: <http://www.w3.org/ns/pim/space#>.
23
+ @prefix schema: <http://schema.org/>.
24
+
25
+ <>
26
+ a foaf:PersonalProfileDocument ;
27
+ foaf:maker <#me> ;
28
+ foaf:primaryTopic <#me> .
29
+
30
+ <#me>
31
+ a foaf:Person, schema:Person ;
32
+ foaf:name "Alice" ;
33
+ pim:preferencesFile </settings/prefs.ttl> ;
34
+ pim:storage </> ;
35
+ solid:oidcIssuer <https://solidcommunity.net> ;
36
+ solid:privateTypeIndex </settings/privateTypeIndex.ttl> ;
37
+ solid:publicTypeIndex </settings/publicTypeIndex.ttl> .
38
+ `,
39
+ { 'WAC-Allow': 'user="read control write"' },
40
+ ),
41
+ );
42
+
43
+ // Act
44
+ const profile = await fetchLoginUserProfile(webId, { fetch: server.fetch });
45
+
46
+ // Assert
47
+ expect(server.getRequests()).toHaveLength(1);
48
+
49
+ expect(profile).toEqual({
50
+ webId,
51
+ name: 'Alice',
52
+ cloaked: false,
53
+ oidcIssuerUrl: 'https://solidcommunity.net',
54
+ storageUrls: ['https://alice.solidcommunity.net/'],
55
+ privateTypeIndexUrl: 'https://alice.solidcommunity.net/settings/privateTypeIndex.ttl',
56
+ publicTypeIndexUrl: 'https://alice.solidcommunity.net/settings/publicTypeIndex.ttl',
57
+ writableProfileUrl: 'https://alice.solidcommunity.net/profile/card',
58
+ });
59
+ });
60
+
61
+ it('reads ESS profiles (public)', async () => {
62
+ // Arrange
63
+ const server = new FakeServer();
64
+ const webId = 'https://id.inrupt.com/alice';
65
+
66
+ // The first request returns a 303 to `${webId}?lookup`,
67
+ // but in order to simplify mocking requests we're assuming it doesn't.
68
+ server.respondOnce(
69
+ 'https://id.inrupt.com/alice',
70
+ FakeResponse.success(`
71
+ @prefix foaf: <http://xmlns.com/foaf/0.1/>.
72
+ @prefix solid: <http://www.w3.org/ns/solid/terms#>.
73
+ @prefix pim: <http://www.w3.org/ns/pim/space#>.
74
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
75
+
76
+ <${webId}>
77
+ a foaf:Agent ;
78
+ rdfs:seeAlso <https://storage.inrupt.com/storage-hash/extendedProfile> ;
79
+ pim:storage <https://storage.inrupt.com/storage-hash/> ;
80
+ solid:oidcIssuer <https://login.inrupt.com> ;
81
+ foaf:isPrimaryTopicOf <https://storage.inrupt.com/storage-hash/extendedProfile> .
82
+ `),
83
+ );
84
+ server.respondOnce(
85
+ 'https://storage.inrupt.com/storage-hash/extendedProfile',
86
+ new FakeResponse(undefined, undefined, 401),
87
+ );
88
+
89
+ // Act
90
+ const profile = await fetchLoginUserProfile(webId, { fetch: server.fetch });
91
+
92
+ // Assert
93
+ expect(server.getRequests()).toHaveLength(2);
94
+
95
+ expect(profile).toEqual({
96
+ webId,
97
+ cloaked: true,
98
+ oidcIssuerUrl: 'https://login.inrupt.com',
99
+ storageUrls: ['https://storage.inrupt.com/storage-hash/'],
100
+ writableProfileUrl: null,
101
+ });
102
+ });
103
+
104
+ it('reads ESS profiles (authenticated)', async () => {
105
+ // Arrange
106
+ const server = new FakeServer();
107
+ const webId = 'https://id.inrupt.com/alice';
108
+
109
+ // The first request returns a 303 to `${webId}?lookup`,
110
+ // but in order to simplify mocking requests we're assuming it doesn't.
111
+ server.respondOnce(
112
+ 'https://id.inrupt.com/alice',
113
+ FakeResponse.success(`
114
+ @prefix foaf: <http://xmlns.com/foaf/0.1/>.
115
+ @prefix solid: <http://www.w3.org/ns/solid/terms#>.
116
+ @prefix pim: <http://www.w3.org/ns/pim/space#>.
117
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
118
+
119
+ <${webId}>
120
+ a foaf:Agent ;
121
+ rdfs:seeAlso <https://storage.inrupt.com/storage-hash/extendedProfile> ;
122
+ pim:storage <https://storage.inrupt.com/storage-hash/> ;
123
+ solid:oidcIssuer <https://login.inrupt.com> ;
124
+ foaf:isPrimaryTopicOf <https://storage.inrupt.com/storage-hash/extendedProfile> .
125
+ `),
126
+ );
127
+ server.respondOnce(
128
+ 'https://storage.inrupt.com/storage-hash/extendedProfile',
129
+ FakeResponse.success(
130
+ `
131
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
132
+ @prefix schema: <http://schema.org/> .
133
+
134
+ <${webId}>
135
+ a foaf:Person, schema:Person ;
136
+ foaf:name "Alice" .
137
+
138
+ <https://storage.inrupt.com/storage-hash/extendedProfile>
139
+ a foaf:Document ;
140
+ foaf:maker <${webId}> ;
141
+ foaf:primaryTopic <${webId}> .
142
+ `,
143
+ { 'WAC-Allow': 'user="read control write"' },
144
+ ),
145
+ );
146
+
147
+ // Act
148
+ const profile = await fetchLoginUserProfile(webId, { fetch: server.fetch });
149
+
150
+ // Assert
151
+ expect(server.getRequests()).toHaveLength(2);
152
+
153
+ expect(profile).toEqual({
154
+ webId,
155
+ name: 'Alice',
156
+ cloaked: false,
157
+ oidcIssuerUrl: 'https://login.inrupt.com',
158
+ storageUrls: ['https://storage.inrupt.com/storage-hash/'],
159
+ writableProfileUrl: 'https://storage.inrupt.com/storage-hash/extendedProfile',
160
+ });
161
+ });
162
+
163
+ it('reads use.id profiles', async () => {
164
+ // Arrange
165
+ const server = new FakeServer();
166
+ const webId = 'https://use.id/alice';
167
+ const profileTurtle = `
168
+ @prefix foaf: <http://xmlns.com/foaf/0.1/>.
169
+ @prefix solid: <http://www.w3.org/ns/solid/terms#>.
170
+ @prefix pim: <http://www.w3.org/ns/pim/space#>.
171
+
172
+ <${webId}/profile>
173
+ a foaf:PersonalProfileDocument;
174
+ foaf:maker <${webId}>;
175
+ foaf:primaryTopic <${webId}>.
176
+
177
+ <${webId}>
178
+ solid:oidcIssuer <https://idp.use.id/>;
179
+ pim:storage <https://pods.use.id/storage-hash/>.
180
+ `;
181
+
182
+ // The first request returns a 303 to `${webId}/profile`,
183
+ // but in order to simplify mocking requests we're assuming it doesn't.
184
+ server.respondOnce('https://use.id/alice', FakeResponse.success(profileTurtle, { 'WAC-Allow': 'user="read"' }));
185
+ server.respondOnce(
186
+ 'https://use.id/alice/profile',
187
+ FakeResponse.success(profileTurtle, { 'WAC-Allow': 'user="read"' }),
188
+ );
189
+
190
+ // Act
191
+ const profile = await fetchLoginUserProfile(webId, { fetch: server.fetch });
192
+
193
+ // Assert
194
+ expect(server.getRequests()).toHaveLength(2);
195
+
196
+ expect(profile).toEqual({
197
+ webId,
198
+ cloaked: false,
199
+ oidcIssuerUrl: 'https://idp.use.id/',
200
+ storageUrls: ['https://pods.use.id/storage-hash/'],
201
+ writableProfileUrl: null,
202
+ });
203
+ });
204
+
205
+ it('throws errors reading required profiles', async () => {
206
+ // Arrange
207
+ const server = new FakeServer();
208
+ const webId = 'https://pod.example.com/profile/card#me';
209
+
210
+ server.respondOnce('https://pod.example.com/profile/card', FakeResponse.success('invalid turtle'));
211
+
212
+ // Act
213
+ const fetchProfile = fetchLoginUserProfile(webId, { fetch: server.fetch, required: true });
214
+
215
+ // Assert
216
+ await expect(fetchProfile).rejects.toBeInstanceOf(MalformedSolidDocumentError);
217
+
218
+ expect(server.getRequests()).toHaveLength(1);
219
+ });
220
+
221
+ });
@@ -19,7 +19,10 @@ export interface SolidUserProfile {
19
19
  privateTypeIndexUrl?: string;
20
20
  }
21
21
 
22
- async function fetchExtendedUserProfile(webIdDocument: SolidDocument, options?: FetchSolidDocumentOptions): Promise<{
22
+ async function fetchExtendedUserProfile(
23
+ webIdDocument: SolidDocument,
24
+ options?: FetchSolidDocumentOptions,
25
+ ): Promise<{
23
26
  store: SolidStore;
24
27
  cloaked: boolean;
25
28
  writableProfileUrl: string | null;
@@ -29,12 +32,12 @@ async function fetchExtendedUserProfile(webIdDocument: SolidDocument, options?:
29
32
  const addReferencedDocumentUrls = (document: SolidDocument) => {
30
33
  document
31
34
  .statements(undefined, 'foaf:isPrimaryTopicOf')
32
- .map(quad => quad.object.value)
33
- .forEach(profileDocumentUrl => documents[profileDocumentUrl] = documents[profileDocumentUrl] ?? null);
35
+ .map((quad) => quad.object.value)
36
+ .forEach((profileDocumentUrl) => (documents[profileDocumentUrl] = documents[profileDocumentUrl] ?? null));
34
37
  document
35
38
  .statements(undefined, 'foaf:primaryTopic')
36
- .map(quad => quad.subject.value)
37
- .forEach(profileDocumentUrl => documents[profileDocumentUrl] = documents[profileDocumentUrl] ?? null);
39
+ .map((quad) => quad.subject.value)
40
+ .forEach((profileDocumentUrl) => (documents[profileDocumentUrl] = documents[profileDocumentUrl] ?? null));
38
41
  };
39
42
  const loadProfileDocuments = async (): Promise<void> => {
40
43
  for (const [url, document] of Object.entries(documents)) {
@@ -43,12 +46,12 @@ async function fetchExtendedUserProfile(webIdDocument: SolidDocument, options?:
43
46
  }
44
47
 
45
48
  try {
46
- const document = await fetchSolidDocument(url, options);
49
+ const _document = await fetchSolidDocument(url, options);
47
50
 
48
- documents[url] = document;
49
- store.addQuads(document.getQuads());
51
+ documents[url] = _document;
52
+ store.addQuads(_document.getQuads());
50
53
 
51
- addReferencedDocumentUrls(document);
54
+ addReferencedDocumentUrls(_document);
52
55
  } catch (error) {
53
56
  if (error instanceof UnauthorizedError) {
54
57
  documents[url] = false;
@@ -65,18 +68,16 @@ async function fetchExtendedUserProfile(webIdDocument: SolidDocument, options?:
65
68
 
66
69
  do {
67
70
  await loadProfileDocuments();
68
- } while (Object.values(documents).some(document => document === null));
71
+ } while (Object.values(documents).some((document) => document === null));
69
72
 
70
73
  return {
71
74
  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,
75
+ cloaked: Object.values(documents).some((document) => document === false),
76
+ writableProfileUrl: webIdDocument.isUserWritable()
77
+ ? webIdDocument.url
78
+ : (Object.values(documents).find(
79
+ (document): document is SolidDocument => !!document && document.isUserWritable(),
80
+ )?.url ?? null),
80
81
  };
81
82
  }
82
83
 
@@ -97,7 +98,7 @@ async function fetchUserProfile(webId: string, options: FetchUserProfileOptions
97
98
  }
98
99
 
99
100
  const { store, writableProfileUrl, cloaked } = await fetchExtendedUserProfile(document, options);
100
- const storageUrls = store.statements(webId, 'pim:storage').map(storage => storage.object.value);
101
+ const storageUrls = store.statements(webId, 'pim:storage').map((storage) => storage.object.value);
101
102
  const publicTypeIndex = store.statement(webId, 'solid:publicTypeIndex');
102
103
  const privateTypeIndex = store.statement(webId, 'solid:privateTypeIndex');
103
104
 
@@ -118,7 +119,7 @@ async function fetchUserProfile(webId: string, options: FetchUserProfileOptions
118
119
  throw new Error(`Could not find any storage for ${webId}.`);
119
120
  }
120
121
 
121
- await options.onLoaded?.(new SolidStore(store.statements(webId)));
122
+ await options.onLoaded?.(store);
122
123
 
123
124
  return {
124
125
  webId,
@@ -126,9 +127,7 @@ async function fetchUserProfile(webId: string, options: FetchUserProfileOptions
126
127
  writableProfileUrl,
127
128
  storageUrls: arrayUnique(storageUrls) as [string, ...string[]],
128
129
  ...objectWithoutEmpty({
129
- name:
130
- store.statement(webId, 'vcard:fn')?.object.value ??
131
- store.statement(webId, 'foaf:name')?.object.value,
130
+ name: store.statement(webId, 'vcard:fn')?.object.value ?? store.statement(webId, 'foaf:name')?.object.value,
132
131
  avatarUrl:
133
132
  store.statement(webId, 'vcard:hasPhoto')?.object.value ??
134
133
  store.statement(webId, 'foaf:img')?.object.value,
@@ -156,9 +155,11 @@ export async function fetchLoginUserProfile(
156
155
  return fetchUserProfile(loginUrl, options);
157
156
  }
158
157
 
159
- const fetchProfile = silenced(url => fetchUserProfile(url, options));
158
+ const fetchProfile = silenced((url) => fetchUserProfile(url, options));
160
159
 
161
- return await fetchProfile(loginUrl)
162
- ?? await fetchProfile(loginUrl.replace(/\/$/, '').concat('/profile/card#me'))
163
- ?? await fetchProfile(urlRoot(loginUrl).concat('/profile/card#me'));
160
+ return (
161
+ (await fetchProfile(loginUrl)) ??
162
+ (await fetchProfile(loginUrl.replace(/\/$/, '').concat('/profile/card#me'))) ??
163
+ (await fetchProfile(urlRoot(loginUrl).concat('/profile/card#me')))
164
+ );
164
165
  }
@@ -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 '@/helpers';
3
+ import type { JsonLD, JsonLDResource } from '@noeldemartin/solid-utils/helpers';
4
4
 
5
5
  export interface SubjectParts {
6
6
  containerUrl?: string;
@@ -9,11 +9,9 @@ export interface SubjectParts {
9
9
  }
10
10
 
11
11
  function getContainerPath(parts: UrlParts): string | null {
12
- if (!parts.path || !parts.path.startsWith('/'))
13
- return null;
12
+ if (!parts.path || !parts.path.startsWith('/')) return null;
14
13
 
15
- if (parts.path.match(/^\/[^/]*$/))
16
- return '/';
14
+ if (parts.path.match(/^\/[^/]*$/)) return '/';
17
15
 
18
16
  return `/${arr(parts.path.split('/')).filter().slice(0, -1).join('/')}/`.replace('//', '/');
19
17
  }
@@ -27,30 +25,29 @@ function getContainerUrl(parts: UrlParts): string | null {
27
25
  }
28
26
 
29
27
  function __mintJsonLDIdentifiers(jsonld: JsonLD): void {
30
- if (!('@type' in jsonld) || '@value' in jsonld)
31
- return;
28
+ if (!('@type' in jsonld) || '@value' in jsonld) return;
32
29
 
33
30
  jsonld['@id'] = jsonld['@id'] ?? uuid();
34
31
 
35
32
  for (const propertyValue of Object.values(jsonld)) {
36
- if (isObject(propertyValue))
37
- __mintJsonLDIdentifiers(propertyValue);
33
+ if (isObject(propertyValue)) __mintJsonLDIdentifiers(propertyValue);
38
34
 
39
- if (isArray(propertyValue))
40
- propertyValue.forEach(value => isObject(value) && __mintJsonLDIdentifiers(value));
35
+ if (isArray(propertyValue)) propertyValue.forEach((value) => isObject(value) && __mintJsonLDIdentifiers(value));
41
36
  }
42
37
  }
43
38
 
44
39
  export function mintJsonLDIdentifiers(jsonld: JsonLD): JsonLDResource {
45
- return tap(objectDeepClone(jsonld) as JsonLDResource, clone => __mintJsonLDIdentifiers(clone));
40
+ return tap(objectDeepClone(jsonld) as JsonLDResource, (clone) => __mintJsonLDIdentifiers(clone));
46
41
  }
47
42
 
48
43
  export function parseResourceSubject(subject: string): SubjectParts {
49
44
  const parts = urlParse(subject);
50
45
 
51
- return !parts ? {} : objectWithoutEmpty({
52
- containerUrl: getContainerUrl(parts),
53
- documentName: parts.path ? parts.path.split('/').pop() : null,
54
- resourceHash: parts.fragment,
55
- });
46
+ return !parts
47
+ ? {}
48
+ : objectWithoutEmpty({
49
+ containerUrl: getContainerUrl(parts),
50
+ documentName: parts.path ? parts.path.split('/').pop() : null,
51
+ resourceHash: parts.fragment,
52
+ });
56
53
  }
@@ -3,6 +3,5 @@ export * from './identifiers';
3
3
  export * from './interop';
4
4
  export * from './io';
5
5
  export * from './jsonld';
6
- export * from './testing';
7
6
  export * from './vocabs';
8
7
  export * from './wac';
@@ -1,8 +1,13 @@
1
1
  import { uuid } from '@noeldemartin/utils';
2
2
 
3
- import { createSolidDocument, fetchSolidDocument, solidDocumentExists, updateSolidDocument } from '@/helpers/io';
4
- import type { Fetch } from '@/helpers/io';
5
- import type { SolidUserProfile } from '@/helpers/auth';
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 = type === 'public'
29
- ? '<> a <http://www.w3.org/ns/solid/terms#TypeIndex> .'
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.map(
64
- type => typeIndex
65
- .statements(undefined, 'rdf:type', 'solid:TypeRegistration')
66
- .filter(statement => typeIndex.contains(statement.subject.value, 'solid:forClass', type))
67
- .map(statement => typeIndex.statements(statement.subject.value, predicate))
68
- .flat()
69
- .map(statement => statement.object.value)
70
- .filter(url => !!url),
71
- ).flat();
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
  /**