@ultimat3/flags 29.4.0 → 30.0.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/CLAUDE.md CHANGED
@@ -80,7 +80,7 @@ what lets `policy` (tier 2) call it from inside a predicate.
80
80
  an inherited function, and a non-string id never reaches `bucketOf`. Same rule for the targeting
81
81
  map, which is why the loop is `for…in` + `Object.hasOwn` and not `Object.entries`.
82
82
  - **Every app-supplied string in a `fix:` goes through `JSON.stringify`, and a subject kind becomes
83
- a computed key.** A `bank-integration` kind is not a valid identifier, so `{ bank-integration: … }`
83
+ a computed key.** A `payments-provider` kind is not a valid identifier, so `{ payments-provider: … }`
84
84
  would be a fix that does not parse — axiom 4 wants an instruction that runs. Pinned by
85
85
  `errors.test.ts` running the generated snippet through `new Function`.
86
86
  - **An unknown key throws.** Answering `false` is a branch that never runs and never says so.
package/README.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  Feature flags: permanent switches, and temporary ones that cannot be forgotten.
4
4
 
5
+ `bun add @ultimat3/flags` · guide: [Feature Flags](https://github.com/developerz-ai/ultimate/wiki/Feature-Flags)
6
+
7
+ ```ts
8
+ import { defineFlag } from '@ultimat3/flags';
9
+
10
+ export const dunning = defineFlag({
11
+ kind: 'permanent',
12
+ key: 'billing.dunning-emails',
13
+ description: 'ops kill switch for dunning email delivery',
14
+ targeting: { default: true },
15
+ });
16
+ ```
17
+
5
18
  Feature flags are normally a bad trade — N flags are 2^N states nothing tested. This package takes
6
19
  the trade only on terms that bound the exponent: every flag declares which of two kinds it is, and
7
20
  a `temporary` flag carries an expiry it cannot be declared without. Past that date, every
package/package.json CHANGED
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "name": "@ultimat3/flags",
3
- "version": "29.4.0",
3
+ "version": "30.0.0",
4
4
  "description": "Feature flags: permanent switches, and temporary ones that cannot be forgotten",
5
+ "keywords": [
6
+ "ultimate",
7
+ "bun",
8
+ "framework",
9
+ "feature-flags",
10
+ "flags",
11
+ "toggles"
12
+ ],
13
+ "homepage": "https://github.com/developerz-ai/ultimate/wiki/Feature-Flags",
5
14
  "license": "MIT",
6
15
  "type": "module",
7
16
  "repository": {
@@ -32,6 +41,6 @@
32
41
  "test": "bun test"
33
42
  },
34
43
  "dependencies": {
35
- "@ultimat3/core": "29.4.0"
44
+ "@ultimat3/core": "30.0.0"
36
45
  }
37
46
  }
package/src/errors.ts CHANGED
@@ -94,8 +94,8 @@ export const flagSubjectRequired = (init: {
94
94
  code: 'X_FLAG_SUBJECT_REQUIRED',
95
95
  cause: `${init.key} decides by the "${init.kind}" subject (targeting.${init.via}) but the evaluation context carries no ${init.kind} id for actor "${init.actorId}", so there is nothing to decide about`,
96
96
  // Every app-supplied string goes through JSON.stringify, and the kind becomes a COMPUTED key:
97
- // a `bank-integration` kind — the realistic shape, next to treasury's `bank_integration:` ids
98
- // — is not a valid identifier, so `{ bank-integration: … }` would hand the reader a fix that
97
+ // a `payments-provider` kind — the realistic shape, next to one adopter's `<kind>:<name>` ids
98
+ // — is not a valid identifier, so `{ payments-provider: … }` would hand the reader a fix that
99
99
  // does not parse. Axiom 4: an instruction that cannot be run is not one.
100
100
  fix:
101
101
  init.kind === 'org'
package/src/evaluate.ts CHANGED
@@ -6,7 +6,7 @@ import type { Actor } from '@ultimat3/core';
6
6
  import { flagExpired } from './errors';
7
7
  import type { Flag } from './flag';
8
8
  import { flagFor } from './registry';
9
- import { flagsClock, reportOnce } from './runtime';
9
+ import { flagsClock, recordEvaluation, reportOnce } from './runtime';
10
10
  import type { FlagSubjects } from './subject';
11
11
  import { evaluateTargeting } from './targeting';
12
12
 
@@ -51,5 +51,5 @@ export function isEnabled(
51
51
  ): boolean {
52
52
  const flag = flagFor(key);
53
53
  reportIfOverdue(flag);
54
- return evaluateTargeting(flag.key, flag.targeting, actor, subjects);
54
+ return recordEvaluation(flag.key, evaluateTargeting(flag.key, flag.targeting, actor, subjects));
55
55
  }
package/src/runtime.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  // caught failure, while a flag is evaluated on every request.
9
9
 
10
10
  import type { Clock, UltimateError } from '@ultimat3/core';
11
- import { finiteCount, reportError, systemClock } from '@ultimat3/core';
11
+ import { finiteCount, recordFailureFact, reportError, systemClock } from '@ultimat3/core';
12
12
 
13
13
  /**
14
14
  * One hour. Small enough that an overdue flag shows up the same day, large enough that a flag read
@@ -81,6 +81,15 @@ export function reportOnce(key: string, build: () => UltimateError): boolean {
81
81
  return true;
82
82
  }
83
83
 
84
+ /**
85
+ * What an evaluation answered, into the failure buffer when a captured call is in flight — so
86
+ * `x repro` replays the branch the failing request took. Off, one `undefined` check in core.
87
+ */
88
+ export function recordEvaluation(key: string, on: boolean): boolean {
89
+ recordFailureFact({ kind: 'flag', key, value: on });
90
+ return on;
91
+ }
92
+
84
93
  /** Test-only, and the counterpart to every other reset in the framework. */
85
94
  export function resetFlagReporting(): void {
86
95
  clock = systemClock;