eyeling 1.27.9 → 1.28.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/README.md +3 -0
- package/dist/browser/eyeling.browser.js +313 -71
- package/examples/alma-rdf-messages.n3 +202 -0
- package/examples/output/alma-rdf-messages.n3 +0 -0
- package/eyeling.js +320 -72
- package/index.d.ts +3 -2
- package/lib/cli.js +140 -27
- package/lib/engine.js +31 -19
- package/lib/rdfjs.js +142 -25
- package/notes/rdf-message-logs.md +228 -0
- package/notes/rdfjs-integration.md +378 -0
- package/package.json +1 -1
- package/test/api.test.js +75 -20
- package/test/stream_messages.test.js +102 -3
- package/tools/bundle.js +7 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# RDF Message Logs internals
|
|
2
|
+
|
|
3
|
+
Eyeling handles RDF Message Logs as an RDF-compatibility normalization step. A message log is not parsed as one flat graph first; it is split into message chunks and rewritten into ordinary N3 facts that describe the stream, its ordered message envelopes, and each message payload graph.
|
|
4
|
+
|
|
5
|
+
There are two execution paths:
|
|
6
|
+
|
|
7
|
+
- normal RDF mode, where a whole message log is normalized into one replay document;
|
|
8
|
+
- `--stream-messages`, where the CLI reads one message at a time and runs the rules once per replayed message.
|
|
9
|
+
|
|
10
|
+
Both paths expose the same basic `eymsg:` vocabulary and use N3 quoted formulas for payload graphs.
|
|
11
|
+
|
|
12
|
+
## Input shape
|
|
13
|
+
|
|
14
|
+
A log is recognized by a message-version directive:
|
|
15
|
+
|
|
16
|
+
```trig
|
|
17
|
+
VERSION "1.2-messages"
|
|
18
|
+
PREFIX : <urn:example#>
|
|
19
|
+
|
|
20
|
+
:a :value 1 .
|
|
21
|
+
|
|
22
|
+
MESSAGE
|
|
23
|
+
|
|
24
|
+
# Empty heartbeat.
|
|
25
|
+
|
|
26
|
+
MESSAGE
|
|
27
|
+
|
|
28
|
+
:b :value 2 .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The current code accepts message versions matching:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
VERSION "1.1-messages"
|
|
35
|
+
VERSION "1.2-messages"
|
|
36
|
+
VERSION "1.2-basic-messages"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Old-style `@version` and `@message` forms are also recognized.
|
|
40
|
+
|
|
41
|
+
## Normal, whole-log RDF mode
|
|
42
|
+
|
|
43
|
+
When `lex(input, { rdf: true })` sees a `*-messages` version directive, `normalizeRdfCompatibility()` dispatches to `normalizeRdfMessageLog()` in `lib/lexer.js`.
|
|
44
|
+
|
|
45
|
+
That function:
|
|
46
|
+
|
|
47
|
+
1. strips the version directive;
|
|
48
|
+
2. splits the text at top-level `MESSAGE` / `@message` delimiters;
|
|
49
|
+
3. creates deterministic stream, envelope, and payload IRIs from a hash of the whole source text;
|
|
50
|
+
4. normalizes each message chunk through the RDF/TriG compatibility layer;
|
|
51
|
+
5. rewrites blank-node labels so each message gets its own blank-node scope;
|
|
52
|
+
6. emits ordinary N3 replay facts.
|
|
53
|
+
|
|
54
|
+
Conceptually, the input above becomes something like:
|
|
55
|
+
|
|
56
|
+
```n3
|
|
57
|
+
<urn:eyeling:message-log:HASH#stream>
|
|
58
|
+
a eymsg:RDFMessageStream ;
|
|
59
|
+
eymsg:messageCount "3"^^xsd:integer ;
|
|
60
|
+
eymsg:orderedEnvelopes (
|
|
61
|
+
<urn:eyeling:message-log:HASH#m001>
|
|
62
|
+
<urn:eyeling:message-log:HASH#m002>
|
|
63
|
+
<urn:eyeling:message-log:HASH#m003>
|
|
64
|
+
) ;
|
|
65
|
+
eymsg:firstEnvelope <urn:eyeling:message-log:HASH#m001> ;
|
|
66
|
+
eymsg:lastEnvelope <urn:eyeling:message-log:HASH#m003> ;
|
|
67
|
+
eymsg:envelope <urn:eyeling:message-log:HASH#m001>,
|
|
68
|
+
<urn:eyeling:message-log:HASH#m002>,
|
|
69
|
+
<urn:eyeling:message-log:HASH#m003> .
|
|
70
|
+
|
|
71
|
+
<urn:eyeling:message-log:HASH#m001>
|
|
72
|
+
a eymsg:MessageEnvelope ;
|
|
73
|
+
eymsg:offset "1"^^xsd:integer ;
|
|
74
|
+
eymsg:payloadKind eymsg:nonEmpty ;
|
|
75
|
+
eymsg:nextEnvelope <urn:eyeling:message-log:HASH#m002> ;
|
|
76
|
+
eymsg:payloadGraph <urn:eyeling:message-log:HASH#m001/payload> .
|
|
77
|
+
|
|
78
|
+
<urn:eyeling:message-log:HASH#m001/payload>
|
|
79
|
+
log:nameOf {
|
|
80
|
+
:a :value 1 .
|
|
81
|
+
} .
|
|
82
|
+
|
|
83
|
+
<urn:eyeling:message-log:HASH#m002>
|
|
84
|
+
a eymsg:MessageEnvelope ;
|
|
85
|
+
eymsg:offset "2"^^xsd:integer ;
|
|
86
|
+
eymsg:payloadKind eymsg:empty ;
|
|
87
|
+
eymsg:nextEnvelope <urn:eyeling:message-log:HASH#m003> .
|
|
88
|
+
|
|
89
|
+
<urn:eyeling:message-log:HASH#m003>
|
|
90
|
+
a eymsg:MessageEnvelope ;
|
|
91
|
+
eymsg:offset "3"^^xsd:integer ;
|
|
92
|
+
eymsg:payloadKind eymsg:nonEmpty ;
|
|
93
|
+
eymsg:payloadGraph <urn:eyeling:message-log:HASH#m003/payload> .
|
|
94
|
+
|
|
95
|
+
<urn:eyeling:message-log:HASH#m003/payload>
|
|
96
|
+
log:nameOf {
|
|
97
|
+
:b :value 2 .
|
|
98
|
+
} .
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
After this rewrite, the ordinary N3 parser and reasoner handle the result. There is no separate RDF Message Log AST.
|
|
102
|
+
|
|
103
|
+
## Payload graphs
|
|
104
|
+
|
|
105
|
+
Payloads are represented exactly like other RDF/TriG named graphs in RDF compatibility mode:
|
|
106
|
+
|
|
107
|
+
```n3
|
|
108
|
+
?Payload log:nameOf { ...payload triples... } .
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The object is an N3 quoted formula. Rules inspect it with formula-aware built-ins such as `log:includes`:
|
|
112
|
+
|
|
113
|
+
```n3
|
|
114
|
+
@prefix eymsg: <https://eyereasoner.github.io/eyeling/vocab/message#> .
|
|
115
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
116
|
+
|
|
117
|
+
{
|
|
118
|
+
?Envelope eymsg:payloadGraph ?Payload .
|
|
119
|
+
?Payload log:nameOf ?Graph .
|
|
120
|
+
?Graph log:includes { ?Subject :value ?Value . } .
|
|
121
|
+
} => {
|
|
122
|
+
?Envelope :sawValue ?Value .
|
|
123
|
+
} .
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This is the key design choice: message boundaries are preserved by putting each payload in its own quoted graph instead of merging all message triples into the top-level fact set.
|
|
127
|
+
|
|
128
|
+
## Per-message normalization
|
|
129
|
+
|
|
130
|
+
Each message body is normalized before it is embedded as a payload formula:
|
|
131
|
+
|
|
132
|
+
- RDF 1.2 triple terms are converted to singleton N3 graph terms;
|
|
133
|
+
- RDF 1.2 annotation syntax is expanded;
|
|
134
|
+
- TriG named graphs are converted to `log:nameOf` triples;
|
|
135
|
+
- blank-node labels are rewritten with a message-specific prefix.
|
|
136
|
+
|
|
137
|
+
For example, the same blank-node label in two different messages does not denote the same internal blank node. Message 1 rewrites roughly to:
|
|
138
|
+
|
|
139
|
+
```n3
|
|
140
|
+
_:eyeling_m001_b :value 1 .
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
and message 3 rewrites roughly to:
|
|
144
|
+
|
|
145
|
+
```n3
|
|
146
|
+
_:eyeling_m003_b :value 2 .
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
If the message log appears as the second parsed source, the parser's usual source prefixing may add another source prefix around those labels, for example `_:src2_eyeling_m001_b`.
|
|
150
|
+
|
|
151
|
+
## Empty heartbeat messages
|
|
152
|
+
|
|
153
|
+
A message chunk is considered empty when, after directives and comments are ignored, it contains no RDF payload.
|
|
154
|
+
|
|
155
|
+
Empty messages still get an envelope:
|
|
156
|
+
|
|
157
|
+
```n3
|
|
158
|
+
?Envelope a eymsg:MessageEnvelope ;
|
|
159
|
+
eymsg:offset "2"^^xsd:integer ;
|
|
160
|
+
eymsg:payloadKind eymsg:empty .
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
but they do not get an `eymsg:payloadGraph` triple. This lets rules distinguish heartbeats from non-empty data messages without accidentally reusing a previous payload.
|
|
164
|
+
|
|
165
|
+
## `--stream-messages` mode
|
|
166
|
+
|
|
167
|
+
The CLI streaming path lives in `lib/cli.js`.
|
|
168
|
+
|
|
169
|
+
With:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
eyeling --rdf --stream-messages rules.n3 messages.trig
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
the CLI separates ordinary sources from message-log sources. Ordinary sources are parsed once as the reusable rule/program documents. Message logs are then read chunk by chunk.
|
|
176
|
+
|
|
177
|
+
For each chunk, `buildSingleMessageReplayDocument()` creates a small replay document containing one stream, one envelope, and optionally one payload graph:
|
|
178
|
+
|
|
179
|
+
```n3
|
|
180
|
+
<urn:eyeling:message-stream:HASH#stream>
|
|
181
|
+
a eymsg:RDFMessageStream ;
|
|
182
|
+
eymsg:envelope <urn:eyeling:message-stream:HASH#m000001> ;
|
|
183
|
+
eymsg:orderedEnvelopes (<urn:eyeling:message-stream:HASH#m000001>) ;
|
|
184
|
+
eymsg:firstEnvelope <urn:eyeling:message-stream:HASH#m000001> ;
|
|
185
|
+
eymsg:lastEnvelope <urn:eyeling:message-stream:HASH#m000001> .
|
|
186
|
+
|
|
187
|
+
<urn:eyeling:message-stream:HASH#m000001>
|
|
188
|
+
a eymsg:MessageEnvelope ;
|
|
189
|
+
eymsg:offset "1"^^xsd:integer ;
|
|
190
|
+
eymsg:payloadKind eymsg:nonEmpty ;
|
|
191
|
+
eymsg:payloadGraph <urn:eyeling:message-stream:HASH#m000001/payload> .
|
|
192
|
+
|
|
193
|
+
<urn:eyeling:message-stream:HASH#m000001/payload>
|
|
194
|
+
log:nameOf {
|
|
195
|
+
...one message payload...
|
|
196
|
+
} .
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
That one-message replay document is merged with the already-parsed program sources and run immediately. The next message gets a fresh replay document and a fresh run.
|
|
200
|
+
|
|
201
|
+
The streaming replay document intentionally does not expose the whole global message list. It only exposes the current message envelope, because the point of `--stream-messages` is one-message-at-a-time processing without materializing the whole log.
|
|
202
|
+
|
|
203
|
+
## Remote logs
|
|
204
|
+
|
|
205
|
+
For local files, `--stream-messages` reads line by line. For HTTP(S) sources, the CLI first checks the prefix to detect a message-version directive. When processing a remote text/plain RDF Message Log, it downloads the source into a temporary file and then streams that local copy line by line.
|
|
206
|
+
|
|
207
|
+
## Output behavior
|
|
208
|
+
|
|
209
|
+
RDF Message Logs do not introduce special output syntax. Once replay facts have been produced, output is the normal Eyeling output path:
|
|
210
|
+
|
|
211
|
+
- `log:outputString` facts are collected and printed as text;
|
|
212
|
+
- `log:query` output is printed from query conclusions;
|
|
213
|
+
- in RDF mode, triples are printed with RDF-compatible output formatting where possible.
|
|
214
|
+
|
|
215
|
+
## Practical mental model
|
|
216
|
+
|
|
217
|
+
RDF Message Log support is best understood as:
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
message-log syntax
|
|
221
|
+
-> split into message chunks
|
|
222
|
+
-> normalize each chunk as RDF/TriG/RDF 1.2
|
|
223
|
+
-> wrap each chunk in an eymsg: envelope
|
|
224
|
+
-> expose the payload as log:nameOf { ... }
|
|
225
|
+
-> run the ordinary N3 reasoner
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
So the feature is not a separate streaming RDF reasoner. It is a replay encoding that turns message boundaries into explicit N3 facts and quoted payload graphs, which ordinary Eyeling rules can inspect.
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# RDF/JS integration in Eyeling
|
|
2
|
+
|
|
3
|
+
## Short version
|
|
4
|
+
|
|
5
|
+
Eyeling still reasons over its normal N3 data model. RDF/JS support is an adapter layer:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
RDF/JS quads, RDF/JS Quad terms, N3 text, or Eyeling rule objects
|
|
9
|
+
↓
|
|
10
|
+
lib/rdfjs.js input normalization
|
|
11
|
+
↓
|
|
12
|
+
Eyeling N3 triples/rules, including GraphTerm and log:nameOf encodings
|
|
13
|
+
↓
|
|
14
|
+
normal parser + reasoner
|
|
15
|
+
↓
|
|
16
|
+
N3 output and, when requested, RDF/JS quads
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The important RDF 1.2 mappings are:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
RDF/JS Quad term in subject/object position
|
|
23
|
+
↔ singleton N3 GraphTerm
|
|
24
|
+
↔ RDF 1.2 triple term
|
|
25
|
+
|
|
26
|
+
RDF/JS named graph quad
|
|
27
|
+
↔ graph log:nameOf { ... }
|
|
28
|
+
↔ TriG-style named graph output/input
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
So RDF/JS is now aligned with the same internal representation used by Eyeling's RDF 1.2/TriG compatibility mode.
|
|
32
|
+
|
|
33
|
+
## Public API pieces
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
const {
|
|
37
|
+
reason,
|
|
38
|
+
reasonStream,
|
|
39
|
+
reasonRdfJs,
|
|
40
|
+
rdfjs,
|
|
41
|
+
} = require('eyeling');
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `rdfjs` is Eyeling's lightweight RDF/JS-style `DataFactory`.
|
|
45
|
+
- `reasonStream(input, { rdfjs: true })` returns the normal structured result plus RDF/JS quad arrays.
|
|
46
|
+
- `reasonRdfJs(input, opts)` returns an async iterable of derived RDF/JS quads.
|
|
47
|
+
- `dataFactory` can be supplied in options to use another RDF/JS factory.
|
|
48
|
+
|
|
49
|
+
## Lightweight DataFactory
|
|
50
|
+
|
|
51
|
+
`lib/rdfjs.js` defines small RDF/JS-compatible term classes:
|
|
52
|
+
|
|
53
|
+
- `NamedNode`
|
|
54
|
+
- `BlankNode`
|
|
55
|
+
- `Literal`
|
|
56
|
+
- `Variable`
|
|
57
|
+
- `DefaultGraph`
|
|
58
|
+
- `Quad`
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
const { rdfjs } = require('eyeling');
|
|
64
|
+
|
|
65
|
+
const ex = 'http://example.org/';
|
|
66
|
+
const s = rdfjs.namedNode(ex + 's');
|
|
67
|
+
const p = rdfjs.namedNode(ex + 'p');
|
|
68
|
+
const o = rdfjs.literal('hello');
|
|
69
|
+
const q = rdfjs.quad(s, p, o, rdfjs.defaultGraph());
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Each term has `termType`, `value`, and `equals(other)`. Literals also carry `language` and `datatype`. `defaultGraph()` returns a singleton default graph term.
|
|
73
|
+
|
|
74
|
+
## RDF/JS input forms
|
|
75
|
+
|
|
76
|
+
Eyeling accepts RDF/JS quads through any of these object keys:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
{ quads: iterableOfQuads }
|
|
80
|
+
{ facts: iterableOfQuads }
|
|
81
|
+
{ dataset: iterableOfQuads }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The iterable may be synchronous for `reasonStream()` and may be synchronous or asynchronous for `reasonRdfJs()`.
|
|
85
|
+
|
|
86
|
+
Accepted RDF/JS term types are:
|
|
87
|
+
|
|
88
|
+
- `NamedNode`
|
|
89
|
+
- `BlankNode`
|
|
90
|
+
- `Literal`
|
|
91
|
+
- `Variable`
|
|
92
|
+
- `Quad` in subject or object position
|
|
93
|
+
- `DefaultGraph` only as a quad graph
|
|
94
|
+
|
|
95
|
+
`Quad` terms are rejected in predicate position, because RDF triple terms are not valid RDF predicates. A `Quad` term used as a quoted triple term must itself have the default graph.
|
|
96
|
+
|
|
97
|
+
## Default graph input quads
|
|
98
|
+
|
|
99
|
+
A normal RDF/JS quad:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
rdfjs.quad(
|
|
103
|
+
rdfjs.namedNode('http://example.org/s'),
|
|
104
|
+
rdfjs.namedNode('http://example.org/p'),
|
|
105
|
+
rdfjs.literal('hello'),
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
becomes the N3 fact:
|
|
110
|
+
|
|
111
|
+
```n3
|
|
112
|
+
<http://example.org/s> <http://example.org/p> "hello" .
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
and internally:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
Triple(
|
|
119
|
+
Iri('http://example.org/s'),
|
|
120
|
+
Iri('http://example.org/p'),
|
|
121
|
+
Literal('"hello"')
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Language and datatype literals are preserved:
|
|
126
|
+
|
|
127
|
+
```n3
|
|
128
|
+
"hello"@en
|
|
129
|
+
"42"^^<http://www.w3.org/2001/XMLSchema#integer>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Named graph input quads
|
|
133
|
+
|
|
134
|
+
Named graph RDF/JS quads are now accepted.
|
|
135
|
+
|
|
136
|
+
Input:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
rdfjs.quad(
|
|
140
|
+
rdfjs.namedNode('http://example.org/s'),
|
|
141
|
+
rdfjs.namedNode('http://example.org/p'),
|
|
142
|
+
rdfjs.namedNode('http://example.org/o'),
|
|
143
|
+
rdfjs.namedNode('http://example.org/g'),
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
is represented internally as the same shape used for TriG compatibility:
|
|
148
|
+
|
|
149
|
+
```n3
|
|
150
|
+
<http://example.org/g> log:nameOf {
|
|
151
|
+
<http://example.org/s> <http://example.org/p> <http://example.org/o> .
|
|
152
|
+
} .
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Multiple input quads with the same graph are grouped into one `log:nameOf` graph term:
|
|
156
|
+
|
|
157
|
+
```n3
|
|
158
|
+
:g log:nameOf {
|
|
159
|
+
:s1 :p :o1 .
|
|
160
|
+
:s2 :p :o2 .
|
|
161
|
+
} .
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
On RDF/JS output, this `log:nameOf` representation expands back to RDF/JS quads with the corresponding `graph` term.
|
|
165
|
+
|
|
166
|
+
## RDF/JS `Quad` terms as RDF 1.2 triple terms
|
|
167
|
+
|
|
168
|
+
RDF/JS `Quad` terms in subject or object position are now accepted as RDF 1.2 quoted triple terms.
|
|
169
|
+
|
|
170
|
+
Input:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
const quoted = rdfjs.quad(
|
|
174
|
+
rdfjs.namedNode('http://example.org/s'),
|
|
175
|
+
rdfjs.namedNode('http://example.org/p'),
|
|
176
|
+
rdfjs.namedNode('http://example.org/o'),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
rdfjs.quad(
|
|
180
|
+
rdfjs.namedNode('http://example.org/obs'),
|
|
181
|
+
rdfjs.namedNode('http://example.org/about'),
|
|
182
|
+
quoted,
|
|
183
|
+
);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
is normalized to a singleton N3 graph term:
|
|
187
|
+
|
|
188
|
+
```n3
|
|
189
|
+
<http://example.org/obs> <http://example.org/about> {
|
|
190
|
+
<http://example.org/s> <http://example.org/p> <http://example.org/o> .
|
|
191
|
+
} .
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
With RDF compatibility output enabled, the same structure can print as RDF 1.2 triple-term syntax:
|
|
195
|
+
|
|
196
|
+
```turtle
|
|
197
|
+
<http://example.org/obs> <http://example.org/about> <<(
|
|
198
|
+
<http://example.org/s> <http://example.org/p> <http://example.org/o>
|
|
199
|
+
)>> .
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Internally, the object is:
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
GraphTerm([
|
|
206
|
+
Triple(Iri(s), Iri(p), Iri(o))
|
|
207
|
+
])
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This is the same representation used by the lexer when `<<( s p o )>>` is parsed in RDF compatibility mode.
|
|
211
|
+
|
|
212
|
+
## Mixing `{ quads, n3 }`
|
|
213
|
+
|
|
214
|
+
The object form implied by the README is now supported: RDF/JS quads and N3 text are merged before reasoning.
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
const { reasonStream, rdfjs } = require('eyeling');
|
|
220
|
+
const ex = 'http://example.org/';
|
|
221
|
+
|
|
222
|
+
const result = reasonStream(
|
|
223
|
+
{
|
|
224
|
+
n3: `
|
|
225
|
+
@prefix : <http://example.org/> .
|
|
226
|
+
{ ?x :p ?y } => { ?x :q ?y } .
|
|
227
|
+
`,
|
|
228
|
+
quads: [
|
|
229
|
+
rdfjs.quad(
|
|
230
|
+
rdfjs.namedNode(ex + 'a'),
|
|
231
|
+
rdfjs.namedNode(ex + 'p'),
|
|
232
|
+
rdfjs.namedNode(ex + 'b'),
|
|
233
|
+
),
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
rdfjs: true,
|
|
238
|
+
includeInputFactsInClosure: false,
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
console.log(result.closureN3);
|
|
243
|
+
console.log(result.closureQuads);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The RDF/JS fact supplies `:a :p :b`; the N3 rule derives `:a :q :b`.
|
|
247
|
+
|
|
248
|
+
The merge also works with Eyeling rule objects and RDF/JS facts. In that path, `normalizeParsedReasonerInputSync()` or `normalizeParsedReasonerInputAsync()` builds an Eyeling document and appends the RDF/JS quads as facts.
|
|
249
|
+
|
|
250
|
+
## RDF/JS output from `reasonStream()`
|
|
251
|
+
|
|
252
|
+
When `rdfjs: true` is passed to `reasonStream()`, the result can include:
|
|
253
|
+
|
|
254
|
+
```js
|
|
255
|
+
result.closureQuads
|
|
256
|
+
result.queryQuads
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The `onDerived` callback receives RDF/JS quads too:
|
|
260
|
+
|
|
261
|
+
```js
|
|
262
|
+
reasonStream(input, {
|
|
263
|
+
rdfjs: true,
|
|
264
|
+
onDerived({ triple, quad, quads, df }) {
|
|
265
|
+
console.log(triple); // N3 or RDF-compatible text form
|
|
266
|
+
console.log(quad); // first RDF/JS quad when exactly/conveniently available
|
|
267
|
+
console.log(quads); // all RDF/JS quads emitted for this derived fact
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
A single internal triple can produce more than one RDF/JS quad when it is a `log:nameOf` named-graph wrapper, so the plural `quads` payload is the complete form. `quad` is present when there is exactly one emitted quad.
|
|
273
|
+
|
|
274
|
+
## Output conversion rules
|
|
275
|
+
|
|
276
|
+
The normal output path now uses `internalTripleToRdfJsQuads()`.
|
|
277
|
+
|
|
278
|
+
Ordinary terms map directly:
|
|
279
|
+
|
|
280
|
+
```text
|
|
281
|
+
Iri → NamedNode
|
|
282
|
+
Blank → BlankNode
|
|
283
|
+
Literal → Literal
|
|
284
|
+
Var → Variable
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Singleton graph terms in subject or object position map to RDF/JS `Quad` terms:
|
|
288
|
+
|
|
289
|
+
```n3
|
|
290
|
+
:x :holds { :s :p :o } .
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
becomes an RDF/JS quad whose object is:
|
|
294
|
+
|
|
295
|
+
```js
|
|
296
|
+
rdfjs.quad(
|
|
297
|
+
rdfjs.namedNode('http://example.org/s'),
|
|
298
|
+
rdfjs.namedNode('http://example.org/p'),
|
|
299
|
+
rdfjs.namedNode('http://example.org/o'),
|
|
300
|
+
rdfjs.defaultGraph(),
|
|
301
|
+
)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Named graph wrappers map back to named-graph RDF/JS quads:
|
|
305
|
+
|
|
306
|
+
```n3
|
|
307
|
+
:g log:nameOf {
|
|
308
|
+
:s :p :o .
|
|
309
|
+
} .
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
becomes:
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
rdfjs.quad(s, p, o, g)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Remaining N3-only cases
|
|
319
|
+
|
|
320
|
+
`skipUnsupportedRdfJs` is still useful, but the previous RDF 1.2 cases no longer require it.
|
|
321
|
+
|
|
322
|
+
Still unsupported as ordinary RDF/JS output:
|
|
323
|
+
|
|
324
|
+
- non-singleton `GraphTerm` in subject/object position;
|
|
325
|
+
- `GraphTerm` in predicate or graph position;
|
|
326
|
+
- `ListTerm` and `OpenListTerm`;
|
|
327
|
+
- other N3-only terms that do not have an RDF/JS representation.
|
|
328
|
+
|
|
329
|
+
By default, unsupported output raises a conversion error. With:
|
|
330
|
+
|
|
331
|
+
```js
|
|
332
|
+
skipUnsupportedRdfJs: true
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Eyeling keeps the N3 result and omits the unsupported RDF/JS quads from `closureQuads`, `queryQuads`, and `onDerived` payloads.
|
|
336
|
+
|
|
337
|
+
## `reasonRdfJs()`
|
|
338
|
+
|
|
339
|
+
`reasonRdfJs(input, opts)` returns an async iterable of derived RDF/JS quads:
|
|
340
|
+
|
|
341
|
+
```js
|
|
342
|
+
const { reasonRdfJs, rdfjs } = require('eyeling');
|
|
343
|
+
|
|
344
|
+
for await (const quad of reasonRdfJs({
|
|
345
|
+
quads: [
|
|
346
|
+
rdfjs.quad(
|
|
347
|
+
rdfjs.namedNode('http://example.org/a'),
|
|
348
|
+
rdfjs.namedNode('http://example.org/p'),
|
|
349
|
+
rdfjs.namedNode('http://example.org/b'),
|
|
350
|
+
),
|
|
351
|
+
],
|
|
352
|
+
n3: `
|
|
353
|
+
@prefix : <http://example.org/> .
|
|
354
|
+
{ ?x :p ?y } => { ?x :q ?y } .
|
|
355
|
+
`,
|
|
356
|
+
})) {
|
|
357
|
+
console.log(quad.subject.value, quad.predicate.value, quad.object.value);
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Internally, `reasonRdfJs()`:
|
|
362
|
+
|
|
363
|
+
1. normalizes the input, collecting async RDF/JS input if necessary;
|
|
364
|
+
2. runs `reasonStream()` on the normalized N3/Eyeling document;
|
|
365
|
+
3. converts each derived fact with `internalTripleToRdfJsQuads()`;
|
|
366
|
+
4. yields all resulting RDF/JS quads.
|
|
367
|
+
|
|
368
|
+
It is an async output interface, not a streaming RDF parser. Async input quads are collected before reasoning starts.
|
|
369
|
+
|
|
370
|
+
## Practical summary
|
|
371
|
+
|
|
372
|
+
Use RDF/JS integration when Eyeling needs to sit inside a JavaScript RDF pipeline:
|
|
373
|
+
|
|
374
|
+
- feed default-graph or named-graph RDF/JS quads as facts;
|
|
375
|
+
- use RDF/JS `Quad` terms for RDF 1.2 triple terms in subject/object positions;
|
|
376
|
+
- mix RDF/JS facts with N3 text using `{ quads, n3 }`;
|
|
377
|
+
- request RDF/JS output with `rdfjs: true` or `reasonRdfJs()`;
|
|
378
|
+
- keep `skipUnsupportedRdfJs: true` only for genuinely N3-only terms such as lists or non-singleton formulas.
|