@farthershore/business
v0.8.0
Published
Farther Shore Business-as-Code SDK — declare your software business in TypeScript
Downloads
1,498
Maintainers
Readme
@farthershore/business
Declare your software business in TypeScript. You write one decorated
@Business class — its surfaces, meters, features/routes, plans, and policies —
and Farther Shore compiles it to a deterministic manifest that the platform
applies: edge auth, plan and rate-limit enforcement, usage metering, and
Stripe-backed billing.
This is the authoring SDK for Business-as-Code. Your business config is the
contractual source of truth: instead of clicking through a dashboard to wire up
pricing, limits, and routes, you express them in code, commit them, and the
platform enforces exactly what you declared. Presentation (display name,
description, logo, colors) is platform-owned and set in the dashboard/CLI/API —
it is not part of @Business.
Status:
0.8.0. Pre-1.0 — minor versions may include breaking changes. Pin an exact version in yourpackage.jsonand bump deliberately. This SDK versions independently from the frontend and backend SDKs.
Install
pnpm add @farthershore/businessRepos generated by Farther Shore already pin this dependency. You author your
business in business/business.config.ts:
business/
package.json
business.config.ts ← the single authored entrypoint
frontend/ ← the editable starter UI (built separately)business/business.config.ts must export default exactly one decorated
@Business class.
Quickstart
import {
Business,
Requests,
Meter,
Surface,
Feature,
Plan,
} from "@farthershore/business";
@Business({
name: "croncloud",
})
export default class CronCloud {
@Surface("api")
api!: unknown;
// The platform-managed request meter: counts 1 per successful metered route.
@Requests()
requests!: unknown;
// A dynamic meter your backend reports per request (see @farthershore/backend).
@Meter("tokens_used", { unit: "token", estimate: 500 })
tokensUsed!: unknown;
@Feature("runs", {
routes: {
"POST /v1/runs": { reports: "tokens_used" },
},
})
runs!: unknown;
@Plan("starter", {
name: "Starter",
price: { amount: 2900, currency: "usd", interval: "month" },
limits: {
requests: { rate: 600, interval: "minute", enforcement: "enforce" },
},
})
starter!: unknown;
}Member decorators (@Meter, @Feature, @Plan, …) declare resources in source
order; the @Business class decorator collects them into one business and
compiles it. Cross-references are by string key — a route's
reports: "tokens_used" names the @Meter("tokens_used", …) key.
Metering & routes
@Requests() declares the platform-managed request meter and counts 1 on
every metered route — no backend code needed for plain request counting. Add
@Meter for dimensions your backend reports (tokens, credits, rows) or for
fixed per-route costs.
Each @Feature route is keyed "METHOD /path", and its entry controls metering:
report/reports— meter key(s) your backend may report per request (via@farthershore/backend).cost— fixed gateway-known costs, keyed by meter:{ cost: { credits: 10 } }.estimates— per-route pre-request estimates used for admission checks.unmetered: true— clears all inherited and explicit metering for the route.inheritDefaultMeters: false— drops only the inherited defaults (e.g.requests = 1).
A @Meter's routeDefault applies a reusable fixed cost to every metered route.
import { Business, Meter, Feature } from "@farthershore/business";
@Business({ name: "credits" })
export default class Credits {
@Meter("api_credits", { unit: "credit", routeDefault: 1 })
apiCredits!: unknown;
@Feature("exports", {
routes: {
"POST /v1/bulk-export": { cost: { api_credits: 10 } },
"GET /healthz": { unmetered: true },
},
})
exports!: unknown;
}Plans
@Plan uses structured, integer-only money. Prices are cents; per-unit overage
is micro-dollars. Limits are declared per dimension as either a rate limit or a
resource count, and are kept in declaration order.
import { Business, Plan, capabilityGrant } from "@farthershore/business";
@Business({ name: "croncloud" })
export default class CronCloud {
@Plan("free", {
name: "Free",
price: { free: true },
limits: {
requests: { rate: 60, interval: "minute", enforcement: "enforce" },
},
})
free!: unknown;
@Plan("pro", {
name: "Pro",
price: { amount: 4900, currency: "usd", interval: "month" },
limits: {
requests: { rate: 600, interval: "minute", enforcement: "enforce" },
cron_jobs: { count: 50 }, // a counted-resource cap
},
// Per-unit overage billing for a metered dimension (micro-dollars / unit).
meter: { tokens_used: { micros: 2, includedUnits: 100_000 } },
grants: [capabilityGrant("priority_runs", { limits: { concurrency: 4 } })],
})
pro!: unknown;
}Public API
All exports come from the package root:
@Business(options)— the class decorator.options.namedefaults to the class name. Options:visibility,authHeader,upstreamAuth,billOn4xx,customerContext,billing. The upstream origin is platform-owned — set it out-of-band viafarthershore business update --origin <url> [--env <env>].@Surface(type, options?)— declare a surface customers interact with (frontend,api,docs,widget,webhook,worker,agent,dashboard).@Meter(key, options?)— declare a billable or enforceable dimension; arouteDefaultapplies a reusable fixed cost to metered routes.@Requests(options?)— declare and inherit the platform-managed successful-request meter.@Resource(name, options?)— declare a counted resource for resource-count constraints.@Capability(key, options?)— declare capability bundles; grant them on a plan viacapabilityGrant(key, { limits })in a@Plan'sgrants.@Feature(key, options?)— declare gateway routes (keyed"METHOD /path"), fixed costs, dynamic reports, estimates, and actions.@Backend(id, options?)— declare a first-class backend; bind routes via a route entry'sbackend. Single-backend businesses stay zero-config.@Policy(name, options)— declare a policy layer.@Plan(key, options?)— declare pricing (price),limits,caps, per-unitmeteroverages,grants,capacity, trials, spend caps, and archival.@Workflow(key, options?)— declare a non-HTTP business workflow.@Entitlement(key, options?)— group capabilities, gates, limits, and meters into reusable access metadata.@Frontend(options)— declare the frontend nav/pages manifest.@Rbac(options?)— enable managed RBAC (@Rbac()enforces role permissions on every user key;@Rbac({ capability })gates it to a plan capability).capabilityGrant(key, { limits? })— build a capability grant with optional per-capability limits for a plan'sgrants[].
The package also exports the manifest IR types and validation helpers
(validateManifestIr, hashIr, ManifestValidationError, SDK_VERSION, …)
used by the platform toolchain.
Changing a live business
A @Business class is a snapshot of business state — not a migration. To
change pricing, limits, or routes, edit the config and publish; existing
subscribers are grandfathered by default. Actively moving subscribers onto a new
plan version is a separate operate action run via the farthershore CLI (plan
migrate), not a declaration.
Determinism
business.config.ts and everything it imports must be deterministic: no dates,
randomness, network calls, or process state. Compilation is a pure function of
your declarations — collections are emitted sorted by key, while route order is
preserved because the gateway matcher is first-match-wins. The platform compiles
your config twice and rejects the change if the two outputs differ.
Documentation
Full platform docs — the manifest model, metering and limits, billing, and the
lifecycle — are available in the Farther Shore dashboard. The @Business
options and decorators are also documented inline via their TypeScript types.
