@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/CHANGELOG.md +82 -0
- package/README.md +38 -2
- package/authz/server.mjs +23 -2
- package/bin/engine.mjs +22 -1
- package/bin/factoidal.mjs +710 -38
- package/bin/pack-host.mjs +5 -0
- package/bin/serve.mjs +19 -3
- package/browser.d.ts +14 -0
- package/browser.js +16 -0
- package/index.d.ts +325 -9
- package/index.mjs +19 -0
- package/l4-assets/l4factoidal.js +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +6 -6
- package/lib/api.mjs +303 -12
- package/lib/browser-engine.mjs +28 -1
- package/lib/cid-node.mjs +77 -0
- package/lib/cid.mjs +146 -0
- package/lib/cipher-host.mjs +126 -0
- package/lib/engine-integrity.mjs +56 -0
- package/lib/errors.mjs +6 -0
- package/lib/nodes.mjs +234 -11
- package/lib/sbm11.mjs +14 -1
- package/lib/version.mjs +1 -1
- package/lws/README.md +9 -0
- package/lws/client.mjs +19 -2
- package/lws/persist.mjs +169 -0
- package/lws/server.mjs +82 -29
- package/package.json +2 -1
- package/solid/server/index.mjs +79 -13
- package/webdav/server.mjs +154 -25
- package/xmpp/client.mjs +332 -0
- package/xmpp/proxy.mjs +114 -0
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
|
package/l4-assets/l4factoidal.js
CHANGED
|
Binary file
|
package/l4-assets/version.json
CHANGED
|
@@ -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-
|
|
6
|
-
"releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/
|
|
7
|
-
"gitSha": "
|
|
8
|
-
"builtAt": "2026-10-
|
|
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": "
|
|
15
|
-
"wasmBytes":
|
|
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
|
-
|
|
668
|
-
|
|
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
|
-
*
|
|
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(
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
|
|
921
|
-
const
|
|
922
|
-
|
|
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,
|
package/lib/browser-engine.mjs
CHANGED
|
@@ -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
|
-
|
|
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;
|