@moreapp/common-nodejs 0.8.0 → 0.9.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/README.md CHANGED
@@ -8,27 +8,7 @@ Run `yarn prepare` once to install Git hooks, doing lints and prettier formattin
8
8
 
9
9
  ## Project usage
10
10
 
11
- Most code can be used as is, except for tracing, see below.
12
-
13
- ### Tracing
14
-
15
- Tracing code should always be loaded (`import`/`require`) first, before any other libraries. This is because the
16
- OpenTelemetry instrumentations monkey patch libraries to add tracing. Patching has to happen first, because already
17
- loaded libraries will be cached by the module system.
18
-
19
- The recommended way to do this, is to create a file `instrumentation.ts` with the following content (the extra
20
- instrumentation is there as an example):
21
-
22
- ```
23
- import { tracer } from "@moreapp/common-nodejs";
24
- import { MongoDBInstrumentation } from "@opentelemetry/instrumentation-mongodb";
25
-
26
- tracer("<SERVICE_NAME>", {
27
- extraInstrumentations: [new MongoDBInstrumentation()],
28
- });
29
- ```
30
-
31
- And then use the `--require` [Node.js CLI option](https://nodejs.org/api/cli.html#-r---require-module) to load this file.
11
+ Most code can be used as is. See [service configuration](./docs/service-configuration.md) for code that needs more setup.
32
12
 
33
13
  ## Creating a new release
34
14
 
package/dist/dateUtil.js CHANGED
@@ -22,9 +22,9 @@ function formatDate(dateString, format) {
22
22
  exports.formatDate = formatDate;
23
23
  function formatDateTime(dateTime, format) {
24
24
  const date = typeof dateTime === "string"
25
- ? date_and_time_1.default.parse(dateTime, "YYYY-MM-DD HH:mm", true)
25
+ ? date_and_time_1.default.parse(dateTime, "YYYY-MM-DD HH:mm", { timeZone: "UTC" })
26
26
  : new Date(dateTime);
27
- return date_and_time_1.default.format(date, formatDateFormat(format) + " HH:mm", true);
27
+ return date_and_time_1.default.format(date, formatDateFormat(format) + " HH:mm", { timeZone: "UTC" });
28
28
  }
29
29
  exports.formatDateTime = formatDateTime;
30
30
  function formatDateFormat(format) {
@@ -0,0 +1,6 @@
1
+ import { NextFunction, Request, Response } from "express";
2
+ export default class ExpressRequestTracker {
3
+ private activeRequests;
4
+ track(): (_req: Request, res: Response, next: NextFunction) => void;
5
+ nrOfActiveRequests(): number;
6
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class ExpressRequestTracker {
4
+ activeRequests = 0;
5
+ track() {
6
+ return (_req, res, next) => {
7
+ this.activeRequests++;
8
+ res.on("finish", () => {
9
+ this.activeRequests--;
10
+ });
11
+ next();
12
+ };
13
+ }
14
+ nrOfActiveRequests() {
15
+ return this.activeRequests;
16
+ }
17
+ }
18
+ exports.default = ExpressRequestTracker;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const ExpressRequestTracker_1 = __importDefault(require("./ExpressRequestTracker"));
7
+ const node_events_1 = require("node:events");
8
+ // Create a minimal Response mock that can emit the 'finish' event
9
+ const createMockResponse = () => {
10
+ const emitter = new node_events_1.EventEmitter();
11
+ return Object.assign(emitter, {
12
+ emitFinish: () => emitter.emit("finish"),
13
+ });
14
+ };
15
+ describe("ExpressRequestTracker", () => {
16
+ test("increments active requests on middleware entry and calls next", () => {
17
+ const tracker = new ExpressRequestTracker_1.default();
18
+ const middleware = tracker.track();
19
+ const res = createMockResponse();
20
+ const next = jest.fn();
21
+ expect(tracker.nrOfActiveRequests()).toBe(0);
22
+ middleware({}, res, next);
23
+ expect(tracker.nrOfActiveRequests()).toBe(1);
24
+ expect(next).toHaveBeenCalledTimes(1);
25
+ // Finish should decrement
26
+ res.emitFinish();
27
+ expect(tracker.nrOfActiveRequests()).toBe(0);
28
+ });
29
+ test("decrements active requests when response finishes", () => {
30
+ const tracker = new ExpressRequestTracker_1.default();
31
+ const middleware = tracker.track();
32
+ const res = createMockResponse();
33
+ middleware({}, res, (() => { }));
34
+ expect(tracker.nrOfActiveRequests()).toBe(1);
35
+ res.emitFinish();
36
+ expect(tracker.nrOfActiveRequests()).toBe(0);
37
+ });
38
+ test("tracks multiple concurrent requests and decrements in order", () => {
39
+ const tracker = new ExpressRequestTracker_1.default();
40
+ const middleware = tracker.track();
41
+ const res1 = createMockResponse();
42
+ const res2 = createMockResponse();
43
+ middleware({}, res1, (() => { }));
44
+ middleware({}, res2, (() => { }));
45
+ expect(tracker.nrOfActiveRequests()).toBe(2);
46
+ // Finish the first request
47
+ res1.emitFinish();
48
+ expect(tracker.nrOfActiveRequests()).toBe(1);
49
+ // Finish the second request
50
+ res2.emitFinish();
51
+ expect(tracker.nrOfActiveRequests()).toBe(0);
52
+ });
53
+ });
@@ -0,0 +1,17 @@
1
+ import ExpressRequestTracker from "./ExpressRequestTracker";
2
+ type HealthServerOptions = {
3
+ port: number;
4
+ };
5
+ export default class HealthServer {
6
+ private readonly requestTracker;
7
+ private readonly options;
8
+ private readonly server;
9
+ private status;
10
+ private isReady;
11
+ private constructor();
12
+ static create(requestTracker: ExpressRequestTracker, options: HealthServerOptions): Promise<HealthServer>;
13
+ start(): Promise<void>;
14
+ stop(): Promise<void>;
15
+ setReady(value: boolean): void;
16
+ }
17
+ export {};
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_http_1 = __importDefault(require("node:http"));
7
+ const logger_1 = require("../logger");
8
+ class HealthServer {
9
+ requestTracker;
10
+ options;
11
+ server;
12
+ status = "stopped";
13
+ isReady = false;
14
+ constructor(requestTracker, options) {
15
+ this.requestTracker = requestTracker;
16
+ this.options = options;
17
+ this.server = node_http_1.default.createServer((req, res) => {
18
+ if (req.url === "/live") {
19
+ res.writeHead(200, { "Content-Type": "application/json" });
20
+ return res.end(JSON.stringify({ status: "ok" }));
21
+ }
22
+ else if (req.url === "/ready") {
23
+ res.writeHead(this.isReady ? 200 : 503, { "Content-Type": "application/json" });
24
+ return res.end(JSON.stringify({
25
+ status: this.isReady ? "ok" : "not ready",
26
+ activeRequests: this.requestTracker.nrOfActiveRequests(),
27
+ }));
28
+ }
29
+ else {
30
+ res.statusCode = 404;
31
+ return res.end();
32
+ }
33
+ });
34
+ }
35
+ static async create(requestTracker, options) {
36
+ const hs = new HealthServer(requestTracker, options);
37
+ await hs.start();
38
+ return hs;
39
+ }
40
+ start() {
41
+ if (this.status !== "stopped") {
42
+ logger_1.logger.info("Cannot start Health server, due to invalid status", { status: this.status });
43
+ return Promise.resolve();
44
+ }
45
+ this.status = "starting";
46
+ return new Promise((resolve) => {
47
+ this.server.listen(this.options.port, () => {
48
+ this.status = "started";
49
+ logger_1.logger.info("Started Health server");
50
+ resolve();
51
+ });
52
+ });
53
+ }
54
+ stop() {
55
+ if (this.status !== "started") {
56
+ logger_1.logger.warn("Cannot stop Health server, due to invalid status", { status: this.status });
57
+ return Promise.reject(new Error("Health server not started"));
58
+ }
59
+ this.status = "stopping";
60
+ this.isReady = false;
61
+ return new Promise((resolve) => this.server.close(() => resolve()));
62
+ }
63
+ setReady(value) {
64
+ this.isReady = value;
65
+ }
66
+ }
67
+ exports.default = HealthServer;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const testUtils_1 = require("../testUtils");
7
+ (0, testUtils_1.silenceLogger)();
8
+ const node_http_1 = __importDefault(require("node:http"));
9
+ const node_events_1 = require("node:events");
10
+ const HealthServer_1 = __importDefault(require("./HealthServer"));
11
+ describe("HealthServer", () => {
12
+ const createFakeServer = () => {
13
+ const emitter = new node_events_1.EventEmitter();
14
+ const server = {
15
+ listen: jest.fn((...args) => {
16
+ const cb = args.find((a) => typeof a === "function");
17
+ if (cb)
18
+ cb();
19
+ return server;
20
+ }),
21
+ close: jest.fn((cb) => {
22
+ if (cb)
23
+ cb();
24
+ return server;
25
+ }),
26
+ on: jest.fn((event, listener) => {
27
+ emitter.on(event, listener);
28
+ return server;
29
+ }),
30
+ emit: emitter.emit.bind(emitter),
31
+ };
32
+ return server;
33
+ };
34
+ const createRes = () => {
35
+ const res = {
36
+ statusCode: 200,
37
+ headers: {},
38
+ writeHead: function (code, headers) {
39
+ this.statusCode = code;
40
+ this.headers = headers;
41
+ },
42
+ end: function (body) {
43
+ this.body = body;
44
+ },
45
+ };
46
+ return res;
47
+ };
48
+ test("handles /live and /ready endpoints and start/stop lifecycle", async () => {
49
+ const fakeServer = createFakeServer();
50
+ // Capture handler passed to createServer
51
+ let capturedHandler;
52
+ const createServerSpy = jest
53
+ .spyOn(node_http_1.default, "createServer")
54
+ // @ts-expect-error simplify handler typing for test
55
+ .mockImplementation((handler) => {
56
+ capturedHandler = handler;
57
+ return fakeServer;
58
+ });
59
+ const requestTracker = { nrOfActiveRequests: jest.fn(() => 0) };
60
+ const hs = await HealthServer_1.default.create(requestTracker, { port: 0 });
61
+ expect(fakeServer.listen).toHaveBeenCalled();
62
+ // /live
63
+ const resLive = createRes();
64
+ capturedHandler({ url: "/live" }, resLive);
65
+ expect(resLive.statusCode).toBe(200);
66
+ expect(resLive.headers).toEqual({ "Content-Type": "application/json" });
67
+ expect(JSON.parse(resLive.body)).toEqual({ status: "ok" });
68
+ // /ready when not ready
69
+ const resReady1 = createRes();
70
+ capturedHandler({ url: "/ready" }, resReady1);
71
+ expect(resReady1.statusCode).toBe(503);
72
+ expect(JSON.parse(resReady1.body)).toEqual({
73
+ status: "not ready",
74
+ activeRequests: 0,
75
+ });
76
+ // Now mark ready and simulate active requests
77
+ requestTracker.nrOfActiveRequests.mockReturnValue(3);
78
+ hs.setReady(true);
79
+ const resReady2 = createRes();
80
+ capturedHandler({ url: "/ready" }, resReady2);
81
+ expect(resReady2.statusCode).toBe(200);
82
+ expect(JSON.parse(resReady2.body)).toEqual({
83
+ status: "ok",
84
+ activeRequests: 3,
85
+ });
86
+ // unknown route
87
+ const res404 = createRes();
88
+ capturedHandler({ url: "/unknown" }, res404);
89
+ expect(res404.statusCode).toBe(404);
90
+ await hs.stop();
91
+ expect(fakeServer.close).toHaveBeenCalled();
92
+ createServerSpy.mockRestore();
93
+ });
94
+ });
@@ -0,0 +1,12 @@
1
+ /// <reference types="node" />
2
+ import ExpressRequestTracker from "./ExpressRequestTracker";
3
+ import HealthServer from "./HealthServer";
4
+ import { Server } from "node:http";
5
+ export default class TerminationHandler {
6
+ private readonly server;
7
+ private readonly hs;
8
+ private readonly requestTracker;
9
+ private readonly sockets;
10
+ constructor(server: Server, hs: HealthServer, requestTracker: ExpressRequestTracker);
11
+ shutdown(): void;
12
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const logger_1 = require("../logger");
4
+ const node_timers_1 = require("node:timers");
5
+ const TERMINATION_SIGNALS = ["SIGTERM", "SIGHUP", "SIGINT"];
6
+ const SHUTDOWN_TIMEOUT = 30_000;
7
+ class TerminationHandler {
8
+ server;
9
+ hs;
10
+ requestTracker;
11
+ sockets = new Set();
12
+ constructor(server, hs, requestTracker) {
13
+ this.server = server;
14
+ this.hs = hs;
15
+ this.requestTracker = requestTracker;
16
+ for (const signal of TERMINATION_SIGNALS) {
17
+ process.on(signal, () => {
18
+ logger_1.logger.info("Received termination signal", { signal });
19
+ this.shutdown();
20
+ });
21
+ }
22
+ server.on("connection", (socket) => {
23
+ this.sockets.add(socket);
24
+ socket.on("close", () => this.sockets.delete(socket));
25
+ });
26
+ }
27
+ shutdown() {
28
+ this.hs.setReady(false);
29
+ logger_1.logger.info("Stopping server");
30
+ this.server.close();
31
+ const interval = (0, node_timers_1.setInterval)(() => {
32
+ const activeRequests = this.requestTracker.nrOfActiveRequests();
33
+ if (activeRequests === 0) {
34
+ (0, node_timers_1.clearInterval)(interval);
35
+ // Destroy any remaining sockets
36
+ this.sockets.forEach((socket) => socket.destroy());
37
+ this.hs.stop().finally(() => {
38
+ logger_1.logger.info("Server stopped");
39
+ process.exit(0);
40
+ });
41
+ }
42
+ else {
43
+ logger_1.logger.info("Waiting for requests to finish", { activeRequests });
44
+ }
45
+ }, 1_000);
46
+ setTimeout(() => {
47
+ logger_1.logger.info("Server forcefully stopped due to timeout");
48
+ this.sockets.forEach((socket) => socket.destroy());
49
+ process.exit(1);
50
+ }, SHUTDOWN_TIMEOUT);
51
+ }
52
+ }
53
+ exports.default = TerminationHandler;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const testUtils_1 = require("../testUtils");
30
+ (0, testUtils_1.silenceLogger)();
31
+ const node_events_1 = require("node:events");
32
+ // Mock node:timers so we can control callbacks (TerminationHandler imports from node:timers)
33
+ jest.mock("node:timers", () => ({
34
+ setInterval: jest.fn(),
35
+ clearInterval: jest.fn(),
36
+ setTimeout: jest.fn(),
37
+ }));
38
+ const timers = __importStar(require("node:timers"));
39
+ const TerminationHandler_1 = __importDefault(require("./TerminationHandler"));
40
+ describe("TerminationHandler", () => {
41
+ let intervalCb;
42
+ let timeoutCb;
43
+ beforeEach(() => {
44
+ intervalCb = undefined;
45
+ timeoutCb = undefined;
46
+ timers.setInterval.mockImplementation((cb) => {
47
+ intervalCb = cb;
48
+ return 1;
49
+ });
50
+ timers.clearInterval.mockImplementation(() => { });
51
+ jest
52
+ .spyOn(globalThis, "setTimeout")
53
+ // @ts-ignore simplify types for test
54
+ .mockImplementation((cb) => {
55
+ timeoutCb = cb;
56
+ return 2;
57
+ });
58
+ });
59
+ afterEach(() => {
60
+ jest.resetAllMocks();
61
+ });
62
+ const createFakeServer = () => {
63
+ const emitter = new node_events_1.EventEmitter();
64
+ const server = {
65
+ close: jest.fn((cb) => {
66
+ if (cb)
67
+ cb();
68
+ return server;
69
+ }),
70
+ on: jest.fn((event, listener) => {
71
+ emitter.on(event, listener);
72
+ return server;
73
+ }),
74
+ emit: emitter.emit.bind(emitter),
75
+ };
76
+ return server;
77
+ };
78
+ const createSocket = () => {
79
+ const emitter = new node_events_1.EventEmitter();
80
+ const socket = emitter;
81
+ socket.destroy = jest.fn();
82
+ return socket;
83
+ };
84
+ test("shutdown waits for active requests to reach 0 then stops and exits 0", async () => {
85
+ const server = createFakeServer();
86
+ const hs = {
87
+ setReady: jest.fn(),
88
+ stop: jest.fn(() => ({
89
+ finally: (cb) => {
90
+ cb();
91
+ return Promise.resolve();
92
+ },
93
+ })),
94
+ };
95
+ const requestTracker = { nrOfActiveRequests: jest.fn(() => 0) };
96
+ const exitSpy = jest
97
+ .spyOn(process, "exit")
98
+ // never return to process
99
+ .mockImplementation(((..._args) => undefined));
100
+ const th = new TerminationHandler_1.default(server, hs, requestTracker);
101
+ th.shutdown();
102
+ expect(hs.setReady).toHaveBeenCalledWith(false);
103
+ expect(server.close).toHaveBeenCalled();
104
+ // First interval tick should see 0 active and exit
105
+ intervalCb?.();
106
+ // ensure hs.stop called and exit called with 0
107
+ expect(hs.stop).toHaveBeenCalled();
108
+ expect(exitSpy).toHaveBeenCalledWith(0);
109
+ });
110
+ test("shutdown polls until requests finish then exits 0", async () => {
111
+ const server = createFakeServer();
112
+ const hs = {
113
+ setReady: jest.fn(),
114
+ stop: jest.fn(() => ({
115
+ finally: (cb) => {
116
+ cb();
117
+ return Promise.resolve();
118
+ },
119
+ })),
120
+ };
121
+ const requestTracker = { nrOfActiveRequests: jest.fn() };
122
+ // Return >0 first, then 0
123
+ requestTracker.nrOfActiveRequests.mockReturnValueOnce(2).mockReturnValueOnce(0);
124
+ const exitSpy = jest
125
+ .spyOn(process, "exit")
126
+ .mockImplementation(((..._args) => undefined));
127
+ const th = new TerminationHandler_1.default(server, hs, requestTracker);
128
+ th.shutdown();
129
+ // First tick: still waiting
130
+ intervalCb?.();
131
+ expect(exitSpy).not.toHaveBeenCalled();
132
+ // Second tick: now 0
133
+ intervalCb?.();
134
+ expect(hs.stop).toHaveBeenCalled();
135
+ expect(exitSpy).toHaveBeenCalledWith(0);
136
+ });
137
+ test("forceful stop after 30s destroys sockets and exits 1", () => {
138
+ const server = createFakeServer();
139
+ const hs = { setReady: jest.fn(), stop: jest.fn(() => Promise.resolve()) };
140
+ const requestTracker = { nrOfActiveRequests: jest.fn(() => 5) }; // never reaches 0
141
+ const exitSpy = jest
142
+ .spyOn(process, "exit")
143
+ .mockImplementation(((..._args) => undefined));
144
+ const th = new TerminationHandler_1.default(server, hs, requestTracker);
145
+ // Register a socket via the server's connection event
146
+ const socket = createSocket();
147
+ server.emit("connection", socket);
148
+ th.shutdown();
149
+ // Before timeout callback, no exit
150
+ expect(exitSpy).not.toHaveBeenCalled();
151
+ expect(socket.destroy).not.toHaveBeenCalled();
152
+ // Trigger timeout
153
+ timeoutCb?.();
154
+ expect(socket.destroy).toHaveBeenCalled();
155
+ expect(exitSpy).toHaveBeenCalledWith(1);
156
+ });
157
+ });
@@ -0,0 +1,2 @@
1
+ /// <reference types="jest" />
2
+ export declare const silenceLogger: () => typeof jest;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.silenceLogger = void 0;
4
+ // Utility to silence logger output in tests, must be at the top of the test file
5
+ const silenceLogger = () => jest.mock("./logger", () => ({
6
+ logger: {
7
+ info: jest.fn(),
8
+ warn: jest.fn(),
9
+ error: jest.fn(),
10
+ debug: jest.fn(),
11
+ },
12
+ }));
13
+ exports.silenceLogger = silenceLogger;
@@ -81,6 +81,18 @@ describe("parseDynamicRecipients", () => {
81
81
  "contact2@example.com",
82
82
  ]);
83
83
  });
84
+ test("should skip invalid emails", () => {
85
+ const fields = [
86
+ { uid: "UID-root", properties: { data_name: "customer" } },
87
+ { uid: "UID-object", properties: { data_name: "vendor" } },
88
+ { uid: "UID-array", properties: { data_name: "contacts" } },
89
+ ];
90
+ expect((0, utils_1.parseDynamicRecipients)(["UID-root", "UID-object.email", "UID-array"], fields, {
91
+ customer: "invalid1",
92
+ vendor: { id: 1, email: "invalid2" },
93
+ contacts: ["invalid3", "invalid4"],
94
+ })).toEqual([]);
95
+ });
84
96
  });
85
97
  describe("getDataForDataName", () => {
86
98
  test("should return (nested) values in objects based on dataName", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moreapp/common-nodejs",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "license": "UNLICENSED",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -33,29 +33,31 @@
33
33
  "@opentelemetry/sdk-trace-base": "1.22.0",
34
34
  "@opentelemetry/sdk-trace-node": "1.22.0",
35
35
  "@opentelemetry/semantic-conventions": "1.22.0",
36
- "axios": "1.8.2",
36
+ "axios": "1.13.2",
37
37
  "content-disposition-parser": "1.0.2",
38
- "date-and-time": "3.1.1",
38
+ "date-and-time": "4.1.0",
39
+ "express": "5.2.1",
39
40
  "lodash": "4.17.21",
40
41
  "winston": "3.13.0",
41
- "zod": "3.22.4"
42
+ "zod": "3.25.76"
42
43
  },
43
44
  "devDependencies": {
45
+ "@types/express": "5.0.6",
44
46
  "@types/jest": "29.5.11",
45
47
  "@types/lodash": "4.17.0",
46
- "@types/node": "22.16.2",
48
+ "@types/node": "24.10.1",
47
49
  "@typescript-eslint/eslint-plugin": "6.21.0",
48
50
  "@typescript-eslint/parser": "6.21.0",
49
51
  "eslint": "8.57.0",
50
- "eslint-config-airbnb-typescript": "17.1.0",
52
+ "eslint-config-airbnb-typescript": "18.0.0",
51
53
  "eslint-config-prettier": "9.1.0",
52
54
  "eslint-plugin-import": "2.29.1",
53
55
  "eslint-plugin-prettier": "5.1.3",
54
56
  "husky": "9.0.7",
55
57
  "jest": "29.7.0",
56
- "jest-mock-extended": "3.0.5",
57
- "lint-staged": "15.2.1",
58
- "nock": "13.5.4",
58
+ "jest-mock-extended": "4.0.0",
59
+ "lint-staged": "16.2.6",
60
+ "nock": "14.0.10",
59
61
  "prettier": "3.2.4",
60
62
  "ts-jest": "29.1.2",
61
63
  "ts-node": "10.9.2",