diff --git a/.gitignore b/.gitignore index ec31c1d..d619766 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ out dist tmp lib +data .vscode .env @@ -22,4 +23,5 @@ src/update.ini !eslint.config.js coverage -*.tsbuildinfo \ No newline at end of file +*.tsbuildinfo +.DS_Store \ No newline at end of file diff --git a/apps/api-master/environment.d.ts b/apps/api-master/environment.d.ts new file mode 100644 index 0000000..bc62d23 --- /dev/null +++ b/apps/api-master/environment.d.ts @@ -0,0 +1,10 @@ +declare module "ip-location-api" { + export function lookup(ip: string): Promise<{ + latitude: number; + longitude: number; + country: string; + } | null>; + + export function updateDb(options: { fields?: string[]; dataDir?: string; tmpDataDir?: string }): Promise; + export function reload(options: { fields?: string[]; dataDir?: string; tmpDataDir?: string }): Promise; +} diff --git a/apps/api-master/package.json b/apps/api-master/package.json index 4568f4d..95287e4 100644 --- a/apps/api-master/package.json +++ b/apps/api-master/package.json @@ -22,6 +22,7 @@ "cron": "^3.1.7", "debug": "^4.3.6", "ioredis": "^5.3.2", + "ip-location-api": "^1.0.0", "ky": "^1.7.2", "p-limit": "^6.1.0" }, diff --git a/apps/api-master/src/functions/clearOldSessions.ts b/apps/api-master/src/functions/clearOldSessions.ts index 349e6a4..e946848 100644 --- a/apps/api-master/src/functions/clearOldSessions.ts +++ b/apps/api-master/src/functions/clearOldSessions.ts @@ -11,6 +11,7 @@ export async function clearOldSessions() { inProgress = true; const now = Date.now(); + const pattern = "pmd-api.sessions.*"; let cursor = "0"; let totalSessions = 0; let cleared = 0; @@ -22,21 +23,15 @@ export async function clearOldSessions() { const limit = pLimit(100); // Create a limit of 100 concurrent operations do { - const [nextCursor, result] = await redis.hscan("pmd-api.sessions", cursor, "COUNT", batchSize); - cursor = nextCursor; - totalSessions += result.length / 2; + const [newCursor, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 1000); //* Use SCAN with COUNT for memory efficiency - const deletePromises = []; + cursor = newCursor; + totalSessions += keys.length; - for (let i = 0; i < result.length; i += 2) { - const key = result[i]; - const value = result[i + 1]; + const deletePromises: Promise[] = []; - if (!key || !value) { - continue; - } - - const session = JSON.parse(value) as { + for (const key of keys) { + const session = await redis.hgetall(key) as unknown as { token: string; session: string; lastUpdated: number; @@ -57,13 +52,13 @@ export async function clearOldSessions() { }); if (keysToDelete.length >= batchSize) { - await redis.hdel("pmd-api.sessions", ...keysToDelete); + await redis.del(...keysToDelete); keysToDelete = []; } } while (cursor !== "0"); if (keysToDelete.length > 0) { - await redis.hdel("pmd-api.sessions", ...keysToDelete); + await redis.del(...keysToDelete); } if (totalSessions === 0) { diff --git a/apps/api-master/src/functions/clearableGaugeMetric.ts b/apps/api-master/src/functions/clearableGaugeMetric.ts new file mode 100644 index 0000000..5b33e44 --- /dev/null +++ b/apps/api-master/src/functions/clearableGaugeMetric.ts @@ -0,0 +1,83 @@ +import type { ServerResponse } from "node:http"; +import type { Attributes } from "@opentelemetry/api"; +import { ValueType, diag } from "@opentelemetry/api"; +import type { PrometheusExporter, PrometheusSerializer } from "@opentelemetry/exporter-prometheus"; +import { AggregationTemporality, DataPointType, type GaugeMetricData, InstrumentType } from "@opentelemetry/sdk-metrics"; + +const registeredMetrics = new Map(); + +//* Custom gauge metric class +export class ClearableGaugeMetric { + private data: Map; + private name: string; + private description: string; + + constructor(name: string, description: string) { + this.data = new Map(); + this.name = name; + this.description = description; + registeredMetrics.set(name, this); + } + + set(key: string, value: number, attributes: Attributes) { + this.data.set(key, { value, attributes }); + } + + clear() { + this.data.clear(); + } + + toMetricData(): GaugeMetricData { + return { + descriptor: { + name: this.name, + description: this.description, + unit: "", + type: InstrumentType.GAUGE, + valueType: ValueType.INT, + }, + dataPointType: DataPointType.GAUGE, + dataPoints: Array.from(this.data.values()).map(({ value, attributes }) => ({ + value, + attributes, + startTime: [0, 0], + endTime: [0, 0], + })), + aggregationTemporality: AggregationTemporality.CUMULATIVE, + }; + } + + get hasData() { + return this.data.size > 0; + } +} + +export function updatePrometheusMetrics(prometheusExporter: PrometheusExporter) { + // @ts-expect-error We are modifying a private method + prometheusExporter._exportMetrics = function (this: PrometheusExporter, response: ServerResponse) { + response.statusCode = 200; + response.setHeader("content-type", "text/plain"); + this.collect().then( + (collectionResult) => { + const { resourceMetrics, errors } = collectionResult; + if (errors.length) { + diag.error( + "PrometheusExporter: metrics collection errors", + ...errors, + ); + } + + for (const metric of registeredMetrics.values()) { + if (metric.hasData) { + resourceMetrics.scopeMetrics[0]!.metrics.push(metric.toMetricData()); + } + } + + response.end((this as unknown as { _serializer: PrometheusSerializer })._serializer.serialize(resourceMetrics)); + }, + (err) => { + response.end(`# failed to export metrics: ${err}`); + }, + ); + }.bind(prometheusExporter); +} diff --git a/apps/api-master/src/functions/lookupIp.ts b/apps/api-master/src/functions/lookupIp.ts new file mode 100644 index 0000000..987baae --- /dev/null +++ b/apps/api-master/src/functions/lookupIp.ts @@ -0,0 +1,46 @@ +import { join } from "node:path"; +import process from "node:process"; +import { lookup, reload, updateDb } from "ip-location-api"; +import { mainLog } from "../index.js"; + +const fields = ["latitude", "longitude", "country"]; + +const dataDir = join(process.cwd(), "data"); +const tmpDataDir = join(process.cwd(), "tmp"); + +let initialized = false; + +export async function lookupIp(ip: string): Promise<{ latitude: number; longitude: number; country: string } | undefined> { + if (!initialized) { + reloadIpLocationApi(); + return undefined; + } + try { + return await lookup(ip) ?? undefined; + } + catch { + return undefined; + } +} + +let reloading: Promise | undefined; +let log: debug.Debugger | undefined; + +export async function reloadIpLocationApi() { + log ??= mainLog.extend("IP-Location-API"); + + if (reloading) + return reloading; + + reloading = new Promise((resolve, reject) => { + log?.("Reloading IP location API"); + updateDb({ fields, dataDir, tmpDataDir }).then(async () => { + await reload({ fields, dataDir, tmpDataDir }); + log?.("IP location API reloaded"); + initialized = true; + reloading = undefined; + resolve(); + }).catch(reject); + }); + return reloading; +} diff --git a/apps/api-master/src/functions/setCounter.ts b/apps/api-master/src/functions/setSessionCounter.ts similarity index 58% rename from apps/api-master/src/functions/setCounter.ts rename to apps/api-master/src/functions/setSessionCounter.ts index c1baccf..84231c0 100644 --- a/apps/api-master/src/functions/setCounter.ts +++ b/apps/api-master/src/functions/setSessionCounter.ts @@ -1,13 +1,13 @@ import { redis } from "../index.js"; -import { counter } from "../tracing.js"; +import { activeSessionsCounter } from "../tracing.js"; let activeActivities = 0; -counter.add(0); -export async function setCounter() { +activeSessionsCounter.add(0); +export async function setSessionCounter() { const length = await redis.hlen("pmd-api.sessions"); if (length === activeActivities) return; const diff = length - activeActivities; activeActivities = length; - counter.add(diff); + activeSessionsCounter.add(diff); } diff --git a/apps/api-master/src/functions/updateActivePresenceGauge.ts b/apps/api-master/src/functions/updateActivePresenceGauge.ts index c5ce4fa..8dde664 100644 --- a/apps/api-master/src/functions/updateActivePresenceGauge.ts +++ b/apps/api-master/src/functions/updateActivePresenceGauge.ts @@ -1,14 +1,16 @@ import { redis } from "../index.js"; -import { activePresenceGauge } from "../tracing.js"; - -//* Track previously recorded services -const previousServices = new Set(); +import { activeIpsGauge, activePresenceGauge } from "../tracing.js"; +import { lookupIp } from "./lookupIp.js"; //* Function to update the gauge with per-service counts export async function updateActivePresenceGauge() { const pattern = "pmd-api.heartbeatUpdates.*"; let cursor: string = "0"; const serviceCounts = new Map(); + const ips = new Map(); do { const [newCursor, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 1000); //* Use SCAN with COUNT for memory efficiency @@ -17,23 +19,46 @@ export async function updateActivePresenceGauge() { const hash = await redis.hgetall(key); const service = hash.service; const version = hash.version; //* Get version from hash - serviceCounts.set(`${service}:${version}`, (serviceCounts.get(`${service}:${version}`) || 0) + 1); + const ip = hash.ip_address; + if (service && version) { + serviceCounts.set(`${service}:${version}`, (serviceCounts.get(`${service}:${version}`) || 0) + 1); + } + else { + serviceCounts.set("none", (serviceCounts.get("none") || 0) + 1); + } + if (ip) { + const presenceName = service && version ? `${service}:${version}` : undefined; + const ipData = ips.get(ip) || { presences: [], sessions: 0 }; + ipData.presences = [...new Set([...ipData.presences, presenceName].filter(Boolean) as string[])]; + ipData.sessions++; + ips.set(ip, ipData); + } } } while (cursor !== "0"); - // Set current counts and remove from previousServices - serviceCounts.forEach((count, serviceVersion) => { - const [presence_name, version] = serviceVersion.split(":"); - activePresenceGauge.record(count, { presence_name, version }); - previousServices.delete(serviceVersion); - }); + // Clear previous data + activePresenceGauge.clear(); + activeIpsGauge.clear(); - // Set gauge to 0 for services that are no longer active - previousServices.forEach((serviceVersion) => { + // Set new data + for (const [serviceVersion, count] of serviceCounts.entries()) { const [presence_name, version] = serviceVersion.split(":"); - activePresenceGauge.record(0, { presence_name, version }); - }); + activePresenceGauge.set(serviceVersion, count, { + presence_name, + version, + }); + } - // Update the set of previous services - serviceCounts.forEach((_, serviceVersion) => previousServices.add(serviceVersion)); + await Promise.all(Array.from(ips).map(async ([ip, { presences, sessions }]) => { + const parsed = await lookupIp(ip); + if (parsed) { + activeIpsGauge.set(ip, sessions, { + country: parsed.country, + ip, + latitude: parsed.latitude, + longitude: parsed.longitude, + presence_names: presences, + }); + } + })); } diff --git a/apps/api-master/src/index.ts b/apps/api-master/src/index.ts index 69aca67..18ab48d 100644 --- a/apps/api-master/src/index.ts +++ b/apps/api-master/src/index.ts @@ -3,14 +3,17 @@ import { CronJob } from "cron"; import debug from "debug"; import { clearOldSessions } from "./functions/clearOldSessions.js"; import createRedis from "./functions/createRedis.js"; -import { setCounter } from "./functions/setCounter.js"; +import { setSessionCounter } from "./functions/setSessionCounter.js"; import "./tracing.js"; -import { updateActivePresenceGauge } from "./functions/updateActivePresenceGauge.js"; //* Added import +import { updateActivePresenceGauge } from "./functions/updateActivePresenceGauge.js"; +import { reloadIpLocationApi } from "./functions/lookupIp.js"; export const redis = createRedis(); export const mainLog = debug("api-master"); +reloadIpLocationApi(); + debug("Starting cron jobs"); void new CronJob( @@ -27,7 +30,7 @@ void new CronJob( // Every second "* * * * * *", () => { - setCounter(); + setSessionCounter(); }, undefined, true, @@ -42,3 +45,13 @@ void new CronJob( undefined, true, ); + +void new CronJob( + // Every day at 9am + "0 9 * * *", + () => { + reloadIpLocationApi(); + }, + undefined, + true, +); diff --git a/apps/api-master/src/tracing.ts b/apps/api-master/src/tracing.ts index 37558b3..da5897d 100644 --- a/apps/api-master/src/tracing.ts +++ b/apps/api-master/src/tracing.ts @@ -1,6 +1,7 @@ import { ValueType } from "@opentelemetry/api"; import { PrometheusExporter } from "@opentelemetry/exporter-prometheus"; import { MeterProvider } from "@opentelemetry/sdk-metrics"; +import { ClearableGaugeMetric, updatePrometheusMetrics } from "./functions/clearableGaugeMetric.js"; const prometheusExporter = new PrometheusExporter(); @@ -10,15 +11,21 @@ const provider = new MeterProvider({ const meter = provider.getMeter("nice"); -export const counter = meter.createUpDownCounter("active_activites", { - description: "Number of active activities", +export const activeSessionsCounter = meter.createUpDownCounter("active_sessions", { + description: "Number of active sessions", valueType: ValueType.INT, }); -// * Replace Observable Gauge with regular Gauge -export const activePresenceGauge = meter.createGauge("active_presence_names", { - description: "Number of active presence names per service", - valueType: ValueType.INT, -}); +export const activePresenceGauge = new ClearableGaugeMetric( + "active_presences", + "Per presence name+version, active number of users", +); + +export const activeIpsGauge = new ClearableGaugeMetric( + "active_ips", + "Per ip, list of presences and the number of sessions", +); + +updatePrometheusMetrics(prometheusExporter); prometheusExporter.startServer(); diff --git a/apps/api-master/tsconfig.app.json b/apps/api-master/tsconfig.app.json index e89156e..6092bec 100644 --- a/apps/api-master/tsconfig.app.json +++ b/apps/api-master/tsconfig.app.json @@ -3,6 +3,7 @@ "compilerOptions": { "composite": true, "rootDir": "src", + "types": ["./environment.d.ts"], "outDir": "dist" }, "include": ["src/**/*"] diff --git a/apps/api-worker/src/functions/createServer.ts b/apps/api-worker/src/functions/createServer.ts index c56ba06..f8cae97 100644 --- a/apps/api-worker/src/functions/createServer.ts +++ b/apps/api-worker/src/functions/createServer.ts @@ -1,6 +1,5 @@ import { readFile } from "node:fs/promises"; import { resolve } from "node:path"; -import { useSentry } from "@envelop/sentry"; import { maxAliasesPlugin } from "@escape.tech/graphql-armor-max-aliases"; import { maxDepthPlugin } from "@escape.tech/graphql-armor-max-depth"; import { maxDirectivesPlugin } from "@escape.tech/graphql-armor-max-directives"; diff --git a/apps/api-worker/src/graphql/resolvers/v5/Mutation/addScience.ts b/apps/api-worker/src/graphql/resolvers/v5/Mutation/addScience.ts index 51fd721..51be9d3 100644 --- a/apps/api-worker/src/graphql/resolvers/v5/Mutation/addScience.ts +++ b/apps/api-worker/src/graphql/resolvers/v5/Mutation/addScience.ts @@ -1,4 +1,5 @@ import { type } from "arktype"; +import { GraphQLError } from "graphql"; import { redis } from "../../../../functions/createServer.js"; import type { MutationResolvers } from "../../../../generated/graphql-v5.js"; @@ -15,7 +16,7 @@ const mutation: MutationResolvers["addScience"] = async (_parent, input) => { const out = addScienceSchema(input); if (out instanceof type.errors) - throw new Error(out.summary); + throw new GraphQLError(out.summary); await redis.hset( "pmd-api.scienceUpdates", diff --git a/apps/api-worker/src/graphql/resolvers/v5/Mutation/heartbeat.ts b/apps/api-worker/src/graphql/resolvers/v5/Mutation/heartbeat.ts index a77bd85..5703a8b 100644 --- a/apps/api-worker/src/graphql/resolvers/v5/Mutation/heartbeat.ts +++ b/apps/api-worker/src/graphql/resolvers/v5/Mutation/heartbeat.ts @@ -1,16 +1,17 @@ import { type } from "arktype"; +import { GraphQLError } from "graphql"; import type { MutationResolvers } from "../../../../generated/graphql-v5.js"; import { redis } from "../../../../functions/createServer.js"; const heartbeatSchema = type({ - identifier: "string.uuid & string.lower", - presence: { + "identifier": "string.uuid & string.lower", + "presence?": { service: "string.trim", version: "string.semver", language: "string.trim", since: "number.epoch", }, - extension: { + "extension": { "version": "string.semver", "language": "string.trim", "connected?": { @@ -20,23 +21,27 @@ const heartbeatSchema = type({ }, }); -const mutation: MutationResolvers["heartbeat"] = async (_parent, input) => { +const mutation: MutationResolvers["heartbeat"] = async (_parent, input, context) => { const out = heartbeatSchema(input); if (out instanceof type.errors) - throw new Error(out.summary); + throw new GraphQLError(out.summary); + + //* Get the user's IP address from Cloudflare headers or fallback to the request IP + const userIp = context.request.headers.get("cf-connecting-ip") || context.request.ip; // * Use Redis Hash with 'service' in the key to store heartbeat data const redisKey = `pmd-api.heartbeatUpdates.${out.identifier}`; await redis.hset(redisKey, { - service: out.presence.service, - version: out.presence.version, - language: out.presence.language, - since: out.presence.since.toString(), + service: out.presence?.service, + version: out.presence?.version, + language: out.presence?.language, + since: out.presence?.since.toString(), extension_version: out.extension.version, extension_language: out.extension.language, extension_connected_app: out.extension.connected?.app?.toString(), extension_connected_discord: out.extension.connected?.discord?.toString(), + ip_address: userIp, }); await redis.expire(redisKey, 300); diff --git a/apps/api-worker/src/routes/sessionKeepAlive.ts b/apps/api-worker/src/routes/sessionKeepAlive.ts index 790145b..c57131e 100644 --- a/apps/api-worker/src/routes/sessionKeepAlive.ts +++ b/apps/api-worker/src/routes/sessionKeepAlive.ts @@ -31,17 +31,15 @@ export async function sessionKeepAlive(request: FastifyRequest, reply: FastifyRe if (!await isTokenValid(out.token)) return reply.status(400).send({ code: "INVALID_TOKEN", message: "The token is invalid" }); - await redis.hset( - "pmd-api.sessions", - out.scienceId, - JSON.stringify({ - session: out.session, - token: out.token, - lastUpdated: Date.now(), - }), - ); + const redisKey = `pmd-api.sessions.${out.scienceId}`; + await redis.hset(redisKey, { + session: out.session, + token: out.token, + lastUpdated: Date.now(), + }); + await redis.expire(redisKey, 300); // 5 minutes - const interval = Number.parseInt(process.env.SESSION_KEEP_ALIVE_INTERVAL ?? "5000"); + const interval = Number.parseInt(process.env.SESSION_KEEP_ALIVE_INTERVAL ?? "5000"); // 5 seconds return reply.status(200).send({ code: "OK", diff --git a/package.json b/package.json index 70ce1a4..c529104 100644 --- a/package.json +++ b/package.json @@ -38,5 +38,10 @@ "prettier": "^3.2.5", "typescript": "^5.5.4", "vitest": "^2.0.2" + }, + "pnpm": { + "patchedDependencies": { + "ip-location-api@1.0.0": "patches/ip-location-api@1.0.0.patch" + } } } diff --git a/patches/ip-location-api@1.0.0.patch b/patches/ip-location-api@1.0.0.patch new file mode 100644 index 0000000..86d468c --- /dev/null +++ b/patches/ip-location-api@1.0.0.patch @@ -0,0 +1,62 @@ +diff --git a/browser/country/README.md b/browser/country/README.md +deleted file mode 100644 +index ac8fc934b4998f2a2cb7a92bf68bbdadd9e3d36d..0000000000000000000000000000000000000000 +diff --git a/browser/country-extra/README.md b/browser/country-extra/README.md +deleted file mode 100644 +index 71e7237722915b2697b56ccb14171524eb4b40fb..0000000000000000000000000000000000000000 +diff --git a/browser/geocode/README.md b/browser/geocode/README.md +deleted file mode 100644 +index 9d9a2205061f332b363b82d7561a0e3829d5bf2c..0000000000000000000000000000000000000000 +diff --git a/browser/geocode-extra/README.md b/browser/geocode-extra/README.md +deleted file mode 100644 +index 38e17eebdd8532d07b460fbe7f385f36625ece9d..0000000000000000000000000000000000000000 +diff --git a/src/db.mjs b/src/db.mjs +index 378b8a22084f860cc89720d1783a235c034717b2..cbffe1eaa84a94df536059d4b4af3f8f5ceb0ca7 100644 +--- a/src/db.mjs ++++ b/src/db.mjs +@@ -33,7 +33,12 @@ export const update = async () => { + if(refreshTmpDir || !fsSync.existsSync(setting.tmpDataDir)){ + // refresh tmp folder + await rimraf(setting.tmpDataDir) +- await fs.mkdir(setting.tmpDataDir) ++ await fs.mkdir(setting.tmpDataDir, {recursive: true}) ++ } ++ ++ // When specifying a custom dataDir, it doesn't always exists ++ if (!fsSync.existsSync(setting.dataDir)){ ++ await fs.mkdir(setting.dataDir, {recursive: true}) + } + + console.log('Downloading database') +diff --git a/src/main.mjs b/src/main.mjs +index d001aca60902bc7fe41271c6fa7a0b6648607b15..5b2c125d8e7590afee82c794bf771accd656b2b7 100644 +--- a/src/main.mjs ++++ b/src/main.mjs +@@ -3,7 +3,7 @@ import fs from 'fs/promises' + import fsSync from 'fs' + import path from 'path' + import { exec } from 'child_process' +- ++import { fileURLToPath } from "url" + import { countries, continents } from 'countries-list' + + import { setting, setSetting, getSettingCmd } from './setting.mjs' +@@ -14,6 +14,9 @@ const v6db = setting.v6 + const locFieldHash = setting.locFieldHash + const mainFieldHash = setting.mainFieldHash + ++const __filename = fileURLToPath(import.meta.url) ++const __dirname = path.dirname(__filename) ++ + //--------------------------------------- + // Database lookup + //--------------------------------------- +@@ -235,7 +238,7 @@ export const updateDb = (_setting) => { + // However, db.js import many external modules, it makes slow down the startup time and uses more memory. + // Therefore, we use exec() to run the script in the other process. + return new Promise((resolve, reject) => { +- var cmd = 'node ' + path.resolve(__dirname, '..', 'script', 'updatedb.js') ++ var cmd = 'node ' + path.resolve(__dirname, '..', 'script', 'updatedb.mjs') + var arg + if(_setting){ + var oldSetting = Object.assign({}, setting) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6be7c84..d1e8fd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + ip-location-api@1.0.0: + hash: rjznwunaa6otlvuetmywn36scu + path: patches/ip-location-api@1.0.0.patch + importers: .: @@ -77,6 +82,9 @@ importers: ioredis: specifier: ^5.3.2 version: 5.4.1 + ip-location-api: + specifier: ^1.0.0 + version: 1.0.0(patch_hash=rjznwunaa6otlvuetmywn36scu)(debug@4.3.7) ky: specifier: ^1.7.2 version: 1.7.2 @@ -177,7 +185,7 @@ importers: devDependencies: vitepress: specifier: 1.3.1 - version: 1.3.1(@algolia/client-search@5.4.0)(@types/node@22.5.4)(change-case@4.1.2)(postcss@8.4.45)(sass@1.78.0)(search-insights@2.17.2)(terser@5.32.0)(typescript@5.6.2) + version: 1.3.1(@algolia/client-search@5.4.0)(@types/node@22.5.4)(axios@1.7.7)(change-case@4.1.2)(postcss@8.4.45)(sass@1.78.0)(search-insights@2.17.2)(terser@5.32.0)(typescript@5.6.2) apps/pd: dependencies: @@ -268,7 +276,7 @@ importers: version: 3.13.1(magicast@0.3.5)(rollup@4.21.2)(webpack-sources@3.2.3) '@nuxt/scripts': specifier: ^0.8.5 - version: 0.8.5(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(fuse.js@7.0.0)(ioredis@5.4.1)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1)) + version: 0.8.5(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(axios@1.7.7)(fuse.js@7.0.0)(ioredis@5.4.1)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1)) '@nuxtjs/device': specifier: ^3.2.2 version: 3.2.2(@parcel/watcher@2.4.1)(@types/node@22.5.4)(@vitest/ui@2.0.5(vitest@2.0.5))(h3@1.12.0)(ioredis@5.4.1)(jiti@1.21.6)(magicast@0.3.5)(nitropack@2.9.7(magicast@0.3.5)(webpack-sources@3.2.3))(nuxi@3.13.1)(optionator@0.9.4)(playwright-core@1.47.0)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-router@4.4.4(vue@3.5.4(typescript@5.5.4)))(vue-tsc@2.1.6(typescript@5.5.4))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3) @@ -1694,6 +1702,12 @@ packages: resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fast-csv/format@5.0.0': + resolution: {integrity: sha512-IyMpHwYIOGa2f0BJi6Wk55UF0oBA5urdIydoEDYxPo88LFbeb3Yr4rgpu98OAO1glUWheSnNtUgS80LE+/dqmw==} + + '@fast-csv/parse@5.0.0': + resolution: {integrity: sha512-ecF8tCm3jVxeRjEB6VPzmA+1wGaJ5JgaUX2uesOXdXD6qQp0B3EdshOIed4yT1Xlj/F2f8v4zHSo0Oi31L697g==} + '@fastify/accept-negotiator@1.1.0': resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==} engines: {node: '>=14'} @@ -4027,6 +4041,9 @@ packages: avvio@8.3.2: resolution: {integrity: sha512-st8e519GWHa/azv8S87mcJvZs4WsgTBjOw/Ih1CP6u+8SZvcOeAYNG6JbsIrAUUJJ7JfmrnOkR8ipDS+u9SIRQ==} + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + b4a@1.6.6: resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} @@ -4122,6 +4139,9 @@ packages: resolution: {integrity: sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==} engines: {node: '>=16.20.1'} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-crc32@1.0.0: resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} engines: {node: '>=8.0.0'} @@ -4514,6 +4534,9 @@ packages: typescript: optional: true + countries-list@3.1.1: + resolution: {integrity: sha512-nPklKJ5qtmY5MdBKw1NiBAoyx5Sa7p2yPpljZyQ7gyCN1m+eMFs9I6CT37Mxt8zvR5L3VzD3DJBE4WQzX3WF4A==} + crawler-user-agents@1.0.143: resolution: {integrity: sha512-tVnQF0rrrzmiwHL/ASloGdW4wQ5Cjfd1ujiUWfeDjqXR6vTxrN4bIWnDd1tgUhDWx3z0msiktWjI51r3Dnw0CA==} @@ -4642,6 +4665,9 @@ packages: dataloader@2.2.2: resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==} + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + db0@0.1.4: resolution: {integrity: sha512-Ft6eCwONYxlwLjBXSJxw0t0RYtA5gW9mq8JfBXn9TtC0nDPlqePAhpv9v4g9aONBi6JI1OXHTKKkUYGd+BOrCA==} peerDependencies: @@ -5239,6 +5265,10 @@ packages: fast-content-type-parse@1.1.0: resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} + fast-csv@5.0.1: + resolution: {integrity: sha512-Q43zC4NdQD5MAWOVQOF8KA+D6ddvTJjX2ib8zqysm74jZhtk6+dc8C75/OqRV6Y9CLc4kgvbC3PLG8YL4YZfgw==} + engines: {node: '>=10.0.0'} + fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -5381,6 +5411,15 @@ packages: focus-trap@7.5.4: resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + fontaine@0.5.0: resolution: {integrity: sha512-vPDSWKhVAfTx4hRKT777+N6Szh2pAosAuzLpbppZ6O3UdD/1m6OlHjNcC3vIbgkRTIcLjzySLHXzPeLO2rE8cA==} @@ -5833,6 +5872,14 @@ packages: resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==} engines: {node: '>=12.22.0'} + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + + ip-location-api@1.0.0: + resolution: {integrity: sha512-kx2597JI+SzijYEZ0X8zG3u77wVlcCwoJ9feHt11+COa39o4gzPJC6D62P0O6AAwrT0yUPI7JSo10e5tpFBzUA==} + engines: {node: '>=14.8.0'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -6066,6 +6113,9 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + jsdoc-type-pratt-parser@4.1.0: resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} engines: {node: '>=12.0.0'} @@ -6223,12 +6273,33 @@ packages: lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + + lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.isundefined@3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + lodash.kebabcase@4.1.1: resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} @@ -7014,6 +7085,9 @@ packages: resolution: {integrity: sha512-U94a+eXHzct7vAd19GH3UQ2dH4Satbng0MyYTMaQatL0pvYYL5CTPR25HBhKtecl+4bfu1/i3vC6k0hydO5Vcw==} engines: {node: '>=14.16'} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -7342,6 +7416,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -7810,6 +7887,9 @@ packages: sponge-case@1.0.1: resolution: {integrity: sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==} + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} @@ -8887,6 +8967,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yauzl@3.1.3: + resolution: {integrity: sha512-JCCdmlJJWv7L0q/KylOekyRaUrdEoUxWkWVcgorosTROCFWiS9p2NNPE9Yb91ak7b1N5SxAZEliWpspbZccivw==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -10246,6 +10330,23 @@ snapshots: '@eslint/object-schema@2.1.4': {} + '@fast-csv/format@5.0.0': + dependencies: + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@fast-csv/parse@5.0.0': + dependencies: + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + '@fastify/accept-negotiator@1.1.0': optional: true @@ -11143,7 +11244,7 @@ snapshots: - supports-color - webpack-sources - '@nuxt/devtools-ui-kit@1.4.1(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))': + '@nuxt/devtools-ui-kit@1.4.1(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(axios@1.7.7)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))': dependencies: '@iconify-json/carbon': 1.2.1 '@iconify-json/logos': 1.2.0 @@ -11159,7 +11260,7 @@ snapshots: '@unocss/preset-mini': 0.62.3 '@unocss/reset': 0.62.3 '@vueuse/core': 11.0.3(vue@3.5.4(typescript@5.5.4)) - '@vueuse/integrations': 11.0.3(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.5.4(typescript@5.5.4)) + '@vueuse/integrations': 11.0.3(axios@1.7.7)(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.5.4(typescript@5.5.4)) '@vueuse/nuxt': 11.0.3(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(rollup@4.21.2)(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3) defu: 6.1.4 focus-trap: 7.5.4 @@ -11537,10 +11638,10 @@ snapshots: - supports-color - webpack-sources - '@nuxt/scripts@0.8.5(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(fuse.js@7.0.0)(ioredis@5.4.1)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))': + '@nuxt/scripts@0.8.5(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(axios@1.7.7)(fuse.js@7.0.0)(ioredis@5.4.1)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1))': dependencies: '@nuxt/devtools-kit': 1.4.1(magicast@0.3.5)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(webpack-sources@3.2.3) - '@nuxt/devtools-ui-kit': 1.4.1(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1)) + '@nuxt/devtools-ui-kit': 1.4.1(@nuxt/devtools@1.4.2(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3))(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.94.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.4)(axios@1.7.7)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.1(@parcel/watcher@2.4.1)(@types/node@22.5.4)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(sass@1.78.0)(terser@5.32.0)(typescript@5.5.4)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue-tsc@2.1.6(typescript@5.5.4))(webpack-sources@3.2.3))(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))(vue@3.5.4(typescript@5.5.4))(webpack-sources@3.2.3)(webpack@5.94.0(esbuild@0.23.1)) '@nuxt/kit': 3.13.1(magicast@0.3.5)(rollup@4.21.2)(webpack-sources@3.2.3) '@types/google.maps': 3.58.0 '@types/stripe-v3': 3.1.33 @@ -13750,24 +13851,26 @@ snapshots: - '@vue/composition-api' - vue - '@vueuse/integrations@10.11.1(change-case@4.1.2)(focus-trap@7.5.4)(vue@3.5.4(typescript@5.6.2))': + '@vueuse/integrations@10.11.1(axios@1.7.7)(change-case@4.1.2)(focus-trap@7.5.4)(vue@3.5.4(typescript@5.6.2))': dependencies: '@vueuse/core': 10.11.1(vue@3.5.4(typescript@5.6.2)) '@vueuse/shared': 10.11.1(vue@3.5.4(typescript@5.6.2)) vue-demi: 0.14.10(vue@3.5.4(typescript@5.6.2)) optionalDependencies: + axios: 1.7.7(debug@4.3.7) change-case: 4.1.2 focus-trap: 7.5.4 transitivePeerDependencies: - '@vue/composition-api' - vue - '@vueuse/integrations@11.0.3(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.5.4(typescript@5.5.4))': + '@vueuse/integrations@11.0.3(axios@1.7.7)(focus-trap@7.5.4)(fuse.js@7.0.0)(vue@3.5.4(typescript@5.5.4))': dependencies: '@vueuse/core': 11.0.3(vue@3.5.4(typescript@5.5.4)) '@vueuse/shared': 11.0.3(vue@3.5.4(typescript@5.5.4)) vue-demi: 0.14.10(vue@3.5.4(typescript@5.5.4)) optionalDependencies: + axios: 1.7.7(debug@4.3.7) focus-trap: 7.5.4 fuse.js: 7.0.0 transitivePeerDependencies: @@ -14128,6 +14231,14 @@ snapshots: '@fastify/error': 3.4.1 fastq: 1.17.1 + axios@1.7.7(debug@4.3.7): + dependencies: + follow-redirects: 1.15.9(debug@4.3.7) + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + b4a@1.6.6: {} babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: {} @@ -14255,6 +14366,8 @@ snapshots: bson@6.8.0: {} + buffer-crc32@0.2.13: {} + buffer-crc32@1.0.0: {} buffer-from@1.1.2: {} @@ -14730,6 +14843,8 @@ snapshots: optionalDependencies: typescript: 5.6.2 + countries-list@3.1.1: {} + crawler-user-agents@1.0.143: {} crc-32@1.2.2: {} @@ -14868,6 +14983,8 @@ snapshots: dataloader@2.2.2: {} + dayjs@1.11.13: {} + db0@0.1.4: {} de-indent@1.0.2: {} @@ -15706,6 +15823,11 @@ snapshots: fast-content-type-parse@1.1.0: {} + fast-csv@5.0.1: + dependencies: + '@fast-csv/format': 5.0.0 + '@fast-csv/parse': 5.0.0 + fast-decode-uri-component@1.0.1: {} fast-deep-equal@3.1.3: {} @@ -15876,6 +15998,10 @@ snapshots: dependencies: tabbable: 6.2.0 + follow-redirects@1.15.9(debug@4.3.7): + optionalDependencies: + debug: 4.3.7 + fontaine@0.5.0(webpack-sources@3.2.3): dependencies: '@capsizecss/metrics': 2.2.0 @@ -16460,6 +16586,22 @@ snapshots: transitivePeerDependencies: - supports-color + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + + ip-location-api@1.0.0(patch_hash=rjznwunaa6otlvuetmywn36scu)(debug@4.3.7): + dependencies: + axios: 1.7.7(debug@4.3.7) + countries-list: 3.1.1 + dayjs: 1.11.13 + fast-csv: 5.0.1 + ip-address: 9.0.5 + yauzl: 3.1.3 + transitivePeerDependencies: + - debug + ipaddr.js@1.9.1: {} ipaddr.js@2.2.0: {} @@ -16682,6 +16824,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsbn@1.1.0: {} + jsdoc-type-pratt-parser@4.1.0: {} jsesc@0.5.0: {} @@ -16841,10 +16985,24 @@ snapshots: lodash.defaults@4.2.0: {} + lodash.escaperegexp@4.1.2: {} + + lodash.groupby@4.6.0: {} + lodash.isarguments@3.1.0: {} + lodash.isboolean@3.0.3: {} + + lodash.isequal@4.5.0: {} + + lodash.isfunction@3.0.9: {} + + lodash.isnil@4.0.0: {} + lodash.isplainobject@4.0.6: {} + lodash.isundefined@3.0.1: {} + lodash.kebabcase@4.1.1: {} lodash.memoize@4.1.2: {} @@ -18054,6 +18212,8 @@ snapshots: peek-readable@5.2.0: {} + pend@1.2.0: {} + perfect-debounce@1.0.0: {} pg-int8@1.0.1: {} @@ -18362,6 +18522,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} + pump@3.0.0: dependencies: end-of-stream: 1.4.4 @@ -18895,6 +19057,8 @@ snapshots: dependencies: tslib: 2.6.3 + sprintf-js@1.1.3: {} + stable-hash@0.0.4: {} stackback@0.0.2: {} @@ -19762,7 +19926,7 @@ snapshots: sass: 1.78.0 terser: 5.32.0 - vitepress@1.3.1(@algolia/client-search@5.4.0)(@types/node@22.5.4)(change-case@4.1.2)(postcss@8.4.45)(sass@1.78.0)(search-insights@2.17.2)(terser@5.32.0)(typescript@5.6.2): + vitepress@1.3.1(@algolia/client-search@5.4.0)(@types/node@22.5.4)(axios@1.7.7)(change-case@4.1.2)(postcss@8.4.45)(sass@1.78.0)(search-insights@2.17.2)(terser@5.32.0)(typescript@5.6.2): dependencies: '@docsearch/css': 3.6.1 '@docsearch/js': 3.6.1(@algolia/client-search@5.4.0)(search-insights@2.17.2) @@ -19773,7 +19937,7 @@ snapshots: '@vue/devtools-api': 7.4.5 '@vue/shared': 3.5.4 '@vueuse/core': 10.11.1(vue@3.5.4(typescript@5.6.2)) - '@vueuse/integrations': 10.11.1(change-case@4.1.2)(focus-trap@7.5.4)(vue@3.5.4(typescript@5.6.2)) + '@vueuse/integrations': 10.11.1(axios@1.7.7)(change-case@4.1.2)(focus-trap@7.5.4)(vue@3.5.4(typescript@5.6.2)) focus-trap: 7.5.4 mark.js: 8.11.1 minisearch: 7.1.0 @@ -20186,6 +20350,11 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yauzl@3.1.3: + dependencies: + buffer-crc32: 0.2.13 + pend: 1.2.0 + yocto-queue@0.1.0: {} yocto-queue@1.1.1: {}