@common-stack/store-redis 8.3.1-alpha.7 → 8.4.1-alpha.0
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/core/index.d.ts +0 -5
- package/lib/core/keyBuilder/generate-query-cache-key.d.ts +39 -0
- package/lib/core/keyBuilder/generate-query-cache-key.js +46 -2
- package/lib/core/keyBuilder/generate-query-cache-key.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/module.d.ts +1 -2
- package/lib/plugins/invalidateCachePlugin.js +68 -15
- package/lib/plugins/invalidateCachePlugin.js.map +1 -1
- package/lib/plugins/responseCachePlugin.js.map +1 -1
- package/package.json +7 -7
- package/lib/core/index.js +0 -6
- package/lib/core/index.js.map +0 -1
package/lib/core/index.d.ts
CHANGED
|
@@ -19,6 +19,45 @@ export type GenerateQueryCacheKeyOptions = {
|
|
|
19
19
|
appName?: string;
|
|
20
20
|
logger?: any;
|
|
21
21
|
};
|
|
22
|
+
export type GenerateQueryCachePatternOptions = {
|
|
23
|
+
appName: string;
|
|
24
|
+
tenantId?: string;
|
|
25
|
+
userId?: string;
|
|
26
|
+
queryName: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Generates a wildcard pattern for invalidating GraphQL query cache keys
|
|
30
|
+
*
|
|
31
|
+
* This function creates patterns that match the keys generated by `generateQueryCacheKey`.
|
|
32
|
+
* Use this for cache invalidation to ensure consistent pattern matching.
|
|
33
|
+
*
|
|
34
|
+
* Key pattern format: `appName:tenantId:[userId]:queryName:*`
|
|
35
|
+
* - All components are sanitized the same way as cache key generation
|
|
36
|
+
* - Trailing wildcard matches the query hash and variables hash
|
|
37
|
+
*
|
|
38
|
+
* @param options - Pattern generation options
|
|
39
|
+
* @returns Redis pattern string for SCAN/KEYS operations
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* generateQueryCachePattern({
|
|
44
|
+
* appName: 'CDMBASE_TEST',
|
|
45
|
+
* tenantId: 'default',
|
|
46
|
+
* userId: 'auth0|123',
|
|
47
|
+
* queryName: 'pageSettings'
|
|
48
|
+
* })
|
|
49
|
+
* // Returns: 'CDMBASE_TEST:default:auth0-123:pageSettings:*'
|
|
50
|
+
*
|
|
51
|
+
* // Without userId (for public queries)
|
|
52
|
+
* generateQueryCachePattern({
|
|
53
|
+
* appName: 'CDMBASE_TEST',
|
|
54
|
+
* tenantId: 'default',
|
|
55
|
+
* queryName: 'pageSettings'
|
|
56
|
+
* })
|
|
57
|
+
* // Returns: 'CDMBASE_TEST:default:pageSettings:*'
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare const generateQueryCachePattern: ({ appName, tenantId, userId, queryName, }: GenerateQueryCachePatternOptions) => string;
|
|
22
61
|
/**
|
|
23
62
|
* Generates a cache key for GraphQL queries using standardized Redis key builder
|
|
24
63
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {createHash}from'crypto';import {print}from'graphql/language/index.js';import {buildRedisKey}from'./sanitize-redis-key.js';/**
|
|
1
|
+
import {createHash}from'crypto';import {print}from'graphql/language/index.js';import {sanitizeRedisKeyComponent,buildRedisKey}from'./sanitize-redis-key.js';/**
|
|
2
2
|
* @file generate-query-cache-key.ts
|
|
3
3
|
* @description Generates consistent cache keys for GraphQL queries
|
|
4
4
|
*
|
|
@@ -10,6 +10,50 @@ import {createHash}from'crypto';import {print}from'graphql/language/index.js';im
|
|
|
10
10
|
* - Variables are optionally hashed if provided
|
|
11
11
|
* - All components are sanitized for Redis key safety
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* Generates a wildcard pattern for invalidating GraphQL query cache keys
|
|
15
|
+
*
|
|
16
|
+
* This function creates patterns that match the keys generated by `generateQueryCacheKey`.
|
|
17
|
+
* Use this for cache invalidation to ensure consistent pattern matching.
|
|
18
|
+
*
|
|
19
|
+
* Key pattern format: `appName:tenantId:[userId]:queryName:*`
|
|
20
|
+
* - All components are sanitized the same way as cache key generation
|
|
21
|
+
* - Trailing wildcard matches the query hash and variables hash
|
|
22
|
+
*
|
|
23
|
+
* @param options - Pattern generation options
|
|
24
|
+
* @returns Redis pattern string for SCAN/KEYS operations
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* generateQueryCachePattern({
|
|
29
|
+
* appName: 'CDMBASE_TEST',
|
|
30
|
+
* tenantId: 'default',
|
|
31
|
+
* userId: 'auth0|123',
|
|
32
|
+
* queryName: 'pageSettings'
|
|
33
|
+
* })
|
|
34
|
+
* // Returns: 'CDMBASE_TEST:default:auth0-123:pageSettings:*'
|
|
35
|
+
*
|
|
36
|
+
* // Without userId (for public queries)
|
|
37
|
+
* generateQueryCachePattern({
|
|
38
|
+
* appName: 'CDMBASE_TEST',
|
|
39
|
+
* tenantId: 'default',
|
|
40
|
+
* queryName: 'pageSettings'
|
|
41
|
+
* })
|
|
42
|
+
* // Returns: 'CDMBASE_TEST:default:pageSettings:*'
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
const generateQueryCachePattern = ({ appName, tenantId, userId, queryName, }) => {
|
|
46
|
+
const parts = [sanitizeRedisKeyComponent(appName)];
|
|
47
|
+
if (tenantId) {
|
|
48
|
+
parts.push(sanitizeRedisKeyComponent(tenantId));
|
|
49
|
+
}
|
|
50
|
+
if (userId) {
|
|
51
|
+
parts.push(sanitizeRedisKeyComponent(userId));
|
|
52
|
+
}
|
|
53
|
+
parts.push(sanitizeRedisKeyComponent(queryName));
|
|
54
|
+
parts.push('*'); // Wildcard to match queryHash and variablesHash
|
|
55
|
+
return parts.join(':');
|
|
56
|
+
};
|
|
13
57
|
/**
|
|
14
58
|
* Generates a cache key for GraphQL queries using standardized Redis key builder
|
|
15
59
|
*
|
|
@@ -79,4 +123,4 @@ const generateQueryCacheKey = ({ query, variables, logger, userId, tenantId, app
|
|
|
79
123
|
});
|
|
80
124
|
// Remove the temporary appName prefix: __temp__:
|
|
81
125
|
return fullKey.substring('__temp__:'.length);
|
|
82
|
-
};export{generateQueryCacheKey};//# sourceMappingURL=generate-query-cache-key.js.map
|
|
126
|
+
};export{generateQueryCacheKey,generateQueryCachePattern};//# sourceMappingURL=generate-query-cache-key.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-query-cache-key.js","sources":["../../../src/core/keyBuilder/generate-query-cache-key.ts"],"sourcesContent":["/**\n * @file generate-query-cache-key.ts\n * @description Generates consistent cache keys for GraphQL queries\n *\n * This utility creates deterministic cache keys by hashing GraphQL queries and variables,\n * with proper sanitization for Redis key compatibility (e.g., Auth0 userIds with pipes).\n *\n * Key format: queryName:queryHash[:variablesHash]\n * - Query is hashed using SHA-256 for consistent, compact keys\n * - Variables are optionally hashed if provided\n * - All components are sanitized for Redis key safety\n */\n\nimport { createHash } from 'crypto';\nimport { DocumentNode, print } from 'graphql/language/index.js';\nimport { buildRedisKey } from './sanitize-redis-key';\n\nexport type GenerateQueryCacheKeyOptions = {\n query: string | DocumentNode;\n variables: Record<string, unknown>;\n userId?: string;\n tenantId?: string;\n appName?: string;\n logger?: any;\n};\n\n/**\n * Generates a cache key for GraphQL queries using standardized Redis key builder\n *\n * Key format: `[appName]:[tenantId]:[userId]:queryName:queryHash[:variablesHash]`\n * or legacy format: `[tenantId]:[userId]:queryName:queryHash[:variablesHash]`\n * - Query and variables are hashed for consistent, compact keys\n * - Uses buildRedisKey for proper sanitization and validation\n * - Optional components are omitted if not provided\n *\n * @param options - Cache key generation options\n * @returns Generated cache key string\n *\n * @example\n * ```typescript\n * // With appName (recommended)\n * generateQueryCacheKey({\n * query: GetUserDocument,\n * variables: { userId: '123' },\n * userId: 'auth0|123',\n * tenantId: 'default',\n * appName: 'MY_APP',\n * logger: myLogger\n * })\n * // Returns: 'MY_APP:default:auth0-123:user:hash1:hash2'\n *\n * // Legacy format without appName (for backward compatibility)\n * generateQueryCacheKey({\n * query: GetUserDocument,\n * variables: { userId: '123' },\n * userId: 'auth0|123',\n * tenantId: 'default',\n * })\n * // Returns: 'default:auth0-123:user:hash1:hash2'\n * ```\n */\nexport const generateQueryCacheKey = ({\n query,\n variables,\n logger,\n userId,\n tenantId,\n appName,\n}: GenerateQueryCacheKeyOptions): string => {\n const normalizedQuery = (typeof query === 'string' ? query : print(query)).trim();\n const [, queryName] = normalizedQuery?.match(/{\\s*(\\w+)/) ?? [];\n\n // Log the data right before hashing\n if (logger && typeof logger.trace === 'function') {\n logger.trace('About to hash normalized query: [%s]', normalizedQuery);\n logger.trace('About to hash variables: [%j]', variables);\n }\n\n const queryHash = createHash('sha256').update(normalizedQuery).digest('hex');\n const variablesHash = variables ? createHash('sha256').update(JSON.stringify(variables)).digest('hex') : null;\n\n // Build segments: queryName, queryHash, and optional variablesHash\n const segments = [queryName, queryHash];\n if (variablesHash) {\n segments.push(variablesHash);\n }\n\n // If appName is provided, use the full standardized format\n if (appName) {\n return buildRedisKey({\n appName,\n tenantId,\n userId,\n segments,\n });\n }\n\n // Legacy format for backward compatibility (no appName)\n // Still uses buildRedisKey for sanitization, but strips the appName prefix\n const fullKey = buildRedisKey({\n appName: '__temp__',\n tenantId,\n userId,\n segments,\n });\n\n // Remove the temporary appName prefix: __temp__:\n return fullKey.substring('__temp__:'.length);\n};\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate-query-cache-key.js","sources":["../../../src/core/keyBuilder/generate-query-cache-key.ts"],"sourcesContent":["/**\n * @file generate-query-cache-key.ts\n * @description Generates consistent cache keys for GraphQL queries\n *\n * This utility creates deterministic cache keys by hashing GraphQL queries and variables,\n * with proper sanitization for Redis key compatibility (e.g., Auth0 userIds with pipes).\n *\n * Key format: queryName:queryHash[:variablesHash]\n * - Query is hashed using SHA-256 for consistent, compact keys\n * - Variables are optionally hashed if provided\n * - All components are sanitized for Redis key safety\n */\n\nimport { createHash } from 'crypto';\nimport { DocumentNode, print } from 'graphql/language/index.js';\nimport { buildRedisKey, sanitizeRedisKeyComponent } from './sanitize-redis-key';\n\nexport type GenerateQueryCacheKeyOptions = {\n query: string | DocumentNode;\n variables: Record<string, unknown>;\n userId?: string;\n tenantId?: string;\n appName?: string;\n logger?: any;\n};\n\nexport type GenerateQueryCachePatternOptions = {\n appName: string;\n tenantId?: string;\n userId?: string;\n queryName: string;\n};\n\n/**\n * Generates a wildcard pattern for invalidating GraphQL query cache keys\n *\n * This function creates patterns that match the keys generated by `generateQueryCacheKey`.\n * Use this for cache invalidation to ensure consistent pattern matching.\n *\n * Key pattern format: `appName:tenantId:[userId]:queryName:*`\n * - All components are sanitized the same way as cache key generation\n * - Trailing wildcard matches the query hash and variables hash\n *\n * @param options - Pattern generation options\n * @returns Redis pattern string for SCAN/KEYS operations\n *\n * @example\n * ```typescript\n * generateQueryCachePattern({\n * appName: 'CDMBASE_TEST',\n * tenantId: 'default',\n * userId: 'auth0|123',\n * queryName: 'pageSettings'\n * })\n * // Returns: 'CDMBASE_TEST:default:auth0-123:pageSettings:*'\n *\n * // Without userId (for public queries)\n * generateQueryCachePattern({\n * appName: 'CDMBASE_TEST',\n * tenantId: 'default',\n * queryName: 'pageSettings'\n * })\n * // Returns: 'CDMBASE_TEST:default:pageSettings:*'\n * ```\n */\nexport const generateQueryCachePattern = ({\n appName,\n tenantId,\n userId,\n queryName,\n}: GenerateQueryCachePatternOptions): string => {\n const parts: string[] = [sanitizeRedisKeyComponent(appName)];\n\n if (tenantId) {\n parts.push(sanitizeRedisKeyComponent(tenantId));\n }\n\n if (userId) {\n parts.push(sanitizeRedisKeyComponent(userId));\n }\n\n parts.push(sanitizeRedisKeyComponent(queryName));\n parts.push('*'); // Wildcard to match queryHash and variablesHash\n\n return parts.join(':');\n};\n\n/**\n * Generates a cache key for GraphQL queries using standardized Redis key builder\n *\n * Key format: `[appName]:[tenantId]:[userId]:queryName:queryHash[:variablesHash]`\n * or legacy format: `[tenantId]:[userId]:queryName:queryHash[:variablesHash]`\n * - Query and variables are hashed for consistent, compact keys\n * - Uses buildRedisKey for proper sanitization and validation\n * - Optional components are omitted if not provided\n *\n * @param options - Cache key generation options\n * @returns Generated cache key string\n *\n * @example\n * ```typescript\n * // With appName (recommended)\n * generateQueryCacheKey({\n * query: GetUserDocument,\n * variables: { userId: '123' },\n * userId: 'auth0|123',\n * tenantId: 'default',\n * appName: 'MY_APP',\n * logger: myLogger\n * })\n * // Returns: 'MY_APP:default:auth0-123:user:hash1:hash2'\n *\n * // Legacy format without appName (for backward compatibility)\n * generateQueryCacheKey({\n * query: GetUserDocument,\n * variables: { userId: '123' },\n * userId: 'auth0|123',\n * tenantId: 'default',\n * })\n * // Returns: 'default:auth0-123:user:hash1:hash2'\n * ```\n */\nexport const generateQueryCacheKey = ({\n query,\n variables,\n logger,\n userId,\n tenantId,\n appName,\n}: GenerateQueryCacheKeyOptions): string => {\n const normalizedQuery = (typeof query === 'string' ? query : print(query)).trim();\n const [, queryName] = normalizedQuery?.match(/{\\s*(\\w+)/) ?? [];\n\n // Log the data right before hashing\n if (logger && typeof logger.trace === 'function') {\n logger.trace('About to hash normalized query: [%s]', normalizedQuery);\n logger.trace('About to hash variables: [%j]', variables);\n }\n\n const queryHash = createHash('sha256').update(normalizedQuery).digest('hex');\n const variablesHash = variables ? createHash('sha256').update(JSON.stringify(variables)).digest('hex') : null;\n\n // Build segments: queryName, queryHash, and optional variablesHash\n const segments = [queryName, queryHash];\n if (variablesHash) {\n segments.push(variablesHash);\n }\n\n // If appName is provided, use the full standardized format\n if (appName) {\n return buildRedisKey({\n appName,\n tenantId,\n userId,\n segments,\n });\n }\n\n // Legacy format for backward compatibility (no appName)\n // Still uses buildRedisKey for sanitization, but strips the appName prefix\n const fullKey = buildRedisKey({\n appName: '__temp__',\n tenantId,\n userId,\n segments,\n });\n\n // Remove the temporary appName prefix: __temp__:\n return fullKey.substring('__temp__:'.length);\n};\n"],"names":[],"mappings":"4JAAA;;;;;;;;;;;AAWG;AAsBH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BG;AACI,MAAM,yBAAyB,GAAG,CAAC,EACtC,OAAO,EACP,QAAQ,EACR,MAAM,EACN,SAAS,GACsB,KAAY;IAC3C,MAAM,KAAK,GAAa,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,CAAC;IAE7D,IAAI,QAAQ,EAAE;QACV,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC,CAAC;KACnD;IAED,IAAI,MAAM,EAAE;QACR,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC;KACjD;IAED,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,SAAS,CAAC,CAAC,CAAC;AACjD,IAAA,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEhB,IAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3B,EAAE;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCG;AACU,MAAA,qBAAqB,GAAG,CAAC,EAClC,KAAK,EACL,SAAS,EACT,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAO,GACoB,KAAY;IACvC,MAAM,eAAe,GAAG,CAAC,OAAO,KAAK,KAAK,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;AAClF,IAAA,MAAM,GAAG,SAAS,CAAC,GAAG,eAAe,EAAE,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;;IAGhE,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,UAAU,EAAE;AAC9C,QAAA,MAAM,CAAC,KAAK,CAAC,sCAAsC,EAAE,eAAe,CAAC,CAAC;AACtE,QAAA,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE,SAAS,CAAC,CAAC;KAC5D;AAED,IAAA,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC7E,IAAA,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;;AAG9G,IAAA,MAAM,QAAQ,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACxC,IAAI,aAAa,EAAE;AACf,QAAA,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;KAChC;;IAGD,IAAI,OAAO,EAAE;AACT,QAAA,OAAO,aAAa,CAAC;YACjB,OAAO;YACP,QAAQ;YACR,MAAM;YACN,QAAQ;AACX,SAAA,CAAC,CAAC;KACN;;;IAID,MAAM,OAAO,GAAG,aAAa,CAAC;AAC1B,QAAA,OAAO,EAAE,UAAU;QACnB,QAAQ;QACR,MAAM;QACN,QAAQ;AACX,KAAA,CAAC,CAAC;;IAGH,OAAO,OAAO,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;AACjD"}
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{
|
|
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{invalidateCachePlugin}from'./plugins/invalidateCachePlugin.js';export{responseCachePlugin}from'./plugins/responseCachePlugin.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,generateQueryCachePattern}from'./core/keyBuilder/generate-query-cache-key.js';//# sourceMappingURL=index.js.map
|
package/lib/module.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Feature } from '@common-stack/server-core';
|
|
2
1
|
import type { interfaces } from 'inversify';
|
|
3
2
|
export declare const redisServiceGen: (container: interfaces.Container) => {
|
|
4
3
|
redisCacheManager: IRedisCacheManager;
|
|
5
4
|
};
|
|
6
|
-
declare const _default:
|
|
5
|
+
declare const _default: any;
|
|
7
6
|
export default _default;
|
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
import {uniq}from'lodash-es';import {extractTenantId,getDirectiveArgsFromSchema,CACHE_CONTROL_DIRECTIVE}from'@common-stack/server-core';import {config}from'../config/env-config.js';
|
|
1
|
+
import {uniq}from'lodash-es';import {extractTenantId,getDirectiveArgsFromSchema,CACHE_CONTROL_DIRECTIVE}from'@common-stack/server-core';import {config}from'../config/env-config.js';import {generateQueryCachePattern}from'../core/keyBuilder/generate-query-cache-key.js';/**
|
|
2
|
+
* Scans Redis keys using SCAN command instead of KEYS for better performance.
|
|
3
|
+
* SCAN is non-blocking and safe to use in production environments.
|
|
4
|
+
*/
|
|
5
|
+
async function scanKeys(redisClient, pattern) {
|
|
6
|
+
const allKeys = [];
|
|
7
|
+
let cursor = '0';
|
|
8
|
+
do {
|
|
9
|
+
// SCAN returns [cursor, keys[]]
|
|
10
|
+
const [nextCursor, keys] = await redisClient.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
|
|
11
|
+
cursor = nextCursor;
|
|
12
|
+
if (keys.length) {
|
|
13
|
+
allKeys.push(...keys);
|
|
14
|
+
}
|
|
15
|
+
} while (cursor !== '0');
|
|
16
|
+
return allKeys;
|
|
17
|
+
}
|
|
18
|
+
const invalidateCachePlugin = ({ cache: redisClient, invalidateCacheKeyGenerator, }) => ({
|
|
2
19
|
requestDidStart(requestContext) {
|
|
3
20
|
return {
|
|
4
21
|
willSendResponse: async (responseContext) => {
|
|
@@ -9,38 +26,74 @@ import {uniq}from'lodash-es';import {extractTenantId,getDirectiveArgsFromSchema,
|
|
|
9
26
|
try {
|
|
10
27
|
const hasErrors = !!requestContext.errors?.length;
|
|
11
28
|
const { schema } = requestContext;
|
|
12
|
-
|
|
29
|
+
// Safely access contextValue properties
|
|
30
|
+
const contextValue = requestContext.contextValue;
|
|
31
|
+
if (!contextValue) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const { queriesToInvalidate, user, req } = contextValue;
|
|
13
35
|
const tenantId = extractTenantId(req?.currentPageUriSegments?.authority);
|
|
14
|
-
|
|
36
|
+
// Safely extract operation from document definitions
|
|
37
|
+
const definitions = responseContext.document?.definitions;
|
|
38
|
+
if (!definitions?.length) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const firstDefinition = definitions[0];
|
|
42
|
+
const operation = firstDefinition?.operation;
|
|
15
43
|
const isMutation = operation === 'mutation';
|
|
16
44
|
if (hasErrors || !queriesToInvalidate?.length || !isMutation) {
|
|
45
|
+
if (isMutation) {
|
|
46
|
+
console.log('🔍 invalidateCachePlugin: skipping - hasErrors:', hasErrors, 'queriesToInvalidate:', queriesToInvalidate);
|
|
47
|
+
}
|
|
17
48
|
return;
|
|
18
49
|
}
|
|
50
|
+
console.log('🗑️ invalidateCachePlugin: invalidating queries:', queriesToInvalidate);
|
|
19
51
|
const nestedKeys = await Promise.all(queriesToInvalidate.map(async (query) => {
|
|
20
52
|
// Build keys in order of specificity
|
|
21
53
|
let keys = [];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
54
|
+
// Safely get cache policy directive
|
|
55
|
+
let cachePolicy = null;
|
|
56
|
+
try {
|
|
57
|
+
cachePolicy = getDirectiveArgsFromSchema({
|
|
58
|
+
schema,
|
|
59
|
+
queryName: query,
|
|
60
|
+
directiveName: CACHE_CONTROL_DIRECTIVE,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
requestContext.logger?.warn?.(`Failed to get cache policy for query: ${query}`);
|
|
65
|
+
}
|
|
66
|
+
const isPrivate = cachePolicy?.scope?.toLowerCase() === 'private';
|
|
28
67
|
// Add tenant-specific key if tenant exists
|
|
29
68
|
if (tenantId) {
|
|
30
|
-
|
|
31
|
-
|
|
69
|
+
// Pattern without userId (for public queries or as fallback)
|
|
70
|
+
keys.push(generateQueryCachePattern({
|
|
71
|
+
appName: config.APP_NAME,
|
|
72
|
+
tenantId,
|
|
73
|
+
queryName: query,
|
|
74
|
+
}));
|
|
75
|
+
// Add user-specific pattern if user exists and query is private
|
|
32
76
|
if (user?.sub && isPrivate) {
|
|
33
|
-
keys.push(
|
|
77
|
+
keys.push(generateQueryCachePattern({
|
|
78
|
+
appName: config.APP_NAME,
|
|
79
|
+
tenantId,
|
|
80
|
+
userId: user.sub,
|
|
81
|
+
queryName: query,
|
|
82
|
+
}));
|
|
34
83
|
}
|
|
35
84
|
}
|
|
36
|
-
// Add global
|
|
37
|
-
keys.push(
|
|
85
|
+
// Add global pattern as fallback (no tenant)
|
|
86
|
+
keys.push(generateQueryCachePattern({
|
|
87
|
+
appName: config.APP_NAME,
|
|
88
|
+
queryName: query,
|
|
89
|
+
}));
|
|
38
90
|
// Allow custom key generation if provided
|
|
39
91
|
if (typeof invalidateCacheKeyGenerator === 'function') {
|
|
40
92
|
keys = keys.map((key) => invalidateCacheKeyGenerator(requestContext, responseContext, key));
|
|
41
93
|
}
|
|
94
|
+
// Use SCAN instead of KEYS for better performance (non-blocking)
|
|
42
95
|
const matchedKeys = await Promise.all(keys.map(async (key) => {
|
|
43
|
-
const matchingKeys = await redisClient
|
|
96
|
+
const matchingKeys = await scanKeys(redisClient, key);
|
|
44
97
|
if (matchingKeys.length) {
|
|
45
98
|
return matchingKeys;
|
|
46
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invalidateCachePlugin.js","sources":["../../src/plugins/invalidateCachePlugin.ts"],"sourcesContent":["import { Redis, Cluster } from 'ioredis';\nimport { uniq } from 'lodash-es';\nimport { OperationDefinitionNode } from 'graphql/language/ast.js';\nimport type { BaseContext, GraphQLRequestContextWillSendResponse } from '@apollo/server';\nimport { ApolloServerOptions, GraphQLRequestContext } from '@apollo/server';\nimport { CACHE_CONTROL_DIRECTIVE, extractTenantId, getDirectiveArgsFromSchema } from '@common-stack/server-core';\nimport { IGraphQLCacheContext } from 'common/server';\nimport { config } from '../config';\n\nexport type InvalidationKeyGenerator = (\n requestContext: GraphQLRequestContext<unknown>,\n responseContext: GraphQLRequestContextWillSendResponse<unknown>,\n cacheKey?: string,\n) => string;\nexport const invalidateCachePlugin = ({\n cache: redisClient,\n invalidateCacheKeyGenerator,\n}: {\n cache: Redis | Cluster;\n invalidateCacheKeyGenerator: InvalidationKeyGenerator;\n}) =>\n ({\n requestDidStart(requestContext: GraphQLRequestContext<IGraphQLCacheContext>) {\n return {\n willSendResponse: async (responseContext: GraphQLRequestContextWillSendResponse<unknown>) => {\n // in case of websocket request initially the requestContext comes empty\n if (!requestContext) {\n return;\n }\n try {\n const hasErrors = !!requestContext.errors?.length;\n const { schema } = requestContext;\n const { queriesToInvalidate, user, req } = requestContext.contextValue;\n const tenantId = extractTenantId(req?.currentPageUriSegments?.authority);\n const [{ operation }] = responseContext.document.definitions as OperationDefinitionNode[];\n const isMutation = operation === 'mutation';\n if (hasErrors || !queriesToInvalidate?.length || !isMutation) {\n return;\n }\n\n const nestedKeys = await Promise.all<string[]>(\n queriesToInvalidate.map(async (query: string) => {\n // Build keys in order of specificity\n let keys: string[] = [];\n const cachePolicy: { scope: string } = getDirectiveArgsFromSchema({\n schema,\n queryName: query,\n directiveName: CACHE_CONTROL_DIRECTIVE,\n });\n const isPrivate = cachePolicy.scope?.toLowerCase() === 'private';\n // Add tenant-specific key if tenant exists\n if (tenantId) {\n keys.push(`${config.APP_NAME}:${tenantId}:${query}:*`);\n // Add user-specific key if user exists\n if (user?.sub && isPrivate) {\n keys.push(`${config.APP_NAME}:${tenantId}:${user.sub}:${query}:*`);\n }\n }\n // Add global key as fallback\n keys.push(`${config.APP_NAME}:${query}:*`);\n\n // Allow custom key generation if provided\n if (typeof invalidateCacheKeyGenerator === 'function') {\n keys = keys.map((key) =>\n invalidateCacheKeyGenerator(requestContext, responseContext, key),\n );\n }\n const matchedKeys = await Promise.all(\n keys.map(async (key) => {\n const matchingKeys = await redisClient.keys(key);\n if (matchingKeys.length) {\n return matchingKeys;\n }\n return [];\n }),\n );\n return matchedKeys.flat();\n }),\n );\n const keys = nestedKeys.flat();\n if (keys?.length) {\n await redisClient.del(uniq(keys));\n }\n } catch (e) {\n requestContext.logger.error('Error occurred in invalidateCachePlugin');\n requestContext.logger.error(e);\n }\n },\n };\n },\n }) as unknown as ApolloServerOptions<BaseContext>['plugins'][0];\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"invalidateCachePlugin.js","sources":["../../src/plugins/invalidateCachePlugin.ts"],"sourcesContent":["import { Redis, Cluster } from 'ioredis';\nimport { uniq } from 'lodash-es';\nimport { OperationDefinitionNode } from 'graphql/language/ast.js';\nimport type { BaseContext, GraphQLRequestContextWillSendResponse } from '@apollo/server';\nimport { ApolloServerOptions, GraphQLRequestContext } from '@apollo/server';\nimport { CACHE_CONTROL_DIRECTIVE, extractTenantId, getDirectiveArgsFromSchema } from '@common-stack/server-core';\nimport { IGraphQLCacheContext } from 'common/server';\nimport { config } from '../config';\nimport { generateQueryCachePattern } from '../core/keyBuilder/generate-query-cache-key';\n\nexport type InvalidationKeyGenerator = (\n requestContext: GraphQLRequestContext<unknown>,\n responseContext: GraphQLRequestContextWillSendResponse<unknown>,\n cacheKey?: string,\n) => string;\n\n/**\n * Scans Redis keys using SCAN command instead of KEYS for better performance.\n * SCAN is non-blocking and safe to use in production environments.\n */\nasync function scanKeys(redisClient: Redis | Cluster, pattern: string): Promise<string[]> {\n const allKeys: string[] = [];\n let cursor = '0';\n\n do {\n // SCAN returns [cursor, keys[]]\n const [nextCursor, keys] = await redisClient.scan(cursor, 'MATCH', pattern, 'COUNT', 100);\n cursor = nextCursor;\n if (keys.length) {\n allKeys.push(...keys);\n }\n } while (cursor !== '0');\n\n return allKeys;\n}\n\nexport const invalidateCachePlugin = ({\n cache: redisClient,\n invalidateCacheKeyGenerator,\n}: {\n cache: Redis | Cluster;\n invalidateCacheKeyGenerator: InvalidationKeyGenerator;\n}) =>\n ({\n requestDidStart(requestContext: GraphQLRequestContext<IGraphQLCacheContext>) {\n return {\n willSendResponse: async (responseContext: GraphQLRequestContextWillSendResponse<unknown>) => {\n // in case of websocket request initially the requestContext comes empty\n if (!requestContext) {\n return;\n }\n try {\n const hasErrors = !!requestContext.errors?.length;\n const { schema } = requestContext;\n\n // Safely access contextValue properties\n const contextValue = requestContext.contextValue;\n if (!contextValue) {\n return;\n }\n const { queriesToInvalidate, user, req } = contextValue;\n const tenantId = extractTenantId(req?.currentPageUriSegments?.authority);\n\n // Safely extract operation from document definitions\n const definitions = responseContext.document?.definitions as OperationDefinitionNode[];\n if (!definitions?.length) {\n return;\n }\n const firstDefinition = definitions[0];\n const operation = firstDefinition?.operation;\n const isMutation = operation === 'mutation';\n\n if (hasErrors || !queriesToInvalidate?.length || !isMutation) {\n if (isMutation) {\n console.log(\n '🔍 invalidateCachePlugin: skipping - hasErrors:',\n hasErrors,\n 'queriesToInvalidate:',\n queriesToInvalidate,\n );\n }\n return;\n }\n\n console.log('🗑️ invalidateCachePlugin: invalidating queries:', queriesToInvalidate);\n\n const nestedKeys = await Promise.all<string[]>(\n queriesToInvalidate.map(async (query: string) => {\n // Build keys in order of specificity\n let keys: string[] = [];\n\n // Safely get cache policy directive\n let cachePolicy: { scope?: string } | null = null;\n try {\n cachePolicy = getDirectiveArgsFromSchema({\n schema,\n queryName: query,\n directiveName: CACHE_CONTROL_DIRECTIVE,\n });\n } catch (e) {\n requestContext.logger?.warn?.(`Failed to get cache policy for query: ${query}`);\n }\n\n const isPrivate = cachePolicy?.scope?.toLowerCase() === 'private';\n // Add tenant-specific key if tenant exists\n if (tenantId) {\n // Pattern without userId (for public queries or as fallback)\n keys.push(\n generateQueryCachePattern({\n appName: config.APP_NAME,\n tenantId,\n queryName: query,\n }),\n );\n // Add user-specific pattern if user exists and query is private\n if (user?.sub && isPrivate) {\n keys.push(\n generateQueryCachePattern({\n appName: config.APP_NAME,\n tenantId,\n userId: user.sub,\n queryName: query,\n }),\n );\n }\n }\n // Add global pattern as fallback (no tenant)\n keys.push(\n generateQueryCachePattern({\n appName: config.APP_NAME,\n queryName: query,\n }),\n );\n\n // Allow custom key generation if provided\n if (typeof invalidateCacheKeyGenerator === 'function') {\n keys = keys.map((key) =>\n invalidateCacheKeyGenerator(requestContext, responseContext, key),\n );\n }\n // Use SCAN instead of KEYS for better performance (non-blocking)\n const matchedKeys = await Promise.all(\n keys.map(async (key) => {\n const matchingKeys = await scanKeys(redisClient, key);\n if (matchingKeys.length) {\n return matchingKeys;\n }\n return [];\n }),\n );\n return matchedKeys.flat();\n }),\n );\n const keys = nestedKeys.flat();\n if (keys?.length) {\n await redisClient.del(uniq(keys));\n }\n } catch (e) {\n requestContext.logger.error('Error occurred in invalidateCachePlugin');\n requestContext.logger.error(e);\n }\n },\n };\n },\n }) as unknown as ApolloServerOptions<BaseContext>['plugins'][0];\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responseCachePlugin.js","sources":["../../src/plugins/responseCachePlugin.ts"],"sourcesContent":["/* eslint-disable no-param-reassign */\nimport { GraphQLRequestContext } from '@apollo/server';\nimport { CACHE_CONTROL_DIRECTIVE, extractTenantId, getDirectiveArgsFromSchema } from '@common-stack/server-core';\nimport { isEmpty } from 'lodash-es';\nimport apolloCachePlugin from '@apollo/server-plugin-response-cache';\nimport { ILogger } from '@cdm-logger/core/lib/interface';\nimport { IGraphQLCacheContext, IGraphqlCacheKeyGenerator } from 'common/server';\nimport { generateQueryCacheKey } from '../core/keyBuilder/generate-query-cache-key';\n\
|
|
1
|
+
{"version":3,"file":"responseCachePlugin.js","sources":["../../src/plugins/responseCachePlugin.ts"],"sourcesContent":["/* eslint-disable no-param-reassign */\nimport { GraphQLRequestContext } from '@apollo/server';\nimport { CACHE_CONTROL_DIRECTIVE, extractTenantId, getDirectiveArgsFromSchema } from '@common-stack/server-core';\nimport { isEmpty } from 'lodash-es';\nimport apolloCachePlugin from '@apollo/server-plugin-response-cache';\nimport { ILogger } from '@cdm-logger/core/lib/interface';\nimport { IGraphQLCacheContext, IGraphqlCacheKeyGenerator } from 'common/server';\nimport { generateQueryCacheKey } from '../core/keyBuilder/generate-query-cache-key';\n\nconst cachePlugin = (apolloCachePlugin as any).default ?? apolloCachePlugin;\n\ntype ApolloCachePluginOptions = {\n logger: ILogger;\n cacheKeyGenerator: IGraphqlCacheKeyGenerator;\n};\n\nexport const isCacheable = (requestContext: GraphQLRequestContext<IGraphQLCacheContext>) => {\n const { document, schema } = requestContext;\n const cache = getDirectiveArgsFromSchema({\n schema,\n document,\n directiveName: CACHE_CONTROL_DIRECTIVE,\n });\n if (!cache) return false;\n if (cache.scope && !cache.maxAge) {\n cache.maxAge = 86400;\n }\n if (!requestContext.overallCachePolicy) {\n // eslint-disable-next-line no-param-reassign\n // to support test cases\n (requestContext as any).overallCachePolicy = {};\n }\n // eslint-disable-next-line no-param-reassign\n requestContext.overallCachePolicy.maxAge = cache.maxAge;\n return cache.maxAge > 0;\n};\n\nexport const generateCacheKey =\n ({ logger, cacheKeyGenerator }: ApolloCachePluginOptions) =>\n (requestContext: GraphQLRequestContext<IGraphQLCacheContext>): string => {\n if (!isCacheable(requestContext)) {\n return null;\n }\n const { request, contextValue, document, schema } = requestContext;\n const { user, req } = contextValue ?? {};\n const { query, variables } = request;\n const cacheControlDirective = getDirectiveArgsFromSchema({\n schema,\n document,\n directiveName: CACHE_CONTROL_DIRECTIVE,\n });\n const { scope } = cacheControlDirective ?? {};\n const isPrivate = scope?.toLowerCase() === 'private';\n const tenantId = req.tenant ?? extractTenantId(req?.currentPageUriSegments?.authority);\n const cacheKey = generateQueryCacheKey({\n query,\n variables,\n logger,\n userId: isPrivate ? user?.sub || null : null,\n tenantId,\n });\n try {\n if (typeof cacheKeyGenerator === 'function') {\n const generatedKey = cacheKeyGenerator(requestContext, cacheKey);\n return generatedKey || cacheKey;\n }\n return cacheKey;\n } catch (e) {\n console.warn('GenerateCacheKey Failed %s', e.message);\n return cacheKey;\n }\n };\nexport const responseCachePlugin = ({ logger, cacheKeyGenerator }: ApolloCachePluginOptions) =>\n cachePlugin({\n sessionId: ({ contextValue }) => Promise.resolve(contextValue?.user?.sub ?? null),\n generateCacheKey: generateCacheKey({ logger, cacheKeyGenerator }),\n shouldWriteToCache: (ctx) =>\n // Cache only successful responses\n isCacheable(ctx) && isEmpty(ctx?.response?.body?.singleResult.errors),\n });\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@common-stack/store-redis",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.1-alpha.0",
|
|
4
4
|
"description": "Redis store utilities and services for common-stack",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "CDMBase LLC",
|
|
@@ -24,16 +24,16 @@
|
|
|
24
24
|
"watch": "npm run build:lib:watch"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@common-stack/core": "8.
|
|
27
|
+
"@common-stack/core": "8.4.1-alpha.0",
|
|
28
|
+
"@upstash/redis": "^1.36.1",
|
|
29
|
+
"ioredis": "^5.4.1"
|
|
28
30
|
},
|
|
29
31
|
"devDependencies": {
|
|
30
|
-
"common": "8.3.1-alpha.
|
|
31
|
-
"ioredis": "^5.3.2"
|
|
32
|
+
"common": "8.3.1-alpha.12"
|
|
32
33
|
},
|
|
33
34
|
"peerDependencies": {
|
|
34
35
|
"graphql": ">=16.0.0",
|
|
35
|
-
"inversify": "*"
|
|
36
|
-
"ioredis": ">=5.0.0"
|
|
36
|
+
"inversify": "*"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
]
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "ac86412a3e65f14ccbf8b3a6e46f9b82ed7299da",
|
|
58
58
|
"typescript": {
|
|
59
59
|
"definition": "lib/index.d.ts"
|
|
60
60
|
}
|
package/lib/core/index.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import'ioredis';import'@upstash/redis/cloudflare';import'crypto';import'graphql/language/index.js';/**
|
|
2
|
-
* Core utilities for Redis operations
|
|
3
|
-
* @packageDocumentation
|
|
4
|
-
*/
|
|
5
|
-
// Ensure this file generates a JS output in rollup build
|
|
6
|
-
const __esModule = true;export{__esModule};//# sourceMappingURL=index.js.map
|
package/lib/core/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/core/index.ts"],"sourcesContent":["/**\n * Core utilities for Redis operations\n * @packageDocumentation\n */\n\nexport * from './ioredis';\nexport * from './upstash-redis';\nexport * from './keyBuilder';\n\n// Ensure this file generates a JS output in rollup build\nexport const __esModule = true;\n"],"names":[],"mappings":"mGAAA;;;AAGG;AAMH;AACO,MAAM,UAAU,GAAG"}
|