diff --git a/.claude/hooks/git-gate.sh b/.claude/hooks/git-gate.sh new file mode 100755 index 0000000000..3117f985bc --- /dev/null +++ b/.claude/hooks/git-gate.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +# Claude Code PreToolUse hook (Bash matcher). +# Denies git commit/push/merge/rebase when: +# - the command uses --no-verify (bypasses Husky hooks), or +# - `npx tsc --noEmit` fails, or +# - ESLint fails on the staged (commit), unpushed (push) or uncommitted (merge/rebase) .ts/.js +# files under src/; push with no upstream/origin/main base checks all of src/, +# - or the toolchain (node_modules/.bin/tsc, eslint) is missing: run `npm ci`. +# Fails closed: unparseable input that looks like a git write, or a failing git diff, is +# blocked or escalated to a full check instead of passing. +# Emits a PreToolUse deny decision as JSON; otherwise exits 0 silently. + +set -o pipefail + +INPUT=$(cat) +# Parse the tool input with jq, falling back to node (always present in this repo). If neither +# can parse it, fail closed: an unparsed command must never be allowed through silently. +if command -v jq >/dev/null 2>&1; then + CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null) || CMD_PARSE_FAILED=1 +elif command -v node >/dev/null 2>&1; then + CMD=$(printf '%s' "$INPUT" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{process.stdout.write(String((JSON.parse(s).tool_input||{}).command||""))})' 2>/dev/null) || CMD_PARSE_FAILED=1 +else + CMD_PARSE_FAILED=1 +fi +if [ -n "$CMD_PARSE_FAILED" ]; then + # Only block unparsed input that looks like a git write; other commands are not gated. + if printf '%s' "$INPUT" | grep -qE 'git[^a-z].*(commit|push|merge|rebase)'; then + printf '%s\n' "Git gate: could not parse the tool input (install jq or node). Git write blocked." >&2 + exit 2 + fi + exit 0 +fi + +GIT_WRITE='(^|[[:space:]&|;(])git([[:space:]]+-[^[:space:]]+)*[[:space:]]+(commit|push|merge|rebase)([[:space:]]|$)' +if ! printf '%s' "$CMD" | grep -qE "$GIT_WRITE"; then + exit 0 +fi + +deny() { + local reason=$1 + if command -v jq >/dev/null 2>&1; then + jq -n --arg reason "$reason" \ + '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":$reason}}' + else + # Fallback: exit code 2 blocks the tool call and shows stderr to the model. + printf '%s\n' "$reason" >&2 + exit 2 + fi + exit 0 +} + +# Quoted strings (commit messages, echo text) are stripped before matching flags, so +# `git commit -m "fix -n parsing --no-verify"` is not mistaken for a bypass. +STRIPPED=$(printf '%s' "$CMD" | sed -E "s/\"([^\"\\\\]|\\\\.)*\"//g; s/'[^']*'//g") +if ! printf '%s' "$STRIPPED" | grep -qE "$GIT_WRITE"; then + exit 0 +fi + +# --no-verify on any git write, or `-n` (alone or in a short-flag cluster) on `git commit` only. +GIT_COMMIT='git([[:space:]]+-[^[:space:]]+)*[[:space:]]+commit' +if printf '%s' "$STRIPPED" | grep -qE -- '(^|[[:space:]])--no-verify([[:space:]=]|$)' \ + || printf '%s' "$STRIPPED" | grep -qE -- "${GIT_COMMIT}[^|;&]*[[:space:]]-[[:alpha:]]*n[[:alpha:]]*([[:space:]]|\$)"; then + deny "Git gate: --no-verify is not allowed. Fix the failing Husky hooks instead of bypassing them." +fi + +ROOT=${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)} +[ -n "$ROOT" ] || exit 0 +cd "$ROOT" || exit 0 + +# FULL=1 checks the whole project (tsc + eslint on src/) instead of the changed files only. +FULL=0 +if printf '%s' "$STRIPPED" | grep -qE 'git([[:space:]]+-[^[:space:]]+)*[[:space:]]+push'; then + BASE=$(git rev-parse --abbrev-ref '@{u}' 2>/dev/null) + if [ -z "$BASE" ] && git rev-parse --verify --quiet origin/main >/dev/null; then + BASE=origin/main + fi + if [ -n "$BASE" ]; then + # A failing diff (e.g. no merge base) falls back to a full check instead of passing. + FILES=$(git diff --name-only --diff-filter=d "$BASE"...HEAD 2>/dev/null) || FULL=1 + else + # No base ref to diff against: fail closed with a full check instead of passing. + FULL=1 + fi +elif printf '%s' "$STRIPPED" | grep -qE "${GIT_COMMIT}([[:space:]]|\$)"; then + FILES=$(git diff --cached --name-only --diff-filter=d 2>/dev/null) || FULL=1 +else + # merge/rebase: the incoming commits already passed their own gates; check that the local + # working tree (staged + unstaged src/ changes vs HEAD) is not broken before rewriting history. + FILES=$(git diff HEAD --name-only --diff-filter=d 2>/dev/null) || FULL=1 +fi + +if [ "$FULL" -eq 1 ]; then + CODE_FILES=src +else + CODE_FILES=$(printf '%s\n' "$FILES" | grep -E '^src/.*\.(ts|js)$' || true) + [ -n "$CODE_FILES" ] || exit 0 +fi + +if [ ! -x node_modules/.bin/tsc ] || [ ! -x node_modules/.bin/eslint ]; then + deny "Git gate: BLOCKED because the local toolchain is missing (node_modules/.bin/tsc or eslint not found). Run \`npm ci\` in ${ROOT} and retry." +fi + +FAILURES="" + +TSC=$(node_modules/.bin/tsc --noEmit 2>&1) +if [ $? -ne 0 ]; then + FAILURES="${FAILURES}--- TypeScript (tsc --noEmit) --- +${TSC} + +" +fi + +LINT=$(printf '%s\n' "$CODE_FILES" | xargs node_modules/.bin/eslint 2>&1) +if [ $? -ne 0 ]; then + FAILURES="${FAILURES}--- ESLint --- +${LINT} + +" +fi + +if [ -n "$FAILURES" ]; then + deny "Git gate: commit/push/merge/rebase BLOCKED by errors in changed files. + +Fix the errors below and retry. + +${FAILURES}" +fi + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000000..df0c1e360f --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,35 @@ +{ + "permissions": { + "allow": [ + "mcp__plugin_engram_engram__mem_search", + "mcp__plugin_engram_engram__mem_get_observation", + "mcp__plugin_engram_engram__mem_save", + "mcp__plugin_engram_engram__mem_context", + "mcp__plugin_engram_engram__mem_session_summary" + ] + }, + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/git-gate.sh\"" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Edit|Write|MultiEdit", + "hooks": [ + { + "type": "command", + "command": "F=$(jq -r '.tool_input.file_path // empty'); [ -z \"$F\" ] && exit 0; case \"$F\" in *.ts|*.js|*.json|*.md) (cd \"$CLAUDE_PROJECT_DIR\" && npx --no prettier --config .prettierrc.js --write --log-level=silent \"$F\" >/dev/null 2>&1) || true ;; esac; exit 0" + } + ] + } + ] + } +} diff --git a/.claude/skills/purple-baileys/SKILL.md b/.claude/skills/purple-baileys/SKILL.md new file mode 100644 index 0000000000..34e099441f --- /dev/null +++ b/.claude/skills/purple-baileys/SKILL.md @@ -0,0 +1,69 @@ +--- +name: purple-baileys +description: > + Landmarks and invariants of the Baileys channel in evolution-in-purple: + whatsapp.baileys.service.ts entry points, onWhatsApp guards, the on-WhatsApp cache, + createJid, @lid (LID) addressing rules, QR / pairing code flow and the Baileys version pin. + Read BEFORE touching message receive/send, JID handling or connection logic. + Trigger: change in whatsapp.baileys.service.ts, onWhatsappCache.ts, createJid.ts, @lid / remoteJidAlt handling, QR or pairing, Baileys upgrade. +--- + +# Skill: purple-baileys + +File: `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts` (~5k lines). +Baileys is pinned exactly: `"baileys": "7.0.0-rc.9"` (`package.json:80`). Never use a caret; +an upgrade is its own task with manual QR + send/receive verification. + +## Landmarks (verify line numbers before citing; they drift) + +| Area | Location | +|---|---| +| QR / pairing in `connectionUpdate` | `:334`, pairing code `:375-380`, QR limit `:336` | +| Auth state selection (Redis / Prisma / provider files) | `defineAuthState` `:556` | +| Socket creation | `createClient` `:576` | +| Public connect entry | `connectToWhatsapp` `:723` | +| Inbound handler | `messageHandle['messages.upsert']` `:1082` | +| LID → PN swap before emit | `:1478-1480` | +| Contact upsert using `remoteJidAlt` | `:1512-1517` | +| Event dispatch (`connection.update`, `messages.upsert`) | `:1902-1919` | +| Outbound core | `sendMessageWithTyping` `:2289` | +| onWhatsApp guards (throw 400 when `!exists`) | `:2297`, `sendPresence` `:2571`, `blockUser` `:4118` | +| Number verification | `whatsappNumber` `:3507` (cache → Baileys `onWhatsApp` `:3566`) | + +Helpers: `src/utils/onWhatsappCache.ts` (`saveOnWhatsappCache` `:73`, `getOnWhatsappCache` +`:182`, `normalizeJid` `:68`) and `src/utils/createJid.ts` (`createJid` `:35`). + +## @lid rules (hard) + +1. **LID → PN is NOT derivable.** A `@lid` carries no phone number. Never compute, + guess or strip the suffix to obtain one. +2. **PN is only learned** from: `key.remoteJidAlt` / `participantAlt` on received messages, + group participant metadata, or history sync. Persist the mapping when observed + (contact upsert at `:1512-1517`, `saveOnWhatsappCache`). +3. **Never strip `@lid` on outbound.** `createJid` returns `@lid` JIDs unchanged + (`createJid.ts:38-40`); `whatsappNumber` skips Baileys verification for `@lid` and treats an + uncached LID as existing (`:3562`, `:3586-3594`). Keep both behaviors. +4. The inbound swap at `:1478-1480` rewrites `remoteJid` to `remoteJidAlt` only when the alt is + present. Consumers (orbit, stenfar) also read `remoteJidAlt`/`addressingMode` themselves — + changing this is a contract change (`purple-contract`). + +Detail, failure modes and examples: `references/lid.md`. + +## onWhatsApp guards + +`sendMessageWithTyping`, `sendPresence` and `blockUser` all call `whatsappNumber` and throw +`BadRequestException(isWA)` when `!isWA.exists` and the JID is not a group/broadcast. The 400 +body (the `OnWhatsAppDto`) is what consumers see on "number not on WhatsApp". Do not bypass the +guard; fix the cache or the JID instead. + +## QR / pairing + +- `connectToWhatsapp(number?)` — passing a number enables pairing-code mode + (`this.phoneNumber`, `:375-380`); otherwise only QR. +- QR emits `qrcode.updated` with `{ pairingCode, code, base64 }` (`:392`, `:400`); the count is + capped by `QRCODE.LIMIT` (`:336`), after which the instance stops retrying. + +## Verification + +No automated tests. After changes: `npx tsc --noEmit`, `npm run build`, then a manual run +with a test instance — QR scan, inbound 1:1 (PN and LID contact), inbound group, outbound text. diff --git a/.claude/skills/purple-baileys/references/lid.md b/.claude/skills/purple-baileys/references/lid.md new file mode 100644 index 0000000000..6f45c3f734 --- /dev/null +++ b/.claude/skills/purple-baileys/references/lid.md @@ -0,0 +1,44 @@ +# purple-baileys — @lid detail + +## What a LID is + +WhatsApp privacy addressing: some 1:1 and group participants appear as `@lid` +(`key.addressingMode === 'lid'`) instead of `@s.whatsapp.net`. The digits are an opaque +identifier, not a phone number. + +## Where the phone number (PN) can come from + +| Source | Field | Code | +|---|---|---| +| Received message key | `key.remoteJidAlt` (1:1), `key.participantAlt` (groups) | `whatsapp.baileys.service.ts:159-160` (key type), `:1250-1251`, `:1478`, `:1516-1517` | +| Group metadata | participant entries returned by Baileys group metadata | group fetch paths (shape depends on Baileys version — verify) | +| History sync | message keys in `messaging-history.set` | `whatsapp.baileys.service.ts:927` | +| On-WhatsApp cache | `jidOptions`, `lid` | `src/utils/onWhatsappCache.ts:73`, `:182` | + +If none of these has been observed, the PN is unknown. Reply to the LID JID. + +## Outbound + +- `createJid` keeps `@lid`, `@g.us`, `@s.whatsapp.net`, `@broadcast` untouched + (`src/utils/createJid.ts:38-44`); only bare numbers get normalized (BR/MX/AR rules at `:2`, `:17`). +- `whatsappNumber` (`whatsapp.baileys.service.ts:3507`) resolves users from the cache first, then + calls `client.onWhatsApp` only for non-LID JIDs (`:3562-3566`). An uncached LID returns + `exists: true` with `lid: 'lid'` (`:3586-3594`). + +## Known failure modes + +- Stripping `@lid` and sending to `@s.whatsapp.net` → 400 "exists: false" from the guard + or delivery to the wrong account. +- Treating `remoteJid` as the stable chat key: the swap at `:1478-1480` means the same chat can + arrive as PN or LID depending on whether `remoteJidAlt` was present. +- Persisting a LID as a phone number in consumer CRMs. Consumers must read `remoteJidAlt` + (orbit: `orbit-backend/src/evolution-api-webhook/evolution-api-webhook.service.ts:227-231`; + stenfar: `canal-stenfar/src/services/whatsapp/phoneExtractor.js:35-44`). + +## Manual verification matrix + +1. 1:1 inbound from a PN contact → `remoteJid` is `@s.whatsapp.net`. +2. 1:1 inbound from a LID contact with alt → `remoteJid` swapped to PN, `remoteJidAlt` kept. +3. Group inbound with LID participant → `participant` LID, `participantAlt` PN when present. +4. Outbound to a LID JID → delivered; no 400. +5. Outbound to an unknown PN → `onWhatsApp` check, 400 when not registered. diff --git a/.claude/skills/purple-contract/SKILL.md b/.claude/skills/purple-contract/SKILL.md new file mode 100644 index 0000000000..9228b51944 --- /dev/null +++ b/.claude/skills/purple-contract/SKILL.md @@ -0,0 +1,72 @@ +--- +name: purple-contract +description: > + The external contract of evolution-in-purple as consumed by orbit, canal-whatsapp and + stenfar: webhook payload envelope, event naming (MESSAGES_UPSERT vs messages-upsert with + webhookByEvents), and the REST routes each consumer calls. Read BEFORE changing any route, + DTO, response shape, webhook event or payload field. + Trigger: change to a REST route, request/response shape, webhook event name or payload, message key fields (remoteJid, remoteJidAlt), or instance/webhook config. +--- + +# Skill: purple-contract + +This engine replaces Evolution API 2.3.7 for three consumers. **The contract is "behaves +exactly like Evolution 2.3.7"** unless a versioned breaking change says otherwise. +Consumer matrix (endpoint/event × consumer, with source paths): `references/consumer-matrix.md`. + +## Webhook envelope + +Built in `src/api/integrations/event/webhook/webhook.controller.ts:93-103`: + +```ts +{ ...extra, event, instance, data, destination, date_time, sender, server_url, apikey } +``` + +- `event` is the dotted lowercase name (`messages.upsert`, `connection.update`, ...). +- Enabled-events list is stored uppercase with underscores (`MESSAGES_UPSERT`), derived at + `webhook.controller.ts:88`. +- With `webhookByEvents: true` the URL becomes `${url}/${event-kebab}` — e.g. + `/messages-upsert` (`webhook.controller.ts:89`, `:109-110`). orbit depends on this + (routes `whatsapp/messages-upsert`, `qrcode-updated`, `connection-update`). +- canal-whatsapp and stenfar use a single URL (`byEvents: false`) and switch on `event`. + +## Message key fields consumers read + +`data.key.remoteJid`, `remoteJidAlt`, `participant`, `participantAlt`, `addressingMode`, +`fromMe`, `id`. The LID → PN swap (`whatsapp.baileys.service.ts:1478-1480`) is part of the +contract (see `purple-baileys`). + +## REST surface consumers use + +Union of all consumers (details per consumer in the matrix): +`/instance/{create,fetchInstances,connect,connectionState,logout}`, +`/message/{sendText,sendMedia,sendWhatsAppAudio,sendReaction}`, +`/chat/{markMessageAsRead,sendPresence,getBase64FromMediaMessage,findMessages,findContacts}`, +`/group/findGroupInfos`, `/webhook/{set,find}`. Auth: `apikey` header. +canal also calls `/group/findGroupByJid`, which does **not** exist here (see matrix §Open questions). + +## Rules + +1. **Non-breaking by default.** Additive fields are allowed; renaming, removing, retyping a + field, changing a status code, an event name or a URL suffix is breaking. +2. **Breaking change ⇒ all of:** (a) new fork MAJOR/MINOR per `purple-release`, (b) a + `CHANGELOG.md` entry under the `evolution-in-purple` section with a migration note, + (c) a consumer check: grep each consumer listed in the matrix for the affected + route/event/field and record positives/negatives in the task doc. +3. Update `references/consumer-matrix.md` in the same change whenever a consumer starts or + stops using a route/event. +4. Consumers are read-only from this repo. Never edit orbit, canal-whatsapp or stenfar from + here; open a task in the consumer instead. + +## Consumer check (procedure) + +```bash +rg -n '' \ + ../../orbit/orbit-backend/src \ + ../../canal-whatsapp/canal-whatsapp-backend/src \ + ../../stenfar/canal-stenfar/src +``` + +Paths are relative to a checkout under `~/Programacion/evolution/`; adjust if the +layout differs. canal-whatsapp calls REST through `@alien-2-0-llc/sdk-evolution-api`, so also +grep that SDK's method names (`sendText`, `setWebhook`, ...). diff --git a/.claude/skills/purple-contract/references/consumer-matrix.md b/.claude/skills/purple-contract/references/consumer-matrix.md new file mode 100644 index 0000000000..e5f7de09b4 --- /dev/null +++ b/.claude/skills/purple-contract/references/consumer-matrix.md @@ -0,0 +1,74 @@ +# Consumer matrix — evolution-in-purple + +Snapshot taken 2026-10-21 by reading the consumers (read-only). Re-verify before relying on a +row; consumers evolve independently. + +Consumer roots: +- **orbit** — `orbit/orbit-backend/src/channels/evolution-api.service.ts` (REST, axios) and + `orbit/orbit-backend/src/evolution-api-webhook/` (webhook receiver). +- **canal** — `canal-whatsapp/canal-whatsapp-backend/src/evolution-client/` plus + `@alien-2-0-llc/sdk-evolution-api@^0.11.0` (REST) and `src/inbound-webhook/inbound-webhook.controller.ts` + (`POST /evolution/webhook`). +- **stenfar** — `stenfar/canal-stenfar/src/services/whatsapp/evolution.service.js`, + `src/routes/widget.routes.js` (REST, fetch) and `src/routes/whatsapp.routes.js` + (`POST /webhook/whatsapp`). + +## REST routes + +| Route | orbit | canal | stenfar | +|---|---|---|---| +| `POST /instance/create` | yes (`evolution-api.service.ts:38`, with `webhook` body) | yes (`evolution-client.service.ts:139`) | — | +| `GET /instance/fetchInstances` | yes (`:20`) | yes (`evolution-client.service.ts:109`) | yes (`widget.routes.js:29`) | +| `GET /instance/connect/:instance` | yes (`:107`) | yes (`:116`) | yes (`widget.routes.js:74`) | +| `GET /instance/connectionState/:instance` | yes (`:212`) | — | yes (`evolution.service.js:299`) | +| `DELETE /instance/logout/:instance` | yes (`:233`) | yes (`:123`) | — | +| `POST /webhook/set/:instance` | — | yes (`:181`) | — | +| `GET /webhook/find/:instance` | — | yes (`:215`) | — | +| `POST /message/sendText/:instance` | yes (`:183`) | yes (SDK `sendText`) | yes (`evolution.service.js:23`) | +| `POST /message/sendMedia/:instance` | yes (`:248`) | yes (SDK `sendMedia`) | yes (`evolution.service.js:83`) | +| `POST /message/sendWhatsAppAudio/:instance` | — | yes (SDK `sendAudio`) | yes (`evolution.service.js:148`) | +| `POST /message/sendReaction/:instance` | — | yes (SDK `sendReaction`) | — | +| `POST /chat/markMessageAsRead/:instance` | yes (`:135`) | yes (SDK) | — | +| `POST /chat/sendPresence/:instance` | yes (`:155`) | in SDK, no call site seen | — | +| `POST /chat/getBase64FromMediaMessage/:instance` | — | yes (`inbound-worker.service.ts:2981`) | yes (`evolution.service.js:204`) | +| `POST /chat/findMessages/:instance` | — | in SDK, no call site seen | yes (`evolution.service.js:340`) | +| `POST /chat/findContacts/:instance` | — | — | yes (`evolution.service.js:254`) | +| `GET /group/findGroupInfos/:instance` | — | yes (SDK `findGroupInfos`) | — | +| `GET /group/findGroupByJid/:instance` (**not a route in this repo**) | — | yes (`evolution-group.client.ts:82`) | — | + +## Webhook delivery + +| Aspect | orbit | canal | stenfar | +|---|---|---|---| +| Mode | `byEvents: true` (`evolution-api.service.ts:46`) | `byEvents: false` (`evolution-client.service.ts:188`) | single URL (config not set from code — verify) | +| Receiver | `POST /evolution-api-webhook/whatsapp/` | `POST /evolution/webhook` | `POST /webhook/whatsapp` | + +## Webhook events + +| Event | orbit | canal | stenfar | +|---|---|---|---| +| `messages.upsert` / `MESSAGES_UPSERT` | handled (`/messages-upsert`) | subscribed (`webhook-config.ts:31`) | handled (`whatsapp.controller.js:73`) | +| `messages.update` / `MESSAGES_UPDATE` | subscribed | subscribed (`:32`) | handled (acks, `mensaje.service.js:114-132`) | +| `connection.update` / `CONNECTION_UPDATE` | handled (`/connection-update`) | subscribed (`:33`) | handled (`whatsapp.controller.js:67`) | +| `qrcode.updated` / `QRCODE_UPDATED` | handled (`/qrcode-updated`) | — | — | +| `logout.instance` / `LOGOUT_INSTANCE` | — | subscribed (`:34`) | handled (`whatsapp.controller.js:67`) | +| `send.message` / `SEND_MESSAGE` | subscribed | — | referenced | +| Others (`MESSAGES_SET`, `MESSAGES_DELETE`, `CONTACTS_*`, `PRESENCE_UPDATE`, `CHATS_SET`, ...) | subscribed in create body (`evolution-api.service.ts:47+`) | — | — | + +## Payload fields relied on + +| Field | orbit | canal | stenfar | +|---|---|---|---| +| `data.key.remoteJid` / `remoteJidAlt` / `addressingMode` | yes (`evolution-api-webhook.service.ts:227-231`, dto `whatsapp-message-event.dto.ts:14-17`) | yes (inbound worker; exact call sites not audited) | yes (`phoneExtractor.js:35-44`) | +| `messages.update` numeric status | — | yes | yes (`mensaje.service.js:132`: ints in update, strings in upsert) | + +## Open questions + +- canal comments (`webhook-config.ts:8`) state that 2.3.7 `/instance/create` ignores the + `webhook` field, while orbit relies on it and this repo calls + `eventManager.setInstance(...)` from create (`src/api/controllers/instance.controller.ts:88`). + Verify before changing either path. +- canal calls `GET /group/findGroupByJid/:instance` (`evolution-group.client.ts:39`, "ASSUMED RESPONSE SHAPE"), but + `src/api/routes/group.router.ts` only defines `findGroupInfos` (`:79`) and similar. The call + likely 404s today; decide whether to add the route or fix canal. +- stenfar's webhook registration is not visible in code (likely manual / manager UI). diff --git a/.claude/skills/purple-release/SKILL.md b/.claude/skills/purple-release/SKILL.md new file mode 100644 index 0000000000..57d8ed50fb --- /dev/null +++ b/.claude/skills/purple-release/SKILL.md @@ -0,0 +1,75 @@ +--- +name: purple-release +description: > + Release procedure for evolution-in-purple: fork SemVer independent of upstream (VERSION file, + git tag vX.Y.Z, GHCR image), CHANGELOG.md layout with an evolution-in-purple section above + the upstream history, and the approval gates for push / PR / release / deploy. + Trigger: release, version bump, tag, changelog entry, publishing the Docker image, deploy. +--- + +# Skill: purple-release + +## Versioning + +- The fork has its **own SemVer**, independent of upstream (`package.json` keeps the upstream + `2.3.7` lineage; do not use it as the fork version). +- Source of truth: `VERSION` file at repo root, single line `X.Y.Z`. First release `0.1.0`. +- Git tag: `vX.Y.Z` (annotated), must equal `VERSION`. Upstream tags look like `2.3.7` (no `v`) — + never create or move those. +- Image: `.github/workflows/publish_docker_image.yml` runs on pushed `v*` tags and publishes + `ghcr.io/purple-dice/evolution-in-purple:X.Y.Z` and `:X.Y` (metadata-action semver patterns + `{{version}}` and `{{major}}.{{minor}}`). No `latest` tag is published. + `publish_docker_image_latest.yml` and `publish_docker_image_homolog.yml` are disabled + `workflow_dispatch` no-op stubs (upstream pushed to Docker Hub `evoapicloud/evolution-api`). + +### Bump rules (pre-1.0) + +| Change | Bump | +|---|---| +| Breaking contract change (see `purple-contract`) | MINOR (`0.x` → `0.(x+1).0`) | +| Feature / backport adding behavior | MINOR | +| Fix only | PATCH | + +After `1.0.0`, standard SemVer (breaking ⇒ MAJOR). + +## CHANGELOG.md layout + +```markdown +# evolution-in-purple + +## X.Y.Z (YYYY-MM-DD) + +### Breaking +- — consumers affected: orbit | canal-whatsapp | stenfar; migration: + +### Features / Fixes / Backports / Chores +- (upstream evolution-foundation/evolution-api#, ) when backported + +# Upstream Evolution API changelog + +# 2.3.7 (2025-12-05) ← upstream history, untouched below +``` + +New fork releases go at the top of the `# evolution-in-purple` section (newest first). Everything +below the `# Upstream Evolution API changelog` heading is upstream history and is never edited. + +## Procedure + +1. Ensure `main` is clean and green: `npx tsc --noEmit`, `npm run build`, `npm run lint:check`. +2. Decide the next version from the bump rules; list the changes since the last `v*` tag: + `git log --oneline $(git describe --tags --match 'v*' --abbrev=0)..HEAD` + (first release: since `2.3.7`). +3. Update `VERSION` and the `evolution-in-purple` section of `CHANGELOG.md` on a + `release/vX.Y.Z` branch. Any `Breaking` entry requires a completed consumer check. +4. **Ask the user** before: pushing the branch, opening the PR, merging, creating/pushing the + tag, publishing the image, deploying. Each is a separate approval. +5. After merge (approved): `git tag -a vX.Y.Z -m "evolution-in-purple vX.Y.Z"` on the merge + commit, push the tag (approved), let CI publish the image. +6. Verify the image tag exists in GHCR and report it. Consumers switch versions in their own + repos — never edit them from here. + +## Invariants + +- `VERSION` == latest `v*` tag == image tag. +- One release per tag; never re-tag. Fix forward with a PATCH. +- No push, PR, release or deploy without explicit user approval in the current conversation. diff --git a/.claude/skills/purple-typescript/SKILL.md b/.claude/skills/purple-typescript/SKILL.md new file mode 100644 index 0000000000..a0e05290d8 --- /dev/null +++ b/.claude/skills/purple-typescript/SKILL.md @@ -0,0 +1,78 @@ +--- +name: purple-typescript +description: > + Codebase conventions for evolution-in-purple (Evolution API 2.3.7 fork): RouterBroker + routes, thin controllers, services, plain DTOs, JSONSchema7 validation, manual singletons + in server.module.ts, ConfigService env, three Prisma schemas, cache engine, and the + verification commands. Read BEFORE editing anything under src/ or prisma/. + Trigger: new or changed endpoint, controller, service, DTO, schema, env var, Prisma model, or cache usage. +--- + +# Skill: purple-typescript + +TypeScript + Express + Prisma. No DI container, no decorators. Follow the existing shape of +the nearest sibling file; do not introduce new patterns. + +## Request flow + +1. **Router** extends `RouterBroker` (`src/api/abstract/abstract.router.ts:20`). + - `routerPath(path)` appends `/:instanceName` by default (`abstract.router.ts:22-26`). + - `dataValidate({ request, schema, ClassRef, execute })` (`abstract.router.ts:29`) + merges `params` + `query` into an `InstanceDto`, `Object.assign`s the body into a new + `ClassRef`, validates with `jsonschema` and throws `BadRequestException` on failure. + - Routers are mounted in `src/api/routes/index.router.ts:217-230` (`/instance`, `/message`, + `/chat`, `/group`, ... plus `''` for channel/event/chatbot/storage routers). +2. **Controller** — thin, delegates to a service or to `waMonitor.waInstances[instanceName]`. +3. **Service** — business logic; channel-specific behavior lives in + `src/api/integrations/channel/**` (see `purple-baileys`). +4. **DTO** — plain classes in `src/api/dto/` (no class-validator). +5. **Schema** — `JSONSchema7` objects in `src/validate/*.schema.ts`, re-exported by + `src/validate/validate.schema.ts`. + +## Wiring (manual singletons) + +`src/api/server.module.ts` instantiates everything once and exports it: +`cache` (`:55`), `prismaRepository` (`:63`), `waMonitor` (`:65`), controllers (`:90-139`). +A new service/controller is added there with explicit constructor args; routers import the +exported controller. Detail and a checklist: `references/conventions.md`. + +## Config + +`ConfigService.get(key)` (`src/config/env.config.ts:443`) returns typed sections loaded from +env. New env vars: add the type + loader in `env.config.ts` and document in `.env.example`. +Never read `process.env` directly in services. + +## Database + +- Three schemas: `prisma/postgresql-schema.prisma`, `prisma/mysql-schema.prisma`, + `prisma/psql_bouncer-schema.prisma`. A model change must land in **all** of them. +- Migrations per provider: `prisma/postgresql-migrations/`, `prisma/mysql-migrations/`. +- Scripts resolve the provider via `runWithProvider.js` and `DATABASE_PROVIDER` + (`package.json:17-22`). Always scope queries by `instanceId`. + +## Cache + +`CacheEngine` (`src/cache/cacheengine.ts:10`) picks Redis when `CACHE.REDIS.ENABLED` and a +URI are set, else local node-cache (`cacheengine.ts:19-25`). Wrap it in `CacheService` with a +module name, as `server.module.ts:55-56` does. + +## Commands + +| Purpose | Command | +|---|---| +| Typecheck | `npx tsc --noEmit` | +| Build (typecheck + tsup) | `npm run build` (`package.json:8`) | +| Lint check / fix | `npm run lint:check` / `npm run lint` (`package.json:13-14`) | +| Dev server | `npm run dev:server` | +| Prisma client | `npm run db:generate` (needs `DATABASE_PROVIDER`) | + +There is **no test suite** (`package.json:12` points at a non-existent `test/all.test.ts`). +Verification = `tsc --noEmit` + `npm run build` + `npm run lint:check` + manual run. + +## Rules + +- Style: ESLint + Prettier, 2 spaces, single quotes, 120 cols, `simple-import-sort`. +- Path aliases (`@api/*`, `@config/*`, `@utils/*`, `@exceptions`) — never deep relative imports. +- Keep diffs minimal: this is a fork; every unnecessary change costs on upstream cherry-picks + (see `purple-upstream-sync`). +- Any change to a REST route or webhook payload is a contract change: read `purple-contract`. diff --git a/.claude/skills/purple-typescript/references/conventions.md b/.claude/skills/purple-typescript/references/conventions.md new file mode 100644 index 0000000000..e347607150 --- /dev/null +++ b/.claude/skills/purple-typescript/references/conventions.md @@ -0,0 +1,44 @@ +# purple-typescript — conventions detail + +## Adding an endpoint (checklist) + +1. DTO in `src/api/dto/.dto.ts` — plain class, optional fields with `?`. +2. Schema in `src/validate/.schema.ts` — `JSONSchema7` with `$id: v4()`; export it from + `src/validate/validate.schema.ts`. +3. Service method (or channel-service method on `ChannelStartupService` subclasses when it + depends on the WhatsApp provider). +4. Controller method — one line delegating to the service. +5. Router — `this.router.post(this.routerPath('name'), ...guards, async (req, res) => { + const response = await this.dataValidate({ request: req, schema, ClassRef: Dto, + execute: (instance, data) => controller.method(instance, data) }); res.status(201).json(response); })`. +6. If it is a new router, mount it in `src/api/routes/index.router.ts:217-230`. +7. If it is a new service/controller, instantiate it in `src/api/server.module.ts` next to its + siblings and export the controller. +8. Run `npx tsc --noEmit`, `npm run build`, `npm run lint:check`. +9. Update `purple-contract/references/consumer-matrix.md` if a consumer will call it. + +## RouterBroker details + +- `routerPath(path, param = true)` — pass `false` for routes without `:instanceName`. +- `dataValidate` special-cases `/instance/create` by merging the body into the instance DTO + (`src/api/abstract/abstract.router.ts:40-42`). +- `groupNoValidate` / `groupValidate` variants exist for group routes (`abstract.router.ts:65+`). +- Validation error messages come from the schema `description` when present. + +## server.module.ts + +- Order matters: `configService`, caches (`:52-56`), `providerFiles` (`:60`), + `prismaRepository` (`:63`), `waMonitor` (`:65`), then services/controllers. +- Chatbot integrations share `openaiService` (`:119`) as a constructor arg. + +## Prisma + +- Edit the three schema files consistently; types differ by provider (`@db.JsonB` vs `@db.Json`, + `@db.Integer` vs `@db.Int`). +- Generate migrations with `npm run db:migrate:dev` per provider; the script copies + `prisma/migrations` back into the provider folder. +- Destructive migrations require explicit user approval. + +## Logging + +`new Logger('Context')` from `@config/logger.config`; use `verbose` for chatty paths. diff --git a/.claude/skills/purple-upstream-sync/SKILL.md b/.claude/skills/purple-upstream-sync/SKILL.md new file mode 100644 index 0000000000..912bc414af --- /dev/null +++ b/.claude/skills/purple-upstream-sync/SKILL.md @@ -0,0 +1,68 @@ +--- +name: purple-upstream-sync +description: > + Procedure to backport fixes from upstream Evolution API into evolution-in-purple by + cherry-pick, with provenance, conflict resolution, build verification and a denylist of + upstream changes that must never be imported (license activation, telemetry, EvoHub, + mass dependency bumps). + Trigger: backport, cherry-pick, upstream fix, "sync with upstream", porting an Evolution API PR or commit. +--- + +# Skill: purple-upstream-sync + +Base: tag `2.3.7` (`cd800f29 Merge branch 'release/2.3.7'`). The fork does **not** merge or +rebase onto upstream; it imports selected commits only. + +## Remote + +```bash +git remote -v # upstream must exist +# if missing or pointing elsewhere (e.g. the old EvolutionAPI org): +git remote add upstream https://github.com/evolution-foundation/evolution-api.git \ + || git remote set-url upstream https://github.com/evolution-foundation/evolution-api.git +git fetch upstream --tags +``` + +`upstream` must point to `https://github.com/evolution-foundation/evolution-api.git` +(`EvolutionAPI/evolution-api` is the old org name and only redirects). Never push to upstream. + +## Procedure (one backport = one branch = one PR) + +1. Identify the source: upstream PR number and commit sha(s). `git log upstream/main --oneline` + or `gh pr view --repo evolution-foundation/evolution-api`. +2. Check the denylist below. If the commit touches any denylisted area, stop and ask. +3. Branch from `main`: `git switch -c backport/`. +4. Cherry-pick with provenance: + - normal commit: `git cherry-pick -x ` + - merge commit: `git cherry-pick -x -m 1 ` +5. Resolve conflicts preserving fork behavior (contract, `@lid` rules). Prefer the minimal + hunk; drop unrelated upstream refactors. +6. Amend the message body (keep the `(cherry picked from commit ...)` trailer) with: + ``` + Upstream: evolution-foundation/evolution-api# () + Conflicts: + ``` +7. Verify: `npx tsc --noEmit`, `npm run build`, `npm run lint:check`; manual run if it touches + Baileys (`purple-baileys`). +8. If the change alters routes or payloads, follow `purple-contract` before opening the PR. +9. Add a `CHANGELOG.md` line under the `evolution-in-purple` section (see `purple-release`). +10. Push / open PR only with explicit user approval. + +## Denylist (never import) + +| Upstream change | Reason | +|---|---| +| License activation — PR #2530, commit `3fb04ed6` | Phones home / gates features | +| Telemetry additions or expansions (`src/utils/sendTelemetry.ts` call sites) | Data leaves the deployment | +| EvoHub — PRs #2577, #2636 | Upstream SaaS coupling | +| Mass dependency bump — PR #2575 | Unreviewable; bump deps individually instead | + +A commit that mixes a wanted fix with a denylisted area: port the fix by hand, cite the source +sha in the body as `Adapted-from:` instead of `-x`. + +## Conflict hot spots + +`src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts`, `package.json` / +`package-lock.json` (Baileys pin `package.json:80`), the three `prisma/*-schema.prisma` files +and provider migrations. For lockfile conflicts, take ours and re-run the install only with user +approval. diff --git a/.env.example b/.env.example index 73a3b40d35..090f4df1d7 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ SSL_CONF_FULLCHAIN=/path/to/cert.crt SENTRY_DSN= # Telemetry - Set to false to disable telemetry -TELEMETRY_ENABLED=true +TELEMETRY_ENABLED=false TELEMETRY_URL= # Prometheus metrics - Set to true to enable Prometheus metrics diff --git a/.eslintrc.js b/.eslintrc.js index 8f54a77661..f869514184 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,7 +1,7 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { - project: 'tsconfig.json', + project: ['tsconfig.json', 'tsconfig.test.json'], tsconfigRootDir: __dirname, sourceType: 'module', warnOnUnsupportedTypeScriptVersion: false, diff --git a/.github/workflows/check_code_quality.yml b/.github/workflows/check_code_quality.yml index df156f2117..ef6315a6fd 100644 --- a/.github/workflows/check_code_quality.yml +++ b/.github/workflows/check_code_quality.yml @@ -19,7 +19,7 @@ jobs: - name: Install Node uses: actions/setup-node@v5 with: - node-version: 20.x + node-version: 24.x - name: Cache node modules uses: actions/cache@v4 diff --git a/.github/workflows/image-scan.yml b/.github/workflows/image-scan.yml new file mode 100644 index 0000000000..163345fd41 --- /dev/null +++ b/.github/workflows/image-scan.yml @@ -0,0 +1,75 @@ +name: Image vulnerability scan + +on: + schedule: + - cron: '0 9 * * *' + workflow_dispatch: + +permissions: + contents: read + packages: read + security-events: write + +jobs: + scan: + if: github.repository_owner == 'purple-dice' + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Resolve the latest released image + id: image + run: | + tag="$(git tag -l 'v*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)" + test -n "$tag" + echo "ref=ghcr.io/${GITHUB_REPOSITORY}:${tag#v}" >> "$GITHUB_OUTPUT" + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Scan published image + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: ${{ steps.image.outputs.ref }} + format: sarif + output: trivy.sarif + severity: CRITICAL,HIGH + ignore-unfixed: true + limit-severities-for-sarif: true + + - name: Upload to code scanning + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 + with: + sarif_file: trivy.sarif + category: image-release + + - name: Fail on fixable critical or high findings + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: ${{ steps.image.outputs.ref }} + format: table + output: trivy.txt + severity: CRITICAL,HIGH + ignore-unfixed: true + exit-code: '1' + skip-setup-trivy: true + + - name: Write job summary + if: always() + env: + IMAGE_REF: ${{ steps.image.outputs.ref }} + run: | + { + echo "### Fixable CRITICAL/HIGH findings in ${IMAGE_REF}" + echo '```' + cat trivy.txt 2>/dev/null || echo 'The scan did not produce a findings table; see the scan step logs.' + echo '```' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/image-smoke-test.yml b/.github/workflows/image-smoke-test.yml new file mode 100644 index 0000000000..68f7d460ab --- /dev/null +++ b/.github/workflows/image-smoke-test.yml @@ -0,0 +1,103 @@ +name: Image smoke test + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + smoke: + runs-on: ubuntu-latest + timeout-minutes: 30 + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_USER: evolution + POSTGRES_PASSWORD: evolution + POSTGRES_DB: evolution + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U evolution" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + submodules: recursive + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image (amd64) + uses: docker/build-push-action@v6 + with: + context: . + load: true + tags: evolution-api:smoke + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Node.js in the image matches the Dockerfile pin + run: | + pins="$(grep -oE '^FROM node:[0-9]+\.[0-9]+\.[0-9]+' Dockerfile | sed 's/^FROM node://' | sort -u)" + test "$(echo "$pins" | wc -l)" -eq 1 + expected="v$pins" + actual="$(docker run --rm --entrypoint node evolution-api:smoke -v)" + echo "expected=$expected actual=$actual" + test "$expected" = "$actual" + + - name: Start the container + run: | + docker run -d --name evolution --network host \ + -e SERVER_PORT=8080 \ + -e DATABASE_PROVIDER=postgresql \ + -e DATABASE_CONNECTION_URI='postgresql://evolution:evolution@localhost:5432/evolution?schema=evolution_api' \ + -e CACHE_REDIS_ENABLED=false \ + -e CACHE_LOCAL_ENABLED=true \ + -e AUTHENTICATION_API_KEY=smoke-test-key \ + -e TELEMETRY_ENABLED=false \ + evolution-api:smoke + + - name: Wait for HTTP 200 on / + run: | + for _ in $(seq 1 60); do + if curl -fsS --max-time 5 http://localhost:8080/ -o body.json; then + cat body.json + grep -q '"status":200' body.json + exit 0 + fi + if [ "$(docker inspect -f '{{.State.Running}}' evolution)" != "true" ]; then + echo "container exited" + exit 1 + fi + sleep 5 + done + echo "timed out waiting for the API" + exit 1 + + - name: Authenticated route answers + run: | + curl -fsS --max-time 10 -H 'apikey: smoke-test-key' http://localhost:8080/instance/fetchInstances + + - name: Migrations ran on start + env: + PGPASSWORD: evolution + run: | + expected=$(find prisma/postgresql-migrations -mindepth 1 -maxdepth 1 -type d | wc -l) + applied=$(psql -h localhost -U evolution -d evolution -At -v ON_ERROR_STOP=1 \ + -c 'select count(*) from evolution_api."_prisma_migrations" where finished_at is not null and rolled_back_at is null') + echo "expected=$expected applied=$applied" + test "$expected" -eq "$applied" + psql -h localhost -U evolution -d evolution -v ON_ERROR_STOP=1 -c 'select count(*) from evolution_api."IsOnWhatsappJid"' + + - name: Container logs + if: always() + run: docker logs evolution 2>&1 | tail -200 diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml index c5a3996edb..7af0ab6b71 100644 --- a/.github/workflows/publish_docker_image.yml +++ b/.github/workflows/publish_docker_image.yml @@ -1,9 +1,11 @@ -name: Build Docker image +name: Publish Docker image (GHCR) +# Fork publishing: version tags (v*) push to ghcr.io/purple-dice/evolution-in-purple. +# Upstream pushed to Docker Hub (evoapicloud/evolution-api); that is disabled in this fork. on: push: tags: - - "*.*.*" + - 'v*' jobs: build_deploy: @@ -22,8 +24,12 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: evoapicloud/evolution-api - tags: type=semver,pattern=v{{version}} + images: ghcr.io/purple-dice/evolution-in-purple + flavor: | + latest=false + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -34,8 +40,9 @@ jobs: - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push id: docker_build @@ -47,4 +54,4 @@ jobs: labels: ${{ steps.meta.outputs.labels }} - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} \ No newline at end of file + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/publish_docker_image_homolog.yml b/.github/workflows/publish_docker_image_homolog.yml index a76e1008be..03b0d3f1d1 100644 --- a/.github/workflows/publish_docker_image_homolog.yml +++ b/.github/workflows/publish_docker_image_homolog.yml @@ -1,50 +1,13 @@ -name: Build Docker image +name: Build Docker image (homolog) - disabled +# Disabled in evolution-in-purple: upstream pushed to Docker Hub (evoapicloud/evolution-api), +# which this fork does not own. Fork images are published to GHCR by publish_docker_image.yml +# on v* tags. Kept as a manual no-op so the file can be deleted in a dedicated change. on: - push: - branches: - - develop + workflow_dispatch: jobs: - build_deploy: - name: Build and Deploy + disabled: runs-on: ubuntu-latest - permissions: - contents: read - packages: write steps: - - name: Checkout - uses: actions/checkout@v5 - with: - submodules: recursive - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: evoapicloud/evolution-api - tags: homolog - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v6 - with: - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} + - run: echo "Disabled in this fork; see publish_docker_image.yml (GHCR on v* tags)." diff --git a/.github/workflows/publish_docker_image_latest.yml b/.github/workflows/publish_docker_image_latest.yml index f73fe80e4e..60a84f74cd 100644 --- a/.github/workflows/publish_docker_image_latest.yml +++ b/.github/workflows/publish_docker_image_latest.yml @@ -1,50 +1,13 @@ -name: Build Docker image +name: Build Docker image (latest) - disabled +# Disabled in evolution-in-purple: upstream pushed to Docker Hub (evoapicloud/evolution-api), +# which this fork does not own. Fork images are published to GHCR by publish_docker_image.yml +# on v* tags. Kept as a manual no-op so the file can be deleted in a dedicated change. on: - push: - branches: - - main + workflow_dispatch: jobs: - build_deploy: - name: Build and Deploy + disabled: runs-on: ubuntu-latest - permissions: - contents: read - packages: write steps: - - name: Checkout - uses: actions/checkout@v5 - with: - submodules: recursive - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: evoapicloud/evolution-api - tags: latest - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v6 - with: - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} + - run: echo "Disabled in this fork; see publish_docker_image.yml (GHCR on v* tags)." diff --git a/.github/workflows/purple-checks.yml b/.github/workflows/purple-checks.yml new file mode 100644 index 0000000000..cba83272b7 --- /dev/null +++ b/.github/workflows/purple-checks.yml @@ -0,0 +1,47 @@ +name: Purple Checks + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + checks: + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + DATABASE_PROVIDER: postgresql + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-node@v5 + with: + node-version: 24.x + cache: npm + + - name: Install packages + run: npm ci + + - name: Generate Prisma client + run: npm run db:generate + + - name: Typecheck + run: npx tsc --noEmit + + - name: Typecheck tests + run: npm run typecheck:test + + - name: Build + run: npm run build + + - name: Lint + run: npm run lint:check + + - name: Unit tests + run: npm test + + - name: Check docs + run: bash scripts/check-docs.sh diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index b83f0b5d98..0407098b41 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -53,3 +53,5 @@ jobs: submodules: recursive - name: Dependency Review uses: actions/dependency-review-action@v4 + with: + allow-ghsas: GHSA-vfj7-8cjw-p6xm diff --git a/.gitignore b/.gitignore index 768d8afa41..cddea7dcd1 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,6 @@ lerna-debug.log* # Project related /instances/* !/instances/.gitkeep -/test/ /src/env.yml /store *.env @@ -46,3 +45,7 @@ lerna-debug.log* .tool-versions /prisma/migrations/* + +# ODD feature docs are local working notes (public repo) +odd/tasks/ +odd/archive/ diff --git a/.husky/pre-commit b/.husky/pre-commit index 2312dc587f..f07aa1278d 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1,6 @@ npx lint-staged + +# Docs drift guard: only when agent docs/tooling are staged. +if git diff --cached --name-only | grep -qE '^(AGENTS\.md|CLAUDE\.md|Makefile|\.claude/|\.pi/|odd/)'; then + bash scripts/check-docs.sh +fi diff --git a/.pi/settings.json b/.pi/settings.json new file mode 100644 index 0000000000..130a365e75 --- /dev/null +++ b/.pi/settings.json @@ -0,0 +1,3 @@ +{ + "skills": ["../.claude/skills"] +} diff --git a/.pi/subagents.json b/.pi/subagents.json new file mode 100644 index 0000000000..2c3d257cbf --- /dev/null +++ b/.pi/subagents.json @@ -0,0 +1,22 @@ +{ + "kind": "gentle-pi.agent-model-config", + "version": 1, + "model_profiles": { + "review-risk": { + "model": "nan/mimo-v2.6-flash", + "effort": "medium" + }, + "review-resilience": { + "model": "nan/glm5.3-flash", + "effort": "low" + }, + "review-readability": { + "model": "nan/glm5.3-flash", + "effort": "low" + }, + "review-reliability": { + "model": "nan/glm5.3-flash", + "effort": "low" + } + } +} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 143e6c748e..4e288657b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,355 +1,221 @@ -# Evolution API - AI Agent Guidelines - -This document provides comprehensive guidelines for AI agents (Claude, GPT, Cursor, etc.) working with the Evolution API codebase. - -## Project Overview - -**Evolution API** is a production-ready, multi-tenant WhatsApp API platform built with Node.js, TypeScript, and Express.js. It supports multiple WhatsApp providers and extensive integrations with chatbots, CRM systems, and messaging platforms. - -## Project Structure & Module Organization - -### Core Directories -- **`src/`** – TypeScript source code with modular architecture - - `api/controllers/` – HTTP route handlers (thin layer) - - `api/services/` – Business logic (core functionality) - - `api/routes/` – Express route definitions (RouterBroker pattern) - - `api/integrations/` – External service integrations - - `channel/` – WhatsApp providers (Baileys, Business API, Evolution) - - `chatbot/` – AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot) - - `event/` – Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher) - - `storage/` – File storage (S3, MinIO) - - `dto/` – Data Transfer Objects (simple classes, no decorators) - - `guards/` – Authentication/authorization middleware - - `types/` – TypeScript type definitions - - `repository/` – Data access layer (Prisma) -- **`prisma/`** – Database schemas and migrations - - `postgresql-schema.prisma` / `mysql-schema.prisma` – Provider-specific schemas - - `postgresql-migrations/` / `mysql-migrations/` – Provider-specific migrations -- **`config/`** – Environment and application configuration -- **`utils/`** – Shared utilities and helper functions -- **`validate/`** – JSONSchema7 validation schemas -- **`exceptions/`** – Custom HTTP exception classes -- **`cache/`** – Redis and local cache implementations - -### Build & Deployment -- **`dist/`** – Build output (do not edit directly) -- **`public/`** – Static assets and media files -- **`Docker*`**, **`docker-compose*.yaml`** – Containerization and local development stack - -## Build, Test, and Development Commands - -### Development Workflow -```bash -# Development server with hot reload -npm run dev:server +# AGENTS.md -# Direct execution for testing -npm start +Canonical index for `purple-dice/evolution-in-purple`, a fork of Evolution API 2.3.7 (TypeScript, Express, +Prisma, Baileys 7.0.0-rc.9). Read by Pi (gentle-ai) and Claude Code; `CLAUDE.md` only points here. Detailed +patterns live in the skills under `.claude/skills/`, loaded on demand. + +This engine replaces Evolution API for three consumer products: **orbit**, **canal-whatsapp** (a.k.a. alien) +and **stenfar**. Anything they consume is a public contract. + +## Repo map + +| Path | Content | +| --- | --- | +| `src/main.ts` | Bootstrap (Express server, routers, monitors) | +| `src/api/controllers/` | HTTP handlers (thin layer) | +| `src/api/services/` | Business logic; `monitor.service.ts` owns the WhatsApp instance registry | +| `src/api/routes/` | Express routes (`RouterBroker` pattern) | +| `src/api/guards/` | API-key auth and instance resolution (security-critical) | +| `src/api/dto/`, `src/validate/` | Plain-class DTOs and JSONSchema7 validation | +| `src/api/integrations/channel/` | WhatsApp providers: Baileys (`whatsapp/`), Meta Business API (`meta/`), `evolution/` | +| `src/api/integrations/chatbot/` | Chatwoot, Typebot, OpenAI, Dify, Flowise, N8N, EvoAI, EvolutionBot | +| `src/api/integrations/event/` | Webhook, WebSocket, RabbitMQ, SQS, NATS, Kafka, Pusher | +| `src/api/integrations/storage/` | S3 / MinIO | +| `src/config/`, `src/cache/`, `src/utils/`, `src/exceptions/` | Env config, Redis/local cache, helpers, HTTP exceptions | +| `prisma/` | Three schemas (`postgresql-schema.prisma`, `mysql-schema.prisma`, `psql_bouncer-schema.prisma`) + `postgresql-migrations/` and `mysql-migrations/` | +| `odd/` | Active feature docs (`odd/tasks/`), closed ones (`odd/archive/`), `odd/agent-protocol.md` | +| `.claude/skills/purple-*` | Project skills (shared with Pi via `.pi/settings.json`) | +| `VERSION`, `CHANGELOG.md` | Fork versioning, managed by the `purple-release` skill | +| `dist/` | Build output — never edit | -# Production build and run -npm run build -npm run start:prod -``` +## Commands & danger zones -### Code Quality -```bash -# Linting and formatting -npm run lint # ESLint with auto-fix -npm run lint:check # ESLint check only +- **Local / safe:** `npm run dev:server` (tsx watch), `npm start`, `npm run build` (`tsc --noEmit && tsup`), + `npm test` (Vitest, `test/**/*.test.ts`), `npm run lint:check` (src + test), `npx tsc --noEmit`, + `npm run typecheck:test` (src + test + `vitest.config.ts`), `make check-docs`, + `npm run db:generate`, `docker-compose -f docker-compose.dev.yaml up -d`. +- **Database:** always `export DATABASE_PROVIDER=postgresql` (or `mysql`) first. `npm run db:migrate:dev` + only against a local database. Every schema change ships migrations for BOTH providers. +- **PROD — never without explicit user approval:** `npm run db:deploy`, image publish workflows, any deploy, + release tag, or push to a shared environment. +- `npm run lint` auto-fixes files; prefer `lint:check` when verifying. -# Commit with conventional commits -npm run commit # Interactive commit with Commitizen -``` +## Commits -### Database Management -```bash -# Set database provider first (CRITICAL) -export DATABASE_PROVIDER=postgresql # or mysql - -# Generate Prisma client -npm run db:generate +- Conventional Commits (enforced by commitlint), English, subject <= 100 chars. +- Tests and docs travel in the same commit as the behavior they cover. +- Husky runs `lint-staged` on pre-commit and `build` + `lint:check` on pre-push. Never `--no-verify`. +- No `Co-Authored-By` or AI attribution. +- The parent agent commits; subagents never commit (`odd/agent-protocol.md` §2). +- Push, PR and merge are user decisions. Releases only via the `purple-release` skill. -# Development migrations (with provider sync) -npm run db:migrate:dev # Unix/Mac -npm run db:migrate:dev:win # Windows +## Hard rules -# Production deployment -npm run db:deploy # Unix/Mac -npm run db:deploy:win # Windows +### (a) Consumer contract -# Database tools -npm run db:studio # Open Prisma Studio -``` +Webhook payloads (event names, field names, shapes) and REST routes (paths, params, response shapes) +consumed by orbit, canal-whatsapp or stenfar MUST NOT break without versioning and a `CHANGELOG.md` note. +Adding optional fields is fine; renaming, removing, retyping or changing semantics is a breaking change. +Procedure: skill `purple-contract`. -### Docker Development -```bash -# Start local services (Redis, PostgreSQL, etc.) -docker-compose up -d +### (b) Upstream sync -# Full development stack -docker-compose -f docker-compose.dev.yaml up -d -``` +- We cherry-pick from `evolution-foundation/evolution-api` (git remote `upstream`); we never merge it wholesale. +- NEVER import license activation, telemetry or EvoHub code. +- Record the source PR number and/or upstream sha in the commit body (e.g. `Upstream: #1234 / abc1234`). +- Procedure: skill `purple-upstream-sync`. -## Coding Standards & Architecture Patterns +### (c) Multi-instance isolation -### Code Style (Enforced by ESLint + Prettier) -- **TypeScript strict mode** with full type coverage -- **2-space indentation**, single quotes, trailing commas -- **120-character line limit** -- **Import order** via `simple-import-sort` -- **File naming**: `feature.kind.ts` (e.g., `whatsapp.baileys.service.ts`) -- **Naming conventions**: - - Classes: `PascalCase` - - Functions/variables: `camelCase` - - Constants: `UPPER_SNAKE_CASE` - - Files: `kebab-case.type.ts` +Several clients share one server. Every operation is scoped by `instanceName` / `instanceId`; every Prisma +query on tenant data includes `where: { instanceId }`. A request authenticated for one instance must never +read or mutate another. Changes to auth guards, API-key handling or instance resolution are +**security-critical**: call them out in the PR and get explicit review. -### Architecture Patterns +### (d) WhatsApp JIDs -#### Service Layer Pattern -```typescript -export class ExampleService { - constructor(private readonly waMonitor: WAMonitoringService) {} - - private readonly logger = new Logger('ExampleService'); - - public async create(instance: InstanceDto, data: ExampleDto) { - // Business logic here - return { example: { ...instance, data } }; - } - - public async find(instance: InstanceDto): Promise { - try { - const result = await this.waMonitor.waInstances[instance.instanceName].findData(); - return result || null; // Return null on not found (Evolution pattern) - } catch (error) { - this.logger.error('Error finding data:', error); - return null; // Return null on error (Evolution pattern) - } - } -} -``` +Preserve `@lid` addressing end to end. Never strip a JID with `split('@')[0]` (or equivalent) to build an +outbound target — that turns a `@lid` into a bogus phone number and the message goes nowhere or to the wrong +user. Normalize only through the existing JID helpers. Detail: skill `purple-baileys`. -#### Controller Pattern (Thin Layer) -```typescript -export class ExampleController { - constructor(private readonly exampleService: ExampleService) {} - - public async createExample(instance: InstanceDto, data: ExampleDto) { - return this.exampleService.create(instance, data); - } -} -``` +### (e) No code comments -#### RouterBroker Pattern -```typescript -export class ExampleRouter extends RouterBroker { - constructor(...guards: any[]) { - super(); - this.router.post(this.routerPath('create'), ...guards, async (req, res) => { - const response = await this.dataValidate({ - request: req, - schema: exampleSchema, // JSONSchema7 - ClassRef: ExampleDto, - execute: (instance, data) => controller.createExample(instance, data), - }); - res.status(201).json(response); - }); - } -} -``` +Code must be self-declarative: never add comments to source code. Express intent through names — extract +a well-named function, constant or variable instead of explaining a line. Rationale belongs in the commit +body and the PR description. Existing upstream comments may stay; do not add new ones. -#### DTO Pattern (Simple Classes) -```typescript -// CORRECT - Evolution API pattern (no decorators) -export class ExampleDto { - name: string; - description?: string; - enabled: boolean; -} +## Skills per task -// INCORRECT - Don't use class-validator decorators -export class BadExampleDto { - @IsString() // ❌ Evolution API doesn't use decorators - name: string; -} -``` +Read the matching skill BEFORE writing code. Skills live in `.claude/skills//SKILL.md`. -#### Validation Pattern (JSONSchema7) -```typescript -import { JSONSchema7 } from 'json-schema'; -import { v4 } from 'uuid'; - -export const exampleSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - name: { type: 'string' }, - description: { type: 'string' }, - enabled: { type: 'boolean' }, - }, - required: ['name', 'enabled'], -}; -``` +| Task | Skills | +| --- | --- | +| Any TypeScript change in `src/` | `purple-typescript` | +| Baileys channel, JIDs, connection/session, message send/receive | `purple-baileys` + `purple-typescript` | +| Webhook payload, event, REST route or DTO consumed externally | `purple-contract` + `purple-typescript` | +| Cherry-picking / porting from upstream | `purple-upstream-sync` | +| Version bump, `CHANGELOG.md`, image publish | `purple-release` | -## Multi-Tenant Architecture +## How this repo fits ODD -### Instance Isolation -- **CRITICAL**: All operations must be scoped by `instanceName` or `instanceId` -- **Database queries**: Always include `where: { instanceId: ... }` -- **Authentication**: Validate instance ownership before operations -- **Data isolation**: Complete separation between tenant instances +ODD (gentle-ai) is the workflow; its protocol is not repeated here. What this repo adds: -### WhatsApp Instance Management -```typescript -// Access instance via WAMonitoringService -const waInstance = this.waMonitor.waInstances[instance.instanceName]; -if (!waInstance) { - throw new NotFoundException(`Instance ${instance.instanceName} not found`); -} -``` +- **Feature docs:** each feature lives in `odd/tasks/.md` (objective, scope, checklist, acceptance + criteria, evidence with commit ids). Closed features move to `odd/archive/`. Both folders are gitignored: + this repo is public, so feature docs stay local and unfixed security findings never go into commits, + PR descriptions or issues. +- **Operational rules** for agents and parents: `odd/agent-protocol.md`. +- **Contract first:** if a change touches anything a consumer reads, the first task states the contract + change (skill `purple-contract`). + +## CodeGraph — ordering rule + +- Structural questions (map, call flow, references, impact) are answered with CodeGraph **before** any + `grep`/`find`. Read-only commands only: `status`, `query`, `explore`, `context`, `node`, `files`, + `callers`, `callees`, `impact`, `affected`. +- Each worktree has its own `.codegraph/` index. `index` only to recover a corrupt index. +- Fall back to `grep`/`find`/`read` only if CodeGraph fails, and say so explicitly. -## Database Patterns +## Verification -### Multi-Provider Support -- **PostgreSQL**: Uses `@db.Integer`, `@db.JsonB`, `@default(now())` -- **MySQL**: Uses `@db.Int`, `@db.Json`, `@default(now())` -- **Environment**: Set `DATABASE_PROVIDER=postgresql` or `mysql` -- **Migrations**: Provider-specific folders auto-selected +Unit tests use Vitest and live in `test/`, mirroring `src/` (e.g. `test/utils/createJid.test.ts`). They cover +how our code processes data; Baileys is mocked where it touches the socket. Before handing off a change, run: -### Prisma Repository Pattern -```typescript -// Always use PrismaRepository for database operations -const result = await this.prismaRepository.instance.findUnique({ - where: { name: instanceName }, -}); +```bash +npm test +npx tsc --noEmit +npm run typecheck:test +npm run build +npm run lint:check +make check-docs # docs drift guard (pointers, skill frontmatter) ``` -## Integration Patterns +Behavior that needs a real WhatsApp account (pairing, sending, receiving) requires a human with a phone; +report it as unverified rather than faking it. When tests are added, they ship with the behavior. -### Channel Integration (WhatsApp Providers) -- **Baileys**: WhatsApp Web with QR code authentication -- **Business API**: Official Meta WhatsApp Business API -- **Evolution API**: Custom WhatsApp integration -- **Pattern**: Extend base channel service classes +Known bugs in tests: a test that pins buggy behavior is named `it('KNOWN BUG: (current behavior)')` +and sits next to an `it.fails('')` asserting the intended behavior. When you fix the bug, the +`it.fails` starts failing; replace both with one normal test for the correct contract. Skip the `it.fails` only when +the correct contract is unclear (e.g. Argentine/Mexican numbers in `createJid`). -### Chatbot Integration -- **Base classes**: Extend `BaseChatbotService` and `BaseChatbotController` -- **Trigger system**: Support keyword, regex, and advanced triggers -- **Session management**: Handle conversation state per user -- **Available integrations**: EvolutionBot, OpenAI, Dify, Typebot, Chatwoot, Flowise, N8N, EvoAI +## Documentation conventions -### Event Integration -- **Internal events**: EventEmitter2 for application events -- **External events**: WebSocket, RabbitMQ, SQS, NATS, Pusher -- **Webhook delivery**: Reliable delivery with retry logic +1. This file is an index: hard rules + pointers. Implementation detail goes to skills. +2. Whoever changes behavior updates the affected skill or this index in the same commit. +3. Changes to the hard rules are decided by the user. +4. Run `make check-docs` before committing doc changes. -## Testing Guidelines +## Codebase reference -### Current State -- **No formal test suite** currently implemented -- **Manual testing** is the primary approach -- **Integration testing** in development environment +Condensed from the upstream Evolution API guidelines. -### Testing Strategy -```typescript -// Place tests in test/ directory as *.test.ts -// Run: npm test (watches test/all.test.ts) - -describe('ExampleService', () => { - it('should create example', async () => { - // Mock external dependencies - // Test business logic - // Assert expected behavior - }); -}); -``` +### Architecture -### Recommended Approach -- Focus on **critical business logic** in services -- **Mock external dependencies** (WhatsApp APIs, databases) -- **Integration tests** for API endpoints -- **Manual testing** for WhatsApp connection flows +- **Multi-provider DB:** PostgreSQL (`@db.Integer`, `@db.JsonB`) and MySQL (`@db.Int`, `@db.Json`) via Prisma, + provider chosen by `DATABASE_PROVIDER`; `runWithProvider.js` substitutes it into schema/migration paths. +- **Events:** EventEmitter2 internally; external delivery via webhook (with retry), WebSocket, RabbitMQ, SQS, + NATS, Kafka, Pusher. All go through `event.manager.ts`. +- **Cache:** Redis primary, node-cache fallback; set explicit TTLs and invalidate on writes. +- **Instances:** `WAMonitoringService.waInstances[instanceName]` holds live channel services; one WhatsApp + connection per instance with lifecycle management. +- **Media:** stored in S3/MinIO when enabled; otherwise inline base64. -## Commit & Pull Request Guidelines +### Patterns -### Conventional Commits (Enforced by commitlint) -```bash -# Use interactive commit tool -npm run commit +```typescript +// Service: business logic, Evolution returns null on not-found +export class ExampleService { + constructor(private readonly waMonitor: WAMonitoringService) {} + private readonly logger = new Logger('ExampleService'); -# Commit format: type(scope): subject (max 100 chars) -# Types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert, security -``` + public async find(instance: InstanceDto): Promise { + try { + return (await this.waMonitor.waInstances[instance.instanceName].findData()) || null; + } catch (error) { + this.logger.error('Error finding data:', error); + return null; + } + } +} -### Examples -- `feat(api): add WhatsApp message status endpoint` -- `fix(baileys): resolve connection timeout issue` -- `docs(readme): update installation instructions` -- `refactor(service): extract common message validation logic` +// Controller: thin pass-through +export class ExampleController { + constructor(private readonly exampleService: ExampleService) {} + public async find(instance: InstanceDto) { + return this.exampleService.find(instance); + } +} -### Pull Request Requirements -- **Clear description** of changes and motivation -- **Linked issues** if applicable -- **Migration impact** (specify database provider) -- **Local testing steps** with screenshots/logs -- **Breaking changes** clearly documented +// Router: RouterBroker + JSONSchema7 validation +this.router.post(this.routerPath('create'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: exampleSchema, + ClassRef: ExampleDto, + execute: (instance, data) => controller.create(instance, data), + }); + res.status(201).json(response); +}); +``` -## Security & Configuration +- **DTOs** are plain classes — no `class-validator` decorators. Validation is JSONSchema7 in `src/validate/` + (`$id: v4()`, explicit `required`). +- **Instance lookup:** throw `NotFoundException` when `waMonitor.waInstances[name]` is missing. +- **Chatbots** extend `BaseChatbotService` / `BaseChatbotController` (keyword, regex, advanced triggers; + per-contact sessions). +- **Channels** extend the base channel service; keep provider-specific logic inside its folder. -### Environment Setup -```bash -# Copy example environment file -cp .env.example .env +### Code style -# NEVER commit secrets to version control -# Set DATABASE_PROVIDER before database commands -export DATABASE_PROVIDER=postgresql # or mysql -``` +- TypeScript strict, 2-space indent, single quotes, trailing commas, 120-char lines, `simple-import-sort`. +- Files `feature.kind.ts` (e.g. `whatsapp.baileys.service.ts`); classes `PascalCase`, functions/variables + `camelCase`, constants `UPPER_SNAKE_CASE`. +- Logging via the project `Logger` (Pino); never log API keys or tokens. +- Code, API responses and docs in English. No code comments (hard rule (e)). -### Security Best Practices -- **API key authentication** via `apikey` header -- **Input validation** with JSONSchema7 -- **Rate limiting** on all endpoints -- **Webhook signature validation** -- **Instance-based access control** -- **Secure defaults** for all configurations - -### Vulnerability Reporting -- See `SECURITY.md` for security vulnerability reporting process -- Contact: `contato@evolution-api.com` - -## Communication Standards - -### Language Requirements -- **User communication**: Always respond in Portuguese (PT-BR) -- **Code/comments**: English for technical documentation -- **API responses**: English for consistency -- **Error messages**: Portuguese for user-facing errors - -### Documentation Standards -- **Inline comments**: Document complex business logic -- **API documentation**: Document all public endpoints -- **Integration guides**: Document new integration patterns -- **Migration guides**: Document database schema changes - -## Performance & Scalability - -### Caching Strategy -- **Redis primary**: Distributed caching for production -- **Node-cache fallback**: Local caching when Redis unavailable -- **TTL strategy**: Appropriate cache expiration per data type -- **Cache invalidation**: Proper invalidation on data changes - -### Connection Management -- **Database**: Prisma connection pooling -- **WhatsApp**: One connection per instance with lifecycle management -- **Redis**: Connection pooling and retry logic -- **External APIs**: Rate limiting and retry with exponential backoff - -### Monitoring & Observability -- **Structured logging**: Pino logger with correlation IDs -- **Error tracking**: Comprehensive error scenarios -- **Health checks**: Instance status and connection monitoring -- **Telemetry**: Usage analytics (non-sensitive data only) +### Environment +- Copy `.env.example` to `.env` locally; never commit secrets. +- Key settings: `DATABASE_PROVIDER`, `DATABASE_CONNECTION_URI`, `AUTHENTICATION_API_KEY`, `CACHE_REDIS_*`, + `WEBHOOK_*`, `S3_*`. See `src/config/env.config.ts` for the full list. +- Security reports: `SECURITY.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5102b5375c..3c5d515690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,110 @@ +# evolution-in-purple + +## 0.1.4 (2026-11-05) + +### Fixes + +* **Sent message status**: messages sent by the instance now advance `Message.status` from WhatsApp acks instead of staying `PENDING`, so `findChats` `lastMessageStatus` and `findMessages` reflect the real status. Applies to 1:1 chats; Baileys delivers no per-message status for groups. Backward acks are ignored and `DELETED`/`EDITED` are never overwritten (#36) +* **QR code limit**: unpaired instances whose QR code limit is reached now end in `close` (memory and database) instead of staying `connecting`, so requesting the QR code again starts a fresh connection (#37) +* **Deleted messages resent on retry**: messages deleted for everyone are no longer resent when a device sends a retry receipt, so deleted content no longer reappears. Incoming revokes now mark the stored message `DELETED` and record a `DELETED` message update; the `MESSAGES_DELETE` webhook payload is unchanged (#38) +* **Instance-scoped chat and edit lookups**: recalculating a chat's unread count and editing a sent message now only look up rows of the current instance, so another instance's chat or message with the same `remoteJid` or message id is no longer updated (#39) +* **Payload logging**: the Baileys channel no longer writes full message payloads (including media base64) to stdout on every received message, message update, call or history sync; these logs now go through the logger at `VERBOSE`/`DEBUG` level and include only ids and counts. Webhook payloads are unchanged (#41) +* **Event error isolation**: a failure while processing one message of a `messages.upsert` or `messages.update` batch no longer drops the rest of the batch and their webhooks, and a failing or ignored event type (for example a group `presence.update` with `groupsIgnore`, or a label event) no longer skips the other event types delivered with it. Webhook payloads and event order are unchanged (#42) +* **`@lid` remoteJidAlt**: history sync messages from `@lid` chats now store `key.remoteJidAlt`, and it is backfilled on stored messages when a lid mapping is learned; payload contacts with conflicting lids are skipped (#33, #34) +* **Archive and mark unread**: `archiveChat` and `markChatUnread` no longer fail with 500 when only `chat` is sent: the last message is looked up by JSON path on the jid built from the number, chats without stored messages can be archived, and the fallback timestamp is in seconds. Response shapes are unchanged (#43; based on the fix by Razario) + +### Performance + +* **Message lookup indexes**: PostgreSQL migrations add `idx_message_instance_key_id` (`instanceId`, `key->>'id'`) and `idx_message_instance_remotejid_fromme` (`instanceId`, `key->>'remoteJid'`, `key->>'fromMe'`) so message updates and read receipts no longer scan every message of the instance. They are created `IF NOT EXISTS` inside the migration transaction, which runs before the server starts, so databases that already have them are left untouched and a failed build leaves no invalid index (#40) + +### Chores + +* `scripts/` adds a one-off, row-locking backfill for `@lid` `key.remoteJidAlt` with backup and rollback (#35) +* The daily image scan fails on fixable CRITICAL or HIGH findings (#32) + +## 0.1.3 (2026-10-24) + +### Security + +* **Image vulnerabilities**: the release image goes from 162 fixable CRITICAL/HIGH Trivy findings in 0.1.2 to **0 findings of any severity**, and `npm audit` reports 0 including development dependencies (#26, #27, #29, #30) + * The image ships only production dependencies; `tsup` moves to devDependencies (#26) + * Semver-compatible updates of production dependencies, including axios 1.20, express, undici, multer and ws (#27) + * `@figuro/chatwoot-sdk` runs on the shared axios 1.x instead of axios 0.27.2 (#27) + * sharp 0.35 and link-preview-js 4 (#27); node-cron 4; zod 4 for jimp and openai (#29) + * `minio` is patched to run on query-string 10 and stream-json 3, verified against a SigV4-validating S3 server (#29) + * The image runs without npm/npx: start scripts call node and prisma directly (#29) + * vitest 5 and semver fixes for development tooling (#30) + +### Fixes + +* **Media mimetype**: sending media with a file name without a known extension no longer sets the mimetype to the string "false"; images keep `image/jpeg` and URL media keep the downloaded content type (#28) + +### Chores + +* CI builds the image and smoke-tests it against Postgres on every PR, checking the Node.js pin and that every migration applied (#24; adapted from @nogara) +* A daily Trivy scan reports findings in the latest released image to code scanning (#25; adapted from @nogara) + +### Upgrade notes + +* `npm` and `npx` are no longer available inside the container. Use `node` and `./node_modules/.bin/prisma` for manual operations. +* `zod` is overridden to 4 for jimp and openai; Evolution's OpenAI structured-output helpers are not used. + +## 0.1.2 (2026-10-23) + +### Features + +* **onWhatsapp cache storage**: the cache moves to a new `IsOnWhatsappJid` table with one row per jid and an indexed canonical `remoteJid`. Lookups are two indexed queries and a 2,500-contact sync takes about 30 queries instead of ~5,000; writes are serialized per process. The migration is additive (the old `IsOnWhatsapp` table is kept, so an upstream image still runs) and backfills Postgres from `jidOptions` (#22) + +### Fixes + +* **Database pool exhaustion**: contact syncs no longer open thousands of concurrent onWhatsapp cache queries (#16) +* **Status updates without `remoteJid`**: `messages.update` takes the jid from the stored message, so the webhook carries it and `messageUpdate.create` no longer fails and aborts the batch (#17) +* **Group number changes**: plain-jid `messageStubParameters` no longer throw in `processMessage`, so `group-participants.update` is emitted (#18; Baileys patch) +* **Link previews without an image**: pages without an image get a title and description preview instead of none (#19; Baileys patch) +* **Telemetry**: disabled unless `TELEMETRY_ENABLED=true`; it sent every request path, including instance names, to `log.evolution-api.com` (#21, by @nogara) + +### Chores + +* Docker base image pinned to the official `node:24.22.1-alpine3.24` by digest (#20, by @nogara) + +### Upgrade notes + +* The image applies the new migration on start (`Docker/scripts/deploy_database.sh`); it only adds a table and backfills it. +* Set `TELEMETRY_ENABLED=false` where it is set to `true` explicitly in the environment, since #21 only changes the default. + +## 0.1.1 (2026-10-22) + +### Fixes + +* **Logout state**: `logoutInstance` closes the connection state (401), persists it and sends `STATUS_INSTANCE` and `CONNECTION_UPDATE` immediately instead of ~40s later; `logout.instance` is emitted after the Session row is removed and the late Baileys close is ignored (#9) +* **Argentine JIDs**: `createJid` keeps the mobile 9 (`549…`); the third-digit removal now applies only to Mexico (#10) +* **"Waiting for this message"**: `getMessage` returns no message for unknown keys, so Baileys stops resending an empty message on retry receipts (#11; upstream evolution-foundation/evolution-api#2705, #2706, #1934) +* **Duplicate messages**: live `messages.upsert` stores and emits each message once per recipient, deduping redeliveries and `@lid`/phone copies through the baileys cache; history sync no longer re-stores live messages (#12; upstream evolution-foundation/evolution-api#2737, #2675) +* **onWhatsapp cache**: unique-constraint races in `saveOnWhatsappCache` retry and merge instead of dropping data, and cache JIDs get a single domain (#13) +* **Baileys memory**: one shared `AsyncLocalStorage` for signal key transactions instead of one per socket, stopping growth with every reconnect (#14; port of WhiskeySockets/Baileys#2722) + +### Chores + +* Release images are no longer tagged `latest` (#8) + +## 0.1.0 (2026-10-21) + +### Fixes + +* **Device pairing**: Baileys `7.0.0-rc.9` → `7.0.0-rc14` with a `patch-package` patch that handles WhatsApp's `companion_reg_refresh` notification and guards `link_code_companion_reg` without crypto fields, fixing intermittent "Couldn't link device" failures. rc14 also fixes CVE-2026-48063 (#4; backport of upstream evolution-foundation/evolution-api#2759, `5cdf3240`, vendoring WhiskeySockets/Baileys#2765 and #2602) +* **App-state sync keys**: the Prisma, Redis and provider-files auth states restore `app-state-sync-key` with `AppStateSyncKeyData.fromObject` so base64 key data is converted back to bytes (#5; upstream evolution-foundation/evolution-api#2685, `65b5acf3`) +* **Instance auth**: instance routes authorize the path `instanceName` and ignore a query override, preventing cross-instance access (#2; adapted from upstream evolution-foundation/evolution-api#2549, `7a55a2bf`) + +### Chores + +* Fork baseline from Evolution API 2.3.7 +* Agentic setup: AGENTS.md, Claude skills and hooks, docs drift check, Makefile, fork CI (`purple-checks.yml`) and GHCR publishing on `v*` tags +* Vitest unit suite (150 tests) for our own data processing, run shuffled in CI with type-checking and linting; known bugs pinned as `KNOWN BUG` + `it.fails` (#6) +* `@lid` → phone JID resolution for `MESSAGES_UPSERT` extracted to `resolvePhoneRemoteJid`, behavior unchanged (#6) +* `lint-staged` 17 and CI on Node 24; dependency review allows GHSA-vfj7-8cjw-p6xm (`braces`, no patched release, reached only via `patch-package` at install time) (#6) + +# Upstream Evolution API changelog + # 2.3.7 (2025-12-05) ### Features diff --git a/CLAUDE.md b/CLAUDE.md index 7c0e87a040..6eaecd7212 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,223 +1,3 @@ -# CLAUDE.md +@AGENTS.md -This file provides comprehensive guidance to Claude AI when working with the Evolution API codebase. - -## Project Overview - -**Evolution API** is a powerful, production-ready REST API for WhatsApp communication that supports multiple WhatsApp providers: -- **Baileys** (WhatsApp Web) - Open-source WhatsApp Web client -- **Meta Business API** - Official WhatsApp Business API -- **Evolution API** - Custom WhatsApp integration - -Built with **Node.js 20+**, **TypeScript 5+**, and **Express.js**, it provides extensive integrations with chatbots, CRM systems, and messaging platforms in a **multi-tenant architecture**. - -## Common Development Commands - -### Build and Run -```bash -# Development -npm run dev:server # Run in development with hot reload (tsx watch) - -# Production -npm run build # TypeScript check + tsup build -npm run start:prod # Run production build - -# Direct execution -npm start # Run with tsx -``` - -### Code Quality -```bash -npm run lint # ESLint with auto-fix -npm run lint:check # ESLint check only -npm run commit # Interactive commit with commitizen -``` - -### Database Management -```bash -# Set database provider first -export DATABASE_PROVIDER=postgresql # or mysql - -# Generate Prisma client (automatically uses DATABASE_PROVIDER env) -npm run db:generate - -# Deploy migrations (production) -npm run db:deploy # Unix/Mac -npm run db:deploy:win # Windows - -# Development migrations (with sync to provider folder) -npm run db:migrate:dev # Unix/Mac -npm run db:migrate:dev:win # Windows - -# Open Prisma Studio -npm run db:studio - -# Development migrations -npm run db:migrate:dev # Unix/Mac -npm run db:migrate:dev:win # Windows -``` - -### Testing -```bash -npm test # Run tests with watch mode -``` - -## Architecture Overview - -### Core Structure -- **Multi-tenant SaaS**: Complete instance isolation with per-tenant authentication -- **Multi-provider database**: PostgreSQL and MySQL via Prisma ORM with provider-specific schemas and migrations -- **WhatsApp integrations**: Baileys, Meta Business API, and Evolution API with unified interface -- **Event-driven architecture**: EventEmitter2 for internal events + WebSocket, RabbitMQ, SQS, NATS, Pusher for external events -- **Microservices pattern**: Modular integrations for chatbots, storage, and external services - -### Directory Layout -``` -src/ -├── api/ -│ ├── controllers/ # HTTP route handlers (thin layer) -│ ├── services/ # Business logic (core functionality) -│ ├── repository/ # Data access layer (Prisma) -│ ├── dto/ # Data Transfer Objects (simple classes) -│ ├── guards/ # Authentication/authorization middleware -│ ├── integrations/ # External service integrations -│ │ ├── channel/ # WhatsApp providers (Baileys, Business API, Evolution) -│ │ ├── chatbot/ # AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot) -│ │ ├── event/ # Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher) -│ │ └── storage/ # File storage (S3, MinIO) -│ ├── routes/ # Express route definitions (RouterBroker pattern) -│ └── types/ # TypeScript type definitions -├── config/ # Environment and app configuration -├── cache/ # Redis and local cache implementations -├── exceptions/ # Custom HTTP exception classes -├── utils/ # Shared utilities and helpers -└── validate/ # JSONSchema7 validation schemas -``` - -### Key Integration Points - -**Channel Integrations** (`src/api/integrations/channel/`): -- **Baileys**: WhatsApp Web client with QR code authentication -- **Business API**: Official Meta WhatsApp Business API -- **Evolution API**: Custom WhatsApp integration -- Connection lifecycle management per instance with automatic reconnection - -**Chatbot Integrations** (`src/api/integrations/chatbot/`): -- **EvolutionBot**: Native chatbot with trigger system -- **Chatwoot**: Customer service platform integration -- **Typebot**: Visual chatbot flow builder -- **OpenAI**: AI capabilities including GPT and Whisper (audio transcription) -- **Dify**: AI agent workflow platform -- **Flowise**: LangChain visual builder -- **N8N**: Workflow automation platform -- **EvoAI**: Custom AI integration - -**Event Integrations** (`src/api/integrations/event/`): -- **WebSocket**: Real-time Socket.io connections -- **RabbitMQ**: Message queue for async processing -- **Amazon SQS**: Cloud-based message queuing -- **NATS**: High-performance messaging system -- **Pusher**: Real-time push notifications - -**Storage Integrations** (`src/api/integrations/storage/`): -- **AWS S3**: Cloud object storage -- **MinIO**: Self-hosted S3-compatible storage -- Media file management and URL generation - -### Database Schema Management -- Separate schema files: `postgresql-schema.prisma` and `mysql-schema.prisma` -- Environment variable `DATABASE_PROVIDER` determines active database -- Migration folders are provider-specific and auto-selected during deployment - -### Authentication & Security -- **API key-based authentication** via `apikey` header (global or per-instance) -- **Instance-specific tokens** for WhatsApp connection authentication -- **Guards system** for route protection and authorization -- **Input validation** using JSONSchema7 with RouterBroker `dataValidate` -- **Rate limiting** and security middleware -- **Webhook signature validation** for external integrations - -## Important Implementation Details - -### WhatsApp Instance Management -- Each WhatsApp connection is an "instance" with unique name -- Instance data stored in database with connection state -- Session persistence in database or file system (configurable) -- Automatic reconnection handling with exponential backoff - -### Message Queue Architecture -- Supports RabbitMQ, Amazon SQS, and WebSocket for events -- Event types: message.received, message.sent, connection.update, etc. -- Configurable per instance which events to send - -### Media Handling -- Local storage or S3/Minio for media files -- Automatic media download from WhatsApp -- Media URL generation for external access -- Support for audio transcription via OpenAI - -### Multi-tenancy Support -- Instance isolation at database level -- Separate webhook configurations per instance -- Independent integration settings per instance - -## Environment Configuration - -Key environment variables are defined in `.env.example`. The system uses a strongly-typed configuration system via `src/config/env.config.ts`. - -Critical configurations: -- `DATABASE_PROVIDER`: postgresql or mysql -- `DATABASE_CONNECTION_URI`: Database connection string -- `AUTHENTICATION_API_KEY`: Global API authentication -- `REDIS_ENABLED`: Enable Redis cache -- `RABBITMQ_ENABLED`/`SQS_ENABLED`: Message queue options - -## Development Guidelines - -The project follows comprehensive development standards defined in `.cursor/rules/`: - -### Core Principles -- **Always respond in Portuguese (PT-BR)** for user communication -- **Follow established architecture patterns** (Service Layer, RouterBroker, etc.) -- **Robust error handling** with retry logic and graceful degradation -- **Multi-database compatibility** (PostgreSQL and MySQL) -- **Security-first approach** with input validation and rate limiting -- **Performance optimizations** with Redis caching and connection pooling - -### Code Standards -- **TypeScript strict mode** with full type coverage -- **JSONSchema7** for input validation (not class-validator) -- **Conventional Commits** enforced by commitlint -- **ESLint + Prettier** for code formatting -- **Service Object pattern** for business logic -- **RouterBroker pattern** for route handling with `dataValidate` - -### Architecture Patterns -- **Multi-tenant isolation** at database and instance level -- **Event-driven communication** with EventEmitter2 -- **Microservices integration** pattern for external services -- **Connection pooling** and lifecycle management -- **Caching strategy** with Redis primary and Node-cache fallback - -## Testing Approach - -Currently, the project has minimal formal testing infrastructure: -- **Manual testing** is the primary approach -- **Integration testing** in development environment -- **No unit test suite** currently implemented -- Test files can be placed in `test/` directory as `*.test.ts` -- Run `npm test` for watch mode development testing - -### Recommended Testing Strategy -- Focus on **critical business logic** in services -- **Mock external dependencies** (WhatsApp APIs, databases) -- **Integration tests** for API endpoints -- **Manual testing** for WhatsApp connection flows - -## Deployment Considerations - -- Docker support with `Dockerfile` and `docker-compose.yaml` -- Graceful shutdown handling for connections -- Health check endpoints for monitoring -- Sentry integration for error tracking -- Telemetry for usage analytics (non-sensitive data only) \ No newline at end of file +All agent guidance for this repo lives in `AGENTS.md`; do not add content here. diff --git a/Docker/scripts/deploy_database.sh b/Docker/scripts/deploy_database.sh index fea58ff850..7c00117c57 100755 --- a/Docker/scripts/deploy_database.sh +++ b/Docker/scripts/deploy_database.sh @@ -2,6 +2,10 @@ source ./Docker/scripts/env_functions.sh +run_package_script() { + PATH="$PWD/node_modules/.bin:$PATH" bash -c "$(node -p "require('./package.json').scripts['$1']")" +} + if [ "$DOCKER_ENV" != "true" ]; then export_env_vars fi @@ -12,14 +16,14 @@ if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" || echo "Database URL: $DATABASE_URL" # rm -rf ./prisma/migrations # cp -r ./prisma/$DATABASE_PROVIDER-migrations ./prisma/migrations - npm run db:deploy + run_package_script db:deploy if [ $? -ne 0 ]; then echo "Migration failed" exit 1 else echo "Migration succeeded" fi - npm run db:generate + run_package_script db:generate if [ $? -ne 0 ]; then echo "Prisma generate failed" exit 1 diff --git a/Dockerfile b/Dockerfile index 24c4e3bc7f..1ab6676947 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:24-alpine AS builder +FROM node:24.22.1-alpine3.24@sha256:06d7adf3e0ef61a13f1d28682726366f97a15aa1888c934d3ecec86c33918b53 AS builder RUN apk update && \ apk add --no-cache git ffmpeg wget curl bash openssl @@ -10,6 +10,7 @@ LABEL contact="contato@evolution-api.com" WORKDIR /evolution COPY ./package*.json ./ +COPY ./patches ./patches COPY ./tsconfig.json ./ COPY ./tsup.config.ts ./ @@ -30,11 +31,15 @@ RUN ./Docker/scripts/generate_database.sh RUN npm run build -FROM node:24-alpine AS final +RUN npm prune --omit=dev + +FROM node:24.22.1-alpine3.24@sha256:06d7adf3e0ef61a13f1d28682726366f97a15aa1888c934d3ecec86c33918b53 AS final RUN apk update && \ apk add tzdata ffmpeg bash openssl +RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx + ENV TZ=America/Sao_Paulo ENV DOCKER_ENV=true @@ -51,10 +56,9 @@ COPY --from=builder /evolution/public ./public COPY --from=builder /evolution/.env ./.env COPY --from=builder /evolution/Docker ./Docker COPY --from=builder /evolution/runWithProvider.js ./runWithProvider.js -COPY --from=builder /evolution/tsup.config.ts ./tsup.config.ts ENV DOCKER_ENV=true EXPOSE 8080 -ENTRYPOINT ["/bin/bash", "-c", ". ./Docker/scripts/deploy_database.sh && npm run start:prod" ] +ENTRYPOINT ["/bin/bash", "-c", ". ./Docker/scripts/deploy_database.sh && exec node dist/main" ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..0a1de5455b --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +# evolution-in-purple developer entry points. +# Production deploy targets are intentionally absent until the deploy story is defined. + +IMAGE ?= evolution-in-purple:dev + +.PHONY: help install dev build lint test typecheck check-docs hooks-install docker-build _guard-not-prod + +help: ## Show available targets + @grep -hE '^[a-zA-Z][a-zA-Z_-]*:.*## ' $(MAKEFILE_LIST) | \ + awk 'BEGIN { FS = ":.*## " } { printf " %-14s %s\n", $$1, $$2 }' + +install: ## Install dependencies from package-lock.json (npm ci) + npm ci + +dev: _guard-not-prod ## Run the API with hot reload (npm run dev:server) + npm run dev:server + +build: ## Typecheck and build to dist/ (npm run build) + npm run build + +lint: ## ESLint with auto-fix (npm run lint) + npm run lint + +test: ## Run the unit tests once (npm test) + npm test + +typecheck: ## TypeScript check without emit (src, then src + test) + npx --no tsc --noEmit + npm run typecheck:test + +check-docs: ## Validate agent docs (AGENTS.md, CLAUDE.md, skills, links) + bash scripts/check-docs.sh + +hooks-install: ## Install Husky git hooks (.husky/) + npx --no husky + +docker-build: _guard-not-prod ## Build the local Docker image (default tag evolution-in-purple:dev) + docker build -t $(IMAGE) . + +# Refuses dev-only targets on a host that looks like production. +# Override with FORCE_DEV=1. +_guard-not-prod: + @if [ "$$FORCE_DEV" = "1" ]; then \ + echo "FORCE_DEV=1 - skipping prod-host guard"; \ + exit 0; \ + fi; \ + if [ "$$NODE_ENV" = "production" ]; then \ + echo "Refusing to run: NODE_ENV=production. Set FORCE_DEV=1 to override."; \ + exit 1; \ + fi; \ + if docker ps --format '{{.Names}}' 2>/dev/null | grep -qE '^evolution-in-purple(-prod|-blue|-green)'; then \ + echo "Refusing to run: production evolution-in-purple containers detected on this host."; \ + echo "Set FORCE_DEV=1 to override."; \ + exit 1; \ + fi diff --git a/VERSION b/VERSION new file mode 100644 index 0000000000..6e8bf73aa5 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/env.example b/env.example index 5fe448b82c..72ebd5230d 100644 --- a/env.example +++ b/env.example @@ -258,7 +258,7 @@ METRICS_ALLOWED_IPS= # =========================================== # TELEMETRIA # =========================================== -TELEMETRY_ENABLED=true +TELEMETRY_ENABLED=false TELEMETRY_URL= # =========================================== diff --git a/odd/agent-protocol.md b/odd/agent-protocol.md new file mode 100644 index 0000000000..1ce1b08eff --- /dev/null +++ b/odd/agent-protocol.md @@ -0,0 +1,75 @@ +# Agent protocol — preventing known failure loops + +Operational rules for agents (and their parents) working in this repo. Adapted from the orbit protocol, where +each rule exists because the loop it prevents actually happened. Imperative, no exceptions without an explicit +note in the feature's tracking doc. + +## 1. Environment first + +Verify against an isolated local stack (`docker-compose -f docker-compose.dev.yaml up -d`, local database, +`DATABASE_PROVIDER` set). If the environment is broken (database down, Prisma client stale, Redis +unreachable), STOP and report `environment: ` — never debug the feature against a broken stack. +Never verify against shared or production instances, and never connect a test to a consumer's real +WhatsApp number. + +**On-device WhatsApp validation** (QR pairing, real send/receive, `@lid` delivery, media) needs a human with +a phone. Agents prepare the steps and expected results; the human runs them. Report it as unverified until +the human confirms. + +## 2. Commits (subagents do NOT commit) + +**The parent owns commits; workers do not.** A worker implements, runs only the focused checks, and hands +back the diff plus evidence. It must not stage, commit or push. + +**Every ODD task closes with at least one Conventional work-unit commit**, and its commit id is recorded in +the feature doc. Slice work into commits of ~400 authored changed lines. Husky runs `lint-staged` on commit +and `build` + `lint:check` on push; never bypass it. + +**One branch / PR per feature phase.** Native review takes the whole branch diff against its base; an +accumulated branch exceeds the reviewer's context budget. Keep each phase under ~400 authored changed lines +and review per branch before starting the next phase. + +Safety note: uncommitted work lives in the worktree and survives a killed session. The parent commits +promptly after each worker returns and never runs `git checkout`/`git clean` while a worker is mid-flight. + +## 3. Verification budget + +At most TWO verification attempts per task, always on isolated state. Then commit what is green and report +exactly what stayed unverified. Do not write ad-hoc probe or soak scripts — use the sanctioned checks +(`npx tsc --noEmit`, `npm run build`, `npm run lint:check`, `make check-docs`, and tests once they exist). + +## 4. Liveness rule (parent) + +If a delegated agent has ~40 turns with no file write, CANCEL and re-scope with evidence. Ask the agent for +its blocker instead of polling it repeatedly. + +## 5. Review findings + +The native review (RDD) owns severity and stop rules; this repo adds no severity semantics of its own. +Non-blocking findings are recorded in the feature doc as follow-ups and never trigger another fix cycle — +advisory-driven iteration never terminates. + +## 6. Worktree/HEAD checks + +Before accepting a delegated report, run: + +``` +git log --oneline -1 +git status -s +``` + +Verify the commit landed on the intended branch and the tree contains only the expected changes. Commits +landing on a detached HEAD or in the wrong order have happened before. + +## 7. Host gotchas (Pi / gentle-pi) + +- When a review stops, run the recovery command that STATUS returns; do not diagnose the stop by hand. +- Fix managed-asset staleness by running the bundle's `gentle-ai sync --agent pi`, not the workspace's. +- `gentle-pi`'s version is pinned in `~/.pi/agent/settings.json` (`packages`), not in this repo's + `package.json`; never add it as a dependency here. + +## 8. Handoff + +The feature doc (`odd/tasks/.md`) is the verbatim spec and the resume point. Keep it current: state, +commit id per task, and known debts. A restart must be cheap — the next session resumes from the doc, not from +memory. Closed feature docs move to `odd/archive/`. diff --git a/package-lock.json b/package-lock.json index c45e8fef38..6caeaf3e35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "evolution-api", "version": "2.3.7", + "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@adiwajshing/keyed-db": "^0.2.4", @@ -15,13 +16,13 @@ "@figuro/chatwoot-sdk": "^1.1.16", "@hapi/boom": "^10.0.1", "@paralleldrive/cuid2": "^2.2.2", - "@prisma/client": "^6.16.2", + "@prisma/client": "^6.19.3", "@sentry/node": "^10.12.0", "@types/uuid": "^10.0.0", "amqplib": "^0.10.5", "audio-decode": "^2.2.3", "axios": "^1.7.9", - "baileys": "7.0.0-rc.9", + "baileys": "7.0.0-rc14", "class-validator": "^0.14.1", "compression": "^1.7.5", "cors": "^2.8.5", @@ -42,7 +43,7 @@ "jsonwebtoken": "^9.0.2", "kafkajs": "^2.2.4", "libphonenumber-js": "^1.12.25", - "link-preview-js": "^3.0.13", + "link-preview-js": "^4.0.4", "long": "^5.2.3", "mediainfo.js": "^0.3.4", "mime": "^4.0.0", @@ -51,22 +52,22 @@ "multer": "^2.0.2", "nats": "^2.29.1", "node-cache": "^5.1.2", - "node-cron": "^3.0.3", + "node-cron": "^4.6.0", "openai": "^4.77.3", + "patch-package": "^8.0.1", "pg": "^8.13.1", "pino": "^9.10.0", - "prisma": "^6.1.0", + "prisma": "^6.19.3", "pusher": "^5.2.0", "qrcode": "^1.5.4", "qrcode-terminal": "^0.12.0", "redis": "^4.7.0", "rxjs": "^7.8.2", - "sharp": "^0.34.2", + "sharp": "^0.35.5", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", "socks-proxy-agent": "^8.0.5", "swagger-ui-express": "^5.0.1", - "tsup": "^8.3.5", "undici": "^7.16.0", "uuid": "^13.0.0" }, @@ -80,7 +81,6 @@ "@types/mime": "^4.0.0", "@types/mime-types": "^2.1.4", "@types/node": "^24.5.2", - "@types/node-cron": "^3.0.11", "@types/qrcode": "^1.5.5", "@types/qrcode-terminal": "^0.12.2", "@typescript-eslint/eslint-plugin": "^8.44.0", @@ -93,11 +93,13 @@ "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-simple-import-sort": "^12.1.1", "husky": "^9.1.7", - "lint-staged": "^16.1.6", + "lint-staged": "^17.6.0", "prettier": "^3.4.2", "tsconfig-paths": "^4.2.0", + "tsup": "^8.3.5", "tsx": "^4.20.5", - "typescript": "^5.7.2" + "typescript": "^5.7.2", + "vitest": "^5.0.5" } }, "node_modules/@adiwajshing/keyed-db": { @@ -106,689 +108,289 @@ "integrity": "sha512-yprSnAtj80/VKuDqRcFFLDYltoNV8tChNwFfIgcf6PGD4sjzWIBgs08pRuTqGH5mk5wgL6PBRSsMCZqtZwzFEw==", "license": "MIT" }, - "node_modules/@apm-js-collab/code-transformer": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.8.2.tgz", - "integrity": "sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA==", - "license": "Apache-2.0" - }, - "node_modules/@apm-js-collab/tracing-hooks": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.3.1.tgz", - "integrity": "sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==", - "license": "Apache-2.0", - "dependencies": { - "@apm-js-collab/code-transformer": "^0.8.0", - "debug": "^4.4.1", - "module-details-from-path": "^1.0.4" - } - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@aws-sdk/client-sqs": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.936.0.tgz", - "integrity": "sha512-JDYdeGV8L+zXr9Ce7gVeleQB2z0F3YWWmNvbE6cS20fR9E0XVIsYw5Grymjw9/3AG4wbOIQJ5Nayy+HgxKANjA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-node": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-sdk-sqs": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/md5-js": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.936.0.tgz", - "integrity": "sha512-0G73S2cDqYwJVvqL08eakj79MZG2QRaB56Ul8/Ps9oQxllr7DMI1IQ/N3j3xjxgpq/U36pkoFZ8aK1n7Sbr3IQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "version": "3.1156.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.1156.0.tgz", + "integrity": "sha512-tkDeDv5kVb87RobSQ1RLfeCcLKA/IXwj0TkGWjUIfLSYQ+mL+hbimEZ/yOr46ahR+sJXRQN+JXffqrNQeiCLIw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.5", + "@aws-sdk/credential-provider-node": "^3.972.88", + "@aws-sdk/middleware-sdk-sqs": "^3.972.46", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/core": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.936.0.tgz", - "integrity": "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==", + "version": "3.978.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.5.tgz", + "integrity": "sha512-ruI0AWqcrfL20XDS6SwZIJFPOBZ6N0ADa9nLzWlBhxk1aoUjbUX3JkTR9vEmk7o35XpAHi68ahZ4T6RE2U9XyA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws-sdk/xml-builder": "3.930.0", - "@smithy/core": "^3.18.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/types": "^3.975.0", + "@aws-sdk/xml-builder": "^3.972.41", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.36.1", + "@smithy/signature-v4": "^5.7.4", + "@smithy/types": "^4.19.0", + "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.936.0.tgz", - "integrity": "sha512-dKajFuaugEA5i9gCKzOaVy9uTeZcApE+7Z5wdcZ6j40523fY1a56khDAUYkCfwqa7sHci4ccmxBkAo+fW1RChA==", + "version": "3.972.76", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.76.tgz", + "integrity": "sha512-YxmL1Ar7bV9ZkJvtv+lj+MF6le76NnZnbmgySyZuxpZaCv0/1lZ3KxKy6RIj1h9ui2hWnVjZxbmTMeO0y/NrQA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.978.5", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.936.0.tgz", - "integrity": "sha512-5FguODLXG1tWx/x8fBxH+GVrk7Hey2LbXV5h9SFzYCx/2h50URBm0+9hndg0Rd23+xzYe14F6SI9HA9c1sPnjg==", + "version": "3.972.78", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.78.tgz", + "integrity": "sha512-3VHknDRngoebsaqyT1iR75b5cBT2zfEtODK6yYmYltljkK1HEAQLs2FEd1IpWqAeOYdMGaG9wikngWLTufY7RA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@aws-sdk/core": "^3.978.5", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.936.0.tgz", - "integrity": "sha512-TbUv56ERQQujoHcLMcfL0Q6bVZfYF83gu/TjHkVkdSlHPOIKaG/mhE2XZSQzXv1cud6LlgeBbfzVAxJ+HPpffg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-login": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "version": "3.973.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.21.tgz", + "integrity": "sha512-aLx9hbCX6mGAyv4ZZd8UagJ4RASHVJw6BFDSIyuKmLkqNL6H47EjBPK6qaJ4B5TDV6mc+vMa0SJFYyo4mkvVQg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.5", + "@aws-sdk/credential-provider-env": "^3.972.76", + "@aws-sdk/credential-provider-http": "^3.972.78", + "@aws-sdk/credential-provider-login": "^3.972.83", + "@aws-sdk/credential-provider-process": "^3.972.76", + "@aws-sdk/credential-provider-sso": "^3.973.20", + "@aws-sdk/credential-provider-web-identity": "^3.972.82", + "@aws-sdk/nested-clients": "^3.997.50", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.936.0.tgz", - "integrity": "sha512-8DVrdRqPyUU66gfV7VZNToh56ZuO5D6agWrkLQE/xbLJOm2RbeRgh6buz7CqV8ipRd6m+zCl9mM4F3osQLZn8Q==", + "version": "3.972.83", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.83.tgz", + "integrity": "sha512-9bJU6FjRacF4FK/QHf8Dyxhur7B1grRj8C1Yh4IkpoFW2exQflPdCv0VrbemLW9ZctLNle+AE7TETs9KNvuLYg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.978.5", + "@aws-sdk/nested-clients": "^3.997.50", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.936.0.tgz", - "integrity": "sha512-rk/2PCtxX9xDsQW8p5Yjoca3StqmQcSfkmD7nQ61AqAHL1YgpSQWqHE+HjfGGiHDYKG7PvE33Ku2GyA7lEIJAw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-ini": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "version": "3.972.88", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.88.tgz", + "integrity": "sha512-AG0KID5qhQPzrFkh5b0Cws1Amytvzo0L0Xj6hMgJXEwD6ZXAqninDhSNAWJiN5zU4YVVZnuidnqwPwYJ04IamA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.76", + "@aws-sdk/credential-provider-http": "^3.972.78", + "@aws-sdk/credential-provider-ini": "^3.973.21", + "@aws-sdk/credential-provider-process": "^3.972.76", + "@aws-sdk/credential-provider-sso": "^3.973.20", + "@aws-sdk/credential-provider-web-identity": "^3.972.82", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.936.0.tgz", - "integrity": "sha512-GpA4AcHb96KQK2PSPUyvChvrsEKiLhQ5NWjeef2IZ3Jc8JoosiedYqp6yhZR+S8cTysuvx56WyJIJc8y8OTrLA==", + "version": "3.972.76", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.76.tgz", + "integrity": "sha512-pdhKFTXv1zPYhh92MTlp0jVbLWvUEV9v+rziuEkoskGGQ2iJskxHTO38nA8UAbT/N4IM3n8OZJ9rIY5IBO15/A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.978.5", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.936.0.tgz", - "integrity": "sha512-wHlEAJJvtnSyxTfNhN98JcU4taA1ED2JvuI2eePgawqBwS/Tzi0mhED1lvNIaWOkjfLd+nHALwszGrtJwEq4yQ==", + "version": "3.973.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.20.tgz", + "integrity": "sha512-DlxrAB3MnjNLx/da7b89W+3DyJQYso/q7Ns15MsST2IV2nkAYQOYTUCaffNSjgrpMP99Dm2mV2jxzcbyF94ZfA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.936.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/token-providers": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.978.5", + "@aws-sdk/nested-clients": "^3.997.50", + "@aws-sdk/token-providers": "3.1155.0", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.936.0.tgz", - "integrity": "sha512-v3qHAuoODkoRXsAF4RG+ZVO6q2P9yYBT4GMpMEfU9wXVNn7AIfwZgTwzSUfnjNiGva5BKleWVpRpJ9DeuLFbUg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", - "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", - "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", - "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "version": "3.972.82", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.82.tgz", + "integrity": "sha512-LUo46IqPTJ4xKBsrLuoaxiv/D2GTBCcFDMD1y3jKIqPR/p7Rw14RJFZ0iRWftqZtyLPQX9JMGnSi3mBu4y/pNA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws/lambda-invoke-store": "^0.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.978.5", + "@aws-sdk/nested-clients": "^3.997.50", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-sdk-sqs": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.936.0.tgz", - "integrity": "sha512-39WohFCCPeD6LV8zLQq7CyYbIieetEDDNLsEPeGJSh2Uv9qpY9r6zJRSTjb8hTuQbHDSEOGntHMYKpLoHdoxdQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.936.0.tgz", - "integrity": "sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw==", + "version": "3.972.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.46.tgz", + "integrity": "sha512-xXUZj3pl5rc2F2cf/n00WZRF4eI19gL5xr1cfqN882CJ6j53RAOt3GRLGOuFWz1iRgGm2OOVgpHqBDz/MXAP8Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@smithy/core": "^3.18.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.936.0.tgz", - "integrity": "sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "version": "3.997.50", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.50.tgz", + "integrity": "sha512-nKb8qu4QsnpZTq/6JIXc72mzuzfh7BK4sUQkpQznPec2z4W7Ob2xYkcM4i5e1yGTBnbJVeFv/0sf3jcz85hclA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.978.5", + "@aws-sdk/signature-v4-multi-region": "^3.996.49", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", - "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.49", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.49.tgz", + "integrity": "sha512-nvuAZNRrRRVCiARRKsYlQ0VnXiEQiq7vXPH+UA+LSo+kN3Djl9eo/V2r0udkw85PnsNshmv2Em1clld5M3j67w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "^3.975.0", + "@smithy/signature-v4": "^5.7.4", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.936.0.tgz", - "integrity": "sha512-vvw8+VXk0I+IsoxZw0mX9TMJawUJvEsg3EF7zcCSetwhNPAU8Xmlhv7E/sN/FgSmm7b7DsqKoW6rVtQiCs1PWQ==", + "version": "3.1155.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1155.0.tgz", + "integrity": "sha512-iIcQnYLhWuq9qOLQbIWFrxF3PF0svVeZvHnUS4DUnO4zUFmFHIyGyjqrntXG+e/5PUgfavV0pYfDIKHxTzN2LA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "^3.978.5", + "@aws-sdk/nested-clients": "^3.997.50", + "@aws-sdk/types": "^3.975.0", + "@smithy/core": "^3.36.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", - "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", - "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-endpoints": "^3.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", - "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", - "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.936.0.tgz", - "integrity": "sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw==", + "version": "3.975.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.975.0.tgz", + "integrity": "sha512-WQlhs6juByzWOzj5fwxOx0mPJaEqMcG/sDpl3Id61B3XhKE63xY+0QWcnG+sWR/sCfprmreyUo1lM7IKZmHMzw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.930.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", - "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.41.tgz", + "integrity": "sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", - "fast-xml-parser": "5.2.5", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.1.tgz", - "integrity": "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -829,9 +431,9 @@ } }, "node_modules/@borewit/text-codec": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.0.tgz", - "integrity": "sha512-X999CKBxGwX8wW+4gFibsbiNdwqmdQEXmUejIWaIqdrHBgS5ARIOOeyiQbHjP9G58xVEPcuvP6VwwH3A0OFTOA==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", "license": "MIT", "funding": { "type": "github", @@ -1132,9 +734,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "license": "MIT", "optional": true, "dependencies": { @@ -1148,6 +750,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1164,6 +767,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1180,6 +784,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1196,6 +801,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1212,6 +818,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1228,6 +835,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1244,6 +852,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1260,6 +869,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1276,6 +886,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1292,6 +903,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1308,6 +920,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1324,6 +937,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1340,6 +954,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1356,6 +971,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1372,6 +988,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1388,6 +1005,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1404,6 +1022,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1420,6 +1039,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1436,6 +1056,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1452,6 +1073,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1468,6 +1090,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1484,6 +1107,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1500,6 +1124,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1516,6 +1141,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1532,6 +1158,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1548,6 +1175,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1617,9 +1245,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1634,9 +1262,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -1662,9 +1290,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1819,16 +1447,6 @@ "axios": "^0.27.2" } }, - "node_modules/@figuro/chatwoot-sdk/node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, "node_modules/@hapi/boom": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", @@ -1861,9 +1479,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -1872,9 +1490,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1907,18 +1525,18 @@ "license": "BSD-3-Clause" }, "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", "cpu": [ "arm64" ], @@ -1928,19 +1546,19 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", "cpu": [ "x64" ], @@ -1950,35 +1568,54 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" + "@img/sharp-libvips-darwin-x64": "1.3.4" } }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", "optional": true, "os": [ - "darwin" + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", "cpu": [ "x64" ], @@ -1992,9 +1629,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", "cpu": [ "arm" ], @@ -2008,9 +1645,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", "cpu": [ "arm64" ], @@ -2024,9 +1661,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", "cpu": [ "ppc64" ], @@ -2040,9 +1677,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", "cpu": [ "riscv64" ], @@ -2056,9 +1693,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", "cpu": [ "s390x" ], @@ -2072,9 +1709,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", "cpu": [ "x64" ], @@ -2088,9 +1725,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", "cpu": [ "arm64" ], @@ -2104,9 +1741,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", "cpu": [ "x64" ], @@ -2120,9 +1757,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", "cpu": [ "arm" ], @@ -2132,19 +1769,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" + "@img/sharp-libvips-linux-arm": "1.3.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", "cpu": [ "arm64" ], @@ -2154,19 +1791,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" + "@img/sharp-libvips-linux-arm64": "1.3.4" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", "cpu": [ "ppc64" ], @@ -2176,19 +1813,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" + "@img/sharp-libvips-linux-ppc64": "1.3.4" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", "cpu": [ "riscv64" ], @@ -2198,19 +1835,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" + "@img/sharp-libvips-linux-riscv64": "1.3.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", "cpu": [ "s390x" ], @@ -2220,19 +1857,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" + "@img/sharp-libvips-linux-s390x": "1.3.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", "cpu": [ "x64" ], @@ -2242,19 +1879,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" + "@img/sharp-libvips-linux-x64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", "cpu": [ "arm64" ], @@ -2264,19 +1901,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", "cpu": [ "x64" ], @@ -2286,38 +1923,54 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", "cpu": [ "wasm32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "license": "Apache-2.0", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.7.0" + "@img/sharp-wasm32": "0.35.5" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", "cpu": [ "arm64" ], @@ -2327,16 +1980,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", "cpu": [ "ia32" ], @@ -2346,16 +1999,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": "^20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", "cpu": [ "x64" ], @@ -2365,24 +2018,63 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/@jimp/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", - "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.1.tgz", + "integrity": "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==", "license": "MIT", "dependencies": { - "@jimp/file-ops": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/file-ops": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", - "file-type": "^16.0.0", + "file-type": "^21.3.3", "mime": "3" }, "engines": { @@ -2402,14 +2094,14 @@ } }, "node_modules/@jimp/diff": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", - "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.1.tgz", + "integrity": "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==", "license": "MIT", "dependencies": { - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "pixelmatch": "^5.3.0" }, "engines": { @@ -2417,23 +2109,23 @@ } }, "node_modules/@jimp/file-ops": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", - "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.1.tgz", + "integrity": "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@jimp/js-bmp": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", - "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.1.tgz", + "integrity": "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "bmp-ts": "^1.0.9" }, "engines": { @@ -2441,13 +2133,13 @@ } }, "node_modules/@jimp/js-gif": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", - "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.1.tgz", + "integrity": "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", "gifwrap": "^0.10.1", "omggif": "^1.0.10" }, @@ -2456,13 +2148,13 @@ } }, "node_modules/@jimp/js-jpeg": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", - "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.1.tgz", + "integrity": "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", "jpeg-js": "^0.4.4" }, "engines": { @@ -2470,13 +2162,13 @@ } }, "node_modules/@jimp/js-png": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", - "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.1.tgz", + "integrity": "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", "pngjs": "^7.0.0" }, "engines": { @@ -2484,13 +2176,13 @@ } }, "node_modules/@jimp/js-tiff": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", - "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.1.tgz", + "integrity": "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", "utif2": "^4.1.0" }, "engines": { @@ -2498,13 +2190,13 @@ } }, "node_modules/@jimp/plugin-blit": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", - "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.1.tgz", + "integrity": "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2512,25 +2204,25 @@ } }, "node_modules/@jimp/plugin-blur": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", - "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.1.tgz", + "integrity": "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/utils": "1.6.0" + "@jimp/core": "1.6.1", + "@jimp/utils": "1.6.1" }, "engines": { "node": ">=18" } }, "node_modules/@jimp/plugin-circle": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", - "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.1.tgz", + "integrity": "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", + "@jimp/types": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2538,14 +2230,14 @@ } }, "node_modules/@jimp/plugin-color": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", - "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.1.tgz", + "integrity": "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "tinycolor2": "^1.6.0", "zod": "^3.23.8" }, @@ -2554,16 +2246,16 @@ } }, "node_modules/@jimp/plugin-contain": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", - "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.1.tgz", + "integrity": "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2571,15 +2263,15 @@ } }, "node_modules/@jimp/plugin-cover": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", - "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.1.tgz", + "integrity": "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2587,14 +2279,14 @@ } }, "node_modules/@jimp/plugin-crop": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", - "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.1.tgz", + "integrity": "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2602,13 +2294,13 @@ } }, "node_modules/@jimp/plugin-displace": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", - "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.1.tgz", + "integrity": "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2616,25 +2308,25 @@ } }, "node_modules/@jimp/plugin-dither": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", - "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.1.tgz", + "integrity": "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0" + "@jimp/types": "1.6.1" }, "engines": { "node": ">=18" } }, "node_modules/@jimp/plugin-fisheye": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", - "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.1.tgz", + "integrity": "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2642,12 +2334,12 @@ } }, "node_modules/@jimp/plugin-flip": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", - "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.1.tgz", + "integrity": "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", + "@jimp/types": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2655,20 +2347,20 @@ } }, "node_modules/@jimp/plugin-hash": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", - "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/js-bmp": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/js-tiff": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.1.tgz", + "integrity": "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/js-bmp": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/js-tiff": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "any-base": "^1.1.0" }, "engines": { @@ -2676,12 +2368,12 @@ } }, "node_modules/@jimp/plugin-mask": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", - "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.1.tgz", + "integrity": "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", + "@jimp/types": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2689,16 +2381,16 @@ } }, "node_modules/@jimp/plugin-print": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", - "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.1.tgz", + "integrity": "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/types": "1.6.1", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", @@ -2710,9 +2402,9 @@ } }, "node_modules/@jimp/plugin-quantize": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", - "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.1.tgz", + "integrity": "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==", "license": "MIT", "dependencies": { "image-q": "^4.0.0", @@ -2723,13 +2415,13 @@ } }, "node_modules/@jimp/plugin-resize": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", - "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.1.tgz", + "integrity": "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2737,16 +2429,16 @@ } }, "node_modules/@jimp/plugin-rotate": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", - "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.1.tgz", + "integrity": "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2754,16 +2446,16 @@ } }, "node_modules/@jimp/plugin-threshold": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", - "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.1.tgz", + "integrity": "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-hash": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-hash": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2771,9 +2463,9 @@ } }, "node_modules/@jimp/types": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", - "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.1.tgz", + "integrity": "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==", "license": "MIT", "dependencies": { "zod": "^3.23.8" @@ -2783,12 +2475,12 @@ } }, "node_modules/@jimp/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", + "@jimp/types": "1.6.1", "tinycolor2": "^1.6.0" }, "engines": { @@ -2799,6 +2491,7 @@ "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", @@ -2809,21 +2502,24 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2852,6 +2548,23 @@ "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", "license": "MIT" }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -2864,6 +2577,18 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@nodable/entities": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.1.0.tgz", + "integrity": "sha512-LsS/DjHr+uDM647Gru/cA8+J3a3HfhttwCKLyuoyN7yXTFCxKBtSgMQBvrW9yNPa2/zDRUNuGPaH5QUFoa4arQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2903,18 +2628,18 @@ } }, "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", "license": "Apache-2.0", "engines": { "node": ">=8.0.0" } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.204.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.204.0.tgz", - "integrity": "sha512-DqxY8yoAaiBPivoJD4UtgrMS8gEmzZ5lnaxzPojzLVHBGqPxgWm4zcuvcUHZiqQ6kRX2Klel2r9y8cA2HAtqpw==", + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.220.0.tgz", + "integrity": "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -2923,22 +2648,10 @@ "node": ">=8.0.0" } }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.2.0.tgz", - "integrity": "sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==", - "license": "Apache-2.0", - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, "node_modules/@opentelemetry/core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", - "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.12.0.tgz", + "integrity": "sha512-1HsSAuvT4/my0QrXWsyXFdWjKaPeCHsismKZgEEIb8NK13LP6DrMRlsUbvD/0F9mDWK3xDom9AMv3IscxgOnOQ==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" @@ -2951,14 +2664,14 @@ } }, "node_modules/@opentelemetry/instrumentation": { - "version": "0.204.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.204.0.tgz", - "integrity": "sha512-vV5+WSxktzoMP8JoYWKeopChy6G3HKk4UQ2hESCRDUUTZqQ3+nM3u8noVG0LmNfRWwcFBnbZ71GKC7vaYYdJ1g==", + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.220.0.tgz", + "integrity": "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.204.0", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1" + "@opentelemetry/api-logs": "0.220.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -2967,786 +2680,643 @@ "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.51.0.tgz", - "integrity": "sha512-XGmjYwjVRktD4agFnWBWQXo9SiYHKBxR6Ag3MLXwtLE4R99N3a08kGKM5SC1qOFKIELcQDGFEFT9ydXMH00Luw==", + "node_modules/@opentelemetry/resources": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.12.0.tgz", + "integrity": "sha512-+rSRoOdln6NhXTVcBdHSlZxJDNzwxadeweK2ltZO3tVtfp6aklABc06xclDk6ovRR+EiGgIcSbh/6NgjFhnb3A==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/core": "2.12.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.48.0.tgz", - "integrity": "sha512-OMjc3SFL4pC16PeK+tDhwP7MRvDPalYCGSvGqUhX5rASkI2H0RuxZHOWElYeXkV0WP+70Gw6JHWac/2Zqwmhdw==", + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.12.0.tgz", + "integrity": "sha512-ImVuRQ6faOsTi1Wna88s2avgrrf/iDgXPigIeynkUhjstMuoQUDOJXgkez2j1mlU4btjXcXBGa+q7qKFYR3GsA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/connect": "3.4.38" + "@opentelemetry/core": "2.12.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.22.0.tgz", - "integrity": "sha512-bXnTcwtngQsI1CvodFkTemrrRSQjAjZxqHVc+CJZTDnidT0T6wt3jkKhnsjU/Kkkc0lacr6VdRpCu2CUWa0OKw==", + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.12.0.tgz", + "integrity": "sha512-JOERgZCxRM06FEJ5kEWxlSRChah6MmXDx3KwCruqEv/iEyLkTokCSemf2y1w8oIHtfrr8EVTpzebq/31vn2Cpg==", + "deprecated": "Use @opentelemetry/sdk-trace package", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0" + "@opentelemetry/core": "2.12.0", + "@opentelemetry/resources": "2.12.0", + "@opentelemetry/sdk-trace": "2.12.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.53.0.tgz", - "integrity": "sha512-r/PBafQmFYRjuxLYEHJ3ze1iBnP2GDA1nXOSS6E02KnYNZAVjj6WcDA1MSthtdAUUK0XnotHvvWM8/qz7DMO5A==", + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=14" } }, - "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.24.0.tgz", - "integrity": "sha512-HjIxJ6CBRD770KNVaTdMXIv29Sjz4C1kPCCK5x1Ujpc6SNnLGPqUVyJYZ3LUhhnHAqdbrl83ogVWjCgeT4Q0yw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node_modules/@oxc-project/types": { + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz", + "integrity": "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/oxc-project" } }, - "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.48.0.tgz", - "integrity": "sha512-TLv/On8pufynNR+pUbpkyvuESVASZZKMlqCm4bBImTpXKTpqXaJJ3o/MUDeMlM91rpen+PEv2SeyOKcHCSlgag==", - "license": "Apache-2.0", + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@noble/hashes": "^1.1.5" } }, - "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.52.0.tgz", - "integrity": "sha512-3fEJ8jOOMwopvldY16KuzHbRhPk8wSsOTSF0v2psmOCGewh6ad+ZbkTx/xyUK9rUdUMWAxRVU0tFpj4Wx1vkPA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0" - }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "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": "^18.19.0 || >=20.6.0" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "funding": { + "url": "https://opencollective.com/pkgr" } }, - "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.51.0.tgz", - "integrity": "sha512-qyf27DaFNL1Qhbo/da+04MSCw982B02FhuOS5/UF+PMhM61CcOiu7fPuXj8TvbqyReQuJFljXE6UirlvoT/62g==", + "node_modules/@prisma/client": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.3.tgz", + "integrity": "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==", + "hasInstallScript": true, "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, "engines": { - "node": "^18.19.0 || >=20.6.0" + "node": ">=18.18" }, "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } } }, - "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.204.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.204.0.tgz", - "integrity": "sha512-1afJYyGRA4OmHTv0FfNTrTAzoEjPQUYgd+8ih/lX0LlZBnGio/O80vxA0lN3knsJPS7FiDrsDrWq25K7oAzbkw==", + "node_modules/@prisma/config": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.3.tgz", + "integrity": "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "2.1.0", - "@opentelemetry/instrumentation": "0.204.0", - "@opentelemetry/semantic-conventions": "^1.29.0", - "forwarded-parse": "2.1.2" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.21.0", + "empathic": "2.0.0" } }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", - "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } + "node_modules/@prisma/debug": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.3.tgz", + "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==", + "license": "Apache-2.0" }, - "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.52.0.tgz", - "integrity": "sha512-rUvlyZwI90HRQPYicxpDGhT8setMrlHKokCtBtZgYxQWRF5RBbG4q0pGtbZvd7kyseuHbFpA3I/5z7M8b/5ywg==", + "node_modules/@prisma/engines": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.3.tgz", + "integrity": "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==", + "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/redis-common": "^0.38.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/fetch-engine": "6.19.3", + "@prisma/get-platform": "6.19.3" } }, - "node_modules/@opentelemetry/instrumentation-kafkajs": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.14.0.tgz", - "integrity": "sha512-kbB5yXS47dTIdO/lfbbXlzhvHFturbux4EpP0+6H78Lk0Bn4QXiZQW7rmZY1xBCY16mNcCb8Yt0mhz85hTnSVA==", + "node_modules/@prisma/engines-version": { + "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz", + "integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.3.tgz", + "integrity": "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.30.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/get-platform": "6.19.3" } }, - "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.49.0.tgz", - "integrity": "sha512-NKsRRT27fbIYL4Ix+BjjP8h4YveyKc+2gD6DMZbr5R5rUeDqfC8+DTfIt3c3ex3BIc5Vvek4rqHnN7q34ZetLQ==", + "node_modules/@prisma/get-platform": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.3.tgz", + "integrity": "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.33.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@prisma/debug": "6.19.3" } }, - "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.52.0.tgz", - "integrity": "sha512-JJSBYLDx/mNSy8Ibi/uQixu2rH0bZODJa8/cz04hEhRaiZQoeJ5UrOhO/mS87IdgVsHrnBOsZ6vDu09znupyuA==", - "license": "Apache-2.0", + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@protobufjs/aspromise": "^1.1.1" } }, - "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.49.0.tgz", - "integrity": "sha512-ctXu+O/1HSadAxtjoEg2w307Z5iPyLOMM8IRNwjaKrIpNAthYGSOanChbk1kqY6zU5CrpkPHGdAT6jk8dXiMqw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause" + }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "license": "MIT", "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@redis/client": "^1.0.0" } }, - "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.57.0.tgz", - "integrity": "sha512-KD6Rg0KSHWDkik+qjIOWoksi1xqSpix8TSPfquIK1DTmd9OTFb5PHmMkzJe16TAPVEuElUW8gvgP59cacFcrMQ==", - "license": "Apache-2.0", + "node_modules/@redis/client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" }, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=14" } }, - "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.51.0.tgz", - "integrity": "sha512-gwWaAlhhV2By7XcbyU3DOLMvzsgeaymwP/jktDC+/uPkCmgB61zurwqOQdeiRq9KAf22Y2dtE5ZLXxytJRbEVA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "license": "MIT", "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@redis/client": "^1.0.0" } }, - "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.50.0.tgz", - "integrity": "sha512-duKAvMRI3vq6u9JwzIipY9zHfikN20bX05sL7GjDeLKr2qV0LQ4ADtKST7KStdGcQ+MTN5wghWbbVdLgNcB3rA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/mysql": "2.15.27" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "license": "MIT", "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@redis/client": "^1.0.0" } }, - "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.51.0.tgz", - "integrity": "sha512-zT2Wg22Xn43RyfU3NOUmnFtb5zlDI0fKcijCj9AcK9zuLZ4ModgtLXOyBJSSfO+hsOCZSC1v/Fxwj+nZJFdzLQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@opentelemetry/sql-common": "^0.41.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "license": "MIT", "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@redis/client": "^1.0.0" } }, - "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.57.0.tgz", - "integrity": "sha512-dWLGE+r5lBgm2A8SaaSYDE3OKJ/kwwy5WLyGyzor8PLhUL9VnJRiY6qhp4njwhnljiLtzeffRtG2Mf/YyWLeTw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.34.0", - "@opentelemetry/sql-common": "^0.41.0", - "@types/pg": "8.15.5", - "@types/pg-pool": "2.0.6" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@redis/client": "^1.0.0" } }, - "node_modules/@opentelemetry/instrumentation-redis": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.53.0.tgz", - "integrity": "sha512-WUHV8fr+8yo5RmzyU7D5BIE1zwiaNQcTyZPwtxlfr7px6NYYx7IIpSihJK7WA60npWynfxxK1T67RAVF0Gdfjg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/redis-common": "^0.38.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz", + "integrity": "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.23.0.tgz", - "integrity": "sha512-3TMTk/9VtlRonVTaU4tCzbg4YqW+Iq/l5VnN2e5whP6JgEg/PKfrGbqQ+CxQWNLfLaQYIUgEZqAn5gk/inh1uQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/tedious": "^4.0.14" - }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz", + "integrity": "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.15.0.tgz", - "integrity": "sha512-sNFGA/iCDlVkNjzTzPRcudmI11vT/WAfAguRdZY9IspCw02N4WSC72zTuQhSMheh2a1gdeM9my1imnKRvEEvEg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0" - }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz", + "integrity": "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.7.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/redis-common": { - "version": "0.38.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz", - "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==", - "license": "Apache-2.0", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz", + "integrity": "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, "engines": { - "node": "^18.19.0 || >=20.6.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/resources": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", - "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.2.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz", + "integrity": "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", - "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.2.0", - "@opentelemetry/resources": "2.2.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz", + "integrity": "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", - "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", - "license": "Apache-2.0", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz", + "integrity": "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": ">=14" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@opentelemetry/sql-common": { - "version": "0.41.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", - "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0" - }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz", + "integrity": "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", - "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz", + "integrity": "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@noble/hashes": "^1.1.5" + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, - "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==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz", + "integrity": "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@prisma/client": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.0.tgz", - "integrity": "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==", - "hasInstallScript": true, - "license": "Apache-2.0", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz", + "integrity": "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": ">=18.18" - }, - "peerDependencies": { - "prisma": "*", - "typescript": ">=5.1.0" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@prisma/config": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz", - "integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==", - "license": "Apache-2.0", - "dependencies": { - "c12": "3.1.0", - "deepmerge-ts": "7.1.5", - "effect": "3.18.4", - "empathic": "2.0.0" - } - }, - "node_modules/@prisma/debug": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", - "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.0.tgz", - "integrity": "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "6.19.0", - "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", - "@prisma/fetch-engine": "6.19.0", - "@prisma/get-platform": "6.19.0" - } - }, - "node_modules/@prisma/engines-version": { - "version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773.tgz", - "integrity": "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/fetch-engine": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.0.tgz", - "integrity": "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "6.19.0", - "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", - "@prisma/get-platform": "6.19.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@prisma/get-platform": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz", - "integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "6.19.0" + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz", + "integrity": "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@prisma/instrumentation": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.15.0.tgz", - "integrity": "sha512-6TXaH6OmDkMOQvOxwLZ8XS51hU2v4A3vmE2pSijCIiGRJYyNeMcL6nMHQMyYdZRD8wl7LF3Wzc+AMPMV/9Oo7A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.8" + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz", + "integrity": "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", - "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz", + "integrity": "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, "engines": { - "node": ">=14" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", - "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.57.2", - "@types/shimmer": "^1.2.0", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" - }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz", + "integrity": "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT", + "peer": true }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.65.0.tgz", + "integrity": "sha512-t2AnVba25baFdq7gczzS13JyVcdubLVbFX1X04mxrggDrTQPUXNv3XM0k9H1hr5tqvg7AVvtQ720QHAj0iYqhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.65.0.tgz", + "integrity": "sha512-dNONUaEwPGhl/nuO2KV8VABWlcjYyU6KtZGN8dQv5OS5JU7JSCxPijVNKXbRJd0iuTzTiJ4TjPt/4bbvDRNiMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.65.0.tgz", + "integrity": "sha512-zWQqUH+9zavr2Bxuzfpuv2SIF3X31cr58TvSQC9o1GhpOvkqRwr60GkZdQcudyT/BEgdpdSafjgd2I13MbTL7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" - }, - "node_modules/@redis/bloom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", - "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", - "license": "MIT", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/client": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", - "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", - "license": "MIT", - "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@redis/graph": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", - "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", - "license": "MIT", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/json": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", - "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", - "license": "MIT", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/search": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", - "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", - "license": "MIT", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/time-series": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", - "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", - "license": "MIT", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.65.0.tgz", + "integrity": "sha512-RQYreBFWtO4//hIuOaZkYUnF/lPU4lz35dveDimDshWCJvVisVtJ/shmT9bkXK8wFJY+FJF89X6F3Cq1qI3LCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.65.0.tgz", + "integrity": "sha512-A0NfMj6Cuh/9BE5k3awJKv6HoxFVXIcLAb6SZqAMoxUHPA1ZNNK4RsGuUPD/vL7nEAm9LG+ZzXWWTrnC0XtkpA==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3754,12 +3324,13 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.65.0.tgz", + "integrity": "sha512-BI6/H7q3aP0I7S7GOrjyxTjgEJlSnovXE38+bdxBpuKN6z+d//uF+T+VQ3Q7ltJL/71p17ufartARX9Ja5gOGQ==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3767,12 +3338,13 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.65.0.tgz", + "integrity": "sha512-W1vUprf09RWaVWnsFP1vlUkEWJzTZlpObOEAYZXt9Fq0UVAOYEkeGr7wBX7McKqqIQskXdBnpiOMgwPujordgA==", "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3780,12 +3352,13 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.65.0.tgz", + "integrity": "sha512-s8QTcZWgrl9haFzEGIqyE5BWyo89pFDGLMBFZ752WYxk8DJwNU/O1X6pZgPw829d9/sm6CCHyrMP3aurt76p1Q==", "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3793,12 +3366,13 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.65.0.tgz", + "integrity": "sha512-ke1f6dLRzkqveyjjF2fkEwQYZvxHtNNwjegY8nJrwcO0Bdqr8CzFO/pKE0Bns13cx0qz2Ba2Y0Hs3dSwMdnHxA==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3806,12 +3380,13 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.65.0.tgz", + "integrity": "sha512-926SMeUcy4y18Rz/w2aJiUKOnyAnm6czg72bXkQEaeemsll+Wr0hnsjb2BGRpHvWgwAMYy5ACJCotbvfLCfj0A==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3819,12 +3394,27 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.65.0.tgz", + "integrity": "sha512-6T7JNRP1hc4hqF2dUsEoVLOt9K8tmODoCLldtQRYnrddGGQY8Gqe2pM/qMMUo7EMowpP7oa2N95k0/8sYK/ipw==", "cpu": [ "loong64" ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.65.0.tgz", + "integrity": "sha512-SImGFl1Je5kpyAgOCPONVT7h6ASV8mAUwGRjY7eZvVjPg//PfHOXgI+P241RF/+HqsA4PP3qD527ysWFKNQyhQ==", + "cpu": [ + "loong64" + ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3832,12 +3422,27 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.65.0.tgz", + "integrity": "sha512-XeDTbmPF1Yu5asfXGPrMkWoxTDhgryQbmw5Q1+R582O51VNCk23J6f2pDrsAp5YkneBVBwtExx6ZGOb9D2aQ6A==", "cpu": [ "ppc64" ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.65.0.tgz", + "integrity": "sha512-VS55JUI/1nsljmV/jfqNkLZXFw8uMO8nNlfoHmSevNf6fO3neuhyDvx9dv3c/Ytt8r/431SL8Qax1dpYvOMZTg==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3845,12 +3450,13 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.65.0.tgz", + "integrity": "sha512-Vblw84TbX7RS4la1Y4+yQ7aore/oSyaNtFbppuBr5Vs/4LfBeXKq9dIfoExw6zOvRERqd2rRAjnZRaMhH/0x5w==", "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3858,12 +3464,13 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.65.0.tgz", + "integrity": "sha512-7+gHjtONvKgwR9InTKFaLq53f6YzBJkgeMRBENdWTsQr+spahGqQm8HF5aXpq8i4JaRrOqYyv6Zn1WltDTxaZQ==", "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3871,12 +3478,13 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.65.0.tgz", + "integrity": "sha512-KvNDC0J6s4vVHhtJ6KrDXU0Er0au/hmRIBq/+1maCV8TseGupeKizq0bWcrbp2HE1T8xoknvN0Q/KZ/YF0qX3w==", "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3884,12 +3492,13 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.65.0.tgz", + "integrity": "sha512-GExdD15WPvu/x/drELjVam7vX8EsPgvTMOa/iJL/p/4m/oOqzbb7JXZg8fXR4hlXtg/sF2zyTZ3bR0AEtEMlhQ==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3897,25 +3506,41 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.65.0.tgz", + "integrity": "sha512-8sT9PemIZfIMED4Vd4KGoOXVDj5CMSWNcGdbheRUx9TYS03yOWdT2Q0lXPyEyNBB9YTilwewIQKNIBk6Nxi0Kg==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.65.0.tgz", + "integrity": "sha512-pO6Ehi0jo5yE0R6PltKhJhv9mlIiyzBJk5N5NUF2OF6xtVKI+1bdXxj/rb0jGXy3hck1ELVT0QPY3tzkAm360w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.65.0.tgz", + "integrity": "sha512-vo7N3/+cjjPDYr8vtxC+qSNgWa9sDvJ7g8N8R5SWqFY4WiNH1ULcAU70SKVyHeiIZOYYwNP9pTipmhN5sl1NPw==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3923,12 +3548,13 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.65.0.tgz", + "integrity": "sha512-ArwvgFAbjKcDM7qqotZ4OVYf+UMT9xH2YzkCzK4+AuZSDihMlz1Ekoe4INwBVXzn2mnJzuIYOcSx07iTBijGRQ==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3936,12 +3562,13 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.65.0.tgz", + "integrity": "sha512-QGMmK4/cxKE/36FoLMSHQBCyymv/eFTnsCvvsBF0rgmOvVbvX78VWZRerL3dHa77f4pM6Xcc2iGDhJ10w/svKQ==", "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3949,12 +3576,13 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.65.0.tgz", + "integrity": "sha512-XfKYFWHIPs4HPODIMsGF1IKlS3H1e3WokUZIgpmn4+uNnZBlnRDDHEylNyazbQXwhMKZre68OjtRYbKqb5+j8w==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3962,12 +3590,13 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.65.0.tgz", + "integrity": "sha512-wPWi++K4Bs1VR4XihOhfGi4IGX6Cub7FlerTf8X2v+2BtDHddgSqWCXwGD+h2yAe4/niIhNDwAltioOC+wEogQ==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3986,692 +3615,192 @@ "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", "hasInstallScript": true, - "license": "Apache-2.0" - }, - "node_modules/@sentry/core": { - "version": "10.26.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.26.0.tgz", - "integrity": "sha512-TjDe5QI37SLuV0q3nMOH8JcPZhv2e85FALaQMIhRILH9Ce6G7xW5GSjmH91NUVq8yc3XtiqYlz/EenEZActc4Q==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@sentry/node": { - "version": "10.26.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.26.0.tgz", - "integrity": "sha512-VUwNoKYhRpnHQSj9lty1TgooO+1wcpS1V0K87HU8sZEas5gx3Ujiouk5ocPjlgbKreoYOApgOnEEIq5W/hfQcQ==", - "license": "MIT", - "dependencies": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.1.0", - "@opentelemetry/core": "^2.1.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/instrumentation-amqplib": "0.51.0", - "@opentelemetry/instrumentation-connect": "0.48.0", - "@opentelemetry/instrumentation-dataloader": "0.22.0", - "@opentelemetry/instrumentation-express": "0.53.0", - "@opentelemetry/instrumentation-fs": "0.24.0", - "@opentelemetry/instrumentation-generic-pool": "0.48.0", - "@opentelemetry/instrumentation-graphql": "0.52.0", - "@opentelemetry/instrumentation-hapi": "0.51.0", - "@opentelemetry/instrumentation-http": "0.204.0", - "@opentelemetry/instrumentation-ioredis": "0.52.0", - "@opentelemetry/instrumentation-kafkajs": "0.14.0", - "@opentelemetry/instrumentation-knex": "0.49.0", - "@opentelemetry/instrumentation-koa": "0.52.0", - "@opentelemetry/instrumentation-lru-memoizer": "0.49.0", - "@opentelemetry/instrumentation-mongodb": "0.57.0", - "@opentelemetry/instrumentation-mongoose": "0.51.0", - "@opentelemetry/instrumentation-mysql": "0.50.0", - "@opentelemetry/instrumentation-mysql2": "0.51.0", - "@opentelemetry/instrumentation-pg": "0.57.0", - "@opentelemetry/instrumentation-redis": "0.53.0", - "@opentelemetry/instrumentation-tedious": "0.23.0", - "@opentelemetry/instrumentation-undici": "0.15.0", - "@opentelemetry/resources": "^2.1.0", - "@opentelemetry/sdk-trace-base": "^2.1.0", - "@opentelemetry/semantic-conventions": "^1.37.0", - "@prisma/instrumentation": "6.15.0", - "@sentry/core": "10.26.0", - "@sentry/node-core": "10.26.0", - "@sentry/opentelemetry": "10.26.0", - "import-in-the-middle": "^1.14.2", - "minimatch": "^9.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@sentry/node-core": { - "version": "10.26.0", - "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.26.0.tgz", - "integrity": "sha512-7OrHVn8XAsq9mMVMlpL18XTKQEVcLOJSo0n2M7QGKfFk/OfVtSFMcUWGqN1qhYtT9aMTr2bjtR5+BI33djnNTQ==", - "license": "MIT", - "dependencies": { - "@apm-js-collab/tracing-hooks": "^0.3.1", - "@sentry/core": "10.26.0", - "@sentry/opentelemetry": "10.26.0", - "import-in-the-middle": "^1.14.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", - "@opentelemetry/core": "^1.30.1 || ^2.1.0", - "@opentelemetry/instrumentation": ">=0.57.1 <1", - "@opentelemetry/resources": "^1.30.1 || ^2.1.0", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", - "@opentelemetry/semantic-conventions": "^1.37.0" - } - }, - "node_modules/@sentry/opentelemetry": { - "version": "10.26.0", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.26.0.tgz", - "integrity": "sha512-ASJdOwn6NwMH2ZeBrnGJI+l/xkJp1AOiQ5FWkvTqLc/vHX+r3PDMO7c+koecY+LiZxSzZF4IV8sALXfOh6UnwA==", - "license": "MIT", - "dependencies": { - "@sentry/core": "10.26.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", - "@opentelemetry/core": "^1.30.1 || ^2.1.0", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", - "@opentelemetry/semantic-conventions": "^1.37.0" - } - }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", - "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", - "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core": { - "version": "3.18.5", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", - "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-serde": "^4.2.6", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", - "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", - "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", - "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", - "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/md5-js": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.5.tgz", - "integrity": "sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", - "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", - "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.18.5", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-middleware": "^4.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", - "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/service-error-classification": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", - "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", - "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", - "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", - "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", - "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", - "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", - "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-uri-escape": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", - "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", - "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", - "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", - "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "4.9.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", - "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.18.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", - "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", - "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.2.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "Apache-2.0" }, - "node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, + "node_modules/@sentry/conventions": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz", + "integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==", + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=14" } }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", - "license": "Apache-2.0", + "node_modules/@sentry/core": { + "version": "10.76.2", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.76.2.tgz", + "integrity": "sha512-vndrQalRiwYatTkjlLWvyf69KocXIb9TKJ4A9oiyWguL8PJYtqIRTlRe4OpkpjLrxS18fvRdV0jEm29c/9p3hQ==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@sentry/conventions": "^0.16.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", - "license": "Apache-2.0", + "node_modules/@sentry/node": { + "version": "10.76.2", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.76.2.tgz", + "integrity": "sha512-LSwGGE/GceTbkDHRoQQr3lq/dpxxA6RdooBbuGTJ/S6BdqKX2kw2FdV7Se9eJzZJJJ5AxCZum37+ejYS7N2pJg==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/instrumentation": "^0.220.0", + "@opentelemetry/sdk-trace-base": "^2.9.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.76.2", + "@sentry/node-core": "10.76.2", + "@sentry/opentelemetry": "10.76.2", + "@sentry/server-utils": "10.76.2", + "import-in-the-middle": "^3.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", - "license": "Apache-2.0", + "node_modules/@sentry/node-core": { + "version": "10.76.2", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.76.2.tgz", + "integrity": "sha512-TrgAc/SvU5kQfjw8pz6I8LLIUiblOSf9BPPNkTbS0Dzcqa4yQLVbmsbS3rvGT7ot64m9Vjzn327ZRy63MlI6/w==", + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "tslib": "^2.6.2" + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.76.2", + "@sentry/opentelemetry": "10.76.2", + "import-in-the-middle": "^3.0.0" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" + "node": ">=18" }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", - "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", + "@opentelemetry/instrumentation": ">=0.57.1 <1", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/core": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-http": { + "optional": true + }, + "@opentelemetry/instrumentation": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + } } }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", - "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", - "license": "Apache-2.0", + "node_modules/@sentry/opentelemetry": { + "version": "10.76.2", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.76.2.tgz", + "integrity": "sha512-deqWrKJGaku3nhiZchg34z8W9v6kUwtzoQ4cmEq6CeT8/4X93X4YfeJgoh1dhbswFRpwor4pQ6kj0KttagKtfA==", + "license": "MIT", "dependencies": { - "@smithy/config-resolver": "^4.4.3", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.76.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-endpoints": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", - "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" + "node": ">=18" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" } }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", - "license": "Apache-2.0", + "node_modules/@sentry/server-utils": { + "version": "10.76.2", + "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.76.2.tgz", + "integrity": "sha512-wogj6Trj8fAu+aD7HbpvHEBlyANMOvvO9MP5NFJaiQbprLKugtCdE6rFTxDhofXvRo4nswL9m4Cvysg3k5KcvQ==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.76.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", - "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "node_modules/@smithy/core": { + "version": "3.36.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.36.2.tgz", + "integrity": "sha512-oRw0rB9QSUiCZfEs23G5F4Af+b2rQt1lItIICWu5IejFNd+uPM8SWqiKLMpQ6bX1Q7slsLbjuoE2niErFtkVPQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/util-retry": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", - "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/util-stream": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", - "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "node_modules/@smithy/node-http-handler": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", "license": "Apache-2.0", "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "node_modules/@smithy/signature-v4": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.4.tgz", + "integrity": "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/uuid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", - "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "node_modules/@smithy/types": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.19.0.tgz", + "integrity": "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4687,9 +3816,9 @@ "license": "MIT" }, "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, "node_modules/@thi.ng/bitstream": { @@ -4758,34 +3887,6 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/@tokenizer/inflate/node_modules/@borewit/text-codec": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", - "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/inflate/node_modules/token-types": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", - "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.1.0", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -4803,6 +3904,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/compression": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", @@ -4818,6 +3930,7 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4842,10 +3955,18 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, "license": "MIT" }, "node_modules/@types/express": { @@ -4895,12 +4016,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "license": "MIT" - }, "node_modules/@types/mime": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-4.0.0.tgz", @@ -4919,15 +4034,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mysql": { - "version": "2.15.27", - "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", - "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { "version": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", @@ -4937,13 +4043,6 @@ "undici-types": "~7.16.0" } }, - "node_modules/@types/node-cron": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", - "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node-fetch": { "version": "2.6.13", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", @@ -4954,26 +4053,6 @@ "form-data": "^4.0.4" } }, - "node_modules/@types/pg": { - "version": "8.15.5", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.5.tgz", - "integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/@types/pg-pool": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", - "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", - "license": "MIT", - "dependencies": { - "@types/pg": "*" - } - }, "node_modules/@types/qrcode": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", @@ -5045,21 +4124,6 @@ "@types/node": "*" } }, - "node_modules/@types/shimmer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", - "license": "MIT" - }, - "node_modules/@types/tedious": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", - "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -5072,6 +4136,15 @@ "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.47.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz", @@ -5314,6 +4387,54 @@ "dev": true, "license": "ISC" }, + "node_modules/@vitest/mocker": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.5.tgz", + "integrity": "sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.5", + "estree-walker": "^3.0.3", + "magic-string": "^1.4.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9 || ^3.0.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.5.tgz", + "integrity": "sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@wasm-audio-decoders/common": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/@wasm-audio-decoders/common/-/common-9.0.7.tgz", @@ -5365,12 +4486,11 @@ "url": "https://github.com/sponsors/eshaz" } }, - "node_modules/@zxing/text-encoding": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", - "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", - "license": "(Unlicense OR Apache-2.0)", - "optional": true + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "license": "BSD-2-Clause" }, "node_modules/abort-controller": { "version": "3.0.0", @@ -5410,6 +4530,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5418,15 +4539,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -5459,9 +4571,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", "dependencies": { @@ -5548,6 +4660,19 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT" }, "node_modules/append-field": { @@ -5698,6 +4823,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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": "0.2.10", "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", @@ -5782,6 +4917,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -5803,32 +4939,59 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, "node_modules/baileys": { - "version": "7.0.0-rc.9", - "resolved": "https://registry.npmjs.org/baileys/-/baileys-7.0.0-rc.9.tgz", - "integrity": "sha512-Txd2dZ9MHbojvsHckeuCnAKPO/bQjKxua/0tQSJwOKXffK5vpS82k4eA/Nb46K0cK0Bx+fyY0zhnQHYMBriQcw==", + "version": "7.0.0-rc14", + "resolved": "https://registry.npmjs.org/baileys/-/baileys-7.0.0-rc14.tgz", + "integrity": "sha512-pewtrljhWx5JTUBvvkXZz1fL3JPiwzjBsnhx/DWf2LWBx1cZNH7J/sF22xDehba5mySWUQU4a1Pwt7lWARUxaA==", "hasInstallScript": true, "license": "MIT", "dependencies": { "@cacheable/node-cache": "^1.4.0", "@hapi/boom": "^9.1.3", "async-mutex": "^0.5.0", - "libsignal": "git+https://github.com/whiskeysockets/libsignal-node.git", + "libsignal": "^6.0.0", "lru-cache": "^11.1.0", - "music-metadata": "^11.7.0", + "music-metadata": "^11.12.3", "p-queue": "^9.0.0", "pino": "^9.6", - "protobufjs": "^7.2.4", + "protobufjs": "^7.5.6", + "whatsapp-rust-bridge": "0.5.4", "ws": "^8.13.0" }, "engines": { @@ -5836,7 +4999,7 @@ }, "peerDependencies": { "audio-decode": "^2.1.3", - "jimp": "^1.6.0", + "jimp": "^1.6.1", "link-preview-js": "^3.0.0", "sharp": "*" }, @@ -5871,12 +5034,14 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -5930,23 +5095,23 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.9", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.9.tgz", + "integrity": "sha512-FISIQxV5u91Y9gvuFq3q/tA/qTqLJn3AhRcEvyozyhJTh5OBFFtv827AdHU7Rbyct4r+Ba1/99oFa1Y3yuFsdw==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.16.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -5962,12 +5127,41 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -5975,15 +5169,16 @@ "license": "ISC" }, "node_modules/bowser": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.0.tgz", - "integrity": "sha512-yHAbSRuT6LTeKi6k2aS40csueHqgAsFEgmrOsfRyFpJnFv5O2hl9FYmWEUZ97gZ/dG17U4IQQcTx4YAFYPuWRQ==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", "license": "MIT" }, "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==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -5993,7 +5188,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -6048,12 +5242,6 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, "node_modules/buffer-more-ints": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", @@ -6064,6 +5252,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, "license": "MIT", "dependencies": { "load-tsconfig": "^0.2.3" @@ -6127,6 +5316,7 @@ "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6146,9 +5336,9 @@ } }, "node_modules/cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", "dev": true, "license": "MIT", "engines": { @@ -6221,6 +5411,16 @@ "node": ">=6" } }, + "node_modules/chai": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -6235,9 +5435,9 @@ } }, "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", "dev": true, "license": "MIT" }, @@ -6295,6 +5495,21 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/citty": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", @@ -6305,9 +5520,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.3.0.tgz", + "integrity": "sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==", "license": "MIT" }, "node_modules/class-validator": { @@ -6347,69 +5562,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-truncate": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", - "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^7.1.0", - "string-width": "^8.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -6476,13 +5628,6 @@ "dev": true, "license": "MIT" }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6495,24 +5640,14 @@ "node": ">= 0.8" } }, - "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/commitizen": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.3.1.tgz", - "integrity": "sha512-gwAPAVTy/j5YcOOebcCRIijn+mSjWJC+IYKivTu6aG8Ei/scoXgfsMRnuAk6b0GRste2J4NGxVdMN3ZpfNaVaw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.3.2.tgz", + "integrity": "sha512-1Zs37z9JPvAcuTSSricZZwBhOPVNNxJouuY4yDEt+eD70EoxT2TU9kViG8CuB/PmVg2G4XsAGQiK4YCst97aDQ==", "dev": true, "license": "MIT", "dependencies": { - "cachedir": "2.3.0", + "cachedir": "2.4.0", "cz-conventional-changelog": "3.3.0", "dedent": "0.7.0", "detect-indent": "6.1.0", @@ -6520,10 +5655,10 @@ "find-root": "1.1.0", "fs-extra": "9.1.0", "glob": "7.2.3", - "inquirer": "8.2.5", + "inquirer": "8.2.7", "is-utf8": "^0.2.1", - "lodash": "4.17.21", - "minimist": "1.2.7", + "lodash": "4.18.1", + "minimist": "1.2.8", "strip-bom": "4.0.0", "strip-json-comments": "3.1.1" }, @@ -6533,17 +5668,7 @@ "git-cz": "bin/git-cz" }, "engines": { - "node": ">= 12" - } - }, - "node_modules/commitizen/node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 18" } }, "node_modules/compare-func": { @@ -6570,14 +5695,15 @@ } }, "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz", + "integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==", "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", + "destroy": "1.2.0", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", @@ -6585,6 +5711,10 @@ }, "engines": { "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/compression/node_modules/debug": { @@ -6609,25 +5739,10 @@ "dev": true, "license": "MIT" }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "license": "MIT" }, "node_modules/consola": { @@ -6789,7 +5904,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6992,15 +6106,6 @@ "node": ">=0.10.0" } }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -7016,12 +6121,22 @@ "license": "MIT" }, "node_modules/deepmerge-ts": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", - "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz", + "integrity": "sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==", + "funding": [ + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/deepmerge-ts" + } + ], "license": "BSD-3-Clause", "engines": { - "node": ">=16.0.0" + "node": ">=16.9.0" } }, "node_modules/defaults": { @@ -7083,9 +6198,9 @@ } }, "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "license": "MIT" }, "node_modules/delayed-stream": { @@ -7280,9 +6395,9 @@ "license": "MIT" }, "node_modules/effect": { - "version": "3.18.4", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", - "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz", + "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -7314,59 +6429,42 @@ } }, "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "version": "6.6.11", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.11.tgz", + "integrity": "sha512-z34mGpLoY/82ZbkX1LqGMIaf3wjVMPL6gVC681QDqnmzJ26synI8qAE32P+a7UazmpJZ8UGrFIeAavgRfXzYrg==", "license": "MIT", "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", "accepts": "~1.3.4", - "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", - "debug": "~4.3.1", + "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" + "ws": "~8.21.0" }, "engines": { "node": ">=10.2.0" } }, "node_modules/engine.io-client": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", - "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.7.tgz", + "integrity": "sha512-GyAvmPA7gnRCpDQbYACAy79Lg4ewevrmaPzySZz0OVvgyZc3gEfLLqPTYk8wVBScjOEpM9ZO4KryyFpVItjGEA==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", + "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.17.1", + "ws": "~8.21.0", "xmlhttprequest-ssl": "~2.1.1" } }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/engine.io-client/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -7402,27 +6500,10 @@ "node": ">= 0.6" } }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/engine.io/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -7462,19 +6543,6 @@ "node": ">=6" } }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", @@ -7572,6 +6640,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -7634,6 +6709,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -7857,9 +6933,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -7904,9 +6980,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -8021,9 +7097,9 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -8054,9 +7130,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -8162,9 +7238,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -8296,6 +7372,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -8336,15 +7422,6 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/exif-parser": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", @@ -8363,40 +7440,50 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz", + "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.13", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.16.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -8434,26 +7521,11 @@ "license": "MIT" }, "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.3.tgz", + "integrity": "sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==", "license": "MIT" }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/fast-check": { "version": "3.23.2", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", @@ -8535,9 +7607,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "dev": true, "funding": [ { @@ -8551,10 +7623,26 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-xml-builder": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", + "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" + } + }, "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.2.tgz", + "integrity": "sha512-R9iDuNrQYeQut46cn2r2wHKn4HYzVDvDm5J1wW+koZewykv0yuO2HChTYeZtrULyHIDM9cj9TUXloCsueCQUog==", "funding": [ { "type": "github", @@ -8563,7 +7651,12 @@ ], "license": "MIT", "dependencies": { - "strnum": "^2.1.0" + "@nodable/entities": "^3.0.1", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.2", + "xml-naming": "^0.3.0" }, "bin": { "fxparser": "src/cli/cli.js" @@ -8629,17 +7722,18 @@ } }, "node_modules/file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", "license": "MIT", "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" }, "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sindresorhus/file-type?sponsor=1" @@ -8649,7 +7743,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -8659,12 +7752,15 @@ } }, "node_modules/filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-6.1.0.tgz", + "integrity": "sha512-xdMtCAODmPloU9qtmPcdBV9Kd27NtMse+4ayThxqIHUES5Z2S6bGpap5PpdmNM56ub7y3i1eyr+vJJIIgWGKmA==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/finalhandler": { @@ -8736,6 +7832,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/findup-sync": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", @@ -8756,6 +7861,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, "license": "MIT", "dependencies": { "magic-string": "^0.30.17", @@ -8789,9 +7895,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -8822,9 +7928,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", "funding": [ { "type": "individual", @@ -8845,6 +7951,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -8857,16 +7964,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -8898,13 +8005,7 @@ "license": "MIT", "engines": { "node": ">= 0.6" - } - }, - "node_modules/forwarded-parse": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", - "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", - "license": "MIT" + } }, "node_modules/fresh": { "version": "0.5.2", @@ -8942,6 +8043,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -8996,6 +8098,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9090,7 +8193,7 @@ "version": "4.13.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -9180,9 +8283,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -9191,9 +8294,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -9320,7 +8423,6 @@ "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/graphemer": { @@ -9421,9 +8523,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -9632,17 +8734,25 @@ } }, "node_modules/import-in-the-middle": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", - "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.5.2.tgz", + "integrity": "sha512-WseIA/4o56+GYO3RmlOPvejYi771OFeJEeK/ejROsr9bdjW+E5NXTOD1Di8TL40Qgg3JJX9aa97Hf8iiSVIKZw==", "license": "Apache-2.0", "dependencies": { - "acorn": "^8.14.0", - "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^1.2.2", - "module-details-from-path": "^1.0.3" + "cjs-module-lexer": "^2.2.0", + "es-module-lexer": "^3.0.2", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" } }, + "node_modules/import-in-the-middle/node_modules/es-module-lexer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-3.0.4.tgz", + "integrity": "sha512-BldNY5mwkVu+cQ5Ymn3e6hB9Sx8XHx4ZevWbiUyZbfKxJxYO27Hf0wfzkxIaD5CAJMaGl3YspKELj0ff/7nCMA==", + "license": "MIT" + }, "node_modules/import-meta-resolve": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", @@ -9693,17 +8803,17 @@ } }, "node_modules/inquirer": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", - "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "version": "8.2.7", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", + "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", "dev": true, "license": "MIT", "dependencies": { + "@inquirer/external-editor": "^1.0.0", "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", "cli-cursor": "^3.1.0", "cli-width": "^3.0.0", - "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.21", "mute-stream": "0.0.8", @@ -9713,7 +8823,7 @@ "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6", - "wrap-ansi": "^7.0.0" + "wrap-ansi": "^6.0.1" }, "engines": { "node": ">=12.0.0" @@ -9795,6 +8905,21 @@ "node": ">=8" } }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -9811,9 +8936,9 @@ } }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.7.4", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.4.tgz", + "integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==", "license": "MIT", "engines": { "node": ">= 12" @@ -9828,22 +8953,6 @@ "node": ">= 10" } }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -9936,6 +9045,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9948,6 +9058,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -9994,6 +9105,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -10020,26 +9146,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.4", @@ -10108,7 +9219,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -10155,6 +9265,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -10250,6 +9361,7 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -10274,6 +9386,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", + "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -10337,11 +9461,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -10351,38 +9486,38 @@ "license": "ISC" }, "node_modules/jimp": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", - "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/diff": "1.6.0", - "@jimp/js-bmp": "1.6.0", - "@jimp/js-gif": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/js-tiff": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/plugin-blur": "1.6.0", - "@jimp/plugin-circle": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-contain": "1.6.0", - "@jimp/plugin-cover": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-displace": "1.6.0", - "@jimp/plugin-dither": "1.6.0", - "@jimp/plugin-fisheye": "1.6.0", - "@jimp/plugin-flip": "1.6.0", - "@jimp/plugin-hash": "1.6.0", - "@jimp/plugin-mask": "1.6.0", - "@jimp/plugin-print": "1.6.0", - "@jimp/plugin-quantize": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/plugin-rotate": "1.6.0", - "@jimp/plugin-threshold": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.1.tgz", + "integrity": "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/diff": "1.6.1", + "@jimp/js-bmp": "1.6.1", + "@jimp/js-gif": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/js-tiff": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/plugin-blur": "1.6.1", + "@jimp/plugin-circle": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-contain": "1.6.1", + "@jimp/plugin-cover": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-displace": "1.6.1", + "@jimp/plugin-dither": "1.6.1", + "@jimp/plugin-fisheye": "1.6.1", + "@jimp/plugin-flip": "1.6.1", + "@jimp/plugin-hash": "1.6.1", + "@jimp/plugin-mask": "1.6.1", + "@jimp/plugin-print": "1.6.1", + "@jimp/plugin-quantize": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/plugin-rotate": "1.6.1", + "@jimp/plugin-threshold": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1" }, "engines": { "node": ">=18" @@ -10401,6 +9536,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -10420,10 +9556,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -10459,6 +9605,25 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -10483,7 +9648,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -10492,6 +9656,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -10562,12 +9735,12 @@ } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, @@ -10589,6 +9762,15 @@ "@keyv/serialize": "^1.1.1" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -10610,57 +9792,293 @@ "license": "MIT" }, "node_modules/libsignal": { - "name": "@whiskeysockets/libsignal-node", - "version": "2.0.1", - "resolved": "git+ssh://git@github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/libsignal/-/libsignal-6.0.0.tgz", + "integrity": "sha512-d/5V3YFtDljbFMufz4ncyUYGYhJl+vzAe+c2EFFBQ6bz1h8Q3IOMEGXYMzlibU60I+e8GagMMpji18iez3P1hA==", "license": "GPL-3.0", "dependencies": { "curve25519-js": "^0.0.4", - "protobufjs": "6.8.8" + "protobufjs": "^7.5.5" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/libsignal/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "license": "MIT" + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/libsignal/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "license": "Apache-2.0" + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/libsignal/node_modules/protobufjs": { - "version": "6.8.8", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", - "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.0", - "@types/node": "^10.1.0", - "long": "^4.0.0" + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -10673,133 +10091,63 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/link-preview-js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/link-preview-js/-/link-preview-js-3.2.0.tgz", - "integrity": "sha512-FvrLltjOPGbTzt+RugbzM7g8XuUNLPO2U/INSLczrYdAA32E7nZVUrVL1gr61DGOArGJA2QkPGMEvNMLLsXREA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/link-preview-js/-/link-preview-js-4.0.4.tgz", + "integrity": "sha512-hJ2UlkFtwCihTtx8y5zBH6iuMYwz+W5v06GY7owDfFvTdAtMZMBfKzg7TjKegL+ehHw4CDIzZClJNKhwAD6Q0Q==", "license": "MIT", "dependencies": { - "cheerio": "1.0.0-rc.11", - "url": "0.11.0" + "cheerio": "1.0.0-rc.11" }, "engines": { "node": ">=18" } }, "node_modules/lint-staged": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", - "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.6.0.tgz", + "integrity": "sha512-nOhBfYkZEwkFtNzD5pK9s9LkcWoxJ+whLzEnhqi9nl7zTB5n7tg8+9iYgMGx2Wm3VlLu8cbNj0RACzY+nWi5iQ==", "dev": true, "license": "MIT", "dependencies": { - "commander": "^14.0.2", - "listr2": "^9.0.5", - "micromatch": "^4.0.8", - "nano-spawn": "^2.0.0", - "pidtree": "^0.6.0", + "picomatch": "^4.0.7", "string-argv": "^0.3.2", - "yaml": "^2.8.1" + "tinyexec": "^1.3.1" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": ">=20.17" + "node": ">=22.22.1" }, "funding": { "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/listr2": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/listr2/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "yaml": "^2.9.1" } }, - "node_modules/listr2/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/lint-staged/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/load-tsconfig": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -10822,9 +10170,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.camelcase": { @@ -11025,169 +10373,6 @@ "node": ">=8" } }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/log-update/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -11217,6 +10402,7 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -11232,12 +10418,16 @@ } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-2.0.0.tgz", + "integrity": "sha512-kOy3OxT2HH39N70UnKgu4NWDZjLOz8W/mfyvniHjRH/DrL3f2pOfvWQ4p60offbbtDAnXWp0v9LfMIqMec269Q==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mediainfo.js": { @@ -11408,7 +10598,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -11473,26 +10662,14 @@ "node": ">=6" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -11511,9 +10688,9 @@ } }, "node_modules/minio": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/minio/-/minio-8.0.6.tgz", - "integrity": "sha512-sOeh2/b/XprRmEtYsnNRFtOqNRTPDvYtMWh+spWlfsuCV/+IdxNeKVUMKLqI7b5Dr07ZqCPuaRGU/rB9pZYVdQ==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minio/-/minio-8.0.7.tgz", + "integrity": "sha512-E737MgufW8CeQAsTAtnEMrxZ9scMSf29kkhZoXzDTKj/Jszzo2SfeZUH9wbDQH2Rsq6TCtl/yQL0+XdVKZansQ==", "license": "Apache-2.0", "dependencies": { "async": "^3.2.4", @@ -11521,14 +10698,13 @@ "browser-or-node": "^2.1.1", "buffer-crc32": "^1.0.0", "eventemitter3": "^5.0.1", - "fast-xml-parser": "^4.4.1", + "fast-xml-parser": "^5.3.4", "ipaddr.js": "^2.0.1", "lodash": "^4.17.21", "mime-types": "^2.1.35", "query-string": "^7.1.3", "stream-json": "^1.8.0", "through2": "^4.0.2", - "web-encoding": "^1.1.5", "xml2js": "^0.5.0 || ^0.6.2" }, "engines": { @@ -11541,52 +10717,11 @@ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, - "node_modules/minio/node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.1.1" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/minio/node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/mlly": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.15.0", @@ -11599,12 +10734,14 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, "license": "MIT" }, "node_modules/mlly/node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, "license": "MIT", "dependencies": { "confbox": "^0.1.8", @@ -11638,27 +10775,27 @@ "license": "MIT" }, "node_modules/multer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", - "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.5.0.tgz", + "integrity": "sha512-BsqOzYGVq49JcW2IzoSAxeKYRevb+dGGPPWleOR8Lrlk4igwxu1/9pwSXlByhCb7MVZDrNJFC0IBP5/O5nLYRw==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", - "concat-stream": "^2.0.0", - "mkdirp": "^0.5.6", - "object-assign": "^4.1.1", - "type-is": "^1.6.18", - "xtend": "^4.0.2" + "type-is": "^1.6.18" }, "engines": { "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/music-metadata": { - "version": "11.10.3", - "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.10.3.tgz", - "integrity": "sha512-j0g/x4cNNZW6I5gdcPAY+GFkJY9WHTpkFDMBJKQLxJQyvSfQbXm57fTE3haGFFuOzCgtsTd4Plwc49Sn9RacDQ==", + "version": "11.16.1", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.16.1.tgz", + "integrity": "sha512-uR/mHK6eyfl8h3jVCobF5b38Mfg1IDJMiBxIVbmm5F+G1OXEFcUpQHveO84tJBAqvY93GCRY2R4kDyx7s06Rug==", "funding": [ { "type": "github", @@ -11671,80 +10808,32 @@ ], "license": "MIT", "dependencies": { - "@borewit/text-codec": "^0.2.0", + "@borewit/text-codec": "^0.2.2", "@tokenizer/token": "^0.3.0", - "content-type": "^1.0.5", + "content-type": "^2.1.0", "debug": "^4.4.3", - "file-type": "^21.1.1", - "media-typer": "^1.1.0", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.5.0" + "file-type": "^21.3.4", + "media-typer": "^2.0.0", + "strtok3": "^10.3.5", + "token-types": "^6.1.2", + "uint8array-extras": "^1.5.0", + "win-guid": "^0.2.1" }, "engines": { "node": ">=18" } }, - "node_modules/music-metadata/node_modules/file-type": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.1.tgz", - "integrity": "sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg==", - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/music-metadata/node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "node_modules/music-metadata/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, "engines": { "node": ">=18" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/music-metadata/node_modules/token-types": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", - "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.1.0", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/music-metadata/node_modules/token-types/node_modules/@borewit/text-codec": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", - "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mute-stream": { @@ -11758,6 +10847,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -11765,17 +10855,24 @@ "thenify-all": "^1.0.0" } }, - "node_modules/nano-spawn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", - "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "engines": { - "node": ">=20.17" + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" }, - "funding": { - "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/nats": { @@ -11831,24 +10928,12 @@ } }, "node_modules/node-cron": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", - "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.6.0.tgz", + "integrity": "sha512-Si/bzYiKRHOB8/a99T2+SDGN582ONDMSTlJr5oCkT6GtnqPjZ2s10eoQRYkW9ZHwjVxONL+W8Fb+qR0AHMQsdg==", "license": "ISC", - "dependencies": { - "uuid": "8.3.2" - }, "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/node-cron/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "node": ">=20" } }, "node_modules/node-domexception": { @@ -11919,24 +11004,28 @@ } }, "node_modules/nypm": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", - "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.11.tgz", + "integrity": "sha512-wrVBTLWDu7nTUKGWplQebWOXWnhaIwyKQhBxobH/CGxJHoVw27r9vG5ba7kdZnxG+f4rC6dZd6m2oHmpkOcO7g==", "license": "MIT", "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.2", + "citty": "^0.2.2", "pathe": "^2.0.3", - "pkg-types": "^2.3.0", - "tinyexec": "^1.0.1" + "tinyexec": "^1.3.1" }, "bin": { "nypm": "dist/cli.mjs" }, "engines": { - "node": "^14.16.0 || >=16.10.0" + "node": ">=18" } }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -11962,7 +11051,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -12042,6 +11130,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/ogg-opus-decoder": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/ogg-opus-decoder/-/ogg-opus-decoder-1.7.3.tgz", @@ -12059,9 +11161,9 @@ } }, "node_modules/ohash": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", "license": "MIT" }, "node_modules/omggif": { @@ -12126,6 +11228,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openai": { "version": "4.104.0", "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", @@ -12302,16 +11420,6 @@ "node": ">=8" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -12500,32 +11608,154 @@ "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", "license": "MIT", "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/patch-package/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/patch-package/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/patch-package/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/patch-package/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/patch-package/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=8" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/patch-package/node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=14.14" } }, "node_modules/path-exists": { @@ -12538,6 +11768,21 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -12552,7 +11797,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12562,12 +11806,13 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/pathe": { @@ -12576,19 +11821,6 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, - "node_modules/peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -12688,13 +11920,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -12703,19 +11935,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/pino": { "version": "9.14.0", "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", @@ -12757,6 +11976,7 @@ "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" @@ -12784,16 +12004,22 @@ } }, "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.4.0.tgz", + "integrity": "sha512-07Fys7iVx0X6TcviMOgogM1Lk33ZyqOezMnU79S+kffWqwolG/5W9V8EgQSeYCIfFTYVgssRbnH630Oa2+soBA==", "license": "MIT", "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", + "confbox": "^0.3.1", + "exsolve": "^1.1.3", "pathe": "^2.0.3" } }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "license": "MIT" + }, "node_modules/pngjs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", @@ -12807,15 +12033,47 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/postcss-load-config": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, "funding": [ { "type": "opencollective", @@ -12933,14 +12191,14 @@ } }, "node_modules/prisma": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz", - "integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.3.tgz", + "integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "6.19.0", - "@prisma/engines": "6.19.0" + "@prisma/config": "6.19.3", + "@prisma/engines": "6.19.3" }, "bin": { "prisma": "build/index.js" @@ -12957,15 +12215,6 @@ } } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/process-warning": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", @@ -12983,33 +12232,32 @@ "license": "MIT" }, "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", - "long": "^5.0.0" + "long": "^5.3.2" }, "engines": { "node": ">=12.0.0" } }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -13017,6 +12265,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/proxy-addr/node_modules/ipaddr.js": { @@ -13029,16 +12281,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/pure-rand": { "version": "6.1.0", @@ -13289,12 +12538,13 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.16.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz", + "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -13304,32 +12554,21 @@ } }, "node_modules/query-string": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", - "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-10.0.0.tgz", + "integrity": "sha512-QYodx/Ywk8lCcOmsyupXVN91yWPAdjq0xVqE2FE2BHFYnma71C8txR8JCBptjzoUpkfAK7Qjua9VjYRTru4LYg==", "license": "MIT", "dependencies": { - "decode-uri-component": "^0.2.2", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" + "filter-obj": "^6.1.0", + "split-on-first": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=22" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -13373,16 +12612,45 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -13411,62 +12679,6 @@ "node": ">= 6" } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", - "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", - "license": "MIT", - "dependencies": { - "readable-stream": "^4.7.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -13570,17 +12782,16 @@ } }, "node_modules/require-in-the-middle": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", - "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", "license": "MIT", "dependencies": { "debug": "^4.3.5", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.8" + "module-details-from-path": "^1.0.3" }, "engines": { - "node": ">=8.6.0" + "node": ">=9.3.0 || >=8.10.0 <9.0.0" } }, "node_modules/require-main-filename": { @@ -13599,6 +12810,7 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -13633,6 +12845,7 @@ "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" @@ -13642,7 +12855,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "devOptional": true, + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -13673,13 +12886,6 @@ "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -13697,13 +12903,49 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rolldown": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz", + "integrity": "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/types": "=0.155.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.15", + "@rolldown/binding-android-arm64": "1.2.15", + "@rolldown/binding-darwin-arm64": "1.2.15", + "@rolldown/binding-darwin-x64": "1.2.15", + "@rolldown/binding-freebsd-x64": "1.2.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.15", + "@rolldown/binding-linux-arm64-gnu": "1.2.15", + "@rolldown/binding-linux-arm64-musl": "1.2.15", + "@rolldown/binding-linux-ppc64-gnu": "1.2.15", + "@rolldown/binding-linux-s390x-gnu": "1.2.15", + "@rolldown/binding-linux-x64-gnu": "1.2.15", + "@rolldown/binding-linux-x64-musl": "1.2.15", + "@rolldown/binding-openharmony-arm64": "1.2.15", + "@rolldown/binding-win32-arm64-msvc": "1.2.15", + "@rolldown/binding-win32-x64-msvc": "1.2.15" + } + }, "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.65.0.tgz", + "integrity": "sha512-QVCvQUTGDN6JRwi3jVGCY3H4aTHvi9rX0LyoBMkGUvdh/Z6O51CzcLz85BWZaTCDGhl6ySGqYZs+xI1jmPqGcw==", + "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -13713,28 +12955,32 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.65.0", + "@rollup/rollup-android-arm64": "4.65.0", + "@rollup/rollup-darwin-arm64": "4.65.0", + "@rollup/rollup-darwin-x64": "4.65.0", + "@rollup/rollup-freebsd-arm64": "4.65.0", + "@rollup/rollup-freebsd-x64": "4.65.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.65.0", + "@rollup/rollup-linux-arm-musleabihf": "4.65.0", + "@rollup/rollup-linux-arm64-gnu": "4.65.0", + "@rollup/rollup-linux-arm64-musl": "4.65.0", + "@rollup/rollup-linux-loong64-gnu": "4.65.0", + "@rollup/rollup-linux-loong64-musl": "4.65.0", + "@rollup/rollup-linux-ppc64-gnu": "4.65.0", + "@rollup/rollup-linux-ppc64-musl": "4.65.0", + "@rollup/rollup-linux-riscv64-gnu": "4.65.0", + "@rollup/rollup-linux-riscv64-musl": "4.65.0", + "@rollup/rollup-linux-s390x-gnu": "4.65.0", + "@rollup/rollup-linux-x64-gnu": "4.65.0", + "@rollup/rollup-linux-x64-musl": "4.65.0", + "@rollup/rollup-openbsd-x64": "4.65.0", + "@rollup/rollup-openharmony-arm64": "4.65.0", + "@rollup/rollup-win32-arm64-msvc": "4.65.0", + "@rollup/rollup-win32-ia32-msvc": "4.65.0", + "@rollup/rollup-win32-x64-gnu": "4.65.0", + "@rollup/rollup-win32-x64-msvc": "4.65.0", "fsevents": "~2.3.2" } }, @@ -13842,6 +13088,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -13877,9 +13124,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -14024,54 +13271,58 @@ "license": "ISC" }, "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "hasInstallScript": true, + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", "license": "Apache-2.0", "dependencies": { - "@img/colour": "^1.0.0", + "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", - "semver": "^7.7.3" + "semver": "^7.8.5" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -14084,27 +13335,20 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", - "license": "BSD-2-Clause" - }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -14116,13 +13360,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -14176,9 +13420,9 @@ "license": "ISC" }, "node_modules/simple-xml-to-json": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz", - "integrity": "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz", + "integrity": "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==", "license": "MIT", "engines": { "node": ">=20.12.2" @@ -14194,21 +13438,13 @@ "url": "https://github.com/sponsors/eshaz" } }, - "node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "node": ">=6" } }, "node_modules/smart-buffer": { @@ -14240,36 +13476,19 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", - "license": "MIT", - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" - } - }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz", + "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "debug": "~4.4.1", + "ws": "~8.21.0" } }, "node_modules/socket.io-adapter/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -14320,33 +13539,16 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" + "debug": "~4.4.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=10.0.0" } }, "node_modules/socket.io/node_modules/debug": { @@ -14407,18 +13609,33 @@ "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 12" } }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-4.0.0.tgz", + "integrity": "sha512-pyrgNqiEBHA9KlEtg9quYGagtrlTyukL3ryQ+RmjRn9SYmcNQmfa+sWIsI+hlcL4WrmFdrt7IfM6o8ktBAooDQ==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/split2": { @@ -14439,6 +13656,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -14454,18 +13678,24 @@ } }, "node_modules/stream-chain": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", - "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", - "license": "BSD-3-Clause" + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-4.2.6.tgz", + "integrity": "sha512-1zeJ8CrtJfmiba26ui8jXkq/xLRFvhzkdH02D5QLO9Cnovgeb28IJxg88DdraWnjnkbOol/++uIAybJbJhk7ig==", + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/sponsors/uhop" + } }, "node_modules/stream-json": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", - "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-3.7.0.tgz", + "integrity": "sha512-rCSBdcBP/bPk6T8QFcxAj1MSzAuc5i49cYW6IE7sYObNEPccBJIUiL6fU9c9BWt40aJK0siVynLX7lWaW8alXw==", "license": "BSD-3-Clause", "dependencies": { - "stream-chain": "^2.2.5" + "stream-chain": "^4.2.5" + }, + "funding": { + "url": "https://github.com/sponsors/uhop" } }, "node_modules/streamsearch": { @@ -14476,15 +13706,6 @@ "node": ">=10.0.0" } }, - "node_modules/strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -14628,28 +13849,30 @@ } }, "node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.5.1.tgz", + "integrity": "sha512-UM4nTLWzQiDBseVxUGxN0wJzIk0UU89Qi7pKXz9qISo+lhviJikTuikBsK/oFRlpvYHvSdz0TVoLyQ+sEpYwxA==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } }, "node_modules/strtok3": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", - "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", "license": "MIT", "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" + "@tokenizer/token": "^0.3.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "type": "github", @@ -14660,6 +13883,7 @@ "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -14682,6 +13906,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -14704,6 +13929,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -14776,6 +14002,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -14785,6 +14012,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -14818,6 +14046,16 @@ "readable-stream": "3" } }, + "node_modules/tinybench": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.2.tgz", + "integrity": "sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/tinycolor2": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", @@ -14825,22 +14063,23 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -14853,6 +14092,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -14867,9 +14107,10 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -14878,24 +14119,10 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -14914,16 +14141,17 @@ } }, "node_modules/token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", "license": "MIT", "dependencies": { + "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "type": "github", @@ -14940,6 +14168,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, "license": "MIT", "bin": { "tree-kill": "cli.js" @@ -14962,6 +14191,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, "license": "Apache-2.0" }, "node_modules/tsconfig-paths": { @@ -14999,6 +14229,7 @@ "version": "8.5.1", "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, "license": "MIT", "dependencies": { "bundle-require": "^5.1.0", @@ -15051,13 +14282,14 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, "license": "MIT" }, "node_modules/tsx": { "version": "4.20.6", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "esbuild": "~0.25.0", @@ -15519,7 +14751,7 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -15695,12 +14927,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -15719,12 +14945,13 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, "license": "MIT" }, "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.6.0.tgz", + "integrity": "sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==", "license": "MIT", "engines": { "node": ">=18" @@ -15753,9 +14980,9 @@ } }, "node_modules/undici": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", - "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz", + "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -15784,7 +15011,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -15819,16 +15045,6 @@ "node": ">=6" } }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", - "license": "MIT", - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -15848,19 +15064,6 @@ "pako": "^1.0.11" } }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -15877,9 +15080,9 @@ } }, "node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz", + "integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -15907,26 +15110,213 @@ "node": ">= 0.8" } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "node_modules/vite": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "defaults": "^1.0.3" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/web-encoding": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", - "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "node_modules/vitest": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.5.tgz", + "integrity": "sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==", + "dev": true, "license": "MIT", "dependencies": { - "util": "^0.12.3" + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.5", + "chai": "^6.3.0", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.4.3", + "obug": "^3.0.0", + "picomatch": "^4.0.7", + "std-env": "^4.3.0", + "tinybench": "^6.2.2", + "tinyexec": "^1.3.1", + "tinyglobby": "^0.2.17", + "why-is-node-running": "3.2.1" }, - "optionalDependencies": { - "@zxing/text-encoding": "0.9.0" + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.5", + "@vitest/browser-preview": "5.0.5", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.5", + "@vitest/coverage-v8": "5.0.5", + "@vitest/ui": "5.0.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/magic-string": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" } }, "node_modules/web-streams-polyfill": { @@ -15944,6 +15334,12 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, + "node_modules/whatsapp-rust-bridge": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/whatsapp-rust-bridge/-/whatsapp-rust-bridge-0.5.4.tgz", + "integrity": "sha512-yYO1qSs0Fe7tGtnxOFHomocUD6IZtoAgmA4oDFyGIRZ67D3QZk3w7swA6XXFXNQngiyrg2k7tul6IrM3eUFh7A==", + "license": "MIT" + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -15958,7 +15354,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -16047,6 +15442,7 @@ "version": "1.1.19", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -16064,6 +15460,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz", + "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==", + "dev": true, + "license": "MIT", + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=20.11" + } + }, + "node_modules/win-guid": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz", + "integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==", + "license": "MIT" + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -16136,9 +15551,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -16156,6 +15571,21 @@ } } }, + "node_modules/xml-naming": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/xml-parse-from-string": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", @@ -16217,16 +15647,18 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "devOptional": true, + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { @@ -16272,9 +15704,9 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 56e32fcc8a..b0249c9ddd 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,21 @@ "start": "tsx ./src/main.ts", "start:prod": "node dist/main", "dev:server": "tsx watch ./src/main.ts", - "test": "tsx watch ./test/all.test.ts", - "lint": "eslint --fix --ext .ts src", - "lint:check": "eslint --ext .ts src", + "test": "vitest run", + "lint": "eslint --fix --ext .ts src test", + "lint:check": "eslint --ext .ts src test", + "typecheck:test": "tsc -p tsconfig.test.json --noEmit", "commit": "cz", "commitlint": "commitlint --edit", - "db:generate": "node runWithProvider.js \"npx prisma generate --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"", - "db:deploy": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate deploy --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"", - "db:deploy:win": "node runWithProvider.js \"xcopy /E /I prisma\\DATABASE_PROVIDER-migrations prisma\\migrations && npx prisma migrate deploy --schema prisma\\DATABASE_PROVIDER-schema.prisma\"", - "db:studio": "node runWithProvider.js \"npx prisma studio --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"", - "db:migrate:dev": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate dev --schema ./prisma/DATABASE_PROVIDER-schema.prisma && cp -r ./prisma/migrations/* ./prisma/DATABASE_PROVIDER-migrations\"", - "db:migrate:dev:win": "node runWithProvider.js \"xcopy /E /I prisma\\DATABASE_PROVIDER-migrations prisma\\migrations && npx prisma migrate dev --schema prisma\\DATABASE_PROVIDER-schema.prisma\"", - "prepare": "husky" + "db:generate": "node runWithProvider.js \"prisma generate --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"", + "db:deploy": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && prisma migrate deploy --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"", + "db:deploy:win": "node runWithProvider.js \"xcopy /E /I prisma\\DATABASE_PROVIDER-migrations prisma\\migrations && prisma migrate deploy --schema prisma\\DATABASE_PROVIDER-schema.prisma\"", + "db:studio": "node runWithProvider.js \"prisma studio --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"", + "db:migrate:dev": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && prisma migrate dev --schema ./prisma/DATABASE_PROVIDER-schema.prisma && cp -r ./prisma/migrations/* ./prisma/DATABASE_PROVIDER-migrations\"", + "db:migrate:dev:win": "node runWithProvider.js \"xcopy /E /I prisma\\DATABASE_PROVIDER-migrations prisma\\migrations && prisma migrate dev --schema prisma\\DATABASE_PROVIDER-schema.prisma\"", + "prepare": "husky", + "postinstall": "patch-package", + "test:watch": "vitest" }, "repository": { "type": "git", @@ -57,6 +60,9 @@ ], "src/**/*.ts": [ "sh -c 'tsc --noEmit'" + ], + "test/**/*.ts": [ + "eslint --fix" ] }, "config": { @@ -71,13 +77,13 @@ "@figuro/chatwoot-sdk": "^1.1.16", "@hapi/boom": "^10.0.1", "@paralleldrive/cuid2": "^2.2.2", - "@prisma/client": "^6.16.2", + "@prisma/client": "^6.19.3", "@sentry/node": "^10.12.0", "@types/uuid": "^10.0.0", "amqplib": "^0.10.5", "audio-decode": "^2.2.3", "axios": "^1.7.9", - "baileys": "7.0.0-rc.9", + "baileys": "7.0.0-rc14", "class-validator": "^0.14.1", "compression": "^1.7.5", "cors": "^2.8.5", @@ -87,10 +93,10 @@ "eventemitter2": "^6.4.9", "express": "^4.21.2", "express-async-errors": "^3.1.1", + "fetch-socks": "^1.3.2", "fluent-ffmpeg": "^2.1.3", "form-data": "^4.0.1", "https-proxy-agent": "^7.0.6", - "fetch-socks": "^1.3.2", "i18next": "^23.7.19", "jimp": "^1.6.0", "json-schema": "^0.4.0", @@ -98,7 +104,7 @@ "jsonwebtoken": "^9.0.2", "kafkajs": "^2.2.4", "libphonenumber-js": "^1.12.25", - "link-preview-js": "^3.0.13", + "link-preview-js": "^4.0.4", "long": "^5.2.3", "mediainfo.js": "^0.3.4", "mime": "^4.0.0", @@ -107,22 +113,22 @@ "multer": "^2.0.2", "nats": "^2.29.1", "node-cache": "^5.1.2", - "node-cron": "^3.0.3", + "node-cron": "^4.6.0", "openai": "^4.77.3", + "patch-package": "^8.0.1", "pg": "^8.13.1", "pino": "^9.10.0", - "prisma": "^6.1.0", + "prisma": "^6.19.3", "pusher": "^5.2.0", "qrcode": "^1.5.4", "qrcode-terminal": "^0.12.0", "redis": "^4.7.0", "rxjs": "^7.8.2", - "sharp": "^0.34.2", + "sharp": "^0.35.5", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", "socks-proxy-agent": "^8.0.5", "swagger-ui-express": "^5.0.1", - "tsup": "^8.3.5", "undici": "^7.16.0", "uuid": "^13.0.0" }, @@ -136,7 +142,6 @@ "@types/mime": "^4.0.0", "@types/mime-types": "^2.1.4", "@types/node": "^24.5.2", - "@types/node-cron": "^3.0.11", "@types/qrcode": "^1.5.5", "@types/qrcode-terminal": "^0.12.2", "@typescript-eslint/eslint-plugin": "^8.44.0", @@ -149,10 +154,25 @@ "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-simple-import-sort": "^12.1.1", "husky": "^9.1.7", - "lint-staged": "^16.1.6", + "lint-staged": "^17.6.0", "prettier": "^3.4.2", "tsconfig-paths": "^4.2.0", + "tsup": "^8.3.5", "tsx": "^4.20.5", - "typescript": "^5.7.2" + "typescript": "^5.7.2", + "vitest": "^5.0.5" + }, + "overrides": { + "@figuro/chatwoot-sdk": { + "axios": "^1.20.0" + }, + "lodash": "^4.18.1", + "deepmerge-ts": "^8.0.0", + "link-preview-js": "^4.0.4", + "zod": "^4.6.7", + "minio": { + "query-string": "^10.0.0", + "stream-json": "^3.7.0" + } } } diff --git a/patches/baileys+7.0.0-rc14.patch b/patches/baileys+7.0.0-rc14.patch new file mode 100644 index 0000000000..d2726fcc40 --- /dev/null +++ b/patches/baileys+7.0.0-rc14.patch @@ -0,0 +1,313 @@ +diff --git a/node_modules/baileys/lib/Socket/messages-recv.js b/node_modules/baileys/lib/Socket/messages-recv.js +index f2003ac..1f72102 100644 +--- a/node_modules/baileys/lib/Socket/messages-recv.js ++++ b/node_modules/baileys/lib/Socket/messages-recv.js +@@ -880,6 +880,7 @@ export const makeMessagesRecvSocket = (config) => { + break; + case 'link_code_companion_reg': + const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg'); ++ if (!getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub')) { break; } + const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref')); + const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub')); + const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub')); +diff --git a/node_modules/baileys/lib/Socket/socket.js b/node_modules/baileys/lib/Socket/socket.js +index ae34a8f..9e6a580 100644 +--- a/node_modules/baileys/lib/Socket/socket.js ++++ b/node_modules/baileys/lib/Socket/socket.js +@@ -6,7 +6,7 @@ import { proto } from '../../WAProto/index.js'; + import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, NOISE_WA_HEADER, PROCESSABLE_HISTORY_TYPES, TimeMs, UPLOAD_TIMEOUT } from '../Defaults/index.js'; + import { QueryIds, ReachoutTimelockEnforcementType } from '../Types/index.js'; + import { DisconnectReason, XWAPaths } from '../Types/index.js'; +-import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, buildPairingQRData, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getCompanionPlatformId, getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js'; ++import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, buildPairingQRData, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getCompanionPlatformId, getErrorCodeFromStreamError, getNextPreKeysNode, handleCompanionRegRefresh, makeEventBuffer, makeNoiseHandler, makePairingQRRenderer, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js'; + import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isLidUser, jidDecode, jidEncode, S_WHATSAPP_NET } from '../WABinary/index.js'; + import { BinaryInfo } from '../WAM/BinaryInfo.js'; + import { USyncQuery, USyncUser } from '../WAUSync/index.js'; +@@ -690,6 +690,7 @@ export const makeSocket = (config) => { + ws.on('close', () => void end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionClosed }))); + // the server terminated the connection + ws.on('CB:xmlstreamend', () => void end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed }))); ++ let refreshPairingQR; + // QR gen + ws.on('CB:iq,type:set,pair-device', async (stanza) => { + const iq = { +@@ -705,25 +706,33 @@ export const makeSocket = (config) => { + const refNodes = getBinaryNodeChildren(pairDeviceNode, 'ref'); + const noiseKeyB64 = Buffer.from(creds.noiseKey.public).toString('base64'); + const identityKeyB64 = Buffer.from(creds.signedIdentityKey.public).toString('base64'); +- const advB64 = creds.advSecretKey; ++ const renderer = makePairingQRRenderer(refNodes.map(refNode => refNode.content.toString('utf-8')), ++ ref => ev.emit('connection.update', { ++ qr: buildPairingQRData(ref, noiseKeyB64, identityKeyB64, creds.advSecretKey, browser) ++ })); ++ refreshPairingQR = () => void renderer.refresh(); + let qrMs = qrTimeout || 60000; // time to let a QR live + const genPairQR = () => { + if (!ws.isOpen) { + return; + } +- const refNode = refNodes.shift(); +- if (!refNode) { ++ if (!renderer.next()) { + void end(new Boom('QR refs attempts ended', { statusCode: DisconnectReason.timedOut })); + return; + } +- const ref = refNode.content.toString('utf-8'); +- const qr = buildPairingQRData(ref, noiseKeyB64, identityKeyB64, advB64, browser); +- ev.emit('connection.update', { qr }); + qrTimer = setTimeout(genPairQR, qrMs); + qrMs = qrTimeout || 20000; // shorter subsequent qrs + }; + genPairQR(); + }); ++ ws.on('CB:notification,type:companion_reg_refresh', (node) => { ++ handleCompanionRegRefresh(node, { ++ creds, ++ emitCredsUpdate: update => ev.emit('creds.update', update), ++ refreshQR: () => refreshPairingQR?.(), ++ logger ++ }); ++ }); + // device paired for the first time + // if device pairs successfully, the server asks to restart the connection + ws.on('CB:iq,,pair-success', async (stanza) => { +diff --git a/node_modules/baileys/lib/Utils/auth-utils.js b/node_modules/baileys/lib/Utils/auth-utils.js +index 24a3583..183d237 100644 +--- a/node_modules/baileys/lib/Utils/auth-utils.js ++++ b/node_modules/baileys/lib/Utils/auth-utils.js +@@ -74,6 +74,7 @@ export function makeCacheableSignalKeyStore(store, logger, _cache) { + } + }; + } ++const txStorage = new AsyncLocalStorage(); + /** + * Adds DB-like transaction capability to the SignalKeyStore + * Uses AsyncLocalStorage for automatic context management +@@ -82,7 +83,10 @@ export function makeCacheableSignalKeyStore(store, logger, _cache) { + * @returns SignalKeyStore with transaction capability + */ + export const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetweenTriesMs }) => { +- const txStorage = new AsyncLocalStorage(); ++ const storeToken = Symbol('signalTxStore'); ++ function getContext() { ++ return txStorage.getStore()?.get(storeToken); ++ } + // Queues for concurrency control (keyed by signal data type - bounded set) + const keyQueues = new Map(); + // Transaction mutexes with reference counting for cleanup +@@ -135,7 +139,7 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela + * Check if currently in a transaction + */ + function isInTransaction() { +- return !!txStorage.getStore(); ++ return !!getContext(); + } + /** + * Commit transaction with retries +@@ -164,7 +168,7 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela + } + return { + get: async (type, ids) => { +- const ctx = txStorage.getStore(); ++ const ctx = getContext(); + if (!ctx) { + // No transaction - direct read without exclusive lock for concurrency + return state.get(type, ids); +@@ -191,7 +195,7 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela + return result; + }, + set: async (data) => { +- const ctx = txStorage.getStore(); ++ const ctx = getContext(); + if (!ctx) { + // No transaction - direct write with queue protection + const types = Object.keys(data); +@@ -229,7 +233,8 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela + }, + isInTransaction, + transaction: async (work, key) => { +- const existing = txStorage.getStore(); ++ const stores = txStorage.getStore(); ++ const existing = stores?.get(storeToken); + // Nested transaction - reuse existing context + if (existing) { + logger.trace('reusing existing transaction context'); +@@ -245,9 +250,11 @@ export const addTransactionCapability = (state, logger, { maxCommitRetries, dela + mutations: {}, + dbQueries: 0 + }; ++ const next = new Map(stores); ++ next.set(storeToken, ctx); + logger.trace('entering transaction'); + try { +- const result = await txStorage.run(ctx, work); ++ const result = await txStorage.run(next, work); + // Commit mutations + await commitWithRetry(ctx.mutations); + logger.trace({ dbQueries: ctx.dbQueries }, 'transaction completed'); +diff --git a/node_modules/baileys/lib/Utils/companion-reg-client-utils.d.ts b/node_modules/baileys/lib/Utils/companion-reg-client-utils.d.ts +index a79a52c..3c45d4e 100644 +--- a/node_modules/baileys/lib/Utils/companion-reg-client-utils.d.ts ++++ b/node_modules/baileys/lib/Utils/companion-reg-client-utils.d.ts +@@ -1,4 +1,6 @@ +-import type { WABrowserDescription } from '../Types/index.js'; ++import type { AuthenticationCreds, WABrowserDescription } from '../Types/index.js'; ++import type { BinaryNode } from '../WABinary/index.js'; ++import type { ILogger } from './logger.js'; + export declare enum CompanionWebClientType { + UNKNOWN = 0, + CHROME = 1, +@@ -14,4 +16,17 @@ export declare enum CompanionWebClientType { + export declare const getCompanionWebClientType: ([os, browserName]: WABrowserDescription) => CompanionWebClientType; + export declare const getCompanionPlatformId: (browser: WABrowserDescription) => string; + export declare const buildPairingQRData: (ref: string, noiseKeyB64: string, identityKeyB64: string, advB64: string, browser: WABrowserDescription) => string; ++export type PairingQRRenderer = { ++ next(): boolean; ++ refresh(): boolean; ++}; ++export declare const makePairingQRRenderer: (refs: string[], render: (ref: string) => void) => PairingQRRenderer; ++export type CompanionRegRefreshContext = { ++ creds: AuthenticationCreds; ++ emitCredsUpdate: (update: Partial) => void; ++ refreshQR: () => void; ++ logger: ILogger; ++}; ++export type CompanionRegRefreshOutcome = 'rotated' | 'ignored_malformed' | 'ignored_registered'; ++export declare const handleCompanionRegRefresh: (node: BinaryNode, { creds, emitCredsUpdate, refreshQR, logger }: CompanionRegRefreshContext) => CompanionRegRefreshOutcome; + //# sourceMappingURL=companion-reg-client-utils.d.ts.map +\ No newline at end of file +diff --git a/node_modules/baileys/lib/Utils/companion-reg-client-utils.js b/node_modules/baileys/lib/Utils/companion-reg-client-utils.js +index f5edebd..9efdd5e 100644 +--- a/node_modules/baileys/lib/Utils/companion-reg-client-utils.js ++++ b/node_modules/baileys/lib/Utils/companion-reg-client-utils.js +@@ -1,3 +1,5 @@ ++import { randomBytes } from 'crypto'; ++import { getBinaryNodeChild } from '../WABinary/index.js'; + export var CompanionWebClientType; + (function (CompanionWebClientType) { + CompanionWebClientType[CompanionWebClientType["UNKNOWN"] = 0] = "UNKNOWN"; +@@ -32,4 +34,43 @@ export const buildPairingQRData = (ref, noiseKeyB64, identityKeyB64, advB64, bro + return ('https://wa.me/settings/linked_devices#' + + [ref, noiseKeyB64, identityKeyB64, advB64, getCompanionPlatformId(browser)].join(',')); + }; ++export const makePairingQRRenderer = (refs, render) => { ++ let index = 0; ++ let current; ++ return { ++ next() { ++ const ref = refs[index]; ++ if (ref === undefined) { ++ return false; ++ } ++ index += 1; ++ current = ref; ++ render(ref); ++ return true; ++ }, ++ refresh() { ++ if (current === undefined) { ++ return false; ++ } ++ render(current); ++ return true; ++ } ++ }; ++}; ++const COMPANION_REG_REFRESH_CHILDREN = ['companion_reg_refresh', 'pair-device-rotate-qr']; ++export const handleCompanionRegRefresh = (node, { creds, emitCredsUpdate, refreshQR, logger }) => { ++ if (!COMPANION_REG_REFRESH_CHILDREN.some(tag => getBinaryNodeChild(node, tag))) { ++ logger.warn({ node }, 'companion_reg_refresh carries neither expected child; ignoring'); ++ return 'ignored_malformed'; ++ } ++ if (creds.me) { ++ logger.debug({ id: node.attrs.id }, 'companion_reg_refresh on a registered session; keeping the adv secret'); ++ return 'ignored_registered'; ++ } ++ creds.advSecretKey = randomBytes(32).toString('base64'); ++ emitCredsUpdate({ advSecretKey: creds.advSecretKey }); ++ logger.info({ id: node.attrs.id }, 'rotated the adv secret the server asked to retire; re-rendering the pairing QR'); ++ refreshQR(); ++ return 'rotated'; ++}; + //# sourceMappingURL=companion-reg-client-utils.js.map +\ No newline at end of file +diff --git a/node_modules/baileys/lib/Utils/link-preview.js b/node_modules/baileys/lib/Utils/link-preview.js +index 0f4ff1c..1d29fe6 100644 +--- a/node_modules/baileys/lib/Utils/link-preview.js ++++ b/node_modules/baileys/lib/Utils/link-preview.js +@@ -56,7 +56,7 @@ export const getUrlInfo = async (text, opts = { + description: info.description, + originalThumbnailUrl: image + }; +- if (opts.uploadImage) { ++ if (opts.uploadImage && image) { + const { imageMessage } = await prepareWAMessageMedia({ image: { url: image } }, { + upload: opts.uploadImage, + mediaTypeOverride: 'thumbnail-link', +diff --git a/node_modules/baileys/lib/Utils/process-message.js b/node_modules/baileys/lib/Utils/process-message.js +index 37449db..678f3d3 100644 +--- a/node_modules/baileys/lib/Utils/process-message.js ++++ b/node_modules/baileys/lib/Utils/process-message.js +@@ -2,7 +2,7 @@ import { Boom } from '@hapi/boom'; + import { proto } from '../../WAProto/index.js'; + import { WAMessageStubType } from '../Types/index.js'; + import { getContentType, normalizeMessageContent } from '../Utils/messages.js'; +-import { areJidsSameUser, isHostedLidUser, isHostedPnUser, isJidBroadcast, isJidStatusBroadcast, isLidUser, jidDecode, jidEncode, jidNormalizedUser } from '../WABinary/index.js'; ++import { areJidsSameUser, isHostedLidUser, isHostedPnUser, isJidBroadcast, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser } from '../WABinary/index.js'; + import { aesDecryptGCM, hmacSign } from './crypto.js'; + import { getKeyAuthor, toNumber } from './generics.js'; + import { downloadAndProcessHistorySyncNotification } from './history.js'; +@@ -183,6 +183,16 @@ export function decryptEventResponse({ encPayload, encIv }, { eventCreatorJid, e + return Buffer.from(txt); + } + } ++const parseStubParticipant = (parameter) => { ++ try { ++ const parsed = JSON.parse(parameter); ++ if (parsed && typeof parsed === 'object') { ++ return parsed; ++ } ++ } ++ catch { } ++ return isPnUser(parameter) ? { id: parameter, phoneNumber: parameter } : { id: parameter }; ++}; + const processMessage = async (message, { shouldProcessHistoryMsg, placeholderResendCache, ev, creds, signalRepository, keyStore, logger, options, getMessage }) => { + const meId = creds.me.id; + const { accountSettings } = creds; +@@ -507,12 +517,12 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes + const participantsIncludesMe = () => participants.find(jid => areJidsSameUser(meId, jid.phoneNumber)); // ADD SUPPORT FOR LID + switch (message.messageStubType) { + case WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER: +- participants = message.messageStubParameters.map((a) => JSON.parse(a)) || []; ++ participants = message.messageStubParameters.map(parseStubParticipant) || []; + emitParticipantsUpdate('modify'); + break; + case WAMessageStubType.GROUP_PARTICIPANT_LEAVE: + case WAMessageStubType.GROUP_PARTICIPANT_REMOVE: +- participants = message.messageStubParameters.map((a) => JSON.parse(a)) || []; ++ participants = message.messageStubParameters.map(parseStubParticipant) || []; + emitParticipantsUpdate('remove'); + // mark the chat read only if you left the group + if (participantsIncludesMe()) { +@@ -522,18 +532,18 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes + case WAMessageStubType.GROUP_PARTICIPANT_ADD: + case WAMessageStubType.GROUP_PARTICIPANT_INVITE: + case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN: +- participants = message.messageStubParameters.map((a) => JSON.parse(a)) || []; ++ participants = message.messageStubParameters.map(parseStubParticipant) || []; + if (participantsIncludesMe()) { + chat.readOnly = false; + } + emitParticipantsUpdate('add'); + break; + case WAMessageStubType.GROUP_PARTICIPANT_DEMOTE: +- participants = message.messageStubParameters.map((a) => JSON.parse(a)) || []; ++ participants = message.messageStubParameters.map(parseStubParticipant) || []; + emitParticipantsUpdate('demote'); + break; + case WAMessageStubType.GROUP_PARTICIPANT_PROMOTE: +- participants = message.messageStubParameters.map((a) => JSON.parse(a)) || []; ++ participants = message.messageStubParameters.map(parseStubParticipant) || []; + emitParticipantsUpdate('promote'); + break; + case WAMessageStubType.GROUP_CHANGE_ANNOUNCE: diff --git a/patches/minio+8.0.7.patch b/patches/minio+8.0.7.patch new file mode 100644 index 0000000000..ac0d0a0b47 --- /dev/null +++ b/patches/minio+8.0.7.patch @@ -0,0 +1,95 @@ +diff --git a/node_modules/minio/dist/esm/helpers.mjs b/node_modules/minio/dist/esm/helpers.mjs +index edac39c..8e547bc 100644 +--- a/node_modules/minio/dist/esm/helpers.mjs ++++ b/node_modules/minio/dist/esm/helpers.mjs +@@ -1,6 +1,6 @@ + import * as fs from "fs"; + import * as path from "path"; +-import * as querystring from 'query-string'; ++import querystring from 'query-string'; + import * as errors from "./errors.mjs"; + import { getEncryptionHeaders, isEmpty, isEmptyObject, isNumber, isObject, isString, isValidBucketName, isValidObjectName } from "./internal/helper.mjs"; + import { RETENTION_MODES } from "./internal/type.mjs"; +diff --git a/node_modules/minio/dist/esm/internal/client.mjs b/node_modules/minio/dist/esm/internal/client.mjs +index e9f9669..17c4c8d 100644 +--- a/node_modules/minio/dist/esm/internal/client.mjs ++++ b/node_modules/minio/dist/esm/internal/client.mjs +@@ -8,7 +8,7 @@ import * as async from 'async'; + import BlockStream2 from 'block-stream2'; + import { isBrowser } from 'browser-or-node'; + import _ from 'lodash'; +-import * as qs from 'query-string'; ++import qs from 'query-string'; + import xml2js from 'xml2js'; + import { CredentialProvider } from "../CredentialProvider.mjs"; + import * as errors from "../errors.mjs"; +diff --git a/node_modules/minio/dist/esm/notification.mjs b/node_modules/minio/dist/esm/notification.mjs +index 2fe075b..a07da8d 100644 +--- a/node_modules/minio/dist/esm/notification.mjs ++++ b/node_modules/minio/dist/esm/notification.mjs +@@ -15,7 +15,7 @@ + */ + + import { EventEmitter } from 'eventemitter3'; +-import jsonLineParser from 'stream-json/jsonl/Parser.js'; ++import jsonLineParser from 'stream-json/jsonl/parser.js'; + import { DEFAULT_REGION } from "./helpers.mjs"; + import { pipesetup, uriEscape } from "./internal/helper.mjs"; + +@@ -176,7 +176,7 @@ export class NotificationPoller extends EventEmitter { + bucketName: this.bucketName, + query + }, '', [200], region).then(response => { +- const asm = jsonLineParser.make(); ++ const asm = jsonLineParser.asStream(); + pipesetup(response, asm).on('data', data => { + // Data is flushed periodically (every 5 seconds), so we should + // handle it after flushing from the JSON parser. +diff --git a/node_modules/minio/dist/main/helpers.js b/node_modules/minio/dist/main/helpers.js +index bea5b04..d19b46c 100644 +--- a/node_modules/minio/dist/main/helpers.js ++++ b/node_modules/minio/dist/main/helpers.js +@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { + exports.removeDirAndFiles = removeDirAndFiles; + var fs = _interopRequireWildcard(require("fs"), true); + var path = _interopRequireWildcard(require("path"), true); +-var querystring = _interopRequireWildcard(require("query-string"), true); ++var querystring = require("query-string").default; + var errors = _interopRequireWildcard(require("./errors.js"), true); + var _helper = require("./internal/helper.js"); + var _type = require("./internal/type.js"); +diff --git a/node_modules/minio/dist/main/internal/client.js b/node_modules/minio/dist/main/internal/client.js +index 5ab16ff..306b586 100644 +--- a/node_modules/minio/dist/main/internal/client.js ++++ b/node_modules/minio/dist/main/internal/client.js +@@ -13,7 +13,7 @@ var async = _interopRequireWildcard(require("async"), true); + var _blockStream = require("block-stream2"); + var _browserOrNode = require("browser-or-node"); + var _lodash = require("lodash"); +-var qs = _interopRequireWildcard(require("query-string"), true); ++var qs = require("query-string").default; + var _xml2js = require("xml2js"); + var _CredentialProvider = require("../CredentialProvider.js"); + var errors = _interopRequireWildcard(require("../errors.js"), true); +diff --git a/node_modules/minio/dist/main/notification.js b/node_modules/minio/dist/main/notification.js +index 8d8e8b3..6fe6e90 100644 +--- a/node_modules/minio/dist/main/notification.js ++++ b/node_modules/minio/dist/main/notification.js +@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", { + value: true + }); + var _eventemitter = require("eventemitter3"); +-var _Parser = require("stream-json/jsonl/Parser.js"); ++var _jsonlParser = require("stream-json/jsonl/parser.js").default; + var _helpers = require("./helpers.js"); + var _helper = require("./internal/helper.js"); + /* +@@ -196,7 +196,7 @@ class NotificationPoller extends _eventemitter.EventEmitter { + bucketName: this.bucketName, + query + }, '', [200], region).then(response => { +- const asm = _Parser.make(); ++ const asm = _jsonlParser.asStream(); + (0, _helper.pipesetup)(response, asm).on('data', data => { + // Data is flushed periodically (every 5 seconds), so we should + // handle it after flushing from the JSON parser. diff --git a/prisma/mysql-migrations/20261023120000_add_is_on_whatsapp_jid/migration.sql b/prisma/mysql-migrations/20261023120000_add_is_on_whatsapp_jid/migration.sql new file mode 100644 index 0000000000..29a04fc32e --- /dev/null +++ b/prisma/mysql-migrations/20261023120000_add_is_on_whatsapp_jid/migration.sql @@ -0,0 +1,10 @@ +-- CreateTable +CREATE TABLE `IsOnWhatsappJid` ( + `jid` VARCHAR(100) NOT NULL, + `remoteJid` VARCHAR(100) NOT NULL, + `lid` VARCHAR(100) NULL, + `updatedAt` TIMESTAMP NOT NULL, + + INDEX `IsOnWhatsappJid_remoteJid_idx`(`remoteJid`), + PRIMARY KEY (`jid`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/prisma/mysql-schema.prisma b/prisma/mysql-schema.prisma index 71b5a743f0..16f6103694 100644 --- a/prisma/mysql-schema.prisma +++ b/prisma/mysql-schema.prisma @@ -659,6 +659,15 @@ model IsOnWhatsapp { updatedAt DateTime @updatedAt @db.Timestamp } +model IsOnWhatsappJid { + jid String @id @db.VarChar(100) + remoteJid String @db.VarChar(100) + lid String? @db.VarChar(100) + updatedAt DateTime @updatedAt @db.Timestamp + + @@index([remoteJid]) +} + model N8n { id String @id @default(cuid()) enabled Boolean @default(true) @db.TinyInt() diff --git a/prisma/postgresql-migrations/20261023120000_add_is_on_whatsapp_jid/migration.sql b/prisma/postgresql-migrations/20261023120000_add_is_on_whatsapp_jid/migration.sql new file mode 100644 index 0000000000..0144101b61 --- /dev/null +++ b/prisma/postgresql-migrations/20261023120000_add_is_on_whatsapp_jid/migration.sql @@ -0,0 +1,27 @@ +-- CreateTable +CREATE TABLE "IsOnWhatsappJid" ( + "jid" VARCHAR(100) NOT NULL, + "remoteJid" VARCHAR(100) NOT NULL, + "lid" VARCHAR(100), + "updatedAt" TIMESTAMP NOT NULL, + + CONSTRAINT "IsOnWhatsappJid_pkey" PRIMARY KEY ("jid") +); + +-- CreateIndex +CREATE INDEX "IsOnWhatsappJid_remoteJid_idx" ON "IsOnWhatsappJid"("remoteJid"); + +-- Backfill from IsOnWhatsapp, one row per jid option +INSERT INTO "IsOnWhatsappJid" ("jid", "remoteJid", "lid", "updatedAt") +SELECT DISTINCT ON ("jid") "jid", "remoteJid", "lid", "updatedAt" +FROM ( + SELECT trim(option) AS "jid", o."remoteJid", o."lid", o."updatedAt" + FROM "IsOnWhatsapp" o + CROSS JOIN LATERAL unnest(string_to_array(o."jidOptions", ',') || o."remoteJid") AS option +) expanded +WHERE "jid" <> '' + AND length("jid") <= 100 + AND length("jid") - length(replace("jid", '@', '')) <= 1 + AND "jid" NOT LIKE '%@undefined' +ORDER BY "jid", "updatedAt" DESC +ON CONFLICT ("jid") DO NOTHING; diff --git a/prisma/postgresql-migrations/20261024120000_add_message_instance_key_id_index/migration.sql b/prisma/postgresql-migrations/20261024120000_add_message_instance_key_id_index/migration.sql new file mode 100644 index 0000000000..95c4d39538 --- /dev/null +++ b/prisma/postgresql-migrations/20261024120000_add_message_instance_key_id_index/migration.sql @@ -0,0 +1 @@ +CREATE INDEX IF NOT EXISTS "idx_message_instance_key_id" ON "Message" USING btree ("instanceId", (("key" ->> 'id'::text))); diff --git a/prisma/postgresql-migrations/20261024120100_add_message_instance_remotejid_fromme_index/migration.sql b/prisma/postgresql-migrations/20261024120100_add_message_instance_remotejid_fromme_index/migration.sql new file mode 100644 index 0000000000..ed58c2917a --- /dev/null +++ b/prisma/postgresql-migrations/20261024120100_add_message_instance_remotejid_fromme_index/migration.sql @@ -0,0 +1 @@ +CREATE INDEX IF NOT EXISTS "idx_message_instance_remotejid_fromme" ON "Message" USING btree ("instanceId", (("key" ->> 'remoteJid'::text)), ((("key" ->> 'fromMe'::text))::boolean)); diff --git a/prisma/postgresql-schema.prisma b/prisma/postgresql-schema.prisma index 6b98f88da4..64ff71f4a7 100644 --- a/prisma/postgresql-schema.prisma +++ b/prisma/postgresql-schema.prisma @@ -665,6 +665,15 @@ model IsOnWhatsapp { updatedAt DateTime @updatedAt @db.Timestamp } +model IsOnWhatsappJid { + jid String @id @db.VarChar(100) + remoteJid String @db.VarChar(100) + lid String? @db.VarChar(100) + updatedAt DateTime @updatedAt @db.Timestamp + + @@index([remoteJid]) +} + model N8n { id String @id @default(cuid()) enabled Boolean @default(true) @db.Boolean diff --git a/prisma/psql_bouncer-schema.prisma b/prisma/psql_bouncer-schema.prisma index a3f4dbe902..3e8327ae50 100644 --- a/prisma/psql_bouncer-schema.prisma +++ b/prisma/psql_bouncer-schema.prisma @@ -665,6 +665,15 @@ model IsOnWhatsapp { updatedAt DateTime @updatedAt @db.Timestamp } +model IsOnWhatsappJid { + jid String @id @db.VarChar(100) + remoteJid String @db.VarChar(100) + lid String? @db.VarChar(100) + updatedAt DateTime @updatedAt @db.Timestamp + + @@index([remoteJid]) +} + model N8n { id String @id @default(cuid()) enabled Boolean @default(true) @db.Boolean diff --git a/scripts/backfill-lid-remote-jid-alt/README.md b/scripts/backfill-lid-remote-jid-alt/README.md new file mode 100644 index 0000000000..b4c14d253e --- /dev/null +++ b/scripts/backfill-lid-remote-jid-alt/README.md @@ -0,0 +1,69 @@ +# Backfill `key.remoteJidAlt` for `@lid` messages + +One-off, idempotent, operator-run PostgreSQL script. Inspired by upstream EvolutionAPI/evolution-api#2663 by @fidelis05. + +## Purpose + +Older `"Message"` rows (history-sync shaped) have `key.remoteJid = "@lid"` and no `key.remoteJidAlt`. +Live messages from the same contact usually carry `key.remoteJidAlt = "@s.whatsapp.net"`. This script +copies that phone JID onto the older rows of the **same `instanceId`** and the same `key.remoteJid`. + +Guarantees: + +- It **never changes `key.remoteJid`**. Only `key.remoteJidAlt` is written. +- It writes only when `key.remoteJidAlt` is missing, empty or not a phone JID, and only with a value ending in `@s.whatsapp.net`. +- Mappings never cross instances. +- A lid that maps to more than one distinct phone within an instance is skipped and counted. +- Re-running it updates 0 rows. + +PostgreSQL only. + +## Steps + +1. Back up the table first: + + ```bash + pg_dump "$DATABASE_CONNECTION_URI" -t '"Message"' -Fc -f message-backup.dump + ``` + +2. Dry run (read-only). Reports candidate rows, fixable rows, rows with a non-phone `remoteJidAlt` + (these are replaced when fixable), ambiguous lids, and the top 10 instances by fixable rows: + + ```bash + psql "$DATABASE_CONNECTION_URI" -f scripts/backfill-lid-remote-jid-alt/dry-run.sql + ``` + +3. Apply (single transaction, prints `updated_rows`, `changed_meanwhile_rows_skipped` and `ambiguous_lids_skipped`). Before updating, it copies + the original `(id, key)` of every row it is about to change into `"_lid_remote_jid_alt_backup"` (created if + missing; on re-runs the first saved original wins). It runs with `lock_timeout = '5s'` and + `statement_timeout = '10min'` so it fails fast instead of blocking live writes; on a timeout nothing is + committed and it can simply be re-run. Updated rows stay locked until the transaction commits, so live status + updates on those rows wait for it; run it during low traffic. Measured candidates are about 20k rows per + database, which commits in seconds: + + ```bash + psql "$DATABASE_CONNECTION_URI" -f scripts/backfill-lid-remote-jid-alt/apply.sql + ``` + +4. Verify: run the dry run again. `fixable_rows` must be 0; running `apply.sql` again must print `updated_rows = 0`. Rows changed by Evolution while the script ran are skipped (`changed_meanwhile_rows_skipped`); run it again to pick them up. + +## Rollback + +Precise rollback from the backup table (single transaction: restores `key` for every backed-up id, prints +`restored_rows`, then drops `"_lid_remote_jid_alt_backup"`): + +```bash +psql "$DATABASE_CONNECTION_URI" -f scripts/backfill-lid-remote-jid-alt/rollback.sql +``` + +Rollback replaces the whole `key` of each backed-up row, so any change made to those keys after `apply.sql` +(for example a status update) is lost. Roll back soon after applying, or not at all. + +The full `pg_dump` from step 1 remains the last-resort backup; restore individual rows from it rather than +dropping and recreating `"Message"`. + +Once the backfill is verified and no rollback is needed, drop the backup table manually: + +```sql +DROP TABLE "_lid_remote_jid_alt_backup"; +``` diff --git a/scripts/backfill-lid-remote-jid-alt/apply.sql b/scripts/backfill-lid-remote-jid-alt/apply.sql new file mode 100644 index 0000000000..c021c99614 --- /dev/null +++ b/scripts/backfill-lid-remote-jid-alt/apply.sql @@ -0,0 +1,58 @@ +\set ON_ERROR_STOP on +BEGIN; +SET LOCAL lock_timeout = '5s'; +SET LOCAL statement_timeout = '10min'; + +CREATE TABLE IF NOT EXISTS "_lid_remote_jid_alt_backup" ( + id text PRIMARY KEY, + key jsonb NOT NULL, + backed_up_at timestamp DEFAULT now() +); + +CREATE TEMP TABLE lid_backfill_targets ON COMMIT DROP AS +WITH lid_phone_map AS ( + SELECT "instanceId", key->>'remoteJid' AS lid, min(key->>'remoteJidAlt') AS phone_jid + FROM "Message" + WHERE key->>'remoteJid' LIKE '%@lid' + AND key->>'remoteJidAlt' LIKE '%@s.whatsapp.net' + GROUP BY "instanceId", key->>'remoteJid' + HAVING count(DISTINCT key->>'remoteJidAlt') = 1 +) +SELECT m.id, m.key AS original_key, p.phone_jid +FROM "Message" m +JOIN lid_phone_map p ON p."instanceId" = m."instanceId" AND p.lid = m.key->>'remoteJid' +WHERE m.key->>'remoteJid' LIKE '%@lid' + AND coalesce(m.key->>'remoteJidAlt', '') NOT LIKE '%@s.whatsapp.net' + AND p.phone_jid LIKE '%@s.whatsapp.net'; + +WITH updated AS ( + UPDATE "Message" m + SET key = jsonb_set(m.key, '{remoteJidAlt}', to_jsonb(t.phone_jid), true) + FROM lid_backfill_targets t + WHERE t.id = m.id + AND m.key = t.original_key + RETURNING m.id +), +backed_up AS ( + INSERT INTO "_lid_remote_jid_alt_backup" (id, key) + SELECT t.id, t.original_key + FROM lid_backfill_targets t + JOIN updated u ON u.id = t.id + ON CONFLICT (id) DO NOTHING + RETURNING id +) +SELECT + (SELECT count(*) FROM updated) AS updated_rows, + (SELECT count(*) FROM lid_backfill_targets) - (SELECT count(*) FROM updated) AS changed_meanwhile_rows_skipped; + +WITH ambiguous AS ( + SELECT "instanceId", key->>'remoteJid' AS lid + FROM "Message" + WHERE key->>'remoteJid' LIKE '%@lid' + AND key->>'remoteJidAlt' LIKE '%@s.whatsapp.net' + GROUP BY "instanceId", key->>'remoteJid' + HAVING count(DISTINCT key->>'remoteJidAlt') > 1 +) +SELECT count(*) AS ambiguous_lids_skipped FROM ambiguous; + +COMMIT; diff --git a/scripts/backfill-lid-remote-jid-alt/dry-run.sql b/scripts/backfill-lid-remote-jid-alt/dry-run.sql new file mode 100644 index 0000000000..d2e32ec4e2 --- /dev/null +++ b/scripts/backfill-lid-remote-jid-alt/dry-run.sql @@ -0,0 +1,37 @@ +WITH lid_phone_map AS ( + SELECT "instanceId", key->>'remoteJid' AS lid, count(DISTINCT key->>'remoteJidAlt') AS phone_count + FROM "Message" + WHERE key->>'remoteJid' LIKE '%@lid' + AND key->>'remoteJidAlt' LIKE '%@s.whatsapp.net' + GROUP BY "instanceId", key->>'remoteJid' +), +candidates AS ( + SELECT m."instanceId", key->>'remoteJid' AS lid, coalesce(key->>'remoteJidAlt', '') = '' AS alt_missing + FROM "Message" m + WHERE key->>'remoteJid' LIKE '%@lid' + AND coalesce(key->>'remoteJidAlt', '') NOT LIKE '%@s.whatsapp.net' +) +SELECT + (SELECT count(*) FROM candidates) AS candidate_rows, + (SELECT count(*) FROM candidates c JOIN lid_phone_map p USING ("instanceId", lid) + WHERE p.phone_count = 1) AS fixable_rows, + (SELECT count(*) FROM candidates WHERE NOT alt_missing) AS non_phone_alt_rows, + (SELECT count(*) FROM lid_phone_map WHERE phone_count > 1) AS ambiguous_lids, + (SELECT count(*) FROM candidates c JOIN lid_phone_map p USING ("instanceId", lid) + WHERE p.phone_count > 1) AS ambiguous_rows_skipped; + +WITH lid_phone_map AS ( + SELECT "instanceId", key->>'remoteJid' AS lid, count(DISTINCT key->>'remoteJidAlt') AS phone_count + FROM "Message" + WHERE key->>'remoteJid' LIKE '%@lid' + AND key->>'remoteJidAlt' LIKE '%@s.whatsapp.net' + GROUP BY "instanceId", key->>'remoteJid' +) +SELECT m."instanceId", count(*) AS fixable_rows +FROM "Message" m +JOIN lid_phone_map p ON p."instanceId" = m."instanceId" AND p.lid = m.key->>'remoteJid' +WHERE p.phone_count = 1 + AND coalesce(m.key->>'remoteJidAlt', '') NOT LIKE '%@s.whatsapp.net' +GROUP BY m."instanceId" +ORDER BY fixable_rows DESC +LIMIT 10; diff --git a/scripts/backfill-lid-remote-jid-alt/rollback.sql b/scripts/backfill-lid-remote-jid-alt/rollback.sql new file mode 100644 index 0000000000..2dd4ff322e --- /dev/null +++ b/scripts/backfill-lid-remote-jid-alt/rollback.sql @@ -0,0 +1,17 @@ +\set ON_ERROR_STOP on +BEGIN; +SET LOCAL lock_timeout = '5s'; +SET LOCAL statement_timeout = '10min'; + +WITH restored AS ( + UPDATE "Message" m + SET key = b.key + FROM "_lid_remote_jid_alt_backup" b + WHERE b.id = m.id + RETURNING m.id +) +SELECT count(*) AS restored_rows FROM restored; + +DROP TABLE "_lid_remote_jid_alt_backup"; + +COMMIT; diff --git a/scripts/check-docs.sh b/scripts/check-docs.sh new file mode 100755 index 0000000000..df5134a66f --- /dev/null +++ b/scripts/check-docs.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Docs drift guard. Usage: scripts/check-docs.sh [root] +# +# Rules (one `file:line: reason` per violation; exit 1 when any is found): +# a) CLAUDE.md is a pointer to AGENTS.md (references AGENTS.md, at most 20 lines) +# b) every .claude/skills/*/SKILL.md has frontmatter with `name:` and a +# `description:` containing `Trigger:` +# c) every .claude/skills/ name appears in AGENTS.md +# d) relative markdown links in AGENTS.md, odd/agent-protocol.md and +# .claude/skills/**/*.md resolve (http(s)/mailto/anchors are skipped) +# Run via `make check-docs`; also invoked by .husky/pre-commit and CI. +set -euo pipefail + +root=${1:-$(git -C "$(dirname "$0")/.." rev-parse --show-toplevel)} +cd "$root" + +violations=$(mktemp) +trap 'rm -f "$violations"' EXIT +report() { echo "$1" >>"$violations"; } + +# --- a) CLAUDE.md pointer --------------------------------------------------- +if [[ ! -f AGENTS.md ]]; then + report "AGENTS.md:0: missing (canonical agent guide)" +fi +if [[ ! -f CLAUDE.md ]]; then + report "CLAUDE.md:0: missing (must be a pointer to AGENTS.md)" +else + grep -q 'AGENTS\.md' CLAUDE.md || report "CLAUDE.md:1: does not reference AGENTS.md" + lines=$(wc -l /dev/null | + while IFS=: read -r line link; do + target=${link#](} + target=${target%)} + target=${target%% *} # drop optional "title" + target=${target%%#*} # drop anchors + case $target in + '' | http://* | https://* | mailto:*) continue ;; + /*) path=.$target ;; + *) path=$base/$target ;; + esac + [[ -e $path ]] || report "$f:$line: broken link '$target'" + done || true +done + +if [[ -s $violations ]]; then + cat "$violations" + echo "check-docs: $(wc -l <"$violations" | tr -d ' ') violation(s)" >&2 + exit 1 +fi +echo "check-docs: OK" diff --git a/src/api/abstract/abstract.router.ts b/src/api/abstract/abstract.router.ts index e8449a8c8a..6d556c9b73 100644 --- a/src/api/abstract/abstract.router.ts +++ b/src/api/abstract/abstract.router.ts @@ -17,6 +17,14 @@ type DataValidate = { const logger = new Logger('Validate'); +const isInstanceCreateRoute = (request: Request) => + request.baseUrl.endsWith('/instance') && request.route?.path === '/create'; + +const restoreAuthorizedInstance = (instance: InstanceDto, authorizedInstanceName: string) => { + instance.instanceName = authorizedInstanceName; + delete instance.instanceId; +}; + export abstract class RouterBroker { constructor() {} public routerPath(path: string, param = true) { @@ -32,15 +40,20 @@ export abstract class RouterBroker { const ref = new ClassRef(); const body = request.body; const instance = request.params as unknown as InstanceDto; + const authorizedInstanceName = request.params?.instanceName; if (request?.query && Object.keys(request.query).length > 0) { Object.assign(instance, request.query); } - if (request.originalUrl.includes('/instance/create')) { + if (isInstanceCreateRoute(request)) { Object.assign(instance, body); } + if (authorizedInstanceName) { + restoreAuthorizedInstance(instance, authorizedInstanceName); + } + Object.assign(ref, body); const v = schema ? validate(ref, schema) : { valid: true, errors: [] }; diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 60e857fcc1..3b0676488c 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -80,11 +80,17 @@ import ffmpegPath from '@ffmpeg-installer/ffmpeg'; import { Boom } from '@hapi/boom'; import { createId as cuid } from '@paralleldrive/cuid2'; import { Instance, Message } from '@prisma/client'; +import { nextMessageStatus, statusesAdvanceableTo } from '@utils/advanceMessageStatus'; import { createJid } from '@utils/createJid'; import { fetchLatestWaWebVersion } from '@utils/fetchLatestWaWebVersion'; +import { headerAsString } from '@utils/headerAsString'; +import { LidRemoteJidAltBackfill } from '@utils/lidRemoteJidAltBackfill'; import { makeProxyAgent, makeProxyAgentUndici } from '@utils/makeProxyAgent'; +import { MESSAGE_DEDUPE_TTL_SECONDS, messageDedupeKey, MessageDedupeKeyInput } from '@utils/messageDedupe'; import { getOnWhatsappCache, saveOnWhatsappCache } from '@utils/onWhatsappCache'; import { status } from '@utils/renderStatus'; +import { resolveLidRemoteJidAlt } from '@utils/resolveLidRemoteJidAlt'; +import { resolvePhoneRemoteJid } from '@utils/resolvePhoneRemoteJid'; import { sendTelemetry } from '@utils/sendTelemetry'; import useMultiFileAuthStatePrisma from '@utils/use-multi-file-auth-state-prisma'; import { AuthStateProvider } from '@utils/use-multi-file-auth-state-provider-files'; @@ -251,6 +257,15 @@ export class BaileysStartupService extends ChannelStartupService { private endSession = false; private logBaileys = this.configService.get('LOG').BAILEYS; private eventProcessingQueue: Promise = Promise.resolve(); + private readonly lidRemoteJidAltBackfill = new LidRemoteJidAltBackfill(this.prismaRepository, this.logger); + + private scheduleLidRemoteJidAltBackfill(lid?: string, phoneJid?: string) { + const database = this.configService.get('DATABASE'); + if (database.PROVIDER !== 'postgresql') return; + if (!database.SAVE_DATA.NEW_MESSAGE && !database.SAVE_DATA.HISTORIC) return; + + void this.lidRemoteJidAltBackfill.schedule(this.instanceId, lid, phoneJid); + } // Cache TTL constants (in seconds) private readonly MESSAGE_CACHE_TTL_SECONDS = 5 * 60; // 5 minutes - avoid duplicate message processing @@ -268,6 +283,28 @@ export class BaileysStartupService extends ChannelStartupService { this.messageProcessor.onDestroy(); await this.client?.logout('Log out instance: ' + this.instanceName); + this.endSession = true; + this.stateConnection = { state: 'close', statusReason: DisconnectReason.loggedOut }; + const disconnectionAt = new Date(); + + this.sendDataWebhook(Events.STATUS_INSTANCE, { + instance: this.instance.name, + status: 'closed', + disconnectionAt, + disconnectionReasonCode: DisconnectReason.loggedOut, + }); + + try { + await this.prismaRepository.instance.update({ + where: { id: this.instanceId }, + data: { connectionStatus: 'close', disconnectionAt, disconnectionReasonCode: DisconnectReason.loggedOut }, + }); + } catch (error) { + this.logger.error(['Error persisting the closed connection state on logout', error?.message]); + } + + this.sendDataWebhook(Events.CONNECTION_UPDATE, { instance: this.instance.name, ...this.stateConnection }); + this.client?.ws?.close(); const db = this.configService.get('DATABASE'); @@ -296,6 +333,8 @@ export class BaileysStartupService extends ChannelStartupService { if (sessionExists) { await this.prismaRepository.session.delete({ where: { sessionId: this.instanceId } }); } + + this.eventEmitter.emit('logout.instance', this.instance.name, 'inner'); } public async getProfileName() { @@ -416,6 +455,10 @@ export class BaileysStartupService extends ChannelStartupService { }); } + if (connection === 'close' && this.endSession && this.stateConnection.state === 'close') { + return; + } + if (connection) { this.stateConnection = { state: connection, @@ -532,6 +575,13 @@ export class BaileysStartupService extends ChannelStartupService { if (full) { return webMessageInfo[0]; } + if (!webMessageInfo[0]) { + return undefined; + } + if (await this.isDeletedMessage(webMessageInfo[0] as any, key)) { + this.logger.verbose(`Skipping retry resend of deleted message: ${key.id}`); + return undefined; + } if (webMessageInfo[0].message?.pollCreationMessage) { const messageSecretBase64 = webMessageInfo[0].message?.messageContextInfo?.messageSecret; @@ -549,10 +599,20 @@ export class BaileysStartupService extends ChannelStartupService { return webMessageInfo[0].message; } catch { - return { conversation: '' }; + return undefined; } } + private async isDeletedMessage(row: { status?: string; key?: { deleted?: boolean } }, key: proto.IMessageKey) { + if (row.status === 'DELETED' || row.key?.deleted === true) { + return true; + } + const deletedUpdate = await this.prismaRepository.messageUpdate.findFirst({ + where: { instanceId: this.instanceId, keyId: key.id, status: 'DELETED' }, + }); + return !!deletedUpdate; + } + private async defineAuthState() { const db = this.configService.get('DATABASE'); const cache = this.configService.get('CACHE'); @@ -704,13 +764,13 @@ export class BaileysStartupService extends ChannelStartupService { this.eventHandler(); this.client.ws.on('CB:call', (packet) => { - console.log('CB:call', packet); + this.logger.verbose(`CB:call received: ${packet?.attrs?.id ?? 'unknown'}`); const payload = { event: 'CB:call', packet: packet }; this.sendDataWebhook(Events.CALL, payload, true, ['websocket']); }); this.client.ws.on('CB:ack,class:call', (packet) => { - console.log('CB:ack,class:call', packet); + this.logger.verbose(`CB:ack,class:call received: ${packet?.attrs?.id ?? 'unknown'}`); const payload = { event: 'CB:ack,class:call', packet: packet }; this.sendDataWebhook(Events.CALL, payload, true, ['websocket']); }); @@ -923,6 +983,31 @@ export class BaileysStartupService extends ChannelStartupService { }, }; + private async isDuplicateMessage(key: MessageDedupeKeyInput): Promise { + if (!key?.id) { + return false; + } + + try { + return Boolean(await this.baileysCache.get(messageDedupeKey(this.instanceId, key))); + } catch (error) { + this.logger.warn(`Message dedupe lookup failed, processing message: ${error?.message}`); + return false; + } + } + + private async markMessageSeen(key: MessageDedupeKeyInput): Promise { + if (!key?.id) { + return; + } + + try { + await this.baileysCache.set(messageDedupeKey(this.instanceId, key), 1, MESSAGE_DEDUPE_TTL_SECONDS); + } catch (error) { + this.logger.warn(`Message dedupe mark failed: ${error?.message}`); + } + } + private readonly messageHandle = { 'messaging-history.set': async ({ messages, @@ -941,9 +1026,9 @@ export class BaileysStartupService extends ChannelStartupService { }) => { try { if (syncType === proto.HistorySync.HistorySyncType.ON_DEMAND) { - console.log('received on-demand history sync, messages=', messages); + this.logger.verbose(`Received on-demand history sync, messages: ${messages.length}`); } - console.log( + this.logger.verbose( `recv ${chats.length} chats, ${contacts.length} contacts, ${messages.length} msgs (is latest: ${isLatest}, progress: ${progress}%), type: ${syncType}`, ); @@ -996,6 +1081,7 @@ export class BaileysStartupService extends ChannelStartupService { } const messagesRaw: any[] = []; + const seenInBatch = new Set(); const messagesRepository: Set = new Set( chatwootImport.getRepositoryMessagesCache(instance) ?? @@ -1015,6 +1101,14 @@ export class BaileysStartupService extends ChannelStartupService { chatwootImport.setRepositoryMessagesCache(instance, messagesRepository); } + const lidMapping = this.client?.signalRepository?.lidMapping; + await resolveLidRemoteJidAlt( + messages, + contacts, + lidMapping?.getPNsForLIDs ? (lids) => lidMapping.getPNsForLIDs(lids) : undefined, + (error) => this.logger.warn(`History LID to PN lookup failed: ${(error as Error)?.message}`), + ); + for (const m of messages) { if (!m.message || !m.key || !m.messageTimestamp) { continue; @@ -1030,9 +1124,17 @@ export class BaileysStartupService extends ChannelStartupService { } } - if (messagesRepository?.has(m.key.id)) { + const batchDedupeKey = m.key.id ? messageDedupeKey(this.instanceId, m.key) : undefined; + if ( + messagesRepository?.has(m.key.id) || + (batchDedupeKey && seenInBatch.has(batchDedupeKey)) || + (await this.isDuplicateMessage(m.key)) + ) { continue; } + if (batchDedupeKey) { + seenInBatch.add(batchDedupeKey); + } if (!m.pushName && !m.key.fromMe) { const participantJid = m.participant || m.key.participant || m.key.remoteJid; @@ -1083,8 +1185,8 @@ export class BaileysStartupService extends ChannelStartupService { { messages, type, requestId }: { messages: WAMessage[]; type: MessageUpsertType; requestId?: string }, settings: any, ) => { - try { - for (const received of messages) { + for (const received of messages) { + try { if ( received?.messageStubParameters?.some?.((param) => [ @@ -1102,20 +1204,24 @@ export class BaileysStartupService extends ChannelStartupService { this.logger.warn(`Message ignored with messageStubParameters: ${JSON.stringify(received, null, 2)}`); continue; } + + this.scheduleLidRemoteJidAltBackfill(received.key?.remoteJid, received.key?.remoteJidAlt); if (received.message?.conversation || received.message?.extendedTextMessage?.text) { const text = received.message?.conversation || received.message?.extendedTextMessage?.text; if (text == 'requestPlaceholder' && !requestId) { const messageId = await this.client.requestPlaceholderResend(received.key); - console.log('requested placeholder resync, id=', messageId); + this.logger.verbose(`Requested placeholder resync, id: ${messageId}`); } else if (requestId) { - console.log('Message received from phone, id=', requestId, received); + this.logger.verbose( + `Message received from phone, request id: ${requestId}, message id: ${received.key?.id}`, + ); } if (text == 'onDemandHistSync') { const messageId = await this.client.fetchMessageHistory(50, received.key, received.messageTimestamp!); - console.log('requested on-demand sync, id=', messageId); + this.logger.verbose(`Requested on-demand sync, id: ${messageId}`); } } @@ -1167,6 +1273,11 @@ export class BaileysStartupService extends ChannelStartupService { continue; } + if (await this.isDuplicateMessage(received.key)) { + this.logger.verbose(`Duplicate message ignored: ${received.key.id}`); + continue; + } + if (Long.isLong(received.messageTimestamp)) { received.messageTimestamp = received.messageTimestamp?.toNumber(); } @@ -1196,7 +1307,7 @@ export class BaileysStartupService extends ChannelStartupService { data: { name: received.pushName }, }); } catch { - console.log(`Chat insert record ignored: ${received.key.remoteJid} - ${this.instanceId}`); + this.logger.verbose(`Chat insert record ignored: ${received.key.remoteJid} - ${this.instanceId}`); } } } @@ -1472,16 +1583,17 @@ export class BaileysStartupService extends ChannelStartupService { } } - this.logger.verbose(messageRaw); + this.logger.verbose(`Message received: ${messageRaw.key?.id} (${messageRaw.messageType})`); sendTelemetry(`received.message.${messageRaw.messageType ?? 'unknown'}`); - if (messageRaw.key.remoteJid?.includes('@lid') && messageRaw.key.remoteJidAlt) { - messageRaw.key.remoteJid = messageRaw.key.remoteJidAlt; + const phoneRemoteJid = resolvePhoneRemoteJid(messageRaw.key); + if (phoneRemoteJid !== messageRaw.key.remoteJid) { + messageRaw.key.remoteJid = phoneRemoteJid; } - console.log(messageRaw); - this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); + await this.markMessageSeen(received.key); + await chatbotController.emit({ instance: { instanceName: this.instance.name, instanceId: this.instanceId }, remoteJid: messageRaw.key.remoteJid, @@ -1549,180 +1661,221 @@ export class BaileysStartupService extends ChannelStartupService { update: contactRaw, create: contactRaw, }); + } catch (error) { + this.logger.error(`Failed to process upserted message ${received?.key?.id}: ${error?.message ?? error}`); } - } catch (error) { - this.logger.error(error); } }, 'messages.update': async (args: { update: Partial; key: WAMessageKey }[], settings: any) => { - this.logger.verbose(`Update messages ${JSON.stringify(args, undefined, 2)}`); + this.logger.verbose(`Update messages: ${args.length}`); const readChatToUpdate: Record = {}; // {remoteJid: true} for await (const { key, update } of args) { - if (settings?.groupsIgnore && key.remoteJid?.includes('@g.us')) { - continue; - } + try { + if (settings?.groupsIgnore && key.remoteJid?.includes('@g.us')) { + continue; + } - const updateKey = `${this.instance.id}_${key.id}_${update.status}`; + const updateKey = `${this.instance.id}_${key.id}_${update.status}`; - const cached = await this.baileysCache.get(updateKey); + const cached = await this.baileysCache.get(updateKey); - const secondsSinceEpoch = Math.floor(Date.now() / 1000); - console.log('CACHE:', { cached, updateKey, messageTimestamp: update.messageTimestamp, secondsSinceEpoch }); + const secondsSinceEpoch = Math.floor(Date.now() / 1000); + this.logger.debug(`Message update cache check: ${updateKey}, cached: ${cached}`); - if ( - (update.messageTimestamp && update.messageTimestamp === cached) || - (!update.messageTimestamp && secondsSinceEpoch === cached) - ) { - this.logger.info(`Update Message duplicated ignored [avoid deadlock]: ${updateKey}`); - continue; - } + if ( + (update.messageTimestamp && update.messageTimestamp === cached) || + (!update.messageTimestamp && secondsSinceEpoch === cached) + ) { + this.logger.info(`Update Message duplicated ignored [avoid deadlock]: ${updateKey}`); + continue; + } - if (update.messageTimestamp) { - await this.baileysCache.set(updateKey, update.messageTimestamp, 30 * 60); - } else { - await this.baileysCache.set(updateKey, secondsSinceEpoch, 30 * 60); - } + if (update.messageTimestamp) { + await this.baileysCache.set(updateKey, update.messageTimestamp, 30 * 60); + } else { + await this.baileysCache.set(updateKey, secondsSinceEpoch, 30 * 60); + } - if (status[update.status] === 'READ' && key.fromMe) { - if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { - this.chatwootService.eventWhatsapp( - 'messages.read', - { instanceName: this.instance.name, instanceId: this.instanceId }, - { key: key }, - ); + if (status[update.status] === 'READ' && key.fromMe) { + if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { + this.chatwootService.eventWhatsapp( + 'messages.read', + { instanceName: this.instance.name, instanceId: this.instanceId }, + { key: key }, + ); + } } - } - if (key.remoteJid !== 'status@broadcast' && key.id !== undefined) { - let pollUpdates: any; + if (key.remoteJid !== 'status@broadcast' && key.id !== undefined) { + let pollUpdates: any; - if (update.pollUpdates) { - const pollCreation = await this.getMessage(key); + if (update.pollUpdates) { + const pollCreation = await this.getMessage(key); - if (pollCreation) { - pollUpdates = getAggregateVotesInPollMessage({ - message: pollCreation as proto.IMessage, - pollUpdates: update.pollUpdates, - }); + if (pollCreation) { + pollUpdates = getAggregateVotesInPollMessage({ + message: pollCreation as proto.IMessage, + pollUpdates: update.pollUpdates, + }); + } } - } - const message: any = { - keyId: key.id, - remoteJid: key?.remoteJid, - fromMe: key.fromMe, - participant: key?.participant, - status: status[update.status] ?? 'SERVER_ACK', - pollUpdates, - instanceId: this.instanceId, - }; + const message: any = { + keyId: key.id, + remoteJid: key?.remoteJid, + fromMe: key.fromMe, + participant: key?.participant, + status: status[update.status] ?? 'SERVER_ACK', + pollUpdates, + instanceId: this.instanceId, + }; - if (update.message) { - message.message = update.message; - } + if (update.message) { + message.message = update.message; + } - let findMessage: any; - const configDatabaseData = this.configService.get('DATABASE').SAVE_DATA; - if (configDatabaseData.HISTORIC || configDatabaseData.NEW_MESSAGE) { - // Use raw SQL to avoid JSON path issues - const protocolMapKey = `protocol_${key.id}`; - const originalMessageId = (await this.baileysCache.get(protocolMapKey)) as string; + let findMessage: any; + const configDatabaseData = this.configService.get('DATABASE').SAVE_DATA; + if (configDatabaseData.HISTORIC || configDatabaseData.NEW_MESSAGE) { + // Use raw SQL to avoid JSON path issues + const protocolMapKey = `protocol_${key.id}`; + const originalMessageId = (await this.baileysCache.get(protocolMapKey)) as string; - if (originalMessageId) { - message.keyId = originalMessageId; - } + if (originalMessageId) { + message.keyId = originalMessageId; + } - const searchId = originalMessageId || key.id; + const searchId = originalMessageId || key.id; - const messages = (await this.prismaRepository.$queryRaw` - SELECT * FROM "Message" - WHERE "instanceId" = ${this.instanceId} - AND "key"->>'id' = ${searchId} - LIMIT 1 - `) as any[]; - findMessage = messages[0] || null; + const messages = (await this.prismaRepository.$queryRaw` + SELECT * FROM "Message" + WHERE "instanceId" = ${this.instanceId} + AND "key"->>'id' = ${searchId} + LIMIT 1 + `) as any[]; + findMessage = messages[0] || null; - if (!findMessage?.id) { - this.logger.warn(`Original message not found for update. Skipping. Key: ${JSON.stringify(key)}`); - continue; + if (!findMessage?.id) { + this.logger.warn(`Original message not found for update. Skipping. Key: ${JSON.stringify(key)}`); + continue; + } + message.messageId = findMessage.id; + message.remoteJid = message.remoteJid ?? findMessage.key?.remoteJid; } - message.messageId = findMessage.id; - } - if (update.message === null && update.status === undefined) { - this.sendDataWebhook(Events.MESSAGES_DELETE, { ...key, status: 'DELETED' }); + if (update.message === null && update.status === undefined) { + this.sendDataWebhook(Events.MESSAGES_DELETE, { ...key, status: 'DELETED' }); - if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) - await this.prismaRepository.messageUpdate.create({ data: message }); + message.status = 'DELETED'; - if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { - this.chatwootService.eventWhatsapp( - Events.MESSAGES_DELETE, - { instanceName: this.instance.name, instanceId: this.instanceId }, - { key: key }, - ); + if (findMessage?.id) { + try { + await this.prismaRepository.message.update({ + where: { id: findMessage.id }, + data: { status: 'DELETED' }, + }); + } catch (error) { + this.logger.warn( + `Failed to mark revoked message ${findMessage.id} as deleted: ${error?.message ?? error}`, + ); + } + } + + if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) + await this.prismaRepository.messageUpdate.create({ data: message }); + + if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { + this.chatwootService.eventWhatsapp( + Events.MESSAGES_DELETE, + { instanceName: this.instance.name, instanceId: this.instanceId }, + { key: key }, + ); + } + + continue; } - continue; - } + if (findMessage && update.status !== undefined && status[update.status] !== findMessage.status) { + if (!key.fromMe && key.remoteJid) { + readChatToUpdate[key.remoteJid] = true; - if (findMessage && update.status !== undefined && status[update.status] !== findMessage.status) { - if (!key.fromMe && key.remoteJid) { - readChatToUpdate[key.remoteJid] = true; + const { remoteJid } = key; + const timestamp = findMessage.messageTimestamp; + const fromMe = key.fromMe.toString(); + const messageKey = `${remoteJid}_${timestamp}_${fromMe}`; - const { remoteJid } = key; - const timestamp = findMessage.messageTimestamp; - const fromMe = key.fromMe.toString(); - const messageKey = `${remoteJid}_${timestamp}_${fromMe}`; + const cachedTimestamp = await this.baileysCache.get(messageKey); - const cachedTimestamp = await this.baileysCache.get(messageKey); + if (!cachedTimestamp) { + if (status[update.status] === status[4]) { + this.logger.log(`Update as read in message.update ${remoteJid} - ${timestamp}`); + await this.updateMessagesReadedByTimestamp(remoteJid, timestamp); + await this.baileysCache.set(messageKey, true, this.MESSAGE_CACHE_TTL_SECONDS); + } - if (!cachedTimestamp) { - if (status[update.status] === status[4]) { - this.logger.log(`Update as read in message.update ${remoteJid} - ${timestamp}`); - await this.updateMessagesReadedByTimestamp(remoteJid, timestamp); - await this.baileysCache.set(messageKey, true, this.MESSAGE_CACHE_TTL_SECONDS); + await this.prismaRepository.message.update({ + where: { id: findMessage.id }, + data: { status: status[update.status] }, + }); + } else { + this.logger.info( + `Update readed messages duplicated ignored in message.update [avoid deadlock]: ${messageKey}`, + ); } + } - await this.prismaRepository.message.update({ - where: { id: findMessage.id }, - data: { status: status[update.status] }, - }); - } else { - this.logger.info( - `Update readed messages duplicated ignored in message.update [avoid deadlock]: ${messageKey}`, - ); + if (key.fromMe) { + const nextStatus = nextMessageStatus(findMessage.status, status[update.status]); + if (nextStatus !== undefined) { + try { + await this.prismaRepository.message.updateMany({ + where: { + id: findMessage.id, + OR: [{ status: null }, { status: { in: statusesAdvanceableTo(nextStatus) } }], + }, + data: { status: nextStatus }, + }); + } catch (error) { + this.logger.warn( + `Failed to update status of sent message ${findMessage.id}: ${error?.message ?? error}`, + ); + } + } } } - } - this.sendDataWebhook(Events.MESSAGES_UPDATE, message); + this.sendDataWebhook(Events.MESSAGES_UPDATE, message); - if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { message: _msg, ...messageData } = message; - await this.prismaRepository.messageUpdate.create({ data: messageData }); - } + if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { message: _msg, ...messageData } = message; + await this.prismaRepository.messageUpdate.create({ data: messageData }); + } - const existingChat = await this.prismaRepository.chat.findFirst({ - where: { instanceId: this.instanceId, remoteJid: message.remoteJid }, - }); + const existingChat = await this.prismaRepository.chat.findFirst({ + where: { instanceId: this.instanceId, remoteJid: message.remoteJid }, + }); - if (existingChat) { - const chatToInsert = { remoteJid: message.remoteJid, instanceId: this.instanceId, unreadMessages: 0 }; + if (existingChat) { + const chatToInsert = { remoteJid: message.remoteJid, instanceId: this.instanceId, unreadMessages: 0 }; - this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]); - if (this.configService.get('DATABASE').SAVE_DATA.CHATS) { - try { - await this.prismaRepository.chat.update({ where: { id: existingChat.id }, data: chatToInsert }); - } catch { - console.log(`Chat insert record ignored: ${chatToInsert.remoteJid} - ${chatToInsert.instanceId}`); + this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]); + if (this.configService.get('DATABASE').SAVE_DATA.CHATS) { + try { + await this.prismaRepository.chat.update({ where: { id: existingChat.id }, data: chatToInsert }); + } catch { + this.logger.verbose( + `Chat insert record ignored: ${chatToInsert.remoteJid} - ${chatToInsert.instanceId}`, + ); + } } } } + } catch (error) { + this.logger.error(`Failed to process message update ${key?.id}: ${error?.message ?? error}`); } } @@ -1872,6 +2025,14 @@ export class BaileysStartupService extends ChannelStartupService { }, }; + private async runEventStep(eventName: string, step: () => Promise) { + try { + await step(); + } catch (error) { + this.logger.error(`Failed to process ${eventName} event: ${error?.message ?? error}`); + } + } + private eventHandler() { this.client.ev.process(async (events) => { this.eventProcessingQueue = this.eventProcessingQueue.then(async () => { @@ -1881,128 +2042,169 @@ export class BaileysStartupService extends ChannelStartupService { const settings = await this.findSettings(); if (events.call) { - const call = events.call[0]; - - if (settings?.rejectCall && call.status == 'offer') { - this.client.rejectCall(call.id, call.from); - } + await this.runEventStep('call', async () => { + const call = events.call[0]; - if (settings?.msgCall?.trim().length > 0 && call.status == 'offer') { - if (call.from.endsWith('@lid')) { - call.from = await this.client.signalRepository.lidMapping.getPNForLID(call.from as string); + if (settings?.rejectCall && call.status == 'offer') { + this.client.rejectCall(call.id, call.from); } - const msg = await this.client.sendMessage(call.from, { text: settings.msgCall }); - this.client.ev.emit('messages.upsert', { messages: [msg], type: 'notify' }); - } + if (settings?.msgCall?.trim().length > 0 && call.status == 'offer') { + if (call.from.endsWith('@lid')) { + call.from = await this.client.signalRepository.lidMapping.getPNForLID(call.from as string); + } + const msg = await this.client.sendMessage(call.from, { text: settings.msgCall }); + + this.client.ev.emit('messages.upsert', { messages: [msg], type: 'notify' }); + } - this.sendDataWebhook(Events.CALL, call); + this.sendDataWebhook(Events.CALL, call); + }); } if (events['connection.update']) { - this.connectionUpdate(events['connection.update']); + await this.runEventStep('connection.update', async () => { + this.connectionUpdate(events['connection.update']); + }); } if (events['creds.update']) { - this.instance.authState.saveCreds(); + await this.runEventStep('creds.update', async () => { + this.instance.authState.saveCreds(); + }); } if (events['messaging-history.set']) { - const payload = events['messaging-history.set']; - await this.messageHandle['messaging-history.set'](payload); + await this.runEventStep('messaging-history.set', async () => { + const payload = events['messaging-history.set']; + await this.messageHandle['messaging-history.set'](payload); + }); + } + + if (events['lid-mapping.update']) { + await this.runEventStep('lid-mapping.update', async () => { + const { lid, pn } = events['lid-mapping.update']; + this.scheduleLidRemoteJidAltBackfill(lid, pn); + }); } if (events['messages.upsert']) { - const payload = events['messages.upsert']; + await this.runEventStep('messages.upsert', async () => { + const payload = events['messages.upsert']; - // this.messageProcessor.processMessage(payload, settings); - await this.messageHandle['messages.upsert'](payload, settings); + // this.messageProcessor.processMessage(payload, settings); + await this.messageHandle['messages.upsert'](payload, settings); + }); } if (events['messages.update']) { - const payload = events['messages.update']; - await this.messageHandle['messages.update'](payload, settings); + await this.runEventStep('messages.update', async () => { + const payload = events['messages.update']; + await this.messageHandle['messages.update'](payload, settings); + }); } if (events['message-receipt.update']) { - const payload = events['message-receipt.update'] as MessageUserReceiptUpdate[]; - const remotesJidMap: Record = {}; + await this.runEventStep('message-receipt.update', async () => { + const payload = events['message-receipt.update'] as MessageUserReceiptUpdate[]; + const remotesJidMap: Record = {}; - for (const event of payload) { - if (typeof event.key.remoteJid === 'string' && typeof event.receipt.readTimestamp === 'number') { - remotesJidMap[event.key.remoteJid] = event.receipt.readTimestamp; + for (const event of payload) { + if (typeof event.key.remoteJid === 'string' && typeof event.receipt.readTimestamp === 'number') { + remotesJidMap[event.key.remoteJid] = event.receipt.readTimestamp; + } } - } - await Promise.all( - Object.keys(remotesJidMap).map(async (remoteJid) => - this.updateMessagesReadedByTimestamp(remoteJid, remotesJidMap[remoteJid]), - ), - ); + await Promise.all( + Object.keys(remotesJidMap).map(async (remoteJid) => + this.updateMessagesReadedByTimestamp(remoteJid, remotesJidMap[remoteJid]), + ), + ); + }); } if (events['presence.update']) { - const payload = events['presence.update']; + await this.runEventStep('presence.update', async () => { + const payload = events['presence.update']; - if (settings?.groupsIgnore && payload.id.includes('@g.us')) { - return; - } + const isIgnoredGroupPresence = settings?.groupsIgnore && payload.id.includes('@g.us'); - this.sendDataWebhook(Events.PRESENCE_UPDATE, payload); + if (!isIgnoredGroupPresence) { + this.sendDataWebhook(Events.PRESENCE_UPDATE, payload); + } + }); } if (!settings?.groupsIgnore) { if (events['groups.upsert']) { - const payload = events['groups.upsert']; - this.groupHandler['groups.upsert'](payload); + await this.runEventStep('groups.upsert', async () => { + const payload = events['groups.upsert']; + this.groupHandler['groups.upsert'](payload); + }); } if (events['groups.update']) { - const payload = events['groups.update']; - this.groupHandler['groups.update'](payload); + await this.runEventStep('groups.update', async () => { + const payload = events['groups.update']; + this.groupHandler['groups.update'](payload); + }); } if (events['group-participants.update']) { - const payload = events['group-participants.update'] as any; - this.groupHandler['group-participants.update'](payload); + await this.runEventStep('group-participants.update', async () => { + const payload = events['group-participants.update'] as any; + this.groupHandler['group-participants.update'](payload); + }); } } if (events['chats.upsert']) { - const payload = events['chats.upsert']; - this.chatHandle['chats.upsert'](payload); + await this.runEventStep('chats.upsert', async () => { + const payload = events['chats.upsert']; + this.chatHandle['chats.upsert'](payload); + }); } if (events['chats.update']) { - const payload = events['chats.update']; - this.chatHandle['chats.update'](payload); + await this.runEventStep('chats.update', async () => { + const payload = events['chats.update']; + this.chatHandle['chats.update'](payload); + }); } if (events['chats.delete']) { - const payload = events['chats.delete']; - this.chatHandle['chats.delete'](payload); + await this.runEventStep('chats.delete', async () => { + const payload = events['chats.delete']; + this.chatHandle['chats.delete'](payload); + }); } if (events['contacts.upsert']) { - const payload = events['contacts.upsert']; - this.contactHandle['contacts.upsert'](payload); + await this.runEventStep('contacts.upsert', async () => { + const payload = events['contacts.upsert']; + this.contactHandle['contacts.upsert'](payload); + }); } if (events['contacts.update']) { - const payload = events['contacts.update']; - this.contactHandle['contacts.update'](payload); + await this.runEventStep('contacts.update', async () => { + const payload = events['contacts.update']; + this.contactHandle['contacts.update'](payload); + }); } if (events[Events.LABELS_ASSOCIATION]) { - const payload = events[Events.LABELS_ASSOCIATION]; - this.labelHandle[Events.LABELS_ASSOCIATION](payload, database); - return; + await this.runEventStep(Events.LABELS_ASSOCIATION, async () => { + const payload = events[Events.LABELS_ASSOCIATION]; + this.labelHandle[Events.LABELS_ASSOCIATION](payload, database); + }); } if (events[Events.LABELS_EDIT]) { - const payload = events[Events.LABELS_EDIT]; - this.labelHandle[Events.LABELS_EDIT](payload); - return; + await this.runEventStep(Events.LABELS_EDIT, async () => { + const payload = events[Events.LABELS_EDIT]; + this.labelHandle[Events.LABELS_EDIT](payload); + }); } } } catch (error) { @@ -2540,7 +2742,7 @@ export class BaileysStartupService extends ChannelStartupService { } } - this.logger.verbose(messageSent); + this.logger.verbose(`Message sent: ${messageSent?.key?.id} (${messageRaw.messageType})`); this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw); @@ -2829,7 +3031,7 @@ export class BaileysStartupService extends ChannelStartupService { const response = await axios.get(mediaMessage.media, config); - mimetype = response.headers['content-type']; + mimetype = headerAsString(response.headers['content-type']) ?? false; } } @@ -2867,11 +3069,9 @@ export class BaileysStartupService extends ChannelStartupService { } } - if (mediaMessage?.fileName) { - mimetype = mimeTypes.lookup(mediaMessage.fileName).toString(); - if (mimetype === 'application/mp4') { - mimetype = 'video/mp4'; - } + const fileNameMimetype = mediaMessage?.fileName && mimeTypes.lookup(mediaMessage.fileName); + if (fileNameMimetype) { + mimetype = fileNameMimetype === 'application/mp4' ? 'video/mp4' : fileNameMimetype; } prepareMedia[mediaType].caption = mediaMessage?.caption; @@ -3153,7 +3353,7 @@ export class BaileysStartupService extends ChannelStartupService { }); outputAudioStream.on('error', (error) => { - console.log('error', error); + this.logger.error(['Error in output audio stream', error?.message]); reject(error); }); @@ -3195,8 +3395,8 @@ export class BaileysStartupService extends ChannelStartupService { '0', ]) .pipe(outputAudioStream, { end: true }) - .on('error', function (error) { - console.log('error', error); + .on('error', (error) => { + this.logger.error(['Error in ffmpeg audio conversion', error?.message]); reject(error); }); }); @@ -3689,27 +3889,23 @@ export class BaileysStartupService extends ChannelStartupService { } public async getLastMessage(number: string) { - const where: any = { key: { remoteJid: number }, instanceId: this.instance.id }; - - const messages = await this.prismaRepository.message.findMany({ - where, - orderBy: { messageTimestamp: 'desc' }, - take: 1, - }); + const lastMessage = await this.findLastMessageOrNone(number); - if (messages.length === 0) { + if (!lastMessage) { throw new NotFoundException('Messages not found'); } - let lastMessage = messages.pop(); + return lastMessage; + } - for (const message of messages) { - if (message.messageTimestamp >= lastMessage.messageTimestamp) { - lastMessage = message; - } - } + private async findLastMessageOrNone(number: string): Promise { + const messages = await this.prismaRepository.message.findMany({ + where: { key: { path: ['remoteJid'], equals: createJid(number) }, instanceId: this.instance.id }, + orderBy: { messageTimestamp: 'desc' }, + take: 1, + }); - return lastMessage as unknown as LastMessage; + return messages[0] as unknown as LastMessage | undefined; } public async archiveChat(data: ArchiveChatDto) { @@ -3718,18 +3914,16 @@ export class BaileysStartupService extends ChannelStartupService { let number = data.chat; if (!last_message && number) { - last_message = await this.getLastMessage(number); + last_message = await this.findLastMessageOrNone(number); } else { last_message = data.lastMessage; - last_message.messageTimestamp = last_message?.messageTimestamp ?? Date.now(); + last_message.messageTimestamp = last_message?.messageTimestamp ?? Math.floor(Date.now() / 1000); number = last_message?.key?.remoteJid; } - if (!last_message || Object.keys(last_message).length === 0) { - throw new NotFoundException('Last message not found'); - } + const lastMessages = last_message && Object.keys(last_message).length > 0 ? [last_message] : []; - await this.client.chatModify({ archive: data.archive, lastMessages: [last_message] }, createJid(number)); + await this.client.chatModify({ archive: data.archive, lastMessages }, createJid(number)); return { chatId: number, archived: true }; } catch (error) { @@ -3749,7 +3943,7 @@ export class BaileysStartupService extends ChannelStartupService { last_message = await this.getLastMessage(number); } else { last_message = data.lastMessage; - last_message.messageTimestamp = last_message?.messageTimestamp ?? Date.now(); + last_message.messageTimestamp = last_message?.messageTimestamp ?? Math.floor(Date.now() / 1000); number = last_message?.key?.remoteJid; } @@ -3776,7 +3970,7 @@ export class BaileysStartupService extends ChannelStartupService { if (messageId) { const isLogicalDeleted = configService.get('DATABASE').DELETE_DATA.LOGICAL_MESSAGE_DELETE; let message = await this.prismaRepository.message.findFirst({ - where: { key: { path: ['id'], equals: messageId } }, + where: { instanceId: this.instanceId, key: { path: ['id'], equals: messageId } }, }); if (isLogicalDeleted) { if (!message) return response; @@ -4196,15 +4390,15 @@ export class BaileysStartupService extends ChannelStartupService { const messageId = messageSent.message?.protocolMessage?.key?.id; if (messageId && this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { let message = await this.prismaRepository.message.findFirst({ - where: { key: { path: ['id'], equals: messageId } }, + where: { instanceId: this.instanceId, key: { path: ['id'], equals: messageId } }, }); if (!message) throw new NotFoundException('Message not found'); if (!(message.key.valueOf() as any).fromMe) { - new BadRequestException('You cannot edit others messages'); + throw new BadRequestException('You cannot edit others messages'); } if ((message.key.valueOf() as any)?.deleted) { - new BadRequestException('You cannot edit deleted messages'); + throw new BadRequestException('You cannot edit deleted messages'); } if (oldMessage.messageType === 'conversation' || oldMessage.messageType === 'extendedTextMessage') { @@ -4309,7 +4503,7 @@ export class BaileysStartupService extends ChannelStartupService { if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { if (await groupMetadataCache?.has(groupJid)) { - console.log(`Cache request for group: ${groupJid}`); + this.logger.verbose(`Cache request for group: ${groupJid}`); const meta = await groupMetadataCache.get(groupJid); if (Date.now() - meta.timestamp > 3600000) { @@ -4319,7 +4513,7 @@ export class BaileysStartupService extends ChannelStartupService { return meta.data; } - console.log(`Cache request for group: ${groupJid} - not found`); + this.logger.verbose(`Cache request for group: ${groupJid} - not found`); return await this.updateGroupMetadataCache(groupJid); } @@ -4758,7 +4952,7 @@ export class BaileysStartupService extends ChannelStartupService { private async updateChatUnreadMessages(remoteJid: string): Promise { const [chat, unreadMessages] = await Promise.all([ - this.prismaRepository.chat.findFirst({ where: { remoteJid } }), + this.prismaRepository.chat.findFirst({ where: { instanceId: this.instanceId, remoteJid } }), // Use raw SQL to avoid JSON path issues this.prismaRepository.$queryRaw` SELECT COUNT(*)::int as count FROM "Message" @@ -4860,7 +5054,7 @@ export class BaileysStartupService extends ChannelStartupService { } public async baileysSendNode(stanza: any) { - console.log('stanza', JSON.stringify(stanza)); + this.logger.verbose(`Sending stanza: ${stanza?.tag}`); const response = await this.client.sendNode(stanza); return response; @@ -4944,7 +5138,7 @@ export class BaileysStartupService extends ChannelStartupService { catalog: productsCatalog, }; } catch (error) { - console.log(error); + this.logger.error(['Error fetching business profile', error?.message]); return { wuid: jid, name: null, isBusiness: false }; } } diff --git a/src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts index 906fff1881..63e003fd7f 100644 --- a/src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts @@ -20,6 +20,7 @@ import ChatwootClient, { } from '@figuro/chatwoot-sdk'; import { request as chatwootRequest } from '@figuro/chatwoot-sdk/dist/core/request'; import { Chatwoot as ChatwootModel, Contact as ContactModel, Message as MessageModel } from '@prisma/client'; +import { headerAsString } from '@utils/headerAsString'; import i18next from '@utils/i18n'; import { sendTelemetry } from '@utils/sendTelemetry'; import axios from 'axios'; @@ -1204,7 +1205,7 @@ export class ChatwootService { const response = await axios.get(media, { responseType: 'arraybuffer', }); - mimeType = response.headers['content-type']; + mimeType = headerAsString(response.headers['content-type']); } let type = 'document'; @@ -2214,7 +2215,8 @@ export class ChatwootService { if (isAdsMessage) { const imgBuffer = await axios.get(adsMessage.thumbnailUrl, { responseType: 'arraybuffer' }); - const extension = mimeTypes.extension(imgBuffer.headers['content-type']); + const contentType = headerAsString(imgBuffer.headers['content-type']); + const extension = contentType && mimeTypes.extension(contentType); const mimeType = extension && mimeTypes.lookup(extension); if (!mimeType) { diff --git a/src/api/services/monitor.service.ts b/src/api/services/monitor.service.ts index 438530b57e..7cd120fcd2 100644 --- a/src/api/services/monitor.service.ts +++ b/src/api/services/monitor.service.ts @@ -7,6 +7,7 @@ import { CacheConf, Chatwoot, ConfigService, Database, DelInstance, ProviderSess import { Logger } from '@config/logger.config'; import { INSTANCE_DIR, STORE_DIR } from '@config/path.config'; import { NotFoundException } from '@exceptions'; +import { DisconnectReason } from 'baileys'; import { execFileSync } from 'child_process'; import EventEmitter2 from 'eventemitter2'; import { rmSync } from 'fs'; @@ -427,13 +428,28 @@ export class WAMonitoringService { private noConnection() { this.eventEmitter.on('no.connection', async (instanceName) => { + const waInstance = this.waInstances[instanceName]; + if (!waInstance) { + return; + } + try { - await this.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName); + try { + await waInstance.client?.logout('Log out instance: ' + instanceName); + } catch (error) { + this.logger.verbose(`Instance "${instanceName}" - logout skipped: ${error?.message ?? error}`); + } - this.waInstances[instanceName]?.client?.ws?.close(); + waInstance.instance.qrcode = { count: 0 }; + waInstance.stateConnection = { state: 'close', statusReason: DisconnectReason.connectionClosed }; - this.waInstances[instanceName].instance.qrcode = { count: 0 }; - this.waInstances[instanceName].stateConnection.state = 'close'; + waInstance.client?.ws?.close(); + waInstance.client?.end?.(undefined); + + await this.prismaRepository.instance.update({ + where: { id: waInstance.instanceId }, + data: { connectionStatus: 'close' }, + }); } catch (error) { this.logger.error({ localError: 'noConnection', diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 7c4e382e7e..9b031b201a 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -878,7 +878,7 @@ export class ConfigService { ALLOWED_IPS: process.env?.METRICS_ALLOWED_IPS, }, TELEMETRY: { - ENABLED: process.env?.TELEMETRY_ENABLED === undefined || process.env?.TELEMETRY_ENABLED === 'true', + ENABLED: process.env?.TELEMETRY_ENABLED === 'true', URL: process.env?.TELEMETRY_URL, }, PROXY: { diff --git a/src/utils/advanceMessageStatus.ts b/src/utils/advanceMessageStatus.ts new file mode 100644 index 0000000000..da8e14308f --- /dev/null +++ b/src/utils/advanceMessageStatus.ts @@ -0,0 +1,24 @@ +import { status } from '@utils/renderStatus'; + +const FINAL_STATUSES = ['DELETED', 'EDITED']; +const ERROR_STATUS = status[0]; +const DELIVERY_ACK_STATUS = status[3]; + +const statusRank = (value: string | null | undefined): number => { + const entry = Object.entries(status).find(([, name]) => name === value); + return entry === undefined ? -1 : Number(entry[0]); +}; + +export const nextMessageStatus = (current: string | null | undefined, incoming: string): string | undefined => { + const hasCurrent = current !== null && current !== undefined; + if (hasCurrent && (FINAL_STATUSES.includes(current) || statusRank(current) === -1)) { + return undefined; + } + if (incoming === ERROR_STATUS) { + return statusRank(current) < statusRank(DELIVERY_ACK_STATUS) ? incoming : undefined; + } + return statusRank(incoming) > statusRank(current) ? incoming : undefined; +}; + +export const statusesAdvanceableTo = (incoming: string): string[] => + Object.values(status).filter((current) => current !== incoming && nextMessageStatus(current, incoming) === incoming); diff --git a/src/utils/createJid.ts b/src/utils/createJid.ts index a680e821e1..8ca21e2a35 100644 --- a/src/utils/createJid.ts +++ b/src/utils/createJid.ts @@ -1,15 +1,8 @@ -// Check if the number is MX or AR -function formatMXOrARNumber(jid: string): string { - const countryCode = jid.substring(0, 2); - - if (Number(countryCode) === 52 || Number(countryCode) === 54) { - if (jid.length === 13) { - const number = countryCode + jid.substring(3); - return number; - } - - return jid; +function formatMXNumber(jid: string): string { + if (jid.startsWith('52') && jid.length === 13) { + return jid.substring(0, 2) + jid.substring(3); } + return jid; } @@ -63,7 +56,7 @@ export function createJid(number: string): string { return `${number}@g.us`; } - number = formatMXOrARNumber(number); + number = formatMXNumber(number); number = formatBRNumber(number); diff --git a/src/utils/headerAsString.ts b/src/utils/headerAsString.ts new file mode 100644 index 0000000000..b49096a4ca --- /dev/null +++ b/src/utils/headerAsString.ts @@ -0,0 +1 @@ +export const headerAsString = (value: unknown): string | undefined => (typeof value === 'string' ? value : undefined); diff --git a/src/utils/lidRemoteJidAltBackfill.ts b/src/utils/lidRemoteJidAltBackfill.ts new file mode 100644 index 0000000000..ac00320181 --- /dev/null +++ b/src/utils/lidRemoteJidAltBackfill.ts @@ -0,0 +1,52 @@ +import { PrismaRepository } from '@api/repository/repository.service'; +import { Logger } from '@config/logger.config'; +import { jidNormalizedUser } from 'baileys'; + +const MAX_TRACKED_LIDS = 10_000; +const LID_SUFFIX = '@lid'; +const PHONE_SUFFIX = '@s.whatsapp.net'; + +export class LidRemoteJidAltBackfill { + private readonly backfilledLids = new Set(); + private backfillQueue: Promise = Promise.resolve(); + + constructor( + private readonly prismaRepository: PrismaRepository, + private readonly logger: Logger, + ) {} + + public schedule(instanceId: string, lid?: string, phoneJid?: string): Promise | undefined { + if (!instanceId || !lid || !phoneJid) return undefined; + + const normalizedLid = jidNormalizedUser(lid); + const normalizedPhoneJid = jidNormalizedUser(phoneJid); + if (!normalizedLid.endsWith(LID_SUFFIX) || !normalizedPhoneJid.endsWith(PHONE_SUFFIX)) return undefined; + + const trackingKey = `${instanceId}:${normalizedLid}`; + if (this.backfilledLids.has(trackingKey)) return undefined; + if (this.backfilledLids.size >= MAX_TRACKED_LIDS) this.backfilledLids.clear(); + this.backfilledLids.add(trackingKey); + + const queuedBackfill = this.backfillQueue.then(() => + this.backfill(trackingKey, instanceId, normalizedLid, normalizedPhoneJid), + ); + this.backfillQueue = queuedBackfill; + return queuedBackfill; + } + + private async backfill(trackingKey: string, instanceId: string, lid: string, phoneJid: string): Promise { + try { + const updatedRows = await this.prismaRepository.$executeRaw` + UPDATE "Message" + SET "key" = jsonb_set("key"::jsonb, '{remoteJidAlt}', to_jsonb(${phoneJid}::text), true) + WHERE "instanceId" = ${instanceId} + AND "key"->>'remoteJid' = ${lid} + AND COALESCE("key"->>'remoteJidAlt', '') NOT LIKE ${`%${PHONE_SUFFIX}`} + `; + if (updatedRows > 0) this.logger.verbose(`Backfilled remoteJidAlt on ${updatedRows} messages for ${lid}`); + } catch (error) { + this.backfilledLids.delete(trackingKey); + this.logger.warn(`Failed to backfill remoteJidAlt for ${lid}: ${error?.message ?? error}`); + } + } +} diff --git a/src/utils/messageDedupe.ts b/src/utils/messageDedupe.ts new file mode 100644 index 0000000000..90ceacab5b --- /dev/null +++ b/src/utils/messageDedupe.ts @@ -0,0 +1,14 @@ +import { resolvePhoneRemoteJid } from '@utils/resolvePhoneRemoteJid'; + +export type MessageDedupeKeyInput = { + id?: string; + fromMe?: boolean; + remoteJid?: string; + remoteJidAlt?: string; +}; + +export const MESSAGE_DEDUPE_TTL_SECONDS = 24 * 60 * 60; + +export function messageDedupeKey(instanceId: string, key: MessageDedupeKeyInput): string { + return `message_seen_${instanceId}_${key.id}_${key.fromMe ? 1 : 0}_${resolvePhoneRemoteJid(key)}`; +} diff --git a/src/utils/onWhatsappCache.ts b/src/utils/onWhatsappCache.ts index 08de0714e2..58e48beb5d 100644 --- a/src/utils/onWhatsappCache.ts +++ b/src/utils/onWhatsappCache.ts @@ -12,14 +12,12 @@ function getAvailableNumbers(remoteJid: string) { remoteJid = remoteJid.slice(1); } - const [number, domain] = remoteJid.split('@'); + const [number, domain = 's.whatsapp.net'] = remoteJid.split('@'); - // TODO: Se já for @lid, retornar apenas ele mesmo SEM adicionar @domain novamente if (domain === 'lid' || domain === 'g.us') { - return [remoteJid]; // Retorna direto para @lid e @g.us + return [remoteJid]; } - // Brazilian numbers if (remoteJid.startsWith('55')) { const numberWithDigit = number.slice(4, 5) === '9' && number.length === 13 ? number : `${number.slice(0, 4)}9${number.slice(4)}`; @@ -27,11 +25,7 @@ function getAvailableNumbers(remoteJid: string) { numbersAvailable.push(numberWithDigit); numbersAvailable.push(numberWithoutDigit); - } - - // Mexican/Argentina numbers - // Ref: https://faq.whatsapp.com/1294841057948784 - else if (number.startsWith('52') || number.startsWith('54')) { + } else if (number.startsWith('52') || number.startsWith('54')) { let prefix = ''; if (number.startsWith('52')) { prefix = '1'; @@ -48,14 +42,10 @@ function getAvailableNumbers(remoteJid: string) { numbersAvailable.push(numberWithDigit); numbersAvailable.push(numberWithoutDigit); + } else { + numbersAvailable.push(number); } - // Other countries - else { - numbersAvailable.push(remoteJid); - } - - // TODO: Adiciona @domain apenas para números que não são @lid return numbersAvailable.map((number) => `${number}@${domain}`); } @@ -65,147 +55,189 @@ interface ISaveOnWhatsappCacheParams { lid?: 'lid' | undefined; } +interface CacheGroup { + remoteJid: string; + jids: Set; + lid: string | null; +} + +interface NormalizedItem { + remoteJid: string; + jids: string[]; + lid: string | null; +} + +const SAVE_CHUNK_SIZE = 500; + function normalizeJid(jid: string | null | undefined): string | null { if (!jid) return null; return jid.startsWith('+') ? jid.slice(1) : jid; } +function normalizeItem(item: ISaveOnWhatsappCacheParams | undefined): NormalizedItem | null { + const remoteJid = normalizeJid(item?.remoteJid); + if (!remoteJid) { + logger.warn('[saveOnWhatsappCache] Item skipped, missing remoteJid.'); + return null; + } + + const altJid = normalizeJid(item.remoteJidAlt); + const baseJids = altJid && altJid.includes('@lid') ? [remoteJid, altJid] : [remoteJid]; + + return { + remoteJid, + jids: [...new Set(baseJids.flatMap((jid) => getAvailableNumbers(jid)))], + lid: item.lid === 'lid' || item.remoteJid.includes('@lid') ? 'lid' : null, + }; +} + +async function loadExistingGroups(jids: string[]) { + const groups = new Map(); + const hits = await prismaRepository.isOnWhatsappJid.findMany({ where: { jid: { in: jids } } }); + const canonicals = [...new Set(hits.map((row) => row.remoteJid))]; + if (canonicals.length === 0) return groups; + + const rows = await prismaRepository.isOnWhatsappJid.findMany({ where: { remoteJid: { in: canonicals } } }); + for (const row of [...hits, ...rows]) { + const group = groups.get(row.remoteJid) ?? { remoteJid: row.remoteJid, jids: new Set(), lid: null }; + group.jids.add(row.jid); + group.lid = group.lid ?? row.lid ?? null; + groups.set(row.remoteJid, group); + } + return groups; +} + +function mergeGroups(items: NormalizedItem[], existing: Map) { + const existingByJid = new Map(); + existing.forEach((group) => group.jids.forEach((jid) => existingByJid.set(jid, group))); + + const builtByJid = new Map(); + for (const item of items) { + const group: CacheGroup = { remoteJid: item.remoteJid, jids: new Set(item.jids), lid: item.lid }; + + for (const jid of item.jids) { + const found = existingByJid.get(jid); + if (!found) continue; + found.jids.forEach((existingJid) => group.jids.add(existingJid)); + group.lid = group.lid ?? found.lid; + } + + const previous = new Set([...group.jids].map((jid) => builtByJid.get(jid)).filter(Boolean)); + for (const earlier of previous) { + earlier.jids.forEach((jid) => group.jids.add(jid)); + group.lid = group.lid ?? earlier.lid; + } + + group.jids.forEach((jid) => builtByJid.set(jid, group)); + } + + return new Set(builtByJid.values()); +} + +async function saveChunk(items: NormalizedItem[]) { + const variants = [...new Set(items.flatMap((item) => item.jids))]; + const groups = mergeGroups(items, await loadExistingGroups(variants)); + + const rows = new Map(); + groups.forEach((group) => + group.jids.forEach((jid) => rows.set(jid, { jid, remoteJid: group.remoteJid, lid: group.lid })), + ); + const jids = [...rows.keys()]; + + logger.verbose(`[saveOnWhatsappCache] Saving ${jids.length} jids in ${groups.size} groups`); + + await prismaRepository.$transaction([ + prismaRepository.isOnWhatsappJid.deleteMany({ where: { jid: { in: jids } } }), + prismaRepository.isOnWhatsappJid.createMany({ data: [...rows.values()] }), + ]); +} + +const writeQueue: { tail: Promise } = { tail: Promise.resolve() }; + +function enqueueWrite(write: () => Promise): Promise { + const run = writeQueue.tail.then(write); + writeQueue.tail = run.catch(() => undefined); + return run; +} + export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) { if (!configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) { return; } - // Processa todos os itens em paralelo para melhor performance - const processingPromises = data.map(async (item) => { + const items = data.map(normalizeItem).filter((item): item is NormalizedItem => item !== null); + await enqueueWrite(() => saveItems(items)); +} + +async function saveChunkWithRetry(chunk: NormalizedItem[]) { + try { + await saveChunk(chunk); + } catch (error) { + if ((error as { code?: string })?.code !== 'P2002') throw error; + await saveChunk(chunk); + } +} + +async function saveItems(items: NormalizedItem[]) { + const chunks = Array.from({ length: Math.ceil(items.length / SAVE_CHUNK_SIZE) }, (_, index) => + items.slice(index * SAVE_CHUNK_SIZE, (index + 1) * SAVE_CHUNK_SIZE), + ); + + for (const [index, chunk] of chunks.entries()) { try { - const remoteJid = normalizeJid(item.remoteJid); - if (!remoteJid) { - logger.warn('[saveOnWhatsappCache] Item skipped, missing remoteJid.'); - return; - } - - const altJidNormalized = normalizeJid(item.remoteJidAlt); - const lidAltJid = altJidNormalized && altJidNormalized.includes('@lid') ? altJidNormalized : null; - - const baseJids = [remoteJid]; // Garante que o remoteJid esteja na lista inicial - if (lidAltJid) { - baseJids.push(lidAltJid); - } - - const expandedJids = baseJids.flatMap((jid) => getAvailableNumbers(jid)); - - // 1. Busca entrada por jidOptions e também remoteJid - // Às vezes acontece do remoteJid atual NÃO ESTAR no jidOptions ainda, ocasionando o erro: - // 'Unique constraint failed on the fields: (`remoteJid`)' - // Isso acontece principalmente em grupos que possuem o número do criador no ID (ex.: '559911223345-1234567890@g.us') - const existingRecord = await prismaRepository.isOnWhatsapp.findFirst({ - where: { - OR: [ - ...expandedJids.map((jid) => ({ jidOptions: { contains: jid } })), - { remoteJid: remoteJid }, // TODO: Descobrir o motivo que causa o remoteJid não estar (às vezes) incluso na lista de jidOptions - ], - }, - }); - - logger.verbose( - `[saveOnWhatsappCache] Register exists for [${expandedJids.join(',')}]? => ${existingRecord ? existingRecord.remoteJid : 'Not found'}`, - ); - - // 2. Unifica todos os JIDs usando um Set para garantir valores únicos - const finalJidOptions = new Set(expandedJids); - - if (lidAltJid) { - finalJidOptions.add(lidAltJid); - } - - if (existingRecord?.jidOptions) { - existingRecord.jidOptions.split(',').forEach((jid) => finalJidOptions.add(jid)); - } - - // 3. Prepara o payload final - // Ordena os JIDs para garantir consistência na string final - const sortedJidOptions = [...finalJidOptions].sort(); - const newJidOptionsString = sortedJidOptions.join(','); - const newLid = item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null; - - const dataPayload = { - remoteJid: remoteJid, - jidOptions: newJidOptionsString, - lid: newLid, - }; - - // 4. Decide entre Criar ou Atualizar - if (existingRecord) { - // Compara a string de JIDs ordenada existente com a nova - const existingJidOptionsString = existingRecord.jidOptions - ? existingRecord.jidOptions.split(',').sort().join(',') - : ''; - - const isDataSame = - existingRecord.remoteJid === dataPayload.remoteJid && - existingJidOptionsString === dataPayload.jidOptions && - existingRecord.lid === dataPayload.lid; - - if (isDataSame) { - logger.verbose(`[saveOnWhatsappCache] Data for ${remoteJid} is already up-to-date. Skipping update.`); - return; // Pula para o próximo item - } - - // Os dados são diferentes, então atualiza - logger.verbose( - `[saveOnWhatsappCache] Register exists, updating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`, - ); - await prismaRepository.isOnWhatsapp.update({ - where: { id: existingRecord.id }, - data: dataPayload, - }); - } else { - // Cria nova entrada - logger.verbose( - `[saveOnWhatsappCache] Register does not exist, creating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`, - ); - await prismaRepository.isOnWhatsapp.create({ - data: dataPayload, - }); - } - } catch (e) { - // Loga o erro mas não para a execução dos outros promises - logger.error(`[saveOnWhatsappCache] Error processing item for ${item.remoteJid}: `); - logger.error(e); + await saveChunkWithRetry(chunk); + } catch (error) { + logger.error(`[saveOnWhatsappCache] Error saving chunk starting at item ${index * SAVE_CHUNK_SIZE}: `); + logger.error(error); } - }); - - // Espera todas as operações paralelas terminarem - await Promise.allSettled(processingPromises); + } } export async function getOnWhatsappCache(remoteJids: string[]) { - let results: { + const results: { remoteJid: string; number: string; jidOptions: string[]; lid?: string; }[] = []; - if (configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) { - const remoteJidsWithoutPlus = remoteJids.map((remoteJid) => getAvailableNumbers(remoteJid)).flat(); - - const onWhatsappCache = await prismaRepository.isOnWhatsapp.findMany({ - where: { - OR: remoteJidsWithoutPlus.map((remoteJid) => ({ jidOptions: { contains: remoteJid } })), - updatedAt: { - gte: dayjs().subtract(configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP_DAYS, 'days').toDate(), - }, - }, - }); - - results = onWhatsappCache.map((item) => ({ - remoteJid: item.remoteJid, - number: item.remoteJid.split('@')[0], - jidOptions: item.jidOptions.split(','), - lid: item.lid, - })); + if (!configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) { + return results; + } + + const cutoff = dayjs() + .subtract(configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP_DAYS, 'days') + .toDate(); + const variants = [...new Set(remoteJids.flatMap((remoteJid) => getAvailableNumbers(remoteJid)))]; + + const hits = await prismaRepository.isOnWhatsappJid.findMany({ + where: { jid: { in: variants }, updatedAt: { gte: cutoff } }, + }); + const canonicals = [...new Set(hits.map((row) => row.remoteJid))]; + if (canonicals.length === 0) { + return results; } + const rows = await prismaRepository.isOnWhatsappJid.findMany({ + where: { remoteJid: { in: canonicals }, updatedAt: { gte: cutoff } }, + }); + + const groups = new Map; lid?: string }>(); + for (const row of rows) { + const group = groups.get(row.remoteJid) ?? { jids: new Set() }; + group.jids.add(row.jid); + group.lid = group.lid ?? row.lid ?? undefined; + groups.set(row.remoteJid, group); + } + + groups.forEach((group, remoteJid) => + results.push({ + remoteJid, + number: remoteJid.split('@')[0], + jidOptions: [...group.jids].sort(), + lid: group.lid, + }), + ); + return results; } diff --git a/src/utils/resolveLidRemoteJidAlt.ts b/src/utils/resolveLidRemoteJidAlt.ts new file mode 100644 index 0000000000..8b23e37f40 --- /dev/null +++ b/src/utils/resolveLidRemoteJidAlt.ts @@ -0,0 +1,62 @@ +import { type Contact, isLidUser, isPnUser, jidNormalizedUser, type LIDMapping, type WAMessage } from 'baileys'; + +export type LidToPnLookup = (lids: string[]) => Promise; + +const toPhoneJid = (jid?: string | null): string | undefined => { + const normalized = jid ? jidNormalizedUser(jid) : undefined; + return normalized && isPnUser(normalized) ? normalized : undefined; +}; + +const contactLidToPn = (contacts: Contact[]): Map => { + const pairs = new Map(); + for (const contact of contacts) { + const lids = new Set( + [contact.lid, contact.id].filter((jid) => jid && isLidUser(jid)).map((jid) => jidNormalizedUser(jid)), + ); + const pn = toPhoneJid(contact.phoneNumber) ?? toPhoneJid(contact.id); + if (lids.size === 1 && pn) { + pairs.set([...lids][0], pn); + } + } + return pairs; +}; + +export async function resolveLidRemoteJidAlt( + messages: WAMessage[], + contacts: Contact[], + lookup?: LidToPnLookup, + onLookupError?: (error: unknown) => void, +): Promise { + const pending = messages.filter( + (m) => m.key?.remoteJid && isLidUser(m.key.remoteJid) && !toPhoneJid(m.key.remoteJidAlt), + ); + if (pending.length === 0) { + return; + } + + const lidToPn = contactLidToPn(contacts); + const unresolvedLids = [...new Set(pending.map((m) => jidNormalizedUser(m.key.remoteJid)))].filter( + (lid) => !lidToPn.has(lid), + ); + + if (unresolvedLids.length > 0 && lookup) { + try { + const mappings = (await lookup(unresolvedLids)) ?? []; + for (const { lid, pn } of mappings) { + const phoneJid = toPhoneJid(pn); + if (lid && phoneJid) { + lidToPn.set(jidNormalizedUser(lid), phoneJid); + } + } + } catch (error) { + onLookupError?.(error); + } + } + + for (const m of pending) { + const pn = lidToPn.get(jidNormalizedUser(m.key.remoteJid)); + if (pn) { + m.key.remoteJidAlt = pn; + } + } +} diff --git a/src/utils/resolvePhoneRemoteJid.ts b/src/utils/resolvePhoneRemoteJid.ts new file mode 100644 index 0000000000..fce8595ce4 --- /dev/null +++ b/src/utils/resolvePhoneRemoteJid.ts @@ -0,0 +1,7 @@ +export function resolvePhoneRemoteJid(key: { remoteJid?: string; remoteJidAlt?: string }): string | undefined { + if (key.remoteJid?.includes('@lid') && key.remoteJidAlt) { + return key.remoteJidAlt; + } + + return key.remoteJid; +} diff --git a/src/utils/use-multi-file-auth-state-prisma.ts b/src/utils/use-multi-file-auth-state-prisma.ts index d090780234..bd11afa2fe 100644 --- a/src/utils/use-multi-file-auth-state-prisma.ts +++ b/src/utils/use-multi-file-auth-state-prisma.ts @@ -182,7 +182,7 @@ export default async function useMultiFileAuthStatePrisma( ids.map(async (id) => { let value = await readData(`${type}-${id}`); if (type === 'app-state-sync-key' && value) { - value = proto.Message.AppStateSyncKeyData.create(value); + value = proto.Message.AppStateSyncKeyData.fromObject(value); } data[id] = value; diff --git a/src/utils/use-multi-file-auth-state-provider-files.ts b/src/utils/use-multi-file-auth-state-provider-files.ts index eecc3100e5..157918169d 100644 --- a/src/utils/use-multi-file-auth-state-provider-files.ts +++ b/src/utils/use-multi-file-auth-state-provider-files.ts @@ -116,7 +116,7 @@ export class AuthStateProvider { ids.map(async (id) => { let value = await readData(`${type}-${id}`); if (type === 'app-state-sync-key' && value) { - value = proto.Message.AppStateSyncKeyData.create(value); + value = proto.Message.AppStateSyncKeyData.fromObject(value); } data[id] = value; diff --git a/src/utils/use-multi-file-auth-state-redis-db.ts b/src/utils/use-multi-file-auth-state-redis-db.ts index e0981c700c..4d5d5a5457 100644 --- a/src/utils/use-multi-file-auth-state-redis-db.ts +++ b/src/utils/use-multi-file-auth-state-redis-db.ts @@ -61,7 +61,7 @@ export async function useMultiFileAuthStateRedisDb( ids.map(async (id) => { let value = await readData(`${type}-${id}`); if (type === 'app-state-sync-key' && value) { - value = proto.Message.AppStateSyncKeyData.create(value); + value = proto.Message.AppStateSyncKeyData.fromObject(value); } data[id] = value; diff --git a/test/api/integrations/channel/whatsapp/chatModifyLastMessage.test.ts b/test/api/integrations/channel/whatsapp/chatModifyLastMessage.test.ts new file mode 100644 index 0000000000..0a4eacbcc6 --- /dev/null +++ b/test/api/integrations/channel/whatsapp/chatModifyLastMessage.test.ts @@ -0,0 +1,150 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('@api/server.module', () => ({ + chatbotController: { emit: vi.fn(async () => undefined) }, + waMonitor: {}, +})); + +vi.mock('@utils/onWhatsappCache', () => ({ + getOnWhatsappCache: vi.fn(async () => []), + saveOnWhatsappCache: vi.fn(async () => undefined), +})); + +vi.mock('@utils/sendTelemetry', () => ({ sendTelemetry: vi.fn() })); + +vi.mock('@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper', () => ({ + chatwootImport: { getRepositoryMessagesCache: vi.fn(() => null), setRepositoryMessagesCache: vi.fn() }, +})); + +const phoneNumber = '5492615550101'; +const phoneJid = '5492615550101@s.whatsapp.net'; +const lidJid = '123456789012345@lid'; + +const buildService = (storedMessages: unknown[]) => { + const config: Record = { + LOG: { BAILEYS: 'error' }, + CHATWOOT: { ENABLED: false }, + OPENAI: { ENABLED: false }, + S3: { ENABLE: false }, + }; + const configService = { get: vi.fn((key: string) => config[key]) } as any; + const prismaRepository = { + message: { findMany: vi.fn(async () => storedMessages) }, + } as any; + const service = new BaileysStartupService( + configService, + { emit: vi.fn() } as any, + prismaRepository, + {} as any, + {} as any, + { get: vi.fn(), set: vi.fn() } as any, + {} as any, + ) as any; + service.instance.id = 'instance-id'; + service.client = { chatModify: vi.fn(async () => undefined) }; + return { service, prismaRepository }; +}; + +const storedMessage = (remoteJid: string) => ({ + key: { id: 'MSG-1', remoteJid, fromMe: false }, + messageTimestamp: 1700000000, +}); + +describe('archiveChat by chat number', () => { + it('looks up the last message by the JSON remoteJid path of the normalized JID within the instance', async () => { + const { service, prismaRepository } = buildService([storedMessage(phoneJid)]); + + await service.archiveChat({ chat: phoneNumber, archive: true }); + + expect(prismaRepository.message.findMany).toHaveBeenCalledWith({ + where: { key: { path: ['remoteJid'], equals: phoneJid }, instanceId: 'instance-id' }, + orderBy: { messageTimestamp: 'desc' }, + take: 1, + }); + }); + + it('archives with the stored last message and keeps the response shape', async () => { + const { service } = buildService([storedMessage(phoneJid)]); + + const response = await service.archiveChat({ chat: phoneNumber, archive: true }); + + expect(service.client.chatModify).toHaveBeenCalledWith( + { archive: true, lastMessages: [storedMessage(phoneJid)] }, + phoneJid, + ); + expect(response).toEqual({ chatId: phoneNumber, archived: true }); + }); + + it('preserves @lid addressing in the lookup and the chat modification', async () => { + const { service, prismaRepository } = buildService([storedMessage(lidJid)]); + + await service.archiveChat({ chat: lidJid, archive: true }); + + expect(prismaRepository.message.findMany.mock.calls[0][0].where.key).toEqual({ + path: ['remoteJid'], + equals: lidJid, + }); + expect(service.client.chatModify.mock.calls[0][1]).toBe(lidJid); + }); + + it('archives a chat without stored messages using an empty lastMessages list', async () => { + const { service } = buildService([]); + + const response = await service.archiveChat({ chat: phoneNumber, archive: true }); + + expect(service.client.chatModify).toHaveBeenCalledWith({ archive: true, lastMessages: [] }, phoneJid); + expect(response).toEqual({ chatId: phoneNumber, archived: true }); + }); +}); + +describe('archiveChat with an explicit lastMessage', () => { + it('uses the provided message without querying storage and keeps the response shape', async () => { + const { service, prismaRepository } = buildService([]); + const lastMessage = storedMessage(phoneJid); + + const response = await service.archiveChat({ lastMessage, archive: true }); + + expect(prismaRepository.message.findMany).not.toHaveBeenCalled(); + expect(service.client.chatModify).toHaveBeenCalledWith({ archive: true, lastMessages: [lastMessage] }, phoneJid); + expect(response).toEqual({ chatId: phoneJid, archived: true }); + }); + + it('fills a missing messageTimestamp with unix seconds', async () => { + const { service } = buildService([]); + const lastMessage = { key: { id: 'MSG-1', remoteJid: phoneJid, fromMe: false } } as any; + const nowSeconds = Math.floor(Date.now() / 1000); + + await service.archiveChat({ lastMessage, archive: true }); + + expect(lastMessage.messageTimestamp).toBeGreaterThanOrEqual(nowSeconds); + expect(lastMessage.messageTimestamp).toBeLessThan(nowSeconds + 60); + }); +}); + +describe('markChatUnread by chat number', () => { + it('looks up the last message by the JSON remoteJid path and marks the chat unread', async () => { + const { service, prismaRepository } = buildService([storedMessage(phoneJid)]); + + const response = await service.markChatUnread({ chat: phoneNumber }); + + expect(prismaRepository.message.findMany.mock.calls[0][0].where).toEqual({ + key: { path: ['remoteJid'], equals: phoneJid }, + instanceId: 'instance-id', + }); + expect(service.client.chatModify).toHaveBeenCalledWith( + { markRead: false, lastMessages: [storedMessage(phoneJid)] }, + phoneJid, + ); + expect(response).toEqual({ chatId: phoneNumber, markedChatUnread: true }); + }); + + it('still fails when the chat has no stored messages', async () => { + const { service } = buildService([]); + + await expect(service.markChatUnread({ chat: phoneNumber })).rejects.toMatchObject({ + message: expect.anything(), + }); + expect(service.client.chatModify).not.toHaveBeenCalled(); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/eventErrorIsolation.test.ts b/test/api/integrations/channel/whatsapp/eventErrorIsolation.test.ts new file mode 100644 index 0000000000..0ec44096ee --- /dev/null +++ b/test/api/integrations/channel/whatsapp/eventErrorIsolation.test.ts @@ -0,0 +1,186 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { Events } from '@api/types/wa.types'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@api/server.module', () => ({ + chatbotController: { emit: vi.fn(async () => undefined) }, + waMonitor: {}, +})); + +vi.mock('@utils/onWhatsappCache', () => ({ + getOnWhatsappCache: vi.fn(async () => []), + saveOnWhatsappCache: vi.fn(async () => undefined), +})); + +vi.mock('@utils/sendTelemetry', () => ({ sendTelemetry: vi.fn() })); + +vi.mock('@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper', () => ({ + chatwootImport: { getRepositoryMessagesCache: vi.fn(() => null), setRepositoryMessagesCache: vi.fn() }, +})); + +const buildCache = () => { + const store = new Map(); + return { + store, + get: vi.fn(async (key: string) => store.get(key)), + set: vi.fn(async (key: string, value: unknown) => { + store.set(key, value); + }), + }; +}; + +const buildService = () => { + const config: Record = { + LOG: { BAILEYS: 'error' }, + DATABASE: { SAVE_DATA: { NEW_MESSAGE: true, HISTORIC: true, MESSAGE_UPDATE: true, CHATS: false, CONTACTS: false } }, + CHATWOOT: { ENABLED: false }, + OPENAI: { ENABLED: false }, + S3: { ENABLE: false }, + }; + const configService = { get: vi.fn((key: string) => config[key]) } as any; + const prismaRepository = { + chat: { findFirst: vi.fn(async () => null), findMany: vi.fn(async () => []), createMany: vi.fn(async () => ({})) }, + message: { + create: vi.fn(async ({ data }) => ({ id: 'row-id', ...data })), + createMany: vi.fn(async () => ({})), + findMany: vi.fn(async () => []), + update: vi.fn(async () => ({})), + updateMany: vi.fn(async () => ({ count: 1 })), + }, + messageUpdate: { create: vi.fn(async () => ({})) }, + contact: { findFirst: vi.fn(async () => null) }, + } as any; + const service = new BaileysStartupService( + configService, + { emit: vi.fn() } as any, + prismaRepository, + {} as any, + {} as any, + buildCache() as any, + {} as any, + ) as any; + service.instance.id = 'instance-id'; + service.instance.name = 'instance-name'; + service.sendDataWebhook = vi.fn(); + service.profilePicture = vi.fn(async () => ({ profilePictureUrl: null })); + service.updateMessagesReadedByTimestamp = vi.fn(async () => undefined); + service.updateChatUnreadMessages = vi.fn(async () => undefined); + service.client = { readMessages: vi.fn() }; + service.contactHandle = { 'contacts.upsert': vi.fn(async () => undefined) }; + return { service, prismaRepository }; +}; + +const remoteJid = '5492615550101@s.whatsapp.net'; +const groupJid = '120363000000000001@g.us'; + +const message = (id: string) => ({ + key: { id, fromMe: false, remoteJid }, + message: { conversation: 'hello' }, + messageTimestamp: 1700000000, + pushName: 'Someone', +}); + +const webhookCalls = (service: any, event: Events) => + service.sendDataWebhook.mock.calls.filter(([sent]) => sent === event); + +const processEvents = async (service: any, events: Record, settings: Record) => { + let processCallback: (events: Record) => Promise; + service.client = { + ev: { + process: vi.fn((callback) => { + processCallback = callback; + }), + }, + }; + service.findSettings = vi.fn(async () => settings); + service.chatHandle = { + 'chats.upsert': vi.fn(async () => undefined), + 'chats.update': vi.fn(async () => undefined), + }; + service.labelHandle = { + [Events.LABELS_EDIT]: vi.fn(async () => undefined), + [Events.LABELS_ASSOCIATION]: vi.fn(async () => undefined), + }; + service.eventHandler(); + await processCallback(events); + await service.eventProcessingQueue; +}; + +describe('event error isolation', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('emits the remaining upserts of a batch when one message fails to store', async () => { + const { service, prismaRepository } = buildService(); + prismaRepository.message.create + .mockImplementationOnce(async ({ data }) => ({ id: 'row-1', ...data })) + .mockRejectedValueOnce(new Error('database unavailable')); + + await service.messageHandle['messages.upsert']( + { messages: [message('MSG-1'), message('MSG-2'), message('MSG-3')], type: 'notify' }, + {}, + ); + + const emittedIds = webhookCalls(service, Events.MESSAGES_UPSERT).map(([, data]) => data.key.id); + expect(emittedIds).toEqual(['MSG-1', 'MSG-3']); + }); + + it('handles the remaining updates of a batch when one update fails', async () => { + const { service, prismaRepository } = buildService(); + prismaRepository.$queryRaw = vi + .fn() + .mockRejectedValueOnce(new Error('database unavailable')) + .mockResolvedValueOnce([ + { id: 'row-MSG-2', key: { id: 'MSG-2', remoteJid, fromMe: false }, status: 'DELIVERY_ACK' }, + ]); + + await service.messageHandle['messages.update']( + [ + { key: { id: 'MSG-1', fromMe: false, remoteJid }, update: { message: null } }, + { key: { id: 'MSG-2', fromMe: false, remoteJid }, update: { message: null } }, + ], + {}, + ); + + const deletedIds = webhookCalls(service, Events.MESSAGES_DELETE).map(([, data]) => data.id); + expect(deletedIds).toEqual(['MSG-2']); + }); + + it('runs later event types after an ignored group presence update', async () => { + const { service } = buildService(); + const chats = [{ id: remoteJid }]; + + await processEvents( + service, + { 'presence.update': { id: groupJid, presences: {} }, 'chats.upsert': chats }, + { groupsIgnore: true }, + ); + + expect(webhookCalls(service, Events.PRESENCE_UPDATE)).toHaveLength(0); + expect(service.chatHandle['chats.upsert']).toHaveBeenCalledWith(chats); + }); + + it('runs the labels edit handler after a labels association', async () => { + const { service } = buildService(); + const association = { type: 'add', association: { chatId: remoteJid, labelId: '1' } }; + const label = { id: '1', name: 'Lead' }; + + await processEvents(service, { [Events.LABELS_ASSOCIATION]: association, [Events.LABELS_EDIT]: label }, {}); + + expect(service.labelHandle[Events.LABELS_ASSOCIATION]).toHaveBeenCalled(); + expect(service.labelHandle[Events.LABELS_EDIT]).toHaveBeenCalledWith(label); + }); + + it('runs later event types when an earlier event type throws', async () => { + const { service } = buildService(); + const label = { id: '1', name: 'Lead' }; + service.messageHandle['messages.update'] = vi.fn(async () => { + throw new Error('update failed'); + }); + + await processEvents(service, { 'messages.update': [], [Events.LABELS_EDIT]: label }, {}); + + expect(service.labelHandle[Events.LABELS_EDIT]).toHaveBeenCalledWith(label); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/getMessage.test.ts b/test/api/integrations/channel/whatsapp/getMessage.test.ts new file mode 100644 index 0000000000..6fbc9f0978 --- /dev/null +++ b/test/api/integrations/channel/whatsapp/getMessage.test.ts @@ -0,0 +1,96 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { describe, expect, it, vi } from 'vitest'; + +const buildService = (queryRaw: () => Promise) => { + const service = Object.create(BaileysStartupService.prototype) as any; + service.instance = { id: 'instance-id', name: 'instance-name' }; + service.prismaRepository = { $queryRaw: vi.fn(queryRaw), messageUpdate: { findFirst: vi.fn(async () => null) } }; + service.logger = { verbose: vi.fn(), error: vi.fn() }; + return service; +}; + +const key = { id: 'MESSAGE-ID', remoteJid: '5491123456789@s.whatsapp.net', fromMe: true }; + +describe('BaileysStartupService.getMessage', () => { + it('returns undefined for an unknown key so Baileys skips the retry resend', async () => { + const service = buildService(async () => []); + + await expect(service.getMessage(key)).resolves.toBeUndefined(); + await expect(service.getMessage(key, true)).resolves.toBeUndefined(); + }); + + it('returns undefined when the lookup fails', async () => { + const service = buildService(async () => { + throw new Error('database unavailable'); + }); + + await expect(service.getMessage(key)).resolves.toBeUndefined(); + await expect(service.getMessage(key, true)).resolves.toBeUndefined(); + }); + + it('returns the stored message content for a known key', async () => { + const stored = { key, message: { conversation: 'hello' } }; + const service = buildService(async () => [stored]); + + await expect(service.getMessage(key)).resolves.toEqual({ conversation: 'hello' }); + await expect(service.getMessage(key, true)).resolves.toEqual(stored); + }); + + it('returns the poll secret for a stored poll creation', async () => { + const pollCreationMessage = { name: 'poll', options: [{ optionName: 'a' }] }; + const service = buildService(async () => [ + { key, message: { pollCreationMessage, messageContextInfo: { messageSecret: 'c2VjcmV0' } } }, + ]); + + const result = await service.getMessage(key); + + expect(result.pollCreationMessage).toEqual(pollCreationMessage); + expect(Buffer.from(result.messageContextInfo.messageSecret).toString()).toBe('secret'); + }); +}); + +describe('BaileysStartupService.getMessage for deleted messages', () => { + const buildDeletedAwareService = (rows: unknown[], deletedUpdate: unknown = null) => { + const service = buildService(async () => rows); + service.prismaRepository.messageUpdate.findFirst = vi.fn(async () => deletedUpdate); + return service; + }; + + it('returns undefined for a row with status DELETED', async () => { + const service = buildDeletedAwareService([{ key, status: 'DELETED', message: { conversation: 'secret' } }]); + + await expect(service.getMessage(key)).resolves.toBeUndefined(); + }); + + it('returns undefined for a row whose key is flagged deleted', async () => { + const service = buildDeletedAwareService([ + { key: { ...key, deleted: true }, status: 'SERVER_ACK', message: { conversation: 'secret' } }, + ]); + + await expect(service.getMessage(key)).resolves.toBeUndefined(); + }); + + it('returns undefined when a DELETED update exists for the instance', async () => { + const service = buildDeletedAwareService([{ key, status: 'SERVER_ACK', message: { conversation: 'secret' } }], { + id: 'update-id', + }); + + await expect(service.getMessage(key)).resolves.toBeUndefined(); + expect(service.prismaRepository.messageUpdate.findFirst).toHaveBeenCalledWith({ + where: { instanceId: 'instance-id', keyId: 'MESSAGE-ID', status: 'DELETED' }, + }); + }); + + it('keeps returning the full row of a deleted message', async () => { + const stored = { key, status: 'DELETED', message: { conversation: 'secret' } }; + const service = buildDeletedAwareService([stored], { id: 'update-id' }); + + await expect(service.getMessage(key, true)).resolves.toEqual(stored); + }); + + it('returns the content of a live message', async () => { + const service = buildDeletedAwareService([{ key, status: 'READ', message: { conversation: 'hello' } }]); + + await expect(service.getMessage(key)).resolves.toEqual({ conversation: 'hello' }); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/instanceScoping.test.ts b/test/api/integrations/channel/whatsapp/instanceScoping.test.ts new file mode 100644 index 0000000000..ca6e702df0 --- /dev/null +++ b/test/api/integrations/channel/whatsapp/instanceScoping.test.ts @@ -0,0 +1,109 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('@api/server.module', () => ({ + chatbotController: { emit: vi.fn(async () => undefined) }, + waMonitor: {}, +})); + +vi.mock('@utils/onWhatsappCache', () => ({ + getOnWhatsappCache: vi.fn(async () => []), + saveOnWhatsappCache: vi.fn(async () => undefined), +})); + +vi.mock('@utils/sendTelemetry', () => ({ sendTelemetry: vi.fn() })); + +vi.mock('@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper', () => ({ + chatwootImport: { getRepositoryMessagesCache: vi.fn(() => null), setRepositoryMessagesCache: vi.fn() }, +})); + +const remoteJid = '5492615550101@s.whatsapp.net'; + +const buildService = () => { + const config: Record = { + LOG: { BAILEYS: 'error' }, + DATABASE: { SAVE_DATA: { NEW_MESSAGE: true, HISTORIC: true, MESSAGE_UPDATE: true, CHATS: false, CONTACTS: false } }, + CHATWOOT: { ENABLED: false }, + OPENAI: { ENABLED: false }, + S3: { ENABLE: false }, + }; + const configService = { get: vi.fn((key: string) => config[key]) } as any; + const prismaRepository = { + chat: { findFirst: vi.fn(async () => null), update: vi.fn(async () => ({})) }, + message: { findFirst: vi.fn(async () => null), update: vi.fn(async ({ data }) => ({ id: 'row-id', ...data })) }, + messageUpdate: { create: vi.fn(async () => ({})) }, + $queryRaw: vi.fn(async () => [{ count: 0 }]), + } as any; + const service = new BaileysStartupService( + configService, + { emit: vi.fn() } as any, + prismaRepository, + {} as any, + {} as any, + { get: vi.fn(), set: vi.fn() } as any, + {} as any, + ) as any; + service.instance.id = 'instance-id'; + service.instance.name = 'instance-name'; + service.sendDataWebhook = vi.fn(); + return { service, prismaRepository }; +}; + +describe('updateChatUnreadMessages instance scoping', () => { + it('looks up the chat within the current instance', async () => { + const { service, prismaRepository } = buildService(); + + await service.updateChatUnreadMessages(remoteJid); + + expect(prismaRepository.chat.findFirst).toHaveBeenCalledWith({ + where: { instanceId: 'instance-id', remoteJid }, + }); + }); +}); + +describe('updateMessage instance scoping and guards', () => { + const buildEdit = (storedKey: Record) => { + const { service, prismaRepository } = buildService(); + service.formatUpdateMessage = vi.fn(async () => ({ text: 'edited' })); + service.getMessage = vi.fn(async () => ({ + key: { id: 'MSG-1', remoteJid, fromMe: true }, + messageType: 'conversation', + message: { conversation: 'original' }, + messageTimestamp: 1, + })); + service.client = { + sendMessage: vi.fn(async () => ({ + key: { id: 'EDIT-1', remoteJid, fromMe: true }, + message: { protocolMessage: { key: { id: 'MSG-1', remoteJid, fromMe: true } } }, + })), + }; + prismaRepository.message.findFirst = vi.fn(async () => ({ id: 'row-MSG-1', key: storedKey })); + const data = { number: '5492615550101', text: 'edited', key: { id: 'MSG-1', remoteJid, fromMe: true } }; + return { service, prismaRepository, data }; + }; + + it('looks up the edited message within the current instance', async () => { + const { service, prismaRepository, data } = buildEdit({ id: 'MSG-1', remoteJid, fromMe: true }); + + await service.updateMessage(data); + + expect(prismaRepository.message.findFirst).toHaveBeenCalledWith({ + where: { instanceId: 'instance-id', key: { path: ['id'], equals: 'MSG-1' } }, + }); + expect(prismaRepository.message.update).toHaveBeenCalledTimes(1); + }); + + it('rejects editing a message not sent by the instance and does not update it', async () => { + const { service, prismaRepository, data } = buildEdit({ id: 'MSG-1', remoteJid, fromMe: false }); + + await expect(service.updateMessage(data)).rejects.toThrow('You cannot edit others messages'); + expect(prismaRepository.message.update).not.toHaveBeenCalled(); + }); + + it('rejects editing a deleted message and does not update it', async () => { + const { service, prismaRepository, data } = buildEdit({ id: 'MSG-1', remoteJid, fromMe: true, deleted: true }); + + await expect(service.updateMessage(data)).rejects.toThrow('You cannot edit deleted messages'); + expect(prismaRepository.message.update).not.toHaveBeenCalled(); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/logoutInstance.test.ts b/test/api/integrations/channel/whatsapp/logoutInstance.test.ts new file mode 100644 index 0000000000..064fa67c2d --- /dev/null +++ b/test/api/integrations/channel/whatsapp/logoutInstance.test.ts @@ -0,0 +1,110 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { Events } from '@api/types/wa.types'; +import { Boom } from '@hapi/boom'; +import { DisconnectReason } from 'baileys'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@utils/use-multi-file-auth-state-prisma', () => ({ + default: vi.fn(async () => ({ removeCreds: vi.fn() })), +})); + +vi.mock('@utils/use-multi-file-auth-state-redis-db', () => ({ + useMultiFileAuthStateRedisDb: vi.fn(async () => ({ removeCreds: vi.fn() })), +})); + +const buildService = () => { + const service = Object.create(BaileysStartupService.prototype) as any; + const config: Record = { + DATABASE: { SAVE_DATA: { INSTANCE: true } }, + CACHE: { REDIS: { ENABLED: false } }, + PROVIDER: { ENABLED: false }, + CHATWOOT: { ENABLED: false }, + }; + service.messageProcessor = { onDestroy: vi.fn() }; + service.client = { logout: vi.fn(async () => undefined), ws: { close: vi.fn() }, end: vi.fn() }; + service.configService = { get: vi.fn((key: string) => config[key]) }; + service.prismaRepository = { + instance: { update: vi.fn(async () => ({})) }, + session: { findFirst: vi.fn(async () => null), delete: vi.fn() }, + }; + service.instance = { id: 'instance-id', name: 'instance-name', wuid: 'wuid' }; + service.eventEmitter = { emit: vi.fn() }; + service.sendDataWebhook = vi.fn(); + service.connectToWhatsapp = vi.fn(); + service.localChatwoot = { enabled: false }; + service.cache = {}; + service.stateConnection = { state: 'open' }; + service.endSession = false; + return service; +}; + +const connectionUpdates = (service: any) => + service.sendDataWebhook.mock.calls.filter(([event]: [string]) => event === Events.CONNECTION_UPDATE); + +const logoutEmits = (service: any) => + service.eventEmitter.emit.mock.calls.filter(([event]: [string]) => event === 'logout.instance'); + +describe('BaileysStartupService.logoutInstance', () => { + let service: any; + + beforeEach(() => { + service = buildService(); + }); + + it('closes the connection state immediately without waiting for Baileys', async () => { + await service.logoutInstance(); + + expect(service.connectionStatus).toEqual({ state: 'close', statusReason: DisconnectReason.loggedOut }); + expect(service.prismaRepository.instance.update).toHaveBeenCalledWith({ + where: { id: 'instance-id' }, + data: expect.objectContaining({ + connectionStatus: 'close', + disconnectionReasonCode: DisconnectReason.loggedOut, + }), + }); + expect(connectionUpdates(service)).toEqual([ + [Events.CONNECTION_UPDATE, expect.objectContaining({ instance: 'instance-name', state: 'close' })], + ]); + expect(logoutEmits(service)).toHaveLength(1); + expect(service.connectToWhatsapp).not.toHaveBeenCalled(); + }); + + it('ignores the late Baileys loggedOut close event after logout', async () => { + await service.logoutInstance(); + + await service.connectionUpdate({ + connection: 'close', + lastDisconnect: { error: new Boom('logged out', { statusCode: DisconnectReason.loggedOut }), date: new Date() }, + }); + + expect(connectionUpdates(service)).toHaveLength(1); + expect(logoutEmits(service)).toHaveLength(1); + expect(service.connectToWhatsapp).not.toHaveBeenCalled(); + }); + + it('emits logout.instance only after the session row is deleted', async () => { + service.prismaRepository.session.findFirst.mockResolvedValue({ sessionId: 'instance-id' }); + + await service.logoutInstance(); + + const sessionDeleteOrder = service.prismaRepository.session.delete.mock.invocationCallOrder[0]; + const logoutEmitOrder = + service.eventEmitter.emit.mock.invocationCallOrder[ + service.eventEmitter.emit.mock.calls.findIndex(([event]: [string]) => event === 'logout.instance') + ]; + expect(sessionDeleteOrder).toBeLessThan(logoutEmitOrder); + }); + + it('still removes the session and emits logout.instance when persisting the closed state fails', async () => { + service.prismaRepository.instance.update.mockRejectedValue(new Error('database unavailable')); + service.prismaRepository.session.findFirst.mockResolvedValue({ sessionId: 'instance-id' }); + service.logger = { error: vi.fn() }; + + await expect(service.logoutInstance()).resolves.toBeUndefined(); + + expect(service.connectionStatus).toEqual({ state: 'close', statusReason: 401 }); + expect(service.prismaRepository.session.delete).toHaveBeenCalledWith({ where: { sessionId: 'instance-id' } }); + expect(logoutEmits(service)).toHaveLength(1); + expect(service.logger.error).toHaveBeenCalled(); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/messagesUpdateOwnStatus.test.ts b/test/api/integrations/channel/whatsapp/messagesUpdateOwnStatus.test.ts new file mode 100644 index 0000000000..4f74f45403 --- /dev/null +++ b/test/api/integrations/channel/whatsapp/messagesUpdateOwnStatus.test.ts @@ -0,0 +1,155 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { Events } from '@api/types/wa.types'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@api/server.module', () => ({ + chatbotController: { emit: vi.fn(async () => undefined) }, + waMonitor: {}, +})); + +vi.mock('@utils/onWhatsappCache', () => ({ + getOnWhatsappCache: vi.fn(async () => []), + saveOnWhatsappCache: vi.fn(async () => undefined), +})); + +vi.mock('@utils/sendTelemetry', () => ({ sendTelemetry: vi.fn() })); + +vi.mock('@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper', () => ({ + chatwootImport: { getRepositoryMessagesCache: vi.fn(() => null), setRepositoryMessagesCache: vi.fn() }, +})); + +const buildCache = () => { + const store = new Map(); + return { + store, + get: vi.fn(async (key: string) => store.get(key)), + set: vi.fn(async (key: string, value: unknown) => { + store.set(key, value); + }), + }; +}; + +const buildService = (baileysCache = buildCache()) => { + const config: Record = { + LOG: { BAILEYS: 'error' }, + DATABASE: { SAVE_DATA: { NEW_MESSAGE: true, HISTORIC: true, MESSAGE_UPDATE: true, CHATS: false, CONTACTS: false } }, + CHATWOOT: { ENABLED: false }, + OPENAI: { ENABLED: false }, + S3: { ENABLE: false }, + }; + const configService = { get: vi.fn((key: string) => config[key]) } as any; + const prismaRepository = { + chat: { findFirst: vi.fn(async () => null), findMany: vi.fn(async () => []), createMany: vi.fn(async () => ({})) }, + message: { + create: vi.fn(async ({ data }) => ({ id: 'row-id', ...data })), + createMany: vi.fn(async () => ({})), + findMany: vi.fn(async () => []), + update: vi.fn(async () => ({})), + updateMany: vi.fn(async () => ({ count: 1 })), + }, + messageUpdate: { create: vi.fn(async () => ({})) }, + contact: { findFirst: vi.fn(async () => null) }, + } as any; + const service = new BaileysStartupService( + configService, + { emit: vi.fn() } as any, + prismaRepository, + {} as any, + {} as any, + baileysCache as any, + {} as any, + ) as any; + service.instance.id = 'instance-id'; + service.instance.name = 'instance-name'; + service.sendDataWebhook = vi.fn(); + service.profilePicture = vi.fn(async () => ({ profilePictureUrl: null })); + service.updateMessagesReadedByTimestamp = vi.fn(async () => undefined); + service.updateChatUnreadMessages = vi.fn(async () => undefined); + service.client = { readMessages: vi.fn() }; + service.contactHandle = { 'contacts.upsert': vi.fn(async () => undefined) }; + return { service, prismaRepository, baileysCache }; +}; + +const storedMessage = (id: string, fromMe: boolean, status: string) => ({ + id: `row-${id}`, + key: { id, remoteJid: '5491111111111@s.whatsapp.net', fromMe }, + status, + messageTimestamp: 1700000000, +}); + +const updateWebhooks = (service: any) => + service.sendDataWebhook.mock.calls.filter(([event]) => event === Events.MESSAGES_UPDATE).map(([, data]) => data); + +const runUpdate = async (fromMe: boolean, storedStatus: string, incomingStatus: number, remoteJid?: string) => { + const { service, prismaRepository } = buildService(); + prismaRepository.$queryRaw = vi.fn(async () => [storedMessage('MSG-1', fromMe, storedStatus)]); + await service.messageHandle['messages.update']( + [{ key: { id: 'MSG-1', fromMe, remoteJid }, update: { status: incomingStatus } }], + {}, + ); + return { service, prismaRepository }; +}; + +describe('messages.update status of own messages', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('writes the ack status of a sent message by row id', async () => { + const { prismaRepository } = await runUpdate(true, 'PENDING', 3, '5491111111111@s.whatsapp.net'); + + expect(prismaRepository.message.updateMany).toHaveBeenCalledWith({ + where: { id: 'row-MSG-1', OR: [{ status: null }, { status: { in: ['ERROR', 'PENDING', 'SERVER_ACK'] } }] }, + data: { status: 'DELIVERY_ACK' }, + }); + }); + + it('writes the ack status of a sent message without a remote jid in the key', async () => { + const { prismaRepository } = await runUpdate(true, 'SERVER_ACK', 4); + + expect(prismaRepository.message.updateMany).toHaveBeenCalledWith({ + where: { + id: 'row-MSG-1', + OR: [{ status: null }, { status: { in: ['ERROR', 'PENDING', 'SERVER_ACK', 'DELIVERY_ACK'] } }], + }, + data: { status: 'READ' }, + }); + }); + + it('skips a backward ack but still emits the webhook and records the update', async () => { + const { service, prismaRepository } = await runUpdate(true, 'READ', 3, '5491111111111@s.whatsapp.net'); + + expect(prismaRepository.message.updateMany).not.toHaveBeenCalled(); + expect(updateWebhooks(service)).toHaveLength(1); + expect(updateWebhooks(service)[0]).not.toHaveProperty('key'); + expect(prismaRepository.messageUpdate.create).toHaveBeenCalledTimes(1); + }); + + it('still emits the webhook and records the update when the status write fails', async () => { + const { service, prismaRepository } = buildService(); + prismaRepository.$queryRaw = vi.fn(async () => [storedMessage('MSG-1', true, 'PENDING')]); + prismaRepository.message.updateMany = vi.fn(async () => { + throw new Error('db down'); + }); + + await service.messageHandle['messages.update']( + [{ key: { id: 'MSG-1', fromMe: true, remoteJid: '5491111111111@s.whatsapp.net' }, update: { status: 3 } }], + {}, + ); + + expect(updateWebhooks(service)).toHaveLength(1); + expect(prismaRepository.messageUpdate.create).toHaveBeenCalledTimes(1); + }); + + it('keeps the received message path marking the chat as read', async () => { + const { service, prismaRepository } = await runUpdate(false, 'DELIVERY_ACK', 4, '5491111111111@s.whatsapp.net'); + + expect(service.updateMessagesReadedByTimestamp).toHaveBeenCalledWith('5491111111111@s.whatsapp.net', 1700000000); + expect(prismaRepository.message.update).toHaveBeenCalledWith({ + where: { id: 'row-MSG-1' }, + data: { status: 'READ' }, + }); + expect(prismaRepository.message.updateMany).not.toHaveBeenCalled(); + expect(prismaRepository.messageUpdate.create).toHaveBeenCalledTimes(1); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/messagesUpdateRemoteJid.test.ts b/test/api/integrations/channel/whatsapp/messagesUpdateRemoteJid.test.ts new file mode 100644 index 0000000000..e8e4ddf330 --- /dev/null +++ b/test/api/integrations/channel/whatsapp/messagesUpdateRemoteJid.test.ts @@ -0,0 +1,111 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { Events } from '@api/types/wa.types'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@api/server.module', () => ({ + chatbotController: { emit: vi.fn(async () => undefined) }, + waMonitor: {}, +})); + +vi.mock('@utils/onWhatsappCache', () => ({ + getOnWhatsappCache: vi.fn(async () => []), + saveOnWhatsappCache: vi.fn(async () => undefined), +})); + +vi.mock('@utils/sendTelemetry', () => ({ sendTelemetry: vi.fn() })); + +vi.mock('@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper', () => ({ + chatwootImport: { getRepositoryMessagesCache: vi.fn(() => null), setRepositoryMessagesCache: vi.fn() }, +})); + +const buildCache = () => { + const store = new Map(); + return { + store, + get: vi.fn(async (key: string) => store.get(key)), + set: vi.fn(async (key: string, value: unknown) => { + store.set(key, value); + }), + }; +}; + +const buildService = (baileysCache = buildCache()) => { + const config: Record = { + LOG: { BAILEYS: 'error' }, + DATABASE: { SAVE_DATA: { NEW_MESSAGE: true, HISTORIC: true, MESSAGE_UPDATE: true, CHATS: false, CONTACTS: false } }, + CHATWOOT: { ENABLED: false }, + OPENAI: { ENABLED: false }, + S3: { ENABLE: false }, + }; + const configService = { get: vi.fn((key: string) => config[key]) } as any; + const prismaRepository = { + chat: { findFirst: vi.fn(async () => null), findMany: vi.fn(async () => []), createMany: vi.fn(async () => ({})) }, + message: { + create: vi.fn(async ({ data }) => ({ id: 'row-id', ...data })), + createMany: vi.fn(async () => ({})), + findMany: vi.fn(async () => []), + update: vi.fn(async () => ({})), + }, + messageUpdate: { create: vi.fn(async () => ({})) }, + contact: { findFirst: vi.fn(async () => null) }, + } as any; + const service = new BaileysStartupService( + configService, + { emit: vi.fn() } as any, + prismaRepository, + {} as any, + {} as any, + baileysCache as any, + {} as any, + ) as any; + service.instance.id = 'instance-id'; + service.instance.name = 'instance-name'; + service.sendDataWebhook = vi.fn(); + service.profilePicture = vi.fn(async () => ({ profilePictureUrl: null })); + service.updateMessagesReadedByTimestamp = vi.fn(async () => undefined); + service.updateChatUnreadMessages = vi.fn(async () => undefined); + service.client = { readMessages: vi.fn() }; + service.contactHandle = { 'contacts.upsert': vi.fn(async () => undefined) }; + return { service, prismaRepository, baileysCache }; +}; + +const storedMessage = (id: string, remoteJid: string) => ({ + id: `row-${id}`, + key: { id, remoteJid, fromMe: true }, + status: 'SERVER_ACK', + messageTimestamp: 1700000000, +}); + +const updateWebhooks = (service: any) => + service.sendDataWebhook.mock.calls.filter(([event]) => event === Events.MESSAGES_UPDATE).map(([, data]) => data); + +describe('messages.update without a remote jid in the key', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('records and emits the update with the stored message remote jid and keeps processing the batch', async () => { + const { service, prismaRepository } = buildService(); + prismaRepository.$queryRaw = vi.fn(async (_strings: TemplateStringsArray, _instanceId: string, id: string) => [ + storedMessage(id, `${id === 'MSG-1' ? '5491111111111' : '5492222222222'}@s.whatsapp.net`), + ]); + + await service.messageHandle['messages.update']( + [ + { key: { id: 'MSG-1', fromMe: true }, update: { status: 3 } }, + { key: { id: 'MSG-2', fromMe: true, remoteJid: '5492222222222@s.whatsapp.net' }, update: { status: 3 } }, + ], + {}, + ); + + const recorded = prismaRepository.messageUpdate.create.mock.calls.map(([{ data }]) => data); + expect(recorded.map((data) => [data.keyId, data.remoteJid])).toEqual([ + ['MSG-1', '5491111111111@s.whatsapp.net'], + ['MSG-2', '5492222222222@s.whatsapp.net'], + ]); + expect(updateWebhooks(service).map((data) => data.remoteJid)).toEqual([ + '5491111111111@s.whatsapp.net', + '5492222222222@s.whatsapp.net', + ]); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/messagesUpdateRevoke.test.ts b/test/api/integrations/channel/whatsapp/messagesUpdateRevoke.test.ts new file mode 100644 index 0000000000..a902c747bb --- /dev/null +++ b/test/api/integrations/channel/whatsapp/messagesUpdateRevoke.test.ts @@ -0,0 +1,122 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { Events } from '@api/types/wa.types'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@api/server.module', () => ({ + chatbotController: { emit: vi.fn(async () => undefined) }, + waMonitor: {}, +})); + +vi.mock('@utils/onWhatsappCache', () => ({ + getOnWhatsappCache: vi.fn(async () => []), + saveOnWhatsappCache: vi.fn(async () => undefined), +})); + +vi.mock('@utils/sendTelemetry', () => ({ sendTelemetry: vi.fn() })); + +vi.mock('@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper', () => ({ + chatwootImport: { getRepositoryMessagesCache: vi.fn(() => null), setRepositoryMessagesCache: vi.fn() }, +})); + +const buildCache = () => { + const store = new Map(); + return { + store, + get: vi.fn(async (key: string) => store.get(key)), + set: vi.fn(async (key: string, value: unknown) => { + store.set(key, value); + }), + }; +}; + +const buildService = (baileysCache = buildCache()) => { + const config: Record = { + LOG: { BAILEYS: 'error' }, + DATABASE: { SAVE_DATA: { NEW_MESSAGE: true, HISTORIC: true, MESSAGE_UPDATE: true, CHATS: false, CONTACTS: false } }, + CHATWOOT: { ENABLED: false }, + OPENAI: { ENABLED: false }, + S3: { ENABLE: false }, + }; + const configService = { get: vi.fn((key: string) => config[key]) } as any; + const prismaRepository = { + chat: { findFirst: vi.fn(async () => null), findMany: vi.fn(async () => []), createMany: vi.fn(async () => ({})) }, + message: { + create: vi.fn(async ({ data }) => ({ id: 'row-id', ...data })), + createMany: vi.fn(async () => ({})), + findMany: vi.fn(async () => []), + update: vi.fn(async () => ({})), + updateMany: vi.fn(async () => ({ count: 1 })), + }, + messageUpdate: { create: vi.fn(async () => ({})) }, + contact: { findFirst: vi.fn(async () => null) }, + } as any; + const service = new BaileysStartupService( + configService, + { emit: vi.fn() } as any, + prismaRepository, + {} as any, + {} as any, + baileysCache as any, + {} as any, + ) as any; + service.instance.id = 'instance-id'; + service.instance.name = 'instance-name'; + service.sendDataWebhook = vi.fn(); + service.profilePicture = vi.fn(async () => ({ profilePictureUrl: null })); + service.updateMessagesReadedByTimestamp = vi.fn(async () => undefined); + service.updateChatUnreadMessages = vi.fn(async () => undefined); + service.client = { readMessages: vi.fn() }; + service.contactHandle = { 'contacts.upsert': vi.fn(async () => undefined) }; + return { service, prismaRepository, baileysCache }; +}; + +const remoteJid = '5492615550101@s.whatsapp.net'; + +const runRevoke = async () => { + const { service, prismaRepository } = buildService(); + prismaRepository.$queryRaw = vi.fn(async () => [ + { id: 'row-MSG-1', key: { id: 'MSG-1', remoteJid, fromMe: false }, status: 'DELIVERY_ACK' }, + ]); + await service.messageHandle['messages.update']( + [{ key: { id: 'MSG-1', fromMe: false, remoteJid }, update: { message: null } }], + {}, + ); + return { service, prismaRepository }; +}; + +describe('messages.update revoke', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('records the revoke as a DELETED message update', async () => { + const { prismaRepository } = await runRevoke(); + + expect(prismaRepository.messageUpdate.create).toHaveBeenCalledWith({ + data: expect.objectContaining({ + keyId: 'MSG-1', + messageId: 'row-MSG-1', + status: 'DELETED', + instanceId: 'instance-id', + }), + }); + }); + + it('marks the stored message row as DELETED', async () => { + const { prismaRepository } = await runRevoke(); + + expect(prismaRepository.message.update).toHaveBeenCalledWith({ + where: { id: 'row-MSG-1' }, + data: { status: 'DELETED' }, + }); + }); + + it('keeps the MESSAGES_DELETE webhook payload unchanged', async () => { + const { service } = await runRevoke(); + + const deleteWebhooks = service.sendDataWebhook.mock.calls.filter(([event]) => event === Events.MESSAGES_DELETE); + expect(deleteWebhooks).toEqual([ + [Events.MESSAGES_DELETE, { id: 'MSG-1', fromMe: false, remoteJid, status: 'DELETED' }], + ]); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/messagesUpsertDedupe.test.ts b/test/api/integrations/channel/whatsapp/messagesUpsertDedupe.test.ts new file mode 100644 index 0000000000..617ff03345 --- /dev/null +++ b/test/api/integrations/channel/whatsapp/messagesUpsertDedupe.test.ts @@ -0,0 +1,229 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { Events } from '@api/types/wa.types'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@api/server.module', () => ({ + chatbotController: { emit: vi.fn(async () => undefined) }, + waMonitor: {}, +})); + +vi.mock('@utils/onWhatsappCache', () => ({ + getOnWhatsappCache: vi.fn(async () => []), + saveOnWhatsappCache: vi.fn(async () => undefined), +})); + +vi.mock('@utils/sendTelemetry', () => ({ sendTelemetry: vi.fn() })); + +vi.mock('@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper', () => ({ + chatwootImport: { getRepositoryMessagesCache: vi.fn(() => null), setRepositoryMessagesCache: vi.fn() }, +})); + +const buildCache = () => { + const store = new Map(); + return { + store, + get: vi.fn(async (key: string) => store.get(key)), + set: vi.fn(async (key: string, value: unknown) => { + store.set(key, value); + }), + }; +}; + +const buildService = (baileysCache = buildCache()) => { + const config: Record = { + LOG: { BAILEYS: 'error' }, + DATABASE: { SAVE_DATA: { NEW_MESSAGE: true, HISTORIC: true, CHATS: false, CONTACTS: false } }, + CHATWOOT: { ENABLED: false }, + OPENAI: { ENABLED: false }, + S3: { ENABLE: false }, + }; + const configService = { get: vi.fn((key: string) => config[key]) } as any; + const prismaRepository = { + chat: { findFirst: vi.fn(async () => null), findMany: vi.fn(async () => []), createMany: vi.fn(async () => ({})) }, + message: { + create: vi.fn(async ({ data }) => ({ id: 'row-id', ...data })), + createMany: vi.fn(async () => ({})), + findMany: vi.fn(async () => []), + }, + contact: { findFirst: vi.fn(async () => null) }, + } as any; + const service = new BaileysStartupService( + configService, + { emit: vi.fn() } as any, + prismaRepository, + {} as any, + {} as any, + baileysCache as any, + {} as any, + ) as any; + service.instance.id = 'instance-id'; + service.instance.name = 'instance-name'; + service.sendDataWebhook = vi.fn(); + service.profilePicture = vi.fn(async () => ({ profilePictureUrl: null })); + service.updateMessagesReadedByTimestamp = vi.fn(async () => undefined); + service.updateChatUnreadMessages = vi.fn(async () => undefined); + service.client = { readMessages: vi.fn() }; + service.contactHandle = { 'contacts.upsert': vi.fn(async () => undefined) }; + return { service, prismaRepository, baileysCache }; +}; + +const message = (key: Record) => ({ + key: { id: 'MSG-1', fromMe: false, ...key }, + message: { conversation: 'hello' }, + messageTimestamp: 1700000000, + pushName: 'Someone', +}); + +const upsertWebhooks = (service: any) => + service.sendDataWebhook.mock.calls.filter(([event]) => event === Events.MESSAGES_UPSERT); + +const syncHistory = (service: any, messages: unknown[]) => + service.messageHandle['messaging-history.set']({ messages, chats: [], contacts: [], isLatest: true, progress: 100 }); + +const storedHistoryRows = (prismaRepository: any) => + prismaRepository.message.createMany.mock.calls.flatMap(([{ data }]) => data); + +const deliver = (service: any, received: unknown) => + service.messageHandle['messages.upsert']({ messages: [received], type: 'notify' }, {}); + +describe('messages.upsert dedupe', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('stores and emits a redelivered message once', async () => { + const { service, prismaRepository } = buildService(); + const received = { remoteJid: '5491123456789@s.whatsapp.net' }; + + await deliver(service, message(received)); + await deliver(service, message(received)); + + expect(prismaRepository.message.create).toHaveBeenCalledTimes(1); + expect(upsertWebhooks(service)).toHaveLength(1); + }); + + it('stores a message delivered with lid and phone jids once', async () => { + const { service, prismaRepository } = buildService(); + + await deliver(service, message({ remoteJid: '123456789@lid', remoteJidAlt: '5491123456789@s.whatsapp.net' })); + await deliver(service, message({ remoteJid: '5491123456789@s.whatsapp.net' })); + + expect(prismaRepository.message.create).toHaveBeenCalledTimes(1); + expect(upsertWebhooks(service)).toHaveLength(1); + }); + + it('processes each recipient copy of a broadcast once', async () => { + const { service, prismaRepository } = buildService(); + const recipients = ['5491111111111@s.whatsapp.net', '5492222222222@s.whatsapp.net', '5493333333333@s.whatsapp.net']; + + for (const remoteJid of recipients) { + await deliver(service, message({ remoteJid, fromMe: true })); + } + await deliver(service, message({ remoteJid: recipients[1], fromMe: true })); + + expect(prismaRepository.message.create).toHaveBeenCalledTimes(3); + expect(upsertWebhooks(service)).toHaveLength(3); + }); + + it('processes the phone delivery again when the first lid delivery had no phone', async () => { + const { service, prismaRepository } = buildService(); + + await deliver(service, message({ remoteJid: '123456789@lid' })); + await deliver(service, message({ remoteJid: '123456789@lid' })); + await deliver(service, message({ remoteJid: '5491123456789@s.whatsapp.net' })); + + expect(prismaRepository.message.create).toHaveBeenCalledTimes(2); + expect(upsertWebhooks(service)).toHaveLength(2); + }); + + it('processes a redelivery when storing the first delivery failed', async () => { + const { service, prismaRepository } = buildService(); + prismaRepository.message.create.mockRejectedValueOnce(new Error('database unavailable')); + const received = { remoteJid: '5491123456789@s.whatsapp.net' }; + + await deliver(service, message(received)); + await deliver(service, message(received)); + + expect(prismaRepository.message.create).toHaveBeenCalledTimes(2); + expect(upsertWebhooks(service)).toHaveLength(1); + }); + + it('keeps processing when the dedupe cache fails', async () => { + const cache = buildCache(); + const failOnDedupeKey = async (key: string) => { + if (key.startsWith('message_seen_')) throw new Error('cache down'); + return undefined; + }; + cache.get.mockImplementation(failOnDedupeKey); + cache.set.mockImplementation(failOnDedupeKey); + const { service, prismaRepository } = buildService(cache); + + await deliver(service, message({ remoteJid: '5491123456789@s.whatsapp.net' })); + + expect(prismaRepository.message.create).toHaveBeenCalledTimes(1); + expect(upsertWebhooks(service)).toHaveLength(1); + }); + + it('processes every message that has no id', async () => { + const { service, prismaRepository } = buildService(); + + await deliver(service, message({ id: undefined, remoteJid: '5491123456789@s.whatsapp.net' })); + await deliver(service, message({ id: undefined, remoteJid: '5491187654321@s.whatsapp.net' })); + + expect(prismaRepository.message.create).toHaveBeenCalledTimes(2); + }); + + it('stores a history message delivered with lid and phone jids once', async () => { + const { service, prismaRepository } = buildService(); + + await syncHistory(service, [ + message({ remoteJid: '123456789@lid', remoteJidAlt: '5491123456789@s.whatsapp.net' }), + message({ remoteJid: '5491123456789@s.whatsapp.net' }), + ]); + + expect(storedHistoryRows(prismaRepository)).toHaveLength(1); + }); + + it('keeps the phone copy of a history message whose lid copy had no phone', async () => { + const { service, prismaRepository } = buildService(); + + await syncHistory(service, [ + message({ remoteJid: '123456789@lid' }), + message({ remoteJid: '5491123456789@s.whatsapp.net' }), + ]); + + expect(storedHistoryRows(prismaRepository)).toHaveLength(2); + }); + + it('still delivers a live message first seen by history sync', async () => { + const { service, prismaRepository } = buildService(); + const received = message({ remoteJid: '5491123456789@s.whatsapp.net' }); + + await syncHistory(service, [received]); + await deliver(service, received); + + expect(prismaRepository.message.create).toHaveBeenCalledTimes(1); + expect(upsertWebhooks(service)).toHaveLength(1); + }); + + it('does not store again in history sync a message already delivered live', async () => { + const { service, prismaRepository } = buildService(); + const received = message({ remoteJid: '5491123456789@s.whatsapp.net' }); + + await deliver(service, received); + await syncHistory(service, [received]); + + expect(storedHistoryRows(prismaRepository)).toHaveLength(0); + }); + + it('processes a live delivery when storing the history batch failed', async () => { + const { service, prismaRepository } = buildService(); + prismaRepository.message.createMany.mockRejectedValueOnce(new Error('database unavailable')); + const received = message({ remoteJid: '5491123456789@s.whatsapp.net' }); + + await syncHistory(service, [received]); + await deliver(service, received); + + expect(prismaRepository.message.create).toHaveBeenCalledTimes(1); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/messagingHistorySetLid.test.ts b/test/api/integrations/channel/whatsapp/messagingHistorySetLid.test.ts new file mode 100644 index 0000000000..ef8ac991d5 --- /dev/null +++ b/test/api/integrations/channel/whatsapp/messagingHistorySetLid.test.ts @@ -0,0 +1,87 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { Events } from '@api/types/wa.types'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@api/server.module', () => ({ + chatbotController: { emit: vi.fn(async () => undefined) }, + waMonitor: {}, +})); + +vi.mock('@utils/onWhatsappCache', () => ({ + getOnWhatsappCache: vi.fn(async () => []), + saveOnWhatsappCache: vi.fn(async () => undefined), +})); + +vi.mock('@utils/sendTelemetry', () => ({ sendTelemetry: vi.fn() })); + +vi.mock('@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper', () => ({ + chatwootImport: { getRepositoryMessagesCache: vi.fn(() => null), setRepositoryMessagesCache: vi.fn() }, +})); + +const buildService = () => { + const store = new Map(); + const baileysCache = { + get: vi.fn(async (key: string) => store.get(key)), + set: vi.fn(async (key: string, value: unknown) => { + store.set(key, value); + }), + }; + const config: Record = { + LOG: { BAILEYS: 'error' }, + DATABASE: { SAVE_DATA: { NEW_MESSAGE: true, HISTORIC: true, MESSAGE_UPDATE: true, CHATS: false, CONTACTS: false } }, + CHATWOOT: { ENABLED: false }, + OPENAI: { ENABLED: false }, + S3: { ENABLE: false }, + }; + const configService = { get: vi.fn((key: string) => config[key]) } as any; + const prismaRepository = { + chat: { findMany: vi.fn(async () => []), createMany: vi.fn(async () => ({})) }, + message: { createMany: vi.fn(async () => ({})), findMany: vi.fn(async () => []) }, + } as any; + const service = new BaileysStartupService( + configService, + { emit: vi.fn() } as any, + prismaRepository, + {} as any, + {} as any, + baileysCache as any, + {} as any, + ) as any; + service.instance.id = 'instance-id'; + service.instance.name = 'instance-name'; + service.sendDataWebhook = vi.fn(); + service.contactHandle = { 'contacts.upsert': vi.fn(async () => undefined) }; + const getPNsForLIDs = vi.fn(async () => [{ lid: '111@lid', pn: '5491111111111@s.whatsapp.net' }]); + service.client = { signalRepository: { lidMapping: { getPNsForLIDs } } }; + return { service, prismaRepository, getPNsForLIDs }; +}; + +describe('messaging-history.set with @lid messages', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('persists and emits history messages with remoteJidAlt resolved and remoteJid preserved', async () => { + const { service, prismaRepository, getPNsForLIDs } = buildService(); + + await service.messageHandle['messaging-history.set']({ + chats: [{ id: '111@lid' }], + contacts: [], + messages: [ + { + key: { id: 'HIST-1', remoteJid: '111@lid', fromMe: false }, + message: { conversation: 'hi' }, + messageTimestamp: 1700000000, + pushName: 'Someone', + }, + ], + }); + + expect(getPNsForLIDs).toHaveBeenCalledWith(['111@lid']); + const [{ data }] = prismaRepository.message.createMany.mock.calls[0]; + expect(data[0].key).toMatchObject({ remoteJid: '111@lid', remoteJidAlt: '5491111111111@s.whatsapp.net' }); + const [, emitted] = service.sendDataWebhook.mock.calls.find(([event]) => event === Events.MESSAGES_SET); + expect(emitted[0].key.remoteJidAlt).toBe('5491111111111@s.whatsapp.net'); + expect(prismaRepository.chat.createMany.mock.calls[0][0].data[0].remoteJid).toBe('111@lid'); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/noPayloadConsoleLog.test.ts b/test/api/integrations/channel/whatsapp/noPayloadConsoleLog.test.ts new file mode 100644 index 0000000000..54085ed50e --- /dev/null +++ b/test/api/integrations/channel/whatsapp/noPayloadConsoleLog.test.ts @@ -0,0 +1,125 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { Events } from '@api/types/wa.types'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('baileys', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + downloadMediaMessage: vi.fn(async () => Buffer.from('fictional-image-bytes')), + }; +}); + +vi.mock('@api/server.module', () => ({ + chatbotController: { emit: vi.fn(async () => undefined) }, + waMonitor: {}, +})); + +vi.mock('@utils/onWhatsappCache', () => ({ + getOnWhatsappCache: vi.fn(async () => []), + saveOnWhatsappCache: vi.fn(async () => undefined), +})); + +vi.mock('@utils/sendTelemetry', () => ({ sendTelemetry: vi.fn() })); + +vi.mock('@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper', () => ({ + chatwootImport: { getRepositoryMessagesCache: vi.fn(() => null), setRepositoryMessagesCache: vi.fn() }, +})); + +const buildCache = () => { + const store = new Map(); + return { + store, + get: vi.fn(async (key: string) => store.get(key)), + set: vi.fn(async (key: string, value: unknown) => { + store.set(key, value); + }), + }; +}; +const buildService = () => { + const config: Record = { + LOG: { BAILEYS: 'error' }, + DATABASE: { SAVE_DATA: { NEW_MESSAGE: true, HISTORIC: true, MESSAGE_UPDATE: true, CHATS: false, CONTACTS: false } }, + CHATWOOT: { ENABLED: false }, + OPENAI: { ENABLED: false }, + S3: { ENABLE: false }, + }; + const configService = { get: vi.fn((key: string) => config[key]) } as any; + const prismaRepository = { + chat: { findFirst: vi.fn(async () => null), findMany: vi.fn(async () => []), createMany: vi.fn(async () => ({})) }, + message: { + create: vi.fn(async ({ data }) => ({ id: 'row-id', ...data })), + createMany: vi.fn(async () => ({})), + findMany: vi.fn(async () => []), + update: vi.fn(async () => ({})), + updateMany: vi.fn(async () => ({ count: 1 })), + }, + messageUpdate: { create: vi.fn(async () => ({})) }, + contact: { findFirst: vi.fn(async () => null) }, + $queryRaw: vi.fn(async () => []), + } as any; + const service = new BaileysStartupService( + configService, + { emit: vi.fn() } as any, + prismaRepository, + {} as any, + {} as any, + buildCache() as any, + {} as any, + ) as any; + service.instance.id = 'instance-id'; + service.instance.name = 'instance-name'; + service.localWebhook = { enabled: true, webhookBase64: true }; + service.sendDataWebhook = vi.fn(); + service.profilePicture = vi.fn(async () => ({ profilePictureUrl: null })); + service.updateMessagesReadedByTimestamp = vi.fn(async () => undefined); + service.updateChatUnreadMessages = vi.fn(async () => undefined); + service.client = { readMessages: vi.fn(), updateMediaMessage: vi.fn() }; + service.contactHandle = { 'contacts.upsert': vi.fn(async () => undefined) }; + return service; +}; + +const MEDIA_BASE64 = Buffer.from('fictional-image-bytes').toString('base64'); + +const imageMessage = { + key: { id: 'MSG-IMG-1', fromMe: false, remoteJid: '5492615550101@s.whatsapp.net' }, + message: { imageMessage: { mimetype: 'image/jpeg', caption: 'photo', url: 'https://example.invalid/media' } }, + messageTimestamp: 1700000000, + pushName: 'Someone', +}; + +const loggerCallArgs = (service: any) => + Object.values(service.logger) + .filter((member: any) => typeof member === 'function' && 'mock' in member) + .flatMap((spy: any) => spy.mock.calls.flat()); + +const serialize = (value: unknown) => (typeof value === 'string' ? value : (JSON.stringify(value) ?? '')); + +describe('baileys message handlers do not dump payloads to console', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('never calls console.log nor logs media base64 on messages.upsert and messages.update', async () => { + const service = buildService(); + const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined); + for (const level of ['log', 'info', 'warn', 'error', 'verbose', 'debug']) { + vi.spyOn(service.logger, level).mockImplementation(() => undefined); + } + + await service.messageHandle['messages.upsert']({ messages: [imageMessage], type: 'notify' }, {}); + await service.messageHandle['messages.update']( + [{ key: { id: 'MSG-IMG-1', fromMe: false, remoteJid: '5492615550101@s.whatsapp.net' }, update: { status: 3 } }], + {}, + ); + + const upsertPayload = service.sendDataWebhook.mock.calls.find(([event]) => event === Events.MESSAGES_UPSERT)?.[1]; + expect(upsertPayload?.message?.base64).toBe(MEDIA_BASE64); + expect(consoleLog).not.toHaveBeenCalled(); + expect(loggerCallArgs(service).map(serialize).join('\n')).not.toContain(MEDIA_BASE64); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/prepareMediaMessage.test.ts b/test/api/integrations/channel/whatsapp/prepareMediaMessage.test.ts new file mode 100644 index 0000000000..f14a42273a --- /dev/null +++ b/test/api/integrations/channel/whatsapp/prepareMediaMessage.test.ts @@ -0,0 +1,63 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import axios from 'axios'; +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('baileys', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + prepareWAMessageMedia: vi.fn(async (content: Record) => ({ + [`${Object.keys(content)[0]}Message`]: {}, + })), + }; +}); + +vi.mock('axios', () => ({ default: { get: vi.fn() } })); + +const onePixelPng = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; + +const buildService = () => { + const service = Object.create(BaileysStartupService.prototype) as any; + service.client = { waUploadToServer: vi.fn() }; + service.instance = { wuid: '5491100000000@s.whatsapp.net' }; + service.logger = { verbose: vi.fn(), error: vi.fn() }; + return service; +}; + +const preparedMimetype = async (mediaMessage: Record) => { + const prepared = await buildService().prepareMediaMessage(mediaMessage); + return Object.values(prepared.message)[0] as { mimetype: unknown; fileName: unknown }; +}; + +describe('BaileysStartupService.prepareMediaMessage mimetype', () => { + it('keeps the downloaded content type when the file name has no extension', async () => { + vi.mocked(axios.get).mockResolvedValue({ headers: { 'content-type': 'application/pdf' }, data: Buffer.from('x') }); + + const media = await preparedMimetype({ + mediatype: 'document', + media: 'https://files.example.com/report', + fileName: 'report', + }); + + expect(media.mimetype).toBe('application/pdf'); + expect(media.fileName).toBe('report'); + }); + + it('keeps image/jpeg for images whose file name has no extension', async () => { + const media = await preparedMimetype({ mediatype: 'image', media: onePixelPng, fileName: 'logo' }); + + expect(media.mimetype).toBe('image/jpeg'); + }); + + it('still derives the mimetype from a file name extension', async () => { + const media = await preparedMimetype({ mediatype: 'document', media: 'aGVsbG8=', fileName: 'notes.txt' }); + + expect(media.mimetype).toBe('text/plain'); + }); + + it('still maps application/mp4 file names to video/mp4', async () => { + const media = await preparedMimetype({ mediatype: 'document', media: 'aGVsbG8=', fileName: 'clip.mp4' }); + + expect(media.mimetype).toBe('video/mp4'); + }); +}); diff --git a/test/api/integrations/channel/whatsapp/scheduleLidRemoteJidAltBackfill.test.ts b/test/api/integrations/channel/whatsapp/scheduleLidRemoteJidAltBackfill.test.ts new file mode 100644 index 0000000000..5afc705bab --- /dev/null +++ b/test/api/integrations/channel/whatsapp/scheduleLidRemoteJidAltBackfill.test.ts @@ -0,0 +1,140 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@api/server.module', () => ({ + chatbotController: { emit: vi.fn(async () => undefined) }, + waMonitor: {}, +})); + +vi.mock('@utils/onWhatsappCache', () => ({ + getOnWhatsappCache: vi.fn(async () => []), + saveOnWhatsappCache: vi.fn(async () => undefined), +})); + +vi.mock('@utils/sendTelemetry', () => ({ sendTelemetry: vi.fn() })); + +vi.mock('@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper', () => ({ + chatwootImport: { getRepositoryMessagesCache: vi.fn(() => null), setRepositoryMessagesCache: vi.fn() }, +})); + +const LID = '123456789@lid'; +const PHONE_JID = '5491123456789@s.whatsapp.net'; + +const buildService = (provider: string, saveData: { NEW_MESSAGE: boolean; HISTORIC: boolean }) => { + const config: Record = { + LOG: { BAILEYS: 'error' }, + DATABASE: { PROVIDER: provider, SAVE_DATA: { ...saveData, CHATS: false, CONTACTS: false } }, + CHATWOOT: { ENABLED: false }, + OPENAI: { ENABLED: false }, + S3: { ENABLE: false }, + }; + const configService = { get: vi.fn((key: string) => config[key]) } as any; + const prismaRepository = { $executeRaw: vi.fn(async () => 0) } as any; + const service = new BaileysStartupService( + configService, + { emit: vi.fn() } as any, + prismaRepository, + {} as any, + {} as any, + {} as any, + {} as any, + ) as any; + service.instance.id = 'instance-id'; + service.instance.name = 'instance-name'; + const schedule = vi.spyOn(service.lidRemoteJidAltBackfill, 'schedule').mockReturnValue(undefined); + return { service, schedule, prismaRepository }; +}; + +const deliverUpsert = async (service: any, key: Record) => { + await service.messageHandle['messages.upsert']( + { messages: [{ key: { id: 'MSG-1', fromMe: false, ...key }, messageTimestamp: 1700000000 }], type: 'notify' }, + {}, + ); +}; + +const runRealSchedule = (service: any, schedule: any) => { + const backfills: Array | undefined> = []; + schedule.mockRestore(); + const spy = vi.spyOn(service.lidRemoteJidAltBackfill, 'schedule'); + const original = service.lidRemoteJidAltBackfill.constructor.prototype.schedule; + spy.mockImplementation((...args: unknown[]) => { + const backfill = original.apply(service.lidRemoteJidAltBackfill, args); + backfills.push(backfill); + return backfill; + }); + return { spy, settled: () => Promise.all(backfills) }; +}; + +const dispatch = async (service: any, events: Record) => { + let processEvents: (events: Record) => Promise; + service.client = { ev: { process: vi.fn((handler) => (processEvents = handler)) } }; + service.findSettings = vi.fn(async () => ({})); + service.eventHandler(); + await processEvents(events); + await service.eventProcessingQueue; +}; + +describe('scheduleLidRemoteJidAltBackfill', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('does not schedule a backfill on mysql', () => { + const { service, schedule } = buildService('mysql', { NEW_MESSAGE: true, HISTORIC: true }); + + service.scheduleLidRemoteJidAltBackfill(LID, PHONE_JID); + + expect(schedule).not.toHaveBeenCalled(); + }); + + it('does not schedule a backfill on postgresql when messages are not saved', () => { + const { service, schedule } = buildService('postgresql', { NEW_MESSAGE: false, HISTORIC: false }); + + service.scheduleLidRemoteJidAltBackfill(LID, PHONE_JID); + + expect(schedule).not.toHaveBeenCalled(); + }); + + it.each([ + { NEW_MESSAGE: true, HISTORIC: false }, + { NEW_MESSAGE: false, HISTORIC: true }, + ])('schedules a backfill on postgresql with save flags %o', (saveData) => { + const { service, schedule } = buildService('postgresql', saveData); + + service.scheduleLidRemoteJidAltBackfill(LID, PHONE_JID); + + expect(schedule).toHaveBeenCalledWith('instance-id', LID, PHONE_JID); + }); + + it('schedules a backfill with the lid and pn of a lid-mapping.update event', async () => { + const { service, schedule } = buildService('postgresql', { NEW_MESSAGE: true, HISTORIC: false }); + + await dispatch(service, { 'lid-mapping.update': { lid: LID, pn: PHONE_JID } }); + + expect(schedule).toHaveBeenCalledWith('instance-id', LID, PHONE_JID); + }); + + it('schedules a backfill with the lid and phone of a 1:1 messages.upsert', async () => { + const { service, schedule, prismaRepository } = buildService('postgresql', { NEW_MESSAGE: true, HISTORIC: false }); + const { spy, settled } = runRealSchedule(service, schedule); + + await deliverUpsert(service, { remoteJid: '111@lid', remoteJidAlt: '5491111@s.whatsapp.net' }); + await settled(); + + expect(spy).toHaveBeenCalledWith('instance-id', '111@lid', '5491111@s.whatsapp.net'); + expect(prismaRepository.$executeRaw).toHaveBeenCalledTimes(1); + }); + + it.each([ + { name: 'a phone remoteJid', key: { remoteJid: PHONE_JID, remoteJidAlt: LID } }, + { name: 'a group remoteJid', key: { remoteJid: '120363000000000000@g.us', participant: LID } }, + ])('does not backfill a messages.upsert with $name', async ({ key }) => { + const { service, schedule, prismaRepository } = buildService('postgresql', { NEW_MESSAGE: true, HISTORIC: false }); + const { settled } = runRealSchedule(service, schedule); + + await deliverUpsert(service, key); + await settled(); + + expect(prismaRepository.$executeRaw).not.toHaveBeenCalled(); + }); +}); diff --git a/test/api/services/monitorNoConnection.test.ts b/test/api/services/monitorNoConnection.test.ts new file mode 100644 index 0000000000..b1d551bbb4 --- /dev/null +++ b/test/api/services/monitorNoConnection.test.ts @@ -0,0 +1,123 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { WAMonitoringService } from '@api/services/monitor.service'; +import { Boom } from '@hapi/boom'; +import { DisconnectReason } from 'baileys'; +import EventEmitter2 from 'eventemitter2'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@api/server.module', () => ({ channelController: {} })); + +const QRCODE_LIMIT = 3; + +const config: Record = { + DATABASE: { SAVE_DATA: { INSTANCE: true } }, + CACHE: { REDIS: { ENABLED: false }, LOCAL: { ENABLED: false } }, + PROVIDER: { ENABLED: false }, + CHATWOOT: { ENABLED: false }, + QRCODE: { LIMIT: QRCODE_LIMIT, COLOR: '#000000' }, +}; + +const buildMonitor = () => { + const eventEmitter = new EventEmitter2(); + const prismaRepository = { instance: { update: vi.fn(async () => ({})) } }; + const configService = { get: vi.fn((key: string) => config[key]) }; + const monitor = new WAMonitoringService( + eventEmitter, + configService as any, + prismaRepository as any, + {} as any, + {} as any, + {} as any, + {} as any, + ); + return { monitor, eventEmitter, prismaRepository, configService }; +}; + +const buildService = (eventEmitter: EventEmitter2, prismaRepository: any, configService: any) => { + const service = Object.create(BaileysStartupService.prototype) as any; + service.client = { logout: vi.fn(async () => undefined), ws: { close: vi.fn() }, end: vi.fn() }; + service.configService = configService; + service.prismaRepository = prismaRepository; + service.instance = { + id: 'instance-id', + name: 'instance-name', + wuid: undefined, + qrcode: { count: QRCODE_LIMIT, code: 'stale-qr', base64: 'stale-base64' }, + }; + service.eventEmitter = eventEmitter; + service.sendDataWebhook = vi.fn(); + service.connectToWhatsapp = vi.fn(); + service.getProfileName = vi.fn(async () => undefined); + service.localChatwoot = { enabled: false }; + service.stateConnection = { state: 'connecting' }; + service.endSession = false; + return service; +}; + +const flushHandlers = () => new Promise((resolve) => setTimeout(resolve, 0)); + +describe('WAMonitoringService no.connection', () => { + let monitor: WAMonitoringService; + let eventEmitter: EventEmitter2; + let prismaRepository: any; + let service: any; + + beforeEach(() => { + const built = buildMonitor(); + monitor = built.monitor; + eventEmitter = built.eventEmitter; + prismaRepository = built.prismaRepository; + service = buildService(eventEmitter, prismaRepository, built.configService); + monitor.waInstances['instance-name'] = service; + }); + + const expectClosed = () => { + expect(service.client.ws.close).toHaveBeenCalled(); + expect(service.client.end).toHaveBeenCalled(); + expect(service.instance.qrcode).toEqual({ count: 0 }); + expect(service.stateConnection).toEqual({ state: 'close', statusReason: DisconnectReason.connectionClosed }); + expect(prismaRepository.instance.update).toHaveBeenCalledWith({ + where: { id: 'instance-id' }, + data: { connectionStatus: 'close' }, + }); + }; + + it('closes the instance when the unpaired socket rejects logout', async () => { + service.client.logout.mockRejectedValue(new Error('Connection Closed')); + + await eventEmitter.emitAsync('no.connection', 'instance-name'); + + expectClosed(); + }); + + it('closes the instance when logout succeeds', async () => { + await eventEmitter.emitAsync('no.connection', 'instance-name'); + + expect(service.client.logout).toHaveBeenCalled(); + expectClosed(); + }); + + it('ignores an unknown instance name', async () => { + await expect(eventEmitter.emitAsync('no.connection', 'missing-instance')).resolves.toBeDefined(); + + expect(prismaRepository.instance.update).not.toHaveBeenCalled(); + }); + + it('does not reconnect after the QR limit closes the socket', async () => { + service.client.logout.mockRejectedValue(new Error('Connection Closed')); + + await service.connectionUpdate({ qr: 'next-qr' }); + await flushHandlers(); + + await service.connectionUpdate({ + connection: 'close', + lastDisconnect: { + error: new Boom('closed', { statusCode: DisconnectReason.connectionClosed }), + date: new Date(), + }, + }); + + expectClosed(); + expect(service.connectToWhatsapp).not.toHaveBeenCalled(); + }); +}); diff --git a/test/config/telemetryConfig.test.ts b/test/config/telemetryConfig.test.ts new file mode 100644 index 0000000000..b6deb3751f --- /dev/null +++ b/test/config/telemetryConfig.test.ts @@ -0,0 +1,23 @@ +import { ConfigService } from '@config/env.config'; +import { describe, expect, it, vi } from 'vitest'; + +const telemetryEnabled = () => new ConfigService().get('TELEMETRY').ENABLED; + +describe('TELEMETRY config', () => { + it('stays disabled when TELEMETRY_ENABLED is unset', () => { + vi.stubEnv('TELEMETRY_ENABLED', undefined); + expect(telemetryEnabled()).toBe(false); + }); + + it('turns on only when TELEMETRY_ENABLED is true', () => { + vi.stubEnv('TELEMETRY_ENABLED', 'true'); + expect(telemetryEnabled()).toBe(true); + }); + + it('stays disabled for any other value', () => { + for (const value of ['false', '', '1', 'TRUE']) { + vi.stubEnv('TELEMETRY_ENABLED', value); + expect(telemetryEnabled()).toBe(false); + } + }); +}); diff --git a/test/helpers/cache.ts b/test/helpers/cache.ts new file mode 100644 index 0000000000..145e8b08be --- /dev/null +++ b/test/helpers/cache.ts @@ -0,0 +1,90 @@ +import { ICache } from '@api/abstract/abstract.cache'; +import { CacheService } from '@api/services/cache.service'; +import { BufferJSON } from 'baileys'; +import { vi } from 'vitest'; + +export class InMemoryRedisCache implements ICache { + readonly hashes = new Map>(); + readonly values = new Map(); + down = false; + readonly error = new Error('redis down'); + + private hash(key: string) { + if (!this.hashes.has(key)) this.hashes.set(key, new Map()); + return this.hashes.get(key); + } + + private ensureUp() { + if (this.down) throw this.error; + } + + async get(key: string) { + this.ensureUp(); + return JSON.parse(this.values.get(key) ?? 'null'); + } + + async hGet(key: string, field: string) { + this.ensureUp(); + const data = this.hashes.get(key)?.get(field); + if (data) { + return JSON.parse(data, BufferJSON.reviver); + } + return null; + } + + async set(key: string, value: any) { + this.ensureUp(); + this.values.set(key, JSON.stringify(value)); + } + + async hSet(key: string, field: string, value: any) { + this.ensureUp(); + this.hash(key).set(field, JSON.stringify(value, BufferJSON.replacer)); + } + + async has(key: string) { + this.ensureUp(); + return this.values.has(key) || this.hashes.has(key); + } + + async delete(key: string) { + this.ensureUp(); + const existed = this.values.delete(key) || this.hashes.delete(key); + return existed ? 1 : 0; + } + + async hDelete(key: string, field: string) { + this.ensureUp(); + return this.hashes.get(key)?.delete(field) ? 1 : 0; + } + + async keys() { + this.ensureUp(); + return [...new Set([...this.values.keys(), ...this.hashes.keys()])]; + } + + async deleteAll() { + this.ensureUp(); + const count = this.values.size + this.hashes.size; + this.values.clear(); + this.hashes.clear(); + return count; + } + + readRaw(key: string, field: string) { + const data = this.hashes.get(key)?.get(field); + return data === undefined ? undefined : JSON.parse(data, BufferJSON.reviver); + } + + writeRaw(key: string, field: string, raw: string) { + this.hash(key).set(field, JSON.stringify(raw, BufferJSON.replacer)); + } +} + +export const createCacheService = () => { + const inner = new InMemoryRedisCache(); + const service = new CacheService(inner); + vi.spyOn(service, 'hDelete'); + vi.spyOn(service, 'delete'); + return { inner, service }; +}; diff --git a/test/helpers/fs-promises.ts b/test/helpers/fs-promises.ts new file mode 100644 index 0000000000..ce0a5273e9 --- /dev/null +++ b/test/helpers/fs-promises.ts @@ -0,0 +1,25 @@ +import { vi } from 'vitest'; + +export const files = new Map(); + +const enoent = () => Object.assign(new Error('ENOENT'), { code: 'ENOENT' }); + +const fs = { + mkdir: vi.fn(async () => undefined), + stat: vi.fn(async (file: string) => { + if (!files.has(file)) throw enoent(); + return { isFile: () => true }; + }), + readFile: vi.fn(async (file: string) => { + if (!files.has(file)) throw enoent(); + return files.get(file); + }), + writeFile: vi.fn(async (file: string, data: string) => { + files.set(file, data); + }), + unlink: vi.fn(async (file: string) => { + if (!files.delete(file)) throw enoent(); + }), +}; + +export const fsPromisesModule = { default: fs, ...fs }; diff --git a/test/helpers/logger.ts b/test/helpers/logger.ts new file mode 100644 index 0000000000..a28f8be9f0 --- /dev/null +++ b/test/helpers/logger.ts @@ -0,0 +1,27 @@ +import { vi } from 'vitest'; + +export const loggers: any[] = []; + +export const lastLogger = () => loggers[loggers.length - 1]; + +export const loggersNamed = (name: string) => loggers.filter((logger) => logger.context === name); + +export const onlyLoggerNamed = (name: string) => { + const found = loggersNamed(name); + if (found.length !== 1) { + throw new Error(`Expected exactly one logger named ${name}, found ${found.length}`); + } + return found[0]; +}; + +export class Logger { + verbose = vi.fn(); + info = vi.fn(); + warn = vi.fn(); + error = vi.fn(); + constructor(public context?: string) { + loggers.push(this); + } +} + +export const loggerModule = { Logger }; diff --git a/test/patches/baileysGroupStubParameters.test.ts b/test/patches/baileysGroupStubParameters.test.ts new file mode 100644 index 0000000000..52ccc24929 --- /dev/null +++ b/test/patches/baileysGroupStubParameters.test.ts @@ -0,0 +1,58 @@ +import { proto, WAMessageStubType } from 'baileys'; +import processMessage from 'baileys/lib/Utils/process-message.js'; +import { describe, expect, it, vi } from 'vitest'; + +const groupStub = (messageStubType: number, messageStubParameters: string[]) => + ({ + key: { remoteJid: '120363000000000000@g.us', fromMe: false, id: 'STUB-1', participant: '111111111111111@lid' }, + messageStubType, + messageStubParameters, + messageTimestamp: 1700000000, + }) as proto.IWebMessageInfo; + +const run = async (message: proto.IWebMessageInfo) => { + const ev = { emit: vi.fn() }; + await processMessage( + message as any, + { + shouldProcessHistoryMsg: false, + ev, + creds: { me: { id: '5491100000000@s.whatsapp.net', lid: '999999999999999@lid' } }, + signalRepository: {}, + keyStore: {}, + logger: undefined, + options: {}, + getMessage: vi.fn(), + } as any, + ); + return ev.emit.mock.calls.filter(([event]) => event === 'group-participants.update').map(([, data]) => data); +}; + +describe('processMessage group stub parameters', () => { + it('emits a number change whose parameters are plain jids', async () => { + const updates = await run(groupStub(WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER, ['222222222222222@lid'])); + + expect(updates).toHaveLength(1); + expect(updates[0]).toMatchObject({ id: '120363000000000000@g.us', action: 'modify' }); + expect(updates[0].participants).toEqual([{ id: '222222222222222@lid' }]); + }); + + it('keeps parsing participants serialized as JSON', async () => { + const participant = { id: '333333333333333@lid', phoneNumber: '5491133333333@s.whatsapp.net' }; + const updates = await run(groupStub(WAMessageStubType.GROUP_PARTICIPANT_ADD, [JSON.stringify(participant)])); + + expect(updates).toHaveLength(1); + expect(updates[0]).toMatchObject({ action: 'add', participants: [participant] }); + }); + + it('reads plain phone jids as the participant phone number', async () => { + const updates = await run( + groupStub(WAMessageStubType.GROUP_PARTICIPANT_REMOVE, ['5491100000000@s.whatsapp.net', '12345']), + ); + + expect(updates[0].participants).toEqual([ + { id: '5491100000000@s.whatsapp.net', phoneNumber: '5491100000000@s.whatsapp.net' }, + { id: '12345' }, + ]); + }); +}); diff --git a/test/patches/baileysLinkPreview.test.ts b/test/patches/baileysLinkPreview.test.ts new file mode 100644 index 0000000000..ba4a317f0f --- /dev/null +++ b/test/patches/baileysLinkPreview.test.ts @@ -0,0 +1,30 @@ +import { getUrlInfo } from 'baileys'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +const pageUrl = 'https://plain-page.example/article'; +const pageWithoutImage = + 'Plain pagehi'; + +describe('getUrlInfo for a page without an image', () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('returns the title and description without uploading a thumbnail', async () => { + vi.stubGlobal( + 'fetch', + vi.fn(async (url: string) => { + const response = new Response(pageWithoutImage, { headers: { 'content-type': 'text/html' } }); + Object.defineProperty(response, 'url', { value: url }); + return response; + }), + ); + const uploadImage = vi.fn(); + + const info = await getUrlInfo(pageUrl, { thumbnailWidth: 192, fetchOpts: { timeout: 3000 }, uploadImage } as any); + + expect(info).toMatchObject({ title: 'Plain page', description: 'No image here', 'matched-text': pageUrl }); + expect(info.jpegThumbnail).toBeUndefined(); + expect(uploadImage).not.toHaveBeenCalled(); + }); +}); diff --git a/test/patches/baileysTransactionIsolation.test.ts b/test/patches/baileysTransactionIsolation.test.ts new file mode 100644 index 0000000000..bb34bb3585 --- /dev/null +++ b/test/patches/baileysTransactionIsolation.test.ts @@ -0,0 +1,168 @@ +import { AsyncLocalStorage } from 'async_hooks'; +import { addTransactionCapability, SignalKeyStore } from 'baileys'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +const silentLogger = (): any => ({ + level: 'silent', + child: () => silentLogger(), + trace: () => {}, + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + fatal: () => {}, +}); + +const makeInMemoryStore = () => { + const data: Record> = {}; + const store: SignalKeyStore = { + async get(type: string, ids: string[]) { + const bucket = data[type] ?? {}; + const out: Record = {}; + for (const id of ids) { + if (bucket[id] !== undefined) out[id] = bucket[id]; + } + return out as any; + }, + async set(d: any) { + for (const type of Object.keys(d)) { + data[type] = data[type] ?? {}; + for (const id of Object.keys(d[type])) { + const v = d[type][id]; + if (v === null || v === undefined) delete data[type][id]; + else data[type][id] = v; + } + } + }, + } as any; + return store; +}; + +const opts = { maxCommitRetries: 1, delayBetweenTriesMs: 1 }; +const read = async (store: SignalKeyStore, id = '1') => (await store.get('app-state-sync-key', [id]))[id]; +const write = (keyId: number, id = '1') => ({ 'app-state-sync-key': { [id]: { keyId } } }) as any; + +describe('addTransactionCapability isolation', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('keeps concurrent transactions of different stores isolated', async () => { + const backingA = makeInMemoryStore(); + const backingB = makeInMemoryStore(); + const a = addTransactionCapability(backingA, silentLogger(), opts); + const b = addTransactionCapability(backingB, silentLogger(), opts); + const tick = () => new Promise((resolve) => setTimeout(resolve, 0)); + + await Promise.all([ + a.transaction(async () => { + await a.set(write(1)); + await tick(); + expect(await read(a)).toMatchObject({ keyId: 1 }); + await tick(); + }, 'key'), + b.transaction(async () => { + await tick(); + await b.set(write(2)); + expect(await read(b)).toMatchObject({ keyId: 2 }); + await tick(); + }, 'key'), + ]); + + expect(await read(backingA)).toMatchObject({ keyId: 1 }); + expect(await read(backingB)).toMatchObject({ keyId: 2 }); + }); + + it('writes to store B inside a transaction of store A go to B only', async () => { + const backingA = makeInMemoryStore(); + const backingB = makeInMemoryStore(); + const a = addTransactionCapability(backingA, silentLogger(), opts); + const b = addTransactionCapability(backingB, silentLogger(), opts); + + await a.transaction(async () => { + expect(b.isInTransaction()).toBe(false); + await b.set(write(9)); + await a.set(write(1)); + + expect(await read(backingB)).toMatchObject({ keyId: 9 }); + expect(await read(backingA)).toBeUndefined(); + }, 'key'); + + expect(await read(backingA)).toMatchObject({ keyId: 1 }); + expect(await read(backingB)).toMatchObject({ keyId: 9 }); + }); + + it('keeps a nested transaction on another store isolated', async () => { + const backingA = makeInMemoryStore(); + const backingB = makeInMemoryStore(); + const a = addTransactionCapability(backingA, silentLogger(), opts); + const b = addTransactionCapability(backingB, silentLogger(), opts); + + await a.transaction(async () => { + await a.set(write(1)); + await b.transaction(async () => { + expect(a.isInTransaction()).toBe(true); + expect(b.isInTransaction()).toBe(true); + await b.set(write(2)); + }, 'key'); + + expect(b.isInTransaction()).toBe(false); + expect(a.isInTransaction()).toBe(true); + expect(await read(backingB)).toMatchObject({ keyId: 2 }); + expect(await read(backingA)).toBeUndefined(); + }, 'key'); + + expect(await read(backingA)).toMatchObject({ keyId: 1 }); + }); + + it('reuses the context for a nested transaction on the same store', async () => { + const backing = makeInMemoryStore(); + const a = addTransactionCapability(backing, silentLogger(), opts); + + await a.transaction(async () => { + await a.set(write(1)); + await a.transaction(async () => { + await a.set(write(2, '2')); + expect(await read(a)).toMatchObject({ keyId: 1 }); + }, 'other'); + + expect(await read(backing, '2')).toBeUndefined(); + expect(await read(a, '2')).toMatchObject({ keyId: 2 }); + }, 'key'); + + expect(await read(backing)).toMatchObject({ keyId: 1 }); + expect(await read(backing, '2')).toMatchObject({ keyId: 2 }); + }); + + it('get inside a transaction sees pending writes of its own store only', async () => { + const backingA = makeInMemoryStore(); + const backingB = makeInMemoryStore(); + const a = addTransactionCapability(backingA, silentLogger(), opts); + const b = addTransactionCapability(backingB, silentLogger(), opts); + + await a.transaction(async () => { + await a.set(write(1)); + expect(await read(a)).toMatchObject({ keyId: 1 }); + expect(await read(b)).toBeUndefined(); + }, 'key'); + }); + + it('runs every transaction on one shared AsyncLocalStorage', async () => { + const instances = new Set>(); + const originalRun = AsyncLocalStorage.prototype.run; + vi.spyOn(AsyncLocalStorage.prototype, 'run').mockImplementation(function ( + this: AsyncLocalStorage, + ...args: Parameters + ) { + instances.add(this); + return originalRun.apply(this, args); + }); + + for (let i = 0; i < 20; i++) { + const keys = addTransactionCapability(makeInMemoryStore(), silentLogger(), opts); + await keys.transaction(async () => {}, `key-${i}`); + } + + expect(instances.size).toBe(1); + }); +}); diff --git a/test/patches/chatwootSdkAxios.test.ts b/test/patches/chatwootSdkAxios.test.ts new file mode 100644 index 0000000000..3c51c14e8c --- /dev/null +++ b/test/patches/chatwootSdkAxios.test.ts @@ -0,0 +1,54 @@ +import { createServer, IncomingMessage, Server } from 'node:http'; +import { AddressInfo } from 'node:net'; + +import { request as chatwootRequest } from '@figuro/chatwoot-sdk/dist/core/request'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const readBody = (request: IncomingMessage) => + new Promise((resolve) => { + const chunks: Buffer[] = []; + request.on('data', (chunk) => chunks.push(chunk)); + request.on('end', () => resolve(Buffer.concat(chunks).toString())); + }); + +describe('chatwoot sdk on the shared axios', () => { + let server: Server; + let base: string; + const seen: { method?: string; url?: string; token?: string; body?: string } = {}; + + beforeAll(async () => { + server = createServer(async (request, response) => { + seen.method = request.method; + seen.url = request.url; + seen.token = request.headers.api_access_token as string; + seen.body = await readBody(request); + const status = request.url?.includes('missing') ? 404 : 200; + response.writeHead(status, { 'content-type': 'application/json' }); + response.end(JSON.stringify({ payload: [{ id: 7 }] })); + }); + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); + base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; + }); + + afterAll(async () => { + await new Promise((resolve) => server.close(resolve)); + }); + + const config = () => ({ basePath: base, with_credentials: true, credentials: 'include', token: 'token-1' }) as any; + + it('sends a json request and resolves the response body', async () => { + const result = await chatwootRequest(config(), { + method: 'POST', + url: '/api/v1/accounts/1/contacts/filter', + body: { payload: [{ attribute_key: 'phone_number' }] }, + }); + + expect(result).toEqual({ payload: [{ id: 7 }] }); + expect(seen).toMatchObject({ method: 'POST', url: '/api/v1/accounts/1/contacts/filter', token: 'token-1' }); + expect(JSON.parse(seen.body)).toEqual({ payload: [{ attribute_key: 'phone_number' }] }); + }); + + it('rejects with the http status on errors', async () => { + await expect(chatwootRequest(config(), { method: 'GET', url: '/missing' })).rejects.toMatchObject({ status: 404 }); + }); +}); diff --git a/test/patches/jimpImageProcessing.test.ts b/test/patches/jimpImageProcessing.test.ts new file mode 100644 index 0000000000..16b6275a76 --- /dev/null +++ b/test/patches/jimpImageProcessing.test.ts @@ -0,0 +1,34 @@ +import { Jimp, JimpMime } from 'jimp'; +import sharp from 'sharp'; +import { describe, expect, it } from 'vitest'; + +const png = (width: number, height: number) => + sharp({ create: { width, height, channels: 3, background: { r: 10, g: 120, b: 200 } } }) + .png() + .toBuffer(); + +describe('jimp image processing', () => { + it('covers an image to the Chatwoot ads thumbnail size', async () => { + const image = await Jimp.read(await png(640, 480)); + + image.cover({ w: 320, h: 180 }); + const output = await sharp(await image.getBuffer(JimpMime.png)).metadata(); + + expect(output).toMatchObject({ format: 'png', width: 320, height: 180 }); + }); + + it('resizes and encodes jpeg like the Baileys fallback thumbnail', async () => { + const image = await Jimp.read(await png(200, 100)); + + image.resize({ w: 32 }); + const output = await sharp(await image.getBuffer(JimpMime.jpeg, { quality: 50 })).metadata(); + + expect(output).toMatchObject({ format: 'jpeg', width: 32, height: 16 }); + }); + + it('rejects invalid plugin options through its schema validation', async () => { + const image = await Jimp.read(await png(10, 10)); + + expect(() => image.resize({ w: 'wide' } as any)).toThrow(); + }); +}); diff --git a/test/patches/minioDependencies.test.ts b/test/patches/minioDependencies.test.ts new file mode 100644 index 0000000000..d15f28e44c --- /dev/null +++ b/test/patches/minioDependencies.test.ts @@ -0,0 +1,62 @@ +import { readdirSync, readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { basename, dirname } from 'node:path'; +import { Readable } from 'node:stream'; + +import * as Minio from 'minio'; +import { describe, expect, it, vi } from 'vitest'; + +const requireFromMinio = createRequire(createRequire(__filename).resolve('minio')); +const { NotificationPoller } = requireFromMinio('./notification.js'); + +describe('minio on its patched dependencies', () => { + it('requires the stream-json parser with the exact file name case', () => { + const source = readFileSync(requireFromMinio.resolve('./notification.js'), 'utf8'); + const [, specifier] = source.match(/require\("(stream-json[^"]*)"\)/); + const resolved = requireFromMinio.resolve(specifier); + + expect(readdirSync(dirname(resolved))).toContain(basename(resolved)); + }); + + it.each([ + ['commonjs', () => requireFromMinio('./minio.js').Client], + ['esm', () => Minio.Client], + ])('signs presigned urls with response header parameters (%s build)', async (_build, loadClient) => { + const Client = loadClient(); + const client = new Client({ + endPoint: 's3.example.com', + accessKey: 'access', + secretKey: 'secret', + region: 'us-east-1', + }); + + const url = new URL( + await client.presignedGetObject('media', 'photo.png', 60, { 'response-content-type': 'image/png' }, new Date(0)), + ); + + expect(url.searchParams.get('response-content-type')).toBe('image/png'); + expect(url.searchParams.get('X-Amz-Signature')).toMatch(/^[0-9a-f]{64}$/); + expect(url.searchParams.get('X-Amz-Credential')).toBe('access/19700101/us-east-1/s3/aws4_request'); + }); + + it('emits bucket notifications parsed from the json lines stream', async () => { + const records = [{ s3: { object: { key: 'a.png' } } }, { s3: { object: { key: 'b.png' } } }]; + const body = `${JSON.stringify({ Records: [records[0]] })}\n${JSON.stringify({ Records: [records[1]] })}\n`; + const client = { region: 'us-east-1', makeRequestAsync: vi.fn(async () => Readable.from([body])) }; + const poller = new NotificationPoller(client, 'media', '', '', ['s3:ObjectCreated:*']); + const received: unknown[] = []; + + await new Promise((resolve) => { + poller.on('notification', (record) => { + received.push(record); + if (received.length === records.length) { + poller.stop(); + resolve(); + } + }); + poller.start(); + }); + + expect(received).toEqual(records); + }); +}); diff --git a/test/patches/sharpImageProcessing.test.ts b/test/patches/sharpImageProcessing.test.ts new file mode 100644 index 0000000000..a7cc669bf2 --- /dev/null +++ b/test/patches/sharpImageProcessing.test.ts @@ -0,0 +1,54 @@ +import { BaileysStartupService } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service'; +import { extractImageThumb } from 'baileys'; +import sharp from 'sharp'; +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('baileys', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + prepareWAMessageMedia: vi.fn(async (content: Record) => ({ + [`${Object.keys(content)[0]}Message`]: { input: Object.values(content)[0] }, + })), + }; +}); + +const png = (width: number, height: number) => + sharp({ create: { width, height, channels: 3, background: { r: 200, g: 30, b: 30 } } }) + .png() + .toBuffer(); + +const buildService = () => { + const service = Object.create(BaileysStartupService.prototype) as any; + service.client = { waUploadToServer: vi.fn() }; + service.instance = { wuid: '5491100000000@s.whatsapp.net' }; + service.logger = { verbose: vi.fn(), error: vi.fn() }; + return service; +}; + +describe('image processing on the bundled sharp', () => { + it('converts images sent as media to jpeg', async () => { + const source = await png(40, 20); + + const prepared = await buildService().prepareMediaMessage({ + mediatype: 'image', + media: source.toString('base64'), + }); + const converted = await sharp(prepared.message.imageMessage.input).metadata(); + + expect(converted).toMatchObject({ format: 'jpeg', width: 40, height: 20 }); + }); + + it('converts sticker images to webp', async () => { + const webp = await buildService().convertToWebP((await png(30, 30)).toString('base64')); + + expect(await sharp(webp).metadata()).toMatchObject({ format: 'webp', width: 30, height: 30 }); + }); + + it('builds the Baileys thumbnail with sharp', async () => { + const thumb = await extractImageThumb(await png(200, 100), 32); + + expect(thumb.original).toEqual({ width: 200, height: 100 }); + expect(await sharp(thumb.buffer).metadata()).toMatchObject({ format: 'jpeg', width: 32 }); + }); +}); diff --git a/test/utils/advanceMessageStatus.test.ts b/test/utils/advanceMessageStatus.test.ts new file mode 100644 index 0000000000..d0db0afb8b --- /dev/null +++ b/test/utils/advanceMessageStatus.test.ts @@ -0,0 +1,52 @@ +import { nextMessageStatus, statusesAdvanceableTo } from '@utils/advanceMessageStatus'; +import { describe, expect, it } from 'vitest'; + +describe('nextMessageStatus', () => { + it('writes the first ack when there is no current status', () => { + expect(nextMessageStatus(undefined, 'SERVER_ACK')).toBe('SERVER_ACK'); + expect(nextMessageStatus(null, 'DELIVERY_ACK')).toBe('DELIVERY_ACK'); + }); + + it('never overwrites an unknown stored status', () => { + expect(nextMessageStatus('UNKNOWN', 'READ')).toBeUndefined(); + expect(nextMessageStatus('UNKNOWN', 'ERROR')).toBeUndefined(); + }); + + it('advances to a higher status', () => { + expect(nextMessageStatus('PENDING', 'SERVER_ACK')).toBe('SERVER_ACK'); + expect(nextMessageStatus('READ', 'PLAYED')).toBe('PLAYED'); + }); + + it('skips a backward ack', () => { + expect(nextMessageStatus('READ', 'DELIVERY_ACK')).toBeUndefined(); + }); + + it('skips an equal status', () => { + expect(nextMessageStatus('DELIVERY_ACK', 'DELIVERY_ACK')).toBeUndefined(); + }); + + it('writes ERROR over non-final statuses', () => { + expect(nextMessageStatus('PENDING', 'ERROR')).toBe('ERROR'); + expect(nextMessageStatus('SERVER_ACK', 'ERROR')).toBe('ERROR'); + }); + + it('does not write ERROR after the message was delivered', () => { + for (const current of ['DELIVERY_ACK', 'READ', 'PLAYED']) { + expect(nextMessageStatus(current, 'ERROR')).toBeUndefined(); + } + }); + + it('never overwrites DELETED or EDITED', () => { + for (const current of ['DELETED', 'EDITED']) { + for (const incoming of ['ERROR', 'PENDING', 'SERVER_ACK', 'DELIVERY_ACK', 'READ', 'PLAYED']) { + expect(nextMessageStatus(current, incoming)).toBeUndefined(); + } + } + }); + + it('lists the stored statuses an incoming status may replace', () => { + expect(statusesAdvanceableTo('DELIVERY_ACK')).toEqual(['ERROR', 'PENDING', 'SERVER_ACK']); + expect(statusesAdvanceableTo('ERROR')).toEqual(['PENDING', 'SERVER_ACK']); + expect(statusesAdvanceableTo('PENDING')).toEqual(['ERROR']); + }); +}); diff --git a/test/utils/advancedOperatorsSearch.test.ts b/test/utils/advancedOperatorsSearch.test.ts new file mode 100644 index 0000000000..1814107682 --- /dev/null +++ b/test/utils/advancedOperatorsSearch.test.ts @@ -0,0 +1,68 @@ +import { advancedOperatorsSearch } from '@utils/advancedOperatorsSearch'; +import { describe, expect, it } from 'vitest'; + +describe('advancedOperatorsSearch', () => { + it('matches contains ignoring case and accents', () => { + expect(advancedOperatorsSearch('Olá Mundo', 'contains:ola')).toBe(true); + expect(advancedOperatorsSearch('Hello', 'contains:bye')).toBe(false); + }); + + it('matches notcontains only when the value is absent', () => { + expect(advancedOperatorsSearch('hello world', 'notcontains:bye')).toBe(true); + expect(advancedOperatorsSearch('hello world', 'notcontains:world')).toBe(false); + }); + + it('matches startswith and endswith', () => { + expect(advancedOperatorsSearch('Hello world', 'startswith:hello')).toBe(true); + expect(advancedOperatorsSearch('Hello world', 'startswith:world')).toBe(false); + expect(advancedOperatorsSearch('Hello world', 'endswith:WORLD')).toBe(true); + expect(advancedOperatorsSearch('Hello world', 'endswith:hello')).toBe(false); + }); + + it('matches exact only on the whole normalized text', () => { + expect(advancedOperatorsSearch('Ação', 'exact:acao')).toBe(true); + expect(advancedOperatorsSearch('Ação rápida', 'exact:acao')).toBe(false); + }); + + it('strips accents from the query value', () => { + expect(advancedOperatorsSearch('acao', 'contains:Ação')).toBe(true); + }); + + it('returns false for an empty query', () => { + expect(advancedOperatorsSearch('hello', '')).toBe(false); + }); + + it('accepts operator names in any case', () => { + expect(advancedOperatorsSearch('hello', 'CONTAINS:ell')).toBe(true); + }); + + it('returns false for unknown operators', () => { + expect(advancedOperatorsSearch('hello', 'matches:hello')).toBe(false); + }); + + it('requires every comma separated value to match', () => { + expect(advancedOperatorsSearch('buy red shoes', 'contains:red,shoes')).toBe(true); + expect(advancedOperatorsSearch('buy red shoes', 'contains:red,hat')).toBe(false); + }); + + it('accepts any repeated value for the same operator', () => { + expect(advancedOperatorsSearch('buy shoes', 'contains:hat contains:shoes')).toBe(true); + }); + + it('requires every distinct operator to match', () => { + expect(advancedOperatorsSearch('buy shoes', 'startswith:buy notcontains:hat')).toBe(true); + expect(advancedOperatorsSearch('buy shoes', 'startswith:buy notcontains:shoes')).toBe(false); + }); + + it('keeps colons inside the value', () => { + expect(advancedOperatorsSearch('time 10:30', 'endswith:10:30')).toBe(true); + }); + + it('KNOWN BUG: never matches values that contain spaces (current behavior)', () => { + expect(advancedOperatorsSearch('good morning', 'exact:good morning')).toBe(false); + }); + + it.fails('matches values that contain spaces', () => { + expect(advancedOperatorsSearch('good morning', 'exact:good morning')).toBe(true); + }); +}); diff --git a/test/utils/createJid.test.ts b/test/utils/createJid.test.ts new file mode 100644 index 0000000000..10e58e8adc --- /dev/null +++ b/test/utils/createJid.test.ts @@ -0,0 +1,100 @@ +import { createJid } from '@utils/createJid'; +import { describe, expect, it } from 'vitest'; + +describe('createJid', () => { + it('returns group, user, lid and broadcast jids unchanged', () => { + expect(createJid('120363012345678901@g.us')).toBe('120363012345678901@g.us'); + expect(createJid('5511987654321@s.whatsapp.net')).toBe('5511987654321@s.whatsapp.net'); + expect(createJid('123456789@lid')).toBe('123456789@lid'); + expect(createJid('status@broadcast')).toBe('status@broadcast'); + }); + + it('strips the device suffix before passing a jid through', () => { + expect(createJid('5511987654321:12@s.whatsapp.net')).toBe('5511987654321@s.whatsapp.net'); + expect(createJid('123456789:3@lid')).toBe('123456789@lid'); + expect(createJid('status:1@broadcast')).toBe('status@broadcast'); + }); + + it('drops unknown domains and builds a user jid', () => { + expect(createJid('15551234567@x1.net')).toBe('15551234567@s.whatsapp.net'); + }); + + it('treats dashed ids shorter than 24 characters as numbers', () => { + expect(createJid('123-456')).toBe('123456@s.whatsapp.net'); + }); + + it('removes plus signs, spaces and parentheses from plain numbers', () => { + expect(createJid('+1 (555) 123 4567')).toBe('15551234567@s.whatsapp.net'); + }); + + it('removes dashes from short plain numbers', () => { + expect(createJid('+55 (11) 98765-4321')).toBe('5511987654321@s.whatsapp.net'); + }); + + it('treats dashed ids with at least 24 characters as groups', () => { + expect(createJid('5511999999999-1623281429')).toBe('5511999999999-1623281429@g.us'); + }); + + it('treats numbers with at least 18 digits as groups', () => { + expect(createJid('120363012345678901')).toBe('120363012345678901@g.us'); + }); + + it('keeps 17 digit numbers as user jids', () => { + expect(createJid('12036301234567890')).toBe('12036301234567890@s.whatsapp.net'); + }); + + it('keeps the mobile 9 in 13 digit Argentine numbers', () => { + expect(createJid('5492615550101')).toBe('5492615550101@s.whatsapp.net'); + expect(createJid('+54 9 261 555-0101')).toBe('5492615550101@s.whatsapp.net'); + }); + + it.each([ + ['Buenos Aires', '5491123456789'], + ['Cordoba', '5493511234567'], + ['Rosario', '5493411234567'], + ['four digit area code', '5492966123456'], + ])('keeps the mobile 9 for %s mobiles', (_area, number) => { + expect(createJid(number)).toBe(`${number}@s.whatsapp.net`); + }); + + it.each(['+54 9 11 2345-6789', '+54 (9) 11 2345 6789', '549-11-2345-6789', '+549 11 23456789'])( + 'normalizes the formatted Argentine mobile %s keeping the 9', + (formatted) => { + expect(createJid(formatted)).toBe('5491123456789@s.whatsapp.net'); + }, + ); + + it('passes Argentine jids through unchanged with or without the 9', () => { + expect(createJid('5491123456789@s.whatsapp.net')).toBe('5491123456789@s.whatsapp.net'); + expect(createJid('541123456789@s.whatsapp.net')).toBe('541123456789@s.whatsapp.net'); + expect(createJid('5491123456789:7@s.whatsapp.net')).toBe('5491123456789@s.whatsapp.net'); + }); + + it('keeps Argentine numbers with fewer than 12 digits unchanged', () => { + expect(createJid('54112345678')).toBe('54112345678@s.whatsapp.net'); + }); + + it('KNOWN BUG: removes the third digit from 13 digit Mexican numbers (current behavior)', () => { + expect(createJid('5215512345678')).toBe('525512345678@s.whatsapp.net'); + }); + + it('keeps 12 digit Argentine numbers unchanged', () => { + expect(createJid('542615550101')).toBe('542615550101@s.whatsapp.net'); + }); + + it('keeps the Brazilian ninth digit when the area code is below 31', () => { + expect(createJid('5511987654321')).toBe('5511987654321@s.whatsapp.net'); + }); + + it('keeps the Brazilian ninth digit when the subscriber number starts below 7', () => { + expect(createJid('5541961234567')).toBe('5541961234567@s.whatsapp.net'); + }); + + it('drops the Brazilian ninth digit for area codes from 31 with subscriber numbers from 7', () => { + expect(createJid('5541987654321')).toBe('554187654321@s.whatsapp.net'); + }); + + it('leaves non Brazilian 13 digit numbers unchanged', () => { + expect(createJid('4412345678901')).toBe('4412345678901@s.whatsapp.net'); + }); +}); diff --git a/test/utils/findBotByTrigger.test.ts b/test/utils/findBotByTrigger.test.ts new file mode 100644 index 0000000000..a80f301e49 --- /dev/null +++ b/test/utils/findBotByTrigger.test.ts @@ -0,0 +1,174 @@ +import { findBotByTrigger } from '@utils/findBotByTrigger'; +import { describe, expect, it, vi } from 'vitest'; + +type Bot = { + id: string; + triggerType: string; + triggerOperator?: string; + triggerValue?: string; + enabled?: boolean; + instanceId?: string; +}; + +const createRepository = (seed: Bot[]) => { + const bots = seed.map((bot) => ({ enabled: true, instanceId: 'inst', ...bot })); + const scoped = (where: any) => + bots.filter((bot) => bot.enabled === where.enabled && bot.instanceId === where.instanceId); + return { + findFirst: vi.fn(async ({ where }) => { + if (where.triggerType?.in) + return scoped(where).find((bot) => where.triggerType.in.includes(bot.triggerType)) ?? null; + return ( + scoped(where).find( + (bot) => + bot.triggerType === where.triggerType && + bot.triggerOperator === where.triggerOperator && + bot.triggerValue === where.triggerValue, + ) ?? null + ); + }), + findMany: vi.fn(async ({ where }) => + scoped(where).filter( + (bot) => + bot.triggerType === where.triggerType && + (where.triggerOperator === undefined || bot.triggerOperator === where.triggerOperator), + ), + ), + }; +}; + +describe('findBotByTrigger', () => { + it('returns a bot with trigger type all before any other bot', async () => { + const repository = createRepository([ + { id: 'kw', triggerType: 'keyword', triggerOperator: 'equals', triggerValue: 'hi' }, + { id: 'all', triggerType: 'all' }, + ]); + + expect(await findBotByTrigger(repository, 'hi', 'inst')).toMatchObject({ id: 'all', triggerType: 'all' }); + }); + + it('ignores disabled bots and bots from other instances', async () => { + const repository = createRepository([ + { id: 'off', triggerType: 'all', enabled: false }, + { id: 'other', triggerType: 'all', instanceId: 'inst-2' }, + { id: 'adv-off', triggerType: 'advanced', triggerValue: 'contains:hi', enabled: false }, + { id: 'eq-other', triggerType: 'keyword', triggerOperator: 'equals', triggerValue: 'hi', instanceId: 'inst-2' }, + { id: 'ct-off', triggerType: 'keyword', triggerOperator: 'contains', triggerValue: 'h', enabled: false }, + { id: 'ct', triggerType: 'keyword', triggerOperator: 'contains', triggerValue: 'i' }, + ]); + + expect((await findBotByTrigger(repository, 'hi', 'inst'))?.id).toBe('ct'); + expect(await findBotByTrigger(repository, 'hi', 'inst-3')).toBeNull(); + }); + + it('returns a bot with trigger type none', async () => { + const repository = createRepository([{ id: 'none', triggerType: 'none' }]); + + expect((await findBotByTrigger(repository, 'anything', 'inst'))?.id).toBe('none'); + }); + + it('filters every query by enabled and instance id', async () => { + const repository = createRepository([]); + + await findBotByTrigger(repository, 'hi', 'inst-1'); + + for (const call of [...repository.findFirst.mock.calls, ...repository.findMany.mock.calls]) { + expect(call[0].where).toMatchObject({ enabled: true, instanceId: 'inst-1' }); + } + }); + + it('returns the first advanced bot whose query matches', async () => { + const repository = createRepository([ + { id: 'a1', triggerType: 'advanced', triggerValue: 'contains:bye' }, + { id: 'a2', triggerType: 'advanced', triggerValue: 'startswith:hello' }, + ]); + + expect((await findBotByTrigger(repository, 'Hello there', 'inst'))?.id).toBe('a2'); + }); + + it('prefers advanced bots over keyword bots', async () => { + const repository = createRepository([ + { id: 'eq', triggerType: 'keyword', triggerOperator: 'equals', triggerValue: 'hello' }, + { id: 'adv', triggerType: 'advanced', triggerValue: 'exact:hello' }, + ]); + + expect((await findBotByTrigger(repository, 'hello', 'inst'))?.id).toBe('adv'); + }); + + it('returns a keyword bot on exact equality', async () => { + const repository = createRepository([ + { id: 'eq', triggerType: 'keyword', triggerOperator: 'equals', triggerValue: 'menu' }, + ]); + + expect((await findBotByTrigger(repository, 'menu', 'inst'))?.id).toBe('eq'); + expect(await findBotByTrigger(repository, 'Menu', 'inst')).toBeNull(); + }); + + it('returns a keyword bot whose regex matches', async () => { + const repository = createRepository([ + { id: 're', triggerType: 'keyword', triggerOperator: 'regex', triggerValue: '^order\\s\\d+$' }, + ]); + + expect((await findBotByTrigger(repository, 'order 42', 'inst'))?.id).toBe('re'); + expect(await findBotByTrigger(repository, 'order x', 'inst')).toBeNull(); + }); + + it('returns a keyword bot whose value starts the content', async () => { + const repository = createRepository([ + { id: 'sw', triggerType: 'keyword', triggerOperator: 'startsWith', triggerValue: '/start' }, + ]); + + expect((await findBotByTrigger(repository, '/start now', 'inst'))?.id).toBe('sw'); + expect(await findBotByTrigger(repository, 'now /start', 'inst')).toBeNull(); + }); + + it('returns a keyword bot whose value ends the content', async () => { + const repository = createRepository([ + { id: 'ew', triggerType: 'keyword', triggerOperator: 'endsWith', triggerValue: '?' }, + ]); + + expect((await findBotByTrigger(repository, 'help?', 'inst'))?.id).toBe('ew'); + expect(await findBotByTrigger(repository, '?help', 'inst')).toBeNull(); + }); + + it('returns a keyword bot whose value is contained in the content', async () => { + const repository = createRepository([ + { id: 'ct', triggerType: 'keyword', triggerOperator: 'contains', triggerValue: 'price' }, + ]); + + expect((await findBotByTrigger(repository, 'what is the price today', 'inst'))?.id).toBe('ct'); + expect(await findBotByTrigger(repository, 'hello', 'inst')).toBeNull(); + }); + + it('checks keyword operators in the order equals, regex, startsWith, endsWith, contains', async () => { + const repository = createRepository([ + { id: 'ct', triggerType: 'keyword', triggerOperator: 'contains', triggerValue: 'a' }, + { id: 'ew', triggerType: 'keyword', triggerOperator: 'endsWith', triggerValue: 'a' }, + { id: 'sw', triggerType: 'keyword', triggerOperator: 'startsWith', triggerValue: 'a' }, + { id: 're', triggerType: 'keyword', triggerOperator: 'regex', triggerValue: 'a' }, + ]); + + expect((await findBotByTrigger(repository, 'aba', 'inst'))?.id).toBe('re'); + }); + + it('KNOWN BUG: rejects the whole lookup when a stored regex is invalid (current behavior)', async () => { + const repository = createRepository([ + { id: 're', triggerType: 'keyword', triggerOperator: 'regex', triggerValue: '(' }, + ]); + + await expect(findBotByTrigger(repository, 'x', 'inst')).rejects.toThrow(SyntaxError); + }); + + it.fails('skips a bot with an invalid stored regex and keeps searching', async () => { + const repository = createRepository([ + { id: 're', triggerType: 'keyword', triggerOperator: 'regex', triggerValue: '(' }, + { id: 'ct', triggerType: 'keyword', triggerOperator: 'contains', triggerValue: 'x' }, + ]); + + expect((await findBotByTrigger(repository, 'x', 'inst'))?.id).toBe('ct'); + }); + + it('returns null when nothing matches', async () => { + expect(await findBotByTrigger(createRepository([]), 'hi', 'inst')).toBeNull(); + }); +}); diff --git a/test/utils/getConversationMessage.test.ts b/test/utils/getConversationMessage.test.ts new file mode 100644 index 0000000000..e9ca0ca307 --- /dev/null +++ b/test/utils/getConversationMessage.test.ts @@ -0,0 +1,193 @@ +import { getConversationMessage } from '@utils/getConversationMessage'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const s3 = vi.hoisted(() => ({ ENABLE: false, SAVE_VIDEO: false })); + +vi.mock('@config/env.config', () => ({ + configService: { get: vi.fn(() => s3) }, +})); + +const key = { id: 'MSG1' }; + +describe('getConversationMessage', () => { + beforeEach(() => { + s3.ENABLE = false; + s3.SAVE_VIDEO = false; + }); + + it('returns plain conversation text', () => { + expect(getConversationMessage({ key, message: { conversation: 'hello' } })).toBe('hello'); + }); + + it('returns extended text message text', () => { + expect(getConversationMessage({ key, message: { extendedTextMessage: { text: 'link' } } })).toBe('link'); + }); + + it('formats images with the key id when S3 is disabled', () => { + expect(getConversationMessage({ key, message: { imageMessage: {} } })).toBe('imageMessage|MSG1'); + expect(getConversationMessage({ key, message: { imageMessage: { caption: 'pic' } } })).toBe( + 'imageMessage|MSG1|pic', + ); + }); + + it('formats images with the media url when S3 is enabled', () => { + s3.ENABLE = true; + + expect(getConversationMessage({ key, message: { imageMessage: {}, mediaUrl: 'https://s3/x.jpg' } })).toBe( + 'imageMessage|https://s3/x.jpg', + ); + }); + + it('formats videos with and without caption', () => { + expect(getConversationMessage({ key, message: { videoMessage: {} } })).toBe('videoMessage|MSG1'); + expect(getConversationMessage({ key, message: { videoMessage: { caption: 'clip' } } })).toBe( + 'videoMessage|MSG1|clip', + ); + }); + + it('uses the key id for videos when S3 is enabled but video saving is off', () => { + s3.ENABLE = true; + + expect(getConversationMessage({ key, message: { videoMessage: {}, mediaUrl: 'https://s3/v.mp4' } })).toBe( + 'videoMessage|MSG1', + ); + }); + + it('uses the media url for videos when S3 and video saving are enabled', () => { + s3.ENABLE = true; + s3.SAVE_VIDEO = true; + + expect(getConversationMessage({ key, message: { videoMessage: {}, mediaUrl: 'https://s3/v.mp4' } })).toBe( + 'videoMessage|https://s3/v.mp4', + ); + }); + + it('formats documents with and without caption', () => { + expect(getConversationMessage({ key, message: { documentMessage: {} } })).toBe('documentMessage|MSG1'); + expect(getConversationMessage({ key, message: { documentMessage: { caption: 'doc' } } })).toBe( + 'documentMessage|MSG1|doc', + ); + }); + + it('formats documents wrapped with a caption', () => { + expect( + getConversationMessage({ + key, + message: { documentWithCaptionMessage: { message: { documentMessage: { caption: 'cv' } } } }, + }), + ).toBe('documentWithCaptionMessage|MSG1|cv'); + }); + + it('formats audio with the media id', () => { + expect(getConversationMessage({ key, message: { audioMessage: {} } })).toBe('audioMessage|MSG1'); + }); + + it('prefers speech to text over the audio reference', () => { + expect(getConversationMessage({ key, message: { audioMessage: {}, speechToText: 'transcribed' } })).toBe( + 'transcribed', + ); + }); + + it('returns the latitude for location messages', () => { + expect( + getConversationMessage({ key, message: { locationMessage: { degreesLatitude: -32.89, degreesLongitude: -68 } } }), + ).toBe('-32.89'); + }); + + it('KNOWN BUG: throws for location messages without latitude (current behavior)', () => { + expect(() => getConversationMessage({ key, message: { locationMessage: {} } })).toThrow(TypeError); + }); + + it.fails('does not throw for location messages without latitude', () => { + expect(() => getConversationMessage({ key, message: { locationMessage: {} } })).not.toThrow(); + }); + + it('returns the contact display name', () => { + expect(getConversationMessage({ key, message: { contactMessage: { displayName: 'Ana' } } })).toBe('Ana'); + }); + + it('returns the list response title', () => { + expect( + getConversationMessage({ + key, + message: { listResponseMessage: { title: 'Option A', singleSelectReply: { selectedRowId: 'row-1' } } }, + }), + ).toBe('Option A'); + }); + + it('returns the selected row id when the list response has no title', () => { + expect( + getConversationMessage({ + key, + message: { listResponseMessage: { singleSelectReply: { selectedRowId: 'row-1' } } }, + }), + ).toBe('row-1'); + }); + + it('returns the selected id of template and buttons responses', () => { + expect(getConversationMessage({ key, message: { templateButtonReplyMessage: { selectedId: 'tpl' } } })).toBe('tpl'); + expect(getConversationMessage({ key, message: { buttonsResponseMessage: { selectedButtonId: 'btn' } } })).toBe( + 'btn', + ); + }); + + it('returns view once media urls', () => { + expect( + getConversationMessage({ key, message: { viewOnceMessageV2: { message: { imageMessage: { url: 'u1' } } } } }), + ).toBe('u1'); + }); + + it('falls back to view once video and audio urls', () => { + expect( + getConversationMessage({ key, message: { viewOnceMessageV2: { message: { videoMessage: { url: 'v1' } } } } }), + ).toBe('v1'); + expect( + getConversationMessage({ key, message: { viewOnceMessageV2: { message: { audioMessage: { url: 'a1' } } } } }), + ).toBe('a1'); + }); + + it('falls back to a top level list response title', () => { + expect(getConversationMessage({ key, message: {}, listResponseMessage: { title: 'Top' } })).toBe('Top'); + }); + + it('uses the key id when S3 is enabled and a view once video is present without video saving', () => { + s3.ENABLE = true; + + expect( + getConversationMessage({ + key, + message: { viewOnceMessageV2: { message: { videoMessage: {} } }, audioMessage: {}, mediaUrl: 'https://s3/a' }, + }), + ).toBe('audioMessage|MSG1'); + }); + + it('appends the external ad reply body on a new line', () => { + expect( + getConversationMessage({ + key, + message: { conversation: 'hi' }, + contextInfo: { externalAdReply: { body: 'Ad text' } }, + }), + ).toBe('hi\nexternalAdReplyBody|Ad text'); + }); + + it('KNOWN BUG: duplicates the external ad reply body when it is the only content (current behavior)', () => { + expect(getConversationMessage({ key, message: {}, contextInfo: { externalAdReply: { body: 'Ad' } } })).toBe( + 'externalAdReplyBody\nexternalAdReplyBody|Ad', + ); + }); + + it.fails('returns only the external ad reply body when it is the only content', () => { + expect(getConversationMessage({ key, message: {}, contextInfo: { externalAdReply: { body: 'Ad' } } })).toBe( + 'externalAdReplyBody|Ad', + ); + }); + + it('returns the first matching type when several are present', () => { + expect(getConversationMessage({ key, message: { conversation: 'text', imageMessage: {} } })).toBe('text'); + }); + + it('returns the message type name for unknown messages', () => { + expect(getConversationMessage({ key, message: { reactionMessage: { text: '👍' } } })).toBe('unknown'); + }); +}); diff --git a/test/utils/headerAsString.test.ts b/test/utils/headerAsString.test.ts new file mode 100644 index 0000000000..9535850f50 --- /dev/null +++ b/test/utils/headerAsString.test.ts @@ -0,0 +1,16 @@ +import { headerAsString } from '@utils/headerAsString'; +import { describe, expect, it } from 'vitest'; + +describe('headerAsString', () => { + it('returns string header values', () => { + expect(headerAsString('image/png')).toBe('image/png'); + }); + + it('returns undefined for missing or non-string header values', () => { + expect(headerAsString(undefined)).toBeUndefined(); + expect(headerAsString(null)).toBeUndefined(); + expect(headerAsString(42)).toBeUndefined(); + expect(headerAsString(true)).toBeUndefined(); + expect(headerAsString(['a', 'b'])).toBeUndefined(); + }); +}); diff --git a/test/utils/lidRemoteJidAltBackfill.test.ts b/test/utils/lidRemoteJidAltBackfill.test.ts new file mode 100644 index 0000000000..0d5d422ad1 --- /dev/null +++ b/test/utils/lidRemoteJidAltBackfill.test.ts @@ -0,0 +1,104 @@ +import { LidRemoteJidAltBackfill } from '@utils/lidRemoteJidAltBackfill'; +import { describe, expect, it, vi } from 'vitest'; + +const LID = '100000000000001@lid'; +const PHONE = '5492615550102@s.whatsapp.net'; + +function makeBackfill(executeRaw = vi.fn().mockResolvedValue(3)) { + const prisma = { $executeRaw: executeRaw } as any; + const logger = { warn: vi.fn(), verbose: vi.fn() } as any; + return { backfill: new LidRemoteJidAltBackfill(prisma, logger), executeRaw, logger }; +} + +describe('LidRemoteJidAltBackfill', () => { + it('backfills stored messages when a LID->phone pair is learned', async () => { + const { backfill, executeRaw } = makeBackfill(); + await backfill.schedule('instance-1', LID, PHONE); + expect(executeRaw).toHaveBeenCalledTimes(1); + }); + + it('updates only this instance rows for the lid with bound parameters', async () => { + const { backfill, executeRaw } = makeBackfill(); + await backfill.schedule('instance-1', LID, PHONE); + expect(executeRaw).toHaveBeenCalledTimes(1); + const [strings, ...values] = executeRaw.mock.calls[0]; + const sql = strings.join('?'); + expect(sql).toContain('jsonb_set'); + expect(sql).toContain('"instanceId" = ?'); + expect(sql).not.toContain(LID); + expect(sql).not.toContain(PHONE); + expect(values).toEqual(expect.arrayContaining(['instance-1', LID, PHONE])); + }); + + it('normalizes device suffixes before backfilling', async () => { + const { backfill, executeRaw } = makeBackfill(); + await backfill.schedule('instance-1', '100000000000001:12@lid', '5492615550102:3@s.whatsapp.net'); + const values = executeRaw.mock.calls[0].slice(1); + expect(values).toEqual(expect.arrayContaining([LID, PHONE])); + }); + + it('runs at most once per instance and lid', async () => { + const { backfill, executeRaw } = makeBackfill(); + await backfill.schedule('instance-1', LID, PHONE); + expect(backfill.schedule('instance-1', LID, PHONE)).toBeUndefined(); + await backfill.schedule('instance-2', LID, PHONE); + expect(executeRaw).toHaveBeenCalledTimes(2); + }); + + it.each([ + ['missing lid', undefined, PHONE], + ['missing phone', LID, undefined], + ['phone that is not a user JID', LID, '123@lid'], + ['lid that is not a lid', '5492615550102@s.whatsapp.net', PHONE], + ['group participant', '120363000000000000@g.us', PHONE], + ])('skips a %s', (_label, lid, phone) => { + const { backfill, executeRaw } = makeBackfill(); + expect(backfill.schedule('instance-1', lid, phone)).toBeUndefined(); + expect(executeRaw).not.toHaveBeenCalled(); + }); + + it('logs a warning and resolves when the update fails', async () => { + const { backfill, logger } = makeBackfill(vi.fn().mockRejectedValue(new Error('db down'))); + await expect(backfill.schedule('instance-1', LID, PHONE)).resolves.toBeUndefined(); + expect(logger.warn).toHaveBeenCalledTimes(1); + }); + + it('retries a lid whose previous update failed', async () => { + const executeRaw = vi.fn().mockRejectedValueOnce(new Error('db down')).mockResolvedValue(1); + const { backfill } = makeBackfill(executeRaw); + await backfill.schedule('instance-1', LID, PHONE); + await backfill.schedule('instance-1', LID, PHONE); + expect(executeRaw).toHaveBeenCalledTimes(2); + }); + + it('runs one update at a time and keeps going after a failure', async () => { + let running = 0; + let maxRunning = 0; + const executeRaw = vi.fn().mockImplementation(async () => { + running++; + maxRunning = Math.max(maxRunning, running); + await new Promise((resolve) => setTimeout(resolve, 5)); + running--; + if (executeRaw.mock.calls.length === 1) throw new Error('db down'); + return 1; + }); + const { backfill } = makeBackfill(executeRaw); + await Promise.all([ + backfill.schedule('instance-1', LID, PHONE), + backfill.schedule('instance-1', '1@lid', PHONE), + backfill.schedule('instance-1', '2@lid', PHONE), + ]); + expect(executeRaw).toHaveBeenCalledTimes(3); + expect(maxRunning).toBe(1); + }); + + it('clears the dedupe set once it grows past its bound', async () => { + const { backfill, executeRaw } = makeBackfill(); + await backfill.schedule('instance-1', LID, PHONE); + for (let index = 0; index <= 10_000; index++) { + backfill.schedule('instance-1', `${index}@lid`, PHONE); + } + await backfill.schedule('instance-1', LID, PHONE); + expect(executeRaw.mock.calls.filter((call) => call.includes(LID))).toHaveLength(2); + }); +}); diff --git a/test/utils/messageDedupe.test.ts b/test/utils/messageDedupe.test.ts new file mode 100644 index 0000000000..6b0dd65901 --- /dev/null +++ b/test/utils/messageDedupe.test.ts @@ -0,0 +1,42 @@ +import { MESSAGE_DEDUPE_TTL_SECONDS, messageDedupeKey } from '@utils/messageDedupe'; +import { describe, expect, it } from 'vitest'; + +const phone = '5491123456789@s.whatsapp.net'; +const lidKey = { id: 'MSG-1', remoteJid: '123456789@lid', fromMe: false }; +const pnKey = { id: 'MSG-1', remoteJid: phone, fromMe: false }; + +describe('messageDedupeKey', () => { + it('shares a key between a lid copy with phone alternative and the phone copy', () => { + expect(messageDedupeKey('instance-id', { ...lidKey, remoteJidAlt: phone })).toBe( + messageDedupeKey('instance-id', pnKey), + ); + expect(messageDedupeKey('instance-id', pnKey)).toBe(`message_seen_instance-id_MSG-1_0_${phone}`); + }); + + it('separates copies sent to different recipients', () => { + expect(messageDedupeKey('instance-id', { ...pnKey, fromMe: true })).not.toBe( + messageDedupeKey('instance-id', { ...pnKey, fromMe: true, remoteJid: '5491187654321@s.whatsapp.net' }), + ); + }); + + it('separates a lid copy without phone alternative from the phone copy', () => { + expect(messageDedupeKey('instance-id', lidKey)).not.toBe(messageDedupeKey('instance-id', pnKey)); + }); + + it('separates messages sent by me from received ones', () => { + expect(messageDedupeKey('instance-id', { ...pnKey, fromMe: true })).toBe( + `message_seen_instance-id_MSG-1_1_${phone}`, + ); + expect(messageDedupeKey('instance-id', { ...pnKey, fromMe: true })).not.toBe( + messageDedupeKey('instance-id', pnKey), + ); + }); + + it('separates instances', () => { + expect(messageDedupeKey('a', pnKey)).not.toBe(messageDedupeKey('b', pnKey)); + }); + + it('keeps entries for 24 hours in seconds', () => { + expect(MESSAGE_DEDUPE_TTL_SECONDS).toBe(86400); + }); +}); diff --git a/test/utils/onWhatsappCache.test.ts b/test/utils/onWhatsappCache.test.ts new file mode 100644 index 0000000000..b8ab6afde8 --- /dev/null +++ b/test/utils/onWhatsappCache.test.ts @@ -0,0 +1,347 @@ +import { getOnWhatsappCache, saveOnWhatsappCache } from '@utils/onWhatsappCache'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +type Row = { jid: string; remoteJid: string; lid: string | null; updatedAt: Date }; + +const mocks = vi.hoisted(() => ({ + database: { SAVE_DATA: { IS_ON_WHATSAPP: true, IS_ON_WHATSAPP_DAYS: 7 } }, + rows: new Map(), + isOnWhatsappJid: { findMany: vi.fn(), deleteMany: vi.fn(), createMany: vi.fn() }, + transaction: vi.fn(), + logger: { verbose: vi.fn(), warn: vi.fn(), error: vi.fn() }, +})); + +vi.mock('@api/server.module', () => ({ + prismaRepository: { isOnWhatsappJid: mocks.isOnWhatsappJid, $transaction: mocks.transaction }, +})); +vi.mock('@config/env.config', () => ({ configService: { get: vi.fn(() => mocks.database) } })); +vi.mock('@config/logger.config', () => ({ + Logger: class { + verbose = mocks.logger.verbose; + warn = mocks.logger.warn; + error = mocks.logger.error; + }, +})); + +const { rows, isOnWhatsappJid, transaction, logger } = mocks; + +const uniqueViolation = () => Object.assign(new Error('Unique constraint failed'), { code: 'P2002' }); + +const matches = (row: Row, where: any) => + (!where.jid || where.jid.in.includes(row.jid)) && + (!where.remoteJid || where.remoteJid.in.includes(row.remoteJid)) && + (!where.updatedAt || row.updatedAt >= where.updatedAt.gte); + +const lazy = (run: () => Promise) => ({ + run, + then: (resolve: (value: T) => unknown, reject: (reason: unknown) => unknown) => run().then(resolve, reject), +}); + +const installFake = () => { + isOnWhatsappJid.findMany.mockImplementation(async ({ where }) => + [...rows.values()].filter((row) => matches(row, where)).map((row) => ({ ...row })), + ); + isOnWhatsappJid.deleteMany.mockImplementation(({ where }) => + lazy(async () => { + const targets = [...rows.values()].filter((row) => matches(row, where)); + targets.forEach((row) => rows.delete(row.jid)); + return { count: targets.length }; + }), + ); + isOnWhatsappJid.createMany.mockImplementation(({ data }) => + lazy(async () => { + for (const row of data) { + if (rows.has(row.jid)) throw uniqueViolation(); + } + for (const row of data) { + rows.set(row.jid, { lid: null, updatedAt: new Date(), ...row }); + } + return { count: data.length }; + }), + ); + transaction.mockImplementation(async (operations: ReturnType[]) => { + const results = []; + for (const operation of operations) results.push(await operation.run()); + return results; + }); +}; + +const seed = (remoteJid: string, jids: string[], lid: string | null = null, updatedAt = new Date()) => + jids.forEach((jid) => rows.set(jid, { jid, remoteJid, lid, updatedAt })); + +const group = (remoteJid: string) => + [...rows.values()] + .filter((row) => row.remoteJid === remoteJid) + .map((row) => row.jid) + .sort(); + +const queriedJids = () => isOnWhatsappJid.findMany.mock.calls[0][0].where.jid.in; + +beforeEach(() => { + vi.clearAllMocks(); + rows.clear(); + mocks.database.SAVE_DATA.IS_ON_WHATSAPP = true; + mocks.database.SAVE_DATA.IS_ON_WHATSAPP_DAYS = 7; + installFake(); +}); + +describe('getOnWhatsappCache', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-01-15T12:00:00.000Z')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('returns an empty list without querying when the cache is disabled', async () => { + mocks.database.SAVE_DATA.IS_ON_WHATSAPP = false; + + expect(await getOnWhatsappCache(['5511987654321@s.whatsapp.net'])).toEqual([]); + expect(isOnWhatsappJid.findMany).not.toHaveBeenCalled(); + }); + + it('only returns rows updated within the configured number of days', async () => { + seed('15551234567@s.whatsapp.net', ['15551234567@s.whatsapp.net'], null, new Date('2026-01-01T00:00:00.000Z')); + + expect(await getOnWhatsappCache(['15551234567@s.whatsapp.net'])).toEqual([]); + expect(isOnWhatsappJid.findMany.mock.calls[0][0].where.updatedAt.gte).toEqual(new Date('2026-01-08T12:00:00.000Z')); + }); + + it('returns the whole group with sorted jid options and lid using two queries', async () => { + seed('5491123456789@s.whatsapp.net', ['5491123456789@s.whatsapp.net', '541123456789@s.whatsapp.net']); + seed('5491123456789@s.whatsapp.net', ['999@lid'], 'lid'); + + expect(await getOnWhatsappCache(['541123456789@s.whatsapp.net'])).toEqual([ + { + remoteJid: '5491123456789@s.whatsapp.net', + number: '5491123456789', + jidOptions: ['541123456789@s.whatsapp.net', '5491123456789@s.whatsapp.net', '999@lid'], + lid: 'lid', + }, + ]); + expect(isOnWhatsappJid.findMany).toHaveBeenCalledTimes(2); + expect(isOnWhatsappJid.findMany.mock.calls[1][0].where.remoteJid.in).toEqual(['5491123456789@s.whatsapp.net']); + }); + + it('finds a group by its lid', async () => { + seed('15551234567@s.whatsapp.net', ['15551234567@s.whatsapp.net', '888@lid']); + + const [result] = await getOnWhatsappCache(['888@lid']); + + expect(result.remoteJid).toBe('15551234567@s.whatsapp.net'); + expect(result.jidOptions).toEqual(['15551234567@s.whatsapp.net', '888@lid']); + }); + + it('returns one entry per group when several inputs hit the same group', async () => { + seed('5511987654321@s.whatsapp.net', ['5511987654321@s.whatsapp.net', '551187654321@s.whatsapp.net']); + seed('15551234567@s.whatsapp.net', ['15551234567@s.whatsapp.net']); + + const results = await getOnWhatsappCache([ + '5511987654321@s.whatsapp.net', + '551187654321@s.whatsapp.net', + '15551234567@s.whatsapp.net', + ]); + + expect(results.map((result) => result.remoteJid).sort()).toEqual([ + '15551234567@s.whatsapp.net', + '5511987654321@s.whatsapp.net', + ]); + }); + + it('queries Brazilian numbers with and without the ninth digit', async () => { + await getOnWhatsappCache(['5511987654321@s.whatsapp.net', '551187654321@s.whatsapp.net']); + + expect(queriedJids()).toEqual(['5511987654321@s.whatsapp.net', '551187654321@s.whatsapp.net']); + }); + + it('queries Argentine numbers with and without the 9 prefix', async () => { + await getOnWhatsappCache(['542615550101@s.whatsapp.net']); + + expect(queriedJids()).toEqual(['5492615550101@s.whatsapp.net', '542615550101@s.whatsapp.net']); + }); + + it('queries Mexican numbers with and without the 1 prefix', async () => { + await getOnWhatsappCache(['525512345678@s.whatsapp.net']); + + expect(queriedJids()).toEqual(['5215512345678@s.whatsapp.net', '525512345678@s.whatsapp.net']); + }); + + it('queries other numbers without the plus sign and with a single domain', async () => { + await getOnWhatsappCache(['+15551234567@s.whatsapp.net']); + + expect(queriedJids()).toEqual(['15551234567@s.whatsapp.net']); + }); + + it('queries lid and group jids as given', async () => { + await getOnWhatsappCache(['123456@lid', '5511999999999-1623281429@g.us']); + + expect(queriedJids()).toEqual(['123456@lid', '5511999999999-1623281429@g.us']); + }); + + it('queries numbers without a domain as user jids', async () => { + await getOnWhatsappCache(['15551234567']); + + expect(queriedJids()).toEqual(['15551234567@s.whatsapp.net']); + }); + + it('skips the second query when nothing matches', async () => { + expect(await getOnWhatsappCache(['15551234567@s.whatsapp.net'])).toEqual([]); + expect(isOnWhatsappJid.findMany).toHaveBeenCalledTimes(1); + }); +}); + +describe('saveOnWhatsappCache', () => { + it('does nothing when the cache is disabled', async () => { + mocks.database.SAVE_DATA.IS_ON_WHATSAPP = false; + + await saveOnWhatsappCache([{ remoteJid: '15551234567@s.whatsapp.net' }]); + + expect(isOnWhatsappJid.findMany).not.toHaveBeenCalled(); + expect(transaction).not.toHaveBeenCalled(); + }); + + it('skips items without a remote jid', async () => { + await saveOnWhatsappCache([{ remoteJid: '' }]); + + expect(transaction).not.toHaveBeenCalled(); + expect(logger.warn).toHaveBeenCalledWith('[saveOnWhatsappCache] Item skipped, missing remoteJid.'); + }); + + it('creates one row per variant pointing to the canonical remote jid', async () => { + await saveOnWhatsappCache([{ remoteJid: '+5511987654321@s.whatsapp.net' }]); + + expect(group('5511987654321@s.whatsapp.net')).toEqual([ + '551187654321@s.whatsapp.net', + '5511987654321@s.whatsapp.net', + ]); + expect(rows.get('551187654321@s.whatsapp.net')?.lid).toBeNull(); + }); + + it('adds a lid alternate jid and marks the group as lid', async () => { + await saveOnWhatsappCache([{ remoteJid: '5511987654321@s.whatsapp.net', remoteJidAlt: '999@lid', lid: 'lid' }]); + + expect(group('5511987654321@s.whatsapp.net')).toEqual([ + '551187654321@s.whatsapp.net', + '5511987654321@s.whatsapp.net', + '999@lid', + ]); + expect([...rows.values()].every((row) => row.lid === 'lid')).toBe(true); + }); + + it('ignores alternate jids that are not lids', async () => { + await saveOnWhatsappCache([{ remoteJid: '999@lid', remoteJidAlt: '15550000000@s.whatsapp.net' }]); + + expect([...rows.keys()]).toEqual(['999@lid']); + expect(rows.get('999@lid')).toMatchObject({ remoteJid: '999@lid', lid: 'lid' }); + }); + + it('merges an existing group and makes the item remote jid canonical', async () => { + seed('551187654321@s.whatsapp.net', ['551187654321@s.whatsapp.net', '888@lid'], 'lid', new Date('2020-01-01')); + + await saveOnWhatsappCache([{ remoteJid: '5511987654321@s.whatsapp.net' }]); + + expect(group('5511987654321@s.whatsapp.net')).toEqual([ + '551187654321@s.whatsapp.net', + '5511987654321@s.whatsapp.net', + '888@lid', + ]); + expect(group('551187654321@s.whatsapp.net')).toEqual([]); + expect([...rows.values()].every((row) => row.lid === 'lid' && row.updatedAt > new Date('2020-01-01'))).toBe(true); + }); + + it('saves a jid repeated in one batch as a single group with both alternates', async () => { + await saveOnWhatsappCache([ + { remoteJid: '5491123456789@s.whatsapp.net', remoteJidAlt: '111@lid' }, + { remoteJid: '5491123456789@s.whatsapp.net', remoteJidAlt: '999@lid' }, + ]); + + expect(group('5491123456789@s.whatsapp.net')).toEqual([ + '111@lid', + '541123456789@s.whatsapp.net', + '5491123456789@s.whatsapp.net', + '999@lid', + ]); + expect(rows.size).toBe(4); + }); + + it('saves 2,500 items with a bounded number of queries', async () => { + const items = Array.from({ length: 2500 }, (_, index) => ({ remoteJid: `1555${index}@s.whatsapp.net` })); + + await saveOnWhatsappCache(items); + + expect(rows.size).toBe(2500); + expect(isOnWhatsappJid.findMany.mock.calls.length).toBeLessThanOrEqual(10); + expect(transaction).toHaveBeenCalledTimes(5); + expect(isOnWhatsappJid.createMany).toHaveBeenCalledTimes(5); + }); + + it('reads whole existing groups with two queries per chunk', async () => { + seed('15551234567@s.whatsapp.net', ['15551234567@s.whatsapp.net', '888@lid']); + const items = Array.from({ length: 1000 }, (_, index) => ({ remoteJid: `1555${index}@s.whatsapp.net` })); + items[700] = { remoteJid: '888@lid' }; + + await saveOnWhatsappCache(items); + + expect(isOnWhatsappJid.findMany).toHaveBeenCalledTimes(3); + expect(transaction).toHaveBeenCalledTimes(2); + expect(group('888@lid')).toEqual(['15551234567@s.whatsapp.net', '888@lid']); + }); + + it('retries a chunk once when a concurrent writer causes a unique violation', async () => { + transaction.mockRejectedValueOnce(uniqueViolation()); + + await saveOnWhatsappCache([{ remoteJid: '15551234567@s.whatsapp.net' }]); + + expect(transaction).toHaveBeenCalledTimes(2); + expect(group('15551234567@s.whatsapp.net')).toEqual(['15551234567@s.whatsapp.net']); + expect(logger.error).not.toHaveBeenCalled(); + }); + + it('logs other errors and keeps processing the next chunk', async () => { + transaction.mockRejectedValueOnce(new Error('database unavailable')); + const items = Array.from({ length: 600 }, (_, index) => ({ remoteJid: `1555${index}@s.whatsapp.net` })); + + await expect(saveOnWhatsappCache(items)).resolves.toBeUndefined(); + + expect(transaction).toHaveBeenCalledTimes(2); + expect(rows.size).toBe(100); + expect(logger.error).toHaveBeenCalled(); + }); + + it('keeps saving the rest of the batch when it contains empty items', async () => { + const items = Array.from({ length: 30 }, (_, index) => + index < 10 ? undefined : { remoteJid: `1555000${index}@s.whatsapp.net` }, + ); + + await saveOnWhatsappCache(items as any); + + expect(rows.size).toBe(20); + }); + + it('keeps every alternate jid when concurrent saves touch the same contact', async () => { + const tick = () => new Promise((resolve) => setTimeout(resolve, 0)); + transaction.mockImplementation(async (operations: ReturnType[]) => { + const results = []; + for (const operation of operations) { + await tick(); + results.push(await operation.run()); + } + return results; + }); + const contact = '5491123456789@s.whatsapp.net'; + + await Promise.all( + ['111@lid', '222@lid', '333@lid'].map((alt) => saveOnWhatsappCache([{ remoteJid: contact, remoteJidAlt: alt }])), + ); + + expect(logger.error).not.toHaveBeenCalled(); + expect( + [...rows.values()] + .filter((row) => row.jid.endsWith('@lid')) + .map((row) => `${row.jid}>${row.remoteJid}`) + .sort(), + ).toEqual([`111@lid>${contact}`, `222@lid>${contact}`, `333@lid>${contact}`]); + }); +}); diff --git a/test/utils/resolveLidRemoteJidAlt.test.ts b/test/utils/resolveLidRemoteJidAlt.test.ts new file mode 100644 index 0000000000..28afdc1418 --- /dev/null +++ b/test/utils/resolveLidRemoteJidAlt.test.ts @@ -0,0 +1,106 @@ +import { resolveLidRemoteJidAlt } from '@utils/resolveLidRemoteJidAlt'; +import { describe, expect, it, vi } from 'vitest'; + +const message = (remoteJid: string, remoteJidAlt?: string) => + ({ key: { id: `id-${remoteJid}`, remoteJid, fromMe: false, ...(remoteJidAlt ? { remoteJidAlt } : {}) } }) as any; + +describe('resolveLidRemoteJidAlt', () => { + it('fills remoteJidAlt from a payload contact carrying lid and phone number', async () => { + const messages = [message('111@lid')]; + const lookup = vi.fn(async () => []); + + await resolveLidRemoteJidAlt(messages, [{ id: '111@lid', phoneNumber: '5491111111111@s.whatsapp.net' }], lookup); + + expect(messages[0].key).toMatchObject({ remoteJid: '111@lid', remoteJidAlt: '5491111111111@s.whatsapp.net' }); + expect(lookup).not.toHaveBeenCalled(); + }); + + it('replaces a remoteJidAlt that is not a phone jid', async () => { + const messages = [message('555@lid', '555@lid')]; + + await resolveLidRemoteJidAlt(messages, [{ id: '555@lid', phoneNumber: '5495555555555@s.whatsapp.net' }]); + + expect(messages[0].key).toMatchObject({ remoteJid: '555@lid', remoteJidAlt: '5495555555555@s.whatsapp.net' }); + }); + + it('ignores a contact whose id and lid are different lids', async () => { + const messages = [message('666@lid'), message('777@lid')]; + + await resolveLidRemoteJidAlt(messages, [ + { id: '666@lid', lid: '777@lid', phoneNumber: '5496666666666@s.whatsapp.net' }, + ]); + + expect(messages.map((m) => m.key.remoteJidAlt)).toEqual([undefined, undefined]); + }); + + it('fills remoteJidAlt from a contact whose id is the phone jid and lid is set', async () => { + const messages = [message('222@lid')]; + + await resolveLidRemoteJidAlt(messages, [{ id: '5492222222222@s.whatsapp.net', lid: '222@lid' }]); + + expect(messages[0].key.remoteJidAlt).toBe('5492222222222@s.whatsapp.net'); + }); + + it('looks up the remaining distinct lids once and normalizes the phone jid', async () => { + const messages = [message('333@lid'), message('333@lid'), message('444@lid')]; + const lookup = vi.fn(async () => [ + { lid: '333@lid', pn: '5493333333333:12@s.whatsapp.net' }, + { lid: '444@lid', pn: '5494444444444@s.whatsapp.net' }, + ]); + + await resolveLidRemoteJidAlt(messages, [], lookup); + + expect(lookup).toHaveBeenCalledTimes(1); + expect(lookup).toHaveBeenCalledWith(['333@lid', '444@lid']); + expect(messages.map((m) => m.key.remoteJidAlt)).toEqual([ + '5493333333333@s.whatsapp.net', + '5493333333333@s.whatsapp.net', + '5494444444444@s.whatsapp.net', + ]); + }); + + it('leaves remoteJid untouched and skips groups, phone jids and already resolved messages', async () => { + const messages = [ + message('555@lid', '5495555555555@s.whatsapp.net'), + message('123@g.us'), + message('5496666666666@s.whatsapp.net'), + ]; + const lookup = vi.fn(async () => [{ lid: '555@lid', pn: '5490000000000@s.whatsapp.net' }]); + + await resolveLidRemoteJidAlt(messages, [], lookup); + + expect(lookup).not.toHaveBeenCalled(); + expect(messages.map((m) => [m.key.remoteJid, m.key.remoteJidAlt])).toEqual([ + ['555@lid', '5495555555555@s.whatsapp.net'], + ['123@g.us', undefined], + ['5496666666666@s.whatsapp.net', undefined], + ]); + }); + + it('ignores lookup results that are not phone jids', async () => { + const messages = [message('777@lid')]; + + await resolveLidRemoteJidAlt(messages, [], async () => [{ lid: '777@lid', pn: '888@lid' }]); + + expect(messages[0].key.remoteJidAlt).toBeUndefined(); + }); + + it('reports lookup failures without throwing', async () => { + const messages = [message('999@lid')]; + const onLookupError = vi.fn(); + + await expect( + resolveLidRemoteJidAlt( + messages, + [], + async () => { + throw new Error('boom'); + }, + onLookupError, + ), + ).resolves.toBeUndefined(); + + expect(onLookupError).toHaveBeenCalledTimes(1); + expect(messages[0].key.remoteJidAlt).toBeUndefined(); + }); +}); diff --git a/test/utils/resolvePhoneRemoteJid.test.ts b/test/utils/resolvePhoneRemoteJid.test.ts new file mode 100644 index 0000000000..8082c03b5d --- /dev/null +++ b/test/utils/resolvePhoneRemoteJid.test.ts @@ -0,0 +1,46 @@ +import { resolvePhoneRemoteJid } from '@utils/resolvePhoneRemoteJid'; +import { describe, expect, it } from 'vitest'; + +describe('resolvePhoneRemoteJid', () => { + it('replaces a @lid remoteJid with the phone JID from remoteJidAlt', () => { + expect( + resolvePhoneRemoteJid({ remoteJid: '100000000000001@lid', remoteJidAlt: '5492615550102@s.whatsapp.net' }), + ).toBe('5492615550102@s.whatsapp.net'); + }); + + it('keeps a @lid remoteJid when remoteJidAlt is missing', () => { + expect(resolvePhoneRemoteJid({ remoteJid: '100000000000001@lid' })).toBe('100000000000001@lid'); + }); + + it('keeps a @lid remoteJid when remoteJidAlt is empty', () => { + expect(resolvePhoneRemoteJid({ remoteJid: '100000000000001@lid', remoteJidAlt: '' })).toBe('100000000000001@lid'); + }); + + it('keeps a phone remoteJid even when remoteJidAlt is present', () => { + expect( + resolvePhoneRemoteJid({ remoteJid: '5492615550102@s.whatsapp.net', remoteJidAlt: '100000000000001@lid' }), + ).toBe('5492615550102@s.whatsapp.net'); + }); + + it('keeps a group remoteJid', () => { + expect(resolvePhoneRemoteJid({ remoteJid: '120363204608479348@g.us', remoteJidAlt: undefined })).toBe( + '120363204608479348@g.us', + ); + }); + + it('KNOWN BUG: replaces a remoteJid that contains @lid outside the suffix (current behavior)', () => { + expect(resolvePhoneRemoteJid({ remoteJid: 'x@lid.example', remoteJidAlt: '5492615550102@s.whatsapp.net' })).toBe( + '5492615550102@s.whatsapp.net', + ); + }); + + it.fails('keeps a remoteJid that contains @lid outside the suffix', () => { + expect(resolvePhoneRemoteJid({ remoteJid: 'x@lid.example', remoteJidAlt: '5492615550102@s.whatsapp.net' })).toBe( + 'x@lid.example', + ); + }); + + it('returns undefined when there is no remoteJid', () => { + expect(resolvePhoneRemoteJid({ remoteJidAlt: '5492615550102@s.whatsapp.net' })).toBeUndefined(); + }); +}); diff --git a/test/utils/use-multi-file-auth-state-prisma.test.ts b/test/utils/use-multi-file-auth-state-prisma.test.ts new file mode 100644 index 0000000000..dda722afcc --- /dev/null +++ b/test/utils/use-multi-file-auth-state-prisma.test.ts @@ -0,0 +1,260 @@ +import { prismaRepository } from '@api/server.module'; +import { CacheService } from '@api/services/cache.service'; +import useMultiFileAuthStatePrisma from '@utils/use-multi-file-auth-state-prisma'; +import { proto } from 'baileys'; +import fs from 'fs/promises'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { createCacheService, InMemoryRedisCache } from '../helpers/cache'; +import { files } from '../helpers/fs-promises'; +import { loggersNamed, onlyLoggerNamed } from '../helpers/logger'; + +const mocks = vi.hoisted(() => ({ + cacheConf: { REDIS: { ENABLED: false } }, + sessions: new Map(), +})); + +vi.mock('@config/logger.config', async () => (await import('../helpers/logger')).loggerModule); +vi.mock('@config/env.config', () => ({ configService: { get: vi.fn(() => mocks.cacheConf) } })); +vi.mock('@config/path.config', () => ({ INSTANCE_DIR: '/instances' })); +vi.mock('@api/server.module', () => ({ + prismaRepository: { + session: { + findUnique: vi.fn(async ({ where }) => + mocks.sessions.has(where.sessionId) + ? { sessionId: where.sessionId, creds: mocks.sessions.get(where.sessionId) } + : null, + ), + create: vi.fn(async ({ data }) => mocks.sessions.set(data.sessionId, data.creds)), + update: vi.fn(async ({ where, data }) => mocks.sessions.set(where.sessionId, data.creds)), + delete: vi.fn(async ({ where }) => mocks.sessions.delete(where.sessionId)), + }, + }, +})); +vi.mock('fs/promises', async () => (await import('../helpers/fs-promises')).fsPromisesModule); + +describe('useMultiFileAuthStatePrisma', () => { + let inner: InMemoryRedisCache; + let cache: CacheService; + + beforeEach(() => { + mocks.sessions.clear(); + files.clear(); + mocks.cacheConf.REDIS.ENABLED = false; + ({ inner, service: cache } = createCacheService()); + }); + + const open = () => useMultiFileAuthStatePrisma('session', cache); + const authLogger = () => onlyLoggerNamed('useMultiFileAuthStatePrisma'); + + it('creates and persists fresh creds when nothing is stored', async () => { + const { state } = await open(); + + expect(state.creds.registered).toBe(false); + expect(state.creds.noiseKey.private).toHaveLength(32); + expect(prismaRepository.session.create).toHaveBeenCalledTimes(1); + expect(typeof JSON.parse(mocks.sessions.get('session'))).toBe('string'); + }); + + it('restores saved creds with binary fields intact after reopening', async () => { + const first = await open(); + first.state.creds.registered = true; + await first.saveCreds(); + + expect(prismaRepository.session.update).toHaveBeenCalledTimes(1); + + const second = await open(); + + expect(second.state.creds).toEqual(first.state.creds); + expect(second.state.creds.registered).toBe(true); + expect(Buffer.isBuffer(second.state.creds.noiseKey.private)).toBe(true); + expect(Buffer.from(second.state.creds.signedIdentityKey.public)).toEqual( + Buffer.from(first.state.creds.signedIdentityKey.public), + ); + }); + + describe.each([ + ['local files', false], + ['redis', true], + ])('keys stored in %s', (_, redisEnabled) => { + beforeEach(() => { + mocks.cacheConf.REDIS.ENABLED = redisEnabled; + }); + + const storedKeys = () => (redisEnabled ? [...(inner.hashes.get('session')?.keys() ?? [])] : [...files.keys()]); + + it('round trips pre-keys and sessions through serialized storage', async () => { + const { state } = await open(); + const preKey = { public: Buffer.from([1, 2, 3]), private: new Uint8Array([4, 5, 6]) }; + const session = Buffer.from('session-bytes'); + + await state.keys.set({ 'pre-key': { '1': preKey }, session: { '123@s.whatsapp.net:1': session } }); + + const preKeys = await state.keys.get('pre-key', ['1', '2']); + const sessions = await state.keys.get('session', ['123@s.whatsapp.net:1']); + + expect(Buffer.isBuffer(preKeys['1'].public)).toBe(true); + expect(Buffer.from(preKeys['1'].private)).toEqual(Buffer.from([4, 5, 6])); + expect(preKeys['2']).toBeNull(); + expect(Buffer.from(sessions['123@s.whatsapp.net:1'] as any)).toEqual(session); + if (!redisEnabled) expect(storedKeys()).toContain('/instances/session/session-123@s.whatsapp.net-1.json'); + }); + + it('restores app state sync keys as protobuf messages with byte key data', async () => { + const { state } = await open(); + const keyData = new Uint8Array([9, 8, 7, 6]); + const value = proto.Message.AppStateSyncKeyData.create({ keyData, timestamp: 1700 }); + + await state.keys.set({ 'app-state-sync-key': { AAA: value } }); + + const result = await state.keys.get('app-state-sync-key', ['AAA']); + + expect(result.AAA).toBeInstanceOf(proto.Message.AppStateSyncKeyData); + expect(result.AAA.keyData).toBeInstanceOf(Uint8Array); + expect(Buffer.from(result.AAA.keyData)).toEqual(Buffer.from(keyData)); + }); + + it('decodes app state sync key data persisted as a base64 string', async () => { + const raw = JSON.stringify({ keyData: 'AQID' }); + if (redisEnabled) inner.writeRaw('session', 'app-state-sync-key-BBB', raw); + else files.set('/instances/session/app-state-sync-key-BBB.json', raw); + const { state } = await open(); + + const result = await state.keys.get('app-state-sync-key', ['BBB']); + + expect(result.BBB.keyData).toBeInstanceOf(Uint8Array); + expect(Buffer.from(result.BBB.keyData)).toEqual(Buffer.from([1, 2, 3])); + }); + + it('removes keys set to null or undefined', async () => { + const { state } = await open(); + await state.keys.set({ 'pre-key': { '1': { public: Buffer.from([1]), private: Buffer.from([2]) } } }); + await state.keys.set({ session: { a: Buffer.from([1]) } }); + + await state.keys.set({ 'pre-key': { '1': null }, session: { a: undefined } }); + + expect(storedKeys()).toEqual([]); + expect(await state.keys.get('pre-key', ['1'])).toEqual({ '1': null }); + }); + }); + + it('maps slashes in key names to double underscores in local file paths', async () => { + const { state } = await open(); + + await state.keys.set({ session: { 'a/b': Buffer.from([1]) } }); + + expect([...files.keys()]).toContain('/instances/session/session-a__b.json'); + }); + + it('KNOWN BUG: leaves local key files behind on removeCreds without redis (current behavior)', async () => { + const { state, removeCreds } = await open(); + await state.keys.set({ session: { a: Buffer.from([1]) } }); + + await removeCreds(); + + expect(mocks.sessions.has('session')).toBe(false); + expect(files.size).toBe(1); + expect(authLogger().info).toHaveBeenCalledWith({ action: 'auth.key.delete', sessionId: 'session' }); + }); + + it.fails('removes the database creds and local key files on removeCreds without redis', async () => { + const { state, removeCreds } = await open(); + await state.keys.set({ session: { a: Buffer.from([1]) } }); + + await removeCreds(); + + expect(mocks.sessions.has('session')).toBe(false); + expect(files.size).toBe(0); + }); + + it('KNOWN BUG: keeps the database creds on removeCreds with redis (current behavior)', async () => { + mocks.cacheConf.REDIS.ENABLED = true; + const { state, removeCreds } = await open(); + await state.keys.set({ session: { a: Buffer.from([1]) } }); + + await removeCreds(); + + expect(inner.hashes.has('session')).toBe(false); + expect(mocks.sessions.has('session')).toBe(true); + expect(authLogger().info).toHaveBeenCalledWith({ action: 'redis.delete', sessionId: 'session' }); + }); + + it.fails('clears the redis hash and the database creds on removeCreds with redis', async () => { + mocks.cacheConf.REDIS.ENABLED = true; + const { state, removeCreds } = await open(); + await state.keys.set({ session: { a: Buffer.from([1]) } }); + + await removeCreds(); + + expect(inner.hashes.has('session')).toBe(false); + expect(mocks.sessions.has('session')).toBe(false); + }); + + it('logs the redis failure and falls back to deleting database creds', async () => { + mocks.cacheConf.REDIS.ENABLED = true; + const { removeCreds } = await open(); + inner.down = true; + + await removeCreds(); + + expect(authLogger().warn).toHaveBeenCalledWith({ action: 'redis.delete', sessionId: 'session', err: inner.error }); + expect(mocks.sessions.has('session')).toBe(false); + }); + + it('drops redis key writes and reads without throwing when redis is down', async () => { + mocks.cacheConf.REDIS.ENABLED = true; + const { state } = await open(); + inner.down = true; + + await expect(state.keys.set({ session: { a: Buffer.from([1]), b: null } })).resolves.toBeUndefined(); + await expect(state.keys.get('session', ['a'])).resolves.toEqual({ a: null }); + inner.down = false; + + expect(inner.hashes.get('session')?.has('session-a')).toBeFalsy(); + expect(await state.keys.get('session', ['a'])).toEqual({ a: null }); + expect(loggersNamed('CacheService').slice(-1)[0].error).toHaveBeenCalledWith(inner.error); + }); + + it('swallows database and file read errors without logging them', async () => { + vi.mocked(prismaRepository.session.findUnique).mockRejectedValueOnce(new Error('db down')); + vi.mocked(fs.readFile).mockRejectedValueOnce(new Error('disk error')); + files.set('/instances/session/session-a.json', '{}'); + + const { state } = await open(); + const result = await state.keys.get('session', ['a']); + + expect(state.creds.registered).toBe(false); + expect(result).toEqual({ a: null }); + expect(authLogger().error).not.toHaveBeenCalled(); + }); + + it('swallows database write errors and leaves creds unsaved', async () => { + vi.mocked(prismaRepository.session.create).mockRejectedValue(new Error('db down')); + + const { saveCreds } = await open(); + + await expect(saveCreds()).resolves.toBeUndefined(); + expect(mocks.sessions.has('session')).toBe(false); + expect(authLogger().error).not.toHaveBeenCalled(); + }); + + it('KNOWN BUG: rejects keys.set when a local file write fails (current behavior)', async () => { + const { state } = await open(); + vi.mocked(fs.writeFile).mockRejectedValueOnce(new Error('disk full')); + + await expect(state.keys.set({ session: { a: Buffer.from([1]) } })).rejects.toThrow('disk full'); + }); + + it.fails('swallows local file write errors in keys.set like other storage errors', async () => { + const { state } = await open(); + vi.mocked(fs.writeFile).mockRejectedValueOnce(new Error('disk full')); + + await expect(state.keys.set({ session: { a: Buffer.from([1]) } })).resolves.toBeUndefined(); + }); + + it('swallows unlink errors when removing a missing key file', async () => { + const { state } = await open(); + + await expect(state.keys.set({ session: { missing: null } })).resolves.toBeUndefined(); + }); +}); diff --git a/test/utils/use-multi-file-auth-state-provider-files.test.ts b/test/utils/use-multi-file-auth-state-provider-files.test.ts new file mode 100644 index 0000000000..89b5435342 --- /dev/null +++ b/test/utils/use-multi-file-auth-state-provider-files.test.ts @@ -0,0 +1,207 @@ +import { AuthStateProvider } from '@utils/use-multi-file-auth-state-provider-files'; +import { proto } from 'baileys'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { loggers, loggersNamed, onlyLoggerNamed } from '../helpers/logger'; + +vi.mock('@config/logger.config', async () => (await import('../helpers/logger')).loggerModule); + +const createProviderFiles = () => { + const files = new Map(); + const path = (instance: string, key: string) => `${instance}/${key}`; + return { + files, + parseResponses: true, + create: vi.fn(async (): Promise<[any, any?]> => [{ status: 201 }]), + write: vi.fn(async (instance: string, key: string, body: { data: string }): Promise<[any, any?]> => { + files.set(path(instance, key), body.data); + return [{ status: 201, data: 'ok' }]; + }), + read: vi.fn(async function (instance: string, key: string): Promise<[any, any?]> { + const stored = files.get(path(instance, key)); + if (stored === undefined) return [{ status: 404 }, new Error('not found')]; + return [{ status: 200, data: this.parseResponses ? JSON.parse(stored) : stored }]; + }), + delete: vi.fn(async (instance: string, key: string): Promise<[any, any?]> => { + files.delete(path(instance, key)); + return [{ status: 200 }]; + }), + removeSession: vi.fn(async (instance: string): Promise<[any, any?]> => { + for (const key of [...files.keys()]) if (key.startsWith(`${instance}/`)) files.delete(key); + return [{ status: 200, data: 'removed' }]; + }), + }; +}; + +describe('AuthStateProvider', () => { + let providerFiles: ReturnType; + + beforeEach(() => { + providerFiles = createProviderFiles(); + }); + + const open = () => new AuthStateProvider(providerFiles as any).authStateProvider('instance'); + + it('starts with fresh creds when nothing is stored', async () => { + const { state } = await open(); + + expect(providerFiles.create).toHaveBeenCalledWith('instance'); + expect(state.creds.registered).toBe(false); + expect(state.creds.noiseKey.private).toHaveLength(32); + }); + + it('restores saved creds with binary fields intact after reopening', async () => { + const first = await open(); + await first.saveCreds(); + + expect(typeof providerFiles.files.get('instance/creds')).toBe('string'); + + const second = await open(); + + expect(second.state.creds).toEqual(first.state.creds); + expect(Buffer.isBuffer(second.state.creds.noiseKey.private)).toBe(true); + expect(Buffer.from(second.state.creds.signedIdentityKey.public)).toEqual( + Buffer.from(first.state.creds.signedIdentityKey.public), + ); + }); + + it('KNOWN BUG: returns creds as an unparsed string when the provider responds with raw text (current behavior)', async () => { + const first = await open(); + await first.saveCreds(); + providerFiles.parseResponses = false; + + const second = await open(); + + expect(typeof second.state.creds).toBe('string'); + }); + + it.fails('parses creds when the provider responds with raw text', async () => { + const first = await open(); + await first.saveCreds(); + providerFiles.parseResponses = false; + + const second = await open(); + + expect(second.state.creds).toEqual(first.state.creds); + }); + + it('round trips pre-keys and sessions through serialized storage', async () => { + const { state } = await open(); + const preKey = { public: Buffer.from([1, 2, 3]), private: new Uint8Array([4, 5, 6]) }; + const session = Buffer.from('session-bytes'); + + await state.keys.set({ 'pre-key': { '1': preKey }, session: { 'jid.0': session } }); + + const preKeys = await state.keys.get('pre-key', ['1', '2']); + const sessions = await state.keys.get('session', ['jid.0']); + + expect(Buffer.isBuffer(preKeys['1'].public)).toBe(true); + expect(Buffer.from(preKeys['1'].private)).toEqual(Buffer.from([4, 5, 6])); + expect(preKeys['2']).toBeUndefined(); + expect(Buffer.from(sessions['jid.0'] as any)).toEqual(session); + }); + + it('restores app state sync keys as protobuf messages with byte key data', async () => { + const { state } = await open(); + const keyData = new Uint8Array([9, 8, 7, 6]); + const value = proto.Message.AppStateSyncKeyData.create({ keyData, timestamp: 1700 }); + + await state.keys.set({ 'app-state-sync-key': { AAA: value } }); + + expect(JSON.parse(providerFiles.files.get('instance/app-state-sync-key-AAA')).keyData).toBe('CQgHBg=='); + + const result = await state.keys.get('app-state-sync-key', ['AAA']); + + expect(result.AAA).toBeInstanceOf(proto.Message.AppStateSyncKeyData); + expect(result.AAA.keyData).toBeInstanceOf(Uint8Array); + expect(Buffer.from(result.AAA.keyData)).toEqual(Buffer.from(keyData)); + }); + + it('decodes app state sync key data persisted as a base64 string', async () => { + providerFiles.files.set('instance/app-state-sync-key-BBB', JSON.stringify({ keyData: 'AQID' })); + const { state } = await open(); + + const result = await state.keys.get('app-state-sync-key', ['BBB']); + + expect(result.BBB.keyData).toBeInstanceOf(Uint8Array); + expect(Buffer.from(result.BBB.keyData)).toEqual(Buffer.from([1, 2, 3])); + }); + + it('removes keys set to null or undefined', async () => { + const { state } = await open(); + await state.keys.set({ 'pre-key': { '1': { public: Buffer.from([1]), private: Buffer.from([2]) } } }); + await state.keys.set({ session: { a: Buffer.from([1]) } }); + + await state.keys.set({ 'pre-key': { '1': null }, session: { a: undefined } }); + + expect(providerFiles.delete).toHaveBeenCalledWith('instance', 'pre-key-1'); + expect(providerFiles.delete).toHaveBeenCalledWith('instance', 'session-a'); + expect(providerFiles.files.size).toBe(0); + }); + + it('removes the whole session on removeCreds', async () => { + const { saveCreds, removeCreds } = await open(); + await saveCreds(); + + await removeCreds(); + + expect(providerFiles.removeSession).toHaveBeenCalledWith('instance'); + expect(providerFiles.files.size).toBe(0); + expect(onlyLoggerNamed('useMultiFileAuthStatePrisma').info).toHaveBeenCalledWith({ + action: 'remove.session', + instance: 'instance', + response: { status: 200, data: 'removed' }, + }); + }); + + it('returns undefined and logs when the session folder cannot be created', async () => { + const error = new Error('provider offline'); + providerFiles.create.mockResolvedValue([{ status: 500 }, error]); + + const result = await open(); + + expect(result).toBeUndefined(); + expect(loggersNamed('AuthStateProvider').slice(-1)[0].error).toHaveBeenCalledWith([ + 'Failed to create folder on file server', + 'provider offline', + error.stack, + ]); + }); + + it('KNOWN BUG: swallows read, write and delete errors without logging them (current behavior)', async () => { + const error = new Error('provider down'); + providerFiles.read.mockResolvedValue([{ status: 500 }, error]); + providerFiles.write.mockResolvedValue([{ status: 500 }, error]); + providerFiles.delete.mockResolvedValue([{ status: 500 }, error]); + providerFiles.removeSession.mockResolvedValue([{ status: 500 }, error]); + + const { state, saveCreds, removeCreds } = await open(); + + expect(state.creds.registered).toBe(false); + await expect(saveCreds()).resolves.toBeUndefined(); + await expect(state.keys.set({ session: { a: Buffer.from([1]), b: null } })).resolves.toBeUndefined(); + await expect(state.keys.get('session', ['a'])).resolves.toEqual({ a: undefined }); + await expect(removeCreds()).resolves.toBeUndefined(); + + for (const logger of loggers) { + expect(logger.error).not.toHaveBeenCalled(); + expect(logger.info).not.toHaveBeenCalled(); + } + }); + + it.fails('logs read, write and delete errors', async () => { + const error = new Error('provider down'); + providerFiles.read.mockResolvedValue([{ status: 500 }, error]); + providerFiles.write.mockResolvedValue([{ status: 500 }, error]); + providerFiles.delete.mockResolvedValue([{ status: 500 }, error]); + providerFiles.removeSession.mockResolvedValue([{ status: 500 }, error]); + + const { state, saveCreds, removeCreds } = await open(); + await saveCreds(); + await state.keys.set({ session: { a: Buffer.from([1]), b: null } }); + await state.keys.get('session', ['a']); + await removeCreds(); + + expect(loggers.some((logger) => logger.error.mock.calls.length > 0)).toBe(true); + }); +}); diff --git a/test/utils/use-multi-file-auth-state-redis-db.test.ts b/test/utils/use-multi-file-auth-state-redis-db.test.ts new file mode 100644 index 0000000000..93a096ce39 --- /dev/null +++ b/test/utils/use-multi-file-auth-state-redis-db.test.ts @@ -0,0 +1,145 @@ +import { CacheService } from '@api/services/cache.service'; +import { useMultiFileAuthStateRedisDb } from '@utils/use-multi-file-auth-state-redis-db'; +import { proto } from 'baileys'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { createCacheService, InMemoryRedisCache } from '../helpers/cache'; +import { lastLogger, loggers, loggersNamed } from '../helpers/logger'; + +vi.mock('@config/logger.config', async () => (await import('../helpers/logger')).loggerModule); + +describe('useMultiFileAuthStateRedisDb', () => { + let inner: InMemoryRedisCache; + let cache: CacheService; + + beforeEach(() => { + loggers.length = 0; + ({ inner, service: cache } = createCacheService()); + }); + + const open = () => useMultiFileAuthStateRedisDb('instance', cache); + + it('starts with fresh creds when nothing is stored', async () => { + const { state } = await open(); + + expect(state.creds.registered).toBe(false); + expect(state.creds.noiseKey.private).toHaveLength(32); + expect(inner.hashes.get('instance')?.has('creds')).toBeFalsy(); + }); + + it('restores saved creds with binary fields intact after reopening', async () => { + const first = await open(); + await first.saveCreds(); + + expect(typeof inner.readRaw('instance', 'creds')).toBe('string'); + + const second = await open(); + + expect(second.state.creds).toEqual(first.state.creds); + expect(Buffer.isBuffer(second.state.creds.noiseKey.private)).toBe(true); + expect(Buffer.from(second.state.creds.signedIdentityKey.public)).toEqual( + Buffer.from(first.state.creds.signedIdentityKey.public), + ); + }); + + it('round trips pre-keys and sessions through serialized storage', async () => { + const { state } = await open(); + const preKey = { public: Buffer.from([1, 2, 3]), private: new Uint8Array([4, 5, 6]) }; + const session = Buffer.from('session-bytes'); + + await state.keys.set({ 'pre-key': { '1': preKey }, session: { 'jid.0': session } }); + + const preKeys = await state.keys.get('pre-key', ['1', '2']); + const sessions = await state.keys.get('session', ['jid.0']); + + expect(Buffer.isBuffer(preKeys['1'].public)).toBe(true); + expect(Buffer.from(preKeys['1'].private)).toEqual(Buffer.from([4, 5, 6])); + expect(preKeys['2']).toBeNull(); + expect(Buffer.from(sessions['jid.0'] as any)).toEqual(session); + }); + + it('restores app state sync keys as protobuf messages with byte key data', async () => { + const { state } = await open(); + const keyData = new Uint8Array([9, 8, 7, 6]); + const value = proto.Message.AppStateSyncKeyData.create({ keyData, timestamp: 1700 }); + + await state.keys.set({ 'app-state-sync-key': { AAA: value } }); + + expect(JSON.parse(inner.readRaw('instance', 'app-state-sync-key-AAA')).keyData).toBe('CQgHBg=='); + + const result = await state.keys.get('app-state-sync-key', ['AAA']); + + expect(result.AAA).toBeInstanceOf(proto.Message.AppStateSyncKeyData); + expect(result.AAA.keyData).toBeInstanceOf(Uint8Array); + expect(Buffer.from(result.AAA.keyData)).toEqual(Buffer.from(keyData)); + }); + + it('decodes app state sync key data persisted as a base64 string', async () => { + inner.writeRaw('instance', 'app-state-sync-key-BBB', JSON.stringify({ keyData: 'AQID' })); + const { state } = await open(); + + const result = await state.keys.get('app-state-sync-key', ['BBB']); + + expect(result.BBB.keyData).toBeInstanceOf(Uint8Array); + expect(Buffer.from(result.BBB.keyData)).toEqual(Buffer.from([1, 2, 3])); + }); + + it('removes keys set to null or undefined', async () => { + const { state } = await open(); + await state.keys.set({ 'pre-key': { '1': { public: Buffer.from([1]), private: Buffer.from([2]) } } }); + await state.keys.set({ session: { a: Buffer.from([1]) } }); + + await state.keys.set({ 'pre-key': { '1': null }, session: { a: undefined } }); + + expect(cache.hDelete).toHaveBeenCalledWith('instance', 'pre-key-1'); + expect(cache.hDelete).toHaveBeenCalledWith('instance', 'session-a'); + expect(await state.keys.get('pre-key', ['1'])).toEqual({ '1': null }); + expect(await state.keys.get('session', ['a'])).toEqual({ a: null }); + }); + + it('clears the whole instance hash on removeCreds', async () => { + const { saveCreds, removeCreds } = await open(); + await saveCreds(); + + await removeCreds(); + + expect(cache.delete).toHaveBeenCalledWith('instance'); + expect(inner.hashes.has('instance')).toBe(false); + expect(lastLogger().warn).toHaveBeenCalledWith({ action: 'redis.delete', instanceName: 'instance' }); + }); + + it('silently drops saved creds when redis is down so reopening yields fresh creds', async () => { + inner.down = true; + const first = await open(); + first.state.creds.registered = true; + + await expect(first.saveCreds()).resolves.toBeUndefined(); + inner.down = false; + const second = await open(); + + expect(inner.hashes.get('instance')?.has('creds')).toBeFalsy(); + expect(second.state.creds.registered).toBe(false); + expect(second.state.creds.noiseKey.private).not.toEqual(first.state.creds.noiseKey.private); + }); + + it('swallows read, write and delete errors inside CacheService when redis is down', async () => { + inner.down = true; + const { state, removeCreds } = await open(); + + expect(state.creds.registered).toBe(false); + await expect(state.keys.set({ session: { a: Buffer.from([1]), b: null } })).resolves.toBeUndefined(); + await expect(state.keys.get('session', ['a'])).resolves.toEqual({ a: null }); + await expect(removeCreds()).resolves.toBeUndefined(); + + expect(loggersNamed('CacheService')[0].error).toHaveBeenCalledWith(inner.error); + expect(loggersNamed('useMultiFileAuthStateRedisDb')[0].error).not.toHaveBeenCalled(); + }); + + it('does not persist fresh creds on open, unlike the prisma store', async () => { + const hSet = vi.spyOn(cache, 'hSet'); + + await open(); + + expect(hSet).not.toHaveBeenCalled(); + }); +}); diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000000..29fffa09ab --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "incremental": false + }, + "include": ["src/**/*", "src/**/*.json", "test/**/*", "vitest.config.ts"], + "exclude": ["node_modules", "./dist", "./prisma"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000000..0651aa82a4 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,27 @@ +import { resolve } from 'path'; +import { defineConfig } from 'vitest/config'; + +const src = (dir: string) => resolve(__dirname, 'src', dir); + +export default defineConfig({ + resolve: { + alias: { + '@api': src('api'), + '@cache': src('cache'), + '@config': src('config'), + '@exceptions': src('exceptions'), + '@libs': src('libs'), + '@utils': src('utils'), + '@validate': src('validate'), + }, + }, + test: { + include: ['test/**/*.test.ts'], + environment: 'node', + mockReset: true, + restoreMocks: true, + unstubEnvs: true, + unstubGlobals: true, + sequence: { shuffle: true }, + }, +});