@twin.org/api-server-fastify 0.0.1-next.9 → 0.0.2-next.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.
@@ -3,10 +3,24 @@
3
3
  var FastifyCompress = require('@fastify/compress');
4
4
  var FastifyCors = require('@fastify/cors');
5
5
  var apiModels = require('@twin.org/api-models');
6
+ var apiProcessors = require('@twin.org/api-processors');
6
7
  var core = require('@twin.org/core');
7
8
  var loggingModels = require('@twin.org/logging-models');
8
9
  var web = require('@twin.org/web');
9
10
  var Fastify = require('fastify');
11
+ var fp = require('fastify-plugin');
12
+ var socket_io = require('socket.io');
13
+
14
+ // This is a clone of fastify-socket.io which runs with recent fastify versions.
15
+ const fastifySocketIO = fp(async (fastify, opts) => {
16
+ const ioServer = new socket_io.Server(fastify.server, opts);
17
+ fastify.decorate("io", ioServer);
18
+ fastify.addHook("preClose", done => {
19
+ ioServer.disconnectSockets();
20
+ ioServer.close();
21
+ done();
22
+ });
23
+ }, { fastify: ">=5.x.x", name: "socket.io" });
10
24
 
11
25
  // Copyright 2024 IOTA Stiftung.
12
26
  // SPDX-License-Identifier: Apache-2.0.
@@ -48,23 +62,49 @@ class FastifyWebServer {
48
62
  * @internal
49
63
  */
50
64
  _fastify;
65
+ /**
66
+ * The options for the socket server.
67
+ * @internal
68
+ */
69
+ _socketConfig;
51
70
  /**
52
71
  * Whether the server has been started.
53
72
  * @internal
54
73
  */
55
74
  _started;
75
+ /**
76
+ * The mime type processors.
77
+ * @internal
78
+ */
79
+ _mimeTypeProcessors;
80
+ /**
81
+ * Include the stack with errors.
82
+ * @internal
83
+ */
84
+ _includeErrorStack;
56
85
  /**
57
86
  * Create a new instance of FastifyWebServer.
58
87
  * @param options The options for the server.
59
- * @param options.loggingConnectorType The type of the logging connector to use, if undefined, no logging will happen.
60
- * @param options.config Additional options for the Fastify server.
61
88
  */
62
89
  constructor(options) {
63
90
  this._loggingConnector = core.Is.stringValue(options?.loggingConnectorType)
64
91
  ? loggingModels.LoggingConnectorFactory.get(options.loggingConnectorType)
65
92
  : undefined;
66
- this._fastify = Fastify({ maxParamLength: 2000, ...options?.config });
93
+ this._fastify = Fastify({
94
+ maxParamLength: 2000,
95
+ ...options?.config?.web
96
+ });
97
+ this._socketConfig = {
98
+ path: "/socket",
99
+ ...options?.config?.socket
100
+ };
67
101
  this._started = false;
102
+ this._mimeTypeProcessors = options?.mimeTypeProcessors ?? [];
103
+ const hasJsonLd = this._mimeTypeProcessors.find(processor => processor.CLASS_NAME === "json-ld");
104
+ if (!hasJsonLd) {
105
+ this._mimeTypeProcessors.push(new apiProcessors.JsonLdMimeTypeProcessor());
106
+ }
107
+ this._includeErrorStack = options?.config?.includeErrorStack ?? false;
68
108
  }
69
109
  /**
70
110
  * Get the web server instance.
@@ -75,14 +115,19 @@ class FastifyWebServer {
75
115
  }
76
116
  /**
77
117
  * Build the server.
78
- * @param restRouteProcessors The hooks to process the incoming requests.
118
+ * @param restRouteProcessors The processors for incoming requests over REST.
79
119
  * @param restRoutes The REST routes.
120
+ * @param socketRouteProcessors The processors for incoming requests over Sockets.
121
+ * @param socketRoutes The socket routes.
80
122
  * @param options Options for building the server.
81
123
  * @returns Nothing.
82
124
  */
83
- async build(restRouteProcessors, restRoutes, options) {
84
- if (!core.Is.arrayValue(restRouteProcessors)) {
85
- throw new core.GeneralError(this.CLASS_NAME, "noProcessors");
125
+ async build(restRouteProcessors, restRoutes, socketRouteProcessors, socketRoutes, options) {
126
+ if (core.Is.arrayValue(restRoutes) && !core.Is.arrayValue(restRouteProcessors)) {
127
+ throw new core.GeneralError(this.CLASS_NAME, "noRestProcessors");
128
+ }
129
+ if (core.Is.arrayValue(socketRoutes) && !core.Is.arrayValue(socketRouteProcessors)) {
130
+ throw new core.GeneralError(this.CLASS_NAME, "noSocketProcessors");
86
131
  }
87
132
  await this._loggingConnector?.log({
88
133
  level: "info",
@@ -92,8 +137,25 @@ class FastifyWebServer {
92
137
  });
93
138
  this._options = options;
94
139
  await this._fastify.register(FastifyCompress);
140
+ if (core.Is.arrayValue(socketRoutes)) {
141
+ await this._fastify.register(fastifySocketIO, this._socketConfig);
142
+ }
143
+ if (core.Is.arrayValue(this._mimeTypeProcessors)) {
144
+ for (const contentTypeHandler of this._mimeTypeProcessors) {
145
+ this._fastify.addContentTypeParser(contentTypeHandler.getTypes(), { parseAs: "buffer" }, (request, body, done) => {
146
+ // Fastify does not handle this method correctly if it is async
147
+ // so we have to use the callback method
148
+ contentTypeHandler
149
+ .handle(body)
150
+ // eslint-disable-next-line promise/prefer-await-to-then, promise/no-callback-in-promise
151
+ .then(processed => done(null, processed))
152
+ // eslint-disable-next-line promise/prefer-await-to-then, promise/no-callback-in-promise
153
+ .catch(err => done(core.BaseError.fromError(err)));
154
+ });
155
+ }
156
+ }
95
157
  await this.initCors(options);
96
- this._fastify.setNotFoundHandler({}, async (request, reply) => this.handleRequest(restRouteProcessors, request, reply));
158
+ this._fastify.setNotFoundHandler({}, async (request, reply) => this.handleRequestRest(restRouteProcessors ?? [], request, reply));
97
159
  this._fastify.setErrorHandler(async (error, request, reply) => {
98
160
  // If code property is set this is a fastify error
99
161
  // otherwise it's from our framework
@@ -123,22 +185,8 @@ class FastifyWebServer {
123
185
  error: err
124
186
  });
125
187
  });
126
- // Add the routes to the server.
127
- for (const restRoute of restRoutes) {
128
- let path = core.StringHelper.trimTrailingSlashes(restRoute.path);
129
- if (!path.startsWith("/")) {
130
- path = `/${path}`;
131
- }
132
- await this._loggingConnector?.log({
133
- level: "info",
134
- ts: Date.now(),
135
- source: this.CLASS_NAME,
136
- message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.restRouteAdded`,
137
- data: { route: path, method: restRoute.method }
138
- });
139
- const method = restRoute.method.toLowerCase();
140
- this._fastify[method](path, async (request, reply) => this.handleRequest(restRouteProcessors, request, reply, restRoute));
141
- }
188
+ await this.addRoutesRest(restRouteProcessors, restRoutes);
189
+ await this.addRoutesSocket(socketRouteProcessors, socketRoutes);
142
190
  }
143
191
  /**
144
192
  * Start the server.
@@ -203,14 +251,109 @@ class FastifyWebServer {
203
251
  }
204
252
  }
205
253
  /**
206
- * Handle the incoming request.
254
+ * Add the REST routes to the server.
255
+ * @param restRouteProcessors The processors for the incoming requests.
256
+ * @param restRoutes The REST routes to add.
257
+ * @internal
258
+ */
259
+ async addRoutesRest(restRouteProcessors, restRoutes) {
260
+ if (core.Is.arrayValue(restRouteProcessors) && core.Is.arrayValue(restRoutes)) {
261
+ for (const restRoute of restRoutes) {
262
+ let path = core.StringHelper.trimTrailingSlashes(restRoute.path);
263
+ if (!path.startsWith("/")) {
264
+ path = `/${path}`;
265
+ }
266
+ await this._loggingConnector?.log({
267
+ level: "info",
268
+ ts: Date.now(),
269
+ source: this.CLASS_NAME,
270
+ message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.restRouteAdded`,
271
+ data: { route: path, method: restRoute.method }
272
+ });
273
+ const method = restRoute.method.toLowerCase();
274
+ this._fastify[method](path, async (request, reply) => this.handleRequestRest(restRouteProcessors, request, reply, restRoute));
275
+ }
276
+ }
277
+ }
278
+ /**
279
+ * Add the socket routes to the server.
280
+ * @param socketRouteProcessors The processors for the incoming requests.
281
+ * @param socketRoutes The socket routes to add.
282
+ * @internal
283
+ */
284
+ async addRoutesSocket(socketRouteProcessors, socketRoutes) {
285
+ if (core.Is.arrayValue(socketRouteProcessors) && core.Is.arrayValue(socketRoutes)) {
286
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
287
+ const io = this._fastify.io;
288
+ for (const socketRoute of socketRoutes) {
289
+ const path = core.StringHelper.trimLeadingSlashes(core.StringHelper.trimTrailingSlashes(socketRoute.path));
290
+ const pathParts = path.split("/");
291
+ await this._loggingConnector?.log({
292
+ level: "info",
293
+ ts: Date.now(),
294
+ source: this.CLASS_NAME,
295
+ message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.socketRouteAdded`,
296
+ data: { route: `/${path}` }
297
+ });
298
+ const socketNamespace = io.of(`/${pathParts[0]}`);
299
+ const topic = pathParts.slice(1).join("/");
300
+ socketNamespace.on("connection", async (socket) => {
301
+ const httpServerRequest = {
302
+ method: web.HttpMethod.GET,
303
+ url: socket.handshake.url,
304
+ query: socket.handshake.query,
305
+ headers: socket.handshake.headers
306
+ };
307
+ // Pass the connected information on to any processors
308
+ try {
309
+ const processorState = {
310
+ socketId: socket.id
311
+ };
312
+ for (const socketRouteProcessor of socketRouteProcessors) {
313
+ if (core.Is.function(socketRouteProcessor.connected)) {
314
+ await socketRouteProcessor.connected(httpServerRequest, socketRoute, processorState);
315
+ }
316
+ }
317
+ }
318
+ catch (err) {
319
+ const { error, httpStatusCode } = apiModels.HttpErrorHelper.processError(err, this._includeErrorStack);
320
+ const response = {};
321
+ apiModels.HttpErrorHelper.buildResponse(response, error, httpStatusCode);
322
+ socket.emit(topic, response);
323
+ }
324
+ socket.on("disconnect", async () => {
325
+ try {
326
+ const processorState = {
327
+ socketId: socket.id
328
+ };
329
+ // The socket disconnected so notify any processors
330
+ for (const socketRouteProcessor of socketRouteProcessors) {
331
+ if (core.Is.function(socketRouteProcessor.disconnected)) {
332
+ await socketRouteProcessor.disconnected(httpServerRequest, socketRoute, processorState);
333
+ }
334
+ }
335
+ }
336
+ catch {
337
+ // If something fails on a disconnect there is not much we can do with it
338
+ }
339
+ });
340
+ // Handle any incoming messages
341
+ socket.on(topic, async (data) => {
342
+ await this.handleRequestSocket(socketRouteProcessors, socketRoute, socket, `/${pathParts.join("/")}`, topic, data);
343
+ });
344
+ });
345
+ }
346
+ }
347
+ }
348
+ /**
349
+ * Handle the incoming REST request.
207
350
  * @param restRouteProcessors The hooks to process the incoming requests.
208
351
  * @param request The incoming request.
209
352
  * @param reply The outgoing response.
210
353
  * @param restRoute The REST route to handle.
211
354
  * @internal
212
355
  */
213
- async handleRequest(restRouteProcessors, request, reply, restRoute) {
356
+ async handleRequestRest(restRouteProcessors, request, reply, restRoute) {
214
357
  const httpServerRequest = {
215
358
  method: request.method.toUpperCase(),
216
359
  url: `${request.protocol}://${request.hostname}${request.url}`,
@@ -222,21 +365,7 @@ class FastifyWebServer {
222
365
  const httpResponse = {};
223
366
  const httpRequestIdentity = {};
224
367
  const processorState = {};
225
- for (const restRouteProcessor of restRouteProcessors) {
226
- if (core.Is.function(restRouteProcessor.pre)) {
227
- await restRouteProcessor.pre(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
228
- }
229
- }
230
- for (const restRouteProcessor of restRouteProcessors) {
231
- if (core.Is.function(restRouteProcessor.process)) {
232
- await restRouteProcessor.process(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
233
- }
234
- }
235
- for (const restRouteProcessor of restRouteProcessors) {
236
- if (core.Is.function(restRouteProcessor.post)) {
237
- await restRouteProcessor.post(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
238
- }
239
- }
368
+ await this.runProcessorsRest(restRouteProcessors, restRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState);
240
369
  if (!core.Is.empty(httpResponse.headers)) {
241
370
  for (const header of Object.keys(httpResponse.headers)) {
242
371
  reply.header(header, httpResponse.headers[header]);
@@ -246,6 +375,135 @@ class FastifyWebServer {
246
375
  .status((httpResponse.statusCode ?? web.HttpStatusCode.ok))
247
376
  .send(httpResponse.body);
248
377
  }
378
+ /**
379
+ * Run the REST processors for the route.
380
+ * @param restRouteProcessors The processors to run.
381
+ * @param restRoute The route to process.
382
+ * @param httpServerRequest The incoming request.
383
+ * @param httpResponse The outgoing response.
384
+ * @param httpRequestIdentity The identity context for the request.
385
+ * @internal
386
+ */
387
+ async runProcessorsRest(restRouteProcessors, restRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState) {
388
+ try {
389
+ for (const routeProcessor of restRouteProcessors) {
390
+ if (core.Is.function(routeProcessor.pre)) {
391
+ await routeProcessor.pre(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
392
+ }
393
+ }
394
+ for (const routeProcessor of restRouteProcessors) {
395
+ if (core.Is.function(routeProcessor.process)) {
396
+ await routeProcessor.process(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
397
+ }
398
+ }
399
+ for (const routeProcessor of restRouteProcessors) {
400
+ if (core.Is.function(routeProcessor.post)) {
401
+ await routeProcessor.post(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
402
+ }
403
+ }
404
+ }
405
+ catch (err) {
406
+ const { error, httpStatusCode } = apiModels.HttpErrorHelper.processError(err, this._includeErrorStack);
407
+ apiModels.HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode);
408
+ }
409
+ }
410
+ /**
411
+ * Handle the incoming socket request.
412
+ * @param socketRouteProcessors The hooks to process the incoming requests.
413
+ * @param socketRoute The socket route to handle.
414
+ * @param socket The socket to handle.
415
+ * @param fullPath The full path of the socket route.
416
+ * @param emitTopic The topic to emit the response on.
417
+ * @param data The incoming data.
418
+ * @internal
419
+ */
420
+ async handleRequestSocket(socketRouteProcessors, socketRoute, socket, fullPath, emitTopic, request) {
421
+ const httpServerRequest = {
422
+ method: web.HttpMethod.GET,
423
+ url: fullPath,
424
+ query: socket.handshake.query,
425
+ headers: socket.handshake.headers,
426
+ body: request.body
427
+ };
428
+ const httpResponse = {};
429
+ const httpRequestIdentity = {};
430
+ const processorState = {
431
+ socketId: socket.id
432
+ };
433
+ delete httpServerRequest.query?.EIO;
434
+ delete httpServerRequest.query?.transport;
435
+ await this.runProcessorsSocket(socketRouteProcessors, socketRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState, emitTopic, async (topic, response) => {
436
+ await socket.emit(topic, response);
437
+ });
438
+ }
439
+ /**
440
+ * Run the socket processors for the route.
441
+ * @param socketRouteProcessors The processors to run.
442
+ * @param socketRoute The route to process.
443
+ * @param httpServerRequest The incoming request.
444
+ * @param httpResponse The outgoing response.
445
+ * @param httpRequestIdentity The identity context for the request.
446
+ * @param processorState The state handed through the processors.
447
+ * @param requestTopic The topic of the request.
448
+ * @internal
449
+ */
450
+ async runProcessorsSocket(socketRouteProcessors, socketRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState, requestTopic, responseEmitter) {
451
+ // Custom emit method which will also call the post processors
452
+ const postProcessEmit = async (topic, response, responseProcessorState) => {
453
+ await responseEmitter(topic, response);
454
+ try {
455
+ // The post processors are called after the response has been emitted
456
+ for (const postSocketRouteProcessor of socketRouteProcessors) {
457
+ if (core.Is.function(postSocketRouteProcessor.post)) {
458
+ await postSocketRouteProcessor.post(httpServerRequest, response, socketRoute, httpRequestIdentity, responseProcessorState);
459
+ }
460
+ }
461
+ }
462
+ catch (err) {
463
+ this._loggingConnector?.log({
464
+ level: "error",
465
+ ts: Date.now(),
466
+ source: this.CLASS_NAME,
467
+ message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.postProcessorError`,
468
+ error: core.BaseError.fromError(err),
469
+ data: {
470
+ route: socketRoute.path
471
+ }
472
+ });
473
+ }
474
+ };
475
+ try {
476
+ for (const socketRouteProcessor of socketRouteProcessors) {
477
+ if (core.Is.function(socketRouteProcessor.pre)) {
478
+ await socketRouteProcessor.pre(httpServerRequest, httpResponse, socketRoute, httpRequestIdentity, processorState);
479
+ }
480
+ }
481
+ // We always call all the processors regardless of any response set by a previous processor.
482
+ // But if a pre processor sets a status code, we will emit the response manually, as the pre
483
+ // and post processors do not receive the emit method, they just populate the response object.
484
+ if (!core.Is.empty(httpResponse.statusCode)) {
485
+ await postProcessEmit(requestTopic, httpResponse, processorState);
486
+ }
487
+ for (const socketRouteProcessor of socketRouteProcessors) {
488
+ if (core.Is.function(socketRouteProcessor.process)) {
489
+ await socketRouteProcessor.process(httpServerRequest, httpResponse, socketRoute, httpRequestIdentity, processorState, async (topic, processResponse) => {
490
+ await postProcessEmit(topic, processResponse, processorState);
491
+ });
492
+ }
493
+ }
494
+ // If the processors set the status to any kind of error then we should emit this manually
495
+ if (core.Is.integer(httpResponse.statusCode) &&
496
+ httpResponse.statusCode >= web.HttpStatusCode.badRequest) {
497
+ await postProcessEmit(requestTopic, httpResponse, processorState);
498
+ }
499
+ }
500
+ catch (err) {
501
+ // Emit any unhandled errors manually
502
+ const { error, httpStatusCode } = apiModels.HttpErrorHelper.processError(err, this._includeErrorStack);
503
+ apiModels.HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode);
504
+ await postProcessEmit(requestTopic, httpResponse, processorState);
505
+ }
506
+ }
249
507
  /**
250
508
  * Initialize the cors options.
251
509
  * @param options The web server options.
@@ -1,10 +1,24 @@
1
1
  import FastifyCompress from '@fastify/compress';
2
2
  import FastifyCors from '@fastify/cors';
3
3
  import { HttpErrorHelper } from '@twin.org/api-models';
4
+ import { JsonLdMimeTypeProcessor } from '@twin.org/api-processors';
4
5
  import { StringHelper, Is, GeneralError, BaseError } from '@twin.org/core';
5
6
  import { LoggingConnectorFactory } from '@twin.org/logging-models';
6
7
  import { HttpStatusCode, HttpMethod, HeaderTypes } from '@twin.org/web';
7
8
  import Fastify from 'fastify';
9
+ import fp from 'fastify-plugin';
10
+ import { Server } from 'socket.io';
11
+
12
+ // This is a clone of fastify-socket.io which runs with recent fastify versions.
13
+ const fastifySocketIO = fp(async (fastify, opts) => {
14
+ const ioServer = new Server(fastify.server, opts);
15
+ fastify.decorate("io", ioServer);
16
+ fastify.addHook("preClose", done => {
17
+ ioServer.disconnectSockets();
18
+ ioServer.close();
19
+ done();
20
+ });
21
+ }, { fastify: ">=5.x.x", name: "socket.io" });
8
22
 
9
23
  // Copyright 2024 IOTA Stiftung.
10
24
  // SPDX-License-Identifier: Apache-2.0.
@@ -46,23 +60,49 @@ class FastifyWebServer {
46
60
  * @internal
47
61
  */
48
62
  _fastify;
63
+ /**
64
+ * The options for the socket server.
65
+ * @internal
66
+ */
67
+ _socketConfig;
49
68
  /**
50
69
  * Whether the server has been started.
51
70
  * @internal
52
71
  */
53
72
  _started;
73
+ /**
74
+ * The mime type processors.
75
+ * @internal
76
+ */
77
+ _mimeTypeProcessors;
78
+ /**
79
+ * Include the stack with errors.
80
+ * @internal
81
+ */
82
+ _includeErrorStack;
54
83
  /**
55
84
  * Create a new instance of FastifyWebServer.
56
85
  * @param options The options for the server.
57
- * @param options.loggingConnectorType The type of the logging connector to use, if undefined, no logging will happen.
58
- * @param options.config Additional options for the Fastify server.
59
86
  */
60
87
  constructor(options) {
61
88
  this._loggingConnector = Is.stringValue(options?.loggingConnectorType)
62
89
  ? LoggingConnectorFactory.get(options.loggingConnectorType)
63
90
  : undefined;
64
- this._fastify = Fastify({ maxParamLength: 2000, ...options?.config });
91
+ this._fastify = Fastify({
92
+ maxParamLength: 2000,
93
+ ...options?.config?.web
94
+ });
95
+ this._socketConfig = {
96
+ path: "/socket",
97
+ ...options?.config?.socket
98
+ };
65
99
  this._started = false;
100
+ this._mimeTypeProcessors = options?.mimeTypeProcessors ?? [];
101
+ const hasJsonLd = this._mimeTypeProcessors.find(processor => processor.CLASS_NAME === "json-ld");
102
+ if (!hasJsonLd) {
103
+ this._mimeTypeProcessors.push(new JsonLdMimeTypeProcessor());
104
+ }
105
+ this._includeErrorStack = options?.config?.includeErrorStack ?? false;
66
106
  }
67
107
  /**
68
108
  * Get the web server instance.
@@ -73,14 +113,19 @@ class FastifyWebServer {
73
113
  }
74
114
  /**
75
115
  * Build the server.
76
- * @param restRouteProcessors The hooks to process the incoming requests.
116
+ * @param restRouteProcessors The processors for incoming requests over REST.
77
117
  * @param restRoutes The REST routes.
118
+ * @param socketRouteProcessors The processors for incoming requests over Sockets.
119
+ * @param socketRoutes The socket routes.
78
120
  * @param options Options for building the server.
79
121
  * @returns Nothing.
80
122
  */
81
- async build(restRouteProcessors, restRoutes, options) {
82
- if (!Is.arrayValue(restRouteProcessors)) {
83
- throw new GeneralError(this.CLASS_NAME, "noProcessors");
123
+ async build(restRouteProcessors, restRoutes, socketRouteProcessors, socketRoutes, options) {
124
+ if (Is.arrayValue(restRoutes) && !Is.arrayValue(restRouteProcessors)) {
125
+ throw new GeneralError(this.CLASS_NAME, "noRestProcessors");
126
+ }
127
+ if (Is.arrayValue(socketRoutes) && !Is.arrayValue(socketRouteProcessors)) {
128
+ throw new GeneralError(this.CLASS_NAME, "noSocketProcessors");
84
129
  }
85
130
  await this._loggingConnector?.log({
86
131
  level: "info",
@@ -90,8 +135,25 @@ class FastifyWebServer {
90
135
  });
91
136
  this._options = options;
92
137
  await this._fastify.register(FastifyCompress);
138
+ if (Is.arrayValue(socketRoutes)) {
139
+ await this._fastify.register(fastifySocketIO, this._socketConfig);
140
+ }
141
+ if (Is.arrayValue(this._mimeTypeProcessors)) {
142
+ for (const contentTypeHandler of this._mimeTypeProcessors) {
143
+ this._fastify.addContentTypeParser(contentTypeHandler.getTypes(), { parseAs: "buffer" }, (request, body, done) => {
144
+ // Fastify does not handle this method correctly if it is async
145
+ // so we have to use the callback method
146
+ contentTypeHandler
147
+ .handle(body)
148
+ // eslint-disable-next-line promise/prefer-await-to-then, promise/no-callback-in-promise
149
+ .then(processed => done(null, processed))
150
+ // eslint-disable-next-line promise/prefer-await-to-then, promise/no-callback-in-promise
151
+ .catch(err => done(BaseError.fromError(err)));
152
+ });
153
+ }
154
+ }
93
155
  await this.initCors(options);
94
- this._fastify.setNotFoundHandler({}, async (request, reply) => this.handleRequest(restRouteProcessors, request, reply));
156
+ this._fastify.setNotFoundHandler({}, async (request, reply) => this.handleRequestRest(restRouteProcessors ?? [], request, reply));
95
157
  this._fastify.setErrorHandler(async (error, request, reply) => {
96
158
  // If code property is set this is a fastify error
97
159
  // otherwise it's from our framework
@@ -121,22 +183,8 @@ class FastifyWebServer {
121
183
  error: err
122
184
  });
123
185
  });
124
- // Add the routes to the server.
125
- for (const restRoute of restRoutes) {
126
- let path = StringHelper.trimTrailingSlashes(restRoute.path);
127
- if (!path.startsWith("/")) {
128
- path = `/${path}`;
129
- }
130
- await this._loggingConnector?.log({
131
- level: "info",
132
- ts: Date.now(),
133
- source: this.CLASS_NAME,
134
- message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.restRouteAdded`,
135
- data: { route: path, method: restRoute.method }
136
- });
137
- const method = restRoute.method.toLowerCase();
138
- this._fastify[method](path, async (request, reply) => this.handleRequest(restRouteProcessors, request, reply, restRoute));
139
- }
186
+ await this.addRoutesRest(restRouteProcessors, restRoutes);
187
+ await this.addRoutesSocket(socketRouteProcessors, socketRoutes);
140
188
  }
141
189
  /**
142
190
  * Start the server.
@@ -201,14 +249,109 @@ class FastifyWebServer {
201
249
  }
202
250
  }
203
251
  /**
204
- * Handle the incoming request.
252
+ * Add the REST routes to the server.
253
+ * @param restRouteProcessors The processors for the incoming requests.
254
+ * @param restRoutes The REST routes to add.
255
+ * @internal
256
+ */
257
+ async addRoutesRest(restRouteProcessors, restRoutes) {
258
+ if (Is.arrayValue(restRouteProcessors) && Is.arrayValue(restRoutes)) {
259
+ for (const restRoute of restRoutes) {
260
+ let path = StringHelper.trimTrailingSlashes(restRoute.path);
261
+ if (!path.startsWith("/")) {
262
+ path = `/${path}`;
263
+ }
264
+ await this._loggingConnector?.log({
265
+ level: "info",
266
+ ts: Date.now(),
267
+ source: this.CLASS_NAME,
268
+ message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.restRouteAdded`,
269
+ data: { route: path, method: restRoute.method }
270
+ });
271
+ const method = restRoute.method.toLowerCase();
272
+ this._fastify[method](path, async (request, reply) => this.handleRequestRest(restRouteProcessors, request, reply, restRoute));
273
+ }
274
+ }
275
+ }
276
+ /**
277
+ * Add the socket routes to the server.
278
+ * @param socketRouteProcessors The processors for the incoming requests.
279
+ * @param socketRoutes The socket routes to add.
280
+ * @internal
281
+ */
282
+ async addRoutesSocket(socketRouteProcessors, socketRoutes) {
283
+ if (Is.arrayValue(socketRouteProcessors) && Is.arrayValue(socketRoutes)) {
284
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
+ const io = this._fastify.io;
286
+ for (const socketRoute of socketRoutes) {
287
+ const path = StringHelper.trimLeadingSlashes(StringHelper.trimTrailingSlashes(socketRoute.path));
288
+ const pathParts = path.split("/");
289
+ await this._loggingConnector?.log({
290
+ level: "info",
291
+ ts: Date.now(),
292
+ source: this.CLASS_NAME,
293
+ message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.socketRouteAdded`,
294
+ data: { route: `/${path}` }
295
+ });
296
+ const socketNamespace = io.of(`/${pathParts[0]}`);
297
+ const topic = pathParts.slice(1).join("/");
298
+ socketNamespace.on("connection", async (socket) => {
299
+ const httpServerRequest = {
300
+ method: HttpMethod.GET,
301
+ url: socket.handshake.url,
302
+ query: socket.handshake.query,
303
+ headers: socket.handshake.headers
304
+ };
305
+ // Pass the connected information on to any processors
306
+ try {
307
+ const processorState = {
308
+ socketId: socket.id
309
+ };
310
+ for (const socketRouteProcessor of socketRouteProcessors) {
311
+ if (Is.function(socketRouteProcessor.connected)) {
312
+ await socketRouteProcessor.connected(httpServerRequest, socketRoute, processorState);
313
+ }
314
+ }
315
+ }
316
+ catch (err) {
317
+ const { error, httpStatusCode } = HttpErrorHelper.processError(err, this._includeErrorStack);
318
+ const response = {};
319
+ HttpErrorHelper.buildResponse(response, error, httpStatusCode);
320
+ socket.emit(topic, response);
321
+ }
322
+ socket.on("disconnect", async () => {
323
+ try {
324
+ const processorState = {
325
+ socketId: socket.id
326
+ };
327
+ // The socket disconnected so notify any processors
328
+ for (const socketRouteProcessor of socketRouteProcessors) {
329
+ if (Is.function(socketRouteProcessor.disconnected)) {
330
+ await socketRouteProcessor.disconnected(httpServerRequest, socketRoute, processorState);
331
+ }
332
+ }
333
+ }
334
+ catch {
335
+ // If something fails on a disconnect there is not much we can do with it
336
+ }
337
+ });
338
+ // Handle any incoming messages
339
+ socket.on(topic, async (data) => {
340
+ await this.handleRequestSocket(socketRouteProcessors, socketRoute, socket, `/${pathParts.join("/")}`, topic, data);
341
+ });
342
+ });
343
+ }
344
+ }
345
+ }
346
+ /**
347
+ * Handle the incoming REST request.
205
348
  * @param restRouteProcessors The hooks to process the incoming requests.
206
349
  * @param request The incoming request.
207
350
  * @param reply The outgoing response.
208
351
  * @param restRoute The REST route to handle.
209
352
  * @internal
210
353
  */
211
- async handleRequest(restRouteProcessors, request, reply, restRoute) {
354
+ async handleRequestRest(restRouteProcessors, request, reply, restRoute) {
212
355
  const httpServerRequest = {
213
356
  method: request.method.toUpperCase(),
214
357
  url: `${request.protocol}://${request.hostname}${request.url}`,
@@ -220,21 +363,7 @@ class FastifyWebServer {
220
363
  const httpResponse = {};
221
364
  const httpRequestIdentity = {};
222
365
  const processorState = {};
223
- for (const restRouteProcessor of restRouteProcessors) {
224
- if (Is.function(restRouteProcessor.pre)) {
225
- await restRouteProcessor.pre(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
226
- }
227
- }
228
- for (const restRouteProcessor of restRouteProcessors) {
229
- if (Is.function(restRouteProcessor.process)) {
230
- await restRouteProcessor.process(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
231
- }
232
- }
233
- for (const restRouteProcessor of restRouteProcessors) {
234
- if (Is.function(restRouteProcessor.post)) {
235
- await restRouteProcessor.post(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
236
- }
237
- }
366
+ await this.runProcessorsRest(restRouteProcessors, restRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState);
238
367
  if (!Is.empty(httpResponse.headers)) {
239
368
  for (const header of Object.keys(httpResponse.headers)) {
240
369
  reply.header(header, httpResponse.headers[header]);
@@ -244,6 +373,135 @@ class FastifyWebServer {
244
373
  .status((httpResponse.statusCode ?? HttpStatusCode.ok))
245
374
  .send(httpResponse.body);
246
375
  }
376
+ /**
377
+ * Run the REST processors for the route.
378
+ * @param restRouteProcessors The processors to run.
379
+ * @param restRoute The route to process.
380
+ * @param httpServerRequest The incoming request.
381
+ * @param httpResponse The outgoing response.
382
+ * @param httpRequestIdentity The identity context for the request.
383
+ * @internal
384
+ */
385
+ async runProcessorsRest(restRouteProcessors, restRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState) {
386
+ try {
387
+ for (const routeProcessor of restRouteProcessors) {
388
+ if (Is.function(routeProcessor.pre)) {
389
+ await routeProcessor.pre(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
390
+ }
391
+ }
392
+ for (const routeProcessor of restRouteProcessors) {
393
+ if (Is.function(routeProcessor.process)) {
394
+ await routeProcessor.process(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
395
+ }
396
+ }
397
+ for (const routeProcessor of restRouteProcessors) {
398
+ if (Is.function(routeProcessor.post)) {
399
+ await routeProcessor.post(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
400
+ }
401
+ }
402
+ }
403
+ catch (err) {
404
+ const { error, httpStatusCode } = HttpErrorHelper.processError(err, this._includeErrorStack);
405
+ HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode);
406
+ }
407
+ }
408
+ /**
409
+ * Handle the incoming socket request.
410
+ * @param socketRouteProcessors The hooks to process the incoming requests.
411
+ * @param socketRoute The socket route to handle.
412
+ * @param socket The socket to handle.
413
+ * @param fullPath The full path of the socket route.
414
+ * @param emitTopic The topic to emit the response on.
415
+ * @param data The incoming data.
416
+ * @internal
417
+ */
418
+ async handleRequestSocket(socketRouteProcessors, socketRoute, socket, fullPath, emitTopic, request) {
419
+ const httpServerRequest = {
420
+ method: HttpMethod.GET,
421
+ url: fullPath,
422
+ query: socket.handshake.query,
423
+ headers: socket.handshake.headers,
424
+ body: request.body
425
+ };
426
+ const httpResponse = {};
427
+ const httpRequestIdentity = {};
428
+ const processorState = {
429
+ socketId: socket.id
430
+ };
431
+ delete httpServerRequest.query?.EIO;
432
+ delete httpServerRequest.query?.transport;
433
+ await this.runProcessorsSocket(socketRouteProcessors, socketRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState, emitTopic, async (topic, response) => {
434
+ await socket.emit(topic, response);
435
+ });
436
+ }
437
+ /**
438
+ * Run the socket processors for the route.
439
+ * @param socketRouteProcessors The processors to run.
440
+ * @param socketRoute The route to process.
441
+ * @param httpServerRequest The incoming request.
442
+ * @param httpResponse The outgoing response.
443
+ * @param httpRequestIdentity The identity context for the request.
444
+ * @param processorState The state handed through the processors.
445
+ * @param requestTopic The topic of the request.
446
+ * @internal
447
+ */
448
+ async runProcessorsSocket(socketRouteProcessors, socketRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState, requestTopic, responseEmitter) {
449
+ // Custom emit method which will also call the post processors
450
+ const postProcessEmit = async (topic, response, responseProcessorState) => {
451
+ await responseEmitter(topic, response);
452
+ try {
453
+ // The post processors are called after the response has been emitted
454
+ for (const postSocketRouteProcessor of socketRouteProcessors) {
455
+ if (Is.function(postSocketRouteProcessor.post)) {
456
+ await postSocketRouteProcessor.post(httpServerRequest, response, socketRoute, httpRequestIdentity, responseProcessorState);
457
+ }
458
+ }
459
+ }
460
+ catch (err) {
461
+ this._loggingConnector?.log({
462
+ level: "error",
463
+ ts: Date.now(),
464
+ source: this.CLASS_NAME,
465
+ message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.postProcessorError`,
466
+ error: BaseError.fromError(err),
467
+ data: {
468
+ route: socketRoute.path
469
+ }
470
+ });
471
+ }
472
+ };
473
+ try {
474
+ for (const socketRouteProcessor of socketRouteProcessors) {
475
+ if (Is.function(socketRouteProcessor.pre)) {
476
+ await socketRouteProcessor.pre(httpServerRequest, httpResponse, socketRoute, httpRequestIdentity, processorState);
477
+ }
478
+ }
479
+ // We always call all the processors regardless of any response set by a previous processor.
480
+ // But if a pre processor sets a status code, we will emit the response manually, as the pre
481
+ // and post processors do not receive the emit method, they just populate the response object.
482
+ if (!Is.empty(httpResponse.statusCode)) {
483
+ await postProcessEmit(requestTopic, httpResponse, processorState);
484
+ }
485
+ for (const socketRouteProcessor of socketRouteProcessors) {
486
+ if (Is.function(socketRouteProcessor.process)) {
487
+ await socketRouteProcessor.process(httpServerRequest, httpResponse, socketRoute, httpRequestIdentity, processorState, async (topic, processResponse) => {
488
+ await postProcessEmit(topic, processResponse, processorState);
489
+ });
490
+ }
491
+ }
492
+ // If the processors set the status to any kind of error then we should emit this manually
493
+ if (Is.integer(httpResponse.statusCode) &&
494
+ httpResponse.statusCode >= HttpStatusCode.badRequest) {
495
+ await postProcessEmit(requestTopic, httpResponse, processorState);
496
+ }
497
+ }
498
+ catch (err) {
499
+ // Emit any unhandled errors manually
500
+ const { error, httpStatusCode } = HttpErrorHelper.processError(err, this._includeErrorStack);
501
+ HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode);
502
+ await postProcessEmit(requestTopic, httpResponse, processorState);
503
+ }
504
+ }
247
505
  /**
248
506
  * Initialize the cors options.
249
507
  * @param options The web server options.
@@ -0,0 +1,4 @@
1
+ import type { FastifyPluginAsync } from "fastify";
2
+ import { type ServerOptions } from "socket.io";
3
+ declare const fastifySocketIO: FastifyPluginAsync<Partial<ServerOptions>>;
4
+ export default fastifySocketIO;
@@ -1,5 +1,6 @@
1
- import { type IHttpRestRouteProcessor, type IRestRoute, type IWebServer, type IWebServerOptions } from "@twin.org/api-models";
2
- import { type FastifyServerOptions, type FastifyInstance } from "fastify";
1
+ import { type IRestRoute, type IRestRouteProcessor, type ISocketRoute, type ISocketRouteProcessor, type IWebServer, type IWebServerOptions } from "@twin.org/api-models";
2
+ import { type FastifyInstance } from "fastify";
3
+ import type { IFastifyWebServerConstructorOptions } from "./models/IFastifyWebServerConstructorOptions";
3
4
  /**
4
5
  * Implementation of the web server using Fastify.
5
6
  */
@@ -11,13 +12,8 @@ export declare class FastifyWebServer implements IWebServer<FastifyInstance> {
11
12
  /**
12
13
  * Create a new instance of FastifyWebServer.
13
14
  * @param options The options for the server.
14
- * @param options.loggingConnectorType The type of the logging connector to use, if undefined, no logging will happen.
15
- * @param options.config Additional options for the Fastify server.
16
15
  */
17
- constructor(options?: {
18
- loggingConnectorType?: string;
19
- config?: FastifyServerOptions;
20
- });
16
+ constructor(options?: IFastifyWebServerConstructorOptions);
21
17
  /**
22
18
  * Get the web server instance.
23
19
  * @returns The web server instance.
@@ -25,12 +21,14 @@ export declare class FastifyWebServer implements IWebServer<FastifyInstance> {
25
21
  getInstance(): FastifyInstance;
26
22
  /**
27
23
  * Build the server.
28
- * @param restRouteProcessors The hooks to process the incoming requests.
24
+ * @param restRouteProcessors The processors for incoming requests over REST.
29
25
  * @param restRoutes The REST routes.
26
+ * @param socketRouteProcessors The processors for incoming requests over Sockets.
27
+ * @param socketRoutes The socket routes.
30
28
  * @param options Options for building the server.
31
29
  * @returns Nothing.
32
30
  */
33
- build(restRouteProcessors: IHttpRestRouteProcessor[], restRoutes: IRestRoute[], options?: IWebServerOptions): Promise<void>;
31
+ build(restRouteProcessors?: IRestRouteProcessor[], restRoutes?: IRestRoute[], socketRouteProcessors?: ISocketRouteProcessor[], socketRoutes?: ISocketRoute[], options?: IWebServerOptions): Promise<void>;
34
32
  /**
35
33
  * Start the server.
36
34
  * @returns Nothing.
@@ -1 +1,3 @@
1
1
  export * from "./fastifyWebServer";
2
+ export * from "./models/IFastifyWebServerConfig";
3
+ export * from "./models/IFastifyWebServerConstructorOptions";
@@ -0,0 +1,19 @@
1
+ import type { FastifyServerOptions } from "fastify";
2
+ import type { ServerOptions } from "socket.io";
3
+ /**
4
+ * The configuration for the Fastify web server.
5
+ */
6
+ export interface IFastifyWebServerConfig {
7
+ /**
8
+ * The web server options.
9
+ */
10
+ web?: Partial<FastifyServerOptions>;
11
+ /**
12
+ * The socket server options.
13
+ */
14
+ socket?: Partial<ServerOptions>;
15
+ /**
16
+ * Include the stack with errors.
17
+ */
18
+ includeErrorStack?: boolean;
19
+ }
@@ -0,0 +1,19 @@
1
+ import type { IMimeTypeProcessor } from "@twin.org/api-models";
2
+ import type { IFastifyWebServerConfig } from "./IFastifyWebServerConfig";
3
+ /**
4
+ * The options for the Fastify web server constructor.
5
+ */
6
+ export interface IFastifyWebServerConstructorOptions {
7
+ /**
8
+ * The type of the logging connector to use, if undefined, no logging will happen.
9
+ */
10
+ loggingConnectorType?: string;
11
+ /**
12
+ * Additional configuration for the server.
13
+ */
14
+ config?: IFastifyWebServerConfig;
15
+ /**
16
+ * Additional MIME type processors.
17
+ */
18
+ mimeTypeProcessors?: IMimeTypeProcessor[];
19
+ }
package/docs/changelog.md CHANGED
@@ -1,5 +1,125 @@
1
1
  # @twin.org/api-server-fastify - Changelog
2
2
 
3
- ## v0.0.1-next.9
3
+ ## [0.0.2-next.1](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.2-next.0...api-server-fastify-v0.0.2-next.1) (2025-07-08)
4
+
5
+
6
+ ### Features
7
+
8
+ * add json-ld mime type processor and auth admin component ([8861791](https://github.com/twinfoundation/api/commit/88617916e23bfbca023dbae1976fe421983a02ff))
9
+ * update dependencies ([1171dc4](https://github.com/twinfoundation/api/commit/1171dc416a9481737f6a640e3cf30145768f37e9))
10
+ * use shared store mechanism ([#19](https://github.com/twinfoundation/api/issues/19)) ([32116df](https://github.com/twinfoundation/api/commit/32116df3b4380a30137f5056f242a5c99afa2df9))
11
+
12
+
13
+ ### Dependencies
14
+
15
+ * The following workspace dependencies were updated
16
+ * dependencies
17
+ * @twin.org/api-core bumped from 0.0.2-next.0 to 0.0.2-next.1
18
+ * @twin.org/api-models bumped from 0.0.2-next.0 to 0.0.2-next.1
19
+ * @twin.org/api-processors bumped from 0.0.2-next.0 to 0.0.2-next.1
20
+
21
+ ## 0.0.1 (2025-07-03)
22
+
23
+
24
+ ### Features
25
+
26
+ * release to production ([70ee2d5](https://github.com/twinfoundation/api/commit/70ee2d56a1dc9537d7c9c154d4cb78a235678a3a))
27
+
28
+
29
+ ### Dependencies
30
+
31
+ * The following workspace dependencies were updated
32
+ * dependencies
33
+ * @twin.org/api-models bumped from ^0.0.0 to ^0.0.1
34
+ * @twin.org/api-core bumped from ^0.0.0 to ^0.0.1
35
+ * devDependencies
36
+ * @twin.org/api-processors bumped from ^0.0.0 to ^0.0.1
37
+
38
+ ## [0.0.1-next.36](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.1-next.35...api-server-fastify-v0.0.1-next.36) (2025-06-17)
39
+
40
+
41
+ ### Miscellaneous Chores
42
+
43
+ * **api-server-fastify:** Synchronize repo versions
44
+
45
+
46
+ ### Dependencies
47
+
48
+ * The following workspace dependencies were updated
49
+ * dependencies
50
+ * @twin.org/api-models bumped from 0.0.1-next.35 to 0.0.1-next.36
51
+ * @twin.org/api-core bumped from 0.0.1-next.35 to 0.0.1-next.36
52
+ * devDependencies
53
+ * @twin.org/api-processors bumped from 0.0.1-next.35 to 0.0.1-next.36
54
+
55
+ ## [0.0.1-next.35](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.1-next.34...api-server-fastify-v0.0.1-next.35) (2025-06-11)
56
+
57
+
58
+ ### Features
59
+
60
+ * update dependencies ([1171dc4](https://github.com/twinfoundation/api/commit/1171dc416a9481737f6a640e3cf30145768f37e9))
61
+
62
+
63
+ ### Dependencies
64
+
65
+ * The following workspace dependencies were updated
66
+ * dependencies
67
+ * @twin.org/api-models bumped from 0.0.1-next.34 to 0.0.1-next.35
68
+ * @twin.org/api-core bumped from 0.0.1-next.34 to 0.0.1-next.35
69
+ * devDependencies
70
+ * @twin.org/api-processors bumped from 0.0.1-next.34 to 0.0.1-next.35
71
+
72
+ ## [0.0.1-next.34](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.1-next.33...api-server-fastify-v0.0.1-next.34) (2025-05-27)
73
+
74
+
75
+ ### Miscellaneous Chores
76
+
77
+ * **api-server-fastify:** Synchronize repo versions
78
+
79
+
80
+ ### Dependencies
81
+
82
+ * The following workspace dependencies were updated
83
+ * dependencies
84
+ * @twin.org/api-models bumped from 0.0.1-next.33 to 0.0.1-next.34
85
+ * @twin.org/api-core bumped from 0.0.1-next.33 to 0.0.1-next.34
86
+ * devDependencies
87
+ * @twin.org/api-processors bumped from 0.0.1-next.33 to 0.0.1-next.34
88
+
89
+ ## [0.0.1-next.33](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.1-next.32...api-server-fastify-v0.0.1-next.33) (2025-04-17)
90
+
91
+
92
+ ### Features
93
+
94
+ * use shared store mechanism ([#19](https://github.com/twinfoundation/api/issues/19)) ([32116df](https://github.com/twinfoundation/api/commit/32116df3b4380a30137f5056f242a5c99afa2df9))
95
+
96
+
97
+ ### Dependencies
98
+
99
+ * The following workspace dependencies were updated
100
+ * dependencies
101
+ * @twin.org/api-models bumped from 0.0.1-next.32 to 0.0.1-next.33
102
+ * @twin.org/api-core bumped from 0.0.1-next.32 to 0.0.1-next.33
103
+ * devDependencies
104
+ * @twin.org/api-processors bumped from 0.0.1-next.32 to 0.0.1-next.33
105
+
106
+ ## [0.0.1-next.32](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.1-next.31...api-server-fastify-v0.0.1-next.32) (2025-03-28)
107
+
108
+
109
+ ### Miscellaneous Chores
110
+
111
+ * **api-server-fastify:** Synchronize repo versions
112
+
113
+
114
+ ### Dependencies
115
+
116
+ * The following workspace dependencies were updated
117
+ * dependencies
118
+ * @twin.org/api-models bumped from 0.0.1-next.31 to 0.0.1-next.32
119
+ * @twin.org/api-core bumped from 0.0.1-next.31 to 0.0.1-next.32
120
+ * devDependencies
121
+ * @twin.org/api-processors bumped from next to 0.0.1-next.32
122
+
123
+ ## v0.0.1-next.31
4
124
 
5
125
  - Initial Release
@@ -8,29 +8,23 @@ Implementation of the web server using Fastify.
8
8
 
9
9
  ## Constructors
10
10
 
11
- ### new FastifyWebServer()
11
+ ### Constructor
12
12
 
13
- > **new FastifyWebServer**(`options`?): [`FastifyWebServer`](FastifyWebServer.md)
13
+ > **new FastifyWebServer**(`options?`): `FastifyWebServer`
14
14
 
15
15
  Create a new instance of FastifyWebServer.
16
16
 
17
17
  #### Parameters
18
18
 
19
- **options?**
19
+ ##### options?
20
20
 
21
- The options for the server.
22
-
23
- • **options.loggingConnectorType?**: `string`
24
-
25
- The type of the logging connector to use, if undefined, no logging will happen.
26
-
27
- • **options.config?**: `FastifyServerOptions`
21
+ [`IFastifyWebServerConstructorOptions`](../interfaces/IFastifyWebServerConstructorOptions.md)
28
22
 
29
- Additional options for the Fastify server.
23
+ The options for the server.
30
24
 
31
25
  #### Returns
32
26
 
33
- [`FastifyWebServer`](FastifyWebServer.md)
27
+ `FastifyWebServer`
34
28
 
35
29
  ## Properties
36
30
 
@@ -44,13 +38,13 @@ Runtime name for the class.
44
38
 
45
39
  ### getInstance()
46
40
 
47
- > **getInstance**(): `FastifyInstance`\<`RawServerDefault`, `IncomingMessage`, `ServerResponse`\<`IncomingMessage`\>, `FastifyBaseLogger`, `FastifyTypeProviderDefault`\>
41
+ > **getInstance**(): `FastifyInstance`
48
42
 
49
43
  Get the web server instance.
50
44
 
51
45
  #### Returns
52
46
 
53
- `FastifyInstance`\<`RawServerDefault`, `IncomingMessage`, `ServerResponse`\<`IncomingMessage`\>, `FastifyBaseLogger`, `FastifyTypeProviderDefault`\>
47
+ `FastifyInstance`
54
48
 
55
49
  The web server instance.
56
50
 
@@ -62,21 +56,39 @@ The web server instance.
62
56
 
63
57
  ### build()
64
58
 
65
- > **build**(`restRouteProcessors`, `restRoutes`, `options`?): `Promise`\<`void`\>
59
+ > **build**(`restRouteProcessors?`, `restRoutes?`, `socketRouteProcessors?`, `socketRoutes?`, `options?`): `Promise`\<`void`\>
66
60
 
67
61
  Build the server.
68
62
 
69
63
  #### Parameters
70
64
 
71
- **restRouteProcessors**: `IHttpRestRouteProcessor`[]
65
+ ##### restRouteProcessors?
66
+
67
+ `IRestRouteProcessor`[]
68
+
69
+ The processors for incoming requests over REST.
72
70
 
73
- The hooks to process the incoming requests.
71
+ ##### restRoutes?
74
72
 
75
- • **restRoutes**: `IRestRoute`\<`any`, `any`\>[]
73
+ `IRestRoute`\<`any`, `any`\>[]
76
74
 
77
75
  The REST routes.
78
76
 
79
- **options?**: `IWebServerOptions`
77
+ ##### socketRouteProcessors?
78
+
79
+ `ISocketRouteProcessor`[]
80
+
81
+ The processors for incoming requests over Sockets.
82
+
83
+ ##### socketRoutes?
84
+
85
+ `ISocketRoute`\<`any`, `any`\>[]
86
+
87
+ The socket routes.
88
+
89
+ ##### options?
90
+
91
+ `IWebServerOptions`
80
92
 
81
93
  Options for building the server.
82
94
 
@@ -3,3 +3,8 @@
3
3
  ## Classes
4
4
 
5
5
  - [FastifyWebServer](classes/FastifyWebServer.md)
6
+
7
+ ## Interfaces
8
+
9
+ - [IFastifyWebServerConfig](interfaces/IFastifyWebServerConfig.md)
10
+ - [IFastifyWebServerConstructorOptions](interfaces/IFastifyWebServerConstructorOptions.md)
@@ -0,0 +1,27 @@
1
+ # Interface: IFastifyWebServerConfig
2
+
3
+ The configuration for the Fastify web server.
4
+
5
+ ## Properties
6
+
7
+ ### web?
8
+
9
+ > `optional` **web**: `Partial`\<`FastifyServerOptions`\>
10
+
11
+ The web server options.
12
+
13
+ ***
14
+
15
+ ### socket?
16
+
17
+ > `optional` **socket**: `Partial`\<`ServerOptions`\>
18
+
19
+ The socket server options.
20
+
21
+ ***
22
+
23
+ ### includeErrorStack?
24
+
25
+ > `optional` **includeErrorStack**: `boolean`
26
+
27
+ Include the stack with errors.
@@ -0,0 +1,27 @@
1
+ # Interface: IFastifyWebServerConstructorOptions
2
+
3
+ The options for the Fastify web server constructor.
4
+
5
+ ## Properties
6
+
7
+ ### loggingConnectorType?
8
+
9
+ > `optional` **loggingConnectorType**: `string`
10
+
11
+ The type of the logging connector to use, if undefined, no logging will happen.
12
+
13
+ ***
14
+
15
+ ### config?
16
+
17
+ > `optional` **config**: [`IFastifyWebServerConfig`](IFastifyWebServerConfig.md)
18
+
19
+ Additional configuration for the server.
20
+
21
+ ***
22
+
23
+ ### mimeTypeProcessors?
24
+
25
+ > `optional` **mimeTypeProcessors**: `IMimeTypeProcessor`[]
26
+
27
+ Additional MIME type processors.
package/locales/en.json CHANGED
@@ -7,6 +7,9 @@
7
7
  "stopped": "The Web Server was stopped",
8
8
  "badRequest": "The web server could not handle the request",
9
9
  "restRouteAdded": "Added REST route \"{route}\" \"{method}\"",
10
- "noProcessors": "You must configure at least one processor"
10
+ "socketRouteAdded": "Added socket route \"{route}\"",
11
+ "noRestProcessors": "You must configure at least one REST processor",
12
+ "noSocketProcessors": "You must configure at least one socket processor",
13
+ "postProcessorError": "There was a failure after in a post processor for route \"{route}\""
11
14
  }
12
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/api-server-fastify",
3
- "version": "0.0.1-next.9",
3
+ "version": "0.0.2-next.1",
4
4
  "description": "Use Fastify as the core web server for APIs",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,24 +14,26 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@fastify/compress": "8.0.1",
18
- "@fastify/cors": "10.0.1",
19
- "@twin.org/api-core": "0.0.1-next.9",
20
- "@twin.org/api-models": "0.0.1-next.9",
17
+ "@fastify/compress": "8.0.3",
18
+ "@fastify/cors": "11.0.1",
19
+ "@twin.org/api-core": "0.0.2-next.1",
20
+ "@twin.org/api-models": "0.0.2-next.1",
21
+ "@twin.org/api-processors": "0.0.2-next.1",
21
22
  "@twin.org/core": "next",
22
23
  "@twin.org/logging-models": "next",
23
24
  "@twin.org/nameof": "next",
24
25
  "@twin.org/web": "next",
25
- "fastify": "5.0.0"
26
+ "fastify": "5.3.3",
27
+ "socket.io": "4.8.1"
26
28
  },
27
29
  "main": "./dist/cjs/index.cjs",
28
30
  "module": "./dist/esm/index.mjs",
29
31
  "types": "./dist/types/index.d.ts",
30
32
  "exports": {
31
33
  ".": {
34
+ "types": "./dist/types/index.d.ts",
32
35
  "require": "./dist/cjs/index.cjs",
33
- "import": "./dist/esm/index.mjs",
34
- "types": "./dist/types/index.d.ts"
36
+ "import": "./dist/esm/index.mjs"
35
37
  }
36
38
  },
37
39
  "files": [