eyeling 1.28.9 → 1.29.1
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/README.md +73 -2
- package/dist/browser/eyeling.browser.js +1284 -33
- package/dist/browser/index.mjs +10 -0
- package/eyeling.js +1284 -33
- package/index.d.ts +59 -0
- package/index.js +17 -0
- package/lib/cli.js +309 -33
- package/lib/engine.js +279 -0
- package/lib/entry.js +4 -0
- package/lib/rdfjs.js +1 -0
- package/lib/store.js +688 -0
- package/package.json +8 -3
- package/test/store.test.js +167 -0
- package/test/stream_messages.test.js +13 -0
- package/tools/bundle.js +10 -0
package/eyeling.js
CHANGED
|
@@ -5842,10 +5842,12 @@ function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes
|
|
|
5842
5842
|
|
|
5843
5843
|
let derived = [];
|
|
5844
5844
|
let outTriples = [];
|
|
5845
|
+
let queryDerived = [];
|
|
5845
5846
|
if (hasQueries) {
|
|
5846
5847
|
const res = engine.forwardChainAndCollectLogQueryConclusions(facts, frules, brules, qrules, { prefixes });
|
|
5847
5848
|
derived = res.derived;
|
|
5848
5849
|
outTriples = res.queryTriples;
|
|
5850
|
+
queryDerived = res.queryDerived || [];
|
|
5849
5851
|
} else {
|
|
5850
5852
|
const skipDerivedCollection = mayAutoRenderOutputStrings;
|
|
5851
5853
|
derived = engine.forwardChain(facts, frules, brules, null, {
|
|
@@ -5859,7 +5861,7 @@ function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes
|
|
|
5859
5861
|
const renderedOutputTriples = hasQueries ? outTriples : facts;
|
|
5860
5862
|
if (factsContainOutputStrings(renderedOutputTriples)) {
|
|
5861
5863
|
process.stdout.write(engine.collectOutputStringsFromFacts(renderedOutputTriples, prefixes));
|
|
5862
|
-
return;
|
|
5864
|
+
return { facts, derived, outTriples, queryDerived, queryMode: !!hasQueries };
|
|
5863
5865
|
}
|
|
5864
5866
|
|
|
5865
5867
|
const outPrefixEnv = outputPrefixes || prefixes;
|
|
@@ -5871,9 +5873,99 @@ function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes
|
|
|
5871
5873
|
} else {
|
|
5872
5874
|
for (const df of derived) console.log(engine.tripleToN3(df.fact, outPrefixEnv));
|
|
5873
5875
|
}
|
|
5876
|
+
return { facts, derived, outTriples, queryDerived, queryMode: !!hasQueries };
|
|
5877
|
+
}
|
|
5878
|
+
|
|
5879
|
+
async function createCliStore({ storeName, storePath = null, storeClear = false } = {}) {
|
|
5880
|
+
if (!storeName) return null;
|
|
5881
|
+
return engine.createFactStore({ name: storeName, path: storePath || undefined, clear: !!storeClear });
|
|
5882
|
+
}
|
|
5883
|
+
|
|
5884
|
+
async function persistRunResultToStore(store, runResult) {
|
|
5885
|
+
if (!store || !runResult) return;
|
|
5886
|
+
await store.batchAdd(runResult.facts || [], 'explicit');
|
|
5887
|
+
await store.batchAdd((runResult.derived || []).map((df) => df.fact), 'inferred');
|
|
5888
|
+
await store.batchAdd((runResult.queryDerived || []).map((df) => df.fact), 'inferred');
|
|
5889
|
+
await store.batchAdd(runResult.outTriples || [], 'inferred');
|
|
5890
|
+
}
|
|
5891
|
+
|
|
5892
|
+
function sourceLooksLikeLineBasedRdf(sourceLabel) {
|
|
5893
|
+
if (typeof sourceLabel !== 'string') return false;
|
|
5894
|
+
const clean = sourceLabel.split(/[?#]/, 1)[0].toLowerCase();
|
|
5895
|
+
return /\.(?:nt|nq)(?:\.gz|\.br|\.deflate)?$/.test(clean);
|
|
5896
|
+
}
|
|
5897
|
+
|
|
5898
|
+
function parseLineBasedRdfLine(line, { sourceLabel, lineNumber, rdfMode }) {
|
|
5899
|
+
const text = String(line || '');
|
|
5900
|
+
if (!text.trim() || /^\s*#/.test(text)) return [];
|
|
5901
|
+
const doc = parseN3Text(text, {
|
|
5902
|
+
baseIri: __sourceLabelToBaseIri(sourceLabel),
|
|
5903
|
+
label: `${sourceLabel}:${lineNumber}`,
|
|
5904
|
+
keepSourceArtifacts: false,
|
|
5905
|
+
sourceLocations: false,
|
|
5906
|
+
rdf: rdfMode,
|
|
5907
|
+
});
|
|
5908
|
+
return doc.triples || [];
|
|
5909
|
+
}
|
|
5910
|
+
|
|
5911
|
+
async function ingestLineBasedRdfSourceToStore(sourceLabel, store, { rdfMode = true } = {}) {
|
|
5912
|
+
let lineNumber = 0;
|
|
5913
|
+
let count = 0;
|
|
5914
|
+
const onLine = async (line) => {
|
|
5915
|
+
lineNumber += 1;
|
|
5916
|
+
let triples;
|
|
5917
|
+
try {
|
|
5918
|
+
triples = parseLineBasedRdfLine(line, { sourceLabel, lineNumber, rdfMode });
|
|
5919
|
+
} catch (e) {
|
|
5920
|
+
if (e && e.name === 'N3SyntaxError') {
|
|
5921
|
+
throw new Error(formatN3SyntaxError(e, line, `${sourceLabel}:${lineNumber}`));
|
|
5922
|
+
}
|
|
5923
|
+
throw e;
|
|
5924
|
+
}
|
|
5925
|
+
count += await store.batchAdd(triples, 'explicit');
|
|
5926
|
+
};
|
|
5927
|
+
|
|
5928
|
+
const filePath = __localPathForSource(sourceLabel);
|
|
5929
|
+
if (filePath) {
|
|
5930
|
+
const fd = fs.openSync(filePath, 'r');
|
|
5931
|
+
const decoder = new TextDecoder('utf8');
|
|
5932
|
+
const buf = Buffer.allocUnsafe(64 * 1024);
|
|
5933
|
+
let carry = '';
|
|
5934
|
+
try {
|
|
5935
|
+
for (;;) {
|
|
5936
|
+
const n = fs.readSync(fd, buf, 0, buf.length, null);
|
|
5937
|
+
if (n === 0) break;
|
|
5938
|
+
carry += decoder.decode(buf.subarray(0, n), { stream: true });
|
|
5939
|
+
for (;;) {
|
|
5940
|
+
const m = /\r\n|\n|\r/.exec(carry);
|
|
5941
|
+
if (!m) break;
|
|
5942
|
+
const end = m.index + m[0].length;
|
|
5943
|
+
await onLine(carry.slice(0, end));
|
|
5944
|
+
carry = carry.slice(end);
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
carry += decoder.decode();
|
|
5948
|
+
if (carry) await onLine(carry);
|
|
5949
|
+
} finally {
|
|
5950
|
+
fs.closeSync(fd);
|
|
5951
|
+
}
|
|
5952
|
+
return count;
|
|
5953
|
+
}
|
|
5954
|
+
|
|
5955
|
+
if (__isHttpSource(sourceLabel)) {
|
|
5956
|
+
const body = await __openHttpTextStream(sourceLabel);
|
|
5957
|
+
const rl = readline.createInterface({ input: body, crlfDelay: Infinity });
|
|
5958
|
+
for await (const line of rl) await onLine(line + '\n');
|
|
5959
|
+
return count;
|
|
5960
|
+
}
|
|
5961
|
+
|
|
5962
|
+
const text = __readInputSourceSync(sourceLabel);
|
|
5963
|
+
for (const line of text.match(/.*(?:\r\n|\n|\r)|.+$/g) || []) await onLine(line);
|
|
5964
|
+
return count;
|
|
5874
5965
|
}
|
|
5875
5966
|
|
|
5876
|
-
|
|
5967
|
+
|
|
5968
|
+
async function runStreamMessagesMode(sourceLabels, { rdfMode, storeName = null, storePath = null, storeClear = false } = {}) {
|
|
5877
5969
|
const ordinarySourceLabels = [];
|
|
5878
5970
|
const messageSourceLabels = [];
|
|
5879
5971
|
|
|
@@ -5922,40 +6014,49 @@ async function runStreamMessagesMode(sourceLabels, { rdfMode }) {
|
|
|
5922
6014
|
}
|
|
5923
6015
|
}
|
|
5924
6016
|
|
|
6017
|
+
const store = await createCliStore({ storeName, storePath, storeClear });
|
|
6018
|
+
let pendingStoreWrites = Promise.resolve();
|
|
6019
|
+
|
|
5925
6020
|
const fullIriPrefixes = new PrefixEnv({});
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
let messageDoc;
|
|
5936
|
-
try {
|
|
5937
|
-
messageDoc = parseN3Text(messageText, {
|
|
5938
|
-
baseIri: `${__sourceLabelToBaseIri(messageSourceLabel)}#message-${messageIndex}`,
|
|
5939
|
-
label: `${messageSourceLabel}#message-${messageIndex}`,
|
|
5940
|
-
keepSourceArtifacts: false,
|
|
5941
|
-
sourceLocations: false,
|
|
5942
|
-
rdf: false,
|
|
6021
|
+
try {
|
|
6022
|
+
for (const messageSourceLabel of messageSourceLabels) {
|
|
6023
|
+
try {
|
|
6024
|
+
await __forEachRdfMessageChunk(messageSourceLabel, ({ messageIndex, chunk, directives }) => {
|
|
6025
|
+
const messageText = buildSingleMessageReplayDocument({
|
|
6026
|
+
sourceLabel: messageSourceLabel,
|
|
6027
|
+
messageIndex,
|
|
6028
|
+
chunk,
|
|
6029
|
+
directives,
|
|
5943
6030
|
});
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
6031
|
+
let messageDoc;
|
|
6032
|
+
try {
|
|
6033
|
+
messageDoc = parseN3Text(messageText, {
|
|
6034
|
+
baseIri: `${__sourceLabelToBaseIri(messageSourceLabel)}#message-${messageIndex}`,
|
|
6035
|
+
label: `${messageSourceLabel}#message-${messageIndex}`,
|
|
6036
|
+
keepSourceArtifacts: false,
|
|
6037
|
+
sourceLocations: false,
|
|
6038
|
+
rdf: false,
|
|
6039
|
+
});
|
|
6040
|
+
} catch (e) {
|
|
6041
|
+
if (e && e.name === 'N3SyntaxError') {
|
|
6042
|
+
console.error(formatN3SyntaxError(e, messageText, `${messageSourceLabel}#message-${messageIndex}`));
|
|
6043
|
+
process.exit(1);
|
|
6044
|
+
}
|
|
6045
|
+
throw e;
|
|
5948
6046
|
}
|
|
5949
|
-
throw e;
|
|
5950
|
-
}
|
|
5951
6047
|
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
6048
|
+
const merged = mergeParsedDocuments(programSources.concat([messageDoc]));
|
|
6049
|
+
const result = runParsedDocumentOnce(merged, { rdfMode, outputPrefixes: fullIriPrefixes });
|
|
6050
|
+
if (store) pendingStoreWrites = pendingStoreWrites.then(() => persistRunResultToStore(store, result));
|
|
6051
|
+
});
|
|
6052
|
+
} catch (e) {
|
|
6053
|
+
console.error(`Error streaming RDF Message Log ${JSON.stringify(messageSourceLabel)}: ${e && e.message ? e.message : String(e)}`);
|
|
6054
|
+
process.exit(1);
|
|
6055
|
+
}
|
|
5958
6056
|
}
|
|
6057
|
+
await pendingStoreWrites;
|
|
6058
|
+
} finally {
|
|
6059
|
+
if (store && typeof store.close === 'function') await store.close();
|
|
5959
6060
|
}
|
|
5960
6061
|
}
|
|
5961
6062
|
|
|
@@ -5990,6 +6091,9 @@ async function main() {
|
|
|
5990
6091
|
` -p, --proof Enable proof explanations.\n` +
|
|
5991
6092
|
` -r, --rdf Enable RDF/TriG input/output compatibility.\n` +
|
|
5992
6093
|
` --stream-messages Process RDF Message Logs one message at a time under -r.\n` +
|
|
6094
|
+
` --store <name> Use an optional persistent fact store.\n` +
|
|
6095
|
+
` --store-clear Clear the named store before this run.\n` +
|
|
6096
|
+
` --store-path <dir> Node.js persistent store directory.\n` +
|
|
5993
6097
|
` -s, --super-restricted Disable all builtins except => and <=.\n` +
|
|
5994
6098
|
` -t, --stream Stream derived triples as soon as they are derived.\n` +
|
|
5995
6099
|
` -v, --version Print version and exit.\n`;
|
|
@@ -6026,6 +6130,35 @@ async function main() {
|
|
|
6026
6130
|
builtinModules.push(a.slice('--builtin='.length));
|
|
6027
6131
|
continue;
|
|
6028
6132
|
}
|
|
6133
|
+
if (a === '--store') {
|
|
6134
|
+
const next = argv[i + 1];
|
|
6135
|
+
if (!next || next.startsWith('-')) {
|
|
6136
|
+
console.error('Error: --store expects a store name.');
|
|
6137
|
+
process.exit(1);
|
|
6138
|
+
}
|
|
6139
|
+
argv.__storeName = next;
|
|
6140
|
+
i += 1;
|
|
6141
|
+
continue;
|
|
6142
|
+
}
|
|
6143
|
+
if (typeof a === 'string' && a.startsWith('--store=')) {
|
|
6144
|
+
argv.__storeName = a.slice('--store='.length);
|
|
6145
|
+
continue;
|
|
6146
|
+
}
|
|
6147
|
+
if (a === '--store-path') {
|
|
6148
|
+
const next = argv[i + 1];
|
|
6149
|
+
if (!next || next.startsWith('-')) {
|
|
6150
|
+
console.error('Error: --store-path expects a directory path.');
|
|
6151
|
+
process.exit(1);
|
|
6152
|
+
}
|
|
6153
|
+
argv.__storePath = next;
|
|
6154
|
+
i += 1;
|
|
6155
|
+
continue;
|
|
6156
|
+
}
|
|
6157
|
+
if (typeof a === 'string' && a.startsWith('--store-path=')) {
|
|
6158
|
+
argv.__storePath = a.slice('--store-path='.length);
|
|
6159
|
+
continue;
|
|
6160
|
+
}
|
|
6161
|
+
if (a === '--store-clear') continue;
|
|
6029
6162
|
if (a === '-' || !a.startsWith('-')) positional.push(a);
|
|
6030
6163
|
}
|
|
6031
6164
|
|
|
@@ -6033,6 +6166,9 @@ async function main() {
|
|
|
6033
6166
|
const streamMode = argv.includes('--stream') || argv.includes('-t');
|
|
6034
6167
|
const streamMessagesMode = argv.includes('--stream-messages');
|
|
6035
6168
|
const rdfMode = argv.includes('--rdf') || argv.includes('-r');
|
|
6169
|
+
const storeName = argv.__storeName || null;
|
|
6170
|
+
const storePath = argv.__storePath || null;
|
|
6171
|
+
const storeClear = argv.includes('--store-clear');
|
|
6036
6172
|
|
|
6037
6173
|
// --enforce-https: rewrite http:// -> https:// for log dereferencing builtins
|
|
6038
6174
|
if (argv.includes('--enforce-https') || argv.includes('-e')) {
|
|
@@ -6091,17 +6227,94 @@ async function main() {
|
|
|
6091
6227
|
}
|
|
6092
6228
|
}
|
|
6093
6229
|
|
|
6094
|
-
|
|
6230
|
+
let sourceLabels = useImplicitStdin ? ['<stdin>'] : positional.map((item) => (item === '-' ? '<stdin>' : item));
|
|
6095
6231
|
if (sourceLabels.filter((item) => item === '<stdin>').length > 1) {
|
|
6096
6232
|
console.error('Error: stdin can only be used once.');
|
|
6097
6233
|
process.exit(1);
|
|
6098
6234
|
}
|
|
6099
6235
|
|
|
6100
6236
|
if (streamMessagesMode) {
|
|
6101
|
-
await runStreamMessagesMode(sourceLabels, { rdfMode });
|
|
6237
|
+
await runStreamMessagesMode(sourceLabels, { rdfMode, storeName, storePath, storeClear });
|
|
6102
6238
|
return;
|
|
6103
6239
|
}
|
|
6104
6240
|
|
|
6241
|
+
if (storeName && rdfMode) {
|
|
6242
|
+
const lineBasedSources = sourceLabels.filter(sourceLooksLikeLineBasedRdf);
|
|
6243
|
+
if (lineBasedSources.length) {
|
|
6244
|
+
if (showAst) {
|
|
6245
|
+
console.error('Error: line-based --store ingestion cannot be combined with --ast.');
|
|
6246
|
+
process.exit(1);
|
|
6247
|
+
}
|
|
6248
|
+
if (streamMode) {
|
|
6249
|
+
console.error('Error: line-based --store ingestion cannot be combined with --stream.');
|
|
6250
|
+
process.exit(1);
|
|
6251
|
+
}
|
|
6252
|
+
if (engine.getProofCommentsEnabled()) {
|
|
6253
|
+
console.error('Error: line-based --store ingestion currently does not support proof output.');
|
|
6254
|
+
process.exit(1);
|
|
6255
|
+
}
|
|
6256
|
+
|
|
6257
|
+
const store = await createCliStore({ storeName, storePath, storeClear });
|
|
6258
|
+
try {
|
|
6259
|
+
for (const sourceLabel of lineBasedSources) {
|
|
6260
|
+
try {
|
|
6261
|
+
await ingestLineBasedRdfSourceToStore(sourceLabel, store, { rdfMode });
|
|
6262
|
+
} catch (e) {
|
|
6263
|
+
console.error(`Error streaming RDF source ${JSON.stringify(sourceLabel)}: ${e && e.message ? e.message : String(e)}`);
|
|
6264
|
+
process.exit(1);
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
|
|
6268
|
+
sourceLabels = sourceLabels.filter((sourceLabel) => !sourceLooksLikeLineBasedRdf(sourceLabel));
|
|
6269
|
+
if (!sourceLabels.length) return;
|
|
6270
|
+
|
|
6271
|
+
const parsedRuleSources = [];
|
|
6272
|
+
for (const sourceLabel of sourceLabels) {
|
|
6273
|
+
let text;
|
|
6274
|
+
try {
|
|
6275
|
+
text = __readInputSourceSync(sourceLabel);
|
|
6276
|
+
} catch (e) {
|
|
6277
|
+
if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
|
|
6278
|
+
else console.error(`Error reading source ${JSON.stringify(sourceLabel)}: ${e.message}`);
|
|
6279
|
+
process.exit(1);
|
|
6280
|
+
}
|
|
6281
|
+
|
|
6282
|
+
try {
|
|
6283
|
+
parsedRuleSources.push(
|
|
6284
|
+
parseN3Text(text, {
|
|
6285
|
+
baseIri: __sourceLabelToBaseIri(sourceLabel),
|
|
6286
|
+
label: sourceLabel,
|
|
6287
|
+
collectUsedPrefixes: false,
|
|
6288
|
+
keepSourceArtifacts: false,
|
|
6289
|
+
sourceLocations: false,
|
|
6290
|
+
rdf: rdfMode,
|
|
6291
|
+
}),
|
|
6292
|
+
);
|
|
6293
|
+
} catch (e) {
|
|
6294
|
+
if (e && e.name === 'N3SyntaxError') {
|
|
6295
|
+
console.error(formatN3SyntaxError(e, text, sourceLabel));
|
|
6296
|
+
process.exit(1);
|
|
6297
|
+
}
|
|
6298
|
+
throw e;
|
|
6299
|
+
}
|
|
6300
|
+
}
|
|
6301
|
+
|
|
6302
|
+
const mergedRuleDocument = mergeParsedDocuments(parsedRuleSources);
|
|
6303
|
+
const result = await engine.runStoreBacked(mergedRuleDocument, store, { rdf: rdfMode });
|
|
6304
|
+
const outTriples = result.queryMode ? result.queryTriples || [] : (result.derived || []).map((df) => df.fact);
|
|
6305
|
+
if (result.queryMode) {
|
|
6306
|
+
const bodyText = engine.prettyPrintQueryTriples(outTriples, result.prefixes);
|
|
6307
|
+
if (bodyText) process.stdout.write(String(bodyText).replace(/\s*$/g, '') + '\n');
|
|
6308
|
+
} else {
|
|
6309
|
+
for (const tr of outTriples) console.log(engine.tripleToRdfCompatible(tr, result.prefixes));
|
|
6310
|
+
}
|
|
6311
|
+
return;
|
|
6312
|
+
} finally {
|
|
6313
|
+
if (store && typeof store.close === 'function') await store.close();
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
|
|
6105
6318
|
const parsedSources = [];
|
|
6106
6319
|
for (const sourceLabel of sourceLabels) {
|
|
6107
6320
|
let text;
|
|
@@ -6211,6 +6424,69 @@ function factsContainOutputStrings(triplesForOutput) {
|
|
|
6211
6424
|
const hasQueries = Array.isArray(qrules) && qrules.length;
|
|
6212
6425
|
const mayAutoRenderOutputStrings = programMayProduceOutputStrings(triples, frules, qrules);
|
|
6213
6426
|
|
|
6427
|
+
if (storeName) {
|
|
6428
|
+
if (streamMode) {
|
|
6429
|
+
console.error('Error: --store cannot be combined with --stream yet.');
|
|
6430
|
+
process.exit(1);
|
|
6431
|
+
}
|
|
6432
|
+
|
|
6433
|
+
const storeResult = await engine.runAsync(
|
|
6434
|
+
{ prefixes, triples, frules, brules, logQueryRules: qrules },
|
|
6435
|
+
{
|
|
6436
|
+
proof: engine.getProofCommentsEnabled(),
|
|
6437
|
+
rdf: rdfMode,
|
|
6438
|
+
store: { name: storeName, clear: storeClear, path: storePath || undefined },
|
|
6439
|
+
},
|
|
6440
|
+
);
|
|
6441
|
+
|
|
6442
|
+
const storeFacts = storeResult.facts || [];
|
|
6443
|
+
const storeDerived = storeResult.derived || [];
|
|
6444
|
+
const storeHasQueries = !!storeResult.queryMode;
|
|
6445
|
+
const storeOutTriples = storeHasQueries ? (storeResult.queryTriples || []) : (mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled() ? [] : storeDerived.map((df) => df.fact));
|
|
6446
|
+
const storeOutDerived = storeHasQueries ? (storeResult.queryDerived || []) : storeDerived;
|
|
6447
|
+
const renderedOutputTriples = storeHasQueries ? storeOutTriples : storeFacts;
|
|
6448
|
+
|
|
6449
|
+
if (factsContainOutputStrings(renderedOutputTriples)) {
|
|
6450
|
+
process.stdout.write(engine.collectOutputStringsFromFacts(renderedOutputTriples, prefixes));
|
|
6451
|
+
if (storeResult.store && typeof storeResult.store.close === 'function') await storeResult.store.close();
|
|
6452
|
+
return;
|
|
6453
|
+
}
|
|
6454
|
+
|
|
6455
|
+
if (engine.getProofCommentsEnabled()) {
|
|
6456
|
+
process.stdout.write(engine.renderProofDocument(storeOutDerived, storeDerived.concat(storeOutDerived || []), triples, prefixes, brules));
|
|
6457
|
+
if (storeResult.store && typeof storeResult.store.close === 'function') await storeResult.store.close();
|
|
6458
|
+
return;
|
|
6459
|
+
}
|
|
6460
|
+
|
|
6461
|
+
let bodyText = '';
|
|
6462
|
+
if (rdfMode) bodyText = storeOutTriples.map((tr) => engine.tripleToRdfCompatible(tr, prefixes)).join('\n');
|
|
6463
|
+
else if (storeHasQueries) bodyText = engine.prettyPrintQueryTriples(storeOutTriples, prefixes);
|
|
6464
|
+
|
|
6465
|
+
let usedPrefixes = prefixes.prefixesUsedForOutput(storeOutTriples);
|
|
6466
|
+
if (rdfMode && bodyText) usedPrefixes = usedPrefixes.filter(([pfx]) => pfx === '' || bodyText.includes(pfx + ':'));
|
|
6467
|
+
|
|
6468
|
+
if (rdfMode && bodyText.includes('<<(')) {
|
|
6469
|
+
console.log('VERSION "1.2"');
|
|
6470
|
+
console.log();
|
|
6471
|
+
}
|
|
6472
|
+
|
|
6473
|
+
for (const [pfx, base] of usedPrefixes) {
|
|
6474
|
+
if (pfx === '') console.log(`@prefix : <${base}> .`);
|
|
6475
|
+
else console.log(`@prefix ${pfx}: <${base}> .`);
|
|
6476
|
+
}
|
|
6477
|
+
if (storeOutTriples.length && usedPrefixes.length) console.log();
|
|
6478
|
+
|
|
6479
|
+
if (bodyText) process.stdout.write(String(bodyText).replace(/\s*$/g, '') + '\n');
|
|
6480
|
+
else {
|
|
6481
|
+
for (const df of storeOutDerived) {
|
|
6482
|
+
console.log(rdfMode ? engine.tripleToRdfCompatible(df.fact, prefixes) : engine.tripleToN3(df.fact, prefixes));
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
|
|
6486
|
+
if (storeResult.store && typeof storeResult.store.close === 'function') await storeResult.store.close();
|
|
6487
|
+
return;
|
|
6488
|
+
}
|
|
6489
|
+
|
|
6214
6490
|
if (streamMode && !hasQueries && !mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled()) {
|
|
6215
6491
|
const usedInInput = mergedDocument.usedPrefixes instanceof Set ? new Set(mergedDocument.usedPrefixes) : new Set();
|
|
6216
6492
|
const outPrefixes = restrictPrefixEnv(prefixes, usedInInput);
|
|
@@ -6886,6 +7162,7 @@ const {
|
|
|
6886
7162
|
getDataFactory,
|
|
6887
7163
|
internalTripleToRdfJsQuads,
|
|
6888
7164
|
normalizeParsedReasonerInputSync,
|
|
7165
|
+
normalizeParsedReasonerInputAsync,
|
|
6889
7166
|
normalizeReasonerInputSync,
|
|
6890
7167
|
normalizeReasonerInputAsync,
|
|
6891
7168
|
} = require('./rdfjs');
|
|
@@ -6894,6 +7171,7 @@ const trace = require('./trace');
|
|
|
6894
7171
|
const { deterministicSkolemIdFromKey } = require('./skolem');
|
|
6895
7172
|
|
|
6896
7173
|
const deref = require('./deref');
|
|
7174
|
+
const { createFactStore, collectStore, MemoryFactStore, PersistentFactStore, tripleToStoreKey } = require('./store');
|
|
6897
7175
|
|
|
6898
7176
|
const hasOwn = Object.prototype.hasOwnProperty;
|
|
6899
7177
|
|
|
@@ -10572,6 +10850,278 @@ function reasonStream(input, opts = {}) {
|
|
|
10572
10850
|
return __out;
|
|
10573
10851
|
}
|
|
10574
10852
|
|
|
10853
|
+
|
|
10854
|
+
async function __parseRunAsyncInput(input, opts) {
|
|
10855
|
+
const {
|
|
10856
|
+
baseIri = null,
|
|
10857
|
+
proof = false,
|
|
10858
|
+
rdf = false,
|
|
10859
|
+
sourceLabel = '<input>',
|
|
10860
|
+
} = opts || {};
|
|
10861
|
+
|
|
10862
|
+
const parsedSourceList = parseN3SourceList(input, { baseIri, rdf: !!rdf, sourceLocations: !!proof });
|
|
10863
|
+
if (parsedSourceList) return parsedSourceList;
|
|
10864
|
+
|
|
10865
|
+
const parsedObject = await normalizeParsedReasonerInputAsync(input);
|
|
10866
|
+
if (parsedObject) {
|
|
10867
|
+
if (baseIri && parsedObject.prefixes && typeof parsedObject.prefixes.setBase === 'function') parsedObject.prefixes.setBase(baseIri);
|
|
10868
|
+
return parsedObject;
|
|
10869
|
+
}
|
|
10870
|
+
|
|
10871
|
+
const n3Text = await normalizeReasonerInputAsync(input);
|
|
10872
|
+
return parseN3Text(n3Text, {
|
|
10873
|
+
baseIri: baseIri || '',
|
|
10874
|
+
label: sourceLabel || '<input>',
|
|
10875
|
+
keepSourceArtifacts: false,
|
|
10876
|
+
sourceLocations: !!proof,
|
|
10877
|
+
rdf: !!rdf,
|
|
10878
|
+
});
|
|
10879
|
+
}
|
|
10880
|
+
|
|
10881
|
+
function __storePatternTerm(t, subst) {
|
|
10882
|
+
const applied = applySubstTerm(t, subst || __emptySubst());
|
|
10883
|
+
return containsVarTerm(applied) ? null : applied;
|
|
10884
|
+
}
|
|
10885
|
+
|
|
10886
|
+
function __mergeStoreSubst(base, delta) {
|
|
10887
|
+
let out = base;
|
|
10888
|
+
for (const k of Object.keys(delta || {})) {
|
|
10889
|
+
const v = delta[k];
|
|
10890
|
+
if (Object.prototype.hasOwnProperty.call(out, k)) {
|
|
10891
|
+
if (!termsEqual(out[k], v)) return null;
|
|
10892
|
+
} else {
|
|
10893
|
+
if (out === base) out = __cloneSubst(base);
|
|
10894
|
+
out[k] = v;
|
|
10895
|
+
}
|
|
10896
|
+
}
|
|
10897
|
+
return out;
|
|
10898
|
+
}
|
|
10899
|
+
|
|
10900
|
+
async function __proveGoalsAgainstStore(goals, subst, store, backRules, localFacts, depth, varGen, opts = {}) {
|
|
10901
|
+
if (!Array.isArray(goals) || goals.length === 0) return [subst || __emptySubst()];
|
|
10902
|
+
if (depth > 64) return [];
|
|
10903
|
+
|
|
10904
|
+
const goal0 = applySubstTriple(goals[0], subst || __emptySubst());
|
|
10905
|
+
const rest = goals.slice(1);
|
|
10906
|
+
const out = [];
|
|
10907
|
+
|
|
10908
|
+
if (isBuiltinPred(goal0.p)) {
|
|
10909
|
+
const deltas = evalBuiltin(goal0, __emptySubst(), localFacts || [], backRules || [], depth, varGen, undefined);
|
|
10910
|
+
for (const delta of deltas) {
|
|
10911
|
+
const nextSubst = __mergeStoreSubst(subst || __emptySubst(), delta);
|
|
10912
|
+
if (nextSubst === null) continue;
|
|
10913
|
+
out.push(...await __proveGoalsAgainstStore(rest, nextSubst, store, backRules, localFacts, depth + 1, varGen, opts));
|
|
10914
|
+
if (opts.maxResults && out.length >= opts.maxResults) return out.slice(0, opts.maxResults);
|
|
10915
|
+
}
|
|
10916
|
+
return out;
|
|
10917
|
+
}
|
|
10918
|
+
|
|
10919
|
+
// Backward rules are kept in memory; they may call back into the persistent
|
|
10920
|
+
// fact store for their own premises.
|
|
10921
|
+
if (goal0.p instanceof Iri && Array.isArray(backRules) && backRules.length) {
|
|
10922
|
+
ensureBackRuleIndexes(backRules);
|
|
10923
|
+
const candRules = (backRules.__byHeadPred.get(goal0.p.__tid) || []).concat(backRules.__wildHeadPred || []);
|
|
10924
|
+
for (const r of candRules) {
|
|
10925
|
+
if (!r || !Array.isArray(r.conclusion) || r.conclusion.length !== 1) continue;
|
|
10926
|
+
const rStd = standardizeRule(r, varGen);
|
|
10927
|
+
const head = rStd.conclusion[0];
|
|
10928
|
+
const s2 = unifyTriple(head, goal0, subst || __emptySubst());
|
|
10929
|
+
if (s2 === null) continue;
|
|
10930
|
+
const newGoals = (rStd.premise || []).concat(rest);
|
|
10931
|
+
out.push(...await __proveGoalsAgainstStore(newGoals, s2, store, backRules, localFacts, depth + 1, varGen, opts));
|
|
10932
|
+
if (opts.maxResults && out.length >= opts.maxResults) return out.slice(0, opts.maxResults);
|
|
10933
|
+
}
|
|
10934
|
+
}
|
|
10935
|
+
|
|
10936
|
+
const sPat = __storePatternTerm(goal0.s, subst || __emptySubst());
|
|
10937
|
+
const pPat = __storePatternTerm(goal0.p, subst || __emptySubst());
|
|
10938
|
+
const oPat = __storePatternTerm(goal0.o, subst || __emptySubst());
|
|
10939
|
+
|
|
10940
|
+
for await (const fact of store.match(sPat, pPat, oPat)) {
|
|
10941
|
+
const s2 = unifyTriple(goal0, fact, subst || __emptySubst());
|
|
10942
|
+
if (s2 === null) continue;
|
|
10943
|
+
out.push(...await __proveGoalsAgainstStore(rest, s2, store, backRules, localFacts, depth + 1, varGen, opts));
|
|
10944
|
+
if (opts.maxResults && out.length >= opts.maxResults) return out.slice(0, opts.maxResults);
|
|
10945
|
+
}
|
|
10946
|
+
|
|
10947
|
+
return out;
|
|
10948
|
+
}
|
|
10949
|
+
|
|
10950
|
+
async function runStoreBacked(input, store, opts = {}) {
|
|
10951
|
+
const parsed = parseN3SourceList(input, {
|
|
10952
|
+
baseIri: opts.baseIri || null,
|
|
10953
|
+
rdf: !!opts.rdf,
|
|
10954
|
+
sourceLocations: !!opts.proof,
|
|
10955
|
+
}) || input;
|
|
10956
|
+
|
|
10957
|
+
const prefixes = parsed.prefixes;
|
|
10958
|
+
const triples = parsed.triples || [];
|
|
10959
|
+
const frules = parsed.frules || [];
|
|
10960
|
+
const brules = parsed.brules || [];
|
|
10961
|
+
const qrules = parsed.logQueryRules || [];
|
|
10962
|
+
|
|
10963
|
+
materializeRdfLists(triples, frules.concat(qrules || []), brules);
|
|
10964
|
+
await store.batchAdd(triples, 'explicit');
|
|
10965
|
+
|
|
10966
|
+
const derived = [];
|
|
10967
|
+
const derivedKeys = new Set();
|
|
10968
|
+
const varGen = [0];
|
|
10969
|
+
const maxIterations = Number.isInteger(opts.maxIterations) && opts.maxIterations > 0 ? opts.maxIterations : 1024;
|
|
10970
|
+
|
|
10971
|
+
for (let iteration = 0; iteration < maxIterations; iteration += 1) {
|
|
10972
|
+
let changed = false;
|
|
10973
|
+
for (let ruleIndex = 0; ruleIndex < frules.length; ruleIndex += 1) {
|
|
10974
|
+
const r = frules[ruleIndex];
|
|
10975
|
+
if (!r || r.isFuse || r.__dynamicConclusionTerm) continue;
|
|
10976
|
+
__prepareForwardRule(r);
|
|
10977
|
+
const solutions = await __proveGoalsAgainstStore(r.premise || [], __emptySubst(), store, brules, triples, 0, varGen, {});
|
|
10978
|
+
for (const subst of solutions) {
|
|
10979
|
+
for (const cpat of r.conclusion || []) {
|
|
10980
|
+
const fact = applySubstTriple(cpat, subst);
|
|
10981
|
+
if (!isGroundTriple(fact)) continue;
|
|
10982
|
+
if (await store.add(fact, 'inferred')) {
|
|
10983
|
+
const df = makeDerivedRecord(fact, r, (r.premise || []).map((p) => applySubstTriple(p, subst)), subst, false);
|
|
10984
|
+
const key = tripleToStoreKey(fact);
|
|
10985
|
+
if (!derivedKeys.has(key)) {
|
|
10986
|
+
derivedKeys.add(key);
|
|
10987
|
+
derived.push(df);
|
|
10988
|
+
}
|
|
10989
|
+
changed = true;
|
|
10990
|
+
}
|
|
10991
|
+
}
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
if (!changed) break;
|
|
10995
|
+
}
|
|
10996
|
+
|
|
10997
|
+
let queryTriples = [];
|
|
10998
|
+
const queryDerived = [];
|
|
10999
|
+
if (qrules.length) {
|
|
11000
|
+
for (const r of qrules) {
|
|
11001
|
+
const solutions = await __proveGoalsAgainstStore(r.premise || [], __emptySubst(), store, brules, triples, 0, varGen, {});
|
|
11002
|
+
for (const subst of solutions) {
|
|
11003
|
+
for (const cpat of r.conclusion || []) {
|
|
11004
|
+
const fact = applySubstTriple(cpat, subst);
|
|
11005
|
+
if (!isGroundTriple(fact)) continue;
|
|
11006
|
+
queryTriples.push(fact);
|
|
11007
|
+
queryDerived.push(makeDerivedRecord(fact, r, (r.premise || []).map((p) => applySubstTriple(p, subst)), subst, false));
|
|
11008
|
+
}
|
|
11009
|
+
}
|
|
11010
|
+
}
|
|
11011
|
+
}
|
|
11012
|
+
|
|
11013
|
+
if (queryTriples.length) {
|
|
11014
|
+
const seen = new Set();
|
|
11015
|
+
queryTriples = queryTriples.filter((tr) => {
|
|
11016
|
+
const key = tripleToStoreKey(tr);
|
|
11017
|
+
if (seen.has(key)) return false;
|
|
11018
|
+
seen.add(key);
|
|
11019
|
+
return true;
|
|
11020
|
+
});
|
|
11021
|
+
}
|
|
11022
|
+
|
|
11023
|
+
return {
|
|
11024
|
+
prefixes,
|
|
11025
|
+
facts: [],
|
|
11026
|
+
derived,
|
|
11027
|
+
queryMode: !!qrules.length,
|
|
11028
|
+
queryTriples,
|
|
11029
|
+
queryDerived,
|
|
11030
|
+
closureN3: qrules.length
|
|
11031
|
+
? prettyPrintQueryTriples(queryTriples, prefixes)
|
|
11032
|
+
: derived.map((df) => (opts.rdf ? tripleToRdfCompatible(df.fact, prefixes) : tripleToN3(df.fact, prefixes))).join('\n'),
|
|
11033
|
+
store,
|
|
11034
|
+
storeBacked: true,
|
|
11035
|
+
};
|
|
11036
|
+
}
|
|
11037
|
+
|
|
11038
|
+
|
|
11039
|
+
function __withoutStoreOptions(opts) {
|
|
11040
|
+
const out = { ...(opts || {}) };
|
|
11041
|
+
delete out.store;
|
|
11042
|
+
delete out.storePath;
|
|
11043
|
+
delete out.storeClear;
|
|
11044
|
+
return out;
|
|
11045
|
+
}
|
|
11046
|
+
|
|
11047
|
+
function __normalizeStoreOption(opts) {
|
|
11048
|
+
const cfg = opts && opts.store;
|
|
11049
|
+
if (!cfg) return null;
|
|
11050
|
+
if (typeof cfg === 'string') return { name: cfg, path: opts.storePath || undefined, clear: !!opts.storeClear };
|
|
11051
|
+
if (typeof cfg === 'object') {
|
|
11052
|
+
return {
|
|
11053
|
+
...cfg,
|
|
11054
|
+
path: cfg.path || opts.storePath || undefined,
|
|
11055
|
+
clear: !!(cfg.clear || opts.storeClear),
|
|
11056
|
+
};
|
|
11057
|
+
}
|
|
11058
|
+
throw new TypeError('runAsync option "store" must be a store name string or a store options object');
|
|
11059
|
+
}
|
|
11060
|
+
|
|
11061
|
+
async function runAsync(input, opts = {}) {
|
|
11062
|
+
const storeConfig = __normalizeStoreOption(opts);
|
|
11063
|
+
const runOpts = __withoutStoreOptions(opts);
|
|
11064
|
+
|
|
11065
|
+
// No store configured: keep ordinary in-memory behavior, but accept async RDF/JS
|
|
11066
|
+
// iterables by normalizing the input before calling the synchronous engine.
|
|
11067
|
+
if (!storeConfig) {
|
|
11068
|
+
const normalizedInput = parseN3SourceList(input, {
|
|
11069
|
+
baseIri: runOpts.baseIri || null,
|
|
11070
|
+
rdf: !!runOpts.rdf,
|
|
11071
|
+
sourceLocations: !!runOpts.proof,
|
|
11072
|
+
}) || (await normalizeReasonerInputAsync(input));
|
|
11073
|
+
return reasonStream(normalizedInput, runOpts);
|
|
11074
|
+
}
|
|
11075
|
+
|
|
11076
|
+
const store = await createFactStore(storeConfig);
|
|
11077
|
+
let closeStore = true;
|
|
11078
|
+
try {
|
|
11079
|
+
const parsed = await __parseRunAsyncInput(input, runOpts);
|
|
11080
|
+
|
|
11081
|
+
// Persist the newly supplied explicit facts first, then include all stored
|
|
11082
|
+
// facts as the starting closure for this run. This lets --store reuse facts
|
|
11083
|
+
// across runs while keeping the current in-memory prover semantics intact.
|
|
11084
|
+
await store.batchAdd(parsed.triples || [], 'explicit');
|
|
11085
|
+
const storedTriples = await collectStore(store);
|
|
11086
|
+
|
|
11087
|
+
const storedKeys = new Set();
|
|
11088
|
+
for (const tr of storedTriples) storedKeys.add(require('./store').tripleToStoreKey(tr));
|
|
11089
|
+
const mergedTriples = storedTriples.slice();
|
|
11090
|
+
for (const tr of parsed.triples || []) {
|
|
11091
|
+
const key = require('./store').tripleToStoreKey(tr);
|
|
11092
|
+
if (!storedKeys.has(key)) {
|
|
11093
|
+
storedKeys.add(key);
|
|
11094
|
+
mergedTriples.push(tr);
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
11097
|
+
|
|
11098
|
+
const result = reasonStream(
|
|
11099
|
+
{
|
|
11100
|
+
prefixes: parsed.prefixes,
|
|
11101
|
+
triples: mergedTriples,
|
|
11102
|
+
frules: parsed.frules,
|
|
11103
|
+
brules: parsed.brules,
|
|
11104
|
+
logQueryRules: parsed.logQueryRules,
|
|
11105
|
+
},
|
|
11106
|
+
runOpts,
|
|
11107
|
+
);
|
|
11108
|
+
|
|
11109
|
+
await store.batchAdd((result.derived || []).map((df) => df.fact), 'inferred');
|
|
11110
|
+
result.store = store;
|
|
11111
|
+
closeStore = false;
|
|
11112
|
+
return result;
|
|
11113
|
+
} catch (e) {
|
|
11114
|
+
await store.close();
|
|
11115
|
+
throw e;
|
|
11116
|
+
} finally {
|
|
11117
|
+
// Keep result.store usable for callers. On exceptional paths it is closed
|
|
11118
|
+
// above; on success callers may close it when they are done.
|
|
11119
|
+
if (closeStore) {
|
|
11120
|
+
try { await store.close(); } catch {}
|
|
11121
|
+
}
|
|
11122
|
+
}
|
|
11123
|
+
}
|
|
11124
|
+
|
|
10575
11125
|
function reasonRdfJs(input, opts = {}) {
|
|
10576
11126
|
const { dataFactory = null, skipUnsupportedRdfJs = false, ...restOpts } = opts || {};
|
|
10577
11127
|
const rdfFactory = getDataFactory(dataFactory);
|
|
@@ -10679,6 +11229,8 @@ function setTracePrefixes(v) {
|
|
|
10679
11229
|
|
|
10680
11230
|
module.exports = {
|
|
10681
11231
|
reasonStream,
|
|
11232
|
+
runAsync,
|
|
11233
|
+
runStoreBacked,
|
|
10682
11234
|
reasonRdfJs,
|
|
10683
11235
|
collectLogQueryConclusions,
|
|
10684
11236
|
forwardChainAndCollectLogQueryConclusions,
|
|
@@ -10715,6 +11267,9 @@ module.exports = {
|
|
|
10715
11267
|
loadBuiltinModule,
|
|
10716
11268
|
listBuiltinIris,
|
|
10717
11269
|
INFERENCE_FUSE_EXIT_CODE,
|
|
11270
|
+
createFactStore,
|
|
11271
|
+
MemoryFactStore,
|
|
11272
|
+
PersistentFactStore,
|
|
10718
11273
|
};
|
|
10719
11274
|
|
|
10720
11275
|
};
|
|
@@ -10738,6 +11293,7 @@ const { dataFactory } = require('./rdfjs');
|
|
|
10738
11293
|
module.exports = {
|
|
10739
11294
|
// public
|
|
10740
11295
|
reasonStream: engine.reasonStream,
|
|
11296
|
+
runAsync: engine.runAsync,
|
|
10741
11297
|
reasonRdfJs: engine.reasonRdfJs,
|
|
10742
11298
|
rdfjs: dataFactory,
|
|
10743
11299
|
main: engine.main,
|
|
@@ -10762,6 +11318,9 @@ module.exports = {
|
|
|
10762
11318
|
registerBuiltinModule: engine.registerBuiltinModule,
|
|
10763
11319
|
loadBuiltinModule: engine.loadBuiltinModule,
|
|
10764
11320
|
listBuiltinIris: engine.listBuiltinIris,
|
|
11321
|
+
createFactStore: engine.createFactStore,
|
|
11322
|
+
MemoryFactStore: engine.MemoryFactStore,
|
|
11323
|
+
PersistentFactStore: engine.PersistentFactStore,
|
|
10765
11324
|
getEnforceHttpsEnabled: engine.getEnforceHttpsEnabled,
|
|
10766
11325
|
setEnforceHttpsEnabled: engine.setEnforceHttpsEnabled,
|
|
10767
11326
|
getProofCommentsEnabled: engine.getProofCommentsEnabled,
|
|
@@ -17050,6 +17609,7 @@ module.exports = {
|
|
|
17050
17609
|
internalTripleToRdfJsQuads,
|
|
17051
17610
|
internalTripleToRdfJsQuadInGraph,
|
|
17052
17611
|
normalizeParsedReasonerInputSync,
|
|
17612
|
+
normalizeParsedReasonerInputAsync,
|
|
17053
17613
|
normalizeReasonerInputSync,
|
|
17054
17614
|
normalizeReasonerInputAsync,
|
|
17055
17615
|
hasEyelingObjectInput,
|
|
@@ -17215,6 +17775,697 @@ module.exports = {
|
|
|
17215
17775
|
deterministicSkolemIdFromKey,
|
|
17216
17776
|
};
|
|
17217
17777
|
|
|
17778
|
+
};
|
|
17779
|
+
__modules["lib/store.js"] = function(require, module, exports){
|
|
17780
|
+
/**
|
|
17781
|
+
* Eyeling Reasoner — fact stores
|
|
17782
|
+
*
|
|
17783
|
+
* Optional async fact-store abstraction used by runAsync() and by tests. The
|
|
17784
|
+
* in-memory reasoner remains the default; persistent stores are opt-in.
|
|
17785
|
+
*/
|
|
17786
|
+
|
|
17787
|
+
'use strict';
|
|
17788
|
+
|
|
17789
|
+
const {
|
|
17790
|
+
Iri,
|
|
17791
|
+
Literal,
|
|
17792
|
+
Var,
|
|
17793
|
+
Blank,
|
|
17794
|
+
ListTerm,
|
|
17795
|
+
OpenListTerm,
|
|
17796
|
+
GraphTerm,
|
|
17797
|
+
Triple,
|
|
17798
|
+
} = require('./prelude');
|
|
17799
|
+
|
|
17800
|
+
const KIND_EXPLICIT = 1;
|
|
17801
|
+
const KIND_INFERRED = 2;
|
|
17802
|
+
|
|
17803
|
+
function __dynamicRequire(name) {
|
|
17804
|
+
try {
|
|
17805
|
+
// Keep this indirect so the browser bundle can include this module without
|
|
17806
|
+
// eagerly trying to bundle Node-only or optional Level dependencies.
|
|
17807
|
+
const req = typeof require === 'function' ? require : null;
|
|
17808
|
+
return req ? req(name) : null;
|
|
17809
|
+
} catch {
|
|
17810
|
+
return null;
|
|
17811
|
+
}
|
|
17812
|
+
}
|
|
17813
|
+
|
|
17814
|
+
function base64url(s) {
|
|
17815
|
+
const text = String(s);
|
|
17816
|
+
if (typeof Buffer !== 'undefined') return Buffer.from(text, 'utf8').toString('base64url');
|
|
17817
|
+
const encoder = globalThis.TextEncoder ? new globalThis.TextEncoder() : null;
|
|
17818
|
+
if (!encoder || typeof globalThis.btoa !== 'function') {
|
|
17819
|
+
throw new Error('No UTF-8/base64 encoder available for persistent store keys');
|
|
17820
|
+
}
|
|
17821
|
+
const bytes = encoder.encode(text);
|
|
17822
|
+
let bin = '';
|
|
17823
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
17824
|
+
return globalThis.btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
17825
|
+
}
|
|
17826
|
+
|
|
17827
|
+
function idPart(id) {
|
|
17828
|
+
return String(id).padStart(16, '0');
|
|
17829
|
+
}
|
|
17830
|
+
|
|
17831
|
+
function safeStoreName(name) {
|
|
17832
|
+
const text = String(name || 'default');
|
|
17833
|
+
return encodeURIComponent(text).replace(/%/g, '_');
|
|
17834
|
+
}
|
|
17835
|
+
|
|
17836
|
+
function termToJson(term) {
|
|
17837
|
+
if (term instanceof Iri) return ['Iri', term.value];
|
|
17838
|
+
if (term instanceof Literal) return ['Literal', term.value];
|
|
17839
|
+
if (term instanceof Var) return ['Var', term.name];
|
|
17840
|
+
if (term instanceof Blank) return ['Blank', term.label];
|
|
17841
|
+
if (term instanceof ListTerm) return ['ListTerm', term.elems.map(termToJson)];
|
|
17842
|
+
if (term instanceof OpenListTerm) return ['OpenListTerm', term.prefix.map(termToJson), term.tailVar];
|
|
17843
|
+
if (term instanceof GraphTerm) return ['GraphTerm', term.triples.map(tripleToJson)];
|
|
17844
|
+
|
|
17845
|
+
// RDF/JS terms, for callers that use the store abstraction directly.
|
|
17846
|
+
if (term && typeof term === 'object' && typeof term.termType === 'string') {
|
|
17847
|
+
switch (term.termType) {
|
|
17848
|
+
case 'NamedNode':
|
|
17849
|
+
return ['Iri', term.value];
|
|
17850
|
+
case 'BlankNode':
|
|
17851
|
+
return ['Blank', term.value && term.value.startsWith('_:') ? term.value : `_:${term.value}`];
|
|
17852
|
+
case 'Variable':
|
|
17853
|
+
return ['Var', term.value];
|
|
17854
|
+
case 'Literal': {
|
|
17855
|
+
const dt = term.datatype && term.datatype.termType === 'NamedNode' ? term.datatype.value : '';
|
|
17856
|
+
const lang = typeof term.language === 'string' ? term.language : '';
|
|
17857
|
+
const q = JSON.stringify(String(term.value));
|
|
17858
|
+
if (lang) return ['Literal', `${q}@${lang}`];
|
|
17859
|
+
if (!dt || dt === 'http://www.w3.org/2001/XMLSchema#string') return ['Literal', q];
|
|
17860
|
+
return ['Literal', `${q}^^<${dt}>`];
|
|
17861
|
+
}
|
|
17862
|
+
case 'Quad':
|
|
17863
|
+
return ['GraphTerm', [tripleToJson({ s: term.subject, p: term.predicate, o: term.object })]];
|
|
17864
|
+
case 'DefaultGraph':
|
|
17865
|
+
return ['DefaultGraph'];
|
|
17866
|
+
default:
|
|
17867
|
+
break;
|
|
17868
|
+
}
|
|
17869
|
+
}
|
|
17870
|
+
|
|
17871
|
+
throw new TypeError(`Unsupported fact-store term: ${term && term.constructor ? term.constructor.name : String(term)}`);
|
|
17872
|
+
}
|
|
17873
|
+
|
|
17874
|
+
function termFromJson(json) {
|
|
17875
|
+
if (!Array.isArray(json)) throw new TypeError('Invalid serialized term');
|
|
17876
|
+
switch (json[0]) {
|
|
17877
|
+
case 'Iri':
|
|
17878
|
+
return new Iri(json[1]);
|
|
17879
|
+
case 'Literal':
|
|
17880
|
+
return new Literal(json[1]);
|
|
17881
|
+
case 'Var':
|
|
17882
|
+
return new Var(json[1]);
|
|
17883
|
+
case 'Blank':
|
|
17884
|
+
return new Blank(json[1]);
|
|
17885
|
+
case 'ListTerm':
|
|
17886
|
+
return new ListTerm((json[1] || []).map(termFromJson));
|
|
17887
|
+
case 'OpenListTerm':
|
|
17888
|
+
return new OpenListTerm((json[1] || []).map(termFromJson), json[2]);
|
|
17889
|
+
case 'GraphTerm':
|
|
17890
|
+
return new GraphTerm((json[1] || []).map(tripleFromJson));
|
|
17891
|
+
case 'DefaultGraph':
|
|
17892
|
+
return new Iri('urn:eyeling:default-graph');
|
|
17893
|
+
default:
|
|
17894
|
+
throw new TypeError(`Unsupported serialized term type: ${json[0]}`);
|
|
17895
|
+
}
|
|
17896
|
+
}
|
|
17897
|
+
|
|
17898
|
+
function tripleToJson(triple) {
|
|
17899
|
+
return [termToJson(triple.s || triple.subject), termToJson(triple.p || triple.predicate), termToJson(triple.o || triple.object)];
|
|
17900
|
+
}
|
|
17901
|
+
|
|
17902
|
+
function tripleFromJson(json) {
|
|
17903
|
+
return new Triple(termFromJson(json[0]), termFromJson(json[1]), termFromJson(json[2]));
|
|
17904
|
+
}
|
|
17905
|
+
|
|
17906
|
+
function termToStoreKey(term) {
|
|
17907
|
+
return JSON.stringify(termToJson(term));
|
|
17908
|
+
}
|
|
17909
|
+
|
|
17910
|
+
function tripleToStoreKey(triple) {
|
|
17911
|
+
return JSON.stringify(tripleToJson(triple));
|
|
17912
|
+
}
|
|
17913
|
+
|
|
17914
|
+
function kindMask(kind) {
|
|
17915
|
+
if (kind === 'explicit') return KIND_EXPLICIT;
|
|
17916
|
+
if (kind === 'inferred') return KIND_INFERRED;
|
|
17917
|
+
if (typeof kind === 'number') return kind & (KIND_EXPLICIT | KIND_INFERRED);
|
|
17918
|
+
return KIND_EXPLICIT;
|
|
17919
|
+
}
|
|
17920
|
+
|
|
17921
|
+
class MemoryKv {
|
|
17922
|
+
constructor() {
|
|
17923
|
+
this.map = new Map();
|
|
17924
|
+
}
|
|
17925
|
+
|
|
17926
|
+
async get(key) {
|
|
17927
|
+
return this.map.get(key);
|
|
17928
|
+
}
|
|
17929
|
+
|
|
17930
|
+
async put(key, value) {
|
|
17931
|
+
this.map.set(key, value);
|
|
17932
|
+
}
|
|
17933
|
+
|
|
17934
|
+
async del(key) {
|
|
17935
|
+
this.map.delete(key);
|
|
17936
|
+
}
|
|
17937
|
+
|
|
17938
|
+
async clear() {
|
|
17939
|
+
this.map.clear();
|
|
17940
|
+
}
|
|
17941
|
+
|
|
17942
|
+
async batch(ops) {
|
|
17943
|
+
for (const op of ops || []) {
|
|
17944
|
+
if (!op) continue;
|
|
17945
|
+
if (op.type === 'del') this.map.delete(op.key);
|
|
17946
|
+
else this.map.set(op.key, op.value);
|
|
17947
|
+
}
|
|
17948
|
+
}
|
|
17949
|
+
|
|
17950
|
+
async *entries(prefix = '') {
|
|
17951
|
+
const keys = Array.from(this.map.keys())
|
|
17952
|
+
.filter((key) => key.startsWith(prefix))
|
|
17953
|
+
.sort();
|
|
17954
|
+
for (const key of keys) yield [key, this.map.get(key)];
|
|
17955
|
+
}
|
|
17956
|
+
|
|
17957
|
+
async close() {}
|
|
17958
|
+
}
|
|
17959
|
+
|
|
17960
|
+
class JsonFileKv extends MemoryKv {
|
|
17961
|
+
constructor(filePath) {
|
|
17962
|
+
super();
|
|
17963
|
+
this.filePath = filePath;
|
|
17964
|
+
this.loaded = false;
|
|
17965
|
+
this.dirty = false;
|
|
17966
|
+
}
|
|
17967
|
+
|
|
17968
|
+
async open() {
|
|
17969
|
+
if (this.loaded) return;
|
|
17970
|
+
this.loaded = true;
|
|
17971
|
+
const fs = __dynamicRequire('node:fs');
|
|
17972
|
+
const path = __dynamicRequire('node:path');
|
|
17973
|
+
if (!fs || !path) throw new Error('Persistent stores need node:fs in this runtime');
|
|
17974
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
17975
|
+
if (!fs.existsSync(this.filePath)) return;
|
|
17976
|
+
const text = fs.readFileSync(this.filePath, 'utf8');
|
|
17977
|
+
if (!text.trim()) return;
|
|
17978
|
+
const data = JSON.parse(text);
|
|
17979
|
+
this.map = new Map(Array.isArray(data.entries) ? data.entries : []);
|
|
17980
|
+
}
|
|
17981
|
+
|
|
17982
|
+
async flush() {
|
|
17983
|
+
if (!this.loaded || !this.dirty) return;
|
|
17984
|
+
const fs = __dynamicRequire('node:fs');
|
|
17985
|
+
const path = __dynamicRequire('node:path');
|
|
17986
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
17987
|
+
const tmp = `${this.filePath}.tmp`;
|
|
17988
|
+
fs.writeFileSync(tmp, JSON.stringify({ entries: Array.from(this.map.entries()) }), 'utf8');
|
|
17989
|
+
fs.renameSync(tmp, this.filePath);
|
|
17990
|
+
this.dirty = false;
|
|
17991
|
+
}
|
|
17992
|
+
|
|
17993
|
+
async get(key) {
|
|
17994
|
+
await this.open();
|
|
17995
|
+
return super.get(key);
|
|
17996
|
+
}
|
|
17997
|
+
|
|
17998
|
+
async put(key, value) {
|
|
17999
|
+
await this.open();
|
|
18000
|
+
await super.put(key, value);
|
|
18001
|
+
this.dirty = true;
|
|
18002
|
+
await this.flush();
|
|
18003
|
+
}
|
|
18004
|
+
|
|
18005
|
+
async del(key) {
|
|
18006
|
+
await this.open();
|
|
18007
|
+
await super.del(key);
|
|
18008
|
+
this.dirty = true;
|
|
18009
|
+
await this.flush();
|
|
18010
|
+
}
|
|
18011
|
+
|
|
18012
|
+
async clear() {
|
|
18013
|
+
await this.open();
|
|
18014
|
+
await super.clear();
|
|
18015
|
+
this.dirty = true;
|
|
18016
|
+
await this.flush();
|
|
18017
|
+
}
|
|
18018
|
+
|
|
18019
|
+
async batch(ops) {
|
|
18020
|
+
await this.open();
|
|
18021
|
+
await super.batch(ops);
|
|
18022
|
+
this.dirty = true;
|
|
18023
|
+
await this.flush();
|
|
18024
|
+
}
|
|
18025
|
+
|
|
18026
|
+
async *entries(prefix = '') {
|
|
18027
|
+
await this.open();
|
|
18028
|
+
yield* super.entries(prefix);
|
|
18029
|
+
}
|
|
18030
|
+
|
|
18031
|
+
async close() {
|
|
18032
|
+
await this.flush();
|
|
18033
|
+
}
|
|
18034
|
+
}
|
|
18035
|
+
|
|
18036
|
+
class ClassicLevelKv {
|
|
18037
|
+
constructor(location) {
|
|
18038
|
+
const classic = __dynamicRequire('classic-level');
|
|
18039
|
+
const ClassicLevel = classic && (classic.ClassicLevel || classic.Level || classic.default);
|
|
18040
|
+
if (!ClassicLevel) throw new Error('classic-level is not installed');
|
|
18041
|
+
const fs = __dynamicRequire('node:fs');
|
|
18042
|
+
const path = __dynamicRequire('node:path');
|
|
18043
|
+
if (fs && path) fs.mkdirSync(path.dirname(location), { recursive: true });
|
|
18044
|
+
this.db = new ClassicLevel(location, { valueEncoding: 'json', createIfMissing: true, errorIfExists: false });
|
|
18045
|
+
this.opened = false;
|
|
18046
|
+
}
|
|
18047
|
+
|
|
18048
|
+
async open() {
|
|
18049
|
+
if (this.opened) return;
|
|
18050
|
+
if (typeof this.db.open === 'function') await this.db.open();
|
|
18051
|
+
this.opened = true;
|
|
18052
|
+
}
|
|
18053
|
+
|
|
18054
|
+
async get(key) {
|
|
18055
|
+
await this.open();
|
|
18056
|
+
try {
|
|
18057
|
+
return await this.db.get(key);
|
|
18058
|
+
} catch (e) {
|
|
18059
|
+
if (e && (e.notFound || e.code === 'LEVEL_NOT_FOUND' || e.code === 'NOT_FOUND')) return undefined;
|
|
18060
|
+
throw e;
|
|
18061
|
+
}
|
|
18062
|
+
}
|
|
18063
|
+
|
|
18064
|
+
async put(key, value) {
|
|
18065
|
+
await this.open();
|
|
18066
|
+
await this.db.put(key, value);
|
|
18067
|
+
}
|
|
18068
|
+
|
|
18069
|
+
async del(key) {
|
|
18070
|
+
await this.open();
|
|
18071
|
+
await this.db.del(key);
|
|
18072
|
+
}
|
|
18073
|
+
|
|
18074
|
+
async clear() {
|
|
18075
|
+
await this.open();
|
|
18076
|
+
if (typeof this.db.clear === 'function') await this.db.clear();
|
|
18077
|
+
else {
|
|
18078
|
+
const ops = [];
|
|
18079
|
+
for await (const [key] of this.entries('')) ops.push({ type: 'del', key });
|
|
18080
|
+
if (ops.length) await this.batch(ops);
|
|
18081
|
+
}
|
|
18082
|
+
}
|
|
18083
|
+
|
|
18084
|
+
async batch(ops) {
|
|
18085
|
+
await this.open();
|
|
18086
|
+
if (!ops || !ops.length) return;
|
|
18087
|
+
await this.db.batch(ops);
|
|
18088
|
+
}
|
|
18089
|
+
|
|
18090
|
+
async *entries(prefix = '') {
|
|
18091
|
+
await this.open();
|
|
18092
|
+
const gte = prefix;
|
|
18093
|
+
const lt = prefix + '\uffff';
|
|
18094
|
+
const iterator = this.db.iterator({ gte, lt });
|
|
18095
|
+
for await (const item of iterator) {
|
|
18096
|
+
if (Array.isArray(item)) yield item;
|
|
18097
|
+
else if (item && Array.isArray(item.key)) yield item.key;
|
|
18098
|
+
}
|
|
18099
|
+
}
|
|
18100
|
+
|
|
18101
|
+
async close() {
|
|
18102
|
+
if (this.db && typeof this.db.close === 'function') await this.db.close();
|
|
18103
|
+
}
|
|
18104
|
+
}
|
|
18105
|
+
|
|
18106
|
+
class IndexedDbKv {
|
|
18107
|
+
constructor(name) {
|
|
18108
|
+
this.name = name;
|
|
18109
|
+
this.dbPromise = null;
|
|
18110
|
+
}
|
|
18111
|
+
|
|
18112
|
+
open() {
|
|
18113
|
+
if (this.dbPromise) return this.dbPromise;
|
|
18114
|
+
const idb = typeof globalThis !== 'undefined' ? globalThis.indexedDB : null;
|
|
18115
|
+
if (!idb) throw new Error('IndexedDB is not available in this runtime');
|
|
18116
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
18117
|
+
const req = idb.open(`eyeling:${this.name}`, 1);
|
|
18118
|
+
req.onupgradeneeded = () => {
|
|
18119
|
+
const db = req.result;
|
|
18120
|
+
if (!db.objectStoreNames.contains('kv')) db.createObjectStore('kv');
|
|
18121
|
+
};
|
|
18122
|
+
req.onerror = () => reject(req.error || new Error('Failed to open IndexedDB store'));
|
|
18123
|
+
req.onsuccess = () => resolve(req.result);
|
|
18124
|
+
});
|
|
18125
|
+
return this.dbPromise;
|
|
18126
|
+
}
|
|
18127
|
+
|
|
18128
|
+
async __tx(mode, fn) {
|
|
18129
|
+
const db = await this.open();
|
|
18130
|
+
return new Promise((resolve, reject) => {
|
|
18131
|
+
const tx = db.transaction('kv', mode);
|
|
18132
|
+
const store = tx.objectStore('kv');
|
|
18133
|
+
let value;
|
|
18134
|
+
tx.oncomplete = () => resolve(value);
|
|
18135
|
+
tx.onerror = () => reject(tx.error || new Error('IndexedDB transaction failed'));
|
|
18136
|
+
tx.onabort = () => reject(tx.error || new Error('IndexedDB transaction aborted'));
|
|
18137
|
+
try {
|
|
18138
|
+
value = fn(store, resolve, reject);
|
|
18139
|
+
} catch (e) {
|
|
18140
|
+
try { tx.abort(); } catch {}
|
|
18141
|
+
reject(e);
|
|
18142
|
+
}
|
|
18143
|
+
});
|
|
18144
|
+
}
|
|
18145
|
+
|
|
18146
|
+
async get(key) {
|
|
18147
|
+
return this.__tx('readonly', (store, resolve, reject) => {
|
|
18148
|
+
const req = store.get(key);
|
|
18149
|
+
req.onerror = () => reject(req.error);
|
|
18150
|
+
req.onsuccess = () => resolve(req.result);
|
|
18151
|
+
});
|
|
18152
|
+
}
|
|
18153
|
+
|
|
18154
|
+
async put(key, value) {
|
|
18155
|
+
return this.__tx('readwrite', (store) => {
|
|
18156
|
+
store.put(value, key);
|
|
18157
|
+
});
|
|
18158
|
+
}
|
|
18159
|
+
|
|
18160
|
+
async del(key) {
|
|
18161
|
+
return this.__tx('readwrite', (store) => {
|
|
18162
|
+
store.delete(key);
|
|
18163
|
+
});
|
|
18164
|
+
}
|
|
18165
|
+
|
|
18166
|
+
async clear() {
|
|
18167
|
+
return this.__tx('readwrite', (store) => {
|
|
18168
|
+
store.clear();
|
|
18169
|
+
});
|
|
18170
|
+
}
|
|
18171
|
+
|
|
18172
|
+
async batch(ops) {
|
|
18173
|
+
return this.__tx('readwrite', (store) => {
|
|
18174
|
+
for (const op of ops || []) {
|
|
18175
|
+
if (op.type === 'del') store.delete(op.key);
|
|
18176
|
+
else store.put(op.value, op.key);
|
|
18177
|
+
}
|
|
18178
|
+
});
|
|
18179
|
+
}
|
|
18180
|
+
|
|
18181
|
+
async *entries(prefix = '') {
|
|
18182
|
+
const db = await this.open();
|
|
18183
|
+
const rows = await new Promise((resolve, reject) => {
|
|
18184
|
+
const tx = db.transaction('kv', 'readonly');
|
|
18185
|
+
const store = tx.objectStore('kv');
|
|
18186
|
+
const out = [];
|
|
18187
|
+
const range = globalThis.IDBKeyRange.bound(prefix, prefix + '\uffff', false, true);
|
|
18188
|
+
const req = store.openCursor(range);
|
|
18189
|
+
req.onerror = () => reject(req.error);
|
|
18190
|
+
req.onsuccess = () => {
|
|
18191
|
+
const cursor = req.result;
|
|
18192
|
+
if (!cursor) return;
|
|
18193
|
+
out.push([cursor.key, cursor.value]);
|
|
18194
|
+
cursor.continue();
|
|
18195
|
+
};
|
|
18196
|
+
tx.oncomplete = () => resolve(out);
|
|
18197
|
+
tx.onerror = () => reject(tx.error || new Error('IndexedDB transaction failed'));
|
|
18198
|
+
});
|
|
18199
|
+
for (const row of rows) yield row;
|
|
18200
|
+
}
|
|
18201
|
+
|
|
18202
|
+
async close() {
|
|
18203
|
+
if (!this.dbPromise) return;
|
|
18204
|
+
const db = await this.dbPromise;
|
|
18205
|
+
if (db && typeof db.close === 'function') db.close();
|
|
18206
|
+
}
|
|
18207
|
+
}
|
|
18208
|
+
|
|
18209
|
+
class MemoryFactStore {
|
|
18210
|
+
constructor() {
|
|
18211
|
+
this.map = new Map();
|
|
18212
|
+
}
|
|
18213
|
+
|
|
18214
|
+
async add(triple, kind = 'explicit') {
|
|
18215
|
+
const key = tripleToStoreKey(triple);
|
|
18216
|
+
const prev = this.map.get(key);
|
|
18217
|
+
const mask = kindMask(kind);
|
|
18218
|
+
if (prev) {
|
|
18219
|
+
const nextKind = prev.kind | mask;
|
|
18220
|
+
if (nextKind !== prev.kind) prev.kind = nextKind;
|
|
18221
|
+
return false;
|
|
18222
|
+
}
|
|
18223
|
+
this.map.set(key, { triple, kind: mask });
|
|
18224
|
+
return true;
|
|
18225
|
+
}
|
|
18226
|
+
|
|
18227
|
+
async batchAdd(triples, kind = 'explicit') {
|
|
18228
|
+
let n = 0;
|
|
18229
|
+
for (const triple of triples || []) if (await this.add(triple, kind)) n += 1;
|
|
18230
|
+
return n;
|
|
18231
|
+
}
|
|
18232
|
+
|
|
18233
|
+
async has(triple) {
|
|
18234
|
+
return this.map.has(tripleToStoreKey(triple));
|
|
18235
|
+
}
|
|
18236
|
+
|
|
18237
|
+
async kindOf(triple) {
|
|
18238
|
+
const row = this.map.get(tripleToStoreKey(triple));
|
|
18239
|
+
return row ? row.kind : 0;
|
|
18240
|
+
}
|
|
18241
|
+
|
|
18242
|
+
async *match(s, p, o) {
|
|
18243
|
+
const sk = s == null ? null : termToStoreKey(s);
|
|
18244
|
+
const pk = p == null ? null : termToStoreKey(p);
|
|
18245
|
+
const ok = o == null ? null : termToStoreKey(o);
|
|
18246
|
+
for (const { triple } of this.map.values()) {
|
|
18247
|
+
if (sk !== null && termToStoreKey(triple.s) !== sk) continue;
|
|
18248
|
+
if (pk !== null && termToStoreKey(triple.p) !== pk) continue;
|
|
18249
|
+
if (ok !== null && termToStoreKey(triple.o) !== ok) continue;
|
|
18250
|
+
yield triple;
|
|
18251
|
+
}
|
|
18252
|
+
}
|
|
18253
|
+
|
|
18254
|
+
async clear() {
|
|
18255
|
+
this.map.clear();
|
|
18256
|
+
}
|
|
18257
|
+
|
|
18258
|
+
async close() {}
|
|
18259
|
+
}
|
|
18260
|
+
|
|
18261
|
+
class PersistentFactStore {
|
|
18262
|
+
constructor(kv, options = {}) {
|
|
18263
|
+
this.kv = kv;
|
|
18264
|
+
this.termCacheByKey = new Map();
|
|
18265
|
+
this.termCacheById = new Map();
|
|
18266
|
+
this.name = options.name || 'default';
|
|
18267
|
+
}
|
|
18268
|
+
|
|
18269
|
+
async clear() {
|
|
18270
|
+
await this.kv.clear();
|
|
18271
|
+
this.termCacheByKey.clear();
|
|
18272
|
+
this.termCacheById.clear();
|
|
18273
|
+
}
|
|
18274
|
+
|
|
18275
|
+
async __nextTermId() {
|
|
18276
|
+
const key = 'meta/nextTermId';
|
|
18277
|
+
const current = Number((await this.kv.get(key)) || 1);
|
|
18278
|
+
await this.kv.put(key, current + 1);
|
|
18279
|
+
return idPart(current);
|
|
18280
|
+
}
|
|
18281
|
+
|
|
18282
|
+
async __idForTerm(term, create) {
|
|
18283
|
+
const canonical = termToStoreKey(term);
|
|
18284
|
+
if (this.termCacheByKey.has(canonical)) return this.termCacheByKey.get(canonical);
|
|
18285
|
+
const byLexKey = `term/byLex/${base64url(canonical)}`;
|
|
18286
|
+
const found = await this.kv.get(byLexKey);
|
|
18287
|
+
if (found !== undefined) {
|
|
18288
|
+
this.termCacheByKey.set(canonical, found);
|
|
18289
|
+
return found;
|
|
18290
|
+
}
|
|
18291
|
+
if (!create) return null;
|
|
18292
|
+
const id = await this.__nextTermId();
|
|
18293
|
+
const json = termToJson(term);
|
|
18294
|
+
await this.kv.batch([
|
|
18295
|
+
{ type: 'put', key: byLexKey, value: id },
|
|
18296
|
+
{ type: 'put', key: `term/byId/${id}`, value: json },
|
|
18297
|
+
]);
|
|
18298
|
+
this.termCacheByKey.set(canonical, id);
|
|
18299
|
+
this.termCacheById.set(id, term);
|
|
18300
|
+
return id;
|
|
18301
|
+
}
|
|
18302
|
+
|
|
18303
|
+
async __termById(id) {
|
|
18304
|
+
if (this.termCacheById.has(id)) return this.termCacheById.get(id);
|
|
18305
|
+
const json = await this.kv.get(`term/byId/${id}`);
|
|
18306
|
+
if (json === undefined) throw new Error(`Corrupt fact store: missing term ${id}`);
|
|
18307
|
+
const term = termFromJson(json);
|
|
18308
|
+
this.termCacheById.set(id, term);
|
|
18309
|
+
return term;
|
|
18310
|
+
}
|
|
18311
|
+
|
|
18312
|
+
async __tripleFromIds(sid, pid, oid) {
|
|
18313
|
+
return new Triple(await this.__termById(sid), await this.__termById(pid), await this.__termById(oid));
|
|
18314
|
+
}
|
|
18315
|
+
|
|
18316
|
+
async add(triple, kind = 'explicit') {
|
|
18317
|
+
const sid = await this.__idForTerm(triple.s, true);
|
|
18318
|
+
const pid = await this.__idForTerm(triple.p, true);
|
|
18319
|
+
const oid = await this.__idForTerm(triple.o, true);
|
|
18320
|
+
const mask = kindMask(kind);
|
|
18321
|
+
const primary = `triple/${sid}/${pid}/${oid}`;
|
|
18322
|
+
const prev = await this.kv.get(primary);
|
|
18323
|
+
if (prev !== undefined) {
|
|
18324
|
+
const nextKind = (typeof prev === 'number' ? prev : prev.kind || 0) | mask;
|
|
18325
|
+
if (nextKind !== prev) await this.kv.put(primary, nextKind);
|
|
18326
|
+
return false;
|
|
18327
|
+
}
|
|
18328
|
+
await this.kv.batch([
|
|
18329
|
+
{ type: 'put', key: primary, value: mask },
|
|
18330
|
+
{ type: 'put', key: `i/spo/${sid}/${pid}/${oid}`, value: mask },
|
|
18331
|
+
{ type: 'put', key: `i/pos/${pid}/${oid}/${sid}`, value: mask },
|
|
18332
|
+
{ type: 'put', key: `i/osp/${oid}/${sid}/${pid}`, value: mask },
|
|
18333
|
+
]);
|
|
18334
|
+
return true;
|
|
18335
|
+
}
|
|
18336
|
+
|
|
18337
|
+
async batchAdd(triples, kind = 'explicit') {
|
|
18338
|
+
let n = 0;
|
|
18339
|
+
for (const triple of triples || []) if (await this.add(triple, kind)) n += 1;
|
|
18340
|
+
return n;
|
|
18341
|
+
}
|
|
18342
|
+
|
|
18343
|
+
async has(triple) {
|
|
18344
|
+
return (await this.kindOf(triple)) !== 0;
|
|
18345
|
+
}
|
|
18346
|
+
|
|
18347
|
+
async kindOf(triple) {
|
|
18348
|
+
const sid = await this.__idForTerm(triple.s, false);
|
|
18349
|
+
if (sid === null) return 0;
|
|
18350
|
+
const pid = await this.__idForTerm(triple.p, false);
|
|
18351
|
+
if (pid === null) return 0;
|
|
18352
|
+
const oid = await this.__idForTerm(triple.o, false);
|
|
18353
|
+
if (oid === null) return 0;
|
|
18354
|
+
const value = await this.kv.get(`triple/${sid}/${pid}/${oid}`);
|
|
18355
|
+
return typeof value === 'number' ? value : value && typeof value.kind === 'number' ? value.kind : 0;
|
|
18356
|
+
}
|
|
18357
|
+
|
|
18358
|
+
async __idsForPattern(s, p, o) {
|
|
18359
|
+
const sid = s == null ? null : await this.__idForTerm(s, false);
|
|
18360
|
+
const pid = p == null ? null : await this.__idForTerm(p, false);
|
|
18361
|
+
const oid = o == null ? null : await this.__idForTerm(o, false);
|
|
18362
|
+
if ((s != null && sid === null) || (p != null && pid === null) || (o != null && oid === null)) return null;
|
|
18363
|
+
return { sid, pid, oid };
|
|
18364
|
+
}
|
|
18365
|
+
|
|
18366
|
+
__scanPlan(sid, pid, oid) {
|
|
18367
|
+
if (sid && pid && oid) return { index: 'triple', prefix: `triple/${sid}/${pid}/${oid}`, order: 'spo' };
|
|
18368
|
+
if (sid && pid) return { index: 'spo', prefix: `i/spo/${sid}/${pid}/`, order: 'spo' };
|
|
18369
|
+
if (pid && oid) return { index: 'pos', prefix: `i/pos/${pid}/${oid}/`, order: 'pos' };
|
|
18370
|
+
if (sid && oid) return { index: 'osp', prefix: `i/osp/${oid}/${sid}/`, order: 'osp' };
|
|
18371
|
+
if (pid) return { index: 'pos', prefix: `i/pos/${pid}/`, order: 'pos' };
|
|
18372
|
+
if (sid) return { index: 'spo', prefix: `i/spo/${sid}/`, order: 'spo' };
|
|
18373
|
+
if (oid) return { index: 'osp', prefix: `i/osp/${oid}/`, order: 'osp' };
|
|
18374
|
+
return { index: 'spo', prefix: 'i/spo/', order: 'spo' };
|
|
18375
|
+
}
|
|
18376
|
+
|
|
18377
|
+
__decodeIndexKey(key, plan) {
|
|
18378
|
+
if (plan.index === 'triple') {
|
|
18379
|
+
const parts = key.split('/');
|
|
18380
|
+
return { sid: parts[1], pid: parts[2], oid: parts[3] };
|
|
18381
|
+
}
|
|
18382
|
+
const rest = key.slice(`i/${plan.index}/`.length).split('/');
|
|
18383
|
+
if (plan.order === 'spo') return { sid: rest[0], pid: rest[1], oid: rest[2] };
|
|
18384
|
+
if (plan.order === 'pos') return { pid: rest[0], oid: rest[1], sid: rest[2] };
|
|
18385
|
+
return { oid: rest[0], sid: rest[1], pid: rest[2] };
|
|
18386
|
+
}
|
|
18387
|
+
|
|
18388
|
+
async *match(s, p, o) {
|
|
18389
|
+
const ids = await this.__idsForPattern(s, p, o);
|
|
18390
|
+
if (!ids) return;
|
|
18391
|
+
const plan = this.__scanPlan(ids.sid, ids.pid, ids.oid);
|
|
18392
|
+
const seen = new Set();
|
|
18393
|
+
for await (const [key] of this.kv.entries(plan.prefix)) {
|
|
18394
|
+
const row = this.__decodeIndexKey(key, plan);
|
|
18395
|
+
if (ids.sid && row.sid !== ids.sid) continue;
|
|
18396
|
+
if (ids.pid && row.pid !== ids.pid) continue;
|
|
18397
|
+
if (ids.oid && row.oid !== ids.oid) continue;
|
|
18398
|
+
const primary = `${row.sid}/${row.pid}/${row.oid}`;
|
|
18399
|
+
if (seen.has(primary)) continue;
|
|
18400
|
+
seen.add(primary);
|
|
18401
|
+
yield this.__tripleFromIds(row.sid, row.pid, row.oid);
|
|
18402
|
+
}
|
|
18403
|
+
}
|
|
18404
|
+
|
|
18405
|
+
async close() {
|
|
18406
|
+
if (this.kv && typeof this.kv.close === 'function') await this.kv.close();
|
|
18407
|
+
}
|
|
18408
|
+
}
|
|
18409
|
+
|
|
18410
|
+
function nodeStoreLocation(name, storePath) {
|
|
18411
|
+
const path = __dynamicRequire('node:path');
|
|
18412
|
+
const os = __dynamicRequire('node:os');
|
|
18413
|
+
if (!path || !os) return null;
|
|
18414
|
+
const base = storePath || path.join(os.homedir ? os.homedir() : '.', '.eyeling-store');
|
|
18415
|
+
return path.join(base, safeStoreName(name));
|
|
18416
|
+
}
|
|
18417
|
+
|
|
18418
|
+
async function createPersistentFactStore(options = {}) {
|
|
18419
|
+
const name = typeof options === 'string' ? options : options.name || 'default';
|
|
18420
|
+
const clear = !!(options && options.clear);
|
|
18421
|
+
let kv;
|
|
18422
|
+
|
|
18423
|
+
if (typeof globalThis !== 'undefined' && globalThis.indexedDB && !__dynamicRequire('node:fs')) {
|
|
18424
|
+
kv = new IndexedDbKv(name);
|
|
18425
|
+
} else {
|
|
18426
|
+
const location = nodeStoreLocation(name, options && options.path);
|
|
18427
|
+
try {
|
|
18428
|
+
kv = new ClassicLevelKv(location);
|
|
18429
|
+
} catch {
|
|
18430
|
+
kv = new JsonFileKv(`${location}.json`);
|
|
18431
|
+
}
|
|
18432
|
+
}
|
|
18433
|
+
|
|
18434
|
+
const store = new PersistentFactStore(kv, { name });
|
|
18435
|
+
if (clear) await store.clear();
|
|
18436
|
+
return store;
|
|
18437
|
+
}
|
|
18438
|
+
|
|
18439
|
+
async function createFactStore(options = null) {
|
|
18440
|
+
if (!options) return new MemoryFactStore();
|
|
18441
|
+
if (typeof options === 'string') return createPersistentFactStore({ name: options });
|
|
18442
|
+
if (options.type === 'memory') return new MemoryFactStore();
|
|
18443
|
+
if (options.backend === 'memory') return new MemoryFactStore();
|
|
18444
|
+
return createPersistentFactStore(options);
|
|
18445
|
+
}
|
|
18446
|
+
|
|
18447
|
+
async function collectStore(store) {
|
|
18448
|
+
const out = [];
|
|
18449
|
+
for await (const tr of store.match(null, null, null)) out.push(tr);
|
|
18450
|
+
return out;
|
|
18451
|
+
}
|
|
18452
|
+
|
|
18453
|
+
module.exports = {
|
|
18454
|
+
KIND_EXPLICIT,
|
|
18455
|
+
KIND_INFERRED,
|
|
18456
|
+
MemoryFactStore,
|
|
18457
|
+
PersistentFactStore,
|
|
18458
|
+
createFactStore,
|
|
18459
|
+
createPersistentFactStore,
|
|
18460
|
+
collectStore,
|
|
18461
|
+
termToStoreKey,
|
|
18462
|
+
tripleToStoreKey,
|
|
18463
|
+
termToJson,
|
|
18464
|
+
termFromJson,
|
|
18465
|
+
tripleToJson,
|
|
18466
|
+
tripleFromJson,
|
|
18467
|
+
};
|
|
18468
|
+
|
|
17218
18469
|
};
|
|
17219
18470
|
__modules["lib/time.js"] = function(require, module, exports){
|
|
17220
18471
|
/**
|