@farthershore/business

中文文档
3.5.0发布于 2026/11/10MITTypeScriptES Module近 30 天下载 5480

Farther Shore Business-as-Code SDK — declare your software business in TypeScript

原始使用文档
文档目录6 节
  1. Install
  2. Authoring model
  3. Folder discovery and isolation
  4. Determinism
  5. Main declarations
  6. Renaming a meter

@farthershore/business

The functional Business-as-Code SDK for declaring routes, meters, plans, resources, and backends in ordinary TypeScript. Farther Shore compiles these declarations into deterministic Manifest IR and applies edge authorization, limits, usage metering, and billing.

Status: 3.5.0. This SDK versions independently from the frontend and backend SDKs.

Install

pnpm add @farthershore/business

The 3.x API is a functional value model. Use one namespace import:

fs.business() returns an opaque handle whose compiled manifest is held in a non-exported package singleton. Raw objects and schema-valid JSON cannot impersonate an SDK-produced business. A business built by a separately bundled physical copy of the SDK is therefore rejected; run that copy in its own process or resolve every business module to the same installed package instance.

import * as fs from "@farthershore/business";

const requests = fs.requests();
const tokens = fs.meter("tokens", { unit: "token" });
const seats = fs.resource("seats", { cap: fs.scope.subscription });

// Routes need a backend; with exactly one, every route uses it implicitly.
fs.backend("api");

const chat = fs.route("/v1/chat", {
  post: { costs: [requests.fixed(1)], reports: [tokens] },
});
const admin = fs.route("/v1/admin", {
  post: { surfaces: [fs.surfaces.api] },
});

fs.plan("pro", {
  kind: fs.plan.kind.flat,
  price: fs.money.usd(49).monthly(),
  grants: [chat, admin],
  limits: [tokens.perMonth(1000000), seats.max(25)],
});

export default fs.business();

Authoring model

Declaration functions return immutable, branded refs. Cross-references accept those refs, so a typo cannot silently name a missing meter, backend, resource, route, or grant group. Platform-controlled vocabulary is grouped under typed namespaces such as fs.surfaces, fs.scope, and fs.money; builder-owned labels such as a meter's unit remain strings.

Prices use human major units: fs.money.usd(49).monthly() means USD 49 per month and lowers to exact integer minor units. Every plan declares its kind (fs.plan.kind.free | flat | usage | prepaid | hybrid | trial | custom); the kind is validated against the plan's economics controls (price, usagePricing, funding, lifecycle, spendPolicy) and is never inferred from shape. Usage money lives only in fs.pricing() catalogs and funding buckets — a plan carries no per-meter rate. Meter limit helpers make the window explicit, for example tokens.perMinute(600) or tokens.perMonth(1000000).

Plans grant route refs directly. fs.group() may bundle route refs for reuse. Features, capabilities, feature gates, and string-key authorization are not part of the 3.x authoring surface. RBAC enablement is platform-managed — a dashboard/API flag on the Business, not a repo-authored declaration; frontend permission strings remain the independent component/member authorization vocabulary.

Subscriber cohorts are also platform-owned release state. Builders author plan refs and route groups; Core pins subscribers to immutable compiled-plan cohorts during apply and migration. Do not put cohort identifiers or RBAC flags into a Business SDK program or handcrafted Manifest IR.

Folder discovery and isolation

The conventional program lives under business/; the managed starter uses business/business.ts, but discovery is filename-agnostic:

business/
  business.ts
  routes.ts
  plans.ts

The compiler imports every source module under business/ in canonical order. Exactly one module must default-export fs.business(). Declarations register in a compile-scoped registry that is reset in a fresh worker realm for every build, so separate builds and duplicate physical SDK installations cannot cross-contaminate each other.

Ordinary TypeScript is inert: helper functions, unrelated imports, computed constants, comments, and extra exports do not enter the manifest. Only SDK-created declarations and the sealed default fs.business() result are consumed. A module that throws during import produces a file-located build error.

Determinism

Business modules must be deterministic. The platform compiles the program twice and rejects differing hashes. Avoid time, randomness, network calls, filesystem reads, and environment-dependent declarations.

Main declarations

  • fs.requests() and fs.meter() declare metering dimensions.
  • fs.route(path, operations) declares path-first HTTP operations.
  • fs.plan(key, options) declares price, direct route grants, limits, credits, trials, spend bounds, and archival behavior.
  • fs.resource() declares counted resources; resource.max(n) creates a typed per-plan ownership limit.
  • fs.backend() declares a backend and returns a route-bindable ref.
  • fs.group() bundles route refs for reuse across plans.
  • fs.business() seals the registry and is the sole valid default export.

Renaming a meter

A meter's id is its stable identity and defaults to its key. Plan limits, route metering, pricing, backend allow-lists, and usage history are all keyed by the identity, so rename a meter by changing its key and pinning the old key as id:

// was: fs.meter("calls", { unit: "call" })
const apiCalls = fs.meter("api_calls", { id: "calls", unit: "call" });
// was: fs.requests()
const requests = fs.meter("billable_requests", { id: "requests" });

Renaming fs.requests() keeps its defaults (unit request, COUNT aggregation, estimated_then_settled settlement) for every option you omit, so the rename changes nothing but the name. (An ordinary meter you declared as fs.meter("requests", ...) is not fs.requests(): when renaming it, spell out any of those options you rely on.)

Keep the same id on every later rename. A rename is a no-op for existing subscribers (same plan version, prices, limits and usage history); the default display label follows the new key, and backends report the meter under its new key. Changing a key without id declares a new meter (existing usage does not carry over). A meter's key may not equal another meter's id, and no two meters may share a key or an id.

Frontend pages and navigation live in the editable frontend/ application. Route surfaces declare whether an operation is callable from API credentials, the platform UI, or both; they do not declare frontend routes.

Routes use concrete or OpenAPI-style {parameter} paths and list the HTTP operations that actually exist. Wildcards are non-granting selectors for attaching shared metering to separately declared routes:

const tokens = fs.meter("tokens", { unit: "token" });

const createChat = fs.route("/v1/chat", {
  post: { surfaces: [fs.surfaces.api] },
});

const getChat = fs.route("/v1/chat/{id}", {
  get: { surfaces: [fs.surfaces.api, fs.surfaces.ui] },
});

fs.meterRoutes("chat-tokens", "/v1/chat/**", {
  reports: [tokens],
});

fs.plan("pro", {
  kind: fs.plan.kind.flat,
  price: fs.money.usd(49).monthly(),
  grants: [createChat, getChat],
  limits: [tokens.perMonth(1000000)],
});

meterRoutes(key, target, options) always carries an author-supplied stable key. A wildcard string, grant group, or array target is a structural metering overlay on the matched routes; a single route ref target is that route's commerce metering binding (reports of meters declared with measures, plus admission bounds). It never creates or grants an operation, and a selector that matches no declared route fails the build. A route-local reports or costs binding replaces inherited wildcard meters for that operation; An operation with no costs/reports (and no overlay match) is unmetered.

Business identity and presentation metadata are platform-owned and are not authored in this SDK.

版本与发布记录

@farthershore/business 共发布 25 个版本,首次发布于 2026/06/27;过去 12 个月发布了 25 个版本。

版本发布日期距今
3.5.0latest2026/11/104 天前
3.4.12026/11/068 天前
4.0.0next2026/11/0113 天前
3.4.02026/10/1430 天前
3.3.02026/10/041 个月前
3.2.02026/09/072 个月前

关于 @farthershore/business 的常见问题

@farthershore/business 是什么?

Farther Shore Business-as-Code SDK — declare your software business in TypeScript

如何安装 @farthershore/business?

在项目目录运行 npm install @farthershore/business,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 farthershore-manifest-build,可以用 npx farthershore-manifest-build 直接运行。

@farthershore/business 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@farthershore/business 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@farthershore/business 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@farthershore/business 可以商用吗?

@farthershore/business 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@farthershore/business 的下载量有多少?

根据 npm 官方下载统计,@farthershore/business 在 2026/10/15 – 2026/11/13 这 30 天内被下载 5,480 次,日均约 183 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@farthershore/business 还在维护吗?

最新版本 3.5.0 发布于 2026/11/10(4 天前);至今共发布 25 个版本,其中过去 12 个月发布了 25 个;首次发布于 2026/06/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。