rps-flagforge 1.1.2 ā 1.1.3
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/dist/bin/mergeSchema.js +0 -0
- package/dist/bin/mergeSchema.js.map +1 -1
- package/dist/index.d.mts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +286 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +273 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/bin/mergeSchema.js
CHANGED
|
File without changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../bin/mergeSchema.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport fs from \"fs\";\r\nimport path from \"path\";\r\nimport chalk from \"chalk\";\r\nimport prompts from \"prompts\";\r\n\r\n// -----------------------------\r\n// CLI Flags\r\n// -----------------------------\r\nconst args = process.argv.slice(2);\r\nconst isDryRun = args.includes(\"--dry-run\");\r\nconst isForce = args.includes(\"--force\");\r\n\r\n// -----------------------------\r\n// Paths\r\n// -----------------------------\r\nconst appSchema = path.resolve(process.cwd(), \"prisma/schema.prisma\");\r\nconst packageSchema = path.resolve(__dirname, \"../../prisma/schema.prisma\");\r\n\r\n// -----------------------------\r\n// Helpers\r\n// -----------------------------\r\nfunction extractBlocks(schema: string, type: \"model\" | \"enum\"): string[] {\r\n const regex = new RegExp(`${type}\\\\s+\\\\w+\\\\s+{[\\\\s\\\\S]*?}\\\\n?`, \"g\");\r\n return schema.match(regex) || [];\r\n}\r\n\r\nfunction parseBlockFields(block: string) {\r\n return block\r\n .split(\"\\n\")\r\n .map(l => l.trim())\r\n .filter(\r\n l =>\r\n l &&\r\n !l.startsWith(\"//\") &&\r\n !l.startsWith(\"model\") &&\r\n !l.startsWith(\"enum\") &&\r\n !l.startsWith(\"{\") &&\r\n !l.startsWith(\"}\")\r\n );\r\n}\r\n\r\nfunction mergeModelFields(existing: string, incoming: string) {\r\n const existingFields = parseBlockFields(existing);\r\n const incomingFields = parseBlockFields(incoming);\r\n\r\n const mergedFields: string[] = [...existingFields];\r\n const diff: string[] = [];\r\n\r\n const map = new Map(existingFields.map(f => [f.split(\" \")[0], f]));\r\n\r\n for (const field of incomingFields) {\r\n const name = field.split(\" \")[0];\r\n if (!map.has(name)) {\r\n mergedFields.push(field);\r\n diff.push(chalk.green(`+ ${field}`));\r\n } else if (map.get(name) !== field) {\r\n const idx = mergedFields.findIndex(f => f.split(\" \")[0] === name);\r\n mergedFields[idx] = field;\r\n diff.push(chalk.yellow(`~ ${field}`));\r\n }\r\n }\r\n\r\n const mergedBlock = existing.replace(/\\{[\\s\\S]*\\}/, `{ \\n ${mergedFields.join(\"\\n \")} \\n}`);\r\n return { mergedBlock, diff };\r\n}\r\n\r\nfunction mergeEnumValues(existing: string, incoming: string) {\r\n const existingValues = parseBlockFields(existing);\r\n const incomingValues = parseBlockFields(incoming);\r\n\r\n const mergedValues = [...existingValues];\r\n const diff: string[] = [];\r\n\r\n for (const v of incomingValues) {\r\n if (!existingValues.includes(v)) {\r\n mergedValues.push(v);\r\n diff.push(chalk.green(`+ ${v}`));\r\n }\r\n }\r\n\r\n const mergedBlock = existing.replace(/\\{[\\s\\S]*\\}/, `{ \\n ${mergedValues.join(\"\\n \")} \\n}`);\r\n return { mergedBlock, diff };\r\n}\r\n\r\n// Replace blocks in schema (update existing, append only new)\r\nfunction applyMergedBlocks(original: string, blocks: string[], type: \"model\" | \"enum\") {\r\n let content = original;\r\n for (const block of blocks) {\r\n const match = block.match(new RegExp(`${type}\\\\s+(\\\\w+)\\\\s+{`));\r\n if (!match) continue;\r\n const name = match[1];\r\n\r\n const regex = new RegExp(`${type}\\\\s+${name}\\\\s+{[\\\\s\\\\S]*?}`, \"g\");\r\n if (regex.test(content)) {\r\n content = content.replace(regex, block); // update existing\r\n } else {\r\n content += `\\n\\n${block}`; // append new\r\n }\r\n }\r\n return content;\r\n}\r\n\r\n// -----------------------------\r\n// Main\r\n// -----------------------------\r\nasync function main() {\r\n console.log(chalk.blueBright.bold(\"\\nā” FlagsForge Prisma Schema Smart Merger\\n\"));\r\n\r\n if (isDryRun) console.log(chalk.cyan(\"š Dry run: preview all changes\\n\"));\r\n if (isForce) console.log(chalk.yellow(\"ā ļø Force mode enabled (skipping backup prompts)\\n\"));\r\n\r\n if (!fs.existsSync(appSchema)) {\r\n console.error(chalk.red(\"ā App schema not found at prisma/schema.prisma\"));\r\n process.exit(1);\r\n }\r\n if (!fs.existsSync(packageSchema)) {\r\n console.error(chalk.red(\"ā Package Prisma models not found\"));\r\n process.exit(1);\r\n }\r\n\r\n const appContent = fs.readFileSync(appSchema, \"utf-8\");\r\n const packageRaw = fs.readFileSync(packageSchema, \"utf-8\");\r\n const packageContent = packageRaw.replace(/generator\\s+\\w+\\s+{[\\s\\S]*?}\\n?/g, \"\").replace(/datasource\\s+\\w+\\s+{[\\s\\S]*?}\\n?/g, \"\");\r\n\r\n const appModels = extractBlocks(appContent, \"model\");\r\n const appEnums = extractBlocks(appContent, \"enum\");\r\n const packageModels = extractBlocks(packageContent, \"model\");\r\n const packageEnums = extractBlocks(packageContent, \"enum\");\r\n\r\n // Merge models\r\n const mergedModels: string[] = [];\r\n const modelDiffs: string[] = [];\r\n for (const pModel of packageModels) {\r\n const match = pModel.match(/model\\s+(\\w+)\\s+{/);\r\n if (!match) continue;\r\n const name = match[1];\r\n\r\n const existing = appModels.find(m => m.match(new RegExp(`model\\\\s+${name}\\\\s+{`)));\r\n if (existing) {\r\n const { mergedBlock, diff } = mergeModelFields(existing, pModel);\r\n mergedModels.push(mergedBlock);\r\n modelDiffs.push(...diff);\r\n } else {\r\n mergedModels.push(pModel);\r\n modelDiffs.push(chalk.green(`+ New model: ${name}`));\r\n }\r\n }\r\n\r\n // Merge enums\r\n const mergedEnums: string[] = [];\r\n const enumDiffs: string[] = [];\r\n for (const pEnum of packageEnums) {\r\n const match = pEnum.match(/enum\\s+(\\w+)\\s+{/);\r\n if (!match) continue;\r\n const name = match[1];\r\n\r\n const existing = appEnums.find(e => e.match(new RegExp(`enum\\\\s+${name}\\\\s+{`)));\r\n if (existing) {\r\n const { mergedBlock, diff } = mergeEnumValues(existing, pEnum);\r\n mergedEnums.push(mergedBlock);\r\n enumDiffs.push(...diff);\r\n } else {\r\n mergedEnums.push(pEnum);\r\n enumDiffs.push(chalk.green(`+ New enum: ${name}`));\r\n }\r\n }\r\n\r\n // ------------------------------\r\n // Prepend the FlagsForge Schema comment\r\n // ------------------------------\r\n const extensionComment = \"\\n\\n// ------------------------------\\n\" +\r\n \"// FlagsForge Schema Extensions\\n\" +\r\n \"// ------------------------------\\n\\n\";\r\n\r\n let mergedContent = appContent.trim() + extensionComment;\r\n mergedContent = applyMergedBlocks(mergedContent, mergedModels, \"model\");\r\n mergedContent = applyMergedBlocks(mergedContent, mergedEnums, \"enum\");\r\n\r\n // Dry-run preview\r\n if (isDryRun) {\r\n console.log(chalk.gray(\"----- FULL MERGE DIFF -----\\n\"));\r\n [...enumDiffs, ...modelDiffs].forEach(line => console.log(line));\r\n console.log(chalk.gray(\"\\n------------------------------\"));\r\n console.log(chalk.cyan(\"⨠Dry run complete. No changes were made.\\n\"));\r\n process.exit(0);\r\n }\r\n\r\n // Backup prompt\r\n let shouldBackup = true;\r\n if (!isForce) {\r\n const response = await prompts({\r\n type: \"confirm\",\r\n name: \"backup\",\r\n message: \"Create a backup of your current schema?\",\r\n initial: true,\r\n });\r\n shouldBackup = response.backup;\r\n }\r\n\r\n if (shouldBackup) {\r\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\r\n const backupPath = `${appSchema}.bak-${timestamp}`;\r\n fs.copyFileSync(appSchema, backupPath);\r\n console.log(chalk.green(`ā
Backup created: ${backupPath}\\n`));\r\n }\r\n\r\n fs.writeFileSync(appSchema, mergedContent);\r\n console.log(chalk.green.bold(\"ā
Prisma schema merged successfully!\"));\r\n console.log(chalk.yellow(\"Run `npx prisma generate` to update your client.\\n\"));\r\n}\r\n\r\nmain().catch(err => {\r\n console.error(chalk.red(\"ā Error merging schemas:\"), err);\r\n process.exit(1);\r\n});\r\n"],"mappings":"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","names":["path","chalk","fs","prompts"]}
|
|
1
|
+
{"version":3,"sources":["../../bin/mergeSchema.ts"],"sourcesContent":["#!/usr/bin/env node\nimport fs from \"fs\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport prompts from \"prompts\";\n\n// -----------------------------\n// CLI Flags\n// -----------------------------\nconst args = process.argv.slice(2);\nconst isDryRun = args.includes(\"--dry-run\");\nconst isForce = args.includes(\"--force\");\n\n// -----------------------------\n// Paths\n// -----------------------------\nconst appSchema = path.resolve(process.cwd(), \"prisma/schema.prisma\");\nconst packageSchema = path.resolve(__dirname, \"../../prisma/schema.prisma\");\n\n// -----------------------------\n// Helpers\n// -----------------------------\nfunction extractBlocks(schema: string, type: \"model\" | \"enum\"): string[] {\n const regex = new RegExp(`${type}\\\\s+\\\\w+\\\\s+{[\\\\s\\\\S]*?}\\\\n?`, \"g\");\n return schema.match(regex) || [];\n}\n\nfunction parseBlockFields(block: string) {\n return block\n .split(\"\\n\")\n .map(l => l.trim())\n .filter(\n l =>\n l &&\n !l.startsWith(\"//\") &&\n !l.startsWith(\"model\") &&\n !l.startsWith(\"enum\") &&\n !l.startsWith(\"{\") &&\n !l.startsWith(\"}\")\n );\n}\n\nfunction mergeModelFields(existing: string, incoming: string) {\n const existingFields = parseBlockFields(existing);\n const incomingFields = parseBlockFields(incoming);\n\n const mergedFields: string[] = [...existingFields];\n const diff: string[] = [];\n\n const map = new Map(existingFields.map(f => [f.split(\" \")[0], f]));\n\n for (const field of incomingFields) {\n const name = field.split(\" \")[0];\n if (!map.has(name)) {\n mergedFields.push(field);\n diff.push(chalk.green(`+ ${field}`));\n } else if (map.get(name) !== field) {\n const idx = mergedFields.findIndex(f => f.split(\" \")[0] === name);\n mergedFields[idx] = field;\n diff.push(chalk.yellow(`~ ${field}`));\n }\n }\n\n const mergedBlock = existing.replace(/\\{[\\s\\S]*\\}/, `{ \\n ${mergedFields.join(\"\\n \")} \\n}`);\n return { mergedBlock, diff };\n}\n\nfunction mergeEnumValues(existing: string, incoming: string) {\n const existingValues = parseBlockFields(existing);\n const incomingValues = parseBlockFields(incoming);\n\n const mergedValues = [...existingValues];\n const diff: string[] = [];\n\n for (const v of incomingValues) {\n if (!existingValues.includes(v)) {\n mergedValues.push(v);\n diff.push(chalk.green(`+ ${v}`));\n }\n }\n\n const mergedBlock = existing.replace(/\\{[\\s\\S]*\\}/, `{ \\n ${mergedValues.join(\"\\n \")} \\n}`);\n return { mergedBlock, diff };\n}\n\n// Replace blocks in schema (update existing, append only new)\nfunction applyMergedBlocks(original: string, blocks: string[], type: \"model\" | \"enum\") {\n let content = original;\n for (const block of blocks) {\n const match = block.match(new RegExp(`${type}\\\\s+(\\\\w+)\\\\s+{`));\n if (!match) continue;\n const name = match[1];\n\n const regex = new RegExp(`${type}\\\\s+${name}\\\\s+{[\\\\s\\\\S]*?}`, \"g\");\n if (regex.test(content)) {\n content = content.replace(regex, block); // update existing\n } else {\n content += `\\n\\n${block}`; // append new\n }\n }\n return content;\n}\n\n// -----------------------------\n// Main\n// -----------------------------\nasync function main() {\n console.log(chalk.blueBright.bold(\"\\nā” FlagsForge Prisma Schema Smart Merger\\n\"));\n\n if (isDryRun) console.log(chalk.cyan(\"š Dry run: preview all changes\\n\"));\n if (isForce) console.log(chalk.yellow(\"ā ļø Force mode enabled (skipping backup prompts)\\n\"));\n\n if (!fs.existsSync(appSchema)) {\n console.error(chalk.red(\"ā App schema not found at prisma/schema.prisma\"));\n process.exit(1);\n }\n if (!fs.existsSync(packageSchema)) {\n console.error(chalk.red(\"ā Package Prisma models not found\"));\n process.exit(1);\n }\n\n const appContent = fs.readFileSync(appSchema, \"utf-8\");\n const packageRaw = fs.readFileSync(packageSchema, \"utf-8\");\n const packageContent = packageRaw.replace(/generator\\s+\\w+\\s+{[\\s\\S]*?}\\n?/g, \"\").replace(/datasource\\s+\\w+\\s+{[\\s\\S]*?}\\n?/g, \"\");\n\n const appModels = extractBlocks(appContent, \"model\");\n const appEnums = extractBlocks(appContent, \"enum\");\n const packageModels = extractBlocks(packageContent, \"model\");\n const packageEnums = extractBlocks(packageContent, \"enum\");\n\n // Merge models\n const mergedModels: string[] = [];\n const modelDiffs: string[] = [];\n for (const pModel of packageModels) {\n const match = pModel.match(/model\\s+(\\w+)\\s+{/);\n if (!match) continue;\n const name = match[1];\n\n const existing = appModels.find(m => m.match(new RegExp(`model\\\\s+${name}\\\\s+{`)));\n if (existing) {\n const { mergedBlock, diff } = mergeModelFields(existing, pModel);\n mergedModels.push(mergedBlock);\n modelDiffs.push(...diff);\n } else {\n mergedModels.push(pModel);\n modelDiffs.push(chalk.green(`+ New model: ${name}`));\n }\n }\n\n // Merge enums\n const mergedEnums: string[] = [];\n const enumDiffs: string[] = [];\n for (const pEnum of packageEnums) {\n const match = pEnum.match(/enum\\s+(\\w+)\\s+{/);\n if (!match) continue;\n const name = match[1];\n\n const existing = appEnums.find(e => e.match(new RegExp(`enum\\\\s+${name}\\\\s+{`)));\n if (existing) {\n const { mergedBlock, diff } = mergeEnumValues(existing, pEnum);\n mergedEnums.push(mergedBlock);\n enumDiffs.push(...diff);\n } else {\n mergedEnums.push(pEnum);\n enumDiffs.push(chalk.green(`+ New enum: ${name}`));\n }\n }\n\n // ------------------------------\n // Prepend the FlagsForge Schema comment\n // ------------------------------\n const extensionComment = \"\\n\\n// ------------------------------\\n\" +\n \"// FlagsForge Schema Extensions\\n\" +\n \"// ------------------------------\\n\\n\";\n\n let mergedContent = appContent.trim() + extensionComment;\n mergedContent = applyMergedBlocks(mergedContent, mergedModels, \"model\");\n mergedContent = applyMergedBlocks(mergedContent, mergedEnums, \"enum\");\n\n // Dry-run preview\n if (isDryRun) {\n console.log(chalk.gray(\"----- FULL MERGE DIFF -----\\n\"));\n [...enumDiffs, ...modelDiffs].forEach(line => console.log(line));\n console.log(chalk.gray(\"\\n------------------------------\"));\n console.log(chalk.cyan(\"⨠Dry run complete. No changes were made.\\n\"));\n process.exit(0);\n }\n\n // Backup prompt\n let shouldBackup = true;\n if (!isForce) {\n const response = await prompts({\n type: \"confirm\",\n name: \"backup\",\n message: \"Create a backup of your current schema?\",\n initial: true,\n });\n shouldBackup = response.backup;\n }\n\n if (shouldBackup) {\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupPath = `${appSchema}.bak-${timestamp}`;\n fs.copyFileSync(appSchema, backupPath);\n console.log(chalk.green(`ā
Backup created: ${backupPath}\\n`));\n }\n\n fs.writeFileSync(appSchema, mergedContent);\n console.log(chalk.green.bold(\"ā
Prisma schema merged successfully!\"));\n console.log(chalk.yellow(\"Run `npx prisma generate` to update your client.\\n\"));\n}\n\nmain().catch(err => {\n console.error(chalk.red(\"ā Error merging schemas:\"), err);\n process.exit(1);\n});\n"],"mappings":"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","names":["path","chalk","fs","prompts"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PrismaClient } from '@custom-prisma/client';
|
|
2
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
2
|
import React, { ReactNode } from 'react';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
type Operator = "equals" | "not_equals" | "gt" | "lt" | "contains" | "in";
|
|
6
6
|
interface Condition {
|
|
@@ -71,6 +71,17 @@ declare class MemoryFlagAdapter implements FlagAdapter {
|
|
|
71
71
|
clear(): void;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
interface FeatureFlagManagerProps {
|
|
75
|
+
engine: FlagEngine;
|
|
76
|
+
isOpen: boolean;
|
|
77
|
+
onClose: () => void;
|
|
78
|
+
fetchFlags: () => Promise<FeatureFlag[]>;
|
|
79
|
+
fetchUsers?: () => Promise<any[]>;
|
|
80
|
+
fetchGroups?: () => Promise<any[]>;
|
|
81
|
+
updateFlag?: (flag: FeatureFlag) => Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
declare const FeatureFlagManager: React.FC<FeatureFlagManagerProps>;
|
|
84
|
+
|
|
74
85
|
interface FlagProviderValue {
|
|
75
86
|
engine: FlagEngine;
|
|
76
87
|
context: FlagContext;
|
|
@@ -103,4 +114,4 @@ declare function requireFlag({ key, engine }: RequireFlagOptions): (context: Fla
|
|
|
103
114
|
user?: any;
|
|
104
115
|
}, next: () => void | Promise<void>, handleDenied?: () => void) => Promise<void>;
|
|
105
116
|
|
|
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 };
|
|
117
|
+
export { type Condition, type FeatureFlag, FeatureFlagManager, 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.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PrismaClient } from '@custom-prisma/client';
|
|
2
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
2
|
import React, { ReactNode } from 'react';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
type Operator = "equals" | "not_equals" | "gt" | "lt" | "contains" | "in";
|
|
6
6
|
interface Condition {
|
|
@@ -71,6 +71,17 @@ declare class MemoryFlagAdapter implements FlagAdapter {
|
|
|
71
71
|
clear(): void;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
interface FeatureFlagManagerProps {
|
|
75
|
+
engine: FlagEngine;
|
|
76
|
+
isOpen: boolean;
|
|
77
|
+
onClose: () => void;
|
|
78
|
+
fetchFlags: () => Promise<FeatureFlag[]>;
|
|
79
|
+
fetchUsers?: () => Promise<any[]>;
|
|
80
|
+
fetchGroups?: () => Promise<any[]>;
|
|
81
|
+
updateFlag?: (flag: FeatureFlag) => Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
declare const FeatureFlagManager: React.FC<FeatureFlagManagerProps>;
|
|
84
|
+
|
|
74
85
|
interface FlagProviderValue {
|
|
75
86
|
engine: FlagEngine;
|
|
76
87
|
context: FlagContext;
|
|
@@ -103,4 +114,4 @@ declare function requireFlag({ key, engine }: RequireFlagOptions): (context: Fla
|
|
|
103
114
|
user?: any;
|
|
104
115
|
}, next: () => void | Promise<void>, handleDenied?: () => void) => Promise<void>;
|
|
105
116
|
|
|
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 };
|
|
117
|
+
export { type Condition, type FeatureFlag, FeatureFlagManager, 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
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,11 +17,20 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
33
|
+
FeatureFlagManager: () => FeatureFlagManager,
|
|
23
34
|
FlagContextReact: () => FlagContextReact,
|
|
24
35
|
FlagEngine: () => FlagEngine,
|
|
25
36
|
FlagProvider: () => FlagProvider,
|
|
@@ -147,28 +158,292 @@ var MemoryFlagAdapter = class {
|
|
|
147
158
|
}
|
|
148
159
|
};
|
|
149
160
|
|
|
150
|
-
// src/react/
|
|
161
|
+
// src/react/FeatureFlagsDialog.tsx
|
|
151
162
|
var import_react = require("react");
|
|
163
|
+
var import_framer_motion2 = require("framer-motion");
|
|
164
|
+
var import_clsx2 = __toESM(require("clsx"));
|
|
165
|
+
|
|
166
|
+
// src/react/Tabs/FlagList.tsx
|
|
167
|
+
var import_framer_motion = require("framer-motion");
|
|
168
|
+
var import_clsx = __toESM(require("clsx"));
|
|
152
169
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
153
|
-
var
|
|
170
|
+
var FlagList = ({ flags, selectedFlag, setSelectedFlag, updateFlag }) => {
|
|
171
|
+
const toggleFlag = async (flag) => {
|
|
172
|
+
flag.enabled = !flag.enabled;
|
|
173
|
+
if (updateFlag) await updateFlag(flag);
|
|
174
|
+
};
|
|
175
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "space-y-4", children: flags.map((flag) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
176
|
+
import_framer_motion.motion.div,
|
|
177
|
+
{
|
|
178
|
+
className: "flex justify-between items-center border rounded p-2 hover:bg-gray-50 cursor-pointer",
|
|
179
|
+
onClick: () => setSelectedFlag(flag),
|
|
180
|
+
whileHover: { scale: 1.02 },
|
|
181
|
+
children: [
|
|
182
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
183
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-semibold", children: flag.key }),
|
|
184
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-gray-500 text-sm", children: [
|
|
185
|
+
"Enabled: ",
|
|
186
|
+
flag.enabled ? "Yes" : "No"
|
|
187
|
+
] })
|
|
188
|
+
] }),
|
|
189
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
190
|
+
"button",
|
|
191
|
+
{
|
|
192
|
+
onClick: (e) => {
|
|
193
|
+
e.stopPropagation();
|
|
194
|
+
toggleFlag(flag);
|
|
195
|
+
},
|
|
196
|
+
className: (0, import_clsx.default)(
|
|
197
|
+
"px-3 py-1 rounded text-white",
|
|
198
|
+
flag.enabled ? "bg-red-500 hover:bg-red-600" : "bg-green-500 hover:bg-green-600"
|
|
199
|
+
),
|
|
200
|
+
children: flag.enabled ? "Disable" : "Enable"
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
flag.key
|
|
206
|
+
)) });
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// src/react/Tabs/RulesEditor.tsx
|
|
210
|
+
var import_uuid = require("uuid");
|
|
211
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
212
|
+
var ruleTypes = [
|
|
213
|
+
"USER_ID",
|
|
214
|
+
"USER_ATTRIBUTE",
|
|
215
|
+
"PERMISSION",
|
|
216
|
+
"GROUP",
|
|
217
|
+
"PERCENTAGE",
|
|
218
|
+
"CUSTOM"
|
|
219
|
+
];
|
|
220
|
+
var RulesEditor = ({ selectedFlag, updateFlag }) => {
|
|
221
|
+
const addRule = () => {
|
|
222
|
+
selectedFlag.rules.push({ id: (0, import_uuid.v4)(), type: "USER_ID", priority: selectedFlag.rules.length, conditions: [] });
|
|
223
|
+
updateFlag?.(selectedFlag);
|
|
224
|
+
};
|
|
225
|
+
const addCondition = (rule) => {
|
|
226
|
+
const conditions = Array.isArray(rule.conditions) ? rule.conditions : [];
|
|
227
|
+
conditions.push({ field: "", operator: "equals", value: "" });
|
|
228
|
+
rule.conditions = conditions;
|
|
229
|
+
updateFlag?.(selectedFlag);
|
|
230
|
+
};
|
|
231
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 border-t pt-4 space-y-2", children: [
|
|
232
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h3", { className: "font-bold mb-2", children: [
|
|
233
|
+
"Rules for ",
|
|
234
|
+
selectedFlag.key
|
|
235
|
+
] }),
|
|
236
|
+
selectedFlag.rules.map((rule) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 border p-2 rounded", children: [
|
|
237
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2 items-center", children: [
|
|
238
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
239
|
+
"select",
|
|
240
|
+
{
|
|
241
|
+
value: rule.type,
|
|
242
|
+
onChange: (e) => {
|
|
243
|
+
rule.type = e.target.value;
|
|
244
|
+
updateFlag?.(selectedFlag);
|
|
245
|
+
},
|
|
246
|
+
className: "border p-1 rounded",
|
|
247
|
+
children: ruleTypes.map((rt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: rt, children: rt }, rt))
|
|
248
|
+
}
|
|
249
|
+
),
|
|
250
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
251
|
+
"input",
|
|
252
|
+
{
|
|
253
|
+
type: "number",
|
|
254
|
+
value: rule.priority,
|
|
255
|
+
onChange: (e) => {
|
|
256
|
+
rule.priority = Number(e.target.value);
|
|
257
|
+
updateFlag?.(selectedFlag);
|
|
258
|
+
},
|
|
259
|
+
className: "border p-1 rounded w-20"
|
|
260
|
+
}
|
|
261
|
+
),
|
|
262
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => addCondition(rule), className: "bg-blue-500 text-white px-2 py-1 rounded hover:bg-blue-600", children: "+ Condition" })
|
|
263
|
+
] }),
|
|
264
|
+
(Array.isArray(rule.conditions) ? rule.conditions : []).map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2 items-center ml-4", children: [
|
|
265
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "text", value: c.field, onChange: (e) => {
|
|
266
|
+
c.field = e.target.value;
|
|
267
|
+
updateFlag?.(selectedFlag);
|
|
268
|
+
}, className: "border p-1 rounded w-24" }),
|
|
269
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "text", value: c.operator, onChange: (e) => {
|
|
270
|
+
c.operator = e.target.value;
|
|
271
|
+
updateFlag?.(selectedFlag);
|
|
272
|
+
}, className: "border p-1 rounded w-24" }),
|
|
273
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "text", value: c.value, onChange: (e) => {
|
|
274
|
+
c.value = e.target.value;
|
|
275
|
+
updateFlag?.(selectedFlag);
|
|
276
|
+
}, className: "border p-1 rounded w-24" }),
|
|
277
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => {
|
|
278
|
+
rule.conditions.splice(i, 1);
|
|
279
|
+
updateFlag?.(selectedFlag);
|
|
280
|
+
}, className: "bg-red-500 text-white px-2 py-1 rounded hover:bg-red-600", children: "\u2715" })
|
|
281
|
+
] }, i))
|
|
282
|
+
] }, rule.id)),
|
|
283
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: addRule, className: "bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600 mt-2", children: "Add Rule" })
|
|
284
|
+
] });
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// src/react/Tabs/GrantEditor.tsx
|
|
288
|
+
var import_uuid2 = require("uuid");
|
|
289
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
290
|
+
var GrantEditor = ({ selectedFlag, users, groups, updateFlag }) => {
|
|
291
|
+
const grantCondition = (rule, cond) => {
|
|
292
|
+
const conditions = Array.isArray(rule.conditions) ? rule.conditions : [];
|
|
293
|
+
conditions.push(cond);
|
|
294
|
+
rule.conditions = conditions;
|
|
295
|
+
selectedFlag.rules.push(rule);
|
|
296
|
+
updateFlag?.(selectedFlag);
|
|
297
|
+
};
|
|
298
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-4", children: [
|
|
299
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("h3", { className: "font-bold mb-2", children: [
|
|
300
|
+
"Grant Access for ",
|
|
301
|
+
selectedFlag.key
|
|
302
|
+
] }),
|
|
303
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
|
|
304
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { className: "font-semibold", children: "Users" }),
|
|
305
|
+
users.map((u) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-between items-center border p-2 rounded hover:bg-gray-50", children: [
|
|
306
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: u.username || u.id }),
|
|
307
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: () => grantCondition({ id: (0, import_uuid2.v4)(), type: "USER_ID", priority: selectedFlag.rules.length, conditions: [] }, { field: "userId", operator: "equals", value: u.id }), className: "bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600", children: "Grant" })
|
|
308
|
+
] }, u.id))
|
|
309
|
+
] }),
|
|
310
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
|
|
311
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { className: "font-semibold", children: "Groups" }),
|
|
312
|
+
groups.map((g) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-between items-center border p-2 rounded hover:bg-gray-50", children: [
|
|
313
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: g.name }),
|
|
314
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: () => grantCondition({ id: (0, import_uuid2.v4)(), type: "GROUP", priority: selectedFlag.rules.length, conditions: [] }, { field: "groupId", operator: "equals", value: g.id }), className: "bg-purple-500 text-white px-3 py-1 rounded hover:bg-purple-600", children: "Grant" })
|
|
315
|
+
] }, g.id))
|
|
316
|
+
] })
|
|
317
|
+
] });
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// src/react/Tabs/AuditView.tsx
|
|
321
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
322
|
+
var AuditView = ({ flags }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
323
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "font-bold mb-2", children: "Audit / Access Management" }),
|
|
324
|
+
flags.map((flag) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "border p-2 rounded mb-2", children: [
|
|
325
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "font-semibold", children: flag.key }),
|
|
326
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "ml-4 text-sm", children: flag.rules.map((r) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { children: [
|
|
327
|
+
r.type,
|
|
328
|
+
" - ",
|
|
329
|
+
JSON.stringify(r.conditions)
|
|
330
|
+
] }, r.id)) })
|
|
331
|
+
] }, flag.key))
|
|
332
|
+
] });
|
|
333
|
+
|
|
334
|
+
// src/react/FeatureFlagsDialog.tsx
|
|
335
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
336
|
+
var FeatureFlagManager = ({
|
|
337
|
+
engine,
|
|
338
|
+
isOpen,
|
|
339
|
+
onClose,
|
|
340
|
+
fetchFlags,
|
|
341
|
+
fetchUsers,
|
|
342
|
+
fetchGroups,
|
|
343
|
+
updateFlag
|
|
344
|
+
}) => {
|
|
345
|
+
const [flags, setFlags] = (0, import_react.useState)([]);
|
|
346
|
+
const [selectedFlag, setSelectedFlag] = (0, import_react.useState)(null);
|
|
347
|
+
const [tab, setTab] = (0, import_react.useState)("flags");
|
|
348
|
+
const [users, setUsers] = (0, import_react.useState)([]);
|
|
349
|
+
const [groups, setGroups] = (0, import_react.useState)([]);
|
|
350
|
+
(0, import_react.useEffect)(() => {
|
|
351
|
+
if (!isOpen) return;
|
|
352
|
+
const loadFlags = async () => setFlags(await fetchFlags());
|
|
353
|
+
loadFlags();
|
|
354
|
+
}, [isOpen]);
|
|
355
|
+
(0, import_react.useEffect)(() => {
|
|
356
|
+
if (!isOpen || tab !== "grant") return;
|
|
357
|
+
const loadData = async () => {
|
|
358
|
+
if (fetchUsers) setUsers(await fetchUsers());
|
|
359
|
+
if (fetchGroups) setGroups(await fetchGroups());
|
|
360
|
+
};
|
|
361
|
+
loadData();
|
|
362
|
+
}, [isOpen, tab, fetchUsers, fetchGroups]);
|
|
363
|
+
if (!isOpen) return null;
|
|
364
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_framer_motion2.AnimatePresence, { children: isOpen && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
365
|
+
import_framer_motion2.motion.div,
|
|
366
|
+
{
|
|
367
|
+
className: "fixed inset-0 flex items-center justify-center z-50 bg-black/50",
|
|
368
|
+
initial: { opacity: 0 },
|
|
369
|
+
animate: { opacity: 1 },
|
|
370
|
+
exit: { opacity: 0 },
|
|
371
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
372
|
+
import_framer_motion2.motion.div,
|
|
373
|
+
{
|
|
374
|
+
className: "bg-white rounded-lg shadow-xl w-11/12 max-w-6xl p-6",
|
|
375
|
+
initial: { scale: 0.8, opacity: 0 },
|
|
376
|
+
animate: { scale: 1, opacity: 1 },
|
|
377
|
+
exit: { scale: 0.8, opacity: 0 },
|
|
378
|
+
transition: { type: "spring", stiffness: 300, damping: 25 },
|
|
379
|
+
children: [
|
|
380
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex justify-between items-center mb-4", children: [
|
|
381
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "text-xl font-bold", children: "Feature Flags Manager" }),
|
|
382
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: onClose, className: "text-gray-500 hover:text-gray-800", children: "\u2715" })
|
|
383
|
+
] }),
|
|
384
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex gap-2 mb-4 border-b", children: ["flags", "grant", "audit"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
385
|
+
"button",
|
|
386
|
+
{
|
|
387
|
+
className: (0, import_clsx2.default)(
|
|
388
|
+
"px-4 py-2 rounded-t font-semibold",
|
|
389
|
+
tab === t ? "bg-white border-t border-x border-gray-300" : "bg-gray-200 hover:bg-gray-300"
|
|
390
|
+
),
|
|
391
|
+
onClick: () => setTab(t),
|
|
392
|
+
children: t === "flags" ? "Feature Flags" : t === "grant" ? "Grant Access" : "Audit"
|
|
393
|
+
},
|
|
394
|
+
t
|
|
395
|
+
)) }),
|
|
396
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "max-h-[60vh] overflow-y-auto", children: [
|
|
397
|
+
tab === "flags" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
398
|
+
FlagList,
|
|
399
|
+
{
|
|
400
|
+
flags,
|
|
401
|
+
selectedFlag,
|
|
402
|
+
setSelectedFlag,
|
|
403
|
+
updateFlag
|
|
404
|
+
}
|
|
405
|
+
),
|
|
406
|
+
tab === "grant" && selectedFlag && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
407
|
+
GrantEditor,
|
|
408
|
+
{
|
|
409
|
+
selectedFlag,
|
|
410
|
+
users,
|
|
411
|
+
groups,
|
|
412
|
+
updateFlag
|
|
413
|
+
}
|
|
414
|
+
),
|
|
415
|
+
tab === "audit" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AuditView, { flags }),
|
|
416
|
+
selectedFlag && tab === "flags" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RulesEditor, { selectedFlag, updateFlag })
|
|
417
|
+
] })
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
) });
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// src/react/FlagProvider.tsx
|
|
426
|
+
var import_react2 = require("react");
|
|
427
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
428
|
+
var FlagContextReact = (0, import_react2.createContext)(null);
|
|
154
429
|
function FlagProvider({
|
|
155
430
|
engine,
|
|
156
431
|
context,
|
|
157
432
|
children
|
|
158
433
|
}) {
|
|
159
|
-
return /* @__PURE__ */ (0,
|
|
434
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FlagContextReact.Provider, { value: { engine, context }, children });
|
|
160
435
|
}
|
|
161
436
|
|
|
162
437
|
// src/react/useFlag.ts
|
|
163
|
-
var
|
|
438
|
+
var import_react3 = require("react");
|
|
164
439
|
function useFlag(key) {
|
|
165
|
-
const ctx = (0,
|
|
440
|
+
const ctx = (0, import_react3.useContext)(FlagContextReact);
|
|
166
441
|
if (!ctx) {
|
|
167
442
|
throw new Error("useFlag must be used inside FlagProvider");
|
|
168
443
|
}
|
|
169
444
|
const { engine, context } = ctx;
|
|
170
|
-
const [enabled, setEnabled] = (0,
|
|
171
|
-
(0,
|
|
445
|
+
const [enabled, setEnabled] = (0, import_react3.useState)(false);
|
|
446
|
+
(0, import_react3.useEffect)(() => {
|
|
172
447
|
let cancelled = false;
|
|
173
448
|
engine.isEnabled(key, context).then((result) => {
|
|
174
449
|
if (!cancelled) {
|
|
@@ -183,15 +458,15 @@ function useFlag(key) {
|
|
|
183
458
|
}
|
|
184
459
|
|
|
185
460
|
// src/react/withFlag.tsx
|
|
186
|
-
var
|
|
461
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
187
462
|
function withFlag(flagKey, options) {
|
|
188
463
|
return function(WrappedComponent) {
|
|
189
464
|
const ComponentWithFlag = (props) => {
|
|
190
465
|
const enabled = useFlag(flagKey);
|
|
191
466
|
if (!enabled) {
|
|
192
|
-
return /* @__PURE__ */ (0,
|
|
467
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: options?.fallback ?? null });
|
|
193
468
|
}
|
|
194
|
-
return /* @__PURE__ */ (0,
|
|
469
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(WrappedComponent, { ...props });
|
|
195
470
|
};
|
|
196
471
|
ComponentWithFlag.displayName = `withFlag(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`;
|
|
197
472
|
return ComponentWithFlag;
|
|
@@ -216,6 +491,7 @@ function requireFlag({ key, engine }) {
|
|
|
216
491
|
}
|
|
217
492
|
// Annotate the CommonJS export names for ESM import in node:
|
|
218
493
|
0 && (module.exports = {
|
|
494
|
+
FeatureFlagManager,
|
|
219
495
|
FlagContextReact,
|
|
220
496
|
FlagEngine,
|
|
221
497
|
FlagProvider,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/engine.ts","../src/adapters/prisma.ts","../src/adapters/memory.ts","../src/react/FeatureFlagsDialog.tsx","../src/react/Tabs/FlagList.tsx","../src/react/Tabs/RulesEditor.tsx","../src/react/Tabs/GrantEditor.tsx","../src/react/Tabs/AuditView.tsx","../src/react/FlagProvider.tsx","../src/react/useFlag.ts","../src/react/withFlag.tsx","../src/node/middleware.ts"],"sourcesContent":["// Types\nexport * from \"./core/types\" // exports both types & values (if any)\nexport type { FlagAdapter, FeatureFlag, FlagContext, FlagRule, RuleType, Condition, Operator } from \"./core/types\"\n\n// Engine\nexport { FlagEngine } from \"./core/engine\"\n\n// Adapters\nexport { PrismaFlagAdapter } from \"./adapters/prisma\"\nexport { MemoryFlagAdapter, MemoryAdapterOptions } from \"./adapters/memory\"\n\n// React\nexport { FeatureFlagManager } from \"./react/FeatureFlagsDialog\"\nexport { FlagProvider, FlagProviderValue, FlagContextReact } from \"./react/FlagProvider\"\nexport { useFlag } from \"./react/useFlag\"\nexport { withFlag } from \"./react/withFlag\"\n\n// Node\nexport { requireFlag, RequireFlagOptions } from \"./node/middleware\"\n","import { FlagAdapter, FlagContext } from \"./types\"\n\nexport class FlagEngine {\n constructor(private adapter: FlagAdapter) {}\n\n async isEnabled(key: string, context: FlagContext): Promise<boolean> {\n const flag = await this.adapter.getFlag(key)\n if (!flag || !flag.enabled) return false\n\n const sortedRules = flag.rules.sort((a: any, b: any) => b.priority - a.priority)\n\n for (const rule of sortedRules) {\n const result = await this.evaluateRule(rule, context)\n if (result !== null) return result\n }\n\n return false\n }\n\n private async evaluateRule(rule: any, context: FlagContext): Promise<boolean | null> {\n switch (rule.type) {\n case \"USER_ID\":\n return rule.conditions.includes(context.userId)\n\n case \"PERMISSION\":\n return context.permissions?.includes(rule.conditions.permission) ?? false\n\n case \"USER_ATTRIBUTE\":\n const { field, operator, value } = rule.conditions\n const userValue = context.attributes?.[field]\n\n switch (operator) {\n case \"equals\":\n return userValue === value\n case \"gt\":\n return userValue > value\n case \"lt\":\n return userValue < value\n case \"contains\":\n return userValue?.includes(value)\n }\n return false\n\n case \"GROUP_MEMBERSHIP\":\n return context.groups?.includes(rule.conditions.groupId) ?? false\n\n case \"PERCENTAGE\":\n return this.percentageRollout(context.userId!, rule.percentage)\n\n default:\n return null\n }\n }\n\n private percentageRollout(userId: string, percentage: number) {\n const hash = this.hash(userId)\n return (hash % 100) < percentage\n }\n\n private hash(str: string) {\n let hash = 0\n for (let i = 0; i < str.length; i++) {\n hash = (hash << 5) - hash + str.charCodeAt(i)\n hash |= 0\n }\n return Math.abs(hash)\n }\n}\n","import { PrismaClient } from \"@custom-prisma/client\" // ā match schema generator\nimport { FlagAdapter, FeatureFlag } from \"../core/types\"\n\nexport class PrismaFlagAdapter implements FlagAdapter {\n constructor(private prisma: PrismaClient) {}\n\n async getFlag(key: string): Promise<FeatureFlag | null> {\n const flag = await this.prisma.featureFlag.findUnique({\n where: { key },\n include: { rules: true } // make sure 'rules' relation exists\n })\n\n if (!flag) return null\n\n return {\n key: flag.key,\n enabled: flag.enabled,\n rules: flag.rules as any\n }\n }\n}\n","import type {\n FlagAdapter,\n FeatureFlag\n} from \"../core/types\"\n\nexport interface MemoryAdapterOptions {\n initialFlags?: FeatureFlag[]\n}\n\nexport class MemoryFlagAdapter implements FlagAdapter {\n private flags = new Map<string, FeatureFlag>()\n\n constructor(options?: MemoryAdapterOptions) {\n if (options?.initialFlags) {\n for (const flag of options.initialFlags) {\n this.flags.set(flag.key, flag)\n }\n }\n }\n\n async getFlag(key: string): Promise<FeatureFlag | null> {\n return this.flags.get(key) ?? null\n }\n\n /**\n * Add or update a flag\n */\n setFlag(flag: FeatureFlag): void {\n this.flags.set(flag.key, flag)\n }\n\n /**\n * Remove a flag\n */\n removeFlag(key: string): void {\n this.flags.delete(key)\n }\n\n /**\n * Get all flags (useful for debugging)\n */\n getAllFlags(): FeatureFlag[] {\n return Array.from(this.flags.values())\n }\n\n /**\n * Clear all flags\n */\n clear(): void {\n this.flags.clear()\n }\n}\n","import React, { useState, useEffect } from \"react\"\nimport { motion, AnimatePresence } from \"framer-motion\"\nimport clsx from \"clsx\"\n\nimport type { FeatureFlag, FlagRule, RuleType } from \"../core/types\"\nimport { FlagEngine } from \"../core/engine\"\n\nimport { FlagList } from \"./Tabs/FlagList\";\nimport { RulesEditor } from \"./Tabs/RulesEditor\";\nimport { GrantEditor } from \"./Tabs/GrantEditor\";\nimport { AuditView } from \"./Tabs/AuditView\";\n\ntype Tab = \"flags\" | \"grant\" | \"audit\"\n\ninterface FeatureFlagManagerProps {\n engine: FlagEngine\n isOpen: boolean\n onClose: () => void\n fetchFlags: () => Promise<FeatureFlag[]>\n fetchUsers?: () => Promise<any[]>\n fetchGroups?: () => Promise<any[]>\n updateFlag?: (flag: FeatureFlag) => Promise<void>\n}\n\nexport const FeatureFlagManager: React.FC<FeatureFlagManagerProps> = ({\n engine,\n isOpen,\n onClose,\n fetchFlags,\n fetchUsers,\n fetchGroups,\n updateFlag\n}) => {\n const [flags, setFlags] = useState<FeatureFlag[]>([])\n const [selectedFlag, setSelectedFlag] = useState<FeatureFlag | null>(null)\n const [tab, setTab] = useState<Tab>(\"flags\")\n const [users, setUsers] = useState<any[]>([])\n const [groups, setGroups] = useState<any[]>([])\n\n useEffect(() => {\n if (!isOpen) return\n const loadFlags = async () => setFlags(await fetchFlags())\n loadFlags()\n }, [isOpen])\n\n useEffect(() => {\n if (!isOpen || tab !== \"grant\") return\n const loadData = async () => {\n if (fetchUsers) setUsers(await fetchUsers())\n if (fetchGroups) setGroups(await fetchGroups())\n }\n loadData()\n }, [isOpen, tab, fetchUsers, fetchGroups])\n\n if (!isOpen) return null\n\n return (\n <AnimatePresence>\n {isOpen && (\n <motion.div\n className=\"fixed inset-0 flex items-center justify-center z-50 bg-black/50\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n >\n <motion.div\n className=\"bg-white rounded-lg shadow-xl w-11/12 max-w-6xl p-6\"\n initial={{ scale: 0.8, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0.8, opacity: 0 }}\n transition={{ type: \"spring\", stiffness: 300, damping: 25 }}\n >\n {/* Header */}\n <div className=\"flex justify-between items-center mb-4\">\n <h2 className=\"text-xl font-bold\">Feature Flags Manager</h2>\n <button onClick={onClose} className=\"text-gray-500 hover:text-gray-800\">ā</button>\n </div>\n\n {/* Tabs */}\n <div className=\"flex gap-2 mb-4 border-b\">\n {([\"flags\", \"grant\", \"audit\"] as Tab[]).map(t => (\n <button\n key={t}\n className={clsx(\n \"px-4 py-2 rounded-t font-semibold\",\n tab === t ? \"bg-white border-t border-x border-gray-300\" : \"bg-gray-200 hover:bg-gray-300\"\n )}\n onClick={() => setTab(t)}\n >\n {t === \"flags\" ? \"Feature Flags\" : t === \"grant\" ? \"Grant Access\" : \"Audit\"}\n </button>\n ))}\n </div>\n\n {/* Tab content */}\n <div className=\"max-h-[60vh] overflow-y-auto\">\n {tab === \"flags\" && (\n <FlagList\n flags={flags}\n selectedFlag={selectedFlag}\n setSelectedFlag={setSelectedFlag}\n updateFlag={updateFlag}\n />\n )}\n {tab === \"grant\" && selectedFlag && (\n <GrantEditor\n selectedFlag={selectedFlag}\n users={users}\n groups={groups}\n updateFlag={updateFlag}\n />\n )}\n {tab === \"audit\" && <AuditView flags={flags} />}\n {selectedFlag && tab === \"flags\" && (\n <RulesEditor selectedFlag={selectedFlag} updateFlag={updateFlag} />\n )}\n </div>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n )\n}\n","import React from \"react\"\nimport { motion } from \"framer-motion\"\nimport clsx from \"clsx\"\nimport type { FeatureFlag } from \"../../core/types\"\n\ninterface FlagListProps {\n flags: FeatureFlag[]\n selectedFlag: FeatureFlag | null\n setSelectedFlag: (flag: FeatureFlag) => void\n updateFlag?: (flag: FeatureFlag) => Promise<void>\n}\n\nexport const FlagList: React.FC<FlagListProps> = ({ flags, selectedFlag, setSelectedFlag, updateFlag }) => {\n const toggleFlag = async (flag: FeatureFlag) => {\n flag.enabled = !flag.enabled\n if (updateFlag) await updateFlag(flag)\n }\n\n return (\n <div className=\"space-y-4\">\n {flags.map(flag => (\n <motion.div\n key={flag.key}\n className=\"flex justify-between items-center border rounded p-2 hover:bg-gray-50 cursor-pointer\"\n onClick={() => setSelectedFlag(flag)}\n whileHover={{ scale: 1.02 }}\n >\n <div>\n <p className=\"font-semibold\">{flag.key}</p>\n <p className=\"text-gray-500 text-sm\">\n Enabled: {flag.enabled ? \"Yes\" : \"No\"}\n </p>\n </div>\n <button\n onClick={e => {\n e.stopPropagation()\n toggleFlag(flag)\n }}\n className={clsx(\n \"px-3 py-1 rounded text-white\",\n flag.enabled ? \"bg-red-500 hover:bg-red-600\" : \"bg-green-500 hover:bg-green-600\"\n )}\n >\n {flag.enabled ? \"Disable\" : \"Enable\"}\n </button>\n </motion.div>\n ))}\n </div>\n )\n}\n","import React from \"react\"\nimport { v4 as uuidv4 } from \"uuid\"\nimport type { FeatureFlag, FlagRule, RuleType, Condition } from \"../../core/types\"\n\ninterface RulesEditorProps {\n selectedFlag: FeatureFlag\n updateFlag?: (flag: FeatureFlag) => Promise<void>\n}\n\nconst ruleTypes: RuleType[] = [\n \"USER_ID\",\n \"USER_ATTRIBUTE\",\n \"PERMISSION\",\n \"GROUP\",\n \"PERCENTAGE\",\n \"CUSTOM\"\n]\n\nexport const RulesEditor: React.FC<RulesEditorProps> = ({ selectedFlag, updateFlag }) => {\n\n const addRule = () => {\n selectedFlag.rules.push({ id: uuidv4(), type: \"USER_ID\", priority: selectedFlag.rules.length, conditions: [] })\n updateFlag?.(selectedFlag)\n }\n\n const addCondition = (rule: FlagRule) => {\n const conditions = Array.isArray(rule.conditions) ? rule.conditions : []\n conditions.push({ field: \"\", operator: \"equals\", value: \"\" })\n rule.conditions = conditions\n updateFlag?.(selectedFlag)\n }\n\n return (\n <div className=\"mt-4 border-t pt-4 space-y-2\">\n <h3 className=\"font-bold mb-2\">Rules for {selectedFlag.key}</h3>\n {selectedFlag.rules.map(rule => (\n <div key={rule.id} className=\"flex flex-col gap-2 border p-2 rounded\">\n <div className=\"flex gap-2 items-center\">\n <select\n value={rule.type}\n onChange={e => {\n rule.type = e.target.value as RuleType\n updateFlag?.(selectedFlag)\n }}\n className=\"border p-1 rounded\"\n >\n {ruleTypes.map(rt => (\n <option key={rt} value={rt}>{rt}</option>\n ))}\n </select>\n <input\n type=\"number\"\n value={rule.priority}\n onChange={e => {\n rule.priority = Number(e.target.value)\n updateFlag?.(selectedFlag)\n }}\n className=\"border p-1 rounded w-20\"\n />\n <button onClick={() => addCondition(rule)} className=\"bg-blue-500 text-white px-2 py-1 rounded hover:bg-blue-600\">\n + Condition\n </button>\n </div>\n {/* Render conditions */}\n {(Array.isArray(rule.conditions) ? rule.conditions : []).map((c, i) => (\n <div key={i} className=\"flex gap-2 items-center ml-4\">\n <input type=\"text\" value={c.field} onChange={e => { c.field = e.target.value; updateFlag?.(selectedFlag) }} className=\"border p-1 rounded w-24\" />\n <input type=\"text\" value={c.operator} onChange={e => { c.operator = e.target.value as any; updateFlag?.(selectedFlag) }} className=\"border p-1 rounded w-24\" />\n <input type=\"text\" value={c.value} onChange={e => { c.value = e.target.value; updateFlag?.(selectedFlag) }} className=\"border p-1 rounded w-24\" />\n <button onClick={() => { (rule.conditions as Condition[]).splice(i, 1); updateFlag?.(selectedFlag) }} className=\"bg-red-500 text-white px-2 py-1 rounded hover:bg-red-600\">ā</button>\n </div>\n ))}\n </div>\n ))}\n <button onClick={addRule} className=\"bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600 mt-2\">Add Rule</button>\n </div>\n )\n}\n","import React from \"react\"\nimport { v4 as uuidv4 } from \"uuid\"\nimport type { FeatureFlag, FlagRule, RuleType, Condition } from \"../../core/types\"\n\ninterface GrantEditorProps {\n selectedFlag: FeatureFlag\n users: any[]\n groups: any[]\n updateFlag?: (flag: FeatureFlag) => Promise<void>\n}\n\nexport const GrantEditor: React.FC<GrantEditorProps> = ({ selectedFlag, users, groups, updateFlag }) => {\n\n const grantCondition = (rule: FlagRule, cond: Condition) => {\n const conditions = Array.isArray(rule.conditions) ? rule.conditions : []\n conditions.push(cond)\n rule.conditions = conditions\n selectedFlag.rules.push(rule)\n updateFlag?.(selectedFlag)\n }\n\n return (\n <div className=\"space-y-4\">\n <h3 className=\"font-bold mb-2\">Grant Access for {selectedFlag.key}</h3>\n\n {/* Users */}\n <div className=\"space-y-2\">\n <h4 className=\"font-semibold\">Users</h4>\n {users.map(u => (\n <div key={u.id} className=\"flex justify-between items-center border p-2 rounded hover:bg-gray-50\">\n <p>{u.username || u.id}</p>\n <button onClick={() => grantCondition({ id: uuidv4(), type: \"USER_ID\", priority: selectedFlag.rules.length, conditions: [] }, { field: \"userId\", operator: \"equals\", value: u.id })} className=\"bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600\">Grant</button>\n </div>\n ))}\n </div>\n\n {/* Groups */}\n <div className=\"space-y-2\">\n <h4 className=\"font-semibold\">Groups</h4>\n {groups.map(g => (\n <div key={g.id} className=\"flex justify-between items-center border p-2 rounded hover:bg-gray-50\">\n <p>{g.name}</p>\n <button onClick={() => grantCondition({ id: uuidv4(), type: \"GROUP\", priority: selectedFlag.rules.length, conditions: [] }, { field: \"groupId\", operator: \"equals\", value: g.id })} className=\"bg-purple-500 text-white px-3 py-1 rounded hover:bg-purple-600\">Grant</button>\n </div>\n ))}\n </div>\n </div>\n )\n}\n","import React from \"react\"\nimport type { FeatureFlag } from \"../../core/types\"\n\ninterface AuditViewProps {\n flags: FeatureFlag[]\n}\n\nexport const AuditView: React.FC<AuditViewProps> = ({ flags }) => (\n <div>\n <h3 className=\"font-bold mb-2\">Audit / Access Management</h3>\n {flags.map(flag => (\n <div key={flag.key} className=\"border p-2 rounded mb-2\">\n <p className=\"font-semibold\">{flag.key}</p>\n <ul className=\"ml-4 text-sm\">\n {flag.rules.map(r => (\n <li key={r.id}>{r.type} - {JSON.stringify(r.conditions)}</li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n)\n","import React, { createContext, ReactNode } from \"react\"\nimport type { FlagEngine } from \"../core/engine\"\nimport type { FlagContext } from \"../core/types\"\n\nexport interface FlagProviderValue {\n engine: FlagEngine\n context: FlagContext\n}\n\nexport const FlagContextReact =\n createContext<FlagProviderValue | null>(null)\n\ninterface FlagProviderProps {\n engine: FlagEngine\n context: FlagContext\n children: ReactNode\n}\n\nexport function FlagProvider({\n engine,\n context,\n children\n}: FlagProviderProps) {\n return (\n <FlagContextReact.Provider value={{ engine, context }}>\n {children}\n </FlagContextReact.Provider>\n )\n}\n","import { useContext, useEffect, useState } from \"react\"\nimport { FlagContextReact } from \"./FlagProvider\"\n\nexport function useFlag(key: string): boolean {\n const ctx = useContext(FlagContextReact)\n\n if (!ctx) {\n throw new Error(\"useFlag must be used inside FlagProvider\")\n }\n\n const { engine, context } = ctx\n const [enabled, setEnabled] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n\n engine.isEnabled(key, context).then(result => {\n if (!cancelled) {\n setEnabled(result)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [engine, key, context])\n\n return enabled\n}\n","import React from \"react\"\nimport { useFlag } from \"./useFlag\"\n\ninterface WithFlagOptions {\n fallback?: React.ReactNode\n}\n\nexport function withFlag(\n flagKey: string,\n options?: WithFlagOptions\n) {\n return function <P extends object>(\n WrappedComponent: React.ComponentType<P>\n ) {\n const ComponentWithFlag: React.FC<P> = (props) => {\n const enabled = useFlag(flagKey)\n\n if (!enabled) {\n return <>{options?.fallback ?? null}</>\n }\n\n return <WrappedComponent {...props} />\n }\n\n ComponentWithFlag.displayName = `withFlag(${\n WrappedComponent.displayName ||\n WrappedComponent.name ||\n \"Component\"\n })`\n\n return ComponentWithFlag\n }\n}\n","import { FlagEngine } from \"../core/engine\"\nimport type { FlagContext } from \"../core/types\"\n\nexport interface RequireFlagOptions {\n key: string\n engine: FlagEngine\n}\n\n/**\n * Universal middleware/check function for any Node.js system\n * @param options.key - Feature flag key\n * @param options.engine - FlagEngine instance\n */\nexport function requireFlag({ key, engine }: RequireFlagOptions) {\n /**\n * context: object containing user info or any relevant attributes\n * next: function to call if feature is enabled\n * handleDenied: optional function to handle denial (default throws)\n */\n return async (\n context: FlagContext & { user?: any },\n next: () => void | Promise<void>,\n handleDenied?: () => void\n ) => {\n const allowed = await engine.isEnabled(key, {\n userId: context.user?.id,\n attributes: context.user,\n permissions: context.user?.permissions,\n groups: context.user?.groups ?? context.groups\n })\n\n if (!allowed) {\n if (handleDenied) return handleDenied()\n throw new Error(`Feature \"${key}\" is disabled for this user`)\n }\n\n return next()\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,aAAN,MAAiB;AAAA,EACtB,YAAoB,SAAsB;AAAtB;AAAA,EAAuB;AAAA,EAE3C,MAAM,UAAU,KAAa,SAAwC;AACnE,UAAM,OAAO,MAAM,KAAK,QAAQ,QAAQ,GAAG;AAC3C,QAAI,CAAC,QAAQ,CAAC,KAAK,QAAS,QAAO;AAEnC,UAAM,cAAc,KAAK,MAAM,KAAK,CAAC,GAAQ,MAAW,EAAE,WAAW,EAAE,QAAQ;AAE/E,eAAW,QAAQ,aAAa;AAC9B,YAAM,SAAS,MAAM,KAAK,aAAa,MAAM,OAAO;AACpD,UAAI,WAAW,KAAM,QAAO;AAAA,IAC9B;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,aAAa,MAAW,SAA+C;AACnF,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AACH,eAAO,KAAK,WAAW,SAAS,QAAQ,MAAM;AAAA,MAEhD,KAAK;AACH,eAAO,QAAQ,aAAa,SAAS,KAAK,WAAW,UAAU,KAAK;AAAA,MAEtE,KAAK;AACH,cAAM,EAAE,OAAO,UAAU,MAAM,IAAI,KAAK;AACxC,cAAM,YAAY,QAAQ,aAAa,KAAK;AAE5C,gBAAQ,UAAU;AAAA,UAChB,KAAK;AACH,mBAAO,cAAc;AAAA,UACvB,KAAK;AACH,mBAAO,YAAY;AAAA,UACrB,KAAK;AACH,mBAAO,YAAY;AAAA,UACrB,KAAK;AACH,mBAAO,WAAW,SAAS,KAAK;AAAA,QACpC;AACA,eAAO;AAAA,MAET,KAAK;AACH,eAAO,QAAQ,QAAQ,SAAS,KAAK,WAAW,OAAO,KAAK;AAAA,MAE9D,KAAK;AACH,eAAO,KAAK,kBAAkB,QAAQ,QAAS,KAAK,UAAU;AAAA,MAEhE;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,kBAAkB,QAAgB,YAAoB;AAC5D,UAAM,OAAO,KAAK,KAAK,MAAM;AAC7B,WAAQ,OAAO,MAAO;AAAA,EACxB;AAAA,EAEQ,KAAK,KAAa;AACxB,QAAI,OAAO;AACX,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,cAAQ,QAAQ,KAAK,OAAO,IAAI,WAAW,CAAC;AAC5C,cAAQ;AAAA,IACV;AACA,WAAO,KAAK,IAAI,IAAI;AAAA,EACtB;AACF;;;AChEO,IAAM,oBAAN,MAA+C;AAAA,EACpD,YAAoB,QAAsB;AAAtB;AAAA,EAAuB;AAAA,EAE3C,MAAM,QAAQ,KAA0C;AACtD,UAAM,OAAO,MAAM,KAAK,OAAO,YAAY,WAAW;AAAA,MACpD,OAAO,EAAE,IAAI;AAAA,MACb,SAAS,EAAE,OAAO,KAAK;AAAA;AAAA,IACzB,CAAC;AAED,QAAI,CAAC,KAAM,QAAO;AAElB,WAAO;AAAA,MACL,KAAK,KAAK;AAAA,MACV,SAAS,KAAK;AAAA,MACd,OAAO,KAAK;AAAA,IACd;AAAA,EACF;AACF;;;ACXO,IAAM,oBAAN,MAA+C;AAAA,EAGpD,YAAY,SAAgC;AAF5C,SAAQ,QAAQ,oBAAI,IAAyB;AAG3C,QAAI,SAAS,cAAc;AACzB,iBAAW,QAAQ,QAAQ,cAAc;AACvC,aAAK,MAAM,IAAI,KAAK,KAAK,IAAI;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,KAA0C;AACtD,WAAO,KAAK,MAAM,IAAI,GAAG,KAAK;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,MAAyB;AAC/B,SAAK,MAAM,IAAI,KAAK,KAAK,IAAI;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,KAAmB;AAC5B,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,cAA6B;AAC3B,WAAO,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,MAAM,MAAM;AAAA,EACnB;AACF;;;ACnDA,mBAA2C;AAC3C,IAAAA,wBAAwC;AACxC,IAAAC,eAAiB;;;ACDjB,2BAAuB;AACvB,kBAAiB;AA0BL;AAhBL,IAAM,WAAoC,CAAC,EAAE,OAAO,cAAc,iBAAiB,WAAW,MAAM;AACzG,QAAM,aAAa,OAAO,SAAsB;AAC9C,SAAK,UAAU,CAAC,KAAK;AACrB,QAAI,WAAY,OAAM,WAAW,IAAI;AAAA,EACvC;AAEA,SACE,4CAAC,SAAI,WAAU,aACZ,gBAAM,IAAI,UACT;AAAA,IAAC,4BAAO;AAAA,IAAP;AAAA,MAEC,WAAU;AAAA,MACV,SAAS,MAAM,gBAAgB,IAAI;AAAA,MACnC,YAAY,EAAE,OAAO,KAAK;AAAA,MAE1B;AAAA,qDAAC,SACC;AAAA,sDAAC,OAAE,WAAU,iBAAiB,eAAK,KAAI;AAAA,UACvC,6CAAC,OAAE,WAAU,yBAAwB;AAAA;AAAA,YACzB,KAAK,UAAU,QAAQ;AAAA,aACnC;AAAA,WACF;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,OAAK;AACZ,gBAAE,gBAAgB;AAClB,yBAAW,IAAI;AAAA,YACjB;AAAA,YACA,eAAW,YAAAC;AAAA,cACT;AAAA,cACA,KAAK,UAAU,gCAAgC;AAAA,YACjD;AAAA,YAEC,eAAK,UAAU,YAAY;AAAA;AAAA,QAC9B;AAAA;AAAA;AAAA,IAtBK,KAAK;AAAA,EAuBZ,CACD,GACH;AAEJ;;;AChDA,kBAA6B;AAiCvB,IAAAC,sBAAA;AAzBN,IAAM,YAAwB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,cAA0C,CAAC,EAAE,cAAc,WAAW,MAAM;AAEvF,QAAM,UAAU,MAAM;AACpB,iBAAa,MAAM,KAAK,EAAE,QAAI,YAAAC,IAAO,GAAG,MAAM,WAAW,UAAU,aAAa,MAAM,QAAQ,YAAY,CAAC,EAAE,CAAC;AAC9G,iBAAa,YAAY;AAAA,EAC3B;AAEA,QAAM,eAAe,CAAC,SAAmB;AACvC,UAAM,aAAa,MAAM,QAAQ,KAAK,UAAU,IAAI,KAAK,aAAa,CAAC;AACvE,eAAW,KAAK,EAAE,OAAO,IAAI,UAAU,UAAU,OAAO,GAAG,CAAC;AAC5D,SAAK,aAAa;AAClB,iBAAa,YAAY;AAAA,EAC3B;AAEA,SACE,8CAAC,SAAI,WAAU,gCACb;AAAA,kDAAC,QAAG,WAAU,kBAAiB;AAAA;AAAA,MAAW,aAAa;AAAA,OAAI;AAAA,IAC1D,aAAa,MAAM,IAAI,UACtB,8CAAC,SAAkB,WAAU,0CAC3B;AAAA,oDAAC,SAAI,WAAU,2BACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,KAAK;AAAA,YACZ,UAAU,OAAK;AACb,mBAAK,OAAO,EAAE,OAAO;AACrB,2BAAa,YAAY;AAAA,YAC3B;AAAA,YACA,WAAU;AAAA,YAET,oBAAU,IAAI,QACb,6CAAC,YAAgB,OAAO,IAAK,gBAAhB,EAAmB,CACjC;AAAA;AAAA,QACH;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO,KAAK;AAAA,YACZ,UAAU,OAAK;AACb,mBAAK,WAAW,OAAO,EAAE,OAAO,KAAK;AACrC,2BAAa,YAAY;AAAA,YAC3B;AAAA,YACA,WAAU;AAAA;AAAA,QACZ;AAAA,QACA,6CAAC,YAAO,SAAS,MAAM,aAAa,IAAI,GAAG,WAAU,8DAA6D,yBAElH;AAAA,SACF;AAAA,OAEE,MAAM,QAAQ,KAAK,UAAU,IAAI,KAAK,aAAa,CAAC,GAAG,IAAI,CAAC,GAAG,MAC/D,8CAAC,SAAY,WAAU,gCACrB;AAAA,qDAAC,WAAM,MAAK,QAAO,OAAO,EAAE,OAAO,UAAU,OAAK;AAAE,YAAE,QAAQ,EAAE,OAAO;AAAO,uBAAa,YAAY;AAAA,QAAE,GAAG,WAAU,2BAA0B;AAAA,QAChJ,6CAAC,WAAM,MAAK,QAAO,OAAO,EAAE,UAAU,UAAU,OAAK;AAAE,YAAE,WAAW,EAAE,OAAO;AAAc,uBAAa,YAAY;AAAA,QAAE,GAAG,WAAU,2BAA0B;AAAA,QAC7J,6CAAC,WAAM,MAAK,QAAO,OAAO,EAAE,OAAO,UAAU,OAAK;AAAE,YAAE,QAAQ,EAAE,OAAO;AAAO,uBAAa,YAAY;AAAA,QAAE,GAAG,WAAU,2BAA0B;AAAA,QAChJ,6CAAC,YAAO,SAAS,MAAM;AAAE,UAAC,KAAK,WAA2B,OAAO,GAAG,CAAC;AAAG,uBAAa,YAAY;AAAA,QAAE,GAAG,WAAU,4DAA2D,oBAAC;AAAA,WAJpK,CAKV,CACD;AAAA,SAnCO,KAAK,EAoCf,CACD;AAAA,IACD,6CAAC,YAAO,SAAS,SAAS,WAAU,mEAAkE,sBAAQ;AAAA,KAChH;AAEJ;;;AC5EA,IAAAC,eAA6B;AAsBvB,IAAAC,sBAAA;AAZC,IAAM,cAA0C,CAAC,EAAE,cAAc,OAAO,QAAQ,WAAW,MAAM;AAEtG,QAAM,iBAAiB,CAAC,MAAgB,SAAoB;AAC1D,UAAM,aAAa,MAAM,QAAQ,KAAK,UAAU,IAAI,KAAK,aAAa,CAAC;AACvE,eAAW,KAAK,IAAI;AACpB,SAAK,aAAa;AAClB,iBAAa,MAAM,KAAK,IAAI;AAC5B,iBAAa,YAAY;AAAA,EAC3B;AAEA,SACE,8CAAC,SAAI,WAAU,aACb;AAAA,kDAAC,QAAG,WAAU,kBAAiB;AAAA;AAAA,MAAkB,aAAa;AAAA,OAAI;AAAA,IAGlE,8CAAC,SAAI,WAAU,aACb;AAAA,mDAAC,QAAG,WAAU,iBAAgB,mBAAK;AAAA,MAClC,MAAM,IAAI,OACT,8CAAC,SAAe,WAAU,yEACxB;AAAA,qDAAC,OAAG,YAAE,YAAY,EAAE,IAAG;AAAA,QACvB,6CAAC,YAAO,SAAS,MAAM,eAAe,EAAE,QAAI,aAAAC,IAAO,GAAG,MAAM,WAAW,UAAU,aAAa,MAAM,QAAQ,YAAY,CAAC,EAAE,GAAG,EAAE,OAAO,UAAU,UAAU,UAAU,OAAO,EAAE,GAAG,CAAC,GAAG,WAAU,gEAA+D,mBAAK;AAAA,WAF3P,EAAE,EAGZ,CACD;AAAA,OACH;AAAA,IAGA,8CAAC,SAAI,WAAU,aACb;AAAA,mDAAC,QAAG,WAAU,iBAAgB,oBAAM;AAAA,MACnC,OAAO,IAAI,OACV,8CAAC,SAAe,WAAU,yEACxB;AAAA,qDAAC,OAAG,YAAE,MAAK;AAAA,QACX,6CAAC,YAAO,SAAS,MAAM,eAAe,EAAE,QAAI,aAAAA,IAAO,GAAG,MAAM,SAAS,UAAU,aAAa,MAAM,QAAQ,YAAY,CAAC,EAAE,GAAG,EAAE,OAAO,WAAW,UAAU,UAAU,OAAO,EAAE,GAAG,CAAC,GAAG,WAAU,kEAAiE,mBAAK;AAAA,WAF5P,EAAE,EAGZ,CACD;AAAA,OACH;AAAA,KACF;AAEJ;;;ACvCI,IAAAC,sBAAA;AAFG,IAAM,YAAsC,CAAC,EAAE,MAAM,MAC1D,8CAAC,SACC;AAAA,+CAAC,QAAG,WAAU,kBAAiB,uCAAyB;AAAA,EACvD,MAAM,IAAI,UACT,8CAAC,SAAmB,WAAU,2BAC5B;AAAA,iDAAC,OAAE,WAAU,iBAAiB,eAAK,KAAI;AAAA,IACvC,6CAAC,QAAG,WAAU,gBACX,eAAK,MAAM,IAAI,OACd,8CAAC,QAAe;AAAA,QAAE;AAAA,MAAK;AAAA,MAAI,KAAK,UAAU,EAAE,UAAU;AAAA,SAA7C,EAAE,EAA6C,CACzD,GACH;AAAA,OANQ,KAAK,GAOf,CACD;AAAA,GACH;;;AJqDU,IAAAC,sBAAA;AAjDL,IAAM,qBAAwD,CAAC;AAAA,EACpE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAwB,CAAC,CAAC;AACpD,QAAM,CAAC,cAAc,eAAe,QAAI,uBAA6B,IAAI;AACzE,QAAM,CAAC,KAAK,MAAM,QAAI,uBAAc,OAAO;AAC3C,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAgB,CAAC,CAAC;AAC5C,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAgB,CAAC,CAAC;AAE9C,8BAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,YAAY,YAAY,SAAS,MAAM,WAAW,CAAC;AACzD,cAAU;AAAA,EACZ,GAAG,CAAC,MAAM,CAAC;AAEX,8BAAU,MAAM;AACd,QAAI,CAAC,UAAU,QAAQ,QAAS;AAChC,UAAM,WAAW,YAAY;AAC3B,UAAI,WAAY,UAAS,MAAM,WAAW,CAAC;AAC3C,UAAI,YAAa,WAAU,MAAM,YAAY,CAAC;AAAA,IAChD;AACA,aAAS;AAAA,EACX,GAAG,CAAC,QAAQ,KAAK,YAAY,WAAW,CAAC;AAEzC,MAAI,CAAC,OAAQ,QAAO;AAEpB,SACE,6CAAC,yCACE,oBACC;AAAA,IAAC,6BAAO;AAAA,IAAP;AAAA,MACC,WAAU;AAAA,MACV,SAAS,EAAE,SAAS,EAAE;AAAA,MACtB,SAAS,EAAE,SAAS,EAAE;AAAA,MACtB,MAAM,EAAE,SAAS,EAAE;AAAA,MAEnB;AAAA,QAAC,6BAAO;AAAA,QAAP;AAAA,UACC,WAAU;AAAA,UACV,SAAS,EAAE,OAAO,KAAK,SAAS,EAAE;AAAA,UAClC,SAAS,EAAE,OAAO,GAAG,SAAS,EAAE;AAAA,UAChC,MAAM,EAAE,OAAO,KAAK,SAAS,EAAE;AAAA,UAC/B,YAAY,EAAE,MAAM,UAAU,WAAW,KAAK,SAAS,GAAG;AAAA,UAG1D;AAAA,0DAAC,SAAI,WAAU,0CACb;AAAA,2DAAC,QAAG,WAAU,qBAAoB,mCAAqB;AAAA,cACvD,6CAAC,YAAO,SAAS,SAAS,WAAU,qCAAoC,oBAAC;AAAA,eAC3E;AAAA,YAGA,6CAAC,SAAI,WAAU,4BACX,WAAC,SAAS,SAAS,OAAO,EAAY,IAAI,OAC1C;AAAA,cAAC;AAAA;AAAA,gBAEC,eAAW,aAAAC;AAAA,kBACT;AAAA,kBACA,QAAQ,IAAI,+CAA+C;AAAA,gBAC7D;AAAA,gBACA,SAAS,MAAM,OAAO,CAAC;AAAA,gBAEtB,gBAAM,UAAU,kBAAkB,MAAM,UAAU,iBAAiB;AAAA;AAAA,cAP/D;AAAA,YAQP,CACD,GACH;AAAA,YAGA,8CAAC,SAAI,WAAU,gCACZ;AAAA,sBAAQ,WACP;AAAA,gBAAC;AAAA;AAAA,kBACC;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA;AAAA,cACF;AAAA,cAED,QAAQ,WAAW,gBAClB;AAAA,gBAAC;AAAA;AAAA,kBACC;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA;AAAA,cACF;AAAA,cAED,QAAQ,WAAW,6CAAC,aAAU,OAAc;AAAA,cAC5C,gBAAgB,QAAQ,WACvB,6CAAC,eAAY,cAA4B,YAAwB;AAAA,eAErE;AAAA;AAAA;AAAA,MACF;AAAA;AAAA,EACF,GAEJ;AAEJ;;;AK1HA,IAAAC,gBAAgD;AAwB5C,IAAAC,sBAAA;AAfG,IAAM,uBACX,6BAAwC,IAAI;AAQvC,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,SACE,6CAAC,iBAAiB,UAAjB,EAA0B,OAAO,EAAE,QAAQ,QAAQ,GACjD,UACH;AAEJ;;;AC5BA,IAAAC,gBAAgD;AAGzC,SAAS,QAAQ,KAAsB;AAC5C,QAAM,UAAM,0BAAW,gBAAgB;AAEvC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAEA,QAAM,EAAE,QAAQ,QAAQ,IAAI;AAC5B,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAS,KAAK;AAE5C,+BAAU,MAAM;AACd,QAAI,YAAY;AAEhB,WAAO,UAAU,KAAK,OAAO,EAAE,KAAK,YAAU;AAC5C,UAAI,CAAC,WAAW;AACd,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF,CAAC;AAED,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC;AAEzB,SAAO;AACT;;;ACVe,IAAAC,sBAAA;AAXR,SAAS,SACd,SACA,SACA;AACA,SAAO,SACL,kBACA;AACA,UAAM,oBAAiC,CAAC,UAAU;AAChD,YAAM,UAAU,QAAQ,OAAO;AAE/B,UAAI,CAAC,SAAS;AACZ,eAAO,6EAAG,mBAAS,YAAY,MAAK;AAAA,MACtC;AAEA,aAAO,6CAAC,oBAAkB,GAAG,OAAO;AAAA,IACtC;AAEA,sBAAkB,cAAc,YAC9B,iBAAiB,eACjB,iBAAiB,QACjB,WACF;AAEA,WAAO;AAAA,EACT;AACF;;;ACnBO,SAAS,YAAY,EAAE,KAAK,OAAO,GAAuB;AAM/D,SAAO,OACL,SACA,MACA,iBACG;AACH,UAAM,UAAU,MAAM,OAAO,UAAU,KAAK;AAAA,MAC1C,QAAQ,QAAQ,MAAM;AAAA,MACtB,YAAY,QAAQ;AAAA,MACpB,aAAa,QAAQ,MAAM;AAAA,MAC3B,QAAQ,QAAQ,MAAM,UAAU,QAAQ;AAAA,IAC1C,CAAC;AAED,QAAI,CAAC,SAAS;AACZ,UAAI,aAAc,QAAO,aAAa;AACtC,YAAM,IAAI,MAAM,YAAY,GAAG,6BAA6B;AAAA,IAC9D;AAEA,WAAO,KAAK;AAAA,EACd;AACF;","names":["import_framer_motion","import_clsx","clsx","import_jsx_runtime","uuidv4","import_uuid","import_jsx_runtime","uuidv4","import_jsx_runtime","import_jsx_runtime","clsx","import_react","import_jsx_runtime","import_react","import_jsx_runtime"]}
|
package/dist/index.mjs
CHANGED
|
@@ -114,28 +114,292 @@ var MemoryFlagAdapter = class {
|
|
|
114
114
|
}
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
// src/react/FeatureFlagsDialog.tsx
|
|
118
|
+
import { useState, useEffect } from "react";
|
|
119
|
+
import { motion as motion2, AnimatePresence } from "framer-motion";
|
|
120
|
+
import clsx2 from "clsx";
|
|
121
|
+
|
|
122
|
+
// src/react/Tabs/FlagList.tsx
|
|
123
|
+
import { motion } from "framer-motion";
|
|
124
|
+
import clsx from "clsx";
|
|
125
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
126
|
+
var FlagList = ({ flags, selectedFlag, setSelectedFlag, updateFlag }) => {
|
|
127
|
+
const toggleFlag = async (flag) => {
|
|
128
|
+
flag.enabled = !flag.enabled;
|
|
129
|
+
if (updateFlag) await updateFlag(flag);
|
|
130
|
+
};
|
|
131
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: flags.map((flag) => /* @__PURE__ */ jsxs(
|
|
132
|
+
motion.div,
|
|
133
|
+
{
|
|
134
|
+
className: "flex justify-between items-center border rounded p-2 hover:bg-gray-50 cursor-pointer",
|
|
135
|
+
onClick: () => setSelectedFlag(flag),
|
|
136
|
+
whileHover: { scale: 1.02 },
|
|
137
|
+
children: [
|
|
138
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
139
|
+
/* @__PURE__ */ jsx("p", { className: "font-semibold", children: flag.key }),
|
|
140
|
+
/* @__PURE__ */ jsxs("p", { className: "text-gray-500 text-sm", children: [
|
|
141
|
+
"Enabled: ",
|
|
142
|
+
flag.enabled ? "Yes" : "No"
|
|
143
|
+
] })
|
|
144
|
+
] }),
|
|
145
|
+
/* @__PURE__ */ jsx(
|
|
146
|
+
"button",
|
|
147
|
+
{
|
|
148
|
+
onClick: (e) => {
|
|
149
|
+
e.stopPropagation();
|
|
150
|
+
toggleFlag(flag);
|
|
151
|
+
},
|
|
152
|
+
className: clsx(
|
|
153
|
+
"px-3 py-1 rounded text-white",
|
|
154
|
+
flag.enabled ? "bg-red-500 hover:bg-red-600" : "bg-green-500 hover:bg-green-600"
|
|
155
|
+
),
|
|
156
|
+
children: flag.enabled ? "Disable" : "Enable"
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
flag.key
|
|
162
|
+
)) });
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// src/react/Tabs/RulesEditor.tsx
|
|
166
|
+
import { v4 as uuidv4 } from "uuid";
|
|
167
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
168
|
+
var ruleTypes = [
|
|
169
|
+
"USER_ID",
|
|
170
|
+
"USER_ATTRIBUTE",
|
|
171
|
+
"PERMISSION",
|
|
172
|
+
"GROUP",
|
|
173
|
+
"PERCENTAGE",
|
|
174
|
+
"CUSTOM"
|
|
175
|
+
];
|
|
176
|
+
var RulesEditor = ({ selectedFlag, updateFlag }) => {
|
|
177
|
+
const addRule = () => {
|
|
178
|
+
selectedFlag.rules.push({ id: uuidv4(), type: "USER_ID", priority: selectedFlag.rules.length, conditions: [] });
|
|
179
|
+
updateFlag?.(selectedFlag);
|
|
180
|
+
};
|
|
181
|
+
const addCondition = (rule) => {
|
|
182
|
+
const conditions = Array.isArray(rule.conditions) ? rule.conditions : [];
|
|
183
|
+
conditions.push({ field: "", operator: "equals", value: "" });
|
|
184
|
+
rule.conditions = conditions;
|
|
185
|
+
updateFlag?.(selectedFlag);
|
|
186
|
+
};
|
|
187
|
+
return /* @__PURE__ */ jsxs2("div", { className: "mt-4 border-t pt-4 space-y-2", children: [
|
|
188
|
+
/* @__PURE__ */ jsxs2("h3", { className: "font-bold mb-2", children: [
|
|
189
|
+
"Rules for ",
|
|
190
|
+
selectedFlag.key
|
|
191
|
+
] }),
|
|
192
|
+
selectedFlag.rules.map((rule) => /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 border p-2 rounded", children: [
|
|
193
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-2 items-center", children: [
|
|
194
|
+
/* @__PURE__ */ jsx2(
|
|
195
|
+
"select",
|
|
196
|
+
{
|
|
197
|
+
value: rule.type,
|
|
198
|
+
onChange: (e) => {
|
|
199
|
+
rule.type = e.target.value;
|
|
200
|
+
updateFlag?.(selectedFlag);
|
|
201
|
+
},
|
|
202
|
+
className: "border p-1 rounded",
|
|
203
|
+
children: ruleTypes.map((rt) => /* @__PURE__ */ jsx2("option", { value: rt, children: rt }, rt))
|
|
204
|
+
}
|
|
205
|
+
),
|
|
206
|
+
/* @__PURE__ */ jsx2(
|
|
207
|
+
"input",
|
|
208
|
+
{
|
|
209
|
+
type: "number",
|
|
210
|
+
value: rule.priority,
|
|
211
|
+
onChange: (e) => {
|
|
212
|
+
rule.priority = Number(e.target.value);
|
|
213
|
+
updateFlag?.(selectedFlag);
|
|
214
|
+
},
|
|
215
|
+
className: "border p-1 rounded w-20"
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => addCondition(rule), className: "bg-blue-500 text-white px-2 py-1 rounded hover:bg-blue-600", children: "+ Condition" })
|
|
219
|
+
] }),
|
|
220
|
+
(Array.isArray(rule.conditions) ? rule.conditions : []).map((c, i) => /* @__PURE__ */ jsxs2("div", { className: "flex gap-2 items-center ml-4", children: [
|
|
221
|
+
/* @__PURE__ */ jsx2("input", { type: "text", value: c.field, onChange: (e) => {
|
|
222
|
+
c.field = e.target.value;
|
|
223
|
+
updateFlag?.(selectedFlag);
|
|
224
|
+
}, className: "border p-1 rounded w-24" }),
|
|
225
|
+
/* @__PURE__ */ jsx2("input", { type: "text", value: c.operator, onChange: (e) => {
|
|
226
|
+
c.operator = e.target.value;
|
|
227
|
+
updateFlag?.(selectedFlag);
|
|
228
|
+
}, className: "border p-1 rounded w-24" }),
|
|
229
|
+
/* @__PURE__ */ jsx2("input", { type: "text", value: c.value, onChange: (e) => {
|
|
230
|
+
c.value = e.target.value;
|
|
231
|
+
updateFlag?.(selectedFlag);
|
|
232
|
+
}, className: "border p-1 rounded w-24" }),
|
|
233
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => {
|
|
234
|
+
rule.conditions.splice(i, 1);
|
|
235
|
+
updateFlag?.(selectedFlag);
|
|
236
|
+
}, className: "bg-red-500 text-white px-2 py-1 rounded hover:bg-red-600", children: "\u2715" })
|
|
237
|
+
] }, i))
|
|
238
|
+
] }, rule.id)),
|
|
239
|
+
/* @__PURE__ */ jsx2("button", { onClick: addRule, className: "bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600 mt-2", children: "Add Rule" })
|
|
240
|
+
] });
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/react/Tabs/GrantEditor.tsx
|
|
244
|
+
import { v4 as uuidv42 } from "uuid";
|
|
245
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
246
|
+
var GrantEditor = ({ selectedFlag, users, groups, updateFlag }) => {
|
|
247
|
+
const grantCondition = (rule, cond) => {
|
|
248
|
+
const conditions = Array.isArray(rule.conditions) ? rule.conditions : [];
|
|
249
|
+
conditions.push(cond);
|
|
250
|
+
rule.conditions = conditions;
|
|
251
|
+
selectedFlag.rules.push(rule);
|
|
252
|
+
updateFlag?.(selectedFlag);
|
|
253
|
+
};
|
|
254
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-4", children: [
|
|
255
|
+
/* @__PURE__ */ jsxs3("h3", { className: "font-bold mb-2", children: [
|
|
256
|
+
"Grant Access for ",
|
|
257
|
+
selectedFlag.key
|
|
258
|
+
] }),
|
|
259
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
260
|
+
/* @__PURE__ */ jsx3("h4", { className: "font-semibold", children: "Users" }),
|
|
261
|
+
users.map((u) => /* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-center border p-2 rounded hover:bg-gray-50", children: [
|
|
262
|
+
/* @__PURE__ */ jsx3("p", { children: u.username || u.id }),
|
|
263
|
+
/* @__PURE__ */ jsx3("button", { onClick: () => grantCondition({ id: uuidv42(), type: "USER_ID", priority: selectedFlag.rules.length, conditions: [] }, { field: "userId", operator: "equals", value: u.id }), className: "bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600", children: "Grant" })
|
|
264
|
+
] }, u.id))
|
|
265
|
+
] }),
|
|
266
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
267
|
+
/* @__PURE__ */ jsx3("h4", { className: "font-semibold", children: "Groups" }),
|
|
268
|
+
groups.map((g) => /* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-center border p-2 rounded hover:bg-gray-50", children: [
|
|
269
|
+
/* @__PURE__ */ jsx3("p", { children: g.name }),
|
|
270
|
+
/* @__PURE__ */ jsx3("button", { onClick: () => grantCondition({ id: uuidv42(), type: "GROUP", priority: selectedFlag.rules.length, conditions: [] }, { field: "groupId", operator: "equals", value: g.id }), className: "bg-purple-500 text-white px-3 py-1 rounded hover:bg-purple-600", children: "Grant" })
|
|
271
|
+
] }, g.id))
|
|
272
|
+
] })
|
|
273
|
+
] });
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// src/react/Tabs/AuditView.tsx
|
|
277
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
278
|
+
var AuditView = ({ flags }) => /* @__PURE__ */ jsxs4("div", { children: [
|
|
279
|
+
/* @__PURE__ */ jsx4("h3", { className: "font-bold mb-2", children: "Audit / Access Management" }),
|
|
280
|
+
flags.map((flag) => /* @__PURE__ */ jsxs4("div", { className: "border p-2 rounded mb-2", children: [
|
|
281
|
+
/* @__PURE__ */ jsx4("p", { className: "font-semibold", children: flag.key }),
|
|
282
|
+
/* @__PURE__ */ jsx4("ul", { className: "ml-4 text-sm", children: flag.rules.map((r) => /* @__PURE__ */ jsxs4("li", { children: [
|
|
283
|
+
r.type,
|
|
284
|
+
" - ",
|
|
285
|
+
JSON.stringify(r.conditions)
|
|
286
|
+
] }, r.id)) })
|
|
287
|
+
] }, flag.key))
|
|
288
|
+
] });
|
|
289
|
+
|
|
290
|
+
// src/react/FeatureFlagsDialog.tsx
|
|
291
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
292
|
+
var FeatureFlagManager = ({
|
|
293
|
+
engine,
|
|
294
|
+
isOpen,
|
|
295
|
+
onClose,
|
|
296
|
+
fetchFlags,
|
|
297
|
+
fetchUsers,
|
|
298
|
+
fetchGroups,
|
|
299
|
+
updateFlag
|
|
300
|
+
}) => {
|
|
301
|
+
const [flags, setFlags] = useState([]);
|
|
302
|
+
const [selectedFlag, setSelectedFlag] = useState(null);
|
|
303
|
+
const [tab, setTab] = useState("flags");
|
|
304
|
+
const [users, setUsers] = useState([]);
|
|
305
|
+
const [groups, setGroups] = useState([]);
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
if (!isOpen) return;
|
|
308
|
+
const loadFlags = async () => setFlags(await fetchFlags());
|
|
309
|
+
loadFlags();
|
|
310
|
+
}, [isOpen]);
|
|
311
|
+
useEffect(() => {
|
|
312
|
+
if (!isOpen || tab !== "grant") return;
|
|
313
|
+
const loadData = async () => {
|
|
314
|
+
if (fetchUsers) setUsers(await fetchUsers());
|
|
315
|
+
if (fetchGroups) setGroups(await fetchGroups());
|
|
316
|
+
};
|
|
317
|
+
loadData();
|
|
318
|
+
}, [isOpen, tab, fetchUsers, fetchGroups]);
|
|
319
|
+
if (!isOpen) return null;
|
|
320
|
+
return /* @__PURE__ */ jsx5(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsx5(
|
|
321
|
+
motion2.div,
|
|
322
|
+
{
|
|
323
|
+
className: "fixed inset-0 flex items-center justify-center z-50 bg-black/50",
|
|
324
|
+
initial: { opacity: 0 },
|
|
325
|
+
animate: { opacity: 1 },
|
|
326
|
+
exit: { opacity: 0 },
|
|
327
|
+
children: /* @__PURE__ */ jsxs5(
|
|
328
|
+
motion2.div,
|
|
329
|
+
{
|
|
330
|
+
className: "bg-white rounded-lg shadow-xl w-11/12 max-w-6xl p-6",
|
|
331
|
+
initial: { scale: 0.8, opacity: 0 },
|
|
332
|
+
animate: { scale: 1, opacity: 1 },
|
|
333
|
+
exit: { scale: 0.8, opacity: 0 },
|
|
334
|
+
transition: { type: "spring", stiffness: 300, damping: 25 },
|
|
335
|
+
children: [
|
|
336
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex justify-between items-center mb-4", children: [
|
|
337
|
+
/* @__PURE__ */ jsx5("h2", { className: "text-xl font-bold", children: "Feature Flags Manager" }),
|
|
338
|
+
/* @__PURE__ */ jsx5("button", { onClick: onClose, className: "text-gray-500 hover:text-gray-800", children: "\u2715" })
|
|
339
|
+
] }),
|
|
340
|
+
/* @__PURE__ */ jsx5("div", { className: "flex gap-2 mb-4 border-b", children: ["flags", "grant", "audit"].map((t) => /* @__PURE__ */ jsx5(
|
|
341
|
+
"button",
|
|
342
|
+
{
|
|
343
|
+
className: clsx2(
|
|
344
|
+
"px-4 py-2 rounded-t font-semibold",
|
|
345
|
+
tab === t ? "bg-white border-t border-x border-gray-300" : "bg-gray-200 hover:bg-gray-300"
|
|
346
|
+
),
|
|
347
|
+
onClick: () => setTab(t),
|
|
348
|
+
children: t === "flags" ? "Feature Flags" : t === "grant" ? "Grant Access" : "Audit"
|
|
349
|
+
},
|
|
350
|
+
t
|
|
351
|
+
)) }),
|
|
352
|
+
/* @__PURE__ */ jsxs5("div", { className: "max-h-[60vh] overflow-y-auto", children: [
|
|
353
|
+
tab === "flags" && /* @__PURE__ */ jsx5(
|
|
354
|
+
FlagList,
|
|
355
|
+
{
|
|
356
|
+
flags,
|
|
357
|
+
selectedFlag,
|
|
358
|
+
setSelectedFlag,
|
|
359
|
+
updateFlag
|
|
360
|
+
}
|
|
361
|
+
),
|
|
362
|
+
tab === "grant" && selectedFlag && /* @__PURE__ */ jsx5(
|
|
363
|
+
GrantEditor,
|
|
364
|
+
{
|
|
365
|
+
selectedFlag,
|
|
366
|
+
users,
|
|
367
|
+
groups,
|
|
368
|
+
updateFlag
|
|
369
|
+
}
|
|
370
|
+
),
|
|
371
|
+
tab === "audit" && /* @__PURE__ */ jsx5(AuditView, { flags }),
|
|
372
|
+
selectedFlag && tab === "flags" && /* @__PURE__ */ jsx5(RulesEditor, { selectedFlag, updateFlag })
|
|
373
|
+
] })
|
|
374
|
+
]
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
) });
|
|
379
|
+
};
|
|
380
|
+
|
|
117
381
|
// src/react/FlagProvider.tsx
|
|
118
382
|
import { createContext } from "react";
|
|
119
|
-
import { jsx } from "react/jsx-runtime";
|
|
383
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
120
384
|
var FlagContextReact = createContext(null);
|
|
121
385
|
function FlagProvider({
|
|
122
386
|
engine,
|
|
123
387
|
context,
|
|
124
388
|
children
|
|
125
389
|
}) {
|
|
126
|
-
return /* @__PURE__ */
|
|
390
|
+
return /* @__PURE__ */ jsx6(FlagContextReact.Provider, { value: { engine, context }, children });
|
|
127
391
|
}
|
|
128
392
|
|
|
129
393
|
// src/react/useFlag.ts
|
|
130
|
-
import { useContext, useEffect, useState } from "react";
|
|
394
|
+
import { useContext, useEffect as useEffect2, useState as useState2 } from "react";
|
|
131
395
|
function useFlag(key) {
|
|
132
396
|
const ctx = useContext(FlagContextReact);
|
|
133
397
|
if (!ctx) {
|
|
134
398
|
throw new Error("useFlag must be used inside FlagProvider");
|
|
135
399
|
}
|
|
136
400
|
const { engine, context } = ctx;
|
|
137
|
-
const [enabled, setEnabled] =
|
|
138
|
-
|
|
401
|
+
const [enabled, setEnabled] = useState2(false);
|
|
402
|
+
useEffect2(() => {
|
|
139
403
|
let cancelled = false;
|
|
140
404
|
engine.isEnabled(key, context).then((result) => {
|
|
141
405
|
if (!cancelled) {
|
|
@@ -150,15 +414,15 @@ function useFlag(key) {
|
|
|
150
414
|
}
|
|
151
415
|
|
|
152
416
|
// src/react/withFlag.tsx
|
|
153
|
-
import { Fragment, jsx as
|
|
417
|
+
import { Fragment, jsx as jsx7 } from "react/jsx-runtime";
|
|
154
418
|
function withFlag(flagKey, options) {
|
|
155
419
|
return function(WrappedComponent) {
|
|
156
420
|
const ComponentWithFlag = (props) => {
|
|
157
421
|
const enabled = useFlag(flagKey);
|
|
158
422
|
if (!enabled) {
|
|
159
|
-
return /* @__PURE__ */
|
|
423
|
+
return /* @__PURE__ */ jsx7(Fragment, { children: options?.fallback ?? null });
|
|
160
424
|
}
|
|
161
|
-
return /* @__PURE__ */
|
|
425
|
+
return /* @__PURE__ */ jsx7(WrappedComponent, { ...props });
|
|
162
426
|
};
|
|
163
427
|
ComponentWithFlag.displayName = `withFlag(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`;
|
|
164
428
|
return ComponentWithFlag;
|
|
@@ -182,6 +446,7 @@ function requireFlag({ key, engine }) {
|
|
|
182
446
|
};
|
|
183
447
|
}
|
|
184
448
|
export {
|
|
449
|
+
FeatureFlagManager,
|
|
185
450
|
FlagContextReact,
|
|
186
451
|
FlagEngine,
|
|
187
452
|
FlagProvider,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/engine.ts","../src/adapters/prisma.ts","../src/adapters/memory.ts","../src/react/FeatureFlagsDialog.tsx","../src/react/Tabs/FlagList.tsx","../src/react/Tabs/RulesEditor.tsx","../src/react/Tabs/GrantEditor.tsx","../src/react/Tabs/AuditView.tsx","../src/react/FlagProvider.tsx","../src/react/useFlag.ts","../src/react/withFlag.tsx","../src/node/middleware.ts"],"sourcesContent":["import { FlagAdapter, FlagContext } from \"./types\"\n\nexport class FlagEngine {\n constructor(private adapter: FlagAdapter) {}\n\n async isEnabled(key: string, context: FlagContext): Promise<boolean> {\n const flag = await this.adapter.getFlag(key)\n if (!flag || !flag.enabled) return false\n\n const sortedRules = flag.rules.sort((a: any, b: any) => b.priority - a.priority)\n\n for (const rule of sortedRules) {\n const result = await this.evaluateRule(rule, context)\n if (result !== null) return result\n }\n\n return false\n }\n\n private async evaluateRule(rule: any, context: FlagContext): Promise<boolean | null> {\n switch (rule.type) {\n case \"USER_ID\":\n return rule.conditions.includes(context.userId)\n\n case \"PERMISSION\":\n return context.permissions?.includes(rule.conditions.permission) ?? false\n\n case \"USER_ATTRIBUTE\":\n const { field, operator, value } = rule.conditions\n const userValue = context.attributes?.[field]\n\n switch (operator) {\n case \"equals\":\n return userValue === value\n case \"gt\":\n return userValue > value\n case \"lt\":\n return userValue < value\n case \"contains\":\n return userValue?.includes(value)\n }\n return false\n\n case \"GROUP_MEMBERSHIP\":\n return context.groups?.includes(rule.conditions.groupId) ?? false\n\n case \"PERCENTAGE\":\n return this.percentageRollout(context.userId!, rule.percentage)\n\n default:\n return null\n }\n }\n\n private percentageRollout(userId: string, percentage: number) {\n const hash = this.hash(userId)\n return (hash % 100) < percentage\n }\n\n private hash(str: string) {\n let hash = 0\n for (let i = 0; i < str.length; i++) {\n hash = (hash << 5) - hash + str.charCodeAt(i)\n hash |= 0\n }\n return Math.abs(hash)\n }\n}\n","import { PrismaClient } from \"@custom-prisma/client\" // ā match schema generator\nimport { FlagAdapter, FeatureFlag } from \"../core/types\"\n\nexport class PrismaFlagAdapter implements FlagAdapter {\n constructor(private prisma: PrismaClient) {}\n\n async getFlag(key: string): Promise<FeatureFlag | null> {\n const flag = await this.prisma.featureFlag.findUnique({\n where: { key },\n include: { rules: true } // make sure 'rules' relation exists\n })\n\n if (!flag) return null\n\n return {\n key: flag.key,\n enabled: flag.enabled,\n rules: flag.rules as any\n }\n }\n}\n","import type {\n FlagAdapter,\n FeatureFlag\n} from \"../core/types\"\n\nexport interface MemoryAdapterOptions {\n initialFlags?: FeatureFlag[]\n}\n\nexport class MemoryFlagAdapter implements FlagAdapter {\n private flags = new Map<string, FeatureFlag>()\n\n constructor(options?: MemoryAdapterOptions) {\n if (options?.initialFlags) {\n for (const flag of options.initialFlags) {\n this.flags.set(flag.key, flag)\n }\n }\n }\n\n async getFlag(key: string): Promise<FeatureFlag | null> {\n return this.flags.get(key) ?? null\n }\n\n /**\n * Add or update a flag\n */\n setFlag(flag: FeatureFlag): void {\n this.flags.set(flag.key, flag)\n }\n\n /**\n * Remove a flag\n */\n removeFlag(key: string): void {\n this.flags.delete(key)\n }\n\n /**\n * Get all flags (useful for debugging)\n */\n getAllFlags(): FeatureFlag[] {\n return Array.from(this.flags.values())\n }\n\n /**\n * Clear all flags\n */\n clear(): void {\n this.flags.clear()\n }\n}\n","import React, { useState, useEffect } from \"react\"\nimport { motion, AnimatePresence } from \"framer-motion\"\nimport clsx from \"clsx\"\n\nimport type { FeatureFlag, FlagRule, RuleType } from \"../core/types\"\nimport { FlagEngine } from \"../core/engine\"\n\nimport { FlagList } from \"./Tabs/FlagList\";\nimport { RulesEditor } from \"./Tabs/RulesEditor\";\nimport { GrantEditor } from \"./Tabs/GrantEditor\";\nimport { AuditView } from \"./Tabs/AuditView\";\n\ntype Tab = \"flags\" | \"grant\" | \"audit\"\n\ninterface FeatureFlagManagerProps {\n engine: FlagEngine\n isOpen: boolean\n onClose: () => void\n fetchFlags: () => Promise<FeatureFlag[]>\n fetchUsers?: () => Promise<any[]>\n fetchGroups?: () => Promise<any[]>\n updateFlag?: (flag: FeatureFlag) => Promise<void>\n}\n\nexport const FeatureFlagManager: React.FC<FeatureFlagManagerProps> = ({\n engine,\n isOpen,\n onClose,\n fetchFlags,\n fetchUsers,\n fetchGroups,\n updateFlag\n}) => {\n const [flags, setFlags] = useState<FeatureFlag[]>([])\n const [selectedFlag, setSelectedFlag] = useState<FeatureFlag | null>(null)\n const [tab, setTab] = useState<Tab>(\"flags\")\n const [users, setUsers] = useState<any[]>([])\n const [groups, setGroups] = useState<any[]>([])\n\n useEffect(() => {\n if (!isOpen) return\n const loadFlags = async () => setFlags(await fetchFlags())\n loadFlags()\n }, [isOpen])\n\n useEffect(() => {\n if (!isOpen || tab !== \"grant\") return\n const loadData = async () => {\n if (fetchUsers) setUsers(await fetchUsers())\n if (fetchGroups) setGroups(await fetchGroups())\n }\n loadData()\n }, [isOpen, tab, fetchUsers, fetchGroups])\n\n if (!isOpen) return null\n\n return (\n <AnimatePresence>\n {isOpen && (\n <motion.div\n className=\"fixed inset-0 flex items-center justify-center z-50 bg-black/50\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n >\n <motion.div\n className=\"bg-white rounded-lg shadow-xl w-11/12 max-w-6xl p-6\"\n initial={{ scale: 0.8, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0.8, opacity: 0 }}\n transition={{ type: \"spring\", stiffness: 300, damping: 25 }}\n >\n {/* Header */}\n <div className=\"flex justify-between items-center mb-4\">\n <h2 className=\"text-xl font-bold\">Feature Flags Manager</h2>\n <button onClick={onClose} className=\"text-gray-500 hover:text-gray-800\">ā</button>\n </div>\n\n {/* Tabs */}\n <div className=\"flex gap-2 mb-4 border-b\">\n {([\"flags\", \"grant\", \"audit\"] as Tab[]).map(t => (\n <button\n key={t}\n className={clsx(\n \"px-4 py-2 rounded-t font-semibold\",\n tab === t ? \"bg-white border-t border-x border-gray-300\" : \"bg-gray-200 hover:bg-gray-300\"\n )}\n onClick={() => setTab(t)}\n >\n {t === \"flags\" ? \"Feature Flags\" : t === \"grant\" ? \"Grant Access\" : \"Audit\"}\n </button>\n ))}\n </div>\n\n {/* Tab content */}\n <div className=\"max-h-[60vh] overflow-y-auto\">\n {tab === \"flags\" && (\n <FlagList\n flags={flags}\n selectedFlag={selectedFlag}\n setSelectedFlag={setSelectedFlag}\n updateFlag={updateFlag}\n />\n )}\n {tab === \"grant\" && selectedFlag && (\n <GrantEditor\n selectedFlag={selectedFlag}\n users={users}\n groups={groups}\n updateFlag={updateFlag}\n />\n )}\n {tab === \"audit\" && <AuditView flags={flags} />}\n {selectedFlag && tab === \"flags\" && (\n <RulesEditor selectedFlag={selectedFlag} updateFlag={updateFlag} />\n )}\n </div>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n )\n}\n","import React from \"react\"\nimport { motion } from \"framer-motion\"\nimport clsx from \"clsx\"\nimport type { FeatureFlag } from \"../../core/types\"\n\ninterface FlagListProps {\n flags: FeatureFlag[]\n selectedFlag: FeatureFlag | null\n setSelectedFlag: (flag: FeatureFlag) => void\n updateFlag?: (flag: FeatureFlag) => Promise<void>\n}\n\nexport const FlagList: React.FC<FlagListProps> = ({ flags, selectedFlag, setSelectedFlag, updateFlag }) => {\n const toggleFlag = async (flag: FeatureFlag) => {\n flag.enabled = !flag.enabled\n if (updateFlag) await updateFlag(flag)\n }\n\n return (\n <div className=\"space-y-4\">\n {flags.map(flag => (\n <motion.div\n key={flag.key}\n className=\"flex justify-between items-center border rounded p-2 hover:bg-gray-50 cursor-pointer\"\n onClick={() => setSelectedFlag(flag)}\n whileHover={{ scale: 1.02 }}\n >\n <div>\n <p className=\"font-semibold\">{flag.key}</p>\n <p className=\"text-gray-500 text-sm\">\n Enabled: {flag.enabled ? \"Yes\" : \"No\"}\n </p>\n </div>\n <button\n onClick={e => {\n e.stopPropagation()\n toggleFlag(flag)\n }}\n className={clsx(\n \"px-3 py-1 rounded text-white\",\n flag.enabled ? \"bg-red-500 hover:bg-red-600\" : \"bg-green-500 hover:bg-green-600\"\n )}\n >\n {flag.enabled ? \"Disable\" : \"Enable\"}\n </button>\n </motion.div>\n ))}\n </div>\n )\n}\n","import React from \"react\"\nimport { v4 as uuidv4 } from \"uuid\"\nimport type { FeatureFlag, FlagRule, RuleType, Condition } from \"../../core/types\"\n\ninterface RulesEditorProps {\n selectedFlag: FeatureFlag\n updateFlag?: (flag: FeatureFlag) => Promise<void>\n}\n\nconst ruleTypes: RuleType[] = [\n \"USER_ID\",\n \"USER_ATTRIBUTE\",\n \"PERMISSION\",\n \"GROUP\",\n \"PERCENTAGE\",\n \"CUSTOM\"\n]\n\nexport const RulesEditor: React.FC<RulesEditorProps> = ({ selectedFlag, updateFlag }) => {\n\n const addRule = () => {\n selectedFlag.rules.push({ id: uuidv4(), type: \"USER_ID\", priority: selectedFlag.rules.length, conditions: [] })\n updateFlag?.(selectedFlag)\n }\n\n const addCondition = (rule: FlagRule) => {\n const conditions = Array.isArray(rule.conditions) ? rule.conditions : []\n conditions.push({ field: \"\", operator: \"equals\", value: \"\" })\n rule.conditions = conditions\n updateFlag?.(selectedFlag)\n }\n\n return (\n <div className=\"mt-4 border-t pt-4 space-y-2\">\n <h3 className=\"font-bold mb-2\">Rules for {selectedFlag.key}</h3>\n {selectedFlag.rules.map(rule => (\n <div key={rule.id} className=\"flex flex-col gap-2 border p-2 rounded\">\n <div className=\"flex gap-2 items-center\">\n <select\n value={rule.type}\n onChange={e => {\n rule.type = e.target.value as RuleType\n updateFlag?.(selectedFlag)\n }}\n className=\"border p-1 rounded\"\n >\n {ruleTypes.map(rt => (\n <option key={rt} value={rt}>{rt}</option>\n ))}\n </select>\n <input\n type=\"number\"\n value={rule.priority}\n onChange={e => {\n rule.priority = Number(e.target.value)\n updateFlag?.(selectedFlag)\n }}\n className=\"border p-1 rounded w-20\"\n />\n <button onClick={() => addCondition(rule)} className=\"bg-blue-500 text-white px-2 py-1 rounded hover:bg-blue-600\">\n + Condition\n </button>\n </div>\n {/* Render conditions */}\n {(Array.isArray(rule.conditions) ? rule.conditions : []).map((c, i) => (\n <div key={i} className=\"flex gap-2 items-center ml-4\">\n <input type=\"text\" value={c.field} onChange={e => { c.field = e.target.value; updateFlag?.(selectedFlag) }} className=\"border p-1 rounded w-24\" />\n <input type=\"text\" value={c.operator} onChange={e => { c.operator = e.target.value as any; updateFlag?.(selectedFlag) }} className=\"border p-1 rounded w-24\" />\n <input type=\"text\" value={c.value} onChange={e => { c.value = e.target.value; updateFlag?.(selectedFlag) }} className=\"border p-1 rounded w-24\" />\n <button onClick={() => { (rule.conditions as Condition[]).splice(i, 1); updateFlag?.(selectedFlag) }} className=\"bg-red-500 text-white px-2 py-1 rounded hover:bg-red-600\">ā</button>\n </div>\n ))}\n </div>\n ))}\n <button onClick={addRule} className=\"bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600 mt-2\">Add Rule</button>\n </div>\n )\n}\n","import React from \"react\"\nimport { v4 as uuidv4 } from \"uuid\"\nimport type { FeatureFlag, FlagRule, RuleType, Condition } from \"../../core/types\"\n\ninterface GrantEditorProps {\n selectedFlag: FeatureFlag\n users: any[]\n groups: any[]\n updateFlag?: (flag: FeatureFlag) => Promise<void>\n}\n\nexport const GrantEditor: React.FC<GrantEditorProps> = ({ selectedFlag, users, groups, updateFlag }) => {\n\n const grantCondition = (rule: FlagRule, cond: Condition) => {\n const conditions = Array.isArray(rule.conditions) ? rule.conditions : []\n conditions.push(cond)\n rule.conditions = conditions\n selectedFlag.rules.push(rule)\n updateFlag?.(selectedFlag)\n }\n\n return (\n <div className=\"space-y-4\">\n <h3 className=\"font-bold mb-2\">Grant Access for {selectedFlag.key}</h3>\n\n {/* Users */}\n <div className=\"space-y-2\">\n <h4 className=\"font-semibold\">Users</h4>\n {users.map(u => (\n <div key={u.id} className=\"flex justify-between items-center border p-2 rounded hover:bg-gray-50\">\n <p>{u.username || u.id}</p>\n <button onClick={() => grantCondition({ id: uuidv4(), type: \"USER_ID\", priority: selectedFlag.rules.length, conditions: [] }, { field: \"userId\", operator: \"equals\", value: u.id })} className=\"bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600\">Grant</button>\n </div>\n ))}\n </div>\n\n {/* Groups */}\n <div className=\"space-y-2\">\n <h4 className=\"font-semibold\">Groups</h4>\n {groups.map(g => (\n <div key={g.id} className=\"flex justify-between items-center border p-2 rounded hover:bg-gray-50\">\n <p>{g.name}</p>\n <button onClick={() => grantCondition({ id: uuidv4(), type: \"GROUP\", priority: selectedFlag.rules.length, conditions: [] }, { field: \"groupId\", operator: \"equals\", value: g.id })} className=\"bg-purple-500 text-white px-3 py-1 rounded hover:bg-purple-600\">Grant</button>\n </div>\n ))}\n </div>\n </div>\n )\n}\n","import React from \"react\"\nimport type { FeatureFlag } from \"../../core/types\"\n\ninterface AuditViewProps {\n flags: FeatureFlag[]\n}\n\nexport const AuditView: React.FC<AuditViewProps> = ({ flags }) => (\n <div>\n <h3 className=\"font-bold mb-2\">Audit / Access Management</h3>\n {flags.map(flag => (\n <div key={flag.key} className=\"border p-2 rounded mb-2\">\n <p className=\"font-semibold\">{flag.key}</p>\n <ul className=\"ml-4 text-sm\">\n {flag.rules.map(r => (\n <li key={r.id}>{r.type} - {JSON.stringify(r.conditions)}</li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n)\n","import React, { createContext, ReactNode } from \"react\"\nimport type { FlagEngine } from \"../core/engine\"\nimport type { FlagContext } from \"../core/types\"\n\nexport interface FlagProviderValue {\n engine: FlagEngine\n context: FlagContext\n}\n\nexport const FlagContextReact =\n createContext<FlagProviderValue | null>(null)\n\ninterface FlagProviderProps {\n engine: FlagEngine\n context: FlagContext\n children: ReactNode\n}\n\nexport function FlagProvider({\n engine,\n context,\n children\n}: FlagProviderProps) {\n return (\n <FlagContextReact.Provider value={{ engine, context }}>\n {children}\n </FlagContextReact.Provider>\n )\n}\n","import { useContext, useEffect, useState } from \"react\"\nimport { FlagContextReact } from \"./FlagProvider\"\n\nexport function useFlag(key: string): boolean {\n const ctx = useContext(FlagContextReact)\n\n if (!ctx) {\n throw new Error(\"useFlag must be used inside FlagProvider\")\n }\n\n const { engine, context } = ctx\n const [enabled, setEnabled] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n\n engine.isEnabled(key, context).then(result => {\n if (!cancelled) {\n setEnabled(result)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [engine, key, context])\n\n return enabled\n}\n","import React from \"react\"\nimport { useFlag } from \"./useFlag\"\n\ninterface WithFlagOptions {\n fallback?: React.ReactNode\n}\n\nexport function withFlag(\n flagKey: string,\n options?: WithFlagOptions\n) {\n return function <P extends object>(\n WrappedComponent: React.ComponentType<P>\n ) {\n const ComponentWithFlag: React.FC<P> = (props) => {\n const enabled = useFlag(flagKey)\n\n if (!enabled) {\n return <>{options?.fallback ?? null}</>\n }\n\n return <WrappedComponent {...props} />\n }\n\n ComponentWithFlag.displayName = `withFlag(${\n WrappedComponent.displayName ||\n WrappedComponent.name ||\n \"Component\"\n })`\n\n return ComponentWithFlag\n }\n}\n","import { FlagEngine } from \"../core/engine\"\nimport type { FlagContext } from \"../core/types\"\n\nexport interface RequireFlagOptions {\n key: string\n engine: FlagEngine\n}\n\n/**\n * Universal middleware/check function for any Node.js system\n * @param options.key - Feature flag key\n * @param options.engine - FlagEngine instance\n */\nexport function requireFlag({ key, engine }: RequireFlagOptions) {\n /**\n * context: object containing user info or any relevant attributes\n * next: function to call if feature is enabled\n * handleDenied: optional function to handle denial (default throws)\n */\n return async (\n context: FlagContext & { user?: any },\n next: () => void | Promise<void>,\n handleDenied?: () => void\n ) => {\n const allowed = await engine.isEnabled(key, {\n userId: context.user?.id,\n attributes: context.user,\n permissions: context.user?.permissions,\n groups: context.user?.groups ?? context.groups\n })\n\n if (!allowed) {\n if (handleDenied) return handleDenied()\n throw new Error(`Feature \"${key}\" is disabled for this user`)\n }\n\n return next()\n }\n}\n"],"mappings":"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","names":["motion","clsx","jsx","jsxs","uuidv4","jsx","jsxs","jsx","jsxs","jsx","jsxs","motion","clsx","jsx","useEffect","useState","useState","useEffect","jsx"]}
|