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