mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-04 11:30:38 -05:00
feat: telemetry setup (#6888)
Co-authored-by: Matti Nannt <matti@formbricks.com>
This commit is contained in:
committed by
GitHub
parent
c53e4f54cb
commit
018cef61a6
Vendored
+52
-8
@@ -3,7 +3,7 @@ import type { RedisClient } from "@/types/client";
|
||||
import { type CacheError, CacheErrorClass, ErrorCode, type Result, err, ok } from "@/types/error";
|
||||
import type { CacheKey } from "@/types/keys";
|
||||
import { ZCacheKey } from "@/types/keys";
|
||||
import { ZTtlMs } from "@/types/service";
|
||||
import { ZTtlMs, ZTtlMsOptional } from "@/types/service";
|
||||
import { validateInputs } from "./utils/validation";
|
||||
|
||||
/**
|
||||
@@ -116,13 +116,13 @@ export class CacheService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in cache with automatic JSON serialization and TTL
|
||||
* Set a value in cache with automatic JSON serialization and optional TTL
|
||||
* @param key - Cache key to store under
|
||||
* @param value - Value to store
|
||||
* @param ttlMs - Time to live in milliseconds
|
||||
* @param ttlMs - Time to live in milliseconds (optional - if omitted, key persists indefinitely)
|
||||
* @returns Result containing void or an error
|
||||
*/
|
||||
async set(key: CacheKey, value: unknown, ttlMs: number): Promise<Result<void, CacheError>> {
|
||||
async set(key: CacheKey, value: unknown, ttlMs?: number): Promise<Result<void, CacheError>> {
|
||||
// Check Redis availability first
|
||||
if (!this.isRedisClientReady()) {
|
||||
return err({
|
||||
@@ -130,8 +130,8 @@ export class CacheService {
|
||||
});
|
||||
}
|
||||
|
||||
// Validate both key and TTL in one call
|
||||
const validation = validateInputs([key, ZCacheKey], [ttlMs, ZTtlMs]);
|
||||
// Validate key and optional TTL
|
||||
const validation = validateInputs([key, ZCacheKey], [ttlMs, ZTtlMsOptional]);
|
||||
if (!validation.ok) {
|
||||
return validation;
|
||||
}
|
||||
@@ -141,7 +141,13 @@ export class CacheService {
|
||||
const normalizedValue = value === undefined ? null : value;
|
||||
const serialized = JSON.stringify(normalizedValue);
|
||||
|
||||
await this.withTimeout(this.redis.setEx(key, Math.floor(ttlMs / 1000), serialized));
|
||||
if (ttlMs === undefined) {
|
||||
// Set without expiration (persists indefinitely)
|
||||
await this.withTimeout(this.redis.set(key, serialized));
|
||||
} else {
|
||||
// Set with expiration
|
||||
await this.withTimeout(this.redis.setEx(key, Math.floor(ttlMs / 1000), serialized));
|
||||
}
|
||||
return ok(undefined);
|
||||
} catch (error) {
|
||||
logger.error({ error, key, ttlMs }, "Cache set operation failed");
|
||||
@@ -185,6 +191,44 @@ export class CacheService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to acquire a distributed lock (atomic SET NX operation)
|
||||
* @param key - Lock key
|
||||
* @param value - Lock value (typically "locked" or instance identifier)
|
||||
* @param ttlMs - Time to live in milliseconds (lock expiration)
|
||||
* @returns Result containing boolean indicating if lock was acquired, or an error
|
||||
*/
|
||||
async tryLock(key: CacheKey, value: string, ttlMs: number): Promise<Result<boolean, CacheError>> {
|
||||
// Check Redis availability first
|
||||
if (!this.isRedisClientReady()) {
|
||||
return err({
|
||||
code: ErrorCode.RedisConnectionError,
|
||||
});
|
||||
}
|
||||
|
||||
const validation = validateInputs([key, ZCacheKey], [ttlMs, ZTtlMs]);
|
||||
if (!validation.ok) {
|
||||
return validation;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use SET with NX (only set if not exists) and PX (expiration in milliseconds) for atomic lock acquisition
|
||||
const result = await this.withTimeout(
|
||||
this.redis.set(key, value, {
|
||||
NX: true,
|
||||
PX: ttlMs,
|
||||
})
|
||||
);
|
||||
// SET returns "OK" if lock was acquired, null if key already exists
|
||||
return ok(result === "OK");
|
||||
} catch (error) {
|
||||
logger.error({ error, key, ttlMs }, "Cache lock operation failed");
|
||||
return err({
|
||||
code: ErrorCode.RedisOperationError,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache wrapper for functions (cache-aside).
|
||||
* Never throws due to cache errors; function errors propagate without retry.
|
||||
@@ -243,7 +287,7 @@ export class CacheService {
|
||||
}
|
||||
|
||||
private async trySetCache(key: CacheKey, value: unknown, ttlMs: number): Promise<void> {
|
||||
if (typeof value === "undefined") {
|
||||
if (value === undefined) {
|
||||
return; // Skip caching undefined values
|
||||
}
|
||||
|
||||
|
||||
Vendored
+7
@@ -5,3 +5,10 @@ export const ZTtlMs = z
|
||||
.int()
|
||||
.min(1000, "TTL must be at least 1000ms (1 second)")
|
||||
.finite("TTL must be finite");
|
||||
|
||||
export const ZTtlMsOptional = z
|
||||
.number()
|
||||
.int()
|
||||
.min(1000, "TTL must be at least 1000ms (1 second)")
|
||||
.finite("TTL must be finite")
|
||||
.optional();
|
||||
|
||||
Reference in New Issue
Block a user