eyeling 1.20.0 → 1.21.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/HANDBOOK.md CHANGED
@@ -1757,7 +1757,9 @@ The same API can now also emit RDF/JS output. When `rdfjs: true` is passed, ever
1757
1757
  - `triple` — Eyeling’s N3 string form
1758
1758
  - `quad` — the same fact as an RDF/JS default-graph quad
1759
1759
 
1760
- For fully stream-oriented RDF/JS consumers there is also `reasonRdfJs(...)`, which exposes the derived facts as an async iterable of RDF/JS quads.
1760
+ If your closure may contain N3-only terms such as quoted formulas (`GraphTerm`), RDF/JS conversion can fail because those terms have no standard RDF/JS representation. In that case, pass `skipUnsupportedRdfJs: true` to keep the full N3 closure while silently omitting any derived triples that cannot be represented as RDF/JS quads. When this flag is enabled, `onDerived(...)` still fires for every derived fact, but `quad` is only present for the representable ones.
1761
+
1762
+ For fully stream-oriented RDF/JS consumers there is also `reasonRdfJs(...)`, which exposes the derived facts as an async iterable of RDF/JS quads. The same `skipUnsupportedRdfJs: true` flag applies there as well.
1761
1763
 
1762
1764
  ---
1763
1765
 
@@ -2002,8 +2004,11 @@ import eyeling from './eyeling.js';
2002
2004
 
2003
2005
  const result = eyeling.reasonStream(input, {
2004
2006
  proof: false,
2005
- onDerived: ({ quad }) => {
2007
+ rdfjs: true,
2008
+ skipUnsupportedRdfJs: true,
2009
+ onDerived: ({ triple, quad }) => {
2006
2010
  if (quad) console.log(quad);
2011
+ else console.warn('Skipped non-RDF/JS derived triple:', triple);
2007
2012
  },
2008
2013
  });
2009
2014
  ```
@@ -2011,11 +2016,15 @@ const result = eyeling.reasonStream(input, {
2011
2016
  That same path also lets derived results be consumed as an async stream of RDF/JS quads:
2012
2017
 
2013
2018
  ```js
2014
- for await (const quad of eyeling.reasonRdfJs(input)) {
2019
+ for await (const quad of eyeling.reasonRdfJs(input, {
2020
+ skipUnsupportedRdfJs: true,
2021
+ })) {
2015
2022
  console.log(quad);
2016
2023
  }
2017
2024
  ```
2018
2025
 
2026
+ Use `skipUnsupportedRdfJs: true` when you want RDF/JS consumers to ignore derived triples that contain N3-only terms such as quoted formulas. This affects only RDF/JS export. The underlying Eyeling closure and `closureN3` output remain unchanged.
2027
+
2019
2028
  Use these entry points when you need one or more of the following:
2020
2029
 
2021
2030
  - RDF/JS quads as fact input
@@ -8679,6 +8679,23 @@ function forwardChainAndCollectLogQueryConclusions(
8679
8679
 
8680
8680
  // (proof printing + log:outputString moved to lib/explain.js)
8681
8681
 
8682
+ function isUnsupportedRdfJsConversionError(err) {
8683
+ return (
8684
+ err instanceof TypeError &&
8685
+ typeof err.message === 'string' &&
8686
+ err.message.startsWith('Cannot convert N3-only term ')
8687
+ );
8688
+ }
8689
+
8690
+ function maybeTripleToRdfJsQuad(triple, rdfFactory, skipUnsupportedRdfJs) {
8691
+ try {
8692
+ return internalTripleToRdfJsQuad(triple, rdfFactory);
8693
+ } catch (err) {
8694
+ if (skipUnsupportedRdfJs && isUnsupportedRdfJsConversionError(err)) return null;
8695
+ throw err;
8696
+ }
8697
+ }
8698
+
8682
8699
  function reasonStream(input, opts = {}) {
8683
8700
  const {
8684
8701
  baseIri = null,
@@ -8688,6 +8705,7 @@ function reasonStream(input, opts = {}) {
8688
8705
  enforceHttps = false,
8689
8706
  rdfjs = false,
8690
8707
  dataFactory = null,
8708
+ skipUnsupportedRdfJs = false,
8691
8709
  builtinModules = null,
8692
8710
  } = opts;
8693
8711
 
@@ -8750,7 +8768,10 @@ function reasonStream(input, opts = {}) {
8750
8768
  if (typeof onDerived === 'function') {
8751
8769
  for (const qdf of queryDerived) {
8752
8770
  const payload = { triple: tripleToN3(qdf.fact, prefixes), df: qdf };
8753
- if (rdfFactory) payload.quad = internalTripleToRdfJsQuad(qdf.fact, rdfFactory);
8771
+ if (rdfFactory) {
8772
+ const quad = maybeTripleToRdfJsQuad(qdf.fact, rdfFactory, skipUnsupportedRdfJs);
8773
+ if (quad) payload.quad = quad;
8774
+ }
8754
8775
  onDerived(payload);
8755
8776
  }
8756
8777
  }
@@ -8766,7 +8787,10 @@ function reasonStream(input, opts = {}) {
8766
8787
  triple: tripleToN3(df.fact, prefixes),
8767
8788
  df,
8768
8789
  };
8769
- if (rdfFactory) payload.quad = internalTripleToRdfJsQuad(df.fact, rdfFactory);
8790
+ if (rdfFactory) {
8791
+ const quad = maybeTripleToRdfJsQuad(df.fact, rdfFactory, skipUnsupportedRdfJs);
8792
+ if (quad) payload.quad = quad;
8793
+ }
8770
8794
  onDerived(payload);
8771
8795
  }
8772
8796
  },
@@ -8797,15 +8821,19 @@ function reasonStream(input, opts = {}) {
8797
8821
  };
8798
8822
 
8799
8823
  if (rdfFactory) {
8800
- __out.closureQuads = closureTriples.map((t) => internalTripleToRdfJsQuad(t, rdfFactory));
8801
- __out.queryQuads = queryTriples.map((t) => internalTripleToRdfJsQuad(t, rdfFactory));
8824
+ __out.closureQuads = closureTriples
8825
+ .map((t) => maybeTripleToRdfJsQuad(t, rdfFactory, skipUnsupportedRdfJs))
8826
+ .filter(Boolean);
8827
+ __out.queryQuads = queryTriples
8828
+ .map((t) => maybeTripleToRdfJsQuad(t, rdfFactory, skipUnsupportedRdfJs))
8829
+ .filter(Boolean);
8802
8830
  }
8803
8831
  deref.setEnforceHttpsEnabled(__oldEnforceHttps);
8804
8832
  return __out;
8805
8833
  }
8806
8834
 
8807
8835
  function reasonRdfJs(input, opts = {}) {
8808
- const { dataFactory = null, ...restOpts } = opts || {};
8836
+ const { dataFactory = null, skipUnsupportedRdfJs = false, ...restOpts } = opts || {};
8809
8837
  const rdfFactory = getDataFactory(dataFactory);
8810
8838
 
8811
8839
  const queue = [];
@@ -8829,8 +8857,11 @@ function reasonRdfJs(input, opts = {}) {
8829
8857
  ...restOpts,
8830
8858
  rdfjs: false,
8831
8859
  onDerived: ({ df }) => {
8832
- queue.push(internalTripleToRdfJsQuad(df.fact, rdfFactory));
8833
- flush();
8860
+ const quad = maybeTripleToRdfJsQuad(df.fact, rdfFactory, skipUnsupportedRdfJs);
8861
+ if (quad) {
8862
+ queue.push(quad);
8863
+ flush();
8864
+ }
8834
8865
  },
8835
8866
  });
8836
8867
  } catch (e) {
@@ -0,0 +1,374 @@
1
+ # =======================================================================
2
+ # matrix-mechanics.n3
3
+ #
4
+ # Purpose
5
+ # -------
6
+ # This is a small toy "matrix mechanics" example written in Eyeling-style
7
+ # Notation3. It models two 2x2 matrices:
8
+ #
9
+ # H = [[1,0],[0,2]]
10
+ # X = [[0,1],[1,0]]
11
+ #
12
+ # and derives:
13
+ # - trace(H)
14
+ # - det(H)
15
+ # - HX
16
+ # - XH
17
+ # - the commutator [H,X] = HX - XH
18
+ #
19
+ # What the example demonstrates
20
+ # -----------------------------
21
+ # 1. How to encode a small algebraic model as RDF-style facts.
22
+ # 2. How to derive new facts with N3 implication rules.
23
+ # 3. How to present conclusions with log:outputString.
24
+ # 4. How to add hard checks using `=> false`.
25
+ #
26
+ # Expected result
27
+ # ---------------
28
+ # - H has trace 3 and determinant 2.
29
+ # - X is an involution: X^2 = I.
30
+ # - [H,X] is non-zero, so H and X do not commute.
31
+ #
32
+ # Notes
33
+ # -----
34
+ # - This is a direct, concrete 2x2 example, not a general matrix library.
35
+ # - The structure follows the Eyeling handbook style:
36
+ # Facts -> Logic -> Presentation -> Checks
37
+ # - The output is organized in ARC form:
38
+ # Answer -> Reason Why -> Check
39
+ # =======================================================================
40
+
41
+ @prefix : <http://example.org/#> .
42
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
43
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
44
+ @prefix string: <http://www.w3.org/2000/10/swap/string#> .
45
+
46
+ # -----
47
+ # Facts
48
+ # -----
49
+
50
+ :H :a11 1; :a12 0; :a21 0; :a22 2 .
51
+ :X :a11 0; :a12 1; :a21 1; :a22 0 .
52
+
53
+ # -----
54
+ # Logic
55
+ # -----
56
+
57
+ # trace(H) = H11 + H22
58
+ {
59
+ :H :a11 ?h11; :a22 ?h22 .
60
+ (?h11 ?h22) math:sum ?tr .
61
+ }
62
+ =>
63
+ {
64
+ :H :trace ?tr .
65
+ } .
66
+
67
+ # det(H) = H11*H22 - H12*H21
68
+ {
69
+ :H :a11 ?h11; :a12 ?h12; :a21 ?h21; :a22 ?h22 .
70
+ (?h11 ?h22) math:product ?p1 .
71
+ (?h12 ?h21) math:product ?p2 .
72
+ (?p1 ?p2) math:difference ?det .
73
+ }
74
+ =>
75
+ {
76
+ :H :det ?det .
77
+ } .
78
+
79
+ # HX = H * X
80
+ {
81
+ :H :a11 ?h11; :a12 ?h12; :a21 ?h21; :a22 ?h22 .
82
+ :X :a11 ?x11; :a12 ?x12; :a21 ?x21; :a22 ?x22 .
83
+
84
+ (?h11 ?x11) math:product ?hx11a .
85
+ (?h12 ?x21) math:product ?hx11b .
86
+ (?hx11a ?hx11b) math:sum ?hx11 .
87
+
88
+ (?h11 ?x12) math:product ?hx12a .
89
+ (?h12 ?x22) math:product ?hx12b .
90
+ (?hx12a ?hx12b) math:sum ?hx12 .
91
+
92
+ (?h21 ?x11) math:product ?hx21a .
93
+ (?h22 ?x21) math:product ?hx21b .
94
+ (?hx21a ?hx21b) math:sum ?hx21 .
95
+
96
+ (?h21 ?x12) math:product ?hx22a .
97
+ (?h22 ?x22) math:product ?hx22b .
98
+ (?hx22a ?hx22b) math:sum ?hx22 .
99
+ }
100
+ =>
101
+ {
102
+ :HX :a11 ?hx11; :a12 ?hx12; :a21 ?hx21; :a22 ?hx22 .
103
+ } .
104
+
105
+ # XH = X * H
106
+ {
107
+ :X :a11 ?x11; :a12 ?x12; :a21 ?x21; :a22 ?x22 .
108
+ :H :a11 ?h11; :a12 ?h12; :a21 ?h21; :a22 ?h22 .
109
+
110
+ (?x11 ?h11) math:product ?xh11a .
111
+ (?x12 ?h21) math:product ?xh11b .
112
+ (?xh11a ?xh11b) math:sum ?xh11 .
113
+
114
+ (?x11 ?h12) math:product ?xh12a .
115
+ (?x12 ?h22) math:product ?xh12b .
116
+ (?xh12a ?xh12b) math:sum ?xh12 .
117
+
118
+ (?x21 ?h11) math:product ?xh21a .
119
+ (?x22 ?h21) math:product ?xh21b .
120
+ (?xh21a ?xh21b) math:sum ?xh21 .
121
+
122
+ (?x21 ?h12) math:product ?xh22a .
123
+ (?x22 ?h22) math:product ?xh22b .
124
+ (?xh22a ?xh22b) math:sum ?xh22 .
125
+ }
126
+ =>
127
+ {
128
+ :XH :a11 ?xh11; :a12 ?xh12; :a21 ?xh21; :a22 ?xh22 .
129
+ } .
130
+
131
+ # XX = X * X
132
+ {
133
+ :X :a11 ?x11; :a12 ?x12; :a21 ?x21; :a22 ?x22 .
134
+
135
+ (?x11 ?x11) math:product ?xx11a .
136
+ (?x12 ?x21) math:product ?xx11b .
137
+ (?xx11a ?xx11b) math:sum ?xx11 .
138
+
139
+ (?x11 ?x12) math:product ?xx12a .
140
+ (?x12 ?x22) math:product ?xx12b .
141
+ (?xx12a ?xx12b) math:sum ?xx12 .
142
+
143
+ (?x21 ?x11) math:product ?xx21a .
144
+ (?x22 ?x21) math:product ?xx21b .
145
+ (?xx21a ?xx21b) math:sum ?xx21 .
146
+
147
+ (?x21 ?x12) math:product ?xx22a .
148
+ (?x22 ?x22) math:product ?xx22b .
149
+ (?xx22a ?xx22b) math:sum ?xx22 .
150
+ }
151
+ =>
152
+ {
153
+ :XX :a11 ?xx11; :a12 ?xx12; :a21 ?xx21; :a22 ?xx22 .
154
+ } .
155
+
156
+ # C = [H,X] = HX - XH
157
+ {
158
+ :HX :a11 ?hx11; :a12 ?hx12; :a21 ?hx21; :a22 ?hx22 .
159
+ :XH :a11 ?xh11; :a12 ?xh12; :a21 ?xh21; :a22 ?xh22 .
160
+
161
+ (?hx11 ?xh11) math:difference ?c11 .
162
+ (?hx12 ?xh12) math:difference ?c12 .
163
+ (?hx21 ?xh21) math:difference ?c21 .
164
+ (?hx22 ?xh22) math:difference ?c22 .
165
+ }
166
+ =>
167
+ {
168
+ :C :a11 ?c11; :a12 ?c12; :a21 ?c21; :a22 ?c22 .
169
+ } .
170
+
171
+ # Summary facts
172
+ {
173
+ :H :trace 3; :det 2 .
174
+ }
175
+ =>
176
+ {
177
+ :H :spectrumOk "yes" .
178
+ } .
179
+
180
+ {
181
+ :XX :a11 1; :a12 0; :a21 0; :a22 1 .
182
+ }
183
+ =>
184
+ {
185
+ :X :involution "yes" .
186
+ } .
187
+
188
+ {
189
+ :C :a11 ?v .
190
+ ?v math:notEqualTo 0 .
191
+ }
192
+ =>
193
+ {
194
+ :C :nonzero "yes" .
195
+ } .
196
+
197
+ {
198
+ :C :a12 ?v .
199
+ ?v math:notEqualTo 0 .
200
+ }
201
+ =>
202
+ {
203
+ :C :nonzero "yes" .
204
+ } .
205
+
206
+ {
207
+ :C :a21 ?v .
208
+ ?v math:notEqualTo 0 .
209
+ }
210
+ =>
211
+ {
212
+ :C :nonzero "yes" .
213
+ } .
214
+
215
+ {
216
+ :C :a22 ?v .
217
+ ?v math:notEqualTo 0 .
218
+ }
219
+ =>
220
+ {
221
+ :C :nonzero "yes" .
222
+ } .
223
+
224
+ {
225
+ :H :spectrumOk "yes" .
226
+ :X :involution "yes" .
227
+ :C :nonzero "yes" .
228
+ }
229
+ =>
230
+ {
231
+ :model :ok "yes" ;
232
+ :answer "In this toy matrix-mechanics model, the Hamiltonian has two discrete energy levels and does not commute with a second observable." .
233
+ } .
234
+
235
+ # -----------------------------------------------
236
+ # Presentation (ARC: Answer • Reason Why • Check)
237
+ # -----------------------------------------------
238
+
239
+ {
240
+ :model :answer ?txt .
241
+ }
242
+ =>
243
+ {
244
+ :s01 log:outputString "=== Answer ===\n" .
245
+ :s02 log:outputString ?txt .
246
+ :s03 log:outputString "\n\n=== Reason Why ===\n" .
247
+ } .
248
+
249
+ {
250
+ :H :a11 ?a11; :a12 ?a12; :a21 ?a21; :a22 ?a22 .
251
+ ("H = [[" ?a11 "," ?a12 "],[" ?a21 "," ?a22 "]]\n")
252
+ string:concatenation ?line .
253
+ }
254
+ =>
255
+ {
256
+ :s04 log:outputString ?line .
257
+ } .
258
+
259
+ {
260
+ :X :a11 ?a11; :a12 ?a12; :a21 ?a21; :a22 ?a22 .
261
+ ("X = [[" ?a11 "," ?a12 "],[" ?a21 "," ?a22 "]]\n")
262
+ string:concatenation ?line .
263
+ }
264
+ =>
265
+ {
266
+ :s05 log:outputString ?line .
267
+ } .
268
+
269
+ {
270
+ :HX :a11 ?a11; :a12 ?a12; :a21 ?a21; :a22 ?a22 .
271
+ ("HX = [[" ?a11 "," ?a12 "],[" ?a21 "," ?a22 "]]\n")
272
+ string:concatenation ?line .
273
+ }
274
+ =>
275
+ {
276
+ :s06 log:outputString ?line .
277
+ } .
278
+
279
+ {
280
+ :XH :a11 ?a11; :a12 ?a12; :a21 ?a21; :a22 ?a22 .
281
+ ("XH = [[" ?a11 "," ?a12 "],[" ?a21 "," ?a22 "]]\n")
282
+ string:concatenation ?line .
283
+ }
284
+ =>
285
+ {
286
+ :s07 log:outputString ?line .
287
+ } .
288
+
289
+ {
290
+ :C :a11 ?a11; :a12 ?a12; :a21 ?a21; :a22 ?a22 .
291
+ ("[H,X] = [[" ?a11 "," ?a12 "],[" ?a21 "," ?a22 "]]\n")
292
+ string:concatenation ?line .
293
+ }
294
+ =>
295
+ {
296
+ :s08 log:outputString ?line .
297
+ } .
298
+
299
+ {
300
+ :model :ok "yes" .
301
+ }
302
+ =>
303
+ {
304
+ :s09 log:outputString "\n=== Check ===\n" .
305
+ } .
306
+
307
+ {
308
+ :H :spectrumOk "yes" .
309
+ }
310
+ =>
311
+ {
312
+ :s10 log:outputString "trace/determinant match energy levels: yes\n" .
313
+ } .
314
+
315
+ {
316
+ :X :involution "yes" .
317
+ }
318
+ =>
319
+ {
320
+ :s11 log:outputString "X^2 = I : yes\n" .
321
+ } .
322
+
323
+ {
324
+ :C :nonzero "yes" .
325
+ }
326
+ =>
327
+ {
328
+ :s12 log:outputString "[H,X] != 0 : yes\n" .
329
+ } .
330
+
331
+ # ------------------------------------------
332
+ # Checks (fail hard if the algebra is wrong)
333
+ # ------------------------------------------
334
+
335
+ {
336
+ :H :trace ?tr .
337
+ ?tr math:notEqualTo 3 .
338
+ }
339
+ => false .
340
+
341
+ {
342
+ :H :det ?det .
343
+ ?det math:notEqualTo 2 .
344
+ }
345
+ => false .
346
+
347
+ {
348
+ :XX :a11 ?v .
349
+ ?v math:notEqualTo 1 .
350
+ }
351
+ => false .
352
+
353
+ {
354
+ :XX :a12 ?v .
355
+ ?v math:notEqualTo 0 .
356
+ }
357
+ => false .
358
+
359
+ {
360
+ :XX :a21 ?v .
361
+ ?v math:notEqualTo 0 .
362
+ }
363
+ => false .
364
+
365
+ {
366
+ :XX :a22 ?v .
367
+ ?v math:notEqualTo 1 .
368
+ }
369
+ => false .
370
+
371
+ {
372
+ :C :a11 0; :a12 0; :a21 0; :a22 0 .
373
+ }
374
+ => false .
@@ -0,0 +1,14 @@
1
+ === Answer ===
2
+ In this toy matrix-mechanics model, the Hamiltonian has two discrete energy levels and does not commute with a second observable.
3
+
4
+ === Reason Why ===
5
+ H = [[1,0],[0,2]]
6
+ X = [[0,1],[1,0]]
7
+ HX = [[0,1],[2,0]]
8
+ XH = [[0,2],[1,0]]
9
+ [H,X] = [[0,-1],[1,0]]
10
+
11
+ === Check ===
12
+ trace/determinant match energy levels: yes
13
+ X^2 = I : yes
14
+ [H,X] != 0 : yes
package/eyeling.js CHANGED
@@ -8679,6 +8679,23 @@ function forwardChainAndCollectLogQueryConclusions(
8679
8679
 
8680
8680
  // (proof printing + log:outputString moved to lib/explain.js)
8681
8681
 
8682
+ function isUnsupportedRdfJsConversionError(err) {
8683
+ return (
8684
+ err instanceof TypeError &&
8685
+ typeof err.message === 'string' &&
8686
+ err.message.startsWith('Cannot convert N3-only term ')
8687
+ );
8688
+ }
8689
+
8690
+ function maybeTripleToRdfJsQuad(triple, rdfFactory, skipUnsupportedRdfJs) {
8691
+ try {
8692
+ return internalTripleToRdfJsQuad(triple, rdfFactory);
8693
+ } catch (err) {
8694
+ if (skipUnsupportedRdfJs && isUnsupportedRdfJsConversionError(err)) return null;
8695
+ throw err;
8696
+ }
8697
+ }
8698
+
8682
8699
  function reasonStream(input, opts = {}) {
8683
8700
  const {
8684
8701
  baseIri = null,
@@ -8688,6 +8705,7 @@ function reasonStream(input, opts = {}) {
8688
8705
  enforceHttps = false,
8689
8706
  rdfjs = false,
8690
8707
  dataFactory = null,
8708
+ skipUnsupportedRdfJs = false,
8691
8709
  builtinModules = null,
8692
8710
  } = opts;
8693
8711
 
@@ -8750,7 +8768,10 @@ function reasonStream(input, opts = {}) {
8750
8768
  if (typeof onDerived === 'function') {
8751
8769
  for (const qdf of queryDerived) {
8752
8770
  const payload = { triple: tripleToN3(qdf.fact, prefixes), df: qdf };
8753
- if (rdfFactory) payload.quad = internalTripleToRdfJsQuad(qdf.fact, rdfFactory);
8771
+ if (rdfFactory) {
8772
+ const quad = maybeTripleToRdfJsQuad(qdf.fact, rdfFactory, skipUnsupportedRdfJs);
8773
+ if (quad) payload.quad = quad;
8774
+ }
8754
8775
  onDerived(payload);
8755
8776
  }
8756
8777
  }
@@ -8766,7 +8787,10 @@ function reasonStream(input, opts = {}) {
8766
8787
  triple: tripleToN3(df.fact, prefixes),
8767
8788
  df,
8768
8789
  };
8769
- if (rdfFactory) payload.quad = internalTripleToRdfJsQuad(df.fact, rdfFactory);
8790
+ if (rdfFactory) {
8791
+ const quad = maybeTripleToRdfJsQuad(df.fact, rdfFactory, skipUnsupportedRdfJs);
8792
+ if (quad) payload.quad = quad;
8793
+ }
8770
8794
  onDerived(payload);
8771
8795
  }
8772
8796
  },
@@ -8797,15 +8821,19 @@ function reasonStream(input, opts = {}) {
8797
8821
  };
8798
8822
 
8799
8823
  if (rdfFactory) {
8800
- __out.closureQuads = closureTriples.map((t) => internalTripleToRdfJsQuad(t, rdfFactory));
8801
- __out.queryQuads = queryTriples.map((t) => internalTripleToRdfJsQuad(t, rdfFactory));
8824
+ __out.closureQuads = closureTriples
8825
+ .map((t) => maybeTripleToRdfJsQuad(t, rdfFactory, skipUnsupportedRdfJs))
8826
+ .filter(Boolean);
8827
+ __out.queryQuads = queryTriples
8828
+ .map((t) => maybeTripleToRdfJsQuad(t, rdfFactory, skipUnsupportedRdfJs))
8829
+ .filter(Boolean);
8802
8830
  }
8803
8831
  deref.setEnforceHttpsEnabled(__oldEnforceHttps);
8804
8832
  return __out;
8805
8833
  }
8806
8834
 
8807
8835
  function reasonRdfJs(input, opts = {}) {
8808
- const { dataFactory = null, ...restOpts } = opts || {};
8836
+ const { dataFactory = null, skipUnsupportedRdfJs = false, ...restOpts } = opts || {};
8809
8837
  const rdfFactory = getDataFactory(dataFactory);
8810
8838
 
8811
8839
  const queue = [];
@@ -8829,8 +8857,11 @@ function reasonRdfJs(input, opts = {}) {
8829
8857
  ...restOpts,
8830
8858
  rdfjs: false,
8831
8859
  onDerived: ({ df }) => {
8832
- queue.push(internalTripleToRdfJsQuad(df.fact, rdfFactory));
8833
- flush();
8860
+ const quad = maybeTripleToRdfJsQuad(df.fact, rdfFactory, skipUnsupportedRdfJs);
8861
+ if (quad) {
8862
+ queue.push(quad);
8863
+ flush();
8864
+ }
8834
8865
  },
8835
8866
  });
8836
8867
  } catch (e) {
package/index.d.ts CHANGED
@@ -172,6 +172,7 @@ declare module 'eyeling' {
172
172
  enforceHttps?: boolean;
173
173
  rdfjs?: boolean;
174
174
  dataFactory?: RdfJsDataFactory | null;
175
+ skipUnsupportedRdfJs?: boolean;
175
176
  builtinModules?: string | string[];
176
177
  onDerived?: (item: { triple: string; quad?: RdfJsQuad; df: any }) => void;
177
178
  }
package/lib/engine.js CHANGED
@@ -3198,6 +3198,23 @@ function forwardChainAndCollectLogQueryConclusions(
3198
3198
 
3199
3199
  // (proof printing + log:outputString moved to lib/explain.js)
3200
3200
 
3201
+ function isUnsupportedRdfJsConversionError(err) {
3202
+ return (
3203
+ err instanceof TypeError &&
3204
+ typeof err.message === 'string' &&
3205
+ err.message.startsWith('Cannot convert N3-only term ')
3206
+ );
3207
+ }
3208
+
3209
+ function maybeTripleToRdfJsQuad(triple, rdfFactory, skipUnsupportedRdfJs) {
3210
+ try {
3211
+ return internalTripleToRdfJsQuad(triple, rdfFactory);
3212
+ } catch (err) {
3213
+ if (skipUnsupportedRdfJs && isUnsupportedRdfJsConversionError(err)) return null;
3214
+ throw err;
3215
+ }
3216
+ }
3217
+
3201
3218
  function reasonStream(input, opts = {}) {
3202
3219
  const {
3203
3220
  baseIri = null,
@@ -3207,6 +3224,7 @@ function reasonStream(input, opts = {}) {
3207
3224
  enforceHttps = false,
3208
3225
  rdfjs = false,
3209
3226
  dataFactory = null,
3227
+ skipUnsupportedRdfJs = false,
3210
3228
  builtinModules = null,
3211
3229
  } = opts;
3212
3230
 
@@ -3269,7 +3287,10 @@ function reasonStream(input, opts = {}) {
3269
3287
  if (typeof onDerived === 'function') {
3270
3288
  for (const qdf of queryDerived) {
3271
3289
  const payload = { triple: tripleToN3(qdf.fact, prefixes), df: qdf };
3272
- if (rdfFactory) payload.quad = internalTripleToRdfJsQuad(qdf.fact, rdfFactory);
3290
+ if (rdfFactory) {
3291
+ const quad = maybeTripleToRdfJsQuad(qdf.fact, rdfFactory, skipUnsupportedRdfJs);
3292
+ if (quad) payload.quad = quad;
3293
+ }
3273
3294
  onDerived(payload);
3274
3295
  }
3275
3296
  }
@@ -3285,7 +3306,10 @@ function reasonStream(input, opts = {}) {
3285
3306
  triple: tripleToN3(df.fact, prefixes),
3286
3307
  df,
3287
3308
  };
3288
- if (rdfFactory) payload.quad = internalTripleToRdfJsQuad(df.fact, rdfFactory);
3309
+ if (rdfFactory) {
3310
+ const quad = maybeTripleToRdfJsQuad(df.fact, rdfFactory, skipUnsupportedRdfJs);
3311
+ if (quad) payload.quad = quad;
3312
+ }
3289
3313
  onDerived(payload);
3290
3314
  }
3291
3315
  },
@@ -3316,15 +3340,19 @@ function reasonStream(input, opts = {}) {
3316
3340
  };
3317
3341
 
3318
3342
  if (rdfFactory) {
3319
- __out.closureQuads = closureTriples.map((t) => internalTripleToRdfJsQuad(t, rdfFactory));
3320
- __out.queryQuads = queryTriples.map((t) => internalTripleToRdfJsQuad(t, rdfFactory));
3343
+ __out.closureQuads = closureTriples
3344
+ .map((t) => maybeTripleToRdfJsQuad(t, rdfFactory, skipUnsupportedRdfJs))
3345
+ .filter(Boolean);
3346
+ __out.queryQuads = queryTriples
3347
+ .map((t) => maybeTripleToRdfJsQuad(t, rdfFactory, skipUnsupportedRdfJs))
3348
+ .filter(Boolean);
3321
3349
  }
3322
3350
  deref.setEnforceHttpsEnabled(__oldEnforceHttps);
3323
3351
  return __out;
3324
3352
  }
3325
3353
 
3326
3354
  function reasonRdfJs(input, opts = {}) {
3327
- const { dataFactory = null, ...restOpts } = opts || {};
3355
+ const { dataFactory = null, skipUnsupportedRdfJs = false, ...restOpts } = opts || {};
3328
3356
  const rdfFactory = getDataFactory(dataFactory);
3329
3357
 
3330
3358
  const queue = [];
@@ -3348,8 +3376,11 @@ function reasonRdfJs(input, opts = {}) {
3348
3376
  ...restOpts,
3349
3377
  rdfjs: false,
3350
3378
  onDerived: ({ df }) => {
3351
- queue.push(internalTripleToRdfJsQuad(df.fact, rdfFactory));
3352
- flush();
3379
+ const quad = maybeTripleToRdfJsQuad(df.fact, rdfFactory, skipUnsupportedRdfJs);
3380
+ if (quad) {
3381
+ queue.push(quad);
3382
+ flush();
3383
+ }
3353
3384
  },
3354
3385
  });
3355
3386
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
package/test/api.test.js CHANGED
@@ -1706,6 +1706,60 @@ _:x :hates { _:foo :making :mess }.
1706
1706
  assert.equal(tc.quads[0].graph.termType, 'DefaultGraph');
1707
1707
  },
1708
1708
  },
1709
+ {
1710
+ name: '63a RDF/JS export: reasonRdfJs can skip N3-only derived triples',
1711
+ async run() {
1712
+ const ex = 'http://example.org/';
1713
+ const input = `@prefix : <${ex}>.
1714
+ :a :p :b.
1715
+ { :a :p :b. } => { :x :holds { :a :p :b. }. :x :ok :yes. }.`;
1716
+ const quads = [];
1717
+ for await (const quad of reasonRdfJs(input, { skipUnsupportedRdfJs: true })) {
1718
+ quads.push(quad);
1719
+ }
1720
+ this.quads = quads;
1721
+ return quads.map((q) => `${q.subject.value} ${q.predicate.value} ${q.object.value}`).join('\n');
1722
+ },
1723
+ expect: [/http:\/\/example\.org\/ok/],
1724
+ notExpect: [/http:\/\/example\.org\/holds/],
1725
+ check(outputIgnored, tc) {
1726
+ assert.equal(tc.quads.length, 1, 'Expected one yielded RDF/JS quad after skipping GraphTerm output');
1727
+ assert.equal(tc.quads[0].predicate.value, 'http://example.org/ok');
1728
+ assert.equal(tc.quads[0].object.value, 'http://example.org/yes');
1729
+ },
1730
+ },
1731
+ {
1732
+ name: '63b RDF/JS export: reasonStream keeps N3 closure while omitting unsupported closureQuads',
1733
+ run() {
1734
+ const ex = 'http://example.org/';
1735
+ const input = `@prefix : <${ex}>.
1736
+ :a :p :b.
1737
+ { :a :p :b. } => { :x :holds { :a :p :b. }. :x :ok :yes. }.`;
1738
+ const seen = [];
1739
+ const result = reasonStream(input, {
1740
+ rdfjs: true,
1741
+ skipUnsupportedRdfJs: true,
1742
+ includeInputFactsInClosure: false,
1743
+ onDerived: ({ triple, quad }) => seen.push({ triple, quad }),
1744
+ });
1745
+ this.seen = seen;
1746
+ this.result = result;
1747
+ return result.closureN3;
1748
+ },
1749
+ expect: [/:holds/, /:ok/],
1750
+ check(outputIgnored, tc) {
1751
+ assert.equal(tc.seen.length, 2, 'Expected both derived facts to reach onDerived');
1752
+ assert.equal(tc.seen.filter((x) => x.quad).length, 1, 'Expected only one RDF/JS quad in onDerived');
1753
+ assert.ok(Array.isArray(tc.result.closureQuads), 'Expected closureQuads array');
1754
+ assert.equal(
1755
+ tc.result.closureQuads.length,
1756
+ 1,
1757
+ 'Expected unsupported GraphTerm triple to be omitted from closureQuads',
1758
+ );
1759
+ assert.equal(tc.result.closureQuads[0].predicate.value, 'http://example.org/ok');
1760
+ assert.match(tc.result.closureN3, /:holds/, 'Expected N3 closure to retain quoted-formula triple');
1761
+ },
1762
+ },
1709
1763
  {
1710
1764
  name: '64 RDF/JS validation: named-graph input quads are rejected clearly',
1711
1765
  expectError: true,
File without changes
File without changes
File without changes
File without changes