@brandtg/flapjack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,356 @@
1
+ #!/usr/bin/env node
2
+ import yargs from "yargs";
3
+ import { hideBin } from "yargs/helpers";
4
+ import { Pool } from "pg";
5
+ import { FeatureFlagModel } from "./model.js";
6
+ import dotenv from "dotenv";
7
+ dotenv.config();
8
+ function createDatabase() {
9
+ const connectionString = process.env.DATABASE_URL;
10
+ if (!connectionString) {
11
+ throw new Error("DATABASE_URL is not set. Provide it in the environment or a .env file.");
12
+ }
13
+ return new Pool({ connectionString });
14
+ }
15
+ const cli = yargs(hideBin(process.argv))
16
+ .scriptName("flapjack")
17
+ .usage("$0 <command> [options]")
18
+ .help()
19
+ .alias("help", "h")
20
+ .version()
21
+ .alias("version", "v")
22
+ .demandCommand(1, "You need to specify a command")
23
+ .strict();
24
+ // Common options for feature flag fields
25
+ const flagOptions = {
26
+ name: {
27
+ type: "string",
28
+ describe: "Feature flag name",
29
+ },
30
+ everyone: {
31
+ type: "boolean",
32
+ describe: "Enable flag for everyone (overrides all other settings)",
33
+ },
34
+ percent: {
35
+ type: "number",
36
+ describe: "Percentage rollout (0-99.9)",
37
+ },
38
+ roles: {
39
+ type: "array",
40
+ describe: "List of roles that have this flag enabled",
41
+ },
42
+ groups: {
43
+ type: "array",
44
+ describe: "List of user groups that have this flag enabled",
45
+ },
46
+ users: {
47
+ type: "array",
48
+ describe: "List of specific user IDs that have this flag enabled",
49
+ },
50
+ note: {
51
+ type: "string",
52
+ describe: "Description of where this flag is used and what it does",
53
+ },
54
+ };
55
+ // Create command
56
+ cli.command("create", "Create a new feature flag", (yargs) => {
57
+ return yargs
58
+ .options({
59
+ ...flagOptions,
60
+ })
61
+ .demandOption("name", "Feature flag name is required");
62
+ }, async (argv) => {
63
+ const db = createDatabase();
64
+ const model = new FeatureFlagModel(db);
65
+ try {
66
+ const input = { name: argv.name };
67
+ if (argv.everyone !== undefined)
68
+ input.everyone = argv.everyone;
69
+ if (argv.percent !== undefined)
70
+ input.percent = argv.percent;
71
+ if (argv.roles !== undefined)
72
+ input.roles = argv.roles;
73
+ if (argv.groups !== undefined)
74
+ input.groups = argv.groups;
75
+ if (argv.users !== undefined)
76
+ input.users = argv.users;
77
+ if (argv.note !== undefined)
78
+ input.note = argv.note;
79
+ const flag = await model.create(input);
80
+ console.log(JSON.stringify(flag, null, 2));
81
+ }
82
+ catch (error) {
83
+ console.error("Error creating feature flag:", error);
84
+ process.exit(1);
85
+ }
86
+ finally {
87
+ await db.end();
88
+ }
89
+ });
90
+ // Get by ID command
91
+ cli.command("get <id>", "Get a feature flag by ID", (yargs) => {
92
+ return yargs.positional("id", {
93
+ type: "number",
94
+ describe: "Feature flag ID",
95
+ });
96
+ }, async (argv) => {
97
+ const db = createDatabase();
98
+ const model = new FeatureFlagModel(db);
99
+ try {
100
+ const flag = await model.getById(argv.id);
101
+ if (flag) {
102
+ console.log(JSON.stringify(flag, null, 2));
103
+ }
104
+ else {
105
+ console.log(`Feature flag with ID ${argv.id} not found`);
106
+ process.exit(1);
107
+ }
108
+ }
109
+ catch (error) {
110
+ console.error("Error getting feature flag:", error);
111
+ process.exit(1);
112
+ }
113
+ finally {
114
+ await db.end();
115
+ }
116
+ });
117
+ // Get by name command
118
+ cli.command("get-by-name <name>", "Get a feature flag by name", (yargs) => {
119
+ return yargs.positional("name", {
120
+ type: "string",
121
+ describe: "Feature flag name",
122
+ });
123
+ }, async (argv) => {
124
+ const db = createDatabase();
125
+ const model = new FeatureFlagModel(db);
126
+ try {
127
+ const flag = await model.getByName(argv.name);
128
+ if (flag) {
129
+ console.log(JSON.stringify(flag, null, 2));
130
+ }
131
+ else {
132
+ console.log(`Feature flag with name "${argv.name}" not found`);
133
+ process.exit(1);
134
+ }
135
+ }
136
+ catch (error) {
137
+ console.error("Error getting feature flag:", error);
138
+ process.exit(1);
139
+ }
140
+ finally {
141
+ await db.end();
142
+ }
143
+ });
144
+ // List command
145
+ cli.command("list", "List all feature flags", () => { }, async () => {
146
+ const db = createDatabase();
147
+ const model = new FeatureFlagModel(db);
148
+ try {
149
+ const flags = await model.list();
150
+ console.log(JSON.stringify(flags, null, 2));
151
+ }
152
+ catch (error) {
153
+ console.error("Error listing feature flags:", error);
154
+ process.exit(1);
155
+ }
156
+ finally {
157
+ await db.end();
158
+ }
159
+ });
160
+ // Update command
161
+ cli.command("update <id>", "Update a feature flag", (yargs) => {
162
+ return yargs
163
+ .positional("id", {
164
+ type: "number",
165
+ describe: "Feature flag ID",
166
+ })
167
+ .options({
168
+ ...flagOptions,
169
+ "clear-everyone": {
170
+ type: "boolean",
171
+ describe: "Unset the everyone override (set to null)",
172
+ },
173
+ "clear-percent": {
174
+ type: "boolean",
175
+ describe: "Unset percentage rollout",
176
+ },
177
+ "clear-roles": {
178
+ type: "boolean",
179
+ describe: "Clear roles list",
180
+ },
181
+ "clear-groups": {
182
+ type: "boolean",
183
+ describe: "Clear groups list",
184
+ },
185
+ "clear-users": {
186
+ type: "boolean",
187
+ describe: "Clear users list",
188
+ },
189
+ "clear-note": {
190
+ type: "boolean",
191
+ describe: "Clear the note",
192
+ },
193
+ });
194
+ }, async (argv) => {
195
+ const db = createDatabase();
196
+ const model = new FeatureFlagModel(db);
197
+ try {
198
+ const changes = {};
199
+ if (argv.name !== undefined)
200
+ changes.name = argv.name;
201
+ // Everyone: clear flag takes precedence
202
+ if (argv.clearEveryone)
203
+ changes.everyone = null;
204
+ else if (argv.everyone !== undefined)
205
+ changes.everyone = argv.everyone;
206
+ // Percent: clear flag takes precedence
207
+ if (argv.clearPercent)
208
+ changes.percent = null;
209
+ else if (argv.percent !== undefined)
210
+ changes.percent = argv.percent;
211
+ // Roles: clear flag takes precedence
212
+ if (argv.clearRoles)
213
+ changes.roles = null;
214
+ else if (argv.roles !== undefined)
215
+ changes.roles = argv.roles;
216
+ // Groups: clear flag takes precedence
217
+ if (argv.clearGroups)
218
+ changes.groups = null;
219
+ else if (argv.groups !== undefined)
220
+ changes.groups = argv.groups;
221
+ // Users: clear flag takes precedence
222
+ if (argv.clearUsers)
223
+ changes.users = null;
224
+ else if (argv.users !== undefined)
225
+ changes.users = argv.users;
226
+ // Note: clear flag takes precedence
227
+ if (argv.clearNote)
228
+ changes.note = null;
229
+ else if (argv.note !== undefined)
230
+ changes.note = argv.note;
231
+ const flag = await model.update(argv.id, changes);
232
+ if (flag) {
233
+ console.log(JSON.stringify(flag, null, 2));
234
+ }
235
+ else {
236
+ console.log(`Feature flag with ID ${argv.id} not found`);
237
+ process.exit(1);
238
+ }
239
+ }
240
+ catch (error) {
241
+ console.error("Error updating feature flag:", error);
242
+ process.exit(1);
243
+ }
244
+ finally {
245
+ await db.end();
246
+ }
247
+ });
248
+ // Delete command
249
+ cli.command("delete <id>", "Delete a feature flag", (yargs) => {
250
+ return yargs.positional("id", {
251
+ type: "number",
252
+ describe: "Feature flag ID",
253
+ });
254
+ }, async (argv) => {
255
+ const db = createDatabase();
256
+ const model = new FeatureFlagModel(db);
257
+ try {
258
+ const success = await model.delete(argv.id);
259
+ if (success) {
260
+ console.log(`Feature flag with ID ${argv.id} deleted successfully`);
261
+ }
262
+ else {
263
+ console.log(`Feature flag with ID ${argv.id} not found`);
264
+ process.exit(1);
265
+ }
266
+ }
267
+ catch (error) {
268
+ console.error("Error deleting feature flag:", error);
269
+ process.exit(1);
270
+ }
271
+ finally {
272
+ await db.end();
273
+ }
274
+ });
275
+ // Check if active for user command
276
+ cli.command("is-active <name>", "Check if a feature flag is active for a user", (yargs) => {
277
+ return yargs
278
+ .positional("name", {
279
+ type: "string",
280
+ describe: "Feature flag name",
281
+ })
282
+ .options({
283
+ user: {
284
+ type: "string",
285
+ describe: "User ID",
286
+ },
287
+ roles: {
288
+ type: "array",
289
+ describe: "User roles",
290
+ },
291
+ groups: {
292
+ type: "array",
293
+ describe: "User groups",
294
+ },
295
+ });
296
+ }, async (argv) => {
297
+ const db = createDatabase();
298
+ const model = new FeatureFlagModel(db);
299
+ try {
300
+ const isActive = await model.isActiveForUser({
301
+ name: argv.name,
302
+ user: argv.user,
303
+ roles: argv.roles,
304
+ groups: argv.groups,
305
+ });
306
+ console.log(JSON.stringify({
307
+ name: argv.name,
308
+ isActive,
309
+ user: argv.user,
310
+ roles: argv.roles,
311
+ groups: argv.groups,
312
+ }, null, 2));
313
+ }
314
+ catch (error) {
315
+ console.error("Error checking feature flag:", error);
316
+ process.exit(1);
317
+ }
318
+ finally {
319
+ await db.end();
320
+ }
321
+ });
322
+ // Hash user ID command (utility)
323
+ cli.command("hash-user <userId>", "Hash a user ID using the same algorithm as percentage rollout", (yargs) => {
324
+ return yargs.positional("userId", {
325
+ type: "string",
326
+ describe: "User ID to hash",
327
+ });
328
+ }, async (argv) => {
329
+ const db = createDatabase();
330
+ const model = new FeatureFlagModel(db);
331
+ try {
332
+ const hash = await model.hashUserId(argv.userId);
333
+ const bucket = hash % 100;
334
+ console.log(JSON.stringify({
335
+ userId: argv.userId,
336
+ hash,
337
+ bucket,
338
+ }, null, 2));
339
+ }
340
+ catch (error) {
341
+ console.error("Error hashing user ID:", error);
342
+ process.exit(1);
343
+ }
344
+ finally {
345
+ await db.end();
346
+ }
347
+ });
348
+ async function main() {
349
+ await cli.parseAsync();
350
+ }
351
+ main()
352
+ .then(() => process.exit(0))
353
+ .catch((err) => {
354
+ console.error("Error:", err);
355
+ process.exit(1);
356
+ });
@@ -0,0 +1,4 @@
1
+ export type { FeatureFlag } from "./types.js";
2
+ export { FeatureFlagModel } from "./model.js";
3
+ export { runMigrations, type MigrationOptions } from "./migrate.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { FeatureFlagModel } from "./model.js";
2
+ export { runMigrations } from "./migrate.js";
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Configuration options for running Flapjack database migrations.
3
+ */
4
+ export interface MigrationOptions {
5
+ /** PostgreSQL connection URL (e.g., "postgresql://user:pass@localhost/dbname") */
6
+ databaseUrl: string;
7
+ /** Name of the migrations tracking table (default: 'pgmigrations') */
8
+ migrationsTable?: string;
9
+ /** Schema name to create tables in (optional) */
10
+ schema?: string;
11
+ }
12
+ /**
13
+ * Run Flapjack database migrations.
14
+ *
15
+ * This function applies all pending migrations to create or update the
16
+ * flapjack_feature_flag table in your PostgreSQL database.
17
+ *
18
+ * @param options - Migration configuration options
19
+ * @param options.databaseUrl - PostgreSQL connection URL
20
+ * @param options.migrationsTable - Name of the migrations tracking table (default: 'pgmigrations')
21
+ * @param options.schema - Schema name to create tables in (optional)
22
+ * @returns Promise that resolves when migrations are complete
23
+ *
24
+ * @throws Will throw an error if the database connection fails or migrations cannot be applied
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { runMigrations } from "@brandtg/flapjack";
29
+ *
30
+ * // Basic usage
31
+ * await runMigrations({
32
+ * databaseUrl: process.env.DATABASE_URL,
33
+ * });
34
+ *
35
+ * // With custom migrations table
36
+ * await runMigrations({
37
+ * databaseUrl: process.env.DATABASE_URL,
38
+ * migrationsTable: "my_migrations",
39
+ * });
40
+ *
41
+ * // With custom schema
42
+ * await runMigrations({
43
+ * databaseUrl: process.env.DATABASE_URL,
44
+ * schema: "feature_flags",
45
+ * });
46
+ * ```
47
+ */
48
+ export declare function runMigrations(options: MigrationOptions): Promise<void>;
49
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E"}
@@ -0,0 +1,55 @@
1
+ import { runner } from "node-pg-migrate";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ /**
7
+ * Run Flapjack database migrations.
8
+ *
9
+ * This function applies all pending migrations to create or update the
10
+ * flapjack_feature_flag table in your PostgreSQL database.
11
+ *
12
+ * @param options - Migration configuration options
13
+ * @param options.databaseUrl - PostgreSQL connection URL
14
+ * @param options.migrationsTable - Name of the migrations tracking table (default: 'pgmigrations')
15
+ * @param options.schema - Schema name to create tables in (optional)
16
+ * @returns Promise that resolves when migrations are complete
17
+ *
18
+ * @throws Will throw an error if the database connection fails or migrations cannot be applied
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * import { runMigrations } from "@brandtg/flapjack";
23
+ *
24
+ * // Basic usage
25
+ * await runMigrations({
26
+ * databaseUrl: process.env.DATABASE_URL,
27
+ * });
28
+ *
29
+ * // With custom migrations table
30
+ * await runMigrations({
31
+ * databaseUrl: process.env.DATABASE_URL,
32
+ * migrationsTable: "my_migrations",
33
+ * });
34
+ *
35
+ * // With custom schema
36
+ * await runMigrations({
37
+ * databaseUrl: process.env.DATABASE_URL,
38
+ * schema: "feature_flags",
39
+ * });
40
+ * ```
41
+ */
42
+ export async function runMigrations(options) {
43
+ const { databaseUrl, migrationsTable = "pgmigrations", schema } = options;
44
+ // Path to migrations directory (relative to dist in production)
45
+ const migrationsDir = path.resolve(__dirname, "../migrations");
46
+ const migrationConfig = {
47
+ databaseUrl,
48
+ dir: migrationsDir,
49
+ direction: "up",
50
+ migrationsTable,
51
+ ...(schema && { schema }),
52
+ verbose: false,
53
+ };
54
+ await runner(migrationConfig);
55
+ }
@@ -0,0 +1,212 @@
1
+ import type { FeatureFlag } from "./types.js";
2
+ import type { QueryResult } from "pg";
3
+ interface Queryable {
4
+ query: (text: string, params?: any[]) => Promise<QueryResult>;
5
+ }
6
+ type CreateInput = Omit<FeatureFlag, "id" | "created" | "modified">;
7
+ type UpdateChanges = Partial<Omit<FeatureFlag, "id" | "created" | "modified">>;
8
+ /**
9
+ * Model for managing feature flags stored in PostgreSQL.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { Pool } from "pg";
14
+ * import { FeatureFlagModel } from "@brandtg/flapjack";
15
+ *
16
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
17
+ * const featureFlags = new FeatureFlagModel(pool);
18
+ * ```
19
+ */
20
+ export declare class FeatureFlagModel {
21
+ private db;
22
+ /**
23
+ * Creates a new FeatureFlagModel instance.
24
+ *
25
+ * @param db - A PostgreSQL Pool or Client instance that implements the Queryable interface
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
30
+ * const model = new FeatureFlagModel(pool);
31
+ * ```
32
+ */
33
+ constructor(db: Queryable);
34
+ /**
35
+ * Creates a new feature flag in the database.
36
+ *
37
+ * @param input - Feature flag configuration
38
+ * @param input.name - Unique name for the feature flag (required)
39
+ * @param input.everyone - Optional boolean to enable/disable for everyone (overrides all other settings)
40
+ * @param input.percent - Optional percentage rollout (0-99.9)
41
+ * @param input.roles - Optional list of roles that have this flag enabled
42
+ * @param input.groups - Optional list of user groups that have this flag enabled
43
+ * @param input.users - Optional list of specific user IDs that have this flag enabled
44
+ * @param input.note - Optional description of the flag's purpose
45
+ * @returns The created feature flag with generated id, created, and modified timestamps
46
+ *
47
+ * @throws Will throw an error if a flag with the same name already exists
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const flag = await model.create({
52
+ * name: "new_checkout_flow",
53
+ * roles: ["admin"],
54
+ * note: "New checkout redesign",
55
+ * });
56
+ * ```
57
+ */
58
+ create(input: CreateInput): Promise<FeatureFlag>;
59
+ /**
60
+ * Retrieves a feature flag by its ID.
61
+ *
62
+ * @param id - The unique identifier of the feature flag
63
+ * @returns The feature flag if found, null otherwise
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * const flag = await model.getById(123);
68
+ * if (flag) {
69
+ * console.log(`Flag ${flag.name} is configured`);
70
+ * }
71
+ * ```
72
+ */
73
+ getById(id: number): Promise<FeatureFlag | null>;
74
+ /**
75
+ * Retrieves a feature flag by its name.
76
+ *
77
+ * @param name - The unique name of the feature flag
78
+ * @returns The feature flag if found, null otherwise
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const flag = await model.getByName("new_checkout_flow");
83
+ * if (flag) {
84
+ * console.log(`Flag is ${flag.everyone ? 'enabled' : 'disabled'} for everyone`);
85
+ * }
86
+ * ```
87
+ */
88
+ getByName(name: string): Promise<FeatureFlag | null>;
89
+ /**
90
+ * Retrieves all feature flags, ordered by ID.
91
+ *
92
+ * @returns Array of all feature flags in the database
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const flags = await model.list();
97
+ * console.log(`Total flags: ${flags.length}`);
98
+ * flags.forEach(flag => {
99
+ * console.log(`${flag.name}: ${flag.everyone ?? 'conditional'}`);
100
+ * });
101
+ * ```
102
+ */
103
+ list(): Promise<FeatureFlag[]>;
104
+ /**
105
+ * Updates an existing feature flag.
106
+ *
107
+ * @param id - The unique identifier of the feature flag to update
108
+ * @param changes - Object containing the fields to update
109
+ * @returns The updated feature flag if found, null otherwise
110
+ *
111
+ * @remarks
112
+ * The modified timestamp is automatically updated by a database trigger.
113
+ * Pass `null` for a field to clear it (e.g., `everyone: null` removes the override).
114
+ * If no changes are provided, returns the flag unchanged.
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * // Gradually increase rollout percentage
119
+ * await model.update(flagId, { percent: 25 });
120
+ *
121
+ * // Enable for everyone
122
+ * await model.update(flagId, { everyone: true });
123
+ *
124
+ * // Remove everyone override, reverting to other rules
125
+ * await model.update(flagId, { everyone: null });
126
+ * ```
127
+ */
128
+ update(id: number, changes: UpdateChanges): Promise<FeatureFlag | null>;
129
+ /**
130
+ * Deletes a feature flag from the database.
131
+ *
132
+ * @param id - The unique identifier of the feature flag to delete
133
+ * @returns true if the flag was deleted, false if not found
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const deleted = await model.delete(flagId);
138
+ * if (deleted) {
139
+ * console.log("Flag successfully removed");
140
+ * }
141
+ * ```
142
+ */
143
+ delete(id: number): Promise<boolean>;
144
+ /**
145
+ * Checks if a user belongs to any of the specified groups
146
+ */
147
+ private isActiveForGroups;
148
+ /**
149
+ * Computes the hash value for a user ID using MurmurHash3.
150
+ *
151
+ * @param userId - The user ID to hash
152
+ * @returns The hash value used for percentage bucketing
153
+ *
154
+ * @remarks
155
+ * This method is useful for debugging percentage rollouts.
156
+ * The hash value is consistent for the same user ID.
157
+ * The bucket is computed as `hash % 100`.
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const hash = await model.hashUserId("user_123");
162
+ * const bucket = hash % 100;
163
+ * console.log(`User bucket: ${bucket}`);
164
+ * // If bucket is 42 and percent is 50, user is in the rollout
165
+ * ```
166
+ */
167
+ hashUserId(userId: string): Promise<number>;
168
+ /**
169
+ * Checks if a feature flag is active for a user based on configured rules.
170
+ *
171
+ * @param params - Parameters for flag evaluation
172
+ * @param params.name - The name of the feature flag to check
173
+ * @param params.user - Optional user ID
174
+ * @param params.roles - Optional list of roles the user has
175
+ * @param params.groups - Optional list of groups the user belongs to
176
+ * @returns true if the flag is active for the user, false otherwise
177
+ *
178
+ * @remarks
179
+ * Evaluation order (first match wins):
180
+ * 1. Everyone override (if set to true/false, returns immediately)
181
+ * 2. User ID is in the users list
182
+ * 3. User belongs to any group in the groups list
183
+ * 4. User has any role in the roles list
184
+ * 5. User falls within the percentage rollout (based on consistent hashing)
185
+ * 6. Default: returns false
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * // Check for admin user
190
+ * const isActive = await model.isActiveForUser({
191
+ * name: "new_feature",
192
+ * user: "user_123",
193
+ * roles: ["admin"],
194
+ * groups: ["beta_testers"],
195
+ * });
196
+ *
197
+ * if (isActive) {
198
+ * // Show new feature
199
+ * } else {
200
+ * // Show old feature
201
+ * }
202
+ * ```
203
+ */
204
+ isActiveForUser({ name, user, roles, groups, }: {
205
+ name: string;
206
+ user?: string;
207
+ roles?: string[];
208
+ groups?: string[];
209
+ }): Promise<boolean>;
210
+ }
211
+ export {};
212
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;AAItC,UAAU,SAAS;IACjB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;CAC/D;AAiBD,KAAK,WAAW,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,SAAS,GAAG,UAAU,CAAC,CAAC;AACpE,KAAK,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC;AAyB/E;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,EAAE,CAAY;IAEtB;;;;;;;;;;OAUG;gBACS,EAAE,EAAE,SAAS;IAIzB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAmCtD;;;;;;;;;;;;;OAaG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAOtD;;;;;;;;;;;;;OAaG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAO1D;;;;;;;;;;;;;OAaG;IACG,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAMpC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,MAAM,CACV,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IA6C9B;;;;;;;;;;;;;OAaG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK1C;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;;;;;;;;;;;;;;;;;OAkBG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACG,eAAe,CAAC,EACpB,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,MAAM,GACP,EAAE;QACD,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,GAAG,OAAO,CAAC,OAAO,CAAC;CA4CrB"}