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
package/test/api.test.js
CHANGED
|
@@ -2106,29 +2106,32 @@ _:x :hates { _:foo :making :mess }.
|
|
|
2106
2106
|
},
|
|
2107
2107
|
},
|
|
2108
2108
|
{
|
|
2109
|
-
name: '63a RDF/JS export: reasonRdfJs
|
|
2109
|
+
name: '63a RDF/JS export: reasonRdfJs emits RDF 1.2 Quad terms for singleton graph terms',
|
|
2110
2110
|
async run() {
|
|
2111
2111
|
const ex = 'http://example.org/';
|
|
2112
2112
|
const input = `@prefix : <${ex}>.
|
|
2113
2113
|
:a :p :b.
|
|
2114
2114
|
{ :a :p :b. } => { :x :holds { :a :p :b. }. :x :ok :yes. }.`;
|
|
2115
2115
|
const quads = [];
|
|
2116
|
-
for await (const quad of reasonRdfJs(input
|
|
2116
|
+
for await (const quad of reasonRdfJs(input)) {
|
|
2117
2117
|
quads.push(quad);
|
|
2118
2118
|
}
|
|
2119
2119
|
this.quads = quads;
|
|
2120
|
-
return quads.map((q) => `${q.subject.value} ${q.predicate.value} ${q.object.value}`).join('\n');
|
|
2120
|
+
return quads.map((q) => `${q.subject.value} ${q.predicate.value} ${q.object.termType}:${q.object.value}`).join('\n');
|
|
2121
2121
|
},
|
|
2122
|
-
expect: [/http:\/\/example\.org\/ok/],
|
|
2123
|
-
notExpect: [/http:\/\/example\.org\/holds/],
|
|
2122
|
+
expect: [/http:\/\/example\.org\/ok/, /http:\/\/example\.org\/holds\s+Quad:/],
|
|
2124
2123
|
check(outputIgnored, tc) {
|
|
2125
|
-
assert.equal(tc.quads.length,
|
|
2126
|
-
|
|
2127
|
-
assert.
|
|
2124
|
+
assert.equal(tc.quads.length, 2, 'Expected both derived facts as RDF/JS quads');
|
|
2125
|
+
const holds = tc.quads.find((q) => q.predicate.value === 'http://example.org/holds');
|
|
2126
|
+
assert.ok(holds, 'Expected :holds quad');
|
|
2127
|
+
assert.equal(holds.object.termType, 'Quad');
|
|
2128
|
+
assert.equal(holds.object.subject.value, 'http://example.org/a');
|
|
2129
|
+
assert.equal(holds.object.predicate.value, 'http://example.org/p');
|
|
2130
|
+
assert.equal(holds.object.object.value, 'http://example.org/b');
|
|
2128
2131
|
},
|
|
2129
2132
|
},
|
|
2130
2133
|
{
|
|
2131
|
-
name: '63b RDF/JS export: reasonStream
|
|
2134
|
+
name: '63b RDF/JS export: reasonStream converts singleton graph terms without skipUnsupportedRdfJs',
|
|
2132
2135
|
run() {
|
|
2133
2136
|
const ex = 'http://example.org/';
|
|
2134
2137
|
const input = `@prefix : <${ex}>.
|
|
@@ -2137,7 +2140,6 @@ _:x :hates { _:foo :making :mess }.
|
|
|
2137
2140
|
const seen = [];
|
|
2138
2141
|
const result = reasonStream(input, {
|
|
2139
2142
|
rdfjs: true,
|
|
2140
|
-
skipUnsupportedRdfJs: true,
|
|
2141
2143
|
includeInputFactsInClosure: false,
|
|
2142
2144
|
onDerived: ({ triple, quad }) => seen.push({ triple, quad }),
|
|
2143
2145
|
});
|
|
@@ -2148,24 +2150,23 @@ _:x :hates { _:foo :making :mess }.
|
|
|
2148
2150
|
expect: [/:holds/, /:ok/],
|
|
2149
2151
|
check(outputIgnored, tc) {
|
|
2150
2152
|
assert.equal(tc.seen.length, 2, 'Expected both derived facts to reach onDerived');
|
|
2151
|
-
assert.equal(tc.seen.filter((x) => x.quad).length,
|
|
2153
|
+
assert.equal(tc.seen.filter((x) => x.quad).length, 2, 'Expected both RDF/JS quads in onDerived');
|
|
2152
2154
|
assert.ok(Array.isArray(tc.result.closureQuads), 'Expected closureQuads array');
|
|
2153
|
-
assert.equal(
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2155
|
+
assert.equal(tc.result.closureQuads.length, 2, 'Expected both RDF/JS quads in closureQuads');
|
|
2156
|
+
assert.ok(
|
|
2157
|
+
tc.result.closureQuads.some(
|
|
2158
|
+
(q) => q.predicate.value === 'http://example.org/holds' && q.object.termType === 'Quad',
|
|
2159
|
+
),
|
|
2160
|
+
'Expected RDF 1.2 Quad term object in closureQuads',
|
|
2157
2161
|
);
|
|
2158
|
-
assert.equal(tc.result.closureQuads[0].predicate.value, 'http://example.org/ok');
|
|
2159
2162
|
assert.match(tc.result.closureN3, /:holds/, 'Expected N3 closure to retain quoted-formula triple');
|
|
2160
2163
|
},
|
|
2161
2164
|
},
|
|
2162
2165
|
{
|
|
2163
|
-
name: '64 RDF/JS
|
|
2164
|
-
expectError: true,
|
|
2166
|
+
name: '64 RDF/JS input: named-graph quads are represented as log:nameOf graph terms',
|
|
2165
2167
|
run() {
|
|
2166
2168
|
const ex = 'http://example.org/';
|
|
2167
|
-
|
|
2168
|
-
{},
|
|
2169
|
+
const result = reasonStream(
|
|
2169
2170
|
{
|
|
2170
2171
|
quads: [
|
|
2171
2172
|
rdfjs.quad(
|
|
@@ -2176,7 +2177,61 @@ _:x :hates { _:foo :making :mess }.
|
|
|
2176
2177
|
),
|
|
2177
2178
|
],
|
|
2178
2179
|
},
|
|
2180
|
+
{ rdfjs: true },
|
|
2181
|
+
);
|
|
2182
|
+
this.result = result;
|
|
2183
|
+
return result.closureN3;
|
|
2184
|
+
},
|
|
2185
|
+
expect: [/log:nameOf/, /http:\/\/example\.org\/g/, /http:\/\/example\.org\/s/],
|
|
2186
|
+
check(outputIgnored, tc) {
|
|
2187
|
+
assert.equal(tc.result.closureQuads.length, 1, 'Expected one named-graph RDF/JS quad');
|
|
2188
|
+
assert.equal(tc.result.closureQuads[0].graph.value, 'http://example.org/g');
|
|
2189
|
+
},
|
|
2190
|
+
},
|
|
2191
|
+
{
|
|
2192
|
+
name: '64a RDF/JS input: Quad terms are accepted as RDF 1.2 triple terms',
|
|
2193
|
+
run() {
|
|
2194
|
+
const ex = 'http://example.org/';
|
|
2195
|
+
const quoted = rdfjs.quad(
|
|
2196
|
+
rdfjs.namedNode(ex + 's'),
|
|
2197
|
+
rdfjs.namedNode(ex + 'p'),
|
|
2198
|
+
rdfjs.namedNode(ex + 'o'),
|
|
2199
|
+
);
|
|
2200
|
+
const result = reasonStream(
|
|
2201
|
+
{
|
|
2202
|
+
quads: [rdfjs.quad(rdfjs.namedNode(ex + 'obs'), rdfjs.namedNode(ex + 'about'), quoted)],
|
|
2203
|
+
},
|
|
2204
|
+
{ rdf: true, rdfjs: true },
|
|
2205
|
+
);
|
|
2206
|
+
this.result = result;
|
|
2207
|
+
return result.closureN3;
|
|
2208
|
+
},
|
|
2209
|
+
expect: [/<<\(\s+<http:\/\/example\.org\/s>\s+<http:\/\/example\.org\/p>\s+<http:\/\/example\.org\/o>\s+\)>>/],
|
|
2210
|
+
check(outputIgnored, tc) {
|
|
2211
|
+
assert.equal(tc.result.closureQuads.length, 1, 'Expected one RDF/JS quad');
|
|
2212
|
+
assert.equal(tc.result.closureQuads[0].object.termType, 'Quad');
|
|
2213
|
+
assert.equal(tc.result.closureQuads[0].object.subject.value, 'http://example.org/s');
|
|
2214
|
+
},
|
|
2215
|
+
},
|
|
2216
|
+
{
|
|
2217
|
+
name: '64b RDF/JS input: object with quads and n3 text is merged',
|
|
2218
|
+
run() {
|
|
2219
|
+
const ex = 'http://example.org/';
|
|
2220
|
+
const result = reasonStream(
|
|
2221
|
+
{
|
|
2222
|
+
n3: `@prefix : <${ex}>.
|
|
2223
|
+
{ ?x :p ?y. } => { ?x :q ?y. } .`,
|
|
2224
|
+
quads: [rdfjs.quad(rdfjs.namedNode(ex + 'a'), rdfjs.namedNode(ex + 'p'), rdfjs.namedNode(ex + 'b'))],
|
|
2225
|
+
},
|
|
2226
|
+
{ includeInputFactsInClosure: false, rdfjs: true },
|
|
2179
2227
|
);
|
|
2228
|
+
this.result = result;
|
|
2229
|
+
return result.closureN3;
|
|
2230
|
+
},
|
|
2231
|
+
expect: [/:a\s+:q\s+:b/],
|
|
2232
|
+
check(outputIgnored, tc) {
|
|
2233
|
+
assert.equal(tc.result.closureQuads.length, 1, 'Expected one derived RDF/JS quad');
|
|
2234
|
+
assert.equal(tc.result.closureQuads[0].predicate.value, 'http://example.org/q');
|
|
2180
2235
|
},
|
|
2181
2236
|
},
|
|
2182
2237
|
{
|
|
@@ -161,6 +161,88 @@ function startFileServer(file) {
|
|
|
161
161
|
throw new Error('test HTTP server did not start');
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
|
|
165
|
+
function startOpenEndedMessageServer() {
|
|
166
|
+
const dir = mkTmpDir();
|
|
167
|
+
const script = path.join(dir, 'server.js');
|
|
168
|
+
const portFile = path.join(dir, 'server.port');
|
|
169
|
+
fs.writeFileSync(
|
|
170
|
+
script,
|
|
171
|
+
[
|
|
172
|
+
"const http = require('node:http');",
|
|
173
|
+
"const fs = require('node:fs');",
|
|
174
|
+
'const portFile = process.argv[2];',
|
|
175
|
+
'const server = http.createServer((req, res) => {',
|
|
176
|
+
" res.writeHead(200, { 'content-type': 'text/plain' });",
|
|
177
|
+
" res.write('VERSION \\\"1.2-messages\\\"\\nPREFIX : <urn:test#>\\n');",
|
|
178
|
+
" res.write(':a :line \\\"one\\\\n\\\".\\nMESSAGE\\n');",
|
|
179
|
+
" setInterval(() => res.write('# keepalive\\n'), 1000);",
|
|
180
|
+
'});',
|
|
181
|
+
"server.listen(0, '127.0.0.1', () => fs.writeFileSync(portFile, String(server.address().port)));",
|
|
182
|
+
'',
|
|
183
|
+
].join('\n'),
|
|
184
|
+
'utf8',
|
|
185
|
+
);
|
|
186
|
+
const child = cp.spawn(process.execPath, [script, portFile], { cwd: root, stdio: ['ignore', 'ignore', 'pipe'] });
|
|
187
|
+
const deadline = Date.now() + 5000;
|
|
188
|
+
while (Date.now() < deadline) {
|
|
189
|
+
if (fs.existsSync(portFile)) {
|
|
190
|
+
const port = fs.readFileSync(portFile, 'utf8').trim();
|
|
191
|
+
return {
|
|
192
|
+
url: `http://127.0.0.1:${port}/messages.txt`,
|
|
193
|
+
stop: () => {
|
|
194
|
+
child.kill();
|
|
195
|
+
rmrf(dir);
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
|
|
200
|
+
}
|
|
201
|
+
child.kill();
|
|
202
|
+
rmrf(dir);
|
|
203
|
+
throw new Error('test open-ended HTTP server did not start');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function waitForEyelingOutput(args, pattern, opts = {}) {
|
|
207
|
+
const timeoutMs = opts.timeoutMs || 4000;
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
const child = cp.spawn(process.execPath, [eyelingJsPath, ...args], {
|
|
210
|
+
cwd: root,
|
|
211
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
212
|
+
});
|
|
213
|
+
let stdout = '';
|
|
214
|
+
let stderr = '';
|
|
215
|
+
let settled = false;
|
|
216
|
+
const timer = setTimeout(() => {
|
|
217
|
+
if (settled) return;
|
|
218
|
+
settled = true;
|
|
219
|
+
child.kill();
|
|
220
|
+
reject(new Error(`timed out waiting for ${pattern}; stdout=${JSON.stringify(stdout)} stderr=${JSON.stringify(stderr)}`));
|
|
221
|
+
}, timeoutMs);
|
|
222
|
+
|
|
223
|
+
function finish(err) {
|
|
224
|
+
if (settled) return;
|
|
225
|
+
settled = true;
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
child.kill();
|
|
228
|
+
if (err) reject(err);
|
|
229
|
+
else resolve(stdout);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
child.stdout.on('data', (chunk) => {
|
|
233
|
+
stdout += chunk.toString('utf8');
|
|
234
|
+
if (pattern.test(stdout)) finish();
|
|
235
|
+
});
|
|
236
|
+
child.stderr.on('data', (chunk) => {
|
|
237
|
+
stderr += chunk.toString('utf8');
|
|
238
|
+
});
|
|
239
|
+
child.on('error', finish);
|
|
240
|
+
child.on('exit', (code) => {
|
|
241
|
+
if (!settled && code !== 0) finish(new Error(`eyeling exited with ${code}; stdout=${stdout}; stderr=${stderr}`));
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
164
246
|
const cases = [
|
|
165
247
|
{
|
|
166
248
|
name: 'scoped payload rules run once per RDF Message',
|
|
@@ -220,6 +302,20 @@ const cases = [
|
|
|
220
302
|
}
|
|
221
303
|
},
|
|
222
304
|
},
|
|
305
|
+
{
|
|
306
|
+
name: 'remote HTTP RDF Message Logs emit before the response ends',
|
|
307
|
+
async run(tmp) {
|
|
308
|
+
const rules = path.join(tmp, 'rules.n3');
|
|
309
|
+
writeScopedPayloadRules(rules);
|
|
310
|
+
const server = startOpenEndedMessageServer();
|
|
311
|
+
try {
|
|
312
|
+
const out = await waitForEyelingOutput(['-r', '--stream-messages', rules, server.url], /^one\n/);
|
|
313
|
+
assert.equal(out, 'one\n');
|
|
314
|
+
} finally {
|
|
315
|
+
server.stop();
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
},
|
|
223
319
|
{
|
|
224
320
|
name: 'MARC extraction rules fire over each streamed payload graph',
|
|
225
321
|
run(tmp) {
|
|
@@ -251,7 +347,7 @@ const cases = [
|
|
|
251
347
|
},
|
|
252
348
|
];
|
|
253
349
|
|
|
254
|
-
(function main() {
|
|
350
|
+
(async function main() {
|
|
255
351
|
const suiteStart = msNow();
|
|
256
352
|
info(`Running ${cases.length} stream-message tests`);
|
|
257
353
|
let passed = 0;
|
|
@@ -261,7 +357,7 @@ const cases = [
|
|
|
261
357
|
const testName = numberedName(index, tc.name);
|
|
262
358
|
const start = msNow();
|
|
263
359
|
try {
|
|
264
|
-
tc.run(tmp);
|
|
360
|
+
await tc.run(tmp);
|
|
265
361
|
ok(`${testName} ${C.dim}(${msNow() - start} ms)${C.n}`);
|
|
266
362
|
passed++;
|
|
267
363
|
} catch (e) {
|
|
@@ -281,4 +377,7 @@ const cases = [
|
|
|
281
377
|
}
|
|
282
378
|
fail(`Some stream-message tests failed (${passed}/${cases.length})`);
|
|
283
379
|
process.exit(1);
|
|
284
|
-
})()
|
|
380
|
+
})().catch((e) => {
|
|
381
|
+
fail(e && e.stack ? e.stack : String(e));
|
|
382
|
+
process.exit(1);
|
|
383
|
+
});
|
package/tools/bundle.js
CHANGED
|
@@ -195,7 +195,13 @@ function buildBundleSource({ autoRunMain }) {
|
|
|
195
195
|
out.push(
|
|
196
196
|
' if (__outerModule && __outerRequire && __outerRequire.main === __outerModule && typeof __entry.main === "function") {',
|
|
197
197
|
);
|
|
198
|
-
out.push(' __entry.main();');
|
|
198
|
+
out.push(' const __mainResult = __entry.main();');
|
|
199
|
+
out.push(' if (__mainResult && typeof __mainResult.then === "function") {');
|
|
200
|
+
out.push(' __mainResult.catch((e) => {');
|
|
201
|
+
out.push(' try { if (typeof console !== "undefined" && console.error) console.error(e && e.stack ? e.stack : e && e.message ? e.message : String(e)); } catch (ignoredError) {}');
|
|
202
|
+
out.push(' try { if (typeof process !== "undefined" && process.exit) process.exit(1); } catch (ignoredError) {}');
|
|
203
|
+
out.push(' });');
|
|
204
|
+
out.push(' }');
|
|
199
205
|
out.push(' }');
|
|
200
206
|
out.push(' } catch (ignoredError) {}');
|
|
201
207
|
}
|