@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.
Files changed (35) hide show
  1. package/.nvmrc +1 -0
  2. package/.semaphore/semaphore.yml +1 -1
  3. package/dist/noeldemartin-solid-utils.cjs.js +1 -1
  4. package/dist/noeldemartin-solid-utils.cjs.js.map +1 -1
  5. package/dist/noeldemartin-solid-utils.d.ts +94 -16
  6. package/dist/noeldemartin-solid-utils.esm.js +1 -1
  7. package/dist/noeldemartin-solid-utils.esm.js.map +1 -1
  8. package/dist/noeldemartin-solid-utils.umd.js +90 -0
  9. package/dist/noeldemartin-solid-utils.umd.js.map +1 -0
  10. package/noeldemartin.config.js +4 -2
  11. package/package.json +11 -11
  12. package/src/errors/MalformedSolidDocumentError.ts +2 -2
  13. package/src/errors/NetworkRequestError.ts +5 -4
  14. package/src/errors/NotFoundError.ts +2 -2
  15. package/src/errors/UnauthorizedError.ts +2 -2
  16. package/src/errors/UnsuccessfulNetworkRequestError.ts +23 -0
  17. package/src/errors/UnsupportedAuthorizationProtocolError.ts +16 -0
  18. package/src/errors/index.ts +2 -0
  19. package/src/helpers/auth.ts +91 -18
  20. package/src/helpers/identifiers.ts +56 -0
  21. package/src/helpers/index.ts +2 -0
  22. package/src/helpers/interop.ts +68 -30
  23. package/src/helpers/io.ts +147 -60
  24. package/src/helpers/jsonld.ts +25 -1
  25. package/src/helpers/testing.ts +103 -28
  26. package/src/helpers/vocabs.ts +4 -2
  27. package/src/helpers/wac.ts +55 -0
  28. package/src/models/SolidDocument.ts +41 -35
  29. package/src/models/SolidStore.ts +61 -0
  30. package/src/models/index.ts +2 -1
  31. package/src/plugins/chai/assertions.ts +11 -2
  32. package/src/plugins/cypress/types.d.ts +1 -0
  33. package/src/plugins/jest/matchers.ts +45 -32
  34. package/src/plugins/jest/types.d.ts +1 -0
  35. package/src/types/n3.d.ts +9 -0
@@ -1,30 +1,38 @@
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 SolidThing from './SolidThing';
6
+ import SolidStore from './SolidStore';
7
7
 
8
- export default class SolidDocument {
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;
27
+ public isACPResource(): boolean {
28
+ return !!this.headers.get('Link')
29
+ ?.match(/<http:\/\/www\.w3\.org\/ns\/solid\/acp#AccessControlResource>;[^,]+rel="type"/);
22
30
  }
23
31
 
24
32
  public isPersonalProfile(): boolean {
25
33
  return !!this.statement(
26
34
  this.url,
27
- expandIRI('rdfs:type'),
35
+ expandIRI('rdf:type'),
28
36
  expandIRI('foaf:PersonalProfileDocument'),
29
37
  );
30
38
  }
@@ -33,41 +41,27 @@ export default class SolidDocument {
33
41
  return !!this.headers.get('Link')?.match(/<http:\/\/www\.w3\.org\/ns\/pim\/space#Storage>;[^,]+rel="type"/);
34
42
  }
35
43
 
36
- public getLastModified(): Date | null {
37
- return parseDate(this.headers.get('last-modified'))
38
- ?? parseDate(this.statement(this.url, 'purl:modified')?.object.value)
39
- ?? this.getLatestDocumentDate()
40
- ?? null;
44
+ public isUserWritable(): boolean {
45
+ return this.getUserPermissions().includes(SolidDocumentPermission.Write);
41
46
  }
42
47
 
43
- public statements(subject?: string, predicate?: string, object?: string): Quad[] {
44
- return this.quads.filter(
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
- );
48
+ public getUserPermissions(): SolidDocumentPermission[] {
49
+ return this.getPermissionsFromWAC('user');
50
50
  }
51
51
 
52
- public statement(subject?: string, predicate?: string, object?: string): Quad | null {
53
- const statement = this.quads.find(
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;
52
+ public getPublicPermissions(): SolidDocumentPermission[] {
53
+ return this.getPermissionsFromWAC('public');
61
54
  }
62
55
 
63
- public contains(subject: string, predicate?: string, object?: string): boolean {
64
- return this.statement(subject, predicate, object) !== null;
56
+ public getLastModified(): Date | null {
57
+ return parseDate(this.headers.get('last-modified'))
58
+ ?? parseDate(this.statement(this.url, 'purl:modified')?.object.value)
59
+ ?? this.getLatestDocumentDate()
60
+ ?? null;
65
61
  }
66
62
 
67
- public getThing(subject: string): SolidThing {
68
- const statements = this.statements(subject);
69
-
70
- return new SolidThing(subject, statements);
63
+ protected expandIRI(iri: string): string {
64
+ return expandIRI(iri, { defaultPrefix: this.url });
71
65
  }
72
66
 
73
67
  private getLatestDocumentDate(): Date | null {
@@ -81,4 +75,16 @@ export default class SolidDocument {
81
75
  return dates.length > 0 ? dates.reduce((a, b) => a > b ? a : b) : null;
82
76
  }
83
77
 
78
+ private getPermissionsFromWAC(type: string): SolidDocumentPermission[] {
79
+ const wacAllow = this.headers.get('WAC-Allow') ?? '';
80
+ const publicModes = stringMatch<2>(wacAllow, new RegExp(`${type}="([^"]+)"`))?.[1] ?? '';
81
+
82
+ return arrayFilter([
83
+ publicModes.includes('read') && SolidDocumentPermission.Read,
84
+ publicModes.includes('write') && SolidDocumentPermission.Write,
85
+ publicModes.includes('append') && SolidDocumentPermission.Append,
86
+ publicModes.includes('control') && SolidDocumentPermission.Control,
87
+ ]);
88
+ }
89
+
84
90
  }
@@ -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
+ }
@@ -1,2 +1,3 @@
1
- export { default as SolidDocument } from './SolidDocument';
1
+ export { default as SolidDocument, SolidDocumentPermission } from './SolidDocument';
2
+ export { default as SolidStore } from './SolidStore';
2
3
  export { default as SolidThing } from './SolidThing';
@@ -1,4 +1,4 @@
1
- import { sparqlEquals } from '@/helpers/testing';
1
+ import { sparqlEquals, turtleEquals } from '@/helpers/testing';
2
2
 
3
3
  type CustomAssertions = {
4
4
  [assertion in keyof typeof assertions]: typeof assertions[assertion];
@@ -17,6 +17,15 @@ declare global {
17
17
  }
18
18
 
19
19
  const assertions: Record<string, (this: Chai.AssertionStatic, ...args: any[]) => void> = {
20
+ turtle(graph: string): void {
21
+ const self = this as unknown as Chai.AssertionStatic;
22
+ const actual = self._obj;
23
+ const assert = self.assert.bind(this);
24
+ const expected = graph;
25
+ const result = turtleEquals(expected, actual);
26
+
27
+ assert(result.success, result.message, '', result.expected, result.actual);
28
+ },
20
29
  sparql(query: string): void {
21
30
  const self = this as unknown as Chai.AssertionStatic;
22
31
  const actual = self._obj;
@@ -24,7 +33,7 @@ const assertions: Record<string, (this: Chai.AssertionStatic, ...args: any[]) =>
24
33
  const expected = query;
25
34
  const result = sparqlEquals(expected, actual);
26
35
 
27
- assert(result.success, result.message, expected, actual);
36
+ assert(result.success, result.message, '', result.expected, result.actual);
28
37
  },
29
38
  };
30
39
 
@@ -7,6 +7,7 @@ declare global {
7
7
  // TODO generate automatically
8
8
  interface Chainer<Subject> {
9
9
  (chainer: 'be.sparql', update: string): Cypress.Chainable<Subject>;
10
+ (chainer: 'be.turtle', graph: string): Cypress.Chainable<Subject>;
10
11
  }
11
12
 
12
13
  }
@@ -1,41 +1,54 @@
1
- import diff from 'jest-diff';
2
-
3
1
  import { normalizeSparql } from '@/helpers/io';
4
- import { sparqlEquals } from '@/helpers/testing';
2
+ import { jsonldEquals, sparqlEquals } from '@/helpers/testing';
3
+ import type { EqualityResult } from '@/helpers/testing';
4
+
5
+ interface FormatResultOptions {
6
+ context: jest.MatcherContext;
7
+ hint: string;
8
+ expected: unknown;
9
+ received: unknown;
10
+ }
11
+
12
+ function formatResult(result: EqualityResult, options: FormatResultOptions) {
13
+ const pass = result.success;
14
+ const utils = options.context.utils;
15
+ const message = pass
16
+ ? () => [
17
+ result.message,
18
+ utils.matcherHint(options.hint),
19
+ ].join('\n\n')
20
+ : () => [
21
+ result.message,
22
+ utils.matcherHint(options.hint),
23
+ [
24
+ `Expected: not ${utils.printExpected(options.expected)}`,
25
+ `Received: ${utils.printReceived(options.received)}`,
26
+ ].join('\n'),
27
+ ].join('\n\n');
28
+
29
+ return { pass, message };
30
+ }
5
31
 
6
32
  const matchers: jest.ExpectExtendMap = {
33
+ async toEqualJsonLD(received, expected) {
34
+ const result = await jsonldEquals(expected, received);
35
+
36
+ return formatResult(result, {
37
+ context: this,
38
+ hint: 'toEqualJsonLD',
39
+ expected,
40
+ received,
41
+ });
42
+ },
7
43
  toEqualSparql(received, expected) {
8
44
  const result = sparqlEquals(expected, received);
9
- const pass = result.success;
10
- const normalizedReceived = normalizeSparql(received);
11
- const normalizedExpected = normalizeSparql(expected);
12
- const message = pass
13
- ? () => [
14
- result.message,
15
- this.utils.matcherHint('toEqualSparql'),
16
- [
17
- `Expected: not ${this.utils.printExpected(normalizedExpected)}`,
18
- `Received: ${this.utils.printReceived(normalizedReceived)}`,
19
- ].join('\n'),
20
- ].join('\n\n')
21
- : () => {
22
- const diffString = diff(normalizedExpected, normalizedReceived, {
23
- expand: this.expand,
24
- });
25
-
26
- return [
27
- result.message,
28
- this.utils.matcherHint('toEqualJsonLD'),
29
- diffString && diffString.includes('- Expect')
30
- ? `Difference:\n\n${diffString}`
31
- : [
32
- `Expected: ${this.utils.printExpected(normalizedExpected)}`,
33
- `Received: ${this.utils.printReceived(normalizedReceived)}`,
34
- ].join('\n'),
35
- ].join('\n\n');
36
- };
37
45
 
38
- return { pass, message };
46
+ return formatResult(result, {
47
+ context: this,
48
+ hint: 'toEqualSparql',
49
+ expected: normalizeSparql(expected),
50
+ received: normalizeSparql(received),
51
+ });
39
52
  },
40
53
  };
41
54
 
@@ -5,6 +5,7 @@ declare global {
5
5
  // TODO generate automatically
6
6
  interface Matchers<R> {
7
7
  toEqualSparql(sparql: string): R;
8
+ toEqualJsonLD(jsonld: JsonLD): Promise<R>;
8
9
  }
9
10
 
10
11
  }
@@ -0,0 +1,9 @@
1
+ import '@types/n3';
2
+
3
+ declare module 'n3' {
4
+
5
+ interface Parser {
6
+ _resolveRelativeIRI(iri: string): string;
7
+ }
8
+
9
+ }