@unito/integration-sdk 0.1.11 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/src/handler.d.ts +39 -0
  2. package/dist/src/handler.js +9 -0
  3. package/dist/src/httpErrors.d.ts +29 -0
  4. package/dist/src/httpErrors.js +30 -0
  5. package/dist/src/index.cjs +274 -16
  6. package/dist/src/index.d.ts +1 -0
  7. package/dist/src/integration.d.ts +49 -0
  8. package/dist/src/integration.js +51 -0
  9. package/dist/src/middlewares/filters.d.ts +11 -2
  10. package/dist/src/middlewares/secrets.d.ts +5 -0
  11. package/dist/src/middlewares/signal.d.ts +15 -0
  12. package/dist/src/middlewares/signal.js +22 -0
  13. package/dist/src/resources/cache.d.ts +51 -1
  14. package/dist/src/resources/cache.js +51 -1
  15. package/dist/src/resources/context.d.ts +42 -13
  16. package/dist/src/resources/logger.d.ts +17 -0
  17. package/dist/src/resources/logger.js +17 -0
  18. package/dist/src/resources/provider.d.ts +90 -5
  19. package/dist/src/resources/provider.js +92 -11
  20. package/dist/test/middlewares/signal.test.d.ts +1 -0
  21. package/dist/test/middlewares/signal.test.js +20 -0
  22. package/dist/test/resources/provider.test.js +116 -21
  23. package/package.json +4 -4
  24. package/src/handler.ts +48 -0
  25. package/src/httpErrors.ts +30 -0
  26. package/src/index.ts +1 -0
  27. package/src/integration.ts +51 -0
  28. package/src/middlewares/filters.ts +11 -2
  29. package/src/middlewares/secrets.ts +5 -0
  30. package/src/middlewares/signal.ts +41 -0
  31. package/src/resources/cache.ts +51 -1
  32. package/src/resources/context.ts +50 -33
  33. package/src/resources/logger.ts +17 -0
  34. package/src/resources/provider.ts +115 -16
  35. package/test/middlewares/signal.test.ts +28 -0
  36. package/test/resources/provider.test.ts +122 -21
@@ -33,6 +33,9 @@ var LogLevel;
33
33
  LogLevel["LOG"] = "log";
34
34
  LogLevel["DEBUG"] = "debug";
35
35
  })(LogLevel || (LogLevel = {}));
36
+ /**
37
+ * Logger class that can be configured with metadata add creation and when logging to add additional context to your logs.
38
+ */
36
39
  class Logger {
37
40
  metadata;
38
41
  constructor(metadata = {}) {
@@ -85,12 +88,26 @@ class Logger {
85
88
  decorate(metadata) {
86
89
  this.metadata = { ...this.metadata, ...metadata };
87
90
  }
91
+ /**
92
+ * Return a copy of the Logger's metadata.
93
+ * @returns The {@link Metadata} associated with the logger.
94
+ */
88
95
  getMetadata() {
89
96
  return structuredClone(this.metadata);
90
97
  }
98
+ /**
99
+ * Sets a key-value pair in the metadata. If the key already exists, it will be overwritten.
100
+ *
101
+ * @param key Key of the metadata to be set.
102
+ * May be any string other than 'message', which is reserved for the actual message logged.
103
+ * @param value Value of the metadata to be set.
104
+ */
91
105
  setMetadata(key, value) {
92
106
  this.metadata[key] = value;
93
107
  }
108
+ /**
109
+ * Clears the Logger's metadata.
110
+ */
94
111
  clearMetadata() {
95
112
  this.metadata = {};
96
113
  }
@@ -118,28 +135,78 @@ class Logger {
118
135
  }
119
136
  }
120
137
 
138
+ /**
139
+ * The Cache class provides caching capabilities that can be used across your integration.
140
+ * It can be backed by a Redis instance (by passing it a URL to the instance) or a local cache.
141
+ *
142
+ * @see {@link Cache.create}
143
+ */
121
144
  class Cache {
122
145
  cacheInstance;
123
146
  constructor(cacheInstance) {
124
147
  this.cacheInstance = cacheInstance;
125
148
  }
149
+ /**
150
+ * Get or fetch a value
151
+ *
152
+ * @param key The key of the value to get
153
+ * @param ttl The time to live of the value in seconds.
154
+ * @param fetchFn The function that can retrieve the original value
155
+ * @param lockTtl Global distributed lock TTL (in seconds) protecting fetching.
156
+ * If undefined, 0 or falsy, locking is not preformed
157
+ * @param shouldCacheError A callback being passed errors, controlling whether
158
+ * to cache or not errors. Defaults to never cache.
159
+ *
160
+ * @returns The cached or fetched value
161
+ */
126
162
  getOrFetchValue(key, ttl, fetcher, lockTtl, shouldCacheError) {
127
163
  return this.cacheInstance.getOrFetchValue(key, ttl, fetcher, lockTtl, shouldCacheError);
128
164
  }
165
+ /**
166
+ * Get a value from the cache.
167
+ *
168
+ * @param key The key of the value to get.
169
+ *
170
+ * @return The value associated with the key, or undefined if
171
+ * no such value exists.
172
+ */
129
173
  getValue(key) {
130
174
  return this.cacheInstance.getValue(key);
131
175
  }
176
+ /**
177
+ * Set a value in the cache.
178
+ *
179
+ * @param key The key of the value to set.
180
+ * @param value The value to set.
181
+ * @param ttl The time to live of the value in seconds.
182
+ * By default, the value will not expire
183
+ *
184
+ * @return true if the value was stored, false otherwise.
185
+ */
132
186
  setValue(key, value, ttl) {
133
187
  return this.cacheInstance.setValue(key, value, ttl);
134
188
  }
189
+ /**
190
+ * Delete a value from the cache.
191
+ * @param key — The key of the value to set.
192
+ */
135
193
  delValue(key) {
136
194
  return this.cacheInstance.delValue(key);
137
195
  }
196
+ /**
197
+ * Get the TTL of an entry, in ms
198
+ *
199
+ * @param key The key of the entry whose ttl to retrieve
200
+ *
201
+ * @return The remaining TTL on the entry, in ms.
202
+ * undefined if the entry does not exist.
203
+ * 0 if the entry does not expire.
204
+ */
138
205
  getTtl(key) {
139
206
  return this.cacheInstance.getTtl(key);
140
207
  }
141
208
  /**
142
- * Initializes a WriteThroughCache instance with the provided redis url if present, or a LocalCache otherwise.
209
+ * Initializes a Cache backed by the Redis instance at the provided url if present, or a LocalCache otherwise.
143
210
  *
144
211
  * @param redisUrl - The redis url to connect to (optional).
145
212
  * @returns A cache instance.
@@ -163,43 +230,73 @@ class Cache {
163
230
  }
164
231
  }
165
232
 
233
+ /**
234
+ * Error class meant to be returned by integrations in case of exceptions. These errors will be caught and handled
235
+ * appropriately. Any other error would result in an unhandled server error accompanied by a 500 status code.
236
+ *
237
+ * @field message - The error message
238
+ * @field status - The HTTP status code to return
239
+ */
166
240
  class HttpError extends Error {
167
241
  status;
168
242
  constructor(message, status) {
169
243
  super(message);
170
244
  this.status = status;
245
+ this.name = this.constructor.name;
171
246
  }
172
247
  }
248
+ /**
249
+ * Used to generate a 400 Bad Request. Usually used when something is missing to properly handle the request.
250
+ */
173
251
  class BadRequestError extends HttpError {
174
252
  constructor(message) {
175
253
  super(message || 'Bad request', 400);
176
254
  }
177
255
  }
256
+ /**
257
+ * Used to generate a 401 Unauthorized. Usually used when the credentials are missing or invalid.
258
+ */
178
259
  class UnauthorizedError extends HttpError {
179
260
  constructor(message) {
180
261
  super(message || 'Unauthorized', 401);
181
262
  }
182
263
  }
264
+ /**
265
+ * Used to generate a 404 Not Found. Usually used when the requested `Item` is not found.
266
+ */
183
267
  class NotFoundError extends HttpError {
184
268
  constructor(message) {
185
269
  super(message || 'Not found', 404);
186
270
  }
187
271
  }
272
+ /**
273
+ * Used to generate a 408 Timeout Error. Usually used when the call length exceeds the received Operation Deadline.
274
+ */
188
275
  class TimeoutError extends HttpError {
189
276
  constructor(message) {
190
277
  super(message || 'Not found', 408);
191
278
  }
192
279
  }
280
+ /**
281
+ * Used to generate a 410 Resource Gone.
282
+ */
193
283
  class ResourceGoneError extends HttpError {
194
284
  constructor(message) {
195
285
  super(message || 'Resource gone or unavailable', 410);
196
286
  }
197
287
  }
288
+ /**
289
+ * Used to generate a 422 Unprocessable Entity. Usually used when an operation is invalid.
290
+ */
198
291
  class UnprocessableEntityError extends HttpError {
199
292
  constructor(message) {
200
293
  super(message || 'Unprocessable Entity', 422);
201
294
  }
202
295
  }
296
+ /**
297
+ * Used to generate a 429 Unprocessable Entity. Usually used when an operation triggers or would trigger a rate limit
298
+ * error on the provider's side.
299
+ */
203
300
  class RateLimitExceededError extends HttpError {
204
301
  constructor(message) {
205
302
  super(message || 'Rate Limit Exceeded', 429);
@@ -256,13 +353,13 @@ function buildHttpError(responseStatus, message) {
256
353
  return httpError;
257
354
  }
258
355
 
259
- const middleware$7 = (req, res, next) => {
356
+ const middleware$8 = (req, res, next) => {
260
357
  res.locals.correlationId = req.header('X-Unito-Correlation-Id') ?? uuid__namespace.v4();
261
358
  next();
262
359
  };
263
360
 
264
361
  const ADDITIONAL_CONTEXT_HEADER = 'X-Unito-Additional-Logging-Context';
265
- const middleware$6 = (req, res, next) => {
362
+ const middleware$7 = (req, res, next) => {
266
363
  const logger = new Logger({ correlation_id: res.locals.correlationId });
267
364
  res.locals.logger = logger;
268
365
  const rawAdditionalContext = req.header(ADDITIONAL_CONTEXT_HEADER);
@@ -279,7 +376,7 @@ const middleware$6 = (req, res, next) => {
279
376
  };
280
377
 
281
378
  const CREDENTIALS_HEADER = 'X-Unito-Credentials';
282
- const middleware$5 = (req, res, next) => {
379
+ const middleware$6 = (req, res, next) => {
283
380
  const credentialsHeader = req.header(CREDENTIALS_HEADER);
284
381
  if (credentialsHeader) {
285
382
  let credentials;
@@ -294,6 +391,27 @@ const middleware$5 = (req, res, next) => {
294
391
  next();
295
392
  };
296
393
 
394
+ const OPERATION_DEADLINE_HEADER = 'X-Unito-Operation-Deadline';
395
+ const middleware$5 = (req, res, next) => {
396
+ const operationDeadlineHeader = Number(req.header(OPERATION_DEADLINE_HEADER));
397
+ if (operationDeadlineHeader) {
398
+ // `operationDeadlineHeader` represents a timestamp in the future, in seconds.
399
+ // We need to convert it to a number of milliseconds.
400
+ const deadline = operationDeadlineHeader * 1000 - Date.now();
401
+ if (deadline > 0) {
402
+ res.locals.signal = AbortSignal.timeout(deadline);
403
+ }
404
+ else {
405
+ throw new TimeoutError('Request already timed out upon reception');
406
+ }
407
+ }
408
+ else {
409
+ // Default to 20s, which is the maximum time frame allowed for an operation by Unito.
410
+ res.locals.signal = AbortSignal.timeout(20000);
411
+ }
412
+ next();
413
+ };
414
+
297
415
  const SECRETS_HEADER = 'X-Unito-Secrets';
298
416
  const middleware$4 = (req, res, next) => {
299
417
  const secretsHeader = req.header(SECRETS_HEADER);
@@ -491,6 +609,7 @@ class Handler {
491
609
  selects: res.locals.selects,
492
610
  filters: res.locals.filters,
493
611
  logger: res.locals.logger,
612
+ signal: res.locals.signal,
494
613
  params: req.params,
495
614
  query: req.query,
496
615
  });
@@ -510,6 +629,7 @@ class Handler {
510
629
  secrets: res.locals.secrets,
511
630
  body: req.body,
512
631
  logger: res.locals.logger,
632
+ signal: res.locals.signal,
513
633
  params: req.params,
514
634
  query: req.query,
515
635
  });
@@ -527,6 +647,7 @@ class Handler {
527
647
  credentials: res.locals.credentials,
528
648
  secrets: res.locals.secrets,
529
649
  logger: res.locals.logger,
650
+ signal: res.locals.signal,
530
651
  params: req.params,
531
652
  query: req.query,
532
653
  });
@@ -546,6 +667,7 @@ class Handler {
546
667
  secrets: res.locals.secrets,
547
668
  body: req.body,
548
669
  logger: res.locals.logger,
670
+ signal: res.locals.signal,
549
671
  params: req.params,
550
672
  query: req.query,
551
673
  });
@@ -563,6 +685,7 @@ class Handler {
563
685
  credentials: res.locals.credentials,
564
686
  secrets: res.locals.secrets,
565
687
  logger: res.locals.logger,
688
+ signal: res.locals.signal,
566
689
  params: req.params,
567
690
  query: req.query,
568
691
  });
@@ -580,6 +703,7 @@ class Handler {
580
703
  credentials: res.locals.credentials,
581
704
  secrets: res.locals.secrets,
582
705
  logger: res.locals.logger,
706
+ signal: res.locals.signal,
583
707
  params: req.params,
584
708
  query: req.query,
585
709
  });
@@ -594,6 +718,7 @@ class Handler {
594
718
  const response = await handler({
595
719
  secrets: res.locals.secrets,
596
720
  logger: res.locals.logger,
721
+ signal: res.locals.signal,
597
722
  params: req.params,
598
723
  query: req.query,
599
724
  body: req.body,
@@ -609,6 +734,7 @@ class Handler {
609
734
  const response = await handler({
610
735
  secrets: res.locals.secrets,
611
736
  logger: res.locals.logger,
737
+ signal: res.locals.signal,
612
738
  params: req.params,
613
739
  query: req.query,
614
740
  body: req.body,
@@ -629,6 +755,7 @@ class Handler {
629
755
  credentials: res.locals.credentials,
630
756
  body: req.body,
631
757
  logger: res.locals.logger,
758
+ signal: res.locals.signal,
632
759
  params: req.params,
633
760
  query: req.query,
634
761
  });
@@ -645,14 +772,56 @@ function printErrorMessage(message) {
645
772
  console.error(`\x1b[31m Oops! Something went wrong! \x1b[0m`);
646
773
  console.error(message);
647
774
  }
775
+ /**
776
+ * Main class for the Integration SDK providing an abstraction layer between the Integration's Graph definition
777
+ * and the underlying HTTP server.
778
+ *
779
+ * An `Integration` instance can have multiple handlers configured to handle different routes. Upon receiving a request,
780
+ * the Integration will parse the request to extract meaninful information, match the request to the appropriate handler
781
+ * method and forward that information in the form a {@link Context} object.
782
+ * The Integration also offer standardized error handling and logging to help you build a robust
783
+ * and reliable Integration.
784
+ *
785
+ * See our {@link https://dev.unito.io/docs/ | documentation} for more examples on how to build an integration.
786
+ */
648
787
  class Integration {
649
788
  handlers;
650
789
  instance = undefined;
651
790
  port;
791
+ /**
792
+ * Creates a new Integration instance with default port set to 9200.
793
+ *
794
+ * @param options The {@link Options} to configure the Integration instance. Can be used to override the default port.
795
+ */
652
796
  constructor(options = {}) {
653
797
  this.port = options.port || 9200;
654
798
  this.handlers = [];
655
799
  }
800
+ /**
801
+ * Adds a group of common handlers to the integration.
802
+ *
803
+ * Handlers added to the integration can be one of the following:
804
+ * - `ItemHandlers`: A group of handlers defining the implementation of the Operations available for a given item.
805
+ * - `CredentialAccountHandlers`: A handler returning the CredentialAccount linked to the caller's credentials.
806
+ * - `ParseWebhookHandlers`: A handler parsing the content of an incoming webhook.
807
+ * - `WebhookSubscriptionHandlers`: A handler subscribing or unsubscribing to a particular webhook.
808
+ * - `AcknowledgeWebhookHandlers`: A handler acknowledging the reception of a webhook.
809
+ *
810
+ * To accomodate the fact that ItemHandlers may specify multiple operations, some at the collection level, some at the
811
+ * item level, we need a way to define the route for each of these operations.
812
+ * To achieve this, we assume that if the last part of the path is a variable, then it is the item identifier.
813
+ *
814
+ * @example The following path: `/trainer/:trainerId/pokemons/:pokemonId` will lead to the following
815
+ * routes:
816
+ * - getCollection will be called for `GET /trainer/:trainerId/pokemons/` requests
817
+ * - getItem will be called for `GET /trainer/:trainerId/pokemons/:pokemonId` requests
818
+ * - createItem will be called for `POST /trainer/:trainerId/pokemons/` requests
819
+ * - updateItem will be called for `PATCH /trainer/:trainerId/pokemons/:pokemonId` requests
820
+ * - deleteItem will be called for `DELETE /trainer/:trainerId/pokemons/:pokemonId` requests
821
+ *
822
+ * @param path The path to be used as Route for the handlers.
823
+ * @param handlers The Handlers definition.
824
+ */
656
825
  addHandler(path, handlers) {
657
826
  if (this.instance) {
658
827
  printErrorMessage(`
@@ -685,6 +854,13 @@ class Integration {
685
854
  process.exit(1);
686
855
  }
687
856
  }
857
+ /**
858
+ * Starts the server and listens on the specified port (default to 9200).
859
+ *
860
+ * @remarks
861
+ * This function should be called after all the handlers have been added to the integration
862
+ * and any other configuration is completed.
863
+ */
688
864
  start() {
689
865
  // Express Server initialization
690
866
  const app = express();
@@ -693,11 +869,12 @@ class Integration {
693
869
  app.use(express.json());
694
870
  // Must be one of the first handlers (to catch all the errors).
695
871
  app.use(middleware$1);
872
+ app.use(middleware$8);
696
873
  app.use(middleware$7);
697
874
  app.use(middleware$6);
698
- app.use(middleware$5);
699
875
  app.use(middleware$4);
700
876
  app.use(middleware$3);
877
+ app.use(middleware$5);
701
878
  // Load handlers as needed.
702
879
  if (this.handlers.length) {
703
880
  for (const handler of this.handlers) {
@@ -721,11 +898,21 @@ class Integration {
721
898
  }
722
899
  }
723
900
 
901
+ /**
902
+ * The Provider class is a wrapper around the fetch function to call a provider's HTTP API.
903
+ *
904
+ * Defines methods for the following HTTP methods: GET, POST, PUT, PATCH, DELETE.
905
+ *
906
+ * Needs to be initialized with a prepareRequest function to define the Provider's base URL and any specific headers to
907
+ * add to the requests, and can also be configured to use a provided rate limiting function.
908
+ * @see {@link RateLimiter}
909
+ * @see {@link prepareRequest}
910
+ */
724
911
  class Provider {
725
912
  rateLimiter = undefined;
726
913
  prepareRequest;
727
914
  /**
728
- * Initialize a Provider with the given options.
915
+ * Initializes a Provider with the given options.
729
916
  *
730
917
  * @property prepareRequest - function to define the Provider's base URL and specific headers to add to the request.
731
918
  * @property rateLimiter - function to limit the rate of calls to the provider based on the caller's credentials.
@@ -734,52 +921,108 @@ class Provider {
734
921
  this.prepareRequest = options.prepareRequest;
735
922
  this.rateLimiter = options.rateLimiter;
736
923
  }
924
+ /**
925
+ * Performs a GET request to the provider.
926
+ *
927
+ * Uses the prepareRequest function to get the base URL and any specific headers to add to the request and by default
928
+ * adds the following headers:
929
+ * - Accept: application/json
930
+ *
931
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
932
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
933
+ * @returns The {@link Response} extracted from the provider.
934
+ */
737
935
  async get(endpoint, options) {
738
936
  return this.fetchWrapper(endpoint, null, {
739
937
  ...options,
740
938
  method: 'GET',
741
939
  defaultHeaders: {
742
- 'Content-Type': 'application/json',
743
940
  Accept: 'application/json',
744
941
  },
745
942
  });
746
943
  }
944
+ /**
945
+ * Performs a POST request to the provider.
946
+ *
947
+ * Uses the prepareRequest function to get the base URL and any specific headers to add to the request and by default
948
+ * adds the following headers:
949
+ * - Content-Type: application/json',
950
+ * - Accept: application/json
951
+ *
952
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
953
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
954
+ * @returns The {@link Response} extracted from the provider.
955
+ */
747
956
  async post(endpoint, body, options) {
748
957
  return this.fetchWrapper(endpoint, body, {
749
958
  ...options,
750
959
  method: 'POST',
751
960
  defaultHeaders: {
752
- 'Content-Type': 'application/x-www-form-urlencoded',
961
+ 'Content-Type': 'application/json',
753
962
  Accept: 'application/json',
754
963
  },
755
964
  });
756
965
  }
966
+ /**
967
+ * Performs a PUT request to the provider.
968
+ *
969
+ * Uses the prepareRequest function to get the base URL and any specific headers to add to the request and by default
970
+ * adds the following headers:
971
+ * - Content-Type: application/json',
972
+ * - Accept: application/json
973
+ *
974
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
975
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
976
+ * @returns The {@link Response} extracted from the provider.
977
+ */
757
978
  async put(endpoint, body, options) {
758
979
  return this.fetchWrapper(endpoint, body, {
759
980
  ...options,
760
981
  method: 'PUT',
761
982
  defaultHeaders: {
762
- 'Content-Type': 'application/x-www-form-urlencoded',
983
+ 'Content-Type': 'application/json',
763
984
  Accept: 'application/json',
764
985
  },
765
986
  });
766
987
  }
988
+ /**
989
+ * Performs a PATCH request to the provider.
990
+ *
991
+ * Uses the prepareRequest function to get the base URL and any specific headers to add to the request and by default
992
+ * adds the following headers:
993
+ * - Content-Type: application/json',
994
+ * - Accept: application/json
995
+ *
996
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
997
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
998
+ * @returns The {@link Response} extracted from the provider.
999
+ */
767
1000
  async patch(endpoint, body, options) {
768
1001
  return this.fetchWrapper(endpoint, body, {
769
1002
  ...options,
770
1003
  method: 'PATCH',
771
1004
  defaultHeaders: {
772
- 'Content-Type': 'application/x-www-form-urlencoded',
1005
+ 'Content-Type': 'application/json',
773
1006
  Accept: 'application/json',
774
1007
  },
775
1008
  });
776
1009
  }
1010
+ /**
1011
+ * Performs a DELETE request to the provider.
1012
+ *
1013
+ * Uses the prepareRequest function to get the base URL and any specific headers to add to the request and by default
1014
+ * adds the following headers:
1015
+ * - Accept: application/json
1016
+ *
1017
+ * @param endpoint Path to the provider's resource. Will be added to the URL returned by the prepareRequest function.
1018
+ * @param options RequestOptions used to adjust the call made to the provider (use to override default headers).
1019
+ * @returns The {@link Response} extracted from the provider.
1020
+ */
777
1021
  async delete(endpoint, options) {
778
1022
  return this.fetchWrapper(endpoint, null, {
779
1023
  ...options,
780
1024
  method: 'DELETE',
781
1025
  defaultHeaders: {
782
- 'Content-Type': 'application/x-www-form-urlencoded',
783
1026
  Accept: 'application/json',
784
1027
  },
785
1028
  });
@@ -801,11 +1044,26 @@ class Provider {
801
1044
  }
802
1045
  }
803
1046
  const callToProvider = async () => {
804
- const response = await fetch(absoluteUrl, {
805
- method: options.method,
806
- headers,
807
- body: stringifiedBody,
808
- });
1047
+ let response;
1048
+ try {
1049
+ response = await fetch(absoluteUrl, {
1050
+ method: options.method,
1051
+ signal: options.signal,
1052
+ headers,
1053
+ body: stringifiedBody,
1054
+ });
1055
+ }
1056
+ catch (error) {
1057
+ if (error instanceof Error) {
1058
+ switch (error.name) {
1059
+ case 'AbortError':
1060
+ throw buildHttpError(408, 'Request aborted');
1061
+ case 'TimeoutError':
1062
+ throw buildHttpError(408, 'Request timeout');
1063
+ }
1064
+ }
1065
+ throw buildHttpError(500, `Unexpected error while calling the provider: "${error}"`);
1066
+ }
809
1067
  if (response.status >= 400) {
810
1068
  const textResult = await response.text();
811
1069
  throw buildHttpError(response.status, textResult);
@@ -7,3 +7,4 @@ export type { Secrets } from './middlewares/secrets.js';
7
7
  export type { Credentials } from './middlewares/credentials.js';
8
8
  export * as HttpErrors from './httpErrors.js';
9
9
  export * from './resources/context.js';
10
+ export { type default as Logger } from './resources/logger.js';
@@ -2,12 +2,61 @@ import { HandlersInput } from './handler.js';
2
2
  type Options = {
3
3
  port?: number;
4
4
  };
5
+ /**
6
+ * Main class for the Integration SDK providing an abstraction layer between the Integration's Graph definition
7
+ * and the underlying HTTP server.
8
+ *
9
+ * An `Integration` instance can have multiple handlers configured to handle different routes. Upon receiving a request,
10
+ * the Integration will parse the request to extract meaninful information, match the request to the appropriate handler
11
+ * method and forward that information in the form a {@link Context} object.
12
+ * The Integration also offer standardized error handling and logging to help you build a robust
13
+ * and reliable Integration.
14
+ *
15
+ * See our {@link https://dev.unito.io/docs/ | documentation} for more examples on how to build an integration.
16
+ */
5
17
  export default class Integration {
6
18
  private handlers;
7
19
  private instance;
8
20
  private port;
21
+ /**
22
+ * Creates a new Integration instance with default port set to 9200.
23
+ *
24
+ * @param options The {@link Options} to configure the Integration instance. Can be used to override the default port.
25
+ */
9
26
  constructor(options?: Options);
27
+ /**
28
+ * Adds a group of common handlers to the integration.
29
+ *
30
+ * Handlers added to the integration can be one of the following:
31
+ * - `ItemHandlers`: A group of handlers defining the implementation of the Operations available for a given item.
32
+ * - `CredentialAccountHandlers`: A handler returning the CredentialAccount linked to the caller's credentials.
33
+ * - `ParseWebhookHandlers`: A handler parsing the content of an incoming webhook.
34
+ * - `WebhookSubscriptionHandlers`: A handler subscribing or unsubscribing to a particular webhook.
35
+ * - `AcknowledgeWebhookHandlers`: A handler acknowledging the reception of a webhook.
36
+ *
37
+ * To accomodate the fact that ItemHandlers may specify multiple operations, some at the collection level, some at the
38
+ * item level, we need a way to define the route for each of these operations.
39
+ * To achieve this, we assume that if the last part of the path is a variable, then it is the item identifier.
40
+ *
41
+ * @example The following path: `/trainer/:trainerId/pokemons/:pokemonId` will lead to the following
42
+ * routes:
43
+ * - getCollection will be called for `GET /trainer/:trainerId/pokemons/` requests
44
+ * - getItem will be called for `GET /trainer/:trainerId/pokemons/:pokemonId` requests
45
+ * - createItem will be called for `POST /trainer/:trainerId/pokemons/` requests
46
+ * - updateItem will be called for `PATCH /trainer/:trainerId/pokemons/:pokemonId` requests
47
+ * - deleteItem will be called for `DELETE /trainer/:trainerId/pokemons/:pokemonId` requests
48
+ *
49
+ * @param path The path to be used as Route for the handlers.
50
+ * @param handlers The Handlers definition.
51
+ */
10
52
  addHandler(path: string, handlers: HandlersInput): void;
53
+ /**
54
+ * Starts the server and listens on the specified port (default to 9200).
55
+ *
56
+ * @remarks
57
+ * This function should be called after all the handlers have been added to the integration
58
+ * and any other configuration is completed.
59
+ */
11
60
  start(): void;
12
61
  }
13
62
  export {};