@stravigor/banner 0.4.4 → 0.4.6
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
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @stravigor/banner
|
|
2
|
+
|
|
3
|
+
Feature flags for the [Strav](https://www.npmjs.com/package/@stravigor/core) framework. Define, scope, and toggle features with database persistence, in-memory drivers, and per-user/team targeting.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @stravigor/banner
|
|
9
|
+
bun strav package:install banner
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Requires `@stravigor/core` as a peer dependency.
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { BannerProvider } from '@stravigor/banner'
|
|
18
|
+
|
|
19
|
+
app.use(new BannerProvider())
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { banner } from '@stravigor/banner'
|
|
26
|
+
|
|
27
|
+
// Check if a feature is active
|
|
28
|
+
if (await banner.active('dark-mode')) {
|
|
29
|
+
// feature is enabled
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Scoped to a user or team
|
|
33
|
+
if (await banner.for(user).active('beta-dashboard')) {
|
|
34
|
+
// enabled for this user
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Rich values
|
|
38
|
+
const limit = await banner.value('upload-limit', 10)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Middleware
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { ensureFeature } from '@stravigor/banner'
|
|
45
|
+
|
|
46
|
+
router.get('/beta', ensureFeature('beta-dashboard'), betaHandler)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Drivers
|
|
50
|
+
|
|
51
|
+
- **Database** — persistent feature flags in `_strav_features`
|
|
52
|
+
- **Array** — in-memory driver for testing
|
|
53
|
+
|
|
54
|
+
## CLI
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bun strav banner:setup # Create the features table
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Documentation
|
|
61
|
+
|
|
62
|
+
See the full [Banner guide](../../guides/banner.md).
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stravigor/banner",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Feature flags for the Strav framework",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"tsconfig.json"
|
|
19
19
|
],
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"@stravigor/core": "0.4.
|
|
21
|
+
"@stravigor/core": "0.4.5"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"test": "bun test tests/",
|
package/src/banner_manager.ts
CHANGED
|
@@ -65,10 +65,7 @@ export default class BannerManager {
|
|
|
65
65
|
|
|
66
66
|
/** Get all defined feature names (closures + classes). */
|
|
67
67
|
static defined(): string[] {
|
|
68
|
-
return [
|
|
69
|
-
...BannerManager._definitions.keys(),
|
|
70
|
-
...BannerManager._classFeatures.keys(),
|
|
71
|
-
]
|
|
68
|
+
return [...BannerManager._definitions.keys(), ...BannerManager._classFeatures.keys()]
|
|
72
69
|
}
|
|
73
70
|
|
|
74
71
|
// ── Scope helpers ──────────────────────────────────────────────────
|
|
@@ -76,9 +73,7 @@ export default class BannerManager {
|
|
|
76
73
|
static serializeScope(scope: Scopeable | null | undefined): ScopeKey {
|
|
77
74
|
if (!scope) return GLOBAL_SCOPE
|
|
78
75
|
const type =
|
|
79
|
-
typeof scope.featureScope === 'function'
|
|
80
|
-
? scope.featureScope()
|
|
81
|
-
: scope.constructor.name
|
|
76
|
+
typeof scope.featureScope === 'function' ? scope.featureScope() : scope.constructor.name
|
|
82
77
|
return `${type}:${scope.id}`
|
|
83
78
|
}
|
|
84
79
|
|
|
@@ -118,7 +113,7 @@ export default class BannerManager {
|
|
|
118
113
|
}
|
|
119
114
|
|
|
120
115
|
static async inactive(feature: string, scope?: Scopeable | null): Promise<boolean> {
|
|
121
|
-
return !await BannerManager.active(feature, scope)
|
|
116
|
+
return !(await BannerManager.active(feature, scope))
|
|
122
117
|
}
|
|
123
118
|
|
|
124
119
|
static async when<TActive, TInactive>(
|
|
@@ -76,11 +76,16 @@ export function register(program: Command): void {
|
|
|
76
76
|
console.log(chalk.bold(`Stored feature flags (${names.length}):\n`))
|
|
77
77
|
for (const name of names) {
|
|
78
78
|
const records = await BannerManager.store().allFor(name)
|
|
79
|
-
console.log(
|
|
79
|
+
console.log(
|
|
80
|
+
` ${chalk.cyan(name)} ${chalk.dim(`(${records.length} scope${records.length === 1 ? '' : 's'})`)}`
|
|
81
|
+
)
|
|
80
82
|
for (const r of records) {
|
|
81
|
-
const val =
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
const val =
|
|
84
|
+
typeof r.value === 'boolean'
|
|
85
|
+
? r.value
|
|
86
|
+
? chalk.green('active')
|
|
87
|
+
: chalk.red('inactive')
|
|
88
|
+
: chalk.yellow(JSON.stringify(r.value))
|
|
84
89
|
console.log(` ${chalk.dim(r.scope)} → ${val}`)
|
|
85
90
|
}
|
|
86
91
|
}
|
|
@@ -33,7 +33,9 @@ export class ArrayDriver implements FeatureStore {
|
|
|
33
33
|
})
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
async setMany(
|
|
36
|
+
async setMany(
|
|
37
|
+
entries: Array<{ feature: string; scope: ScopeKey; value: unknown }>
|
|
38
|
+
): Promise<void> {
|
|
37
39
|
for (const e of entries) await this.set(e.feature, e.scope, e.value)
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -61,7 +61,9 @@ export class DatabaseDriver implements FeatureStore {
|
|
|
61
61
|
`
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
async setMany(
|
|
64
|
+
async setMany(
|
|
65
|
+
entries: Array<{ feature: string; scope: ScopeKey; value: unknown }>
|
|
66
|
+
): Promise<void> {
|
|
65
67
|
if (entries.length === 0) return
|
|
66
68
|
for (const e of entries) await this.set(e.feature, e.scope, e.value)
|
|
67
69
|
}
|
package/src/helpers.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import BannerManager from './banner_manager.ts'
|
|
2
2
|
import PendingScopedFeature from './pending_scope.ts'
|
|
3
3
|
import type { FeatureStore } from './feature_store.ts'
|
|
4
|
-
import type {
|
|
5
|
-
Scopeable,
|
|
6
|
-
FeatureResolver,
|
|
7
|
-
FeatureClassConstructor,
|
|
8
|
-
DriverConfig,
|
|
9
|
-
} from './types.ts'
|
|
4
|
+
import type { Scopeable, FeatureResolver, FeatureClassConstructor, DriverConfig } from './types.ts'
|
|
10
5
|
|
|
11
6
|
/**
|
|
12
7
|
* Banner helper — the primary convenience API.
|
|
@@ -20,7 +20,7 @@ export function ensureFeature(
|
|
|
20
20
|
return async (ctx, next) => {
|
|
21
21
|
const scope = scopeExtractor
|
|
22
22
|
? scopeExtractor(ctx)
|
|
23
|
-
: (ctx.get('user') as Scopeable | undefined) ?? null
|
|
23
|
+
: ((ctx.get('user') as Scopeable | undefined) ?? null)
|
|
24
24
|
|
|
25
25
|
const isActive = await BannerManager.active(feature, scope)
|
|
26
26
|
|