@particle-academy/fancy-features
v0.1.1
Published
Headless, zero-dependency feature-management engine (boolean + metered-resource gating, feature groups, quota usage). The Node/TypeScript mirror of the PHP particle-academy/laravel-fms — and the owner of the shared feature contract consumed by @particle-a
Downloads
649
Maintainers
Readme
@particle-academy/fancy-features
Headless, zero-dependency feature-management engine — boolean flags +
metered-resource gating, feature groups (with extends, overrides, and
callable gates), and quota usage tracking. The Node/TypeScript mirror of the
PHP particle-academy/laravel-fms
— same resolution semantics, no Laravel/Eloquent. It also owns the shared
feature contract consumed by
@particle-academy/fancy-catalog.
import { createFeatures } from "@particle-academy/fancy-features";
const features = createFeatures({
features: {
"use-mcp": { type: "boolean", enabled: true },
"ai-tokens": { type: "resource", limit: 10_000 },
},
groups: [
{ key: "pro-plan", features: ["use-mcp", "ai-tokens"], overrides: { "ai-tokens": { limit: 50_000 } } },
],
});
await features.canAccess("use-mcp", user); // true
await features.remaining("ai-tokens", user); // 10000 − usage
// Group assignment lifts the cap (MAX wins):
features.groupStore.assign(user, "pro-plan");
await features.remaining("ai-tokens", user); // 50000 − usageResolution order
pre-strategies → gate → registry → groups (OR) → config → sources → default deny
- pre-strategies —
registerPreStrategy(name, (feature, subject, context) => boolean | null); first non-null wins (authoritative).registerPreRemainingStrategyis thenumber | nullanalog forremaining(). - gate — an injected
gate(feature, subject, context) => boolean | null; a boolean is authoritative (can deny even when later sources would allow). - registry / config — programmatic + config-map feature definitions;
enabled/checkevaluated. - groups — the subject's assigned groups (
GroupStore) plus callable-gated groups, OR'd; groupoverridesraise resource limits (MAX wins). - sources —
FeatureSource[]resolveFeatureGrant[]; a grant withenabled:trueturns the feature on. This is where fancy-catalog plugs in. - resource
remaining—MAX(group/source limit, feature.limit) − UsageStore.getUsage, clamped ≥0;null⇒ unlimited.
API
createFeatures(opts) / new FeatureManager(opts):
canAccess(key, subject?, context?)→Promise<boolean>(isEnabled/hasFeatureare aliases)remaining(key, subject?, context?)→Promise<number | null>(null= unlimited)enabled(subject?, context?)→Promise<string[]>(all enabled keys)explain(key, subject?, context?)→Promise<AccessResult>({ allowed, source, remaining?, limit?, used? })registerPreStrategy/registerPreRemainingStrategyregisterSource(FeatureSource)·registerFeature(key, def)·registerGroup(group)- Quota helpers:
increment·decrement·tryConsume(atomic) ·usageFor·resetPeriod
Registry — FeatureRegistry: array | factory fn | class-with-definition().
Groups — FeatureGroupRegistry (resolvedFeatures, resolvedOverrides, groupsContaining, 1-level extends with cycle detection) + GroupStore (in-memory InMemoryGroupStore default).
Usage — UsageStore (in-memory InMemoryUsageStore default).
Guard — requireFeature(manager, keys, subject?) (OR logic, throws FeatureAccessDeniedError) + canAccessAny predicate + requireFeatureMiddleware (generic (req,res,next), no express dependency).
Helpers — feature(), canAccessFeature(), hasFeature(), featureRemaining(), enabledFeatures() bound to a default instance via setDefaultFeatures / configureFeatures.
The shared contract
Exported verbatim from the barrel and mirrored structurally by fancy-catalog's
./features subpath: FeatureType, Subject, BillingPeriod, FeatureGrant,
FeatureSource, UsageStore, AccessResult, Feature, FeatureGroup. A
FeatureSource built in catalog is assignable here with no build-time
dependency between the two packages.
import { createCatalogFeatureSource } from "@particle-academy/fancy-catalog/features";
const features = createFeatures({
sources: [createCatalogFeatureSource(catalog, { resolveSubscription })],
});
await features.canAccess("use-mcp", user); // resolves via the user's subscription's product features⭐ Star Fancy UI
If this package is useful to you, a quick ⭐ on the repo really helps us build a better kit. Thank you!
