11import "reflect-metadata" ;
22import { checkFeatureFlag } from "../lib/utils/featureFlags" ;
3- import { isError } from "../packages/common/result" ;
3+ import { isError , Result , err } from "../packages/common/result" ;
44import { Controller } from "tsoa" ;
55import { JawnAuthenticatedRequest } from "../types/request" ;
66
77const FEATURE_FLAG_METADATA_KEY = Symbol ( "featureFlags" ) ;
88
9+ /**
10+ * Base constraint for error types - can be string enums, string literals, or any serializable type
11+ */
12+ export type ErrorType = string | number | symbol ;
13+
914/**
1015 * Generic error formatter type that can work with any error system
1116 */
12- export type ErrorFormatter < T = any > = ( flag : string ) => {
17+ export type ErrorFormatter < TError extends ErrorType = string > = ( flag : string ) => {
1318 message : string ;
1419 statusCode : number ;
15- error ?: T ;
20+ error ?: TError ;
1621} ;
1722
1823/**
1924 * Options for feature flag decorator with generic error type
2025 */
21- export interface FeatureFlagOptions < T = any > {
22- errorFormatter ?: ErrorFormatter < T > ;
26+ export interface FeatureFlagOptions < TError extends ErrorType = string > {
27+ errorFormatter ?: ErrorFormatter < TError > ;
2328}
2429
2530/**
2631 * Metadata stored for feature flags
2732 */
28- export interface FeatureFlagMetadata < T = any > {
33+ export interface FeatureFlagMetadata < TError extends ErrorType = string > {
2934 flags : string [ ] ;
30- options ?: FeatureFlagOptions < T > ;
35+ options ?: FeatureFlagOptions < TError > ;
3136}
3237
33- export function RequireFeatureFlag < T = any > (
38+ /**
39+ * Type for controller methods that can be decorated
40+ * These methods take JawnAuthenticatedRequest and return Result
41+ */
42+ type ControllerMethod = (
43+ this : Controller ,
44+ ...args : any [ ] // Could include @Body , @Request, @Query params etc.
45+ ) => Promise < Result < any , string > > ;
46+
47+ /**
48+ * Generic decorator to require feature flag(s) for a controller method.
49+ * Can be used with any error system by providing a custom error formatter.
50+ *
51+ * @param flag - The feature flag name to check
52+ * @param options - Optional configuration for error handling
53+ *
54+ * @example
55+ * // Using default error format
56+ * @RequireFeatureFlag ("my-feature")
57+ *
58+ * @example
59+ * // Using with HQL error system (strongly typed)
60+ * @RequireFeatureFlag <HqlErrorCode>(HQL_FEATURE_FLAG, {
61+ * errorFormatter: (flag) => ({
62+ * message: `[${HqlErrorCode.FEATURE_NOT_ENABLED}] Feature not enabled`,
63+ * statusCode: 403,
64+ * error: HqlErrorCode.FEATURE_NOT_ENABLED
65+ * })
66+ * })
67+ *
68+ * @example
69+ * // Using with string literal types
70+ * type MyError = "FEATURE_DISABLED" | "NOT_AVAILABLE";
71+ * @RequireFeatureFlag <MyError>("my-feature", {
72+ * errorFormatter: (flag) => ({
73+ * message: `Feature ${flag} is disabled`,
74+ * statusCode: 403,
75+ * error: "FEATURE_DISABLED"
76+ * })
77+ * })
78+ */
79+ export function RequireFeatureFlag < TError extends ErrorType = string > (
3480 flag : string ,
35- options ?: FeatureFlagOptions < T >
81+ options ?: FeatureFlagOptions < TError >
3682) : MethodDecorator {
3783 return function (
38- target : any ,
84+ target : object ,
3985 propertyKey : string | symbol ,
4086 descriptor : PropertyDescriptor
41- ) {
42- const existingMetadata : FeatureFlagMetadata < T > = Reflect . getMetadata (
87+ ) : PropertyDescriptor {
88+ const existingMetadata : FeatureFlagMetadata < TError > = Reflect . getMetadata (
4389 FEATURE_FLAG_METADATA_KEY ,
4490 target ,
4591 propertyKey
@@ -57,25 +103,26 @@ export function RequireFeatureFlag<T = any>(
57103 propertyKey
58104 ) ;
59105
60- const originalMethod = descriptor . value ;
106+ const originalMethod = descriptor . value as ControllerMethod ;
61107
62108 descriptor . value = async function (
63109 this : Controller ,
64110 ...args : any [ ]
65- ) {
111+ ) : Promise < Result < any , string > > {
112+ // Find the JawnAuthenticatedRequest in the arguments
66113 const request = args . find (
67- ( arg ) => arg && typeof arg === "object" && "authParams" in arg
68- ) as JawnAuthenticatedRequest | undefined ;
114+ ( arg ) : arg is JawnAuthenticatedRequest =>
115+ arg !== null &&
116+ typeof arg === "object" &&
117+ "authParams" in arg
118+ ) ;
69119
70120 if ( ! request ?. authParams ?. organizationId ) {
71121 this . setStatus ( 401 ) ;
72- return {
73- error : "Authentication required" ,
74- data : null ,
75- } ;
122+ return err ( "Authentication required" ) ;
76123 }
77124
78- const metadata : FeatureFlagMetadata < T > = Reflect . getMetadata (
125+ const metadata : FeatureFlagMetadata < TError > = Reflect . getMetadata (
79126 FEATURE_FLAG_METADATA_KEY ,
80127 target ,
81128 propertyKey
@@ -97,10 +144,7 @@ export function RequireFeatureFlag<T = any>(
97144 } ;
98145
99146 this . setStatus ( errorInfo . statusCode ) ;
100- return {
101- error : errorInfo . message ,
102- data : null ,
103- } ;
147+ return err ( errorInfo . message ) ;
104148 }
105149 }
106150
@@ -123,17 +167,17 @@ export function RequireFeatureFlag<T = any>(
123167 * errorFormatter: (flag) => ({ ... })
124168 * })
125169 */
126- export function RequireFeatureFlags < T = any > (
170+ export function RequireFeatureFlags < TError extends ErrorType = string > (
127171 flags : string [ ] ,
128- options ?: FeatureFlagOptions < T >
172+ options ?: FeatureFlagOptions < TError >
129173) : MethodDecorator {
130174 return function (
131- target : any ,
175+ target : object ,
132176 propertyKey : string | symbol ,
133177 descriptor : PropertyDescriptor
134- ) {
178+ ) : PropertyDescriptor {
135179 flags . forEach ( ( flag ) => {
136- RequireFeatureFlag < T > ( flag , options ) ( target , propertyKey , descriptor ) ;
180+ RequireFeatureFlag < TError > ( flag , options ) ( target , propertyKey , descriptor ) ;
137181 } ) ;
138182 return descriptor ;
139183 } ;
@@ -149,11 +193,11 @@ export function RequireFeatureFlags<T = any>(
149193 * 403
150194 * );
151195 */
152- export function createErrorFormatter < T > (
153- errorSelector : ( flag : string ) => T ,
154- messageFormatter : ( error : T , flag : string ) => string ,
196+ export function createErrorFormatter < TError extends ErrorType > (
197+ errorSelector : ( flag : string ) => TError ,
198+ messageFormatter : ( error : TError , flag : string ) => string ,
155199 statusCode : number = 403
156- ) : ErrorFormatter < T > {
200+ ) : ErrorFormatter < TError > {
157201 return ( flag : string ) => {
158202 const error = errorSelector ( flag ) ;
159203 return {
@@ -164,9 +208,12 @@ export function createErrorFormatter<T>(
164208 } ;
165209}
166210
167- export function getFeatureFlagMetadata < T = any > (
168- target : any ,
211+ /**
212+ * Retrieves feature flag metadata from a decorated method
213+ */
214+ export function getFeatureFlagMetadata < TError extends ErrorType = string > (
215+ target : object ,
169216 propertyKey : string | symbol
170- ) : FeatureFlagMetadata < T > | undefined {
217+ ) : FeatureFlagMetadata < TError > | undefined {
171218 return Reflect . getMetadata ( FEATURE_FLAG_METADATA_KEY , target , propertyKey ) ;
172219}
0 commit comments