eyeling 1.28.8 → 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/test/api.test.js CHANGED
@@ -769,6 +769,132 @@ bad.:example a bad.:Person.
769
769
  `,
770
770
  expectError: true,
771
771
  },
772
+ {
773
+ name: '12k2 invalid syntax: raw newline in short string literal should throw',
774
+ opt: { proofComments: false },
775
+ input: `
776
+ @prefix : <http://example.org/> .
777
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
778
+
779
+ :my :value "
780
+ 123".
781
+
782
+ {
783
+ :my :value "\n123".
784
+ }
785
+ =>
786
+ {
787
+ :result :has :crash-syntax-11.
788
+ }.
789
+
790
+ {} => {
791
+ :test :contains :crash-syntax-11.
792
+ }.
793
+
794
+ {
795
+ :result :has :crash-syntax-11.
796
+ }
797
+ =>
798
+ {
799
+ :test :is false.
800
+ }.
801
+ `,
802
+ expectError: true,
803
+ },
804
+ {
805
+ name: '12k3 invalid syntax: repeated dots in numeric-looking literal should throw',
806
+ opt: { proofComments: false },
807
+ input: `
808
+ @prefix : <http://example.org/> .
809
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
810
+
811
+ :my :value 1.2.3.4.5.
812
+
813
+ {
814
+ :my :value 1.2.3.4.5.
815
+ }
816
+ =>
817
+ {
818
+ :result :has :crash-syntax-12.
819
+ }.
820
+
821
+ {} => {
822
+ :test :contains :crash-syntax-12.
823
+ }.
824
+
825
+ {
826
+ :result :has :crash-syntax-12.
827
+ }
828
+ =>
829
+ {
830
+ :test :is false.
831
+ }.
832
+ `,
833
+ expectError: true,
834
+ },
835
+ {
836
+ name: '12k4 success literal: signed integer shorthand parses',
837
+ opt: { proofComments: false },
838
+ input: `
839
+ @prefix : <http://example.org/> .
840
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
841
+
842
+ :my :value +42.
843
+
844
+ {
845
+ :my :value +42.
846
+ }
847
+ =>
848
+ {
849
+ :result :has :success-literal-32.
850
+ }.
851
+
852
+ {} => {
853
+ :test :contains :success-literal-32.
854
+ }.
855
+
856
+ {
857
+ :result :has :success-literal-32.
858
+ }
859
+ =>
860
+ {
861
+ :test :is true.
862
+ }.
863
+ `,
864
+ expect: [/:result\s+:has\s+:success-literal-32\s*\./, /:test\s+:is\s+true\s*\./],
865
+ },
866
+ {
867
+ name: '12k5 success literal: leading-dot decimal shorthand parses',
868
+ opt: { proofComments: false },
869
+ input: `
870
+ @prefix : <http://example.org/> .
871
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
872
+
873
+ :my :value .5.
874
+
875
+ {
876
+ :my :value .5.
877
+ }
878
+ =>
879
+ {
880
+ :result :has :success-literal-33.
881
+ }.
882
+
883
+ {} => {
884
+ :test :contains :success-literal-33.
885
+ }.
886
+
887
+ {
888
+ :result :has :success-literal-33.
889
+ }
890
+ =>
891
+ {
892
+ :test :is true.
893
+ }.
894
+ `,
895
+ expect: [/:result\s+:has\s+:success-literal-33\s*\./, /:test\s+:is\s+true\s*\./],
896
+ },
897
+
772
898
  {
773
899
  name: '12l regression: IRIREF \\u escape decodes before log:uri comparison (mismatch stays falsey)',
774
900
  opt: { proofComments: false },
@@ -1360,7 +1486,7 @@ _:l2 rdf:rest rdf:nil.
1360
1486
  odrl:action ?Ignore ;
1361
1487
  odrl:duty [ odrl:action ?A ]
1362
1488
  ].
1363
- ( "%% Duty_(a,t)(action:%s)\n%% => ~Possible_(a,t)(~action:%s)\n" ?A ?A ) string:format ?Str.
1489
+ ( "%% Duty_(a,t)(action:%s)\\n%% => ~Possible_(a,t)(~action:%s)\\n" ?A ?A ) string:format ?Str.
1364
1490
  }
1365
1491
  =>
1366
1492
  {
@@ -906,16 +906,30 @@ ${JSON.stringify(last, null, 2)}`);
906
906
  const fuseProgram = fs.readFileSync(path.join(ROOT, 'examples', 'fuse.n3'), 'utf8');
907
907
  const outputStringProgram = `@prefix : <#> .
908
908
  @prefix log: <http://www.w3.org/2000/10/swap/log#> .
909
- :report log:outputString "## Hello from output string\n\nLine 2 with **bold** and [Eyeling](https://example.org/eyeling)\n" .
909
+ :report log:outputString """## Hello from output string
910
+
911
+ Line 2 with **bold** and [Eyeling](https://example.org/eyeling)
912
+ """ .
910
913
  `;
911
914
  const riskMarkdownOutputStringProgram = `@prefix : <#> .
912
915
  @prefix log: <http://www.w3.org/2000/10/swap/log#> .
913
- :report log:outputString "# Risk report\n\n### Clause H1 — score 100\n\nRisk: secondary use is permitted without a safeguard. Clause H1: Hospital may provide electronic health data for secondary use.\n\n- **Mitigation for clause H1:** Require a permit before secondary use.\n" .
916
+ :report log:outputString """# Risk report
917
+
918
+ ### Clause H1 — score 100
919
+
920
+ Risk: secondary use is permitted without a safeguard. Clause H1: Hospital may provide electronic health data for secondary use.
921
+
922
+ - **Mitigation for clause H1:** Require a permit before secondary use.
923
+ """ .
914
924
  `;
915
925
  const baseOnlyMarkdownProgram = `@base <https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/smoke-arithmetic.n3> .
916
926
  @prefix : <#> .
917
927
  @prefix log: <http://www.w3.org/2000/10/swap/log#> .
918
- :report log:outputString "# stateurl link base\n\n[N3 rules](../smoke-arithmetic.n3)\n[Input TriG](../input/smoke-arithmetic.trig)\n" .
928
+ :report log:outputString """# stateurl link base
929
+
930
+ [N3 rules](../smoke-arithmetic.n3)
931
+ [Input TriG](../input/smoke-arithmetic.trig)
932
+ """ .
919
933
  `;
920
934
  const logQueryTurtleProgram = `@prefix : <#> .
921
935
  @prefix log: <http://www.w3.org/2000/10/swap/log#> .
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+
8
+ const { pass, failResult, info } = require('./report');
9
+ const { Iri, Triple } = require('../lib/prelude');
10
+ const { createFactStore } = require('../lib/store');
11
+ const { runAsync } = require('../index.js');
12
+
13
+ const EX = 'http://example.org/';
14
+ const a = new Iri(EX + 'a');
15
+ const p = new Iri(EX + 'p');
16
+ const q = new Iri(EX + 'q');
17
+ const b = new Iri(EX + 'b');
18
+ const c = new Iri(EX + 'c');
19
+ const x = new Iri(EX + 'x');
20
+ const y = new Iri(EX + 'y');
21
+ const r = new Iri(EX + 'r');
22
+ const z = new Iri(EX + 'z');
23
+
24
+ const triples = [
25
+ new Triple(a, p, b),
26
+ new Triple(a, p, c),
27
+ new Triple(a, q, b),
28
+ new Triple(x, p, b),
29
+ new Triple(y, r, z),
30
+ ];
31
+
32
+ async function collect(iterable) {
33
+ const out = [];
34
+ for await (const item of iterable) out.push(item);
35
+ return out;
36
+ }
37
+
38
+ async function withStore(factory, fn) {
39
+ const store = await factory();
40
+ try {
41
+ await store.batchAdd(triples, 'explicit');
42
+ await fn(store);
43
+ } finally {
44
+ if (store && typeof store.close === 'function') await store.close();
45
+ }
46
+ }
47
+
48
+ async function expectCount(store, label, args, expected) {
49
+ const out = await collect(store.match(...args));
50
+ if (out.length !== expected) {
51
+ throw new Error(`${label}: expected ${expected} match(es), got ${out.length}`);
52
+ }
53
+ }
54
+
55
+ const tests = [
56
+ {
57
+ name: 'memory store supports all eight triple match shapes',
58
+ fn: async () => withStore(() => createFactStore({ type: 'memory' }), async (store) => {
59
+ await expectCount(store, '(s,p,o)', [a, p, b], 1);
60
+ await expectCount(store, '(s,p,?)', [a, p, null], 2);
61
+ await expectCount(store, '(s,?,o)', [a, null, b], 2);
62
+ await expectCount(store, '(s,?,?)', [a, null, null], 3);
63
+ await expectCount(store, '(?,p,o)', [null, p, b], 2);
64
+ await expectCount(store, '(?,p,?)', [null, p, null], 3);
65
+ await expectCount(store, '(?,?,o)', [null, null, b], 3);
66
+ await expectCount(store, '(?,?,?)', [null, null, null], 5);
67
+ }),
68
+ },
69
+ {
70
+ name: 'persistent store supports all eight triple match shapes and duplicate prevention',
71
+ fn: async () => {
72
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-store-test-'));
73
+ try {
74
+ await withStore(() => createFactStore({ name: 'patterns', path: dir, clear: true }), async (store) => {
75
+ await expectCount(store, '(s,p,o)', [a, p, b], 1);
76
+ await expectCount(store, '(s,p,?)', [a, p, null], 2);
77
+ await expectCount(store, '(s,?,o)', [a, null, b], 2);
78
+ await expectCount(store, '(s,?,?)', [a, null, null], 3);
79
+ await expectCount(store, '(?,p,o)', [null, p, b], 2);
80
+ await expectCount(store, '(?,p,?)', [null, p, null], 3);
81
+ await expectCount(store, '(?,?,o)', [null, null, b], 3);
82
+ await expectCount(store, '(?,?,?)', [null, null, null], 5);
83
+ const added = await store.add(new Triple(a, p, b), 'inferred');
84
+ if (added) throw new Error('duplicate insertion should return false');
85
+ if (typeof store.kindOf === 'function') {
86
+ const mask = await store.kindOf(new Triple(a, p, b));
87
+ if ((mask & 1) === 0 || (mask & 2) === 0) throw new Error('explicit/inferred bitmask should be preserved');
88
+ }
89
+ });
90
+ } finally {
91
+ fs.rmSync(dir, { recursive: true, force: true });
92
+ }
93
+ },
94
+ },
95
+ {
96
+ name: 'runAsync reuses a named persistent store across runs',
97
+ fn: async () => {
98
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-runasync-store-'));
99
+ try {
100
+ const first = await runAsync('@prefix : <http://example.org/> .\n:a :p :b .', { store: { name: 'dataset', path: dir, clear: true } });
101
+ if (first.store) await first.store.close();
102
+
103
+ const second = await runAsync('@prefix : <http://example.org/> .\n{ ?s :p ?o } => { ?s :q ?o } .', { store: { name: 'dataset', path: dir } });
104
+ try {
105
+ const text = second.closureN3 || '';
106
+ if (!text.includes(':a :q :b .') && !text.includes(':a :q :b.')) throw new Error(`expected stored fact to feed rule, got:\n${text}`);
107
+ if (!second.store) throw new Error('runAsync should return the opened store');
108
+ const qMatches = await collect(second.store.match(a, q, b));
109
+ if (qMatches.length !== 1) throw new Error('expected inferred fact to be persisted');
110
+ } finally {
111
+ if (second.store) await second.store.close();
112
+ }
113
+ } finally {
114
+ fs.rmSync(dir, { recursive: true, force: true });
115
+ }
116
+ },
117
+ },
118
+ ];
119
+
120
+ (async function main() {
121
+ const startAll = Date.now();
122
+ let passed = 0;
123
+ for (let i = 0; i < tests.length; i += 1) {
124
+ const n = i + 1;
125
+ const t0 = Date.now();
126
+ try {
127
+ await tests[i].fn();
128
+ pass(n, tests[i].name, Date.now() - t0);
129
+ passed += 1;
130
+ } catch (e) {
131
+ failResult(n, tests[i].name, Date.now() - t0);
132
+ console.error(e && e.stack ? e.stack : e);
133
+ process.exitCode = 1;
134
+ break;
135
+ }
136
+ }
137
+ info(`Store tests: ${passed}/${tests.length} passed`, Date.now() - startAll);
138
+ })();
package/tools/bundle.js CHANGED
@@ -240,6 +240,10 @@ function getBrowserApi() {
240
240
 
241
241
  export const INFERENCE_FUSE_EXIT_CODE = 65;
242
242
 
243
+ export function runAsync(input, opts) {
244
+ return getBrowserApi().runAsync(input, opts);
245
+ }
246
+
243
247
  export function reasonStream(input, opts) {
244
248
  return getBrowserApi().reasonStream(input, opts);
245
249
  }
@@ -248,6 +252,10 @@ export function reasonRdfJs(input, opts) {
248
252
  return getBrowserApi().reasonRdfJs(input, opts);
249
253
  }
250
254
 
255
+ export function createFactStore(options) {
256
+ return getBrowserApi().createFactStore(options);
257
+ }
258
+
251
259
  export function registerBuiltin(iri, handler) {
252
260
  return getBrowserApi().registerBuiltin(iri, handler);
253
261
  }
@@ -295,9 +303,11 @@ const eyeling = {
295
303
  return getBrowserApi().version;
296
304
  },
297
305
  INFERENCE_FUSE_EXIT_CODE,
306
+ runAsync,
298
307
  reasonStream,
299
308
  reasonRdfJs,
300
309
  rdfjs,
310
+ createFactStore,
301
311
  registerBuiltin,
302
312
  unregisterBuiltin,
303
313
  registerBuiltinModule,