eyeling 1.26.7 → 1.27.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.
Files changed (31) hide show
  1. package/HANDBOOK.md +32 -1
  2. package/dist/browser/eyeling.browser.js +396 -3
  3. package/examples/deck/act-barley-seed-lineage.md +1 -1
  4. package/examples/deck/faltings-genus2-finiteness.md +1 -1
  5. package/examples/deck/high-trust-rdf-bloom-envelope.md +1 -1
  6. package/examples/deck/odrl-dpv-risk-ranked.md +1 -1
  7. package/examples/deck/rdf-message-cold-chain-recall.md +71 -0
  8. package/examples/deck/rdf-message-flow.md +1 -1
  9. package/examples/deck/rdf-message-ldes-incremental.md +94 -0
  10. package/examples/deck/rdf-message-window-repair.md +1 -1
  11. package/examples/deck/schema-foaf-mapping.md +2 -0
  12. package/examples/input/rdf-message-cold-chain-recall.trig +668 -0
  13. package/examples/input/rdf-message-flow.trig +3 -0
  14. package/examples/input/rdf-message-ldes-incremental.trig +564 -0
  15. package/examples/input/rdf-message-microgrid.trig +3 -0
  16. package/examples/input/rdf-message-window-repair.trig +3 -0
  17. package/examples/input/rdf-messages.trig +3 -0
  18. package/examples/output/rdf-message-cold-chain-recall.md +13 -0
  19. package/examples/output/rdf-message-ldes-incremental.md +13 -0
  20. package/examples/rdf-message-cold-chain-recall.n3 +229 -0
  21. package/examples/rdf-message-flow.n3 +3 -0
  22. package/examples/rdf-message-ldes-incremental.n3 +231 -0
  23. package/examples/rdf-message-microgrid.n3 +3 -0
  24. package/examples/rdf-message-window-repair.n3 +3 -0
  25. package/examples/rdf-messages.n3 +3 -0
  26. package/eyeling.js +396 -3
  27. package/lib/cli.js +396 -3
  28. package/package.json +5 -3
  29. package/test/examples.test.js +25 -14
  30. package/test/fixtures/marc-rules-stream-messages.n3 +192 -0
  31. package/test/stream_messages.test.js +243 -0
package/lib/cli.js CHANGED
@@ -7,12 +7,15 @@
7
7
 
8
8
  'use strict';
9
9
 
10
+ const fs = require('node:fs');
10
11
  const path = require('node:path');
11
- const { pathToFileURL } = require('node:url');
12
+ const { pathToFileURL, fileURLToPath } = require('node:url');
13
+ const { TextDecoder } = require('node:util');
12
14
 
13
15
  const engine = require('./engine');
14
16
  const deref = require('./deref');
15
17
  const { PrefixEnv } = require('./prelude');
18
+ const { normalizeRdfCompatibility } = require('./lexer');
16
19
  const { parseN3Text, mergeParsedDocuments } = require('./multisource');
17
20
 
18
21
  function offsetToLineCol(text, offset) {
@@ -51,7 +54,6 @@ function formatN3SyntaxError(err, text, path) {
51
54
 
52
55
  // CLI entry point (invoked when eyeling.js is run directly)
53
56
  function readTextFromStdinSync() {
54
- const fs = require('node:fs');
55
57
  return fs.readFileSync(0, { encoding: 'utf8' });
56
58
  }
57
59
 
@@ -74,10 +76,374 @@ function __readInputSourceSync(sourceLabel) {
74
76
  return txt;
75
77
  }
76
78
 
77
- const fs = require('node:fs');
78
79
  return fs.readFileSync(sourceLabel, { encoding: 'utf8' });
79
80
  }
80
81
 
82
+
83
+ const RDF_MESSAGE_VERSION_RE = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/im;
84
+ const RDF_MESSAGE_VERSION_LINE_RE = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/i;
85
+ const RDF_DIRECTIVE_LINE_RE = /^\s*(?:@?(?:prefix|base)\b|PREFIX\b|BASE\b)/i;
86
+ const RDF_MESSAGE_DELIMITER_LINE_RE = /^\s*(?:MESSAGE\b|@message\s*\.?)\s*(?:#.*)?$/i;
87
+ const LOG_NAME_OF_IRI = '<http://www.w3.org/2000/10/swap/log#nameOf>';
88
+ const RDF_TYPE_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>';
89
+ const XSD_INTEGER_IRI = '<http://www.w3.org/2001/XMLSchema#integer>';
90
+ const EYMSG_NS = 'https://eyereasoner.github.io/eyeling/vocab/message#';
91
+ const EYMSG_IRIS = Object.freeze({
92
+ RDFMessageStream: `<${EYMSG_NS}RDFMessageStream>`,
93
+ MessageEnvelope: `<${EYMSG_NS}MessageEnvelope>`,
94
+ envelope: `<${EYMSG_NS}envelope>`,
95
+ firstEnvelope: `<${EYMSG_NS}firstEnvelope>`,
96
+ lastEnvelope: `<${EYMSG_NS}lastEnvelope>`,
97
+ orderedEnvelopes: `<${EYMSG_NS}orderedEnvelopes>`,
98
+ offset: `<${EYMSG_NS}offset>`,
99
+ payloadGraph: `<${EYMSG_NS}payloadGraph>`,
100
+ payloadKind: `<${EYMSG_NS}payloadKind>`,
101
+ empty: `<${EYMSG_NS}empty>`,
102
+ nonEmpty: `<${EYMSG_NS}nonEmpty>`,
103
+ });
104
+
105
+ function simpleHashText(s) {
106
+ let h = 0x811c9dc5;
107
+ const text = String(s || '');
108
+ for (let i = 0; i < text.length; i += 1) {
109
+ h ^= text.charCodeAt(i);
110
+ h = Math.imul(h, 0x01000193) >>> 0;
111
+ }
112
+ return h.toString(16).padStart(8, '0');
113
+ }
114
+
115
+ function __isLocalPathSource(sourceLabel) {
116
+ return typeof sourceLabel === 'string' && sourceLabel !== '<stdin>' && !/^(https?:|file:\/\/)/i.test(sourceLabel);
117
+ }
118
+
119
+ function __localPathForSource(sourceLabel) {
120
+ if (__isLocalPathSource(sourceLabel)) return sourceLabel;
121
+ if (typeof sourceLabel === 'string' && /^file:\/\//i.test(sourceLabel)) return fileURLToPath(sourceLabel);
122
+ return null;
123
+ }
124
+
125
+ function __sourceLooksLikeRdfMessageLogSync(sourceLabel) {
126
+ const filePath = __localPathForSource(sourceLabel);
127
+ if (filePath) {
128
+ const fd = fs.openSync(filePath, 'r');
129
+ try {
130
+ const buf = Buffer.allocUnsafe(64 * 1024);
131
+ const n = fs.readSync(fd, buf, 0, buf.length, 0);
132
+ return RDF_MESSAGE_VERSION_RE.test(buf.toString('utf8', 0, n));
133
+ } finally {
134
+ fs.closeSync(fd);
135
+ }
136
+ }
137
+ return RDF_MESSAGE_VERSION_RE.test(__readInputSourceSync(sourceLabel));
138
+ }
139
+
140
+ function stripRdfDirectiveLines(text) {
141
+ return String(text || '')
142
+ .split(/(?<=\r\n|\n|\r)/)
143
+ .filter((line) => !RDF_DIRECTIVE_LINE_RE.test(line) && !RDF_MESSAGE_VERSION_LINE_RE.test(line))
144
+ .join('');
145
+ }
146
+
147
+ function hasRdfPayload(text) {
148
+ return String(text || '')
149
+ .split(/\r\n|\n|\r/)
150
+ .some((line) => {
151
+ const trimmed = line.replace(/#.*$/g, '').trim();
152
+ return trimmed && !RDF_DIRECTIVE_LINE_RE.test(trimmed) && !RDF_MESSAGE_VERSION_LINE_RE.test(trimmed);
153
+ });
154
+ }
155
+
156
+ function addRdfDirective(directives, seen, line) {
157
+ if (!RDF_DIRECTIVE_LINE_RE.test(line)) return;
158
+ const key = line.trim();
159
+ if (!key || seen.has(key)) return;
160
+ seen.add(key);
161
+ directives.push(line.endsWith('\n') || line.endsWith('\r') ? line : line + '\n');
162
+ }
163
+
164
+ function normalizeStreamingPayloadChunk(chunk, directives) {
165
+ const prelude = directives.join('');
166
+ const normalized = normalizeRdfCompatibility(prelude + String(chunk || ''));
167
+ return stripRdfDirectiveLines(normalized).trim();
168
+ }
169
+
170
+ function buildSingleMessageReplayDocument({ sourceLabel, messageIndex, chunk, directives }) {
171
+ const hash = simpleHashText(sourceLabel || '<stream>');
172
+ const base = `urn:eyeling:message-stream:${hash}`;
173
+ const padded = String(messageIndex).padStart(6, '0');
174
+ const stream = `<${base}#stream>`;
175
+ const envelope = `<${base}#m${padded}>`;
176
+ const payload = `<${base}#m${padded}/payload>`;
177
+ const body = normalizeStreamingPayloadChunk(chunk, directives);
178
+ const hasBody = hasRdfPayload(body);
179
+ const out = [];
180
+
181
+ out.push(...directives.map((line) => line.trim()).filter(Boolean));
182
+ out.push(`${stream} ${RDF_TYPE_IRI} ${EYMSG_IRIS.RDFMessageStream} .`);
183
+ out.push(`${stream} ${EYMSG_IRIS.envelope} ${envelope} .`);
184
+ out.push(`${stream} ${EYMSG_IRIS.orderedEnvelopes} (${envelope}) .`);
185
+ out.push(`${stream} ${EYMSG_IRIS.firstEnvelope} ${envelope} .`);
186
+ out.push(`${stream} ${EYMSG_IRIS.lastEnvelope} ${envelope} .`);
187
+ out.push(`${envelope} ${RDF_TYPE_IRI} ${EYMSG_IRIS.MessageEnvelope} .`);
188
+ out.push(`${envelope} ${EYMSG_IRIS.offset} "${messageIndex}"^^${XSD_INTEGER_IRI} .`);
189
+ out.push(`${envelope} ${EYMSG_IRIS.payloadKind} ${hasBody ? EYMSG_IRIS.nonEmpty : EYMSG_IRIS.empty} .`);
190
+ if (hasBody) {
191
+ out.push(`${envelope} ${EYMSG_IRIS.payloadGraph} ${payload} .`);
192
+ out.push(`${payload} ${LOG_NAME_OF_IRI} {`);
193
+ out.push(body);
194
+ out.push(`} .`);
195
+ }
196
+ return out.join('\n') + '\n';
197
+ }
198
+
199
+ function forEachRdfMessageChunkInText(text, onMessage) {
200
+ const directives = [];
201
+ const seenDirectives = new Set();
202
+ let chunk = '';
203
+ let messageIndex = 1;
204
+ let sawVersion = false;
205
+ let sawDelimiter = false;
206
+
207
+ function emit() {
208
+ onMessage({ messageIndex, chunk, directives: directives.slice() });
209
+ messageIndex += 1;
210
+ chunk = '';
211
+ }
212
+
213
+ const lines = String(text || '').match(/.*(?:\r\n|\n|\r)|.+$/g) || [];
214
+ for (const line of lines) {
215
+ if (RDF_MESSAGE_VERSION_LINE_RE.test(line)) {
216
+ sawVersion = true;
217
+ continue;
218
+ }
219
+ if (RDF_MESSAGE_DELIMITER_LINE_RE.test(line)) {
220
+ emit();
221
+ sawDelimiter = true;
222
+ continue;
223
+ }
224
+ addRdfDirective(directives, seenDirectives, line);
225
+ chunk += line;
226
+ }
227
+ if (!sawVersion) throw new Error('not an RDF Message Log: missing VERSION "*-messages" directive');
228
+ if (sawDelimiter || hasRdfPayload(chunk)) emit();
229
+ }
230
+
231
+ function forEachLineInFileSync(filePath, onLine) {
232
+ const fd = fs.openSync(filePath, 'r');
233
+ const decoder = new TextDecoder('utf8');
234
+ const buf = Buffer.allocUnsafe(64 * 1024);
235
+ let carry = '';
236
+ try {
237
+ for (;;) {
238
+ const n = fs.readSync(fd, buf, 0, buf.length, null);
239
+ if (n === 0) break;
240
+ carry += decoder.decode(buf.subarray(0, n), { stream: true });
241
+ for (;;) {
242
+ const m = /\r\n|\n|\r/.exec(carry);
243
+ if (!m) break;
244
+ const end = m.index + m[0].length;
245
+ onLine(carry.slice(0, end));
246
+ carry = carry.slice(end);
247
+ }
248
+ }
249
+ carry += decoder.decode();
250
+ if (carry) onLine(carry);
251
+ } finally {
252
+ fs.closeSync(fd);
253
+ }
254
+ }
255
+
256
+ function forEachRdfMessageChunkInFileSync(filePath, onMessage) {
257
+ const directives = [];
258
+ const seenDirectives = new Set();
259
+ let chunk = '';
260
+ let messageIndex = 1;
261
+ let sawVersion = false;
262
+ let sawDelimiter = false;
263
+
264
+ function emit() {
265
+ onMessage({ messageIndex, chunk, directives: directives.slice() });
266
+ messageIndex += 1;
267
+ chunk = '';
268
+ }
269
+
270
+ forEachLineInFileSync(filePath, (line) => {
271
+ if (RDF_MESSAGE_VERSION_LINE_RE.test(line)) {
272
+ sawVersion = true;
273
+ return;
274
+ }
275
+ if (RDF_MESSAGE_DELIMITER_LINE_RE.test(line)) {
276
+ emit();
277
+ sawDelimiter = true;
278
+ return;
279
+ }
280
+ addRdfDirective(directives, seenDirectives, line);
281
+ chunk += line;
282
+ });
283
+
284
+ if (!sawVersion) throw new Error('not an RDF Message Log: missing VERSION "*-messages" directive');
285
+ if (sawDelimiter || hasRdfPayload(chunk)) emit();
286
+ }
287
+
288
+
289
+ function __forEachRdfMessageChunkSync(sourceLabel, onMessage) {
290
+ const filePath = __localPathForSource(sourceLabel);
291
+ if (filePath) {
292
+ forEachRdfMessageChunkInFileSync(filePath, onMessage);
293
+ return;
294
+ }
295
+ forEachRdfMessageChunkInText(__readInputSourceSync(sourceLabel), onMessage);
296
+ }
297
+
298
+ function factsContainOutputStrings(triplesForOutput) {
299
+ const LOG_OUTPUT_STRING = 'http://www.w3.org/2000/10/swap/log#outputString';
300
+ return (
301
+ Array.isArray(triplesForOutput) &&
302
+ triplesForOutput.some(
303
+ (tr) => tr && tr.p && tr.p.constructor && tr.p.constructor.name === 'Iri' && tr.p.value === LOG_OUTPUT_STRING,
304
+ )
305
+ );
306
+ }
307
+
308
+ function programMayProduceOutputStrings(topLevelTriples, forwardRules, logQueryRules) {
309
+ const hasOutputStringPredicate = (trs) => factsContainOutputStrings(trs);
310
+ if (hasOutputStringPredicate(topLevelTriples)) return true;
311
+ if (Array.isArray(forwardRules) && forwardRules.some((r) => hasOutputStringPredicate(r && r.conclusion))) return true;
312
+ if (Array.isArray(logQueryRules) && logQueryRules.some((r) => hasOutputStringPredicate(r && r.conclusion))) return true;
313
+ return false;
314
+ }
315
+
316
+ function runParsedDocumentOnce(mergedDocument, { rdfMode = false, outputPrefixes = null } = {}) {
317
+ const prefixes = mergedDocument.prefixes;
318
+ const triples = mergedDocument.triples;
319
+ const frules = mergedDocument.frules;
320
+ const brules = mergedDocument.brules;
321
+ const qrules = mergedDocument.logQueryRules;
322
+
323
+ engine.materializeRdfLists(triples, frules.concat(qrules || []), brules);
324
+ const facts = triples.slice();
325
+ const hasQueries = Array.isArray(qrules) && qrules.length;
326
+ const mayAutoRenderOutputStrings = programMayProduceOutputStrings(triples, frules, qrules);
327
+
328
+ let derived = [];
329
+ let outTriples = [];
330
+ if (hasQueries) {
331
+ const res = engine.forwardChainAndCollectLogQueryConclusions(facts, frules, brules, qrules, { prefixes });
332
+ derived = res.derived;
333
+ outTriples = res.queryTriples;
334
+ } else {
335
+ const skipDerivedCollection = mayAutoRenderOutputStrings;
336
+ derived = engine.forwardChain(facts, frules, brules, null, {
337
+ captureExplanations: false,
338
+ collectDerived: !skipDerivedCollection,
339
+ prefixes,
340
+ });
341
+ outTriples = skipDerivedCollection ? [] : derived.map((df) => df.fact);
342
+ }
343
+
344
+ const renderedOutputTriples = hasQueries ? outTriples : facts;
345
+ if (factsContainOutputStrings(renderedOutputTriples)) {
346
+ process.stdout.write(engine.collectOutputStringsFromFacts(renderedOutputTriples, prefixes));
347
+ return;
348
+ }
349
+
350
+ const outPrefixEnv = outputPrefixes || prefixes;
351
+ if (rdfMode) {
352
+ for (const tr of outTriples) console.log(engine.tripleToRdfCompatible(tr, outPrefixEnv));
353
+ } else if (hasQueries) {
354
+ const bodyText = engine.prettyPrintQueryTriples(outTriples, outPrefixEnv);
355
+ if (bodyText) process.stdout.write(String(bodyText).replace(/\s*$/g, '') + '\n');
356
+ } else {
357
+ for (const df of derived) console.log(engine.tripleToN3(df.fact, outPrefixEnv));
358
+ }
359
+ }
360
+
361
+ function runStreamMessagesMode(sourceLabels, { rdfMode }) {
362
+ const ordinarySourceLabels = [];
363
+ const messageSourceLabels = [];
364
+
365
+ for (const sourceLabel of sourceLabels) {
366
+ try {
367
+ if (__sourceLooksLikeRdfMessageLogSync(sourceLabel)) messageSourceLabels.push(sourceLabel);
368
+ else ordinarySourceLabels.push(sourceLabel);
369
+ } catch (e) {
370
+ console.error(`Error reading source ${JSON.stringify(sourceLabel)}: ${e && e.message ? e.message : String(e)}`);
371
+ process.exit(1);
372
+ }
373
+ }
374
+
375
+ if (!messageSourceLabels.length) {
376
+ console.error('Error: --stream-messages did not find any RDF Message Log input.');
377
+ process.exit(1);
378
+ }
379
+
380
+ const programSources = [];
381
+ for (const sourceLabel of ordinarySourceLabels) {
382
+ let text;
383
+ try {
384
+ text = __readInputSourceSync(sourceLabel);
385
+ } catch (e) {
386
+ if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
387
+ else console.error(`Error reading source ${JSON.stringify(sourceLabel)}: ${e.message}`);
388
+ process.exit(1);
389
+ }
390
+
391
+ try {
392
+ programSources.push(
393
+ parseN3Text(text, {
394
+ baseIri: __sourceLabelToBaseIri(sourceLabel),
395
+ label: sourceLabel,
396
+ keepSourceArtifacts: false,
397
+ sourceLocations: false,
398
+ rdf: rdfMode,
399
+ }),
400
+ );
401
+ } catch (e) {
402
+ if (e && e.name === 'N3SyntaxError') {
403
+ console.error(formatN3SyntaxError(e, text, sourceLabel));
404
+ process.exit(1);
405
+ }
406
+ throw e;
407
+ }
408
+ }
409
+
410
+ const fullIriPrefixes = new PrefixEnv({});
411
+ for (const messageSourceLabel of messageSourceLabels) {
412
+ try {
413
+ __forEachRdfMessageChunkSync(messageSourceLabel, ({ messageIndex, chunk, directives }) => {
414
+ const messageText = buildSingleMessageReplayDocument({
415
+ sourceLabel: messageSourceLabel,
416
+ messageIndex,
417
+ chunk,
418
+ directives,
419
+ });
420
+ let messageDoc;
421
+ try {
422
+ messageDoc = parseN3Text(messageText, {
423
+ baseIri: `${__sourceLabelToBaseIri(messageSourceLabel)}#message-${messageIndex}`,
424
+ label: `${messageSourceLabel}#message-${messageIndex}`,
425
+ keepSourceArtifacts: false,
426
+ sourceLocations: false,
427
+ rdf: false,
428
+ });
429
+ } catch (e) {
430
+ if (e && e.name === 'N3SyntaxError') {
431
+ console.error(formatN3SyntaxError(e, messageText, `${messageSourceLabel}#message-${messageIndex}`));
432
+ process.exit(1);
433
+ }
434
+ throw e;
435
+ }
436
+
437
+ const merged = mergeParsedDocuments(programSources.concat([messageDoc]));
438
+ runParsedDocumentOnce(merged, { rdfMode, outputPrefixes: fullIriPrefixes });
439
+ });
440
+ } catch (e) {
441
+ console.error(`Error streaming RDF Message Log ${JSON.stringify(messageSourceLabel)}: ${e && e.message ? e.message : String(e)}`);
442
+ process.exit(1);
443
+ }
444
+ }
445
+ }
446
+
81
447
  function main() {
82
448
  // Drop "node" and script name; keep only user-provided args
83
449
  // Expand combined short options: -pt == -p -t
@@ -108,6 +474,7 @@ function main() {
108
474
  ` -h, --help Show this help and exit.\n` +
109
475
  ` -p, --proof Enable proof explanations.\n` +
110
476
  ` -r, --rdf Enable RDF/TriG input/output compatibility.\n` +
477
+ ` --stream-messages Process RDF Message Logs one message at a time under -r.\n` +
111
478
  ` -s, --super-restricted Disable all builtins except => and <=.\n` +
112
479
  ` -t, --stream Stream derived triples as soon as they are derived.\n` +
113
480
  ` -v, --version Print version and exit.\n`;
@@ -149,6 +516,7 @@ function main() {
149
516
 
150
517
  const showAst = argv.includes('--ast') || argv.includes('-a');
151
518
  const streamMode = argv.includes('--stream') || argv.includes('-t');
519
+ const streamMessagesMode = argv.includes('--stream-messages');
152
520
  const rdfMode = argv.includes('--rdf') || argv.includes('-r');
153
521
 
154
522
  // --enforce-https: rewrite http:// -> https:// for log dereferencing builtins
@@ -171,6 +539,26 @@ function main() {
171
539
  if (typeof engine.setSuperRestrictedMode === 'function') engine.setSuperRestrictedMode(true);
172
540
  }
173
541
 
542
+
543
+ if (streamMessagesMode) {
544
+ if (!rdfMode) {
545
+ console.error('Error: --stream-messages requires -r/--rdf.');
546
+ process.exit(1);
547
+ }
548
+ if (showAst) {
549
+ console.error('Error: --stream-messages cannot be combined with --ast.');
550
+ process.exit(1);
551
+ }
552
+ if (streamMode) {
553
+ console.error('Error: --stream-messages cannot be combined with --stream.');
554
+ process.exit(1);
555
+ }
556
+ if (engine.getProofCommentsEnabled()) {
557
+ console.error('Error: --stream-messages currently does not support proof output.');
558
+ process.exit(1);
559
+ }
560
+ }
561
+
174
562
  // Positional args (one or more N3 sources).
175
563
  const useImplicitStdin = positional.length === 0 && !process.stdin.isTTY;
176
564
  if (positional.length === 0 && !useImplicitStdin) {
@@ -194,6 +582,11 @@ function main() {
194
582
  process.exit(1);
195
583
  }
196
584
 
585
+ if (streamMessagesMode) {
586
+ runStreamMessagesMode(sourceLabels, { rdfMode });
587
+ return;
588
+ }
589
+
197
590
  const parsedSources = [];
198
591
  for (const sourceLabel of sourceLabels) {
199
592
  let text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.26.7",
3
+ "version": "1.27.0",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
@@ -40,14 +40,16 @@
40
40
  "prebuild": "eslint . --fix",
41
41
  "build": "node tools/bundle.js",
42
42
  "test:packlist": "node test/packlist.test.js",
43
- "test:api": "node test/api.test.js",
43
+ "test:api": "node test/api.test.js && node test/stream_messages.test.js",
44
+ "test:stream-messages": "node test/stream_messages.test.js",
44
45
  "test:builtins": "node test/builtins.test.js",
45
46
  "test:examples": "node test/examples.test.js",
47
+ "test:examples:proof": "node test/examples.test.js --proof-only",
46
48
  "test:manifest": "node test/manifest.test.js",
47
49
  "test:playground": "node test/playground.test.js",
48
50
  "test:package": "node test/package.test.js",
49
51
  "pretest": "npm run build && npm run test:packlist",
50
- "test": "npm run test:api && npm run test:builtins && npm run test:examples && npm run test:manifest && npm run test:playground",
52
+ "test": "npm run test:api && npm run test:builtins && npm run test:examples && npm run test:examples:proof && npm run test:manifest && npm run test:playground",
51
53
  "posttest": "npm run test:package",
52
54
  "preversion": "npm test",
53
55
  "postversion": "git push origin HEAD --follow-tags"
@@ -212,6 +212,8 @@ function runExampleToFile({ root, examplesDir, eyelingJsPath, nodePath, file, ge
212
212
 
213
213
  function main() {
214
214
  const suiteStart = Date.now();
215
+ const proofOnly = process.argv.includes('--proof-only');
216
+
215
217
 
216
218
  // test/examples.test.js -> repo root is one level up
217
219
  const root = path.resolve(__dirname, '..');
@@ -230,11 +232,13 @@ function main() {
230
232
  process.exit(1);
231
233
  }
232
234
 
233
- const files = fs
234
- .readdirSync(examplesDir)
235
- .filter((f) => f.endsWith('.n3'))
236
- .sort((a, b) => a.localeCompare(b));
237
- const proofFiles = fs.existsSync(proofDir)
235
+ const files = proofOnly
236
+ ? []
237
+ : fs
238
+ .readdirSync(examplesDir)
239
+ .filter((f) => f.endsWith('.n3'))
240
+ .sort((a, b) => a.localeCompare(b));
241
+ const proofFiles = proofOnly && fs.existsSync(proofDir)
238
242
  ? fs
239
243
  .readdirSync(proofDir)
240
244
  .filter((f) => f.endsWith('.n3') && fs.existsSync(path.join(examplesDir, f)))
@@ -242,11 +246,13 @@ function main() {
242
246
  : [];
243
247
  const totalTests = files.length + proofFiles.length;
244
248
 
245
- info(`Running ${files.length} examples tests and ${proofFiles.length} proof golden tests`);
249
+ info(proofOnly
250
+ ? `Running ${proofFiles.length} proof golden tests`
251
+ : `Running ${files.length} examples tests`);
246
252
  console.log(`${C.dim}${getEyelingVersion(nodePath, eyelingJsPath, root)}; node ${process.version}${C.n}`);
247
253
 
248
- if (files.length === 0) {
249
- ok('No .n3 files found in examples/');
254
+ if (totalTests === 0) {
255
+ ok(proofOnly ? 'No proof goldens found in examples/proof/' : 'No .n3 files found in examples/');
250
256
  process.exit(0);
251
257
  }
252
258
 
@@ -256,6 +262,7 @@ function main() {
256
262
  // Pretty, stable numbering (e.g., 001..100 when running 100 tests)
257
263
  const idxWidth = String(files.length).length;
258
264
  const proofIdxWidth = String(Math.max(proofFiles.length, 1)).length;
265
+ const proofLabel = proofOnly ? '' : 'proof ';
259
266
 
260
267
  for (let i = 0; i < files.length; i++) {
261
268
  const idx = String(i + 1).padStart(idxWidth, '0');
@@ -357,7 +364,7 @@ function main() {
357
364
  n3Text = fs.readFileSync(filePath, 'utf8');
358
365
  } catch (e) {
359
366
  const ms = Date.now() - start;
360
- fail(`proof ${idx} ${file} ${msTag(ms)}`);
367
+ fail(`${proofLabel}${idx} ${file} ${msTag(ms)}`);
361
368
  fail(`Cannot read proof input: ${e.message}`);
362
369
  failed++;
363
370
  continue;
@@ -381,13 +388,13 @@ function main() {
381
388
 
382
389
  if (diffOk && rcOk) {
383
390
  if (expectedRc === 0) {
384
- ok(`proof ${idx} ${file} ${msTag(ms)}`);
391
+ ok(`${proofLabel}${idx} ${file} ${msTag(ms)}`);
385
392
  } else {
386
- ok(`proof ${idx} ${file} (expected exit ${expectedRc}) ${msTag(ms)}`);
393
+ ok(`${proofLabel}${idx} ${file} (expected exit ${expectedRc}) ${msTag(ms)}`);
387
394
  }
388
395
  passed++;
389
396
  } else {
390
- fail(`proof ${idx} ${file} ${msTag(ms)}`);
397
+ fail(`${proofLabel}${idx} ${file} ${msTag(ms)}`);
391
398
  if (!rcOk) {
392
399
  fail(`Exit code ${rc}, expected ${expectedRc}`);
393
400
  }
@@ -410,10 +417,14 @@ function main() {
410
417
  info(`Total elapsed: ${suiteMs} ms (${(suiteMs / 1000).toFixed(2)} s)`);
411
418
 
412
419
  if (failed === 0) {
413
- ok(`All examples tests passed (${passed}/${totalTests})`);
420
+ ok(proofOnly
421
+ ? `All proof golden tests passed (${passed}/${totalTests})`
422
+ : `All examples tests passed (${passed}/${totalTests})`);
414
423
  process.exit(0);
415
424
  } else {
416
- fail(`Some examples tests failed (${passed}/${totalTests})`);
425
+ fail(proofOnly
426
+ ? `Some proof golden tests failed (${passed}/${totalTests})`
427
+ : `Some examples tests failed (${passed}/${totalTests})`);
417
428
  // keep exit code 2 (matches historical behavior of examples/test)
418
429
  process.exit(2);
419
430
  }