@ds-labs/prose-angular 0.3.1 → 0.4.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/README.md +60 -0
- package/fesm2022/ds-labs-prose-angular.mjs +284 -9
- package/fesm2022/ds-labs-prose-angular.mjs.map +1 -1
- package/index.d.ts +5 -0
- package/odrl-prose.component.d.ts +49 -8
- package/package.json +2 -2
- package/profile-sync.d.ts +23 -0
- package/validation-sync.d.ts +65 -0
- package/validator.service.d.ts +21 -0
- package/wasm-runtime.d.ts +8 -0
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@ edit mode.
|
|
|
11
11
|
<ds-odrl-prose [json]> -> ProseHandle (@ds-labs/prose-wasm) -> <OdrlProse> (prose-yew)
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
## Documentation and demo
|
|
15
|
+
|
|
16
|
+
The [Angular docs](https://ds-labs-org.github.io/ds-prose-rs/angular/) cover the interface, styling, validation and the CSS hooks. The [Angular demo](https://ds-labs-org.github.io/ds-prose-rs/demo-angular/) runs the component with `strict`, `profile` and `validationChange`.
|
|
17
|
+
|
|
14
18
|
## Install
|
|
15
19
|
|
|
16
20
|
```bash
|
|
@@ -51,7 +55,17 @@ export class PolicyComponent {
|
|
|
51
55
|
|---|---|---|
|
|
52
56
|
| `json` | input, required | the ODRL policy as JSON-LD text |
|
|
53
57
|
| `articleClass` | input | extra class(es) for the rendered `<article class="ds-prose">` |
|
|
58
|
+
| `profile` | input | the ODRL profile the prose is worded with, as JSON-LD or Turtle text (`null` or blank: none) |
|
|
59
|
+
| `profiles` | input | several profiles, as text each; a list wins over `profile` (an empty list clears them) |
|
|
60
|
+
| `strict` | input | validate with a closed vocabulary: ODRL plus the loaded profiles (default `false`) |
|
|
61
|
+
| `validation` | input | the full `ValidateOptions`; takes precedence over `strict` |
|
|
62
|
+
| `showViolations` | input | also show the findings and the verdict under the prose (needs `strict` or `validation`) |
|
|
63
|
+
| `validationChange` | output | emits the `ValidationReport` each time its verdict or findings change |
|
|
64
|
+
| `validationError` | output | emits the error when `validation` is not readable options; the previous options stay |
|
|
65
|
+
| `report` | signal | the latest `ValidationReport`, or `null` while validation is off |
|
|
54
66
|
| `loadFailed` | output | emits the error when the wasm cannot be loaded |
|
|
67
|
+
| `profileLoaded` | output | emits a `ProfileSummary` (`iri`, `title`, `version`, `terms`, `counts`, `warnings`) for each profile loaded |
|
|
68
|
+
| `profileError` | output | emits the error when `profile` is not a readable profile; the previous profile stays |
|
|
55
69
|
| `PROSE_WASM_URL` | injection token | where the browser fetches the wasm from, relative to the page's base href (default `prose_wasm_bg.wasm`) |
|
|
56
70
|
|
|
57
71
|
Invalid JSON shows an error message in place of the prose; it recovers on the
|
|
@@ -60,6 +74,52 @@ next valid input. Style the result through the `prose-*` classes
|
|
|
60
74
|
`.prose-prohibition`, `.prose-obligation`, `.prose-error`, ...) or the
|
|
61
75
|
`articleClass` input.
|
|
62
76
|
|
|
77
|
+
With a profile, the sentences use its labels ("may fine-tune" rather than
|
|
78
|
+
"may fine tune") and a glossary of its terms follows each policy. The
|
|
79
|
+
component does no network access: fetch the profile yourself and pass its
|
|
80
|
+
text. It is read again only when the text changes. ODRL's own terms keep
|
|
81
|
+
their built-in wording whatever a profile calls them, and a profile over
|
|
82
|
+
4 MiB, or past the reader's other limits, is refused (`profileError`). See
|
|
83
|
+
the [ds-prose-rs README](https://github.com/ds-labs-org/ds-prose-rs#profiles)
|
|
84
|
+
for what a profile changes and which formats are read.
|
|
85
|
+
|
|
86
|
+
## Validation
|
|
87
|
+
|
|
88
|
+
Without `strict` or `validation` nothing is validated and the component
|
|
89
|
+
renders exactly as before. With `[strict]="true"` the policy is checked
|
|
90
|
+
against the loaded profiles with the vocabulary read as closed: ODRL plus
|
|
91
|
+
those profiles is every term there is, so a term that resolves nowhere is an
|
|
92
|
+
error. The report is `ds-prose/validation-report@1`, the same JSON the Rust
|
|
93
|
+
crates and the `ds-prose-validate` command give.
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<ds-odrl-prose [json]="policy" [profiles]="[researchProfile, otherProfile]"
|
|
97
|
+
[strict]="true" [showViolations]="true"
|
|
98
|
+
(validationChange)="onReport($event)" />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
onReport(r: ValidationReport) {
|
|
103
|
+
// r.verdict is 'conforms', 'violations' or 'incomplete'.
|
|
104
|
+
// 'incomplete' is never a pass: something could not be checked, for
|
|
105
|
+
// example a profile the policy declares that is not loaded.
|
|
106
|
+
this.canPublish = r.verdict === 'conforms';
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`validationChange` is deduplicated: it fires when the verdict or the set of
|
|
111
|
+
findings changes, not on every edit that leaves them as they were.
|
|
112
|
+
`[validation]` takes the other options (`structural`, `failClosed`,
|
|
113
|
+
`denyDeprecated`, `odrl`, `ruleset`, per-rule levels); see `ValidateOptions`
|
|
114
|
+
in the types. Which rules exist, and what each rests on (the standard, or a
|
|
115
|
+
ds-prose convention), is in the
|
|
116
|
+
[ds-prose-rs README](https://github.com/ds-labs-org/ds-prose-rs#strict-validation).
|
|
117
|
+
|
|
118
|
+
To validate without rendering, inject `ProseValidatorService`:
|
|
119
|
+
`await service.validate(policyJson, [profileText])` returns the report, and
|
|
120
|
+
rejects only when the wasm cannot load or a profile or the options cannot be
|
|
121
|
+
read.
|
|
122
|
+
|
|
63
123
|
If the wasm cannot be loaded (a wrong `PROSE_WASM_URL`, a 404, a network
|
|
64
124
|
failure) the element shows an error (`<p class="prose-error" role="alert">`),
|
|
65
125
|
logs it with `console.error`, and emits it from `loadFailed`. A failed load is
|
|
@@ -1,6 +1,140 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, input, output, inject, ElementRef, NgZone, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
-
import init, { ProseHandle } from '@ds-labs/prose-wasm';
|
|
2
|
+
import { InjectionToken, input, output, signal, inject, ElementRef, NgZone, ChangeDetectionStrategy, Component, Injectable } from '@angular/core';
|
|
3
|
+
import init, { ProseValidator, ProseHandle, validate } from '@ds-labs/prose-wasm';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A tracker of the profile text last handed to the wasm, one per component.
|
|
7
|
+
* It returns true when `text` must be handed over now: only when it changed,
|
|
8
|
+
* so a policy change alone does not re-read a large profile, and a profile
|
|
9
|
+
* that failed is not reported again until it changes. `null`, `undefined`
|
|
10
|
+
* and blank text all mean no profile, which is what a fresh mount has.
|
|
11
|
+
*/
|
|
12
|
+
function createProfileTracker() {
|
|
13
|
+
let applied = null;
|
|
14
|
+
return (text) => {
|
|
15
|
+
const next = text == null || text.trim() === '' ? null : text;
|
|
16
|
+
if (next === applied)
|
|
17
|
+
return false;
|
|
18
|
+
applied = next;
|
|
19
|
+
return true;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** The summary `setProfile` returns, or null for `"null"` (no profile). */
|
|
23
|
+
function parseSummary(json) {
|
|
24
|
+
return JSON.parse(json);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether `next` is a report the host has not seen: the verdict, or the set
|
|
29
|
+
* of findings (by fingerprint, with their severity), differs. Wording, order
|
|
30
|
+
* and the rest of the report do not count, so a policy edit that changes
|
|
31
|
+
* nothing the validator found is not an event. No report to no report is not
|
|
32
|
+
* a change; a first report, or one that goes away, is.
|
|
33
|
+
*/
|
|
34
|
+
function reportChanged(prev, next) {
|
|
35
|
+
if (!prev || !next)
|
|
36
|
+
return !!prev !== !!next;
|
|
37
|
+
if (prev.verdict !== next.verdict)
|
|
38
|
+
return true;
|
|
39
|
+
const a = findingKeys(prev);
|
|
40
|
+
const b = findingKeys(next);
|
|
41
|
+
return a.length !== b.length || a.some((k, i) => k !== b[i]);
|
|
42
|
+
}
|
|
43
|
+
function findingKeys(r) {
|
|
44
|
+
return (r.findings ?? []).map((f) => `${f.fingerprint}\u0000${f.severity ?? ''}`).sort();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The options the wasm validator is given for the `strict` and `validation`
|
|
48
|
+
* inputs: `validation` as given (copied) when set, since it takes precedence;
|
|
49
|
+
* else strict mode when `strict` is on; else `null`, no validation.
|
|
50
|
+
*/
|
|
51
|
+
function validationOptions(strict, validation) {
|
|
52
|
+
if (validation != null)
|
|
53
|
+
return JSON.parse(JSON.stringify(validation));
|
|
54
|
+
return strict ? { strict: true } : null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The profile texts for the `profile` and `profiles` inputs. A list wins over
|
|
58
|
+
* the single profile (an empty one clears it); blank texts are dropped; `null`
|
|
59
|
+
* means no profile input at all.
|
|
60
|
+
*/
|
|
61
|
+
function profileTexts(profile, profiles) {
|
|
62
|
+
if (profiles != null)
|
|
63
|
+
return profiles.filter((t) => t.trim() !== '');
|
|
64
|
+
return profile == null || profile.trim() === '' ? null : [profile];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A tracker of the value last handed to the wasm, one per component. It
|
|
68
|
+
* returns true when `value` differs from the last one (compared as JSON), so
|
|
69
|
+
* an unchanged input is not handed over again. `null` and `undefined` are
|
|
70
|
+
* "none", which is what a fresh mount has.
|
|
71
|
+
*/
|
|
72
|
+
function createChangeTracker() {
|
|
73
|
+
let applied = 'null';
|
|
74
|
+
return (value) => {
|
|
75
|
+
const next = value == null ? 'null' : JSON.stringify(value);
|
|
76
|
+
if (next === applied)
|
|
77
|
+
return false;
|
|
78
|
+
applied = next;
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** The report text `ProseHandle.report()` returns, parsed; `null` for none. */
|
|
83
|
+
function parseReport(text) {
|
|
84
|
+
return text == null ? null : JSON.parse(text);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The report for a policy when the handle is not validating: a validator
|
|
88
|
+
* rebuilt when the profiles or the options change. When the options cannot be
|
|
89
|
+
* read, the previous options stay, as the `validationError` output promises:
|
|
90
|
+
* the report is computed with the last options that worked (or is `null`
|
|
91
|
+
* when none did), so it follows the policy and is never a stale one.
|
|
92
|
+
*/
|
|
93
|
+
class StandaloneReporter {
|
|
94
|
+
cached;
|
|
95
|
+
good = null;
|
|
96
|
+
build;
|
|
97
|
+
// No parameter property: the tests run this file with types stripped only.
|
|
98
|
+
constructor(build) {
|
|
99
|
+
this.build = build;
|
|
100
|
+
}
|
|
101
|
+
/** The report text for `json`, and the error when `options` were unreadable. */
|
|
102
|
+
report(profiles, options, json) {
|
|
103
|
+
try {
|
|
104
|
+
const report = this.validator(profiles, options).validate(json);
|
|
105
|
+
this.good = options;
|
|
106
|
+
return { report };
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (this.good) {
|
|
110
|
+
try {
|
|
111
|
+
return { report: this.validator(profiles, this.good).validate(json), error };
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Not even the previous options work (the profiles changed under them).
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { report: null, error };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** Validation is off: no options are "previous" any more. */
|
|
121
|
+
clear() {
|
|
122
|
+
this.good = null;
|
|
123
|
+
}
|
|
124
|
+
free() {
|
|
125
|
+
this.cached?.value.free();
|
|
126
|
+
this.cached = undefined;
|
|
127
|
+
}
|
|
128
|
+
validator(profiles, options) {
|
|
129
|
+
const key = JSON.stringify([profiles, options]);
|
|
130
|
+
if (this.cached?.key !== key) {
|
|
131
|
+
const value = this.build(profiles, options);
|
|
132
|
+
this.cached?.value.free();
|
|
133
|
+
this.cached = { key, value };
|
|
134
|
+
}
|
|
135
|
+
return this.cached.value;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
4
138
|
|
|
5
139
|
/**
|
|
6
140
|
* A loader that runs `init` at most once at a time and shares the result, so
|
|
@@ -35,6 +169,7 @@ const PROSE_WASM_URL = new InjectionToken('PROSE_WASM_URL', {
|
|
|
35
169
|
// The wasm module is fetched and instantiated once per page. A failed load is
|
|
36
170
|
// not remembered, so the next input change retries it.
|
|
37
171
|
const loadWasm = createWasmLoader((url) => init({ module_or_path: new URL(url, document.baseURI) }));
|
|
172
|
+
|
|
38
173
|
/**
|
|
39
174
|
* `<ds-odrl-prose [json]="policy" />` renders ODRL JSON-LD as plain-language
|
|
40
175
|
* prose using the Yew component from `prose-yew`.
|
|
@@ -42,6 +177,23 @@ const loadWasm = createWasmLoader((url) => init({ module_or_path: new URL(url, d
|
|
|
42
177
|
* The component owns the host element's children (Yew keeps its own virtual
|
|
43
178
|
* DOM there); style it through the `prose-*` classes or the `class` input.
|
|
44
179
|
*
|
|
180
|
+
* `[profile]` takes the text of an ODRL profile (JSON-LD or Turtle, as the
|
|
181
|
+
* host fetched it; the component does no network access). The prose then uses
|
|
182
|
+
* the profile's labels, and a glossary of its terms follows each policy.
|
|
183
|
+
* `profileLoaded` emits the profile's summary; an unreadable profile emits
|
|
184
|
+
* `profileError` and the previous profile stays.
|
|
185
|
+
*
|
|
186
|
+
* `[profiles]` takes several profiles at once (a list wins over `profile`).
|
|
187
|
+
*
|
|
188
|
+
* `[strict]="true"` validates the policy against the loaded profiles with a
|
|
189
|
+
* closed vocabulary: ODRL plus those profiles. `[validation]` takes the full
|
|
190
|
+
* options (it wins over `strict`). `validationChange` emits the report
|
|
191
|
+
* (schema `ds-prose/validation-report@1`) each time the verdict or the
|
|
192
|
+
* findings change, and the `report` signal always holds the latest. A
|
|
193
|
+
* verdict of `incomplete` is never a pass: something could not be checked,
|
|
194
|
+
* such as a declared profile that is not loaded. `[showViolations]` also
|
|
195
|
+
* shows the findings under the prose.
|
|
196
|
+
*
|
|
45
197
|
* If the wasm cannot be loaded, the host shows an error (`role="alert"`,
|
|
46
198
|
* class `prose-error`), `loadFailed` emits the error, and the next change of
|
|
47
199
|
* an input tries again.
|
|
@@ -51,12 +203,40 @@ class OdrlProseComponent {
|
|
|
51
203
|
json = input.required();
|
|
52
204
|
/** Extra class(es) for the rendered `<article class="ds-prose">`. */
|
|
53
205
|
articleClass = input();
|
|
206
|
+
/** The ODRL profile the prose is worded with, as JSON-LD or Turtle text. */
|
|
207
|
+
profile = input(null);
|
|
208
|
+
/** Several ODRL profiles, as JSON-LD or Turtle text each; wins over `profile`. */
|
|
209
|
+
profiles = input(null);
|
|
210
|
+
/** Validate with a closed vocabulary: ODRL plus the loaded profiles. */
|
|
211
|
+
strict = input(false);
|
|
212
|
+
/** The full validation options; they take precedence over `strict`. */
|
|
213
|
+
validation = input(null);
|
|
214
|
+
/** Show the findings and the verdict under the prose. Needs `strict` or `validation`. */
|
|
215
|
+
showViolations = input(false);
|
|
54
216
|
/** Emits when the wasm module could not be loaded. */
|
|
55
217
|
loadFailed = output();
|
|
218
|
+
/** Emits the summary of each profile loaded from the `profile` input. */
|
|
219
|
+
profileLoaded = output();
|
|
220
|
+
/** Emits when the `profile` input is not a readable profile; the previous one stays. */
|
|
221
|
+
profileError = output();
|
|
222
|
+
/** Emits the report when the verdict or the findings change. */
|
|
223
|
+
validationChange = output();
|
|
224
|
+
/** Emits when `validation` is not readable options; the previous options stay. */
|
|
225
|
+
validationError = output();
|
|
226
|
+
/** The latest report, or `null` while validation is off. */
|
|
227
|
+
report = signal(null);
|
|
56
228
|
host = inject(ElementRef).nativeElement;
|
|
57
229
|
zone = inject(NgZone);
|
|
58
230
|
wasmUrl = inject(PROSE_WASM_URL);
|
|
59
231
|
handle;
|
|
232
|
+
profilesChanged = createChangeTracker();
|
|
233
|
+
handleOptionsChanged = createChangeTracker();
|
|
234
|
+
// The profiles the handle holds now, and the validator built from them for
|
|
235
|
+
// a report the handle is not asked for.
|
|
236
|
+
appliedProfiles = [];
|
|
237
|
+
standalone = new StandaloneReporter((profiles, options) => new ProseValidator(profiles, options));
|
|
238
|
+
// Whether the handle validates (it was given options, with the findings shown).
|
|
239
|
+
handleValidates = false;
|
|
60
240
|
destroyed = false;
|
|
61
241
|
ngOnChanges() {
|
|
62
242
|
void this.sync();
|
|
@@ -66,6 +246,7 @@ class OdrlProseComponent {
|
|
|
66
246
|
this.handle?.destroy();
|
|
67
247
|
this.handle?.free();
|
|
68
248
|
this.handle = undefined;
|
|
249
|
+
this.standalone.free();
|
|
69
250
|
}
|
|
70
251
|
async sync() {
|
|
71
252
|
try {
|
|
@@ -83,12 +264,78 @@ class OdrlProseComponent {
|
|
|
83
264
|
this.host.replaceChildren();
|
|
84
265
|
const json = this.json();
|
|
85
266
|
const cls = this.articleClass() ?? null;
|
|
267
|
+
const single = this.profiles() == null;
|
|
268
|
+
const texts = profileTexts(this.profile(), this.profiles());
|
|
269
|
+
const options = validationOptions(this.strict(), this.validation());
|
|
270
|
+
const show = this.showViolations() && options != null;
|
|
86
271
|
// Yew schedules its own work; keep it out of Angular's change detection.
|
|
87
|
-
this.zone.runOutsideAngular(() => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
272
|
+
const outcome = this.zone.runOutsideAngular(() => {
|
|
273
|
+
const mounted = this.handle;
|
|
274
|
+
const handle = mounted ?? new ProseHandle(this.host, json, cls);
|
|
275
|
+
this.handle = handle;
|
|
276
|
+
const out = {};
|
|
277
|
+
// The profiles go first, so the validation and the update below use them.
|
|
278
|
+
if (this.profilesChanged(texts)) {
|
|
279
|
+
try {
|
|
280
|
+
out.summaries = single
|
|
281
|
+
? [parseSummary(handle.setProfile(texts?.[0] ?? null))].filter((x) => x != null)
|
|
282
|
+
: JSON.parse(handle.setProfiles(texts ?? []));
|
|
283
|
+
this.appliedProfiles = texts ?? [];
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
out.profileError = error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (this.handleOptionsChanged(show ? options : null)) {
|
|
290
|
+
try {
|
|
291
|
+
handle.setValidation(show ? options : null);
|
|
292
|
+
this.handleValidates = show;
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
out.validationError = error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (mounted)
|
|
299
|
+
handle.update(json, cls);
|
|
300
|
+
if (options) {
|
|
301
|
+
if (this.handleValidates) {
|
|
302
|
+
try {
|
|
303
|
+
out.report = handle.report();
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
out.validationError ??= error;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
// Unreadable options keep the previous ones; see StandaloneReporter.
|
|
311
|
+
const standalone = this.standalone.report(this.appliedProfiles, options, json);
|
|
312
|
+
out.report = standalone.report;
|
|
313
|
+
if (standalone.error !== undefined)
|
|
314
|
+
out.validationError ??= standalone.error;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
this.standalone.clear();
|
|
319
|
+
out.report = null;
|
|
320
|
+
}
|
|
321
|
+
return out;
|
|
322
|
+
});
|
|
323
|
+
if (outcome.profileError !== undefined)
|
|
324
|
+
this.profileError.emit(outcome.profileError);
|
|
325
|
+
for (const summary of outcome.summaries ?? [])
|
|
326
|
+
this.profileLoaded.emit(summary);
|
|
327
|
+
if (outcome.validationError !== undefined)
|
|
328
|
+
this.validationError.emit(outcome.validationError);
|
|
329
|
+
if (outcome.report !== undefined)
|
|
330
|
+
this.publish(parseReport(outcome.report));
|
|
331
|
+
}
|
|
332
|
+
publish(next) {
|
|
333
|
+
// Signals and outputs are Angular's: back inside the zone.
|
|
334
|
+
this.zone.run(() => {
|
|
335
|
+
const prev = this.report();
|
|
336
|
+
this.report.set(next);
|
|
337
|
+
if (next && reportChanged(prev, next))
|
|
338
|
+
this.validationChange.emit(next);
|
|
92
339
|
});
|
|
93
340
|
}
|
|
94
341
|
showLoadError(error) {
|
|
@@ -105,7 +352,7 @@ class OdrlProseComponent {
|
|
|
105
352
|
this.loadFailed.emit(error);
|
|
106
353
|
}
|
|
107
354
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: OdrlProseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
108
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.25", type: OdrlProseComponent, isStandalone: true, selector: "ds-odrl-prose", inputs: { json: { classPropertyName: "json", publicName: "json", isSignal: true, isRequired: true, transformFunction: null }, articleClass: { classPropertyName: "articleClass", publicName: "articleClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadFailed: "loadFailed" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
355
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.25", type: OdrlProseComponent, isStandalone: true, selector: "ds-odrl-prose", inputs: { json: { classPropertyName: "json", publicName: "json", isSignal: true, isRequired: true, transformFunction: null }, articleClass: { classPropertyName: "articleClass", publicName: "articleClass", isSignal: true, isRequired: false, transformFunction: null }, profile: { classPropertyName: "profile", publicName: "profile", isSignal: true, isRequired: false, transformFunction: null }, profiles: { classPropertyName: "profiles", publicName: "profiles", isSignal: true, isRequired: false, transformFunction: null }, strict: { classPropertyName: "strict", publicName: "strict", isSignal: true, isRequired: false, transformFunction: null }, validation: { classPropertyName: "validation", publicName: "validation", isSignal: true, isRequired: false, transformFunction: null }, showViolations: { classPropertyName: "showViolations", publicName: "showViolations", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadFailed: "loadFailed", profileLoaded: "profileLoaded", profileError: "profileError", validationChange: "validationChange", validationError: "validationError" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
109
356
|
}
|
|
110
357
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: OdrlProseComponent, decorators: [{
|
|
111
358
|
type: Component,
|
|
@@ -117,9 +364,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
|
|
|
117
364
|
}]
|
|
118
365
|
}] });
|
|
119
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Validates ODRL policies against ODRL profiles without rendering anything
|
|
369
|
+
* (a form's submit guard, a list of policies). It runs the same engine as
|
|
370
|
+
* `<ds-odrl-prose [strict]>` and the `ds-prose-validate` command, so the
|
|
371
|
+
* report is the same.
|
|
372
|
+
*/
|
|
373
|
+
class ProseValidatorService {
|
|
374
|
+
wasmUrl = inject(PROSE_WASM_URL);
|
|
375
|
+
/**
|
|
376
|
+
* The report for the policy `json` against `profiles` (the text of each
|
|
377
|
+
* profile, JSON-LD or Turtle; the service does no network access). Strict
|
|
378
|
+
* unless `options.strict` is `false`. Waits for the wasm module to load;
|
|
379
|
+
* rejects when it cannot, or when a profile or the options cannot be read.
|
|
380
|
+
* A problem in the policy is never a rejection: it is a finding.
|
|
381
|
+
*/
|
|
382
|
+
async validate(json, profiles = null, options = null) {
|
|
383
|
+
await loadWasm(this.wasmUrl);
|
|
384
|
+
const list = typeof profiles === 'string' || profiles == null ? profiles : [...profiles];
|
|
385
|
+
return JSON.parse(validate(json, list, options));
|
|
386
|
+
}
|
|
387
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: ProseValidatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
388
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: ProseValidatorService, providedIn: 'root' });
|
|
389
|
+
}
|
|
390
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: ProseValidatorService, decorators: [{
|
|
391
|
+
type: Injectable,
|
|
392
|
+
args: [{ providedIn: 'root' }]
|
|
393
|
+
}] });
|
|
394
|
+
|
|
120
395
|
/**
|
|
121
396
|
* Generated bundle index. Do not edit.
|
|
122
397
|
*/
|
|
123
398
|
|
|
124
|
-
export { OdrlProseComponent, PROSE_WASM_URL };
|
|
399
|
+
export { OdrlProseComponent, PROSE_WASM_URL, ProseValidatorService, reportChanged };
|
|
125
400
|
//# sourceMappingURL=ds-labs-prose-angular.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ds-labs-prose-angular.mjs","sources":["../../../prose-angular/src/wasm-loader.ts","../../../prose-angular/src/odrl-prose.component.ts","../../../prose-angular/src/ds-labs-prose-angular.ts"],"sourcesContent":["/** Loads and instantiates the wasm module. Injected so it can be tested. */\nexport type WasmInit = (url: string) => Promise<unknown>;\n\n/**\n * A loader that runs `init` at most once at a time and shares the result, so\n * every component on a page waits on one fetch. A failed load is forgotten:\n * the callers waiting on it all see the failure, and the next call tries again\n * (the file may have been missing only for a moment).\n */\nexport function createWasmLoader(init: WasmInit): (url: string) => Promise<unknown> {\n let ready: Promise<unknown> | undefined;\n return (url) => {\n if (!ready) {\n const attempt: Promise<unknown> = init(url).catch((error: unknown) => {\n if (ready === attempt) ready = undefined;\n throw error;\n });\n ready = attempt;\n }\n return ready;\n };\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n NgZone,\n OnChanges,\n OnDestroy,\n InjectionToken,\n inject,\n input,\n output,\n} from '@angular/core';\nimport init, { ProseHandle } from '@ds-labs/prose-wasm';\nimport { createWasmLoader } from './wasm-loader';\n\n/**\n * Where the browser fetches `prose_wasm_bg.wasm` from, relative to the page's\n * base href. The build does not emit the file for you: copy it into your\n * assets (see README) or provide this token.\n */\nexport const PROSE_WASM_URL = new InjectionToken<string>('PROSE_WASM_URL', {\n providedIn: 'root',\n factory: () => 'prose_wasm_bg.wasm',\n});\n\n// The wasm module is fetched and instantiated once per page. A failed load is\n// not remembered, so the next input change retries it.\nconst loadWasm = createWasmLoader((url) => init({ module_or_path: new URL(url, document.baseURI) }));\n\n/**\n * `<ds-odrl-prose [json]=\"policy\" />` renders ODRL JSON-LD as plain-language\n * prose using the Yew component from `prose-yew`.\n *\n * The component owns the host element's children (Yew keeps its own virtual\n * DOM there); style it through the `prose-*` classes or the `class` input.\n *\n * If the wasm cannot be loaded, the host shows an error (`role=\"alert\"`,\n * class `prose-error`), `loadFailed` emits the error, and the next change of\n * an input tries again.\n */\n@Component({\n selector: 'ds-odrl-prose',\n standalone: true,\n template: '',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class OdrlProseComponent implements OnChanges, OnDestroy {\n /** The ODRL policy as JSON-LD text. */\n readonly json = input.required<string>();\n /** Extra class(es) for the rendered `<article class=\"ds-prose\">`. */\n readonly articleClass = input<string>();\n /** Emits when the wasm module could not be loaded. */\n readonly loadFailed = output<unknown>();\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly zone = inject(NgZone);\n private readonly wasmUrl = inject(PROSE_WASM_URL);\n private handle?: ProseHandle;\n private destroyed = false;\n\n ngOnChanges(): void {\n void this.sync();\n }\n\n ngOnDestroy(): void {\n this.destroyed = true;\n this.handle?.destroy();\n this.handle?.free();\n this.handle = undefined;\n }\n\n private async sync(): Promise<void> {\n try {\n await loadWasm(this.wasmUrl);\n } catch (error) {\n if (!this.destroyed) this.showLoadError(error);\n return;\n }\n if (this.destroyed) return;\n // Only the error message can be in the host before the first mount.\n if (!this.handle) this.host.replaceChildren();\n const json = this.json();\n const cls = this.articleClass() ?? null;\n // Yew schedules its own work; keep it out of Angular's change detection.\n this.zone.runOutsideAngular(() => {\n if (this.handle) this.handle.update(json, cls);\n else this.handle = new ProseHandle(this.host, json, cls);\n });\n }\n\n private showLoadError(error: unknown): void {\n console.error('ds-odrl-prose: could not load the wasm module', error);\n // A mounted Yew tree owns the host's children: leave it alone.\n if (!this.handle) {\n const message = document.createElement('p');\n message.className = 'prose-error';\n message.setAttribute('role', 'alert');\n message.textContent =\n `The ODRL prose renderer could not be loaded (${this.wasmUrl}). Check that the file is served; it is tried again when the input changes.`;\n this.host.replaceChildren(message);\n }\n this.loadFailed.emit(error);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ds-labs-prose-angular.mjs","sources":["../../../prose-angular/src/profile-sync.ts","../../../prose-angular/src/validation-sync.ts","../../../prose-angular/src/wasm-loader.ts","../../../prose-angular/src/wasm-runtime.ts","../../../prose-angular/src/odrl-prose.component.ts","../../../prose-angular/src/validator.service.ts","../../../prose-angular/src/ds-labs-prose-angular.ts"],"sourcesContent":["/**\n * A loaded profile, as `ProseHandle.setProfile` describes it. `counts` has\n * one entry per kind of term present (`action`, `leftOperand`, `operator`,\n * `rightOperand`, `conflictTerm`, ...).\n */\nexport interface ProfileSummary {\n iri: string | null;\n title: string | null;\n version: string | null;\n terms: number;\n counts: Record<string, number>;\n warnings: string[];\n}\n\n/**\n * A tracker of the profile text last handed to the wasm, one per component.\n * It returns true when `text` must be handed over now: only when it changed,\n * so a policy change alone does not re-read a large profile, and a profile\n * that failed is not reported again until it changes. `null`, `undefined`\n * and blank text all mean no profile, which is what a fresh mount has.\n */\nexport function createProfileTracker(): (text: string | null | undefined) => boolean {\n let applied: string | null = null;\n return (text) => {\n const next = text == null || text.trim() === '' ? null : text;\n if (next === applied) return false;\n applied = next;\n return true;\n };\n}\n\n/** The summary `setProfile` returns, or null for `\"null\"` (no profile). */\nexport function parseSummary(json: string): ProfileSummary | null {\n return JSON.parse(json) as ProfileSummary | null;\n}\n","import type { ValidateOptions } from '@ds-labs/prose-wasm';\n\n/** What `reportChanged` reads of a `ValidationReport`. */\nexport interface ReportLike {\n verdict: string;\n findings?: readonly { fingerprint: string; severity?: string }[];\n}\n\n/**\n * Whether `next` is a report the host has not seen: the verdict, or the set\n * of findings (by fingerprint, with their severity), differs. Wording, order\n * and the rest of the report do not count, so a policy edit that changes\n * nothing the validator found is not an event. No report to no report is not\n * a change; a first report, or one that goes away, is.\n */\nexport function reportChanged(prev: ReportLike | null | undefined, next: ReportLike | null | undefined): boolean {\n if (!prev || !next) return !!prev !== !!next;\n if (prev.verdict !== next.verdict) return true;\n const a = findingKeys(prev);\n const b = findingKeys(next);\n return a.length !== b.length || a.some((k, i) => k !== b[i]);\n}\n\nfunction findingKeys(r: ReportLike): string[] {\n return (r.findings ?? []).map((f) => `${f.fingerprint}\\u0000${f.severity ?? ''}`).sort();\n}\n\n/**\n * The options the wasm validator is given for the `strict` and `validation`\n * inputs: `validation` as given (copied) when set, since it takes precedence;\n * else strict mode when `strict` is on; else `null`, no validation.\n */\nexport function validationOptions(strict: boolean, validation: ValidateOptions | null | undefined): ValidateOptions | null {\n if (validation != null) return JSON.parse(JSON.stringify(validation)) as ValidateOptions;\n return strict ? { strict: true } : null;\n}\n\n/**\n * The profile texts for the `profile` and `profiles` inputs. A list wins over\n * the single profile (an empty one clears it); blank texts are dropped; `null`\n * means no profile input at all.\n */\nexport function profileTexts(profile: string | null | undefined, profiles: readonly string[] | null | undefined): string[] | null {\n if (profiles != null) return profiles.filter((t) => t.trim() !== '');\n return profile == null || profile.trim() === '' ? null : [profile];\n}\n\n/**\n * A tracker of the value last handed to the wasm, one per component. It\n * returns true when `value` differs from the last one (compared as JSON), so\n * an unchanged input is not handed over again. `null` and `undefined` are\n * \"none\", which is what a fresh mount has.\n */\nexport function createChangeTracker<T>(): (value: T | null | undefined) => boolean {\n let applied = 'null';\n return (value) => {\n const next = value == null ? 'null' : JSON.stringify(value);\n if (next === applied) return false;\n applied = next;\n return true;\n };\n}\n\n/** The report text `ProseHandle.report()` returns, parsed; `null` for none. */\nexport function parseReport<R = unknown>(text: string | null | undefined): R | null {\n return text == null ? null : (JSON.parse(text) as R);\n}\n\n/** What `StandaloneReporter` needs of a `ProseValidator`. */\nexport interface ValidatorLike {\n validate(json: string): string;\n free(): void;\n}\n\n/**\n * The report for a policy when the handle is not validating: a validator\n * rebuilt when the profiles or the options change. When the options cannot be\n * read, the previous options stay, as the `validationError` output promises:\n * the report is computed with the last options that worked (or is `null`\n * when none did), so it follows the policy and is never a stale one.\n */\nexport class StandaloneReporter<V extends ValidatorLike = ValidatorLike> {\n private cached?: { key: string; value: V };\n private good: ValidateOptions | null = null;\n\n private readonly build: (profiles: string[], options: ValidateOptions) => V;\n\n // No parameter property: the tests run this file with types stripped only.\n constructor(build: (profiles: string[], options: ValidateOptions) => V) {\n this.build = build;\n }\n\n /** The report text for `json`, and the error when `options` were unreadable. */\n report(profiles: string[], options: ValidateOptions, json: string): { report: string | null; error?: unknown } {\n try {\n const report = this.validator(profiles, options).validate(json);\n this.good = options;\n return { report };\n } catch (error) {\n if (this.good) {\n try {\n return { report: this.validator(profiles, this.good).validate(json), error };\n } catch {\n // Not even the previous options work (the profiles changed under them).\n }\n }\n return { report: null, error };\n }\n }\n\n /** Validation is off: no options are \"previous\" any more. */\n clear(): void {\n this.good = null;\n }\n\n free(): void {\n this.cached?.value.free();\n this.cached = undefined;\n }\n\n private validator(profiles: string[], options: ValidateOptions): V {\n const key = JSON.stringify([profiles, options]);\n if (this.cached?.key !== key) {\n const value = this.build(profiles, options);\n this.cached?.value.free();\n this.cached = { key, value };\n }\n return this.cached.value;\n }\n}\n","/** Loads and instantiates the wasm module. Injected so it can be tested. */\nexport type WasmInit = (url: string) => Promise<unknown>;\n\n/**\n * A loader that runs `init` at most once at a time and shares the result, so\n * every component on a page waits on one fetch. A failed load is forgotten:\n * the callers waiting on it all see the failure, and the next call tries again\n * (the file may have been missing only for a moment).\n */\nexport function createWasmLoader(init: WasmInit): (url: string) => Promise<unknown> {\n let ready: Promise<unknown> | undefined;\n return (url) => {\n if (!ready) {\n const attempt: Promise<unknown> = init(url).catch((error: unknown) => {\n if (ready === attempt) ready = undefined;\n throw error;\n });\n ready = attempt;\n }\n return ready;\n };\n}\n","import { InjectionToken } from '@angular/core';\nimport init from '@ds-labs/prose-wasm';\nimport { createWasmLoader } from './wasm-loader';\n\n/**\n * Where the browser fetches `prose_wasm_bg.wasm` from, relative to the page's\n * base href. The build does not emit the file for you: copy it into your\n * assets (see README) or provide this token.\n */\nexport const PROSE_WASM_URL = new InjectionToken<string>('PROSE_WASM_URL', {\n providedIn: 'root',\n factory: () => 'prose_wasm_bg.wasm',\n});\n\n// The wasm module is fetched and instantiated once per page. A failed load is\n// not remembered, so the next input change retries it.\nexport const loadWasm = createWasmLoader((url) => init({ module_or_path: new URL(url, document.baseURI) }));\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n NgZone,\n OnChanges,\n OnDestroy,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\nimport { ProseHandle, ProseValidator, type ValidateOptions, type ValidationReport } from '@ds-labs/prose-wasm';\nimport { ProfileSummary, parseSummary } from './profile-sync';\nimport { StandaloneReporter, createChangeTracker, parseReport, profileTexts, reportChanged, validationOptions } from './validation-sync';\nimport { PROSE_WASM_URL, loadWasm } from './wasm-runtime';\n\nexport { PROSE_WASM_URL };\n\n/**\n * `<ds-odrl-prose [json]=\"policy\" />` renders ODRL JSON-LD as plain-language\n * prose using the Yew component from `prose-yew`.\n *\n * The component owns the host element's children (Yew keeps its own virtual\n * DOM there); style it through the `prose-*` classes or the `class` input.\n *\n * `[profile]` takes the text of an ODRL profile (JSON-LD or Turtle, as the\n * host fetched it; the component does no network access). The prose then uses\n * the profile's labels, and a glossary of its terms follows each policy.\n * `profileLoaded` emits the profile's summary; an unreadable profile emits\n * `profileError` and the previous profile stays.\n *\n * `[profiles]` takes several profiles at once (a list wins over `profile`).\n *\n * `[strict]=\"true\"` validates the policy against the loaded profiles with a\n * closed vocabulary: ODRL plus those profiles. `[validation]` takes the full\n * options (it wins over `strict`). `validationChange` emits the report\n * (schema `ds-prose/validation-report@1`) each time the verdict or the\n * findings change, and the `report` signal always holds the latest. A\n * verdict of `incomplete` is never a pass: something could not be checked,\n * such as a declared profile that is not loaded. `[showViolations]` also\n * shows the findings under the prose.\n *\n * If the wasm cannot be loaded, the host shows an error (`role=\"alert\"`,\n * class `prose-error`), `loadFailed` emits the error, and the next change of\n * an input tries again.\n */\n@Component({\n selector: 'ds-odrl-prose',\n standalone: true,\n template: '',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class OdrlProseComponent implements OnChanges, OnDestroy {\n /** The ODRL policy as JSON-LD text. */\n readonly json = input.required<string>();\n /** Extra class(es) for the rendered `<article class=\"ds-prose\">`. */\n readonly articleClass = input<string>();\n /** The ODRL profile the prose is worded with, as JSON-LD or Turtle text. */\n readonly profile = input<string | null>(null);\n /** Several ODRL profiles, as JSON-LD or Turtle text each; wins over `profile`. */\n readonly profiles = input<string[] | null>(null);\n /** Validate with a closed vocabulary: ODRL plus the loaded profiles. */\n readonly strict = input(false);\n /** The full validation options; they take precedence over `strict`. */\n readonly validation = input<ValidateOptions | null>(null);\n /** Show the findings and the verdict under the prose. Needs `strict` or `validation`. */\n readonly showViolations = input(false);\n /** Emits when the wasm module could not be loaded. */\n readonly loadFailed = output<unknown>();\n /** Emits the summary of each profile loaded from the `profile` input. */\n readonly profileLoaded = output<ProfileSummary>();\n /** Emits when the `profile` input is not a readable profile; the previous one stays. */\n readonly profileError = output<unknown>();\n /** Emits the report when the verdict or the findings change. */\n readonly validationChange = output<ValidationReport>();\n /** Emits when `validation` is not readable options; the previous options stay. */\n readonly validationError = output<unknown>();\n /** The latest report, or `null` while validation is off. */\n readonly report = signal<ValidationReport | null>(null);\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly zone = inject(NgZone);\n private readonly wasmUrl = inject(PROSE_WASM_URL);\n private handle?: ProseHandle;\n private readonly profilesChanged = createChangeTracker<string[]>();\n private readonly handleOptionsChanged = createChangeTracker<ValidateOptions>();\n // The profiles the handle holds now, and the validator built from them for\n // a report the handle is not asked for.\n private appliedProfiles: string[] = [];\n private readonly standalone = new StandaloneReporter((profiles, options) => new ProseValidator(profiles, options));\n // Whether the handle validates (it was given options, with the findings shown).\n private handleValidates = false;\n private destroyed = false;\n\n ngOnChanges(): void {\n void this.sync();\n }\n\n ngOnDestroy(): void {\n this.destroyed = true;\n this.handle?.destroy();\n this.handle?.free();\n this.handle = undefined;\n this.standalone.free();\n }\n\n private async sync(): Promise<void> {\n try {\n await loadWasm(this.wasmUrl);\n } catch (error) {\n if (!this.destroyed) this.showLoadError(error);\n return;\n }\n if (this.destroyed) return;\n // Only the error message can be in the host before the first mount.\n if (!this.handle) this.host.replaceChildren();\n const json = this.json();\n const cls = this.articleClass() ?? null;\n const single = this.profiles() == null;\n const texts = profileTexts(this.profile(), this.profiles());\n const options = validationOptions(this.strict(), this.validation());\n const show = this.showViolations() && options != null;\n // Yew schedules its own work; keep it out of Angular's change detection.\n const outcome = this.zone.runOutsideAngular(() => {\n const mounted = this.handle;\n const handle = mounted ?? new ProseHandle(this.host, json, cls);\n this.handle = handle;\n const out: {\n summaries?: ProfileSummary[];\n profileError?: unknown;\n validationError?: unknown;\n report?: string | null;\n } = {};\n // The profiles go first, so the validation and the update below use them.\n if (this.profilesChanged(texts)) {\n try {\n out.summaries = single\n ? [parseSummary(handle.setProfile(texts?.[0] ?? null))].filter((x) => x != null)\n : (JSON.parse(handle.setProfiles(texts ?? [])) as ProfileSummary[]);\n this.appliedProfiles = texts ?? [];\n } catch (error) {\n out.profileError = error;\n }\n }\n if (this.handleOptionsChanged(show ? options : null)) {\n try {\n handle.setValidation(show ? options : null);\n this.handleValidates = show;\n } catch (error) {\n out.validationError = error;\n }\n }\n if (mounted) handle.update(json, cls);\n if (options) {\n if (this.handleValidates) {\n try {\n out.report = handle.report();\n } catch (error) {\n out.validationError ??= error;\n }\n } else {\n // Unreadable options keep the previous ones; see StandaloneReporter.\n const standalone = this.standalone.report(this.appliedProfiles, options, json);\n out.report = standalone.report;\n if (standalone.error !== undefined) out.validationError ??= standalone.error;\n }\n } else {\n this.standalone.clear();\n out.report = null;\n }\n return out;\n });\n if (outcome.profileError !== undefined) this.profileError.emit(outcome.profileError);\n for (const summary of outcome.summaries ?? []) this.profileLoaded.emit(summary);\n if (outcome.validationError !== undefined) this.validationError.emit(outcome.validationError);\n if (outcome.report !== undefined) this.publish(parseReport<ValidationReport>(outcome.report));\n }\n\n private publish(next: ValidationReport | null): void {\n // Signals and outputs are Angular's: back inside the zone.\n this.zone.run(() => {\n const prev = this.report();\n this.report.set(next);\n if (next && reportChanged(prev, next)) this.validationChange.emit(next);\n });\n }\n\n private showLoadError(error: unknown): void {\n console.error('ds-odrl-prose: could not load the wasm module', error);\n // A mounted Yew tree owns the host's children: leave it alone.\n if (!this.handle) {\n const message = document.createElement('p');\n message.className = 'prose-error';\n message.setAttribute('role', 'alert');\n message.textContent =\n `The ODRL prose renderer could not be loaded (${this.wasmUrl}). Check that the file is served; it is tried again when the input changes.`;\n this.host.replaceChildren(message);\n }\n this.loadFailed.emit(error);\n }\n}\n","import { Injectable, inject } from '@angular/core';\nimport { validate, type ValidateOptions, type ValidationReport } from '@ds-labs/prose-wasm';\nimport { PROSE_WASM_URL, loadWasm } from './wasm-runtime';\n\n/**\n * Validates ODRL policies against ODRL profiles without rendering anything\n * (a form's submit guard, a list of policies). It runs the same engine as\n * `<ds-odrl-prose [strict]>` and the `ds-prose-validate` command, so the\n * report is the same.\n */\n@Injectable({ providedIn: 'root' })\nexport class ProseValidatorService {\n private readonly wasmUrl = inject(PROSE_WASM_URL);\n\n /**\n * The report for the policy `json` against `profiles` (the text of each\n * profile, JSON-LD or Turtle; the service does no network access). Strict\n * unless `options.strict` is `false`. Waits for the wasm module to load;\n * rejects when it cannot, or when a profile or the options cannot be read.\n * A problem in the policy is never a rejection: it is a finding.\n */\n async validate(\n json: string,\n profiles: string | readonly string[] | null = null,\n options: ValidateOptions | null = null,\n ): Promise<ValidationReport> {\n await loadWasm(this.wasmUrl);\n const list = typeof profiles === 'string' || profiles == null ? profiles : [...profiles];\n return JSON.parse(validate(json, list, options)) as ValidationReport;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/index.d.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
1
|
export { OdrlProseComponent, PROSE_WASM_URL } from './odrl-prose.component';
|
|
2
|
+
export { ProseValidatorService } from './validator.service';
|
|
3
|
+
export type { ProfileSummary } from './profile-sync';
|
|
4
|
+
export { reportChanged } from './validation-sync';
|
|
5
|
+
export type { ReportLike } from './validation-sync';
|
|
6
|
+
export type { Finding, RuleId, RuleInfo, ValidateOptions, ValidationReport, Verdict } from '@ds-labs/prose-wasm';
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { OnChanges, OnDestroy
|
|
1
|
+
import { OnChanges, OnDestroy } from '@angular/core';
|
|
2
|
+
import { type ValidateOptions, type ValidationReport } from '@ds-labs/prose-wasm';
|
|
3
|
+
import { ProfileSummary } from './profile-sync';
|
|
4
|
+
import { PROSE_WASM_URL } from './wasm-runtime';
|
|
2
5
|
import * as i0 from "@angular/core";
|
|
3
|
-
|
|
4
|
-
* Where the browser fetches `prose_wasm_bg.wasm` from, relative to the page's
|
|
5
|
-
* base href. The build does not emit the file for you: copy it into your
|
|
6
|
-
* assets (see README) or provide this token.
|
|
7
|
-
*/
|
|
8
|
-
export declare const PROSE_WASM_URL: InjectionToken<string>;
|
|
6
|
+
export { PROSE_WASM_URL };
|
|
9
7
|
/**
|
|
10
8
|
* `<ds-odrl-prose [json]="policy" />` renders ODRL JSON-LD as plain-language
|
|
11
9
|
* prose using the Yew component from `prose-yew`.
|
|
@@ -13,6 +11,23 @@ export declare const PROSE_WASM_URL: InjectionToken<string>;
|
|
|
13
11
|
* The component owns the host element's children (Yew keeps its own virtual
|
|
14
12
|
* DOM there); style it through the `prose-*` classes or the `class` input.
|
|
15
13
|
*
|
|
14
|
+
* `[profile]` takes the text of an ODRL profile (JSON-LD or Turtle, as the
|
|
15
|
+
* host fetched it; the component does no network access). The prose then uses
|
|
16
|
+
* the profile's labels, and a glossary of its terms follows each policy.
|
|
17
|
+
* `profileLoaded` emits the profile's summary; an unreadable profile emits
|
|
18
|
+
* `profileError` and the previous profile stays.
|
|
19
|
+
*
|
|
20
|
+
* `[profiles]` takes several profiles at once (a list wins over `profile`).
|
|
21
|
+
*
|
|
22
|
+
* `[strict]="true"` validates the policy against the loaded profiles with a
|
|
23
|
+
* closed vocabulary: ODRL plus those profiles. `[validation]` takes the full
|
|
24
|
+
* options (it wins over `strict`). `validationChange` emits the report
|
|
25
|
+
* (schema `ds-prose/validation-report@1`) each time the verdict or the
|
|
26
|
+
* findings change, and the `report` signal always holds the latest. A
|
|
27
|
+
* verdict of `incomplete` is never a pass: something could not be checked,
|
|
28
|
+
* such as a declared profile that is not loaded. `[showViolations]` also
|
|
29
|
+
* shows the findings under the prose.
|
|
30
|
+
*
|
|
16
31
|
* If the wasm cannot be loaded, the host shows an error (`role="alert"`,
|
|
17
32
|
* class `prose-error`), `loadFailed` emits the error, and the next change of
|
|
18
33
|
* an input tries again.
|
|
@@ -22,17 +37,43 @@ export declare class OdrlProseComponent implements OnChanges, OnDestroy {
|
|
|
22
37
|
readonly json: import("@angular/core").InputSignal<string>;
|
|
23
38
|
/** Extra class(es) for the rendered `<article class="ds-prose">`. */
|
|
24
39
|
readonly articleClass: import("@angular/core").InputSignal<string | undefined>;
|
|
40
|
+
/** The ODRL profile the prose is worded with, as JSON-LD or Turtle text. */
|
|
41
|
+
readonly profile: import("@angular/core").InputSignal<string | null>;
|
|
42
|
+
/** Several ODRL profiles, as JSON-LD or Turtle text each; wins over `profile`. */
|
|
43
|
+
readonly profiles: import("@angular/core").InputSignal<string[] | null>;
|
|
44
|
+
/** Validate with a closed vocabulary: ODRL plus the loaded profiles. */
|
|
45
|
+
readonly strict: import("@angular/core").InputSignal<boolean>;
|
|
46
|
+
/** The full validation options; they take precedence over `strict`. */
|
|
47
|
+
readonly validation: import("@angular/core").InputSignal<ValidateOptions | null>;
|
|
48
|
+
/** Show the findings and the verdict under the prose. Needs `strict` or `validation`. */
|
|
49
|
+
readonly showViolations: import("@angular/core").InputSignal<boolean>;
|
|
25
50
|
/** Emits when the wasm module could not be loaded. */
|
|
26
51
|
readonly loadFailed: import("@angular/core").OutputEmitterRef<unknown>;
|
|
52
|
+
/** Emits the summary of each profile loaded from the `profile` input. */
|
|
53
|
+
readonly profileLoaded: import("@angular/core").OutputEmitterRef<ProfileSummary>;
|
|
54
|
+
/** Emits when the `profile` input is not a readable profile; the previous one stays. */
|
|
55
|
+
readonly profileError: import("@angular/core").OutputEmitterRef<unknown>;
|
|
56
|
+
/** Emits the report when the verdict or the findings change. */
|
|
57
|
+
readonly validationChange: import("@angular/core").OutputEmitterRef<ValidationReport>;
|
|
58
|
+
/** Emits when `validation` is not readable options; the previous options stay. */
|
|
59
|
+
readonly validationError: import("@angular/core").OutputEmitterRef<unknown>;
|
|
60
|
+
/** The latest report, or `null` while validation is off. */
|
|
61
|
+
readonly report: import("@angular/core").WritableSignal<ValidationReport | null>;
|
|
27
62
|
private readonly host;
|
|
28
63
|
private readonly zone;
|
|
29
64
|
private readonly wasmUrl;
|
|
30
65
|
private handle?;
|
|
66
|
+
private readonly profilesChanged;
|
|
67
|
+
private readonly handleOptionsChanged;
|
|
68
|
+
private appliedProfiles;
|
|
69
|
+
private readonly standalone;
|
|
70
|
+
private handleValidates;
|
|
31
71
|
private destroyed;
|
|
32
72
|
ngOnChanges(): void;
|
|
33
73
|
ngOnDestroy(): void;
|
|
34
74
|
private sync;
|
|
75
|
+
private publish;
|
|
35
76
|
private showLoadError;
|
|
36
77
|
static ɵfac: i0.ɵɵFactoryDeclaration<OdrlProseComponent, never>;
|
|
37
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<OdrlProseComponent, "ds-odrl-prose", never, { "json": { "alias": "json"; "required": true; "isSignal": true; }; "articleClass": { "alias": "articleClass"; "required": false; "isSignal": true; }; }, { "loadFailed": "loadFailed"; }, never, never, true, never>;
|
|
78
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<OdrlProseComponent, "ds-odrl-prose", never, { "json": { "alias": "json"; "required": true; "isSignal": true; }; "articleClass": { "alias": "articleClass"; "required": false; "isSignal": true; }; "profile": { "alias": "profile"; "required": false; "isSignal": true; }; "profiles": { "alias": "profiles"; "required": false; "isSignal": true; }; "strict": { "alias": "strict"; "required": false; "isSignal": true; }; "validation": { "alias": "validation"; "required": false; "isSignal": true; }; "showViolations": { "alias": "showViolations"; "required": false; "isSignal": true; }; }, { "loadFailed": "loadFailed"; "profileLoaded": "profileLoaded"; "profileError": "profileError"; "validationChange": "validationChange"; "validationError": "validationError"; }, never, never, true, never>;
|
|
38
79
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ds-labs/prose-angular",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Angular component that renders ODRL JSON-LD as plain-language prose (ds-prose-rs, compiled to WebAssembly)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@angular/core": ">=19.0.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@ds-labs/prose-wasm": "^0.
|
|
30
|
+
"@ds-labs/prose-wasm": "^0.4.0",
|
|
31
31
|
"tslib": "^2.3.0"
|
|
32
32
|
},
|
|
33
33
|
"module": "fesm2022/ds-labs-prose-angular.mjs",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A loaded profile, as `ProseHandle.setProfile` describes it. `counts` has
|
|
3
|
+
* one entry per kind of term present (`action`, `leftOperand`, `operator`,
|
|
4
|
+
* `rightOperand`, `conflictTerm`, ...).
|
|
5
|
+
*/
|
|
6
|
+
export interface ProfileSummary {
|
|
7
|
+
iri: string | null;
|
|
8
|
+
title: string | null;
|
|
9
|
+
version: string | null;
|
|
10
|
+
terms: number;
|
|
11
|
+
counts: Record<string, number>;
|
|
12
|
+
warnings: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A tracker of the profile text last handed to the wasm, one per component.
|
|
16
|
+
* It returns true when `text` must be handed over now: only when it changed,
|
|
17
|
+
* so a policy change alone does not re-read a large profile, and a profile
|
|
18
|
+
* that failed is not reported again until it changes. `null`, `undefined`
|
|
19
|
+
* and blank text all mean no profile, which is what a fresh mount has.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createProfileTracker(): (text: string | null | undefined) => boolean;
|
|
22
|
+
/** The summary `setProfile` returns, or null for `"null"` (no profile). */
|
|
23
|
+
export declare function parseSummary(json: string): ProfileSummary | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ValidateOptions } from '@ds-labs/prose-wasm';
|
|
2
|
+
/** What `reportChanged` reads of a `ValidationReport`. */
|
|
3
|
+
export interface ReportLike {
|
|
4
|
+
verdict: string;
|
|
5
|
+
findings?: readonly {
|
|
6
|
+
fingerprint: string;
|
|
7
|
+
severity?: string;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Whether `next` is a report the host has not seen: the verdict, or the set
|
|
12
|
+
* of findings (by fingerprint, with their severity), differs. Wording, order
|
|
13
|
+
* and the rest of the report do not count, so a policy edit that changes
|
|
14
|
+
* nothing the validator found is not an event. No report to no report is not
|
|
15
|
+
* a change; a first report, or one that goes away, is.
|
|
16
|
+
*/
|
|
17
|
+
export declare function reportChanged(prev: ReportLike | null | undefined, next: ReportLike | null | undefined): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The options the wasm validator is given for the `strict` and `validation`
|
|
20
|
+
* inputs: `validation` as given (copied) when set, since it takes precedence;
|
|
21
|
+
* else strict mode when `strict` is on; else `null`, no validation.
|
|
22
|
+
*/
|
|
23
|
+
export declare function validationOptions(strict: boolean, validation: ValidateOptions | null | undefined): ValidateOptions | null;
|
|
24
|
+
/**
|
|
25
|
+
* The profile texts for the `profile` and `profiles` inputs. A list wins over
|
|
26
|
+
* the single profile (an empty one clears it); blank texts are dropped; `null`
|
|
27
|
+
* means no profile input at all.
|
|
28
|
+
*/
|
|
29
|
+
export declare function profileTexts(profile: string | null | undefined, profiles: readonly string[] | null | undefined): string[] | null;
|
|
30
|
+
/**
|
|
31
|
+
* A tracker of the value last handed to the wasm, one per component. It
|
|
32
|
+
* returns true when `value` differs from the last one (compared as JSON), so
|
|
33
|
+
* an unchanged input is not handed over again. `null` and `undefined` are
|
|
34
|
+
* "none", which is what a fresh mount has.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createChangeTracker<T>(): (value: T | null | undefined) => boolean;
|
|
37
|
+
/** The report text `ProseHandle.report()` returns, parsed; `null` for none. */
|
|
38
|
+
export declare function parseReport<R = unknown>(text: string | null | undefined): R | null;
|
|
39
|
+
/** What `StandaloneReporter` needs of a `ProseValidator`. */
|
|
40
|
+
export interface ValidatorLike {
|
|
41
|
+
validate(json: string): string;
|
|
42
|
+
free(): void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The report for a policy when the handle is not validating: a validator
|
|
46
|
+
* rebuilt when the profiles or the options change. When the options cannot be
|
|
47
|
+
* read, the previous options stay, as the `validationError` output promises:
|
|
48
|
+
* the report is computed with the last options that worked (or is `null`
|
|
49
|
+
* when none did), so it follows the policy and is never a stale one.
|
|
50
|
+
*/
|
|
51
|
+
export declare class StandaloneReporter<V extends ValidatorLike = ValidatorLike> {
|
|
52
|
+
private cached?;
|
|
53
|
+
private good;
|
|
54
|
+
private readonly build;
|
|
55
|
+
constructor(build: (profiles: string[], options: ValidateOptions) => V);
|
|
56
|
+
/** The report text for `json`, and the error when `options` were unreadable. */
|
|
57
|
+
report(profiles: string[], options: ValidateOptions, json: string): {
|
|
58
|
+
report: string | null;
|
|
59
|
+
error?: unknown;
|
|
60
|
+
};
|
|
61
|
+
/** Validation is off: no options are "previous" any more. */
|
|
62
|
+
clear(): void;
|
|
63
|
+
free(): void;
|
|
64
|
+
private validator;
|
|
65
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ValidateOptions, type ValidationReport } from '@ds-labs/prose-wasm';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Validates ODRL policies against ODRL profiles without rendering anything
|
|
5
|
+
* (a form's submit guard, a list of policies). It runs the same engine as
|
|
6
|
+
* `<ds-odrl-prose [strict]>` and the `ds-prose-validate` command, so the
|
|
7
|
+
* report is the same.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ProseValidatorService {
|
|
10
|
+
private readonly wasmUrl;
|
|
11
|
+
/**
|
|
12
|
+
* The report for the policy `json` against `profiles` (the text of each
|
|
13
|
+
* profile, JSON-LD or Turtle; the service does no network access). Strict
|
|
14
|
+
* unless `options.strict` is `false`. Waits for the wasm module to load;
|
|
15
|
+
* rejects when it cannot, or when a profile or the options cannot be read.
|
|
16
|
+
* A problem in the policy is never a rejection: it is a finding.
|
|
17
|
+
*/
|
|
18
|
+
validate(json: string, profiles?: string | readonly string[] | null, options?: ValidateOptions | null): Promise<ValidationReport>;
|
|
19
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ProseValidatorService, never>;
|
|
20
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<ProseValidatorService>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
/**
|
|
3
|
+
* Where the browser fetches `prose_wasm_bg.wasm` from, relative to the page's
|
|
4
|
+
* base href. The build does not emit the file for you: copy it into your
|
|
5
|
+
* assets (see README) or provide this token.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PROSE_WASM_URL: InjectionToken<string>;
|
|
8
|
+
export declare const loadWasm: (url: string) => Promise<unknown>;
|