eyeling 1.10.21 → 1.11.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 (47) hide show
  1. package/HANDBOOK.md +2 -88
  2. package/eyeling-builtins.ttl +0 -48
  3. package/eyeling.js +67 -314
  4. package/lib/engine.js +67 -309
  5. package/lib/rules.js +0 -5
  6. package/package.json +1 -1
  7. package/test/n3gen.test.js +4 -4
  8. package/test/package.test.js +1 -1
  9. package/tools/n3gen.js +6 -1883
  10. package/examples/bind-builtins.n3 +0 -11
  11. package/examples/bind.n3 +0 -7
  12. package/examples/brussels-brew-club.n3 +0 -119
  13. package/examples/builtins-string-math.n3 +0 -11
  14. package/examples/builtins-triple-termtests.n3 +0 -7
  15. package/examples/family.n3 +0 -10
  16. package/examples/filter-demorgan.n3 +0 -9
  17. package/examples/filter-in-notin.n3 +0 -10
  18. package/examples/filter-nested-or.n3 +0 -10
  19. package/examples/filter.n3 +0 -8
  20. package/examples/input/bind-builtins.srl +0 -30
  21. package/examples/input/bind.srl +0 -12
  22. package/examples/input/builtins-string-math.srl +0 -38
  23. package/examples/input/builtins-triple-termtests.srl +0 -27
  24. package/examples/input/family.srl +0 -12
  25. package/examples/input/filter-demorgan.srl +0 -15
  26. package/examples/input/filter-in-notin.srl +0 -15
  27. package/examples/input/filter-nested-or.srl +0 -15
  28. package/examples/input/filter.srl +0 -9
  29. package/examples/input/snaf.srl +0 -6
  30. package/examples/it-is-about-time.n3 +0 -580
  31. package/examples/json-pointer.n3 +0 -75
  32. package/examples/json-reconcile-vat.n3 +0 -361
  33. package/examples/output/bind-builtins.n3 +0 -9
  34. package/examples/output/bind.n3 +0 -3
  35. package/examples/output/brussels-brew-club.n3 +0 -22
  36. package/examples/output/builtins-string-math.n3 +0 -0
  37. package/examples/output/builtins-triple-termtests.n3 +0 -0
  38. package/examples/output/family.n3 +0 -13
  39. package/examples/output/filter-demorgan.n3 +0 -3
  40. package/examples/output/filter-in-notin.n3 +0 -4
  41. package/examples/output/filter-nested-or.n3 +0 -4
  42. package/examples/output/filter.n3 +0 -3
  43. package/examples/output/it-is-about-time.n3 +0 -0
  44. package/examples/output/json-pointer.n3 +0 -13
  45. package/examples/output/json-reconcile-vat.n3 +0 -226
  46. package/examples/output/snaf.n3 +0 -3
  47. package/examples/snaf.n3 +0 -6
@@ -1,361 +0,0 @@
1
- # =================================================
2
- # Rconcile VAT JSON Pointer example
3
- # See https://datatracker.ietf.org/doc/html/rfc6901
4
- # =================================================
5
-
6
- @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
7
- @prefix string: <http://www.w3.org/2000/10/swap/string#> .
8
- @prefix list: <http://www.w3.org/2000/10/swap/list#> .
9
- @prefix math: <http://www.w3.org/2000/10/swap/math#> .
10
- @prefix log: <http://www.w3.org/2000/10/swap/log#> .
11
- @prefix owl: <http://www.w3.org/2002/07/owl#> .
12
- @prefix ex: <http://example.org/#> .
13
-
14
- # ------------------------------------------------------------------
15
- # Master data (canonical N3 graph)
16
- # ------------------------------------------------------------------
17
-
18
- ex:policy ex:euCountries ("BE" "NL" "DE" "FR" "LU") .
19
-
20
- ex:Widget a ex:Product ;
21
- ex:sku "WID-001" ;
22
- ex:listPriceCents 999 ;
23
- ex:vatBps 2100 .
24
-
25
- ex:Gadget a ex:Product ;
26
- ex:sku "GAD-002" ;
27
- ex:listPriceCents 1500 ;
28
- ex:vatBps 600 .
29
-
30
- # Registry customer keyed by normalized VAT key (alnum only)
31
- ex:RegistryNL
32
- ex:vatKey "NL999999999B01" ;
33
- ex:legalName "Example BV" .
34
-
35
- # ------------------------------------------------------------------
36
- # JSON feed (operational system)
37
- # ------------------------------------------------------------------
38
-
39
- ex:feed ex:json """{
40
- "meta~data": { "batch": "2025-12-18" },
41
- "orders": [
42
- {
43
- "orderId": "A100",
44
- "shipTo": { "country": "BE" },
45
- "customer": { "email": "Ada@Example.ORG", "vatId": null },
46
- "lines": [
47
- { "sku": "wid-001", "qty": 2, "unitCents": 999 },
48
- { "sku": "GAD-002", "qty": 1, "unitCents": 1500 }
49
- ],
50
- "declared": { "netCents": 3498, "vatCents": 509, "grossCents": 4007 }
51
- },
52
- {
53
- "orderId": "A200",
54
- "shipTo": { "country": "US" },
55
- "customer": { "email": "bob@example.org", "vatId": null },
56
- "lines": [
57
- { "sku": "WID-001", "qty": 2, "unitCents": 999 },
58
- { "sku": "GAD-002", "qty": 1, "unitCents": 1500 }
59
- ],
60
- "declared": { "netCents": 3498, "vatCents": 0, "grossCents": 3498 }
61
- },
62
- {
63
- "orderId": "A300",
64
- "shipTo": { "country": "NL" },
65
- "customer": { "email": "acct@company.nl", "vatId": "NL999999999B01" },
66
- "lines": [
67
- { "sku": "WID-001", "qty": 2, "unitCents": 999 },
68
- { "sku": "GAD-002", "qty": 1, "unitCents": 1500 }
69
- ],
70
- "declared": { "netCents": 3498, "vatCents": 0, "grossCents": 3498 }
71
- },
72
- {
73
- "orderId": "A301",
74
- "shipTo": { "country": "NL" },
75
- "customer": { "email": "billing@company.nl", "vatId": "NL999999999B01" },
76
- "lines": [
77
- { "sku": "WID-001", "qty": 2, "unitCents": 999 },
78
- { "sku": "GAD-002", "qty": 1, "unitCents": 1500 }
79
- ],
80
- "declared": { "netCents": 3498, "vatCents": 0, "grossCents": 3498 }
81
- },
82
- {
83
- "orderId": "A400",
84
- "shipTo": { "country": "BE" },
85
- "customer": { "email": "ada@other.example", "vatId": null },
86
- "lines": [
87
- { "sku": "WID-001", "qty": 2, "unitCents": 999 },
88
- { "sku": "GAD-002", "qty": 1, "unitCents": 1500 }
89
- ],
90
- "declared": { "netCents": 3498, "vatCents": 508, "grossCents": 4006 }
91
- }
92
- ]
93
- }"""^^rdf:JSON .
94
-
95
- # ------------------------------------------------------------------
96
- # Sanity checks for JSON Pointer edge-cases
97
- # ------------------------------------------------------------------
98
-
99
- {
100
- ex:feed ex:json ?J .
101
- (?J "#/orders/0/shipTo/country") string:jsonPointer "BE" .
102
- } => {
103
- ex:checks ex:fragmentPointerWorks true .
104
- } .
105
-
106
- {
107
- ex:feed ex:json ?J .
108
- (?J "/meta~0data/batch") string:jsonPointer "2025-12-18" .
109
- } => {
110
- ex:checks ex:tildeEscapeWorks true .
111
- } .
112
-
113
- # ------------------------------------------------------------------
114
- # 1) Materialize orders (JSON -> RDF-ish facts)
115
- # ------------------------------------------------------------------
116
-
117
- {
118
- ex:feed ex:json ?J .
119
- (?J "/orders") string:jsonPointer ?Orders .
120
- ?Orders list:iterate (?OrderIndex ?OrderJson) .
121
-
122
- (?OrderJson "/orderId") string:jsonPointer ?OrderId .
123
- (?OrderJson "/shipTo/country") string:jsonPointer ?Country .
124
-
125
- (?OrderJson "/customer") string:jsonPointer ?CustJson .
126
- (?CustJson "/email") string:jsonPointer ?Email .
127
- (?CustJson "/vatId") string:jsonPointer ?VatIdRaw .
128
-
129
- (?OrderJson "/declared") string:jsonPointer ?Decl .
130
- (?Decl "/netCents") string:jsonPointer ?NetD .
131
- (?Decl "/vatCents") string:jsonPointer ?VatD .
132
- (?Decl "/grossCents") string:jsonPointer ?GrossD .
133
-
134
- ("urn:example:order:%s" ?OrderId) string:format ?OrderUriStr .
135
- ?Order log:uri ?OrderUriStr .
136
- } => {
137
- ?Order a ex:Order ;
138
- ex:orderId ?OrderId ;
139
- ex:orderIndex ?OrderIndex ;
140
- ex:shipCountry ?Country ;
141
- ex:email ?Email ;
142
- ex:vatIdRaw ?VatIdRaw ;
143
- ex:declaredNetCents ?NetD ;
144
- ex:declaredVatCents ?VatD ;
145
- ex:declaredGrossCents ?GrossD .
146
- } .
147
-
148
- # ------------------------------------------------------------------
149
- # 2) Materialize customers
150
- # - If vatId is present -> customer IRI from normalized VAT key
151
- # - Else -> customer IRI is log:skolem(email)
152
- # ------------------------------------------------------------------
153
-
154
- # VAT customer
155
- {
156
- ?Order a ex:Order ; ex:vatIdRaw ?VatIdRaw ; ex:email ?Email .
157
- ?VatIdRaw string:notEqualIgnoringCase "null" .
158
- (?VatIdRaw "[^0-9A-Za-z]" "") string:replace ?VatKey .
159
-
160
- ("urn:example:customer:vat:%s" ?VatKey) string:format ?CustUriStr .
161
- ?Cust log:uri ?CustUriStr .
162
- } => {
163
- ?Cust a ex:Customer ;
164
- ex:vatKey ?VatKey ;
165
- ex:vatIdRaw ?VatIdRaw ;
166
- ex:email ?Email .
167
- ?Order ex:customer ?Cust .
168
- } .
169
-
170
- # Non-VAT customer
171
- {
172
- ?Order a ex:Order ; ex:vatIdRaw "null" ; ex:email ?Email .
173
- ?Email log:skolem ?Cust .
174
- } => {
175
- ?Cust a ex:Customer ;
176
- ex:vatIdRaw "null" ;
177
- ex:email ?Email .
178
- ?Order ex:customer ?Cust .
179
- } .
180
-
181
- # Registry reconciliation (VAT customers only)
182
- {
183
- ?Cust a ex:Customer ; ex:vatKey ?VatKey .
184
- ?Reg ex:vatKey ?VatKey .
185
- } => {
186
- ?Cust owl:sameAs ?Reg .
187
- } .
188
-
189
- # Detect conflicting emails for the SAME VAT-keyed customer
190
- # (Use string:lessThan to avoid duplicate symmetric pairs.)
191
- {
192
- ?Cust a ex:Customer ; ex:vatKey ?VatKey ; ex:email ?E1 ; ex:email ?E2 .
193
- ?E1 string:lessThan ?E2 .
194
- ?E1 string:notEqualIgnoringCase ?E2 .
195
- } => {
196
- ?Cust a ex:CustomerEmailConflict ;
197
- ex:conflictingEmail ?E1, ?E2 .
198
- } .
199
-
200
- # ------------------------------------------------------------------
201
- # 3) Materialize order lines + map SKU -> canonical product + compute money
202
- # VAT logic:
203
- # - ship outside EU -> VAT = 0 (export)
204
- # - ship in EU and customer has VAT -> VAT = 0 (reverse charge)
205
- # - ship in EU and customer has no VAT -> VAT = product vatBps
206
- # ------------------------------------------------------------------
207
-
208
- # Helper macro: create line IRI + compute netCents
209
- {
210
- ex:feed ex:json ?J .
211
- (?J "/orders") string:jsonPointer ?Orders .
212
- ?Orders list:iterate (?OrderIndex ?OrderJson) .
213
-
214
- (?OrderJson "/orderId") string:jsonPointer ?OrderId .
215
- ("urn:example:order:%s" ?OrderId) string:format ?OrderUriStr .
216
- ?Order log:uri ?OrderUriStr .
217
-
218
- (?OrderJson "/lines") string:jsonPointer ?Lines .
219
- ?Lines list:iterate (?LineIndex ?LineJson) .
220
-
221
- (?LineJson "/sku") string:jsonPointer ?SkuJson .
222
- (?LineJson "/qty") string:jsonPointer ?Qty .
223
- (?LineJson "/unitCents") string:jsonPointer ?UnitCents .
224
-
225
- # SKU reconciliation is case-insensitive
226
- # (Guard with a type, otherwise already-derived OrderLine nodes can start acting as “products”.)
227
- ?Product a ex:Product ; ex:sku ?SkuCat .
228
- ?SkuJson string:equalIgnoringCase ?SkuCat .
229
-
230
- (?Qty ?UnitCents) math:product ?NetCents .
231
-
232
- ("urn:example:order:%s#line:%s" ?OrderId ?LineIndex) string:format ?LineUriStr .
233
- ?Line log:uri ?LineUriStr .
234
- } => {
235
- ?Order ex:line ?Line .
236
-
237
- ?Line a ex:OrderLine ;
238
- ex:lineIndex ?LineIndex ;
239
- ex:product ?Product ;
240
- ex:sku ?SkuCat ;
241
- ex:qty ?Qty ;
242
- ex:unitCents ?UnitCents ;
243
- ex:netCents ?NetCents .
244
- } .
245
-
246
- # Export (outside EU): VAT=0
247
- {
248
- ?Order a ex:Order ; ex:shipCountry ?Country .
249
- ex:policy ex:euCountries ?EU .
250
- ?EU list:notMember ?Country .
251
-
252
- ?Order ex:line ?Line .
253
- ?Line ex:netCents ?NetCents .
254
- } => {
255
- ?Line ex:vatBps 0 ;
256
- ex:vatCents 0 ;
257
- ex:grossCents ?NetCents ;
258
- ex:vatReason ex:Export .
259
- } .
260
-
261
- # Reverse charge (EU + VAT id present): VAT=0
262
- {
263
- ?Order a ex:Order ; ex:shipCountry ?Country ; ex:vatIdRaw ?VatIdRaw .
264
- ex:policy ex:euCountries ?EU .
265
- ?EU list:member ?Country .
266
- ?VatIdRaw string:notEqualIgnoringCase "null" .
267
-
268
- ?Order ex:line ?Line .
269
- ?Line ex:netCents ?NetCents .
270
- } => {
271
- ?Line ex:vatBps 0 ;
272
- ex:vatCents 0 ;
273
- ex:grossCents ?NetCents ;
274
- ex:vatReason ex:ReverseCharge .
275
- } .
276
-
277
- # Domestic EU consumer (EU + no VAT): VAT = product vatBps
278
- {
279
- ?Order a ex:Order ; ex:shipCountry ?Country ; ex:vatIdRaw "null" .
280
- ex:policy ex:euCountries ?EU .
281
- ?EU list:member ?Country .
282
-
283
- ?Order ex:line ?Line .
284
- ?Line ex:product ?Product ; ex:netCents ?NetCents .
285
- ?Product ex:vatBps ?VatBps .
286
-
287
- (?NetCents ?VatBps) math:product ?NetTimesBps .
288
- (?NetTimesBps 10000) math:integerQuotient ?VatCents .
289
- (?NetCents ?VatCents) math:sum ?GrossCents .
290
- } => {
291
- ?Line ex:vatBps ?VatBps ;
292
- ex:vatCents ?VatCents ;
293
- ex:grossCents ?GrossCents ;
294
- ex:vatReason ex:Domestic .
295
- } .
296
-
297
- # ------------------------------------------------------------------
298
- # 4) Reconcile computed totals vs declared totals
299
- # (Assumes 2 lines per order: index 0 and 1 — on purpose, to keep the example finite.)
300
- # ------------------------------------------------------------------
301
-
302
- {
303
- ?Order a ex:Order .
304
-
305
- ?Order ex:line ?L0 . ?L0 ex:lineIndex 0 ; ex:netCents ?N0 ; ex:vatCents ?V0 ; ex:grossCents ?G0 .
306
- ?Order ex:line ?L1 . ?L1 ex:lineIndex 1 ; ex:netCents ?N1 ; ex:vatCents ?V1 ; ex:grossCents ?G1 .
307
-
308
- (?N0 ?N1) math:sum ?Net .
309
- (?V0 ?V1) math:sum ?Vat .
310
- (?G0 ?G1) math:sum ?Gross .
311
- } => {
312
- ?Order ex:computedNetCents ?Net ;
313
- ex:computedVatCents ?Vat ;
314
- ex:computedGrossCents ?Gross .
315
- } .
316
-
317
- # Mark OK orders
318
- {
319
- ?Order a ex:Order ;
320
- ex:declaredNetCents ?DN ; ex:declaredVatCents ?DV ; ex:declaredGrossCents ?DG ;
321
- ex:computedNetCents ?CN ; ex:computedVatCents ?CV ; ex:computedGrossCents ?CG .
322
-
323
- ?DN math:equalTo ?CN .
324
- ?DV math:equalTo ?CV .
325
- ?DG math:equalTo ?CG .
326
- } => {
327
- ?Order a ex:OrderOk .
328
- } .
329
-
330
- # Emit issues for VAT mismatch
331
- {
332
- ?Order a ex:Order ; ex:orderId ?Oid ; ex:declaredVatCents ?DV ; ex:computedVatCents ?CV .
333
- ?DV math:notEqualTo ?CV .
334
-
335
- ("urn:example:issue:%s:vat" ?Oid) string:format ?IssueUriStr .
336
- ?Issue log:uri ?IssueUriStr .
337
- } => {
338
- ?Issue a ex:Issue ;
339
- ex:onOrder ?Order ;
340
- ex:field "vatCents" ;
341
- ex:declared ?DV ;
342
- ex:computed ?CV .
343
- ?Order ex:hasIssue ?Issue .
344
- } .
345
-
346
- # Emit issues for GROSS mismatch
347
- {
348
- ?Order a ex:Order ; ex:orderId ?Oid ; ex:declaredGrossCents ?DG ; ex:computedGrossCents ?CG .
349
- ?DG math:notEqualTo ?CG .
350
-
351
- ("urn:example:issue:%s:gross" ?Oid) string:format ?IssueUriStr .
352
- ?Issue log:uri ?IssueUriStr .
353
- } => {
354
- ?Issue a ex:Issue ;
355
- ex:onOrder ?Order ;
356
- ex:field "grossCents" ;
357
- ex:declared ?DG ;
358
- ex:computed ?CG .
359
- ?Order ex:hasIssue ?Issue .
360
- } .
361
-
@@ -1,9 +0,0 @@
1
- @prefix : <http://example/#> .
2
- @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
3
-
4
- :p1 :label "Ada_Lovelace" .
5
- :p1 :labelEn "Ada_Lovelace"@en .
6
- :p1 :hash "0c0844738e0d752f1915aabc2e9737b7f317f606" .
7
- :p1 :birthYear 1815 .
8
- :p1 :calc 3632 .
9
- :p1 :intLit "42"^^xsd:integer .
@@ -1,3 +0,0 @@
1
- @prefix : <http://example/#> .
2
-
3
- :phayes :name "Pat Hayes" .
@@ -1,22 +0,0 @@
1
- @prefix : <http://example.org/brewclub#> .
2
- @prefix genid: <https://eyereasoner.github.io/.well-known/genid/> .
3
- @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
4
- @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
5
-
6
- :alice :age 23 .
7
- :alice :name "Alice" .
8
- :alice :city "Brussels" .
9
- :bob :age 16 .
10
- :bob :name "Bob" .
11
- :bob :city "Brussels" .
12
- genid:9e06e026-ada3-9430-3e11-8dd8c37dac24 a :MembershipCard .
13
- genid:9e06e026-ada3-9430-3e11-8dd8c37dac24 :holder :alice .
14
- genid:9e06e026-ada3-9430-3e11-8dd8c37dac24 :issuedAt "2000-01-01T00:00:00+00:00"^^xsd:dateTime .
15
- genid:9e06e026-ada3-9430-3e11-8dd8c37dac24 rdfs:label "Brew Club card for Alice" .
16
- :order1 :eligibleDiscount true .
17
- genid:35d6c1c8-ee0c-5a7a-8ec2-654c5e1b34b4 a :Receipt .
18
- genid:35d6c1c8-ee0c-5a7a-8ec2-654c5e1b34b4 :forOrder :order1 .
19
- genid:35d6c1c8-ee0c-5a7a-8ec2-654c5e1b34b4 :receiptId "ORD-2025-001" .
20
- genid:fbd10997-9fd6-99ce-fa19-a82424656d08 a :Receipt .
21
- genid:fbd10997-9fd6-99ce-fa19-a82424656d08 :forOrder :order2 .
22
- genid:fbd10997-9fd6-99ce-fa19-a82424656d08 :receiptId "ORD-2025-002" .
File without changes
File without changes
@@ -1,13 +0,0 @@
1
- @prefix : <http://example.org/#> .
2
-
3
- :X :childOf :A .
4
- :X :childOf :B .
5
- :A :childOf :C .
6
- :X :descendedFrom :A .
7
- :X :descendedFrom :B .
8
- :A :descendedFrom :C .
9
- :X :descendedFrom :C .
10
- :A :ancestorOf :X .
11
- :B :ancestorOf :X .
12
- :C :ancestorOf :A .
13
- :C :ancestorOf :X .
@@ -1,3 +0,0 @@
1
- @prefix : <http://example/#> .
2
-
3
- :alice :passesNameFilter true .
@@ -1,4 +0,0 @@
1
- @prefix : <http://example/#> .
2
-
3
- :a :isSpecial true .
4
- :c :isSpecial true .
@@ -1,4 +0,0 @@
1
- @prefix : <http://example/#> .
2
-
3
- :bob :adultNamedByRule true .
4
- :bea :adultNamedByRule true .
@@ -1,3 +0,0 @@
1
- @prefix : <http://example/#> .
2
-
3
- :x :bothPositive true .
File without changes
@@ -1,13 +0,0 @@
1
- @prefix ex: <http://example.org/#> .
2
-
3
- ex:checks ex:firstUserNameOk true .
4
- <urn:example:user:u1> a ex:AllowedUser .
5
- <urn:example:user:u1> ex:name "Ada Lovelace" .
6
- <urn:example:user:u1> ex:email "ada@example.org" .
7
- <urn:example:user:u1> ex:emailDomain "example.org" .
8
- <urn:example:user:u1> ex:userIndex 0 .
9
- <urn:example:user:u2> a ex:BlockedUser .
10
- <urn:example:user:u2> ex:name "Bob Mallory" .
11
- <urn:example:user:u2> ex:email "bob@evil.invalid" .
12
- <urn:example:user:u2> ex:emailDomain "evil.invalid" .
13
- <urn:example:user:u2> ex:userIndex 1 .