n3.js-messages 0.1.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/lib/formats.js ADDED
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.baseVersion = baseVersion;
7
+ exports.isMessagesVersion = isMessagesVersion;
8
+ exports.isSupportedVersion = isSupportedVersion;
9
+ exports.resolveFormat = resolveFormat;
10
+ // **formats** describes the RDF syntaxes that support RDF Messages.
11
+
12
+ const FORMATS = {
13
+ turtle: {
14
+ name: 'Turtle',
15
+ lineMode: false
16
+ },
17
+ trig: {
18
+ name: 'TriG',
19
+ lineMode: false
20
+ },
21
+ ntriples: {
22
+ name: 'N-Triples',
23
+ lineMode: true
24
+ },
25
+ nquads: {
26
+ name: 'N-Quads',
27
+ lineMode: true
28
+ }
29
+ };
30
+
31
+ // The version labels of RDF 1.2 syntaxes, as accepted by N3.js
32
+ const BASE_VERSIONS = ['1.2', '1.2-basic', '1.1'];
33
+
34
+ // ### `resolveFormat` maps a format name or media type to a supported syntax
35
+ function resolveFormat(format = 'TriG') {
36
+ const key = String(format).toLowerCase().split(';')[0].trim().replace(/^(?:text|application)\//, '').replace(/^x-/, '').replace(/[-_ ]/g, '');
37
+ const resolved = FORMATS[key];
38
+ if (!resolved) throw new TypeError(`Unsupported RDF Messages format: "${format}". ` + 'Supported formats are Turtle, TriG, N-Triples and N-Quads.');
39
+ return resolved;
40
+ }
41
+
42
+ // ### `isMessagesVersion` checks whether a version label announces RDF Messages
43
+ function isMessagesVersion(version) {
44
+ return typeof version === 'string' && version.endsWith('-messages');
45
+ }
46
+
47
+ // ### `baseVersion` strips the `-messages` suffix of a version label
48
+ function baseVersion(version) {
49
+ return isMessagesVersion(version) ? version.slice(0, -'-messages'.length) : version;
50
+ }
51
+
52
+ // ### `isSupportedVersion` checks whether a (base) version label is supported
53
+ function isSupportedVersion(version, supported = BASE_VERSIONS) {
54
+ return supported.includes(version);
55
+ }
package/lib/index.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "MessageParser", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _MessageParser.default;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "MessageStreamParser", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _MessageStreamParser.default;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "MessageStreamWriter", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _MessageStreamWriter.default;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "MessageWriter", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _MessageWriter.default;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "parseMessages", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _MessageParser.parseMessages;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "writeMessages", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _MessageWriter.writeMessages;
40
+ }
41
+ });
42
+ var _MessageParser = _interopRequireWildcard(require("./MessageParser.js"));
43
+ var _MessageStreamParser = _interopRequireDefault(require("./MessageStreamParser.js"));
44
+ var _MessageWriter = _interopRequireWildcard(require("./MessageWriter.js"));
45
+ var _MessageStreamWriter = _interopRequireDefault(require("./MessageStreamWriter.js"));
46
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
47
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
@@ -0,0 +1 @@
1
+ { "type": "commonjs" }
package/package.json ADDED
@@ -0,0 +1,107 @@
1
+ {
2
+ "name": "n3.js-messages",
3
+ "version": "0.1.0",
4
+ "description": "RDF Messages (message-delimited Turtle, TriG, N-Triples and N-Quads) for N3.js: message-aware parser, streaming parser and writer.",
5
+ "author": "Pieter Colpaert",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "rdf",
9
+ "rdf-messages",
10
+ "n3",
11
+ "turtle",
12
+ "trig",
13
+ "n-triples",
14
+ "n-quads",
15
+ "streaming",
16
+ "rdfjs"
17
+ ],
18
+ "type": "module",
19
+ "main": "./lib/index.js",
20
+ "module": "./src/index.js",
21
+ "types": "./index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./index.d.ts",
25
+ "import": "./src/index.js",
26
+ "require": "./lib/index.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "sideEffects": false,
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "files": [
35
+ "src",
36
+ "lib",
37
+ "index.d.ts"
38
+ ],
39
+ "peerDependencies": {
40
+ "n3": ">=2.0.0 <3"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/pietercolpaert/n3.js-messages.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/pietercolpaert/n3.js-messages/issues"
48
+ },
49
+ "homepage": "https://github.com/pietercolpaert/n3.js-messages#readme",
50
+ "scripts": {
51
+ "build": "babel src -d lib --copy-files --no-copy-ignored && node scripts/write-lib-package.js",
52
+ "prepack": "npm run build",
53
+ "test": "jest",
54
+ "coverage": "jest --coverage",
55
+ "lint": "eslint src test spec perf scripts",
56
+ "test:spec": "node spec/runner.js",
57
+ "test:spec:earl": "node spec/runner.js --earl > spec/earl-report.ttl",
58
+ "test:types": "tsc -p test/types/tsconfig.json",
59
+ "test:package": "node scripts/test-package.js",
60
+ "perf": "node perf/run.js",
61
+ "perf:compare": "node perf/compare.js",
62
+ "spec:update": "node scripts/update-spec.js"
63
+ },
64
+ "devDependencies": {
65
+ "@babel/cli": "^7.29.7",
66
+ "@babel/core": "^7.29.7",
67
+ "@babel/preset-env": "^7.29.7",
68
+ "@types/n3": "^1.26.4",
69
+ "@types/node": "^26.6.4",
70
+ "babel-jest": "^30.5.2",
71
+ "eslint": "^10.12.0",
72
+ "eslint-plugin-import-x": "^4.17.1",
73
+ "eslint-plugin-jest": "^29.16.6",
74
+ "globals": "^17.13.0",
75
+ "jest": "^30.5.2",
76
+ "n3": "^2.10.6",
77
+ "rdf-isomorphic": "^2.0.1",
78
+ "rdf-parser-ts": "^0.4.0",
79
+ "rdf-writer-ts": "^0.2.1",
80
+ "typescript": "^6.0.3"
81
+ },
82
+ "jest": {
83
+ "testMatch": [
84
+ "**/test/**/*-test.js"
85
+ ],
86
+ "collectCoverage": true,
87
+ "collectCoverageFrom": [
88
+ "src/**/*.js"
89
+ ],
90
+ "coverageReporters": [
91
+ "json-summary",
92
+ "text",
93
+ "lcov"
94
+ ],
95
+ "coverageThreshold": {
96
+ "global": {
97
+ "branches": 100,
98
+ "functions": 100,
99
+ "lines": 100,
100
+ "statements": 100
101
+ }
102
+ }
103
+ },
104
+ "dependencies": {
105
+ "@rdfjs/types": "^2.0.1"
106
+ }
107
+ }
@@ -0,0 +1,71 @@
1
+ // **MessageParser** parses RDF Message Logs (Turtle, TriG, N-Triples and N-Quads).
2
+ import { StringDecoder } from 'string_decoder';
3
+ import MessageParserCore from './MessageParserCore.js';
4
+
5
+ export default class MessageParser {
6
+ constructor(options) {
7
+ this._options = options || {};
8
+ // Fail early on unsupported options
9
+ this._createCore({});
10
+ }
11
+
12
+ _createCore(sink) {
13
+ return new MessageParserCore(this._options, sink);
14
+ }
15
+
16
+ // ### `parse` parses a string or a readable stream of RDF Messages.
17
+ //
18
+ // Without callbacks, a string is parsed synchronously into an array of
19
+ // messages (arrays of quads), and parse errors are thrown.
20
+ // With a callback `(error, message, prefixes)`, or an object with the
21
+ // callbacks `onMessage(error, message, prefixes)`, `onQuad(error, quad, messageIndex)`,
22
+ // `onPrefix(prefix, term)` and `onVersion(label)`, messages are reported as soon
23
+ // as they are complete. The final call has `null` as message.
24
+ parse(input, callbacks) {
25
+ if (typeof callbacks === 'function')
26
+ callbacks = { onMessage: callbacks };
27
+ const messages = callbacks ? null : [];
28
+ let error = null;
29
+ callbacks = callbacks || {};
30
+ const sink = {
31
+ message: (quads, index) => callbacks.onMessage ? callbacks.onMessage(null, quads, undefined, index) : messages && messages.push(quads),
32
+ end: prefixes => callbacks.onMessage && callbacks.onMessage(null, null, prefixes),
33
+ error: e => {
34
+ error = e;
35
+ callbacks.onMessage && callbacks.onMessage(e);
36
+ callbacks.onQuad && callbacks.onQuad(e);
37
+ },
38
+ };
39
+ if (callbacks.onQuad)
40
+ sink.quad = (quad, index) => callbacks.onQuad(null, quad, index);
41
+ if (callbacks.onPrefix)
42
+ sink.prefix = callbacks.onPrefix;
43
+ if (callbacks.onVersion)
44
+ sink.version = callbacks.onVersion;
45
+ const core = this._createCore(sink);
46
+
47
+ // Parse a string at once
48
+ if (typeof input === 'string') {
49
+ core.write(input);
50
+ core.end();
51
+ if (error && messages)
52
+ throw error;
53
+ return messages || undefined;
54
+ }
55
+
56
+ // Parse a stream chunk by chunk
57
+ const decoder = new StringDecoder('utf8');
58
+ input.on('data', chunk => core.write(typeof chunk === 'string' ? chunk : decoder.write(chunk)));
59
+ input.on('end', () => {
60
+ core.write(decoder.end());
61
+ core.end();
62
+ });
63
+ input.on('error', e => sink.error(e));
64
+ return undefined;
65
+ }
66
+ }
67
+
68
+ // ### `parseMessages` synchronously parses a string into an array of messages
69
+ export function parseMessages(input, options) {
70
+ return new MessageParser(options).parse(input);
71
+ }
@@ -0,0 +1,197 @@
1
+ // **MessageParserCore** is the push-based engine behind all message parsers.
2
+ // It feeds scanned text to the public N3.js parser and groups the resulting
3
+ // quads into messages.
4
+ import { EventEmitter } from 'events';
5
+ import { Parser, DataFactory } from 'n3';
6
+ import { TurtleScanner, LineScanner } from './MessageScanner.js';
7
+ import { resolveFormat, isMessagesVersion, baseVersion, isSupportedVersion } from './formats.js';
8
+
9
+ // Unrecognizable by construction: a document cannot contain it by accident
10
+ function createSentinel() {
11
+ return `rdf-messages-${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
12
+ }
13
+
14
+ export default class MessageParserCore {
15
+ // `sink` receives `message(quads, index)`, `end(prefixes)`, `error(error)`
16
+ // and optionally `quad(quad, messageIndex)`, `prefix(prefix, term)`, `version(label)`
17
+ constructor(options, sink) {
18
+ options = options || {};
19
+ const format = resolveFormat(options.format);
20
+ this._sink = sink;
21
+ this._factory = options.factory || DataFactory;
22
+ this._blankNodePrefix = typeof options.blankNodePrefix === 'string' ?
23
+ options.blankNodePrefix.replace(/^_:/, '') : undefined;
24
+ this._blankNodeCounter = 0;
25
+ this._supportedVersions = Parser.SUPPORTED_VERSIONS;
26
+ this._messages = options.messages === true || isMessagesVersion(options.version);
27
+ this._index = 0;
28
+ this._current = null;
29
+ this._scope = null;
30
+ this._seenQuad = false;
31
+ this._failed = false;
32
+ this._finished = false;
33
+
34
+ const n3Options = {
35
+ format: format.name,
36
+ factory: this._factory,
37
+ // Version labels are validated here, because N3.js does not know `-messages` labels
38
+ parseUnsupportedVersions: true,
39
+ };
40
+ if (options.baseIRI)
41
+ n3Options.baseIRI = options.baseIRI;
42
+ if (options.version) {
43
+ this._checkVersion(options.version, true);
44
+ n3Options.version = baseVersion(options.version);
45
+ }
46
+
47
+ const sentinel = createSentinel();
48
+ this._sentinel = sentinel;
49
+ this._scanner = format.lineMode ? new LineScanner(sentinel) : new TurtleScanner(sentinel);
50
+ this._input = new EventEmitter();
51
+ const callbacks = {
52
+ onQuad: (error, quad, prefixes) => this._onQuad(error, quad, prefixes),
53
+ onPrefix: (prefix, term) => this._failed || sink.prefix && sink.prefix(prefix, term),
54
+ onVersion: label => this._onVersion(label),
55
+ };
56
+ if (format.lineMode)
57
+ callbacks.onComment = comment => this._onComment(comment);
58
+ new Parser(n3Options).parse(this._input, callbacks);
59
+ }
60
+
61
+ // ### `write` accepts the next chunk of text
62
+ write(text) {
63
+ if (!this._failed && !this._finished && text.length > 0)
64
+ this._feed(this._scanner.push(text));
65
+ }
66
+
67
+ // ### `end` signals the end of the input
68
+ end() {
69
+ if (!this._failed && !this._finished) {
70
+ this._feed(this._scanner.end());
71
+ this._input.emit('end');
72
+ }
73
+ }
74
+
75
+ _feed(text) {
76
+ if (text.length > 0)
77
+ this._input.emit('data', text);
78
+ }
79
+
80
+ _onQuad(error, quad, prefixes) {
81
+ if (this._failed || this._finished)
82
+ return;
83
+ if (error)
84
+ this._fail(error);
85
+ else if (quad)
86
+ this._addQuad(quad);
87
+ else {
88
+ this._finished = true;
89
+ if (this._current !== null)
90
+ this._emitMessage();
91
+ this._sink.end(prefixes);
92
+ }
93
+ }
94
+
95
+ _addQuad(quad) {
96
+ this._seenQuad = true;
97
+ // Blank node labels are scoped to their message
98
+ if (this._messages)
99
+ quad = this._scopeQuad(quad);
100
+ if (this._current === null)
101
+ this._current = [];
102
+ this._current.push(quad);
103
+ if (this._sink.quad)
104
+ this._sink.quad(quad, this._index);
105
+ }
106
+
107
+ _onVersion(label) {
108
+ if (this._failed)
109
+ return;
110
+ if (label === this._sentinel)
111
+ this._delimiter(this._scanner.delimiterLines.shift());
112
+ else
113
+ this._announce(label);
114
+ }
115
+
116
+ // Line mode reports delimiters and versions as comments
117
+ _onComment(comment) {
118
+ if (this._failed || !comment.startsWith(this._sentinel))
119
+ return;
120
+ const rest = comment.substring(this._sentinel.length);
121
+ if (rest[0] === 'M')
122
+ this._delimiter(Number(rest.substring(1)));
123
+ else
124
+ this._announce(rest.substring(1));
125
+ }
126
+
127
+ _announce(label) {
128
+ const error = this._checkVersion(label);
129
+ if (error)
130
+ return this._fail(error);
131
+ if (isMessagesVersion(label) && !this._messages) {
132
+ if (this._seenQuad)
133
+ return this._fail(new Error(`Version "${label}" must be announced before the first statement.`));
134
+ this._messages = true;
135
+ }
136
+ if (this._sink.version)
137
+ this._sink.version(label);
138
+ }
139
+
140
+ // Validates a version label, and returns an error for unsupported ones
141
+ _checkVersion(label, throwing) {
142
+ if (isSupportedVersion(baseVersion(label), this._supportedVersions))
143
+ return null;
144
+ const error = new Error(`Detected unsupported version: "${label}"`);
145
+ if (throwing)
146
+ throw error;
147
+ return error;
148
+ }
149
+
150
+ _delimiter(line) {
151
+ if (!this._messages) {
152
+ return this._fail(new Error(`Unexpected "MESSAGE" on line ${line}: the document did not announce ` +
153
+ 'a version with the "-messages" suffix.'));
154
+ }
155
+ this._emitMessage();
156
+ }
157
+
158
+ // Finalizes the current message, which is empty if no quad was seen
159
+ _emitMessage() {
160
+ const quads = this._current || [];
161
+ this._current = null;
162
+ this._scope = null;
163
+ this._sink.message(quads, this._index++);
164
+ }
165
+
166
+ _fail(error) {
167
+ this._failed = true;
168
+ this._current = null;
169
+ this._sink.error(error);
170
+ }
171
+
172
+ // ## Blank node scoping
173
+
174
+ _scopeQuad(quad) {
175
+ const subject = this._scopeTerm(quad.subject);
176
+ const predicate = this._scopeTerm(quad.predicate);
177
+ const object = this._scopeTerm(quad.object);
178
+ const graph = this._scopeTerm(quad.graph);
179
+ return subject === quad.subject && predicate === quad.predicate &&
180
+ object === quad.object && graph === quad.graph ?
181
+ quad : this._factory.quad(subject, predicate, object, graph);
182
+ }
183
+
184
+ _scopeTerm(term) {
185
+ if (term.termType === 'BlankNode') {
186
+ const scope = this._scope || (this._scope = new Map());
187
+ let scoped = scope.get(term.value);
188
+ if (!scoped) {
189
+ scoped = this._blankNodePrefix === undefined ? this._factory.blankNode() :
190
+ this._factory.blankNode(`${this._blankNodePrefix}${this._blankNodeCounter++}`);
191
+ scope.set(term.value, scoped);
192
+ }
193
+ return scoped;
194
+ }
195
+ return term.termType === 'Quad' ? this._scopeQuad(term) : term;
196
+ }
197
+ }