@factoidal/core 0.19.0 → 0.20.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/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.20.0 (2026-10-27)
6
+
7
+ - **Breaking:** in a checked pipeline (`node`, `pipe`, `run`), each node now puts a value of its output wire on the wire: text after a writer, the rows after SELECT, a boolean after ASK, the report after `shaclValidator`, `[canonical, sha256]` after `rdfcStage`, the result after `rdfsExplainGraphStage`. Before, such a node left its input Dataset on the wire, so a well-typed reader → writer → reader pipeline failed at run time. `run()` rejects a runner value that is not of the node's `wireOut`. `rdfcStage` as a node takes a Dataset only (https://github.com/danbri/factoidal-core/pull/155).
8
+ - `node()` keeps its own copy of its settings (frozen at every depth; a Dataset is cloned). A change to the caller's objects after `node()` no longer changes what runs while the evidence keeps the old `settingsDigest`. `run()` rejects a node whose settings changed through `node.params` (https://github.com/danbri/factoidal-core/pull/155).
9
+ - **Breaking:** `canonicalHash(dataset)` keeps graph names: it is the SHA-256 of the text `canonicalize()` answers. 0.17.0 to 0.19.0 dropped graph names, so datasets that differ only in graph names got one hash. New `asDefaultGraph(dataset)`; `canonicalHash(asDefaultGraph(g))` gives a graph's hash whatever its name (https://github.com/danbri/factoidal-core/pull/155).
10
+ - SBM11 stores opened with `openSbm11` (and the wasm ops `sbm11Open` / `sbm11Query`) are checked against their Merkle roots by default: the open admits each file's `.merkle` sidecar against `manifest.sbm11`, and every byte a query decodes comes from a 65,536-byte chunk checked by its Merkle proof. A changed data file, sidecar or Merkle reference in the manifest fails the open or the query with an error that names the file (before, a changed `dict.gdv1` was answered as data). `openSbm11(source, { verify: 'none' })` keeps the earlier length-only check; `store.verify` says which mode is in use. Measured cost: within run-to-run noise on stores of 1 to 6 MB (https://github.com/danbri/factoidal-core/pull/157).
11
+ - Parse is faster again after the IRI check of 0.19.0 (https://github.com/danbri/factoidal/issues/1057): the check tests bytes, and a repeated IRI is not checked again. Parse of 100,000 triples is now about 3% slower than 0.18.0 (it was 10% to 19%). The IRIs that are accepted and refused do not change (https://github.com/danbri/factoidal-core/pull/158).
12
+ - CID 1.0 (Controlled Identifiers, W3C Recommendation 2025-05-15): new Lean module `L4Factoidal/CID/` with the section 2 data model (one named error per MUST), Multikey and JsonWebKey material, the five verification relationships, Fragment Resolution, Retrieve Verification Method and the section 3.5 processing errors. The CID context is vendored. Authorization server, CID suite (lws10-authn-ssi-cid): the subject document is checked as a whole and the method is found by Retrieve Verification Method with `authentication`; `expires`/`revoked` need a time zone; `Accept` starts with `application/cid`. VC Data Integrity (Lean): `verificationMethod` can be a method in a controlled identifier document (not only an Ed25519 did:key), and it must be in the relationship that `proofPurpose` names. No npm functions yet (https://github.com/danbri/factoidal/issues/1100, https://github.com/danbri/factoidal-core/pull/161).
13
+ - `parse` takes the option `rdfstar` for JSON-LD, as the 0.18.0 changelog says: `parse(text, { format: 'jsonld', rdfstar: true })` reads `@triple`, `@reifies` and `@annotation` as RDF 1.2 triple terms and reifiers, as `jsonldToRdf` does. 0.18.0 and 0.19.0 refused the option with `option.unknown`. `rdfstar` with another format, or with `lenient`, is refused with `option.conflict` (https://github.com/danbri/factoidal/issues/1091, https://github.com/danbri/factoidal-core/pull/152).
14
+ - Docs: hub page "JSON-LD-star and JSON-LD 1.3" (`docs/web/hub/jsonld-star.md`), with live cells on 0.19.0; the hub pages load 0.19.0 (were 0.16.0).
15
+ - Value matching (https://github.com/danbri/factoidal/issues/1052): new `sameValue(a, b)` is SPARQL 1.1 `=` on two terms (sections 17.3, 17.4.1.7), decided by the engine (Lean `SPARQL/SameValue.lean`). It answers `true`, `false`, or `null` for incomparable (the type error of `=`). `Dataset.has`, `match` and `delete` take `{match: 'value'}`: a literal object then matches each object literal of the same value (`"1"^^xsd:int` finds `"01"^^xsd:integer` and `"1.0"^^xsd:decimal`), through an index of the literals by value. Term matching stays the default. New wasm op `sameValue`; `datasetMatch`, `datasetMatchCount` and `datasetDeleteQuadsJson` take an optional options argument.
16
+ - Syntax names (https://github.com/danbri/factoidal/issues/1053): new `syntaxFor(fileName)` and `syntaxFor({fileName, mediaType})` give the syntax of a document from the W3C registrations (Lean `RDF/MediaType.lean`), or `null`. Each row of `formats()` has `mediaTypes` and `extensions` from the same table (README format table: two new columns). New wasm op `syntaxFor`.
17
+ - IRIs, vocabularies, lists and containers (https://github.com/danbri/factoidal/issues/1051 items 4 to 6): new `splitIri(iri)` (namespace and the longest XML NCName suffix), `isNCName(text)`, `vocabularies()` (rdf, rdfs, xsd and owl with their terms; foaf, dcterms, dc and schema namespaces), `memberIri(n)` and `memberOrdinal(iri)` (`rdf:_n`). Dataset methods `listItems(head, graph?)` (refuses a list that is not well formed, with the reason), `containerItems(node, graph?)` and `addList(items, graph?)`. Lean `RDF/IriSplit.lean`, `RDF/VocabularyTable.lean`, `RDF/Collections.lean`. New wasm ops `splitIri`, `isNCName`, `vocabularies`, `memberIri`, `memberOrdinal`, `datasetListItems`, `datasetContainerItems`, `datasetAddList`. These need a wasm built from this source.
18
+ - Literal values (https://github.com/danbri/factoidal/issues/1051 items 1 to 3). New functions `literalValue(term)`, `isValidLiteral(term)`, `canonicalLiteral(term)` and `literalFromValue(value, {datatype?})`, synchronous after `ready()` (API.md "Literal values"). `literalValue` gives the XSD 1.1 lexical-to-value mapping of a literal for every XSD built-in the engine has (numeric types and the derived integer types with their bounds, boolean, the string types, date/time and `g*` types, durations, binary, lists) and for `rdf:langString`, `rdf:dirLangString`, `rdf:XMLLiteral`, `rdf:JSON`: `{datatype, recognized, valid, value?, canonical?}`, with the value as JSON-safe data (a number that a JavaScript number does not hold exactly is a decimal string). `valid: false` is an ill-typed literal; `recognized: false` is a datatype the engine does not have. `literalFromValue` maps a boolean, number, bigint, string or Date to a typed literal with one engine table (safe integer `xsd:integer`, other number `xsd:double`, bigint `xsd:integer`, Date `xsd:dateTime` in UTC), or to a named datatype (`{datatype: 'int'}`) when the value is in its value space, else a `FactoidalError` with the new code `literal.value`. New wasm ops `literalValue` and `literalFromValue` (Lean `L4Factoidal/RDF/LiteralValue.lean`, `Wasm/Ops/LiteralValue.lean`). An extension function may now answer a `Date` (an `xsd:dateTime`).
19
+ - The canonical form of an `xsd:float` or `xsd:double` value is now the XSD 1.1 Part 2 §3.3.4.2/§3.3.5.2 form (algorithm §E.1): the fewest significant digits that map back to the value. `"0.1"^^xsd:double` has the canonical form `1.0E-1`; before, it was the exact binary expansion `1.000000000000000055511151231257827021181583404541015625E-1`. It is the `canonical` member of `literalValue` and RML's canonical form of a double (RML-Core natural mapping). SPARQL casts do not change (`xsd:double("0.1")` is still `"0.1"^^xsd:double`). Theorems `double_canonical_roundTrip`, `float_canonical_roundTrip`, `integer_canonical_roundTrip` (`XSD/CanonicalTheorems.lean`).
20
+
5
21
  ## 0.19.0 (2026-10-27)
6
22
 
7
23
  - Notice (owner decision, 2026-10-27): "EXPERIMENTAL UNREVIEWED CODE - USE WITH CARE - DO NOT CREATE PUBLIC SERVICES USING THIS WITHOUT CAREFUL INDEPENDENT EXPERT REVIEW." It is at the top of both READMEs and the hub, in the package description, in every `factoidal` help text, and on standard error when `serve`, `lws-serve`, `solid-serve`, `solid-client`, `lws-client`, `authz-serve`, `authz-keys`, `xmpp-serve` or `webdav-serve` starts.
package/README.md CHANGED
@@ -38,10 +38,14 @@ every consumer accepts one, so a pipeline does not re-parse between steps.
38
38
  An unknown option key throws. With no `format`, the format is inferred and
39
39
  reported in `dataset.format`; an explicit `format` is never overridden.
40
40
  RDF 1.2 input uses `turtle12`, `ntriples12`, `nquads12` or `trig12`.
41
+ RDF 1.2 in JSON-LD (JSON-LD-star: `@triple`, `@reifies`, `@annotation`,
42
+ `@direction`) needs the option `rdfstar`: `jsonldToRdf(text, { rdfstar: true })`
43
+ and `dataset.serialize({ format: 'jsonld', rdfstar: true })`.
41
44
 
42
45
  ## What it does
43
46
 
44
47
  - Syntaxes: N-Triples, N-Quads, Turtle, TriG, RDF/XML, JSON-LD; RDF 1.1 and 1.2
48
+ (RDF 1.2 in JSON-LD through the JSON-LD-star draft, option `rdfstar`)
45
49
  - SPARQL 1.1 and 1.2 query and update; GeoSPARQL functions, text search,
46
50
  JavaScript extension functions, SERVICE
47
51
  - Reasoning: RDFS, OWL 2 RL closure, restricted checked OWL consistency,
@@ -118,19 +122,19 @@ and marks those steps in the evidence. Details: API.md, "Stages as nodes" and
118
122
  Formats (generated from `formats()`):
119
123
 
120
124
  <!-- formats-table:start (generated by test/formats-table.test.mjs) -->
121
- | format | read RDF 1.1 | read RDF 1.2 | write RDF 1.1 | write RDF 1.2 | engine |
122
- |---|---|---|---|---|---|
123
- | nquads | yes | yes | yes | yes | core |
124
- | ntriples | yes | yes | yes | yes | core |
125
- | turtle | yes | yes | yes | yes | core |
126
- | trig | yes | yes | yes | yes | core |
127
- | rdfxml | yes | no | yes | no | core |
128
- | jsonld | yes | no | yes | no | full |
129
- | rdfc | no | no | yes | no | core |
130
- | sparql-results-json | no | no | yes | yes | core |
131
- | sparql-results-xml | no | no | yes | yes | core |
132
- | sparql-results-csv | no | no | yes | yes | core |
133
- | sparql-results-tsv | no | no | yes | yes | core |
125
+ | format | read RDF 1.1 | read RDF 1.2 | write RDF 1.1 | write RDF 1.2 | engine | media type | extension |
126
+ |---|---|---|---|---|---|---|---|
127
+ | nquads | yes | yes | yes | yes | core | `application/n-quads` | `.nq` |
128
+ | ntriples | yes | yes | yes | yes | core | `application/n-triples` | `.nt` |
129
+ | turtle | yes | yes | yes | yes | core | `text/turtle` | `.ttl` |
130
+ | trig | yes | yes | yes | yes | core | `application/trig` | `.trig` |
131
+ | rdfxml | yes | no | yes | no | core | `application/rdf+xml` | `.rdf` |
132
+ | jsonld | yes | no | yes | no | full | `application/ld+json` | `.jsonld` |
133
+ | rdfc | no | no | yes | no | core | | |
134
+ | sparql-results-json | no | no | yes | yes | core | | |
135
+ | sparql-results-xml | no | no | yes | yes | core | | |
136
+ | sparql-results-csv | no | no | yes | yes | core | | |
137
+ | sparql-results-tsv | no | no | yes | yes | core | | |
134
138
  <!-- formats-table:end -->
135
139
 
136
140
  Missing writers (HDT, N3, RDFa): https://github.com/danbri/factoidal/issues/883.
@@ -160,6 +164,7 @@ Codes (generated from `engineTables().errorCodes`):
160
164
  | `term.invalid-iri` | <https://www.w3.org/TR/rdf12-concepts/#section-IRIs> | A term that must be an IRI is not one. |
161
165
  | `term.invalid-langtag` | <https://www.w3.org/TR/rdf12-concepts/#section-Graph-Literal> | A language tag is not well-formed (BCP 47). |
162
166
  | `term.invalid` | <https://www.w3.org/TR/sparql11-results-json/#select-encode-terms> | A term or quad given as JSON is not an RDF term or quad. |
167
+ | `literal.value` | <https://www.w3.org/TR/xmlschema11-2/#value-space> | The value is not in the value space of the named datatype, or the datatype is not one the engine has. |
163
168
  | `query.syntax` | <https://www.w3.org/TR/sparql11-query/#grammar> | The SPARQL query does not match the SPARQL grammar. |
164
169
  | `query.failed` | <https://www.w3.org/TR/sparql11-query/#sparqlDefinition> | The SPARQL query parsed but its evaluation was refused. |
165
170
  | `update.syntax` | <https://www.w3.org/TR/sparql11-update/#grammar> | The SPARQL update does not match the SPARQL Update grammar. |
@@ -239,6 +244,8 @@ and the [assurance record](https://github.com/danbri/factoidal-core/blob/main/do
239
244
  | JSON-LD 1.1 toRdf, expand, flatten | 910 / 0 / 910 |
240
245
  | JSON-LD 1.1 compact, fromRdf | 298 / 0 / 300 (2 not run) |
241
246
  | JSON-LD 1.1 Framing | 29 / 63 / 92 |
247
+ | JSON-LD-star draft (RDF 1.2), option `rdfstar` | 168 / 1 / 171 (2 local override; 170 of 171 with them) [J] |
248
+ | RDF 1.2 eval tests written as JSON-LD-star and read back | 140 / 0 / 140 [J] |
242
249
  | RDFC-1.0 | 86 / 0 / 86 |
243
250
  | OWL 2 RL | 126 / 0 / 126 |
244
251
  | OWL 2 DL type-inconsistency | 116 / 11 / 128 (1 other) |
@@ -262,6 +269,7 @@ and the [assurance record](https://github.com/danbri/factoidal-core/blob/main/do
262
269
  [L] 2026-10-22, https://github.com/danbri/factoidal-core/blob/2b9d2e39af53be31ed92463b58d3e132e65c4fe4/docs/designissues/2026-10-21-lws-touchstone-run.md (with server options, 220 of 221 pass in at least one configuration)
263
270
  [S] 2026-10-21, the current W3C manifest (29 active tests), https://github.com/danbri/factoidal-core/blob/2b9d2e39af53be31ed92463b58d3e132e65c4fe4/docs/claude-rules/current-state.md
264
271
  [C] 2026-10-21, validation test308, https://github.com/danbri/factoidal/issues/700
272
+ [J] 2026-10-27, tests of https://github.com/json-ld/json-ld-star/pull/61; the fail is a defective fixture (fromRdf tst07), https://github.com/danbri/factoidal-core/blob/2a656d7bfb4af329d77669afb5e62eb00625bd15/docs/designissues/2026-10-26-jsonld-star.md
265
273
 
266
274
  ## Limits
267
275
 
package/browser.d.ts CHANGED
@@ -12,6 +12,7 @@ export type {
12
12
  QueryOptions, SerializeOptions, StoreReader, OpenStoreOptions,
13
13
  DeltaLogHandle, DeltaLogInspection,
14
14
  Sbm11Reader, Sbm11Answer, Sbm11Store, Sbm11ReadEvidence, Sbm11Transport,
15
+ LiteralValueData, LiteralValueResult,
15
16
  ExplainResult, StageInfo, EvidenceRecord, Wire, FactoidalNode, Pipeline, RunResult, RunOptions,
16
17
  } from './index';
17
18
 
@@ -23,10 +24,22 @@ export {
23
24
  canonicalize,
24
25
  graphs,
25
26
  canonicalHash,
27
+ asDefaultGraph,
26
28
  isomorphic,
27
29
  fromQuads,
30
+ literalValue,
31
+ isValidLiteral,
32
+ canonicalLiteral,
33
+ literalFromValue,
28
34
  ready,
29
35
  engineTables,
36
+ sameValue,
37
+ syntaxFor,
38
+ splitIri,
39
+ isNCName,
40
+ vocabularies,
41
+ memberIri,
42
+ memberOrdinal,
30
43
  openDataset,
31
44
  closure,
32
45
  formats,
package/browser.js CHANGED
@@ -83,10 +83,24 @@ export const serialize = api.serialize;
83
83
  export const canonicalize = api.canonicalize;
84
84
  export const graphs = api.graphs;
85
85
  export const canonicalHash = api.canonicalHash;
86
+ export const asDefaultGraph = api.asDefaultGraph;
86
87
  export const isomorphic = api.isomorphic;
87
88
  export const fromQuads = api.fromQuads;
89
+ // Literal values (https://github.com/danbri/factoidal/issues/1051): synchronous
90
+ // after the engine is loaded.
91
+ export const literalValue = api.literalValue;
92
+ export const isValidLiteral = api.isValidLiteral;
93
+ export const canonicalLiteral = api.canonicalLiteral;
94
+ export const literalFromValue = api.literalFromValue;
88
95
  export const ready = api.ready;
89
96
  export const engineTables = api.engineTables;
97
+ export const sameValue = api.sameValue;
98
+ export const syntaxFor = api.syntaxFor;
99
+ export const splitIri = api.splitIri;
100
+ export const isNCName = api.isNCName;
101
+ export const vocabularies = api.vocabularies;
102
+ export const memberIri = api.memberIri;
103
+ export const memberOrdinal = api.memberOrdinal;
90
104
  export const openDataset = api.openDataset;
91
105
  export const closure = api.closure;
92
106
  export const formats = api.formats;
package/index.d.ts CHANGED
@@ -99,6 +99,7 @@ export type FactoidalErrorCode =
99
99
  | 'term.invalid-iri'
100
100
  | 'term.invalid-langtag'
101
101
  | 'term.invalid'
102
+ | 'literal.value'
102
103
  | 'query.syntax'
103
104
  | 'query.failed'
104
105
  | 'update.syntax'
@@ -236,6 +237,10 @@ export interface FormatCapability {
236
237
  write12: boolean;
237
238
  /** 'core' when both engines serve it; 'full' when only the full engine does. */
238
239
  engine: 'core' | 'full';
240
+ /** The registered media types (W3C registration; empty for a row with none). syntaxFor() reads the same table. */
241
+ mediaTypes: string[];
242
+ /** The registered file extensions, with the leading dot ('.ttl'). */
243
+ extensions: string[];
239
244
  }
240
245
 
241
246
  /**
@@ -283,11 +288,31 @@ export class Dataset implements Iterable<Quad> {
283
288
  toString(formatOrOptions?: WriteFormat | WriteOptions): string;
284
289
  toNQuads(): string;
285
290
  clone(): Dataset;
286
- /** RDF/JS DatasetCore (https://rdf.js.org/dataset-spec/); match and has use the engine indexes. */
287
- has(quad: Quad): boolean;
291
+ /**
292
+ * RDF/JS DatasetCore (https://rdf.js.org/dataset-spec/); match and has use
293
+ * the engine indexes. `{match: 'value'}`: a literal object matches every
294
+ * object literal that is the same term or has the same value (sameValue());
295
+ * the other positions match by term.
296
+ */
297
+ has(quad: Quad, options?: MatchOptions): boolean;
288
298
  add(quad: Quad): this;
289
- delete(quad: Quad): this;
290
- match(subject?: Term | null, predicate?: Term | null, object?: Term | null, graph?: Term | null): Dataset;
299
+ /** With `{match: 'value'}`: remove every quad of the same subject, predicate
300
+ * and graph whose object has the same value as the quad's literal object. */
301
+ delete(quad: Quad, options?: MatchOptions): this;
302
+ match(subject?: Term | null, predicate?: Term | null, object?: Term | null, graph?: Term | null,
303
+ options?: MatchOptions): Dataset;
304
+ /**
305
+ * The items of the RDF list (rdf:first/rdf:rest) at `head`, in order
306
+ * (https://github.com/danbri/factoidal/issues/1051 item 6). `rdf:nil` is
307
+ * the empty list. A list that is not well formed (a node with no or with two
308
+ * rdf:first or rdf:rest, or a cycle) is a FactoidalError, code `op.failed`.
309
+ * `graph`: omitted, null or the DefaultGraph for the default graph.
310
+ */
311
+ listItems(head: Term, graph?: Term | null): Term[];
312
+ /** The members rdf:_1, rdf:_2, ... of `node`, sorted by n, and its kind (rdf:Bag, rdf:Seq, rdf:Alt) from its rdf:type. */
313
+ containerItems(node: Term, graph?: Term | null): { kind: NamedNode | null; items: Array<{ ordinal: number; term: Term }> };
314
+ /** Add a new RDF list of `items` on fresh blank nodes; answers its head (rdf:nil for no items). */
315
+ addList(items: Iterable<Term>, graph?: Term | null): Term;
291
316
  toArray(): Quad[];
292
317
  [Symbol.iterator](): Iterator<Quad>;
293
318
  dispose(): void;
@@ -380,6 +405,12 @@ export interface ParseOptions {
380
405
  * with one diagnostic of class 'too-many-errors'. Default: no limit.
381
406
  */
382
407
  maxErrors?: number;
408
+ /**
409
+ * Read JSON-LD as JSON-LD-star (`@triple`, `@reifies`, `@annotation` as
410
+ * RDF 1.2 triple terms and reifiers), as `jsonldToRdf` does. Format
411
+ * 'jsonld' only (given or inferred); not with `lenient`. Default: false.
412
+ */
413
+ rdfstar?: boolean;
383
414
  }
384
415
 
385
416
  export interface QueryOptions extends DatasetQueryOptions {
@@ -535,17 +566,24 @@ export function canonicalize(
535
566
  export function graphs(dataset: Dataset): Array<[name: NamedNode | BlankNode, graph: Dataset]>;
536
567
 
537
568
  /**
538
- * The RDFC-1.0 canonical hash of a single graph: the SHA-256 of its
539
- * canonical N-Quads document, as 64 lowercase hexadecimal digits (RDF
540
- * Dataset Canonicalization 1.0, section 4.4 note 2). The engine computes
541
- * the canonical form and the digest. Every quad's graph component is
542
- * dropped first, so isomorphic graphs (including under blank-node
543
- * relabeling) give the same hash whatever graph name they were extracted
544
- * from. Typically called with one entry of graphs()'s output. For the
545
- * canonical text, use canonicalize(). Rejects when the input holds an
546
- * RDF 1.2 triple term or the section 4.4 call budget is exceeded.
569
+ * The RDFC-1.0 canonical hash of a dataset: the SHA-256, as 64 lowercase
570
+ * hexadecimal digits, of the canonical N-Quads document that canonicalize()
571
+ * answers (RDF Dataset Canonicalization 1.0, section 4.4 note 2), graph names
572
+ * included. The engine computes the canonical form and the digest. Since
573
+ * 0.20.0; 0.17.0 to 0.19.x dropped every graph name first, so datasets that
574
+ * differ only in their graph names got the same hash. To hash a graph
575
+ * whatever its name (for example one entry of graphs()), use
576
+ * canonicalHash(asDefaultGraph(g)). Rejects when the input holds an RDF 1.2
577
+ * triple term or the section 4.4 call budget is exceeded.
578
+ */
579
+ export function canonicalHash(dataset: Dataset): Promise<string>;
580
+
581
+ /**
582
+ * A new Dataset with the triples of every graph of `dataset`, all in the
583
+ * default graph (the engine's union of the graphs). With one entry of
584
+ * graphs(), it is that graph without its name. Dispose of it when done.
547
585
  */
548
- export function canonicalHash(datasetOrGraph: Dataset): Promise<string>;
586
+ export function asDefaultGraph(dataset: Dataset): Dataset;
549
587
 
550
588
  /**
551
589
  * Whether two datasets are isomorphic (RDF 1.1 Concepts 3.6 and 4; blank
@@ -561,9 +599,137 @@ export function fromQuads(quads: Iterable<Quad>): Promise<Dataset>;
561
599
  /** Load the engine. Afterwards the N-Quads text functions of `@factoidal/core/rdfjs` work. */
562
600
  export function ready(): Promise<void>;
563
601
 
602
+ // ---------------------------------------------------------------------
603
+ // Literal values (https://github.com/danbri/factoidal/issues/1051 items 1-3).
604
+ // Synchronous after the engine is loaded (`await ready()` or any other call);
605
+ // before that they throw a FactoidalError. The engine decides everything
606
+ // (formal/lean4/L4Factoidal/RDF/LiteralValue.lean). API.md "Literal values".
607
+ // ---------------------------------------------------------------------
608
+
609
+ /**
610
+ * The value of a literal, as JSON-safe data. A number that a JavaScript
611
+ * number may not hold exactly is a decimal string; `number` is present only
612
+ * when the value is exactly a binary64 value. Integer components are numbers
613
+ * when |n| <= 2^53 - 1, else decimal strings.
614
+ */
615
+ export type LiteralValueData =
616
+ | { kind: 'string' | 'anyURI'; string: string }
617
+ | { kind: 'boolean'; boolean: boolean }
618
+ | { kind: 'decimal'; decimal: string; integer: boolean; number?: number }
619
+ | { kind: 'float' | 'double'; decimal?: string; number: number;
620
+ negativeZero?: true; special?: 'NaN' | 'INF' | '-INF' }
621
+ | { kind: 'duration'; months: number | string; seconds: string }
622
+ | { kind: 'dateTime' | 'time' | 'date' | 'gYearMonth' | 'gYear' | 'gMonthDay' | 'gDay' | 'gMonth';
623
+ year?: number | string; month?: number; day?: number; hour?: number;
624
+ minute?: number; second?: string; timezoneOffset?: number }
625
+ | { kind: 'hexBinary' | 'base64Binary'; hex: string }
626
+ | { kind: 'QName' | 'NOTATION'; prefix: string; localName: string }
627
+ | { kind: 'list'; items: LiteralValueData[] }
628
+ | { kind: 'langString'; string: string; language: string; direction?: 'ltr' | 'rtl' };
629
+
630
+ export interface LiteralValueResult {
631
+ /** The datatype IRI. */
632
+ datatype: string;
633
+ /** false: the engine has no lexical space for this datatype. */
634
+ recognized: boolean;
635
+ /** The lexical form is in the lexical space (null when not recognized). */
636
+ valid: boolean | null;
637
+ /** Present for a valid literal of a datatype with a value model. */
638
+ value?: LiteralValueData;
639
+ /** The canonical lexical form (XSD 1.1 Part 2 section 2.3.1), when known. */
640
+ canonical?: string;
641
+ }
642
+
643
+ /** The XSD lexical-to-value mapping of a literal (RDF 1.1 Semantics section 7: no whiteSpace processing). */
644
+ export function literalValue(term: Literal): LiteralValueResult;
645
+
646
+ /** Whether the lexical form is in the datatype's lexical space; null when the engine has no lexical space for the datatype. */
647
+ export function isValidLiteral(term: Literal): boolean | null;
648
+
649
+ /** The literal with the canonical form of its value; null when it is not valid or has no canonical mapping. */
650
+ export function canonicalLiteral(term: Literal): Literal | null;
651
+
652
+ /**
653
+ * The literal of a JavaScript value. With no datatype the engine's table
654
+ * chooses: boolean xsd:boolean, string xsd:string, bigint xsd:integer, a safe
655
+ * integer Number xsd:integer, any other Number xsd:double, Date xsd:dateTime
656
+ * (UTC). `datatype` (an IRI, a NamedNode, or an XSD local name such as 'int')
657
+ * names the datatype; the value must be in its value space, else a
658
+ * FactoidalError with code 'literal.value'.
659
+ */
660
+ export function literalFromValue(
661
+ value: boolean | number | bigint | string | Date,
662
+ options?: { datatype?: string | NamedNode }
663
+ ): Literal;
664
+
564
665
  /** The engine's name tables: format names, entailment regimes, query outputs, pack syntaxes. */
565
666
  export function engineTables(): Promise<Record<string, unknown>>;
566
667
 
668
+ /**
669
+ * How Dataset.has, match and delete compare the object position
670
+ * (https://github.com/danbri/factoidal/issues/1052). 'term' (the default):
671
+ * RDF term equality. 'value': a literal object matches by value (sameValue()).
672
+ */
673
+ export interface MatchOptions {
674
+ match?: 'term' | 'value';
675
+ }
676
+
677
+ /**
678
+ * SPARQL 1.1 `=` on two RDF terms (sections 17.3 and 17.4.1.7), decided by the
679
+ * engine: `true` the same value (`"01"^^xsd:integer` and `"1.0"^^xsd:decimal`),
680
+ * `false` different values, `null` incomparable (the operator's type error:
681
+ * two different literals of an unknown datatype, an ill-typed literal and
682
+ * another literal, `xsd:integer` and `xsd:boolean`). `"NaN"^^xsd:double` is
683
+ * not the same value as itself. A term is an RDF/JS term or an N-Triples term
684
+ * string. Synchronous: the engine must be loaded (`await ready()`).
685
+ */
686
+ export function sameValue(a: Term | string, b: Term | string): boolean | null;
687
+
688
+ /**
689
+ * The syntax of a document by its file name (or IRI path; the query and the
690
+ * fragment are ignored) and its media type, from the engine's table of the
691
+ * W3C registrations (https://github.com/danbri/factoidal/issues/1053): Turtle
692
+ * `text/turtle` `.ttl`, N-Triples `application/n-triples` `.nt`, N-Quads
693
+ * `application/n-quads` `.nq`, TriG `application/trig` `.trig`, RDF/XML
694
+ * `application/rdf+xml` `.rdf`, JSON-LD `application/ld+json` `.jsonld`. The
695
+ * media type wins when it is given (its parameters are ignored); an unknown
696
+ * media type does not fall back to the name. Answers a `name` of formats(),
697
+ * or `null`. Synchronous: the engine must be loaded (`await ready()`).
698
+ */
699
+ export function syntaxFor(what: string | { fileName?: string; mediaType?: string }): string | null;
700
+
701
+ /**
702
+ * An IRI as a namespace and a local name: the local name is the longest
703
+ * suffix that is an XML NCName (what an RDF/XML QName can carry), `''` when
704
+ * there is none; `namespace + localName` is the IRI
705
+ * (https://github.com/danbri/factoidal/issues/1051 item 4). Synchronous.
706
+ */
707
+ export function splitIri(iri: string | NamedNode): { namespace: string; localName: string };
708
+
709
+ /** Is the text an XML NCName (Namespaces in XML 1.0, production [4])? A prefix label check. Synchronous. */
710
+ export function isNCName(text: string): boolean;
711
+
712
+ /** One row of the engine's vocabulary table. A term IRI is `namespace + term`. */
713
+ export interface VocabularyRow {
714
+ prefix: string;
715
+ namespace: string;
716
+ /** Local names; empty for a namespace whose vocabulary is not in the engine table. */
717
+ terms: string[];
718
+ }
719
+
720
+ /**
721
+ * The engine's vocabulary table (https://github.com/danbri/factoidal/issues/1051
722
+ * item 5): rdf, rdfs, xsd and owl with their terms; foaf, dcterms, dc and
723
+ * schema as namespaces only. The prefixes are those of the Turtle writer. Synchronous.
724
+ */
725
+ export function vocabularies(): VocabularyRow[];
726
+
727
+ /** The container membership property `rdf:_n` (n >= 1). Synchronous. */
728
+ export function memberIri(n: number): NamedNode;
729
+
730
+ /** The `n` of `rdf:_n` (no leading zero, n >= 1), or `null` for any other IRI. Synchronous. */
731
+ export function memberOrdinal(iri: string | NamedNode): number | null;
732
+
567
733
  /** A SHACL validation-report term (see shaclValidate). */
568
734
  export interface ShaclValidateResult {
569
735
  conforms: boolean;
@@ -758,7 +924,14 @@ export interface EvidenceRecord {
758
924
  outside?: OutsideSetting[];
759
925
  }
760
926
 
761
- /** The value on a wire. */
927
+ /**
928
+ * The value on a wire. After a node, `graph` is a value of the node's
929
+ * `wireOut`: a Dataset for the graph wires, text for String (a writer), the
930
+ * rows for Table (SELECT), a boolean for Answer (ASK), `[canonical, sha256]`
931
+ * for String × String (rdfcStage), the report object for ValidationReport,
932
+ * the explain result for ExplainGraphResult. Since 0.20.0; before, a node
933
+ * whose output was not a Dataset left its input on the wire.
934
+ */
762
935
  export interface Wire<G = unknown> {
763
936
  graph: G;
764
937
  evidence: EvidenceRecord[];
@@ -783,7 +956,12 @@ export interface FactoidalNode {
783
956
  readonly wireOut: string;
784
957
  readonly runnable: boolean;
785
958
  readonly bound: boolean;
786
- /** The settings as given (passed to the package function that runs the stage). */
959
+ /**
960
+ * The node's own copy of the settings as given (passed to the package
961
+ * function that runs the stage): frozen at every depth, a Dataset cloned,
962
+ * taken when node() checked them. A later change to the caller's objects
963
+ * does not reach the node; a change to a Dataset here makes run() refuse.
964
+ */
787
965
  readonly params: Readonly<Record<string, unknown>>;
788
966
  /** The settings as sent to the engine: JSON values, a Dataset as {$nquads}. */
789
967
  readonly settingsWire: Readonly<Record<string, unknown>>;
@@ -876,8 +1054,11 @@ export function pipe(...nodes: Array<FactoidalNode | Pipeline>): Pipeline;
876
1054
  * Run a node or pipeline on `input` (a Wire, or the graph: text for a
877
1055
  * reader, else a Dataset or data input). Rejects before any node runs when
878
1056
  * a node's stage has status 'missing' (unless `options.unchecked`) or has
879
- * no package function, or when the engine's check finds the pipeline not
880
- * well typed (unless `options.unchecked`; then `proved` is false).
1057
+ * no package function, when the engine's check finds the pipeline not
1058
+ * well typed (unless `options.unchecked`; then `proved` is false), or when a
1059
+ * node's settings changed after node() checked them. Rejects with code
1060
+ * 'op.failed' when a node's runner gives a value that is not of the node's
1061
+ * `wireOut` (see Wire).
881
1062
  */
882
1063
  export function run(pipeline: FactoidalNode | Pipeline, input: unknown, options?: RunOptions): Promise<RunResult>;
883
1064
 
@@ -1191,6 +1372,8 @@ export interface Sbm11Store {
1191
1372
  /** the generation CURRENT names, or "" when the source is a generation */
1192
1373
  generation: string;
1193
1374
  terms: number;
1375
+ /** how the engine checks reads; undefined from a wasm module built before the Merkle check */
1376
+ verify: 'merkle' | 'none' | undefined;
1194
1377
  query(sparql: string, options?: { base?: string; sparql12?: boolean; reference?: boolean }): Sbm11Answer;
1195
1378
  /** the resources the reader read, when its reader records them (lwsSbm11Reader) */
1196
1379
  readonly evidence: Sbm11ReadEvidence[] | undefined;
@@ -1198,8 +1381,11 @@ export interface Sbm11Store {
1198
1381
  }
1199
1382
 
1200
1383
  /** Open an SBM11 generation (https://github.com/danbri/factoidal/issues/1009).
1201
- * Node: `source` is a directory unless a reader is given or registered. */
1202
- export function openSbm11(source: string, options?: { reader?: Sbm11Reader }): Promise<Sbm11Store>;
1384
+ * Node: `source` is a directory unless a reader is given or registered.
1385
+ * `verify` (default 'merkle'): the engine checks every byte it decodes against
1386
+ * the manifest's Merkle roots and refuses a changed file with its name;
1387
+ * 'none' checks lengths only. */
1388
+ export function openSbm11(source: string, options?: { reader?: Sbm11Reader; verify?: 'merkle' | 'none' }): Promise<Sbm11Store>;
1203
1389
 
1204
1390
  /** Serve `source` with `reader`; `null` removes it. */
1205
1391
  export function registerSbm11Source(source: string, reader: Sbm11Reader | null): void;
@@ -1679,6 +1865,8 @@ export function capabilities(): Promise<{
1679
1865
  store: boolean;
1680
1866
  /** The deltaLog* functions (deltaLogEncode, deltaLogDecode, deltaLogMerge). */
1681
1867
  deltaLog: boolean;
1868
+ /** ops literalValue and literalFromValue (issue 1051). */
1869
+ literalValues: boolean;
1682
1870
  /** 'full': the whole library is in the wasm module. */
1683
1871
  profile: 'full' | string;
1684
1872
  /** The dispatch ABI version string of the loaded wasm module. */
@@ -1762,10 +1950,18 @@ declare const _default: {
1762
1950
  canonicalize: typeof canonicalize;
1763
1951
  graphs: typeof graphs;
1764
1952
  canonicalHash: typeof canonicalHash;
1953
+ asDefaultGraph: typeof asDefaultGraph;
1765
1954
  isomorphic: typeof isomorphic;
1766
1955
  fromQuads: typeof fromQuads;
1767
1956
  ready: typeof ready;
1768
1957
  engineTables: typeof engineTables;
1958
+ sameValue: typeof sameValue;
1959
+ syntaxFor: typeof syntaxFor;
1960
+ splitIri: typeof splitIri;
1961
+ isNCName: typeof isNCName;
1962
+ vocabularies: typeof vocabularies;
1963
+ memberIri: typeof memberIri;
1964
+ memberOrdinal: typeof memberOrdinal;
1769
1965
  shaclValidate: typeof shaclValidate;
1770
1966
  shexValidate: typeof shexValidate;
1771
1967
  owlClosure: typeof owlClosure;
@@ -1821,6 +2017,10 @@ declare const _default: {
1821
2017
  FactoidalError: typeof FactoidalError;
1822
2018
  dataFactory: DataFactory;
1823
2019
  queryRaw: typeof queryRaw;
2020
+ literalValue: typeof literalValue;
2021
+ isValidLiteral: typeof isValidLiteral;
2022
+ canonicalLiteral: typeof canonicalLiteral;
2023
+ literalFromValue: typeof literalFromValue;
1824
2024
  version: string;
1825
2025
  engine: string;
1826
2026
  };
package/index.mjs CHANGED
@@ -55,10 +55,24 @@ export const serialize = api.serialize;
55
55
  export const canonicalize = api.canonicalize;
56
56
  export const graphs = api.graphs;
57
57
  export const canonicalHash = api.canonicalHash;
58
+ export const asDefaultGraph = api.asDefaultGraph;
58
59
  export const isomorphic = api.isomorphic;
59
60
  export const fromQuads = api.fromQuads;
61
+ // Literal values (https://github.com/danbri/factoidal/issues/1051): synchronous
62
+ // after the engine is loaded.
63
+ export const literalValue = api.literalValue;
64
+ export const isValidLiteral = api.isValidLiteral;
65
+ export const canonicalLiteral = api.canonicalLiteral;
66
+ export const literalFromValue = api.literalFromValue;
60
67
  export const ready = api.ready;
61
68
  export const engineTables = api.engineTables;
69
+ export const sameValue = api.sameValue;
70
+ export const syntaxFor = api.syntaxFor;
71
+ export const splitIri = api.splitIri;
72
+ export const isNCName = api.isNCName;
73
+ export const vocabularies = api.vocabularies;
74
+ export const memberIri = api.memberIri;
75
+ export const memberOrdinal = api.memberOrdinal;
62
76
  export const openDataset = api.openDataset;
63
77
  export const closure = api.closure;
64
78
  export const formats = api.formats;
@@ -37,7 +37,7 @@
37
37
  // bytes change.
38
38
 
39
39
  // Stamped by formal/lean4/Wasm/build-wasm.sh step 9 -- do not hand-edit.
40
- const WASM_VERSION = "6dbf38660cb9";
40
+ const WASM_VERSION = "43c334ec5335";
41
41
 
42
42
  import createModule from './l4factoidal.mjs';
43
43
 
Binary file
@@ -2,17 +2,17 @@
2
2
  "engine": "lean4",
3
3
  "note": "The Lean 4 (L4Factoidal) engine build provenance and claims: the only engine of @factoidal/core since 0.9.0 (issue 713). This committed file is a placeholder. tools/fetch-builds.sh --set web (through tools/stamp-l4-version.mjs) writes the provenance members from the danbri/factoidal-builds release that built the wasm; formal/lean4/Wasm/build-wasm.sh step 9 writes them for a local build. Do not commit a stamped copy.",
4
4
  "stamped": true,
5
- "release": "danbri/factoidal-builds build-b8c6b64b8dc1-web",
6
- "releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/40525043015",
7
- "gitSha": "b8c6b64b8dc18a6daa2151a61e28dbb9a4707ade",
8
- "builtAt": "2026-10-27T15:28:08Z",
5
+ "release": "danbri/factoidal-builds build-0c9a222bdb25-web",
6
+ "releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/40662145237",
7
+ "gitSha": "0c9a222bdb25ff70456611d3fbc87a41902d8324",
8
+ "builtAt": "2026-10-28T09:52:07Z",
9
9
  "leanToolchain": "leanprover/lean4:v4.33.1",
10
10
  "emscripten": "6.0.8",
11
11
  "abiVersion": "1",
12
12
  "engineVersion": "l4factoidal-wasm 0.2.0 (dispatch: RDF parse/serialize, SPARQL query+update, RDFS/OWL-RL closures, RDFC-1.0, CL/IKL)",
13
13
  "version": "l4factoidal-wasm 0.2.0 (dispatch: RDF parse/serialize, SPARQL query+update, RDFS/OWL-RL closures, RDFC-1.0, CL/IKL)",
14
- "wasmSha256": "6dbf38660cb9966ae7c65ecdb68a38e83d09c1283415c1486b618f203f3f2f39",
15
- "wasmBytes": 17694204,
14
+ "wasmSha256": "43c334ec53357cd40339f693d303bda3e422ac92e94ad335cb6b0e83ec4eafbd",
15
+ "wasmBytes": 17958911,
16
16
  "claims": {
17
17
  "source": "formal/lean4 (L4Factoidal): no sorry, no user axioms, no native_decide; W3C behaviour pinned by build-time #guard",
18
18
  "suitesAtBuildSha": "see docs/test-results and formal/lean4/PORT_NOTES.md at gitSha"