@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 +152 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/plugin.d.ts +18 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +88 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/package.json +39 -0
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
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -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
package/dist/plugin.d.ts
ADDED
|
@@ -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
|
+
});
|
package/dist/types.d.ts
ADDED
|
@@ -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
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
|
+
}
|