@m6d/cerebro
v0.2.0
Published
Cerebro's license SDK for TypeScript. Browsers read a license's entitlements from the product's backend; Node and Bun backends verify the license key and serve them. One package, four entries:
Readme
@m6d/cerebro
Cerebro's license SDK for TypeScript. Browsers read a license's entitlements from the product's backend; Node and Bun backends verify the license key and serve them. One package, four entries:
| Entry | Runs in | Gives |
| ---------------------- | ---------------------- | --------------------------------------------------------- |
| @m6d/cerebro | any JavaScript runtime | createCerebro, createEntitlementsLoader, route checks |
| @m6d/cerebro/react | React 18 or later | CerebroProvider, useCerebro |
| @m6d/cerebro/angular | Angular 17 or later | provideCerebro, injectCerebro, entitlementGuard |
| @m6d/cerebro/server | Node, Bun | verifyLicenseKey, loadLicense, entitlementsHandler |
A license grants entitlements by code. get(code) reads any of them;
isEnabled(code) checks an on/off one. A null number means unlimited. The
product page in the admin app has a "Type for developers" button that copies
the product's type; pass it as T and the checks only take that product's
codes.
bun add @m6d/cerebroBrowser
import { createCerebro, createEntitlementsLoader } from "@m6d/cerebro";
type AtlasHREntitlements = {
export: boolean;
maxUsers: number | null;
supportTier: "basic" | "priority";
};
export const cerebro = createCerebro<AtlasHREntitlements>({
load: createEntitlementsLoader({ url: "/licenses/entitlements" }),
});
await cerebro.load();
cerebro.isEnabled("export");
cerebro.get("maxUsers"); // number | null | undefinedReact
import { CerebroProvider, useCerebro } from "@m6d/cerebro/react";
<CerebroProvider cerebro={cerebro}>
<Seats />
</CerebroProvider>;
function Seats() {
const { loaded, get } = useCerebro<AtlasHREntitlements>();
const seats = get("maxUsers");
if (!loaded) return null;
return <p>{seats === null ? "Unlimited seats" : `${seats} seats`}</p>;
}Angular
import { entitlementGuard, injectCerebro, provideCerebro } from "@m6d/cerebro/angular";
bootstrapApplication(App, {
providers: [
provideCerebro(cerebro),
provideRouter([
{ path: "reports", canMatch: [entitlementGuard("export")], loadComponent: () => Reports },
]),
],
});
// In a component: `state` is a signal that follows the store.
readonly cerebro = injectCerebro<AtlasHREntitlements>();Server
import { entitlementsHandler, loadLicense } from "@m6d/cerebro/server";
// Reads and verifies CEREBRO_LICENSE_KEY once; appId is the product's id in Cerebro.
const license = loadLicense<AtlasHREntitlements>({ appId: "<product id>" });
if (!license.result.ok) console.warn("Cerebro license:", license.result.failure);
Bun.serve({ routes: { "/licenses/entitlements": entitlementsHandler(license) } });Pass appId. Every product's keys share one signing key, so without it a
license for another product verifies too (wrong_app with it). Expiry is
checked again on every call, so a server that outlives its license stops
granting.
The .NET SDK, M6d.Cerebro, serves the same endpoint, so the browser entries work against either backend.
Samples
apps/sample-react:CerebroProvideranduseCerebroin a Vite app.apps/sample-angular:provideCerebro,injectCerebroand anentitlementGuardroute.apps/sample-server: the backend both call, verifying a key withloadLicense.
Running them is under "Development" in the root README.md.
Development
From the repository root:
bun run --cwd packages/cerebro-typescript check
bun run --cwd packages/cerebro-typescript test
bun run --cwd packages/cerebro-typescript build
bun run pack:check # installs the packed tarball into a consumer projectReleases run from the monorepo; see "Releasing" in the root README.md.
More detail: docs/licensing/README.md and docs/routing/README.md.
