1+ import type { CommandReply } from '../../commands/generic-transformers' ;
2+ import type { CommandPolicies } from './policies-constants' ;
3+ import { REQUEST_POLICIES_WITH_DEFAULTS , RESPONSE_POLICIES_WITH_DEFAULTS } from './policies-constants' ;
4+ import type { PolicyResolver } from './types' ;
5+ import { StaticPolicyResolver } from './static-policy-resolver' ;
6+ import type { ModulePolicyRecords } from './static-policies-data' ;
7+
8+ /**
9+ * Function type that returns command information from Redis
10+ */
11+ export type CommandFetcher = ( ) => Promise < Array < CommandReply > > ;
12+
13+ /**
14+ * A factory for creating policy resolvers that dynamically build policies based on the Redis server's COMMAND response.
15+ *
16+ * This factory fetches command information from Redis and analyzes the response to determine
17+ * appropriate routing policies for each command, returning a StaticPolicyResolver with the built policies.
18+ */
19+ export class DynamicPolicyResolverFactory {
20+ /**
21+ * Creates a StaticPolicyResolver by fetching command information from Redis
22+ * and building appropriate policies based on the command characteristics.
23+ *
24+ * @param commandFetcher Function to fetch command information from Redis
25+ * @param fallbackResolver Optional fallback resolver to use when policies are not found
26+ * @returns A new StaticPolicyResolver with the fetched policies
27+ */
28+ static async create (
29+ commandFetcher : CommandFetcher ,
30+ fallbackResolver ?: PolicyResolver
31+ ) : Promise < StaticPolicyResolver > {
32+ const commands = await commandFetcher ( ) ;
33+ const policies : ModulePolicyRecords = { } ;
34+
35+ for ( const command of commands ) {
36+ const parsed = DynamicPolicyResolverFactory . #parseCommandName( command . name ) ;
37+
38+ // Skip commands with invalid format (more than one dot)
39+ if ( ! parsed ) {
40+ continue ;
41+ }
42+
43+ const { moduleName, commandName } = parsed ;
44+
45+ // Initialize module if it doesn't exist
46+ if ( ! policies [ moduleName ] ) {
47+ policies [ moduleName ] = { } ;
48+ }
49+
50+ // Determine policies for this command
51+ const commandPolicies = DynamicPolicyResolverFactory . #buildCommandPolicies( command ) ;
52+ policies [ moduleName ] [ commandName ] = commandPolicies ;
53+ }
54+
55+ return new StaticPolicyResolver ( policies , fallbackResolver ) ;
56+ }
57+
58+ /**
59+ * Parses a command name to extract module and command components.
60+ *
61+ * Redis commands can be in format:
62+ * - "ping" -> module: "std", command: "ping"
63+ * - "ft.search" -> module: "ft", command: "search"
64+ *
65+ * Commands with more than one dot are invalid.
66+ */
67+ static #parseCommandName( fullCommandName : string ) : { moduleName : string ; commandName : string } | null {
68+ const parts = fullCommandName . split ( '.' ) ;
69+
70+ if ( parts . length === 1 ) {
71+ return { moduleName : 'std' , commandName : fullCommandName } ;
72+ }
73+
74+ if ( parts . length === 2 ) {
75+ return { moduleName : parts [ 0 ] , commandName : parts [ 1 ] } ;
76+ }
77+
78+ // Commands with more than one dot are invalid in Redis
79+ return null ;
80+ }
81+
82+ /**
83+ * Builds CommandPolicies for a command based on its characteristics.
84+ *
85+ * Priority order:
86+ * 1. Use explicit policies from the command if available
87+ * 2. Classify as DEFAULT_KEYLESS if keySpecification is empty
88+ * 3. Classify as DEFAULT_KEYED if keySpecification is not empty
89+ */
90+ static #buildCommandPolicies( command : CommandReply ) : CommandPolicies {
91+ // Determine if command is keyless based on keySpecification
92+ const isKeyless = command . keySpecifications === 'keyless' ;
93+
94+ // Determine default policies based on key specification
95+ const defaultRequest = isKeyless
96+ ? REQUEST_POLICIES_WITH_DEFAULTS . DEFAULT_KEYLESS
97+ : REQUEST_POLICIES_WITH_DEFAULTS . DEFAULT_KEYED ;
98+ const defaultResponse = isKeyless
99+ ? RESPONSE_POLICIES_WITH_DEFAULTS . DEFAULT_KEYLESS
100+ : RESPONSE_POLICIES_WITH_DEFAULTS . DEFAULT_KEYED ;
101+
102+ return {
103+ // request: command.policies.request ?? defaultRequest,
104+ // response: command.policies.response ?? defaultResponse
105+ request : defaultRequest ,
106+ response : defaultResponse
107+ } ;
108+ }
109+ }
0 commit comments