Appearance
Economy API
Signatures and behavior for @idlekitjs/economy. Concepts and recipes live on the package page.
Imports
ts
import {
createEconomy,
stateKey,
resourceAtLeast,
describeFailure,
} from "@idlekitjs/economy";
import { arrayIndex, recordField } from "@idlekitjs/economy/accessors";
import { costCurve, flat, geometric } from "@idlekitjs/economy/cost-curves";Public subpaths:
| Subpath | Exports |
|---|---|
@idlekitjs/economy | Full facade |
@idlekitjs/economy/accessors | Accessor helpers |
@idlekitjs/economy/cost-curves | Cost curve helpers |
Resources
ts
type ResourceId = string;
interface ResourceAccessor<T> {
get(state: T): number;
add(state: T, amount: number): void;
}
type ResourceInit<T> =
| {
id: ResourceId;
label?: string;
description?: string;
format?: (amount: number) => string;
integer?: boolean;
min?: number;
max?: number;
tags?: readonly string[];
metadata?: Record<string, unknown>;
accessor: ResourceAccessor<T>;
}
| {
id: ResourceId;
label?: string;
description?: string;
format?: (amount: number) => string;
integer?: boolean;
min?: number;
max?: number;
tags?: readonly string[];
metadata?: Record<string, unknown>;
get(state: T): number;
add(state: T, amount: number): void;
};
interface ResourceDef<T> {
id: ResourceId;
label: string;
accessor: ResourceAccessor<T>;
format?: (amount: number) => string;
integer: boolean;
min: number;
max: number;
readonly: boolean;
tags: readonly string[];
description?: string;
metadata?: Record<string, unknown>;
}Helpers:
| Export | Behavior |
|---|---|
defineResource | Normalize a ResourceInit into ResourceDef |
validateResourceId | Throws on empty, whitespace, leading/trailing : |
Accessors
| Export | Use for | Write behavior |
|---|---|---|
stateKey | Top-level numeric state field | Assigns field in place |
arrayIndex | One numeric array slot | Clones array, calls setArray |
recordField | Numeric field inside a record | Clones record and entry, calls setRecord |
computed | Hand-written get/add pair | Pass-through |
readonly | Observable non-writable value | get only; transactions reject in cost/reward |
Economy
ts
function createEconomy<T>(options?: EconomyOptions): Economy<T>;
interface EconomyOptions {
format?: (amount: number) => string;
}
interface Economy<T> {
resource(init: ResourceInit<T>): this;
resource(id: ResourceId): ResourceDef<T>;
resources(inits: readonly ResourceInit<T>[]): this;
has(id: ResourceId): boolean;
ids(): readonly ResourceId[];
get(state: T, id: ResourceId): number;
add(state: T, id: ResourceId, amount: number): number;
spend(state: T, id: ResourceId, amount: number): boolean;
canAfford(state: T, cost: AmountsInput): boolean;
missing(state: T, cost: AmountsInput): Cost;
pay(state: T, cost: AmountsInput): void;
credit(state: T, reward: AmountsInput): Reward;
preview(state: T, transaction: Transaction<T>): TransactionPreview;
execute(state: T, transaction: Transaction<T>): TransactionResult;
canExecute(state: T, transaction: Transaction<T>): boolean;
formatAmount(id: ResourceId, amount: number): string;
formatCost(state: T, cost: AmountsInput): readonly FormattedCostLine[];
formatReward(reward: AmountsInput): readonly FormattedAmount[];
audit(state: T): readonly TransactionFailure[];
}Direct operations throw EconomyError on programming mistakes. Transaction operations return diagnostics for content-shaped failures.
Amounts
ts
interface ResourceAmount {
resourceId: ResourceId;
amount: number;
}
type Cost = readonly ResourceAmount[];
type Reward = readonly ResourceAmount[];
type AmountsInput = readonly (readonly [ResourceId, number] | ResourceAmount)[];Exports:
| Export | Behavior |
|---|---|
normalizeAmounts | Strict normalize; throws on invalid lines |
collectAmounts | Lenient normalize; returns invalid lines |
mergeAmounts | Merge several inputs |
scaleAmounts | Scale by finite factor >= 0 |
Normalization merges duplicates, drops zero totals and rejects negative, NaN and Infinity amounts.
Requirements
ts
interface Requirement<T> {
id: string;
label?: string;
isMet(state: T, economy: EconomyReader<T>): boolean;
progress?(state: T, economy: EconomyReader<T>): { current: number; target: number };
}Exports:
| Export | Behavior |
|---|---|
resourceAtLeast | Non-consuming >= resource gate |
resourceAtMost | Non-consuming <= resource gate |
allOf | All child requirements must pass |
not | Negate one requirement |
Transactions
ts
type CostInput<T> = AmountsInput | ((state: T) => AmountsInput);
type RewardInput<T> = AmountsInput | ((state: T) => AmountsInput);
interface Transaction<T> {
id: string;
label?: string;
requirements?: readonly Requirement<T>[];
cost?: CostInput<T>;
reward?: RewardInput<T>;
apply?: (state: T) => void;
metadata?: Record<string, unknown>;
}ts
interface TransactionPreview {
ok: boolean;
failures: readonly TransactionFailure[];
cost: Cost;
reward: Reward;
missing: Cost;
overflow: Reward;
}
type TransactionResult =
| { ok: true; cost: Cost; reward: Reward; overflow: Reward }
| { ok: false; failures: readonly TransactionFailure[]; missing: Cost };Failure variants:
ts
type TransactionFailure =
| { kind: "unknown-resource"; resourceId: ResourceId; where: "cost" | "reward" }
| { kind: "invalid-amount"; resourceId: ResourceId; amount: number; where: "cost" | "reward" }
| { kind: "invalid-state"; resourceId: ResourceId }
| {
kind: "requirement-failed";
requirementId: string;
label?: string;
progress?: { current: number; target: number };
}
| { kind: "cannot-afford"; resourceId: ResourceId; required: number; available: number }
| { kind: "readonly-resource"; resourceId: ResourceId; where: "cost" | "reward" };Exports:
| Export | Behavior |
|---|---|
previewTransaction | Pure transaction evaluation |
executeTransaction | Preview, then pay -> apply -> credit |
apply must not fail after preview. There is no generic rollback.
Formatting
ts
interface FormattedAmount {
resourceId: ResourceId;
label: string;
amount: string;
rawAmount: number;
}
interface FormattedCostLine extends FormattedAmount {
available: number;
affordable: boolean;
}
function describeFailure(
failure: TransactionFailure,
options?: { label?: (id: ResourceId) => string },
): string;formatCost and formatReward return UI-agnostic view models. Economy never touches the DOM and never requires icons, colors or layout metadata.
Cost Curves
ts
type Rounding = "none" | "floor" | "ceil" | "round";
type CostCurveLine =
| { kind: "flat"; resourceId: ResourceId; amount: number; round?: Rounding }
| {
kind: "geometric";
resourceId: ResourceId;
baseAmount: number;
growth: number;
round?: Rounding;
};
interface CostCurve<T> {
costFor(state: T, quantity: number): Cost;
next(state: T): Cost;
maxAffordable(state: T, economy: EconomyReader<T>): number;
}Exports:
| Export | Behavior |
|---|---|
flat | Flat per-unit cost line |
geometric | Geometric per-unit cost line |
costCurve | Build a multi-line curve |
geometricSum | Closed-form sum helper |
geometricAffordable | Closed-form affordability helper |
growth === 1 is linear. Invalid growth or negative amounts throw at wiring time. costFor(quantity <= 0) returns [].
Mechanics Adapters
Economy adapters are official mechanics subpaths, not Economy subpaths:
txt
@idlekitjs/mechanics/producers/economy
@idlekitjs/mechanics/collections/economy
@idlekitjs/mechanics/crafting/economy
@idlekitjs/mechanics/projects/economy
@idlekitjs/mechanics/boosts/economy
@idlekitjs/mechanics/containers/economy
@idlekitjs/mechanics/pickups/economySee Economy adapters for adapter APIs and examples.