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

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 (52) hide show
  1. package/lib/containers/container.d.ts +2 -0
  2. package/lib/containers/container.js +3 -0
  3. package/lib/containers/container.js.map +1 -0
  4. package/lib/containers/index.d.ts +1 -0
  5. package/lib/core/index.d.ts +3 -0
  6. package/lib/core/ioredis.d.ts +15 -0
  7. package/lib/core/ioredis.js +27 -0
  8. package/lib/core/ioredis.js.map +1 -0
  9. package/lib/core/keyBuilder/generate-query-cache-key.d.ts +57 -0
  10. package/lib/core/keyBuilder/generate-query-cache-key.js +82 -0
  11. package/lib/core/keyBuilder/generate-query-cache-key.js.map +1 -0
  12. package/lib/core/keyBuilder/index.d.ts +18 -0
  13. package/lib/core/keyBuilder/index.js +20 -0
  14. package/lib/core/keyBuilder/index.js.map +1 -0
  15. package/lib/core/keyBuilder/redis-key-builder.d.ts +152 -0
  16. package/lib/core/keyBuilder/redis-key-builder.js +181 -0
  17. package/lib/core/keyBuilder/redis-key-builder.js.map +1 -0
  18. package/lib/core/keyBuilder/sanitize-redis-key.d.ts +118 -0
  19. package/lib/core/keyBuilder/sanitize-redis-key.js +115 -0
  20. package/lib/core/keyBuilder/sanitize-redis-key.js.map +1 -0
  21. package/lib/core/upstash-redis.d.ts +14 -0
  22. package/lib/core/upstash-redis.js +23 -0
  23. package/lib/core/upstash-redis.js.map +1 -0
  24. package/lib/graphql/schema/base-services.graphql +134 -0
  25. package/lib/index.d.ts +2 -1
  26. package/lib/index.js +1 -3
  27. package/lib/index.js.map +1 -0
  28. package/lib/interfaces/index.d.ts +1 -6
  29. package/lib/interfaces/redis.d.ts +11 -0
  30. package/lib/module.d.ts +2 -0
  31. package/lib/module.js +4 -0
  32. package/lib/module.js.map +1 -0
  33. package/lib/services/RedisCacheManager.d.ts +77 -0
  34. package/lib/services/RedisCacheManager.js +177 -0
  35. package/lib/services/RedisCacheManager.js.map +1 -0
  36. package/lib/services/index.d.ts +1 -5
  37. package/lib/templates/constants/SERVER_TYPES.ts.template +0 -1
  38. package/lib/templates/repositories/IRedisKeyBuilder.ts.template +4 -4
  39. package/lib/templates/repositories/redisCommonTypes.ts.template +2 -163
  40. package/lib/templates/{repositories/IRedisCacheManager.ts.template → services/RedisCacheManager.ts.template} +7 -7
  41. package/package.json +8 -7
  42. package/lib/interfaces/cache-manager.d.ts +0 -28
  43. package/lib/interfaces/redis-key-options.d.ts +0 -35
  44. package/lib/interfaces/redis-key-options.js +0 -17
  45. package/lib/interfaces/storage-backend.d.ts +0 -17
  46. package/lib/templates/repositories/IRedisService.ts.template +0 -236
  47. package/lib/templates/repositories/IRedisStorageBackend.ts.template +0 -229
  48. package/lib/utils/index.d.ts +0 -5
  49. package/lib/utils/redis-key-builder.d.ts +0 -32
  50. package/lib/utils/redis-key-builder.js +0 -68
  51. package/lib/utils/redis-key-sanitizer.d.ts +0 -30
  52. 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,115 @@
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.split(':').map(sanitizeRedisKeyComponent).join(':');
82
+ };
83
+ /**
84
+ * Escapes special Redis pattern characters for literal matching
85
+ *
86
+ * @param pattern - Pattern string to escape
87
+ * @returns Escaped pattern string
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * escapeRedisPattern('file*.txt')
92
+ * // Returns: 'file\\*.txt'
93
+ * ```
94
+ */
95
+ const escapeRedisPattern = (pattern) => {
96
+ if (!pattern)
97
+ return '';
98
+ return pattern.replace(/[*?[\]^\\]/g, '\\$&');
99
+ };
100
+ /**
101
+ * Validates if a Redis key is properly formatted
102
+ *
103
+ * @param key - Redis key to validate
104
+ * @returns true if key is valid, false otherwise
105
+ */
106
+ const isValidRedisKey = (key) => {
107
+ if (!key || typeof key !== 'string')
108
+ return false;
109
+ if (key.length > 512)
110
+ return false; // Redis key length limit
111
+ // Check for invalid characters (whitespace, pipes, etc.)
112
+ if (/[\s|"'\\]/.test(key))
113
+ return false;
114
+ return true;
115
+ };export{buildRedisKey,escapeRedisPattern,isHashLikeTenantId,isValidRedisKey,sanitizeRedisKey,sanitizeRedisKeyComponent};//# sourceMappingURL=sanitize-redis-key.js.map
@@ -0,0 +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"}
@@ -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,23 @@
1
+ import {Redis}from'@upstash/redis/cloudflare';class UpstashRedisClient {
2
+ redis;
3
+ constructor(config) {
4
+ this.redis = new Redis({ url: config.url, token: config.token });
5
+ }
6
+ async get(key) {
7
+ return await this.redis.get(key);
8
+ }
9
+ async set(key, value, options) {
10
+ if (options?.ex) {
11
+ return await this.redis.set(key, value, { ex: options?.ex });
12
+ }
13
+ else {
14
+ return await this.redis.set(key, value);
15
+ }
16
+ }
17
+ async delMulti(keys) {
18
+ return await Promise.all(keys.map((key) => this.redis.del(key)));
19
+ }
20
+ async del(key) {
21
+ return await this.redis.del(key);
22
+ }
23
+ }export{UpstashRedisClient};//# sourceMappingURL=upstash-redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upstash-redis.js","sources":["../../src/core/upstash-redis.ts"],"sourcesContent":["import { Redis } from '@upstash/redis/cloudflare';\nimport { IRedisClient } from 'common/server';\n\nexport class UpstashRedisClient implements IRedisClient {\n private redis: Redis;\n\n constructor(config: { url: string; token?: string }) {\n this.redis = new Redis({ url: config.url, token: config.token });\n }\n\n async get(key: string): Promise<any | null> {\n return await this.redis.get(key);\n }\n\n async set(key: string, value: string, options?: { ex?: number }): Promise<string> {\n if (options?.ex) {\n return await this.redis.set(key, value, { ex: options?.ex });\n } else {\n return await this.redis.set(key, value);\n }\n }\n\n async delMulti(keys: string[]): Promise<any> {\n return await Promise.all(keys.map((key) => this.redis.del(key)));\n }\n\n async del(key: string): Promise<number> {\n return await this.redis.del(key);\n }\n}\n"],"names":[],"mappings":"oDAGa,kBAAkB,CAAA;AACnB,IAAA,KAAK,CAAQ;AAErB,IAAA,WAAA,CAAY,MAAuC,EAAA;QAC/C,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;KACpE;IAED,MAAM,GAAG,CAAC,GAAW,EAAA;QACjB,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;KACpC;AAED,IAAA,MAAM,GAAG,CAAC,GAAW,EAAE,KAAa,EAAE,OAAyB,EAAA;AAC3D,QAAA,IAAI,OAAO,EAAE,EAAE,EAAE;AACb,YAAA,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;SAChE;aAAM;YACH,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;SAC3C;KACJ;IAED,MAAM,QAAQ,CAAC,IAAc,EAAA;QACzB,OAAO,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KACpE;IAED,MAAM,GAAG,CAAC,GAAW,EAAA;QACjB,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;KACpC;AACJ"}
@@ -0,0 +1,134 @@
1
+ """
2
+ Standard Redis namespaces for organizing keys.
3
+ Using namespaces helps prevent key collisions and makes bulk operations easier.
4
+ """
5
+ enum RedisNamespace {
6
+ """Cache-related keys"""
7
+ cache
8
+ """Session data keys"""
9
+ session
10
+ """Tenant-specific keys"""
11
+ tenant
12
+ """Configuration keys"""
13
+ config
14
+ """Extension-related keys"""
15
+ extension
16
+ """Contribution-related keys"""
17
+ contribution
18
+ """Storage keys"""
19
+ storage
20
+ """Permission keys"""
21
+ permission
22
+ """Temporary keys"""
23
+ temp
24
+ """Queue-related keys"""
25
+ queue
26
+ """Lock keys"""
27
+ lock
28
+ }
29
+
30
+ """
31
+ Options for building Redis keys with standardized format.
32
+ Example: APP_NAME:tenant-123:cache:user:profile
33
+ """
34
+ input RedisKeyOptions {
35
+ """Tenant ID for multi-tenant isolation (defaults to 'default')"""
36
+ tenantId: String
37
+ """Namespace to categorize keys (e.g., 'cache', 'session', 'storage')"""
38
+ namespace: String!
39
+ """Additional key segments to append"""
40
+ segments: [String!]!
41
+ """Optional user ID for user-specific keys"""
42
+ userId: String
43
+ }
44
+
45
+ """
46
+ Context for cache operations.
47
+ Provides tenant, user, and organization isolation for cached data.
48
+ """
49
+ input CacheContext {
50
+ """Tenant identifier for multi-tenant applications"""
51
+ tenantId: String
52
+ """User identifier for user-specific caching"""
53
+ userId: String
54
+ """Organization identifier for org-level caching"""
55
+ orgId: String
56
+ }
57
+
58
+ """
59
+ Cache policy for controlling cache behavior.
60
+ """
61
+ input CachePolicy {
62
+ """Maximum age in seconds (TTL)"""
63
+ maxAge: Int!
64
+ """Cache scope (PUBLIC, PRIVATE, etc.)"""
65
+ scope: String
66
+ }
67
+
68
+ """
69
+ Options for scanning Redis keys.
70
+ Used for iterating through keys matching a pattern.
71
+ """
72
+ input RedisScanOptions {
73
+ """Pattern to match keys (supports wildcards)"""
74
+ match: String
75
+ """Number of keys to return per iteration"""
76
+ count: Int
77
+ """Optional Redis data type to filter by"""
78
+ type: RedisDataType
79
+ }
80
+
81
+ """
82
+ Redis data types for filtering scan operations
83
+ """
84
+ enum RedisDataType {
85
+ string
86
+ list
87
+ set
88
+ zset
89
+ hash
90
+ stream
91
+ }
92
+
93
+ """
94
+ Result from a Redis SCAN operation
95
+ """
96
+ type RedisScanResult {
97
+ """Next cursor position (0 means scan is complete)"""
98
+ cursor: String!
99
+ """Array of keys matching the scan criteria"""
100
+ keys: [String!]!
101
+ }
102
+
103
+ """
104
+ Options for Redis bulk operations.
105
+ Used when performing operations on multiple keys.
106
+ """
107
+ input RedisBulkOptions {
108
+ """Whether to use Redis pipeline for batch operations"""
109
+ pipeline: Boolean
110
+ """Whether to wrap operations in a MULTI/EXEC transaction"""
111
+ transaction: Boolean
112
+ }
113
+
114
+ """
115
+ Redis connection configuration
116
+ """
117
+ input RedisConnectionConfig {
118
+ """Redis server host"""
119
+ host: String
120
+ """Redis server port"""
121
+ port: Int
122
+ """Optional password for authentication"""
123
+ password: String
124
+ """Database number to use"""
125
+ db: Int
126
+ """Prefix to add to all keys"""
127
+ keyPrefix: String
128
+ """Maximum retry attempts"""
129
+ maxRetriesPerRequest: Int
130
+ """Whether to check connection is ready"""
131
+ enableReadyCheck: Boolean
132
+ """Whether to delay connection until first command"""
133
+ lazyConnect: Boolean
134
+ }
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 @@
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';export{UpstashRedisClient}from'./core/upstash-redis.js';export{buildRedisKey,buildRedisKeyPattern}from'./core/keyBuilder/index.js';export{RedisCacheManager}from'./services/RedisCacheManager.js';export{escapeRedisPattern,isHashLikeTenantId,isValidRedisKey,sanitizeRedisKey,sanitizeRedisKeyComponent}from'./core/keyBuilder/sanitize-redis-key.js';export{RedisNamespace,buildRedisKeyPatternWithNamespace,buildRedisKeyWithNamespace,extractNamespaceFromRedisKey,extractTenantIdFromRedisKey,isValidRedisKeyWithNamespace,parseRedisKey}from'./core/keyBuilder/redis-key-builder.js';export{generateQueryCacheKey}from'./core/keyBuilder/generate-query-cache-key.js';//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -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,4 @@
1
+ import {Feature}from'@common-stack/server-core';import {infraContainer}from'./containers/container.js';var module = new Feature({
2
+ createContainerFunc: [infraContainer],
3
+ createMicroServiceContainerFunc: [infraContainer],
4
+ });export{module as default};//# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","sources":["../src/module.ts"],"sourcesContent":["import { Feature } from '@common-stack/server-core';\nimport { infraContainer } from './containers';\n\nexport default new Feature({\n createContainerFunc: [infraContainer],\n createMicroServiceContainerFunc: [infraContainer],\n});\n"],"names":[],"mappings":"uGAGA,aAAe,IAAI,OAAO,CAAC;IACvB,mBAAmB,EAAE,CAAC,cAAc,CAAC;IACrC,+BAA+B,EAAE,CAAC,cAAc,CAAC;AACpD,CAAA,CAAC"}
@@ -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
+ }