@unito/integration-sdk 0.1.6 → 0.1.8

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 (40) hide show
  1. package/.eslintrc.cjs +2 -0
  2. package/dist/src/errors.d.ts +0 -6
  3. package/dist/src/errors.js +3 -6
  4. package/dist/src/httpErrors.d.ts +3 -3
  5. package/dist/src/httpErrors.js +5 -5
  6. package/dist/src/index.cjs +829 -0
  7. package/dist/src/index.d.ts +1 -0
  8. package/dist/src/index.js +1 -0
  9. package/dist/src/integration.d.ts +0 -1
  10. package/dist/src/integration.js +2 -7
  11. package/dist/src/middlewares/errors.d.ts +9 -1
  12. package/dist/src/middlewares/errors.js +28 -13
  13. package/dist/src/middlewares/finish.d.ts +3 -1
  14. package/dist/src/middlewares/finish.js +25 -2
  15. package/dist/src/resources/cache.d.ts +18 -4
  16. package/dist/src/resources/cache.js +52 -21
  17. package/dist/src/resources/logger.d.ts +49 -10
  18. package/dist/src/resources/logger.js +64 -18
  19. package/dist/src/resources/provider.d.ts +1 -1
  20. package/dist/src/resources/provider.js +2 -2
  21. package/dist/test/errors.test.js +1 -0
  22. package/dist/test/middlewares/errors.test.js +1 -1
  23. package/dist/test/resources/cache.test.js +7 -15
  24. package/dist/test/resources/logger.test.js +53 -6
  25. package/dist/test/resources/provider.test.js +6 -6
  26. package/package.json +11 -4
  27. package/src/errors.ts +2 -6
  28. package/src/httpErrors.ts +6 -6
  29. package/src/index.ts +1 -0
  30. package/src/integration.ts +2 -9
  31. package/src/middlewares/errors.ts +39 -14
  32. package/src/middlewares/finish.ts +28 -3
  33. package/src/resources/cache.ts +66 -23
  34. package/src/resources/logger.ts +84 -24
  35. package/src/resources/provider.ts +3 -3
  36. package/test/errors.test.ts +1 -0
  37. package/test/middlewares/errors.test.ts +1 -1
  38. package/test/resources/cache.test.ts +7 -17
  39. package/test/resources/logger.test.ts +60 -7
  40. package/test/resources/provider.test.ts +6 -6
@@ -41,7 +41,7 @@ describe('Provider', () => {
41
41
  },
42
42
  },
43
43
  ]);
44
- assert.deepEqual(actualResponse, { status: 200, headers: response.headers, data: { data: 'value' } });
44
+ assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
45
45
  });
46
46
  it('post with url encoded body', async (context) => {
47
47
  const response = new Response('{"data": "value"}', {
@@ -71,7 +71,7 @@ describe('Provider', () => {
71
71
  },
72
72
  },
73
73
  ]);
74
- assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
74
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
75
75
  });
76
76
  it('put with json body', async (context) => {
77
77
  const response = new Response('{"data": "value"}', {
@@ -102,7 +102,7 @@ describe('Provider', () => {
102
102
  },
103
103
  },
104
104
  ]);
105
- assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
105
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
106
106
  });
107
107
  it('patch with query params', async (context) => {
108
108
  const response = new Response('{"data": "value"}', {
@@ -133,7 +133,7 @@ describe('Provider', () => {
133
133
  },
134
134
  },
135
135
  ]);
136
- assert.deepEqual(actualResponse, { status: 201, headers: response.headers, data: { data: 'value' } });
136
+ assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
137
137
  });
138
138
  it('delete', async (context) => {
139
139
  const response = new Response(undefined, {
@@ -161,7 +161,7 @@ describe('Provider', () => {
161
161
  },
162
162
  },
163
163
  ]);
164
- assert.deepEqual(actualResponse, { status: 204, headers: response.headers, data: undefined });
164
+ assert.deepEqual(actualResponse, { status: 204, headers: response.headers, body: undefined });
165
165
  });
166
166
  it('uses rate limiter if provided', async (context) => {
167
167
  const mockRateLimiter = context.mock.fn((_context, request) => Promise.resolve(request()));
@@ -205,7 +205,7 @@ describe('Provider', () => {
205
205
  },
206
206
  },
207
207
  ]);
208
- assert.deepEqual(actualResponse, { status: 204, headers: response.headers, data: undefined });
208
+ assert.deepEqual(actualResponse, { status: 204, headers: response.headers, body: undefined });
209
209
  });
210
210
  it('throws on invalid json response', async (context) => {
211
211
  const response = new Response('{invalidJSON}', {
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@unito/integration-sdk",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Integration SDK",
5
5
  "type": "module",
6
6
  "types": "dist/src/index.d.ts",
7
7
  "exports": {
8
8
  "./package.json": "./package.json",
9
- ".": "./dist/src/index.js"
9
+ ".": {
10
+ "import": "./dist/src/index.js",
11
+ "require": "./dist/src/index.cjs"
12
+ }
10
13
  },
11
14
  "license": "LicenseRef-LICENSE",
12
15
  "engines": {
@@ -21,8 +24,10 @@
21
24
  "prepublishOnly": "npm run lint && npm run test",
22
25
  "prepare": "npm run compile",
23
26
  "lint": "eslint --fix src test --ext .ts && prettier --write src test",
24
- "compile": "tsc --build",
25
- "compile:watch": "npm run compile -- --watch",
27
+ "compile": "npm run compile:esm && npm run compile:cjs",
28
+ "compile:watch": "nodemon --watch \"src/**\" --ext js,ts --exec \"npm run compile\"",
29
+ "compile:esm": "tsc --build",
30
+ "compile:cjs": "rollup dist/src/index.js --file dist/src/index.cjs --format cjs",
26
31
  "test": "NODE_ENV=test node --test --no-warnings --loader ts-node/esm --test-name-pattern=${ONLY:-.*} $(find test -type f -name '*.test.ts')",
27
32
  "test:debug": "NODE_ENV=test node --test --loader ts-node/esm --test-name-pattern=${ONLY:-.*} --inspect-brk $(find test -type f -name '*.test.ts')",
28
33
  "ci:test": "npm run test"
@@ -36,7 +41,9 @@
36
41
  "c8": "9.x",
37
42
  "eslint": "8.x",
38
43
  "prettier": "3.x",
44
+ "rollup": "4.x",
39
45
  "ts-node": "10.x",
46
+ "nodemon": "3.x",
40
47
  "typescript": "5.x"
41
48
  },
42
49
  "dependencies": {
package/src/errors.ts CHANGED
@@ -1,11 +1,5 @@
1
1
  import * as HttpErrors from './httpErrors.js';
2
2
 
3
- export class NoIntegrationFoundError extends Error {}
4
-
5
- export class NoConfigurationFileError extends Error {}
6
-
7
- export class ConfigurationMalformed extends Error {}
8
-
9
3
  export class InvalidHandler extends Error {}
10
4
 
11
5
  /**
@@ -25,6 +19,8 @@ export function buildHttpError(responseStatus: number, message: string): HttpErr
25
19
  httpError = new HttpErrors.NotFoundError(message);
26
20
  } else if (responseStatus === 408) {
27
21
  httpError = new HttpErrors.TimeoutError(message);
22
+ } else if (responseStatus === 410) {
23
+ httpError = new HttpErrors.ResourceGoneError(message);
28
24
  } else if (responseStatus === 422) {
29
25
  httpError = new HttpErrors.UnprocessableEntityError(message);
30
26
  } else if (responseStatus === 429) {
package/src/httpErrors.ts CHANGED
@@ -31,20 +31,20 @@ export class TimeoutError extends HttpError {
31
31
  }
32
32
  }
33
33
 
34
- export class UnprocessableEntityError extends HttpError {
34
+ export class ResourceGoneError extends HttpError {
35
35
  constructor(message?: string) {
36
- super(message || 'Unprocessable Entity', 422);
36
+ super(message || 'Resource gone or unavailable', 410);
37
37
  }
38
38
  }
39
39
 
40
- export class RateLimitExceededError extends HttpError {
40
+ export class UnprocessableEntityError extends HttpError {
41
41
  constructor(message?: string) {
42
- super(message || 'Rate Limit Exceeded', 429);
42
+ super(message || 'Unprocessable Entity', 422);
43
43
  }
44
44
  }
45
45
 
46
- export class WouldExceedLimitError extends HttpError {
46
+ export class RateLimitExceededError extends HttpError {
47
47
  constructor(message?: string) {
48
- super(message || 'Would Exceed Limit', 429);
48
+ super(message || 'Rate Limit Exceeded', 429);
49
49
  }
50
50
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /* c8 ignore start */
2
2
  export * as Api from '@unito/integration-api';
3
+ export { Cache } from './resources/cache.js';
3
4
  export { default as Integration } from './integration.js';
4
5
  export * from './handler.js';
5
6
  export {
@@ -9,7 +9,7 @@ import selectsMiddleware from './middlewares/selects.js';
9
9
  import errorsMiddleware from './middlewares/errors.js';
10
10
  import finishMiddleware from './middlewares/finish.js';
11
11
  import notFoundMiddleware from './middlewares/notFound.js';
12
- import { initializeCache, Cache } from './resources/cache.js';
12
+ import { shutdownCaches } from './resources/cache.js';
13
13
  import { HandlersInput, Handler } from './handler.js';
14
14
 
15
15
  function printErrorMessage(message: string) {
@@ -27,8 +27,6 @@ export default class Integration {
27
27
 
28
28
  private instance: Server<typeof IncomingMessage, typeof ServerResponse> | undefined = undefined;
29
29
 
30
- private cache: Cache | undefined = undefined;
31
-
32
30
  private port: number;
33
31
 
34
32
  constructor(options: Options = {}) {
@@ -69,9 +67,6 @@ export default class Integration {
69
67
  }
70
68
 
71
69
  public start() {
72
- // Initialize the cache.
73
- this.cache = initializeCache();
74
-
75
70
  // Express Server initialization
76
71
  const app: express.Application = express();
77
72
 
@@ -121,9 +116,7 @@ export default class Integration {
121
116
  this.instance.close();
122
117
  }
123
118
 
124
- if (this.cache) {
125
- await this.cache.quit();
126
- }
119
+ await shutdownCaches();
127
120
  } catch (e) {
128
121
  console.error('Failed to gracefully exit', e);
129
122
  }
@@ -1,30 +1,55 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { Error as APIError } from '@unito/integration-api';
2
+ import { Error as ApiError } from '@unito/integration-api';
3
+
3
4
  import { HttpError } from '../httpErrors.js';
4
5
 
6
+ declare global {
7
+ // eslint-disable-next-line @typescript-eslint/no-namespace
8
+ namespace Express {
9
+ interface Locals {
10
+ error?: ApiError;
11
+ }
12
+ }
13
+ }
14
+
5
15
  const middleware = (err: Error, _req: Request, res: Response, next: NextFunction) => {
6
16
  if (res.headersSent) {
7
17
  return next(err);
8
18
  }
9
19
 
10
- if (process.env.NODE_ENV !== 'production') {
11
- res.locals.logger.error(err);
12
- }
20
+ let error: ApiError;
13
21
 
14
22
  if (err instanceof HttpError) {
15
- return res.status(err.status).json(err.message);
23
+ error = {
24
+ code: err.status.toString(),
25
+ message: err.message,
26
+ details: {
27
+ stack: err.stack,
28
+ },
29
+ };
30
+ } else {
31
+ error = {
32
+ code: '500',
33
+ message: 'Oops! Something went wrong',
34
+ originalError: {
35
+ code: err.name,
36
+ message: err.message,
37
+ details: {
38
+ stack: err.stack,
39
+ },
40
+ },
41
+ };
16
42
  }
17
43
 
18
- const originalError: APIError = {
19
- code: err.name,
20
- message: err.message,
21
- };
44
+ res.locals.error = structuredClone(error);
45
+
46
+ // Keep the stack details in development for the Debugger
47
+ if (process.env.NODE_ENV !== 'development') {
48
+ delete error.details;
49
+ delete error.originalError?.details;
50
+ }
22
51
 
23
- res.status(500).json({
24
- code: '500',
25
- message: 'Oops! Something went wrong',
26
- originalError,
27
- } as APIError);
52
+ res.status(Number(error.code)).json(error);
28
53
  };
29
54
 
30
55
  export default middleware;
@@ -1,11 +1,13 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import Logger from '../resources/logger.js';
2
+ import { Error as ApiError } from '@unito/integration-api';
3
+ import { default as Logger, Metadata } from '../resources/logger.js';
3
4
 
4
5
  declare global {
5
6
  // eslint-disable-next-line @typescript-eslint/no-namespace
6
7
  namespace Express {
7
8
  interface Locals {
8
9
  logger: Logger;
10
+ error?: ApiError;
9
11
  }
10
12
  }
11
13
  }
@@ -13,8 +15,31 @@ declare global {
13
15
  const middleware = (req: Request, res: Response, next: NextFunction) => {
14
16
  if (req.originalUrl !== '/health') {
15
17
  res.on('finish', function () {
16
- const loggerLevel = res.statusCode >= 500 ? 'error' : 'info';
17
- res.locals.logger[loggerLevel](`${req.method} ${req.originalUrl} ${res.statusCode}`);
18
+ const error = res.locals.error;
19
+
20
+ const message = `${req.method} ${req.originalUrl} ${res.statusCode}`;
21
+ const metadata: Metadata = {
22
+ // Use reserved and standard attributes of Datadog
23
+ // https://app.datadoghq.com/logs/pipelines/standard-attributes
24
+ http: { method: req.method, status_code: res.statusCode, url_details: { path: req.originalUrl } },
25
+ ...(error
26
+ ? {
27
+ error: {
28
+ kind: error.message,
29
+ stack: (error.originalError?.details?.stack ?? error.details?.stack) as string,
30
+ message: error.originalError?.message ?? error.message,
31
+ },
32
+ }
33
+ : {}),
34
+ };
35
+
36
+ if ([404, 429].includes(res.statusCode)) {
37
+ res.locals.logger.warn(message, metadata);
38
+ } else if (res.statusCode >= 400) {
39
+ res.locals.logger.error(message, metadata);
40
+ } else {
41
+ res.locals.logger.info(message, metadata);
42
+ }
18
43
  });
19
44
  }
20
45
 
@@ -1,34 +1,77 @@
1
- import { WriteThroughCache, LocalCache, CacheInstance } from 'cachette';
2
- import { createHash } from 'node:crypto';
1
+ import { WriteThroughCache, LocalCache, CacheInstance, FetchingFunction, CachableValue } from 'cachette';
3
2
  import * as uuid from 'uuid';
4
3
  import Logger from './logger.js';
5
4
 
6
- export type Cache = CacheInstance;
5
+ /**
6
+ * Array of created caches kept to allow for graceful shutdown on exit signals.
7
+ */
8
+ const caches: CacheInstance[] = [];
7
9
 
8
- export function initializeCache(): Cache {
9
- const cacheInstance: Cache = process.env.REDIS_URL ? new WriteThroughCache(process.env.REDIS_URL) : new LocalCache();
10
+ export const shutdownCaches = async () => {
11
+ return Promise.allSettled(caches.map(cache => cache.quit()));
12
+ };
10
13
 
11
- // Intended: the correlation id will be the same for all logs of Cachette.
12
- const correlationId = uuid.v4();
14
+ export class Cache {
15
+ private cacheInstance: CacheInstance;
13
16
 
14
- const logger = new Logger({ correlation_id: correlationId });
17
+ private constructor(cacheInstance: CacheInstance) {
18
+ this.cacheInstance = cacheInstance;
19
+ }
15
20
 
16
- cacheInstance
17
- .on('info', message => {
18
- logger.info(message);
19
- })
20
- .on('warn', message => {
21
- logger.warn(message);
22
- })
23
- .on('error', message => {
24
- logger.error(message);
25
- });
21
+ public getOrFetchValue<F extends FetchingFunction = FetchingFunction>(
22
+ key: string,
23
+ ttl: number,
24
+ fetcher: F,
25
+ lockTtl?: number,
26
+ shouldCacheError?: (err: Error) => boolean,
27
+ ): Promise<ReturnType<F>> {
28
+ return this.cacheInstance.getOrFetchValue(key, ttl, fetcher, lockTtl, shouldCacheError);
29
+ }
26
30
 
27
- return cacheInstance;
28
- }
31
+ public getValue(key: string): Promise<CachableValue> {
32
+ return this.cacheInstance.getValue(key);
33
+ }
34
+
35
+ public setValue(key: string, value: CachableValue, ttl?: number): Promise<boolean> {
36
+ return this.cacheInstance.setValue(key, value, ttl);
37
+ }
38
+
39
+ public delValue(key: string): Promise<void> {
40
+ return this.cacheInstance.delValue(key);
41
+ }
42
+
43
+ public getTtl(key: string): Promise<number | undefined> {
44
+ return this.cacheInstance.getTtl(key);
45
+ }
46
+
47
+ /**
48
+ * Initializes a WriteThroughCache instance with the provided redis url if present, or a LocalCache otherwise.
49
+ *
50
+ * @param redisUrl - The redis url to connect to (optional).
51
+ * @returns A cache instance.
52
+ */
53
+ public static create(redisUrl?: string): Cache {
54
+ const cacheInstance: CacheInstance = redisUrl ? new WriteThroughCache(redisUrl) : new LocalCache();
55
+
56
+ // Push to the array of caches for graceful shutdown on exit signals.
57
+ caches.push(cacheInstance);
58
+
59
+ // Intended: the correlation id will be the same for all logs of Cachette.
60
+ const correlationId = uuid.v4();
61
+
62
+ const logger = new Logger({ correlation_id: correlationId });
29
63
 
30
- const shake256 = createHash('shake256');
64
+ cacheInstance
65
+ .on('info', message => {
66
+ logger.info(message);
67
+ })
68
+ .on('warn', message => {
69
+ logger.warn(message);
70
+ })
71
+ .on('error', message => {
72
+ logger.error(message);
73
+ });
31
74
 
32
- export function generateCacheKey(value: string): string {
33
- return shake256.update(value).digest('hex');
75
+ return new Cache(cacheInstance);
76
+ }
34
77
  }
@@ -1,4 +1,4 @@
1
- enum LogLevel {
1
+ const enum LogLevel {
2
2
  ERROR = 'error',
3
3
  WARN = 'warn',
4
4
  INFO = 'info',
@@ -6,52 +6,112 @@ enum LogLevel {
6
6
  DEBUG = 'debug',
7
7
  }
8
8
 
9
- export default class Logger {
10
- private metadata: Record<string, unknown> = {};
9
+ type PrimitiveValue = undefined | null | string | string[] | number | number[] | boolean | boolean[];
10
+ type Value = {
11
+ [key: string]: PrimitiveValue | Value;
12
+ };
11
13
 
12
- constructor(metadata?: Record<string, unknown>) {
13
- if (metadata) {
14
- this.metadata = structuredClone(metadata);
15
- }
16
- }
14
+ export type Metadata = Value & { message?: never };
15
+ type ForbidenMetadataKey = 'message';
17
16
 
18
- private send(logLevel: LogLevel, message: unknown) {
19
- console[logLevel](JSON.stringify({ message, ...this.metadata }));
17
+ export default class Logger {
18
+ private metadata: Metadata;
19
+
20
+ constructor(metadata: Metadata = {}) {
21
+ this.metadata = structuredClone(metadata);
20
22
  }
21
23
 
22
- public log(data: unknown) {
23
- this.send(LogLevel.LOG, data);
24
+ /**
25
+ * Logs a message with the 'log' log level.
26
+ * @param message The message to be logged.
27
+ * @param metadata Optional metadata to be associated with the log message.
28
+ */
29
+ public log(message: string, metadata?: Metadata): void {
30
+ this.send(LogLevel.LOG, message, metadata);
24
31
  }
25
32
 
26
- public error(data: unknown) {
27
- this.send(LogLevel.ERROR, data);
33
+ /**
34
+ * Logs an error message with the 'error' log level.
35
+ * @param message The error message to be logged.
36
+ * @param metadata Optional metadata to be associated with the log message.
37
+ */
38
+ public error(message: string, metadata?: Metadata): void {
39
+ this.send(LogLevel.ERROR, message, metadata);
28
40
  }
29
41
 
30
- public warn(data: unknown) {
31
- this.send(LogLevel.WARN, data);
42
+ /**
43
+ * Logs a warning message with the 'warn' log level.
44
+ * @param message The warning message to be logged.
45
+ * @param metadata Optional metadata to be associated with the log message.
46
+ */
47
+ public warn(message: string, metadata?: Metadata): void {
48
+ this.send(LogLevel.WARN, message, metadata);
32
49
  }
33
50
 
34
- public info(data: unknown) {
35
- this.send(LogLevel.INFO, data);
51
+ /**
52
+ * Logs an informational message with the 'info' log level.
53
+ * @param message The informational message to be logged.
54
+ * @param metadata Optional metadata to be associated with the log message.
55
+ */
56
+ public info(message: string, metadata?: Metadata): void {
57
+ this.send(LogLevel.INFO, message, metadata);
36
58
  }
37
59
 
38
- public debug(data: unknown) {
39
- this.send(LogLevel.DEBUG, data);
60
+ /**
61
+ * Logs a debug message with the 'debug' log level.
62
+ * @param message The debug message to be logged.
63
+ * @param metadata Optional metadata to be associated with the log message.
64
+ */
65
+ public debug(message: string, metadata?: Metadata): void {
66
+ this.send(LogLevel.DEBUG, message, metadata);
40
67
  }
41
68
 
42
- public decorate(metadata: Record<string, unknown>) {
69
+ /**
70
+ * Decorates the logger with additional metadata.
71
+ * @param metadata Additional metadata to be added to the logger.
72
+ */
73
+ public decorate(metadata: Metadata): void {
43
74
  this.metadata = { ...this.metadata, ...metadata };
44
75
  }
45
76
 
46
- public getMetadata() {
77
+ public getMetadata(): Metadata {
47
78
  return structuredClone(this.metadata);
48
79
  }
49
80
 
50
- public setMetadata(key: string, value: unknown) {
81
+ public setMetadata<Key extends string>(
82
+ key: Key extends ForbidenMetadataKey ? never : Key,
83
+ value: PrimitiveValue | Value,
84
+ ): void {
51
85
  this.metadata[key] = value;
52
86
  }
53
87
 
54
- public clearMetadata() {
88
+ public clearMetadata(): void {
55
89
  this.metadata = {};
56
90
  }
91
+
92
+ private send(logLevel: LogLevel, message: string, metadata?: Metadata): void {
93
+ const processedMessage = this.snakifyKeys({
94
+ ...this.metadata,
95
+ ...metadata,
96
+ message,
97
+ });
98
+
99
+ if (process.env.NODE_ENV === 'development') {
100
+ console[logLevel](JSON.stringify(processedMessage, null, 2));
101
+ } else {
102
+ console[logLevel](JSON.stringify(processedMessage));
103
+ }
104
+ }
105
+
106
+ private snakifyKeys<T extends Value>(value: T): T {
107
+ const result: Value = {};
108
+
109
+ for (const key in value) {
110
+ const deepValue = typeof value[key] === 'object' ? this.snakifyKeys(value[key] as Value) : value[key];
111
+ const snakifiedKey = key.replace(/[\w](?<!_)([A-Z])/g, k => `${k[0]}_${k[1]}`).toLowerCase();
112
+ result[snakifiedKey] = deepValue;
113
+ }
114
+
115
+ return result as T;
116
+ }
57
117
  }
@@ -31,7 +31,7 @@ export interface RequestOptions {
31
31
  }
32
32
 
33
33
  export interface Response<T> {
34
- data: T;
34
+ body: T;
35
35
  status: number;
36
36
  headers: Headers;
37
37
  }
@@ -151,8 +151,8 @@ export class Provider {
151
151
  }
152
152
 
153
153
  try {
154
- const data: T = response.body ? await response.json() : undefined;
155
- return { status: response.status, headers: response.headers, data };
154
+ const body: T = response.body ? await response.json() : undefined;
155
+ return { status: response.status, headers: response.headers, body };
156
156
  } catch {
157
157
  throw buildHttpError(400, 'Invalid JSON response');
158
158
  }
@@ -9,6 +9,7 @@ describe('handleErrorResponse', () => {
9
9
  assert.ok(errors.buildHttpError(403, 'forbidden') instanceof httpErrors.UnauthorizedError);
10
10
  assert.ok(errors.buildHttpError(404, 'not found') instanceof httpErrors.NotFoundError);
11
11
  assert.ok(errors.buildHttpError(408, 'timeout') instanceof httpErrors.TimeoutError);
12
+ assert.ok(errors.buildHttpError(410, 'resource gone') instanceof httpErrors.ResourceGoneError);
12
13
  assert.ok(errors.buildHttpError(422, 'unprocessable entity') instanceof httpErrors.UnprocessableEntityError);
13
14
  assert.ok(errors.buildHttpError(429, 'rate limit exceeded') instanceof httpErrors.RateLimitExceededError);
14
15
  assert.ok(errors.buildHttpError(500, 'internal server error') instanceof httpErrors.HttpError);
@@ -49,7 +49,7 @@ describe('errors middleware', () => {
49
49
  middleware(new HttpError('httpError', 429), {} as express.Request, response, () => {});
50
50
 
51
51
  assert.strictEqual(actualStatus, 429);
52
- assert.strictEqual(actualJson, 'httpError');
52
+ assert.deepEqual(actualJson, { code: '429', message: 'httpError' });
53
53
  });
54
54
 
55
55
  it('handles other error', () => {
@@ -1,31 +1,21 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { describe, it } from 'node:test';
3
3
  import { LocalCache } from 'cachette';
4
- import { generateCacheKey, initializeCache } from '../../src/resources/cache.js';
4
+ import { Cache, shutdownCaches } from '../../src/resources/cache.js';
5
5
 
6
6
  describe('Cache', () => {
7
7
  describe('initializeCache', () => {
8
- it('no redis url returns LocalCache', () => {
9
- const cache = initializeCache();
8
+ it('no redis url returns Cache with a inner LocalCache', async () => {
9
+ const cache = Cache.create();
10
10
 
11
- assert.equal(cache instanceof LocalCache, true);
11
+ assert.ok(cache instanceof Cache);
12
+ assert.ok(cache['cacheInstance'] instanceof LocalCache);
12
13
 
13
- cache.quit();
14
+ await shutdownCaches();
14
15
  });
15
16
 
16
17
  it('redis url returns (tries) WriteThroughCache', () => {
17
- process.env.REDIS_URL = 'fakeredis'; //'redis://localhost:6379';
18
- assert.throws(() => initializeCache(), Error, 'Invalid redis url fakereis.');
19
- });
20
- });
21
-
22
- describe('generateCacheKey', () => {
23
- it('hashes string and returns hex value', () => {
24
- const value = 'test';
25
- const hash = generateCacheKey(value);
26
-
27
- assert.equal(typeof hash, 'string');
28
- assert.equal(hash.length, 64);
18
+ assert.throws(() => Cache.create('fakeredis'), Error, 'Invalid redis url fakereis.');
29
19
  });
30
20
  });
31
21
  });