@factoidal/core 0.20.0 → 0.22.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/index.mjs CHANGED
@@ -25,6 +25,7 @@ import { sbm11Api, registerSbm11Source, setDefaultSbm11Reader, mapReader } from
25
25
  import { nodeDefaultReaderFactory } from './lib/sbm11-node.mjs';
26
26
  import { lwsSbm11Reader as lwsSbm11ReaderWith } from './lib/sbm11-lws.mjs';
27
27
  import { nodeSyncTransport } from './lib/sync-http-node.mjs';
28
+ import { nodeCidHost } from './lib/cid-node.mjs';
28
29
 
29
30
  /**
30
31
  * The loaded Lean engine handle: `call(op, args)`, `callBlob`,
@@ -45,6 +46,8 @@ const api = buildApi({
45
46
  engineName: 'lean4-wasm',
46
47
  loadEntry: async () => entryFromEngine(await loadEngine()),
47
48
  storeReaderFor: (root) => (path) => readFileSync(join(root, path)),
49
+ // Name resolution and a GET pinned to one address (issue 1100 item 9).
50
+ cidHost: nodeCidHost,
48
51
  });
49
52
 
50
53
  // RDF and SPARQL
@@ -57,6 +60,7 @@ export const graphs = api.graphs;
57
60
  export const canonicalHash = api.canonicalHash;
58
61
  export const asDefaultGraph = api.asDefaultGraph;
59
62
  export const isomorphic = api.isomorphic;
63
+ export const datasetMerge = api.datasetMerge;
60
64
  export const fromQuads = api.fromQuads;
61
65
  // Literal values (https://github.com/danbri/factoidal/issues/1051): synchronous
62
66
  // after the engine is loaded.
@@ -75,6 +79,7 @@ export const memberIri = api.memberIri;
75
79
  export const memberOrdinal = api.memberOrdinal;
76
80
  export const openDataset = api.openDataset;
77
81
  export const closure = api.closure;
82
+ export const sparqlRlInfer = api.sparqlRlInfer;
78
83
  export const formats = api.formats;
79
84
  export const queryRaw = api.queryRaw;
80
85
  export const queryHdt = api.queryHdt;
@@ -129,6 +134,7 @@ export const explain = api.explain;
129
134
  export const stages = api.stages;
130
135
  export const node = api.node;
131
136
  export const pipe = api.pipe;
137
+ export const graph = api.graph;
132
138
  export const run = api.run;
133
139
  export const rdfsPlusClosure = api.rdfsPlusClosure;
134
140
  export const rhoDfClosure = api.rhoDfClosure;
@@ -147,6 +153,10 @@ export const jsonldFromRdf = api.jsonldFromRdf;
147
153
  export const xmlWellformed = api.xmlWellformed;
148
154
  export const xpathEval = api.xpathEval;
149
155
  export const xsltTransform = api.xsltTransform;
156
+ // XPath 4.0, XQuery 4.0 and XSLT 4.0 (formal/lean4/Wasm/Ops/Qt4.lean).
157
+ export const xpath4Eval = api.xpath4Eval;
158
+ export const xqueryEval = api.xqueryEval;
159
+ export const xslt4Transform = api.xslt4Transform;
150
160
  export const schematronValidate = api.schematronValidate;
151
161
  export const jsonSchemaValidate = api.jsonSchemaValidate;
152
162
  export const mathmlEval = api.mathmlEval;
@@ -170,12 +180,21 @@ export const vcSha256Hex = api.vcSha256Hex;
170
180
  export const vcEd25519SecretToPublic = api.vcEd25519SecretToPublic;
171
181
  export const vcEd25519Sign = api.vcEd25519Sign;
172
182
  export const vcEd25519Verify = api.vcEd25519Verify;
183
+
184
+ // XML Encryption 1.1 and JWE; AES through WebCrypto (lib/cipher-host.mjs)
185
+ export const xmlDecrypt = api.xmlDecrypt;
186
+ export const xmlEncrypt = api.xmlEncrypt;
187
+ export const jweDecrypt = api.jweDecrypt;
188
+ export const jweEncrypt = api.jweEncrypt;
173
189
  export const vcEddsaCreateFromCanonical = api.vcEddsaCreateFromCanonical;
174
190
  export const vcEddsaVerifyFromCanonical = api.vcEddsaVerifyFromCanonical;
175
191
  export const vcCheckCredential = api.vcCheckCredential;
176
192
  export const vcCheckCredentialSubject = api.vcCheckCredentialSubject;
177
193
  export const vcCheckNoDataLoss = api.vcCheckNoDataLoss;
178
194
  export const vcCheckRelatedResourceDigests = api.vcCheckRelatedResourceDigests;
195
+ // Controlled Identifiers v1.0 (https://github.com/danbri/factoidal/issues/1100).
196
+ export const cidValidate = api.cidValidate;
197
+ export const cidRetrieveVerificationMethod = api.cidRetrieveVerificationMethod;
179
198
  export const didKeyResolve = api.didKeyResolve;
180
199
 
181
200
  // Types, probes and metadata
@@ -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 = "43c334ec5335";
40
+ const WASM_VERSION = "b4f1a5b8cc1d";
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-0c9a222bdb25-web",
6
- "releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/40662145237",
7
- "gitSha": "0c9a222bdb25ff70456611d3fbc87a41902d8324",
8
- "builtAt": "2026-10-28T09:52:07Z",
5
+ "release": "danbri/factoidal-builds build-24e7d6edaa7d-web",
6
+ "releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/40826788173",
7
+ "gitSha": "24e7d6edaa7da12a9445922c949f762ddacdf17c",
8
+ "builtAt": "2026-10-29T07:16:14Z",
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": "43c334ec53357cd40339f693d303bda3e422ac92e94ad335cb6b0e83ec4eafbd",
15
- "wasmBytes": 17958911,
14
+ "wasmSha256": "b4f1a5b8cc1d6d5307a836120ba8d09bc7dd7680530bb276366d47a8f95f0d12",
15
+ "wasmBytes": 29931615,
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"
package/lib/api.mjs CHANGED
@@ -30,6 +30,8 @@ import { fullEngineMessage, encodeJsValue } from './lean-entry.mjs';
30
30
  import { memoryDeltaLogStore } from './delta-log-hosts.mjs';
31
31
  import { FactoidalError, errorFromEnvelope, envelopeResult } from './errors.mjs';
32
32
  import { buildNodes } from './nodes.mjs';
33
+ import { runWithCipher } from './cipher-host.mjs';
34
+ import { cidFunctions } from './cid.mjs';
33
35
 
34
36
  // The format names, regimes and query outputs are the engine's tables (op
35
37
  // engineTables, formal/lean4/Wasm/Ops/Handles.lean), read once per engine:
@@ -265,6 +267,10 @@ class Store {
265
267
  * Where the deltaLog* functions keep their records
266
268
  * (lib/delta-log-hosts.mjs): IndexedDB in the browser entries. The
267
269
  * default keeps them in the memory of this process.
270
+ * @param {object} [driver.cidHost]
271
+ * Name resolution and the GET of cidRetrieveVerificationMethod
272
+ * (lib/cid-node.mjs under Node and Deno). The default is the browser
273
+ * host of lib/cid.mjs: the page's fetch, no name resolution.
268
274
  */
269
275
  function buildApi(driver) {
270
276
  let entryPromise; // memoized; a failed load is retried on the next call
@@ -655,6 +661,17 @@ function buildApi(driver) {
655
661
  const entail = opts.entail || 'none';
656
662
  const sparql12 = opts.sparql12 === true || String(opts.version || '') === '1.2';
657
663
  const e = c.e;
664
+ if (sparql12 && entail !== 'none' && opts.output === undefined) {
665
+ // SPARQL 1.2 under an entailment regime: the engine closes the stored
666
+ // dataset and runs the SPARQL 1.2 evaluator (op datasetQuery12Entail,
667
+ // https://github.com/danbri/factoidal/issues/1109).
668
+ requireEntryFn(e, 'datasetQuery12Entail', 'query {sparql12, entail}');
669
+ const r = await withExtensionRounds(() => entryResult(
670
+ e.datasetQuery12Entail(ds.handle, sparql, entail), 'query', sparql));
671
+ if (r.kind === 'ask') return r.boolean;
672
+ if (r.kind === 'construct' || r.kind === 'describe') return c.fromNQuads(r.nquads, ds.prefixes);
673
+ return bindingsFromSrj(r.srj);
674
+ }
658
675
  if (entail !== 'none' || opts.output !== undefined) {
659
676
  // queryRaw over the stored dataset: an entailment regime, or a
660
677
  // results format (SPARQL 1.1 Query Results JSON, XML, CSV, TSV).
@@ -664,15 +681,18 @@ function buildApi(driver) {
664
681
  requireEntryFn(e, 'datasetQueryRaw', `query {entail/output}`);
665
682
  if (opts.output === undefined) {
666
683
  // An entailment regime with the default answer shape.
667
- const r = entryResult(e.datasetQueryRaw(ds.handle, sparql, JSON.stringify({
668
- entail, output: graphForm ? 'ntriples' : 'json' })), 'query', sparql);
684
+ // withExtensionRounds: a registered async extension function
685
+ // (SPARQL 1.1 §17.6) is resolved here as in datasetQuery
686
+ // (https://github.com/danbri/factoidal/issues/1090).
687
+ const r = await withExtensionRounds(() => entryResult(e.datasetQueryRaw(ds.handle, sparql,
688
+ JSON.stringify({ entail, output: graphForm ? 'ntriples' : 'json' })), 'query', sparql));
669
689
  if (graphForm) return c.fromNQuads(r.text, ds.prefixes);
670
690
  const srj = JSON.parse(r.text);
671
691
  if (typeof srj.boolean === 'boolean') return srj.boolean;
672
692
  return bindingsFromSrj(srj);
673
693
  }
674
- const r = entryResult(e.datasetQueryRaw(ds.handle, sparql,
675
- JSON.stringify({ entail, output })), 'query', sparql);
694
+ const r = await withExtensionRounds(() => entryResult(e.datasetQueryRaw(ds.handle, sparql,
695
+ JSON.stringify({ entail, output })), 'query', sparql));
676
696
  return output === 'json' && !graphForm ? JSON.parse(r.text) : r.text;
677
697
  }
678
698
  const fn = sparql12 ? e.datasetQuery12 : e.datasetQuery;
@@ -910,17 +930,28 @@ function buildApi(driver) {
910
930
  * name first, so datasets that differ only in their graph names got the
911
931
  * same hash. To hash a graph whatever its name, use
912
932
  * canonicalHash(asDefaultGraph(g)).
913
- * @param {Dataset} dataset
933
+ *
934
+ * The input is what canonicalize() takes: a Dataset, or text (a string,
935
+ * or {text, format, baseIRI}), with the options {format, baseIRI}. Text is
936
+ * a JavaScript string: characters, not bytes. Decode bytes first (the RDF
937
+ * text syntaxes are UTF-8: `new TextDecoder('utf-8', { fatal: true })`);
938
+ * a Uint8Array is refused.
939
+ * @param {Dataset|string|{text: string, format?: string, baseIRI?: string}} data
940
+ * @param {{format?: string, baseIRI?: string}} [options]
914
941
  * @returns {Promise<string>}
915
942
  */
916
- async function canonicalHash(dataset) {
917
- if (!(dataset instanceof Dataset)) {
918
- throw new TypeError('canonicalHash: expected a Dataset');
943
+ async function canonicalHash(data, options) {
944
+ const hashOf = (e, ds) => {
945
+ requireEntryFn(e, 'datasetCanonicalHash', 'canonicalHash');
946
+ return entryResult(e.datasetCanonicalHash(ds.handle), 'canonicalHash').hash;
947
+ };
948
+ if (data instanceof Dataset && options === undefined) {
949
+ data.size; // throws the disposed error
950
+ return hashOf(data._ctx.e, data);
919
951
  }
920
- dataset.size; // throws the disposed error
921
- const c = dataset._ctx;
922
- requireEntryFn(c.e, 'datasetCanonicalHash', 'canonicalHash');
923
- return entryResult(c.e.datasetCanonicalHash(dataset.handle), 'canonicalHash').hash;
952
+ // The options are canonicalize()'s, checked by the engine's table for it.
953
+ const opts = await checkedOptions('canonicalize', optionsOf('canonicalize', options));
954
+ return withDataset(data, opts, 'canonicalHash', (c, ds) => hashOf(c.e, ds));
924
955
  }
925
956
 
926
957
  /**
@@ -960,6 +991,30 @@ function buildApi(driver) {
960
991
  }));
961
992
  }
962
993
 
994
+ /**
995
+ * The RDF merge of two datasets (RDF 1.1 Semantics section 5.2): a new
996
+ * Dataset that holds the quads of both, with the blank nodes of each input
997
+ * kept apart, so `_:b` in `a` and `_:b` in `b` are two blank nodes. The
998
+ * engine computes it (op datasetMerge, formal/lean4/Wasm/Ops/GraphNodes.lean,
999
+ * theorem mergeDatasets_isMerge); the prefixes are those of `a`, then those
1000
+ * of `b` whose label `a` does not declare. A text argument is read as by
1001
+ * parse() (`options.format`, `options.baseIRI`), in its own blank-node
1002
+ * scope. Since 0.21.0 (https://github.com/danbri/factoidal/issues/1130).
1003
+ * @param {Dataset|string|Array} a
1004
+ * @param {Dataset|string|Array} b
1005
+ * @param {{format?: string, baseIRI?: string}} [options] read text arguments
1006
+ * @returns {Promise<Dataset>}
1007
+ */
1008
+ async function datasetMerge(a, b, options) {
1009
+ // An engine built before the op refuses with op.unknown before the
1010
+ // options are read (its option table has no row for this function).
1011
+ requireEntryFn((await ctx()).e, 'datasetMerge', 'datasetMerge');
1012
+ const opts = await checkedOptions('datasetMerge', optionsOf('datasetMerge', options));
1013
+ return withDataset(a, opts, 'datasetMerge', (c, da) =>
1014
+ withDataset(b, opts, 'datasetMerge', (_c, db) =>
1015
+ c.wrap(entryResult(c.e.datasetMerge(da.handle, db.handle), 'datasetMerge'))));
1016
+ }
1017
+
963
1018
  /**
964
1019
  * A Dataset holding RDF/JS quads (op datasetFromQuadsJson). Each term is
965
1020
  * sent as its components; the engine reads no text.
@@ -1120,6 +1175,28 @@ function buildApi(driver) {
1120
1175
  return withDataset(data, opts, 'closure', (c, ds) => closureOnHandle(c, ds, regime));
1121
1176
  }
1122
1177
 
1178
+ /**
1179
+ * SPARQL 1.2 RL (SPARQL-RL) infer: apply a rule set to the default graph
1180
+ * of `data` and answer the inference graph (the triples the rules derive,
1181
+ * the rule set's DATA blocks included, that are not in `data`) as a new
1182
+ * Dataset. The rule set is SPARQL-RL text (https://w3c.github.io/data-shapes/sparql12-rl/).
1183
+ * A rule set that is not well-formed, not stratifiable, or has IMPORTS is
1184
+ * refused. Engine op: datasetSparqlRlInfer.
1185
+ * @param {Dataset|string|Array} data
1186
+ * @param {string} rules
1187
+ * @param {{format?: string, baseIRI?: string}} [options]
1188
+ * @returns {Promise<Dataset>}
1189
+ */
1190
+ async function sparqlRlInfer(data, rules, options) {
1191
+ const opts = await checkedOptions('sparqlRlInfer', optionsOf('sparqlRlInfer', options));
1192
+ if (typeof rules !== 'string') throw new TypeError('sparqlRlInfer: rules must be a string');
1193
+ return withDataset(data, opts, 'sparqlRlInfer(data)', (c, ds) => {
1194
+ requireEntryFn(c.e, 'datasetSparqlRlInfer', 'sparqlRlInfer');
1195
+ const r = entryResult(c.e.datasetSparqlRlInfer(ds.handle, rules), 'sparqlRlInfer');
1196
+ return c.wrap(r, ds.prefixes);
1197
+ });
1198
+ }
1199
+
1123
1200
  /**
1124
1201
  * RDFS or OWL 2 RL closure of the default graph, as a new Dataset.
1125
1202
  * @param {Dataset|string|Array} data
@@ -1652,6 +1729,136 @@ function buildApi(driver) {
1652
1729
  e.vcEd25519Verify(publicKeyHex, messageHex, signatureHex), 'vcEd25519Verify').valid;
1653
1730
  }
1654
1731
 
1732
+ // -----------------------------------------------------------------
1733
+ // XML Encryption 1.1 and JWE (https://github.com/danbri/factoidal/issues/1084,
1734
+ // https://github.com/danbri/factoidal/issues/1085). Every decision is in
1735
+ // Lean (formal/lean4/Wasm/Ops/XmlEnc.lean, Wasm/Ops/Jwe.lean); RSA, P-256
1736
+ // and X25519 run in the wasm (HACL*). AES runs in this host's WebCrypto:
1737
+ // runWithCipher (lib/cipher-host.mjs) answers each request the engine
1738
+ // makes and calls the op again. This code converts bytes to base64 and
1739
+ // draws random bytes; it reads no document and chooses nothing.
1740
+ // -----------------------------------------------------------------
1741
+
1742
+ // The most random octets any of these ops reads (Jwe.randomBytesNeeded is
1743
+ // at most 112; xmlEncrypt reads at most 76).
1744
+ const CIPHER_RANDOM_BYTES = 112;
1745
+
1746
+ function bytesToBase64(bytes) {
1747
+ let bin = '';
1748
+ for (let i = 0; i < bytes.length; i += 0x8000) {
1749
+ bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
1750
+ }
1751
+ return btoa(bin);
1752
+ }
1753
+
1754
+ function base64ToBytes(text) {
1755
+ const bin = atob(text);
1756
+ const out = new Uint8Array(bin.length);
1757
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
1758
+ return out;
1759
+ }
1760
+
1761
+ function randomBase64(n) {
1762
+ const b = new Uint8Array(n);
1763
+ globalThis.crypto.getRandomValues(b);
1764
+ return bytesToBase64(b);
1765
+ }
1766
+
1767
+ // One op that may ask for AES: the engine's envelope after every request
1768
+ // is answered; a refusal throws a FactoidalError.
1769
+ async function cipherOp(fnName, what, args) {
1770
+ const e = await entry();
1771
+ requireEntryFn(e, fnName, what);
1772
+ const res = await runWithCipher((a) => JSON.parse(e[fnName](JSON.stringify(a))), args);
1773
+ return entryResult(JSON.stringify(res), fnName);
1774
+ }
1775
+
1776
+ function checkKeys(fnName, keys) {
1777
+ if (!Array.isArray(keys) || !keys.every((k) => k !== null && typeof k === 'object')) {
1778
+ throw new TypeError(`${fnName}: keys must be an array of JWK objects`);
1779
+ }
1780
+ }
1781
+
1782
+ /**
1783
+ * Decrypt every xenc:EncryptedData of an XML document (XML Encryption 1.1).
1784
+ * Allowlist and key forms: API.md "XML Encryption and JWE".
1785
+ * @param {string} document the XML document
1786
+ * @param {{keys: object[], allowCbc?: boolean}} options private JWKs
1787
+ * (`kid` is the ds:KeyName); `allowCbc` admits aes128-cbc and aes256-cbc
1788
+ * @returns {Promise<{document: string, canonical: string}>}
1789
+ */
1790
+ async function xmlDecrypt(document, options) {
1791
+ if (typeof document !== 'string') throw new TypeError('xmlDecrypt: document must be a string');
1792
+ const o = options || {};
1793
+ checkKeys('xmlDecrypt', o.keys);
1794
+ const r = await cipherOp('xmlDecrypt', 'XML decryption',
1795
+ { document, keys: o.keys, allowCbc: o.allowCbc === true });
1796
+ return { document: r.document, canonical: r.canonical };
1797
+ }
1798
+
1799
+ /**
1800
+ * Encrypt the document element (or, with `content`, its content) of an XML
1801
+ * document for one recipient (XML Encryption 1.1, AES-GCM content).
1802
+ * @param {string} document the XML document
1803
+ * @param {{recipient: object, algorithm?: 'aes128-gcm'|'aes256-gcm',
1804
+ * keyTransport?: 'rsa-oaep-mgf1p'|'rsa-oaep-sha256', keyWrap?: 'kw-aes128'|'kw-aes256',
1805
+ * content?: boolean, saml?: boolean}} options `recipient` is a public JWK
1806
+ * (RSA, EC P-256, OKP X25519) or an `oct` key; `saml` wraps the result in
1807
+ * saml:EncryptedAssertion
1808
+ * @returns {Promise<{document: string, canonical: string}>}
1809
+ */
1810
+ async function xmlEncrypt(document, options) {
1811
+ if (typeof document !== 'string') throw new TypeError('xmlEncrypt: document must be a string');
1812
+ const o = options || {};
1813
+ if (o.recipient === null || typeof o.recipient !== 'object') {
1814
+ throw new TypeError('xmlEncrypt: options.recipient must be a JWK object');
1815
+ }
1816
+ const args = { document, recipient: o.recipient, random: randomBase64(CIPHER_RANDOM_BYTES) };
1817
+ for (const k of ['algorithm', 'keyTransport', 'keyWrap']) if (o[k] !== undefined) args[k] = o[k];
1818
+ if (o.content === true) args.content = true;
1819
+ if (o.saml === true) args.saml = true;
1820
+ const r = await cipherOp('xmlEncrypt', 'XML encryption', args);
1821
+ return { document: r.document, canonical: r.canonical };
1822
+ }
1823
+
1824
+ /**
1825
+ * Decrypt a JWE in the Compact Serialization (RFC 7516).
1826
+ * @param {string} token the compact JWE
1827
+ * @param {{jwk: object}} options the recipient's private JWK
1828
+ * @returns {Promise<{header: object, plaintext: Uint8Array}>}
1829
+ */
1830
+ async function jweDecrypt(token, options) {
1831
+ if (typeof token !== 'string') throw new TypeError('jweDecrypt: token must be a string');
1832
+ const o = options || {};
1833
+ if (o.jwk === null || typeof o.jwk !== 'object') throw new TypeError('jweDecrypt: options.jwk must be a JWK object');
1834
+ const r = await cipherOp('jweDecrypt', 'JWE decryption', { token, jwk: o.jwk });
1835
+ return { header: r.header, plaintext: base64ToBytes(r.plaintext) };
1836
+ }
1837
+
1838
+ /**
1839
+ * Encrypt to a JWE in the Compact Serialization (RFC 7516).
1840
+ * @param {Uint8Array|string} plaintext bytes, or text written as UTF-8
1841
+ * @param {{alg: string, enc: string, jwk: object, header?: object}} options
1842
+ * the recipient's public JWK (or the `oct` key for A128KW, A256KW, dir);
1843
+ * `header`: more protected header members
1844
+ * @returns {Promise<string>} the compact JWE
1845
+ */
1846
+ async function jweEncrypt(plaintext, options) {
1847
+ let bytes;
1848
+ if (typeof plaintext === 'string') bytes = new TextEncoder().encode(plaintext);
1849
+ else if (plaintext instanceof Uint8Array) bytes = plaintext;
1850
+ else throw new TypeError('jweEncrypt: plaintext must be a Uint8Array or a string');
1851
+ const o = options || {};
1852
+ if (typeof o.alg !== 'string' || typeof o.enc !== 'string') {
1853
+ throw new TypeError('jweEncrypt: options.alg and options.enc must be strings');
1854
+ }
1855
+ if (o.jwk === null || typeof o.jwk !== 'object') throw new TypeError('jweEncrypt: options.jwk must be a JWK object');
1856
+ const args = { alg: o.alg, enc: o.enc, jwk: o.jwk, plaintext: bytesToBase64(bytes),
1857
+ random: randomBase64(CIPHER_RANDOM_BYTES) };
1858
+ if (o.header !== undefined) args.header = o.header;
1859
+ return (await cipherOp('jweEncrypt', 'JWE encryption', args)).token;
1860
+ }
1861
+
1655
1862
  /**
1656
1863
  * Create an eddsa-rdfc-2022 Data Integrity proofValue over an
1657
1864
  * already-canonicalized document + proof config (entry_jsoo.ml's
@@ -1826,6 +2033,67 @@ function buildApi(driver) {
1826
2033
  return r.output;
1827
2034
  }
1828
2035
 
2036
+ // XPath 4.0, XQuery 4.0 and XSLT 4.0 (formal/lean4/Wasm/Ops/Qt4.lean). The
2037
+ // engine evaluates and serializes; these functions check argument types and
2038
+ // unwrap the envelope. A QT4 error rejects with a FactoidalError (code
2039
+ // `xml.failed`) whose `qtError` is {code, namespace, message}: the error
2040
+ // code the specification names (XPST0003, FODC0002, XTSE0165, ...).
2041
+ // The engine does no I/O: fn:doc, fn:unparsed-text, import module,
2042
+ // xsl:import and xsl:include of anything but the arguments fail with the
2043
+ // specification's error code.
2044
+ function qt4Result(r) {
2045
+ const { ok: _ok, ...rest } = r;
2046
+ return rest;
2047
+ }
2048
+
2049
+ /**
2050
+ * Evaluate an XPath 4.0 expression with an XML document as context item.
2051
+ * @param {string} xmlText the context document, or '' for no context item
2052
+ * @param {string} expr an XPath 4.0 expression
2053
+ * @returns {Promise<{count:number, items:Array<{type:string,value:string}>, adaptive:string, xml:string|null}>}
2054
+ */
2055
+ async function xpath4Eval(xmlText, expr) {
2056
+ if (typeof xmlText !== 'string' || typeof expr !== 'string') {
2057
+ throw new TypeError('xpath4Eval: xmlText and expr must be strings');
2058
+ }
2059
+ const e = await entry();
2060
+ requireEntryFn(e, 'xpath4Eval', 'XPath 4.0 evaluation');
2061
+ return qt4Result(entryResult(e.xpath4Eval(xmlText, expr), 'xpath4Eval'));
2062
+ }
2063
+
2064
+ /**
2065
+ * Evaluate an XQuery 4.0 main module, optionally with an XML document as
2066
+ * context item.
2067
+ * @param {string} query an XQuery 4.0 main module
2068
+ * @param {string} [contextXml] the context document ('' or absent: none)
2069
+ * @returns {Promise<{count:number, items:Array<{type:string,value:string}>, adaptive:string, xml:string|null}>}
2070
+ */
2071
+ async function xqueryEval(query, contextXml) {
2072
+ if (typeof query !== 'string' || (contextXml !== undefined && typeof contextXml !== 'string')) {
2073
+ throw new TypeError('xqueryEval: query must be a string, contextXml a string when given');
2074
+ }
2075
+ const e = await entry();
2076
+ requireEntryFn(e, 'xqueryEval', 'XQuery 4.0 evaluation');
2077
+ return qt4Result(entryResult(e.xqueryEval(query, contextXml ?? ''), 'xqueryEval'));
2078
+ }
2079
+
2080
+ /**
2081
+ * XSLT 4.0 transform. The principal result is serialized with the
2082
+ * stylesheet's unnamed output definition; `secondary` holds the results of
2083
+ * xsl:result-document, `messages` the xsl:message texts.
2084
+ * @param {string} stylesheetXml an XSLT stylesheet (one module)
2085
+ * @param {string} sourceXml the source document, or '' for no global context item
2086
+ * @returns {Promise<{output:string, method:string, secondary:Array<{href:string,output:string}>, messages:string[]}>}
2087
+ */
2088
+ async function xslt4Transform(stylesheetXml, sourceXml) {
2089
+ if (typeof stylesheetXml !== 'string' || typeof sourceXml !== 'string') {
2090
+ throw new TypeError('xslt4Transform: stylesheetXml and sourceXml must be strings');
2091
+ }
2092
+ const e = await entry();
2093
+ requireEntryFn(e, 'xslt4Transform', 'XSLT 4.0 transform');
2094
+ return qt4Result(entryResult(e.xslt4Transform(stylesheetXml, sourceXml), 'xslt4Transform'));
2095
+ }
2096
+
1829
2097
  /**
1830
2098
  * Evaluate a Content MathML document (entry_jsoo.ml's mathmlEval
1831
2099
  * export -> MathML.Content.eval_doc_env). `bindings` maps
@@ -2282,11 +2550,14 @@ function buildApi(driver) {
2282
2550
  // ops it actually has.
2283
2551
  shacl: has('shaclValidate'),
2284
2552
  shex: has('shexValidate'),
2553
+ sparqlRl: has('datasetSparqlRlInfer'),
2285
2554
  owlClosure: has('owlClosure'),
2286
2555
  explain: has('explainGraph'),
2287
2556
  stages: has('stageList'),
2288
2557
  pipelineCheck: has('pipelineCheck'),
2289
2558
  pipelineCheckNodes: has('pipelineCheckNodes'),
2559
+ graphCheck: has('graphCheckNodes'),
2560
+ datasetMerge: has('datasetMerge'),
2290
2561
  entailment: has('queryRaw'),
2291
2562
  tableau: has('tableauMaterialise') && has('tableauDlInconsistent'),
2292
2563
  rml: has('rmlMap'),
@@ -2298,6 +2569,9 @@ function buildApi(driver) {
2298
2569
  xpath: has('xpathEval'),
2299
2570
  rif: has('rifEval'),
2300
2571
  xslt: has('xsltTransform'),
2572
+ xpath4: has('xpath4Eval'),
2573
+ xquery: has('xqueryEval'),
2574
+ xslt4: has('xslt4Transform'),
2301
2575
  mathml: has('mathmlEval'),
2302
2576
  xforms: has('xformsRecalc'),
2303
2577
  jsonSchema: has('jsonSchemaValidate'),
@@ -2318,6 +2592,7 @@ function buildApi(driver) {
2318
2592
  store: has('storeOpen') && has('storeHandleQuery'),
2319
2593
  deltaLog: has('deltaLogEncode') && has('deltaLogDecode') && has('deltaLogMerge'),
2320
2594
  literalValues: has('literalValue') && has('literalFromValue'),
2595
+ cid: has('cidValidate') && has('cidRetrieve'),
2321
2596
  };
2322
2597
  }
2323
2598
 
@@ -2421,8 +2696,12 @@ function buildApi(driver) {
2421
2696
  const nodeApi = buildNodes({
2422
2697
  entry, entryResult, requireEntryFn, Dataset,
2423
2698
  parse, closure, shaclValidate, query, serialize, canonicalize, canonicalHash, explain, csvwToRdf,
2699
+ datasetMerge,
2424
2700
  });
2425
2701
 
2702
+ // Controlled Identifiers v1.0 (lib/cid.mjs, issue 1100 items 7 and 9).
2703
+ const cid = cidFunctions({ entry, entryResult, requireEntryFn, host: driver.cidHost });
2704
+
2426
2705
  return {
2427
2706
  parse,
2428
2707
  query,
@@ -2443,6 +2722,7 @@ function buildApi(driver) {
2443
2722
  asDefaultGraph,
2444
2723
  shaclValidate,
2445
2724
  shexValidate,
2725
+ sparqlRlInfer,
2446
2726
  owlClosure,
2447
2727
  coreRdfsClosure,
2448
2728
  coreRdfsCheck,
@@ -2450,6 +2730,7 @@ function buildApi(driver) {
2450
2730
  stages: nodeApi.stages,
2451
2731
  node: nodeApi.node,
2452
2732
  pipe: nodeApi.pipe,
2733
+ graph: nodeApi.graph,
2453
2734
  run: nodeApi.run,
2454
2735
  rdfsPlusClosure,
2455
2736
  rhoDfClosure,
@@ -2471,6 +2752,9 @@ function buildApi(driver) {
2471
2752
  clNormalize,
2472
2753
  rifEval,
2473
2754
  xsltTransform,
2755
+ xpath4Eval,
2756
+ xqueryEval,
2757
+ xslt4Transform,
2474
2758
  mathmlEval,
2475
2759
  xformsRecalc,
2476
2760
  jsonSchemaValidate,
@@ -2484,12 +2768,18 @@ function buildApi(driver) {
2484
2768
  vcEd25519SecretToPublic,
2485
2769
  vcEd25519Sign,
2486
2770
  vcEd25519Verify,
2771
+ xmlDecrypt,
2772
+ xmlEncrypt,
2773
+ jweDecrypt,
2774
+ jweEncrypt,
2487
2775
  vcEddsaCreateFromCanonical,
2488
2776
  vcEddsaVerifyFromCanonical,
2489
2777
  vcCheckCredential,
2490
2778
  vcCheckCredentialSubject,
2491
2779
  vcCheckNoDataLoss,
2492
2780
  vcCheckRelatedResourceDigests,
2781
+ cidValidate: cid.cidValidate,
2782
+ cidRetrieveVerificationMethod: cid.cidRetrieveVerificationMethod,
2493
2783
  capabilities,
2494
2784
  openStore,
2495
2785
  queryStore,
@@ -2503,6 +2793,7 @@ function buildApi(driver) {
2503
2793
  _deltaLogCorruptLastForTest,
2504
2794
  queryRaw,
2505
2795
  isomorphic,
2796
+ datasetMerge,
2506
2797
  fromQuads,
2507
2798
  literalValue,
2508
2799
  isValidLiteral,
@@ -13,6 +13,18 @@
13
13
  //
14
14
  // No Node built-ins: the browser imports this file as it is.
15
15
 
16
+ import { checkEngineStamp } from './engine-integrity.mjs';
17
+
18
+ // version.json next to the engine, or null (absent, unreadable, not JSON).
19
+ async function stampOf(url) {
20
+ try {
21
+ const r = await fetch(url);
22
+ return r.ok ? await r.json() : null;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
16
28
  async function instantiate(url, imports) {
17
29
  try {
18
30
  return await WebAssembly.instantiateStreaming(fetch(url), imports);
@@ -59,7 +71,22 @@ export function browserEngine(name, defaultAssetsUrl) {
59
71
  // newer loader (the loader's own locateFile does the same).
60
72
  const version = loader.wasmVersion ? `?v=${loader.wasmVersion}` : '';
61
73
  const wasmUrl = new URL(`${name}.wasm`, assetsUrl).href + version;
62
- return loader.loadL4({ instantiateWasm: (imports) => instantiate(wasmUrl, imports) });
74
+ // The release stamp check (engine-integrity.mjs, issue 1106): with a
75
+ // stamped version.json next to the full engine, its bytes are hashed
76
+ // before they are compiled (no streaming compile then). Without one,
77
+ // the streaming path as before.
78
+ const stamp = name === 'l4factoidal'
79
+ ? await stampOf(new URL('version.json', assetsUrl).href + version) : null;
80
+ if (!stamp || stamp.stamped !== true) {
81
+ return loader.loadL4({ instantiateWasm: (imports) => instantiate(wasmUrl, imports) });
82
+ }
83
+ const response = await fetch(wasmUrl);
84
+ if (!response.ok) {
85
+ throw new Error(`factoidal: fetching ${wasmUrl} failed: ${response.status} ${response.statusText}`);
86
+ }
87
+ const bytes = new Uint8Array(await response.arrayBuffer());
88
+ await checkEngineStamp(bytes, stamp, { where: wasmUrl });
89
+ return loader.loadL4({ instantiateWasm: (imports) => WebAssembly.instantiate(bytes, imports) });
63
90
  })();
64
91
  enginePromise.catch(() => { enginePromise = null; });
65
92
  return enginePromise;