rps-flagforge 1.0.0 → 1.0.2

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.
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=mergeSchema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,106 @@
1
+ import { PrismaClient } from '@custom-prisma/client';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
+ import React, { ReactNode } from 'react';
4
+
5
+ type Operator = "equals" | "not_equals" | "gt" | "lt" | "contains" | "in";
6
+ interface Condition {
7
+ field: string;
8
+ operator: Operator;
9
+ value: any;
10
+ }
11
+ type RuleType = "USER_ID" | "USER_ATTRIBUTE" | "PERMISSION" | "GROUP" | "PERCENTAGE" | "CUSTOM";
12
+ interface FlagRule {
13
+ id: string;
14
+ type: RuleType;
15
+ priority: number;
16
+ conditions?: Condition | Condition[];
17
+ percentage?: number;
18
+ }
19
+ interface FeatureFlag {
20
+ key: string;
21
+ enabled: boolean;
22
+ rules: FlagRule[];
23
+ }
24
+ interface FlagContext {
25
+ userId?: string;
26
+ attributes?: Record<string, any>;
27
+ permissions?: string[];
28
+ groups?: string[];
29
+ }
30
+ interface FlagAdapter {
31
+ getFlag(key: string): Promise<FeatureFlag | null>;
32
+ }
33
+
34
+ declare class FlagEngine {
35
+ private adapter;
36
+ constructor(adapter: FlagAdapter);
37
+ isEnabled(key: string, context: FlagContext): Promise<boolean>;
38
+ private evaluateRule;
39
+ private percentageRollout;
40
+ private hash;
41
+ }
42
+
43
+ declare class PrismaFlagAdapter implements FlagAdapter {
44
+ private prisma;
45
+ constructor(prisma: PrismaClient);
46
+ getFlag(key: string): Promise<FeatureFlag | null>;
47
+ }
48
+
49
+ interface MemoryAdapterOptions {
50
+ initialFlags?: FeatureFlag[];
51
+ }
52
+ declare class MemoryFlagAdapter implements FlagAdapter {
53
+ private flags;
54
+ constructor(options?: MemoryAdapterOptions);
55
+ getFlag(key: string): Promise<FeatureFlag | null>;
56
+ /**
57
+ * Add or update a flag
58
+ */
59
+ setFlag(flag: FeatureFlag): void;
60
+ /**
61
+ * Remove a flag
62
+ */
63
+ removeFlag(key: string): void;
64
+ /**
65
+ * Get all flags (useful for debugging)
66
+ */
67
+ getAllFlags(): FeatureFlag[];
68
+ /**
69
+ * Clear all flags
70
+ */
71
+ clear(): void;
72
+ }
73
+
74
+ interface FlagProviderValue {
75
+ engine: FlagEngine;
76
+ context: FlagContext;
77
+ }
78
+ declare const FlagContextReact: React.Context<FlagProviderValue | null>;
79
+ interface FlagProviderProps {
80
+ engine: FlagEngine;
81
+ context: FlagContext;
82
+ children: ReactNode;
83
+ }
84
+ declare function FlagProvider({ engine, context, children }: FlagProviderProps): react_jsx_runtime.JSX.Element;
85
+
86
+ declare function useFlag(key: string): boolean;
87
+
88
+ interface WithFlagOptions {
89
+ fallback?: React.ReactNode;
90
+ }
91
+ declare function withFlag(flagKey: string, options?: WithFlagOptions): <P extends object>(WrappedComponent: React.ComponentType<P>) => React.FC<P>;
92
+
93
+ interface RequireFlagOptions {
94
+ key: string;
95
+ engine: FlagEngine;
96
+ }
97
+ /**
98
+ * Universal middleware/check function for any Node.js system
99
+ * @param options.key - Feature flag key
100
+ * @param options.engine - FlagEngine instance
101
+ */
102
+ declare function requireFlag({ key, engine }: RequireFlagOptions): (context: FlagContext & {
103
+ user?: any;
104
+ }, next: () => void | Promise<void>, handleDenied?: () => void) => Promise<void>;
105
+
106
+ export { type Condition, type FeatureFlag, type FlagAdapter, type FlagContext, FlagContextReact, FlagEngine, FlagProvider, type FlagProviderValue, type FlagRule, type MemoryAdapterOptions, MemoryFlagAdapter, type Operator, PrismaFlagAdapter, type RequireFlagOptions, type RuleType, requireFlag, useFlag, withFlag };
@@ -0,0 +1,106 @@
1
+ import { PrismaClient } from '@custom-prisma/client';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
+ import React, { ReactNode } from 'react';
4
+
5
+ type Operator = "equals" | "not_equals" | "gt" | "lt" | "contains" | "in";
6
+ interface Condition {
7
+ field: string;
8
+ operator: Operator;
9
+ value: any;
10
+ }
11
+ type RuleType = "USER_ID" | "USER_ATTRIBUTE" | "PERMISSION" | "GROUP" | "PERCENTAGE" | "CUSTOM";
12
+ interface FlagRule {
13
+ id: string;
14
+ type: RuleType;
15
+ priority: number;
16
+ conditions?: Condition | Condition[];
17
+ percentage?: number;
18
+ }
19
+ interface FeatureFlag {
20
+ key: string;
21
+ enabled: boolean;
22
+ rules: FlagRule[];
23
+ }
24
+ interface FlagContext {
25
+ userId?: string;
26
+ attributes?: Record<string, any>;
27
+ permissions?: string[];
28
+ groups?: string[];
29
+ }
30
+ interface FlagAdapter {
31
+ getFlag(key: string): Promise<FeatureFlag | null>;
32
+ }
33
+
34
+ declare class FlagEngine {
35
+ private adapter;
36
+ constructor(adapter: FlagAdapter);
37
+ isEnabled(key: string, context: FlagContext): Promise<boolean>;
38
+ private evaluateRule;
39
+ private percentageRollout;
40
+ private hash;
41
+ }
42
+
43
+ declare class PrismaFlagAdapter implements FlagAdapter {
44
+ private prisma;
45
+ constructor(prisma: PrismaClient);
46
+ getFlag(key: string): Promise<FeatureFlag | null>;
47
+ }
48
+
49
+ interface MemoryAdapterOptions {
50
+ initialFlags?: FeatureFlag[];
51
+ }
52
+ declare class MemoryFlagAdapter implements FlagAdapter {
53
+ private flags;
54
+ constructor(options?: MemoryAdapterOptions);
55
+ getFlag(key: string): Promise<FeatureFlag | null>;
56
+ /**
57
+ * Add or update a flag
58
+ */
59
+ setFlag(flag: FeatureFlag): void;
60
+ /**
61
+ * Remove a flag
62
+ */
63
+ removeFlag(key: string): void;
64
+ /**
65
+ * Get all flags (useful for debugging)
66
+ */
67
+ getAllFlags(): FeatureFlag[];
68
+ /**
69
+ * Clear all flags
70
+ */
71
+ clear(): void;
72
+ }
73
+
74
+ interface FlagProviderValue {
75
+ engine: FlagEngine;
76
+ context: FlagContext;
77
+ }
78
+ declare const FlagContextReact: React.Context<FlagProviderValue | null>;
79
+ interface FlagProviderProps {
80
+ engine: FlagEngine;
81
+ context: FlagContext;
82
+ children: ReactNode;
83
+ }
84
+ declare function FlagProvider({ engine, context, children }: FlagProviderProps): react_jsx_runtime.JSX.Element;
85
+
86
+ declare function useFlag(key: string): boolean;
87
+
88
+ interface WithFlagOptions {
89
+ fallback?: React.ReactNode;
90
+ }
91
+ declare function withFlag(flagKey: string, options?: WithFlagOptions): <P extends object>(WrappedComponent: React.ComponentType<P>) => React.FC<P>;
92
+
93
+ interface RequireFlagOptions {
94
+ key: string;
95
+ engine: FlagEngine;
96
+ }
97
+ /**
98
+ * Universal middleware/check function for any Node.js system
99
+ * @param options.key - Feature flag key
100
+ * @param options.engine - FlagEngine instance
101
+ */
102
+ declare function requireFlag({ key, engine }: RequireFlagOptions): (context: FlagContext & {
103
+ user?: any;
104
+ }, next: () => void | Promise<void>, handleDenied?: () => void) => Promise<void>;
105
+
106
+ export { type Condition, type FeatureFlag, type FlagAdapter, type FlagContext, FlagContextReact, FlagEngine, FlagProvider, type FlagProviderValue, type FlagRule, type MemoryAdapterOptions, MemoryFlagAdapter, type Operator, PrismaFlagAdapter, type RequireFlagOptions, type RuleType, requireFlag, useFlag, withFlag };
package/dist/index.js ADDED
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FlagContextReact: () => FlagContextReact,
24
+ FlagEngine: () => FlagEngine,
25
+ FlagProvider: () => FlagProvider,
26
+ MemoryFlagAdapter: () => MemoryFlagAdapter,
27
+ PrismaFlagAdapter: () => PrismaFlagAdapter,
28
+ requireFlag: () => requireFlag,
29
+ useFlag: () => useFlag,
30
+ withFlag: () => withFlag
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/core/engine.ts
35
+ var FlagEngine = class {
36
+ constructor(adapter) {
37
+ this.adapter = adapter;
38
+ }
39
+ async isEnabled(key, context) {
40
+ const flag = await this.adapter.getFlag(key);
41
+ if (!flag || !flag.enabled) return false;
42
+ const sortedRules = flag.rules.sort((a, b) => b.priority - a.priority);
43
+ for (const rule of sortedRules) {
44
+ const result = await this.evaluateRule(rule, context);
45
+ if (result !== null) return result;
46
+ }
47
+ return false;
48
+ }
49
+ async evaluateRule(rule, context) {
50
+ switch (rule.type) {
51
+ case "USER_ID":
52
+ return rule.conditions.includes(context.userId);
53
+ case "PERMISSION":
54
+ return context.permissions?.includes(rule.conditions.permission) ?? false;
55
+ case "USER_ATTRIBUTE":
56
+ const { field, operator, value } = rule.conditions;
57
+ const userValue = context.attributes?.[field];
58
+ switch (operator) {
59
+ case "equals":
60
+ return userValue === value;
61
+ case "gt":
62
+ return userValue > value;
63
+ case "lt":
64
+ return userValue < value;
65
+ case "contains":
66
+ return userValue?.includes(value);
67
+ }
68
+ return false;
69
+ case "GROUP_MEMBERSHIP":
70
+ return context.groups?.includes(rule.conditions.groupId) ?? false;
71
+ case "PERCENTAGE":
72
+ return this.percentageRollout(context.userId, rule.percentage);
73
+ default:
74
+ return null;
75
+ }
76
+ }
77
+ percentageRollout(userId, percentage) {
78
+ const hash = this.hash(userId);
79
+ return hash % 100 < percentage;
80
+ }
81
+ hash(str) {
82
+ let hash = 0;
83
+ for (let i = 0; i < str.length; i++) {
84
+ hash = (hash << 5) - hash + str.charCodeAt(i);
85
+ hash |= 0;
86
+ }
87
+ return Math.abs(hash);
88
+ }
89
+ };
90
+
91
+ // src/adapters/prisma.ts
92
+ var PrismaFlagAdapter = class {
93
+ constructor(prisma) {
94
+ this.prisma = prisma;
95
+ }
96
+ async getFlag(key) {
97
+ const flag = await this.prisma.featureFlag.findUnique({
98
+ where: { key },
99
+ include: { rules: true }
100
+ // make sure 'rules' relation exists
101
+ });
102
+ if (!flag) return null;
103
+ return {
104
+ key: flag.key,
105
+ enabled: flag.enabled,
106
+ rules: flag.rules
107
+ };
108
+ }
109
+ };
110
+
111
+ // src/adapters/memory.ts
112
+ var MemoryFlagAdapter = class {
113
+ constructor(options) {
114
+ this.flags = /* @__PURE__ */ new Map();
115
+ if (options?.initialFlags) {
116
+ for (const flag of options.initialFlags) {
117
+ this.flags.set(flag.key, flag);
118
+ }
119
+ }
120
+ }
121
+ async getFlag(key) {
122
+ return this.flags.get(key) ?? null;
123
+ }
124
+ /**
125
+ * Add or update a flag
126
+ */
127
+ setFlag(flag) {
128
+ this.flags.set(flag.key, flag);
129
+ }
130
+ /**
131
+ * Remove a flag
132
+ */
133
+ removeFlag(key) {
134
+ this.flags.delete(key);
135
+ }
136
+ /**
137
+ * Get all flags (useful for debugging)
138
+ */
139
+ getAllFlags() {
140
+ return Array.from(this.flags.values());
141
+ }
142
+ /**
143
+ * Clear all flags
144
+ */
145
+ clear() {
146
+ this.flags.clear();
147
+ }
148
+ };
149
+
150
+ // src/react/FlagProvider.tsx
151
+ var import_react = require("react");
152
+ var import_jsx_runtime = require("react/jsx-runtime");
153
+ var FlagContextReact = (0, import_react.createContext)(null);
154
+ function FlagProvider({
155
+ engine,
156
+ context,
157
+ children
158
+ }) {
159
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FlagContextReact.Provider, { value: { engine, context }, children });
160
+ }
161
+
162
+ // src/react/useFlag.ts
163
+ var import_react2 = require("react");
164
+ function useFlag(key) {
165
+ const ctx = (0, import_react2.useContext)(FlagContextReact);
166
+ if (!ctx) {
167
+ throw new Error("useFlag must be used inside FlagProvider");
168
+ }
169
+ const { engine, context } = ctx;
170
+ const [enabled, setEnabled] = (0, import_react2.useState)(false);
171
+ (0, import_react2.useEffect)(() => {
172
+ let cancelled = false;
173
+ engine.isEnabled(key, context).then((result) => {
174
+ if (!cancelled) {
175
+ setEnabled(result);
176
+ }
177
+ });
178
+ return () => {
179
+ cancelled = true;
180
+ };
181
+ }, [engine, key, context]);
182
+ return enabled;
183
+ }
184
+
185
+ // src/react/withFlag.tsx
186
+ var import_jsx_runtime2 = require("react/jsx-runtime");
187
+ function withFlag(flagKey, options) {
188
+ return function(WrappedComponent) {
189
+ const ComponentWithFlag = (props) => {
190
+ const enabled = useFlag(flagKey);
191
+ if (!enabled) {
192
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: options?.fallback ?? null });
193
+ }
194
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...props });
195
+ };
196
+ ComponentWithFlag.displayName = `withFlag(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`;
197
+ return ComponentWithFlag;
198
+ };
199
+ }
200
+
201
+ // src/node/middleware.ts
202
+ function requireFlag({ key, engine }) {
203
+ return async (context, next, handleDenied) => {
204
+ const allowed = await engine.isEnabled(key, {
205
+ userId: context.user?.id,
206
+ attributes: context.user,
207
+ permissions: context.user?.permissions,
208
+ groups: context.user?.groups ?? context.groups
209
+ });
210
+ if (!allowed) {
211
+ if (handleDenied) return handleDenied();
212
+ throw new Error(`Feature "${key}" is disabled for this user`);
213
+ }
214
+ return next();
215
+ };
216
+ }
217
+ // Annotate the CommonJS export names for ESM import in node:
218
+ 0 && (module.exports = {
219
+ FlagContextReact,
220
+ FlagEngine,
221
+ FlagProvider,
222
+ MemoryFlagAdapter,
223
+ PrismaFlagAdapter,
224
+ requireFlag,
225
+ useFlag,
226
+ withFlag
227
+ });
228
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core/engine.ts","../src/adapters/prisma.ts","../src/adapters/memory.ts","../src/react/FlagProvider.tsx","../src/react/useFlag.ts","../src/react/withFlag.tsx","../src/node/middleware.ts"],"sourcesContent":["// Types\r\nexport * from \"./core/types\" // exports both types & values (if any)\r\nexport type { FlagAdapter, FeatureFlag, FlagContext, FlagRule, RuleType, Condition, Operator } from \"./core/types\"\r\n\r\n// Engine\r\nexport { FlagEngine } from \"./core/engine\"\r\n\r\n// Adapters\r\nexport { PrismaFlagAdapter } from \"./adapters/prisma\"\r\nexport { MemoryFlagAdapter, MemoryAdapterOptions } from \"./adapters/memory\"\r\n\r\n// React\r\nexport { FlagProvider, FlagProviderValue, FlagContextReact } from \"./react/FlagProvider\"\r\nexport { useFlag } from \"./react/useFlag\"\r\nexport { withFlag } from \"./react/withFlag\"\r\n\r\n// Node\r\nexport { requireFlag, RequireFlagOptions } from \"./node/middleware\"\r\n","import { FlagAdapter, FlagContext } from \"./types\"\r\n\r\nexport class FlagEngine {\r\n constructor(private adapter: FlagAdapter) {}\r\n\r\n async isEnabled(key: string, context: FlagContext): Promise<boolean> {\r\n const flag = await this.adapter.getFlag(key)\r\n if (!flag || !flag.enabled) return false\r\n\r\n const sortedRules = flag.rules.sort((a: any, b: any) => b.priority - a.priority)\r\n\r\n for (const rule of sortedRules) {\r\n const result = await this.evaluateRule(rule, context)\r\n if (result !== null) return result\r\n }\r\n\r\n return false\r\n }\r\n\r\n private async evaluateRule(rule: any, context: FlagContext): Promise<boolean | null> {\r\n switch (rule.type) {\r\n case \"USER_ID\":\r\n return rule.conditions.includes(context.userId)\r\n\r\n case \"PERMISSION\":\r\n return context.permissions?.includes(rule.conditions.permission) ?? false\r\n\r\n case \"USER_ATTRIBUTE\":\r\n const { field, operator, value } = rule.conditions\r\n const userValue = context.attributes?.[field]\r\n\r\n switch (operator) {\r\n case \"equals\":\r\n return userValue === value\r\n case \"gt\":\r\n return userValue > value\r\n case \"lt\":\r\n return userValue < value\r\n case \"contains\":\r\n return userValue?.includes(value)\r\n }\r\n return false\r\n\r\n case \"GROUP_MEMBERSHIP\":\r\n return context.groups?.includes(rule.conditions.groupId) ?? false\r\n\r\n case \"PERCENTAGE\":\r\n return this.percentageRollout(context.userId!, rule.percentage)\r\n\r\n default:\r\n return null\r\n }\r\n }\r\n\r\n private percentageRollout(userId: string, percentage: number) {\r\n const hash = this.hash(userId)\r\n return (hash % 100) < percentage\r\n }\r\n\r\n private hash(str: string) {\r\n let hash = 0\r\n for (let i = 0; i < str.length; i++) {\r\n hash = (hash << 5) - hash + str.charCodeAt(i)\r\n hash |= 0\r\n }\r\n return Math.abs(hash)\r\n }\r\n}\r\n","import { PrismaClient } from \"@custom-prisma/client\" // ← match schema generator\r\nimport { FlagAdapter, FeatureFlag } from \"../core/types\"\r\n\r\nexport class PrismaFlagAdapter implements FlagAdapter {\r\n constructor(private prisma: PrismaClient) {}\r\n\r\n async getFlag(key: string): Promise<FeatureFlag | null> {\r\n const flag = await this.prisma.featureFlag.findUnique({\r\n where: { key },\r\n include: { rules: true } // make sure 'rules' relation exists\r\n })\r\n\r\n if (!flag) return null\r\n\r\n return {\r\n key: flag.key,\r\n enabled: flag.enabled,\r\n rules: flag.rules as any\r\n }\r\n }\r\n}\r\n","import type {\r\n FlagAdapter,\r\n FeatureFlag\r\n} from \"../core/types\"\r\n\r\nexport interface MemoryAdapterOptions {\r\n initialFlags?: FeatureFlag[]\r\n}\r\n\r\nexport class MemoryFlagAdapter implements FlagAdapter {\r\n private flags = new Map<string, FeatureFlag>()\r\n\r\n constructor(options?: MemoryAdapterOptions) {\r\n if (options?.initialFlags) {\r\n for (const flag of options.initialFlags) {\r\n this.flags.set(flag.key, flag)\r\n }\r\n }\r\n }\r\n\r\n async getFlag(key: string): Promise<FeatureFlag | null> {\r\n return this.flags.get(key) ?? null\r\n }\r\n\r\n /**\r\n * Add or update a flag\r\n */\r\n setFlag(flag: FeatureFlag): void {\r\n this.flags.set(flag.key, flag)\r\n }\r\n\r\n /**\r\n * Remove a flag\r\n */\r\n removeFlag(key: string): void {\r\n this.flags.delete(key)\r\n }\r\n\r\n /**\r\n * Get all flags (useful for debugging)\r\n */\r\n getAllFlags(): FeatureFlag[] {\r\n return Array.from(this.flags.values())\r\n }\r\n\r\n /**\r\n * Clear all flags\r\n */\r\n clear(): void {\r\n this.flags.clear()\r\n }\r\n}\r\n","import React, { createContext, ReactNode } from \"react\"\r\nimport type { FlagEngine } from \"../core/engine\"\r\nimport type { FlagContext } from \"../core/types\"\r\n\r\nexport interface FlagProviderValue {\r\n engine: FlagEngine\r\n context: FlagContext\r\n}\r\n\r\nexport const FlagContextReact =\r\n createContext<FlagProviderValue | null>(null)\r\n\r\ninterface FlagProviderProps {\r\n engine: FlagEngine\r\n context: FlagContext\r\n children: ReactNode\r\n}\r\n\r\nexport function FlagProvider({\r\n engine,\r\n context,\r\n children\r\n}: FlagProviderProps) {\r\n return (\r\n <FlagContextReact.Provider value={{ engine, context }}>\r\n {children}\r\n </FlagContextReact.Provider>\r\n )\r\n}\r\n","import { useContext, useEffect, useState } from \"react\"\r\nimport { FlagContextReact } from \"./FlagProvider\"\r\n\r\nexport function useFlag(key: string): boolean {\r\n const ctx = useContext(FlagContextReact)\r\n\r\n if (!ctx) {\r\n throw new Error(\"useFlag must be used inside FlagProvider\")\r\n }\r\n\r\n const { engine, context } = ctx\r\n const [enabled, setEnabled] = useState(false)\r\n\r\n useEffect(() => {\r\n let cancelled = false\r\n\r\n engine.isEnabled(key, context).then(result => {\r\n if (!cancelled) {\r\n setEnabled(result)\r\n }\r\n })\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [engine, key, context])\r\n\r\n return enabled\r\n}\r\n","import React from \"react\"\r\nimport { useFlag } from \"./useFlag\"\r\n\r\ninterface WithFlagOptions {\r\n fallback?: React.ReactNode\r\n}\r\n\r\nexport function withFlag(\r\n flagKey: string,\r\n options?: WithFlagOptions\r\n) {\r\n return function <P extends object>(\r\n WrappedComponent: React.ComponentType<P>\r\n ) {\r\n const ComponentWithFlag: React.FC<P> = (props) => {\r\n const enabled = useFlag(flagKey)\r\n\r\n if (!enabled) {\r\n return <>{options?.fallback ?? null}</>\r\n }\r\n\r\n return <WrappedComponent {...props} />\r\n }\r\n\r\n ComponentWithFlag.displayName = `withFlag(${\r\n WrappedComponent.displayName ||\r\n WrappedComponent.name ||\r\n \"Component\"\r\n })`\r\n\r\n return ComponentWithFlag\r\n }\r\n}\r\n","import { FlagEngine } from \"../core/engine\"\r\nimport type { FlagContext } from \"../core/types\"\r\n\r\nexport interface RequireFlagOptions {\r\n key: string\r\n engine: FlagEngine\r\n}\r\n\r\n/**\r\n * Universal middleware/check function for any Node.js system\r\n * @param options.key - Feature flag key\r\n * @param options.engine - FlagEngine instance\r\n */\r\nexport function requireFlag({ key, engine }: RequireFlagOptions) {\r\n /**\r\n * context: object containing user info or any relevant attributes\r\n * next: function to call if feature is enabled\r\n * handleDenied: optional function to handle denial (default throws)\r\n */\r\n return async (\r\n context: FlagContext & { user?: any },\r\n next: () => void | Promise<void>,\r\n handleDenied?: () => void\r\n ) => {\r\n const allowed = await engine.isEnabled(key, {\r\n userId: context.user?.id,\r\n attributes: context.user,\r\n permissions: context.user?.permissions,\r\n groups: context.user?.groups ?? context.groups\r\n })\r\n\r\n if (!allowed) {\r\n if (handleDenied) return handleDenied()\r\n throw new Error(`Feature \"${key}\" is disabled for this user`)\r\n }\r\n\r\n return next()\r\n }\r\n}\r\n"],"mappings":"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","names":["import_react","import_jsx_runtime"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,194 @@
1
+ // src/core/engine.ts
2
+ var FlagEngine = class {
3
+ constructor(adapter) {
4
+ this.adapter = adapter;
5
+ }
6
+ async isEnabled(key, context) {
7
+ const flag = await this.adapter.getFlag(key);
8
+ if (!flag || !flag.enabled) return false;
9
+ const sortedRules = flag.rules.sort((a, b) => b.priority - a.priority);
10
+ for (const rule of sortedRules) {
11
+ const result = await this.evaluateRule(rule, context);
12
+ if (result !== null) return result;
13
+ }
14
+ return false;
15
+ }
16
+ async evaluateRule(rule, context) {
17
+ switch (rule.type) {
18
+ case "USER_ID":
19
+ return rule.conditions.includes(context.userId);
20
+ case "PERMISSION":
21
+ return context.permissions?.includes(rule.conditions.permission) ?? false;
22
+ case "USER_ATTRIBUTE":
23
+ const { field, operator, value } = rule.conditions;
24
+ const userValue = context.attributes?.[field];
25
+ switch (operator) {
26
+ case "equals":
27
+ return userValue === value;
28
+ case "gt":
29
+ return userValue > value;
30
+ case "lt":
31
+ return userValue < value;
32
+ case "contains":
33
+ return userValue?.includes(value);
34
+ }
35
+ return false;
36
+ case "GROUP_MEMBERSHIP":
37
+ return context.groups?.includes(rule.conditions.groupId) ?? false;
38
+ case "PERCENTAGE":
39
+ return this.percentageRollout(context.userId, rule.percentage);
40
+ default:
41
+ return null;
42
+ }
43
+ }
44
+ percentageRollout(userId, percentage) {
45
+ const hash = this.hash(userId);
46
+ return hash % 100 < percentage;
47
+ }
48
+ hash(str) {
49
+ let hash = 0;
50
+ for (let i = 0; i < str.length; i++) {
51
+ hash = (hash << 5) - hash + str.charCodeAt(i);
52
+ hash |= 0;
53
+ }
54
+ return Math.abs(hash);
55
+ }
56
+ };
57
+
58
+ // src/adapters/prisma.ts
59
+ var PrismaFlagAdapter = class {
60
+ constructor(prisma) {
61
+ this.prisma = prisma;
62
+ }
63
+ async getFlag(key) {
64
+ const flag = await this.prisma.featureFlag.findUnique({
65
+ where: { key },
66
+ include: { rules: true }
67
+ // make sure 'rules' relation exists
68
+ });
69
+ if (!flag) return null;
70
+ return {
71
+ key: flag.key,
72
+ enabled: flag.enabled,
73
+ rules: flag.rules
74
+ };
75
+ }
76
+ };
77
+
78
+ // src/adapters/memory.ts
79
+ var MemoryFlagAdapter = class {
80
+ constructor(options) {
81
+ this.flags = /* @__PURE__ */ new Map();
82
+ if (options?.initialFlags) {
83
+ for (const flag of options.initialFlags) {
84
+ this.flags.set(flag.key, flag);
85
+ }
86
+ }
87
+ }
88
+ async getFlag(key) {
89
+ return this.flags.get(key) ?? null;
90
+ }
91
+ /**
92
+ * Add or update a flag
93
+ */
94
+ setFlag(flag) {
95
+ this.flags.set(flag.key, flag);
96
+ }
97
+ /**
98
+ * Remove a flag
99
+ */
100
+ removeFlag(key) {
101
+ this.flags.delete(key);
102
+ }
103
+ /**
104
+ * Get all flags (useful for debugging)
105
+ */
106
+ getAllFlags() {
107
+ return Array.from(this.flags.values());
108
+ }
109
+ /**
110
+ * Clear all flags
111
+ */
112
+ clear() {
113
+ this.flags.clear();
114
+ }
115
+ };
116
+
117
+ // src/react/FlagProvider.tsx
118
+ import { createContext } from "react";
119
+ import { jsx } from "react/jsx-runtime";
120
+ var FlagContextReact = createContext(null);
121
+ function FlagProvider({
122
+ engine,
123
+ context,
124
+ children
125
+ }) {
126
+ return /* @__PURE__ */ jsx(FlagContextReact.Provider, { value: { engine, context }, children });
127
+ }
128
+
129
+ // src/react/useFlag.ts
130
+ import { useContext, useEffect, useState } from "react";
131
+ function useFlag(key) {
132
+ const ctx = useContext(FlagContextReact);
133
+ if (!ctx) {
134
+ throw new Error("useFlag must be used inside FlagProvider");
135
+ }
136
+ const { engine, context } = ctx;
137
+ const [enabled, setEnabled] = useState(false);
138
+ useEffect(() => {
139
+ let cancelled = false;
140
+ engine.isEnabled(key, context).then((result) => {
141
+ if (!cancelled) {
142
+ setEnabled(result);
143
+ }
144
+ });
145
+ return () => {
146
+ cancelled = true;
147
+ };
148
+ }, [engine, key, context]);
149
+ return enabled;
150
+ }
151
+
152
+ // src/react/withFlag.tsx
153
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
154
+ function withFlag(flagKey, options) {
155
+ return function(WrappedComponent) {
156
+ const ComponentWithFlag = (props) => {
157
+ const enabled = useFlag(flagKey);
158
+ if (!enabled) {
159
+ return /* @__PURE__ */ jsx2(Fragment, { children: options?.fallback ?? null });
160
+ }
161
+ return /* @__PURE__ */ jsx2(WrappedComponent, { ...props });
162
+ };
163
+ ComponentWithFlag.displayName = `withFlag(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`;
164
+ return ComponentWithFlag;
165
+ };
166
+ }
167
+
168
+ // src/node/middleware.ts
169
+ function requireFlag({ key, engine }) {
170
+ return async (context, next, handleDenied) => {
171
+ const allowed = await engine.isEnabled(key, {
172
+ userId: context.user?.id,
173
+ attributes: context.user,
174
+ permissions: context.user?.permissions,
175
+ groups: context.user?.groups ?? context.groups
176
+ });
177
+ if (!allowed) {
178
+ if (handleDenied) return handleDenied();
179
+ throw new Error(`Feature "${key}" is disabled for this user`);
180
+ }
181
+ return next();
182
+ };
183
+ }
184
+ export {
185
+ FlagContextReact,
186
+ FlagEngine,
187
+ FlagProvider,
188
+ MemoryFlagAdapter,
189
+ PrismaFlagAdapter,
190
+ requireFlag,
191
+ useFlag,
192
+ withFlag
193
+ };
194
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/engine.ts","../src/adapters/prisma.ts","../src/adapters/memory.ts","../src/react/FlagProvider.tsx","../src/react/useFlag.ts","../src/react/withFlag.tsx","../src/node/middleware.ts"],"sourcesContent":["import { FlagAdapter, FlagContext } from \"./types\"\r\n\r\nexport class FlagEngine {\r\n constructor(private adapter: FlagAdapter) {}\r\n\r\n async isEnabled(key: string, context: FlagContext): Promise<boolean> {\r\n const flag = await this.adapter.getFlag(key)\r\n if (!flag || !flag.enabled) return false\r\n\r\n const sortedRules = flag.rules.sort((a: any, b: any) => b.priority - a.priority)\r\n\r\n for (const rule of sortedRules) {\r\n const result = await this.evaluateRule(rule, context)\r\n if (result !== null) return result\r\n }\r\n\r\n return false\r\n }\r\n\r\n private async evaluateRule(rule: any, context: FlagContext): Promise<boolean | null> {\r\n switch (rule.type) {\r\n case \"USER_ID\":\r\n return rule.conditions.includes(context.userId)\r\n\r\n case \"PERMISSION\":\r\n return context.permissions?.includes(rule.conditions.permission) ?? false\r\n\r\n case \"USER_ATTRIBUTE\":\r\n const { field, operator, value } = rule.conditions\r\n const userValue = context.attributes?.[field]\r\n\r\n switch (operator) {\r\n case \"equals\":\r\n return userValue === value\r\n case \"gt\":\r\n return userValue > value\r\n case \"lt\":\r\n return userValue < value\r\n case \"contains\":\r\n return userValue?.includes(value)\r\n }\r\n return false\r\n\r\n case \"GROUP_MEMBERSHIP\":\r\n return context.groups?.includes(rule.conditions.groupId) ?? false\r\n\r\n case \"PERCENTAGE\":\r\n return this.percentageRollout(context.userId!, rule.percentage)\r\n\r\n default:\r\n return null\r\n }\r\n }\r\n\r\n private percentageRollout(userId: string, percentage: number) {\r\n const hash = this.hash(userId)\r\n return (hash % 100) < percentage\r\n }\r\n\r\n private hash(str: string) {\r\n let hash = 0\r\n for (let i = 0; i < str.length; i++) {\r\n hash = (hash << 5) - hash + str.charCodeAt(i)\r\n hash |= 0\r\n }\r\n return Math.abs(hash)\r\n }\r\n}\r\n","import { PrismaClient } from \"@custom-prisma/client\" // ← match schema generator\r\nimport { FlagAdapter, FeatureFlag } from \"../core/types\"\r\n\r\nexport class PrismaFlagAdapter implements FlagAdapter {\r\n constructor(private prisma: PrismaClient) {}\r\n\r\n async getFlag(key: string): Promise<FeatureFlag | null> {\r\n const flag = await this.prisma.featureFlag.findUnique({\r\n where: { key },\r\n include: { rules: true } // make sure 'rules' relation exists\r\n })\r\n\r\n if (!flag) return null\r\n\r\n return {\r\n key: flag.key,\r\n enabled: flag.enabled,\r\n rules: flag.rules as any\r\n }\r\n }\r\n}\r\n","import type {\r\n FlagAdapter,\r\n FeatureFlag\r\n} from \"../core/types\"\r\n\r\nexport interface MemoryAdapterOptions {\r\n initialFlags?: FeatureFlag[]\r\n}\r\n\r\nexport class MemoryFlagAdapter implements FlagAdapter {\r\n private flags = new Map<string, FeatureFlag>()\r\n\r\n constructor(options?: MemoryAdapterOptions) {\r\n if (options?.initialFlags) {\r\n for (const flag of options.initialFlags) {\r\n this.flags.set(flag.key, flag)\r\n }\r\n }\r\n }\r\n\r\n async getFlag(key: string): Promise<FeatureFlag | null> {\r\n return this.flags.get(key) ?? null\r\n }\r\n\r\n /**\r\n * Add or update a flag\r\n */\r\n setFlag(flag: FeatureFlag): void {\r\n this.flags.set(flag.key, flag)\r\n }\r\n\r\n /**\r\n * Remove a flag\r\n */\r\n removeFlag(key: string): void {\r\n this.flags.delete(key)\r\n }\r\n\r\n /**\r\n * Get all flags (useful for debugging)\r\n */\r\n getAllFlags(): FeatureFlag[] {\r\n return Array.from(this.flags.values())\r\n }\r\n\r\n /**\r\n * Clear all flags\r\n */\r\n clear(): void {\r\n this.flags.clear()\r\n }\r\n}\r\n","import React, { createContext, ReactNode } from \"react\"\r\nimport type { FlagEngine } from \"../core/engine\"\r\nimport type { FlagContext } from \"../core/types\"\r\n\r\nexport interface FlagProviderValue {\r\n engine: FlagEngine\r\n context: FlagContext\r\n}\r\n\r\nexport const FlagContextReact =\r\n createContext<FlagProviderValue | null>(null)\r\n\r\ninterface FlagProviderProps {\r\n engine: FlagEngine\r\n context: FlagContext\r\n children: ReactNode\r\n}\r\n\r\nexport function FlagProvider({\r\n engine,\r\n context,\r\n children\r\n}: FlagProviderProps) {\r\n return (\r\n <FlagContextReact.Provider value={{ engine, context }}>\r\n {children}\r\n </FlagContextReact.Provider>\r\n )\r\n}\r\n","import { useContext, useEffect, useState } from \"react\"\r\nimport { FlagContextReact } from \"./FlagProvider\"\r\n\r\nexport function useFlag(key: string): boolean {\r\n const ctx = useContext(FlagContextReact)\r\n\r\n if (!ctx) {\r\n throw new Error(\"useFlag must be used inside FlagProvider\")\r\n }\r\n\r\n const { engine, context } = ctx\r\n const [enabled, setEnabled] = useState(false)\r\n\r\n useEffect(() => {\r\n let cancelled = false\r\n\r\n engine.isEnabled(key, context).then(result => {\r\n if (!cancelled) {\r\n setEnabled(result)\r\n }\r\n })\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [engine, key, context])\r\n\r\n return enabled\r\n}\r\n","import React from \"react\"\r\nimport { useFlag } from \"./useFlag\"\r\n\r\ninterface WithFlagOptions {\r\n fallback?: React.ReactNode\r\n}\r\n\r\nexport function withFlag(\r\n flagKey: string,\r\n options?: WithFlagOptions\r\n) {\r\n return function <P extends object>(\r\n WrappedComponent: React.ComponentType<P>\r\n ) {\r\n const ComponentWithFlag: React.FC<P> = (props) => {\r\n const enabled = useFlag(flagKey)\r\n\r\n if (!enabled) {\r\n return <>{options?.fallback ?? null}</>\r\n }\r\n\r\n return <WrappedComponent {...props} />\r\n }\r\n\r\n ComponentWithFlag.displayName = `withFlag(${\r\n WrappedComponent.displayName ||\r\n WrappedComponent.name ||\r\n \"Component\"\r\n })`\r\n\r\n return ComponentWithFlag\r\n }\r\n}\r\n","import { FlagEngine } from \"../core/engine\"\r\nimport type { FlagContext } from \"../core/types\"\r\n\r\nexport interface RequireFlagOptions {\r\n key: string\r\n engine: FlagEngine\r\n}\r\n\r\n/**\r\n * Universal middleware/check function for any Node.js system\r\n * @param options.key - Feature flag key\r\n * @param options.engine - FlagEngine instance\r\n */\r\nexport function requireFlag({ key, engine }: RequireFlagOptions) {\r\n /**\r\n * context: object containing user info or any relevant attributes\r\n * next: function to call if feature is enabled\r\n * handleDenied: optional function to handle denial (default throws)\r\n */\r\n return async (\r\n context: FlagContext & { user?: any },\r\n next: () => void | Promise<void>,\r\n handleDenied?: () => void\r\n ) => {\r\n const allowed = await engine.isEnabled(key, {\r\n userId: context.user?.id,\r\n attributes: context.user,\r\n permissions: context.user?.permissions,\r\n groups: context.user?.groups ?? context.groups\r\n })\r\n\r\n if (!allowed) {\r\n if (handleDenied) return handleDenied()\r\n throw new Error(`Feature \"${key}\" is disabled for this user`)\r\n }\r\n\r\n return next()\r\n }\r\n}\r\n"],"mappings":"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","names":["jsx"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rps-flagforge",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A Multi-Core Feature Flag Package with support for React & NodeJS",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -28,6 +28,7 @@
28
28
  "devDependencies": {
29
29
  "@types/express": "^5.0.6",
30
30
  "@types/node": "^25.2.3",
31
+ "@types/prompts": "^2.4.9",
31
32
  "@types/react": "^19.2.14",
32
33
  "prisma": "^6.14.0",
33
34
  "tsup": "^8.5.1",