@ds-labs/prose-angular 0.1.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 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
@@ -71,9 +131,9 @@ not remembered: the next change of an input tries again.
71
131
  DOM there). Use it as a leaf.
72
132
  - It renders client-side only. If your app uses SSR, guard it with
73
133
  `isPlatformBrowser` or `@defer`.
74
- - The package version is independent of the Rust crates. The ds-prose-rs
75
- version of the wasm is recorded in `@ds-labs/prose-wasm`'s `package.json`
76
- under `dsProse.prose-yew`.
134
+ - The package version follows the Rust crates' (and `@ds-labs/prose-wasm`'s).
135
+ The ds-prose-rs version of the wasm is also recorded in
136
+ `@ds-labs/prose-wasm`'s `package.json` under `dsProse.prose-yew`.
77
137
 
78
138
  ## Replacing a vendored copy
79
139
 
@@ -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
- if (this.handle)
89
- this.handle.update(json, cls);
90
- else
91
- this.handle = new ProseHandle(this.host, json, cls);
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, InjectionToken } from '@angular/core';
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.1.1",
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.1.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>;