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 addDate configuration
    • 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 DbType columns
    • The DDL now uses the JSON DbType for JSON attributes
    • Reading is backwards compatible with the older TEXT based columns
  • Improved VSCode diagnostics
    • An error is now displayed when too few or too many parameters are passed into a function

Screenshot

New Functions & Features

  • Added paging support for foreign key attribute select inputs
    • By default no paging is applied
    • A new attribute enhancer name foreignKeyRows has 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 beforeRow parameter.
    • 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 totp function 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

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:

  1. 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] ) and len( [b] ) retrieve the length of the name properties for records a and b
    • The lambda returns the difference in length between the two names using sub( len( [a] ), len( [b] ) )
  2. Sorting Logic:
    • If a's name is shorter than b's, the lambda returns a negative value, and a is placed before b
    • If a's name is longer, the lambda returns a positive value, and b is placed before a

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" > >
//     >
// >