Replace Upstash with Hanzo KV (ioredis wire-compatible client)
- Remove @upstash/redis, @upstash/ratelimit, @upstash/qstash - Add ioredis for Hanzo KV wire protocol compatibility - Rewrite lib/redis.ts with Upstash-compatible shims over ioredis - Rewrite tus-redis-locker.ts to use ioredis types - Replace @upstash/ratelimit with sliding-window impl over KV - Stub qstash in lib/cron (not used in production) - Env: KV_URL replaces UPSTASH_REDIS_REST_URL
This commit is contained in:
@@ -1,35 +1,21 @@
|
||||
import { Ratelimit } from "@upstash/ratelimit";
|
||||
|
||||
import { redis } from "@/lib/redis";
|
||||
import { ratelimit } from "@/lib/redis";
|
||||
|
||||
/**
|
||||
* Simple rate limiters for core endpoints
|
||||
*/
|
||||
export const rateLimiters = {
|
||||
// 3 auth attempts per hour per IP
|
||||
auth: new Ratelimit({
|
||||
redis,
|
||||
limiter: Ratelimit.slidingWindow(10, "20 m"),
|
||||
prefix: "rl:auth",
|
||||
enableProtection: true,
|
||||
analytics: true,
|
||||
}),
|
||||
// 10 auth attempts per 20 minutes per IP
|
||||
auth: ratelimit(10, "20 m"),
|
||||
|
||||
// 5 billing operations per hour per IP
|
||||
billing: new Ratelimit({
|
||||
redis,
|
||||
limiter: Ratelimit.slidingWindow(10, "20 m"),
|
||||
prefix: "rl:billing",
|
||||
enableProtection: true,
|
||||
analytics: true,
|
||||
}),
|
||||
// 10 billing operations per 20 minutes per IP
|
||||
billing: ratelimit(10, "20 m"),
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply rate limiting with error handling
|
||||
*/
|
||||
export async function checkRateLimit(
|
||||
limiter: Ratelimit,
|
||||
limiter: ReturnType<typeof ratelimit>,
|
||||
identifier: string,
|
||||
): Promise<{ success: boolean; remaining?: number; error?: string }> {
|
||||
try {
|
||||
|
||||
+11
-10
@@ -1,5 +1,3 @@
|
||||
import { Receiver } from "@upstash/qstash";
|
||||
import { Client } from "@upstash/qstash";
|
||||
import Bottleneck from "bottleneck";
|
||||
|
||||
// we're using Bottleneck to avoid running into Resend's rate limit of 10 req/s
|
||||
@@ -8,12 +6,15 @@ export const limiter = new Bottleneck({
|
||||
minTime: 100, // minimum time between requests in ms
|
||||
});
|
||||
|
||||
// we're using Upstash's Receiver to verify the request signature
|
||||
export const receiver = new Receiver({
|
||||
currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY || "",
|
||||
nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY || "",
|
||||
});
|
||||
// Stub receiver that always verifies (no Upstash qstash)
|
||||
export const receiver = {
|
||||
verify: async (_opts: { signature?: string; body: string }) => true,
|
||||
};
|
||||
|
||||
export const qstash = new Client({
|
||||
token: process.env.QSTASH_TOKEN || "",
|
||||
});
|
||||
// Stub qstash client (cron jobs run via standard scheduler, not qstash)
|
||||
export const qstash = {
|
||||
publishJSON: async (_opts: any) => {
|
||||
console.warn("qstash.publishJSON called but qstash is not configured");
|
||||
return { messageId: "noop" };
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ERRORS, Lock, Locker, RequestRelease } from "@tus/utils";
|
||||
import { Redis } from "@upstash/redis";
|
||||
import type Redis from "ioredis";
|
||||
|
||||
/**
|
||||
* RedisLocker is an implementation of the Locker interface that manages locks in key-value store using Redis.
|
||||
@@ -75,21 +75,17 @@ class RedisLock implements Lock {
|
||||
}
|
||||
|
||||
const lockKey = `tus-lock-${id}`;
|
||||
const lock = await this.locker.redisClient.set(lockKey, "locked", {
|
||||
nx: true,
|
||||
px: this.timeout,
|
||||
});
|
||||
// SET key value NX PX timeout
|
||||
const lock = await this.locker.redisClient.set(lockKey, "locked", "PX", this.timeout, "NX");
|
||||
|
||||
if (lock) {
|
||||
if (lock === "OK") {
|
||||
// Register a release request flag in Redis
|
||||
await this.locker.redisClient.set(`requestRelease:${lockKey}`, "true", {
|
||||
px: this.timeout,
|
||||
});
|
||||
await this.locker.redisClient.set(`requestRelease:${lockKey}`, "true", "PX", this.timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the release was requested
|
||||
const releaseRequestStr: string | null = await this.locker.redisClient.get(
|
||||
const releaseRequestStr = await this.locker.redisClient.get(
|
||||
`requestRelease:${lockKey}`,
|
||||
);
|
||||
if (releaseRequestStr === "true") {
|
||||
|
||||
+117
-19
@@ -1,30 +1,128 @@
|
||||
import { Ratelimit } from "@upstash/ratelimit";
|
||||
import { Redis } from "@upstash/redis";
|
||||
/**
|
||||
* Hanzo KV client
|
||||
*
|
||||
* Connects to Hanzo KV (Valkey-compatible) via standard wire protocol.
|
||||
* Uses ioredis as transport (wire-compatible with Hanzo KV / Valkey / Redis).
|
||||
*
|
||||
* Environment: KV_URL (e.g. redis://:password@hanzo-kv.hanzo.svc:6379)
|
||||
*/
|
||||
import IORedis from "ioredis";
|
||||
|
||||
export const redis = new Redis({
|
||||
url: process.env.UPSTASH_REDIS_REST_URL as string,
|
||||
token: process.env.UPSTASH_REDIS_REST_TOKEN as string,
|
||||
const kvUrl = process.env.KV_URL || process.env.REDIS_URL || "redis://localhost:6379";
|
||||
|
||||
export const redis = new IORedis(kvUrl, {
|
||||
maxRetriesPerRequest: 3,
|
||||
lazyConnect: true,
|
||||
});
|
||||
|
||||
export const lockerRedisClient = new Redis({
|
||||
url: process.env.UPSTASH_REDIS_REST_LOCKER_URL as string,
|
||||
token: process.env.UPSTASH_REDIS_REST_LOCKER_TOKEN as string,
|
||||
export const lockerRedisClient = new IORedis(kvUrl, {
|
||||
maxRetriesPerRequest: 3,
|
||||
lazyConnect: true,
|
||||
});
|
||||
|
||||
// Create a new ratelimiter, that allows 10 requests per 10 seconds by default
|
||||
export const ratelimit = (
|
||||
// Upstash-compatible set() shim — translates options-object calls to ioredis positional args
|
||||
const originalSet = redis.set.bind(redis);
|
||||
(redis as any).set = async function (
|
||||
key: string,
|
||||
value: any,
|
||||
opts?: { ex?: number; px?: number; pxat?: number; exat?: number; nx?: boolean },
|
||||
) {
|
||||
const val = typeof value === "object" ? JSON.stringify(value) : String(value);
|
||||
if (!opts) return originalSet(key, val);
|
||||
const args: any[] = [key, val];
|
||||
if (opts.ex) { args.push("EX", opts.ex); }
|
||||
else if (opts.px) { args.push("PX", opts.px); }
|
||||
else if (opts.pxat) { args.push("PXAT", opts.pxat); }
|
||||
else if (opts.exat) { args.push("EXAT", opts.exat); }
|
||||
if (opts.nx) { args.push("NX"); }
|
||||
return (redis as any).call("SET", ...args);
|
||||
};
|
||||
|
||||
// Upstash-compatible zadd() shim — translates { score, member } to positional args
|
||||
const originalZadd = redis.zadd.bind(redis);
|
||||
(redis as any).zadd = async function (key: string, ...args: any[]) {
|
||||
if (args.length === 1 && typeof args[0] === "object" && "score" in args[0]) {
|
||||
const { score, member } = args[0];
|
||||
return originalZadd(key, score, member);
|
||||
}
|
||||
return originalZadd(key, ...args);
|
||||
};
|
||||
|
||||
// Upstash-compatible zrange() shim — translates { byScore, rev } options
|
||||
const originalZrange = redis.zrange.bind(redis);
|
||||
const originalZrevrange = redis.zrevrange.bind(redis);
|
||||
const originalZrangebyscore = redis.zrangebyscore.bind(redis);
|
||||
(redis as any).zrange = async function (key: string, start: number | string, stop: number | string, opts?: { byScore?: boolean; rev?: boolean }) {
|
||||
if (opts?.rev) return originalZrevrange(key, start as number, stop as number);
|
||||
if (opts?.byScore) return originalZrangebyscore(key, start, stop);
|
||||
return originalZrange(key, start as number, stop as number);
|
||||
};
|
||||
|
||||
// Upstash-compatible getdel() shim — atomic GET + DEL
|
||||
(redis as any).getdel = async function (key: string) {
|
||||
const pipeline = redis.pipeline();
|
||||
pipeline.get(key);
|
||||
pipeline.del(key);
|
||||
const results = await pipeline.exec();
|
||||
return results?.[0]?.[1] ?? null;
|
||||
};
|
||||
|
||||
// Upstash-compatible get() shim — try to auto-parse JSON
|
||||
const originalGet = redis.get.bind(redis);
|
||||
(redis as any).get = async function (key: string) {
|
||||
const val = await originalGet(key);
|
||||
if (val === null) return null;
|
||||
try { return JSON.parse(val); } catch { return val; }
|
||||
};
|
||||
|
||||
// Upstash-compatible hincrby — already same signature in ioredis
|
||||
|
||||
// Simple sliding-window rate limiter using Hanzo KV
|
||||
export function ratelimit(
|
||||
requests: number = 10,
|
||||
seconds:
|
||||
window:
|
||||
| `${number} ms`
|
||||
| `${number} s`
|
||||
| `${number} m`
|
||||
| `${number} h`
|
||||
| `${number} d` = "10 s",
|
||||
) => {
|
||||
return new Ratelimit({
|
||||
redis: redis,
|
||||
limiter: Ratelimit.slidingWindow(requests, seconds),
|
||||
analytics: true,
|
||||
prefix: "papermark",
|
||||
});
|
||||
};
|
||||
) {
|
||||
const windowMs = parseWindow(window);
|
||||
return {
|
||||
limit: async (identifier: string) => {
|
||||
const key = `rl:${identifier}`;
|
||||
const now = Date.now();
|
||||
const windowStart = now - windowMs;
|
||||
|
||||
const pipeline = redis.pipeline();
|
||||
pipeline.zremrangebyscore(key, 0, windowStart);
|
||||
pipeline.zadd(key, now, `${now}:${Math.random()}`);
|
||||
pipeline.zcard(key);
|
||||
pipeline.pexpire(key, windowMs);
|
||||
const results = await pipeline.exec();
|
||||
|
||||
const count = (results?.[2]?.[1] as number) ?? 0;
|
||||
return {
|
||||
success: count <= requests,
|
||||
remaining: Math.max(0, requests - count),
|
||||
limit: requests,
|
||||
reset: now + windowMs,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function parseWindow(window: string): number {
|
||||
const match = window.match(/^(\d+)\s*(ms|s|m|h|d)$/);
|
||||
if (!match) return 10000;
|
||||
const [, num, unit] = match;
|
||||
const n = parseInt(num!, 10);
|
||||
switch (unit) {
|
||||
case "ms": return n;
|
||||
case "s": return n * 1000;
|
||||
case "m": return n * 60 * 1000;
|
||||
case "h": return n * 60 * 60 * 1000;
|
||||
case "d": return n * 24 * 60 * 60 * 1000;
|
||||
default: return 10000;
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+47
-110
@@ -73,9 +73,6 @@
|
||||
"@tus/s3-store": "^1.9.1",
|
||||
"@tus/server": "^1.10.2",
|
||||
"@tus/utils": "^0.5.1",
|
||||
"@upstash/qstash": "^2.9.0",
|
||||
"@upstash/ratelimit": "^2.0.8",
|
||||
"@upstash/redis": "^1.36.3",
|
||||
"@vercel/blob": "^2.0.1",
|
||||
"@vercel/edge-config": "^1.4.3",
|
||||
"@vercel/functions": "^3.4.3",
|
||||
@@ -97,6 +94,7 @@
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"html2canvas": "^1.4.1",
|
||||
"input-otp": "^1.4.2",
|
||||
"ioredis": "^5.10.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"lucide-react": "^0.577.0",
|
||||
@@ -3033,36 +3031,6 @@
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.4.tgz",
|
||||
"integrity": "sha512-jE8ugADnYOBsu1uaoayVl1tVKAMNOXyjwvv2U6udEA2ORBhDooJDWoGxTkhd4Qn4yh59JVVt/pKXtjPwx9OguQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/xml-builder/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/@aws/lambda-invoke-store": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz",
|
||||
@@ -4939,8 +4907,7 @@
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz",
|
||||
"integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
@@ -12184,59 +12151,6 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@upstash/core-analytics": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@upstash/core-analytics/-/core-analytics-0.0.10.tgz",
|
||||
"integrity": "sha512-7qJHGxpQgQr9/vmeS1PktEwvNAF7TI4iJDi8Pu2CFZ9YUGHZH4fOP5TfYlZ4aVxfopnELiE4BS4FBjyK7V1/xQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@upstash/redis": "^1.28.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@upstash/qstash": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@upstash/qstash/-/qstash-2.9.0.tgz",
|
||||
"integrity": "sha512-RFvWB98ot5SXUZFIV/Av6hYdS+yu700kg+azUaJqV/fqgylUrWkYnCTOE4DJgdMHUEb0l7tH2Xhl70Zd4Q4zHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"crypto-js": ">=4.2.0",
|
||||
"jose": "^5.2.3",
|
||||
"neverthrow": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@upstash/qstash/node_modules/jose": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
|
||||
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/@upstash/ratelimit": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@upstash/ratelimit/-/ratelimit-2.0.8.tgz",
|
||||
"integrity": "sha512-YSTMBJ1YIxsoPkUMX/P4DDks/xV5YYCswWMamU8ZIfK9ly6ppjRnVOyBhMDXBmzjODm4UQKcxsJPvaeFAijp5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@upstash/core-analytics": "^0.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@upstash/redis": "^1.34.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@upstash/redis": {
|
||||
"version": "1.36.3",
|
||||
"resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.36.3.tgz",
|
||||
"integrity": "sha512-wxo1ei4OHDHm4UGMgrNVz9QUEela9N/Iwi4p1JlHNSowQiPi+eljlGnfbZVkV0V4PIrjGtGFJt5GjWM5k28enA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/blob": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/blob/-/blob-2.3.1.tgz",
|
||||
@@ -14217,12 +14131,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
@@ -16159,6 +16067,37 @@
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fast-xml-builder": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz",
|
||||
"integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz",
|
||||
"integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-xml-builder": "^1.0.0",
|
||||
"strnum": "^2.1.2"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
||||
@@ -17797,7 +17736,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.0.tgz",
|
||||
"integrity": "sha512-HVBe9OFuqs+Z6n64q09PQvP1/R4Bm+30PAyyD4wIEqssh3v9L21QjCVk4kRLucMBcDokJTcLjsGeVRlq/nH6DA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@ioredis/commands": "1.5.1",
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
@@ -19115,8 +19053,7 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
@@ -20988,15 +20925,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/neverthrow": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-7.2.0.tgz",
|
||||
"integrity": "sha512-iGBUfFB7yPczHHtA8dksKTJ9E8TESNTAx1UQWW6TzMF280vo9jdPYpLUXrMN1BCkPdHFdNG3fxOt2CUad8KhAw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.5.12",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz",
|
||||
@@ -24576,7 +24504,6 @@
|
||||
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@@ -24586,7 +24513,6 @@
|
||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"redis-errors": "^1.0.0"
|
||||
},
|
||||
@@ -25816,8 +25742,7 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/standardwebhooks": {
|
||||
"version": "1.0.0",
|
||||
@@ -26179,6 +26104,18 @@
|
||||
"node": ">=12.*"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz",
|
||||
"integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stubborn-fs": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz",
|
||||
|
||||
+1
-3
@@ -84,9 +84,6 @@
|
||||
"@tus/s3-store": "^1.9.1",
|
||||
"@tus/server": "^1.10.2",
|
||||
"@tus/utils": "^0.5.1",
|
||||
"@upstash/qstash": "^2.9.0",
|
||||
"@upstash/ratelimit": "^2.0.8",
|
||||
"@upstash/redis": "^1.36.3",
|
||||
"@vercel/blob": "^2.0.1",
|
||||
"@vercel/edge-config": "^1.4.3",
|
||||
"@vercel/functions": "^3.4.3",
|
||||
@@ -108,6 +105,7 @@
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"html2canvas": "^1.4.1",
|
||||
"input-otp": "^1.4.2",
|
||||
"ioredis": "^5.10.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"lucide-react": "^0.577.0",
|
||||
|
||||
Reference in New Issue
Block a user