diff --git a/client/src/Components/v1/Inputs/Search/index.jsx b/client/src/Components/v1/Inputs/Search/index.jsx
index 7a3771d47..8138ee6f4 100644
--- a/client/src/Components/v1/Inputs/Search/index.jsx
+++ b/client/src/Components/v1/Inputs/Search/index.jsx
@@ -85,7 +85,12 @@ const Search = ({
const enhancedOptions = React.useMemo(() => {
return multiple && isAdorned
? [
- { [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all" },
+ {
+ [filteredBy]: t("selectAll"),
+ isSelectAll: true,
+ _id: "select_all",
+ id: "select_all",
+ },
...options,
]
: options;
@@ -93,7 +98,7 @@ const Search = ({
const isOptionSelected = (option) => {
if (!multiple && !isAdorned) return false;
if (Array.isArray(value)) {
- return value.some((item) => item._id === option._id);
+ return value.some((item) => (item._id ?? item.id) === (option._id ?? option.id));
}
return false;
};
@@ -145,7 +150,9 @@ const Search = ({
disableClearable
options={enhancedOptions}
getOptionLabel={(option) => option[filteredBy]}
- isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier
+ isOptionEqualToValue={(option, value) =>
+ (option._id ?? option.id) === (value?._id ?? value?.id)
+ } // Compare by unique identifier
renderInput={(params) => (
{
- return option._id;
+ return option._id ?? option.id;
}}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
diff --git a/client/src/Components/v1/Inputs/Select/index.jsx b/client/src/Components/v1/Inputs/Select/index.jsx
index 37ce30dc1..f952b6818 100644
--- a/client/src/Components/v1/Inputs/Select/index.jsx
+++ b/client/src/Components/v1/Inputs/Select/index.jsx
@@ -62,6 +62,7 @@ const Select = ({
fieldWrapperSx = {},
}) => {
const theme = useTheme();
+ const getItemValue = (item) => item?._id ?? item?.id;
const itemStyles = {
fontSize: "var(--env-var-font-size-medium)",
color: theme.palette.primary.contrastTextTertiary,
@@ -123,7 +124,7 @@ const Select = ({
...sx,
}}
renderValue={(selected) => {
- const selectedItem = items.find((item) => item._id === selected);
+ const selectedItem = items.find((item) => getItemValue(item) === selected);
const displayName = selectedItem ? selectedItem.name : placeholder;
return (
)}
- {items.map((item) => (
-
- ))}
+ {items
+ .map((item) => {
+ const itemValue = getItemValue(item);
+ if (itemValue === undefined || itemValue === null) {
+ return null;
+ }
+ return (
+
+ );
+ })
+ .filter(Boolean)}
);
@@ -179,8 +188,8 @@ Select.propTypes = {
.isRequired,
items: PropTypes.arrayOf(
PropTypes.shape({
- _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
- .isRequired,
+ _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
name: PropTypes.string.isRequired,
})
diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js
index ffc356320..bc391ebf6 100644
--- a/client/src/Hooks/monitorHooks.js
+++ b/client/src/Hooks/monitorHooks.js
@@ -6,7 +6,7 @@ import { useMonitorUtils } from "./useMonitorUtils.js";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
-const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
+export const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
const [isLoading, setIsLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [monitorsSummary, setMonitorsSummary] = useState(undefined);
@@ -37,7 +37,7 @@ const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
return [monitors, monitorsSummary, isLoading, networkError];
};
-const useFetchMonitorsWithChecks = ({
+export const useFetchMonitorsWithChecks = ({
types,
limit,
page,
@@ -100,22 +100,9 @@ const useFetchMonitorsWithChecks = ({
return [monitors, count, isLoading, networkError];
};
-const useFetchMonitorsByTeamId = ({
- types,
- limit,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- checkOrder,
- normalize,
- status,
- updateTrigger,
-}) => {
+export const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
- const [summary, setSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
@@ -123,20 +110,11 @@ const useFetchMonitorsByTeamId = ({
try {
setIsLoading(true);
const res = await networkService.getMonitorsByTeamId({
- limit,
types,
- page,
- rowsPerPage,
filter,
- field,
- order,
- checkOrder,
- status,
- normalize,
});
- if (res?.data?.data?.filteredMonitors) {
- setMonitors(res.data.data.filteredMonitors);
- setSummary(res.data.data.summary);
+ if (res?.data?.data) {
+ setMonitors(res.data.data);
}
} catch (error) {
setNetworkError(true);
@@ -148,23 +126,11 @@ const useFetchMonitorsByTeamId = ({
}
};
fetchMonitors();
- }, [
- types,
- limit,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- updateTrigger,
- checkOrder,
- normalize,
- status,
- ]);
- return [monitors, summary, isLoading, networkError];
+ }, [types, filter, updateTrigger]);
+ return [monitors, isLoading, networkError];
};
-const useFetchStatsByMonitorId = ({
+export const useFetchStatsByMonitorId = ({
monitorId,
sortOrder,
limit,
@@ -203,7 +169,7 @@ const useFetchStatsByMonitorId = ({
return [monitor, audits, isLoading, networkError];
};
-const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
+export const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchGames = async () => {
@@ -222,7 +188,7 @@ const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
return [isLoading];
};
-const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
+export const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (typeof monitorId === "undefined") {
@@ -245,7 +211,7 @@ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
return [isLoading];
};
-const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
+export const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitor, setMonitor] = useState(undefined);
@@ -271,8 +237,34 @@ const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) =>
}, [monitorId, dateRange, updateTrigger]);
return [monitor, isLoading, networkError];
};
+export const useFetchPageSpeedMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
+ const [isLoading, setIsLoading] = useState(true);
+ const [networkError, setNetworkError] = useState(false);
+ const [monitor, setMonitor] = useState(undefined);
-const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
+ useEffect(() => {
+ const fetchMonitor = async () => {
+ try {
+ if (!monitorId) {
+ return { monitor: undefined, isLoading: false, networkError: undefined };
+ }
+ const response = await networkService.getPageSpeedDetailsByMonitorId({
+ monitorId: monitorId,
+ dateRange: dateRange,
+ });
+ setMonitor(response.data.data);
+ } catch (error) {
+ setNetworkError(true);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchMonitor();
+ }, [monitorId, dateRange, updateTrigger]);
+ return [monitor, isLoading, networkError];
+};
+
+export const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
const [networkError, setNetworkError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [monitor, setMonitor] = useState(undefined);
@@ -300,7 +292,7 @@ const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
return [monitor, monitorStats, isLoading, networkError];
};
-const useCreateMonitor = () => {
+export const useCreateMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const createMonitor = async ({ monitor, redirect }) => {
@@ -320,7 +312,7 @@ const useCreateMonitor = () => {
return [createMonitor, isLoading];
};
-const useFetchGlobalSettings = () => {
+export const useFetchGlobalSettings = () => {
const [isLoading, setIsLoading] = useState(true);
const [globalSettings, setGlobalSettings] = useState(undefined);
useEffect(() => {
@@ -342,7 +334,7 @@ const useFetchGlobalSettings = () => {
return [globalSettings, isLoading];
};
-const useDeleteMonitor = () => {
+export const useDeleteMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const deleteMonitor = async ({ monitor, redirect }) => {
@@ -363,7 +355,7 @@ const useDeleteMonitor = () => {
return [deleteMonitor, isLoading];
};
-const useUpdateMonitor = () => {
+export const useUpdateMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const updateMonitor = async ({ monitor, redirect }) => {
@@ -410,7 +402,7 @@ const useUpdateMonitor = () => {
return [updateMonitor, isLoading];
};
-const usePauseMonitor = () => {
+export const usePauseMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(undefined);
const pauseMonitor = async ({ monitorId, triggerUpdate }) => {
@@ -433,7 +425,7 @@ const usePauseMonitor = () => {
return [pauseMonitor, isLoading, error];
};
-const useAddDemoMonitors = () => {
+export const useAddDemoMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const addDemoMonitors = async () => {
@@ -450,7 +442,7 @@ const useAddDemoMonitors = () => {
return [addDemoMonitors, isLoading];
};
-const useDeleteAllMonitors = () => {
+export const useDeleteAllMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const deleteAllMonitors = async () => {
@@ -467,7 +459,7 @@ const useDeleteAllMonitors = () => {
return [deleteAllMonitors, isLoading];
};
-const useDeleteMonitorStats = () => {
+export const useDeleteMonitorStats = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const deleteMonitorStats = async () => {
@@ -485,7 +477,7 @@ const useDeleteMonitorStats = () => {
return [deleteMonitorStats, isLoading];
};
-const useCreateBulkMonitors = () => {
+export const useCreateBulkMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const createBulkMonitors = async (file, user) => {
@@ -508,7 +500,7 @@ const useCreateBulkMonitors = () => {
return [createBulkMonitors, isLoading];
};
-const useExportMonitors = () => {
+export const useExportMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
@@ -541,7 +533,7 @@ const useExportMonitors = () => {
return [exportMonitors, isLoading];
};
-const useFetchJson = () => {
+export const useFetchJson = () => {
const [isLoading, setIsLoading] = useState(false);
const fetchJson = async () => {
try {
@@ -557,25 +549,3 @@ const useFetchJson = () => {
};
return [fetchJson, isLoading];
};
-
-export {
- useFetchMonitorsWithSummary,
- useFetchMonitorsWithChecks,
- useFetchMonitorsByTeamId,
- useFetchStatsByMonitorId,
- useFetchMonitorById,
- useFetchUptimeMonitorById,
- useFetchHardwareMonitorById,
- useCreateMonitor,
- useFetchGlobalSettings,
- useDeleteMonitor,
- useUpdateMonitor,
- usePauseMonitor,
- useAddDemoMonitors,
- useDeleteAllMonitors,
- useDeleteMonitorStats,
- useCreateBulkMonitors,
- useExportMonitors,
- useFetchMonitorGames,
- useFetchJson,
-};
diff --git a/client/src/Pages/Incidents2/index.jsx b/client/src/Pages/Incidents2/index.jsx
index eda68d976..966388b51 100644
--- a/client/src/Pages/Incidents2/index.jsx
+++ b/client/src/Pages/Incidents2/index.jsx
@@ -34,7 +34,7 @@ const Incidents2 = () => {
setUpdateTrigger((prev) => !prev);
};
- const [monitors, , isLoadingMonitors, monitorsNetworkError] = useFetchMonitorsByTeamId(
+ const [monitors, isLoadingMonitors, monitorsNetworkError] = useFetchMonitorsByTeamId(
{}
);
diff --git a/client/src/Pages/Infrastructure/Monitors/index.jsx b/client/src/Pages/Infrastructure/Monitors/index.jsx
index 4d739417c..b25636fa9 100644
--- a/client/src/Pages/Infrastructure/Monitors/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/index.jsx
@@ -13,10 +13,7 @@ import { useTheme } from "@emotion/react";
import { useEffect, useState } from "react";
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import { useTranslation } from "react-i18next";
-import {
- useFetchMonitorsByTeamId,
- useFetchMonitorsWithChecks,
-} from "@/Hooks/monitorHooks.js";
+import { useFetchMonitorsWithChecks } from "@/Hooks/monitorHooks.js";
import { useDispatch, useSelector } from "react-redux";
import { setRowsPerPage } from "../../../Features/UI/uiSlice.js";
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
index c3a4681a1..c70912582 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
@@ -24,7 +24,7 @@ const MonitorListItem = ({ monitor, onDelete }) => {
MonitorListItem.propTypes = {
monitor: PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
}).isRequired,
onDelete: PropTypes.func.isRequired,
@@ -33,7 +33,7 @@ MonitorListItem.propTypes = {
const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
const onDelete = (monitorToDelete) => {
const newMonitors = selectedMonitors.filter(
- (monitor) => monitor._id !== monitorToDelete._id
+ (monitor) => monitor.id !== monitorToDelete.id
);
setSelectedMonitors(newMonitors);
};
@@ -47,7 +47,7 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
>
{selectedMonitors?.map((monitor) => (
@@ -59,7 +59,7 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
MonitorList.propTypes = {
selectedMonitors: PropTypes.arrayOf(
PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
})
).isRequired,
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
index 6f180c880..22a61084d 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
@@ -22,6 +22,12 @@ const useMaintenanceActions = () => {
weekly: MS_PER_DAY * 7,
};
const handleSubmitForm = async (maintenanceWindowId, form) => {
+ const toStringId = (monitor) => {
+ if (!monitor) {
+ return "";
+ }
+ return monitor._id ?? monitor.id ?? "";
+ };
const start = dayjs(form.startDate)
.set("hour", form.startTime.hour())
.set("minute", form.startTime.minute());
@@ -33,7 +39,7 @@ const useMaintenanceActions = () => {
const repeat = REPEAT_LOOKUP[form.repeat];
const submit = {
- monitors: form.monitors.map((monitor) => monitor._id),
+ monitors: form.monitors.map((monitor) => toStringId(monitor)).filter(Boolean),
name: form.name,
start: start.toISOString(),
end: end.toISOString(),
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
index c3be0091a..3414334a7 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
@@ -41,7 +41,7 @@ const useMaintenanceData = () => {
limit: null,
types: ["http", "ping", "pagespeed", "port"],
});
- const fetchedMonitors = response.data.data.monitors;
+ const fetchedMonitors = response.data.data;
return fetchedMonitors;
};
@@ -55,7 +55,9 @@ const useMaintenanceData = () => {
const endTime = dayjs(end);
const durationInMs = endTime.diff(startTime, "milliseconds").toString();
const { duration, durationUnit } = getDurationAndUnit(durationInMs);
- const monitor = monitorList.find((monitor) => monitor._id === monitorId);
+ const monitor = monitorList.find(
+ (monitor) => (monitor._id ?? monitor.id) === monitorId
+ );
const maintenanceWindowInformation = {
name,
repeat: REVERSE_REPEAT_LOOKUP[repeat],
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx
index 384805f3a..759679a2d 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx
@@ -108,6 +108,7 @@ const CreateMaintenance = () => {
const handleSubmit = async () => {
if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return;
+
const request = handleSubmitForm(maintenanceWindowId, form);
try {
setIsLoading(true);
diff --git a/client/src/Pages/PageSpeed/Details/index.jsx b/client/src/Pages/PageSpeed/Details/index.jsx
index 8a87133df..df411048a 100644
--- a/client/src/Pages/PageSpeed/Details/index.jsx
+++ b/client/src/Pages/PageSpeed/Details/index.jsx
@@ -11,14 +11,13 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import { useParams } from "react-router-dom";
-import { useFetchStatsByMonitorId } from "../../../Hooks/monitorHooks.js";
+import { useFetchPageSpeedMonitorById } from "../../../Hooks/monitorHooks.js";
import { useState } from "react";
import { useTranslation } from "react-i18next";
// Constants
const BREADCRUMBS = [
{ name: "pagespeed", path: "/pagespeed" },
{ name: "details", path: `` },
- // { name: "details", path: `/pagespeed/${monitorId}` }, // Not needed?
];
const PageSpeedDetails = () => {
@@ -36,13 +35,9 @@ const PageSpeedDetails = () => {
});
const [trigger, setTrigger] = useState(false);
// Network
- const [monitor, audits, isLoading, networkError] = useFetchStatsByMonitorId({
+ const [monitor, isLoading, networkError] = useFetchPageSpeedMonitorById({
monitorId,
- sortOrder: "desc",
- limit: 50,
dateRange: "day",
- numToDisplay: null,
- normalize: null,
updateTrigger: trigger,
});
@@ -116,7 +111,7 @@ const PageSpeedDetails = () => {
/>
);
diff --git a/client/src/Pages/PageSpeed/Monitors/index.jsx b/client/src/Pages/PageSpeed/Monitors/index.jsx
index 548cd3491..4530a41e9 100644
--- a/client/src/Pages/PageSpeed/Monitors/index.jsx
+++ b/client/src/Pages/PageSpeed/Monitors/index.jsx
@@ -11,10 +11,7 @@ import FallbackPageSpeedWarning from "@/Components/v1/Fallback/FallbackPageSpeed
// Utils
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
-import {
- useFetchMonitorsByTeamId,
- useFetchMonitorsWithChecks,
-} from "@/Hooks/monitorHooks.js";
+import { useFetchMonitorsWithChecks } from "@/Hooks/monitorHooks.js";
import { useFetchSettings } from "@/Hooks/settingsHooks.js";
// Constants
const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
@@ -23,16 +20,6 @@ const PageSpeed = () => {
const theme = useTheme();
const isAdmin = useIsAdmin();
- // const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({
- // limit: 10,
- // types: TYPES,
- // page: null,
- // rowsPerPage: null,
- // filter: null,
- // field: null,
- // order: null,
- // });
-
const [
monitorsWithChecks,
monitorsWithChecksCount,
diff --git a/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx b/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx
index 5484202d5..d2a13b2eb 100644
--- a/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx
+++ b/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx
@@ -43,7 +43,7 @@ const MonitorListItem = ({
const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
const onDelete = (monitorToDelete) => {
const newMonitors = selectedMonitors.filter(
- (monitor) => monitor._id !== monitorToDelete._id
+ (monitor) => monitor.id !== monitorToDelete.id
);
setSelectedMonitors(newMonitors);
};
@@ -80,8 +80,8 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
>
{selectedMonitors?.map((monitor, index) => (
{(provided, snapshot) => (
diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
index d11825e69..e84de16dc 100644
--- a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
+++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
@@ -24,7 +24,7 @@ const Content = ({
// Handlers
const handleMonitorsChange = (selectedMonitors) => {
handleFormChange({
- target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor._id) },
+ target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor.id) },
});
setSelectedMonitors(selectedMonitors);
};
diff --git a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
index 95ce2b8c0..03ebb07bf 100644
--- a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
+++ b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
@@ -16,7 +16,7 @@ const useMonitorsFetch = () => {
limit: null, // donot return any checks for the monitors
types: ["http", "ping", "port", "game"], // include game servers in status page monitor selection
});
- setMonitors(response.data.data.monitors);
+ setMonitors(response.data.data);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx
index 7fbe6a8dc..f8fcf0e91 100644
--- a/client/src/Pages/StatusPage/Create/index.jsx
+++ b/client/src/Pages/StatusPage/Create/index.jsx
@@ -62,7 +62,6 @@ const CreateStatusPage = () => {
useStatusPageFetch(isCreate, url);
const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url);
- console.log(JSON.stringify(form, null, 2));
// Handlers
const handleFormChange = (e) => {
let { type, name, value, checked } = e.target;
@@ -144,6 +143,8 @@ const CreateStatusPage = () => {
...form,
logo: { type: form.logo?.type ?? null, size: form.logo?.size ?? null },
};
+
+ console.log(toSubmit);
const { error } = statusPageValidation.validate(toSubmit, {
abortEarly: false,
});
@@ -205,7 +206,7 @@ const CreateStatusPage = () => {
companyName: statusPage?.companyName,
isPublished: statusPage?.isPublished,
timezone: statusPage?.timezone,
- monitors: statusPageMonitors.map((monitor) => monitor._id),
+ monitors: statusPageMonitors.map((monitor) => monitor.id),
color: statusPage?.color,
logo: newLogo,
showCharts: statusPage?.showCharts ?? true,
diff --git a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
index 89e32e5d5..33f771233 100644
--- a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
+++ b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
@@ -28,14 +28,14 @@ const MonitorsList = ({
const status = determineState(monitor);
return (
{
params.append("type", type);
});
}
- if (page) params.append("page", page);
- if (rowsPerPage) params.append("rowsPerPage", rowsPerPage);
- if (filter) params.append("filter", filter);
- if (field) params.append("field", field);
- if (order) params.append("order", order);
+ if (filter !== undefined && filter !== null && filter !== "")
+ params.append("filter", filter);
return this.axiosInstance.get(`/monitors/team?${params.toString()}`, {
headers: {
@@ -202,6 +198,14 @@ class NetworkService {
`/monitors/hardware/details/${config.monitorId}?${params.toString()}`
);
}
+ async getPageSpeedDetailsByMonitorId(config) {
+ const params = new URLSearchParams();
+ if (config.dateRange) params.append("dateRange", config.dateRange);
+
+ return this.axiosInstance.get(
+ `/monitors/pagespeed/details/${config.monitorId}?${params.toString()}`
+ );
+ }
async getUptimeDetailsById(config) {
const params = new URLSearchParams();
if (config.dateRange) params.append("dateRange", config.dateRange);
diff --git a/server/jest.config.ts b/server/jest.config.ts
new file mode 100644
index 000000000..2bcfcbf21
--- /dev/null
+++ b/server/jest.config.ts
@@ -0,0 +1,20 @@
+import type { Config } from "jest";
+
+const config: Config = {
+ rootDir: ".",
+ testEnvironment: "node",
+ extensionsToTreatAsEsm: [".ts"],
+ transform: {
+ "^.+\\.(t|j)sx?$": ["ts-jest", { useESM: true, tsconfig: "./tsconfig.jest.json" }],
+ },
+ moduleNameMapper: {
+ "^@/(.*)$": "/src/$1",
+ },
+ testMatch: ["/test/**/*.test.ts"],
+ setupFilesAfterEnv: [],
+ collectCoverageFrom: ["src/**/*.ts"],
+ coveragePathIgnorePatterns: ["/node_modules/", "/test/"],
+ clearMocks: true,
+};
+
+export default config;
diff --git a/server/package-lock.json b/server/package-lock.json
index d254da407..11e1829b4 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -46,21 +46,27 @@
},
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@types/dockerode": "^4.0.0",
"@types/express": "5.0.3",
+ "@types/gamedig": "^5.0.3",
+ "@types/jest": "^30.0.0",
+ "@types/jmespath": "^0.15.2",
"@types/jsonwebtoken": "9.0.10",
+ "@types/mjml": "^4.7.4",
"@types/multer": "^2.0.0",
"@types/nodemailer": "7.0.1",
+ "@types/papaparse": "^5.5.2",
"@types/ping": "0.4.4",
"c8": "10.1.3",
- "chai": "5.2.0",
"eslint": "^9.17.0",
"eslint-plugin-mocha": "^10.5.0",
"esm": "3.2.25",
"globals": "^15.14.0",
- "mocha": "11.1.0",
+ "jest": "^30.2.0",
"nodemon": "^3.1.11",
"prettier": "^3.3.3",
- "sinon": "19.0.2",
+ "ts-jest": "^29.4.6",
+ "ts-node": "^10.9.2",
"tsc-alias": "1.8.16",
"tsx": "4.20.5",
"typescript": "5.9.2"
@@ -834,12 +840,12 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
+ "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -847,6 +853,174 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz",
+ "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz",
+ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz",
+ "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
@@ -856,6 +1030,285 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz",
+ "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
+ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
@@ -865,6 +1318,54 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz",
+ "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz",
+ "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@balena/dockerignore": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz",
@@ -890,6 +1391,30 @@
"node": ">=0.1.90"
}
},
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"node_modules/@csstools/color-helpers": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
@@ -1035,6 +1560,18 @@
"kuler": "^2.0.0"
}
},
+ "node_modules/@emnapi/core": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
+ "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.1.0",
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@emnapi/runtime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz",
@@ -1045,6 +1582,17 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
+ "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.11",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz",
@@ -2144,6 +2692,113 @@
"node": ">=12"
}
},
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -2154,6 +2809,377 @@
"node": ">=8"
}
},
+ "node_modules/@jest/console": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz",
+ "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz",
+ "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/reporters": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-changed-files": "30.2.0",
+ "jest-config": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-resolve-dependencies": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
+ "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz",
+ "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "30.2.0",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz",
+ "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz",
+ "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@sinonjs/fake-timers": "^13.0.0",
+ "@types/node": "*",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/get-type": {
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
+ "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz",
+ "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/types": "30.2.0",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz",
+ "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "collect-v8-coverage": "^1.0.2",
+ "exit-x": "^0.2.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^5.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.2",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/snapshot-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz",
+ "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "natural-compare": "^1.4.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz",
+ "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "callsites": "^3.1.0",
+ "graceful-fs": "^4.2.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz",
+ "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "collect-v8-coverage": "^1.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz",
+ "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz",
+ "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "babel-plugin-istanbul": "^7.0.1",
+ "chalk": "^4.1.2",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pirates": "^4.0.7",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^5.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz",
+ "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/pattern": "30.0.1",
+ "@jest/schemas": "30.0.5",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -2279,6 +3305,19 @@
"win32"
]
},
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2327,6 +3366,19 @@
"node": ">=14"
}
},
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -2439,6 +3491,13 @@
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
"license": "BSD-3-Clause"
},
+ "node_modules/@sinclair/typebox": {
+ "version": "0.34.47",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.47.tgz",
+ "integrity": "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@sindresorhus/is": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz",
@@ -2471,34 +3530,6 @@
"@sinonjs/commons": "^3.0.1"
}
},
- "node_modules/@sinonjs/samsam": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz",
- "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "type-detect": "^4.1.0"
- }
- },
- "node_modules/@sinonjs/samsam/node_modules/type-detect": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
- "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@sinonjs/text-encoding": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz",
- "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==",
- "dev": true,
- "license": "(Unlicense OR Apache-2.0)"
- },
"node_modules/@smithy/abort-controller": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.3.tgz",
@@ -3197,6 +4228,90 @@
"node": ">=10.13.0"
}
},
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
+ "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
"node_modules/@types/body-parser": {
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
@@ -3218,6 +4333,29 @@
"@types/node": "*"
}
},
+ "node_modules/@types/docker-modem": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz",
+ "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/ssh2": "*"
+ }
+ },
+ "node_modules/@types/dockerode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-4.0.0.tgz",
+ "integrity": "sha512-NBm/UIL1KYWivh9ihJELK4Jv0dzOQCleS0OT5JklErra8mZ/t0UQpHfTkMmPrAqtra7OyFius1EFLVs0DhiHQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/docker-modem": "*",
+ "@types/node": "*",
+ "@types/ssh2": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -3250,6 +4388,13 @@
"@types/send": "*"
}
},
+ "node_modules/@types/gamedig": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@types/gamedig/-/gamedig-5.0.3.tgz",
+ "integrity": "sha512-VLrOVRf72wVE6P0DhFtRVodiONwL51uojn6dNpP/Ks/Vdm3CDH6dZTAw74WmCDLuyvpytNy8auKzlTKfBlO8jg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/http-cache-semantics": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
@@ -3270,6 +4415,44 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "30.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
+ }
+ },
+ "node_modules/@types/jmespath": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz",
+ "integrity": "sha512-pegh49FtNsC389Flyo9y8AfkVIZn9MMPE9yJrO9svhq6Fks2MwymULWjZqySuxmctd3ZH4/n7Mr98D+1Qo5vGA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -3295,6 +4478,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/mjml": {
+ "version": "4.7.4",
+ "resolved": "https://registry.npmjs.org/@types/mjml/-/mjml-4.7.4.tgz",
+ "integrity": "sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mjml-core": "*"
+ }
+ },
+ "node_modules/@types/mjml-core": {
+ "version": "4.15.2",
+ "resolved": "https://registry.npmjs.org/@types/mjml-core/-/mjml-core-4.15.2.tgz",
+ "integrity": "sha512-Q7SxFXgoX979HP57DEVsRI50TV8x1V4lfCA4Up9AvfINDM5oD/X9ARgfoyX1qS987JCnDLv85JjkqAjt3hZSiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
@@ -3317,6 +4517,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -3332,6 +4533,16 @@
"@types/node": "*"
}
},
+ "node_modules/@types/papaparse": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz",
+ "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/ping": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@types/ping/-/ping-0.4.4.tgz",
@@ -3392,6 +4603,40 @@
"@types/node": "*"
}
},
+ "node_modules/@types/ssh2": {
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz",
+ "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^18.11.18"
+ }
+ },
+ "node_modules/@types/ssh2/node_modules/@types/node": {
+ "version": "18.19.130",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
+ "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/ssh2/node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
@@ -3420,6 +4665,299 @@
"@types/webidl-conversions": "*"
}
},
+ "node_modules/@types/yargs": {
+ "version": "17.0.35",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
+ "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
+ "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
+ "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
+ "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
+ "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
+ "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
+ "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
+ "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
+ "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
+ "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
+ "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
+ "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
+ "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
+ "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
+ "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
+ "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
+ "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
+ "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
+ "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -3466,6 +5004,19 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -3501,6 +5052,35 @@
"node": ">=6"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
@@ -3553,6 +5133,13 @@
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
"license": "MIT"
},
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -3584,16 +5171,6 @@
"safer-buffer": "~2.1.0"
}
},
- "node_modules/assertion-error": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
- "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@@ -3617,6 +5194,142 @@
"proxy-from-env": "^1.1.0"
}
},
+ "node_modules/babel-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz",
+ "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/transform": "30.2.0",
+ "@types/babel__core": "^7.20.5",
+ "babel-plugin-istanbul": "^7.0.1",
+ "babel-preset-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
+ "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "workspaces": [
+ "test/babel-8"
+ ],
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-instrument": "^6.0.2",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz",
+ "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/babel__core": "^7.20.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
+ "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz",
+ "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jest-hoist": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-beta.1"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3833,13 +5546,6 @@
"node": ">=8"
}
},
- "node_modules/browser-stdout": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
- "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/browserslist": {
"version": "4.27.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz",
@@ -3874,6 +5580,29 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
"node_modules/bson": {
"version": "6.10.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
@@ -4063,16 +5792,13 @@
}
},
"node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=6"
}
},
"node_modules/caniuse-api": {
@@ -4107,23 +5833,6 @@
],
"license": "CC-BY-4.0"
},
- "node_modules/chai": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
- "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "assertion-error": "^2.0.1",
- "check-error": "^2.1.1",
- "deep-eql": "^5.0.1",
- "loupe": "^3.1.0",
- "pathval": "^2.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -4141,14 +5850,14 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/check-error": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
- "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 16"
+ "node": ">=10"
}
},
"node_modules/cheerio": {
@@ -4231,6 +5940,29 @@
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
+ "node_modules/ci-info": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
+ "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
+ "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -4312,6 +6044,24 @@
"node": ">=0.10.0"
}
},
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz",
+ "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -4598,6 +6348,13 @@
"node": ">=10.0.0"
}
},
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cron-parser": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
@@ -4875,19 +6632,6 @@
}
}
},
- "node_modules/decamelize": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
- "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
@@ -4921,14 +6665,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/deep-eql": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
- "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "node_modules/dedent": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz",
+ "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6"
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
}
},
"node_modules/deep-is": {
@@ -4938,6 +6687,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@@ -4993,6 +6752,16 @@
"node": ">=8"
}
},
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/detect-node": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
@@ -5000,9 +6769,9 @@
"license": "MIT"
},
"node_modules/diff": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
- "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
@@ -5193,6 +6962,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -5600,6 +7382,20 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
@@ -5662,6 +7458,91 @@
"deprecated": "Use promise-toolbox/fromEvent instead",
"license": "MIT"
},
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/execa/node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/execa/node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/execa/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/exit-x": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz",
+ "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
@@ -5827,6 +7708,16 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
@@ -5931,16 +7822,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/flat": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
- "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "bin": {
- "flat": "cli.js"
- }
- },
"node_modules/flat-cache": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
@@ -6065,6 +7946,13 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -6255,6 +8143,16 @@
"node": ">=0.10"
}
},
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -6288,6 +8186,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -6458,6 +8366,13 @@
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
@@ -6528,16 +8443,6 @@
"node": ">= 0.4"
}
},
- "node_modules/he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "he": "bin/he"
- }
- },
"node_modules/helmet": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
@@ -6702,6 +8607,16 @@
"numbered": "^1.1.0"
}
},
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -6767,6 +8682,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -6777,6 +8712,18 @@
"node": ">=0.8.19"
}
},
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -6861,6 +8808,16 @@
"node": ">=8"
}
},
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -6888,16 +8845,6 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-plain-obj": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
- "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -6916,19 +8863,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-unicode-supported": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
- "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@@ -7168,6 +9102,23 @@
"node": ">=8"
}
},
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
@@ -7183,6 +9134,21 @@
"node": ">=10"
}
},
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
@@ -7212,6 +9178,600 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "node_modules/jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz",
+ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/types": "30.2.0",
+ "import-local": "^3.2.0",
+ "jest-cli": "30.2.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz",
+ "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "execa": "^5.1.1",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz",
+ "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "co": "^4.6.0",
+ "dedent": "^1.6.0",
+ "is-generator-fn": "^2.1.0",
+ "jest-each": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "30.2.0",
+ "pure-rand": "^7.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz",
+ "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "exit-x": "^0.2.2",
+ "import-local": "^3.2.0",
+ "jest-config": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz",
+ "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/get-type": "30.1.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/test-sequencer": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "deepmerge": "^4.3.1",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-circus": "30.2.0",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "micromatch": "^4.0.8",
+ "parse-json": "^5.2.0",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "esbuild-register": ">=3.4.0",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "esbuild-register": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-diff": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz",
+ "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/diff-sequences": "30.0.1",
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-docblock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz",
+ "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-newline": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz",
+ "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz",
+ "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz",
+ "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "anymatch": "^3.1.3",
+ "fb-watchman": "^2.0.2",
+ "graceful-fs": "^4.2.11",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "micromatch": "^4.0.8",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.3"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz",
+ "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz",
+ "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.2.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz",
+ "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.2.0",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz",
+ "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz",
+ "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-pnp-resolver": "^1.2.3",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "slash": "^3.0.0",
+ "unrs-resolver": "^1.7.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz",
+ "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-regex-util": "30.0.1",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz",
+ "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/environment": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-leak-detector": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-resolve": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "jest-worker": "30.2.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz",
+ "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/globals": "30.2.0",
+ "@jest/source-map": "30.0.1",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "cjs-module-lexer": "^2.1.0",
+ "collect-v8-coverage": "^1.0.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz",
+ "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1",
+ "@babel/types": "^7.27.3",
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "@jest/snapshot-utils": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0",
+ "chalk": "^4.1.2",
+ "expect": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-diff": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0",
+ "semver": "^7.7.2",
+ "synckit": "^0.11.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz",
+ "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz",
+ "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "camelcase": "^6.3.0",
+ "chalk": "^4.1.2",
+ "leven": "^3.1.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz",
+ "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "jest-util": "30.2.0",
+ "string-length": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz",
+ "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@ungap/structured-clone": "^1.3.0",
+ "jest-util": "30.2.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
"node_modules/jmespath": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
@@ -7291,6 +9851,19 @@
}
}
},
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -7317,6 +9890,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -7427,13 +10013,6 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
- "node_modules/just-extend": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz",
- "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/jwa": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
@@ -7479,6 +10058,16 @@
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
"license": "MIT"
},
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -7612,23 +10201,6 @@
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
"license": "MIT"
},
- "node_modules/log-symbols": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
- "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "^4.1.0",
- "is-unicode-supported": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/logform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
@@ -7652,13 +10224,6 @@
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
- "node_modules/loupe": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
- "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lowercase-keys": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
@@ -7713,6 +10278,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -7758,6 +10340,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -7833,6 +10422,16 @@
"node": ">= 0.6"
}
},
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/mimic-response": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
@@ -8323,81 +10922,6 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
- "node_modules/mocha": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz",
- "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-colors": "^4.1.3",
- "browser-stdout": "^1.3.1",
- "chokidar": "^3.5.3",
- "debug": "^4.3.5",
- "diff": "^5.2.0",
- "escape-string-regexp": "^4.0.0",
- "find-up": "^5.0.0",
- "glob": "^10.4.5",
- "he": "^1.2.0",
- "js-yaml": "^4.1.0",
- "log-symbols": "^4.1.0",
- "minimatch": "^5.1.6",
- "ms": "^2.1.3",
- "serialize-javascript": "^6.0.2",
- "strip-json-comments": "^3.1.1",
- "supports-color": "^8.1.1",
- "workerpool": "^6.5.1",
- "yargs": "^17.7.2",
- "yargs-parser": "^21.1.1",
- "yargs-unparser": "^2.0.0"
- },
- "bin": {
- "_mocha": "bin/_mocha",
- "mocha": "bin/mocha.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/mocha/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/mocha/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/mocha/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
@@ -8613,6 +11137,22 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/napi-postinstall": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
+ "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -8641,31 +11181,6 @@
"integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==",
"license": "MIT"
},
- "node_modules/nise": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz",
- "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "@sinonjs/fake-timers": "^13.0.1",
- "@sinonjs/text-encoding": "^0.7.3",
- "just-extend": "^6.2.0",
- "path-to-regexp": "^8.1.0"
- }
- },
- "node_modules/nise/node_modules/path-to-regexp": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
- "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
"node_modules/node-abort-controller": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
@@ -8749,6 +11264,13 @@
"node-gyp-build-optional-packages-test": "build-test.js"
}
},
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/node-releases": {
"version": "2.0.26",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz",
@@ -8837,6 +11359,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -8921,6 +11456,22 @@
"fn.name": "1.x.x"
}
},
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -8980,6 +11531,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -9090,6 +11651,16 @@
"node": ">=8"
}
},
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -9131,16 +11702,6 @@
"node": ">=8"
}
},
- "node_modules/pathval": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
- "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.16"
- }
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -9168,6 +11729,85 @@
"node": ">=4.0.0"
}
},
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/plimit-lit": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz",
@@ -9819,6 +12459,34 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/pretty-format": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz",
+ "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -9894,6 +12562,23 @@
"node": ">=6"
}
},
+ "node_modules/pure-rand": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
+ "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/qs": {
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
@@ -9959,16 +12644,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/randombytes": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
- "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "^5.1.0"
- }
- },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -10022,6 +12697,13 @@
"node": ">= 0.8"
}
},
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -10093,6 +12775,29 @@
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
"license": "MIT"
},
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -10306,16 +13011,6 @@
"node": ">= 0.8"
}
},
- "node_modules/serialize-javascript": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
- "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "randombytes": "^2.1.0"
- }
- },
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
@@ -10515,35 +13210,6 @@
"node": ">=10"
}
},
- "node_modules/sinon": {
- "version": "19.0.2",
- "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz",
- "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "@sinonjs/fake-timers": "^13.0.2",
- "@sinonjs/samsam": "^8.0.1",
- "diff": "^7.0.0",
- "nise": "^6.1.1",
- "supports-color": "^7.2.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/sinon"
- }
- },
- "node_modules/sinon/node_modules/diff": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
- "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.3.1"
- }
- },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -10581,6 +13247,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
@@ -10596,6 +13273,13 @@
"integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==",
"license": "ISC"
},
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/ssh2": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
@@ -10628,6 +13312,29 @@
"node": "*"
}
},
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
@@ -10669,6 +13376,43 @@
"safe-buffer": "~5.2.0"
}
},
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-length/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-length/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/string-to-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz",
@@ -10811,6 +13555,26 @@
"node": ">=8"
}
},
+ "node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -11009,6 +13773,22 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"license": "MIT"
},
+ "node_modules/synckit": {
+ "version": "0.11.12",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
@@ -11116,6 +13896,13 @@
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
"license": "MIT"
},
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -11180,6 +13967,104 @@
"node": ">= 14.0.0"
}
},
+ "node_modules/ts-jest": {
+ "version": "29.4.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
+ "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bs-logger": "^0.2.6",
+ "fast-json-stable-stringify": "^2.1.0",
+ "handlebars": "^4.7.8",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.7.3",
+ "type-fest": "^4.41.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0 || ^30.0.0",
+ "@jest/types": "^29.0.0 || ^30.0.0",
+ "babel-jest": "^29.0.0 || ^30.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-util": "^29.0.0 || ^30.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jest-util": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
"node_modules/tsc-alias": {
"version": "1.8.16",
"resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz",
@@ -11366,6 +14251,41 @@
"node": ">= 0.8"
}
},
+ "node_modules/unrs-resolver": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
+ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
+ "@unrs/resolver-binding-android-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-x64": "1.11.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
@@ -11434,6 +14354,13 @@
"uuid": "dist/bin/uuid"
}
},
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
@@ -11485,6 +14412,16 @@
"node": ">=18"
}
},
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
"node_modules/web-resource-inliner": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-7.0.0.tgz",
@@ -11753,13 +14690,6 @@
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
"license": "MIT"
},
- "node_modules/workerpool": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
- "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
- "dev": true,
- "license": "Apache-2.0"
- },
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@@ -11854,6 +14784,20 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
+ "node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
@@ -11937,6 +14881,13 @@
"node": ">=10"
}
},
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
@@ -11964,22 +14915,6 @@
"node": ">=12"
}
},
- "node_modules/yargs-unparser": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
- "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "camelcase": "^6.0.0",
- "decamelize": "^4.0.0",
- "flat": "^5.0.2",
- "is-plain-obj": "^2.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/yargs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -12021,6 +14956,16 @@
"node": ">=8"
}
},
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/server/package.json b/server/package.json
index 3547810ce..91ba82988 100755
--- a/server/package.json
+++ b/server/package.json
@@ -5,7 +5,7 @@
"main": "index.js",
"type": "module",
"scripts": {
- "test": "c8 mocha",
+ "test": "NODE_OPTIONS=--experimental-vm-modules c8 jest --runInBand",
"dev": "nodemon --exec tsx src/index.js",
"start": "node --watch ./dist/index.js",
"build": "tsc && tsc-alias",
@@ -58,21 +58,27 @@
},
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@types/dockerode": "^4.0.0",
"@types/express": "5.0.3",
+ "@types/gamedig": "^5.0.3",
+ "@types/jest": "^30.0.0",
+ "@types/jmespath": "^0.15.2",
"@types/jsonwebtoken": "9.0.10",
+ "@types/mjml": "^4.7.4",
"@types/multer": "^2.0.0",
"@types/nodemailer": "7.0.1",
+ "@types/papaparse": "^5.5.2",
"@types/ping": "0.4.4",
"c8": "10.1.3",
- "chai": "5.2.0",
"eslint": "^9.17.0",
"eslint-plugin-mocha": "^10.5.0",
"esm": "3.2.25",
"globals": "^15.14.0",
- "mocha": "11.1.0",
+ "jest": "^30.2.0",
"nodemon": "^3.1.11",
"prettier": "^3.3.3",
- "sinon": "19.0.2",
+ "ts-jest": "^29.4.6",
+ "ts-node": "^10.9.2",
"tsc-alias": "1.8.16",
"tsx": "4.20.5",
"typescript": "5.9.2"
diff --git a/server/scripts/generate-checks.js b/server/scripts/generate-checks.js
new file mode 100644
index 000000000..d3a33b033
--- /dev/null
+++ b/server/scripts/generate-checks.js
@@ -0,0 +1,176 @@
+import mongoose from "mongoose";
+import { MonitorModel } from "../dist/db/models/Monitor.js";
+import { CheckModel } from "../dist/db/models/Check.js";
+
+const DEFAULT_MONITOR_ID = "000000000000000000000001";
+const DEFAULT_TEAM_ID = "0000000000000000000000aa";
+const DEFAULT_USER_ID = "0000000000000000000000bb";
+const DEFAULT_MONITOR_TYPE = "http";
+const DEFAULT_TOTAL = 1_000_000;
+const DEFAULT_BATCH_SIZE = 5_000;
+
+const parseObjectId = (value, fallback) => {
+ try {
+ return new mongoose.Types.ObjectId(value || fallback);
+ } catch (error) {
+ console.warn(`Invalid ObjectId '${value}', falling back to '${fallback}'.`);
+ return new mongoose.Types.ObjectId(fallback);
+ }
+};
+
+async function ensureMonitor({ monitorId, teamId, userId, type }) {
+ const existing = await MonitorModel.findById(monitorId);
+ if (existing) {
+ return existing;
+ }
+
+ console.log(`Monitor ${monitorId.toString()} not found, creating it.`);
+ const monitor = new MonitorModel({
+ _id: monitorId,
+ userId,
+ teamId,
+ name: `Seed Monitor ${monitorId.toString()}`,
+ description: "Synthetic monitor for performance testing",
+ statusWindow: [],
+ statusWindowSize: 5,
+ statusWindowThreshold: 60,
+ type,
+ ignoreTlsErrors: false,
+ url: "https://example.com",
+ isActive: true,
+ interval: 60000,
+ alertThreshold: 5,
+ cpuAlertThreshold: 5,
+ memoryAlertThreshold: 5,
+ diskAlertThreshold: 5,
+ tempAlertThreshold: 5,
+ selectedDisks: [],
+ });
+
+ await monitor.save();
+ return monitor;
+}
+
+async function run() {
+ const mongoUri = process.env.MONGO_URI ?? "mongodb://localhost:27017/uptime_db";
+ const monitorId = parseObjectId(process.env.MONITOR_ID ?? DEFAULT_MONITOR_ID, DEFAULT_MONITOR_ID);
+ const teamId = parseObjectId("69648b0578209af45f9ffe30");
+ const userId = parseObjectId("69648b0678209af45f9ffe32");
+ const monitorType = process.env.MONITOR_TYPE ?? DEFAULT_MONITOR_TYPE;
+ const total = Number(process.env.CHECK_TOTAL ?? DEFAULT_TOTAL);
+ const batchSize = Number(process.env.CHECK_BATCH_SIZE ?? DEFAULT_BATCH_SIZE);
+
+ console.log(`Connecting to MongoDB at ${mongoUri}`);
+ await mongoose.connect(mongoUri);
+
+ await ensureMonitor({ monitorId, teamId, userId, type: monitorType });
+
+ console.log(`Seeding ${total} checks for monitor ${monitorId.toString()} (team ${teamId.toString()}) in batches of ${batchSize}.`);
+
+ const docs = [];
+ const startTime = Date.now();
+
+ for (let i = 0; i < total; i += 1) {
+ const baseTime = Date.now() - (total - i) * 1000;
+ const createdAt = new Date(baseTime);
+ docs.push({
+ metadata: {
+ monitorId,
+ teamId,
+ type: monitorType,
+ },
+ status: i % 50 !== 0,
+ statusCode: i % 50 !== 0 ? 200 : 500,
+ responseTime: Math.floor(Math.random() * 1000),
+ message: i % 50 !== 0 ? "OK" : "Error",
+ expiry: createdAt,
+ createdAt,
+ updatedAt: createdAt,
+ timings: {
+ start: baseTime,
+ socket: baseTime,
+ lookup: baseTime,
+ connect: baseTime,
+ secureConnect: baseTime,
+ upload: baseTime,
+ response: baseTime + 40,
+ end: baseTime + 45,
+ phases: {
+ wait: 0,
+ dns: 1,
+ tcp: 2,
+ tls: 4,
+ request: 0,
+ firstByte: 30,
+ download: 5,
+ total: 45,
+ },
+ },
+ cpu: {
+ physical_core: 8,
+ logical_core: 16,
+ frequency: 3600,
+ temperature: [50 + Math.random() * 10],
+ free_percent: 40,
+ usage_percent: Math.random() * 100,
+ },
+ memory: {
+ total_bytes: 32 * 1024 ** 3,
+ available_bytes: 16 * 1024 ** 3,
+ used_bytes: 16 * 1024 ** 3,
+ usage_percent: Math.random() * 100,
+ },
+ disk: [
+ {
+ device: "/dev/sda1",
+ mountpoint: "/",
+ read_speed_bytes: Math.random() * 10_000_000,
+ write_speed_bytes: Math.random() * 10_000_000,
+ total_bytes: 512 * 1024 ** 3,
+ free_bytes: 128 * 1024 ** 3,
+ usage_percent: Math.random() * 100,
+ },
+ ],
+ host: {
+ os: "linux",
+ platform: "ubuntu",
+ kernel_version: "5.15.0",
+ },
+ net: [
+ {
+ name: "eth0",
+ bytes_sent: Math.random() * 10_000_000,
+ bytes_recv: Math.random() * 10_000_000,
+ packets_sent: Math.random() * 1_000_000,
+ packets_recv: Math.random() * 1_000_000,
+ err_in: 0,
+ err_out: 0,
+ drop_in: 0,
+ drop_out: 0,
+ fifo_in: 0,
+ fifo_out: 0,
+ },
+ ],
+ errors: i % 50 === 0 ? [{ metric: ["uptime"], err: "500" }] : [],
+ });
+
+ if (docs.length === batchSize) {
+ await CheckModel.insertMany(docs, { ordered: false });
+ console.log(`Inserted ${i + 1} / ${total}`);
+ docs.length = 0;
+ }
+ }
+
+ if (docs.length > 0) {
+ await CheckModel.insertMany(docs, { ordered: false });
+ }
+
+ await mongoose.disconnect();
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
+ console.log(`Finished inserting ${total} checks in ${duration}s`);
+}
+
+run().catch((error) => {
+ console.error("Failed to seed checks", error);
+ process.exit(1);
+});
diff --git a/server/src/config/services.js b/server/src/config/services.ts
similarity index 92%
rename from server/src/config/services.js
rename to server/src/config/services.ts
index 0916752ab..74c68c86e 100644
--- a/server/src/config/services.js
+++ b/server/src/config/services.ts
@@ -16,7 +16,7 @@ import CheckService from "../service/business/checkService.js";
import DiagnosticService from "../service/business/diagnosticService.js";
import InviteService from "../service/business/inviteService.js";
import MaintenanceWindowService from "../service/business/maintenanceWindowService.js";
-import MonitorService from "../service/business/monitorService.js";
+import { MonitorService } from "@/service/index.js";
import IncidentService from "../service/business/incidentService.js";
import papaparse from "papaparse";
import axios from "axios";
@@ -34,9 +34,8 @@ const { compile } = pkg;
import mjml2html from "mjml";
import jwt from "jsonwebtoken";
import crypto from "crypto";
-import { games } from "gamedig";
+import { games, GameDig } from "gamedig";
import jmespath from "jmespath";
-import { GameDig } from "gamedig";
import { fileURLToPath } from "url";
import { ObjectId } from "mongodb";
@@ -47,7 +46,6 @@ import { GenerateAvatarImage } from "../utils/imageProcessing.js";
import { ParseBoolean } from "../utils/utils.js";
// Models
-import { CheckModel } from "@/db/models/index.js";
import Monitor from "../db/models/Monitor.js";
import User from "../db/models/User.js";
import InviteToken from "../db/models/InviteToken.js";
@@ -72,11 +70,11 @@ import SettingsModule from "../db/modules/settingsModule.js";
import IncidentModule from "../db/modules/incidentModule.js";
// repositories
-import { MongoMonitorsRepository, MongoChecksRepository } from "@/repositories/index.js";
+import { MongoMonitorsRepository, MongoChecksRepository, MongoMonitorStatsRepository } from "@/repositories/index.js";
-export const initializeServices = async ({ logger, envSettings, settingsService }) => {
+export const initializeServices = async ({ logger, envSettings, settingsService }: { logger: any; envSettings: any; settingsService: any }) => {
const serviceRegistry = new ServiceRegistry({ logger });
- ServiceRegistry.instance = serviceRegistry;
+ (ServiceRegistry as any).instance = serviceRegistry;
const translationService = new TranslationService(logger);
await translationService.initialize();
@@ -84,7 +82,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
const stringService = new StringService(translationService);
// Create DB
- const checkModule = new CheckModule({ logger, CheckModel, Monitor, User });
+ const checkModule = new CheckModule({ logger, Monitor, User });
const inviteModule = new InviteModule({ InviteToken, crypto, stringService });
const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService });
const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService });
@@ -125,6 +123,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
// Repositories
const monitorsRepository = new MongoMonitorsRepository();
const checksRepository = new MongoChecksRepository();
+ const monitorStatsRepository = new MongoMonitorStatsRepository();
const networkService = new NetworkService({
axios,
@@ -152,7 +151,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
const bufferService = new BufferService({ db, logger, envSettings, incidentService });
- const statusService = new StatusService({ db, logger, buffer: bufferService, incidentService });
+ const statusService = new StatusService({ db, logger, buffer: bufferService, incidentService, monitorsRepository });
const notificationUtils = new NotificationUtils({
stringService,
@@ -182,6 +181,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
db,
logger,
helper: superSimpleQueueHelper,
+ monitorsRepository,
});
// Business services
@@ -218,7 +218,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService
});
const monitorService = new MonitorService({
db,
- settingsService,
jobQueue: superSimpleQueue,
stringService,
emailService,
@@ -228,6 +227,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
games,
monitorsRepository,
checksRepository,
+ monitorStatsRepository,
});
const services = {
diff --git a/server/src/controllers/controllerUtils.ts b/server/src/controllers/controllerUtils.ts
index 4d9f9a279..af3eddd44 100755
--- a/server/src/controllers/controllerUtils.ts
+++ b/server/src/controllers/controllerUtils.ts
@@ -1,3 +1,6 @@
+import { AppError } from "@/utils/AppError.js";
+import { type MonitorType, MonitorTypes } from "@/types/index.js";
+
const fetchMonitorCertificate = async (sslChecker: any, monitor: any): Promise => {
const monitorUrl = new URL(monitor.url);
const hostname = monitorUrl.hostname;
@@ -8,5 +11,101 @@ const fetchMonitorCertificate = async (sslChecker: any, monitor: any): Promise {
+ if (typeof value === "string" && value.trim().length > 0) {
+ return value;
+ }
+ throw new AppError({ message: `${fieldName} is required`, status: 400 });
+};
-export { fetchMonitorCertificate };
+const optionalString = (value: unknown, fieldName: string): string | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "string") {
+ return value;
+ }
+ throw new AppError({ message: `${fieldName} must be a string`, status: 400 });
+};
+
+const optionalNumber = (value: unknown, fieldName: string): number | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "number" && Number.isFinite(value)) {
+ return value;
+ }
+ if (typeof value === "string" && value.trim() !== "") {
+ const parsed = Number(value);
+ if (!Number.isNaN(parsed)) {
+ return parsed;
+ }
+ }
+ throw new AppError({ message: `${fieldName} must be a number`, status: 400 });
+};
+
+const optionalBoolean = (value: unknown, fieldName: string): boolean | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "boolean") {
+ return value;
+ }
+ if (typeof value === "string") {
+ if (value === "true") {
+ return true;
+ }
+ if (value === "false") {
+ return false;
+ }
+ }
+ throw new AppError({ message: `${fieldName} must be a boolean`, status: 400 });
+};
+
+const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => {
+ const parseSingle = (input: unknown): MonitorType => {
+ if (typeof input !== "string") {
+ throw new AppError({ message: "Monitor type must be a string", status: 400 });
+ }
+ if (!MonitorTypes.includes(input as MonitorType)) {
+ throw new AppError({ message: `Invalid monitor type: ${input}`, status: 400 });
+ }
+ return input as MonitorType;
+ };
+
+ if (value === undefined) {
+ return undefined;
+ }
+ if (Array.isArray(value)) {
+ return value.map((entry) => parseSingle(entry));
+ }
+ return parseSingle(value);
+};
+
+const parseSortOrder = (value: unknown): "asc" | "desc" | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (value === "asc" || value === "desc") {
+ return value;
+ }
+ throw new AppError({ message: "order must be either 'asc' or 'desc'", status: 400 });
+};
+
+const requireTeamId = (teamId?: string): string => {
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+ return teamId;
+};
+
+export {
+ fetchMonitorCertificate,
+ requireString,
+ optionalString,
+ optionalNumber,
+ optionalBoolean,
+ parseMonitorTypeFilter,
+ parseSortOrder,
+ requireTeamId,
+};
diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts
index 425979062..90ab5e7a5 100644
--- a/server/src/controllers/monitorController.ts
+++ b/server/src/controllers/monitorController.ts
@@ -5,26 +5,35 @@ import {
getMonitorByIdQueryValidation,
getMonitorsByTeamIdParamValidation,
getMonitorsByTeamIdQueryValidation,
+ getMonitorsWithChecksQueryValidation,
createMonitorBodyValidation,
editMonitorBodyValidation,
pauseMonitorParamValidation,
- getMonitorStatsByIdParamValidation,
- getMonitorStatsByIdQueryValidation,
getCertificateParamValidation,
getHardwareDetailsByIdParamValidation,
getHardwareDetailsByIdQueryValidation,
} from "@/validation/joi.js";
import sslChecker from "ssl-checker";
-import { fetchMonitorCertificate } from "./controllerUtils.js";
+import {
+ fetchMonitorCertificate,
+ requireString,
+ optionalString,
+ optionalNumber,
+ optionalBoolean,
+ parseMonitorTypeFilter,
+ parseSortOrder,
+ requireTeamId,
+} from "./controllerUtils.js";
import { AppError } from "@/utils/AppError.js";
+import { IMonitorService } from "@/service/index.js";
const SERVICE_NAME = "monitorController";
class MonitorController {
static SERVICE_NAME = SERVICE_NAME;
- private monitorService: any;
+ private monitorService: IMonitorService;
- constructor(monitorService: any) {
+ constructor(monitorService: IMonitorService) {
this.monitorService = monitorService;
}
@@ -33,33 +42,17 @@ class MonitorController {
}
async verifyTeamAccess(teamId: string, monitorId: string) {
- const monitor = await this.monitorService.getMonitorById(monitorId);
- if (!monitor.teamId.equals(teamId)) {
+ const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
+ if (monitor.teamId !== teamId) {
throw new AppError({ message: "Access denied", status: 403 });
}
}
- getAllMonitors = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const monitors = await this.monitorService.getAllMonitors();
- return res.status(200).json({
- success: true,
- msg: "Retrieved all monitors successfully",
- data: monitors,
- });
- } catch (error) {
- next(error);
- }
- };
-
getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => {
try {
await getCertificateParamValidation.validateAsync(req.params);
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
- const { monitorId } = req.params;
+ const teamId = requireTeamId(req?.user?.teamId);
+ const monitorId = requireString(req.params?.monitorId, "Monitor ID");
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
const certificate = await fetchMonitorCertificate(sslChecker, monitor);
@@ -77,15 +70,10 @@ class MonitorController {
getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => {
try {
- const monitorId = req?.params?.monitorId;
- const dateRange = req?.query?.dateRange;
- const normalize = req?.query?.normalize;
-
- const teamId = req?.user?.teamId;
-
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const dateRange = requireString(req?.query?.dateRange, "dateRange");
+ const normalize = optionalBoolean(req?.query?.normalize, "normalize");
+ const teamId = requireTeamId(req?.user?.teamId);
const data = await this.monitorService.getUptimeDetailsById({
teamId,
@@ -103,50 +91,14 @@ class MonitorController {
}
};
- getMonitorStatsById = async (req: Request, res: Response, next: NextFunction) => {
- try {
- await getMonitorStatsByIdParamValidation.validateAsync(req.params);
- await getMonitorStatsByIdQueryValidation.validateAsync(req.query);
-
- let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query;
- const monitorId = req?.params?.monitorId;
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
-
- const monitorStats = await this.monitorService.getMonitorStatsById({
- teamId,
- monitorId,
- limit,
- sortOrder,
- dateRange,
- numToDisplay,
- normalize,
- });
-
- return res.status(200).json({
- success: true,
- msg: "Monitor stats retrieved successfully",
- data: monitorStats,
- });
- } catch (error) {
- next(error);
- }
- };
-
getHardwareDetailsById = async (req: Request, res: Response, next: NextFunction) => {
try {
await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
- const monitorId = req?.params?.monitorId;
- const dateRange = req?.query?.dateRange;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const dateRange = requireString(req?.query?.dateRange, "dateRange");
+ const teamId = requireTeamId(req?.user?.teamId);
const monitor = await this.monitorService.getHardwareDetailsById({
teamId,
@@ -163,18 +115,40 @@ class MonitorController {
next(error);
}
};
+ getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
+ await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
+
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const dateRange = requireString(req?.query?.dateRange, "dateRange");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const monitor = await this.monitorService.getPageSpeedDetailsById({
+ teamId,
+ monitorId,
+ dateRange,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Page speed details retrieved successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
getMonitorById = async (req: Request, res: Response, next: NextFunction) => {
try {
await getMonitorByIdParamValidation.validateAsync(req.params);
await getMonitorByIdQueryValidation.validateAsync(req.query);
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
+ const teamId = requireTeamId(req?.user?.teamId);
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
- const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId });
+ const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
return res.status(200).json({
success: true,
@@ -190,10 +164,10 @@ class MonitorController {
try {
await createMonitorBodyValidation.validateAsync(req.body);
- const userId = req?.user?._id;
- const teamId = req?.user?.teamId;
+ const userId = requireString(req?.user?._id, "User ID");
+ const teamId = requireTeamId(req?.user?.teamId);
- const monitor = await this.monitorService.createMonitor({ teamId, userId, body: req.body });
+ const monitor = await this.monitorService.createMonitor(teamId, userId, req.body);
return res.status(200).json({
success: true,
@@ -219,19 +193,15 @@ class MonitorController {
throw new AppError({ message: "File is empty", status: 400 });
}
- const userId = req?.user?._id;
- const teamId = req?.user?.teamId;
-
- if (!userId || !teamId) {
- throw new AppError({ message: "Missing userId or teamId", status: 400 });
- }
+ const userId = requireString(req?.user?._id, "User ID");
+ const teamId = requireTeamId(req?.user?.teamId);
const fileData = req?.file?.buffer?.toString("utf-8");
if (!fileData) {
throw new AppError({ message: "Cannot get file from buffer", status: 400 });
}
- const monitors = await this.monitorService.createBulkMonitors({ fileData, userId, teamId });
+ const monitors = await this.monitorService.createBulkMonitors(fileData, userId, teamId);
return res.status(200).json({
success: true,
@@ -246,11 +216,8 @@ class MonitorController {
deleteMonitor = async (req: Request, res: Response, next: NextFunction) => {
try {
await getMonitorByIdParamValidation.validateAsync(req.params);
- const monitorId = req.params.monitorId;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const teamId = requireTeamId(req?.user?.teamId);
const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
@@ -266,10 +233,7 @@ class MonitorController {
deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => {
try {
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
+ const teamId = requireTeamId(req?.user?.teamId);
const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
@@ -286,12 +250,8 @@ class MonitorController {
try {
await getMonitorByIdParamValidation.validateAsync(req.params);
await editMonitorBodyValidation.validateAsync(req.body);
- const monitorId = req?.params?.monitorId;
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const teamId = requireTeamId(req?.user?.teamId);
const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body });
@@ -309,11 +269,8 @@ class MonitorController {
try {
await pauseMonitorParamValidation.validateAsync(req.params);
- const monitorId = req.params.monitorId;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const teamId = requireTeamId(req?.user?.teamId);
const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
@@ -329,7 +286,8 @@ class MonitorController {
addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => {
try {
- const { _id, teamId } = req.user;
+ const _id = requireString(req?.user?._id, "User ID");
+ const teamId = requireTeamId(req?.user?.teamId);
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId });
return res.status(200).json({
@@ -365,10 +323,11 @@ class MonitorController {
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
- let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
- const teamId = req?.user?.teamId;
+ const teamId = requireTeamId(req?.user?.teamId);
+ const type = parseMonitorTypeFilter(req.query?.type);
+ const filter = optionalString(req.query?.filter, "filter");
- const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, limit, type, page, rowsPerPage, filter, field, order });
+ const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, filter });
return res.status(200).json({
success: true,
@@ -385,12 +344,9 @@ class MonitorController {
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
- const explain = req?.query?.explain;
- const type = req?.query?.type;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
+ const explain = optionalBoolean(req?.query?.explain, "explain");
+ const type = parseMonitorTypeFilter(req?.query?.type);
+ const teamId = requireTeamId(req?.user?.teamId);
const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain });
@@ -406,14 +362,17 @@ class MonitorController {
getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
- await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
+ await getMonitorsWithChecksQueryValidation.validateAsync(req.query);
- const explain = req?.query?.explain;
- let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new AppError({ message: "Team ID is required", status: 400 });
- }
+ const explain = optionalBoolean(req?.query?.explain, "explain");
+ const limit = optionalNumber(req?.query?.limit, "limit");
+ const page = optionalNumber(req?.query?.page, "page");
+ const rowsPerPage = optionalNumber(req?.query?.rowsPerPage, "rowsPerPage");
+ const filter = optionalString(req?.query?.filter, "filter");
+ const field = optionalString(req?.query?.field, "field");
+ const order = parseSortOrder(req?.query?.order);
+ const type = parseMonitorTypeFilter(req?.query?.type);
+ const teamId = requireTeamId(req?.user?.teamId);
const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
teamId,
diff --git a/server/src/db/MongoDB.js b/server/src/db/MongoDB.js
index 115c7e9ee..3db17c78f 100755
--- a/server/src/db/MongoDB.js
+++ b/server/src/db/MongoDB.js
@@ -11,12 +11,9 @@ class MongoDB {
inviteModule,
statusPageModule,
userModule,
- hardwareCheckModule,
maintenanceWindowModule,
monitorModule,
- networkCheckModule,
notificationModule,
- pageSpeedCheckModule,
recoveryModule,
settingsModule,
incidentModule,
@@ -26,15 +23,12 @@ class MongoDB {
this.userModule = userModule;
this.inviteModule = inviteModule;
this.recoveryModule = recoveryModule;
- this.pageSpeedCheckModule = pageSpeedCheckModule;
- this.hardwareCheckModule = hardwareCheckModule;
this.checkModule = checkModule;
this.maintenanceWindowModule = maintenanceWindowModule;
this.monitorModule = monitorModule;
this.notificationModule = notificationModule;
this.settingsModule = settingsModule;
this.statusPageModule = statusPageModule;
- this.networkCheckModule = networkCheckModule;
this.incidentModule = incidentModule;
}
diff --git a/server/src/db/models/MonitorStats.js b/server/src/db/models/MonitorStats.js
deleted file mode 100755
index c081c8a4f..000000000
--- a/server/src/db/models/MonitorStats.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import mongoose from "mongoose";
-
-const MonitorStatsSchema = new mongoose.Schema(
- {
- monitorId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Monitor",
- immutable: true,
- index: true,
- },
- avgResponseTime: {
- type: Number,
- default: 0,
- },
- totalChecks: {
- type: Number,
- default: 0,
- },
- totalUpChecks: {
- type: Number,
- default: 0,
- },
- totalDownChecks: {
- type: Number,
- default: 0,
- },
- uptimePercentage: {
- type: Number,
- default: 0,
- },
- lastCheckTimestamp: {
- type: Number,
- default: 0,
- },
- lastResponseTime: {
- type: Number,
- default: 0,
- },
- timeOfLastFailure: {
- type: Number,
- default: 0,
- },
- },
- { timestamps: true }
-);
-
-const MonitorStats = mongoose.model("MonitorStats", MonitorStatsSchema);
-
-export default MonitorStats;
diff --git a/server/src/db/models/MonitorStats.ts b/server/src/db/models/MonitorStats.ts
new file mode 100755
index 000000000..d1d49ca9f
--- /dev/null
+++ b/server/src/db/models/MonitorStats.ts
@@ -0,0 +1,63 @@
+import { Schema, model, type Types } from "mongoose";
+import type { MonitorStats as MonitorStatsEntity } from "@/types/monitorStats.js";
+
+type MonitorStatsDocumentBase = Omit & {
+ monitorId: Types.ObjectId;
+};
+
+interface MonitorStatsDocument extends MonitorStatsDocumentBase {
+ _id: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const MonitorStatsSchema = new Schema(
+ {
+ monitorId: {
+ type: Schema.Types.ObjectId,
+ ref: "Monitor",
+ immutable: true,
+ index: true,
+ required: true,
+ },
+ avgResponseTime: {
+ type: Number,
+ default: 0,
+ },
+ totalChecks: {
+ type: Number,
+ default: 0,
+ },
+ totalUpChecks: {
+ type: Number,
+ default: 0,
+ },
+ totalDownChecks: {
+ type: Number,
+ default: 0,
+ },
+ uptimePercentage: {
+ type: Number,
+ default: 0,
+ },
+ lastCheckTimestamp: {
+ type: Number,
+ default: 0,
+ },
+ lastResponseTime: {
+ type: Number,
+ default: 0,
+ },
+ timeOfLastFailure: {
+ type: Number,
+ default: undefined,
+ },
+ },
+ { timestamps: true }
+);
+
+const MonitorStatsModel = model("MonitorStats", MonitorStatsSchema);
+
+export type { MonitorStatsDocument };
+export { MonitorStatsModel };
+export default MonitorStatsModel;
diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts
index 473940874..3ac99a390 100644
--- a/server/src/db/models/index.ts
+++ b/server/src/db/models/index.ts
@@ -3,3 +3,6 @@ export { default as MonitorModel } from "@/db/models/Monitor.js";
export * from "@/db/models/Check.js";
export { default as CheckModel } from "@/db/models/Check.js";
+
+export * from "@/db/models/MonitorStats.js";
+export { default as MonitorStatsModel } from "@/db/models/MonitorStats.js";
diff --git a/server/src/db/modules/monitorModule.js b/server/src/db/modules/monitorModule.js
index 9c8ca7e32..84ff86996 100755
--- a/server/src/db/modules/monitorModule.js
+++ b/server/src/db/modules/monitorModule.js
@@ -194,17 +194,6 @@ class MonitorModule {
};
};
- getAllMonitors = async () => {
- try {
- const monitors = await this.Monitor.find();
- return monitors;
- } catch (error) {
- error.service = SERVICE_NAME;
- error.method = "getAllMonitors";
- throw error;
- }
- };
-
getMonitorById = async (monitorId) => {
try {
const monitor = await this.Monitor.findById(monitorId);
@@ -264,48 +253,6 @@ class MonitorModule {
}
};
- getMonitorStatsById = async ({ monitorId, sortOrder, dateRange, numToDisplay, normalize }) => {
- try {
- // Get monitor, if we can't find it, abort with error
- const monitor = await this.Monitor.findById(monitorId);
- if (monitor === null || monitor === undefined) {
- throw new Error(this.stringService.getDbFindMonitorById(monitorId));
- }
-
- // Get query params
- const sort = sortOrder === "asc" ? 1 : -1;
-
- // Get Checks for monitor in date range requested
- const dates = this.getDateRange(dateRange);
- const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, dates, sort);
-
- // Build monitor stats
- const monitorStats = {
- ...monitor.toObject(),
- uptimeDuration: this.calculateUptimeDuration(checksAll),
- lastChecked: this.getLastChecked(checksAll),
- latestResponseTime: this.getLatestResponseTime(checksAll),
- periodIncidents: this.getIncidents(checksForDateRange),
- periodTotalChecks: checksForDateRange.length,
- checks: this.processChecksForDisplay(this.NormalizeData, checksForDateRange, numToDisplay, normalize),
- };
-
- if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port" || monitor.type === "game") {
- // HTTP/PING Specific stats
- monitorStats.periodAvgResponseTime = this.getAverageResponseTime(checksForDateRange);
- monitorStats.periodUptime = this.getUptimePercentage(checksForDateRange);
- const groupedChecks = this.groupChecksByTime(checksForDateRange, dateRange);
- monitorStats.aggregateData = Object.values(groupedChecks).map(this.calculateGroupStats);
- }
-
- return monitorStats;
- } catch (error) {
- error.service = SERVICE_NAME;
- error.method = "getMonitorStatsById";
- throw error;
- }
- };
-
getHardwareDetailsById = async ({ monitorId, dateRange }) => {
try {
const monitor = await this.Monitor.findById(monitorId);
@@ -342,47 +289,33 @@ class MonitorModule {
}
};
- getMonitorsByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId }) => {
- limit = parseInt(limit);
- page = parseInt(page);
- rowsPerPage = parseInt(rowsPerPage);
- if (field === undefined) {
- field = "name";
- order = "asc";
- }
- // Build match stage
- const matchStage = { teamId: new this.ObjectId(teamId) };
- if (type !== undefined) {
- matchStage.type = Array.isArray(type) ? { $in: type } : type;
- }
-
- const summaryResult = await this.Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage }));
- const summary = summaryResult[0];
-
- const monitors = await this.Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order }));
-
- const filteredMonitors = await this.Monitor.aggregate(
- buildFilteredMonitorsByTeamIdPipeline({
- matchStage,
- filter,
- page,
- rowsPerPage,
- field,
- order,
- limit,
- type,
- })
- );
-
- const normalizedFilteredMonitors = filteredMonitors.map((monitor) => {
- if (!monitor.checks) {
- return monitor;
+ getMonitorsByTeamId = async ({ teamId, type, filter }) => {
+ try {
+ const matchStage = { teamId: new this.ObjectId(teamId) };
+ if (type !== undefined) {
+ matchStage.type = Array.isArray(type) ? { $in: type } : type;
}
- monitor.checks = this.NormalizeData(monitor.checks, 10, 100);
- return monitor;
- });
-
- return { summary, monitors, filteredMonitors: normalizedFilteredMonitors };
+ if (filter !== undefined && filter !== null && filter !== "") {
+ matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }];
+ }
+ const monitors = await this.Monitor.find(matchStage)
+ .sort({ name: 1 })
+ .select({
+ _id: 1,
+ name: 1,
+ type: 1,
+ url: 1,
+ status: 1,
+ isActive: 1,
+ teamId: 1,
+ })
+ .lean();
+ return monitors;
+ } catch (error) {
+ error.service = SERVICE_NAME;
+ error.method = "getMonitorsByTeamId";
+ throw error;
+ }
};
getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => {
diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts
index df69cf18a..cedea8d86 100644
--- a/server/src/repositories/checks/IChecksRepository.ts
+++ b/server/src/repositories/checks/IChecksRepository.ts
@@ -1,10 +1,65 @@
+import type { Check, CheckAudits, MonitorType } from "@/types/index.js";
import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory.js";
-export interface IChecksRepository {
- // create
- // single fetch
- // collection fetch
- findLatestChecksByMonitorIds(monitorIds: string[]): Promise;
- // update
- // delete
+export interface PageSpeedChecksResult {
+ monitorType: "pagespeed";
+ checks: Check[];
+}
+
+export interface HardwareChecksResult {
+ monitorType: "hardware";
+ aggregateData: {
+ latestCheck: Check | null;
+ totalChecks: number;
+ };
+ upChecks: {
+ totalChecks: number;
+ };
+ checks: Array<{
+ _id: string;
+ avgCpuUsage: number;
+ avgMemoryUsage: number;
+ avgTemperature: number[];
+ disks: Array<{
+ name: string;
+ readSpeed: number;
+ writeSpeed: number;
+ totalBytes: number;
+ freeBytes: number;
+ usagePercent: number;
+ }>;
+ net: Array<{
+ name: string;
+ bytesSentPerSecond: number;
+ deltaBytesRecv: number;
+ deltaPacketsSent: number;
+ deltaPacketsRecv: number;
+ deltaErrIn: number;
+ deltaErrOut: number;
+ deltaDropIn: number;
+ deltaDropOut: number;
+ deltaFifoIn: number;
+ deltaFifoOut: number;
+ }>;
+ }>;
+}
+
+export interface UptimeChecksResult {
+ monitorType: Exclude;
+ groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>;
+ groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>;
+ groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>;
+ uptimePercentage: number;
+ avgResponseTime: number;
+}
+
+export interface IChecksRepository {
+ findLatestChecksByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise;
+ findDateRangeChecksByMonitor(
+ monitorId: string,
+ startDate: Date,
+ endDate: Date,
+ dateString: string,
+ options?: { type?: MonitorType }
+ ): Promise;
}
diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts
index de26efff4..7ede5587e 100644
--- a/server/src/repositories/checks/MongoChecksRepistory.ts
+++ b/server/src/repositories/checks/MongoChecksRepistory.ts
@@ -11,9 +11,15 @@ import type {
CheckMetadata,
CheckNetworkInterfaceInfo,
CheckTimings,
+ MonitorType,
} from "@/types/index.js";
import { CheckModel, type CheckDocument } from "@/db/models/index.js";
import mongoose from "mongoose";
+import {
+ getAggregateData as getHardwareAggregateData,
+ getHardwareStats,
+ getUpChecks as getHardwareUpChecks,
+} from "@/db/modules/monitorModuleQueries.js";
export type LatestChecksMap = Record;
@@ -172,15 +178,16 @@ class MongoChecksRepistory implements IChecksRepository {
};
};
- findLatestChecksByMonitorIds = async (monitorIds: string[]): Promise => {
+ findLatestChecksByMonitorIds = async (monitorIds: string[], options?: { limitPerMonitor?: number }): Promise => {
if (monitorIds.length === 0) {
return {};
}
const mongoIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
- const limitPerMonitor = 25;
- const maxIntervalMs = Number(process.env.MONITOR_MAX_INTERVAL_MS ?? 10 * 60 * 1000);
- const bufferMs = Number(process.env.MONITOR_INTERVAL_BUFFER_MS ?? maxIntervalMs);
- const lookbackMs = 25 * maxIntervalMs + bufferMs;
+ const limitPerMonitor = options?.limitPerMonitor ?? 25;
+ const maxIntervalMs = Number(10 * 60 * 1000);
+ const bufferMs = Number(maxIntervalMs);
+ const lookbackMs = limitPerMonitor * maxIntervalMs + bufferMs;
+
const cutoffDate = new Date(Date.now() - lookbackMs);
const checkGroups = await CheckModel.aggregate([
{
@@ -202,12 +209,187 @@ class MongoChecksRepistory implements IChecksRepository {
},
},
]);
+
return checkGroups.reduce((acc, group) => {
const monitorId = group._id.toString();
acc[monitorId] = (group.latestChecks ?? []).map((doc: CheckDocument) => this.toEntity(doc));
return acc;
}, {});
};
+
+ findDateRangeChecksByMonitor = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => {
+ const monitorObjectId = new mongoose.Types.ObjectId(monitorId);
+ if (options?.type === "hardware") {
+ return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
+ }
+ if (options?.type === "pagespeed") {
+ return this.findPageSpeedDateRangeChecks(monitorObjectId, startDate, endDate);
+ }
+ return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString);
+ };
+
+ private findUptimeDateRangeChecks = async (
+ monitorType: Exclude,
+ monitorObjectId: mongoose.Types.ObjectId,
+ startDate: Date,
+ endDate: Date,
+ dateString: string
+ ) => {
+ const matchStage = {
+ "metadata.monitorId": monitorObjectId,
+ updatedAt: { $gte: startDate, $lte: endDate },
+ };
+ const [result] = await CheckModel.aggregate([
+ { $match: matchStage },
+ { $sort: { updatedAt: 1 } },
+ {
+ $facet: {
+ uptimePercentage: [
+ {
+ $group: {
+ _id: null,
+ upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } },
+ totalChecks: { $sum: 1 },
+ },
+ },
+ {
+ $project: {
+ _id: 0,
+ percentage: {
+ $cond: [{ $eq: ["$totalChecks", 0] }, 0, { $divide: ["$upChecks", "$totalChecks"] }],
+ },
+ },
+ },
+ ],
+ groupedAvgResponseTime: [
+ {
+ $group: {
+ _id: null,
+ avgResponseTime: { $avg: "$responseTime" },
+ },
+ },
+ ],
+ groupedChecks: [
+ {
+ $group: {
+ _id: {
+ $dateToString: { format: dateString, date: "$createdAt" },
+ },
+ avgResponseTime: { $avg: "$responseTime" },
+ totalChecks: { $sum: 1 },
+ },
+ },
+ { $sort: { _id: 1 } },
+ ],
+ groupedUpChecks: [
+ { $match: { status: true } },
+ {
+ $group: {
+ _id: {
+ $dateToString: { format: dateString, date: "$createdAt" },
+ },
+ totalChecks: { $sum: 1 },
+ avgResponseTime: { $avg: "$responseTime" },
+ },
+ },
+ { $sort: { _id: 1 } },
+ ],
+ groupedDownChecks: [
+ { $match: { status: false } },
+ {
+ $group: {
+ _id: {
+ $dateToString: { format: dateString, date: "$createdAt" },
+ },
+ totalChecks: { $sum: 1 },
+ avgResponseTime: { $avg: "$responseTime" },
+ },
+ },
+ { $sort: { _id: 1 } },
+ ],
+ },
+ },
+ ]);
+
+ const uptimePercentage = result?.uptimePercentage?.[0]?.percentage ?? 0;
+ const avgResponseTime = result?.groupedAvgResponseTime?.[0]?.avgResponseTime ?? 0;
+
+ return {
+ monitorType,
+ groupedChecks: result?.groupedChecks ?? [],
+ groupedUpChecks: result?.groupedUpChecks ?? [],
+ groupedDownChecks: result?.groupedDownChecks ?? [],
+ uptimePercentage,
+ avgResponseTime,
+ };
+ };
+
+ private findHardwareDateRangeChecks = async (monitorObjectId: mongoose.Types.ObjectId, startDate: Date, endDate: Date, dateString: string) => {
+ const monitorId = monitorObjectId.toHexString();
+ const dates = { start: startDate, end: endDate };
+ const [aggregateDataDoc, upChecksDoc, hardwareMetrics] = await Promise.all([
+ getHardwareAggregateData(monitorId, dates),
+ getHardwareUpChecks(monitorId, dates),
+ getHardwareStats(monitorId, dates, dateString),
+ ]);
+
+ const aggregateData = {
+ latestCheck: aggregateDataDoc?.latestCheck ? this.toEntity(aggregateDataDoc.latestCheck as CheckDocument) : null,
+ totalChecks: aggregateDataDoc?.totalChecks ?? 0,
+ };
+
+ const upChecks = {
+ totalChecks: upChecksDoc?.totalChecks ?? 0,
+ };
+
+ const checks = (hardwareMetrics ?? []).map((metric) => ({
+ _id: metric._id,
+ avgCpuUsage: metric.avgCpuUsage ?? 0,
+ avgMemoryUsage: metric.avgMemoryUsage ?? 0,
+ avgTemperature: metric.avgTemperature ?? [],
+ disks: (metric.disks ?? []).map((disk: { [key: string]: number | string | undefined }) => ({
+ name: disk?.name ?? "",
+ readSpeed: disk?.readSpeed ?? 0,
+ writeSpeed: disk?.writeSpeed ?? 0,
+ totalBytes: disk?.totalBytes ?? 0,
+ freeBytes: disk?.freeBytes ?? 0,
+ usagePercent: disk?.usagePercent ?? 0,
+ })),
+ net: (metric.net ?? []).map((iface: { [key: string]: number | string | undefined }) => ({
+ name: iface?.name ?? "",
+ bytesSentPerSecond: iface?.bytesSentPerSecond ?? 0,
+ deltaBytesRecv: iface?.deltaBytesRecv ?? 0,
+ deltaPacketsSent: iface?.deltaPacketsSent ?? 0,
+ deltaPacketsRecv: iface?.deltaPacketsRecv ?? 0,
+ deltaErrIn: iface?.deltaErrIn ?? 0,
+ deltaErrOut: iface?.deltaErrOut ?? 0,
+ deltaDropIn: iface?.deltaDropIn ?? 0,
+ deltaDropOut: iface?.deltaDropOut ?? 0,
+ deltaFifoIn: iface?.deltaFifoIn ?? 0,
+ deltaFifoOut: iface?.deltaFifoOut ?? 0,
+ })),
+ }));
+
+ return {
+ monitorType: "hardware" as const,
+ aggregateData,
+ upChecks,
+ checks,
+ };
+ };
+
+ private findPageSpeedDateRangeChecks = async (monitorObjectId: mongoose.Types.ObjectId, startDate: Date, endDate: Date) => {
+ const matchStage = {
+ "metadata.monitorId": monitorObjectId,
+ createdAt: { $gte: startDate, $lte: endDate },
+ };
+
+ const checks = await CheckModel.find(matchStage).sort({ createdAt: -1 }).limit(25).lean();
+ return {
+ monitorType: "pagespeed" as const,
+ checks: checks.map((doc) => this.toEntity(doc)),
+ };
+ };
}
export default MongoChecksRepistory;
diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts
index c2bae6939..44d64f5bb 100644
--- a/server/src/repositories/index.ts
+++ b/server/src/repositories/index.ts
@@ -3,3 +3,6 @@ export { default as MongoMonitorsRepository } from "@/repositories/monitors/Mong
export * from "@/repositories/checks/IChecksRepository.js";
export { default as MongoChecksRepository } from "@/repositories/checks/MongoChecksRepistory.js";
+
+export * from "@/repositories/monitor-stats/IMonitorStatsRepository.js";
+export { default as MongoMonitorStatsRepository } from "@/repositories/monitor-stats/MongoMonitorStatsRepository.js";
diff --git a/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts b/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts
new file mode 100644
index 000000000..a0720619f
--- /dev/null
+++ b/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts
@@ -0,0 +1,9 @@
+import type { MonitorStats } from "@/types/index.js";
+export interface IMonitorStatsRepository {
+ // create
+ // single fetch
+ findByMonitorId(monitorId: string): Promise;
+ // update
+ // delete
+ // other
+}
diff --git a/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts b/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts
new file mode 100644
index 000000000..d1d6a4825
--- /dev/null
+++ b/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts
@@ -0,0 +1,44 @@
+import { type MonitorStatsDocument, MonitorStatsModel } from "@/db/models/index.js";
+import type { MonitorStats } from "@/types/index.js";
+import { IMonitorStatsRepository } from "@/repositories/index.js";
+import mongoose from "mongoose";
+import { AppError } from "@/utils/AppError.js";
+class MongoMonitorStatsRepository implements IMonitorStatsRepository {
+ private toEntity = (doc: MonitorStatsDocument): MonitorStats => {
+ const toStringId = (value: unknown): string => {
+ if (value instanceof mongoose.Types.ObjectId) {
+ return value.toString();
+ }
+ return value?.toString() ?? "";
+ };
+
+ const toDateString = (value: Date | string): string => {
+ return value instanceof Date ? value.toISOString() : value;
+ };
+
+ return {
+ id: toStringId(doc._id),
+ monitorId: toStringId(doc.monitorId),
+ avgResponseTime: doc.avgResponseTime,
+ totalChecks: doc.totalChecks,
+ totalUpChecks: doc.totalUpChecks,
+ totalDownChecks: doc.totalDownChecks,
+ uptimePercentage: doc.uptimePercentage,
+ lastCheckTimestamp: doc.lastCheckTimestamp,
+ lastResponseTime: doc.lastResponseTime,
+ timeOfLastFailure: doc.timeOfLastFailure,
+ createdAt: toDateString(doc.createdAt),
+ updatedAt: toDateString(doc.updatedAt),
+ };
+ };
+
+ findByMonitorId = async (monitorId: string): Promise => {
+ const monitorStats = await MonitorStatsModel.findOne({ monitorId: new mongoose.Types.ObjectId(monitorId) });
+ if (!monitorStats) {
+ throw new AppError({ message: "Monitor stats not found", status: 404 });
+ }
+ return this.toEntity(monitorStats);
+ };
+}
+
+export default MongoMonitorStatsRepository;
diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts
index 865d7f8e4..0da9c7279 100644
--- a/server/src/repositories/monitors/IMonitorsRepository.ts
+++ b/server/src/repositories/monitors/IMonitorsRepository.ts
@@ -12,12 +12,18 @@ export interface TeamQueryConfig {
export interface IMonitorsRepository {
// create
+ create(monitor: Monitor, teamId: string, userId: string): Promise;
+ createBulkMonitors(monitors: Monitor[]): Promise;
// single fetch
+ findById(monitorId: string, teamId?: string): Promise;
+
// collection fetch
findAll(): Promise;
findByTeamId(teamId: string, config: TeamQueryConfig): Promise;
// update
+ update(monitorId: string, updates: Partial): Promise;
// delete
+ deleteByTeamId(teamId: string): Promise<{ monitors: Monitor[]; deletedCount: number }>;
// counts
findMonitorCountByTeamIdAndType(teamId: string, config: TeamQueryConfig): Promise;
diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts
index dd85f9a3d..c9af6668d 100644
--- a/server/src/repositories/monitors/MongoMonitorsRepository.ts
+++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts
@@ -3,11 +3,44 @@ import type { MonitorDocument } from "@/db/models/Monitor.js";
import type { Monitor, MonitorType } from "@/types/monitor.js";
import mongoose, { type FilterQuery } from "mongoose";
import type { IMonitorsRepository, TeamQueryConfig } from "./IMonitorsRepository.js";
+import { AppError } from "@/utils/AppError.js";
class MongoMonitorsRepository implements IMonitorsRepository {
- findAll = async (): Promise => {
- const documents = await MonitorModel.find().exec();
- return this.mapDocuments(documents);
+ create = async (monitor: Monitor, teamId: string, userId: string) => {
+ const monitorModel = new MonitorModel({ ...monitor, teamId, userId });
+ const saved = await monitorModel.save();
+ return this.toEntity(saved);
+ };
+
+ createBulkMonitors = async (monitors: Monitor[]): Promise => {
+ if (!monitors.length) {
+ return [];
+ }
+ const payload = monitors.map((monitor) => ({ ...monitor, notifications: undefined }));
+ const inserted = await MonitorModel.insertMany(payload, { ordered: false });
+ return this.mapDocuments(inserted);
+ };
+
+ findById = async (monitorId: string, teamId?: string): Promise => {
+ const match: { _id: string; teamId?: string } = { _id: monitorId };
+ if (teamId) {
+ match.teamId = teamId;
+ }
+ const monitor = await MonitorModel.findOne(match);
+ if (!monitor) {
+ if (monitor === null || monitor === undefined) {
+ throw new AppError({
+ message: `Monitor with ID ${monitorId} not found.`,
+ status: 404,
+ });
+ }
+ }
+ return this.toEntity(monitor);
+ };
+
+ findAll = async (): Promise => {
+ const monitors = await MonitorModel.find();
+ return this.mapDocuments(monitors);
};
findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise => {
@@ -64,9 +97,32 @@ class MongoMonitorsRepository implements IMonitorsRepository {
return count;
};
- private mapDocuments = (documents: MonitorDocument[]): Monitor[] | null => {
+ update = async (monitorId: string, patch: Partial) => {
+ const updatedMonitor = await MonitorModel.findOneAndUpdate(
+ { _id: monitorId },
+ {
+ $set: {
+ ...patch,
+ },
+ },
+ { new: true, runValidators: true }
+ );
+ if (!updatedMonitor) {
+ throw new AppError({ message: `Failed to update monitor with id ${monitorId}`, status: 500 });
+ }
+ return this.toEntity(updatedMonitor);
+ };
+
+ deleteByTeamId = async (teamId: string) => {
+ const monitors = await MonitorModel.find({ teamId });
+ const { deletedCount } = await MonitorModel.deleteMany({ teamId });
+
+ return { monitors: this.mapDocuments(monitors), deletedCount };
+ };
+
+ private mapDocuments = (documents: MonitorDocument[]): Monitor[] => {
if (!documents?.length) {
- return null;
+ return [];
}
return documents.map((doc) => this.toEntity(doc));
};
diff --git a/server/src/routes/v1/monitorRoute.ts b/server/src/routes/v1/monitorRoute.ts
index e68e0019f..24bc64ec8 100755
--- a/server/src/routes/v1/monitorRoute.ts
+++ b/server/src/routes/v1/monitorRoute.ts
@@ -27,10 +27,11 @@ class MonitorRoutes {
// Hardware routes
this.router.get("/hardware/details/:monitorId", this.monitorController.getHardwareDetailsById);
+ // PageSpeed routes
+ this.router.get("/pagespeed/details/:monitorId", this.monitorController.getPageSpeedDetailsById);
// General monitor routes
this.router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.pauseMonitor);
- this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById);
// Util routes
this.router.get("/certificate/:monitorId", (req, res, next) => {
@@ -38,7 +39,6 @@ class MonitorRoutes {
});
// General monitor CRUD routes
- this.router.get("/", this.monitorController.getAllMonitors);
this.router.post("/", isAllowed(["admin", "superadmin"]), this.monitorController.createMonitor);
this.router.delete("/", isAllowed(["superadmin"]), this.monitorController.deleteAllMonitors);
diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js
deleted file mode 100644
index 4bdc45eb4..000000000
--- a/server/src/service/business/monitorService.js
+++ /dev/null
@@ -1,342 +0,0 @@
-import { createMonitorsBodyValidation } from "@/validation/joi.js";
-import { NormalizeData } from "@/utils/dataUtils.js";
-
-const SERVICE_NAME = "MonitorService";
-class MonitorService {
- static SERVICE_NAME = SERVICE_NAME;
-
- constructor({
- db,
- settingsService,
- jobQueue,
- stringService,
- emailService,
- papaparse,
- logger,
- errorService,
- games,
- monitorsRepository,
- checksRepository,
- }) {
- this.db = db;
- this.settingsService = settingsService;
- this.jobQueue = jobQueue;
- this.stringService = stringService;
- this.emailService = emailService;
- this.papaparse = papaparse;
- this.logger = logger;
- this.errorService = errorService;
- this.games = games;
- this.monitorsRepository = monitorsRepository;
- this.checksRepository = checksRepository;
- }
-
- get serviceName() {
- return MonitorService.SERVICE_NAME;
- }
-
- verifyTeamAccess = async ({ teamId, monitorId }) => {
- const monitor = await this.db.monitorModule.getMonitorById(monitorId);
- if (!monitor?.teamId?.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
- };
-
- getAllMonitors = async () => {
- const monitors = await this.db.monitorModule.getAllMonitors();
- return monitors;
- };
-
- getUptimeDetailsById = async ({ teamId, monitorId, dateRange, normalize }) => {
- await this.verifyTeamAccess({ teamId, monitorId });
- const data = await this.db.monitorModule.getUptimeDetailsById({
- monitorId,
- dateRange,
- normalize,
- });
-
- return data;
- };
-
- getMonitorStatsById = async ({ teamId, monitorId, limit, sortOrder, dateRange, numToDisplay, normalize }) => {
- await this.verifyTeamAccess({ teamId, monitorId });
- const monitorStats = await this.db.monitorModule.getMonitorStatsById({
- monitorId,
- limit,
- sortOrder,
- dateRange,
- numToDisplay,
- normalize,
- });
-
- return monitorStats;
- };
-
- getHardwareDetailsById = async ({ teamId, monitorId, dateRange }) => {
- await this.verifyTeamAccess({ teamId, monitorId });
- const monitor = await this.db.monitorModule.getHardwareDetailsById({ monitorId, dateRange });
-
- return monitor;
- };
-
- getMonitorById = async ({ teamId, monitorId }) => {
- await this.verifyTeamAccess({ teamId, monitorId });
- const monitor = await this.db.monitorModule.getMonitorById(monitorId);
-
- return monitor;
- };
-
- createMonitor = async ({ teamId, userId, body }) => {
- const monitor = await this.db.monitorModule.createMonitor({
- body,
- teamId,
- userId,
- });
-
- this.jobQueue.addJob(monitor._id, monitor);
- };
-
- createBulkMonitors = async ({ fileData, userId, teamId }) => {
- const { parse } = this.papaparse;
-
- return new Promise((resolve, reject) => {
- parse(fileData, {
- header: true,
- skipEmptyLines: true,
- transform: (value, header) => {
- if (value === "") return undefined; // Empty fields become undefined
-
- // Handle 'port' and 'interval' fields, check if they're valid numbers
- if (["port", "interval"].includes(header)) {
- const num = parseInt(value, 10);
- if (isNaN(num)) {
- throw this.errorService.createBadRequestError(`${header} should be a valid number, got: ${value}`);
- }
- return num;
- }
-
- return value;
- },
- complete: async ({ data, errors }) => {
- try {
- if (errors.length > 0) {
- throw this.errorService.createServerError("Error parsing CSV");
- }
-
- if (!data || data.length === 0) {
- throw this.errorService.createServerError("CSV file contains no data rows");
- }
-
- const enrichedData = data.map((monitor) => ({
- userId,
- teamId,
- ...monitor,
- description: monitor.description || monitor.name || monitor.url,
- name: monitor.name || monitor.url,
- type: monitor.type || "http",
- }));
-
- await createMonitorsBodyValidation.validateAsync(enrichedData);
-
- const monitors = await this.db.monitorModule.createBulkMonitors(enrichedData);
-
- await Promise.all(
- monitors.map(async (monitor) => {
- this.jobQueue.addJob(monitor._id, monitor);
- })
- );
-
- resolve(monitors);
- } catch (error) {
- reject(error);
- }
- },
- });
- });
- };
-
- deleteMonitor = async ({ teamId, monitorId }) => {
- await this.verifyTeamAccess({ teamId, monitorId });
- const monitor = await this.db.monitorModule.deleteMonitor({ teamId, monitorId });
- await this.jobQueue.deleteJob(monitor);
- await this.db.statusPageModule.deleteStatusPagesByMonitorId(monitor._id);
- return monitor;
- };
-
- deleteAllMonitors = async ({ teamId }) => {
- const { monitors, deletedCount } = await this.db.monitorModule.deleteAllMonitors(teamId);
- await Promise.all(
- monitors.map(async (monitor) => {
- try {
- await this.jobQueue.deleteJob(monitor);
- await this.db.checkModule.deleteChecks(monitor._id);
- await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor._id);
- await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor._id);
- } catch (error) {
- this.logger.warn({
- message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
- service: SERVICE_NAME,
- method: "deleteAllMonitors",
- stack: error.stack,
- });
- }
- })
- );
- return deletedCount;
- };
-
- editMonitor = async ({ teamId, monitorId, body }) => {
- await this.verifyTeamAccess({ teamId, monitorId });
- const editedMonitor = await this.db.monitorModule.editMonitor({ monitorId, body });
- await this.jobQueue.updateJob(editedMonitor);
- };
-
- pauseMonitor = async ({ teamId, monitorId }) => {
- await this.verifyTeamAccess({ teamId, monitorId });
- const monitor = await this.db.monitorModule.pauseMonitor({ monitorId });
- monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor);
- return monitor;
- };
-
- addDemoMonitors = async ({ userId, teamId }) => {
- const demoMonitors = await this.db.monitorModule.addDemoMonitors(userId, teamId);
-
- await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor)));
- return demoMonitors;
- };
-
- sendTestEmail = async ({ to }) => {
- const subject = this.stringService.testEmailSubject;
- const context = { testName: "Monitoring System" };
-
- const html = await this.emailService.buildEmail("testEmailTemplate", context);
- const messageId = await this.emailService.sendEmail(to, subject, html);
-
- if (!messageId) {
- throw this.errorService.createServerError("Failed to send test email.");
- }
-
- return messageId;
- };
-
- getMonitorsByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order }) => {
- const monitors = await this.db.monitorModule.getMonitorsByTeamId({
- limit,
- type,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- teamId,
- });
- return monitors;
- };
-
- getMonitorsAndSummaryByTeamId = async ({ teamId, type, explain }) => {
- const result = await this.db.monitorModule.getMonitorsAndSummaryByTeamId({
- type,
- explain,
- teamId,
- });
- return result;
- };
-
- getMonitorsWithChecksByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order, explain }) => {
- const count = await this.monitorsRepository.findMonitorCountByTeamIdAndType(teamId, { type, filter });
- const monitors = await this.monitorsRepository.findByTeamId(teamId, {
- limit,
- type,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- });
-
- const monitorIds = monitors?.map((m) => m.id) ?? [];
- const checksMap = await this.checksRepository.findLatestChecksByMonitorIds(monitorIds);
- const monitorsWithChecks = (monitors ?? []).map((monitor) => {
- const checks = NormalizeData(checksMap[monitor.id] ?? [], 10, 100);
- return {
- ...monitor,
- checks,
- };
- });
-
- return { count, monitors: monitorsWithChecks };
- };
-
- exportMonitorsToCSV = async ({ teamId }) => {
- const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId });
-
- if (!monitors || monitors.length === 0) {
- throw this.errorService.createNotFoundError("No monitors to export");
- }
-
- const csvData = monitors?.filteredMonitors?.map((monitor) => ({
- name: monitor.name,
- description: monitor.description,
- type: monitor.type,
- url: monitor.url,
- interval: monitor.interval,
- port: monitor.port,
- ignoreTlsErrors: monitor.ignoreTlsErrors,
- isActive: monitor.isActive,
- }));
-
- const csv = this.papaparse.unparse(csvData);
- return csv;
- };
- exportMonitorsToJSON = async ({ teamId }) => {
- const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId });
-
- if (!monitors || monitors.length === 0) {
- throw this.errorService.createNotFoundError("No monitors to export");
- }
-
- const json = monitors?.filteredMonitors
- ?.map((monitor) => {
- const initialType = monitor.type;
- let parsedType;
-
- if (initialType === "hardware") {
- parsedType = "infrastructure";
- } else if (initialType === "http") {
- if (monitor.url.startsWith("https://")) {
- parsedType = "https";
- } else {
- parsedType = "http";
- }
- } else if (initialType === "pagespeed") {
- parsedType = initialType;
- } else {
- // Skip unsupported types
- return;
- }
-
- return {
- name: monitor.name,
- url: monitor.url,
- type: parsedType,
- interval: monitor.interval,
- n: monitor.statusWindowSize,
- secret: monitor.secret,
- };
- })
- .filter(Boolean);
-
- return json;
- };
-
- getAllGames = () => {
- return this.games;
- };
-
- getGroupsByTeamId = async ({ teamId }) => {
- const groups = await this.db.monitorModule.getGroupsByTeamId({ teamId });
- return groups;
- };
-}
-
-export default MonitorService;
diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts
new file mode 100644
index 000000000..3793ac35e
--- /dev/null
+++ b/server/src/service/business/monitorService.ts
@@ -0,0 +1,566 @@
+import { createMonitorsBodyValidation } from "@/validation/joi.js";
+import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js";
+import { type Monitor } from "@/types/index.js";
+import type { MonitorType } from "@/types/monitor.js";
+import type { IChecksRepository, IMonitorsRepository, IMonitorStatsRepository } from "@/repositories/index.js";
+import fs from "fs";
+import { fileURLToPath } from "url";
+import path from "path";
+
+import { AppError } from "../infrastructure/errorService.js";
+
+const SERVICE_NAME = "MonitorService";
+type DateRangeKey = "recent" | "day" | "week" | "month" | "all";
+
+export interface IMonitorService {
+ readonly serviceName: string;
+ verifyTeamAccess(args: { teamId: string; monitorId: string }): Promise;
+
+ // create
+ createMonitor(teamId: string, userId: string, body: Monitor): Promise;
+ createBulkMonitors(fileData: string, userId: string, teamId: string): Promise;
+ addDemoMonitors(args: { userId: string; teamId: string }): Promise;
+
+ // read
+ getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string; normalize?: boolean }): Promise;
+ getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise;
+ getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise;
+ getMonitorById(args: { teamId: string; monitorId: string }): Promise;
+ getMonitorsByTeamId(args: {
+ teamId: string;
+ limit?: number;
+ type?: MonitorType | MonitorType[];
+ page?: number;
+ rowsPerPage?: number;
+ filter?: string;
+ field?: string;
+ order?: "asc" | "desc";
+ }): Promise;
+ getMonitorsAndSummaryByTeamId(args: { teamId: string; type?: string | string[]; explain?: boolean }): Promise;
+ getMonitorsWithChecksByTeamId(args: {
+ teamId: string;
+ limit?: number;
+ type?: MonitorType | MonitorType[];
+ page?: number;
+ rowsPerPage?: number;
+ filter?: string;
+ field?: string;
+ order?: "asc" | "desc";
+ explain?: boolean;
+ }): Promise<{ count: number; monitors: any[] }>;
+ getAllGames(): any;
+ getGroupsByTeamId(args: { teamId: string }): Promise;
+
+ // update
+ editMonitor(args: { teamId: string; monitorId: string; body: any }): Promise;
+ pauseMonitor(args: { teamId: string; monitorId: string }): Promise;
+
+ // delete
+ deleteMonitor(args: { teamId: string; monitorId: string }): Promise;
+ deleteAllMonitors(args: { teamId: string }): Promise;
+
+ // other
+ sendTestEmail(args: { to: string }): Promise;
+ exportMonitorsToCSV(args: { teamId: string }): Promise;
+ exportMonitorsToJSON(args: { teamId: string }): Promise;
+}
+
+export class MonitorService implements IMonitorService {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private db: any;
+ private jobQueue: any;
+ private stringService: any;
+ private emailService: any;
+ private papaparse: any;
+ private logger: any;
+ private errorService: any;
+ private games: any;
+ private monitorsRepository: IMonitorsRepository;
+ private checksRepository: IChecksRepository;
+ private monitorStatsRepository: IMonitorStatsRepository;
+
+ constructor({
+ db,
+ jobQueue,
+ stringService,
+ emailService,
+ papaparse,
+ logger,
+ errorService,
+ games,
+ monitorsRepository,
+ checksRepository,
+ monitorStatsRepository,
+ }: {
+ db: any;
+ jobQueue: any;
+ stringService: any;
+ emailService: any;
+ papaparse: any;
+ logger: any;
+ errorService: any;
+ games: any;
+ monitorsRepository: IMonitorsRepository;
+ checksRepository: IChecksRepository;
+ monitorStatsRepository: IMonitorStatsRepository;
+ }) {
+ this.db = db;
+ this.jobQueue = jobQueue;
+ this.stringService = stringService;
+ this.emailService = emailService;
+ this.papaparse = papaparse;
+ this.logger = logger;
+ this.errorService = errorService;
+ this.games = games;
+ this.monitorsRepository = monitorsRepository;
+ this.checksRepository = checksRepository;
+ this.monitorStatsRepository = monitorStatsRepository;
+ }
+
+ get serviceName(): string {
+ return MonitorService.SERVICE_NAME;
+ }
+
+ private getDateRange = (dateRange: DateRangeKey) => {
+ const startDates = {
+ recent: new Date(new Date().setHours(new Date().getHours() - 2)),
+ day: new Date(new Date().setDate(new Date().getDate() - 1)),
+ week: new Date(new Date().setDate(new Date().getDate() - 7)),
+ month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
+ all: new Date(0),
+ };
+ return {
+ start: startDates[dateRange],
+ end: new Date(),
+ };
+ };
+
+ private getDateFormat = (dateRange: DateRangeKey): string => {
+ const formatLookup = {
+ recent: "%Y-%m-%dT%H:%M:00Z",
+ day: "%Y-%m-%dT%H:00:00Z",
+ week: "%Y-%m-%dT00:00:00Z",
+ month: "%Y-%m-%dT00:00:00Z",
+ all: "%Y-%m-%dT00:00:00Z",
+ };
+ return formatLookup[dateRange];
+ };
+
+ verifyTeamAccess = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => {
+ const monitor = await this.db.monitorModule.getMonitorById(monitorId);
+ if (!monitor?.teamId?.equals(teamId)) {
+ throw this.errorService.createAuthorizationError();
+ }
+ };
+
+ createMonitor = async (teamId: string, userId: string, body: Monitor): Promise => {
+ const monitor = await this.monitorsRepository.create(body, teamId, userId);
+ if (!monitor) {
+ throw new AppError("Failed to create monitor", 500);
+ }
+
+ this.jobQueue.addJob(monitor.id, monitor);
+ };
+
+ createBulkMonitors = async (fileData: string, userId: string, teamId: string): Promise => {
+ const { parse } = this.papaparse;
+
+ return new Promise((resolve, reject) => {
+ parse(fileData, {
+ header: true,
+ skipEmptyLines: true,
+ transform: (value: string, header: string): string | number | undefined => {
+ if (value === "") return undefined; // Empty fields become undefined
+
+ // Handle 'port' and 'interval' fields, check if they're valid numbers
+ if (["port", "interval"].includes(header)) {
+ const num = parseInt(value, 10);
+ if (isNaN(num)) {
+ throw this.errorService.createBadRequestError(`${header} should be a valid number, got: ${value}`);
+ }
+ return num;
+ }
+
+ return value;
+ },
+ complete: async ({ data, errors }: { data: any[]; errors: Error[] }): Promise => {
+ try {
+ if (errors.length > 0) {
+ throw this.errorService.createServerError("Error parsing CSV");
+ }
+
+ if (!data || data.length === 0) {
+ throw this.errorService.createServerError("CSV file contains no data rows");
+ }
+
+ const enrichedData = data.map((monitor: Monitor) => ({
+ ...monitor,
+ userId,
+ teamId,
+ description: monitor.description || monitor.name || monitor.url,
+ name: monitor.name || monitor.url,
+ type: monitor.type || "http",
+ }));
+
+ await createMonitorsBodyValidation.validateAsync(enrichedData);
+
+ const monitors = await this.monitorsRepository.createBulkMonitors(enrichedData);
+
+ await Promise.all(
+ monitors.map(async (monitor: Monitor) => {
+ this.jobQueue.addJob(monitor.id, monitor);
+ })
+ );
+
+ resolve(monitors);
+ } catch (error) {
+ reject(error);
+ }
+ },
+ });
+ });
+ };
+
+ addDemoMonitors = async ({ userId, teamId }: { userId: string; teamId: string }): Promise => {
+ const __filename = fileURLToPath(import.meta.url);
+ const __dirname = path.dirname(__filename);
+ const demoMonitorsPath = path.resolve(__dirname, "../../utils/demoMonitors.json");
+
+ const demoData = JSON.parse(fs.readFileSync(demoMonitorsPath, "utf8"));
+ const monitors: Monitor[] = demoData.map((monitor: Monitor) => {
+ return {
+ userId,
+ teamId,
+ name: monitor.name,
+ description: monitor.name,
+ type: "http",
+ url: monitor.url,
+ interval: 60000,
+ };
+ });
+ const demoMonitors = await this.monitorsRepository.createBulkMonitors(monitors);
+
+ await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor.id, monitor)));
+ return demoMonitors;
+ };
+
+ getUptimeDetailsById = async ({
+ teamId,
+ monitorId,
+ dateRange,
+ normalize,
+ }: {
+ teamId: string;
+ monitorId: string;
+ dateRange: string;
+ normalize?: boolean;
+ }): Promise => {
+ await this.verifyTeamAccess({ teamId, monitorId });
+
+ const monitor = await this.monitorsRepository.findById(monitorId, teamId);
+ if (!monitor) {
+ throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
+ }
+ const rangeKey = (dateRange as DateRangeKey) ?? "recent";
+ const { start, end } = this.getDateRange(rangeKey);
+ const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), {
+ type: monitor.type,
+ });
+ const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id);
+
+ if (
+ checksData.monitorType !== "http" &&
+ checksData.monitorType !== "ping" &&
+ checksData.monitorType !== "docker" &&
+ checksData.monitorType !== "port" &&
+ checksData.monitorType !== "game"
+ ) {
+ throw new AppError({ message: `${monitor.type} monitors are not supported for uptime details`, status: 400 });
+ }
+
+ return {
+ monitorData: {
+ monitor,
+ groupedChecks: NormalizeDataUptimeDetails(checksData.groupedChecks, 10, 100),
+ groupedUpChecks: NormalizeDataUptimeDetails(checksData.groupedUpChecks, 10, 100),
+ groupedDownChecks: NormalizeDataUptimeDetails(checksData.groupedDownChecks, 10, 100),
+ groupedAvgResponseTime: checksData.avgResponseTime,
+ groupedUptimePercentage: checksData.uptimePercentage,
+ },
+ monitorStats,
+ };
+ };
+
+ getHardwareDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise => {
+ await this.verifyTeamAccess({ teamId, monitorId });
+ const monitor = await this.monitorsRepository.findById(monitorId, teamId);
+ if (!monitor) {
+ throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
+ }
+ if (monitor.type !== "hardware") {
+ throw new AppError({ message: `${monitor.type} monitors are not supported for hardware details`, status: 400 });
+ }
+
+ const rangeKey = (dateRange as DateRangeKey) ?? "recent";
+ const { start, end } = this.getDateRange(rangeKey);
+ const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), {
+ type: monitor.type,
+ });
+
+ if (checksData.monitorType !== "hardware") {
+ throw new AppError({ message: "Unable to load hardware stats for this monitor", status: 500 });
+ }
+
+ const stats = {
+ aggregateData: checksData.aggregateData,
+ upChecks: checksData.upChecks,
+ checks: checksData.checks,
+ };
+
+ return {
+ ...monitor,
+ stats,
+ };
+ };
+
+ getPageSpeedDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise => {
+ await this.verifyTeamAccess({ teamId, monitorId });
+ const monitor = await this.monitorsRepository.findById(monitorId, teamId);
+ if (!monitor) {
+ throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
+ }
+ if (monitor.type !== "pagespeed") {
+ throw new AppError({ message: `${monitor.type} monitors are not supported for pagespeed details`, status: 400 });
+ }
+
+ const rangeKey = (dateRange as DateRangeKey) ?? "recent";
+ const { start, end } = this.getDateRange(rangeKey);
+ const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), {
+ type: monitor.type,
+ });
+
+ if (checksData.monitorType !== "pagespeed") {
+ throw new AppError({ message: "Unable to load pagespeed stats for this monitor", status: 500 });
+ }
+ return {
+ ...monitor,
+ checks: checksData.checks,
+ };
+ };
+ getMonitorById = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => {
+ await this.verifyTeamAccess({ teamId, monitorId });
+ const monitor = await this.monitorsRepository.findById(monitorId, teamId);
+ return monitor;
+ };
+
+ getMonitorsByTeamId = async ({ teamId, type, filter }: { teamId: string; type?: MonitorType | MonitorType[]; filter?: string }): Promise => {
+ const monitors = await this.monitorsRepository.findByTeamId(teamId, {
+ type,
+ filter,
+ });
+ return monitors;
+ };
+
+ getMonitorsAndSummaryByTeamId = async ({
+ teamId,
+ type,
+ explain,
+ }: {
+ teamId: string;
+ type?: string | string[];
+ explain?: boolean;
+ }): Promise => {
+ const result = await this.db.monitorModule.getMonitorsAndSummaryByTeamId({
+ type,
+ explain,
+ teamId,
+ });
+ return result;
+ };
+
+ getMonitorsWithChecksByTeamId = async ({
+ teamId,
+ limit,
+ type,
+ page,
+ rowsPerPage,
+ filter,
+ field,
+ order,
+ explain,
+ }: {
+ teamId: string;
+ limit?: number;
+ type?: MonitorType | MonitorType[];
+ page?: number;
+ rowsPerPage?: number;
+ filter?: string;
+ field?: string;
+ order?: "asc" | "desc";
+ explain?: boolean;
+ }): Promise<{ count: number; monitors: any[] }> => {
+ const count = await this.monitorsRepository.findMonitorCountByTeamIdAndType(teamId, { type, filter });
+ const monitors = await this.monitorsRepository.findByTeamId(teamId, {
+ limit,
+ type,
+ page,
+ rowsPerPage,
+ filter,
+ field,
+ order,
+ });
+
+ const monitorsList = (monitors ?? []) as Monitor[];
+ const snapshotTypes: MonitorType[] = ["hardware", "pagespeed"];
+ const requestedTypes = Array.isArray(type) ? type : type ? [type] : [];
+ const snapshotOnlyRequest =
+ requestedTypes.length > 0 && requestedTypes.every((requestedType) => snapshotTypes.includes(requestedType as MonitorType));
+
+ const limitPerMonitor = snapshotOnlyRequest ? 1 : 25;
+ const checksMap = await this.checksRepository.findLatestChecksByMonitorIds(
+ monitorsList.map((monitor) => monitor.id),
+ { limitPerMonitor }
+ );
+
+ const monitorsWithChecks = monitorsList.map((monitor: Monitor) => {
+ const rawChecks = checksMap[monitor.id] ?? [];
+ const isSnapshotType = snapshotOnlyRequest || snapshotTypes.includes(monitor.type);
+ const checks = isSnapshotType ? rawChecks.slice(0, 1) : NormalizeData(rawChecks, 10, 100);
+ return {
+ ...monitor,
+ checks,
+ };
+ });
+
+ return { count, monitors: monitorsWithChecks };
+ };
+
+ getAllGames = (): any => {
+ return this.games;
+ };
+
+ getGroupsByTeamId = async ({ teamId }: { teamId: string }): Promise => {
+ const groups = await this.db.monitorModule.getGroupsByTeamId({ teamId });
+ return groups;
+ };
+
+ editMonitor = async ({ teamId, monitorId, body }: { teamId: string; monitorId: string; body: any }): Promise => {
+ await this.verifyTeamAccess({ teamId, monitorId });
+ const editedMonitor = await this.db.monitorModule.editMonitor({ monitorId, body });
+ await this.jobQueue.updateJob(editedMonitor);
+ };
+
+ pauseMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => {
+ await this.verifyTeamAccess({ teamId, monitorId });
+ const monitor = await this.db.monitorModule.pauseMonitor({ monitorId });
+ monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor);
+ return monitor;
+ };
+
+ deleteMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => {
+ await this.verifyTeamAccess({ teamId, monitorId });
+ const monitor = await this.db.monitorModule.deleteMonitor({ teamId, monitorId });
+ await this.jobQueue.deleteJob(monitor);
+ await this.db.statusPageModule.deleteStatusPagesByMonitorId(monitor._id);
+ return monitor;
+ };
+
+ deleteAllMonitors = async ({ teamId }: { teamId: string }): Promise => {
+ const { monitors, deletedCount } = await this.monitorsRepository.deleteByTeamId(teamId);
+ await Promise.all(
+ monitors.map(async (monitor) => {
+ try {
+ await this.jobQueue.deleteJob(monitor);
+ await this.db.checkModule.deleteChecks(monitor.id);
+ await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor.id);
+ await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor.id);
+ } catch (error: any) {
+ this.logger.warn({
+ message: `Error deleting associated records for monitor ${monitor.id} with name ${monitor.name}`,
+ service: SERVICE_NAME,
+ method: "deleteAllMonitors",
+ stack: error.stack,
+ });
+ }
+ })
+ );
+ return deletedCount;
+ };
+
+ sendTestEmail = async ({ to }: { to: string }): Promise => {
+ const subject = this.stringService.testEmailSubject;
+ const context = { testName: "Monitoring System" };
+
+ const html = await this.emailService.buildEmail("testEmailTemplate", context);
+ const messageId = await this.emailService.sendEmail(to, subject, html);
+
+ if (!messageId) {
+ throw this.errorService.createServerError("Failed to send test email.");
+ }
+
+ return messageId;
+ };
+
+ exportMonitorsToCSV = async ({ teamId }: { teamId: string }): Promise => {
+ const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId });
+
+ if (!monitors || monitors.length === 0) {
+ throw this.errorService.createNotFoundError("No monitors to export");
+ }
+
+ const csvData = monitors?.filteredMonitors?.map((monitor: any) => ({
+ name: monitor.name,
+ description: monitor.description,
+ type: monitor.type,
+ url: monitor.url,
+ interval: monitor.interval,
+ port: monitor.port,
+ ignoreTlsErrors: monitor.ignoreTlsErrors,
+ isActive: monitor.isActive,
+ }));
+
+ const csv = this.papaparse.unparse(csvData);
+ return csv;
+ };
+ exportMonitorsToJSON = async ({ teamId }: { teamId: string }): Promise => {
+ const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId });
+
+ if (!monitors || monitors.length === 0) {
+ throw this.errorService.createNotFoundError("No monitors to export");
+ }
+
+ const json = monitors?.filteredMonitors
+ ?.map((monitor: any) => {
+ const initialType = monitor.type;
+ let parsedType;
+
+ if (initialType === "hardware") {
+ parsedType = "infrastructure";
+ } else if (initialType === "http") {
+ if (monitor.url.startsWith("https://")) {
+ parsedType = "https";
+ } else {
+ parsedType = "http";
+ }
+ } else if (initialType === "pagespeed") {
+ parsedType = initialType;
+ } else {
+ // Skip unsupported types
+ return;
+ }
+
+ return {
+ name: monitor.name,
+ url: monitor.url,
+ type: parsedType,
+ interval: monitor.interval,
+ n: monitor.statusWindowSize,
+ secret: monitor.secret,
+ };
+ })
+ .filter(Boolean);
+
+ return json;
+ };
+}
diff --git a/server/src/service/index.ts b/server/src/service/index.ts
new file mode 100644
index 000000000..4793f055b
--- /dev/null
+++ b/server/src/service/index.ts
@@ -0,0 +1 @@
+export * from "@/service/business/monitorService.js";
diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
index a3cdea8e7..a6637d4cc 100644
--- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
+++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
@@ -4,19 +4,20 @@ const SERVICE_NAME = "JobQueue";
class SuperSimpleQueue {
static SERVICE_NAME = SERVICE_NAME;
- constructor({ envSettings, db, logger, helper }) {
+ constructor({ envSettings, db, logger, helper, monitorsRepository }) {
this.envSettings = envSettings;
this.db = db;
this.logger = logger;
this.helper = helper;
+ this.monitorsRepository = monitorsRepository;
}
get serviceName() {
return SuperSimpleQueue.SERVICE_NAME;
}
- static async create({ envSettings, db, logger, helper }) {
- const instance = new SuperSimpleQueue({ envSettings, db, logger, helper });
+ static async create({ envSettings, db, logger, helper, monitorsRepository }) {
+ const instance = new SuperSimpleQueue({ envSettings, db, logger, helper, monitorsRepository });
await instance.init();
return instance;
}
@@ -33,11 +34,11 @@ class SuperSimpleQueue {
this.scheduler.start();
this.scheduler.addTemplate("monitor-job", this.helper.getMonitorJob());
- const monitors = await this.db.monitorModule.getAllMonitors();
+ const monitors = await this.monitorsRepository.findAll();
for (const monitor of monitors) {
const randomOffset = Math.floor(Math.random() * 100);
setTimeout(() => {
- this.addJob(monitor._id, monitor);
+ this.addJob(monitor.id, monitor);
}, randomOffset);
}
@@ -55,44 +56,44 @@ class SuperSimpleQueue {
addJob = async (monitorId, monitor) => {
this.scheduler.addJob({
- id: monitorId.toString(),
+ id: monitorId,
template: "monitor-job",
repeat: monitor.interval,
active: monitor.isActive,
- data: monitor.toObject(),
+ data: monitor,
});
};
deleteJob = async (monitor) => {
- this.scheduler.removeJob(monitor._id.toString());
+ this.scheduler.removeJob(monitor.id);
};
pauseJob = async (monitor) => {
- const result = this.scheduler.pauseJob(monitor._id.toString());
+ const result = this.scheduler.pauseJob(monitor.id);
if (result === false) {
throw new Error("Failed to resume monitor");
}
this.logger.debug({
- message: `Paused monitor ${monitor._id}`,
+ message: `Paused monitor ${monitor.id}`,
service: SERVICE_NAME,
method: "pauseJob",
});
};
resumeJob = async (monitor) => {
- const result = this.scheduler.resumeJob(monitor._id.toString());
+ const result = this.scheduler.resumeJob(monitor.id);
if (result === false) {
throw new Error("Failed to resume monitor");
}
this.logger.debug({
- message: `Resumed monitor ${monitor._id}`,
+ message: `Resumed monitor ${monitor.id}`,
service: SERVICE_NAME,
method: "resumeJob",
});
};
updateJob = async (monitor) => {
- this.scheduler.updateJob(monitor._id.toString(), { repeat: monitor.interval, data: monitor.toObject() });
+ this.scheduler.updateJob(monitor.id, { repeat: monitor.interval, data: monitor });
};
shutdown = async () => {
diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
index 2833a59f5..3cd6b37a8 100644
--- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
+++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
@@ -27,7 +27,7 @@ class SuperSimpleQueueHelper {
getMonitorJob = () => {
return async (monitor) => {
try {
- const monitorId = monitor._id;
+ const monitorId = monitor.id;
const teamId = monitor.teamId;
if (!monitorId) {
throw new Error("No monitor id");
@@ -62,7 +62,7 @@ class SuperSimpleQueueHelper {
message: error.message,
service: SERVICE_NAME,
method: "getMonitorJob",
- details: `Error sending notifications for job ${monitor._id}: ${error.message}`,
+ details: `Error sending notifications for job ${monitor.id}: ${error.message}`,
stack: error.stack,
});
});
diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js
index d364f373e..2375e7233 100644
--- a/server/src/service/infrastructure/networkService.js
+++ b/server/src/service/infrastructure/networkService.js
@@ -95,7 +95,7 @@ class NetworkService {
}
const pingResponse = {
- monitorId: monitor._id,
+ monitorId: monitor.id,
type: "ping",
status: response.alive,
code: 200,
@@ -120,9 +120,9 @@ class NetworkService {
}
async requestHttp(monitor) {
- const { url, secret, _id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
+ const { url, secret, id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
const httpResponse = {
- monitorId: _id,
+ monitorId: id,
teamId: teamId,
type,
};
@@ -298,7 +298,7 @@ class NetworkService {
});
const dockerResponse = {
- monitorId: monitor._id,
+ monitorId: monitor.id,
type: monitor.type,
};
@@ -416,7 +416,7 @@ class NetworkService {
code: 200,
status: response.success,
message: this.stringService.portSuccess,
- monitorId: monitor._id,
+ monitorId: monitor.id,
type: monitor.type,
responseTime: responseTime,
};
@@ -444,7 +444,7 @@ class NetworkService {
code: 200,
status: true,
message: "Success",
- monitorId: monitor._id,
+ monitorId: monitor.id,
type: "game",
};
diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js
index 3aa1ced52..e849bbb83 100755
--- a/server/src/service/infrastructure/statusService.js
+++ b/server/src/service/infrastructure/statusService.js
@@ -7,14 +7,19 @@ class StatusService {
/**
* @param {{
+ * db: any
+ * logger: any
* buffer: import("./bufferService.js").BufferService
* incidentService: import("../business/incidentService.js").IncidentService
+ * monitorsRepository: any
* }}
- */ constructor({ db, logger, buffer, incidentService }) {
+ */
+ constructor({ db, logger, buffer, incidentService, monitorsRepository }) {
this.db = db;
this.logger = logger;
this.buffer = buffer;
this.incidentService = incidentService;
+ this.monitorsRepository = monitorsRepository;
}
get serviceName() {
@@ -23,7 +28,7 @@ class StatusService {
async updateRunningStats({ monitor, networkResponse }) {
try {
- const monitorId = monitor._id;
+ const monitorId = monitor.id;
const { responseTime, status } = networkResponse;
// Get stats
let stats = await MonitorStats.findOne({ monitorId });
@@ -124,7 +129,7 @@ class StatusService {
service: this.SERVICE_NAME,
method: "handleIncidentForCheck",
message: `Failed to save check immediately for ${errorContext}: ${checkError.message}`,
- monitorId: monitor._id,
+ monitorId: monitor.id,
stack: checkError.stack,
});
savedCheck = null;
@@ -139,7 +144,7 @@ class StatusService {
service: this.SERVICE_NAME,
method: "handleIncidentForCheck",
message: `Failed to add incident to buffer for ${errorContext}: ${incidentError.message}`,
- monitorId: monitor._id,
+ monitorId: monitor.id,
action,
stack: incidentError.stack,
});
@@ -150,7 +155,7 @@ class StatusService {
service: this.SERVICE_NAME,
method: "handleIncidentForCheck",
message: `Error in ${errorContext}: ${error.message}`,
- monitorId: monitor?._id,
+ monitorId: monitor?.id,
stack: error.stack,
});
}
@@ -172,7 +177,8 @@ class StatusService {
await this.insertCheck(check);
try {
const { monitorId, status, code } = networkResponse;
- const monitor = await this.db.monitorModule.getMonitorById(monitorId);
+
+ const monitor = await this.monitorsRepository.findById(monitorId);
// Update running stats
this.updateRunningStats({ monitor, networkResponse });
@@ -198,9 +204,9 @@ class StatusService {
// Return early if not enough data points
if (monitor.statusWindow.length < monitor.statusWindowSize) {
- await monitor.save();
+ const updated = await this.monitorsRepository.update(monitor.id, monitor);
return {
- monitor,
+ monitor: updated,
statusChanged: false,
prevStatus,
code,
@@ -240,14 +246,14 @@ class StatusService {
if (monitor.status === false && !statusChanged) {
try {
- const lastManuallyResolvedIncident = await this.db.incidentModule.getLastManuallyResolvedIncident(monitor._id);
+ const lastManuallyResolvedIncident = await this.db.incidentModule.getLastManuallyResolvedIncident(monitor.id);
let calculatedFailureRate = failureRate;
if (lastManuallyResolvedIncident && lastManuallyResolvedIncident.endTime) {
try {
const checksAfterResolution = await Check.find({
- monitorId: monitor._id,
+ monitorId: monitor.id,
createdAt: { $gt: lastManuallyResolvedIncident.endTime },
})
.sort({ createdAt: 1 })
@@ -267,7 +273,7 @@ class StatusService {
service: this.SERVICE_NAME,
method: "updateStatus",
message: `Failed to query checks after manual resolution: ${checkQueryError.message}`,
- monitorId: monitor._id,
+ monitorId: monitor.id,
stack: checkQueryError.stack,
});
}
@@ -281,17 +287,17 @@ class StatusService {
service: this.SERVICE_NAME,
method: "updateStatus",
message: `Error handling threshold check without status change: ${error.message}`,
- monitorId: monitor._id,
+ monitorId: monitor.id,
stack: error.stack,
});
}
}
monitor.status = newStatus;
- await monitor.save();
+ const updated = await this.monitorsRepository.update(monitor.id, monitor);
return {
- monitor,
+ monitor: updated,
statusChanged,
prevStatus,
code,
diff --git a/server/src/types/index.ts b/server/src/types/index.ts
index 92bcb69b0..2367f1c1c 100644
--- a/server/src/types/index.ts
+++ b/server/src/types/index.ts
@@ -1,2 +1,3 @@
export * from "@/types/check.js";
export * from "@/types/monitor.js";
+export * from "@/types/monitorStats.js";
diff --git a/server/src/types/monitorStats.ts b/server/src/types/monitorStats.ts
new file mode 100644
index 000000000..8f9603529
--- /dev/null
+++ b/server/src/types/monitorStats.ts
@@ -0,0 +1,14 @@
+export interface MonitorStats {
+ id: string;
+ monitorId: string;
+ avgResponseTime: number;
+ totalChecks: number;
+ totalUpChecks: number;
+ totalDownChecks: number;
+ uptimePercentage: number;
+ lastCheckTimestamp: number;
+ lastResponseTime: number;
+ timeOfLastFailure?: number;
+ createdAt: string;
+ updatedAt: string;
+}
diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js
index b3342de83..c09ac88b9 100755
--- a/server/src/validation/joi.js
+++ b/server/src/validation/joi.js
@@ -120,30 +120,30 @@ const getMonitorByIdQueryValidation = joi.object({
const getMonitorsByTeamIdParamValidation = joi.object({});
const getMonitorsByTeamIdQueryValidation = joi.object({
- limit: joi.number(),
type: joi
.alternatives()
.try(
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game"),
joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game"))
),
- page: joi.number(),
- rowsPerPage: joi.number(),
- filter: joi.string(),
- field: joi.string(),
- order: joi.string().valid("asc", "desc"),
+ filter: joi.string().allow("", null),
});
-const getMonitorStatsByIdParamValidation = joi.object({
- monitorId: joi.string().required(),
-});
-const getMonitorStatsByIdQueryValidation = joi.object({
- status: joi.string(),
- limit: joi.number(),
- sortOrder: joi.string().valid("asc", "desc"),
- dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
- numToDisplay: joi.number(),
- normalize: joi.boolean(),
+const getMonitorsWithChecksQueryValidation = joi.object({
+ limit: joi.number().integer().min(1).max(100).optional(),
+ page: joi.number().integer().min(0).optional(),
+ rowsPerPage: joi.number().integer().min(1).max(100).optional(),
+ filter: joi.string().allow("", null).optional(),
+ field: joi.string().optional(),
+ order: joi.string().valid("asc", "desc").optional(),
+ type: joi
+ .alternatives()
+ .try(
+ joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game"),
+ joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game"))
+ )
+ .optional(),
+ explain: joi.boolean().optional(),
});
const getCertificateParamValidation = joi.object({
@@ -725,8 +725,7 @@ export {
getMonitorByIdQueryValidation,
getMonitorsByTeamIdParamValidation,
getMonitorsByTeamIdQueryValidation,
- getMonitorStatsByIdParamValidation,
- getMonitorStatsByIdQueryValidation,
+ getMonitorsWithChecksQueryValidation,
getHardwareDetailsByIdParamValidation,
getHardwareDetailsByIdQueryValidation,
getCertificateParamValidation,
diff --git a/server/test/monitorService.test.ts b/server/test/monitorService.test.ts
new file mode 100644
index 000000000..7a3bbe12f
--- /dev/null
+++ b/server/test/monitorService.test.ts
@@ -0,0 +1,217 @@
+import { jest } from "@jest/globals";
+import { MonitorService } from "../src/service/business/monitorService.ts";
+import type { IMonitorsRepository, IChecksRepository } from "../src/repositories/index.ts";
+
+const createMonitorsRepositoryMock = () =>
+ ({
+ findMonitorCountByTeamIdAndType: jest.fn(),
+ findByTeamId: jest.fn(),
+ findById: jest.fn(),
+ create: jest.fn(),
+ createBulkMonitors: jest.fn(),
+ deleteByTeamId: jest.fn(),
+ }) as unknown as IMonitorsRepository;
+
+const createChecksRepositoryMock = () =>
+ ({
+ findLatestChecksByMonitorIds: jest.fn(),
+ findDateRangeChecksByMonitor: jest.fn(),
+ }) as unknown as IChecksRepository;
+
+const createService = ({
+ monitorsRepository = createMonitorsRepositoryMock(),
+ checksRepository = createChecksRepositoryMock(),
+ monitorStatsRepository = { findByMonitorId: jest.fn() },
+ monitorModuleOverrides = {},
+}: {
+ monitorsRepository?: IMonitorsRepository;
+ checksRepository?: IChecksRepository;
+ monitorStatsRepository?: { findByMonitorId: jest.Mock };
+ monitorModuleOverrides?: Record;
+} = {}) => {
+ const monitorModule = {
+ getMonitorById: jest.fn().mockResolvedValue({ teamId: { equals: () => true } }),
+ getMonitorStatsById: jest.fn().mockResolvedValue({ latest: {} }),
+ getMonitorsByTeamId: jest.fn().mockResolvedValue([]),
+ getMonitorsAndSummaryByTeamId: jest.fn().mockResolvedValue({ monitors: [], summary: {} }),
+ ...monitorModuleOverrides,
+ };
+
+ return new MonitorService({
+ db: {
+ monitorModule,
+ statusPageModule: { deleteStatusPagesByMonitorId: jest.fn() },
+ checkModule: { deleteChecks: jest.fn() },
+ pageSpeedCheckModule: { deletePageSpeedChecksByMonitorId: jest.fn() },
+ notificationsModule: { deleteNotificationsByMonitorId: jest.fn() },
+ },
+ jobQueue: {
+ addJob: jest.fn(),
+ updateJob: jest.fn(),
+ resumeJob: jest.fn(),
+ pauseJob: jest.fn(),
+ deleteJob: jest.fn(),
+ },
+ stringService: {},
+ emailService: { buildEmail: jest.fn(), sendEmail: jest.fn() },
+ papaparse: { parse: jest.fn(), unparse: jest.fn() },
+ logger: { info: jest.fn(), error: jest.fn(), warn: jest.fn() },
+ errorService: {
+ createAuthorizationError: jest.fn(() => new Error("unauthorized")),
+ createServerError: jest.fn(() => new Error("server")),
+ createBadRequestError: jest.fn(() => new Error("bad request")),
+ createNotFoundError: jest.fn(() => new Error("not found")),
+ },
+ games: [],
+ monitorsRepository,
+ checksRepository,
+ monitorStatsRepository,
+ });
+};
+
+describe("MonitorService", () => {
+ describe("getMonitorsWithChecksByTeamId", () => {
+ it("returns monitors enriched with normalized checks", async () => {
+ const monitorsRepository = createMonitorsRepositoryMock();
+ (monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(2);
+ (monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([
+ { id: "m1", name: "Monitor 1", interval: 60000 },
+ { id: "m2", name: "Monitor 2", interval: 60000 },
+ ]);
+
+ const checksRepository = createChecksRepositoryMock();
+ (checksRepository.findLatestChecksByMonitorIds as jest.Mock).mockResolvedValue({
+ m1: [
+ { responseTime: 10, status: true, message: "OK" },
+ { responseTime: 20, status: true, message: "OK" },
+ ],
+ m2: [{ responseTime: 50, status: true, message: "OK" }],
+ });
+
+ const service = createService({ monitorsRepository, checksRepository });
+ const result = await service.getMonitorsWithChecksByTeamId({ teamId: "team" });
+
+ expect(result).toMatchObject({ count: 2 });
+ expect(result.monitors).toHaveLength(2);
+ expect(result.monitors[0]).toHaveProperty("checks");
+ expect(result.monitors[0].checks.length).toBeGreaterThan(0);
+ expect(result.monitors[0].checks[0]).toEqual(
+ expect.objectContaining({
+ responseTime: expect.any(Number),
+ status: expect.any(Boolean),
+ message: expect.any(String),
+ })
+ );
+ });
+ });
+
+ describe("getMonitorsByTeamId", () => {
+ it("returns monitors array from db module", async () => {
+ const monitorsPayload = [
+ { id: "m1", name: "Monitor 1" },
+ { id: "m2", name: "Monitor 2" },
+ ];
+ const monitorModuleOverrides = {
+ getMonitorsByTeamId: jest.fn().mockResolvedValue(monitorsPayload),
+ };
+ const service = createService({ monitorModuleOverrides });
+ const result = await service.getMonitorsByTeamId({ teamId: "team" } as any);
+ expect(result).toHaveLength(2);
+ expect(result[0]).toHaveProperty("id", "m1");
+ });
+ });
+
+ describe("getMonitorsAndSummaryByTeamId", () => {
+ it("returns monitors with summary block", async () => {
+ const monitorModuleOverrides = {
+ getMonitorsAndSummaryByTeamId: jest.fn().mockResolvedValue({ monitors: [{ id: "m1" }], summary: { total: 1, uptime: 0.99 } }),
+ };
+ const service = createService({ monitorModuleOverrides });
+ const result = await service.getMonitorsAndSummaryByTeamId({ teamId: "team" });
+ expect(result).toEqual({ monitors: [{ id: "m1" }], summary: { total: 1, uptime: 0.99 } });
+ });
+ });
+
+ describe("getUptimeDetailsById", () => {
+ it("returns monitorData and monitorStats with expected shape", async () => {
+ const TEAM_ID = "team";
+ const monitor = {
+ id: "monitor-1",
+ teamId: TEAM_ID,
+ name: "Hardware monitor",
+ interval: 60000,
+ statusWindow: [],
+ statusWindowSize: 5,
+ statusWindowThreshold: 60,
+ type: "http",
+ ignoreTlsErrors: false,
+ url: "https://example.com",
+ isActive: true,
+ alertThreshold: 5,
+ cpuAlertThreshold: 5,
+ memoryAlertThreshold: 5,
+ diskAlertThreshold: 5,
+ tempAlertThreshold: 5,
+ selectedDisks: [],
+ notifications: [],
+ group: null,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+
+ const monitorsRepository = createMonitorsRepositoryMock();
+ (monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
+ const checksRepository = createChecksRepositoryMock();
+ (checksRepository.findDateRangeChecksByMonitor as jest.Mock).mockResolvedValue({
+ monitorType: "uptime",
+ groupedChecks: [{ _id: "2024-01-01", avgResponseTime: 100, totalChecks: 2 }],
+ groupedUpChecks: [{ _id: "2024-01-01", totalChecks: 2, avgResponseTime: 90 }],
+ groupedDownChecks: [{ _id: "2024-01-01", totalChecks: 0, avgResponseTime: 0 }],
+ uptimePercentage: 0.99,
+ avgResponseTime: 95,
+ });
+
+ const monitorStatsRepository = {
+ findByMonitorId: jest.fn().mockResolvedValue({
+ id: "stats-1",
+ monitorId: monitor.id,
+ avgResponseTime: 90,
+ totalChecks: 10,
+ totalUpChecks: 9,
+ totalDownChecks: 1,
+ uptimePercentage: 0.9,
+ lastCheckTimestamp: 123456789,
+ lastResponseTime: 80,
+ timeOfLastFailure: 123456700,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ }),
+ } as any;
+ const monitorModuleOverrides = {
+ getMonitorById: jest.fn().mockResolvedValue({ teamId: { equals: (value: string) => value === TEAM_ID } }),
+ };
+
+ const service = createService({ monitorsRepository, checksRepository, monitorModuleOverrides, monitorStatsRepository });
+ const result = await service.getUptimeDetailsById({ teamId: TEAM_ID, monitorId: "monitor-1", dateRange: "recent" });
+
+ expect(result).toHaveProperty("monitorData");
+ expect(result.monitorData.monitor).toMatchObject({ id: monitor.id, name: monitor.name });
+ expect(result.monitorData.groupedChecks[0]).toEqual(
+ expect.objectContaining({
+ _id: expect.any(String),
+ avgResponseTime: expect.any(Number),
+ totalChecks: expect.any(Number),
+ })
+ );
+ expect(result.monitorStats).toEqual(
+ expect.objectContaining({
+ monitorId: monitor.id,
+ avgResponseTime: 90,
+ totalChecks: 10,
+ totalUpChecks: 9,
+ totalDownChecks: 1,
+ })
+ );
+ });
+ });
+});
diff --git a/server/tsconfig.jest.json b/server/tsconfig.jest.json
new file mode 100644
index 000000000..928b0977c
--- /dev/null
+++ b/server/tsconfig.jest.json
@@ -0,0 +1,9 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "rootDir": ".",
+ "types": ["jest"],
+ "noEmit": true
+ },
+ "include": ["src", "test"]
+}
diff --git a/server/tsconfig.json b/server/tsconfig.json
index f0b8e7223..cbb874656 100644
--- a/server/tsconfig.json
+++ b/server/tsconfig.json
@@ -12,7 +12,8 @@
"module": "nodenext",
"moduleResolution": "nodenext",
"skipLibCheck": true,
- "noUncheckedIndexedAccess": true
+ "noUncheckedIndexedAccess": true,
+ "isolatedModules": true
},
"include": ["src"],
"exclude": ["tests", "dist", "node_modules"]