@common-stack/store-redis 8.6.1-alpha.13 → 8.6.1-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,11 +21,8 @@
21
21
  */
22
22
  export declare const sanitizeRedisKeyComponent: (str: string) => string;
23
23
  /**
24
- * Validates if a tenantId looks like a MongoDB ObjectId hash
25
- * MongoDB ObjectIds are 24 character hexadecimal strings
26
- *
27
- * @param tenantId - The tenant ID to validate
28
- * @returns true if the tenantId appears to be a hash, false otherwise
24
+ * Validates if a tenantId looks like a raw hash value (SHA-1, SHA-256, etc.)
25
+ * MongoDB ObjectIds (24 hex chars) and UUIDs (32 hex chars) are valid tenant IDs.
29
26
  */
30
27
  export declare const isHashLikeTenantId: (tenantId: string) => boolean;
31
28
  /**
@@ -31,16 +31,15 @@ const sanitizeRedisKeyComponent = (str) => {
31
31
  .trim());
32
32
  };
33
33
  /**
34
- * Validates if a tenantId looks like a MongoDB ObjectId hash
35
- * MongoDB ObjectIds are 24 character hexadecimal strings
36
- *
37
- * @param tenantId - The tenant ID to validate
38
- * @returns true if the tenantId appears to be a hash, false otherwise
34
+ * Validates if a tenantId looks like a raw hash value (SHA-1, SHA-256, etc.)
35
+ * MongoDB ObjectIds (24 hex chars) and UUIDs (32 hex chars) are valid tenant IDs.
39
36
  */
40
37
  const isHashLikeTenantId = (tenantId) => {
41
38
  if (!tenantId || tenantId === 'default')
42
39
  return false;
43
- return /^[a-f0-9]{24,}$/i.test(tenantId);
40
+ // Allow MongoDB ObjectIds (24 hex) and UUIDs without hyphens (32 hex)
41
+ // Only flag 40+ hex chars as hash values (SHA-1=40, SHA-256=64, SHA-512=128)
42
+ return /^[a-f0-9]{40,}$/i.test(tenantId);
44
43
  };
45
44
  const buildRedisKey = ({ appName, tenantId, userId, segments = [], logger }) => {
46
45
  // Validate and sanitize tenantId
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize-redis-key.js","sources":["../../../src/core/keyBuilder/sanitize-redis-key.ts"],"sourcesContent":["/**\n * Sanitizes a Redis key component to ensure it's valid and safe\n *\n * Redis keys can contain any binary sequence, but for best practices and compatibility:\n * - Avoid whitespace (spaces, tabs, newlines) - replace with underscores\n * - Avoid pipes (|) which are used in Auth0 userIds - replace with hyphens\n * - Avoid quotes and backslashes that could cause escaping issues - remove them\n * - Avoid control characters - remove them\n *\n * @param str - The string component to sanitize\n * @returns Sanitized string safe for use in Redis keys\n *\n * @example\n * ```typescript\n * sanitizeRedisKeyComponent('auth0|6120ecbb1e5c68006aabe17a')\n * // Returns: 'auth0-6120ecbb1e5c68006aabe17a'\n *\n * sanitizeRedisKeyComponent('user name with spaces')\n * // Returns: 'user_name_with_spaces'\n * ```\n */\nexport const sanitizeRedisKeyComponent = (str: string): string => {\n if (!str) return str;\n return (\n str\n .replace(/[\\s\\r\\n\\t]+/g, '_') // Replace whitespace with underscores\n .replace(/\\|/g, '-') // Replace pipes with hyphens (Auth0 userIds: auth0|123 -> auth0-123)\n .replace(/[\"'\\\\]/g, '') // Remove quotes and backslashes\n // eslint-disable-next-line no-control-regex\n .replace(/[\\x00-\\x1F\\x7F]/g, '') // Remove control characters\n .trim()\n );\n};\n\n/**\n * Validates if a tenantId looks like a MongoDB ObjectId hash\n * MongoDB ObjectIds are 24 character hexadecimal strings\n *\n * @param tenantId - The tenant ID to validate\n * @returns true if the tenantId appears to be a hash, false otherwise\n */\nexport const isHashLikeTenantId = (tenantId: string): boolean => {\n if (!tenantId || tenantId === 'default') return false;\n return /^[a-f0-9]{24,}$/i.test(tenantId);\n};\n\n/**\n * Builds a standardized Redis key with proper tenant isolation and sanitization\n *\n * Key format: `{appName}:{tenantId}:{userId}:{...segments}`\n * - All components are sanitized to remove invalid characters\n * - TenantId is validated and replaced with 'default' if it appears to be a hash\n * - Components can be skipped by passing null/undefined\n *\n * @param options - Key building options\n * @param options.appName - Application name (e.g., 'CDMBASE_TEST')\n * @param options.tenantId - Tenant identifier (validated against hash pattern)\n * @param options.userId - User identifier (sanitized for Auth0 format)\n * @param options.segments - Additional key segments (array of strings)\n * @param options.logger - Optional logger for warnings\n * @returns Constructed Redis key\n *\n * @example\n * ```typescript\n * buildRedisKey({\n * appName: 'CDMBASE_TEST',\n * tenantId: 'default',\n * userId: 'auth0|6120ecbb',\n * segments: ['currentPagePermissions', 'hash123']\n * })\n * // Returns: 'CDMBASE_TEST:default:auth0-6120ecbb:currentPagePermissions:hash123'\n * ```\n */\nexport interface BuildRedisKeyOptions {\n appName: string;\n tenantId?: string;\n userId?: string;\n segments?: string[];\n logger?: {\n warn: (message: string, ...args: unknown[]) => void;\n };\n}\n\nexport const buildRedisKey = ({ appName, tenantId, userId, segments = [], logger }: BuildRedisKeyOptions): string => {\n // Validate and sanitize tenantId\n let sanitizedTenantId = tenantId || 'default';\n if (sanitizedTenantId !== 'default' && isHashLikeTenantId(sanitizedTenantId)) {\n if (logger) {\n logger.warn(\n 'TenantId appears to be a hash (%s...), using \"default\" instead',\n sanitizedTenantId.substring(0, 16),\n );\n }\n sanitizedTenantId = 'default';\n }\n\n // Build key parts\n const parts: string[] = [sanitizeRedisKeyComponent(appName), sanitizeRedisKeyComponent(sanitizedTenantId)];\n\n // Add userId if provided\n if (userId) {\n parts.push(sanitizeRedisKeyComponent(userId));\n }\n\n // Add additional segments\n if (segments && segments.length > 0) {\n parts.push(...segments.map((seg) => sanitizeRedisKeyComponent(seg)));\n }\n\n return parts.join(':');\n};\n\n/**\n * Builds a wildcard pattern for Redis key matching\n * Useful for bulk operations like deleting all keys matching a pattern\n *\n * @param options - Pattern building options (same as buildRedisKey)\n * @returns Redis key pattern with wildcards\n *\n * @example\n * ```typescript\n * buildRedisKeyPattern({\n * appName: 'CDMBASE_TEST',\n * tenantId: 'default',\n * segments: ['currentPagePermissions']\n * })\n * // Returns: 'CDMBASE_TEST:default:*:currentPagePermissions:*'\n * ```\n */\nexport const buildRedisKeyPattern = ({\n appName,\n tenantId,\n userId,\n segments = [],\n}: Omit<BuildRedisKeyOptions, 'logger'>): string => {\n const parts: string[] = [sanitizeRedisKeyComponent(appName), tenantId ? sanitizeRedisKeyComponent(tenantId) : '*'];\n\n // Add userId or wildcard\n parts.push(userId ? sanitizeRedisKeyComponent(userId) : '*');\n\n // Add segments with wildcards\n if (segments && segments.length > 0) {\n parts.push(...segments.map((seg) => (seg === '*' ? '*' : sanitizeRedisKeyComponent(seg))));\n } else {\n parts.push('*');\n }\n\n return parts.join(':');\n};\n\n/**\n * Sanitizes a complete Redis key by sanitizing each component\n *\n * @param key - Redis key to sanitize\n * @returns Sanitized Redis key\n *\n * @example\n * ```typescript\n * sanitizeRedisKey('APP:tenant with spaces:cache:user|data')\n * // Returns: 'APP:tenant_with_spaces:cache:user-data'\n * ```\n */\nexport const sanitizeRedisKey = (key: string): string => {\n if (!key) return '';\n return key.split(':').map(sanitizeRedisKeyComponent).join(':');\n};\n\n/**\n * Escapes special Redis pattern characters for literal matching\n *\n * @param pattern - Pattern string to escape\n * @returns Escaped pattern string\n *\n * @example\n * ```typescript\n * escapeRedisPattern('file*.txt')\n * // Returns: 'file\\\\*.txt'\n * ```\n */\nexport const escapeRedisPattern = (pattern: string): string => {\n if (!pattern) return '';\n return pattern.replace(/[*?[\\]^\\\\]/g, '\\\\$&');\n};\n\n/**\n * Validates if a Redis key is properly formatted\n *\n * @param key - Redis key to validate\n * @returns true if key is valid, false otherwise\n */\nexport const isValidRedisKey = (key: string): boolean => {\n if (!key || typeof key !== 'string') return false;\n if (key.length > 512) return false; // Redis key length limit\n // Check for invalid characters (whitespace, pipes, etc.)\n if (/[\\s|\"'\\\\]/.test(key)) return false;\n return true;\n};\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;AAoBG;AACU,MAAA,yBAAyB,GAAG,CAAC,GAAW,KAAY;AAC7D,IAAA,IAAI,CAAC,GAAG;AAAE,QAAA,OAAO,GAAG,CAAC;AACrB,IAAA,QACI,GAAG;AACE,SAAA,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;AAC5B,SAAA,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;AACnB,SAAA,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;;AAEtB,SAAA,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,IAAI,EAAE,EACb;AACN,EAAE;AAEF;;;;;;AAMG;AACU,MAAA,kBAAkB,GAAG,CAAC,QAAgB,KAAa;AAC5D,IAAA,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,SAAS;AAAE,QAAA,OAAO,KAAK,CAAC;AACtD,IAAA,OAAO,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC7C,EAAE;AAuCW,MAAA,aAAa,GAAG,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,EAAE,EAAE,MAAM,EAAwB,KAAY;;AAEhH,IAAA,IAAI,iBAAiB,GAAG,QAAQ,IAAI,SAAS,CAAC;IAC9C,IAAI,iBAAiB,KAAK,SAAS,IAAI,kBAAkB,CAAC,iBAAiB,CAAC,EAAE;QAC1E,IAAI,MAAM,EAAE;AACR,YAAA,MAAM,CAAC,IAAI,CACP,gEAAgE,EAChE,iBAAiB,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CACrC,CAAC;SACL;QACD,iBAAiB,GAAG,SAAS,CAAC;KACjC;;AAGD,IAAA,MAAM,KAAK,GAAa,CAAC,yBAAyB,CAAC,OAAO,CAAC,EAAE,yBAAyB,CAAC,iBAAiB,CAAC,CAAC,CAAC;;IAG3G,IAAI,MAAM,EAAE;QACR,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC;KACjD;;IAGD,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;AACjC,QAAA,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,yBAAyB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KACxE;AAED,IAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3B,EAAE;AAwCF;;;;;;;;;;;AAWG;AACU,MAAA,gBAAgB,GAAG,CAAC,GAAW,KAAY;AACpD,IAAA,IAAI,CAAC,GAAG;AAAE,QAAA,OAAO,EAAE,CAAC;AACpB,IAAA,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACnE,EAAE;AAEF;;;;;;;;;;;AAWG;AACU,MAAA,kBAAkB,GAAG,CAAC,OAAe,KAAY;AAC1D,IAAA,IAAI,CAAC,OAAO;AAAE,QAAA,OAAO,EAAE,CAAC;IACxB,OAAO,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AAClD,EAAE;AAEF;;;;;AAKG;AACU,MAAA,eAAe,GAAG,CAAC,GAAW,KAAa;AACpD,IAAA,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;AAAE,QAAA,OAAO,KAAK,CAAC;AAClD,IAAA,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,KAAK,CAAC;;AAEnC,IAAA,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;AAAE,QAAA,OAAO,KAAK,CAAC;AACxC,IAAA,OAAO,IAAI,CAAC;AAChB"}
1
+ {"version":3,"file":"sanitize-redis-key.js","sources":["../../../src/core/keyBuilder/sanitize-redis-key.ts"],"sourcesContent":["/**\n * Sanitizes a Redis key component to ensure it's valid and safe\n *\n * Redis keys can contain any binary sequence, but for best practices and compatibility:\n * - Avoid whitespace (spaces, tabs, newlines) - replace with underscores\n * - Avoid pipes (|) which are used in Auth0 userIds - replace with hyphens\n * - Avoid quotes and backslashes that could cause escaping issues - remove them\n * - Avoid control characters - remove them\n *\n * @param str - The string component to sanitize\n * @returns Sanitized string safe for use in Redis keys\n *\n * @example\n * ```typescript\n * sanitizeRedisKeyComponent('auth0|6120ecbb1e5c68006aabe17a')\n * // Returns: 'auth0-6120ecbb1e5c68006aabe17a'\n *\n * sanitizeRedisKeyComponent('user name with spaces')\n * // Returns: 'user_name_with_spaces'\n * ```\n */\nexport const sanitizeRedisKeyComponent = (str: string): string => {\n if (!str) return str;\n return (\n str\n .replace(/[\\s\\r\\n\\t]+/g, '_') // Replace whitespace with underscores\n .replace(/\\|/g, '-') // Replace pipes with hyphens (Auth0 userIds: auth0|123 -> auth0-123)\n .replace(/[\"'\\\\]/g, '') // Remove quotes and backslashes\n // eslint-disable-next-line no-control-regex\n .replace(/[\\x00-\\x1F\\x7F]/g, '') // Remove control characters\n .trim()\n );\n};\n\n/**\n * Validates if a tenantId looks like a raw hash value (SHA-1, SHA-256, etc.)\n * MongoDB ObjectIds (24 hex chars) and UUIDs (32 hex chars) are valid tenant IDs.\n */\nexport const isHashLikeTenantId = (tenantId: string): boolean => {\n if (!tenantId || tenantId === 'default') return false;\n // Allow MongoDB ObjectIds (24 hex) and UUIDs without hyphens (32 hex)\n // Only flag 40+ hex chars as hash values (SHA-1=40, SHA-256=64, SHA-512=128)\n return /^[a-f0-9]{40,}$/i.test(tenantId);\n};\n\n/**\n * Builds a standardized Redis key with proper tenant isolation and sanitization\n *\n * Key format: `{appName}:{tenantId}:{userId}:{...segments}`\n * - All components are sanitized to remove invalid characters\n * - TenantId is validated and replaced with 'default' if it appears to be a hash\n * - Components can be skipped by passing null/undefined\n *\n * @param options - Key building options\n * @param options.appName - Application name (e.g., 'CDMBASE_TEST')\n * @param options.tenantId - Tenant identifier (validated against hash pattern)\n * @param options.userId - User identifier (sanitized for Auth0 format)\n * @param options.segments - Additional key segments (array of strings)\n * @param options.logger - Optional logger for warnings\n * @returns Constructed Redis key\n *\n * @example\n * ```typescript\n * buildRedisKey({\n * appName: 'CDMBASE_TEST',\n * tenantId: 'default',\n * userId: 'auth0|6120ecbb',\n * segments: ['currentPagePermissions', 'hash123']\n * })\n * // Returns: 'CDMBASE_TEST:default:auth0-6120ecbb:currentPagePermissions:hash123'\n * ```\n */\nexport interface BuildRedisKeyOptions {\n appName: string;\n tenantId?: string;\n userId?: string;\n segments?: string[];\n logger?: {\n warn: (message: string, ...args: unknown[]) => void;\n };\n}\n\nexport const buildRedisKey = ({ appName, tenantId, userId, segments = [], logger }: BuildRedisKeyOptions): string => {\n // Validate and sanitize tenantId\n let sanitizedTenantId = tenantId || 'default';\n if (sanitizedTenantId !== 'default' && isHashLikeTenantId(sanitizedTenantId)) {\n if (logger) {\n logger.warn(\n 'TenantId appears to be a hash (%s...), using \"default\" instead',\n sanitizedTenantId.substring(0, 16),\n );\n }\n sanitizedTenantId = 'default';\n }\n\n // Build key parts\n const parts: string[] = [sanitizeRedisKeyComponent(appName), sanitizeRedisKeyComponent(sanitizedTenantId)];\n\n // Add userId if provided\n if (userId) {\n parts.push(sanitizeRedisKeyComponent(userId));\n }\n\n // Add additional segments\n if (segments && segments.length > 0) {\n parts.push(...segments.map((seg) => sanitizeRedisKeyComponent(seg)));\n }\n\n return parts.join(':');\n};\n\n/**\n * Builds a wildcard pattern for Redis key matching\n * Useful for bulk operations like deleting all keys matching a pattern\n *\n * @param options - Pattern building options (same as buildRedisKey)\n * @returns Redis key pattern with wildcards\n *\n * @example\n * ```typescript\n * buildRedisKeyPattern({\n * appName: 'CDMBASE_TEST',\n * tenantId: 'default',\n * segments: ['currentPagePermissions']\n * })\n * // Returns: 'CDMBASE_TEST:default:*:currentPagePermissions:*'\n * ```\n */\nexport const buildRedisKeyPattern = ({\n appName,\n tenantId,\n userId,\n segments = [],\n}: Omit<BuildRedisKeyOptions, 'logger'>): string => {\n const parts: string[] = [sanitizeRedisKeyComponent(appName), tenantId ? sanitizeRedisKeyComponent(tenantId) : '*'];\n\n // Add userId or wildcard\n parts.push(userId ? sanitizeRedisKeyComponent(userId) : '*');\n\n // Add segments with wildcards\n if (segments && segments.length > 0) {\n parts.push(...segments.map((seg) => (seg === '*' ? '*' : sanitizeRedisKeyComponent(seg))));\n } else {\n parts.push('*');\n }\n\n return parts.join(':');\n};\n\n/**\n * Sanitizes a complete Redis key by sanitizing each component\n *\n * @param key - Redis key to sanitize\n * @returns Sanitized Redis key\n *\n * @example\n * ```typescript\n * sanitizeRedisKey('APP:tenant with spaces:cache:user|data')\n * // Returns: 'APP:tenant_with_spaces:cache:user-data'\n * ```\n */\nexport const sanitizeRedisKey = (key: string): string => {\n if (!key) return '';\n return key.split(':').map(sanitizeRedisKeyComponent).join(':');\n};\n\n/**\n * Escapes special Redis pattern characters for literal matching\n *\n * @param pattern - Pattern string to escape\n * @returns Escaped pattern string\n *\n * @example\n * ```typescript\n * escapeRedisPattern('file*.txt')\n * // Returns: 'file\\\\*.txt'\n * ```\n */\nexport const escapeRedisPattern = (pattern: string): string => {\n if (!pattern) return '';\n return pattern.replace(/[*?[\\]^\\\\]/g, '\\\\$&');\n};\n\n/**\n * Validates if a Redis key is properly formatted\n *\n * @param key - Redis key to validate\n * @returns true if key is valid, false otherwise\n */\nexport const isValidRedisKey = (key: string): boolean => {\n if (!key || typeof key !== 'string') return false;\n if (key.length > 512) return false; // Redis key length limit\n // Check for invalid characters (whitespace, pipes, etc.)\n if (/[\\s|\"'\\\\]/.test(key)) return false;\n return true;\n};\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;AAoBG;AACU,MAAA,yBAAyB,GAAG,CAAC,GAAW,KAAY;AAC7D,IAAA,IAAI,CAAC,GAAG;AAAE,QAAA,OAAO,GAAG,CAAC;AACrB,IAAA,QACI,GAAG;AACE,SAAA,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;AAC5B,SAAA,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;AACnB,SAAA,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;;AAEtB,SAAA,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,IAAI,EAAE,EACb;AACN,EAAE;AAEF;;;AAGG;AACU,MAAA,kBAAkB,GAAG,CAAC,QAAgB,KAAa;AAC5D,IAAA,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,SAAS;AAAE,QAAA,OAAO,KAAK,CAAC;;;AAGtD,IAAA,OAAO,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC7C,EAAE;AAuCW,MAAA,aAAa,GAAG,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,EAAE,EAAE,MAAM,EAAwB,KAAY;;AAEhH,IAAA,IAAI,iBAAiB,GAAG,QAAQ,IAAI,SAAS,CAAC;IAC9C,IAAI,iBAAiB,KAAK,SAAS,IAAI,kBAAkB,CAAC,iBAAiB,CAAC,EAAE;QAC1E,IAAI,MAAM,EAAE;AACR,YAAA,MAAM,CAAC,IAAI,CACP,gEAAgE,EAChE,iBAAiB,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CACrC,CAAC;SACL;QACD,iBAAiB,GAAG,SAAS,CAAC;KACjC;;AAGD,IAAA,MAAM,KAAK,GAAa,CAAC,yBAAyB,CAAC,OAAO,CAAC,EAAE,yBAAyB,CAAC,iBAAiB,CAAC,CAAC,CAAC;;IAG3G,IAAI,MAAM,EAAE;QACR,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC;KACjD;;IAGD,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;AACjC,QAAA,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,yBAAyB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KACxE;AAED,IAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3B,EAAE;AAwCF;;;;;;;;;;;AAWG;AACU,MAAA,gBAAgB,GAAG,CAAC,GAAW,KAAY;AACpD,IAAA,IAAI,CAAC,GAAG;AAAE,QAAA,OAAO,EAAE,CAAC;AACpB,IAAA,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACnE,EAAE;AAEF;;;;;;;;;;;AAWG;AACU,MAAA,kBAAkB,GAAG,CAAC,OAAe,KAAY;AAC1D,IAAA,IAAI,CAAC,OAAO;AAAE,QAAA,OAAO,EAAE,CAAC;IACxB,OAAO,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AAClD,EAAE;AAEF;;;;;AAKG;AACU,MAAA,eAAe,GAAG,CAAC,GAAW,KAAa;AACpD,IAAA,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;AAAE,QAAA,OAAO,KAAK,CAAC;AAClD,IAAA,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,KAAK,CAAC;;AAEnC,IAAA,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;AAAE,QAAA,OAAO,KAAK,CAAC;AACxC,IAAA,OAAO,IAAI,CAAC;AAChB"}
@@ -34,9 +34,17 @@ const generateCacheKey = ({ logger, cacheKeyGenerator }) => (requestContext) =>
34
34
  });
35
35
  const { scope } = cacheControlDirective ?? {};
36
36
  const isPrivate = scope?.toLowerCase() === 'private';
37
- const tenantId = (typeof req?.tenant === 'object' ? req.tenant?.id || req.tenant?._id?.toString() : req?.tenant) ??
38
- extractTenantId(req?.currentPageUriSegments?.authority) ??
39
- 'default';
37
+ const tenant = req?.tenant;
38
+ let tenantId;
39
+ if (typeof tenant === 'object' && tenant) {
40
+ tenantId =
41
+ tenant.id ||
42
+ tenant._id?.toString() ||
43
+ 'default';
44
+ }
45
+ else {
46
+ tenantId = tenant ?? extractTenantId(req?.currentPageUriSegments?.authority) ?? 'default';
47
+ }
40
48
  const cacheKey = generateQueryCacheKey({
41
49
  query,
42
50
  variables,
@@ -1 +1 @@
1
- {"version":3,"file":"responseCachePlugin.js","sources":["../../src/plugins/responseCachePlugin.ts"],"sourcesContent":["/* eslint-disable no-param-reassign */\nimport { GraphQLRequestContext } from '@apollo/server';\nimport { CACHE_CONTROL_DIRECTIVE, extractTenantId, getDirectiveArgsFromSchema } from '@common-stack/server-core';\nimport { isEmpty } from 'lodash-es';\nimport apolloCachePlugin from '@apollo/server-plugin-response-cache';\nimport { ILogger } from '@cdm-logger/core/lib/interface';\nimport { IGraphQLCacheContext, IGraphqlCacheKeyGenerator } from 'common/server';\nimport { generateQueryCacheKey } from '../core/keyBuilder/generate-query-cache-key';\n\nconst cachePlugin = (apolloCachePlugin as any).default ?? apolloCachePlugin;\n\ntype ApolloCachePluginOptions = {\n logger: ILogger;\n cacheKeyGenerator: IGraphqlCacheKeyGenerator;\n};\n\nexport const isCacheable = (requestContext: GraphQLRequestContext<IGraphQLCacheContext>) => {\n const { document, schema } = requestContext;\n const cache = getDirectiveArgsFromSchema({\n schema,\n document,\n directiveName: CACHE_CONTROL_DIRECTIVE,\n });\n if (!cache) return false;\n if (cache.scope && !cache.maxAge) {\n cache.maxAge = 86400;\n }\n if (!requestContext.overallCachePolicy) {\n // eslint-disable-next-line no-param-reassign\n // to support test cases\n (requestContext as any).overallCachePolicy = {};\n }\n // eslint-disable-next-line no-param-reassign\n requestContext.overallCachePolicy.maxAge = cache.maxAge;\n return cache.maxAge > 0;\n};\n\nexport const generateCacheKey =\n ({ logger, cacheKeyGenerator }: ApolloCachePluginOptions) =>\n (requestContext: GraphQLRequestContext<IGraphQLCacheContext>): string => {\n if (!isCacheable(requestContext)) {\n return null;\n }\n const { request, contextValue, document, schema } = requestContext;\n const { user, req } = contextValue ?? {};\n const { query, variables } = request;\n const cacheControlDirective = getDirectiveArgsFromSchema({\n schema,\n document,\n directiveName: CACHE_CONTROL_DIRECTIVE,\n });\n const { scope } = cacheControlDirective ?? {};\n const isPrivate = scope?.toLowerCase() === 'private';\n const tenantId =\n (typeof req?.tenant === 'object' ? req.tenant?.id || req.tenant?._id?.toString() : req?.tenant) ??\n extractTenantId(req?.currentPageUriSegments?.authority) ??\n 'default';\n const cacheKey = generateQueryCacheKey({\n query,\n variables,\n logger,\n userId: isPrivate ? user?.sub || null : null,\n tenantId,\n });\n try {\n if (typeof cacheKeyGenerator === 'function') {\n const generatedKey = cacheKeyGenerator(requestContext, cacheKey);\n return generatedKey || cacheKey;\n }\n return cacheKey;\n } catch (e) {\n console.warn('GenerateCacheKey Failed %s', e.message);\n return cacheKey;\n }\n };\nexport const responseCachePlugin = ({ logger, cacheKeyGenerator }: ApolloCachePluginOptions) =>\n cachePlugin({\n sessionId: ({ contextValue }) => Promise.resolve(contextValue?.user?.sub ?? null),\n generateCacheKey: generateCacheKey({ logger, cacheKeyGenerator }),\n shouldWriteToCache: (ctx) =>\n // Cache only successful responses\n isCacheable(ctx) && isEmpty(ctx?.response?.body?.singleResult.errors),\n });\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"responseCachePlugin.js","sources":["../../src/plugins/responseCachePlugin.ts"],"sourcesContent":["/* eslint-disable no-param-reassign */\nimport { GraphQLRequestContext } from '@apollo/server';\nimport { CACHE_CONTROL_DIRECTIVE, extractTenantId, getDirectiveArgsFromSchema } from '@common-stack/server-core';\nimport { isEmpty } from 'lodash-es';\nimport apolloCachePlugin from '@apollo/server-plugin-response-cache';\nimport { ILogger } from '@cdm-logger/core/lib/interface';\nimport { IGraphQLCacheContext, IGraphqlCacheKeyGenerator } from 'common/server';\nimport { generateQueryCacheKey } from '../core/keyBuilder/generate-query-cache-key';\n\nconst cachePlugin = (apolloCachePlugin as any).default ?? apolloCachePlugin;\n\ntype ApolloCachePluginOptions = {\n logger: ILogger;\n cacheKeyGenerator: IGraphqlCacheKeyGenerator;\n};\n\nexport const isCacheable = (requestContext: GraphQLRequestContext<IGraphQLCacheContext>) => {\n const { document, schema } = requestContext;\n const cache = getDirectiveArgsFromSchema({\n schema,\n document,\n directiveName: CACHE_CONTROL_DIRECTIVE,\n });\n if (!cache) return false;\n if (cache.scope && !cache.maxAge) {\n cache.maxAge = 86400;\n }\n if (!requestContext.overallCachePolicy) {\n // eslint-disable-next-line no-param-reassign\n // to support test cases\n (requestContext as any).overallCachePolicy = {};\n }\n // eslint-disable-next-line no-param-reassign\n requestContext.overallCachePolicy.maxAge = cache.maxAge;\n return cache.maxAge > 0;\n};\n\nexport const generateCacheKey =\n ({ logger, cacheKeyGenerator }: ApolloCachePluginOptions) =>\n (requestContext: GraphQLRequestContext<IGraphQLCacheContext>): string => {\n if (!isCacheable(requestContext)) {\n return null;\n }\n const { request, contextValue, document, schema } = requestContext;\n const { user, req } = contextValue ?? {};\n const { query, variables } = request;\n const cacheControlDirective = getDirectiveArgsFromSchema({\n schema,\n document,\n directiveName: CACHE_CONTROL_DIRECTIVE,\n });\n const { scope } = cacheControlDirective ?? {};\n const isPrivate = scope?.toLowerCase() === 'private';\n const tenant = req?.tenant;\n let tenantId: string;\n if (typeof tenant === 'object' && tenant) {\n tenantId =\n (tenant as { id?: string; _id?: { toString(): string } }).id ||\n (tenant as { _id?: { toString(): string } })._id?.toString() ||\n 'default';\n } else {\n tenantId = (tenant as string) ?? extractTenantId(req?.currentPageUriSegments?.authority) ?? 'default';\n }\n const cacheKey = generateQueryCacheKey({\n query,\n variables,\n logger,\n userId: isPrivate ? user?.sub || null : null,\n tenantId,\n });\n try {\n if (typeof cacheKeyGenerator === 'function') {\n const generatedKey = cacheKeyGenerator(requestContext, cacheKey);\n return generatedKey || cacheKey;\n }\n return cacheKey;\n } catch (e) {\n console.warn('GenerateCacheKey Failed %s', e.message);\n return cacheKey;\n }\n };\nexport const responseCachePlugin = ({ logger, cacheKeyGenerator }: ApolloCachePluginOptions) =>\n cachePlugin({\n sessionId: ({ contextValue }) => Promise.resolve(contextValue?.user?.sub ?? null),\n generateCacheKey: generateCacheKey({ logger, cacheKeyGenerator }),\n shouldWriteToCache: (ctx) =>\n // Cache only successful responses\n isCacheable(ctx) && isEmpty(ctx?.response?.body?.singleResult.errors),\n });\n"],"names":[],"mappings":"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"}
@@ -149,7 +149,8 @@ let RedisCacheManager = RedisCacheManager_1 = class RedisCacheManager {
149
149
  */
150
150
  getCacheKey(query, variables, ctx, cachePolicy) {
151
151
  const appName = this.getAppName();
152
- // Validate tenantId - if it looks like a hash (all hex, 24+ chars), use 'default'
152
+ // Validate tenantId - if it looks like a raw hash (32+ hex chars), use 'default'
153
+ // MongoDB ObjectIds (24 hex chars) are valid tenant IDs
153
154
  let tenantId = ctx?.tenantId || 'default';
154
155
  if (isHashLikeTenantId(tenantId)) {
155
156
  this.log('warn', `TenantId appears to be a hash (${tenantId.substring(0, 16)}...), using "default" instead`);
@@ -1 +1 @@
1
- {"version":3,"file":"RedisCacheManager.js","sources":["../../src/services/RedisCacheManager.ts"],"sourcesContent":["/**\n * @file RedisCacheManager.ts\n * @description Redis-based cache manager for GraphQL query caching\n *\n * This implementation provides sophisticated caching for GraphQL operations with:\n * - Automatic query hashing for cache keys\n * - Multi-tenant and user isolation\n * - Wildcard-based cache invalidation\n * - TTL-based expiration\n * - Automatic key sanitization (handles Auth0 userIds with pipes)\n *\n * Migrated from @adminide-stack/platform-server to be shared across applications\n */\n\nimport { generateQueryCacheKey, sanitizeRedisKeyComponent, isHashLikeTenantId } from '../core/keyBuilder';\nimport type { Redis } from 'ioredis';\nimport { print } from 'graphql';\nimport type { DocumentNode } from 'graphql';\nimport { injectable, inject } from 'inversify';\nimport { type ICacheContext, type ICachePolicy, type IRedisCacheManager, SERVER_TYPES } from 'common/server';\nimport { CommonType } from '@common-stack/core';\n\n/**\n * Redis Cache Manager implementation\n *\n * Provides GraphQL query caching with automatic key generation and invalidation\n */\n@injectable()\nexport class RedisCacheManager implements IRedisCacheManager {\n protected logger?: any;\n\n constructor(\n @inject(SERVER_TYPES.RedisClient)\n protected readonly redisClient: Redis,\n @inject(CommonType.LOGGER)\n logger?: any,\n ) {\n if (logger) {\n this.logger = logger.child ? logger.child({ className: RedisCacheManager.name }) : logger;\n }\n }\n\n /**\n * Delete cached data for a GraphQL query\n *\n * @param query - GraphQL query to invalidate\n * @param variables - Optional variables (if omitted, clears all variants)\n * @param ctx - Cache context for tenant/user isolation\n * @param shouldRemoveAll - If true, removes all related cache keys\n */\n async del(\n query: string | DocumentNode,\n variables?: Record<string, unknown>,\n ctx?: ICacheContext,\n shouldRemoveAll = false,\n ): Promise<void> {\n // Clear both PUBLIC and PRIVATE versions of the cache\n const privateKey = this.getCacheKey(query, variables ?? {}, ctx, { maxAge: 1, scope: 'PRIVATE' });\n const publicKey = this.getCacheKey(query, variables ?? {}, ctx, { maxAge: 1, scope: 'PUBLIC' });\n\n // If variables provided, delete exact match (both versions)\n if (variables && Object.keys(variables).length > 0) {\n this.log('debug', `Deleting ${privateKey} and ${publicKey} from redis`);\n await this.redisClient.del(privateKey, publicKey);\n return;\n }\n\n // Build wildcard patterns for both PUBLIC and PRIVATE\n const privatePattern = shouldRemoveAll\n ? this.getWildCardQueryKey(query, ctx)\n : `${privateKey.substring(0, privateKey.lastIndexOf(':'))}:*`;\n\n const publicPattern = shouldRemoveAll\n ? this.getWildCardQueryKey(query, ctx)\n : `${publicKey.substring(0, publicKey.lastIndexOf(':'))}:*`;\n\n // Get keys matching both patterns\n const privateCacheKeys = await this.redisClient.keys(privatePattern);\n const publicCacheKeys = await this.redisClient.keys(publicPattern);\n const allKeys = [...new Set([...privateCacheKeys, ...publicCacheKeys])]; // Deduplicate\n\n this.log('debug', `Found ${allKeys.length} keys against patterns ${privatePattern} and ${publicPattern}`);\n\n if (allKeys.length) {\n this.log('debug', `Deleting ${allKeys.length} keys from redis`);\n await this.redisClient.del(...allKeys);\n }\n }\n\n /**\n * Get cached data for a GraphQL query\n *\n * @param query - GraphQL query\n * @param variables - Query variables\n * @param ctx - Cache context\n * @returns Cached data or null if cache miss\n */\n async get<T>(\n query: string | DocumentNode,\n variables: Record<string, unknown>,\n ctx: ICacheContext,\n ): Promise<T | null> {\n // Try PRIVATE key first (with userId)\n let cacheKey = this.getCacheKey(query, variables, ctx, { maxAge: 1, scope: 'PRIVATE' });\n let cacheResponse = await this.redisClient.get(cacheKey);\n\n // If not found, try PUBLIC key (without userId)\n if (!cacheResponse) {\n cacheKey = this.getCacheKey(query, variables, ctx, { maxAge: 1, scope: 'PUBLIC' });\n cacheResponse = await this.redisClient.get(cacheKey);\n }\n\n if (cacheResponse) {\n try {\n const { data } = JSON.parse(JSON.parse(cacheResponse)?.value) ?? {};\n const queryName = this.getQueryName(query);\n this.log('debug', `Found cache for ${cacheKey}`);\n return data?.[queryName] ?? null;\n } catch (error) {\n this.log('warn', `Failed to parse cache data for ${cacheKey}:`, error);\n return null;\n }\n }\n\n this.log('debug', `No cache found for key ${cacheKey}`);\n return null;\n }\n\n /**\n * Set cached data for a GraphQL query\n *\n * @param query - GraphQL query\n * @param variables - Query variables\n * @param data - Data to cache\n * @param ctx - Cache context\n * @param cachePolicy - Cache policy (TTL, scope)\n */\n async set<T>(\n query: string | DocumentNode,\n variables: Record<string, unknown>,\n data: T,\n ctx: ICacheContext,\n cachePolicy: ICachePolicy = { maxAge: 86400, scope: 'PUBLIC' },\n ): Promise<void> {\n const cacheKey = this.getCacheKey(query, variables, ctx, cachePolicy);\n const cacheTime = Date.now();\n\n // Ensure maxAge is not negative or zero\n const maxAge = Math.max(1, cachePolicy.maxAge);\n\n if (cachePolicy.maxAge <= 0) {\n this.log(\n 'warn',\n `Invalid maxAge (${cachePolicy.maxAge}) for cache key ${cacheKey}, using minimum value of 1 second`,\n );\n }\n\n this.log('debug', `Set cache for key ${cacheKey} with maxAge ${maxAge}`);\n\n await this.redisClient.set(\n cacheKey,\n JSON.stringify({\n value: JSON.stringify({\n data: { [this.getQueryName(query)]: data },\n cachePolicy: { ...cachePolicy, maxAge },\n cacheTime,\n }),\n expires: cacheTime + maxAge * 1000,\n }).trim(),\n 'EX',\n maxAge,\n );\n }\n\n /**\n * Extract query name from GraphQL query\n */\n private getQueryName(query: string | DocumentNode): string {\n const queryStr = typeof query === 'string' ? query : print(query);\n const [, queryName] = queryStr?.match(/{\\s*(\\w+)/) ?? [];\n return queryName;\n }\n\n /**\n * Build wildcard pattern for query invalidation\n */\n private getWildCardQueryKey(query: string | DocumentNode, ctx?: ICacheContext): string {\n const queryStr = typeof query === 'string' ? query : print(query);\n const [, queryName] = queryStr?.match(/{\\s*(\\w+)/) ?? [];\n const { tenantId } = ctx || {};\n\n // Build pattern without namespace - just APP_NAME:tenantId:segments\n const appName = this.getAppName();\n const sanitizedTenantId = tenantId || 'default';\n return `${appName}:${sanitizedTenantId}:*:${queryName || '*'}:*`;\n }\n\n /**\n * Generate cache key for a GraphQL query\n *\n * Format:\n * - With userId (PRIVATE): APP_NAME:tenantId:userId:queryName:queryHash:variablesHash\n * - Without userId (PUBLIC): APP_NAME:tenantId:queryName:queryHash:variablesHash\n */\n private getCacheKey(\n query: string | DocumentNode,\n variables: Record<string, unknown>,\n ctx?: ICacheContext,\n cachePolicy?: ICachePolicy,\n ): string {\n const appName = this.getAppName();\n\n // Validate tenantId - if it looks like a hash (all hex, 24+ chars), use 'default'\n let tenantId = ctx?.tenantId || 'default';\n if (isHashLikeTenantId(tenantId)) {\n this.log(\n 'warn',\n `TenantId appears to be a hash (${tenantId.substring(0, 16)}...), using \"default\" instead`,\n );\n tenantId = 'default';\n }\n\n // For PUBLIC scope, omit userId to share cache across all users\n // For PRIVATE scope or when not specified, include userId (or 'anonymous')\n const isPublicScope = cachePolicy?.scope === 'PUBLIC';\n const userId = isPublicScope ? undefined : ctx?.userId || 'anonymous';\n\n // Use generateQueryCacheKey with all parameters to avoid duplication\n // This will properly sanitize userId (auth0|123 -> auth0-123) and build the complete key\n return generateQueryCacheKey({\n query,\n variables,\n appName,\n tenantId,\n userId,\n logger: this.logger,\n });\n }\n\n /**\n * Clear all cache entries for a tenant\n */\n async clearTenant(tenantId: string): Promise<number> {\n const appName = this.getAppName();\n const sanitizedTenantId = sanitizeRedisKeyComponent(tenantId);\n const pattern = `${appName}:${sanitizedTenantId}:*`;\n\n return await this.clearPattern(pattern);\n }\n\n /**\n * Clear all cache entries for a user\n */\n async clearUser(userId: string, tenantId?: string): Promise<number> {\n const appName = this.getAppName();\n const sanitizedUserId = sanitizeRedisKeyComponent(userId);\n const sanitizedTenantId = tenantId ? sanitizeRedisKeyComponent(tenantId) : '*';\n const pattern = `${appName}:${sanitizedTenantId}:${sanitizedUserId}:*`;\n\n return await this.clearPattern(pattern);\n }\n\n /**\n * Clear all cache entries matching a pattern\n */\n async clearPattern(pattern: string): Promise<number> {\n this.log('debug', `Clearing cache entries matching pattern: ${pattern}`);\n\n const keys = await this.redisClient.keys(pattern);\n\n if (keys.length === 0) {\n this.log('debug', `No keys found matching pattern: ${pattern}`);\n return 0;\n }\n\n this.log('debug', `Deleting ${keys.length} keys matching pattern: ${pattern}`);\n await this.redisClient.del(...keys);\n\n return keys.length;\n }\n\n /**\n * Get cache statistics\n */\n async getStats(tenantId?: string): Promise<{\n totalKeys: number;\n memoryUsage: number;\n hitRate?: number;\n }> {\n const appName = this.getAppName();\n const pattern = tenantId ? `${appName}:${sanitizeRedisKeyComponent(tenantId)}:*` : `${appName}:*`;\n\n const keys = await this.redisClient.keys(pattern);\n const totalKeys = keys.length;\n\n // Get memory usage for the Redis instance\n let memoryUsage = 0;\n try {\n const info = await this.redisClient.info('memory');\n const match = info.match(/used_memory:(\\d+)/);\n if (match) {\n memoryUsage = parseInt(match[1], 10);\n }\n } catch (error) {\n this.log('warn', 'Failed to get memory info from Redis:', error);\n }\n\n return {\n totalKeys,\n memoryUsage,\n hitRate: undefined, // Hit rate tracking would require additional instrumentation\n };\n }\n\n /**\n * Get application name for key prefix\n * Override this method to provide custom app name\n */\n protected getAppName(): string {\n return process.env.APP_NAME || 'COMMON_STACK';\n }\n\n /**\n * Log helper\n */\n private log(level: string, message: string, ...args: any[]): void {\n if (this.logger && typeof this.logger[level] === 'function') {\n this.logger[level](message, ...args);\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RedisCacheManager.js","sources":["../../src/services/RedisCacheManager.ts"],"sourcesContent":["/**\n * @file RedisCacheManager.ts\n * @description Redis-based cache manager for GraphQL query caching\n *\n * This implementation provides sophisticated caching for GraphQL operations with:\n * - Automatic query hashing for cache keys\n * - Multi-tenant and user isolation\n * - Wildcard-based cache invalidation\n * - TTL-based expiration\n * - Automatic key sanitization (handles Auth0 userIds with pipes)\n *\n * Migrated from @adminide-stack/platform-server to be shared across applications\n */\n\nimport { generateQueryCacheKey, sanitizeRedisKeyComponent, isHashLikeTenantId } from '../core/keyBuilder';\nimport type { Redis } from 'ioredis';\nimport { print } from 'graphql';\nimport type { DocumentNode } from 'graphql';\nimport { injectable, inject } from 'inversify';\nimport { type ICacheContext, type ICachePolicy, type IRedisCacheManager, SERVER_TYPES } from 'common/server';\nimport { CommonType } from '@common-stack/core';\n\n/**\n * Redis Cache Manager implementation\n *\n * Provides GraphQL query caching with automatic key generation and invalidation\n */\n@injectable()\nexport class RedisCacheManager implements IRedisCacheManager {\n protected logger?: any;\n\n constructor(\n @inject(SERVER_TYPES.RedisClient)\n protected readonly redisClient: Redis,\n @inject(CommonType.LOGGER)\n logger?: any,\n ) {\n if (logger) {\n this.logger = logger.child ? logger.child({ className: RedisCacheManager.name }) : logger;\n }\n }\n\n /**\n * Delete cached data for a GraphQL query\n *\n * @param query - GraphQL query to invalidate\n * @param variables - Optional variables (if omitted, clears all variants)\n * @param ctx - Cache context for tenant/user isolation\n * @param shouldRemoveAll - If true, removes all related cache keys\n */\n async del(\n query: string | DocumentNode,\n variables?: Record<string, unknown>,\n ctx?: ICacheContext,\n shouldRemoveAll = false,\n ): Promise<void> {\n // Clear both PUBLIC and PRIVATE versions of the cache\n const privateKey = this.getCacheKey(query, variables ?? {}, ctx, { maxAge: 1, scope: 'PRIVATE' });\n const publicKey = this.getCacheKey(query, variables ?? {}, ctx, { maxAge: 1, scope: 'PUBLIC' });\n\n // If variables provided, delete exact match (both versions)\n if (variables && Object.keys(variables).length > 0) {\n this.log('debug', `Deleting ${privateKey} and ${publicKey} from redis`);\n await this.redisClient.del(privateKey, publicKey);\n return;\n }\n\n // Build wildcard patterns for both PUBLIC and PRIVATE\n const privatePattern = shouldRemoveAll\n ? this.getWildCardQueryKey(query, ctx)\n : `${privateKey.substring(0, privateKey.lastIndexOf(':'))}:*`;\n\n const publicPattern = shouldRemoveAll\n ? this.getWildCardQueryKey(query, ctx)\n : `${publicKey.substring(0, publicKey.lastIndexOf(':'))}:*`;\n\n // Get keys matching both patterns\n const privateCacheKeys = await this.redisClient.keys(privatePattern);\n const publicCacheKeys = await this.redisClient.keys(publicPattern);\n const allKeys = [...new Set([...privateCacheKeys, ...publicCacheKeys])]; // Deduplicate\n\n this.log('debug', `Found ${allKeys.length} keys against patterns ${privatePattern} and ${publicPattern}`);\n\n if (allKeys.length) {\n this.log('debug', `Deleting ${allKeys.length} keys from redis`);\n await this.redisClient.del(...allKeys);\n }\n }\n\n /**\n * Get cached data for a GraphQL query\n *\n * @param query - GraphQL query\n * @param variables - Query variables\n * @param ctx - Cache context\n * @returns Cached data or null if cache miss\n */\n async get<T>(\n query: string | DocumentNode,\n variables: Record<string, unknown>,\n ctx: ICacheContext,\n ): Promise<T | null> {\n // Try PRIVATE key first (with userId)\n let cacheKey = this.getCacheKey(query, variables, ctx, { maxAge: 1, scope: 'PRIVATE' });\n let cacheResponse = await this.redisClient.get(cacheKey);\n\n // If not found, try PUBLIC key (without userId)\n if (!cacheResponse) {\n cacheKey = this.getCacheKey(query, variables, ctx, { maxAge: 1, scope: 'PUBLIC' });\n cacheResponse = await this.redisClient.get(cacheKey);\n }\n\n if (cacheResponse) {\n try {\n const { data } = JSON.parse(JSON.parse(cacheResponse)?.value) ?? {};\n const queryName = this.getQueryName(query);\n this.log('debug', `Found cache for ${cacheKey}`);\n return data?.[queryName] ?? null;\n } catch (error) {\n this.log('warn', `Failed to parse cache data for ${cacheKey}:`, error);\n return null;\n }\n }\n\n this.log('debug', `No cache found for key ${cacheKey}`);\n return null;\n }\n\n /**\n * Set cached data for a GraphQL query\n *\n * @param query - GraphQL query\n * @param variables - Query variables\n * @param data - Data to cache\n * @param ctx - Cache context\n * @param cachePolicy - Cache policy (TTL, scope)\n */\n async set<T>(\n query: string | DocumentNode,\n variables: Record<string, unknown>,\n data: T,\n ctx: ICacheContext,\n cachePolicy: ICachePolicy = { maxAge: 86400, scope: 'PUBLIC' },\n ): Promise<void> {\n const cacheKey = this.getCacheKey(query, variables, ctx, cachePolicy);\n const cacheTime = Date.now();\n\n // Ensure maxAge is not negative or zero\n const maxAge = Math.max(1, cachePolicy.maxAge);\n\n if (cachePolicy.maxAge <= 0) {\n this.log(\n 'warn',\n `Invalid maxAge (${cachePolicy.maxAge}) for cache key ${cacheKey}, using minimum value of 1 second`,\n );\n }\n\n this.log('debug', `Set cache for key ${cacheKey} with maxAge ${maxAge}`);\n\n await this.redisClient.set(\n cacheKey,\n JSON.stringify({\n value: JSON.stringify({\n data: { [this.getQueryName(query)]: data },\n cachePolicy: { ...cachePolicy, maxAge },\n cacheTime,\n }),\n expires: cacheTime + maxAge * 1000,\n }).trim(),\n 'EX',\n maxAge,\n );\n }\n\n /**\n * Extract query name from GraphQL query\n */\n private getQueryName(query: string | DocumentNode): string {\n const queryStr = typeof query === 'string' ? query : print(query);\n const [, queryName] = queryStr?.match(/{\\s*(\\w+)/) ?? [];\n return queryName;\n }\n\n /**\n * Build wildcard pattern for query invalidation\n */\n private getWildCardQueryKey(query: string | DocumentNode, ctx?: ICacheContext): string {\n const queryStr = typeof query === 'string' ? query : print(query);\n const [, queryName] = queryStr?.match(/{\\s*(\\w+)/) ?? [];\n const { tenantId } = ctx || {};\n\n // Build pattern without namespace - just APP_NAME:tenantId:segments\n const appName = this.getAppName();\n const sanitizedTenantId = tenantId || 'default';\n return `${appName}:${sanitizedTenantId}:*:${queryName || '*'}:*`;\n }\n\n /**\n * Generate cache key for a GraphQL query\n *\n * Format:\n * - With userId (PRIVATE): APP_NAME:tenantId:userId:queryName:queryHash:variablesHash\n * - Without userId (PUBLIC): APP_NAME:tenantId:queryName:queryHash:variablesHash\n */\n private getCacheKey(\n query: string | DocumentNode,\n variables: Record<string, unknown>,\n ctx?: ICacheContext,\n cachePolicy?: ICachePolicy,\n ): string {\n const appName = this.getAppName();\n\n // Validate tenantId - if it looks like a raw hash (32+ hex chars), use 'default'\n // MongoDB ObjectIds (24 hex chars) are valid tenant IDs\n let tenantId = ctx?.tenantId || 'default';\n if (isHashLikeTenantId(tenantId)) {\n this.log(\n 'warn',\n `TenantId appears to be a hash (${tenantId.substring(0, 16)}...), using \"default\" instead`,\n );\n tenantId = 'default';\n }\n\n // For PUBLIC scope, omit userId to share cache across all users\n // For PRIVATE scope or when not specified, include userId (or 'anonymous')\n const isPublicScope = cachePolicy?.scope === 'PUBLIC';\n const userId = isPublicScope ? undefined : ctx?.userId || 'anonymous';\n\n // Use generateQueryCacheKey with all parameters to avoid duplication\n // This will properly sanitize userId (auth0|123 -> auth0-123) and build the complete key\n return generateQueryCacheKey({\n query,\n variables,\n appName,\n tenantId,\n userId,\n logger: this.logger,\n });\n }\n\n /**\n * Clear all cache entries for a tenant\n */\n async clearTenant(tenantId: string): Promise<number> {\n const appName = this.getAppName();\n const sanitizedTenantId = sanitizeRedisKeyComponent(tenantId);\n const pattern = `${appName}:${sanitizedTenantId}:*`;\n\n return await this.clearPattern(pattern);\n }\n\n /**\n * Clear all cache entries for a user\n */\n async clearUser(userId: string, tenantId?: string): Promise<number> {\n const appName = this.getAppName();\n const sanitizedUserId = sanitizeRedisKeyComponent(userId);\n const sanitizedTenantId = tenantId ? sanitizeRedisKeyComponent(tenantId) : '*';\n const pattern = `${appName}:${sanitizedTenantId}:${sanitizedUserId}:*`;\n\n return await this.clearPattern(pattern);\n }\n\n /**\n * Clear all cache entries matching a pattern\n */\n async clearPattern(pattern: string): Promise<number> {\n this.log('debug', `Clearing cache entries matching pattern: ${pattern}`);\n\n const keys = await this.redisClient.keys(pattern);\n\n if (keys.length === 0) {\n this.log('debug', `No keys found matching pattern: ${pattern}`);\n return 0;\n }\n\n this.log('debug', `Deleting ${keys.length} keys matching pattern: ${pattern}`);\n await this.redisClient.del(...keys);\n\n return keys.length;\n }\n\n /**\n * Get cache statistics\n */\n async getStats(tenantId?: string): Promise<{\n totalKeys: number;\n memoryUsage: number;\n hitRate?: number;\n }> {\n const appName = this.getAppName();\n const pattern = tenantId ? `${appName}:${sanitizeRedisKeyComponent(tenantId)}:*` : `${appName}:*`;\n\n const keys = await this.redisClient.keys(pattern);\n const totalKeys = keys.length;\n\n // Get memory usage for the Redis instance\n let memoryUsage = 0;\n try {\n const info = await this.redisClient.info('memory');\n const match = info.match(/used_memory:(\\d+)/);\n if (match) {\n memoryUsage = parseInt(match[1], 10);\n }\n } catch (error) {\n this.log('warn', 'Failed to get memory info from Redis:', error);\n }\n\n return {\n totalKeys,\n memoryUsage,\n hitRate: undefined, // Hit rate tracking would require additional instrumentation\n };\n }\n\n /**\n * Get application name for key prefix\n * Override this method to provide custom app name\n */\n protected getAppName(): string {\n return process.env.APP_NAME || 'COMMON_STACK';\n }\n\n /**\n * Log helper\n */\n private log(level: string, message: string, ...args: any[]): void {\n if (this.logger && typeof this.logger[level] === 'function') {\n this.logger[level](message, ...args);\n }\n }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@common-stack/store-redis",
3
- "version": "8.6.1-alpha.13",
3
+ "version": "8.6.1-alpha.14",
4
4
  "description": "Redis store utilities and services for common-stack",
5
5
  "license": "UNLICENSED",
6
6
  "author": "CDMBase LLC",
@@ -54,7 +54,7 @@
54
54
  ]
55
55
  }
56
56
  },
57
- "gitHead": "af861507bb86e230ec4e93f09ad8c8987519da90",
57
+ "gitHead": "e17c403046616635bada2cd0169293a8195e76f6",
58
58
  "typescript": {
59
59
  "definition": "lib/index.d.ts"
60
60
  }