Using Functions
Functions are a fundamental part of Sentrie that allow you to perform operations, transform data, and extend functionality. Sentrie supports two types of functions: built-in functions that are always available, and TypeScript module functions that you import using the use statement.
Functions in Sentrie enable you to:
- Perform common operations using built-in reusable utilities
- Extend functionality by importing functions from TypeScript modules
- Optimize performance with function memoization
Function Call Syntax
Section titled “Function Call Syntax”Basic Syntax
Section titled “Basic Syntax”Functions are called using the standard function call syntax with parentheses:
let name = functionName(argument1, argument2, ...)Module Functions
Section titled “Module Functions”Functions imported from TypeScript modules are called using the module alias followed by a dot:
namespace com/example/auth
policy mypolicy { use { sha256, now } from @sentrie/hash as hash use { parse } from @sentrie/json as json
fact data: string
rule processData = default false { let hashValue = hash.sha256(data) let currentTime = hash.now() let parsed = json.parse(data) yield hashValue != "" and currentTime > 0 }
export decision of processData}TypeScript Module Functions
Section titled “TypeScript Module Functions”Sentrie allows you to import and use functions from TypeScript modules, including built-in @sentrie/* modules and your own local TypeScript files. This provides extensive functionality for cryptography, data manipulation, time operations, and more.
Importing Functions
Section titled “Importing Functions”Functions are imported using the use statement:
use { function1, function2 } from @sentrie/module as aliasBuilt-in Modules
Section titled “Built-in Modules”Sentrie provides comprehensive built-in TypeScript modules under the @sentrie/* namespace:
namespace com/example/crypto
policy security { use { sha256, md5 } from @sentrie/hash use { now, parse } from @sentrie/time as time use { isValid } from @sentrie/json as json
fact password: string fact timestamp: number
rule validatePassword = default false { let hash = sha256(password) let currentTime = time.now() yield hash != "" and currentTime > timestamp }
export decision of validatePassword}Local TypeScript Modules
Section titled “Local TypeScript Modules”You can also import functions from your own TypeScript files:
namespace com/example/utils
policy processing { use { calculateAge, validateEmail } from "./utils.ts" as utils
fact user: User
rule validateUser = default false { yield utils.calculateAge(user.birthDate) >= 18 and utils.validateEmail(user.email) }
export decision of validateUser}For detailed information about available TypeScript modules and their functions, see Using TypeScript and Built-in TypeScript Modules.
Function Memoization
Section titled “Function Memoization”Function memoization allows you to cache the results of function calls to improve performance for expensive operations. Memoization is particularly useful for functions that perform heavy computations or external calls.
Syntax
Section titled “Syntax”Memoization is enabled by appending ! to a function call:
functionName(args...)! -- Default TTL (5 minutes)functionName(args...)!300 -- Custom TTL in secondsDefault TTL
Section titled “Default TTL”When you omit the TTL value, memoization uses a default time-to-live of 5 minutes (300 seconds):
let result = expensiveFunction(data)! -- Cached for 5 minutesCustom TTL
Section titled “Custom TTL”You can specify a custom TTL in seconds:
let result = expensiveFunction(data)!60 -- Cached for 60 secondslet result = expensiveFunction(data)!3600 -- Cached for 1 hourMemoization Behavior
Section titled “Memoization Behavior”- TypeScript Module Functions: Memoization is fully supported and provides performance benefits for expensive operations
- Built-in Functions: While the syntax is supported, built-in functions are not actually memoized as they are already optimized and fast enough that caching provides minimal benefit
Example
Section titled “Example”namespace com/example/processing
policy dataProcessing { use { sha256 } from @sentrie/hash use { complexCalculation } from "./heavy-compute.ts" as compute
fact data: string
rule processData = default false { -- Memoize expensive computation for 10 minutes let hash = sha256(data)!600 let result = compute.complexCalculation(data)!600
yield hash != "" and result > 0 }
export decision of processData}Using Functions in Rules and Let Declarations
Section titled “Using Functions in Rules and Let Declarations”Functions can be used anywhere expressions are allowed, including in let declarations and rule bodies:
namespace com/example/complex
policy example { use { sha256 } from @sentrie/hash use { now } from @sentrie/time as time
fact userData: map[string]any fact items: list[string]
-- Policy-level let with function calls let itemCount = count(items) let timestamp = time.now()
rule processUser = default false { -- Rule-level let with function calls let hash = sha256(userData.id) let merged = merge(userData, {"processed": true})
yield hash != "" and itemCount > 0 }
export decision of processUser}Built-in Functions
Section titled “Built-in Functions”Sentrie provides a set of built-in functions that are always available without any imports. These functions are optimized for performance and are commonly used operations.
count(value) => number
Section titled “count(value) => number”Returns the number of elements in a collection or the length of a string.
The count function accepts a list, map, or string and returns the number of elements or characters.
Examples:
count([1, 2, 3])→3count("hello")→5count({"a": 1, "b": 2})→2
let items: list[string] = ["apple", "banana", "cherry"]let itemCount = count(items) -- Returns 3merge(map1, map2) => map[string]any
Section titled “merge(map1, map2) => map[string]any”Recursively merges two maps into a new map.
The merge function combines two maps, with values from the second map overwriting values from the first map. Nested maps are merged recursively rather than being replaced entirely.
Examples:
merge({"a": 1}, {"b": 2})→{"a": 1, "b": 2}merge({"a": {"x": 1}}, {"a": {"y": 2}})→{"a": {"x": 1, "y": 2}}
let userData = {"name": "Alice", "age": 30}let additionalData = {"age": 31, "role": "admin"}let combined = merge(userData, additionalData)-- Returns {"name": "Alice", "age": 31, "role": "admin"}error(format, args...) => error
Section titled “error(format, args...) => error”Short-circuits execution and returns an error with a formatted message.
The error function immediately stops execution and returns an error. It supports format strings similar to fmt.Printf in Go. If only one argument is provided, it’s treated as the error message directly.
Examples:
error("Access denied")error("Invalid value: %v", value)error("User %s not found", username)
rule validateAccess = default false when user.role is defined { if user.role != "admin" { error("Access denied: user must be admin") } yield true}as_list(value) => list[any]
Section titled “as_list(value) => list[any]”Normalizes "one-or-many" inputs by wrapping non-list values in a single-element list.
The as_list function takes a single value and ensures it’s a list. If the input is already a list, it returns it unchanged. If the input is not a list, it wraps it in a single-element list.
Examples:
as_list(42)→[42]as_list("hello")→["hello"]as_list([1, 2, 3])→[1, 2, 3]
let single_value = 42let as_list_value = as_list(single_value) -- Returns [42]
let already_list = [1, 2, 3]let unchanged = as_list(already_list) -- Returns [1, 2, 3]Note: If the input contains undefined values, the function returns undefined.
flatten(list, depth?) => list[any]
Section titled “flatten(list, depth?) => list[any]”Flattens nested lists to a controlled depth.
The flatten function takes a list and optionally a depth parameter, and flattens nested lists up to the specified depth. The default depth is 1 if not specified.
Examples:
flatten([[1, 2], [3, 4]])→[1, 2, 3, 4](default depth 1)flatten([[1, 2], [3, 4]], 1)→[1, 2, 3, 4]flatten([[[1, 2]], [[3, 4]]], 2)→[1, 2, 3, 4]flatten([1, 2, 3], 0)→[1, 2, 3](no flattening)
let nested = [[1, 2], [3, 4], [5]]let flattened = flatten(nested) -- Returns [1, 2, 3, 4, 5]
let deeply_nested = [[[1, 2]], [[3, 4]]]let flattened_deep = flatten(deeply_nested, 2) -- Returns [1, 2, 3, 4]Note: If the input contains undefined values, the function returns undefined.
flatten_deep(list) => list[any]
Section titled “flatten_deep(list) => list[any]”Recursively flattens nested lists to arbitrary depth.
The flatten_deep function takes a list and recursively flattens all nested lists, regardless of nesting depth.
Examples:
flatten_deep([[1, 2], [3, [4, 5]]])→[1, 2, 3, 4, 5]flatten_deep([[[1]], [[2, 3]], [4]])→[1, 2, 3, 4]
let deeply_nested = [[1, [2, [3, 4]]], [5, 6]]let fully_flattened = flatten_deep(deeply_nested) -- Returns [1, 2, 3, 4, 5, 6]Note: If the input contains undefined values, the function returns undefined.
normalise_list(value) => list[any]
Section titled “normalise_list(value) => list[any]”Normalizes messy list inputs with one level of nesting.
The normalise_list function first applies as_list to wrap non-list values, then flattens exactly one level of nesting. It errors if the input contains deeper than one level of nesting.
Examples:
normalise_list(42)→[42](wrapped, then no flattening needed)normalise_list([1, 2, 3])→[1, 2, 3](already flat)normalise_list([[1, 2], [3, 4]])→[1, 2, 3, 4](one level flattened)normalise_list([[[1, 2]]])→ Error (deeper than one level)
let mixed_input = [[1, 2], 3, [4, 5]]let normalized = normalise_list(mixed_input) -- Returns [1, 2, 3, 4, 5]Note: If the input contains undefined values, the function returns undefined.
See Also
Section titled “See Also”- Using TypeScript - Learn how to import and use TypeScript modules
- Built-in TypeScript Modules - Complete reference for all built-in modules
- Intermediate Values - Learn about
letdeclarations where functions are commonly used - Rules - Learn how to use functions in rule bodies
- Collection Operations - Learn about collection-specific operations