@lde/fastify-rdf 0.2.11 → 0.3.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/README.md +29 -4
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +67 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -43,8 +43,8 @@ app.get('/resource', async (request, reply) => {
|
|
|
43
43
|
quad(
|
|
44
44
|
namedNode('http://example.org/subject'),
|
|
45
45
|
namedNode('http://example.org/predicate'),
|
|
46
|
-
literal('object')
|
|
47
|
-
)
|
|
46
|
+
literal('object'),
|
|
47
|
+
),
|
|
48
48
|
);
|
|
49
49
|
return reply.sendRdf(store);
|
|
50
50
|
});
|
|
@@ -66,13 +66,32 @@ app.get('/resource', async () => {
|
|
|
66
66
|
quad(
|
|
67
67
|
namedNode('http://example.org/subject'),
|
|
68
68
|
namedNode('http://example.org/predicate'),
|
|
69
|
-
literal('object')
|
|
70
|
-
)
|
|
69
|
+
literal('object'),
|
|
70
|
+
),
|
|
71
71
|
);
|
|
72
72
|
return store;
|
|
73
73
|
});
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
### Parsing RDF Request Bodies
|
|
77
|
+
|
|
78
|
+
The plugin registers content type parsers for all RDF formats supported by [rdf-parse](https://github.com/rubensworks/rdf-parse.js). Individual routes opt in via `config: { parseRdf: true }` — the body is then parsed into a `DatasetCore`:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
app.post('/data', { config: { parseRdf: true } }, async (request) => {
|
|
82
|
+
const dataset = request.body as DatasetCore; // N3 Store
|
|
83
|
+
console.log(`Received ${dataset.size} quads`);
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
To parse RDF bodies on **all** routes, enable `parseRdf` at the plugin level:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
await app.register(fastifyRdf, { parseRdf: true });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Routes without per-route or plugin-level `parseRdf` get JSON fallback for `application/ld+json` (parsed as plain JSON) and 415 Unsupported Media Type for other RDF content types.
|
|
94
|
+
|
|
76
95
|
### Custom Default Content Type
|
|
77
96
|
|
|
78
97
|
By default, the plugin uses `text/turtle` when no `Accept` header is provided. You can change this:
|
|
@@ -126,6 +145,12 @@ interface FastifyRdfOptions {
|
|
|
126
145
|
* @default false
|
|
127
146
|
*/
|
|
128
147
|
overrideSend?: boolean;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Parse RDF request bodies into a DatasetCore on all routes.
|
|
151
|
+
* @default false
|
|
152
|
+
*/
|
|
153
|
+
parseRdf?: boolean;
|
|
129
154
|
}
|
|
130
155
|
```
|
|
131
156
|
|
package/dist/plugin.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { type FastifyRdfOptions } from './types.js';
|
|
|
8
8
|
* - Optionally overrides reply.send() to serialize all responses as RDF
|
|
9
9
|
* - Handles content negotiation via Accept headers
|
|
10
10
|
* - Defaults to Turtle when no Accept header is provided
|
|
11
|
+
* - Registers content type parsers for RDF request bodies
|
|
11
12
|
*/
|
|
12
13
|
declare function fastifyRdfPlugin(server: FastifyInstance, options: FastifyRdfOptions): Promise<void>;
|
|
13
14
|
/**
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAQ7E,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,YAAY,CAAC;AA4HpB;;;;;;;;;GASG;AACH,iBAAe,gBAAgB,CAC7B,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA4Df;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,yBAGrB,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { fastifyPlugin } from 'fastify-plugin';
|
|
2
2
|
import { fastifyAccepts } from '@fastify/accepts';
|
|
3
3
|
import { rdfSerializer } from 'rdf-serialize';
|
|
4
|
+
import { rdfParser } from 'rdf-parse';
|
|
5
|
+
import { Store } from 'n3';
|
|
4
6
|
import { Readable } from 'node:stream';
|
|
5
7
|
import { DEFAULT_CONTENT_TYPE, } from './types.js';
|
|
6
8
|
/**
|
|
@@ -35,6 +37,69 @@ function negotiateContentType(request, supportedTypes, defaultType) {
|
|
|
35
37
|
}
|
|
36
38
|
return (request.accepts().type(supportedTypes) || defaultType);
|
|
37
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Collect quads from a readable stream into an N3 Store.
|
|
42
|
+
*/
|
|
43
|
+
async function streamToDataset(stream) {
|
|
44
|
+
const store = new Store();
|
|
45
|
+
for await (const quad of stream) {
|
|
46
|
+
store.add(quad);
|
|
47
|
+
}
|
|
48
|
+
return store;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Register Fastify content type parsers for all RDF formats.
|
|
52
|
+
*
|
|
53
|
+
* When `parseAll` is true (from the plugin-level `parseRdf` option), every
|
|
54
|
+
* route gets RDF body parsing. Otherwise, routes opt in individually via
|
|
55
|
+
* `config: { parseRdf: true }`. Non-opted-in routes get JSON fallback for
|
|
56
|
+
* `application/ld+json` and 415 for other RDF types.
|
|
57
|
+
*/
|
|
58
|
+
async function registerRdfParsers(server, parseAll) {
|
|
59
|
+
const contentTypes = (await rdfParser.getContentTypes()).filter((type) => type !== 'application/json');
|
|
60
|
+
server.addContentTypeParser(contentTypes, function (_request, payload, done) {
|
|
61
|
+
// Collect the raw body; the preParsing hook is not needed because
|
|
62
|
+
// Fastify passes the raw payload stream here.
|
|
63
|
+
const chunks = [];
|
|
64
|
+
payload.on('data', (chunk) => chunks.push(chunk));
|
|
65
|
+
payload.on('end', () => done(null, Buffer.concat(chunks)));
|
|
66
|
+
payload.on('error', done);
|
|
67
|
+
});
|
|
68
|
+
server.addHook('preHandler', async (request) => {
|
|
69
|
+
// Only act on requests that matched an RDF content type parser.
|
|
70
|
+
if (!request.body ||
|
|
71
|
+
!Buffer.isBuffer(request.body) ||
|
|
72
|
+
!request.headers['content-type']) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const contentType = request.headers['content-type'].split(';')[0].trim();
|
|
76
|
+
if (!contentTypes.includes(contentType)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (parseAll || request.routeOptions.config.parseRdf) {
|
|
80
|
+
try {
|
|
81
|
+
const bodyStream = Readable.from(request.body);
|
|
82
|
+
const quadStream = rdfParser.parse(bodyStream, { contentType });
|
|
83
|
+
request.body = await streamToDataset(quadStream);
|
|
84
|
+
}
|
|
85
|
+
catch (cause) {
|
|
86
|
+
const error = new Error('Invalid RDF body', {
|
|
87
|
+
cause,
|
|
88
|
+
});
|
|
89
|
+
error.statusCode = 400;
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (contentType === 'application/ld+json') {
|
|
94
|
+
request.body = JSON.parse(request.body.toString('utf8'));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const error = new Error(`Unsupported Media Type: ${contentType}`);
|
|
98
|
+
error.statusCode = 415;
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
38
103
|
/**
|
|
39
104
|
* Fastify plugin for serving RDF data with content negotiation.
|
|
40
105
|
*
|
|
@@ -43,6 +108,7 @@ function negotiateContentType(request, supportedTypes, defaultType) {
|
|
|
43
108
|
* - Optionally overrides reply.send() to serialize all responses as RDF
|
|
44
109
|
* - Handles content negotiation via Accept headers
|
|
45
110
|
* - Defaults to Turtle when no Accept header is provided
|
|
111
|
+
* - Registers content type parsers for RDF request bodies
|
|
46
112
|
*/
|
|
47
113
|
async function fastifyRdfPlugin(server, options) {
|
|
48
114
|
const defaultContentType = options.defaultContentType ?? DEFAULT_CONTENT_TYPE;
|
|
@@ -78,6 +144,7 @@ async function fastifyRdfPlugin(server, options) {
|
|
|
78
144
|
return this.send(serialized);
|
|
79
145
|
});
|
|
80
146
|
}
|
|
147
|
+
await registerRdfParsers(server, options.parseRdf ?? false);
|
|
81
148
|
}
|
|
82
149
|
/**
|
|
83
150
|
* Fastify plugin for serving RDF data with content negotiation.
|
package/dist/types.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export interface FastifyRdfOptions {
|
|
|
19
19
|
* @default false
|
|
20
20
|
*/
|
|
21
21
|
overrideSend?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Parse RDF request bodies into a DatasetCore on all routes.
|
|
24
|
+
* When not set, individual routes can opt in via `config: { parseRdf: true }`.
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
parseRdf?: boolean;
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
24
30
|
* RDF data that can be serialized: either a DatasetCore or an RDF.js Stream of quads.
|
|
@@ -33,5 +39,8 @@ declare module 'fastify' {
|
|
|
33
39
|
*/
|
|
34
40
|
sendRdf(data: RdfData): RdfData | Promise<FastifyReply>;
|
|
35
41
|
}
|
|
42
|
+
interface FastifyContextConfig {
|
|
43
|
+
parseRdf?: boolean;
|
|
44
|
+
}
|
|
36
45
|
}
|
|
37
46
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAExD;;GAEG;AACH,eAAO,MAAM,oBAAoB,gBAAgB,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAExD;;GAEG;AACH,eAAO,MAAM,oBAAoB,gBAAgB,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;AAE3C,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,YAAY;QACpB;;;;WAIG;QACH,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;KACzD;IAED,UAAU,oBAAoB;QAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB;CACF"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/fastify-rdf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Fastify plugin for serving RDF data with content negotiation",
|
|
5
5
|
"repository": {
|
|
6
|
-
"url": "https://github.com/ldengine/lde",
|
|
6
|
+
"url": "git+https://github.com/ldengine/lde.git",
|
|
7
7
|
"directory": "packages/fastify-rdf"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
@@ -26,13 +26,14 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@fastify/accepts": "^5.0.0",
|
|
28
28
|
"fastify-plugin": "^5.0.0",
|
|
29
|
+
"n3": "^2.0.1",
|
|
30
|
+
"rdf-parse": "^5.0.0",
|
|
29
31
|
"rdf-serialize": "^5.1.0",
|
|
30
32
|
"tslib": "^2.3.0"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|
|
33
35
|
"@rdfjs/types": "^2.0.0",
|
|
34
|
-
"fastify": "^5.7.4"
|
|
35
|
-
"n3": "^1.17.0"
|
|
36
|
+
"fastify": "^5.7.4"
|
|
36
37
|
},
|
|
37
38
|
"peerDependencies": {
|
|
38
39
|
"fastify": "^5.7.4"
|