@blackcatinformatics/purrdf 0.3.2 → 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
@@ -14,6 +14,10 @@ It is the same engine, byte-for-byte behavior, that ships as the `purrdf`
14
14
  Rust crates, the `purrdf` PyPI package, and `libpurrdf` — PurRDF's rule is
15
15
  **one engine, one behavior, every language**.
16
16
 
17
+ > **Try it live** — the [RDF-1.2 playground](https://blackcat-informatics.github.io/purrdf/playground/)
18
+ > runs this package in your browser: parse, SPARQL, SHACL, serialize, and
19
+ > canonicalize/compare RDF-1.2 graphs client-side, with no install.
20
+
17
21
  ## Why this instead of an incumbent RDF/JS library?
18
22
 
19
23
  No incumbent RDF/JS library carries the RDF 1.2 features:
@@ -73,7 +77,11 @@ const reparsed = Dataset.parse(nq, "nquads");
73
77
  `fromTerm`, `fromQuad`.
74
78
  - `Dataset` — `Dataset.parse(input, format, base?)`, `serialize(format)`,
75
79
  `add` / `delete` / `has` / `match` / `quads` / `size`, iteration.
76
- Formats: `turtle`, `ntriples`, `nquads`, `trig`, `rdfxml`.
80
+ Formats: `turtle`, `ntriples`, `nquads`, `trig`, `rdfxml` (`serialize` also `jsonld`).
81
+ - `Dataset.canonicalize()` / `Dataset.isomorphic(other)` — RDFC-1.0 canonical N-Quads
82
+ and RDF graph-identity (isomorphism under blank-node relabeling).
83
+ - `shaclValidateToSarif(shapesTtl, dataNt)` / `shaclEntail(shapesTtl, dataNt)` — SHACL
84
+ validation to a SARIF 2.1.0 report and SHACL-AF `sh:rule` entailment to N-Triples.
77
85
  - `Sink`, `datasetToStream`, `streamToDataset` — the async RDF/JS
78
86
  Stream/Sink primitives over the synchronous engine surface.
79
87
  - SPARQL evaluation over the in-memory dataset (no server required).
@@ -83,8 +91,8 @@ Full typings ship in `index.d.ts`.
83
91
  ## Scope
84
92
 
85
93
  In-memory only, by design: no persistent store and no network I/O inside the
86
- wasm module. For the container transport (GTS), SHACL validation, SPARQL
87
- result serializers, and the rest of the toolkit, see the
94
+ wasm module. For the container transport (GTS), SPARQL result serializers, and the
95
+ rest of the toolkit, see the
88
96
  [main repository](https://github.com/Blackcat-Informatics/purrdf).
89
97
 
90
98
  ## Supply chain
package/index.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  // SPDX-FileCopyrightText: 2026 Blackcat Informatics® Inc. <paudley@blackcatinformatics.ca>
2
2
  // SPDX-License-Identifier: MIT OR Apache-2.0
3
3
 
4
- // The wasm-bindgen-generated class declarations (DataFactory/Dataset/Quad/Sink/Term
5
- // and the free `version()` function) are the source of truth for the engine surface.
4
+ // The wasm-bindgen-generated class declarations (DataFactory/Dataset/Quad/Sink/Term)
5
+ // and the free functions (`version()`, `shaclValidateToSarif`, `shaclEntail`) are the
6
+ // source of truth for the engine surface — the whole `#[wasm_bindgen]` surface is
7
+ // re-exported from the package root (Dataset.canonicalize()/isomorphic() ship on the
8
+ // Dataset class itself).
6
9
  export {
7
10
  DataFactory,
8
11
  Dataset,
9
12
  Quad,
13
+ shaclEntail,
14
+ shaclValidateToSarif,
10
15
  Sink,
11
16
  Term,
12
17
  version,
package/index.mjs CHANGED
@@ -3,8 +3,11 @@
3
3
 
4
4
  // purrdf — the idiomatic RDF/JS surface over the wasm engine.
5
5
  //
6
- // The wasm-bindgen-generated classes (DataFactory/Dataset/Quad/Sink/Term) are
7
- // re-exported as-is; this wrapper adds the isomorphic glue that the synchronous
6
+ // The wasm-bindgen-generated classes (DataFactory/Dataset/Quad/Sink/Term) and the
7
+ // free functions (version, shaclValidateToSarif, shaclEntail) are re-exported as-is
8
+ // the whole `#[wasm_bindgen]` surface is reachable from the package root, so
9
+ // SHACL validation/entailment and Dataset.canonicalize()/isomorphic() need no deep
10
+ // `./pkg/` import. This wrapper adds the isomorphic glue that the synchronous
8
11
  // wasm boundary cannot express in Rust:
9
12
  // * `ready()` — one-time async wasm instantiation (required for the `web` target).
10
13
  // * the polymorphic RDF/JS `DataFactory.literal(value, languageOrDatatype)` —
@@ -18,6 +21,8 @@ import init, {
18
21
  DataFactory,
19
22
  Dataset,
20
23
  Quad,
24
+ shaclEntail,
25
+ shaclValidateToSarif,
21
26
  Sink,
22
27
  Term,
23
28
  version,
@@ -125,4 +130,13 @@ export async function streamToDataset(quadStream) {
125
130
  return sink.finish();
126
131
  }
127
132
 
128
- export { DataFactory, Dataset, Quad, Sink, Term, version };
133
+ export {
134
+ DataFactory,
135
+ Dataset,
136
+ Quad,
137
+ shaclEntail,
138
+ shaclValidateToSarif,
139
+ Sink,
140
+ Term,
141
+ version,
142
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blackcatinformatics/purrdf",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "A wasm32, in-memory RDF 1.2 engine with an idiomatic RDF/JS (DataFactory/Dataset/Stream) API. Quoted-triple terms and directional literals included.",
5
5
  "type": "module",
6
6
  "license": "MIT OR Apache-2.0",
@@ -45,6 +45,9 @@ export class DataFactory {
45
45
  * `namedNode(value)` → a `NamedNode` term.
46
46
  */
47
47
  namedNode(value: string): Term;
48
+ /**
49
+ * `new DataFactory()` — a fresh factory with its blank-node counter at zero.
50
+ */
48
51
  constructor();
49
52
  /**
50
53
  * `quad(subject, predicate, object, graph?)` → a `Quad`. The graph defaults to the
@@ -80,6 +83,15 @@ export class Dataset {
80
83
  * `add(quad)` → insert a quad. Returns `true` if the effective set changed.
81
84
  */
82
85
  add(quad: Quad): boolean;
86
+ /**
87
+ * `canonicalize()` → the dataset as canonical, flat N-Quads under RDFC-1.0
88
+ * (SHA-256).
89
+ *
90
+ * The deterministic identity string for the graph: two datasets denote the same
91
+ * RDF graph (under blank-node relabeling) iff their canonical forms are
92
+ * byte-identical. This is the same RDFC-1.0 output the conformance gate pins.
93
+ */
94
+ canonicalize(): string;
83
95
  /**
84
96
  * `delete(quad)` → remove a quad. Returns `true` if the effective set changed.
85
97
  */
@@ -88,6 +100,16 @@ export class Dataset {
88
100
  * `has(quad)` → whether the quad is in the dataset.
89
101
  */
90
102
  has(quad: Quad): boolean;
103
+ /**
104
+ * `isomorphic(other)` → whether this dataset and `other` are the same RDF graph
105
+ * under blank-node relabeling.
106
+ *
107
+ * The formal RDF graph-identity check, backed by full RDFC-1.0 canonicalization:
108
+ * an exact oracle with no false positives or false negatives. Equivalent to
109
+ * comparing the two [`canonicalize`](Self::canonicalize) strings, but avoids
110
+ * materializing them for obviously-different inputs.
111
+ */
112
+ isomorphic(other: Dataset): boolean;
91
113
  /**
92
114
  * `match(subject?, predicate?, object?, graph?)` → a new dataset of the matching
93
115
  * quads. An omitted (`undefined`) position is a wildcard; `defaultGraph()` matches
@@ -150,9 +172,21 @@ export class Quad {
150
172
  * Structural RDF/JS quad equality.
151
173
  */
152
174
  equals(other: Quad): boolean;
175
+ /**
176
+ * The graph [`Term`] of the quad (`DefaultGraph` when unnamed).
177
+ */
153
178
  readonly graph: Term;
179
+ /**
180
+ * The object [`Term`] of the quad.
181
+ */
154
182
  readonly object: Term;
183
+ /**
184
+ * The predicate [`Term`] of the quad.
185
+ */
155
186
  readonly predicate: Term;
187
+ /**
188
+ * The subject [`Term`] of the quad.
189
+ */
156
190
  readonly subject: Term;
157
191
  /**
158
192
  * Always `"Quad"` (a Quad is itself an RDF/JS term).
@@ -176,6 +210,9 @@ export class Sink {
176
210
  * resulting dataset. The sink is consumed; further `push`/`finish` is an error.
177
211
  */
178
212
  finish(): Dataset;
213
+ /**
214
+ * `new Sink()` — an empty sink ready to accept quads via `push`.
215
+ */
179
216
  constructor();
180
217
  /**
181
218
  * `push(quad)` — stream one quad into the sink (interned via the event protocol).
@@ -284,8 +321,10 @@ export interface InitOutput {
284
321
  readonly datafactory_typedLiteral: (a: number, b: number, c: number, d: number, e: number) => void;
285
322
  readonly datafactory_variable: (a: number, b: number, c: number) => number;
286
323
  readonly dataset_add: (a: number, b: number, c: number) => void;
324
+ readonly dataset_canonicalize: (a: number, b: number) => void;
287
325
  readonly dataset_delete: (a: number, b: number, c: number) => void;
288
326
  readonly dataset_has: (a: number, b: number, c: number) => void;
327
+ readonly dataset_isomorphic: (a: number, b: number, c: number) => void;
289
328
  readonly dataset_match: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
290
329
  readonly dataset_new: (a: number) => void;
291
330
  readonly dataset_parse: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
@@ -116,6 +116,9 @@ export class DataFactory {
116
116
  const ret = wasm.datafactory_namedNode(this.__wbg_ptr, ptr0, len0);
117
117
  return Term.__wrap(ret);
118
118
  }
119
+ /**
120
+ * `new DataFactory()` — a fresh factory with its blank-node counter at zero.
121
+ */
119
122
  constructor() {
120
123
  const ret = wasm.datafactory_new();
121
124
  this.__wbg_ptr = ret;
@@ -252,6 +255,39 @@ export class Dataset {
252
255
  wasm.__wbindgen_add_to_stack_pointer(16);
253
256
  }
254
257
  }
258
+ /**
259
+ * `canonicalize()` → the dataset as canonical, flat N-Quads under RDFC-1.0
260
+ * (SHA-256).
261
+ *
262
+ * The deterministic identity string for the graph: two datasets denote the same
263
+ * RDF graph (under blank-node relabeling) iff their canonical forms are
264
+ * byte-identical. This is the same RDFC-1.0 output the conformance gate pins.
265
+ * @returns {string}
266
+ */
267
+ canonicalize() {
268
+ let deferred2_0;
269
+ let deferred2_1;
270
+ try {
271
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
272
+ wasm.dataset_canonicalize(retptr, this.__wbg_ptr);
273
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
274
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
275
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
276
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
277
+ var ptr1 = r0;
278
+ var len1 = r1;
279
+ if (r3) {
280
+ ptr1 = 0; len1 = 0;
281
+ throw takeObject(r2);
282
+ }
283
+ deferred2_0 = ptr1;
284
+ deferred2_1 = len1;
285
+ return getStringFromWasm0(ptr1, len1);
286
+ } finally {
287
+ wasm.__wbindgen_add_to_stack_pointer(16);
288
+ wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
289
+ }
290
+ }
255
291
  /**
256
292
  * `delete(quad)` → remove a quad. Returns `true` if the effective set changed.
257
293
  * @param {Quad} quad
@@ -294,6 +330,33 @@ export class Dataset {
294
330
  wasm.__wbindgen_add_to_stack_pointer(16);
295
331
  }
296
332
  }
333
+ /**
334
+ * `isomorphic(other)` → whether this dataset and `other` are the same RDF graph
335
+ * under blank-node relabeling.
336
+ *
337
+ * The formal RDF graph-identity check, backed by full RDFC-1.0 canonicalization:
338
+ * an exact oracle with no false positives or false negatives. Equivalent to
339
+ * comparing the two [`canonicalize`](Self::canonicalize) strings, but avoids
340
+ * materializing them for obviously-different inputs.
341
+ * @param {Dataset} other
342
+ * @returns {boolean}
343
+ */
344
+ isomorphic(other) {
345
+ try {
346
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
347
+ _assertClass(other, Dataset);
348
+ wasm.dataset_isomorphic(retptr, this.__wbg_ptr, other.__wbg_ptr);
349
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
350
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
351
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
352
+ if (r2) {
353
+ throw takeObject(r1);
354
+ }
355
+ return r0 !== 0;
356
+ } finally {
357
+ wasm.__wbindgen_add_to_stack_pointer(16);
358
+ }
359
+ }
297
360
  /**
298
361
  * `match(subject?, predicate?, object?, graph?)` → a new dataset of the matching
299
362
  * quads. An omitted (`undefined`) position is a wildcard; `defaultGraph()` matches
@@ -550,6 +613,7 @@ export class Quad {
550
613
  return ret !== 0;
551
614
  }
552
615
  /**
616
+ * The graph [`Term`] of the quad (`DefaultGraph` when unnamed).
553
617
  * @returns {Term}
554
618
  */
555
619
  get graph() {
@@ -557,6 +621,7 @@ export class Quad {
557
621
  return Term.__wrap(ret);
558
622
  }
559
623
  /**
624
+ * The object [`Term`] of the quad.
560
625
  * @returns {Term}
561
626
  */
562
627
  get object() {
@@ -564,6 +629,7 @@ export class Quad {
564
629
  return Term.__wrap(ret);
565
630
  }
566
631
  /**
632
+ * The predicate [`Term`] of the quad.
567
633
  * @returns {Term}
568
634
  */
569
635
  get predicate() {
@@ -571,6 +637,7 @@ export class Quad {
571
637
  return Term.__wrap(ret);
572
638
  }
573
639
  /**
640
+ * The subject [`Term`] of the quad.
574
641
  * @returns {Term}
575
642
  */
576
643
  get subject() {
@@ -655,6 +722,9 @@ export class Sink {
655
722
  wasm.__wbindgen_add_to_stack_pointer(16);
656
723
  }
657
724
  }
725
+ /**
726
+ * `new Sink()` — an empty sink ready to accept quads via `push`.
727
+ */
658
728
  constructor() {
659
729
  const ret = wasm.sink_new();
660
730
  this.__wbg_ptr = ret;
Binary file
@@ -19,8 +19,10 @@ export const datafactory_quotedTriple: (a: number, b: number, c: number, d: numb
19
19
  export const datafactory_typedLiteral: (a: number, b: number, c: number, d: number, e: number) => void;
20
20
  export const datafactory_variable: (a: number, b: number, c: number) => number;
21
21
  export const dataset_add: (a: number, b: number, c: number) => void;
22
+ export const dataset_canonicalize: (a: number, b: number) => void;
22
23
  export const dataset_delete: (a: number, b: number, c: number) => void;
23
24
  export const dataset_has: (a: number, b: number, c: number) => void;
25
+ export const dataset_isomorphic: (a: number, b: number, c: number) => void;
24
26
  export const dataset_match: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
25
27
  export const dataset_new: (a: number) => void;
26
28
  export const dataset_parse: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;