@factoidal/core 0.7.1 → 0.10.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 +503 -1
- package/NOTICE +2 -21
- package/README.md +117 -993
- package/api.d.ts +53 -0
- package/api.js +5 -0
- package/api.mjs +56 -0
- package/bin/engine.mjs +38 -19
- package/bin/factoidal.mjs +512 -66
- package/bin/pack.mjs +37 -0
- package/bin/serve-worker.mjs +32 -0
- package/bin/serve.mjs +423 -0
- package/bin/store-worker-host.mjs +1 -1
- package/bin/store.mjs +1 -1
- package/browser.d.ts +110 -507
- package/browser.js +151 -2257
- package/docs/fuseki-replacement.md +108 -0
- package/fn.d.ts +24 -95
- package/fn.js +104 -150
- package/index.d.ts +598 -259
- package/index.js +4 -234
- package/index.mjs +176 -84
- package/l4-assets/l4factoidal.js +47 -7
- package/l4-assets/l4factoidal.mjs +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +11 -7
- package/lib/api.mjs +2300 -0
- package/lib/browser-engine.mjs +69 -0
- package/lib/dataset.mjs +377 -0
- package/lib/delta-log-hosts.mjs +191 -0
- package/lib/lean-entry.mjs +204 -0
- package/lib/pure.mjs +98 -0
- package/lib/store.mjs +115 -0
- package/lib/version.mjs +5 -0
- package/lws/README.md +97 -0
- package/lws/server.mjs +331 -0
- package/package.json +59 -61
- package/rdfjs.js +5 -536
- package/rdfjs.mjs +594 -0
- package/sample-store.d.ts +8 -16
- package/sample-store.mjs +39 -29
- package/solid/client/README.md +100 -0
- package/solid/client/index.mjs +277 -0
- package/solid/server/README.md +96 -0
- package/solid/server/index.mjs +317 -0
- package/store-host/deno.mjs +8 -0
- package/store-host/index.mjs +12 -0
- package/store-host/node.mjs +8 -0
- package/xmpp/README.md +107 -0
- package/xmpp/index.mjs +229 -0
- package/browser-wasm.js +0 -872
- package/factoidal-npm-entry.js +0 -15477
- package/factoidal-npm-entry.wasm.assets/code-5a7fc68f2ab1323718b8.wasm +0 -0
- package/factoidal-npm-entry.wasm.js +0 -455
- package/factoidal.js +0 -13118
- package/factoidal.wasm.assets/code-bbe6099bfb5b10c4c3ab.wasm +0 -0
- package/factoidal.wasm.js +0 -457
- package/hacl-init.js +0 -92
- package/hacl-wasm/FStar.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum25519_51.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum_Base.wasm +0 -0
- package/hacl-wasm/Hacl_Curve25519_51.wasm +0 -0
- package/hacl-wasm/Hacl_Ed25519.wasm +0 -0
- package/hacl-wasm/Hacl_Ed25519_PrecompTable.wasm +0 -0
- package/hacl-wasm/Hacl_Hash_Base.wasm +0 -0
- package/hacl-wasm/Hacl_Hash_SHA2.wasm +0 -0
- package/hacl-wasm/Hacl_IntTypes_Intrinsics.wasm +0 -0
- package/hacl-wasm/LowStar_Endianness.wasm +0 -0
- package/hacl-wasm/WasmSupport.wasm +0 -0
- package/hacl-wasm/api.js +0 -775
- package/hacl-wasm/api.json +0 -3787
- package/hacl-wasm/layouts.json +0 -1
- package/hacl-wasm/loader.js +0 -568
- package/hacl-wasm/shell.js +0 -12
- package/l4-core.d.ts +0 -159
- package/l4-core.js +0 -188
- package/l4.d.ts +0 -50
- package/l4.js +0 -104
- package/lib/api.js +0 -2300
- package/lib/engine-js.js +0 -165
- package/lib/engine-wasm.js +0 -300
- package/sample-store/CURRENT +0 -1
- package/sample-store/gen-1/manifest.sbm2 +0 -0
- package/sample-store/gen-1/manifest.tsv +0 -14
- package/sample-store/gen-1/predicate-0.ibk3 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-0.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-0.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-0.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.oli2.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-1.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-10.ibk3 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-10.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.oli2.merkle +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-10.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-11.ibk3 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-11.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-11.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-2.ibk3 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-2.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-2.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.sri2.merkle +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-3.ibk3 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-3.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-3.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-3.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-4.ibk3 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-4.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-4.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.tli1.merkle +0 -2
- package/sample-store/gen-1/predicate-5.ibk3 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-5.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-5.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-5.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-7.ibk3 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.oli2.merkle +0 -2
- package/sample-store/gen-1/predicate-7.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-7.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-9.ibk3 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-9.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-9.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.sri2.merkle +0 -3
- package/sample-store/gen-1/predicate-9.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.tli1.merkle +0 -2
- package/select.d.ts +0 -116
- package/select.js +0 -492
- package/version.json +0 -102
- package/wasm.d.ts +0 -130
- package/wasm.js +0 -158
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Loading a Lean engine in the browser: one of the two modules of
|
|
2
|
+
// l4-assets/ (issue 713 stage 4), named by its basename:
|
|
3
|
+
//
|
|
4
|
+
// 'l4factoidal' the full engine (browser.js)
|
|
5
|
+
// 'l4factoidal-core' the core engine (core-browser.js)
|
|
6
|
+
//
|
|
7
|
+
// Each engine is three files that keep their names together: the loader
|
|
8
|
+
// <name>.js, Emscripten's glue <name>.mjs and the module <name>.wasm. The
|
|
9
|
+
// loader is imported from the assets directory; the wasm is fetched and
|
|
10
|
+
// compiled with WebAssembly.instantiateStreaming (a server that does not
|
|
11
|
+
// send `Content-Type: application/wasm` gets WebAssembly.instantiate over
|
|
12
|
+
// the fetched bytes instead).
|
|
13
|
+
//
|
|
14
|
+
// No Node built-ins: the browser imports this file as it is.
|
|
15
|
+
|
|
16
|
+
async function instantiate(url, imports) {
|
|
17
|
+
try {
|
|
18
|
+
return await WebAssembly.instantiateStreaming(fetch(url), imports);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
// instantiateStreaming refuses a response without the application/wasm
|
|
21
|
+
// media type with a TypeError; compile from the bytes then. Any other
|
|
22
|
+
// failure (network, a corrupt module) is the caller's.
|
|
23
|
+
if (!(err instanceof TypeError)) throw err;
|
|
24
|
+
const response = await fetch(url);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`factoidal: fetching ${url} failed: ${response.status} ${response.statusText}`);
|
|
27
|
+
}
|
|
28
|
+
return WebAssembly.instantiate(await response.arrayBuffer(), imports);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The asset-directory setters and the engine loader of one engine.
|
|
34
|
+
* @param {string} name 'l4factoidal' or 'l4factoidal-core'
|
|
35
|
+
* @param {string} defaultAssetsUrl the directory URL to load from until
|
|
36
|
+
* setAssetsUrl() names another
|
|
37
|
+
*/
|
|
38
|
+
export function browserEngine(name, defaultAssetsUrl) {
|
|
39
|
+
let assetsUrl = defaultAssetsUrl;
|
|
40
|
+
let enginePromise = null;
|
|
41
|
+
|
|
42
|
+
function setAssetsUrl(url) {
|
|
43
|
+
if (enginePromise) {
|
|
44
|
+
throw new Error('setAssetsUrl: the engine is already loaded from ' + assetsUrl);
|
|
45
|
+
}
|
|
46
|
+
const text = String(url);
|
|
47
|
+
assetsUrl = new URL(text.endsWith('/') ? text : text + '/', globalThis.location?.href).href;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getAssetsUrl() {
|
|
51
|
+
return assetsUrl;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function loadEngine() {
|
|
55
|
+
if (enginePromise) return enginePromise;
|
|
56
|
+
enginePromise = (async () => {
|
|
57
|
+
const loader = await import(new URL(`${name}.js`, assetsUrl).href);
|
|
58
|
+
// The content-hash version keeps a cached wasm from pairing with a
|
|
59
|
+
// newer loader (the loader's own locateFile does the same).
|
|
60
|
+
const version = loader.wasmVersion ? `?v=${loader.wasmVersion}` : '';
|
|
61
|
+
const wasmUrl = new URL(`${name}.wasm`, assetsUrl).href + version;
|
|
62
|
+
return loader.loadL4({ instantiateWasm: (imports) => instantiate(wasmUrl, imports) });
|
|
63
|
+
})();
|
|
64
|
+
enginePromise.catch(() => { enginePromise = null; });
|
|
65
|
+
return enginePromise;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { setAssetsUrl, getAssetsUrl, loadEngine };
|
|
69
|
+
}
|
package/lib/dataset.mjs
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// factoidal — Dataset: an RDF dataset held inside the Lean engine.
|
|
2
|
+
//
|
|
3
|
+
// https://github.com/danbri/factoidal/issues/882 item 1. A Dataset is a
|
|
4
|
+
// handle into the engine's dataset store (formal/lean4/Wasm/Ops/Handles.lean
|
|
5
|
+
// and Wasm/Ops/DatasetFull.lean). The parsed, indexed dataset stays in the
|
|
6
|
+
// wasm memory; a query, an update, a validation, a closure or a
|
|
7
|
+
// serialisation sends the handle id and the query text, not the data. An op
|
|
8
|
+
// that makes a dataset (parse, CSVW, RML, JSON-LD, closures, RIF) answers a
|
|
9
|
+
// new handle, so a pipeline of steps moves no N-Quads text between them.
|
|
10
|
+
//
|
|
11
|
+
// This file only marshals: every decision about RDF is the engine's.
|
|
12
|
+
//
|
|
13
|
+
// Lifetime: dispose() (or `using ds = ...`, Symbol.dispose) releases the
|
|
14
|
+
// engine copy. A Dataset that is garbage collected without dispose() is
|
|
15
|
+
// released by a FinalizationRegistry callback. Every method of a disposed
|
|
16
|
+
// Dataset throws a TypeError that names the method.
|
|
17
|
+
|
|
18
|
+
import { nquadsLineToQuad, quadToNQuads, termToNQuads, termFromSrj } from '../rdfjs.mjs';
|
|
19
|
+
|
|
20
|
+
const INTERNAL = Symbol('factoidal.Dataset.internal');
|
|
21
|
+
|
|
22
|
+
// Writer names (lower case) -> the engine's writer tag. JSON-LD is served
|
|
23
|
+
// by the op datasetToJsonLd; the SPARQL results formats are query outputs.
|
|
24
|
+
const WRITER_TAGS = {
|
|
25
|
+
nquads: 'nquads', nq: 'nquads', 'n-quads': 'nquads',
|
|
26
|
+
ntriples: 'ntriples', nt: 'ntriples', 'n-triples': 'ntriples',
|
|
27
|
+
turtle: 'turtle', ttl: 'turtle',
|
|
28
|
+
trig: 'trig',
|
|
29
|
+
rdfxml: 'rdfxml', 'rdf-xml': 'rdfxml', rdf: 'rdfxml',
|
|
30
|
+
jsonld: 'jsonld', 'json-ld': 'jsonld',
|
|
31
|
+
rdfc: 'rdfc', canonical: 'rdfc', 'rdfc-1.0': 'rdfc',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const VERSIONS = { '1.1': '11', '1.2': '12' };
|
|
35
|
+
|
|
36
|
+
/** Throw a TypeError when `options` has a key outside `allowed`. */
|
|
37
|
+
function checkOptions(who, options, allowed) {
|
|
38
|
+
if (options == null) return {};
|
|
39
|
+
if (typeof options !== 'object' || Array.isArray(options)) {
|
|
40
|
+
throw new TypeError(`${who}: options must be an object, got ${Array.isArray(options) ? 'array' : typeof options}`);
|
|
41
|
+
}
|
|
42
|
+
for (const key of Object.keys(options)) {
|
|
43
|
+
if (!allowed.includes(key)) {
|
|
44
|
+
throw new TypeError(
|
|
45
|
+
`${who}: unknown option '${key}'. Known options: ${allowed.join(', ') || '(none)'}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return options;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function envelope(jsonText, what) {
|
|
52
|
+
const r = JSON.parse(jsonText);
|
|
53
|
+
if (!r.ok) throw new Error(`${what}: ${r.error}`);
|
|
54
|
+
return r;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const SERIALIZE_OPTIONS = ['format', 'version', 'prefixes', 'literalShorthand',
|
|
58
|
+
'useNativeTypes', 'useRdfType', 'rdfDirection'];
|
|
59
|
+
|
|
60
|
+
class Dataset {
|
|
61
|
+
/** @private Datasets come from parse(), csvwToRdf() and the other producers. */
|
|
62
|
+
constructor(token, ctx, handle, size, prefixes, extra) {
|
|
63
|
+
if (token !== INTERNAL) {
|
|
64
|
+
throw new TypeError(
|
|
65
|
+
'Dataset: use parse(), openDataset() or another producer; the constructor is internal');
|
|
66
|
+
}
|
|
67
|
+
this._ctx = ctx;
|
|
68
|
+
this._handle = handle;
|
|
69
|
+
this._size = size;
|
|
70
|
+
this._prefixes = Object.freeze({ ...(prefixes || {}) });
|
|
71
|
+
const x = extra || {};
|
|
72
|
+
this._diagnostics = Object.freeze((x.diagnostics || []).slice());
|
|
73
|
+
this._format = x.format;
|
|
74
|
+
this._formatInferred = !!x.formatInferred;
|
|
75
|
+
this._info = Object.freeze({ ...(x.info || {}) });
|
|
76
|
+
this._disposed = false;
|
|
77
|
+
this._nquads = null; // cached N-Quads text, dropped on update
|
|
78
|
+
this._quads = null; // cached quad array, dropped on update
|
|
79
|
+
if (ctx.registry) ctx.registry.register(this, { e: ctx.e, handle }, this);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The engine's handle id (opaque, e.g. "h7"). */
|
|
83
|
+
get handle() { return this._handle; }
|
|
84
|
+
/** Number of quads. */
|
|
85
|
+
get size() { this._open('size'); return this._size; }
|
|
86
|
+
/** Prefixes the source document declared (label -> namespace IRI). */
|
|
87
|
+
get prefixes() { return this._prefixes; }
|
|
88
|
+
/** Diagnostics of a lenient parse ({lenient:true}); else empty. */
|
|
89
|
+
get diagnostics() { return this._diagnostics; }
|
|
90
|
+
/** The syntax the data was read from, when it was read from text. */
|
|
91
|
+
get format() { return this._format; }
|
|
92
|
+
/** True when `format` was inferred because the caller gave none. */
|
|
93
|
+
get formatInferred() { return this._formatInferred; }
|
|
94
|
+
/** True after dispose(). */
|
|
95
|
+
get disposed() { return this._disposed; }
|
|
96
|
+
/**
|
|
97
|
+
* What the producing op reported beside the data: `rounds` for the rho-df
|
|
98
|
+
* and RDFS-Plus closures; `inputCount`, `derivedCount`, `fuel` for rifEval.
|
|
99
|
+
*/
|
|
100
|
+
get info() { return this._info; }
|
|
101
|
+
|
|
102
|
+
_open(who) {
|
|
103
|
+
if (this._disposed) {
|
|
104
|
+
throw new TypeError(`Dataset.${who}: the Dataset was disposed (handle '${this._handle}')`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_changed(size) {
|
|
109
|
+
this._size = size;
|
|
110
|
+
this._nquads = null;
|
|
111
|
+
this._quads = null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* SPARQL 1.1 (or 1.2) query.
|
|
116
|
+
* @param {string} sparql
|
|
117
|
+
* @param {{sparql12?: boolean, version?: '1.1'|'1.2', entail?: string,
|
|
118
|
+
* output?: 'json'|'xml'|'csv'|'tsv'|'table'|'ntriples'}} [options]
|
|
119
|
+
* With `output` (or an `entail` regime other than 'none') the answer
|
|
120
|
+
* is text (`output` 'json': the parsed results JSON object).
|
|
121
|
+
* @returns {Promise<Array<Map<string, object>>|boolean|Dataset|string|object>}
|
|
122
|
+
*/
|
|
123
|
+
async query(sparql, options) {
|
|
124
|
+
this._open('query');
|
|
125
|
+
return this._ctx.queryHandle(this, sparql,
|
|
126
|
+
checkOptions('Dataset.query', options, ['sparql12', 'version', 'entail', 'output']));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* SPARQL 1.1 Update, applied to this Dataset in place.
|
|
131
|
+
* @param {string} updateText
|
|
132
|
+
* @param {{}} [options] none today
|
|
133
|
+
* @returns {Promise<Dataset>} this Dataset
|
|
134
|
+
*/
|
|
135
|
+
async update(updateText, options) {
|
|
136
|
+
this._open('update');
|
|
137
|
+
checkOptions('Dataset.update', options, []);
|
|
138
|
+
if (typeof updateText !== 'string') {
|
|
139
|
+
throw new TypeError('Dataset.update: updateText must be a string');
|
|
140
|
+
}
|
|
141
|
+
const r = envelope(this._ctx.e.datasetUpdate(this._handle, updateText), 'update');
|
|
142
|
+
this._changed(r.count);
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* SHACL validation with this Dataset as the data graph.
|
|
148
|
+
* @param {Dataset|string} shapes a Dataset or text
|
|
149
|
+
* @param {{format?: string, baseIRI?: string}} [options] read `shapes` text
|
|
150
|
+
* @returns {Promise<{conforms: boolean, report: Dataset}>}
|
|
151
|
+
*/
|
|
152
|
+
async validateShacl(shapes, options) {
|
|
153
|
+
this._open('validateShacl');
|
|
154
|
+
return this._ctx.shacl(this, shapes,
|
|
155
|
+
checkOptions('Dataset.validateShacl', options, ['format', 'baseIRI']));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* ShEx validation of one focus node with this Dataset as the data graph.
|
|
160
|
+
* @param {string} schema ShExC or ShExJ text
|
|
161
|
+
* @param {{focus: string|object, shape?: string|object}} options
|
|
162
|
+
* @returns {Promise<boolean>}
|
|
163
|
+
*/
|
|
164
|
+
async validateShex(schema, options) {
|
|
165
|
+
this._open('validateShex');
|
|
166
|
+
return this._ctx.shex(this, schema,
|
|
167
|
+
checkOptions('Dataset.validateShex', options, ['focus', 'shape']));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Closure of the default graph under a regime, as a new Dataset.
|
|
172
|
+
* @param {'rdfs'|'owl-rl'|'rho-df'|'rdfs-plus'} regime
|
|
173
|
+
* @returns {Promise<Dataset>}
|
|
174
|
+
*/
|
|
175
|
+
async closure(regime) {
|
|
176
|
+
this._open('closure');
|
|
177
|
+
return this._ctx.closure(this, regime);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** RDFC-1.0 canonical N-Quads. @returns {Promise<string>} */
|
|
181
|
+
async canonicalize() {
|
|
182
|
+
this._open('canonicalize');
|
|
183
|
+
return this.toString('rdfc');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The dataset as a document.
|
|
188
|
+
* @param {{format?: string, version?: '1.1'|'1.2', prefixes?: object,
|
|
189
|
+
* literalShorthand?: boolean, useNativeTypes?: boolean,
|
|
190
|
+
* useRdfType?: boolean, rdfDirection?: string}} [options]
|
|
191
|
+
* format: nquads (default), ntriples, turtle, trig, rdfxml, jsonld,
|
|
192
|
+
* rdfc (RDFC-1.0 canonical N-Quads).
|
|
193
|
+
* @returns {Promise<string>}
|
|
194
|
+
*/
|
|
195
|
+
async serialize(options) {
|
|
196
|
+
this._open('serialize');
|
|
197
|
+
if (typeof options === 'string') return this._write({ format: options });
|
|
198
|
+
return this._write(checkOptions('Dataset.serialize', options, SERIALIZE_OPTIONS));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Synchronous serialize(): `toString('turtle')` or `toString({format})`.
|
|
203
|
+
* @param {string|object} [formatOrOptions]
|
|
204
|
+
* @returns {string}
|
|
205
|
+
*/
|
|
206
|
+
toString(formatOrOptions) {
|
|
207
|
+
this._open('toString');
|
|
208
|
+
const opts = typeof formatOrOptions === 'string'
|
|
209
|
+
? { format: formatOrOptions }
|
|
210
|
+
: checkOptions('Dataset.toString', formatOrOptions, SERIALIZE_OPTIONS);
|
|
211
|
+
return this._write(opts);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_write(opts) {
|
|
215
|
+
const e = this._ctx.e;
|
|
216
|
+
const name = String(opts.format || 'nquads').toLowerCase();
|
|
217
|
+
const tag = WRITER_TAGS[name];
|
|
218
|
+
if (!tag) {
|
|
219
|
+
throw new TypeError(
|
|
220
|
+
`Dataset.serialize: unknown format '${opts.format}'. Known formats: ` +
|
|
221
|
+
[...new Set(Object.values(WRITER_TAGS))].join(', '));
|
|
222
|
+
}
|
|
223
|
+
let suffix = '';
|
|
224
|
+
if (opts.version !== undefined) {
|
|
225
|
+
suffix = VERSIONS[String(opts.version)];
|
|
226
|
+
if (!suffix) throw new TypeError("Dataset.serialize: version must be '1.1' or '1.2'");
|
|
227
|
+
}
|
|
228
|
+
if (tag !== 'turtle' && (opts.prefixes !== undefined || opts.literalShorthand !== undefined)) {
|
|
229
|
+
throw new TypeError("Dataset.serialize: 'prefixes' and 'literalShorthand' apply to format 'turtle' only");
|
|
230
|
+
}
|
|
231
|
+
const jsonldKeys = ['useNativeTypes', 'useRdfType', 'rdfDirection'];
|
|
232
|
+
if (tag !== 'jsonld' && jsonldKeys.some((k) => opts[k] !== undefined)) {
|
|
233
|
+
throw new TypeError(`Dataset.serialize: ${jsonldKeys.join(', ')} apply to format 'jsonld' only`);
|
|
234
|
+
}
|
|
235
|
+
if (tag === 'jsonld') {
|
|
236
|
+
if (typeof e.datasetToJsonLd !== 'function') throw this._ctx.missing('serialize {format:jsonld}', 'datasetToJsonLd');
|
|
237
|
+
const r = envelope(e.datasetToJsonLd(this._handle, JSON.stringify({
|
|
238
|
+
...(opts.useNativeTypes ? { useNativeTypes: true } : {}),
|
|
239
|
+
...(opts.useRdfType ? { useRdfType: true } : {}),
|
|
240
|
+
...(opts.rdfDirection ? { rdfDirection: String(opts.rdfDirection) } : {}),
|
|
241
|
+
...(suffix ? { version: String(opts.version) } : {}),
|
|
242
|
+
})), 'serialize');
|
|
243
|
+
return r.jsonld;
|
|
244
|
+
}
|
|
245
|
+
if (tag === 'nquads' && !suffix && this._nquads !== null) return this._nquads;
|
|
246
|
+
const fullTag = tag + suffix;
|
|
247
|
+
let r;
|
|
248
|
+
if (tag === 'turtle' && (opts.prefixes !== undefined || opts.literalShorthand !== undefined ||
|
|
249
|
+
Object.keys(this._prefixes).length > 0)) {
|
|
250
|
+
r = envelope(e.datasetSerializeWith(this._handle, fullTag, JSON.stringify({
|
|
251
|
+
prefixes: { ...this._prefixes, ...(opts.prefixes || {}) },
|
|
252
|
+
...(opts.literalShorthand !== undefined ? { literalShorthand: !!opts.literalShorthand } : {}),
|
|
253
|
+
})), 'serialize');
|
|
254
|
+
} else {
|
|
255
|
+
r = envelope(e.datasetSerialize(this._handle, fullTag), 'serialize');
|
|
256
|
+
}
|
|
257
|
+
const text = r.text !== undefined ? r.text : (tag === 'turtle' ? r.turtle : r.nquads);
|
|
258
|
+
if (tag === 'nquads' && !suffix) this._nquads = text;
|
|
259
|
+
return text;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** N-Quads text (sorted, the engine's canonical line order). */
|
|
263
|
+
toNQuads() {
|
|
264
|
+
this._open('toNQuads');
|
|
265
|
+
return this._write({ format: 'nquads' });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** A new Dataset with the same quads; updates of one leave the other unchanged. */
|
|
269
|
+
clone() {
|
|
270
|
+
this._open('clone');
|
|
271
|
+
const r = envelope(this._ctx.e.datasetClone(this._handle), 'clone');
|
|
272
|
+
return this._ctx.wrap(r, this._prefixes);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* RDF/JS quads, read from the engine's N-Quads text one line at a time.
|
|
277
|
+
* @returns {Iterator<object>}
|
|
278
|
+
*/
|
|
279
|
+
*[Symbol.iterator]() {
|
|
280
|
+
this._open('iterate');
|
|
281
|
+
if (this._quads) { yield* this._quads; return; }
|
|
282
|
+
const text = this.toNQuads();
|
|
283
|
+
let start = 0;
|
|
284
|
+
let lineNo = 0;
|
|
285
|
+
while (start < text.length) {
|
|
286
|
+
let end = text.indexOf('\n', start);
|
|
287
|
+
if (end < 0) end = text.length;
|
|
288
|
+
lineNo += 1;
|
|
289
|
+
const line = text.slice(start, end);
|
|
290
|
+
start = end + 1;
|
|
291
|
+
const q = nquadsLineToQuad(line, lineNo);
|
|
292
|
+
if (q) yield q;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Every quad as an array. */
|
|
297
|
+
toArray() {
|
|
298
|
+
this._open('toArray');
|
|
299
|
+
if (!this._quads) this._quads = Object.freeze([...this]);
|
|
300
|
+
return this._quads.slice();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// A quad pattern for the ops datasetMatch and datasetMatchCount: four
|
|
304
|
+
// N-Triples term tokens, '' for an unbound position, 'DEFAULT' for the
|
|
305
|
+
// default graph.
|
|
306
|
+
_pattern(subject, predicate, object, graph) {
|
|
307
|
+
const tok = (t) => (t == null ? '' : termToNQuads(t));
|
|
308
|
+
const g = graph == null ? '' : (graph.termType === 'DefaultGraph' ? 'DEFAULT' : termToNQuads(graph));
|
|
309
|
+
return JSON.stringify([tok(subject), tok(predicate), tok(object), g]);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** RDF/JS DatasetCore.has: one indexed lookup in the engine. */
|
|
313
|
+
has(quad) {
|
|
314
|
+
this._open('has');
|
|
315
|
+
const r = envelope(this._ctx.e.datasetMatchCount(this._handle,
|
|
316
|
+
this._pattern(quad.subject, quad.predicate, quad.object, quad.graph || { termType: 'DefaultGraph' })), 'has');
|
|
317
|
+
return r.count > 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* RDF/JS DatasetCore.match: the matching quads as a new Dataset, found
|
|
322
|
+
* through the engine's indexes. `null`/`undefined` matches any term.
|
|
323
|
+
*/
|
|
324
|
+
match(subject, predicate, object, graph) {
|
|
325
|
+
this._open('match');
|
|
326
|
+
const r = envelope(this._ctx.e.datasetMatch(this._handle,
|
|
327
|
+
this._pattern(subject, predicate, object, graph)), 'match');
|
|
328
|
+
return this._ctx.wrap(r, this._prefixes);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* RDF/JS DatasetCore.add: add one quad (set semantics). A blank node is
|
|
333
|
+
* the node of that label in this Dataset. Each call changes the engine
|
|
334
|
+
* copy and costs time in the dataset size; for many quads use update()
|
|
335
|
+
* or parse().
|
|
336
|
+
*/
|
|
337
|
+
add(quad) {
|
|
338
|
+
this._open('add');
|
|
339
|
+
const r = envelope(this._ctx.e.datasetAddQuads(this._handle, quadToNQuads(quad)), 'add');
|
|
340
|
+
this._changed(r.count);
|
|
341
|
+
return this;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** RDF/JS DatasetCore.delete: remove one quad. */
|
|
345
|
+
delete(quad) {
|
|
346
|
+
this._open('delete');
|
|
347
|
+
const r = envelope(this._ctx.e.datasetDeleteQuads(this._handle, quadToNQuads(quad)), 'delete');
|
|
348
|
+
this._changed(r.count);
|
|
349
|
+
return this;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Release the engine copy. Idempotent. */
|
|
353
|
+
dispose() {
|
|
354
|
+
if (this._disposed) return;
|
|
355
|
+
this._disposed = true;
|
|
356
|
+
this._nquads = null;
|
|
357
|
+
this._quads = null;
|
|
358
|
+
if (this._ctx.registry) this._ctx.registry.unregister(this);
|
|
359
|
+
try { this._ctx.e.datasetClose(this._handle); } catch (_err) { /* engine gone */ }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** `using ds = await parse(...)` disposes at the end of the block. */
|
|
363
|
+
[Symbol.dispose]() { this.dispose(); }
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* A FinalizationRegistry that closes the engine handle of a Dataset that is
|
|
368
|
+
* collected without dispose(). Null where the platform has none.
|
|
369
|
+
*/
|
|
370
|
+
function makeRegistry() {
|
|
371
|
+
if (typeof FinalizationRegistry !== 'function') return null;
|
|
372
|
+
return new FinalizationRegistry(({ e, handle }) => {
|
|
373
|
+
try { e.datasetClose(handle); } catch (_err) { /* engine gone */ }
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export { Dataset, INTERNAL, checkOptions, makeRegistry, WRITER_TAGS, termFromSrj };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Host stores for the durable update log (deltaLog* in lib/api.mjs; hub
|
|
2
|
+
// post 18; https://github.com/danbri/factoidal/issues/728).
|
|
3
|
+
//
|
|
4
|
+
// A host store keeps, for each log name, the records in commit order. A
|
|
5
|
+
// record is { seq, epoch, hex }: `hex` is the hexadecimal text of one DLB1
|
|
6
|
+
// record as the engine wrote it (op deltaLogEncode,
|
|
7
|
+
// formal/lean4/Wasm/Ops/DeltaLog.lean). The store does not read the hex.
|
|
8
|
+
// The byte format, the checksums, the torn-record rule and the merge are in
|
|
9
|
+
// Lean (formal/lean4/L4Factoidal/Storage/BrowserLog.lean); a host store
|
|
10
|
+
// only stores and loads text.
|
|
11
|
+
//
|
|
12
|
+
// open(name) create the log if it is absent
|
|
13
|
+
// append(name, makeRecord) seq := the number of records; makeRecord(seq)
|
|
14
|
+
// (synchronous) gives {hex, epoch}; the record
|
|
15
|
+
// is committed with that seq, atomically
|
|
16
|
+
// readAll(name) the hex texts, in seq order
|
|
17
|
+
// destroy(name) remove the log
|
|
18
|
+
// corruptLast(name) TEST HOOK: cut the last 8 hex digits (4
|
|
19
|
+
// bytes) of the last record, as a torn write
|
|
20
|
+
// would; false when the log is empty
|
|
21
|
+
//
|
|
22
|
+
// No Node built-ins: the browser imports this file as it is.
|
|
23
|
+
|
|
24
|
+
const DELTA_STORE = 'deltaBatches';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The records in the memory of this process. The default store of the Node
|
|
28
|
+
* entry: the records live as long as the process. Pass another store to
|
|
29
|
+
* buildApi() (driver.deltaLogStore) to keep them elsewhere.
|
|
30
|
+
*/
|
|
31
|
+
export function memoryDeltaLogStore() {
|
|
32
|
+
const logs = new Map();
|
|
33
|
+
const recordsOf = (name) => {
|
|
34
|
+
if (!logs.has(name)) logs.set(name, []);
|
|
35
|
+
return logs.get(name);
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
kind: 'memory',
|
|
39
|
+
async open(name) {
|
|
40
|
+
recordsOf(name);
|
|
41
|
+
},
|
|
42
|
+
async append(name, makeRecord) {
|
|
43
|
+
const records = recordsOf(name);
|
|
44
|
+
const seq = records.length;
|
|
45
|
+
const rec = makeRecord(seq);
|
|
46
|
+
records.push({ seq, epoch: rec.epoch, hex: rec.hex });
|
|
47
|
+
return { seq };
|
|
48
|
+
},
|
|
49
|
+
async readAll(name) {
|
|
50
|
+
return (logs.get(name) || []).map((r) => r.hex);
|
|
51
|
+
},
|
|
52
|
+
async destroy(name) {
|
|
53
|
+
logs.delete(name);
|
|
54
|
+
},
|
|
55
|
+
async corruptLast(name) {
|
|
56
|
+
const records = logs.get(name);
|
|
57
|
+
if (!records || records.length === 0) return false;
|
|
58
|
+
const last = records[records.length - 1];
|
|
59
|
+
last.hex = last.hex.slice(0, Math.max(0, last.hex.length - 8));
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function indexedDbFactory() {
|
|
66
|
+
const factory = globalThis.indexedDB;
|
|
67
|
+
if (!factory) {
|
|
68
|
+
throw new Error('deltaLog: this runtime has no indexedDB; the browser entry keeps the log in IndexedDB');
|
|
69
|
+
}
|
|
70
|
+
return factory;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function idbOpen(dbName) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const req = indexedDbFactory().open(dbName, 1);
|
|
76
|
+
req.onupgradeneeded = () => {
|
|
77
|
+
const db = req.result;
|
|
78
|
+
if (!db.objectStoreNames.contains(DELTA_STORE)) {
|
|
79
|
+
db.createObjectStore(DELTA_STORE, { keyPath: 'seq' });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
req.onsuccess = () => resolve(req.result);
|
|
83
|
+
req.onerror = () => reject(req.error || new Error('indexedDB.open failed'));
|
|
84
|
+
req.onblocked = () => reject(new Error('indexedDB.open blocked (another tab holds an open connection at an older version)'));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function requestDone(req) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
req.onsuccess = () => resolve(req.result);
|
|
91
|
+
req.onerror = () => reject(req.error || new Error('IndexedDB request failed'));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function transactionDone(tx) {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
tx.oncomplete = () => resolve();
|
|
98
|
+
tx.onerror = () => reject(tx.error || new Error('IndexedDB transaction failed'));
|
|
99
|
+
tx.onabort = () => reject(tx.error || new Error('IndexedDB transaction aborted'));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The records in IndexedDB: one database for each log name, one object
|
|
105
|
+
* store 'deltaBatches' keyed by seq, one object { seq, epoch, hex } for each
|
|
106
|
+
* committed update. The default store of the browser entries.
|
|
107
|
+
*
|
|
108
|
+
* append() counts the records, calls makeRecord and adds the record in ONE
|
|
109
|
+
* readwrite transaction. IndexedDB runs the readwrite transactions of one
|
|
110
|
+
* object store one after another, so two tabs that append at the same time
|
|
111
|
+
* get different seq values; `add` (not `put`) refuses a seq that exists. The
|
|
112
|
+
* transaction asks for `durability: 'strict'`: Chrome 121 and later default
|
|
113
|
+
* to 'relaxed', where `complete` can fire before the data is on disk.
|
|
114
|
+
*/
|
|
115
|
+
export function indexedDbDeltaLogStore() {
|
|
116
|
+
return {
|
|
117
|
+
kind: 'indexedDB',
|
|
118
|
+
async open(name) {
|
|
119
|
+
const db = await idbOpen(name);
|
|
120
|
+
db.close();
|
|
121
|
+
},
|
|
122
|
+
async append(name, makeRecord) {
|
|
123
|
+
const db = await idbOpen(name);
|
|
124
|
+
try {
|
|
125
|
+
const tx = db.transaction(DELTA_STORE, 'readwrite', { durability: 'strict' });
|
|
126
|
+
const done = transactionDone(tx);
|
|
127
|
+
const store = tx.objectStore(DELTA_STORE);
|
|
128
|
+
let seq;
|
|
129
|
+
let failure = null;
|
|
130
|
+
const countReq = store.count();
|
|
131
|
+
countReq.onsuccess = () => {
|
|
132
|
+
seq = countReq.result;
|
|
133
|
+
try {
|
|
134
|
+
// Synchronous: the transaction stays active inside this callback.
|
|
135
|
+
const rec = makeRecord(seq);
|
|
136
|
+
store.add({ seq, epoch: rec.epoch, hex: rec.hex });
|
|
137
|
+
} catch (err) {
|
|
138
|
+
failure = err;
|
|
139
|
+
tx.abort();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
try {
|
|
143
|
+
await done;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
throw failure || err;
|
|
146
|
+
}
|
|
147
|
+
return { seq };
|
|
148
|
+
} finally {
|
|
149
|
+
db.close();
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
async readAll(name) {
|
|
153
|
+
const db = await idbOpen(name);
|
|
154
|
+
try {
|
|
155
|
+
const all = await requestDone(
|
|
156
|
+
db.transaction(DELTA_STORE, 'readonly').objectStore(DELTA_STORE).getAll());
|
|
157
|
+
all.sort((a, b) => a.seq - b.seq);
|
|
158
|
+
return all.map((r) => r.hex);
|
|
159
|
+
} finally {
|
|
160
|
+
db.close();
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
async destroy(name) {
|
|
164
|
+
await new Promise((resolve, reject) => {
|
|
165
|
+
const req = indexedDbFactory().deleteDatabase(name);
|
|
166
|
+
req.onsuccess = () => resolve();
|
|
167
|
+
req.onerror = () => reject(req.error || new Error('indexedDB.deleteDatabase failed'));
|
|
168
|
+
req.onblocked = () => reject(new Error('indexedDB.deleteDatabase blocked (another open connection)'));
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
async corruptLast(name) {
|
|
172
|
+
const db = await idbOpen(name);
|
|
173
|
+
try {
|
|
174
|
+
const all = await requestDone(
|
|
175
|
+
db.transaction(DELTA_STORE, 'readonly').objectStore(DELTA_STORE).getAll());
|
|
176
|
+
if (all.length === 0) return false;
|
|
177
|
+
all.sort((a, b) => a.seq - b.seq);
|
|
178
|
+
const last = all[all.length - 1];
|
|
179
|
+
const tx = db.transaction(DELTA_STORE, 'readwrite');
|
|
180
|
+
const done = transactionDone(tx);
|
|
181
|
+
tx.objectStore(DELTA_STORE).put({
|
|
182
|
+
seq: last.seq, epoch: last.epoch, hex: last.hex.slice(0, Math.max(0, last.hex.length - 8)),
|
|
183
|
+
});
|
|
184
|
+
await done;
|
|
185
|
+
return true;
|
|
186
|
+
} finally {
|
|
187
|
+
db.close();
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|