@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.
- package/lib/containers/container.d.ts +2 -0
- package/lib/containers/container.js +3 -0
- package/lib/containers/container.js.map +1 -0
- package/lib/containers/index.d.ts +1 -0
- package/lib/core/index.d.ts +3 -0
- package/lib/core/ioredis.d.ts +15 -0
- package/lib/core/ioredis.js +27 -0
- package/lib/core/ioredis.js.map +1 -0
- package/lib/core/keyBuilder/generate-query-cache-key.d.ts +57 -0
- package/lib/core/keyBuilder/generate-query-cache-key.js +82 -0
- package/lib/core/keyBuilder/generate-query-cache-key.js.map +1 -0
- package/lib/core/keyBuilder/index.d.ts +18 -0
- package/lib/core/keyBuilder/index.js +20 -0
- package/lib/core/keyBuilder/index.js.map +1 -0
- package/lib/core/keyBuilder/redis-key-builder.d.ts +152 -0
- package/lib/core/keyBuilder/redis-key-builder.js +181 -0
- package/lib/core/keyBuilder/redis-key-builder.js.map +1 -0
- package/lib/core/keyBuilder/sanitize-redis-key.d.ts +118 -0
- package/lib/core/keyBuilder/sanitize-redis-key.js +115 -0
- package/lib/core/keyBuilder/sanitize-redis-key.js.map +1 -0
- package/lib/core/upstash-redis.d.ts +14 -0
- package/lib/core/upstash-redis.js +23 -0
- package/lib/core/upstash-redis.js.map +1 -0
- package/lib/graphql/schema/base-services.graphql +134 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -3
- package/lib/index.js.map +1 -0
- package/lib/interfaces/index.d.ts +1 -6
- package/lib/interfaces/redis.d.ts +11 -0
- package/lib/module.d.ts +2 -0
- package/lib/module.js +4 -0
- package/lib/module.js.map +1 -0
- package/lib/services/RedisCacheManager.d.ts +77 -0
- package/lib/services/RedisCacheManager.js +177 -0
- package/lib/services/RedisCacheManager.js.map +1 -0
- package/lib/services/index.d.ts +1 -5
- package/lib/templates/constants/SERVER_TYPES.ts.template +0 -1
- package/lib/templates/repositories/IRedisKeyBuilder.ts.template +4 -4
- package/lib/templates/repositories/redisCommonTypes.ts.template +2 -163
- package/lib/templates/{repositories/IRedisCacheManager.ts.template → services/RedisCacheManager.ts.template} +7 -7
- package/package.json +8 -7
- package/lib/interfaces/cache-manager.d.ts +0 -28
- package/lib/interfaces/redis-key-options.d.ts +0 -35
- package/lib/interfaces/redis-key-options.js +0 -17
- package/lib/interfaces/storage-backend.d.ts +0 -17
- package/lib/templates/repositories/IRedisService.ts.template +0 -236
- package/lib/templates/repositories/IRedisStorageBackend.ts.template +0 -229
- package/lib/utils/index.d.ts +0 -5
- package/lib/utils/redis-key-builder.d.ts +0 -32
- package/lib/utils/redis-key-builder.js +0 -68
- package/lib/utils/redis-key-sanitizer.d.ts +0 -30
- 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
package/lib/index.js
CHANGED
|
@@ -1,3 +1 @@
|
|
|
1
|
-
export
|
|
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
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -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
|
+
}
|
package/lib/module.d.ts
ADDED
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
|
+
}
|