@unito/integration-sdk 0.1.10 → 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 (38) 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 +289 -39
  6. package/dist/src/index.d.ts +2 -0
  7. package/dist/src/integration.d.ts +49 -0
  8. package/dist/src/integration.js +53 -17
  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 -2
  14. package/dist/src/resources/cache.js +49 -8
  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 +93 -8
  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/cache.test.js +2 -2
  23. package/dist/test/resources/provider.test.js +116 -21
  24. package/package.json +4 -4
  25. package/src/handler.ts +48 -0
  26. package/src/httpErrors.ts +30 -0
  27. package/src/index.ts +2 -0
  28. package/src/integration.ts +53 -20
  29. package/src/middlewares/filters.ts +11 -2
  30. package/src/middlewares/secrets.ts +5 -0
  31. package/src/middlewares/signal.ts +41 -0
  32. package/src/resources/cache.ts +49 -11
  33. package/src/resources/context.ts +50 -33
  34. package/src/resources/logger.ts +17 -0
  35. package/src/resources/provider.ts +118 -19
  36. package/test/middlewares/signal.test.ts +28 -0
  37. package/test/resources/cache.test.ts +2 -2
  38. 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
  }
@@ -119,42 +136,83 @@ class Logger {
119
136
  }
120
137
 
121
138
  /**
122
- * Array of created caches kept to allow for graceful shutdown on exit signals.
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}
123
143
  */
124
- const caches = [];
125
- const shutdownCaches = async () => {
126
- return Promise.allSettled(caches.map(cache => cache.quit()));
127
- };
128
144
  class Cache {
129
145
  cacheInstance;
130
146
  constructor(cacheInstance) {
131
147
  this.cacheInstance = cacheInstance;
132
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
+ */
133
162
  getOrFetchValue(key, ttl, fetcher, lockTtl, shouldCacheError) {
134
163
  return this.cacheInstance.getOrFetchValue(key, ttl, fetcher, lockTtl, shouldCacheError);
135
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
+ */
136
173
  getValue(key) {
137
174
  return this.cacheInstance.getValue(key);
138
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
+ */
139
186
  setValue(key, value, ttl) {
140
187
  return this.cacheInstance.setValue(key, value, ttl);
141
188
  }
189
+ /**
190
+ * Delete a value from the cache.
191
+ * @param key — The key of the value to set.
192
+ */
142
193
  delValue(key) {
143
194
  return this.cacheInstance.delValue(key);
144
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
+ */
145
205
  getTtl(key) {
146
206
  return this.cacheInstance.getTtl(key);
147
207
  }
148
208
  /**
149
- * 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.
150
210
  *
151
211
  * @param redisUrl - The redis url to connect to (optional).
152
212
  * @returns A cache instance.
153
213
  */
154
214
  static create(redisUrl) {
155
215
  const cacheInstance = redisUrl ? new cachette.WriteThroughCache(redisUrl) : new cachette.LocalCache();
156
- // Push to the array of caches for graceful shutdown on exit signals.
157
- caches.push(cacheInstance);
158
216
  // Intended: the correlation id will be the same for all logs of Cachette.
159
217
  const correlationId = uuid__namespace.v4();
160
218
  const logger = new Logger({ correlation_id: correlationId });
@@ -172,43 +230,73 @@ class Cache {
172
230
  }
173
231
  }
174
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
+ */
175
240
  class HttpError extends Error {
176
241
  status;
177
242
  constructor(message, status) {
178
243
  super(message);
179
244
  this.status = status;
245
+ this.name = this.constructor.name;
180
246
  }
181
247
  }
248
+ /**
249
+ * Used to generate a 400 Bad Request. Usually used when something is missing to properly handle the request.
250
+ */
182
251
  class BadRequestError extends HttpError {
183
252
  constructor(message) {
184
253
  super(message || 'Bad request', 400);
185
254
  }
186
255
  }
256
+ /**
257
+ * Used to generate a 401 Unauthorized. Usually used when the credentials are missing or invalid.
258
+ */
187
259
  class UnauthorizedError extends HttpError {
188
260
  constructor(message) {
189
261
  super(message || 'Unauthorized', 401);
190
262
  }
191
263
  }
264
+ /**
265
+ * Used to generate a 404 Not Found. Usually used when the requested `Item` is not found.
266
+ */
192
267
  class NotFoundError extends HttpError {
193
268
  constructor(message) {
194
269
  super(message || 'Not found', 404);
195
270
  }
196
271
  }
272
+ /**
273
+ * Used to generate a 408 Timeout Error. Usually used when the call length exceeds the received Operation Deadline.
274
+ */
197
275
  class TimeoutError extends HttpError {
198
276
  constructor(message) {
199
277
  super(message || 'Not found', 408);
200
278
  }
201
279
  }
280
+ /**
281
+ * Used to generate a 410 Resource Gone.
282
+ */
202
283
  class ResourceGoneError extends HttpError {
203
284
  constructor(message) {
204
285
  super(message || 'Resource gone or unavailable', 410);
205
286
  }
206
287
  }
288
+ /**
289
+ * Used to generate a 422 Unprocessable Entity. Usually used when an operation is invalid.
290
+ */
207
291
  class UnprocessableEntityError extends HttpError {
208
292
  constructor(message) {
209
293
  super(message || 'Unprocessable Entity', 422);
210
294
  }
211
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
+ */
212
300
  class RateLimitExceededError extends HttpError {
213
301
  constructor(message) {
214
302
  super(message || 'Rate Limit Exceeded', 429);
@@ -265,13 +353,13 @@ function buildHttpError(responseStatus, message) {
265
353
  return httpError;
266
354
  }
267
355
 
268
- const middleware$6 = (req, res, next) => {
356
+ const middleware$8 = (req, res, next) => {
269
357
  res.locals.correlationId = req.header('X-Unito-Correlation-Id') ?? uuid__namespace.v4();
270
358
  next();
271
359
  };
272
360
 
273
361
  const ADDITIONAL_CONTEXT_HEADER = 'X-Unito-Additional-Logging-Context';
274
- const middleware$5 = (req, res, next) => {
362
+ const middleware$7 = (req, res, next) => {
275
363
  const logger = new Logger({ correlation_id: res.locals.correlationId });
276
364
  res.locals.logger = logger;
277
365
  const rawAdditionalContext = req.header(ADDITIONAL_CONTEXT_HEADER);
@@ -288,7 +376,7 @@ const middleware$5 = (req, res, next) => {
288
376
  };
289
377
 
290
378
  const CREDENTIALS_HEADER = 'X-Unito-Credentials';
291
- const middleware$4 = (req, res, next) => {
379
+ const middleware$6 = (req, res, next) => {
292
380
  const credentialsHeader = req.header(CREDENTIALS_HEADER);
293
381
  if (credentialsHeader) {
294
382
  let credentials;
@@ -303,6 +391,43 @@ const middleware$4 = (req, res, next) => {
303
391
  next();
304
392
  };
305
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
+
415
+ const SECRETS_HEADER = 'X-Unito-Secrets';
416
+ const middleware$4 = (req, res, next) => {
417
+ const secretsHeader = req.header(SECRETS_HEADER);
418
+ if (secretsHeader) {
419
+ let secrets;
420
+ try {
421
+ secrets = JSON.parse(Buffer.from(secretsHeader, 'base64').toString('utf8'));
422
+ }
423
+ catch {
424
+ throw new BadRequestError(`Malformed HTTP header ${SECRETS_HEADER}`);
425
+ }
426
+ res.locals.secrets = secrets;
427
+ }
428
+ next();
429
+ };
430
+
306
431
  const middleware$3 = (req, res, next) => {
307
432
  const rawSelect = req.query.select;
308
433
  if (typeof rawSelect === 'string') {
@@ -484,6 +609,7 @@ class Handler {
484
609
  selects: res.locals.selects,
485
610
  filters: res.locals.filters,
486
611
  logger: res.locals.logger,
612
+ signal: res.locals.signal,
487
613
  params: req.params,
488
614
  query: req.query,
489
615
  });
@@ -503,6 +629,7 @@ class Handler {
503
629
  secrets: res.locals.secrets,
504
630
  body: req.body,
505
631
  logger: res.locals.logger,
632
+ signal: res.locals.signal,
506
633
  params: req.params,
507
634
  query: req.query,
508
635
  });
@@ -520,6 +647,7 @@ class Handler {
520
647
  credentials: res.locals.credentials,
521
648
  secrets: res.locals.secrets,
522
649
  logger: res.locals.logger,
650
+ signal: res.locals.signal,
523
651
  params: req.params,
524
652
  query: req.query,
525
653
  });
@@ -539,6 +667,7 @@ class Handler {
539
667
  secrets: res.locals.secrets,
540
668
  body: req.body,
541
669
  logger: res.locals.logger,
670
+ signal: res.locals.signal,
542
671
  params: req.params,
543
672
  query: req.query,
544
673
  });
@@ -556,6 +685,7 @@ class Handler {
556
685
  credentials: res.locals.credentials,
557
686
  secrets: res.locals.secrets,
558
687
  logger: res.locals.logger,
688
+ signal: res.locals.signal,
559
689
  params: req.params,
560
690
  query: req.query,
561
691
  });
@@ -573,6 +703,7 @@ class Handler {
573
703
  credentials: res.locals.credentials,
574
704
  secrets: res.locals.secrets,
575
705
  logger: res.locals.logger,
706
+ signal: res.locals.signal,
576
707
  params: req.params,
577
708
  query: req.query,
578
709
  });
@@ -587,6 +718,7 @@ class Handler {
587
718
  const response = await handler({
588
719
  secrets: res.locals.secrets,
589
720
  logger: res.locals.logger,
721
+ signal: res.locals.signal,
590
722
  params: req.params,
591
723
  query: req.query,
592
724
  body: req.body,
@@ -602,6 +734,7 @@ class Handler {
602
734
  const response = await handler({
603
735
  secrets: res.locals.secrets,
604
736
  logger: res.locals.logger,
737
+ signal: res.locals.signal,
605
738
  params: req.params,
606
739
  query: req.query,
607
740
  body: req.body,
@@ -622,6 +755,7 @@ class Handler {
622
755
  credentials: res.locals.credentials,
623
756
  body: req.body,
624
757
  logger: res.locals.logger,
758
+ signal: res.locals.signal,
625
759
  params: req.params,
626
760
  query: req.query,
627
761
  });
@@ -638,14 +772,56 @@ function printErrorMessage(message) {
638
772
  console.error(`\x1b[31m Oops! Something went wrong! \x1b[0m`);
639
773
  console.error(message);
640
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
+ */
641
787
  class Integration {
642
788
  handlers;
643
789
  instance = undefined;
644
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
+ */
645
796
  constructor(options = {}) {
646
797
  this.port = options.port || 9200;
647
798
  this.handlers = [];
648
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
+ */
649
825
  addHandler(path, handlers) {
650
826
  if (this.instance) {
651
827
  printErrorMessage(`
@@ -678,6 +854,13 @@ class Integration {
678
854
  process.exit(1);
679
855
  }
680
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
+ */
681
864
  start() {
682
865
  // Express Server initialization
683
866
  const app = express();
@@ -686,10 +869,12 @@ class Integration {
686
869
  app.use(express.json());
687
870
  // Must be one of the first handlers (to catch all the errors).
688
871
  app.use(middleware$1);
872
+ app.use(middleware$8);
873
+ app.use(middleware$7);
689
874
  app.use(middleware$6);
690
- app.use(middleware$5);
691
875
  app.use(middleware$4);
692
876
  app.use(middleware$3);
877
+ app.use(middleware$5);
693
878
  // Load handlers as needed.
694
879
  if (this.handlers.length) {
695
880
  for (const handler of this.handlers) {
@@ -710,30 +895,24 @@ class Integration {
710
895
  app.use(middleware);
711
896
  // Start the server.
712
897
  this.instance = app.listen(this.port, () => console.info(`Server started on port ${this.port}.`));
713
- // Trap exit signals.
714
- ['SIGTERM', 'SIGINT', 'SIGUSR2'].forEach(signalType => {
715
- process.once(signalType, async () => {
716
- console.info(`Received termination signal ${signalType}. Exiting.`);
717
- try {
718
- if (this.instance) {
719
- this.instance.close();
720
- }
721
- await shutdownCaches();
722
- }
723
- catch (e) {
724
- console.error('Failed to gracefully exit', e);
725
- }
726
- process.exit();
727
- });
728
- });
729
898
  }
730
899
  }
731
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
+ */
732
911
  class Provider {
733
912
  rateLimiter = undefined;
734
913
  prepareRequest;
735
914
  /**
736
- * Initialize a Provider with the given options.
915
+ * Initializes a Provider with the given options.
737
916
  *
738
917
  * @property prepareRequest - function to define the Provider's base URL and specific headers to add to the request.
739
918
  * @property rateLimiter - function to limit the rate of calls to the provider based on the caller's credentials.
@@ -742,52 +921,108 @@ class Provider {
742
921
  this.prepareRequest = options.prepareRequest;
743
922
  this.rateLimiter = options.rateLimiter;
744
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
+ */
745
935
  async get(endpoint, options) {
746
936
  return this.fetchWrapper(endpoint, null, {
747
937
  ...options,
748
938
  method: 'GET',
749
939
  defaultHeaders: {
750
- 'Content-Type': 'application/json',
751
940
  Accept: 'application/json',
752
941
  },
753
942
  });
754
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
+ */
755
956
  async post(endpoint, body, options) {
756
957
  return this.fetchWrapper(endpoint, body, {
757
958
  ...options,
758
959
  method: 'POST',
759
960
  defaultHeaders: {
760
- 'Content-Type': 'application/x-www-form-urlencoded',
961
+ 'Content-Type': 'application/json',
761
962
  Accept: 'application/json',
762
963
  },
763
964
  });
764
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
+ */
765
978
  async put(endpoint, body, options) {
766
979
  return this.fetchWrapper(endpoint, body, {
767
980
  ...options,
768
981
  method: 'PUT',
769
982
  defaultHeaders: {
770
- 'Content-Type': 'application/x-www-form-urlencoded',
983
+ 'Content-Type': 'application/json',
771
984
  Accept: 'application/json',
772
985
  },
773
986
  });
774
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
+ */
775
1000
  async patch(endpoint, body, options) {
776
1001
  return this.fetchWrapper(endpoint, body, {
777
1002
  ...options,
778
1003
  method: 'PATCH',
779
1004
  defaultHeaders: {
780
- 'Content-Type': 'application/x-www-form-urlencoded',
1005
+ 'Content-Type': 'application/json',
781
1006
  Accept: 'application/json',
782
1007
  },
783
1008
  });
784
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
+ */
785
1021
  async delete(endpoint, options) {
786
1022
  return this.fetchWrapper(endpoint, null, {
787
1023
  ...options,
788
1024
  method: 'DELETE',
789
1025
  defaultHeaders: {
790
- 'Content-Type': 'application/x-www-form-urlencoded',
791
1026
  Accept: 'application/json',
792
1027
  },
793
1028
  });
@@ -809,11 +1044,26 @@ class Provider {
809
1044
  }
810
1045
  }
811
1046
  const callToProvider = async () => {
812
- const response = await fetch(absoluteUrl, {
813
- method: options.method,
814
- headers,
815
- body: stringifiedBody,
816
- });
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
+ }
817
1067
  if (response.status >= 400) {
818
1068
  const textResult = await response.text();
819
1069
  throw buildHttpError(response.status, textResult);
@@ -3,6 +3,8 @@ export { Cache } from './resources/cache.js';
3
3
  export { default as Integration } from './integration.js';
4
4
  export * from './handler.js';
5
5
  export { Provider, type Response as ProviderResponse, type RequestOptions as ProviderRequestOptions, type RateLimiter, } from './resources/provider.js';
6
+ export type { Secrets } from './middlewares/secrets.js';
6
7
  export type { Credentials } from './middlewares/credentials.js';
7
8
  export * as HttpErrors from './httpErrors.js';
8
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 {};