@zudojs/feature-flags 1.3.0 → 1.4.1
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/README.md +39 -8
- package/dist/evaluator/evaluator.core.d.ts +4 -0
- package/dist/evaluator/evaluator.core.js +9 -53
- package/dist/evaluator/evaluatorGate.core.d.ts +35 -0
- package/dist/evaluator/evaluatorGate.core.js +68 -0
- package/dist/featureFlagTypes/featureFlag.interface.d.ts +10 -1
- package/dist/provider/providerEnvironment.core.d.ts +23 -1
- package/dist/provider/providerEnvironment.core.js +17 -3
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -57,8 +57,9 @@ choose.
|
|
|
57
57
|
interface FeatureFlag {
|
|
58
58
|
key: string;
|
|
59
59
|
enabled: boolean; // the global kill switch
|
|
60
|
-
defaultValue: FeatureFlagValue; // used
|
|
61
|
-
|
|
60
|
+
defaultValue: FeatureFlagValue; // used when the flag is on and no rule decides
|
|
61
|
+
offValue?: FeatureFlagValue; // served while the flag is off (see below)
|
|
62
|
+
state?: "active" | "disabled" | "archived" | "draft";
|
|
62
63
|
visibility?: "client" | "server";
|
|
63
64
|
rules?: FeatureFlagRule[]; // evaluated in order, first match wins
|
|
64
65
|
dependencies?: string[]; // other flags that must be on for the same context
|
|
@@ -69,6 +70,27 @@ interface FeatureFlag {
|
|
|
69
70
|
`expiresAt` may also arrive as an ISO string or a timestamp — a flag loaded
|
|
70
71
|
from JSON does — and expires the flag just the same.
|
|
71
72
|
|
|
73
|
+
### Off means off
|
|
74
|
+
|
|
75
|
+
A flag is **off** when it is killed (`enabled: false` or `state:
|
|
76
|
+
"disabled"`), a draft, archived, expired, or blocked by a dependency. An off
|
|
77
|
+
flag runs no rules and serves its **off value**:
|
|
78
|
+
|
|
79
|
+
1. `offValue`, when the flag declares one;
|
|
80
|
+
2. otherwise `false` for a boolean flag;
|
|
81
|
+
3. otherwise `defaultValue` (a string, number or object flag has no natural
|
|
82
|
+
"off", so the baseline value is served).
|
|
83
|
+
|
|
84
|
+
So `{ key: "x", enabled: false, defaultValue: true }` evaluates to `false`
|
|
85
|
+
and `isEnabled("x")` is `false`. Before 1.4 an off flag served
|
|
86
|
+
`defaultValue`, which made the kill switch fail open for any flag whose
|
|
87
|
+
default was `true`. For a variant flag, declare the variant to fall back to:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
{ key: "checkout", enabled: false, defaultValue: "new", offValue: "control",
|
|
91
|
+
rules: [{ type: "variant", variants: [{ key: "new", weight: 50 }, { key: "control", weight: 50 }] }] }
|
|
92
|
+
```
|
|
93
|
+
|
|
72
94
|
## Rules
|
|
73
95
|
|
|
74
96
|
| Type | Matches when |
|
|
@@ -131,8 +153,8 @@ dependency is **on for the same context**: the prerequisite is evaluated —
|
|
|
131
153
|
state, expiry, rules, rollout, and its own dependencies — and must not be
|
|
132
154
|
disabled, draft, archived or expired, nor evaluate to `false`, `null` or
|
|
133
155
|
`undefined`. A prerequisite rolled out to 10% keeps its dependents off for the
|
|
134
|
-
other 90%. Otherwise the result is `dependency_disabled` with the
|
|
135
|
-
|
|
156
|
+
other 90%. Otherwise the result is `dependency_disabled` with the flag's
|
|
157
|
+
off value. Cycles resolve to disabled; a shared dependency reached down two
|
|
136
158
|
branches is not a cycle.
|
|
137
159
|
|
|
138
160
|
`evaluateFlag()` on its own has no registry and cannot resolve dependencies,
|
|
@@ -177,7 +199,16 @@ calls per window instead of two per evaluation. The first successful call
|
|
|
177
199
|
closes the window, and `refresh()` always probes.
|
|
178
200
|
|
|
179
201
|
`createEnvironmentProvider` parses `true`/`false` and numbers; anything else,
|
|
180
|
-
including an empty `FEATURE_X=`, stays a string.
|
|
202
|
+
including an empty `FEATURE_X=`, stays a string. Every flag it reads is
|
|
203
|
+
enabled.
|
|
204
|
+
|
|
205
|
+
Keys are the variable name without the prefix, **lower-cased with `_`
|
|
206
|
+
turned into `-`**: `FEATURE_NEW_CHECKOUT=true` is the flag `new-checkout`,
|
|
207
|
+
the same key a memory or remote provider uses, so an environment variable
|
|
208
|
+
overrides that flag inside a composite. `get()` normalises the key it is
|
|
209
|
+
asked for the same way, so `NEW_CHECKOUT` and `new_checkout` also find it.
|
|
210
|
+
Pass `keyFormat: "preserve"` to keep the variable's spelling (`NEW_CHECKOUT`),
|
|
211
|
+
which was the behaviour before 1.4.
|
|
181
212
|
|
|
182
213
|
## Change propagation
|
|
183
214
|
|
|
@@ -193,9 +224,9 @@ changes.
|
|
|
193
224
|
| -------------------------------- | ------------------------------------------------------ |
|
|
194
225
|
| Flag not found | `not_found`, value `undefined`; `isEnabled` is `false` |
|
|
195
226
|
| Flag not found, `throwOnMissing` | throws `FeatureFlagNotFoundError` |
|
|
196
|
-
| Flag disabled or draft | `disabled`, the
|
|
197
|
-
| Flag archived or expired | `expired`, the
|
|
198
|
-
| Dependency not satisfied | `dependency_disabled`, the
|
|
227
|
+
| Flag disabled or draft | `disabled`, the off value; `isEnabled` is `false` |
|
|
228
|
+
| Flag archived or expired | `expired`, the off value; `isEnabled` is `false` |
|
|
229
|
+
| Dependency not satisfied | `dependency_disabled`, the off value |
|
|
199
230
|
| Provider unreachable | `error`, reported to `onError`; never enabled |
|
|
200
231
|
|
|
201
232
|
"Unreachable" covers both `getAll()` and a `get()` for a flag not yet loaded:
|
|
@@ -25,6 +25,10 @@ export interface EvaluateFlagOptions {
|
|
|
25
25
|
/**
|
|
26
26
|
* Evaluate a feature flag against a context.
|
|
27
27
|
*
|
|
28
|
+
* A flag that is off — killed, draft, archived, expired, or blocked by a
|
|
29
|
+
* dependency — serves its off value (see {@link offValueOf}): `offValue`
|
|
30
|
+
* when declared, else `false` for a boolean flag, else `defaultValue`.
|
|
31
|
+
*
|
|
28
32
|
* @param flag - The feature flag definition.
|
|
29
33
|
* @param context - The evaluation context.
|
|
30
34
|
* @param options - Facts the caller resolved that this function cannot.
|
|
@@ -6,25 +6,7 @@
|
|
|
6
6
|
* @module evaluator/evaluator
|
|
7
7
|
*/
|
|
8
8
|
import { evaluateRule } from "./evaluatorRule.core.js";
|
|
9
|
-
|
|
10
|
-
* Whether an `expiresAt` lies in the past.
|
|
11
|
-
*
|
|
12
|
-
* The type says `Date`, but a flag loaded from JSON — which is what every
|
|
13
|
-
* remote provider hands over — carries an ISO string, and `"2020-01-01" <
|
|
14
|
-
* new Date()` is always `false`. A flag that had expired at the source
|
|
15
|
-
* therefore never expired here. Anything `Date` can parse is honoured; a
|
|
16
|
-
* value it cannot parse is treated as no expiry.
|
|
17
|
-
*/
|
|
18
|
-
function isExpired(expiresAt) {
|
|
19
|
-
if (expiresAt === undefined || expiresAt === null)
|
|
20
|
-
return false;
|
|
21
|
-
const time = expiresAt instanceof Date
|
|
22
|
-
? expiresAt.getTime()
|
|
23
|
-
: typeof expiresAt === "string" || typeof expiresAt === "number"
|
|
24
|
-
? new Date(expiresAt).getTime()
|
|
25
|
-
: Number.NaN;
|
|
26
|
-
return !Number.isNaN(time) && time < Date.now();
|
|
27
|
-
}
|
|
9
|
+
import { gateReason, offValueOf } from "./evaluatorGate.core.js";
|
|
28
10
|
/** The evaluation reason a matching rule of each type produces. */
|
|
29
11
|
function reasonFor(type) {
|
|
30
12
|
switch (type) {
|
|
@@ -46,48 +28,22 @@ function reasonFor(type) {
|
|
|
46
28
|
/**
|
|
47
29
|
* Evaluate a feature flag against a context.
|
|
48
30
|
*
|
|
31
|
+
* A flag that is off — killed, draft, archived, expired, or blocked by a
|
|
32
|
+
* dependency — serves its off value (see {@link offValueOf}): `offValue`
|
|
33
|
+
* when declared, else `false` for a boolean flag, else `defaultValue`.
|
|
34
|
+
*
|
|
49
35
|
* @param flag - The feature flag definition.
|
|
50
36
|
* @param context - The evaluation context.
|
|
51
37
|
* @param options - Facts the caller resolved that this function cannot.
|
|
52
38
|
* @returns A structured evaluation result.
|
|
53
39
|
*/
|
|
54
40
|
export function evaluateFlag(flag, context = {}, options = {}) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
key: flag.key,
|
|
58
|
-
value: flag.defaultValue,
|
|
59
|
-
reason: "disabled",
|
|
60
|
-
defaulted: true,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
if (flag.state === "archived" || flag.state === "draft") {
|
|
64
|
-
return {
|
|
65
|
-
key: flag.key,
|
|
66
|
-
value: flag.defaultValue,
|
|
67
|
-
reason: flag.state === "archived" ? "expired" : "disabled",
|
|
68
|
-
defaulted: true,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
if (isExpired(flag.metadata?.expiresAt)) {
|
|
72
|
-
return {
|
|
73
|
-
key: flag.key,
|
|
74
|
-
value: flag.defaultValue,
|
|
75
|
-
reason: "expired",
|
|
76
|
-
defaulted: true,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
if (flag.dependencies &&
|
|
80
|
-
flag.dependencies.length > 0 &&
|
|
81
|
-
options.dependenciesSatisfied !== true) {
|
|
82
|
-
// Previously this returned `dependency_disabled` for *every* flag that
|
|
83
|
-
// declared a dependency, satisfied or not — so a flag with dependencies
|
|
84
|
-
// could never turn on, and the caller's own dependency resolution was
|
|
85
|
-
// computed and then discarded.
|
|
41
|
+
const gate = gateReason(flag, options);
|
|
42
|
+
if (gate) {
|
|
86
43
|
return {
|
|
87
44
|
key: flag.key,
|
|
88
|
-
value: flag
|
|
89
|
-
reason:
|
|
90
|
-
matchedRule: undefined,
|
|
45
|
+
value: offValueOf(flag),
|
|
46
|
+
reason: gate,
|
|
91
47
|
defaulted: true,
|
|
92
48
|
};
|
|
93
49
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gates that turn a flag off before any rule runs, and the value it serves
|
|
3
|
+
* while it is off.
|
|
4
|
+
*
|
|
5
|
+
* @module evaluator/evaluatorGate.core
|
|
6
|
+
*/
|
|
7
|
+
import type { FeatureFlag } from "../featureFlagTypes/featureFlag.interface.js";
|
|
8
|
+
import type { FeatureFlagValue } from "../featureFlagTypes/featureFlagRule/featureFlagValue.type.js";
|
|
9
|
+
import type { FeatureFlagEvaluationReason } from "../featureFlagTypes/featureFlagEvaluation.js";
|
|
10
|
+
import type { EvaluateFlagOptions } from "./evaluator.core.js";
|
|
11
|
+
/**
|
|
12
|
+
* Why a flag is off before any rule runs, or `undefined` when it is live.
|
|
13
|
+
*
|
|
14
|
+
* Checked in order: the kill switch (`enabled: false`, `state: "disabled"`),
|
|
15
|
+
* a draft, an archived or expired flag, then unsatisfied dependencies.
|
|
16
|
+
*/
|
|
17
|
+
export declare function gateReason(flag: FeatureFlag, options: EvaluateFlagOptions): FeatureFlagEvaluationReason | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve what a flag serves when it is off — killed (`enabled: false` or
|
|
20
|
+
* `state: "disabled"`), a draft, archived, expired, or blocked by a
|
|
21
|
+
* dependency.
|
|
22
|
+
*
|
|
23
|
+
* 1. `flag.offValue`, when the flag declares one.
|
|
24
|
+
* 2. `false`, for a boolean flag. The kill switch must turn a feature off,
|
|
25
|
+
* and serving `defaultValue: true` from a killed flag left it on.
|
|
26
|
+
* 3. `flag.defaultValue` otherwise. A string, number or object flag has no
|
|
27
|
+
* natural "off", so the declared default — the baseline experience — is
|
|
28
|
+
* the fallback. `isEnabled()` is `false` for these regardless, because it
|
|
29
|
+
* is `true` only for the boolean `true`.
|
|
30
|
+
*
|
|
31
|
+
* @param flag - The flag that is off.
|
|
32
|
+
* @returns The value to serve.
|
|
33
|
+
*/
|
|
34
|
+
export declare function offValueOf(flag: FeatureFlag): FeatureFlagValue;
|
|
35
|
+
//# sourceMappingURL=evaluatorGate.core.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gates that turn a flag off before any rule runs, and the value it serves
|
|
3
|
+
* while it is off.
|
|
4
|
+
*
|
|
5
|
+
* @module evaluator/evaluatorGate.core
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Whether an `expiresAt` lies in the past.
|
|
9
|
+
*
|
|
10
|
+
* The type says `Date`, but a flag loaded from JSON — which is what every
|
|
11
|
+
* remote provider hands over — carries an ISO string, and `"2020-01-01" <
|
|
12
|
+
* new Date()` is always `false`. A flag that had expired at the source
|
|
13
|
+
* therefore never expired here. Anything `Date` can parse is honoured; a
|
|
14
|
+
* value it cannot parse is treated as no expiry.
|
|
15
|
+
*/
|
|
16
|
+
function isExpired(expiresAt) {
|
|
17
|
+
if (expiresAt === undefined || expiresAt === null)
|
|
18
|
+
return false;
|
|
19
|
+
const time = expiresAt instanceof Date
|
|
20
|
+
? expiresAt.getTime()
|
|
21
|
+
: typeof expiresAt === "string" || typeof expiresAt === "number"
|
|
22
|
+
? new Date(expiresAt).getTime()
|
|
23
|
+
: Number.NaN;
|
|
24
|
+
return !Number.isNaN(time) && time < Date.now();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Why a flag is off before any rule runs, or `undefined` when it is live.
|
|
28
|
+
*
|
|
29
|
+
* Checked in order: the kill switch (`enabled: false`, `state: "disabled"`),
|
|
30
|
+
* a draft, an archived or expired flag, then unsatisfied dependencies.
|
|
31
|
+
*/
|
|
32
|
+
export function gateReason(flag, options) {
|
|
33
|
+
if (!flag.enabled || flag.state === "disabled" || flag.state === "draft") {
|
|
34
|
+
return "disabled";
|
|
35
|
+
}
|
|
36
|
+
if (flag.state === "archived" || isExpired(flag.metadata?.expiresAt)) {
|
|
37
|
+
return "expired";
|
|
38
|
+
}
|
|
39
|
+
const hasDependencies = !!flag.dependencies && flag.dependencies.length > 0;
|
|
40
|
+
if (hasDependencies && options.dependenciesSatisfied !== true) {
|
|
41
|
+
return "dependency_disabled";
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve what a flag serves when it is off — killed (`enabled: false` or
|
|
47
|
+
* `state: "disabled"`), a draft, archived, expired, or blocked by a
|
|
48
|
+
* dependency.
|
|
49
|
+
*
|
|
50
|
+
* 1. `flag.offValue`, when the flag declares one.
|
|
51
|
+
* 2. `false`, for a boolean flag. The kill switch must turn a feature off,
|
|
52
|
+
* and serving `defaultValue: true` from a killed flag left it on.
|
|
53
|
+
* 3. `flag.defaultValue` otherwise. A string, number or object flag has no
|
|
54
|
+
* natural "off", so the declared default — the baseline experience — is
|
|
55
|
+
* the fallback. `isEnabled()` is `false` for these regardless, because it
|
|
56
|
+
* is `true` only for the boolean `true`.
|
|
57
|
+
*
|
|
58
|
+
* @param flag - The flag that is off.
|
|
59
|
+
* @returns The value to serve.
|
|
60
|
+
*/
|
|
61
|
+
export function offValueOf(flag) {
|
|
62
|
+
if (flag.offValue !== undefined)
|
|
63
|
+
return flag.offValue;
|
|
64
|
+
if (typeof flag.defaultValue === "boolean")
|
|
65
|
+
return false;
|
|
66
|
+
return flag.defaultValue;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=evaluatorGate.core.js.map
|
|
@@ -30,8 +30,17 @@ export interface FeatureFlag {
|
|
|
30
30
|
readonly key: string;
|
|
31
31
|
/** Default value when no rule matches. */
|
|
32
32
|
readonly defaultValue: FeatureFlagValue;
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Whether the flag is globally enabled — the kill switch. A flag with
|
|
35
|
+
* `enabled: false` serves its off value, never `defaultValue: true`.
|
|
36
|
+
*/
|
|
34
37
|
readonly enabled: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* What the flag serves while it is off: killed, draft, archived, expired,
|
|
40
|
+
* or blocked by a dependency. Default: `false` for a boolean flag, the
|
|
41
|
+
* `defaultValue` for any other.
|
|
42
|
+
*/
|
|
43
|
+
readonly offValue?: FeatureFlagValue;
|
|
35
44
|
/** Human-readable description. */
|
|
36
45
|
readonly description?: string;
|
|
37
46
|
/** Lifecycle state. */
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Reads feature flags from environment variables with a configurable prefix.
|
|
5
5
|
*
|
|
6
|
-
* Example: FEATURE_NEW_UI=true → key "
|
|
6
|
+
* Example: FEATURE_NEW_UI=true → key "new-ui" with value true.
|
|
7
7
|
*
|
|
8
8
|
* @module provider/providerEnvironment
|
|
9
9
|
*/
|
|
@@ -14,7 +14,29 @@ export interface EnvironmentProviderOptions {
|
|
|
14
14
|
readonly prefix?: string;
|
|
15
15
|
/** Process env object to read from (default: process.env). */
|
|
16
16
|
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
17
|
+
/**
|
|
18
|
+
* How a variable name becomes a flag key, once the prefix is removed.
|
|
19
|
+
*
|
|
20
|
+
* - `"kebab"` (default): lower-cased, `_` becomes `-`, so
|
|
21
|
+
* `FEATURE_NEW_CHECKOUT` is the flag `new-checkout` — the same key a
|
|
22
|
+
* memory or remote provider would use.
|
|
23
|
+
* - `"preserve"`: the name as written (`NEW_CHECKOUT`), the behaviour
|
|
24
|
+
* before 1.4.
|
|
25
|
+
*
|
|
26
|
+
* With `"kebab"`, `get()` normalises the key it is asked for the same way,
|
|
27
|
+
* so `get("NEW_CHECKOUT")`, `get("new_checkout")` and
|
|
28
|
+
* `get("new-checkout")` all find the flag.
|
|
29
|
+
*/
|
|
30
|
+
readonly keyFormat?: "kebab" | "preserve";
|
|
17
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Normalise an environment-derived key to kebab case: `NEW_CHECKOUT` and
|
|
34
|
+
* `new_checkout` both become `new-checkout`.
|
|
35
|
+
*
|
|
36
|
+
* @param key - The key, with the prefix already removed.
|
|
37
|
+
* @returns The lower-cased key with underscores replaced by hyphens.
|
|
38
|
+
*/
|
|
39
|
+
export declare function toEnvironmentFlagKey(key: string): string;
|
|
18
40
|
/**
|
|
19
41
|
* Create an environment-variable feature flag provider.
|
|
20
42
|
*
|
|
@@ -3,10 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Reads feature flags from environment variables with a configurable prefix.
|
|
5
5
|
*
|
|
6
|
-
* Example: FEATURE_NEW_UI=true → key "
|
|
6
|
+
* Example: FEATURE_NEW_UI=true → key "new-ui" with value true.
|
|
7
7
|
*
|
|
8
8
|
* @module provider/providerEnvironment
|
|
9
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* Normalise an environment-derived key to kebab case: `NEW_CHECKOUT` and
|
|
12
|
+
* `new_checkout` both become `new-checkout`.
|
|
13
|
+
*
|
|
14
|
+
* @param key - The key, with the prefix already removed.
|
|
15
|
+
* @returns The lower-cased key with underscores replaced by hyphens.
|
|
16
|
+
*/
|
|
17
|
+
export function toEnvironmentFlagKey(key) {
|
|
18
|
+
return key.toLowerCase().replace(/_/g, "-");
|
|
19
|
+
}
|
|
10
20
|
/**
|
|
11
21
|
* Parse a string value into a feature flag value.
|
|
12
22
|
*/
|
|
@@ -33,12 +43,15 @@ function parseEnvValue(raw) {
|
|
|
33
43
|
*/
|
|
34
44
|
export function createEnvironmentProvider(options = {}) {
|
|
35
45
|
const prefix = options.prefix ?? "FEATURE_";
|
|
46
|
+
const normalise = options.keyFormat === "preserve"
|
|
47
|
+
? (key) => key
|
|
48
|
+
: toEnvironmentFlagKey;
|
|
36
49
|
const env = options.env ?? (typeof process !== "undefined" ? process.env : {});
|
|
37
50
|
function readFlags() {
|
|
38
51
|
const flags = [];
|
|
39
52
|
for (const [key, value] of Object.entries(env)) {
|
|
40
53
|
if (key.startsWith(prefix) && value !== undefined) {
|
|
41
|
-
const flagKey = key.slice(prefix.length);
|
|
54
|
+
const flagKey = normalise(key.slice(prefix.length));
|
|
42
55
|
flags.push({
|
|
43
56
|
key: flagKey,
|
|
44
57
|
enabled: true,
|
|
@@ -53,7 +66,8 @@ export function createEnvironmentProvider(options = {}) {
|
|
|
53
66
|
async get(key) {
|
|
54
67
|
if (!cached)
|
|
55
68
|
cached = readFlags();
|
|
56
|
-
|
|
69
|
+
const wanted = normalise(key);
|
|
70
|
+
return cached.find((f) => f.key === wanted);
|
|
57
71
|
},
|
|
58
72
|
async getAll() {
|
|
59
73
|
if (!cached)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/feature-flags",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Feature flag system with deterministic rollouts, rule engine, providers, variants, snapshots, and evaluation context.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@zudojs/errors": "1.
|
|
15
|
-
"@zudojs/types": "1.
|
|
14
|
+
"@zudojs/errors": "1.3.1",
|
|
15
|
+
"@zudojs/types": "1.2.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"typescript": "7.0.2",
|
|
19
|
-
"vitest": "^
|
|
19
|
+
"vitest": "^5.0.1"
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"rollout",
|
|
44
44
|
"a-b-testing"
|
|
45
45
|
],
|
|
46
|
-
"homepage": "https://
|
|
46
|
+
"homepage": "https://zudojs.oyinlola.site/docs/packages-feature-flags",
|
|
47
47
|
"bugs": {
|
|
48
48
|
"url": "https://github.com/oyinlola-tech/zudo/issues"
|
|
49
49
|
},
|