@m6d/cerebro
v1.0.0
Published
Reusable frontend licensing primitives for JavaScript/TypeScript clients.
Readme
Cerebro JavaScript SDK
Reusable frontend licensing primitives for JavaScript/TypeScript clients.
What This SDK Covers
The SDK focuses on generic client-side licensing patterns.
It provides framework-agnostic building blocks for:
- Loading licensed features and checking feature availability
- Evaluating route access constraints (features only)
- Filtering navigation trees by access requirements
Install
bun add @m6d/cerebroFor local development in this repository:
bun installDevelopment Scripts
bun lint
bun check
bun test
bun run build
bun run sample:feature-storeCI/CD and Publishing
- PRs targeting
mainrun quality checks via.github/workflows/pr-checks.yml - Pushes to
mainrun semantic-release via.github/workflows/release.yml - Releases use npm trusted publishing (OIDC), not long-lived npm tokens
- Provenance is currently disabled because npm does not support provenance from private GitHub repositories
To enable trusted publishing for @m6d/cerebro:
- Open the package settings on npmjs.com for
@m6d/cerebro - Add a Trusted Publisher for GitHub Actions
- Set Organization/User, Repository, and workflow filename to
release.yml - Ensure publishes run on GitHub-hosted runners (this workflow uses
ubuntu-latest)
Documentation
docs/README.mddocs/licensing/README.mddocs/routing/README.md
Samples
samples/README.mdsamples/vanilla-feature-store-sample/README.md
Generic Example
import {
createFeatureStore,
createLicenseFeatureLoader,
evaluateRouteAccess,
} from "@m6d/cerebro";
const featureStore = createFeatureStore({
loadFeatures: createLicenseFeatureLoader({
url: "https://example.com/licenses/features",
}),
});
await featureStore.load();
const decision = evaluateRouteAccess(
{
featureIds: ["kpis", "plans"],
},
{
features: featureStore.getState().features,
},
);
if (!decision.allowed) {
console.log("Blocked", decision.reason, decision);
}Use Cases Coverage
These are common licensing use cases and how they map to this SDK:
- Route feature guard (
featureIds) ->evaluateRouteAccess(...)orcanAccessRoute(...) - Filtering navigation/app lists by licensed modules ->
filterAccessibleItems(...) - Inline checks inside components/pages ->
featureStore.hasFeature(...),featureStore.hasAnyFeature(...),hasFeature(...) - Bootstrapping and refreshing licensed features ->
createLicenseFeatureLoader(...)+featureStore.load()
This SDK intentionally does not include permission logic, notifications, router bindings, or framework-specific state wrappers.
Angular Example
// app/core/licensing.service.ts
import { Injectable } from "@angular/core";
import {
createFeatureStore,
createLicenseFeatureLoader,
evaluateRouteAccess,
} from "@m6d/cerebro";
@Injectable({ providedIn: "root" })
export class LicensingService {
private featureStore = createFeatureStore({
loadFeatures: createLicenseFeatureLoader({
url: "/licenses/features",
credentials: "include",
}),
});
async init() {
await this.featureStore.load();
}
hasFeature(featureId: string) {
return this.featureStore.hasFeature(featureId);
}
canAccess(featureIds: readonly string[]) {
return evaluateRouteAccess(
{ featureIds },
{ features: this.featureStore.getState().features },
).allowed;
}
}// app/core/feature.guard.ts
import { inject } from "@angular/core";
import { CanActivateFn } from "@angular/router";
import { LicensingService } from "./licensing.service";
export const featureGuard: CanActivateFn = (route) => {
const licensing = inject(LicensingService);
const featureIds = (route.data?.["featureIds"] as string[] | undefined) ?? [];
return licensing.canAccess(featureIds);
};// app/app.config.ts (or APP_INITIALIZER module setup)
import { APP_INITIALIZER } from "@angular/core";
import { LicensingService } from "./core/licensing.service";
export const licensingInitializer = {
provide: APP_INITIALIZER,
multi: true,
deps: [LicensingService],
useFactory: (licensing: LicensingService) => () => licensing.init(),
};React Example
import {
createFeatureStore,
createLicenseFeatureLoader,
evaluateRouteAccess,
} from "@m6d/cerebro";
import {
createContext,
useContext,
useEffect,
useMemo,
useSyncExternalStore,
} from "react";
const featureStore = createFeatureStore({
loadFeatures: createLicenseFeatureLoader({
url: "/licenses/features",
credentials: "include",
}),
});
type LicensingContextValue = {
features: string[];
isLoading: boolean;
hasFeature: (featureId: string) => boolean;
canAccess: (featureIds: readonly string[]) => boolean;
};
const LicensingContext = createContext<LicensingContextValue | null>(null);
export function LicensingProvider({ children }: { children: React.ReactNode }) {
const state = useSyncExternalStore(
featureStore.subscribe,
featureStore.getState,
);
useEffect(() => {
featureStore.load().catch(() => undefined);
}, []);
const value = useMemo<LicensingContextValue>(() => {
return {
features: state.features,
isLoading: state.isLoading,
hasFeature: (featureId) => featureStore.hasFeature(featureId),
canAccess: (featureIds) => {
return evaluateRouteAccess({ featureIds }, { features: state.features })
.allowed;
},
};
}, [state.features, state.isLoading]);
return <LicensingContext value={value}>{children}</LicensingContext>;
}
export function useLicensing() {
const value = useContext(LicensingContext);
if (!value) {
throw new Error("useLicensing must be used inside <LicensingProvider>");
}
return value;
}// any component
import { useLicensing } from "./licensing-context";
function KpiPage() {
const licensing = useLicensing();
if (!licensing.canAccess(["kpis"])) {
return <div>Feature not available in your license.</div>;
}
return <div>KPI content</div>;
}Notes
- Response parsers support both direct payloads and
Result<T>-style payloads ({ extra: ... }) - Feature checks default to case-insensitive matching
- Route
featureIdsdefault toanymode
