New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

@strav/flag

Package Overview
Dependencies
Maintainers
1
Versions
75
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@strav/flag

Strav feature-flag primitive — FlagManager + scope-aware resolution + in-memory store, with Postgres store, HTTP middleware, and CLI commands shipped under subpaths. Mirrors @strav/cache: kernel-free core in the root, every backend/integration under its o

latest
npmnpm
Version
1.0.3
Version published
Maintainers
1
Created
Source

@strav/flag

Feature flags for Strav 1.x. Define flags, scope them per user / team / tenant, persist resolutions in-memory or in Postgres, and gate routes with an HTTP middleware. Mirrors the shape of @strav/cache — kernel-free core in the root, drivers and integrations under subpaths.

Install

bun add @strav/flag

Quick start

import { FlagManager, FlagProvider } from '@strav/flag'

// Wire the provider (in-memory store by default).
new FlagProvider({
  define(flags) {
    flags.define('beta-ui', (scope) => scope.startsWith('User:1'))
    flags.define('upload-limit', 25)
  },
})

// Resolve and read.
const flags = app.resolve(FlagManager)

if (await flags.active('beta-ui', user)) { ... }
if (await flags.for(team).active('analytics')) { ... }

const limit = await flags.value('upload-limit')  // 25

Drivers

SubpathDriverUse case
(root)MemoryFlagStoredev / tests / single-process apps
@strav/flag/postgresPostgresFlagStoremulti-node deployments (cross-process)

Swap providers — both register under the same FlagManager token:

import { PostgresFlagProvider, applyFlagMigration } from '@strav/flag/postgres'

// Migration:
await applyFlagMigration(db)

// Provider:
providers: [
  new PostgresFlagProvider({
    define(flags) {
      flags.define('beta-ui', (scope) => scope.startsWith('User:'))
    },
  }),
]

HTTP guard

import { ensureFeature } from '@strav/flag/http'

router.get('/beta', ensureFeature('beta-ui'), betaHandler)
router.group(
  { middleware: [authMiddleware(), ensureFeature('analytics')] },
  (r) => r.get('/insights', insights),
)

Default scope is ctx.auth?.user. Pass scopeFrom for team / tenant / custom subjects.

CLI

bun strav flag:list                          # list stored flags + values
bun strav flag:activate beta-ui              # global on
bun strav flag:activate beta-ui '"control"' --scope User:42
bun strav flag:deactivate beta-ui --scope User:42
bun strav flag:purge beta-ui                 # drop stored values → re-resolve

Register the commands provider alongside the flag provider:

import { FlagConsoleProvider } from '@strav/flag/console'

providers: [
  new FlagProvider({ /* … */ }),
  new FlagConsoleProvider(),
]

Strict scopes

// config/flag.ts
export default { strictScopes: true }

With strictScopes: true, reading a flag without a scope throws MissingScopeError instead of silently evaluating the global value. Catches the common bug where middleware forgets to pass ctx.auth.user. Writes keep the loose semantics — activateForEveryone() makes a global write explicit.

Audit hook

activate / deactivate accept an optional actor: { type, id } that flows into the flag:updated event payload alongside previous (the prior stored value). Subscribe via EventBus to wire an audit log without coupling @strav/flag to @strav/audit:

app.resolve(EventBus).on('flag:updated', (e) => {
  if (!e.actor) return
  auditLog.record({
    actor: e.actor,
    on: 'feature_flag',
    feature: e.feature,
    scope: e.scope,
    diff: { from: e.previous, to: e.value },
  })
})

License

MIT

FAQs

Package last updated on 01 Jun 2026

Related posts