@factoidal/core 0.10.0 → 0.11.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 +114 -1
- package/README.md +7 -1
- package/bin/ext.mjs +16 -20
- package/bin/factoidal.mjs +172 -69
- package/browser.d.ts +4 -0
- package/browser.js +4 -0
- package/fn.d.ts +1 -1
- package/fn.js +34 -97
- package/index.d.ts +22 -1
- package/index.mjs +4 -0
- package/l4-assets/l4factoidal.js +1 -1
- package/l4-assets/l4factoidal.mjs +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +6 -6
- package/lib/api.mjs +155 -118
- package/lib/dataset.mjs +128 -34
- package/lib/lean-entry.mjs +23 -14
- package/lib/version.mjs +1 -1
- package/lws/README.md +46 -6
- package/lws/persist.mjs +78 -0
- package/lws/server.mjs +101 -16
- package/package.json +1 -1
- package/rdfjs.mjs +149 -226
- package/solid/client/index.mjs +25 -9
- package/solid/server/index.mjs +93 -15
package/lib/api.mjs
CHANGED
|
@@ -22,66 +22,36 @@
|
|
|
22
22
|
// JSDoc note "entry_jsoo.ml's X" below names that lineage. The Lean op of
|
|
23
23
|
// the same name serves X now (formal/lean4/Wasm/Dispatch.lean lists them).
|
|
24
24
|
|
|
25
|
-
import { dataFactory, termFromSrj,
|
|
25
|
+
import { dataFactory, termFromSrj, termToJson, quadToJson, _setTextEngine } from '../rdfjs.mjs';
|
|
26
26
|
import { Dataset, INTERNAL, checkOptions, makeRegistry, WRITER_TAGS } from './dataset.mjs';
|
|
27
27
|
import { version as packageVersion } from './version.mjs';
|
|
28
28
|
import { openGeneration, readRegion, manifestKeys, hexOfBytes } from './store.mjs';
|
|
29
29
|
import { fullEngineMessage } from './lean-entry.mjs';
|
|
30
30
|
import { memoryDeltaLogStore } from './delta-log-hosts.mjs';
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
ntriples: 'nt',
|
|
36
|
-
nt: 'nt',
|
|
37
|
-
nquads: 'nq',
|
|
38
|
-
nq: 'nq',
|
|
39
|
-
trig: 'trig',
|
|
40
|
-
rdfxml: 'rdf',
|
|
41
|
-
'rdf-xml': 'rdf',
|
|
42
|
-
rdf: 'rdf',
|
|
43
|
-
jsonld: 'jsonld',
|
|
44
|
-
'json-ld': 'jsonld',
|
|
45
|
-
// RDF 1.2 opt-in: these select the engine's RDF 1.2 parsers (triple
|
|
46
|
-
// terms <<( s p o )>>, ~ reifiers, {| |} annotations, VERSION,
|
|
47
|
-
// directional literals "x"@lang--dir; Wasm/Ops/Parse.lean's
|
|
48
|
-
// `Mode.rdf12`); the plain names above stay RDF 1.1.
|
|
49
|
-
turtle12: 'ttl12',
|
|
50
|
-
ttl12: 'ttl12',
|
|
51
|
-
ntriples12: 'nt12',
|
|
52
|
-
nt12: 'nt12',
|
|
53
|
-
nquads12: 'nq12',
|
|
54
|
-
nq12: 'nq12',
|
|
55
|
-
trig12: 'trig12',
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Canonical format tag for the engine's parse ops (parseToDatasetJson,
|
|
59
|
-
// datasetOpen); the *12 tags select the RDF 1.2 mode.
|
|
60
|
-
const DATA_FORMAT_TAG = {
|
|
61
|
-
ttl: 'turtle', nt: 'ntriples', nq: 'nquads', trig: 'trig', rdf: 'rdfxml',
|
|
62
|
-
jsonld: 'jsonld',
|
|
63
|
-
ttl12: 'turtle12', nt12: 'ntriples12', nq12: 'nquads12', trig12: 'trig12',
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const ENTAIL_VALUES = new Set(['none', 'RDFS', 'OWL-RL', 'x-rdfscore', 'x-rdfsplus']);
|
|
67
|
-
|
|
68
|
-
// queryRaw's options (op queryRaw, Wasm/Ops/QueryExtras.lean).
|
|
69
|
-
const RAW_OUTPUT_FORMATS = new Set(['json', 'csv', 'tsv', 'xml', 'table', 'ntriples']);
|
|
70
|
-
const RAW_DATA_FORMATS = {
|
|
71
|
-
turtle: 'turtle', ttl: 'turtle', ntriples: 'ntriples', nt: 'ntriples',
|
|
72
|
-
nquads: 'nquads', nq: 'nquads', trig: 'trig', rdfxml: 'rdfxml',
|
|
73
|
-
'rdf-xml': 'rdfxml', rdf: 'rdfxml',
|
|
74
|
-
};
|
|
32
|
+
// The format names, regimes and query outputs are the engine's tables (op
|
|
33
|
+
// engineTables, formal/lean4/Wasm/Ops/Handles.lean), read once per engine:
|
|
34
|
+
// no host copy (https://github.com/danbri/factoidal/issues/894 item 5).
|
|
75
35
|
|
|
76
|
-
|
|
36
|
+
// The parse tag a format name selects, from the engine's table.
|
|
37
|
+
function parseTagOf(tables, fmt) {
|
|
77
38
|
const key = String(fmt || 'turtle').toLowerCase();
|
|
78
|
-
|
|
39
|
+
const tag = tables.parseFormats[key];
|
|
40
|
+
if (tag === undefined) {
|
|
79
41
|
throw new TypeError(
|
|
80
|
-
`Unknown format '${fmt}'. Expected one of: ` +
|
|
81
|
-
Object.keys(DATA_FORMAT_EXT).join(', ')
|
|
82
|
-
);
|
|
42
|
+
`Unknown format '${fmt}'. Expected one of: ` + Object.keys(tables.parseFormats).join(', '));
|
|
83
43
|
}
|
|
84
|
-
return
|
|
44
|
+
return tag;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The engine's refusal of a regime name (table refusedRegimePrefixes), or null.
|
|
48
|
+
function iklRefusalOf(tables, who, regime) {
|
|
49
|
+
if (regime == null) return null;
|
|
50
|
+
const r = String(regime);
|
|
51
|
+
for (const [prefix, message] of Object.entries(tables.refusedRegimePrefixes)) {
|
|
52
|
+
if (r.toLowerCase().startsWith(prefix)) return message.replace('WHO', who).replace('REGIME', r);
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
85
55
|
}
|
|
86
56
|
|
|
87
57
|
// Where a function takes an options object, a bare string names the
|
|
@@ -114,16 +84,13 @@ function missingOpError(what, e, op) {
|
|
|
114
84
|
'current build.');
|
|
115
85
|
}
|
|
116
86
|
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
.replace(/#[^\n]*/g, ' ');
|
|
125
|
-
const m = cleaned.match(/\b(select|ask|construct|describe)\b/i);
|
|
126
|
-
return m ? m[1].toLowerCase() : 'select';
|
|
87
|
+
// The query form, from the engine's SPARQL parser (op queryForm). A query
|
|
88
|
+
// the parser rejects answers 'select'; the query op then reports the parse
|
|
89
|
+
// error itself.
|
|
90
|
+
function queryFormOf(e, sparql) {
|
|
91
|
+
if (typeof e.queryForm !== 'function') return 'select';
|
|
92
|
+
const r = JSON.parse(e.queryForm(String(sparql)));
|
|
93
|
+
return r.ok ? r.kind : 'select';
|
|
127
94
|
}
|
|
128
95
|
|
|
129
96
|
// Bytes for an op that reads the byte region (queryHdt, storeOpen): a
|
|
@@ -585,15 +552,16 @@ function buildApi(driver) {
|
|
|
585
552
|
return out;
|
|
586
553
|
}
|
|
587
554
|
|
|
588
|
-
// A ShEx focus/shape-label argument is
|
|
589
|
-
// or "_:label"
|
|
590
|
-
// or an RDF/JS term (NamedNode/BlankNode); accepting
|
|
591
|
-
// caller pass a term straight out of a query() binding.
|
|
555
|
+
// A ShEx focus/shape-label argument is a string in N-Triples term syntax
|
|
556
|
+
// ("<http://...>" or "_:label"), which the engine reads with its one
|
|
557
|
+
// term-string reader, or an RDF/JS term (NamedNode/BlankNode); accepting
|
|
558
|
+
// both lets a caller pass a term straight out of a query() binding.
|
|
592
559
|
function shexTermToString(t, who) {
|
|
593
|
-
if (typeof t === 'string') return t;
|
|
560
|
+
if (typeof t === 'string') return JSON.stringify(termToJson(t));
|
|
594
561
|
if (t && typeof t.termType === 'string') {
|
|
595
|
-
if (t.termType === 'BlankNode'
|
|
596
|
-
|
|
562
|
+
if (t.termType === 'BlankNode' || t.termType === 'NamedNode') {
|
|
563
|
+
return JSON.stringify(termToJson(t)); // the engine writes the node
|
|
564
|
+
}
|
|
597
565
|
throw new TypeError(`${who}: expected an IRI or blank node term`);
|
|
598
566
|
}
|
|
599
567
|
throw new TypeError(`${who}: expected a string or an RDF/JS term`);
|
|
@@ -613,9 +581,19 @@ function buildApi(driver) {
|
|
|
613
581
|
async function ctx() {
|
|
614
582
|
const e = await entry();
|
|
615
583
|
if (ctxCache && ctxCache.e === e) return ctxCache;
|
|
584
|
+
let tablesCache = null;
|
|
616
585
|
const c = {
|
|
617
586
|
e,
|
|
618
587
|
registry,
|
|
588
|
+
tables() {
|
|
589
|
+
if (!tablesCache) {
|
|
590
|
+
requireEntryFn(e, 'engineTables', 'format and regime tables');
|
|
591
|
+
tablesCache = entryResult(e.engineTables(), 'engineTables').tables;
|
|
592
|
+
for (const k of Object.keys(WRITER_TAGS)) delete WRITER_TAGS[k];
|
|
593
|
+
Object.assign(WRITER_TAGS, tablesCache.writerFormats);
|
|
594
|
+
}
|
|
595
|
+
return tablesCache;
|
|
596
|
+
},
|
|
619
597
|
missing: (what, op) => missingOpError(what, e, op),
|
|
620
598
|
wrap(r, prefixes, extra) {
|
|
621
599
|
const { ok: _ok, handle, count, prefixes: own, ...info } = r;
|
|
@@ -638,16 +616,34 @@ function buildApi(driver) {
|
|
|
638
616
|
async closure(ds, regime) { return closureOnHandle(c, ds, regime); },
|
|
639
617
|
};
|
|
640
618
|
ctxCache = c;
|
|
619
|
+
installTextEngine(e);
|
|
641
620
|
return c;
|
|
642
621
|
}
|
|
643
622
|
|
|
623
|
+
// rdfjs.mjs's N-Quads text functions (nquadsToQuads, quadsToNQuads,
|
|
624
|
+
// termToNQuads, DatasetCore.fromNQuads/toNQuads) call these engine ops.
|
|
625
|
+
function installTextEngine(e) {
|
|
626
|
+
if (typeof e.parseQuadsJson !== 'function') return;
|
|
627
|
+
_setTextEngine({
|
|
628
|
+
parseQuads(text) {
|
|
629
|
+
return parseResult(e.parseQuadsJson(text, 'nquads12', ''), 'nquadsToQuads', text).quads;
|
|
630
|
+
},
|
|
631
|
+
writeQuads(quadsJson) {
|
|
632
|
+
return entryResult(e.quadsToNQuads(JSON.stringify(quadsJson)), 'quadsToNQuads').nquads;
|
|
633
|
+
},
|
|
634
|
+
termTokens(termsJson) {
|
|
635
|
+
return entryResult(e.termsToNTriples(JSON.stringify(termsJson)), 'termToNQuads').tokens;
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
644
640
|
// The parse tag of `format`, or the engine's inference when it is omitted.
|
|
645
641
|
function formatTagOf(c, text, format, who) {
|
|
646
642
|
if (format === undefined || format === null || format === '') {
|
|
647
643
|
requireEntryFn(c.e, 'detectFormat', `${who} (format inference)`);
|
|
648
644
|
return { tag: entryResult(c.e.detectFormat(text), who).format, inferred: true };
|
|
649
645
|
}
|
|
650
|
-
return { tag:
|
|
646
|
+
return { tag: parseTagOf(c.tables(), format), inferred: false };
|
|
651
647
|
}
|
|
652
648
|
|
|
653
649
|
// Open `text` as a Dataset. A parse error names the format and says
|
|
@@ -714,17 +710,11 @@ function buildApi(driver) {
|
|
|
714
710
|
async function queryDatasetHandle(c, ds, sparql, opts) {
|
|
715
711
|
if (typeof sparql !== 'string') throw new TypeError('query: sparql must be a string');
|
|
716
712
|
const entail = opts.entail || 'none';
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
'was deleted on 2026-08-26 as collateral of the IKL-to-RDF ' +
|
|
723
|
-
'projection purge (danbri/factoidal#626). This is not a ' +
|
|
724
|
-
'policy exclusion.');
|
|
725
|
-
}
|
|
726
|
-
if (!ENTAIL_VALUES.has(entail)) {
|
|
727
|
-
throw new TypeError(`query: entail must be one of ${[...ENTAIL_VALUES].join(', ')}`);
|
|
713
|
+
const tables = c.tables();
|
|
714
|
+
const ikl = iklRefusalOf(tables, 'query', entail);
|
|
715
|
+
if (ikl) throw new TypeError(ikl);
|
|
716
|
+
if (!tables.entailRegimes.includes(entail)) {
|
|
717
|
+
throw new TypeError(`query: entail must be one of ${tables.entailRegimes.join(', ')}`);
|
|
728
718
|
}
|
|
729
719
|
if (opts.version !== undefined && !['1.1', '1.2'].includes(String(opts.version))) {
|
|
730
720
|
throw new TypeError("query: version must be '1.1' or '1.2'");
|
|
@@ -739,10 +729,10 @@ function buildApi(driver) {
|
|
|
739
729
|
"query: entail/output and SPARQL 1.2 cannot be combined; that path reads SPARQL 1.1");
|
|
740
730
|
}
|
|
741
731
|
const output = opts.output || 'json';
|
|
742
|
-
if (!
|
|
743
|
-
throw new TypeError(`query: output must be one of ${
|
|
732
|
+
if (!tables.queryOutputs.includes(output)) {
|
|
733
|
+
throw new TypeError(`query: output must be one of ${tables.queryOutputs.join(', ')}`);
|
|
744
734
|
}
|
|
745
|
-
const form =
|
|
735
|
+
const form = queryFormOf(e, sparql);
|
|
746
736
|
const graphForm = form === 'construct' || form === 'describe';
|
|
747
737
|
requireEntryFn(e, 'datasetQueryRaw', `query {entail/output}`);
|
|
748
738
|
if (opts.output === undefined) {
|
|
@@ -791,14 +781,8 @@ function buildApi(driver) {
|
|
|
791
781
|
c.e.datasetShexValidate(ds.handle, schema, focusStr, shapeStr), 'shexValidate').verdict;
|
|
792
782
|
}
|
|
793
783
|
|
|
794
|
-
const CLOSURE_REGIMES = {
|
|
795
|
-
rdfs: 'rdfs', 'owl-rl': 'owl-rl', owlrl: 'owl-rl', owl_rl: 'owl-rl',
|
|
796
|
-
'rho-df': 'rho-df', rhodf: 'rho-df', 'core-rdfs': 'rho-df',
|
|
797
|
-
'rdfs-plus': 'rdfs-plus', rdfsplus: 'rdfs-plus',
|
|
798
|
-
};
|
|
799
|
-
|
|
800
784
|
async function closureOnHandle(c, ds, regime) {
|
|
801
|
-
const r0 =
|
|
785
|
+
const r0 = c.tables().closureAliases[String(regime || '').toLowerCase()];
|
|
802
786
|
if (!r0) {
|
|
803
787
|
throw new TypeError(
|
|
804
788
|
`closure: regime must be one of rdfs, owl-rl, rho-df, rdfs-plus (got '${regime}')`);
|
|
@@ -892,15 +876,11 @@ function buildApi(driver) {
|
|
|
892
876
|
if (typeof sparql !== 'string') {
|
|
893
877
|
throw new TypeError('queryHdt: sparql must be a string');
|
|
894
878
|
}
|
|
895
|
-
const form = sniffQueryForm(sparql);
|
|
896
|
-
if (form === 'construct' || form === 'describe') {
|
|
897
|
-
throw new TypeError(
|
|
898
|
-
`queryHdt: ${form.toUpperCase()} is not supported over an HDT file (SELECT/ASK only)`);
|
|
899
|
-
}
|
|
900
879
|
const e = await entry();
|
|
901
880
|
requireEntryFn(e, 'queryHdt', 'queryHdt');
|
|
902
881
|
const r = entryResult(e.queryHdt(bytesToUint8Array(hdtBytes, 'queryHdt'), sparql), 'queryHdt');
|
|
903
882
|
if (r.kind === 'ask') return r.boolean;
|
|
883
|
+
if (r.kind === 'construct' || r.kind === 'describe') return (await ctx()).fromNQuads(r.nquads, {});
|
|
904
884
|
return bindingsFromSrj(r.srj);
|
|
905
885
|
}
|
|
906
886
|
|
|
@@ -942,9 +922,10 @@ function buildApi(driver) {
|
|
|
942
922
|
['format', 'version', 'prefixes', 'literalShorthand', 'useNativeTypes',
|
|
943
923
|
'useRdfType', 'rdfDirection', 'inputFormat', 'baseIRI']);
|
|
944
924
|
const { inputFormat, baseIRI, ...writeOpts } = opts;
|
|
945
|
-
|
|
925
|
+
const writerTags = (await ctx()).tables().writerFormats;
|
|
926
|
+
if (writeOpts.format !== undefined && !writerTags[String(writeOpts.format).toLowerCase()]) {
|
|
946
927
|
throw new TypeError(`serialize: unknown format '${writeOpts.format}'. Known formats: ` +
|
|
947
|
-
[...new Set(Object.values(
|
|
928
|
+
[...new Set(Object.values(writerTags))].join(', '));
|
|
948
929
|
}
|
|
949
930
|
return withDataset(data, { format: inputFormat, baseIRI }, 'serialize',
|
|
950
931
|
(_c, ds) => ds.serialize(writeOpts));
|
|
@@ -965,24 +946,24 @@ function buildApi(driver) {
|
|
|
965
946
|
* The named graphs of a Dataset, each as a Dataset (default graph
|
|
966
947
|
* excluded, first-seen order).
|
|
967
948
|
* @param {Dataset} dataset
|
|
968
|
-
* @returns {Array<[
|
|
949
|
+
* @returns {Array<[name: NamedNode|BlankNode, graph: Dataset]>}
|
|
969
950
|
*/
|
|
970
951
|
function graphs(dataset) {
|
|
971
952
|
if (!(dataset instanceof Dataset)) throw new TypeError('graphs: expected a Dataset');
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
return out;
|
|
953
|
+
dataset.size; // throws the disposed error
|
|
954
|
+
const c = dataset._ctx;
|
|
955
|
+
requireEntryFn(c.e, 'datasetGraphNames', 'graphs');
|
|
956
|
+
const names = entryResult(c.e.datasetGraphNames(dataset.handle), 'graphs').graphs;
|
|
957
|
+
return names.map((g) => {
|
|
958
|
+
const term = termFromSrj(g);
|
|
959
|
+
return [term, dataset.match(null, null, null, term)];
|
|
960
|
+
});
|
|
981
961
|
}
|
|
982
962
|
|
|
983
963
|
/**
|
|
984
964
|
* RDFC-1.0 canonical N-Quads of one graph: every quad's graph name is
|
|
985
|
-
* dropped first
|
|
965
|
+
* dropped first (op datasetGraphAsDefault with the union of the graphs).
|
|
966
|
+
* Text, not a digest.
|
|
986
967
|
* @param {Dataset} datasetOrGraph
|
|
987
968
|
* @returns {Promise<string>}
|
|
988
969
|
*/
|
|
@@ -990,9 +971,59 @@ function buildApi(driver) {
|
|
|
990
971
|
if (!(datasetOrGraph instanceof Dataset)) {
|
|
991
972
|
throw new TypeError('canonicalHash: expected a Dataset (e.g. one entry of graphs())');
|
|
992
973
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
974
|
+
datasetOrGraph.size; // throws the disposed error
|
|
975
|
+
const c = datasetOrGraph._ctx;
|
|
976
|
+
requireEntryFn(c.e, 'datasetGraphAsDefault', 'canonicalHash');
|
|
977
|
+
const merged = c.wrap(entryResult(c.e.datasetGraphAsDefault(datasetOrGraph.handle,
|
|
978
|
+
JSON.stringify({ type: 'union' })), 'canonicalHash'), {});
|
|
979
|
+
try {
|
|
980
|
+
return merged.toString('rdfc');
|
|
981
|
+
} finally {
|
|
982
|
+
merged.dispose();
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Whether two datasets are isomorphic (RDF 1.1 Concepts 3.6 and 4; blank
|
|
988
|
+
* nodes inside RDF 1.2 triple terms included): the engine's
|
|
989
|
+
* Dataset.isomorphicOutcome (op datasetIsomorphic). Rejects when the
|
|
990
|
+
* blank-node search budget is exceeded.
|
|
991
|
+
* @param {Dataset|string|Array} a
|
|
992
|
+
* @param {Dataset|string|Array} b
|
|
993
|
+
* @param {{format?: string, baseIRI?: string}} [options] read text arguments
|
|
994
|
+
* @returns {Promise<boolean>}
|
|
995
|
+
*/
|
|
996
|
+
async function isomorphic(a, b, options) {
|
|
997
|
+
const opts = checkOptions('isomorphic', optionsOf('isomorphic', options), ['format', 'baseIRI']);
|
|
998
|
+
return withDataset(a, opts, 'isomorphic', (c, da) =>
|
|
999
|
+
withDataset(b, opts, 'isomorphic', (_c, db) => {
|
|
1000
|
+
requireEntryFn(c.e, 'datasetIsomorphic', 'isomorphic');
|
|
1001
|
+
return entryResult(c.e.datasetIsomorphic(da.handle, db.handle), 'isomorphic').isomorphic;
|
|
1002
|
+
}));
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* A Dataset holding RDF/JS quads (op datasetFromQuadsJson). Each term is
|
|
1007
|
+
* sent as its components; the engine reads no text.
|
|
1008
|
+
* @param {Iterable<object>} quads
|
|
1009
|
+
* @returns {Promise<Dataset>}
|
|
1010
|
+
*/
|
|
1011
|
+
async function fromQuads(quads) {
|
|
1012
|
+
const c = await ctx();
|
|
1013
|
+
requireEntryFn(c.e, 'datasetFromQuadsJson', 'fromQuads');
|
|
1014
|
+
const arr = [];
|
|
1015
|
+
for (const q of quads) arr.push(quadToJson(q));
|
|
1016
|
+
return c.wrap(entryResult(c.e.datasetFromQuadsJson(JSON.stringify(arr)), 'fromQuads'), {});
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/** Load the engine; afterwards the N-Quads text functions of `@factoidal/core/rdfjs` work. */
|
|
1020
|
+
async function ready() {
|
|
1021
|
+
await ctx();
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/** The engine's name tables (formats, regimes, query outputs, pack syntaxes). */
|
|
1025
|
+
async function engineTables() {
|
|
1026
|
+
return (await ctx()).tables();
|
|
996
1027
|
}
|
|
997
1028
|
|
|
998
1029
|
// A wasm module older than this package can lack an op: `e[fnName]` is
|
|
@@ -1051,7 +1082,8 @@ function buildApi(driver) {
|
|
|
1051
1082
|
*/
|
|
1052
1083
|
async function owlClosure(data, mode, options) {
|
|
1053
1084
|
const m = String(mode || '').toLowerCase();
|
|
1054
|
-
|
|
1085
|
+
const aliases = (await ctx()).tables().closureAliases;
|
|
1086
|
+
if (!['rdfs', 'owl-rl'].includes(aliases[m])) {
|
|
1055
1087
|
throw new TypeError(`owlClosure: mode must be 'RDFS' or 'OWL-RL' (got '${mode}')`);
|
|
1056
1088
|
}
|
|
1057
1089
|
return closure(data, m, options);
|
|
@@ -1984,19 +2016,20 @@ function buildApi(driver) {
|
|
|
1984
2016
|
const dataFormat = String(opts.dataFormat || 'turtle').toLowerCase();
|
|
1985
2017
|
const entail = opts.entail || 'none';
|
|
1986
2018
|
const output = opts.output || 'json';
|
|
1987
|
-
|
|
2019
|
+
const tables = (await ctx()).tables();
|
|
2020
|
+
if (!(dataFormat in tables.rawDataFormats)) {
|
|
1988
2021
|
throw new TypeError(`queryRaw: unknown dataFormat '${opts.dataFormat}'`);
|
|
1989
2022
|
}
|
|
1990
|
-
if (!
|
|
1991
|
-
throw new TypeError(`queryRaw: entail must be one of ${
|
|
2023
|
+
if (!tables.entailRegimes.includes(entail)) {
|
|
2024
|
+
throw new TypeError(`queryRaw: entail must be one of ${tables.entailRegimes.join(', ')}`);
|
|
1992
2025
|
}
|
|
1993
|
-
if (!
|
|
1994
|
-
throw new TypeError(`queryRaw: output must be one of ${
|
|
2026
|
+
if (!tables.queryOutputs.includes(output)) {
|
|
2027
|
+
throw new TypeError(`queryRaw: output must be one of ${tables.queryOutputs.join(', ')}`);
|
|
1995
2028
|
}
|
|
1996
2029
|
const e = await entry();
|
|
1997
2030
|
requireEntryFn(e, 'queryRaw', 'queryRaw');
|
|
1998
2031
|
const r = JSON.parse(e.queryRaw(dataString, queryString,
|
|
1999
|
-
JSON.stringify({ dataFormat:
|
|
2032
|
+
JSON.stringify({ dataFormat: tables.rawDataFormats[dataFormat], entail, output })));
|
|
2000
2033
|
if (!r.ok) {
|
|
2001
2034
|
const err = new Error('SPARQL query failed: ' + r.error);
|
|
2002
2035
|
err.stderr = String(r.error);
|
|
@@ -2280,6 +2313,10 @@ function buildApi(driver) {
|
|
|
2280
2313
|
deltaLogDestroy,
|
|
2281
2314
|
_deltaLogCorruptLastForTest,
|
|
2282
2315
|
queryRaw,
|
|
2316
|
+
isomorphic,
|
|
2317
|
+
fromQuads,
|
|
2318
|
+
ready,
|
|
2319
|
+
engineTables,
|
|
2283
2320
|
Dataset,
|
|
2284
2321
|
DatasetHandle,
|
|
2285
2322
|
Store,
|
|
@@ -2295,6 +2332,6 @@ function buildApi(driver) {
|
|
|
2295
2332
|
}
|
|
2296
2333
|
|
|
2297
2334
|
export {
|
|
2298
|
-
buildApi,
|
|
2335
|
+
buildApi, bindingsFromSrj, entryResult, parseResult,
|
|
2299
2336
|
utf16OffsetOf, missingOpError, ParseError, DatasetHandle, Store,
|
|
2300
2337
|
};
|
package/lib/dataset.mjs
CHANGED
|
@@ -15,21 +15,16 @@
|
|
|
15
15
|
// released by a FinalizationRegistry callback. Every method of a disposed
|
|
16
16
|
// Dataset throws a TypeError that names the method.
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { dataFactory, quadFromJson, quadToJson, termToJson, termFromSrj, Literal, NamedNode } from '../rdfjs.mjs';
|
|
19
19
|
|
|
20
20
|
const INTERNAL = Symbol('factoidal.Dataset.internal');
|
|
21
21
|
|
|
22
|
-
// Writer names (lower case) -> the engine's writer tag
|
|
22
|
+
// Writer names (lower case) -> the engine's writer tag: the engine's table
|
|
23
|
+
// (op engineTables, `writerFormats`), read once per engine. JSON-LD is served
|
|
23
24
|
// by the op datasetToJsonLd; the SPARQL results formats are query outputs.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
};
|
|
25
|
+
// `WRITER_TAGS` stays exported for callers that read it; it is filled from
|
|
26
|
+
// the engine on the first Dataset.
|
|
27
|
+
const WRITER_TAGS = {};
|
|
33
28
|
|
|
34
29
|
const VERSIONS = { '1.1': '11', '1.2': '12' };
|
|
35
30
|
|
|
@@ -57,6 +52,84 @@ function envelope(jsonText, what) {
|
|
|
57
52
|
const SERIALIZE_OPTIONS = ['format', 'version', 'prefixes', 'literalShorthand',
|
|
58
53
|
'useNativeTypes', 'useRdfType', 'rdfDirection'];
|
|
59
54
|
|
|
55
|
+
// The quads of the compact answer of datasetQuadsJson: each distinct term is
|
|
56
|
+
// built once and shared (TermJson.compactCertified).
|
|
57
|
+
function quadsFromCompact(terms, ixs) {
|
|
58
|
+
const ts = terms.map(termFromSrj);
|
|
59
|
+
const dg = dataFactory.defaultGraph();
|
|
60
|
+
return ixs.map((ix) => dataFactory.quad(ts[ix[0]], ts[ix[1]], ts[ix[2]],
|
|
61
|
+
ix.length > 3 ? ts[ix[3]] : dg));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The region of op datasetQuadsBin, read as the engine wrote it
|
|
65
|
+
// (formal/lean4/L4Factoidal/RDF/QuadsBin.lean: `binSpec` is the layout,
|
|
66
|
+
// `decodeBin` the reader this function follows, `decodeBin_quadsBin` the
|
|
67
|
+
// theorem that the region reads back as exactly the quads). Every number is a
|
|
68
|
+
// u32 little-endian; each term is in the term codec of DLOG version 2, which
|
|
69
|
+
// names the term kind, the datatype IRI, the language tag and the base
|
|
70
|
+
// direction explicitly, so this function copies fields and decides nothing.
|
|
71
|
+
const UTF8 = new TextDecoder('utf-8', { fatal: true });
|
|
72
|
+
const DIRECTION = ['', 'ltr', 'rtl'];
|
|
73
|
+
|
|
74
|
+
function quadsFromRegion(bytes, out) {
|
|
75
|
+
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
76
|
+
if (bytes.length < 12 || v.getUint32(0, true) !== 0x31425146) {
|
|
77
|
+
throw new Error('iterate: the engine answered no datasetQuadsBin region');
|
|
78
|
+
}
|
|
79
|
+
const nTerms = v.getUint32(4, true);
|
|
80
|
+
const nQuads = v.getUint32(8, true);
|
|
81
|
+
let p = 12;
|
|
82
|
+
// UTF-8 bytes to a string; a short run of bytes below 0x80 (one UTF-16
|
|
83
|
+
// unit each) is copied without a TextDecoder call.
|
|
84
|
+
const str = () => {
|
|
85
|
+
const n = v.getUint32(p, true);
|
|
86
|
+
const a = p + 4;
|
|
87
|
+
p = a + n;
|
|
88
|
+
if (n <= 64) {
|
|
89
|
+
let i = a;
|
|
90
|
+
while (i < p && bytes[i] < 0x80) i++;
|
|
91
|
+
if (i === p) return String.fromCharCode.apply(null, bytes.subarray(a, p));
|
|
92
|
+
}
|
|
93
|
+
return UTF8.decode(bytes.subarray(a, p));
|
|
94
|
+
};
|
|
95
|
+
const datatypes = new Map();
|
|
96
|
+
const term = () => {
|
|
97
|
+
switch (bytes[p++]) {
|
|
98
|
+
case 0: return new NamedNode(str());
|
|
99
|
+
case 1: return dataFactory.blankNode(str());
|
|
100
|
+
case 2: {
|
|
101
|
+
const lexical = str();
|
|
102
|
+
const dt = str();
|
|
103
|
+
let datatype = datatypes.get(dt);
|
|
104
|
+
if (datatype === undefined) { datatype = new NamedNode(dt); datatypes.set(dt, datatype); }
|
|
105
|
+
const flag = bytes[p++];
|
|
106
|
+
const language = flag === 0 ? '' : str();
|
|
107
|
+
const direction = flag === 2 ? DIRECTION[bytes[p++]] : '';
|
|
108
|
+
return new Literal(lexical, language, datatype, direction);
|
|
109
|
+
}
|
|
110
|
+
case 3: {
|
|
111
|
+
const s = term();
|
|
112
|
+
const pr = term();
|
|
113
|
+
return dataFactory.quad(s, pr, term());
|
|
114
|
+
}
|
|
115
|
+
default: throw new Error('iterate: a term tag outside the DLOG version 2 codec');
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const ts = new Array(nTerms);
|
|
119
|
+
for (let i = 0; i < nTerms; i++) ts[i] = term();
|
|
120
|
+
const dg = dataFactory.defaultGraph();
|
|
121
|
+
for (let i = 0; i < nQuads; i++, p += 16) {
|
|
122
|
+
const g = v.getUint32(p + 12, true);
|
|
123
|
+
out.push(dataFactory.quad(ts[v.getUint32(p, true)], ts[v.getUint32(p + 4, true)],
|
|
124
|
+
ts[v.getUint32(p + 8, true)], g === 0 ? dg : ts[g - 1]));
|
|
125
|
+
}
|
|
126
|
+
return nQuads;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Quads of the first datasetQuadsBin call while iterating: an iteration that
|
|
130
|
+
// stops early reads this many; the rest come in one more call.
|
|
131
|
+
const FIRST_PAGE = 4096;
|
|
132
|
+
|
|
60
133
|
class Dataset {
|
|
61
134
|
/** @private Datasets come from parse(), csvwToRdf() and the other producers. */
|
|
62
135
|
constructor(token, ctx, handle, size, prefixes, extra) {
|
|
@@ -214,11 +287,12 @@ class Dataset {
|
|
|
214
287
|
_write(opts) {
|
|
215
288
|
const e = this._ctx.e;
|
|
216
289
|
const name = String(opts.format || 'nquads').toLowerCase();
|
|
217
|
-
const
|
|
290
|
+
const writerTags = this._ctx.tables().writerFormats;
|
|
291
|
+
const tag = writerTags[name];
|
|
218
292
|
if (!tag) {
|
|
219
293
|
throw new TypeError(
|
|
220
294
|
`Dataset.serialize: unknown format '${opts.format}'. Known formats: ` +
|
|
221
|
-
[...new Set(Object.values(
|
|
295
|
+
[...new Set(Object.values(writerTags))].join(', '));
|
|
222
296
|
}
|
|
223
297
|
let suffix = '';
|
|
224
298
|
if (opts.version !== undefined) {
|
|
@@ -273,40 +347,58 @@ class Dataset {
|
|
|
273
347
|
}
|
|
274
348
|
|
|
275
349
|
/**
|
|
276
|
-
* RDF/JS quads,
|
|
350
|
+
* RDF/JS quads, built from the engine's term objects (op datasetQuadsJson),
|
|
351
|
+
* in the line order of toNQuads(). No N-Quads text is read here.
|
|
277
352
|
* @returns {Iterator<object>}
|
|
278
353
|
*/
|
|
279
354
|
*[Symbol.iterator]() {
|
|
280
355
|
this._open('iterate');
|
|
281
356
|
if (this._quads) { yield* this._quads; return; }
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
357
|
+
const e = this._ctx.e;
|
|
358
|
+
if (typeof e.datasetQuadsBin !== 'function') { yield* this._allQuads(); return; }
|
|
359
|
+
// The engine's region (op datasetQuadsBin) in two calls, a first page and
|
|
360
|
+
// the rest; the whole list is cached when the rest is read.
|
|
361
|
+
const acc = [];
|
|
362
|
+
const first = e.datasetQuadsBin(this._handle, `{"offset":0,"limit":${FIRST_PAGE}}`);
|
|
363
|
+
quadsFromRegion(first.bytes, acc);
|
|
364
|
+
for (let i = 0; i < acc.length; i++) yield acc[i];
|
|
365
|
+
if (acc.length < first.envelope.total) {
|
|
366
|
+
const rest = e.datasetQuadsBin(this._handle, `{"offset":${acc.length}}`);
|
|
367
|
+
const from = acc.length;
|
|
368
|
+
quadsFromRegion(rest.bytes, acc);
|
|
369
|
+
for (let i = from; i < acc.length; i++) yield acc[i];
|
|
370
|
+
}
|
|
371
|
+
if (!this._quads) this._quads = Object.freeze(acc);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Every quad, read once and cached.
|
|
375
|
+
_allQuads() {
|
|
376
|
+
if (!this._quads) {
|
|
377
|
+
const e = this._ctx.e;
|
|
378
|
+
if (typeof e.datasetQuadsBin === 'function') {
|
|
379
|
+
const acc = [];
|
|
380
|
+
quadsFromRegion(e.datasetQuadsBin(this._handle, '').bytes, acc);
|
|
381
|
+
this._quads = Object.freeze(acc);
|
|
382
|
+
} else {
|
|
383
|
+
const r = envelope(e.datasetQuadsJson(this._handle, '{"compact":true}'), 'iterate');
|
|
384
|
+
this._quads = Object.freeze(r.ixs ? quadsFromCompact(r.terms, r.ixs) : r.quads.map((q) => quadFromJson(q)));
|
|
385
|
+
}
|
|
293
386
|
}
|
|
387
|
+
return this._quads;
|
|
294
388
|
}
|
|
295
389
|
|
|
296
390
|
/** Every quad as an array. */
|
|
297
391
|
toArray() {
|
|
298
392
|
this._open('toArray');
|
|
299
|
-
|
|
300
|
-
return this._quads.slice();
|
|
393
|
+
return this._allQuads().slice();
|
|
301
394
|
}
|
|
302
395
|
|
|
303
396
|
// A quad pattern for the ops datasetMatch and datasetMatchCount: four
|
|
304
|
-
//
|
|
305
|
-
// default graph.
|
|
397
|
+
// engine term objects, null for an unbound position, {type:'default'} for
|
|
398
|
+
// the default graph.
|
|
306
399
|
_pattern(subject, predicate, object, graph) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return JSON.stringify([tok(subject), tok(predicate), tok(object), g]);
|
|
400
|
+
return JSON.stringify([termToJson(subject), termToJson(predicate), termToJson(object),
|
|
401
|
+
termToJson(graph)]);
|
|
310
402
|
}
|
|
311
403
|
|
|
312
404
|
/** RDF/JS DatasetCore.has: one indexed lookup in the engine. */
|
|
@@ -336,7 +428,8 @@ class Dataset {
|
|
|
336
428
|
*/
|
|
337
429
|
add(quad) {
|
|
338
430
|
this._open('add');
|
|
339
|
-
const r = envelope(this._ctx.e.
|
|
431
|
+
const r = envelope(this._ctx.e.datasetAddQuadsJson(this._handle,
|
|
432
|
+
JSON.stringify([quadToJson(quad)])), 'add');
|
|
340
433
|
this._changed(r.count);
|
|
341
434
|
return this;
|
|
342
435
|
}
|
|
@@ -344,7 +437,8 @@ class Dataset {
|
|
|
344
437
|
/** RDF/JS DatasetCore.delete: remove one quad. */
|
|
345
438
|
delete(quad) {
|
|
346
439
|
this._open('delete');
|
|
347
|
-
const r = envelope(this._ctx.e.
|
|
440
|
+
const r = envelope(this._ctx.e.datasetDeleteQuadsJson(this._handle,
|
|
441
|
+
JSON.stringify([quadToJson(quad)])), 'delete');
|
|
348
442
|
this._changed(r.count);
|
|
349
443
|
return this;
|
|
350
444
|
}
|