API overview
The package's JavaScript exports are defined in src/index.js; TypeScript declarations are in src/index.d.ts. Installed projects import from the package root. Examples in this repository can import from src/index.js. Node.js 20 or later is required.
Model and compilation
import { cashflow, currencies, defineModel, entity, inflow, periods, schedule } from "deccf";
const building = entity.asset();
const rent = cashflow({ owner: building, direction: inflow, schedule: schedule.monthly(), amount: 5_000 });
const model = defineModel({
name: "example",
currency: currencies.SEK,
timeline: periods.monthly({ from: "2026-01", count: 12 }),
entities: { building },
flows: { rent },
});
return model;currency and timeline are required; the other maps are optional. currency is a Currency value. A run may select a different reporting denomination through run(model, { currency }); this does not convert numeric values. Map keys become declaration names in results, and their values are references created by API factories.
compileModel(model)validates the model and returns a plan with dates, references, periods, and dependency order.run(modelOrPlan, options?)evaluates the model. Declarations are compiled automatically.ModelErrorexposes a stable machine-readablecode, optionalpathandperiod, anddetails.
result.deterministic.account_postings serializes account movements with period, account, type, category, side, amount, and flow metadata. It includes explicit booking rows and moves movements. Account-plan closing rows have flow_role: "period-close", allowing a report to distinguish closing entries using only the run output.
Values and inputs
- Currencies:
currency("SEK")creates a three-letter uppercase code.currenciesincludes USD, SEK, EUR, GBP, NOK, DKK, CHF, CAD, AUD, and JPY. - Money:
money(amount, currency),scaleMoney,addMoney,subtractMoney, andconvertMoney(value, target, unitsOfTargetPerSource). - Direction:
inflowandoutflow. Create categories for a model withcategory(path), such ascategory("sales.subscription"). An optional{ label }gives the category a display name indeterministic.categories. The core has no predefined categories; domain packs can provide them. - The accountplan domain pack adds account codes and classifications. The Swedish income-tax pack is separate.
- Inputs:
input.decimal,rate,fraction,integer,str,id,enum,date,money,object,objectArray,textfile,sni,curve,normal,logNormal,uniform, andtriangular.derived({ inputs, calculate })declares a value derived from other references before simulation.input.textfile(defaultText?)is a string precondition; a run can replace it with the contents of a text file or any other string throughdeterministic.inputs.input.sni(mainGroup?)is a string precondition validated against the SNI 2025 two-digit main-group list. It carries dropdown choices in the model description and result metadata for UIs.
fraction must be between 0 and 1; integer must be a whole number. Numeric values must be finite and can be checked against a domain. A date input accepts an ISO year-month or date. Curves and distributions are covered in Valuation and uncertainty; run-time overrides are described in Run configuration.
Components, collections, and funding
input.object({ field: inputValue })groups typed input fields into one value.input.objectArray(rows, { key: "id", item? })declares editable, keyed rows. Use a stable ID field and an explicit item schema when the initial list is empty. Calculator editors render object arrays as add/remove cards from this same schema.component({ entities, accounts, flows, calculations, components, outputs })composes ordinary declarations and exposes selected outputs.repeat(source, template)creates one component per object-array row. Inside the template,source.item.dataexposes row inputs andsource.item.outputsexposes component outputs..where({ role: "adult" })selects rows;.column("salary")gives a keyed column for allocation.allocation({ amount, method, limits?, redistribute?, signed?, unit?, schedule?, phase? })reports shares fromproportional({ weights, overrides?, zeroBasis? })orpriority({ order, requests? }). Allocation has no cash or account side effects.transfer({ from, to, amount, schedule, posting? })makes a balanced movement. Cross-owner transfers need a posting policy.funding({ from, to, amount, method, posting, schedule, limits?, shortfall? })uses the same allocation methods and records real transfers. Its calculation outputs are not counted again as cashflow.
See the declarative collections and funding guide for complete examples and the Household pack guide for a domain-specific composition.
Time
periods.daily/weekly/monthly/quarterly/annual({ from, count, projection? })phase({ from, to });frequencies.day/week/month/quarter/yearschedule.once(date, options?),.onceAfterNoOfDays(days),.every(frequency, options?),.daily,.weekly,.monthly,.quarterly,.annual,.yearEnd,.phaseStart,.phaseEnter, and.phaseEnd
Schedule options include from, to, interval, placement, except, also, dayOfMonth, and endOfMonth. See Time, phases, and schedules.
Entities and calculations
entity(options?)and the.asset,.party,.bundle, and.referencefactories. Optional properties includeowner,parent,fields,attributes, andlifecycle.ownerlinks an entity to another entity and includes it in owner-scoped grouping;parentdefines the entity tree.attributesholds static facts such as a person's date of birth. The Household pack exportshousehold()for household entities.field({ initial, next? })stores a numeric value per period.next(previous, ctx)can calculate the next value.cashflow({ schedule?, amount?, direction?, owner?, agreement?, category?, currency?, active?, dependsOn?, readsInflowsFrom?, readsCashflowsFrom?, readsAccountsFrom?, readsHistoricalAccountsFrom? }). A basic cashflow normally declares a direction. A posting cashflow can omitowner; it inherits the owner if all booked accounts share one.directioncan be omitted when the cash account is selected or inferred, or when postings do not represent cash. A grouped flow can omit settings supplied by its group; compilation reports an error if a required setting is still missing. Flows follow model declaration order;dependsOnadds explicit dependencies.readsCashflowsFromorders a flow after the owner's cashflows, andreadsAccountsFromorders it after flows that post to selected accounts, excluding period-closing flows.readsHistoricalAccountsFromdeclares accounts a flow reads only from earlier periods, without adding same-period ordering edges.flowGroup({ flows, preconditions?, ...defaults })groups flows and nested groups, applies shared settings such asowner,schedule,direction, or dependencies, and can own named run inputs. A nested input likepreconditions: { openingAssets: { remainingLifeYears: input.integer(5) } }is discovered bydefineModel()and receives a stable dotted path based on its flow-group names. Values on an individual flow override the group. Put a group in the model'sflowsmap, or useflows(...items)to flatten groups and flows into an array fordefineModel({ flows }).statement({ name?, owner?, schedule, balances, balancingAccount?, dependsOn? })creates a flow group for importing absolute account values. Each{ account, value }entry targets an account balance. Astdaccountreference is registered automatically; ordinaryaccount()references must still be declared inaccounts. One-time schedules apply values once; recurring schedules spread the remaining adjustment over the remaining occurrences. Income values are positive, expense values are negative, and asset, liability, or equity values use positive normal balances; the engine converts them to signed postings. Untyped and transfer accounts use signed ledger values. SetbalancingAccountwhen the supplied entries do not balance; otherwise the statement requires a balanced set of account adjustments. See Import a historical statement.account({ owner?, side?: "owed" | "due", type?, initial?, category? })creates a balance.typecan beasset,liability,equity,income,expense, ortransfer. Posting rows inheritcategoryfrom their target account. Debits increase the signed balance and credits decrease it. For income and expense categories, reporting signs are reversed so income is positive and expenses negative. Other account categories follow signed movements.sideapplies tomovescashflows.stdaccount({ code, owner, initial? })creates or reuses a plan-backed account. The code is always a string, including numeric business codes such as"1930"and groups such as"19". It selects the account type and category from the Business or Personal account plan; person, household, and company entities can be owners. The compiler automatically registers accounts referenced by flows, statements, and supported domain packs. A matching explicit account keeps its name and opening balance. Declare an account inaccountsto choose its result name, retain an otherwise-unused opening balance, or register a reference used only inside a callback. Creating a parent while a more specific same-owner subaccount already exists is an error.cashflow({ debit, credit, schedule, amount, cashAccount? })is shorthand for two equal posting rows. Usebookings: [{ debit: account, amount }, { credit: account, amount }]for multiple rows. Each row sets exactly one side; amounts must be finite, non-negative, and balanced for each occurrence. If a row omitsamount, it inherits the cashflow's base amount or, for a triggered occurrence, the source amount. Each row inherits the account category; do not setcategoryon a posting cashflow.cashAccountmust be one of the booked accounts and its signed movement determines the flow's net cashflow. If exactly one booked account is an asset, it is inferred. Without a cash account or direction, a posting flow has zero net cashflow.ctx.amountexposes the base amount in booking callbacks.trigger(name?)creates a trigger reference.cashflow({ triggers })emits it for each occurrence. A subscriber usesschedule.onceAfterNoOfDays(days)and may specifytriggeredBy. When only one trigger type is emitted in the model, it can be inferred.ctx.sourcecontains the source flow, model name, optional trigger name, amount, currency, date, and period. Each received trigger creates an occurrence.state(label)andlifecycle({ states, initial, transitions?, onEnter? })define state. Actions includeactions.setState(entity, state),.setField(field, value),.activate(flow), and.deactivate(flow).event({ schedule?, when?, actions? })requires a schedule or condition.agreement({ subject, parties?, from?, to?, terms?, onStart?, onEnd? })attaches to an entity.partiesmaps roles to party entities. UseagreementTerm(value)for terms; declare cashflows separately.option({ subject?, direction?, currency?, schedule?, maxExercises?, when, payoff })creates a separate payoff series. Default direction is inflow and the default limit is one exercise. The condition must become true after being false; further exercises require another false-to-true transition.metric({ calculate, label?, unit?, periodLength?, frequency?, annualAggregation?, owner?, fiscalYearStartMonth? })declares a model metric. The optional label and unit are copied to its result series for presentation. The window length and frequency are counts of model periods. A metric callback receivesctx.metricPeriodwith the inclusive period indexes and dates; annual results are rolled up withsumorlast. When an owner is set, itsfiscalYearStartMonthentity attribute supplies the default year boundary.
Calculation callbacks are synchronous and receive a calculation context with period data, reference readers, state access, and optional draws. ctx.sumAccount(account, from, to, options?) totals the account's debit-minus-credit postings over inclusive dates; set excludePeriodClosing: true to ignore internal account-closing entries. A flow must list current-period dependencies in readsAccountsFrom; historical-only readers can use readsHistoricalAccountsFrom to avoid same-period dependency edges.
Runs and packs
runMonteCarlo(model, { trials, seed, ...runOptions })summarizes numeric metrics across draws and returns per-trial and overall runtime timings.runScenarios(model, scenarios, runOptions?)returns{ name, result }entries, each with its own result timings. The array also exposes atimingsproperty with per-scenario measurements and totals. Each scenario has a name and optionaloverridesmap keyed byInputReference; map values accept scalar inputs and curve series. Put scenario-specific inputs in the maps; shared run options apply across scenarios. Passtrialswithseedin run options to execute Monte Carlo simulations for every scenario.definePack({ name, version, extract })creates a reusable pack;extractPacks(model, packs)adds its declarations to the model.
The Household pack requires the Personal account plan. It exports household() and householdBudget({ household, members, funding? }). Enabling the module makes the declarations available; the model author explicitly creates a budget component for each selected household. Missing budget expense and cash accounts are resolved from the Personal plan during compilation. See the Household pack guide. The SIE import pack requires the Business account plan and exposes sieStatement({ owner, sieContent }) to apply the latest imported account balances at the end of their SIE period.
Period order and actions
Within a period, the engine opens period state, updates fields, applies lifecycle transitions and agreement boundaries, evaluates events, calculates cashflows in dependency order and posts movements, then evaluates options and period-end calculations. Actions cannot change periods already calculated. ctx.previous(accountRef) reads the account's opening balance; closing balances are available from result.account(accountRef).
One current typing mismatch: runtime expects lifecycle onEnter entries as { state, actions }, while TypeScript currently declares Action[]. See Errors and limits.