Function declarations#
Functions express executable behavior. Use them for queries, commands, validation helpers, orchestration, and resolvers that turn configuration-table rows into decisions.
Declaration shape#
public async function post_invoice(
invoice_id: uuid,
posting_date: date = today()
): Invoice {
let invoice = load_invoice(invoice_id);
return invoice;
}A function can have annotations, an access modifier, function modifiers, a name, typed parameters, an optional return type, and a body.
| Part | Shape |
|---|---|
| Annotations | @audited |
| Access | public, private, protected, or internal |
| Modifiers | static, abstract, virtual, override, async, inline |
| Parameters | name: Type with optional = expression |
| Return | : Type, -> Type, : async Type, or -> async Type |
| Body | Block, semicolon, arrow statement, arrow expression, or arrow query |
Parameters#
Parameters are typed bindings. A parameter may include a default expression.
function find_vendor(
company_code_param: CompanyCode,
active_only: bool = true
): Vendor? {
return load_vendor(company_code_param, active_only);
}Use explicit parameter names for scope values such as company, tenant, process, document type, and effective date. Hidden globals make policy resolution harder to test.
Return types#
Return types can use : or ->. The return type can be marked async.
function get_vendor(id: VendorId): Vendor?;
function list_vendors(company_code: CompanyCode) -> Json;
function sync_vendor(id: VendorId) -> async Vendor;Use a non-null return type when missing data is a closed failure. For required configuration, prefer raising a declared message-backed error instead of returning nullable policy.
Block bodies#
Block bodies contain statements.
private function resolve_posting_policy(
company_code_param: CompanyCode,
document_type_param: DocumentType,
posting_date_param: date
): PostingPolicy {
select var policy: PostingPolicy
where PostingPolicy.company_code = company_code_param
and PostingPolicy.document_type = document_type_param
and PostingPolicy.active = true;
if (policy is null) {
raise message posting_policy_required(company_code_param, document_type_param);
}
return policy;
}Function bodies can include query statements, control flow, raises, try/catch, saves, and nested declarations when those statements are valid in the language.
Abstract declarations#
A semicolon body declares the signature without implementation.
public function approve_invoice(invoice_id: uuid): bool;Use abstract declarations for contracts or implementation points. Do not use them to hide configurable policy that should be visible as table data.
Arrow bodies#
Arrow bodies can be statements, expressions, or query statements.
function display_vendor(vendor: Vendor): string => vendor.vendor_name;
function deactivate_vendor(vendor: Vendor) => {
vendor.active = false;
save vendor;
}Use arrow expression bodies for small pure helpers. Use block bodies for commands that validate input, resolve policy, update state, or raise declared messages.
Query bodies#
Arrow bodies can return query statements directly.
function active_vendors(company_code_param: CompanyCode): Json =>
select *
from Vendor
where Vendor.company_code = company_code_param
and Vendor.active = true;
function delete_draft_invoice(invoice_id_param: uuid): Json =>
delete from Invoice
where Invoice.invoice_id == invoice_id_param
and Invoice.status == "draft"
returning invoice_id;For the full query surface, see Queries and DML.
Nested functions#
Function bodies can contain nested function declarations.
function normalize_vendor(vendor: Vendor): Vendor {
function normalize_name(name: string): string => name.trim();
vendor.vendor_name = normalize_name(vendor.vendor_name);
return vendor;
}Keep nested functions local to a single workflow. Shared rules, reusable validations, and cross-module behavior should be declared at module scope.
Policy boundary#
Functions are where configuration is resolved and enforced. They should not encode configurable policy as hardcoded branch trees.
let policy = resolve_posting_policy(company_code, document_type, posting_date);
if (policy.requires_approval && amount > policy.approval_limit) {
raise message invoice_approval_required(invoice_id);
}If thresholds, statuses, routes, reason codes, or workflow outcomes vary by tenant or process, read them from tables and fail closed when required configuration is missing.