@lde/fastify-rdf 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/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # Fastify RDF
2
+
3
+ Fastify plugin for serving RDF data with automatic content negotiation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lde/fastify-rdf
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - Content negotiation for all RDF serialisation formats supported by [rdf-serialize](https://github.com/rubensworks/rdf-serialize.js)
14
+ - Handles `Accept` headers to serve data in the requested RDF format (Turtle, N-Triples, JSON-LD, etc.)
15
+ - Defaults to Turtle when no `Accept` header is provided
16
+ - Provides a `reply.sendRdf()` decorator for explicit RDF responses
17
+ - Optional `overrideSend` mode to automatically serialise all responses as RDF
18
+
19
+ ## Usage
20
+
21
+ ### Basic Setup
22
+
23
+ ```typescript
24
+ import fastify from 'fastify';
25
+ import fastifyRdf from '@lde/fastify-rdf';
26
+
27
+ const app = fastify();
28
+ await app.register(fastifyRdf);
29
+ ```
30
+
31
+ ### Explicit RDF Responses with `reply.sendRdf()`
32
+
33
+ Use `reply.sendRdf()` to send RDF data with content negotiation:
34
+
35
+ ```typescript
36
+ import { Store, DataFactory } from 'n3';
37
+
38
+ const { namedNode, literal, quad } = DataFactory;
39
+
40
+ app.get('/resource', async (request, reply) => {
41
+ const store = new Store();
42
+ store.add(
43
+ quad(
44
+ namedNode('http://example.org/subject'),
45
+ namedNode('http://example.org/predicate'),
46
+ literal('object')
47
+ )
48
+ );
49
+ return reply.sendRdf(store);
50
+ });
51
+ ```
52
+
53
+ ### Automatic Serialisation with `overrideSend`
54
+
55
+ Enable `overrideSend` to automatically serialise all responses as RDF. This is useful for APIs that exclusively serve RDF data:
56
+
57
+ ```typescript
58
+ await app.register(fastifyRdf, {
59
+ overrideSend: true,
60
+ });
61
+
62
+ // Simply return DatasetCore or Stream - they will be serialised automatically
63
+ app.get('/resource', async () => {
64
+ const store = new Store();
65
+ store.add(
66
+ quad(
67
+ namedNode('http://example.org/subject'),
68
+ namedNode('http://example.org/predicate'),
69
+ literal('object')
70
+ )
71
+ );
72
+ return store;
73
+ });
74
+ ```
75
+
76
+ ### Custom Default Content Type
77
+
78
+ By default, the plugin uses `text/turtle` when no `Accept` header is provided. You can change this:
79
+
80
+ ```typescript
81
+ await app.register(fastifyRdf, {
82
+ defaultContentType: 'application/n-triples',
83
+ });
84
+ ```
85
+
86
+ ## Content Negotiation
87
+
88
+ The plugin supports all content types provided by [rdf-serialize](https://github.com/rubensworks/rdf-serialize.js), including:
89
+
90
+ - `text/turtle` (Turtle)
91
+ - `application/n-triples` (N-Triples)
92
+ - `application/n-quads` (N-Quads)
93
+ - `application/ld+json` (JSON-LD)
94
+ - `application/rdf+xml` (RDF/XML)
95
+ - And more...
96
+
97
+ Clients can request their preferred format via the `Accept` header:
98
+
99
+ ```bash
100
+ # Request Turtle
101
+ curl -H "Accept: text/turtle" http://localhost:3000/resource
102
+
103
+ # Request N-Triples
104
+ curl -H "Accept: application/n-triples" http://localhost:3000/resource
105
+
106
+ # No Accept header - defaults to Turtle
107
+ curl http://localhost:3000/resource
108
+ ```
109
+
110
+ ## API
111
+
112
+ ### Plugin Options
113
+
114
+ ```typescript
115
+ interface FastifyRdfOptions {
116
+ /**
117
+ * Default content type when no Accept header is provided.
118
+ * @default 'text/turtle'
119
+ */
120
+ defaultContentType?: string;
121
+
122
+ /**
123
+ * Override reply.send() to serialise all responses as RDF.
124
+ * When enabled, all payloads returned from route handlers will be
125
+ * serialised as RDF without type checking.
126
+ * @default false
127
+ */
128
+ overrideSend?: boolean;
129
+ }
130
+ ```
131
+
132
+ ### Supported Data Types
133
+
134
+ The plugin accepts the following RDF data types:
135
+
136
+ - **DatasetCore**: RDF.js dataset (e.g., from n3 `Store`)
137
+ - **Stream**: RDF.js quad stream (e.g., from parsers)
138
+
139
+ ```typescript
140
+ import type { RdfData, FastifyRdfOptions } from '@lde/fastify-rdf';
141
+ ```
142
+
143
+ ## TypeScript
144
+
145
+ The plugin includes full TypeScript support with type augmentation for Fastify's reply object:
146
+
147
+ ```typescript
148
+ // reply.sendRdf() is automatically typed
149
+ app.get('/data', async (request, reply) => {
150
+ return reply.sendRdf(dataset);
151
+ });
152
+ ```
@@ -0,0 +1,4 @@
1
+ export { fastifyRdf } from './plugin.js';
2
+ export { DEFAULT_CONTENT_TYPE, type FastifyRdfOptions, type RdfData } from './types.js';
3
+ export { fastifyRdf as default } from './plugin.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,KAAK,iBAAiB,EAAE,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,UAAU,IAAI,OAAO,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { fastifyRdf } from './plugin.js';
2
+ export { DEFAULT_CONTENT_TYPE } from './types.js';
3
+ export { fastifyRdf as default } from './plugin.js';
@@ -0,0 +1,18 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import { type FastifyRdfOptions } from './types.js';
3
+ /**
4
+ * Fastify plugin for serving RDF data with content negotiation.
5
+ *
6
+ * This plugin:
7
+ * - Adds a reply.sendRdf() decorator for sending RDF data
8
+ * - Optionally overrides reply.send() to serialize all responses as RDF
9
+ * - Handles content negotiation via Accept headers
10
+ * - Defaults to Turtle when no Accept header is provided
11
+ */
12
+ declare function fastifyRdfPlugin(server: FastifyInstance, options: FastifyRdfOptions): Promise<void>;
13
+ /**
14
+ * Fastify plugin for serving RDF data with content negotiation.
15
+ */
16
+ export declare const fastifyRdf: typeof fastifyRdfPlugin;
17
+ export {};
18
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAM7E,OAAO,EAAwB,KAAK,iBAAiB,EAAgB,MAAM,YAAY,CAAC;AA+CxF;;;;;;;;GAQG;AACH,iBAAe,gBAAgB,CAC7B,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA0Df;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,yBAGrB,CAAC"}
package/dist/plugin.js ADDED
@@ -0,0 +1,88 @@
1
+ import { fastifyPlugin } from 'fastify-plugin';
2
+ import { fastifyAccepts } from '@fastify/accepts';
3
+ import { rdfSerializer } from 'rdf-serialize';
4
+ import { Readable } from 'node:stream';
5
+ import { DEFAULT_CONTENT_TYPE } from './types.js';
6
+ /**
7
+ * Collect a readable stream into a string.
8
+ */
9
+ async function streamToString(stream) {
10
+ const chunks = [];
11
+ for await (const chunk of stream) {
12
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
13
+ }
14
+ return Buffer.concat(chunks).toString('utf8');
15
+ }
16
+ /**
17
+ * Serialize RDF data to a string for the given content type.
18
+ */
19
+ async function serializeRdfToString(data, contentType) {
20
+ const stream = typeof data[Symbol.iterator] === 'function'
21
+ ? Readable.from(data)
22
+ : data;
23
+ const outputStream = rdfSerializer.serialize(stream, {
24
+ contentType,
25
+ });
26
+ return streamToString(outputStream);
27
+ }
28
+ /**
29
+ * Find the best matching content type from supported types based on Accept header.
30
+ */
31
+ function negotiateContentType(request, supportedTypes, defaultType) {
32
+ const acceptHeader = request.headers.accept;
33
+ if (!acceptHeader || acceptHeader === '*/*') {
34
+ return defaultType;
35
+ }
36
+ return (request.accepts().type(supportedTypes) || defaultType);
37
+ }
38
+ /**
39
+ * Fastify plugin for serving RDF data with content negotiation.
40
+ *
41
+ * This plugin:
42
+ * - Adds a reply.sendRdf() decorator for sending RDF data
43
+ * - Optionally overrides reply.send() to serialize all responses as RDF
44
+ * - Handles content negotiation via Accept headers
45
+ * - Defaults to Turtle when no Accept header is provided
46
+ */
47
+ async function fastifyRdfPlugin(server, options) {
48
+ const defaultContentType = options.defaultContentType ?? DEFAULT_CONTENT_TYPE;
49
+ const supportedContentTypes = await rdfSerializer.getContentTypes();
50
+ await server.register(fastifyAccepts);
51
+ if (options.overrideSend) {
52
+ // Serialize all responses as RDF via hooks
53
+ server.addHook('preSerialization', async (request, reply, payload) => {
54
+ const contentType = negotiateContentType(request, supportedContentTypes, defaultContentType);
55
+ reply.type(contentType);
56
+ return serializeRdfToString(payload, contentType);
57
+ });
58
+ server.addHook('onSend', async (request, reply, payload) => {
59
+ // Handle streams (preSerialization doesn't run for streams)
60
+ if (payload !== null && typeof payload !== 'string') {
61
+ const contentType = negotiateContentType(request, supportedContentTypes, defaultContentType);
62
+ reply.type(contentType);
63
+ return serializeRdfToString(payload, contentType);
64
+ }
65
+ return payload;
66
+ });
67
+ // sendRdf just returns data - hooks handle serialization
68
+ server.decorateReply('sendRdf', function (data) {
69
+ return data;
70
+ });
71
+ }
72
+ else {
73
+ // Manual serialization via sendRdf() only
74
+ server.decorateReply('sendRdf', async function (data) {
75
+ const contentType = negotiateContentType(this.request, supportedContentTypes, defaultContentType);
76
+ this.type(contentType);
77
+ const serialized = await serializeRdfToString(data, contentType);
78
+ return this.send(serialized);
79
+ });
80
+ }
81
+ }
82
+ /**
83
+ * Fastify plugin for serving RDF data with content negotiation.
84
+ */
85
+ export const fastifyRdf = fastifyPlugin(fastifyRdfPlugin, {
86
+ fastify: '5.x',
87
+ name: '@lde/fastify-rdf',
88
+ });
@@ -0,0 +1,37 @@
1
+ import type { DatasetCore, Stream } from '@rdfjs/types';
2
+ /**
3
+ * Default content type when no Accept header is provided.
4
+ */
5
+ export declare const DEFAULT_CONTENT_TYPE = "text/turtle";
6
+ /**
7
+ * Options for the fastify-rdf plugin.
8
+ */
9
+ export interface FastifyRdfOptions {
10
+ /**
11
+ * Default content type when no Accept header is provided.
12
+ * @default 'text/turtle'
13
+ */
14
+ defaultContentType?: string;
15
+ /**
16
+ * Override reply.send() to serialize all responses as RDF.
17
+ * When enabled, all payloads returned from route handlers will be
18
+ * serialized as RDF.
19
+ * @default false
20
+ */
21
+ overrideSend?: boolean;
22
+ }
23
+ /**
24
+ * RDF data that can be serialized: either a DatasetCore or an RDF.js Stream of quads.
25
+ */
26
+ export type RdfData = DatasetCore | Stream;
27
+ declare module 'fastify' {
28
+ interface FastifyReply {
29
+ /**
30
+ * Send RDF data with content negotiation based on Accept header.
31
+ * @param data - RDF DatasetCore or Stream to serialize
32
+ * @returns The data (when overrideSend is enabled) or Promise<FastifyReply>
33
+ */
34
+ sendRdf(data: RdfData): RdfData | Promise<FastifyReply>;
35
+ }
36
+ }
37
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +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;CACxB;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;CACF"}
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Default content type when no Accept header is provided.
3
+ */
4
+ export const DEFAULT_CONTENT_TYPE = 'text/turtle';
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@lde/fastify-rdf",
3
+ "version": "0.1.0",
4
+ "description": "Fastify plugin for serving RDF data with content negotiation",
5
+ "repository": {
6
+ "url": "https://github.com/ldengine/lde"
7
+ },
8
+ "type": "module",
9
+ "exports": {
10
+ "./package.json": "./package.json",
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "development": "./src/index.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "main": "./dist/index.js",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "files": [
22
+ "dist",
23
+ "!**/*.tsbuildinfo"
24
+ ],
25
+ "dependencies": {
26
+ "@fastify/accepts": "^5.0.0",
27
+ "fastify-plugin": "^5.0.0",
28
+ "rdf-serialize": "^5.0.0",
29
+ "tslib": "^2.3.0"
30
+ },
31
+ "devDependencies": {
32
+ "@rdfjs/types": "^2.0.0",
33
+ "fastify": "^5.0.0",
34
+ "n3": "^1.17.0"
35
+ },
36
+ "peerDependencies": {
37
+ "fastify": "^5.0.0"
38
+ }
39
+ }