October 2024 (version 2.24.10.23)
reviewed: 7 October 2025
Enhancements
- Improved framework performance by reducing lookups within the call context, leading to faster execution
- Improved support for displaying explicit attributes within the report builder
- Improved support for the
addDateconfiguration- We now always return a date of Kind Local, ensuring proper handling of time zones
- Please note that this functionality is still experimental
- Added
app.timeZoneInfo(), returns a property bag containing information about the applications timezone
- Improved PostgreSQL support for
JSON DbTypecolumns- The DDL now uses the
JSON DbTypefor JSON attributes - Reading is backwards compatible with the older TEXT based columns
- The DDL now uses the
- Improved VSCode diagnostics
- An error is now displayed when too few or too many parameters are passed into a function

New Functions & Features
- Added paging support for foreign key attribute select inputs
- By default no paging is applied
- A new attribute enhancer name
foreignKeyRowshas been added to allow setting the page row count as needed
- Added functions for getting and setting a BO descriptors unique constraint
boDesc.getUniqueConstraint( [bo] )boDesc.setUniqueConstraint( [bo], 'value' )
- Added Word support for inserting new rows in the middle of a table by exposing the
beforeRowparameter.- This parameter represents the index at which the new row will be inserted
word.addRow( [proxy], 4 )
- Added Microsoft Entra ID (Azure Active Directory) as a new OAuth provider for easier integration with Microsoft identity services
- Added a new
totpfunction handler for TOTP (Time-Based One-Time Passwords) - Introduced lambda function support, enabling more flexible and powerful scripting capabilities (see below).
- Added new property bag functions
pb.sort( [pb] )pb.sort( [pb], [comparison] )
- Sorts the items within the property bag (in place), either alphabetically or by the specified comparison lambda, and returns itself
pb.filter( [pb], [predicate] )
- Gets a new property bag that contains the items that match the conditions defined by the specified predicate lambda
pb.first( [pb] )pb.first( [pb], [predicate] )
- Gets the first item in the property bag, either by position, or that satisfies the conditions defined by the specified predicate lambda
pb.last( [pb] )pb.last( [pb], [predicate] )
- Gets the last item in the property bag, either by position, or that satisfies the conditions defined by the specified predicate lambda
pb.pop( [pb] )
- Removes the last item from the property bag and returns it
pb.push( [pb], [value], [name] )
- Adds an item to the end of the property bag, and returns the property bags new count
pb.reverse( [pb] )
- Inverts the order of the items within the property bag (in place) and returns itself
pb.slice( [pb], [start], [end] )
- Gets a new property bag that contains the items between a start and end range (inclusive)
pb.keys( [pb] )
- Gets a new property bag that contains the keys for each item in the provided property bag
pb.groupBy( [pb], [path] )
- Gets a new grouped property bag based on the defined property path
Bug Fixes
- Fix to not write "_audit" based audits when the where clause is not by PK
- Previously this would write erroneous audits with a null anchor PK
- Async fixes:
- Fix for
sql.executeScalar()not having an active connection when called async - Improved exception stack handling for exceptions raised via a different thread
- Fix for
Lambda Functions
Lambda functions are a new and integral feature of the CaseMaster scripting language. They can be used anywhere a script function is normally used.
One of the most common use cases is within the new Property Bag functions.
Property Bag
Below is an example property bag containing four entries:
set('pb',
<
1: < name: 'Victoria Hinds' >
2: < name: 'Rob Calvert' >
3: < name: 'David Swann' >
4: < name: 'Bertus Dispa' >
>
)
Default Sorting Behaviour
When using the pb.sort() function without a lambda function, CaseMaster sorts the entries alphabetically. Specifically, in our example it orders the entries based on the name property of each property
pb.sort( [pb] )
This sorts the property bag alphabetically by the name property, producing the following result:
<
4: <name: "Bertus Dispa">,
3: <name: "David Swann">,
2: <name: "Rob Calvert">,
1: <name: "Victoria Hinds">
>
Custom Sorting with Lambda Functions
Lambda functions can be used to define custom sorting behaviour. By passing a lambda function to the pb.sort() function, you can specify how the properties should be compared during the sort.
In the following example, the lambda function sorts the property bag based on the length of the names:
pb.sort(
[pb],
function (a, b)
return sub(len([a]), len([b]))
end-function
)
Explanation:
- Lambda Definition:
- The lambda function is passed as an argument to
pb.sort() - The parameters a and b represent two records from the property bag being compared
len( [a] )andlen( [b] )retrieve the length of the name properties for recordsaandb- The lambda returns the difference in length between the two names using
sub( len( [a] ), len( [b] ) )
- The lambda function is passed as an argument to
- Sorting Logic:
- If
a's name is shorter thanb's, the lambda returns a negative value, andais placed beforeb - If
a's name is longer, the lambda returns a positive value, andbis placed beforea
- If
Result:
Sorting the pb by the length of the names produces the following order:
<
3: <name: "David Swann">,
2: <name: "Rob Calvert">,
4: <name: "Bertus Dispa">,
1: <name: "Victoria Hinds">
>
Experiment
Lambda functions are not limited to just the new Property Bag functions; they are an integral part of the scripting language.
You can use a lambda function as a parameter in any script function, and even store lambda functions in context as properties, allowing them to be invoked or passed around as parameters themselves. The possibilities are endless...
// Here [guid] is set to the literal string GUID returned by the lambda function
set( 'guid', function () return guid() end-function )
eq( [guid], [guid] ) // True
// Here [getGuid] is set to a lambda function property
set( 'getGuid', lambda.create( function () return guid() end-function ) )
eq( [getGuid], [getGuid] ) // False
// Lambda functions can also be invoked with parameters
set(
'sayHello',
lambda.create(
function (name)
return concat( "Hello, ", [name], "!" )
end-function
)
)
page.render(
<@page/content
lambda.invoke( [sayHello], bo.label( bo.user() ) )
lambda.invoke( [sayHello], "John Smith" )
lambda.invoke( [sayHello], "Hellen Jones" )
>
)
// You can even use existing functions as lambda functions
pb.sort( [pb], script.lambda( './my_sort_function' ) )
New Property Bag Function Examples
set(
'data',
<
1: < name: "Alice", age: 25, address: < city: "Nottingham" > >
2: < name: "Bob", age: 30, address: < city: "Manchester" > >
3: < name: "Charlie", age: 25, address: < city: "Nottingham" > >
4: < name: "Adam", age: 30, address: < city: "Manchester" > >
>
)
pb.filter(
[data],
function (item)
return and(
startsWith( pb.get( [item], 'name' ), "A" ),
ge( pb.get( [item], 'age' ), 30 )
)
end-function
)
// <
// 4: < name: "Adam", age: 30, address: < city: "Manchester" > >
// >
set(
'data',
<
< name: "Alice", age: 25, address: < city: "Nottingham" > >
< name: "Adam", age: 30, address: < city: "Manchester" > >
< name: "Bob", age: 30, address: < city: "Manchester" > >
< name: "Charlie", age: 25, address: < city: "Nottingham" > >
>
)
pb.last(
[data],
function (item)
return eq( pb.get( [item], 'age' ), 30 )
end-function
)
// < name: "Bob", age: 30, address: < city: "Manchester" > >
set(
'data',
<
< name: "Alice", age: 25, address: < city: "Nottingham" > >
< name: "Bob", age: 25, address: < city: "Manchester" > >
< name: "Charlie", age: 30, address: < city: "Nottingham" > >
< name: "David", age: 30, address: < city: "Manchester" > >
>
)
pb.groupBy( [data], 'age' )
// <
// 25: <
// < name: "Alice", age: 25, address: < city: "Nottingham" > >
// < name: "Bob", age: 25, address: < city: "Manchester" > >
// >,
// 30: <
// < name: "Charlie", age: 30, address: < city: "Nottingham" > >
// < name: "David", age: 30, address: < city: "Manchester" > >
// >
// >
pb.groupBy( [data], 'address/city' )
// <
// Nottingham: <
// < name: "Alice", age: 25, address: < city: "Nottingham" > >
// < name: "Charlie", age: 30, address: < city: "Nottingham" > >
// >,
// Manchester: <
// < name: "Bob", age: 25, address: < city: "Manchester" > >
// < name: "David", age: 30, address: < city: "Manchester" > >
// >
// >