Core concepts
The @Product class, the Manifest IR, and edge enforcement.
The @Product class, the Manifest IR, and edge enforcement.
A FartherShore product moves through three stages: you author a decorated
@Product class, FartherShore compiles it to a deterministic Manifest IR,
and the gateway enforces that IR at the edge on every request. This page is
the mental model behind those stages and the vocabulary used everywhere else.
@Product class ──compile──▶ Manifest IR ──apply──▶ edge gateway
(you author) (deterministic) (enforces live)
@Product classA product is one decorated class, export defaulted from
product/product.config.ts. The class decorator carries product-level options;
each member decorator declares one piece of the product. Cross-references
between members are by string key — decorators never hand handles to each
other.
import { Product, Requests, Feature, Plan } from "@farthershore/product";
@Product({ name: "croncloud", origin: "https://api.example.com" })
export default class CronCloud {
@Requests()
requests!: unknown;
@Feature("cron-jobs", { routes: { "POST /v1/cron-jobs": {} } })
cronJobs!: unknown;
@Plan("starter", {
name: "Starter",
price: { amount: 2900, currency: "usd", interval: "month" },
limits: { requests: { rate: 600, interval: "minute" } },
})
starter!: unknown;
}
origin is required: it's the business-logic URL the gateway forwards admitted
requests to. name defaults to the class name.
These are the building blocks. Each is a member decorator unless noted.
| Decorator | Declares |
|---|---|
@Product(options) | The class decorator: product identity, origin, billing/policy options. |
@Surface(type, …) | A surface customers use: frontend, api, docs, widget, webhook, worker, agent, dashboard. |
@Meter(key, …) | A billable/enforceable usage dimension (tokens, compute, credits). |
@Requests(…) | The platform-managed request meter; applies requests = 1 to every route. |
@Resource(name, …) | A counted resource you can cap per plan. |
@Capability(key, …) | A capability bundle a plan grants to unlock features. |
@Feature(key, …) | Gateway routes (keyed "METHOD /path"), costs, reports, and actions. |
@Plan(key, …) | Pricing, limits, grants, and subscriber-change policy. |
@Workflow(key, …) | A non-HTTP product workflow (scheduled, agent task, …). |
@Backend(id, …) | A first-class BYO backend; routes bind to it by id. |
@Entitlement(key, …) | A reusable bundle of capabilities, feature gates, limits, and meters. |
@Policy(name, …) | A reusable policy layer declared in code. |
@Frontend(options) | The frontend nav/pages manifest (a stackable class decorator). |
@Raw(options) | Escape hatch for platform-schema JSON the typed SDK lacks sugar for. |
The key words you'll reuse: a product is the sellable thing (one origin,
some surfaces); a feature is a named group of gateway routes; a
capability is what a plan grants (it includesFeatures, so granting it
admits those routes); a meter is a unit of usage; a plan is a priced
configuration of grants, limits, and meters; a backend is your upstream (the
default is your origin).
@Requests() declares the platform-managed requests meter and needs no
backend code — plain request counting is automatic. Variable usage (tokens,
compute) is a @Meter that the route declares it reports, and your
backend sends with @farthershore/backend:
@Meter("tokens_used", { unit: "token", estimate: 500 })
tokensUsed!: unknown;
@Feature("runs", {
routes: { "POST /v1/runs": { reports: "tokens_used" } },
})
runs!: unknown;
Per-route metering knobs: cost (fixed gateway-known cost), reports (dynamic
meters the backend reports), estimates (pre-request admission estimate),
unmetered: true, and inheritDefaultMeters: false. A @Meter's routeDefault
applies a fixed cost to every metered route.
A @Plan is a configuration of orthogonal knobs. price.amount is integer
cents; per-unit overage is in micros. Plans grant capabilities (with
optional resource caps via capabilityGrant) and set rate limits per meter:
@Plan("pro", {
name: "Pro",
price: { amount: 19900, currency: "usd", interval: "month" },
grants: [capabilityGrant("managed-cron", { limits: { cron_jobs: 100 } })],
limits: { requests: { rate: 6000, interval: "minute", enforcement: "enforce" } },
})
pro!: unknown;
A free plan uses price: { free: true }. Credit grants, trials, and spend caps
are additional knobs (grants, trialDays, maxMonthlySpendCents).
Publishing requires every plan to carry at least one rate-limit rule.
Changing a plan is define (edit the class, publish). Migrating existing
subscribers onto a new version is an operate action — a transition over
live state — done with farthershore plan migrate, not the manifest. Existing
subscribers are grandfathered by default.
When you run farthershore build, the class compiles
to a Manifest IR envelope (manifest-ir.json) — the backend-owned,
deterministic projection the platform actually applies. Member decorators are
folded and sorted into stable collections, while routes keep declaration order
(the gateway is first-match-wins). The same input always yields the same
irHash:
{
"irVersion": 1,
"product": {
"product": { "name": "croncloud", "baseUrl": "https://api.example.com" },
"plans": [ { "key": "pro" }, { "key": "starter" } ]
},
"routes": [
{
"feature": "cron-jobs",
"routes": [
{ "match": { "method": "GET", "path": "/v1/cron-jobs" } },
{ "match": { "method"
You author the ergonomic class; the IR is what gets validated against the live platform contract and applied. The build runs twice and rejects drift, so the IR is reproducible — a pricing change is a reviewable diff.
Two paths reconcile to the same published state: push accepted product/**
changes (the GitHub bot compiles and publishes through Core), or run
farthershore product publish <product> to cut a versioned release directly
(auto-derived semver, gated on a plan, an origin, and verified Stripe). After
either, farthershore product status <product> reports a derived live boolean
once the edge is serving the new snapshot.
The gateway sits in front of your origin. On each request it:
cost values immediately, reports values once your backend returns them.That's the whole loop from a single class: define it, the gateway enforces it, usage is metered, and Stripe bills it.