eyeling 1.28.9 → 1.29.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/README.md +60 -1
- package/dist/browser/eyeling.browser.js +915 -0
- package/dist/browser/index.mjs +10 -0
- package/eyeling.js +915 -0
- package/index.d.ts +59 -0
- package/index.js +17 -0
- package/lib/cli.js +102 -0
- package/lib/engine.js +120 -0
- package/lib/entry.js +4 -0
- package/lib/rdfjs.js +1 -0
- package/lib/store.js +685 -0
- package/package.json +8 -3
- package/test/store.test.js +138 -0
- package/tools/bundle.js +10 -0
|
@@ -5990,6 +5990,9 @@ async function main() {
|
|
|
5990
5990
|
` -p, --proof Enable proof explanations.\n` +
|
|
5991
5991
|
` -r, --rdf Enable RDF/TriG input/output compatibility.\n` +
|
|
5992
5992
|
` --stream-messages Process RDF Message Logs one message at a time under -r.\n` +
|
|
5993
|
+
` --store <name> Use an optional persistent fact store.\n` +
|
|
5994
|
+
` --store-clear Clear the named store before this run.\n` +
|
|
5995
|
+
` --store-path <dir> Node.js persistent store directory.\n` +
|
|
5993
5996
|
` -s, --super-restricted Disable all builtins except => and <=.\n` +
|
|
5994
5997
|
` -t, --stream Stream derived triples as soon as they are derived.\n` +
|
|
5995
5998
|
` -v, --version Print version and exit.\n`;
|
|
@@ -6026,6 +6029,35 @@ async function main() {
|
|
|
6026
6029
|
builtinModules.push(a.slice('--builtin='.length));
|
|
6027
6030
|
continue;
|
|
6028
6031
|
}
|
|
6032
|
+
if (a === '--store') {
|
|
6033
|
+
const next = argv[i + 1];
|
|
6034
|
+
if (!next || next.startsWith('-')) {
|
|
6035
|
+
console.error('Error: --store expects a store name.');
|
|
6036
|
+
process.exit(1);
|
|
6037
|
+
}
|
|
6038
|
+
argv.__storeName = next;
|
|
6039
|
+
i += 1;
|
|
6040
|
+
continue;
|
|
6041
|
+
}
|
|
6042
|
+
if (typeof a === 'string' && a.startsWith('--store=')) {
|
|
6043
|
+
argv.__storeName = a.slice('--store='.length);
|
|
6044
|
+
continue;
|
|
6045
|
+
}
|
|
6046
|
+
if (a === '--store-path') {
|
|
6047
|
+
const next = argv[i + 1];
|
|
6048
|
+
if (!next || next.startsWith('-')) {
|
|
6049
|
+
console.error('Error: --store-path expects a directory path.');
|
|
6050
|
+
process.exit(1);
|
|
6051
|
+
}
|
|
6052
|
+
argv.__storePath = next;
|
|
6053
|
+
i += 1;
|
|
6054
|
+
continue;
|
|
6055
|
+
}
|
|
6056
|
+
if (typeof a === 'string' && a.startsWith('--store-path=')) {
|
|
6057
|
+
argv.__storePath = a.slice('--store-path='.length);
|
|
6058
|
+
continue;
|
|
6059
|
+
}
|
|
6060
|
+
if (a === '--store-clear') continue;
|
|
6029
6061
|
if (a === '-' || !a.startsWith('-')) positional.push(a);
|
|
6030
6062
|
}
|
|
6031
6063
|
|
|
@@ -6033,6 +6065,9 @@ async function main() {
|
|
|
6033
6065
|
const streamMode = argv.includes('--stream') || argv.includes('-t');
|
|
6034
6066
|
const streamMessagesMode = argv.includes('--stream-messages');
|
|
6035
6067
|
const rdfMode = argv.includes('--rdf') || argv.includes('-r');
|
|
6068
|
+
const storeName = argv.__storeName || null;
|
|
6069
|
+
const storePath = argv.__storePath || null;
|
|
6070
|
+
const storeClear = argv.includes('--store-clear');
|
|
6036
6071
|
|
|
6037
6072
|
// --enforce-https: rewrite http:// -> https:// for log dereferencing builtins
|
|
6038
6073
|
if (argv.includes('--enforce-https') || argv.includes('-e')) {
|
|
@@ -6056,6 +6091,10 @@ async function main() {
|
|
|
6056
6091
|
|
|
6057
6092
|
|
|
6058
6093
|
if (streamMessagesMode) {
|
|
6094
|
+
if (storeName) {
|
|
6095
|
+
console.error('Error: --store cannot be combined with --stream-messages yet.');
|
|
6096
|
+
process.exit(1);
|
|
6097
|
+
}
|
|
6059
6098
|
if (!rdfMode) {
|
|
6060
6099
|
console.error('Error: --stream-messages requires -r/--rdf.');
|
|
6061
6100
|
process.exit(1);
|
|
@@ -6211,6 +6250,69 @@ function factsContainOutputStrings(triplesForOutput) {
|
|
|
6211
6250
|
const hasQueries = Array.isArray(qrules) && qrules.length;
|
|
6212
6251
|
const mayAutoRenderOutputStrings = programMayProduceOutputStrings(triples, frules, qrules);
|
|
6213
6252
|
|
|
6253
|
+
if (storeName) {
|
|
6254
|
+
if (streamMode) {
|
|
6255
|
+
console.error('Error: --store cannot be combined with --stream yet.');
|
|
6256
|
+
process.exit(1);
|
|
6257
|
+
}
|
|
6258
|
+
|
|
6259
|
+
const storeResult = await engine.runAsync(
|
|
6260
|
+
{ prefixes, triples, frules, brules, logQueryRules: qrules },
|
|
6261
|
+
{
|
|
6262
|
+
proof: engine.getProofCommentsEnabled(),
|
|
6263
|
+
rdf: rdfMode,
|
|
6264
|
+
store: { name: storeName, clear: storeClear, path: storePath || undefined },
|
|
6265
|
+
},
|
|
6266
|
+
);
|
|
6267
|
+
|
|
6268
|
+
const storeFacts = storeResult.facts || [];
|
|
6269
|
+
const storeDerived = storeResult.derived || [];
|
|
6270
|
+
const storeHasQueries = !!storeResult.queryMode;
|
|
6271
|
+
const storeOutTriples = storeHasQueries ? (storeResult.queryTriples || []) : (mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled() ? [] : storeDerived.map((df) => df.fact));
|
|
6272
|
+
const storeOutDerived = storeHasQueries ? (storeResult.queryDerived || []) : storeDerived;
|
|
6273
|
+
const renderedOutputTriples = storeHasQueries ? storeOutTriples : storeFacts;
|
|
6274
|
+
|
|
6275
|
+
if (factsContainOutputStrings(renderedOutputTriples)) {
|
|
6276
|
+
process.stdout.write(engine.collectOutputStringsFromFacts(renderedOutputTriples, prefixes));
|
|
6277
|
+
if (storeResult.store && typeof storeResult.store.close === 'function') await storeResult.store.close();
|
|
6278
|
+
return;
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
if (engine.getProofCommentsEnabled()) {
|
|
6282
|
+
process.stdout.write(engine.renderProofDocument(storeOutDerived, storeDerived.concat(storeOutDerived || []), triples, prefixes, brules));
|
|
6283
|
+
if (storeResult.store && typeof storeResult.store.close === 'function') await storeResult.store.close();
|
|
6284
|
+
return;
|
|
6285
|
+
}
|
|
6286
|
+
|
|
6287
|
+
let bodyText = '';
|
|
6288
|
+
if (rdfMode) bodyText = storeOutTriples.map((tr) => engine.tripleToRdfCompatible(tr, prefixes)).join('\n');
|
|
6289
|
+
else if (storeHasQueries) bodyText = engine.prettyPrintQueryTriples(storeOutTriples, prefixes);
|
|
6290
|
+
|
|
6291
|
+
let usedPrefixes = prefixes.prefixesUsedForOutput(storeOutTriples);
|
|
6292
|
+
if (rdfMode && bodyText) usedPrefixes = usedPrefixes.filter(([pfx]) => pfx === '' || bodyText.includes(pfx + ':'));
|
|
6293
|
+
|
|
6294
|
+
if (rdfMode && bodyText.includes('<<(')) {
|
|
6295
|
+
console.log('VERSION "1.2"');
|
|
6296
|
+
console.log();
|
|
6297
|
+
}
|
|
6298
|
+
|
|
6299
|
+
for (const [pfx, base] of usedPrefixes) {
|
|
6300
|
+
if (pfx === '') console.log(`@prefix : <${base}> .`);
|
|
6301
|
+
else console.log(`@prefix ${pfx}: <${base}> .`);
|
|
6302
|
+
}
|
|
6303
|
+
if (storeOutTriples.length && usedPrefixes.length) console.log();
|
|
6304
|
+
|
|
6305
|
+
if (bodyText) process.stdout.write(String(bodyText).replace(/\s*$/g, '') + '\n');
|
|
6306
|
+
else {
|
|
6307
|
+
for (const df of storeOutDerived) {
|
|
6308
|
+
console.log(rdfMode ? engine.tripleToRdfCompatible(df.fact, prefixes) : engine.tripleToN3(df.fact, prefixes));
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6311
|
+
|
|
6312
|
+
if (storeResult.store && typeof storeResult.store.close === 'function') await storeResult.store.close();
|
|
6313
|
+
return;
|
|
6314
|
+
}
|
|
6315
|
+
|
|
6214
6316
|
if (streamMode && !hasQueries && !mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled()) {
|
|
6215
6317
|
const usedInInput = mergedDocument.usedPrefixes instanceof Set ? new Set(mergedDocument.usedPrefixes) : new Set();
|
|
6216
6318
|
const outPrefixes = restrictPrefixEnv(prefixes, usedInInput);
|
|
@@ -6886,6 +6988,7 @@ const {
|
|
|
6886
6988
|
getDataFactory,
|
|
6887
6989
|
internalTripleToRdfJsQuads,
|
|
6888
6990
|
normalizeParsedReasonerInputSync,
|
|
6991
|
+
normalizeParsedReasonerInputAsync,
|
|
6889
6992
|
normalizeReasonerInputSync,
|
|
6890
6993
|
normalizeReasonerInputAsync,
|
|
6891
6994
|
} = require('./rdfjs');
|
|
@@ -6894,6 +6997,7 @@ const trace = require('./trace');
|
|
|
6894
6997
|
const { deterministicSkolemIdFromKey } = require('./skolem');
|
|
6895
6998
|
|
|
6896
6999
|
const deref = require('./deref');
|
|
7000
|
+
const { createFactStore, collectStore, MemoryFactStore, PersistentFactStore } = require('./store');
|
|
6897
7001
|
|
|
6898
7002
|
const hasOwn = Object.prototype.hasOwnProperty;
|
|
6899
7003
|
|
|
@@ -10572,6 +10676,120 @@ function reasonStream(input, opts = {}) {
|
|
|
10572
10676
|
return __out;
|
|
10573
10677
|
}
|
|
10574
10678
|
|
|
10679
|
+
|
|
10680
|
+
async function __parseRunAsyncInput(input, opts) {
|
|
10681
|
+
const {
|
|
10682
|
+
baseIri = null,
|
|
10683
|
+
proof = false,
|
|
10684
|
+
rdf = false,
|
|
10685
|
+
sourceLabel = '<input>',
|
|
10686
|
+
} = opts || {};
|
|
10687
|
+
|
|
10688
|
+
const parsedSourceList = parseN3SourceList(input, { baseIri, rdf: !!rdf, sourceLocations: !!proof });
|
|
10689
|
+
if (parsedSourceList) return parsedSourceList;
|
|
10690
|
+
|
|
10691
|
+
const parsedObject = await normalizeParsedReasonerInputAsync(input);
|
|
10692
|
+
if (parsedObject) {
|
|
10693
|
+
if (baseIri && parsedObject.prefixes && typeof parsedObject.prefixes.setBase === 'function') parsedObject.prefixes.setBase(baseIri);
|
|
10694
|
+
return parsedObject;
|
|
10695
|
+
}
|
|
10696
|
+
|
|
10697
|
+
const n3Text = await normalizeReasonerInputAsync(input);
|
|
10698
|
+
return parseN3Text(n3Text, {
|
|
10699
|
+
baseIri: baseIri || '',
|
|
10700
|
+
label: sourceLabel || '<input>',
|
|
10701
|
+
keepSourceArtifacts: false,
|
|
10702
|
+
sourceLocations: !!proof,
|
|
10703
|
+
rdf: !!rdf,
|
|
10704
|
+
});
|
|
10705
|
+
}
|
|
10706
|
+
|
|
10707
|
+
function __withoutStoreOptions(opts) {
|
|
10708
|
+
const out = { ...(opts || {}) };
|
|
10709
|
+
delete out.store;
|
|
10710
|
+
delete out.storePath;
|
|
10711
|
+
delete out.storeClear;
|
|
10712
|
+
return out;
|
|
10713
|
+
}
|
|
10714
|
+
|
|
10715
|
+
function __normalizeStoreOption(opts) {
|
|
10716
|
+
const cfg = opts && opts.store;
|
|
10717
|
+
if (!cfg) return null;
|
|
10718
|
+
if (typeof cfg === 'string') return { name: cfg, path: opts.storePath || undefined, clear: !!opts.storeClear };
|
|
10719
|
+
if (typeof cfg === 'object') {
|
|
10720
|
+
return {
|
|
10721
|
+
...cfg,
|
|
10722
|
+
path: cfg.path || opts.storePath || undefined,
|
|
10723
|
+
clear: !!(cfg.clear || opts.storeClear),
|
|
10724
|
+
};
|
|
10725
|
+
}
|
|
10726
|
+
throw new TypeError('runAsync option "store" must be a store name string or a store options object');
|
|
10727
|
+
}
|
|
10728
|
+
|
|
10729
|
+
async function runAsync(input, opts = {}) {
|
|
10730
|
+
const storeConfig = __normalizeStoreOption(opts);
|
|
10731
|
+
const runOpts = __withoutStoreOptions(opts);
|
|
10732
|
+
|
|
10733
|
+
// No store configured: keep ordinary in-memory behavior, but accept async RDF/JS
|
|
10734
|
+
// iterables by normalizing the input before calling the synchronous engine.
|
|
10735
|
+
if (!storeConfig) {
|
|
10736
|
+
const normalizedInput = parseN3SourceList(input, {
|
|
10737
|
+
baseIri: runOpts.baseIri || null,
|
|
10738
|
+
rdf: !!runOpts.rdf,
|
|
10739
|
+
sourceLocations: !!runOpts.proof,
|
|
10740
|
+
}) || (await normalizeReasonerInputAsync(input));
|
|
10741
|
+
return reasonStream(normalizedInput, runOpts);
|
|
10742
|
+
}
|
|
10743
|
+
|
|
10744
|
+
const store = await createFactStore(storeConfig);
|
|
10745
|
+
let closeStore = true;
|
|
10746
|
+
try {
|
|
10747
|
+
const parsed = await __parseRunAsyncInput(input, runOpts);
|
|
10748
|
+
|
|
10749
|
+
// Persist the newly supplied explicit facts first, then include all stored
|
|
10750
|
+
// facts as the starting closure for this run. This lets --store reuse facts
|
|
10751
|
+
// across runs while keeping the current in-memory prover semantics intact.
|
|
10752
|
+
await store.batchAdd(parsed.triples || [], 'explicit');
|
|
10753
|
+
const storedTriples = await collectStore(store);
|
|
10754
|
+
|
|
10755
|
+
const storedKeys = new Set();
|
|
10756
|
+
for (const tr of storedTriples) storedKeys.add(require('./store').tripleToStoreKey(tr));
|
|
10757
|
+
const mergedTriples = storedTriples.slice();
|
|
10758
|
+
for (const tr of parsed.triples || []) {
|
|
10759
|
+
const key = require('./store').tripleToStoreKey(tr);
|
|
10760
|
+
if (!storedKeys.has(key)) {
|
|
10761
|
+
storedKeys.add(key);
|
|
10762
|
+
mergedTriples.push(tr);
|
|
10763
|
+
}
|
|
10764
|
+
}
|
|
10765
|
+
|
|
10766
|
+
const result = reasonStream(
|
|
10767
|
+
{
|
|
10768
|
+
prefixes: parsed.prefixes,
|
|
10769
|
+
triples: mergedTriples,
|
|
10770
|
+
frules: parsed.frules,
|
|
10771
|
+
brules: parsed.brules,
|
|
10772
|
+
logQueryRules: parsed.logQueryRules,
|
|
10773
|
+
},
|
|
10774
|
+
runOpts,
|
|
10775
|
+
);
|
|
10776
|
+
|
|
10777
|
+
await store.batchAdd((result.derived || []).map((df) => df.fact), 'inferred');
|
|
10778
|
+
result.store = store;
|
|
10779
|
+
closeStore = false;
|
|
10780
|
+
return result;
|
|
10781
|
+
} catch (e) {
|
|
10782
|
+
await store.close();
|
|
10783
|
+
throw e;
|
|
10784
|
+
} finally {
|
|
10785
|
+
// Keep result.store usable for callers. On exceptional paths it is closed
|
|
10786
|
+
// above; on success callers may close it when they are done.
|
|
10787
|
+
if (closeStore) {
|
|
10788
|
+
try { await store.close(); } catch {}
|
|
10789
|
+
}
|
|
10790
|
+
}
|
|
10791
|
+
}
|
|
10792
|
+
|
|
10575
10793
|
function reasonRdfJs(input, opts = {}) {
|
|
10576
10794
|
const { dataFactory = null, skipUnsupportedRdfJs = false, ...restOpts } = opts || {};
|
|
10577
10795
|
const rdfFactory = getDataFactory(dataFactory);
|
|
@@ -10679,6 +10897,7 @@ function setTracePrefixes(v) {
|
|
|
10679
10897
|
|
|
10680
10898
|
module.exports = {
|
|
10681
10899
|
reasonStream,
|
|
10900
|
+
runAsync,
|
|
10682
10901
|
reasonRdfJs,
|
|
10683
10902
|
collectLogQueryConclusions,
|
|
10684
10903
|
forwardChainAndCollectLogQueryConclusions,
|
|
@@ -10715,6 +10934,9 @@ module.exports = {
|
|
|
10715
10934
|
loadBuiltinModule,
|
|
10716
10935
|
listBuiltinIris,
|
|
10717
10936
|
INFERENCE_FUSE_EXIT_CODE,
|
|
10937
|
+
createFactStore,
|
|
10938
|
+
MemoryFactStore,
|
|
10939
|
+
PersistentFactStore,
|
|
10718
10940
|
};
|
|
10719
10941
|
|
|
10720
10942
|
};
|
|
@@ -10738,6 +10960,7 @@ const { dataFactory } = require('./rdfjs');
|
|
|
10738
10960
|
module.exports = {
|
|
10739
10961
|
// public
|
|
10740
10962
|
reasonStream: engine.reasonStream,
|
|
10963
|
+
runAsync: engine.runAsync,
|
|
10741
10964
|
reasonRdfJs: engine.reasonRdfJs,
|
|
10742
10965
|
rdfjs: dataFactory,
|
|
10743
10966
|
main: engine.main,
|
|
@@ -10762,6 +10985,9 @@ module.exports = {
|
|
|
10762
10985
|
registerBuiltinModule: engine.registerBuiltinModule,
|
|
10763
10986
|
loadBuiltinModule: engine.loadBuiltinModule,
|
|
10764
10987
|
listBuiltinIris: engine.listBuiltinIris,
|
|
10988
|
+
createFactStore: engine.createFactStore,
|
|
10989
|
+
MemoryFactStore: engine.MemoryFactStore,
|
|
10990
|
+
PersistentFactStore: engine.PersistentFactStore,
|
|
10765
10991
|
getEnforceHttpsEnabled: engine.getEnforceHttpsEnabled,
|
|
10766
10992
|
setEnforceHttpsEnabled: engine.setEnforceHttpsEnabled,
|
|
10767
10993
|
getProofCommentsEnabled: engine.getProofCommentsEnabled,
|
|
@@ -17050,6 +17276,7 @@ module.exports = {
|
|
|
17050
17276
|
internalTripleToRdfJsQuads,
|
|
17051
17277
|
internalTripleToRdfJsQuadInGraph,
|
|
17052
17278
|
normalizeParsedReasonerInputSync,
|
|
17279
|
+
normalizeParsedReasonerInputAsync,
|
|
17053
17280
|
normalizeReasonerInputSync,
|
|
17054
17281
|
normalizeReasonerInputAsync,
|
|
17055
17282
|
hasEyelingObjectInput,
|
|
@@ -17215,6 +17442,694 @@ module.exports = {
|
|
|
17215
17442
|
deterministicSkolemIdFromKey,
|
|
17216
17443
|
};
|
|
17217
17444
|
|
|
17445
|
+
};
|
|
17446
|
+
__modules["lib/store.js"] = function(require, module, exports){
|
|
17447
|
+
/**
|
|
17448
|
+
* Eyeling Reasoner — fact stores
|
|
17449
|
+
*
|
|
17450
|
+
* Optional async fact-store abstraction used by runAsync() and by tests. The
|
|
17451
|
+
* in-memory reasoner remains the default; persistent stores are opt-in.
|
|
17452
|
+
*/
|
|
17453
|
+
|
|
17454
|
+
'use strict';
|
|
17455
|
+
|
|
17456
|
+
const {
|
|
17457
|
+
Iri,
|
|
17458
|
+
Literal,
|
|
17459
|
+
Var,
|
|
17460
|
+
Blank,
|
|
17461
|
+
ListTerm,
|
|
17462
|
+
OpenListTerm,
|
|
17463
|
+
GraphTerm,
|
|
17464
|
+
Triple,
|
|
17465
|
+
} = require('./prelude');
|
|
17466
|
+
|
|
17467
|
+
const KIND_EXPLICIT = 1;
|
|
17468
|
+
const KIND_INFERRED = 2;
|
|
17469
|
+
|
|
17470
|
+
function __dynamicRequire(name) {
|
|
17471
|
+
try {
|
|
17472
|
+
// Keep this indirect so the browser bundle can include this module without
|
|
17473
|
+
// eagerly trying to bundle Node-only or optional Level dependencies.
|
|
17474
|
+
const req = typeof require === 'function' ? require : null;
|
|
17475
|
+
return req ? req(name) : null;
|
|
17476
|
+
} catch {
|
|
17477
|
+
return null;
|
|
17478
|
+
}
|
|
17479
|
+
}
|
|
17480
|
+
|
|
17481
|
+
function base64url(s) {
|
|
17482
|
+
const text = String(s);
|
|
17483
|
+
if (typeof Buffer !== 'undefined') return Buffer.from(text, 'utf8').toString('base64url');
|
|
17484
|
+
const encoder = globalThis.TextEncoder ? new globalThis.TextEncoder() : null;
|
|
17485
|
+
if (!encoder || typeof globalThis.btoa !== 'function') {
|
|
17486
|
+
throw new Error('No UTF-8/base64 encoder available for persistent store keys');
|
|
17487
|
+
}
|
|
17488
|
+
const bytes = encoder.encode(text);
|
|
17489
|
+
let bin = '';
|
|
17490
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
17491
|
+
return globalThis.btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
17492
|
+
}
|
|
17493
|
+
|
|
17494
|
+
function idPart(id) {
|
|
17495
|
+
return String(id).padStart(16, '0');
|
|
17496
|
+
}
|
|
17497
|
+
|
|
17498
|
+
function safeStoreName(name) {
|
|
17499
|
+
const text = String(name || 'default');
|
|
17500
|
+
return encodeURIComponent(text).replace(/%/g, '_');
|
|
17501
|
+
}
|
|
17502
|
+
|
|
17503
|
+
function termToJson(term) {
|
|
17504
|
+
if (term instanceof Iri) return ['Iri', term.value];
|
|
17505
|
+
if (term instanceof Literal) return ['Literal', term.value];
|
|
17506
|
+
if (term instanceof Var) return ['Var', term.name];
|
|
17507
|
+
if (term instanceof Blank) return ['Blank', term.label];
|
|
17508
|
+
if (term instanceof ListTerm) return ['ListTerm', term.elems.map(termToJson)];
|
|
17509
|
+
if (term instanceof OpenListTerm) return ['OpenListTerm', term.prefix.map(termToJson), term.tailVar];
|
|
17510
|
+
if (term instanceof GraphTerm) return ['GraphTerm', term.triples.map(tripleToJson)];
|
|
17511
|
+
|
|
17512
|
+
// RDF/JS terms, for callers that use the store abstraction directly.
|
|
17513
|
+
if (term && typeof term === 'object' && typeof term.termType === 'string') {
|
|
17514
|
+
switch (term.termType) {
|
|
17515
|
+
case 'NamedNode':
|
|
17516
|
+
return ['Iri', term.value];
|
|
17517
|
+
case 'BlankNode':
|
|
17518
|
+
return ['Blank', term.value && term.value.startsWith('_:') ? term.value : `_:${term.value}`];
|
|
17519
|
+
case 'Variable':
|
|
17520
|
+
return ['Var', term.value];
|
|
17521
|
+
case 'Literal': {
|
|
17522
|
+
const dt = term.datatype && term.datatype.termType === 'NamedNode' ? term.datatype.value : '';
|
|
17523
|
+
const lang = typeof term.language === 'string' ? term.language : '';
|
|
17524
|
+
const q = JSON.stringify(String(term.value));
|
|
17525
|
+
if (lang) return ['Literal', `${q}@${lang}`];
|
|
17526
|
+
if (!dt || dt === 'http://www.w3.org/2001/XMLSchema#string') return ['Literal', q];
|
|
17527
|
+
return ['Literal', `${q}^^<${dt}>`];
|
|
17528
|
+
}
|
|
17529
|
+
case 'Quad':
|
|
17530
|
+
return ['GraphTerm', [tripleToJson({ s: term.subject, p: term.predicate, o: term.object })]];
|
|
17531
|
+
case 'DefaultGraph':
|
|
17532
|
+
return ['DefaultGraph'];
|
|
17533
|
+
default:
|
|
17534
|
+
break;
|
|
17535
|
+
}
|
|
17536
|
+
}
|
|
17537
|
+
|
|
17538
|
+
throw new TypeError(`Unsupported fact-store term: ${term && term.constructor ? term.constructor.name : String(term)}`);
|
|
17539
|
+
}
|
|
17540
|
+
|
|
17541
|
+
function termFromJson(json) {
|
|
17542
|
+
if (!Array.isArray(json)) throw new TypeError('Invalid serialized term');
|
|
17543
|
+
switch (json[0]) {
|
|
17544
|
+
case 'Iri':
|
|
17545
|
+
return new Iri(json[1]);
|
|
17546
|
+
case 'Literal':
|
|
17547
|
+
return new Literal(json[1]);
|
|
17548
|
+
case 'Var':
|
|
17549
|
+
return new Var(json[1]);
|
|
17550
|
+
case 'Blank':
|
|
17551
|
+
return new Blank(json[1]);
|
|
17552
|
+
case 'ListTerm':
|
|
17553
|
+
return new ListTerm((json[1] || []).map(termFromJson));
|
|
17554
|
+
case 'OpenListTerm':
|
|
17555
|
+
return new OpenListTerm((json[1] || []).map(termFromJson), json[2]);
|
|
17556
|
+
case 'GraphTerm':
|
|
17557
|
+
return new GraphTerm((json[1] || []).map(tripleFromJson));
|
|
17558
|
+
case 'DefaultGraph':
|
|
17559
|
+
return new Iri('urn:eyeling:default-graph');
|
|
17560
|
+
default:
|
|
17561
|
+
throw new TypeError(`Unsupported serialized term type: ${json[0]}`);
|
|
17562
|
+
}
|
|
17563
|
+
}
|
|
17564
|
+
|
|
17565
|
+
function tripleToJson(triple) {
|
|
17566
|
+
return [termToJson(triple.s || triple.subject), termToJson(triple.p || triple.predicate), termToJson(triple.o || triple.object)];
|
|
17567
|
+
}
|
|
17568
|
+
|
|
17569
|
+
function tripleFromJson(json) {
|
|
17570
|
+
return new Triple(termFromJson(json[0]), termFromJson(json[1]), termFromJson(json[2]));
|
|
17571
|
+
}
|
|
17572
|
+
|
|
17573
|
+
function termToStoreKey(term) {
|
|
17574
|
+
return JSON.stringify(termToJson(term));
|
|
17575
|
+
}
|
|
17576
|
+
|
|
17577
|
+
function tripleToStoreKey(triple) {
|
|
17578
|
+
return JSON.stringify(tripleToJson(triple));
|
|
17579
|
+
}
|
|
17580
|
+
|
|
17581
|
+
function kindMask(kind) {
|
|
17582
|
+
if (kind === 'explicit') return KIND_EXPLICIT;
|
|
17583
|
+
if (kind === 'inferred') return KIND_INFERRED;
|
|
17584
|
+
if (typeof kind === 'number') return kind & (KIND_EXPLICIT | KIND_INFERRED);
|
|
17585
|
+
return KIND_EXPLICIT;
|
|
17586
|
+
}
|
|
17587
|
+
|
|
17588
|
+
class MemoryKv {
|
|
17589
|
+
constructor() {
|
|
17590
|
+
this.map = new Map();
|
|
17591
|
+
}
|
|
17592
|
+
|
|
17593
|
+
async get(key) {
|
|
17594
|
+
return this.map.get(key);
|
|
17595
|
+
}
|
|
17596
|
+
|
|
17597
|
+
async put(key, value) {
|
|
17598
|
+
this.map.set(key, value);
|
|
17599
|
+
}
|
|
17600
|
+
|
|
17601
|
+
async del(key) {
|
|
17602
|
+
this.map.delete(key);
|
|
17603
|
+
}
|
|
17604
|
+
|
|
17605
|
+
async clear() {
|
|
17606
|
+
this.map.clear();
|
|
17607
|
+
}
|
|
17608
|
+
|
|
17609
|
+
async batch(ops) {
|
|
17610
|
+
for (const op of ops || []) {
|
|
17611
|
+
if (!op) continue;
|
|
17612
|
+
if (op.type === 'del') this.map.delete(op.key);
|
|
17613
|
+
else this.map.set(op.key, op.value);
|
|
17614
|
+
}
|
|
17615
|
+
}
|
|
17616
|
+
|
|
17617
|
+
async *entries(prefix = '') {
|
|
17618
|
+
const keys = Array.from(this.map.keys())
|
|
17619
|
+
.filter((key) => key.startsWith(prefix))
|
|
17620
|
+
.sort();
|
|
17621
|
+
for (const key of keys) yield [key, this.map.get(key)];
|
|
17622
|
+
}
|
|
17623
|
+
|
|
17624
|
+
async close() {}
|
|
17625
|
+
}
|
|
17626
|
+
|
|
17627
|
+
class JsonFileKv extends MemoryKv {
|
|
17628
|
+
constructor(filePath) {
|
|
17629
|
+
super();
|
|
17630
|
+
this.filePath = filePath;
|
|
17631
|
+
this.loaded = false;
|
|
17632
|
+
this.dirty = false;
|
|
17633
|
+
}
|
|
17634
|
+
|
|
17635
|
+
async open() {
|
|
17636
|
+
if (this.loaded) return;
|
|
17637
|
+
this.loaded = true;
|
|
17638
|
+
const fs = __dynamicRequire('node:fs');
|
|
17639
|
+
const path = __dynamicRequire('node:path');
|
|
17640
|
+
if (!fs || !path) throw new Error('Persistent stores need node:fs in this runtime');
|
|
17641
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
17642
|
+
if (!fs.existsSync(this.filePath)) return;
|
|
17643
|
+
const text = fs.readFileSync(this.filePath, 'utf8');
|
|
17644
|
+
if (!text.trim()) return;
|
|
17645
|
+
const data = JSON.parse(text);
|
|
17646
|
+
this.map = new Map(Array.isArray(data.entries) ? data.entries : []);
|
|
17647
|
+
}
|
|
17648
|
+
|
|
17649
|
+
async flush() {
|
|
17650
|
+
if (!this.loaded || !this.dirty) return;
|
|
17651
|
+
const fs = __dynamicRequire('node:fs');
|
|
17652
|
+
const path = __dynamicRequire('node:path');
|
|
17653
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
17654
|
+
const tmp = `${this.filePath}.tmp`;
|
|
17655
|
+
fs.writeFileSync(tmp, JSON.stringify({ entries: Array.from(this.map.entries()) }), 'utf8');
|
|
17656
|
+
fs.renameSync(tmp, this.filePath);
|
|
17657
|
+
this.dirty = false;
|
|
17658
|
+
}
|
|
17659
|
+
|
|
17660
|
+
async get(key) {
|
|
17661
|
+
await this.open();
|
|
17662
|
+
return super.get(key);
|
|
17663
|
+
}
|
|
17664
|
+
|
|
17665
|
+
async put(key, value) {
|
|
17666
|
+
await this.open();
|
|
17667
|
+
await super.put(key, value);
|
|
17668
|
+
this.dirty = true;
|
|
17669
|
+
await this.flush();
|
|
17670
|
+
}
|
|
17671
|
+
|
|
17672
|
+
async del(key) {
|
|
17673
|
+
await this.open();
|
|
17674
|
+
await super.del(key);
|
|
17675
|
+
this.dirty = true;
|
|
17676
|
+
await this.flush();
|
|
17677
|
+
}
|
|
17678
|
+
|
|
17679
|
+
async clear() {
|
|
17680
|
+
await this.open();
|
|
17681
|
+
await super.clear();
|
|
17682
|
+
this.dirty = true;
|
|
17683
|
+
await this.flush();
|
|
17684
|
+
}
|
|
17685
|
+
|
|
17686
|
+
async batch(ops) {
|
|
17687
|
+
await this.open();
|
|
17688
|
+
await super.batch(ops);
|
|
17689
|
+
this.dirty = true;
|
|
17690
|
+
await this.flush();
|
|
17691
|
+
}
|
|
17692
|
+
|
|
17693
|
+
async *entries(prefix = '') {
|
|
17694
|
+
await this.open();
|
|
17695
|
+
yield* super.entries(prefix);
|
|
17696
|
+
}
|
|
17697
|
+
|
|
17698
|
+
async close() {
|
|
17699
|
+
await this.flush();
|
|
17700
|
+
}
|
|
17701
|
+
}
|
|
17702
|
+
|
|
17703
|
+
class ClassicLevelKv {
|
|
17704
|
+
constructor(location) {
|
|
17705
|
+
const classic = __dynamicRequire('classic-level');
|
|
17706
|
+
const ClassicLevel = classic && (classic.ClassicLevel || classic.Level || classic.default);
|
|
17707
|
+
if (!ClassicLevel) throw new Error('classic-level is not installed');
|
|
17708
|
+
this.db = new ClassicLevel(location, { valueEncoding: 'json' });
|
|
17709
|
+
this.opened = false;
|
|
17710
|
+
}
|
|
17711
|
+
|
|
17712
|
+
async open() {
|
|
17713
|
+
if (this.opened) return;
|
|
17714
|
+
if (typeof this.db.open === 'function') await this.db.open();
|
|
17715
|
+
this.opened = true;
|
|
17716
|
+
}
|
|
17717
|
+
|
|
17718
|
+
async get(key) {
|
|
17719
|
+
await this.open();
|
|
17720
|
+
try {
|
|
17721
|
+
return await this.db.get(key);
|
|
17722
|
+
} catch (e) {
|
|
17723
|
+
if (e && (e.notFound || e.code === 'LEVEL_NOT_FOUND' || e.code === 'NOT_FOUND')) return undefined;
|
|
17724
|
+
throw e;
|
|
17725
|
+
}
|
|
17726
|
+
}
|
|
17727
|
+
|
|
17728
|
+
async put(key, value) {
|
|
17729
|
+
await this.open();
|
|
17730
|
+
await this.db.put(key, value);
|
|
17731
|
+
}
|
|
17732
|
+
|
|
17733
|
+
async del(key) {
|
|
17734
|
+
await this.open();
|
|
17735
|
+
await this.db.del(key);
|
|
17736
|
+
}
|
|
17737
|
+
|
|
17738
|
+
async clear() {
|
|
17739
|
+
await this.open();
|
|
17740
|
+
if (typeof this.db.clear === 'function') await this.db.clear();
|
|
17741
|
+
else {
|
|
17742
|
+
const ops = [];
|
|
17743
|
+
for await (const [key] of this.entries('')) ops.push({ type: 'del', key });
|
|
17744
|
+
if (ops.length) await this.batch(ops);
|
|
17745
|
+
}
|
|
17746
|
+
}
|
|
17747
|
+
|
|
17748
|
+
async batch(ops) {
|
|
17749
|
+
await this.open();
|
|
17750
|
+
if (!ops || !ops.length) return;
|
|
17751
|
+
await this.db.batch(ops);
|
|
17752
|
+
}
|
|
17753
|
+
|
|
17754
|
+
async *entries(prefix = '') {
|
|
17755
|
+
await this.open();
|
|
17756
|
+
const gte = prefix;
|
|
17757
|
+
const lt = prefix + '\uffff';
|
|
17758
|
+
const iterator = this.db.iterator({ gte, lt });
|
|
17759
|
+
for await (const item of iterator) {
|
|
17760
|
+
if (Array.isArray(item)) yield item;
|
|
17761
|
+
else if (item && Array.isArray(item.key)) yield item.key;
|
|
17762
|
+
}
|
|
17763
|
+
}
|
|
17764
|
+
|
|
17765
|
+
async close() {
|
|
17766
|
+
if (this.db && typeof this.db.close === 'function') await this.db.close();
|
|
17767
|
+
}
|
|
17768
|
+
}
|
|
17769
|
+
|
|
17770
|
+
class IndexedDbKv {
|
|
17771
|
+
constructor(name) {
|
|
17772
|
+
this.name = name;
|
|
17773
|
+
this.dbPromise = null;
|
|
17774
|
+
}
|
|
17775
|
+
|
|
17776
|
+
open() {
|
|
17777
|
+
if (this.dbPromise) return this.dbPromise;
|
|
17778
|
+
const idb = typeof globalThis !== 'undefined' ? globalThis.indexedDB : null;
|
|
17779
|
+
if (!idb) throw new Error('IndexedDB is not available in this runtime');
|
|
17780
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
17781
|
+
const req = idb.open(`eyeling:${this.name}`, 1);
|
|
17782
|
+
req.onupgradeneeded = () => {
|
|
17783
|
+
const db = req.result;
|
|
17784
|
+
if (!db.objectStoreNames.contains('kv')) db.createObjectStore('kv');
|
|
17785
|
+
};
|
|
17786
|
+
req.onerror = () => reject(req.error || new Error('Failed to open IndexedDB store'));
|
|
17787
|
+
req.onsuccess = () => resolve(req.result);
|
|
17788
|
+
});
|
|
17789
|
+
return this.dbPromise;
|
|
17790
|
+
}
|
|
17791
|
+
|
|
17792
|
+
async __tx(mode, fn) {
|
|
17793
|
+
const db = await this.open();
|
|
17794
|
+
return new Promise((resolve, reject) => {
|
|
17795
|
+
const tx = db.transaction('kv', mode);
|
|
17796
|
+
const store = tx.objectStore('kv');
|
|
17797
|
+
let value;
|
|
17798
|
+
tx.oncomplete = () => resolve(value);
|
|
17799
|
+
tx.onerror = () => reject(tx.error || new Error('IndexedDB transaction failed'));
|
|
17800
|
+
tx.onabort = () => reject(tx.error || new Error('IndexedDB transaction aborted'));
|
|
17801
|
+
try {
|
|
17802
|
+
value = fn(store, resolve, reject);
|
|
17803
|
+
} catch (e) {
|
|
17804
|
+
try { tx.abort(); } catch {}
|
|
17805
|
+
reject(e);
|
|
17806
|
+
}
|
|
17807
|
+
});
|
|
17808
|
+
}
|
|
17809
|
+
|
|
17810
|
+
async get(key) {
|
|
17811
|
+
return this.__tx('readonly', (store, resolve, reject) => {
|
|
17812
|
+
const req = store.get(key);
|
|
17813
|
+
req.onerror = () => reject(req.error);
|
|
17814
|
+
req.onsuccess = () => resolve(req.result);
|
|
17815
|
+
});
|
|
17816
|
+
}
|
|
17817
|
+
|
|
17818
|
+
async put(key, value) {
|
|
17819
|
+
return this.__tx('readwrite', (store) => {
|
|
17820
|
+
store.put(value, key);
|
|
17821
|
+
});
|
|
17822
|
+
}
|
|
17823
|
+
|
|
17824
|
+
async del(key) {
|
|
17825
|
+
return this.__tx('readwrite', (store) => {
|
|
17826
|
+
store.delete(key);
|
|
17827
|
+
});
|
|
17828
|
+
}
|
|
17829
|
+
|
|
17830
|
+
async clear() {
|
|
17831
|
+
return this.__tx('readwrite', (store) => {
|
|
17832
|
+
store.clear();
|
|
17833
|
+
});
|
|
17834
|
+
}
|
|
17835
|
+
|
|
17836
|
+
async batch(ops) {
|
|
17837
|
+
return this.__tx('readwrite', (store) => {
|
|
17838
|
+
for (const op of ops || []) {
|
|
17839
|
+
if (op.type === 'del') store.delete(op.key);
|
|
17840
|
+
else store.put(op.value, op.key);
|
|
17841
|
+
}
|
|
17842
|
+
});
|
|
17843
|
+
}
|
|
17844
|
+
|
|
17845
|
+
async *entries(prefix = '') {
|
|
17846
|
+
const db = await this.open();
|
|
17847
|
+
const rows = await new Promise((resolve, reject) => {
|
|
17848
|
+
const tx = db.transaction('kv', 'readonly');
|
|
17849
|
+
const store = tx.objectStore('kv');
|
|
17850
|
+
const out = [];
|
|
17851
|
+
const range = globalThis.IDBKeyRange.bound(prefix, prefix + '\uffff', false, true);
|
|
17852
|
+
const req = store.openCursor(range);
|
|
17853
|
+
req.onerror = () => reject(req.error);
|
|
17854
|
+
req.onsuccess = () => {
|
|
17855
|
+
const cursor = req.result;
|
|
17856
|
+
if (!cursor) return;
|
|
17857
|
+
out.push([cursor.key, cursor.value]);
|
|
17858
|
+
cursor.continue();
|
|
17859
|
+
};
|
|
17860
|
+
tx.oncomplete = () => resolve(out);
|
|
17861
|
+
tx.onerror = () => reject(tx.error || new Error('IndexedDB transaction failed'));
|
|
17862
|
+
});
|
|
17863
|
+
for (const row of rows) yield row;
|
|
17864
|
+
}
|
|
17865
|
+
|
|
17866
|
+
async close() {
|
|
17867
|
+
if (!this.dbPromise) return;
|
|
17868
|
+
const db = await this.dbPromise;
|
|
17869
|
+
if (db && typeof db.close === 'function') db.close();
|
|
17870
|
+
}
|
|
17871
|
+
}
|
|
17872
|
+
|
|
17873
|
+
class MemoryFactStore {
|
|
17874
|
+
constructor() {
|
|
17875
|
+
this.map = new Map();
|
|
17876
|
+
}
|
|
17877
|
+
|
|
17878
|
+
async add(triple, kind = 'explicit') {
|
|
17879
|
+
const key = tripleToStoreKey(triple);
|
|
17880
|
+
const prev = this.map.get(key);
|
|
17881
|
+
const mask = kindMask(kind);
|
|
17882
|
+
if (prev) {
|
|
17883
|
+
const nextKind = prev.kind | mask;
|
|
17884
|
+
if (nextKind !== prev.kind) prev.kind = nextKind;
|
|
17885
|
+
return false;
|
|
17886
|
+
}
|
|
17887
|
+
this.map.set(key, { triple, kind: mask });
|
|
17888
|
+
return true;
|
|
17889
|
+
}
|
|
17890
|
+
|
|
17891
|
+
async batchAdd(triples, kind = 'explicit') {
|
|
17892
|
+
let n = 0;
|
|
17893
|
+
for (const triple of triples || []) if (await this.add(triple, kind)) n += 1;
|
|
17894
|
+
return n;
|
|
17895
|
+
}
|
|
17896
|
+
|
|
17897
|
+
async has(triple) {
|
|
17898
|
+
return this.map.has(tripleToStoreKey(triple));
|
|
17899
|
+
}
|
|
17900
|
+
|
|
17901
|
+
async kindOf(triple) {
|
|
17902
|
+
const row = this.map.get(tripleToStoreKey(triple));
|
|
17903
|
+
return row ? row.kind : 0;
|
|
17904
|
+
}
|
|
17905
|
+
|
|
17906
|
+
async *match(s, p, o) {
|
|
17907
|
+
const sk = s == null ? null : termToStoreKey(s);
|
|
17908
|
+
const pk = p == null ? null : termToStoreKey(p);
|
|
17909
|
+
const ok = o == null ? null : termToStoreKey(o);
|
|
17910
|
+
for (const { triple } of this.map.values()) {
|
|
17911
|
+
if (sk !== null && termToStoreKey(triple.s) !== sk) continue;
|
|
17912
|
+
if (pk !== null && termToStoreKey(triple.p) !== pk) continue;
|
|
17913
|
+
if (ok !== null && termToStoreKey(triple.o) !== ok) continue;
|
|
17914
|
+
yield triple;
|
|
17915
|
+
}
|
|
17916
|
+
}
|
|
17917
|
+
|
|
17918
|
+
async clear() {
|
|
17919
|
+
this.map.clear();
|
|
17920
|
+
}
|
|
17921
|
+
|
|
17922
|
+
async close() {}
|
|
17923
|
+
}
|
|
17924
|
+
|
|
17925
|
+
class PersistentFactStore {
|
|
17926
|
+
constructor(kv, options = {}) {
|
|
17927
|
+
this.kv = kv;
|
|
17928
|
+
this.termCacheByKey = new Map();
|
|
17929
|
+
this.termCacheById = new Map();
|
|
17930
|
+
this.name = options.name || 'default';
|
|
17931
|
+
}
|
|
17932
|
+
|
|
17933
|
+
async clear() {
|
|
17934
|
+
await this.kv.clear();
|
|
17935
|
+
this.termCacheByKey.clear();
|
|
17936
|
+
this.termCacheById.clear();
|
|
17937
|
+
}
|
|
17938
|
+
|
|
17939
|
+
async __nextTermId() {
|
|
17940
|
+
const key = 'meta/nextTermId';
|
|
17941
|
+
const current = Number((await this.kv.get(key)) || 1);
|
|
17942
|
+
await this.kv.put(key, current + 1);
|
|
17943
|
+
return idPart(current);
|
|
17944
|
+
}
|
|
17945
|
+
|
|
17946
|
+
async __idForTerm(term, create) {
|
|
17947
|
+
const canonical = termToStoreKey(term);
|
|
17948
|
+
if (this.termCacheByKey.has(canonical)) return this.termCacheByKey.get(canonical);
|
|
17949
|
+
const byLexKey = `term/byLex/${base64url(canonical)}`;
|
|
17950
|
+
const found = await this.kv.get(byLexKey);
|
|
17951
|
+
if (found !== undefined) {
|
|
17952
|
+
this.termCacheByKey.set(canonical, found);
|
|
17953
|
+
return found;
|
|
17954
|
+
}
|
|
17955
|
+
if (!create) return null;
|
|
17956
|
+
const id = await this.__nextTermId();
|
|
17957
|
+
const json = termToJson(term);
|
|
17958
|
+
await this.kv.batch([
|
|
17959
|
+
{ type: 'put', key: byLexKey, value: id },
|
|
17960
|
+
{ type: 'put', key: `term/byId/${id}`, value: json },
|
|
17961
|
+
]);
|
|
17962
|
+
this.termCacheByKey.set(canonical, id);
|
|
17963
|
+
this.termCacheById.set(id, term);
|
|
17964
|
+
return id;
|
|
17965
|
+
}
|
|
17966
|
+
|
|
17967
|
+
async __termById(id) {
|
|
17968
|
+
if (this.termCacheById.has(id)) return this.termCacheById.get(id);
|
|
17969
|
+
const json = await this.kv.get(`term/byId/${id}`);
|
|
17970
|
+
if (json === undefined) throw new Error(`Corrupt fact store: missing term ${id}`);
|
|
17971
|
+
const term = termFromJson(json);
|
|
17972
|
+
this.termCacheById.set(id, term);
|
|
17973
|
+
return term;
|
|
17974
|
+
}
|
|
17975
|
+
|
|
17976
|
+
async __tripleFromIds(sid, pid, oid) {
|
|
17977
|
+
return new Triple(await this.__termById(sid), await this.__termById(pid), await this.__termById(oid));
|
|
17978
|
+
}
|
|
17979
|
+
|
|
17980
|
+
async add(triple, kind = 'explicit') {
|
|
17981
|
+
const sid = await this.__idForTerm(triple.s, true);
|
|
17982
|
+
const pid = await this.__idForTerm(triple.p, true);
|
|
17983
|
+
const oid = await this.__idForTerm(triple.o, true);
|
|
17984
|
+
const mask = kindMask(kind);
|
|
17985
|
+
const primary = `triple/${sid}/${pid}/${oid}`;
|
|
17986
|
+
const prev = await this.kv.get(primary);
|
|
17987
|
+
if (prev !== undefined) {
|
|
17988
|
+
const nextKind = (typeof prev === 'number' ? prev : prev.kind || 0) | mask;
|
|
17989
|
+
if (nextKind !== prev) await this.kv.put(primary, nextKind);
|
|
17990
|
+
return false;
|
|
17991
|
+
}
|
|
17992
|
+
await this.kv.batch([
|
|
17993
|
+
{ type: 'put', key: primary, value: mask },
|
|
17994
|
+
{ type: 'put', key: `i/spo/${sid}/${pid}/${oid}`, value: mask },
|
|
17995
|
+
{ type: 'put', key: `i/pos/${pid}/${oid}/${sid}`, value: mask },
|
|
17996
|
+
{ type: 'put', key: `i/osp/${oid}/${sid}/${pid}`, value: mask },
|
|
17997
|
+
]);
|
|
17998
|
+
return true;
|
|
17999
|
+
}
|
|
18000
|
+
|
|
18001
|
+
async batchAdd(triples, kind = 'explicit') {
|
|
18002
|
+
let n = 0;
|
|
18003
|
+
for (const triple of triples || []) if (await this.add(triple, kind)) n += 1;
|
|
18004
|
+
return n;
|
|
18005
|
+
}
|
|
18006
|
+
|
|
18007
|
+
async has(triple) {
|
|
18008
|
+
return (await this.kindOf(triple)) !== 0;
|
|
18009
|
+
}
|
|
18010
|
+
|
|
18011
|
+
async kindOf(triple) {
|
|
18012
|
+
const sid = await this.__idForTerm(triple.s, false);
|
|
18013
|
+
if (sid === null) return 0;
|
|
18014
|
+
const pid = await this.__idForTerm(triple.p, false);
|
|
18015
|
+
if (pid === null) return 0;
|
|
18016
|
+
const oid = await this.__idForTerm(triple.o, false);
|
|
18017
|
+
if (oid === null) return 0;
|
|
18018
|
+
const value = await this.kv.get(`triple/${sid}/${pid}/${oid}`);
|
|
18019
|
+
return typeof value === 'number' ? value : value && typeof value.kind === 'number' ? value.kind : 0;
|
|
18020
|
+
}
|
|
18021
|
+
|
|
18022
|
+
async __idsForPattern(s, p, o) {
|
|
18023
|
+
const sid = s == null ? null : await this.__idForTerm(s, false);
|
|
18024
|
+
const pid = p == null ? null : await this.__idForTerm(p, false);
|
|
18025
|
+
const oid = o == null ? null : await this.__idForTerm(o, false);
|
|
18026
|
+
if ((s != null && sid === null) || (p != null && pid === null) || (o != null && oid === null)) return null;
|
|
18027
|
+
return { sid, pid, oid };
|
|
18028
|
+
}
|
|
18029
|
+
|
|
18030
|
+
__scanPlan(sid, pid, oid) {
|
|
18031
|
+
if (sid && pid && oid) return { index: 'triple', prefix: `triple/${sid}/${pid}/${oid}`, order: 'spo' };
|
|
18032
|
+
if (sid && pid) return { index: 'spo', prefix: `i/spo/${sid}/${pid}/`, order: 'spo' };
|
|
18033
|
+
if (pid && oid) return { index: 'pos', prefix: `i/pos/${pid}/${oid}/`, order: 'pos' };
|
|
18034
|
+
if (sid && oid) return { index: 'osp', prefix: `i/osp/${oid}/${sid}/`, order: 'osp' };
|
|
18035
|
+
if (pid) return { index: 'pos', prefix: `i/pos/${pid}/`, order: 'pos' };
|
|
18036
|
+
if (sid) return { index: 'spo', prefix: `i/spo/${sid}/`, order: 'spo' };
|
|
18037
|
+
if (oid) return { index: 'osp', prefix: `i/osp/${oid}/`, order: 'osp' };
|
|
18038
|
+
return { index: 'spo', prefix: 'i/spo/', order: 'spo' };
|
|
18039
|
+
}
|
|
18040
|
+
|
|
18041
|
+
__decodeIndexKey(key, plan) {
|
|
18042
|
+
if (plan.index === 'triple') {
|
|
18043
|
+
const parts = key.split('/');
|
|
18044
|
+
return { sid: parts[1], pid: parts[2], oid: parts[3] };
|
|
18045
|
+
}
|
|
18046
|
+
const rest = key.slice(`i/${plan.index}/`.length).split('/');
|
|
18047
|
+
if (plan.order === 'spo') return { sid: rest[0], pid: rest[1], oid: rest[2] };
|
|
18048
|
+
if (plan.order === 'pos') return { pid: rest[0], oid: rest[1], sid: rest[2] };
|
|
18049
|
+
return { oid: rest[0], sid: rest[1], pid: rest[2] };
|
|
18050
|
+
}
|
|
18051
|
+
|
|
18052
|
+
async *match(s, p, o) {
|
|
18053
|
+
const ids = await this.__idsForPattern(s, p, o);
|
|
18054
|
+
if (!ids) return;
|
|
18055
|
+
const plan = this.__scanPlan(ids.sid, ids.pid, ids.oid);
|
|
18056
|
+
const seen = new Set();
|
|
18057
|
+
for await (const [key] of this.kv.entries(plan.prefix)) {
|
|
18058
|
+
const row = this.__decodeIndexKey(key, plan);
|
|
18059
|
+
if (ids.sid && row.sid !== ids.sid) continue;
|
|
18060
|
+
if (ids.pid && row.pid !== ids.pid) continue;
|
|
18061
|
+
if (ids.oid && row.oid !== ids.oid) continue;
|
|
18062
|
+
const primary = `${row.sid}/${row.pid}/${row.oid}`;
|
|
18063
|
+
if (seen.has(primary)) continue;
|
|
18064
|
+
seen.add(primary);
|
|
18065
|
+
yield this.__tripleFromIds(row.sid, row.pid, row.oid);
|
|
18066
|
+
}
|
|
18067
|
+
}
|
|
18068
|
+
|
|
18069
|
+
async close() {
|
|
18070
|
+
if (this.kv && typeof this.kv.close === 'function') await this.kv.close();
|
|
18071
|
+
}
|
|
18072
|
+
}
|
|
18073
|
+
|
|
18074
|
+
function nodeStoreLocation(name, storePath) {
|
|
18075
|
+
const path = __dynamicRequire('node:path');
|
|
18076
|
+
const os = __dynamicRequire('node:os');
|
|
18077
|
+
if (!path || !os) return null;
|
|
18078
|
+
const base = storePath || path.join(os.homedir ? os.homedir() : '.', '.eyeling-store');
|
|
18079
|
+
return path.join(base, safeStoreName(name));
|
|
18080
|
+
}
|
|
18081
|
+
|
|
18082
|
+
async function createPersistentFactStore(options = {}) {
|
|
18083
|
+
const name = typeof options === 'string' ? options : options.name || 'default';
|
|
18084
|
+
const clear = !!(options && options.clear);
|
|
18085
|
+
let kv;
|
|
18086
|
+
|
|
18087
|
+
if (typeof globalThis !== 'undefined' && globalThis.indexedDB && !__dynamicRequire('node:fs')) {
|
|
18088
|
+
kv = new IndexedDbKv(name);
|
|
18089
|
+
} else {
|
|
18090
|
+
const location = nodeStoreLocation(name, options && options.path);
|
|
18091
|
+
try {
|
|
18092
|
+
kv = new ClassicLevelKv(location);
|
|
18093
|
+
} catch {
|
|
18094
|
+
kv = new JsonFileKv(`${location}.json`);
|
|
18095
|
+
}
|
|
18096
|
+
}
|
|
18097
|
+
|
|
18098
|
+
const store = new PersistentFactStore(kv, { name });
|
|
18099
|
+
if (clear) await store.clear();
|
|
18100
|
+
return store;
|
|
18101
|
+
}
|
|
18102
|
+
|
|
18103
|
+
async function createFactStore(options = null) {
|
|
18104
|
+
if (!options) return new MemoryFactStore();
|
|
18105
|
+
if (typeof options === 'string') return createPersistentFactStore({ name: options });
|
|
18106
|
+
if (options.type === 'memory') return new MemoryFactStore();
|
|
18107
|
+
if (options.backend === 'memory') return new MemoryFactStore();
|
|
18108
|
+
return createPersistentFactStore(options);
|
|
18109
|
+
}
|
|
18110
|
+
|
|
18111
|
+
async function collectStore(store) {
|
|
18112
|
+
const out = [];
|
|
18113
|
+
for await (const tr of store.match(null, null, null)) out.push(tr);
|
|
18114
|
+
return out;
|
|
18115
|
+
}
|
|
18116
|
+
|
|
18117
|
+
module.exports = {
|
|
18118
|
+
KIND_EXPLICIT,
|
|
18119
|
+
KIND_INFERRED,
|
|
18120
|
+
MemoryFactStore,
|
|
18121
|
+
PersistentFactStore,
|
|
18122
|
+
createFactStore,
|
|
18123
|
+
createPersistentFactStore,
|
|
18124
|
+
collectStore,
|
|
18125
|
+
termToStoreKey,
|
|
18126
|
+
tripleToStoreKey,
|
|
18127
|
+
termToJson,
|
|
18128
|
+
termFromJson,
|
|
18129
|
+
tripleToJson,
|
|
18130
|
+
tripleFromJson,
|
|
18131
|
+
};
|
|
18132
|
+
|
|
17218
18133
|
};
|
|
17219
18134
|
__modules["lib/time.js"] = function(require, module, exports){
|
|
17220
18135
|
/**
|