@factoidal/core 0.12.0 → 0.14.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 +198 -0
- package/README.md +60 -2
- package/authz/server.mjs +331 -0
- package/bin/factoidal.mjs +378 -12
- package/browser.d.ts +7 -0
- package/browser.js +8 -0
- package/index.d.ts +221 -8
- package/index.mjs +8 -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 +57 -5
- package/lib/dataset.mjs +8 -0
- package/lib/evidence.mjs +84 -0
- package/lib/nodes.mjs +215 -0
- package/lib/sbm11-node.mjs +61 -0
- package/lib/sbm11.mjs +148 -0
- package/lib/version.mjs +1 -1
- package/lws/client.mjs +319 -0
- package/lws/persist.mjs +17 -1
- package/lws/server.mjs +25 -3
- package/package.json +9 -1
- package/sample-store.mjs +1 -1
- package/xmpp/README.md +82 -4
- package/xmpp/server.mjs +549 -0
package/lib/nodes.mjs
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// The node-composition API (https://github.com/danbri/factoidal/issues/1002,
|
|
2
|
+
// step 2): registered Lean stages as nodes, each carrying its contract name
|
|
3
|
+
// and its proof status, with evidence flowing on the wires.
|
|
4
|
+
//
|
|
5
|
+
// The stage list is the engine's (op stageList, formal/lean4/Wasm/Ops/
|
|
6
|
+
// StageList.lean), read from the Lean registry
|
|
7
|
+
// L4Factoidal.Dataflow.registry: name, interface type, wire types, status,
|
|
8
|
+
// contract, named assumptions, and whether the stage may run at all
|
|
9
|
+
// (`runnable`, false for status `missing`). This file holds no copy of it.
|
|
10
|
+
//
|
|
11
|
+
// What this file adds is the binding of a stage NAME to the package
|
|
12
|
+
// function that runs it (RUNNERS below; each is the function that
|
|
13
|
+
// foundation/entrypoints.toml maps to that stage), and the wiring: a value
|
|
14
|
+
// on a wire is `{ graph, evidence }`, and each node appends one evidence
|
|
15
|
+
// record `{ stage, status, contract, assumptions, checked, artefact }` (plus
|
|
16
|
+
// `unchecked` and `reason` for a step run under run()'s `unchecked` opt-in;
|
|
17
|
+
// the record logic is in evidence.mjs). The result of
|
|
18
|
+
// a run also reports the union of the named assumptions of the nodes it
|
|
19
|
+
// passed: the premise list of the pipeline's soundness statement
|
|
20
|
+
// (Dataflow/EndToEnd.lean) as the user sees it.
|
|
21
|
+
//
|
|
22
|
+
// ES module, no Node built-ins: the browser loads this file as it is.
|
|
23
|
+
|
|
24
|
+
import { FactoidalError } from './errors.mjs';
|
|
25
|
+
import { refusalFor, makeRecord, summarize, unionAssumptions } from './evidence.mjs';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {object} deps the package functions and the engine entry
|
|
29
|
+
* @returns {{stages: Function, node: Function, pipe: Function, run: Function}}
|
|
30
|
+
*/
|
|
31
|
+
export function buildNodes(deps) {
|
|
32
|
+
const { entry, entryResult, requireEntryFn, Dataset,
|
|
33
|
+
parse, closure, shaclValidate, query, serialize, canonicalize, explain } = deps;
|
|
34
|
+
|
|
35
|
+
const reader = (format) => async (wire, params) => {
|
|
36
|
+
if (typeof wire.graph !== 'string') {
|
|
37
|
+
throw new TypeError(`node: a reader takes text on its wire (got ${typeof wire.graph})`);
|
|
38
|
+
}
|
|
39
|
+
const ds = await parse(wire.graph, { format, ...params });
|
|
40
|
+
return { graph: ds, artefact: { format } };
|
|
41
|
+
};
|
|
42
|
+
const reasoner = (regime) => async (wire, params) => ({
|
|
43
|
+
graph: await closure(wire.graph, regime, params),
|
|
44
|
+
artefact: { regime },
|
|
45
|
+
});
|
|
46
|
+
const writer = (format) => async (wire, params) => ({
|
|
47
|
+
artefact: { format, text: await serialize(wire.graph, { ...params, format }) },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Stage name (stageList `name`) -> the package function that runs it.
|
|
51
|
+
// A runner answers `{ graph?, artefact }`; no `graph` passes the input
|
|
52
|
+
// graph on unchanged.
|
|
53
|
+
const RUNNERS = {
|
|
54
|
+
ntriplesDocumentReader: reader('ntriples'),
|
|
55
|
+
nquadsDocumentReader: reader('nquads'),
|
|
56
|
+
turtleDocumentReader: reader('turtle'),
|
|
57
|
+
trigDocumentReader: reader('trig'),
|
|
58
|
+
rdfxmlDocumentReader: reader('rdfxml'),
|
|
59
|
+
jsonldDocumentReader: reader('jsonld'),
|
|
60
|
+
rdfsReasoner: reasoner('rdfs'),
|
|
61
|
+
owlRlReasoner: reasoner('owl-rl'),
|
|
62
|
+
rhoDfReasoner: reasoner('rho-df'),
|
|
63
|
+
shaclValidator: async (wire, params) => {
|
|
64
|
+
const { shapes, ...opts } = params;
|
|
65
|
+
if (shapes === undefined) throw new TypeError('node shaclValidator: params.shapes is required');
|
|
66
|
+
const r = await shaclValidate(wire.graph, shapes, opts);
|
|
67
|
+
return { artefact: { conforms: r.conforms, report: r.report } };
|
|
68
|
+
},
|
|
69
|
+
shippedEvaluator: async (wire, params) => {
|
|
70
|
+
const { query: sparql, ...opts } = params;
|
|
71
|
+
if (typeof sparql !== 'string') throw new TypeError('node shippedEvaluator: params.query must be a string');
|
|
72
|
+
const answer = await query(wire.graph, sparql, opts);
|
|
73
|
+
return answer instanceof Dataset ? { graph: answer, artefact: { form: 'graph' } }
|
|
74
|
+
: { artefact: { answer } };
|
|
75
|
+
},
|
|
76
|
+
ntriplesDocumentWriter: writer('ntriples'),
|
|
77
|
+
nquadsDocumentWriter: writer('nquads'),
|
|
78
|
+
turtleDocumentWriter: writer('turtle'),
|
|
79
|
+
trigDocumentWriter: writer('trig'),
|
|
80
|
+
rdfxmlDocumentWriter: writer('rdfxml'),
|
|
81
|
+
rdfcStage: async (wire, params) => ({
|
|
82
|
+
artefact: { canonical: await canonicalize(wire.graph, params) },
|
|
83
|
+
}),
|
|
84
|
+
rdfsExplainGraphStage: async (wire, params) => {
|
|
85
|
+
const { description, ...opts } = params;
|
|
86
|
+
if (description === undefined) {
|
|
87
|
+
throw new TypeError('node rdfsExplainGraphStage: params.description is required');
|
|
88
|
+
}
|
|
89
|
+
return { artefact: await explain(wire.graph, description, { ...opts, profile: 'rdfs' }) };
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
let stageCache = null;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The engine's stage list (op stageList), one element per registry entry.
|
|
97
|
+
* @returns {Promise<object[]>}
|
|
98
|
+
*/
|
|
99
|
+
async function stages() {
|
|
100
|
+
if (!stageCache) {
|
|
101
|
+
stageCache = (async () => {
|
|
102
|
+
const e = await entry();
|
|
103
|
+
requireEntryFn(e, 'stageList', 'stages');
|
|
104
|
+
const r = entryResult(e.stageList(), 'stages');
|
|
105
|
+
return Object.freeze(r.stages.map((s) => Object.freeze({
|
|
106
|
+
...s,
|
|
107
|
+
theorems: Object.freeze([...s.theorems]),
|
|
108
|
+
assumptions: Object.freeze([...s.assumptions]),
|
|
109
|
+
tests: Object.freeze([...s.tests]),
|
|
110
|
+
bound: Object.prototype.hasOwnProperty.call(RUNNERS, s.name),
|
|
111
|
+
})));
|
|
112
|
+
})();
|
|
113
|
+
stageCache.catch(() => { stageCache = null; });
|
|
114
|
+
}
|
|
115
|
+
return stageCache;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const NODE = Symbol('factoidal.node');
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A node for the registered stage `name`, with its parameters (the
|
|
122
|
+
* second argument of the package function it calls, e.g. `{shapes}`,
|
|
123
|
+
* `{query}`, `{description}`, reader options).
|
|
124
|
+
* @param {string} name a stageList `name`
|
|
125
|
+
* @param {object} [params]
|
|
126
|
+
* @returns {Promise<object>}
|
|
127
|
+
*/
|
|
128
|
+
async function node(name, params) {
|
|
129
|
+
if (typeof name !== 'string') throw new TypeError('node: name must be a string');
|
|
130
|
+
if (params !== undefined && (params === null || typeof params !== 'object')) {
|
|
131
|
+
throw new TypeError('node: params must be an object');
|
|
132
|
+
}
|
|
133
|
+
const info = (await stages()).find((s) => s.name === name);
|
|
134
|
+
if (!info) {
|
|
135
|
+
throw new FactoidalError(`node: no registered stage named '${name}' (see stages())`,
|
|
136
|
+
{ code: 'option.invalid-value' });
|
|
137
|
+
}
|
|
138
|
+
return Object.freeze({
|
|
139
|
+
[NODE]: true,
|
|
140
|
+
stage: info.name,
|
|
141
|
+
kind: info.kind,
|
|
142
|
+
status: info.status,
|
|
143
|
+
contract: info.contract,
|
|
144
|
+
assumptions: info.assumptions,
|
|
145
|
+
wireIn: info.wireIn,
|
|
146
|
+
wireOut: info.wireOut,
|
|
147
|
+
runnable: info.runnable,
|
|
148
|
+
bound: info.bound,
|
|
149
|
+
params: Object.freeze({ ...(params || {}) }),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function nodesOf(items, who) {
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const n of items) {
|
|
156
|
+
if (n && n[NODE]) out.push(n);
|
|
157
|
+
else if (n && Array.isArray(n.nodes)) out.push(...nodesOf(n.nodes, who));
|
|
158
|
+
else throw new TypeError(`${who}: expected nodes from node() or pipelines from pipe()`);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A pipeline: the nodes in order (pipelines are flattened).
|
|
165
|
+
* `assumptions` is the union of the nodes' named assumptions.
|
|
166
|
+
* @returns {{nodes: object[], assumptions: string[]}}
|
|
167
|
+
*/
|
|
168
|
+
function pipe(...items) {
|
|
169
|
+
const nodes = nodesOf(items, 'pipe');
|
|
170
|
+
if (nodes.length === 0) throw new TypeError('pipe: no nodes');
|
|
171
|
+
return Object.freeze({ nodes: Object.freeze(nodes), assumptions: Object.freeze(unionAssumptions(nodes)) });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const isWire = (v) => v !== null && typeof v === 'object' && !(v instanceof Dataset) &&
|
|
175
|
+
Object.prototype.hasOwnProperty.call(v, 'graph') && Array.isArray(v.evidence);
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Run a node or a pipeline. `input` is a wire value `{graph, evidence}`
|
|
179
|
+
* or the graph itself (text for a reader, else a Dataset or data input).
|
|
180
|
+
* `options.unchecked: true` lets stages that are not `proved` run,
|
|
181
|
+
* `missing` ones too when the package has a runner for them; each such
|
|
182
|
+
* step's record says so (evidence.mjs). Without it, a `missing` stage
|
|
183
|
+
* refuses before any node runs.
|
|
184
|
+
* @returns {Promise<{graph: any, evidence: object[], assumptions: string[],
|
|
185
|
+
* proved: boolean, uncheckedSteps: string[]}>}
|
|
186
|
+
*/
|
|
187
|
+
async function run(pipeline, input, options) {
|
|
188
|
+
if (options !== undefined && (options === null || typeof options !== 'object')) {
|
|
189
|
+
throw new TypeError('run: options must be an object');
|
|
190
|
+
}
|
|
191
|
+
const unchecked = !!(options && options.unchecked);
|
|
192
|
+
const nodes = nodesOf([pipeline], 'run');
|
|
193
|
+
for (const n of nodes) {
|
|
194
|
+
const why = refusalFor(n, unchecked, Object.prototype.hasOwnProperty.call(RUNNERS, n.stage));
|
|
195
|
+
if (why) throw why;
|
|
196
|
+
}
|
|
197
|
+
let wire = isWire(input) ? { graph: input.graph, evidence: [...input.evidence] }
|
|
198
|
+
: { graph: input, evidence: [] };
|
|
199
|
+
let owned = null; // a Dataset an earlier node of this run made
|
|
200
|
+
for (const n of nodes) {
|
|
201
|
+
const out = await RUNNERS[n.stage](wire, n.params);
|
|
202
|
+
const record = makeRecord(n, out.artefact, unchecked);
|
|
203
|
+
let graph = wire.graph;
|
|
204
|
+
if (out.graph !== undefined && out.graph !== wire.graph) {
|
|
205
|
+
if (owned && owned === wire.graph && typeof owned.dispose === 'function') owned.dispose();
|
|
206
|
+
graph = out.graph;
|
|
207
|
+
owned = out.graph instanceof Dataset ? out.graph : null;
|
|
208
|
+
}
|
|
209
|
+
wire = { graph, evidence: [...wire.evidence, record] };
|
|
210
|
+
}
|
|
211
|
+
return { graph: wire.graph, evidence: wire.evidence, ...summarize(wire.evidence) };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { stages, node, pipe, run };
|
|
215
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The Node reader of an SBM11 generation directory for lib/sbm11.mjs: the
|
|
2
|
+
// source is a directory path, and a file of the generation is read with
|
|
3
|
+
// fs.readSync at an offset (https://github.com/danbri/factoidal/issues/1009
|
|
4
|
+
// KR1). It moves bytes only (iron rule 7); the engine checks every length.
|
|
5
|
+
//
|
|
6
|
+
// One open file descriptor per file, kept while the reader lives: a query
|
|
7
|
+
// makes many small reads of the same few files.
|
|
8
|
+
|
|
9
|
+
import { closeSync, fstatSync, openSync, readSync, statSync } from 'node:fs'
|
|
10
|
+
import { join } from 'node:path'
|
|
11
|
+
|
|
12
|
+
export function nodeDirectoryReader (dir) {
|
|
13
|
+
const fds = new Map()
|
|
14
|
+
function fdOf (file) {
|
|
15
|
+
let fd = fds.get(file)
|
|
16
|
+
if (fd === undefined) {
|
|
17
|
+
fd = openSync(join(dir, file), 'r')
|
|
18
|
+
fds.set(file, fd)
|
|
19
|
+
}
|
|
20
|
+
return fd
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
readAt (file, offset, length) {
|
|
24
|
+
const out = new Uint8Array(length)
|
|
25
|
+
const fd = fdOf(file)
|
|
26
|
+
let done = 0
|
|
27
|
+
while (done < length) {
|
|
28
|
+
const n = readSync(fd, out, done, length - done, offset + done)
|
|
29
|
+
if (n === 0) break
|
|
30
|
+
done += n
|
|
31
|
+
}
|
|
32
|
+
return done === length ? out : null
|
|
33
|
+
},
|
|
34
|
+
size (file) {
|
|
35
|
+
try {
|
|
36
|
+
const fd = fds.get(file)
|
|
37
|
+
const st = fd === undefined ? statSync(join(dir, file)) : fstatSync(fd)
|
|
38
|
+
return st.isFile() ? st.size : null
|
|
39
|
+
} catch {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
close () {
|
|
44
|
+
for (const fd of fds.values()) closeSync(fd)
|
|
45
|
+
fds.clear()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A default-reader factory: one reader per directory, kept. */
|
|
51
|
+
export function nodeDefaultReaderFactory () {
|
|
52
|
+
const byDir = new Map()
|
|
53
|
+
return (source) => {
|
|
54
|
+
let r = byDir.get(source)
|
|
55
|
+
if (!r) {
|
|
56
|
+
r = nodeDirectoryReader(source)
|
|
57
|
+
byDir.set(source, r)
|
|
58
|
+
}
|
|
59
|
+
return r
|
|
60
|
+
}
|
|
61
|
+
}
|
package/lib/sbm11.mjs
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// SPARQL over an SBM11 generation, in Node and in the browser, through the
|
|
2
|
+
// wasm ops sbm11Open / sbm11Query / sbm11Close
|
|
3
|
+
// (formal/lean4/Wasm/Ops/Sbm11.lean; https://github.com/danbri/factoidal/issues/1009 KR1).
|
|
4
|
+
//
|
|
5
|
+
// WHAT THIS FILE IS ALLOWED TO DO
|
|
6
|
+
// Move bytes. The engine asks for (source, file, offset, length) through the
|
|
7
|
+
// bridge globalThis.__factoidalReadAt, and for the size of a file through
|
|
8
|
+
// globalThis.__factoidalFileSize (the EM_JS bodies in
|
|
9
|
+
// formal/lean4/ffi/l4_hostread.c call them synchronously). This file finds
|
|
10
|
+
// the reader that serves `source` and answers. It never reads a manifest,
|
|
11
|
+
// checks a length or a digest, or decides which file a query needs: the
|
|
12
|
+
// engine names every file and checks every answer against the manifest
|
|
13
|
+
// (formal/lean4/L4Factoidal/Storage/Sbm11/HostSession.lean). Iron rule 7.
|
|
14
|
+
//
|
|
15
|
+
// READERS
|
|
16
|
+
// A reader is { readAt(file, offset, length) -> Uint8Array, size(file) ->
|
|
17
|
+
// number | null }, both synchronous: the engine is inside WebAssembly and
|
|
18
|
+
// cannot wait for a promise. Node: lib/sbm11-node.mjs reads a directory with
|
|
19
|
+
// fs. A browser worker: a reader over OPFS FileSystemSyncAccessHandle
|
|
20
|
+
// (stage 4 of https://github.com/danbri/factoidal/issues/1007). A caller
|
|
21
|
+
// registers a reader under a source name; a source with no registered reader
|
|
22
|
+
// goes to the default reader (Node: the directory of that name).
|
|
23
|
+
|
|
24
|
+
const readers = new Map()
|
|
25
|
+
let defaultReader = null
|
|
26
|
+
let bridgeInstalled = false
|
|
27
|
+
|
|
28
|
+
function readerFor (source) {
|
|
29
|
+
const r = readers.get(source)
|
|
30
|
+
if (r) return r
|
|
31
|
+
if (defaultReader) return defaultReader(source)
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function installBridge () {
|
|
36
|
+
if (bridgeInstalled) return
|
|
37
|
+
bridgeInstalled = true
|
|
38
|
+
// A thrown error and a wrong length both reach Lean as "the host gave no
|
|
39
|
+
// bytes"; the engine then refuses the read with the file name.
|
|
40
|
+
globalThis.__factoidalReadAt = (source, file, offset, length) => {
|
|
41
|
+
const r = readerFor(source)
|
|
42
|
+
if (!r) return null
|
|
43
|
+
return r.readAt(file, offset, length)
|
|
44
|
+
}
|
|
45
|
+
globalThis.__factoidalFileSize = (source, file) => {
|
|
46
|
+
const r = readerFor(source)
|
|
47
|
+
if (!r) return null
|
|
48
|
+
return r.size(file)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Serve `source` with `reader` ({ readAt(file, offset, length), size(file) },
|
|
54
|
+
* both synchronous). `null` removes the registration.
|
|
55
|
+
*/
|
|
56
|
+
export function registerSbm11Source (source, reader) {
|
|
57
|
+
installBridge()
|
|
58
|
+
if (reader === null) { readers.delete(String(source)); return }
|
|
59
|
+
if (!reader || typeof reader.readAt !== 'function' || typeof reader.size !== 'function') {
|
|
60
|
+
throw new TypeError('registerSbm11Source: reader needs readAt(file, offset, length) and size(file)')
|
|
61
|
+
}
|
|
62
|
+
readers.set(String(source), reader)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The reader for a source with no registration: `(source) => reader | null`. */
|
|
66
|
+
export function setDefaultSbm11Reader (factory) {
|
|
67
|
+
installBridge()
|
|
68
|
+
defaultReader = factory
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function envelope (raw) {
|
|
72
|
+
const j = typeof raw === 'string' ? JSON.parse(raw) : raw
|
|
73
|
+
if (!j || j.ok !== true) {
|
|
74
|
+
const e = new Error((j && j.error) || 'sbm11: unknown error')
|
|
75
|
+
e.code = 'SBM11'
|
|
76
|
+
throw e
|
|
77
|
+
}
|
|
78
|
+
return j
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The SBM11 functions over an engine loader: `loadEngine()` resolves to the
|
|
83
|
+
* engine handle (`call(op, args)`).
|
|
84
|
+
*/
|
|
85
|
+
export function sbm11Api (loadEngine) {
|
|
86
|
+
/**
|
|
87
|
+
* Open an SBM11 generation (a generation directory, or a collection root
|
|
88
|
+
* whose CURRENT file names one). Returns { generation, terms, query(sparql,
|
|
89
|
+
* options), close() }. `query` answers { path, rows, format, result, json }:
|
|
90
|
+
* `result` is the text `l4sbm11 query` writes (SPARQL JSON for SELECT and
|
|
91
|
+
* ASK, N-Triples for CONSTRUCT and DESCRIBE), `json` its parse for
|
|
92
|
+
* SELECT and ASK.
|
|
93
|
+
*/
|
|
94
|
+
async function openSbm11 (source, options = {}) {
|
|
95
|
+
installBridge()
|
|
96
|
+
if (options.reader) registerSbm11Source(source, options.reader)
|
|
97
|
+
const eng = await loadEngine()
|
|
98
|
+
const opened = envelope(eng.call('sbm11Open', [String(source)]))
|
|
99
|
+
const handle = opened.handle
|
|
100
|
+
let open = true
|
|
101
|
+
return {
|
|
102
|
+
handle,
|
|
103
|
+
generation: opened.generation,
|
|
104
|
+
terms: Number(opened.terms),
|
|
105
|
+
query (sparql, qopts = {}) {
|
|
106
|
+
if (!open) throw new Error('sbm11: the store is closed')
|
|
107
|
+
const o = {}
|
|
108
|
+
if (qopts.base != null) o.base = String(qopts.base)
|
|
109
|
+
if (qopts.sparql12) o.sparql12 = true
|
|
110
|
+
if (qopts.reference) o.reference = true
|
|
111
|
+
const args = [handle, String(sparql), JSON.stringify(o)]
|
|
112
|
+
// The byte-region entry returns the result bytes outside the
|
|
113
|
+
// envelope; a large answer is then not escaped into a JSON string
|
|
114
|
+
// and parsed again (Wasm/Ops/Sbm11.lean sbm11QueryBlob).
|
|
115
|
+
let j, bytes
|
|
116
|
+
if (typeof eng.callBlobIO === 'function') {
|
|
117
|
+
const answer = eng.callBlobIO('sbm11Query', args, new Uint8Array(0))
|
|
118
|
+
j = envelope(answer.envelope)
|
|
119
|
+
bytes = answer.bytes
|
|
120
|
+
} else {
|
|
121
|
+
j = envelope(eng.call('sbm11Query', args))
|
|
122
|
+
}
|
|
123
|
+
let text = bytes === undefined ? j.result : undefined
|
|
124
|
+
return {
|
|
125
|
+
path: j.path,
|
|
126
|
+
rows: Number(j.rows),
|
|
127
|
+
format: j.format,
|
|
128
|
+
bytesRead: Number(j.bytesRead),
|
|
129
|
+
reads: Number(j.reads),
|
|
130
|
+
/** the answer as bytes (UTF-8) */
|
|
131
|
+
get bytes () { return bytes ?? new TextEncoder().encode(text) },
|
|
132
|
+
/** the answer as text */
|
|
133
|
+
get result () {
|
|
134
|
+
if (text === undefined) text = new TextDecoder().decode(bytes)
|
|
135
|
+
return text
|
|
136
|
+
},
|
|
137
|
+
get json () { return j.format === 'srj' ? JSON.parse(this.result) : undefined }
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
close () {
|
|
141
|
+
if (!open) return
|
|
142
|
+
open = false
|
|
143
|
+
envelope(eng.call('sbm11Close', [handle]))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { openSbm11 }
|
|
148
|
+
}
|
package/lib/version.mjs
CHANGED