@devmoods/express-extras 0.35.1 → 0.37.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.
package/dist/cache.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import { type RedisClient } from './redis.js';
2
+ export interface CacheOptions {
3
+ key?: string;
4
+ expiresMs?: number | null;
5
+ }
2
6
  export interface CacheAdapter {
3
7
  get(key: string): Promise<any>;
4
8
  set(key: string, value: any, expires?: number | null | undefined): Promise<void>;
@@ -13,5 +17,6 @@ export declare function createCache(adapter: CacheAdapter): {
13
17
  set: (key: string, value: any, expires?: number | null | undefined) => Promise<void>;
14
18
  get: (key: string) => Promise<any>;
15
19
  clear: (key?: string) => Promise<void>;
16
- wrap: <T>(key: string, expiresMs: number | null | undefined, fn: () => Promise<T>) => Promise<T>;
20
+ wrap: <T>(key: string, options: CacheOptions | number | null, fn: () => Promise<T>) => Promise<T>;
21
+ decorate: <T, Arguments extends any[]>(options: CacheOptions | number | null, fn: (...args: Arguments) => T | Promise<T>) => (...args: Arguments) => Promise<T>;
17
22
  };
package/dist/cache.js CHANGED
@@ -53,19 +53,41 @@ export function createMemoryCacheAdapter() {
53
53
  };
54
54
  }
55
55
  export function createCache(adapter) {
56
- async function cached(key, expiresMs, fn) {
56
+ async function cached(key, options, fn) {
57
57
  const cachedValue = await adapter.get(key);
58
58
  if (cachedValue != null) {
59
59
  return cachedValue;
60
60
  }
61
+ const expiresMs = typeof options === 'number' || options == null
62
+ ? options
63
+ : options.expiresMs;
61
64
  const result = await fn();
62
65
  await adapter.set(key, result, expiresMs);
63
66
  return result;
64
67
  }
68
+ function cachedFn(options, fn) {
69
+ return async (...args) => {
70
+ const baseKey = typeof options === 'object' && options != null
71
+ ? options.key
72
+ : undefined;
73
+ const cacheKey = `${baseKey ?? Buffer.from(fn.toString()).toString('hex')}-${JSON.stringify(args)}`;
74
+ const cachedValue = await adapter.get(cacheKey);
75
+ if (cachedValue != null) {
76
+ return cachedValue;
77
+ }
78
+ const expiresMs = typeof options === 'number' || options == null
79
+ ? options
80
+ : options.expiresMs;
81
+ const result = await fn(...args);
82
+ await adapter.set(cacheKey, result, expiresMs);
83
+ return result;
84
+ };
85
+ }
65
86
  return {
66
87
  set: adapter.set.bind(adapter),
67
88
  get: adapter.get.bind(adapter),
68
89
  clear: adapter.clear.bind(adapter),
69
90
  wrap: cached,
91
+ decorate: cachedFn,
70
92
  };
71
93
  }
package/dist/cli.js CHANGED
@@ -31,7 +31,7 @@ export class CLI {
31
31
  const timer = startTimer();
32
32
  try {
33
33
  await action(...args);
34
- this.greenLine(`Command succeeded in ${timer.getDuration().toFixed(4)}s`);
34
+ this.greenLine(`Command succeeded in ${timer.getDuration().toFixed(4)}s`, stderr);
35
35
  }
36
36
  catch (error) {
37
37
  this.redLine(error.message);
package/dist/email.d.ts CHANGED
@@ -6,12 +6,12 @@ export interface EmailOptions {
6
6
  pass?: string;
7
7
  senderName: string;
8
8
  senderAddress: string;
9
+ onEmailSent?: (payload: Record<string, any>) => void | Promise<void>;
9
10
  }
10
11
  export interface SendEmailOptions extends nodemailer.SendMailOptions {
11
12
  message: string;
12
13
  }
13
- export declare function createEmail({ host, port, user, pass, senderName, senderAddress, }: EmailOptions): {
14
+ export declare function createEmail({ host, port, user, pass, senderName, senderAddress, onEmailSent, }: EmailOptions): {
14
15
  send: ({ to, subject, message, ...rest }: SendEmailOptions) => Promise<void>;
15
16
  transporter: nodemailer.Transporter<import("nodemailer/lib/smtp-transport/index.js").SentMessageInfo, import("nodemailer/lib/smtp-transport/index.js").Options>;
16
- readonly outbox: Record<string, any>[];
17
17
  };
package/dist/email.js CHANGED
@@ -1,14 +1,13 @@
1
1
  import * as nodemailer from 'nodemailer';
2
2
  import { getLogger } from './logger.js';
3
3
  const logger = getLogger('email');
4
- export function createEmail({ host, port, user, pass, senderName, senderAddress, }) {
4
+ export function createEmail({ host, port, user, pass, senderName, senderAddress, onEmailSent, }) {
5
5
  const transporter = nodemailer.createTransport({
6
6
  host,
7
7
  port: parseInt(port || '587', 10),
8
8
  secure: false,
9
9
  auth: { user, pass },
10
10
  });
11
- const outbox = [];
12
11
  const send = async ({ to, subject, message, ...rest }) => {
13
12
  const payload = {
14
13
  from: `${senderName} <${senderAddress}>`,
@@ -18,17 +17,11 @@ export function createEmail({ host, port, user, pass, senderName, senderAddress,
18
17
  ...rest,
19
18
  };
20
19
  const result = await transporter.sendMail(payload);
21
- outbox.push(payload);
22
- if (outbox.length > 5) {
23
- outbox.shift();
24
- }
20
+ await onEmailSent?.(payload);
25
21
  logger.info('email-sent', { email: result });
26
22
  };
27
23
  return {
28
24
  send,
29
25
  transporter,
30
- get outbox() {
31
- return outbox;
32
- },
33
26
  };
34
27
  }
package/dist/faktory.d.ts CHANGED
@@ -3,26 +3,32 @@ export interface JobsOptions {
3
3
  queueName: string;
4
4
  faktory?: typeof create;
5
5
  }
6
- export type JobContext = Record<string, any>;
7
- export declare function createJobs({ queueName, faktory: createFactory, }: JobsOptions): {
8
- job: <K extends Record<string, any>, T extends (input: K, ctx: JobContext) => undefined | Promise<any>>(fn: T) => {
6
+ export type AnyJobContext = Record<string, any>;
7
+ export interface JobOptions {
8
+ retry?: number;
9
+ priority?: number;
10
+ at?: Date | string;
11
+ }
12
+ export declare function createJobs<GlobalContext extends AnyJobContext>({ queueName, faktory: createFactory, }: JobsOptions): {
13
+ job: <Arguments extends Record<string, any>, Fn extends (input: Arguments, ctx: GlobalContext) => undefined | Promise<any>>(optionsOrFn: JobOptions | Fn, fnOrUndefined?: Fn) => {
9
14
  $job: boolean;
10
15
  jobName: string;
16
+ options: JobOptions;
11
17
  readonly executionCount: number;
12
- delay(args?: Parameters<T>[0]): Promise<`${string}-${string}-${string}-${string}-${string}`>;
13
- execute(args: Parameters<T>[0], context: JobContext): Promise<any>;
18
+ delay(args?: Parameters<Fn>[0]): Promise<`${string}-${string}-${string}-${string}-${string}`>;
19
+ execute(args: Parameters<Fn>[0], context: GlobalContext): Promise<any>;
14
20
  };
15
21
  queueName: string;
16
22
  };
17
23
  export type Job = ReturnType<ReturnType<typeof createJobs>['job']>;
18
- export interface FaktoryWorkerOptions {
24
+ export interface FaktoryWorkerOptions<GlobalContext> {
19
25
  jobs: unknown[];
20
26
  queueName: string;
21
27
  faktory?: typeof create;
22
- jobContext?: JobContext;
28
+ jobContext?: GlobalContext;
23
29
  }
24
30
  export declare function useJobId(): string | undefined;
25
- export declare function createFaktoryWorker({ queueName, jobs, faktory: createFactory, jobContext, }: FaktoryWorkerOptions): Promise<{
31
+ export declare function createFaktoryWorker<GlobalContext>({ queueName, jobs, faktory: createFactory, jobContext, }: FaktoryWorkerOptions<GlobalContext>): Promise<{
26
32
  start(workerOptions?: WorkerOptions): Promise<void>;
27
33
  stop(): Promise<void>;
28
34
  }>;
package/dist/faktory.js CHANGED
@@ -7,7 +7,12 @@ const logger = getLogger('faktory');
7
7
  const JOB_ID = 'jobId';
8
8
  export function createJobs({ queueName, faktory: createFactory = create, }) {
9
9
  const faktory = createFactory();
10
- function job(fn) {
10
+ function job(optionsOrFn, fnOrUndefined) {
11
+ const fn = typeof optionsOrFn === 'function' ? optionsOrFn : fnOrUndefined;
12
+ const options = typeof optionsOrFn === 'function' ? {} : optionsOrFn;
13
+ if (!fn) {
14
+ throw new Error('job() must get a function has the first or second argument');
15
+ }
11
16
  const jobName = fn.name;
12
17
  let executionCount = 0;
13
18
  if (!jobName) {
@@ -16,6 +21,7 @@ export function createJobs({ queueName, faktory: createFactory = create, }) {
16
21
  return {
17
22
  $job: true,
18
23
  jobName,
24
+ options,
19
25
  get executionCount() {
20
26
  return executionCount;
21
27
  },
@@ -26,6 +32,7 @@ export function createJobs({ queueName, faktory: createFactory = create, }) {
26
32
  const j = client.job(jobName, args);
27
33
  j.jid = jid;
28
34
  j.queue = queueName;
35
+ Object.assign(j, options);
29
36
  await j.push();
30
37
  await client.close();
31
38
  }
package/dist/listen.d.ts CHANGED
@@ -7,7 +7,7 @@ interface StoppableServer extends Server {
7
7
  export declare function stoppable(server: Server, grace?: number): StoppableServer;
8
8
  interface ExpressServerOptions {
9
9
  onShutdown?(): Promise<void> | void;
10
- healthCheck?(): Promise<unknown>;
10
+ healthCheck?(type?: 'live' | 'ready'): Promise<unknown>;
11
11
  }
12
12
  export declare function createExpressServer(app: Application, options?: ExpressServerOptions): {
13
13
  start(): Promise<void>;
package/dist/listen.js CHANGED
@@ -6,6 +6,7 @@ import {} from 'express';
6
6
  import { getLogger } from './logger.js';
7
7
  import { before, route } from './middleware.js';
8
8
  import { envPort, readEnv } from './utils.js';
9
+ import { validateQuery } from './validate.js';
9
10
  const logger = getLogger('http');
10
11
  const READINESS_PERIOD = readEnv.number('READINESS_PERIOD_MS', 0);
11
12
  const GRACE_PERIOD = readEnv.number('GRACE_PERIOD_MS', 20000);
@@ -96,16 +97,23 @@ export function createExpressServer(app, options = {}) {
96
97
  process.on(signal, onSignal);
97
98
  }
98
99
  if (!app.get('__healthAttached')) {
99
- app.get('/health', before(), route(async () => {
100
+ app.get('/health', before(), validateQuery({
101
+ type: 'object',
102
+ properties: {
103
+ type: { type: 'string', enum: ['live', 'ready'] },
104
+ },
105
+ }), route(async (req) => {
100
106
  const timestamp = new Date().toISOString();
101
107
  try {
102
108
  if (stopping) {
103
109
  throw new Error();
104
110
  }
105
- const meta = await options.healthCheck?.();
106
- return { status: 'ok', meta, timestamp };
111
+ const type = req.query.type;
112
+ const meta = await options.healthCheck?.(type);
113
+ return { status: 'ok', type, meta, timestamp };
107
114
  }
108
- catch {
115
+ catch (error) {
116
+ logger.error(error, 'Failed health check');
109
117
  return [503, { status: 'error', timestamp }];
110
118
  }
111
119
  }));
package/dist/manage.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { program } from 'commander';
2
2
  import {} from 'express';
3
- import { parseExpressApp } from 'express-route-parser';
4
3
  import { hashPassword } from './auth.js';
5
4
  import { CLI } from './cli.js';
6
5
  import { asCli } from './migrate.js';
6
+ import { parseRoutes } from './routes.js';
7
7
  export function registerManagementCommand(register) {
8
8
  const cli = new CLI({ printStackTraces: process.argv.includes('--verbose') });
9
9
  register(program.command.bind(program), cli);
@@ -17,12 +17,23 @@ export function registerDefaultCommands(app) {
17
17
  const migrate = command('migrate');
18
18
  asCli(migrate, cli);
19
19
  });
20
- registerManagementCommand((command, cli) => command('routes').action(cli.wrap(async () => {
20
+ registerManagementCommand((command, cli) => command('routes')
21
+ .option('--openapi', 'Output OpenAPI schema')
22
+ .option('--json', 'Output JSON')
23
+ .action(cli.wrap(async (options) => {
21
24
  if (!app)
22
25
  return;
23
- const routes = parseExpressApp(app);
26
+ if (options.openapi) {
27
+ console.error('Not implemented yet');
28
+ return;
29
+ }
30
+ const routes = parseRoutes(app);
31
+ if (options.json) {
32
+ console.log(JSON.stringify(routes, null, 2));
33
+ return;
34
+ }
24
35
  for (const route of routes) {
25
- cli.writeLine(`${route.method.toLocaleUpperCase().padEnd(5)} ${route.path}\n`);
36
+ cli.writeLine(`${route.method.padEnd(7)} ${route.path}\n`);
26
37
  }
27
38
  })));
28
39
  registerManagementCommand((command, cli) => {
package/dist/redis.js CHANGED
@@ -2,8 +2,10 @@ import { createClient } from 'redis';
2
2
  import { readEnv } from './utils.js';
3
3
  export function createRedis({ url = readEnv('REDIS_URL', ''), onError, } = {}) {
4
4
  const client = createClient({ url });
5
- if (onError) {
6
- client.on('error', onError);
7
- }
5
+ // Always attach a listener even if
6
+ // onError is not provided.
7
+ client.on('error', (error) => {
8
+ onError?.(error);
9
+ });
8
10
  return client;
9
11
  }
package/dist/routes.d.ts CHANGED
@@ -1,2 +1,22 @@
1
1
  import { type Express, type RequestHandler } from 'express';
2
2
  export declare function createDocsRouter(app: Express, extras?: Record<string, any>): RequestHandler;
3
+ export declare function parseRoutes(app: Express): {
4
+ method: string;
5
+ path: string;
6
+ params: {
7
+ type: string;
8
+ properties: {
9
+ [k: string]: {
10
+ type: string;
11
+ };
12
+ };
13
+ required: string[];
14
+ } | null;
15
+ query: {} | null;
16
+ body: {} | null;
17
+ }[];
18
+ export interface Key {
19
+ name: string;
20
+ optional: boolean;
21
+ offset: number;
22
+ }
package/dist/routes.js CHANGED
@@ -1,11 +1,168 @@
1
1
  import {} from 'express';
2
- import { parseExpressApp } from 'express-route-parser';
3
2
  import { route } from './middleware.js';
4
3
  export function createDocsRouter(app, extras = {}) {
5
4
  return route(async () => {
6
5
  return {
7
6
  ...extras,
8
- routes: parseExpressApp(app),
7
+ routes: parseRoutes(app),
9
8
  };
10
9
  });
11
10
  }
11
+ export function parseRoutes(app) {
12
+ return new ExpressPathParser(app).getPaths().map((routeMetadata) => {
13
+ const { method, path, pathParams, metadata } = routeMetadata;
14
+ return {
15
+ method: method.toLocaleUpperCase(),
16
+ path,
17
+ params: buildPathParamsSchema(pathParams),
18
+ query: metadata
19
+ ?.filter((m) => typeof m === 'object' && m != null && 'querySchema' in m)
20
+ .at(-1)?.querySchema ?? null,
21
+ body: metadata
22
+ ?.filter((m) => typeof m === 'object' && m != null && 'bodySchema' in m)
23
+ .at(-1)?.bodySchema ?? null,
24
+ };
25
+ });
26
+ }
27
+ const buildPathParamsSchema = (pathParams) => {
28
+ const required = [];
29
+ const params = [];
30
+ for (const param of pathParams) {
31
+ if (param.in !== 'path') {
32
+ continue;
33
+ }
34
+ params.push(param.name);
35
+ if (param.required) {
36
+ required.push(param.name);
37
+ }
38
+ }
39
+ if (params.length === 0) {
40
+ return null;
41
+ }
42
+ return {
43
+ type: 'object',
44
+ properties: Object.fromEntries(params.map((p) => [p, { type: 'string' }])),
45
+ required,
46
+ };
47
+ };
48
+ class ExpressPathParser {
49
+ getPaths() {
50
+ return this._appPaths;
51
+ }
52
+ constructor(app) {
53
+ /**
54
+ * Recursive traversal method for the express router and middleware tree.
55
+ *
56
+ * @param path The current path segment that we have traversed so far
57
+ * @param layer The current 'layer' of the router tree
58
+ * @param keys The keys for the parameter's in the current path branch of the traversal
59
+ * @returns void - base case saves result to internal object
60
+ */
61
+ this.traverse = (path, layer, keys) => {
62
+ keys = [...keys, ...layer.keys];
63
+ if (layer.name === 'router' && layer.handle) {
64
+ layer.handle.stack.forEach((l) => {
65
+ path = path || '';
66
+ this.traverse(`${path}/${pathRegexParser(layer.regexp, layer.keys)}`, l, keys);
67
+ });
68
+ }
69
+ if (!layer.route || !layer.route.stack.length) {
70
+ return;
71
+ }
72
+ this._appPaths.push(this.parseRouteLayer(layer, keys, path));
73
+ };
74
+ /**
75
+ * Parses a route object. Route objects are the leafs of an express router tree
76
+ *
77
+ * @param layer The layer of this route object - represents the stack of middleware and other meta data
78
+ * @param keys The full set of keys for this particular route
79
+ * @param basePath The base path as it was initial declared for this route
80
+ * @returns A ExpressPath object holding the meta data for a given route
81
+ */
82
+ this.parseRouteLayer = (layer, keys, basePath) => {
83
+ const lastRequestHandler = layer.route.stack[layer.route.stack.length - 1];
84
+ const pathParams = keys.map((key) => {
85
+ return { name: key.name, in: 'path', required: !key.optional };
86
+ });
87
+ const filtered = layer.route.stack.filter((element) => element?.handle?.metadata);
88
+ const path = (basePath + layer.route.path).replace(/\/{2,}/gi, '/');
89
+ if (filtered.length === 0) {
90
+ return { path, pathParams, method: lastRequestHandler.method };
91
+ }
92
+ return {
93
+ path,
94
+ pathParams,
95
+ method: lastRequestHandler.method,
96
+ metadata: filtered.map((f) => f.handle.metadata),
97
+ };
98
+ };
99
+ this._appPaths = [];
100
+ const router = app._router;
101
+ if (router) {
102
+ for (const layer of router.stack) {
103
+ this.traverse('', layer, []);
104
+ }
105
+ }
106
+ }
107
+ }
108
+ /** Parses an express layer's regex and converts it to the original format seen in code.
109
+ *
110
+ * @param layerRegexPath The layer's regex pattern
111
+ * @param keys The keys that represent the layer's path parameters
112
+ * @returns The path string for that layer
113
+ * Code inspired and modify from:
114
+ * https://github.com/expressjs/express/issues/3308#issuecomment-300957572
115
+ */
116
+ const pathRegexParser = (layerRegexPath, keys) => {
117
+ if (typeof layerRegexPath === 'string') {
118
+ return layerRegexPath;
119
+ }
120
+ if (layerRegexPath.fast_slash) {
121
+ return '';
122
+ }
123
+ if (layerRegexPath.fast_star) {
124
+ return '*';
125
+ }
126
+ let mappedPath = '';
127
+ if (keys && keys.length) {
128
+ mappedPath = mapKeysToPath(layerRegexPath, keys);
129
+ }
130
+ const match = layerRegexPath
131
+ .toString()
132
+ .replace('\\/?', '')
133
+ .replace('(?=\\/|$)', '$')
134
+ .match(/^\/\^((?:\\[.*+?^${}()|[\]\\/]|[^.*+?^${}()|[\]\\/])*)\$\//);
135
+ if (match) {
136
+ return match[1].replace(/\\(.)/g, '$1').slice(1);
137
+ }
138
+ if (mappedPath) {
139
+ return mappedPath.slice(1);
140
+ }
141
+ return layerRegexPath.toString();
142
+ };
143
+ /**
144
+ * Map's the keys/path variables to the regex inside a given path
145
+ *
146
+ * @param layerRegexPath The regex for a react router with path parameters
147
+ * @param keys The keys that represent the path parameters
148
+ * @returns The regex for a path variable converted to original string on the express route
149
+ */
150
+ const mapKeysToPath = (layerRegexPath, keys) => {
151
+ if (!keys || keys.length === 0) {
152
+ throw Error('must include atleast one key to map');
153
+ }
154
+ let convertedSubPath = layerRegexPath.toString();
155
+ for (const key of keys) {
156
+ if (key.optional) {
157
+ convertedSubPath = convertedSubPath.replace('(?:\\/([^\\/]+?))?\\', `/:${key.name}?`);
158
+ }
159
+ else {
160
+ convertedSubPath = convertedSubPath.replace('(?:([^\\/]+?))', `:${key.name}`);
161
+ }
162
+ }
163
+ return convertedSubPath
164
+ .replace('/?(?=\\/|$)/i', '')
165
+ .replace('/^', '')
166
+ .replace(/\\/gi, '')
167
+ .replace(/\/{2,}/gi, '/');
168
+ };
@@ -1,3 +1,5 @@
1
1
  import 'dotenv/config';
2
+ import EventEmitter from 'node:events';
2
3
  import failOnConsole from 'vitest-fail-on-console';
4
+ EventEmitter.setMaxListeners(20);
3
5
  failOnConsole();
package/dist/validate.js CHANGED
@@ -4,33 +4,39 @@ import { BadRequest } from './errors.js';
4
4
  const Ajv = _Ajv;
5
5
  export function validateBody(schema) {
6
6
  const validate = compileValidateSchema(schema);
7
- return (req, res, next) => {
7
+ const middleware = (req, res, next) => {
8
8
  const result = validate(req.body);
9
9
  if (!result.isValid) {
10
10
  return next(BadRequest({ body: result.errors }));
11
11
  }
12
12
  next();
13
13
  };
14
+ middleware.metadata = { bodySchema: schema };
15
+ return middleware;
14
16
  }
15
17
  export function validateQuery(schema) {
16
18
  const validate = compileValidateSchema(schema, { coerceTypes: true });
17
- return (req, res, next) => {
19
+ const middleware = (req, res, next) => {
18
20
  const result = validate(req.query);
19
21
  if (!result.isValid) {
20
22
  return next(BadRequest({ query: result.errors }));
21
23
  }
22
24
  next();
23
25
  };
26
+ middleware.metadata = { querySchema: schema };
27
+ return middleware;
24
28
  }
25
29
  export function validateParams(schema) {
26
30
  const validate = compileValidateSchema(schema, { coerceTypes: true });
27
- return (req, res, next) => {
31
+ const middleware = (req, res, next) => {
28
32
  const result = validate(req.params);
29
33
  if (!result.isValid) {
30
34
  return next(BadRequest({ params: result.errors }));
31
35
  }
32
36
  next();
33
37
  };
38
+ middleware.metadata = { paramsSchema: schema };
39
+ return middleware;
34
40
  }
35
41
  function createAjv(options) {
36
42
  const ajv = new Ajv({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devmoods/express-extras",
3
- "version": "0.35.1",
3
+ "version": "0.37.0",
4
4
  "description": "Kubernetes-ready utilities for rapid Node.js application development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,11 +20,11 @@
20
20
  "devDependencies": {
21
21
  "@devmoods/eslint-config": "^1.14.0",
22
22
  "@types/koa-compose": "^3.2.8",
23
- "@types/node": "22.7.5",
23
+ "@types/node": "22.8.1",
24
24
  "@types/supertest": "^6.0.2",
25
- "@typescript-eslint/eslint-plugin": "^8.8.1",
26
- "@typescript-eslint/parser": "^8.8.1",
27
- "@vitest/coverage-v8": "^2.1.2",
25
+ "@typescript-eslint/eslint-plugin": "^8.11.0",
26
+ "@typescript-eslint/parser": "^8.11.0",
27
+ "@vitest/coverage-v8": "^2.1.3",
28
28
  "chalk": "^5.3.0",
29
29
  "eslint": "^8.57.0",
30
30
  "eslint-plugin-import": "^2.31.0",
@@ -35,13 +35,13 @@
35
35
  "ts-node": "10.9.2",
36
36
  "tsx": "^4.19.1",
37
37
  "typescript": "5.6.3",
38
- "vitest": "^2.1.2",
38
+ "vitest": "^2.1.3",
39
39
  "vitest-fail-on-console": "^0.7.1"
40
40
  },
41
41
  "dependencies": {
42
42
  "@devmoods/fetch": "^4.0.1",
43
- "@sentry/node": "^8.34.0",
44
- "@sentry/profiling-node": "^8.34.0",
43
+ "@sentry/node": "^8.35.0",
44
+ "@sentry/profiling-node": "^8.35.0",
45
45
  "@types/bcrypt": "^5.0.2",
46
46
  "@types/body-parser": "^1.19.5",
47
47
  "@types/cookie-parser": "^1.4.7",
@@ -59,11 +59,10 @@
59
59
  "cookie-parser": "^1.4.7",
60
60
  "dotenv": "^16.4.5",
61
61
  "express": "^4.21.1",
62
- "express-route-parser": "^1.0.5",
63
62
  "helmet": "^8.0.0",
64
63
  "jsonwebtoken": "^9.0.2",
65
64
  "nodemailer": "^6.9.15",
66
- "pg": "^8.13.0",
65
+ "pg": "^8.13.1",
67
66
  "redis": "^4.7.0",
68
67
  "sql-template-strings": "^2.2.2",
69
68
  "uuid": "^10.0.0"