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/index.d.ts CHANGED
@@ -151,6 +151,14 @@ declare module 'eyeling' {
151
151
  document?: EyelingAstBundle;
152
152
  }
153
153
 
154
+ export interface StoreOptions {
155
+ name?: string;
156
+ clear?: boolean;
157
+ path?: string;
158
+ type?: 'memory' | 'persistent';
159
+ backend?: 'memory' | 'level' | 'indexeddb';
160
+ }
161
+
154
162
  export interface ReasonOptions {
155
163
  proof?: boolean;
156
164
  proofComments?: boolean;
@@ -158,6 +166,9 @@ declare module 'eyeling' {
158
166
  args?: string[];
159
167
  maxBuffer?: number;
160
168
  builtinModules?: string | string[];
169
+ store?: string | StoreOptions;
170
+ storePath?: string;
171
+ storeClear?: boolean;
161
172
  }
162
173
 
163
174
  export interface BuiltinRegistrationContext {
@@ -183,6 +194,9 @@ declare module 'eyeling' {
183
194
  dataFactory?: RdfJsDataFactory | null;
184
195
  skipUnsupportedRdfJs?: boolean;
185
196
  builtinModules?: string | string[];
197
+ store?: string | StoreOptions;
198
+ storePath?: string;
199
+ storeClear?: boolean;
186
200
  onDerived?: (item: { triple: string; quad?: RdfJsQuad; quads?: RdfJsQuad[]; df: any }) => void;
187
201
  }
188
202
 
@@ -198,10 +212,45 @@ declare module 'eyeling' {
198
212
  queryQuads?: RdfJsQuad[];
199
213
  }
200
214
 
215
+ export interface FactStore {
216
+ add(triple: EyelingTriple, kind?: 'explicit' | 'inferred'): Promise<boolean>;
217
+ has(triple: EyelingTriple): Promise<boolean>;
218
+ kindOf?(triple: EyelingTriple): Promise<number>;
219
+ match(s?: EyelingTerm | null, p?: EyelingTerm | null, o?: EyelingTerm | null): AsyncIterable<EyelingTriple>;
220
+ batchAdd?(triples: Iterable<EyelingTriple>, kind?: 'explicit' | 'inferred'): Promise<number>;
221
+ clear?(): Promise<void>;
222
+ close?(): Promise<void>;
223
+ }
224
+
225
+ export class MemoryFactStore implements FactStore {
226
+ constructor();
227
+ add(triple: EyelingTriple, kind?: 'explicit' | 'inferred'): Promise<boolean>;
228
+ has(triple: EyelingTriple): Promise<boolean>;
229
+ kindOf(triple: EyelingTriple): Promise<number>;
230
+ match(s?: EyelingTerm | null, p?: EyelingTerm | null, o?: EyelingTerm | null): AsyncIterable<EyelingTriple>;
231
+ batchAdd(triples: Iterable<EyelingTriple>, kind?: 'explicit' | 'inferred'): Promise<number>;
232
+ clear(): Promise<void>;
233
+ close(): Promise<void>;
234
+ }
235
+
236
+ export class PersistentFactStore implements FactStore {
237
+ add(triple: EyelingTriple, kind?: 'explicit' | 'inferred'): Promise<boolean>;
238
+ has(triple: EyelingTriple): Promise<boolean>;
239
+ kindOf(triple: EyelingTriple): Promise<number>;
240
+ match(s?: EyelingTerm | null, p?: EyelingTerm | null, o?: EyelingTerm | null): AsyncIterable<EyelingTriple>;
241
+ batchAdd(triples: Iterable<EyelingTriple>, kind?: 'explicit' | 'inferred'): Promise<number>;
242
+ clear(): Promise<void>;
243
+ close(): Promise<void>;
244
+ }
245
+
201
246
  export function reason(
202
247
  opts: ReasonOptions,
203
248
  input: string | RdfJsReasonInput | EyelingAstBundle | N3SourceListInput,
204
249
  ): string;
250
+ export function runAsync(
251
+ input: string | RdfJsReasonInput | EyelingAstBundle | N3SourceListInput,
252
+ opts?: ReasonStreamOptions,
253
+ ): Promise<ReasonStreamResult & { store?: FactStore }>;
205
254
  export function reasonStream(
206
255
  input: string | RdfJsReasonInput | EyelingAstBundle | N3SourceListInput,
207
256
  opts?: ReasonStreamOptions,
@@ -213,6 +262,7 @@ declare module 'eyeling' {
213
262
 
214
263
  export const INFERENCE_FUSE_EXIT_CODE: 65;
215
264
  export const rdfjs: RdfJsDataFactory;
265
+ export function createFactStore(options?: string | StoreOptions | null): Promise<FactStore>;
216
266
  export function registerBuiltin(iri: string, handler: BuiltinHandler): BuiltinHandler;
217
267
  export function unregisterBuiltin(iri: string): boolean;
218
268
  export function registerBuiltinModule(mod: any, origin?: string): boolean;
@@ -230,7 +280,13 @@ declare module 'eyeling/browser' {
230
280
  export type ReasonStreamOptions = import('eyeling').ReasonStreamOptions;
231
281
  export type ReasonStreamResult = import('eyeling').ReasonStreamResult;
232
282
  export type BuiltinHandler = import('eyeling').BuiltinHandler;
283
+ export type StoreOptions = import('eyeling').StoreOptions;
284
+ export type FactStore = import('eyeling').FactStore;
233
285
 
286
+ export function runAsync(
287
+ input: string | RdfJsReasonInput | EyelingAstBundle | N3SourceListInput,
288
+ opts?: ReasonStreamOptions,
289
+ ): Promise<ReasonStreamResult & { store?: FactStore }>;
234
290
  export function reasonStream(
235
291
  input: string | RdfJsReasonInput | EyelingAstBundle | N3SourceListInput,
236
292
  opts?: ReasonStreamOptions,
@@ -242,6 +298,7 @@ declare module 'eyeling/browser' {
242
298
 
243
299
  export const INFERENCE_FUSE_EXIT_CODE: 65;
244
300
  export const rdfjs: RdfJsDataFactory;
301
+ export function createFactStore(options?: string | StoreOptions | null): Promise<FactStore>;
245
302
  export function registerBuiltin(iri: string, handler: BuiltinHandler): BuiltinHandler;
246
303
  export function unregisterBuiltin(iri: string): boolean;
247
304
  export function registerBuiltinModule(mod: any, origin?: string): boolean;
@@ -249,10 +306,12 @@ declare module 'eyeling/browser' {
249
306
 
250
307
  const eyeling: {
251
308
  readonly version: string;
309
+ runAsync: typeof runAsync;
252
310
  reasonStream: typeof reasonStream;
253
311
  reasonRdfJs: typeof reasonRdfJs;
254
312
  readonly INFERENCE_FUSE_EXIT_CODE: typeof INFERENCE_FUSE_EXIT_CODE;
255
313
  rdfjs: typeof rdfjs;
314
+ createFactStore: typeof createFactStore;
256
315
  registerBuiltin: typeof registerBuiltin;
257
316
  unregisterBuiltin: typeof unregisterBuiltin;
258
317
  registerBuiltinModule: typeof registerBuiltinModule;
package/index.js CHANGED
@@ -42,6 +42,15 @@ function reason(opt = {}, input = '') {
42
42
 
43
43
  if (opt.rdf) args.push('--rdf');
44
44
 
45
+ if (typeof opt.store === 'string' && opt.store) args.push('--store', opt.store);
46
+ else if (opt.store && typeof opt.store === 'object') {
47
+ if (opt.store.name) args.push('--store', String(opt.store.name));
48
+ if (opt.store.clear) args.push('--store-clear');
49
+ if (opt.store.path) args.push('--store-path', String(opt.store.path));
50
+ }
51
+ if (opt.storePath) args.push('--store-path', String(opt.storePath));
52
+ if (opt.storeClear) args.push('--store-clear');
53
+
45
54
  if (Array.isArray(opt.args)) args.push(...opt.args);
46
55
 
47
56
  const builtinModules = Array.isArray(opt.builtinModules)
@@ -106,8 +115,13 @@ function reason(opt = {}, input = '') {
106
115
  }
107
116
  }
108
117
 
118
+ async function runAsync(input = '', opt = {}) {
119
+ return engine.runAsync(input, opt || {});
120
+ }
121
+
109
122
  module.exports = {
110
123
  reason,
124
+ runAsync,
111
125
  reasonStream: bundleApi.reasonStream,
112
126
  reasonRdfJs: bundleApi.reasonRdfJs,
113
127
  rdfjs: dataFactory,
@@ -116,6 +130,9 @@ module.exports = {
116
130
  registerBuiltinModule: engine.registerBuiltinModule,
117
131
  loadBuiltinModule: engine.loadBuiltinModule,
118
132
  listBuiltinIris: engine.listBuiltinIris,
133
+ createFactStore: engine.createFactStore,
134
+ MemoryFactStore: engine.MemoryFactStore,
135
+ PersistentFactStore: engine.PersistentFactStore,
119
136
  };
120
137
 
121
138
  // small interop nicety for ESM default import
package/lib/cli.js CHANGED
@@ -562,10 +562,12 @@ function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes
562
562
 
563
563
  let derived = [];
564
564
  let outTriples = [];
565
+ let queryDerived = [];
565
566
  if (hasQueries) {
566
567
  const res = engine.forwardChainAndCollectLogQueryConclusions(facts, frules, brules, qrules, { prefixes });
567
568
  derived = res.derived;
568
569
  outTriples = res.queryTriples;
570
+ queryDerived = res.queryDerived || [];
569
571
  } else {
570
572
  const skipDerivedCollection = mayAutoRenderOutputStrings;
571
573
  derived = engine.forwardChain(facts, frules, brules, null, {
@@ -579,7 +581,7 @@ function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes
579
581
  const renderedOutputTriples = hasQueries ? outTriples : facts;
580
582
  if (factsContainOutputStrings(renderedOutputTriples)) {
581
583
  process.stdout.write(engine.collectOutputStringsFromFacts(renderedOutputTriples, prefixes));
582
- return;
584
+ return { facts, derived, outTriples, queryDerived, queryMode: !!hasQueries };
583
585
  }
584
586
 
585
587
  const outPrefixEnv = outputPrefixes || prefixes;
@@ -591,9 +593,99 @@ function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes
591
593
  } else {
592
594
  for (const df of derived) console.log(engine.tripleToN3(df.fact, outPrefixEnv));
593
595
  }
596
+ return { facts, derived, outTriples, queryDerived, queryMode: !!hasQueries };
597
+ }
598
+
599
+ async function createCliStore({ storeName, storePath = null, storeClear = false } = {}) {
600
+ if (!storeName) return null;
601
+ return engine.createFactStore({ name: storeName, path: storePath || undefined, clear: !!storeClear });
602
+ }
603
+
604
+ async function persistRunResultToStore(store, runResult) {
605
+ if (!store || !runResult) return;
606
+ await store.batchAdd(runResult.facts || [], 'explicit');
607
+ await store.batchAdd((runResult.derived || []).map((df) => df.fact), 'inferred');
608
+ await store.batchAdd((runResult.queryDerived || []).map((df) => df.fact), 'inferred');
609
+ await store.batchAdd(runResult.outTriples || [], 'inferred');
610
+ }
611
+
612
+ function sourceLooksLikeLineBasedRdf(sourceLabel) {
613
+ if (typeof sourceLabel !== 'string') return false;
614
+ const clean = sourceLabel.split(/[?#]/, 1)[0].toLowerCase();
615
+ return /\.(?:nt|nq)(?:\.gz|\.br|\.deflate)?$/.test(clean);
616
+ }
617
+
618
+ function parseLineBasedRdfLine(line, { sourceLabel, lineNumber, rdfMode }) {
619
+ const text = String(line || '');
620
+ if (!text.trim() || /^\s*#/.test(text)) return [];
621
+ const doc = parseN3Text(text, {
622
+ baseIri: __sourceLabelToBaseIri(sourceLabel),
623
+ label: `${sourceLabel}:${lineNumber}`,
624
+ keepSourceArtifacts: false,
625
+ sourceLocations: false,
626
+ rdf: rdfMode,
627
+ });
628
+ return doc.triples || [];
594
629
  }
595
630
 
596
- async function runStreamMessagesMode(sourceLabels, { rdfMode }) {
631
+ async function ingestLineBasedRdfSourceToStore(sourceLabel, store, { rdfMode = true } = {}) {
632
+ let lineNumber = 0;
633
+ let count = 0;
634
+ const onLine = async (line) => {
635
+ lineNumber += 1;
636
+ let triples;
637
+ try {
638
+ triples = parseLineBasedRdfLine(line, { sourceLabel, lineNumber, rdfMode });
639
+ } catch (e) {
640
+ if (e && e.name === 'N3SyntaxError') {
641
+ throw new Error(formatN3SyntaxError(e, line, `${sourceLabel}:${lineNumber}`));
642
+ }
643
+ throw e;
644
+ }
645
+ count += await store.batchAdd(triples, 'explicit');
646
+ };
647
+
648
+ const filePath = __localPathForSource(sourceLabel);
649
+ if (filePath) {
650
+ const fd = fs.openSync(filePath, 'r');
651
+ const decoder = new TextDecoder('utf8');
652
+ const buf = Buffer.allocUnsafe(64 * 1024);
653
+ let carry = '';
654
+ try {
655
+ for (;;) {
656
+ const n = fs.readSync(fd, buf, 0, buf.length, null);
657
+ if (n === 0) break;
658
+ carry += decoder.decode(buf.subarray(0, n), { stream: true });
659
+ for (;;) {
660
+ const m = /\r\n|\n|\r/.exec(carry);
661
+ if (!m) break;
662
+ const end = m.index + m[0].length;
663
+ await onLine(carry.slice(0, end));
664
+ carry = carry.slice(end);
665
+ }
666
+ }
667
+ carry += decoder.decode();
668
+ if (carry) await onLine(carry);
669
+ } finally {
670
+ fs.closeSync(fd);
671
+ }
672
+ return count;
673
+ }
674
+
675
+ if (__isHttpSource(sourceLabel)) {
676
+ const body = await __openHttpTextStream(sourceLabel);
677
+ const rl = readline.createInterface({ input: body, crlfDelay: Infinity });
678
+ for await (const line of rl) await onLine(line + '\n');
679
+ return count;
680
+ }
681
+
682
+ const text = __readInputSourceSync(sourceLabel);
683
+ for (const line of text.match(/.*(?:\r\n|\n|\r)|.+$/g) || []) await onLine(line);
684
+ return count;
685
+ }
686
+
687
+
688
+ async function runStreamMessagesMode(sourceLabels, { rdfMode, storeName = null, storePath = null, storeClear = false } = {}) {
597
689
  const ordinarySourceLabels = [];
598
690
  const messageSourceLabels = [];
599
691
 
@@ -642,40 +734,49 @@ async function runStreamMessagesMode(sourceLabels, { rdfMode }) {
642
734
  }
643
735
  }
644
736
 
737
+ const store = await createCliStore({ storeName, storePath, storeClear });
738
+ let pendingStoreWrites = Promise.resolve();
739
+
645
740
  const fullIriPrefixes = new PrefixEnv({});
646
- for (const messageSourceLabel of messageSourceLabels) {
647
- try {
648
- await __forEachRdfMessageChunk(messageSourceLabel, ({ messageIndex, chunk, directives }) => {
649
- const messageText = buildSingleMessageReplayDocument({
650
- sourceLabel: messageSourceLabel,
651
- messageIndex,
652
- chunk,
653
- directives,
654
- });
655
- let messageDoc;
656
- try {
657
- messageDoc = parseN3Text(messageText, {
658
- baseIri: `${__sourceLabelToBaseIri(messageSourceLabel)}#message-${messageIndex}`,
659
- label: `${messageSourceLabel}#message-${messageIndex}`,
660
- keepSourceArtifacts: false,
661
- sourceLocations: false,
662
- rdf: false,
741
+ try {
742
+ for (const messageSourceLabel of messageSourceLabels) {
743
+ try {
744
+ await __forEachRdfMessageChunk(messageSourceLabel, ({ messageIndex, chunk, directives }) => {
745
+ const messageText = buildSingleMessageReplayDocument({
746
+ sourceLabel: messageSourceLabel,
747
+ messageIndex,
748
+ chunk,
749
+ directives,
663
750
  });
664
- } catch (e) {
665
- if (e && e.name === 'N3SyntaxError') {
666
- console.error(formatN3SyntaxError(e, messageText, `${messageSourceLabel}#message-${messageIndex}`));
667
- process.exit(1);
751
+ let messageDoc;
752
+ try {
753
+ messageDoc = parseN3Text(messageText, {
754
+ baseIri: `${__sourceLabelToBaseIri(messageSourceLabel)}#message-${messageIndex}`,
755
+ label: `${messageSourceLabel}#message-${messageIndex}`,
756
+ keepSourceArtifacts: false,
757
+ sourceLocations: false,
758
+ rdf: false,
759
+ });
760
+ } catch (e) {
761
+ if (e && e.name === 'N3SyntaxError') {
762
+ console.error(formatN3SyntaxError(e, messageText, `${messageSourceLabel}#message-${messageIndex}`));
763
+ process.exit(1);
764
+ }
765
+ throw e;
668
766
  }
669
- throw e;
670
- }
671
767
 
672
- const merged = mergeParsedDocuments(programSources.concat([messageDoc]));
673
- runParsedDocumentOnce(merged, { rdfMode, outputPrefixes: fullIriPrefixes });
674
- });
675
- } catch (e) {
676
- console.error(`Error streaming RDF Message Log ${JSON.stringify(messageSourceLabel)}: ${e && e.message ? e.message : String(e)}`);
677
- process.exit(1);
768
+ const merged = mergeParsedDocuments(programSources.concat([messageDoc]));
769
+ const result = runParsedDocumentOnce(merged, { rdfMode, outputPrefixes: fullIriPrefixes });
770
+ if (store) pendingStoreWrites = pendingStoreWrites.then(() => persistRunResultToStore(store, result));
771
+ });
772
+ } catch (e) {
773
+ console.error(`Error streaming RDF Message Log ${JSON.stringify(messageSourceLabel)}: ${e && e.message ? e.message : String(e)}`);
774
+ process.exit(1);
775
+ }
678
776
  }
777
+ await pendingStoreWrites;
778
+ } finally {
779
+ if (store && typeof store.close === 'function') await store.close();
679
780
  }
680
781
  }
681
782
 
@@ -710,6 +811,9 @@ async function main() {
710
811
  ` -p, --proof Enable proof explanations.\n` +
711
812
  ` -r, --rdf Enable RDF/TriG input/output compatibility.\n` +
712
813
  ` --stream-messages Process RDF Message Logs one message at a time under -r.\n` +
814
+ ` --store <name> Use an optional persistent fact store.\n` +
815
+ ` --store-clear Clear the named store before this run.\n` +
816
+ ` --store-path <dir> Node.js persistent store directory.\n` +
713
817
  ` -s, --super-restricted Disable all builtins except => and <=.\n` +
714
818
  ` -t, --stream Stream derived triples as soon as they are derived.\n` +
715
819
  ` -v, --version Print version and exit.\n`;
@@ -746,6 +850,35 @@ async function main() {
746
850
  builtinModules.push(a.slice('--builtin='.length));
747
851
  continue;
748
852
  }
853
+ if (a === '--store') {
854
+ const next = argv[i + 1];
855
+ if (!next || next.startsWith('-')) {
856
+ console.error('Error: --store expects a store name.');
857
+ process.exit(1);
858
+ }
859
+ argv.__storeName = next;
860
+ i += 1;
861
+ continue;
862
+ }
863
+ if (typeof a === 'string' && a.startsWith('--store=')) {
864
+ argv.__storeName = a.slice('--store='.length);
865
+ continue;
866
+ }
867
+ if (a === '--store-path') {
868
+ const next = argv[i + 1];
869
+ if (!next || next.startsWith('-')) {
870
+ console.error('Error: --store-path expects a directory path.');
871
+ process.exit(1);
872
+ }
873
+ argv.__storePath = next;
874
+ i += 1;
875
+ continue;
876
+ }
877
+ if (typeof a === 'string' && a.startsWith('--store-path=')) {
878
+ argv.__storePath = a.slice('--store-path='.length);
879
+ continue;
880
+ }
881
+ if (a === '--store-clear') continue;
749
882
  if (a === '-' || !a.startsWith('-')) positional.push(a);
750
883
  }
751
884
 
@@ -753,6 +886,9 @@ async function main() {
753
886
  const streamMode = argv.includes('--stream') || argv.includes('-t');
754
887
  const streamMessagesMode = argv.includes('--stream-messages');
755
888
  const rdfMode = argv.includes('--rdf') || argv.includes('-r');
889
+ const storeName = argv.__storeName || null;
890
+ const storePath = argv.__storePath || null;
891
+ const storeClear = argv.includes('--store-clear');
756
892
 
757
893
  // --enforce-https: rewrite http:// -> https:// for log dereferencing builtins
758
894
  if (argv.includes('--enforce-https') || argv.includes('-e')) {
@@ -811,17 +947,94 @@ async function main() {
811
947
  }
812
948
  }
813
949
 
814
- const sourceLabels = useImplicitStdin ? ['<stdin>'] : positional.map((item) => (item === '-' ? '<stdin>' : item));
950
+ let sourceLabels = useImplicitStdin ? ['<stdin>'] : positional.map((item) => (item === '-' ? '<stdin>' : item));
815
951
  if (sourceLabels.filter((item) => item === '<stdin>').length > 1) {
816
952
  console.error('Error: stdin can only be used once.');
817
953
  process.exit(1);
818
954
  }
819
955
 
820
956
  if (streamMessagesMode) {
821
- await runStreamMessagesMode(sourceLabels, { rdfMode });
957
+ await runStreamMessagesMode(sourceLabels, { rdfMode, storeName, storePath, storeClear });
822
958
  return;
823
959
  }
824
960
 
961
+ if (storeName && rdfMode) {
962
+ const lineBasedSources = sourceLabels.filter(sourceLooksLikeLineBasedRdf);
963
+ if (lineBasedSources.length) {
964
+ if (showAst) {
965
+ console.error('Error: line-based --store ingestion cannot be combined with --ast.');
966
+ process.exit(1);
967
+ }
968
+ if (streamMode) {
969
+ console.error('Error: line-based --store ingestion cannot be combined with --stream.');
970
+ process.exit(1);
971
+ }
972
+ if (engine.getProofCommentsEnabled()) {
973
+ console.error('Error: line-based --store ingestion currently does not support proof output.');
974
+ process.exit(1);
975
+ }
976
+
977
+ const store = await createCliStore({ storeName, storePath, storeClear });
978
+ try {
979
+ for (const sourceLabel of lineBasedSources) {
980
+ try {
981
+ await ingestLineBasedRdfSourceToStore(sourceLabel, store, { rdfMode });
982
+ } catch (e) {
983
+ console.error(`Error streaming RDF source ${JSON.stringify(sourceLabel)}: ${e && e.message ? e.message : String(e)}`);
984
+ process.exit(1);
985
+ }
986
+ }
987
+
988
+ sourceLabels = sourceLabels.filter((sourceLabel) => !sourceLooksLikeLineBasedRdf(sourceLabel));
989
+ if (!sourceLabels.length) return;
990
+
991
+ const parsedRuleSources = [];
992
+ for (const sourceLabel of sourceLabels) {
993
+ let text;
994
+ try {
995
+ text = __readInputSourceSync(sourceLabel);
996
+ } catch (e) {
997
+ if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
998
+ else console.error(`Error reading source ${JSON.stringify(sourceLabel)}: ${e.message}`);
999
+ process.exit(1);
1000
+ }
1001
+
1002
+ try {
1003
+ parsedRuleSources.push(
1004
+ parseN3Text(text, {
1005
+ baseIri: __sourceLabelToBaseIri(sourceLabel),
1006
+ label: sourceLabel,
1007
+ collectUsedPrefixes: false,
1008
+ keepSourceArtifacts: false,
1009
+ sourceLocations: false,
1010
+ rdf: rdfMode,
1011
+ }),
1012
+ );
1013
+ } catch (e) {
1014
+ if (e && e.name === 'N3SyntaxError') {
1015
+ console.error(formatN3SyntaxError(e, text, sourceLabel));
1016
+ process.exit(1);
1017
+ }
1018
+ throw e;
1019
+ }
1020
+ }
1021
+
1022
+ const mergedRuleDocument = mergeParsedDocuments(parsedRuleSources);
1023
+ const result = await engine.runStoreBacked(mergedRuleDocument, store, { rdf: rdfMode });
1024
+ const outTriples = result.queryMode ? result.queryTriples || [] : (result.derived || []).map((df) => df.fact);
1025
+ if (result.queryMode) {
1026
+ const bodyText = engine.prettyPrintQueryTriples(outTriples, result.prefixes);
1027
+ if (bodyText) process.stdout.write(String(bodyText).replace(/\s*$/g, '') + '\n');
1028
+ } else {
1029
+ for (const tr of outTriples) console.log(engine.tripleToRdfCompatible(tr, result.prefixes));
1030
+ }
1031
+ return;
1032
+ } finally {
1033
+ if (store && typeof store.close === 'function') await store.close();
1034
+ }
1035
+ }
1036
+ }
1037
+
825
1038
  const parsedSources = [];
826
1039
  for (const sourceLabel of sourceLabels) {
827
1040
  let text;
@@ -931,6 +1144,69 @@ function factsContainOutputStrings(triplesForOutput) {
931
1144
  const hasQueries = Array.isArray(qrules) && qrules.length;
932
1145
  const mayAutoRenderOutputStrings = programMayProduceOutputStrings(triples, frules, qrules);
933
1146
 
1147
+ if (storeName) {
1148
+ if (streamMode) {
1149
+ console.error('Error: --store cannot be combined with --stream yet.');
1150
+ process.exit(1);
1151
+ }
1152
+
1153
+ const storeResult = await engine.runAsync(
1154
+ { prefixes, triples, frules, brules, logQueryRules: qrules },
1155
+ {
1156
+ proof: engine.getProofCommentsEnabled(),
1157
+ rdf: rdfMode,
1158
+ store: { name: storeName, clear: storeClear, path: storePath || undefined },
1159
+ },
1160
+ );
1161
+
1162
+ const storeFacts = storeResult.facts || [];
1163
+ const storeDerived = storeResult.derived || [];
1164
+ const storeHasQueries = !!storeResult.queryMode;
1165
+ const storeOutTriples = storeHasQueries ? (storeResult.queryTriples || []) : (mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled() ? [] : storeDerived.map((df) => df.fact));
1166
+ const storeOutDerived = storeHasQueries ? (storeResult.queryDerived || []) : storeDerived;
1167
+ const renderedOutputTriples = storeHasQueries ? storeOutTriples : storeFacts;
1168
+
1169
+ if (factsContainOutputStrings(renderedOutputTriples)) {
1170
+ process.stdout.write(engine.collectOutputStringsFromFacts(renderedOutputTriples, prefixes));
1171
+ if (storeResult.store && typeof storeResult.store.close === 'function') await storeResult.store.close();
1172
+ return;
1173
+ }
1174
+
1175
+ if (engine.getProofCommentsEnabled()) {
1176
+ process.stdout.write(engine.renderProofDocument(storeOutDerived, storeDerived.concat(storeOutDerived || []), triples, prefixes, brules));
1177
+ if (storeResult.store && typeof storeResult.store.close === 'function') await storeResult.store.close();
1178
+ return;
1179
+ }
1180
+
1181
+ let bodyText = '';
1182
+ if (rdfMode) bodyText = storeOutTriples.map((tr) => engine.tripleToRdfCompatible(tr, prefixes)).join('\n');
1183
+ else if (storeHasQueries) bodyText = engine.prettyPrintQueryTriples(storeOutTriples, prefixes);
1184
+
1185
+ let usedPrefixes = prefixes.prefixesUsedForOutput(storeOutTriples);
1186
+ if (rdfMode && bodyText) usedPrefixes = usedPrefixes.filter(([pfx]) => pfx === '' || bodyText.includes(pfx + ':'));
1187
+
1188
+ if (rdfMode && bodyText.includes('<<(')) {
1189
+ console.log('VERSION "1.2"');
1190
+ console.log();
1191
+ }
1192
+
1193
+ for (const [pfx, base] of usedPrefixes) {
1194
+ if (pfx === '') console.log(`@prefix : <${base}> .`);
1195
+ else console.log(`@prefix ${pfx}: <${base}> .`);
1196
+ }
1197
+ if (storeOutTriples.length && usedPrefixes.length) console.log();
1198
+
1199
+ if (bodyText) process.stdout.write(String(bodyText).replace(/\s*$/g, '') + '\n');
1200
+ else {
1201
+ for (const df of storeOutDerived) {
1202
+ console.log(rdfMode ? engine.tripleToRdfCompatible(df.fact, prefixes) : engine.tripleToN3(df.fact, prefixes));
1203
+ }
1204
+ }
1205
+
1206
+ if (storeResult.store && typeof storeResult.store.close === 'function') await storeResult.store.close();
1207
+ return;
1208
+ }
1209
+
934
1210
  if (streamMode && !hasQueries && !mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled()) {
935
1211
  const usedInInput = mergedDocument.usedPrefixes instanceof Set ? new Set(mergedDocument.usedPrefixes) : new Set();
936
1212
  const outPrefixes = restrictPrefixEnv(prefixes, usedInInput);