@common-stack/store-redis 8.2.5-alpha.33 → 8.2.5-alpha.35

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.
Files changed (41) hide show
  1. package/lib/containers/container.d.ts +2 -0
  2. package/lib/containers/container.js +9 -0
  3. package/lib/containers/index.d.ts +1 -0
  4. package/lib/core/index.d.ts +3 -0
  5. package/lib/core/ioredis.d.ts +15 -0
  6. package/lib/core/ioredis.js +31 -0
  7. package/lib/core/keyBuilder/generate-query-cache-key.d.ts +57 -0
  8. package/lib/core/keyBuilder/generate-query-cache-key.js +88 -0
  9. package/lib/core/keyBuilder/index.d.ts +18 -0
  10. package/lib/core/keyBuilder/index.js +27 -0
  11. package/lib/core/keyBuilder/redis-key-builder.d.ts +152 -0
  12. package/lib/core/keyBuilder/redis-key-builder.js +185 -0
  13. package/lib/core/keyBuilder/sanitize-redis-key.d.ts +118 -0
  14. package/lib/core/keyBuilder/sanitize-redis-key.js +120 -0
  15. package/lib/core/upstash-redis.d.ts +14 -0
  16. package/lib/core/upstash-redis.js +27 -0
  17. package/lib/index.d.ts +2 -1
  18. package/lib/index.js +7 -3
  19. package/lib/interfaces/index.d.ts +1 -6
  20. package/lib/interfaces/redis.d.ts +11 -0
  21. package/lib/module.d.ts +2 -0
  22. package/lib/module.js +9 -0
  23. package/lib/services/RedisCacheManager.d.ts +77 -0
  24. package/lib/services/RedisCacheManager.js +185 -0
  25. package/lib/services/index.d.ts +1 -5
  26. package/lib/templates/constants/SERVER_TYPES.ts.template +0 -1
  27. package/lib/templates/repositories/IRedisKeyBuilder.ts.template +4 -4
  28. package/lib/templates/repositories/redisCommonTypes.ts.template +2 -163
  29. package/lib/templates/{repositories/IRedisCacheManager.ts.template → services/RedisCacheManager.ts.template} +7 -7
  30. package/package.json +8 -7
  31. package/lib/interfaces/cache-manager.d.ts +0 -28
  32. package/lib/interfaces/redis-key-options.d.ts +0 -35
  33. package/lib/interfaces/redis-key-options.js +0 -17
  34. package/lib/interfaces/storage-backend.d.ts +0 -17
  35. package/lib/templates/repositories/IRedisService.ts.template +0 -236
  36. package/lib/templates/repositories/IRedisStorageBackend.ts.template +0 -229
  37. package/lib/utils/index.d.ts +0 -5
  38. package/lib/utils/redis-key-builder.d.ts +0 -32
  39. package/lib/utils/redis-key-builder.js +0 -68
  40. package/lib/utils/redis-key-sanitizer.d.ts +0 -30
  41. package/lib/utils/redis-key-sanitizer.js +0 -54
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Sanitizes a Redis key component to ensure it's valid and safe
3
+ *
4
+ * Redis keys can contain any binary sequence, but for best practices and compatibility:
5
+ * - Avoid whitespace (spaces, tabs, newlines) - replace with underscores
6
+ * - Avoid pipes (|) which are used in Auth0 userIds - replace with hyphens
7
+ * - Avoid quotes and backslashes that could cause escaping issues - remove them
8
+ * - Avoid control characters - remove them
9
+ *
10
+ * @param str - The string component to sanitize
11
+ * @returns Sanitized string safe for use in Redis keys
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * sanitizeRedisKeyComponent('auth0|6120ecbb1e5c68006aabe17a')
16
+ * // Returns: 'auth0-6120ecbb1e5c68006aabe17a'
17
+ *
18
+ * sanitizeRedisKeyComponent('user name with spaces')
19
+ * // Returns: 'user_name_with_spaces'
20
+ * ```
21
+ */
22
+ export declare const sanitizeRedisKeyComponent: (str: string) => string;
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
29
+ */
30
+ export declare const isHashLikeTenantId: (tenantId: string) => boolean;
31
+ /**
32
+ * Builds a standardized Redis key with proper tenant isolation and sanitization
33
+ *
34
+ * Key format: `{appName}:{tenantId}:{userId}:{...segments}`
35
+ * - All components are sanitized to remove invalid characters
36
+ * - TenantId is validated and replaced with 'default' if it appears to be a hash
37
+ * - Components can be skipped by passing null/undefined
38
+ *
39
+ * @param options - Key building options
40
+ * @param options.appName - Application name (e.g., 'CDMBASE_TEST')
41
+ * @param options.tenantId - Tenant identifier (validated against hash pattern)
42
+ * @param options.userId - User identifier (sanitized for Auth0 format)
43
+ * @param options.segments - Additional key segments (array of strings)
44
+ * @param options.logger - Optional logger for warnings
45
+ * @returns Constructed Redis key
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * buildRedisKey({
50
+ * appName: 'CDMBASE_TEST',
51
+ * tenantId: 'default',
52
+ * userId: 'auth0|6120ecbb',
53
+ * segments: ['currentPagePermissions', 'hash123']
54
+ * })
55
+ * // Returns: 'CDMBASE_TEST:default:auth0-6120ecbb:currentPagePermissions:hash123'
56
+ * ```
57
+ */
58
+ export interface BuildRedisKeyOptions {
59
+ appName: string;
60
+ tenantId?: string;
61
+ userId?: string;
62
+ segments?: string[];
63
+ logger?: {
64
+ warn: (message: string, ...args: unknown[]) => void;
65
+ };
66
+ }
67
+ export declare const buildRedisKey: ({ appName, tenantId, userId, segments, logger }: BuildRedisKeyOptions) => string;
68
+ /**
69
+ * Builds a wildcard pattern for Redis key matching
70
+ * Useful for bulk operations like deleting all keys matching a pattern
71
+ *
72
+ * @param options - Pattern building options (same as buildRedisKey)
73
+ * @returns Redis key pattern with wildcards
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * buildRedisKeyPattern({
78
+ * appName: 'CDMBASE_TEST',
79
+ * tenantId: 'default',
80
+ * segments: ['currentPagePermissions']
81
+ * })
82
+ * // Returns: 'CDMBASE_TEST:default:*:currentPagePermissions:*'
83
+ * ```
84
+ */
85
+ export declare const buildRedisKeyPattern: ({ appName, tenantId, userId, segments, }: Omit<BuildRedisKeyOptions, "logger">) => string;
86
+ /**
87
+ * Sanitizes a complete Redis key by sanitizing each component
88
+ *
89
+ * @param key - Redis key to sanitize
90
+ * @returns Sanitized Redis key
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * sanitizeRedisKey('APP:tenant with spaces:cache:user|data')
95
+ * // Returns: 'APP:tenant_with_spaces:cache:user-data'
96
+ * ```
97
+ */
98
+ export declare const sanitizeRedisKey: (key: string) => string;
99
+ /**
100
+ * Escapes special Redis pattern characters for literal matching
101
+ *
102
+ * @param pattern - Pattern string to escape
103
+ * @returns Escaped pattern string
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * escapeRedisPattern('file*.txt')
108
+ * // Returns: 'file\\*.txt'
109
+ * ```
110
+ */
111
+ export declare const escapeRedisPattern: (pattern: string) => string;
112
+ /**
113
+ * Validates if a Redis key is properly formatted
114
+ *
115
+ * @param key - Redis key to validate
116
+ * @returns true if key is valid, false otherwise
117
+ */
118
+ export declare const isValidRedisKey: (key: string) => boolean;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Sanitizes a Redis key component to ensure it's valid and safe
3
+ *
4
+ * Redis keys can contain any binary sequence, but for best practices and compatibility:
5
+ * - Avoid whitespace (spaces, tabs, newlines) - replace with underscores
6
+ * - Avoid pipes (|) which are used in Auth0 userIds - replace with hyphens
7
+ * - Avoid quotes and backslashes that could cause escaping issues - remove them
8
+ * - Avoid control characters - remove them
9
+ *
10
+ * @param str - The string component to sanitize
11
+ * @returns Sanitized string safe for use in Redis keys
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * sanitizeRedisKeyComponent('auth0|6120ecbb1e5c68006aabe17a')
16
+ * // Returns: 'auth0-6120ecbb1e5c68006aabe17a'
17
+ *
18
+ * sanitizeRedisKeyComponent('user name with spaces')
19
+ * // Returns: 'user_name_with_spaces'
20
+ * ```
21
+ */
22
+ const sanitizeRedisKeyComponent = (str) => {
23
+ if (!str)
24
+ return str;
25
+ return (str
26
+ .replace(/[\s\r\n\t]+/g, '_') // Replace whitespace with underscores
27
+ .replace(/\|/g, '-') // Replace pipes with hyphens (Auth0 userIds: auth0|123 -> auth0-123)
28
+ .replace(/["'\\]/g, '') // Remove quotes and backslashes
29
+ // eslint-disable-next-line no-control-regex
30
+ .replace(/[\x00-\x1F\x7F]/g, '') // Remove control characters
31
+ .trim());
32
+ };
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
39
+ */
40
+ const isHashLikeTenantId = (tenantId) => {
41
+ if (!tenantId || tenantId === 'default')
42
+ return false;
43
+ return /^[a-f0-9]{24,}$/i.test(tenantId);
44
+ };
45
+ const buildRedisKey = ({ appName, tenantId, userId, segments = [], logger }) => {
46
+ // Validate and sanitize tenantId
47
+ let sanitizedTenantId = tenantId || 'default';
48
+ if (sanitizedTenantId !== 'default' && isHashLikeTenantId(sanitizedTenantId)) {
49
+ if (logger) {
50
+ logger.warn('TenantId appears to be a hash (%s...), using "default" instead', sanitizedTenantId.substring(0, 16));
51
+ }
52
+ sanitizedTenantId = 'default';
53
+ }
54
+ // Build key parts
55
+ const parts = [sanitizeRedisKeyComponent(appName), sanitizeRedisKeyComponent(sanitizedTenantId)];
56
+ // Add userId if provided
57
+ if (userId) {
58
+ parts.push(sanitizeRedisKeyComponent(userId));
59
+ }
60
+ // Add additional segments
61
+ if (segments && segments.length > 0) {
62
+ parts.push(...segments.map((seg) => sanitizeRedisKeyComponent(seg)));
63
+ }
64
+ return parts.join(':');
65
+ };
66
+ /**
67
+ * Sanitizes a complete Redis key by sanitizing each component
68
+ *
69
+ * @param key - Redis key to sanitize
70
+ * @returns Sanitized Redis key
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * sanitizeRedisKey('APP:tenant with spaces:cache:user|data')
75
+ * // Returns: 'APP:tenant_with_spaces:cache:user-data'
76
+ * ```
77
+ */
78
+ const sanitizeRedisKey = (key) => {
79
+ if (!key)
80
+ return '';
81
+ return key
82
+ .split(':')
83
+ .map(sanitizeRedisKeyComponent)
84
+ .join(':');
85
+ };
86
+ /**
87
+ * Escapes special Redis pattern characters for literal matching
88
+ *
89
+ * @param pattern - Pattern string to escape
90
+ * @returns Escaped pattern string
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * escapeRedisPattern('file*.txt')
95
+ * // Returns: 'file\\*.txt'
96
+ * ```
97
+ */
98
+ const escapeRedisPattern = (pattern) => {
99
+ if (!pattern)
100
+ return '';
101
+ return pattern.replace(/[*?[\]^\\]/g, '\\$&');
102
+ };
103
+ /**
104
+ * Validates if a Redis key is properly formatted
105
+ *
106
+ * @param key - Redis key to validate
107
+ * @returns true if key is valid, false otherwise
108
+ */
109
+ const isValidRedisKey = (key) => {
110
+ if (!key || typeof key !== 'string')
111
+ return false;
112
+ if (key.length > 512)
113
+ return false; // Redis key length limit
114
+ // Check for invalid characters (whitespace, pipes, etc.)
115
+ if (/[\s|"'\\]/.test(key))
116
+ return false;
117
+ return true;
118
+ };
119
+
120
+ export { buildRedisKey, escapeRedisPattern, isHashLikeTenantId, isValidRedisKey, sanitizeRedisKey, sanitizeRedisKeyComponent };
@@ -0,0 +1,14 @@
1
+ import { IRedisClient } from 'common/server';
2
+ export declare class UpstashRedisClient implements IRedisClient {
3
+ private redis;
4
+ constructor(config: {
5
+ url: string;
6
+ token?: string;
7
+ });
8
+ get(key: string): Promise<any | null>;
9
+ set(key: string, value: string, options?: {
10
+ ex?: number;
11
+ }): Promise<string>;
12
+ delMulti(keys: string[]): Promise<any>;
13
+ del(key: string): Promise<number>;
14
+ }
@@ -0,0 +1,27 @@
1
+ import { Redis } from '@upstash/redis/cloudflare';
2
+
3
+ class UpstashRedisClient {
4
+ redis;
5
+ constructor(config) {
6
+ this.redis = new Redis({ url: config.url, token: config.token });
7
+ }
8
+ async get(key) {
9
+ return await this.redis.get(key);
10
+ }
11
+ async set(key, value, options) {
12
+ if (options?.ex) {
13
+ return await this.redis.set(key, value, { ex: options?.ex });
14
+ }
15
+ else {
16
+ return await this.redis.set(key, value);
17
+ }
18
+ }
19
+ async delMulti(keys) {
20
+ return await Promise.all(keys.map((key) => this.redis.del(key)));
21
+ }
22
+ async del(key) {
23
+ return await this.redis.del(key);
24
+ }
25
+ }
26
+
27
+ export { UpstashRedisClient };
package/lib/index.d.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * - Storage backends
10
10
  * - Redis patterns and utilities
11
11
  */
12
+ export * from './core';
13
+ export * from './services';
12
14
  export * from './interfaces';
13
15
  export * from './services';
14
- export * from './utils';
package/lib/index.js CHANGED
@@ -1,3 +1,7 @@
1
- export { RedisNamespace } from './interfaces/redis-key-options.js';
2
- export { buildRedisKey, buildRedisKeyPattern, isValidRedisKey } from './utils/redis-key-builder.js';
3
- export { escapeRedisPattern, sanitizeKeyComponent, sanitizeRedisKey } from './utils/redis-key-sanitizer.js';
1
+ export { IORedisClient } from './core/ioredis.js';
2
+ export { UpstashRedisClient } from './core/upstash-redis.js';
3
+ export { buildRedisKey, buildRedisKeyPattern } from './core/keyBuilder/index.js';
4
+ export { RedisCacheManager } from './services/RedisCacheManager.js';
5
+ export { escapeRedisPattern, isHashLikeTenantId, isValidRedisKey, sanitizeRedisKey, sanitizeRedisKeyComponent } from './core/keyBuilder/sanitize-redis-key.js';
6
+ export { RedisNamespace, buildRedisKeyPatternWithNamespace, buildRedisKeyWithNamespace, extractNamespaceFromRedisKey, extractTenantIdFromRedisKey, isValidRedisKeyWithNamespace, parseRedisKey } from './core/keyBuilder/redis-key-builder.js';
7
+ export { generateQueryCacheKey } from './core/keyBuilder/generate-query-cache-key.js';
@@ -1,6 +1 @@
1
- /**
2
- * Interfaces for Redis store operations
3
- */
4
- export * from './redis-key-options';
5
- export * from './storage-backend';
6
- export * from './cache-manager';
1
+ export * from './redis';
@@ -0,0 +1,11 @@
1
+ export interface IRedisClient {
2
+ get?(key: string): Promise<string | null>;
3
+ get?(key: string, callback?: (...args: any[]) => void): any;
4
+ set(key: string, value: string, options?: {
5
+ ex?: number;
6
+ }): Promise<string>;
7
+ del(key: string): Promise<number>;
8
+ delMulti(keys: string[]): Promise<any>;
9
+ on?(event: string | symbol, callback: (...args: any[]) => void): any;
10
+ once?(event: string | symbol, callback: (...args: any[]) => void): any;
11
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
package/lib/module.js ADDED
@@ -0,0 +1,9 @@
1
+ import { Feature } from '@common-stack/server-core';
2
+ import { infraContainer } from './containers/container.js';
3
+
4
+ var module = new Feature({
5
+ createContainerFunc: [infraContainer],
6
+ createMicroServiceContainerFunc: [infraContainer],
7
+ });
8
+
9
+ export { module as default };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @file RedisCacheManager.ts
3
+ * @description Redis-based cache manager for GraphQL query caching
4
+ *
5
+ * This implementation provides sophisticated caching for GraphQL operations with:
6
+ * - Automatic query hashing for cache keys
7
+ * - Multi-tenant and user isolation
8
+ * - Wildcard-based cache invalidation
9
+ * - TTL-based expiration
10
+ * - Automatic key sanitization (handles Auth0 userIds with pipes)
11
+ *
12
+ * Migrated from @adminide-stack/platform-server to be shared across applications
13
+ */
14
+ import type { Redis } from 'ioredis';
15
+ import type { DocumentNode } from 'graphql';
16
+ import type { ICacheContext, ICachePolicy, IRedisCacheManager } from 'common/server';
17
+ /**
18
+ * Redis Cache Manager implementation
19
+ *
20
+ * Provides GraphQL query caching with automatic key generation and invalidation
21
+ */
22
+ export declare class RedisCacheManager implements IRedisCacheManager {
23
+ protected readonly redisClient: Redis;
24
+ protected logger?: any;
25
+ constructor(redisClient: Redis, logger?: any);
26
+ /**
27
+ * Delete cached data for a GraphQL query
28
+ *
29
+ * @param query - GraphQL query to invalidate
30
+ * @param variables - Optional variables (if omitted, clears all variants)
31
+ * @param ctx - Cache context for tenant/user isolation
32
+ * @param shouldRemoveAll - If true, removes all related cache keys
33
+ */
34
+ del(query: string | DocumentNode, variables?: Record<string, unknown>, ctx?: ICacheContext, shouldRemoveAll?: boolean): Promise<void>;
35
+ /**
36
+ * Get cached data for a GraphQL query
37
+ *
38
+ * @param query - GraphQL query
39
+ * @param variables - Query variables
40
+ * @param ctx - Cache context
41
+ * @returns Cached data or null if cache miss
42
+ */
43
+ get<T>(query: string | DocumentNode, variables: Record<string, unknown>, ctx: ICacheContext): Promise<T | null>;
44
+ /**
45
+ * Set cached data for a GraphQL query
46
+ *
47
+ * @param query - GraphQL query
48
+ * @param variables - Query variables
49
+ * @param data - Data to cache
50
+ * @param ctx - Cache context
51
+ * @param cachePolicy - Cache policy (TTL, scope)
52
+ */
53
+ set<T>(query: string | DocumentNode, variables: Record<string, unknown>, data: T, ctx: ICacheContext, cachePolicy?: ICachePolicy): Promise<void>;
54
+ /**
55
+ * Extract query name from GraphQL query
56
+ */
57
+ private getQueryName;
58
+ /**
59
+ * Build wildcard pattern for query invalidation
60
+ */
61
+ private getWildCardQueryKey;
62
+ /**
63
+ * Generate cache key for a GraphQL query
64
+ *
65
+ * Format: APP_NAME:tenantId:userId:queryName:queryHash:variablesHash
66
+ */
67
+ private getCacheKey;
68
+ /**
69
+ * Get application name for key prefix
70
+ * Override this method to provide custom app name
71
+ */
72
+ protected getAppName(): string;
73
+ /**
74
+ * Log helper
75
+ */
76
+ private log;
77
+ }
@@ -0,0 +1,185 @@
1
+ import { __decorate, __param, __metadata } from 'tslib';
2
+ import { isHashLikeTenantId, sanitizeRedisKeyComponent } from '../core/keyBuilder/sanitize-redis-key.js';
3
+ import { generateQueryCacheKey } from '../core/keyBuilder/generate-query-cache-key.js';
4
+ import { print } from 'graphql';
5
+ import { injectable, inject } from 'inversify';
6
+
7
+ /**
8
+ * @file RedisCacheManager.ts
9
+ * @description Redis-based cache manager for GraphQL query caching
10
+ *
11
+ * This implementation provides sophisticated caching for GraphQL operations with:
12
+ * - Automatic query hashing for cache keys
13
+ * - Multi-tenant and user isolation
14
+ * - Wildcard-based cache invalidation
15
+ * - TTL-based expiration
16
+ * - Automatic key sanitization (handles Auth0 userIds with pipes)
17
+ *
18
+ * Migrated from @adminide-stack/platform-server to be shared across applications
19
+ */
20
+ var RedisCacheManager_1;
21
+ /**
22
+ * Redis Cache Manager implementation
23
+ *
24
+ * Provides GraphQL query caching with automatic key generation and invalidation
25
+ */
26
+ let RedisCacheManager = RedisCacheManager_1 = class RedisCacheManager {
27
+ redisClient;
28
+ logger;
29
+ constructor(redisClient, logger) {
30
+ this.redisClient = redisClient;
31
+ if (logger) {
32
+ this.logger = logger.child ? logger.child({ className: RedisCacheManager_1.name }) : logger;
33
+ }
34
+ }
35
+ /**
36
+ * Delete cached data for a GraphQL query
37
+ *
38
+ * @param query - GraphQL query to invalidate
39
+ * @param variables - Optional variables (if omitted, clears all variants)
40
+ * @param ctx - Cache context for tenant/user isolation
41
+ * @param shouldRemoveAll - If true, removes all related cache keys
42
+ */
43
+ async del(query, variables, ctx, shouldRemoveAll = false) {
44
+ const cacheKey = this.getCacheKey(query, variables ?? {}, ctx);
45
+ // If variables provided, delete exact match
46
+ if (variables && Object.keys(variables).length > 0) {
47
+ this.log('debug', `Deleting ${cacheKey} from redis`);
48
+ await this.redisClient.del(cacheKey);
49
+ return;
50
+ }
51
+ // Build wildcard pattern
52
+ const keysWithWildCard = shouldRemoveAll
53
+ ? this.getWildCardQueryKey(query, ctx)
54
+ : `${cacheKey.substring(0, cacheKey.lastIndexOf(':'))}:*`;
55
+ const cacheKeys = await this.redisClient.keys(keysWithWildCard);
56
+ this.log('debug', `Found ${cacheKeys.length} keys against pattern ${keysWithWildCard}`);
57
+ if (cacheKeys.length) {
58
+ this.log('debug', `Deleting ${cacheKeys.length} keys from redis`);
59
+ await this.redisClient.del(...cacheKeys);
60
+ }
61
+ }
62
+ /**
63
+ * Get cached data for a GraphQL query
64
+ *
65
+ * @param query - GraphQL query
66
+ * @param variables - Query variables
67
+ * @param ctx - Cache context
68
+ * @returns Cached data or null if cache miss
69
+ */
70
+ async get(query, variables, ctx) {
71
+ const cacheKey = this.getCacheKey(query, variables, ctx);
72
+ const cacheResponse = await this.redisClient.get(cacheKey);
73
+ if (cacheResponse) {
74
+ try {
75
+ const { data } = JSON.parse(JSON.parse(cacheResponse)?.value) ?? {};
76
+ const queryName = this.getQueryName(query);
77
+ this.log('debug', `Found cache for ${cacheKey}`);
78
+ return data?.[queryName] ?? null;
79
+ }
80
+ catch (error) {
81
+ this.log('warn', `Failed to parse cache data for ${cacheKey}:`, error);
82
+ return null;
83
+ }
84
+ }
85
+ this.log('debug', `No cache found for key ${cacheKey}`);
86
+ return null;
87
+ }
88
+ /**
89
+ * Set cached data for a GraphQL query
90
+ *
91
+ * @param query - GraphQL query
92
+ * @param variables - Query variables
93
+ * @param data - Data to cache
94
+ * @param ctx - Cache context
95
+ * @param cachePolicy - Cache policy (TTL, scope)
96
+ */
97
+ async set(query, variables, data, ctx, cachePolicy = { maxAge: 86400, scope: 'PUBLIC' }) {
98
+ const cacheKey = this.getCacheKey(query, variables, ctx);
99
+ const cacheTime = Date.now();
100
+ // Ensure maxAge is not negative or zero
101
+ const maxAge = Math.max(1, cachePolicy.maxAge);
102
+ if (cachePolicy.maxAge <= 0) {
103
+ this.log('warn', `Invalid maxAge (${cachePolicy.maxAge}) for cache key ${cacheKey}, using minimum value of 1 second`);
104
+ }
105
+ this.log('debug', `Set cache for key ${cacheKey} with maxAge ${maxAge}`);
106
+ await this.redisClient.set(cacheKey, JSON.stringify({
107
+ value: JSON.stringify({
108
+ data: { [this.getQueryName(query)]: data },
109
+ cachePolicy: { ...cachePolicy, maxAge },
110
+ cacheTime,
111
+ }),
112
+ expires: cacheTime + maxAge * 1000,
113
+ }).trim(), 'EX', maxAge);
114
+ }
115
+ /**
116
+ * Extract query name from GraphQL query
117
+ */
118
+ getQueryName(query) {
119
+ const queryStr = typeof query === 'string' ? query : print(query);
120
+ const [, queryName] = queryStr?.match(/{\s*(\w+)/) ?? [];
121
+ return queryName;
122
+ }
123
+ /**
124
+ * Build wildcard pattern for query invalidation
125
+ */
126
+ getWildCardQueryKey(query, ctx) {
127
+ const queryStr = typeof query === 'string' ? query : print(query);
128
+ const [, queryName] = queryStr?.match(/{\s*(\w+)/) ?? [];
129
+ const { tenantId } = ctx || {};
130
+ // Build pattern without namespace - just APP_NAME:tenantId:segments
131
+ const appName = this.getAppName();
132
+ const sanitizedTenantId = tenantId || 'default';
133
+ return `${appName}:${sanitizedTenantId}:*:${queryName || '*'}:*`;
134
+ }
135
+ /**
136
+ * Generate cache key for a GraphQL query
137
+ *
138
+ * Format: APP_NAME:tenantId:userId:queryName:queryHash:variablesHash
139
+ */
140
+ getCacheKey(query, variables, ctx) {
141
+ // Generate the legacy key WITHOUT tenantId/userId since we'll add them via manual construction
142
+ const legacyKey = generateQueryCacheKey({
143
+ query,
144
+ variables,
145
+ logger: this.logger,
146
+ });
147
+ // Build key without namespace - format: APP_NAME:tenantId:userId:legacyKey
148
+ const appName = this.getAppName();
149
+ // Validate tenantId - if it looks like a hash (all hex, 24+ chars), use 'default'
150
+ let tenantId = ctx?.tenantId || 'default';
151
+ if (isHashLikeTenantId(tenantId)) {
152
+ this.log('warn', `TenantId appears to be a hash (${tenantId.substring(0, 16)}...), using "default" instead`);
153
+ tenantId = 'default';
154
+ }
155
+ const userId = ctx?.userId || 'anonymous';
156
+ // Sanitize components (handles Auth0 userIds like "auth0|123")
157
+ const sanitizedAppName = sanitizeRedisKeyComponent(appName);
158
+ const sanitizedTenantId = sanitizeRedisKeyComponent(tenantId);
159
+ const sanitizedUserId = sanitizeRedisKeyComponent(userId);
160
+ return `${sanitizedAppName}:${sanitizedTenantId}:${sanitizedUserId}:${legacyKey}`;
161
+ }
162
+ /**
163
+ * Get application name for key prefix
164
+ * Override this method to provide custom app name
165
+ */
166
+ getAppName() {
167
+ return process.env.APP_NAME || 'COMMON_STACK';
168
+ }
169
+ /**
170
+ * Log helper
171
+ */
172
+ log(level, message, ...args) {
173
+ if (this.logger && typeof this.logger[level] === 'function') {
174
+ this.logger[level](message, ...args);
175
+ }
176
+ }
177
+ };
178
+ RedisCacheManager = RedisCacheManager_1 = __decorate([
179
+ injectable(),
180
+ __param(0, inject('REDIS_CLIENT')),
181
+ __param(1, inject('LOGGER')),
182
+ __metadata("design:paramtypes", [Function, Object])
183
+ ], RedisCacheManager);
184
+
185
+ export { RedisCacheManager };
@@ -1,8 +1,4 @@
1
1
  /**
2
2
  * Redis service implementations
3
- *
4
- * Note: Actual implementations will be migrated from:
5
- * - @adminide-stack/platform-server/RedisCacheManager
6
- * - @adminide-stack/auth0-server-core/RedisStorageBackend
7
3
  */
8
- export {};
4
+ export * from './RedisCacheManager';
@@ -1,7 +1,6 @@
1
1
  export const SERVER_TYPES = {
2
2
  RedisClient: Symbol.for('RedisClient'),
3
3
  RedisCacheManager: Symbol.for('RedisCacheManager'),
4
- RedisStorageBackend: Symbol.for('RedisStorageBackend'),
5
4
  RedisConnectionPool: Symbol.for('RedisConnectionPool'),
6
5
  RedisKeyBuilder: Symbol.for('RedisKeyBuilder'),
7
6
  };
@@ -22,11 +22,11 @@
22
22
  * This pattern helps prevent key collisions, makes debugging easier, and enables
23
23
  * efficient bulk operations through pattern matching.
24
24
  *
25
- * @see RedisKeyOptions - Configuration for key building
25
+ * @see IRedisKeyOptions - Configuration for key building
26
26
  * @see RedisNamespace - Standard namespaces for key organization
27
27
  */
28
28
 
29
- import { RedisKeyOptions } from './redisCommonTypes';
29
+ import { IRedisKeyOptions } from 'common/server';
30
30
 
31
31
  export interface IRedisKeyBuilder {
32
32
  /**
@@ -56,7 +56,7 @@ export interface IRedisKeyBuilder {
56
56
  * });
57
57
  * // Result: "APP_NAME:tenant-123:user-456:session:session-789"
58
58
  */
59
- buildKey(options: RedisKeyOptions): string;
59
+ buildKey(options: IRedisKeyOptions): string;
60
60
 
61
61
  /**
62
62
  * Build a Redis key pattern for wildcard matching
@@ -82,7 +82,7 @@ export interface IRedisKeyBuilder {
82
82
  * segments: ['*']
83
83
  * });
84
84
  */
85
- buildPattern(options: RedisKeyOptions): string;
85
+ buildPattern(options: IRedisKeyOptions): string;
86
86
 
87
87
  /**
88
88
  * Sanitize a key component to ensure it's valid and safe