The type RelTime -> Trigger ()
, as suggested by the error message, is a function that takes a RelTime
and returns a trigger; it is not a trigger itself, nor will the trigger runner summon an argument to pass to that function.
The same goes for the Entitlement
trigger; you can see in FinLib
’s tests that we must actually supply the arguments in order to get a “real” trigger out of it. Just as you see there, you can do the same by adding a separate top-level definition of type Trigger ()
like myTrigger = timedTrigger (days 1)
.
Once you have that working, you will note that rule
is executed for various ledger events, not only when the heartbeat frequency triggers. For example, if you write your timedRule
to always create a contract, then you will find that your heartbeat causes a contract to be created, which causes the rule to be triggered again, which causes another contract to be created, which causes…
To understand how to deal with that, imagine that your trigger is going to go off in a couple minutes, but you restart the service. Is it appropriate that you wait for a whole day for the next event to fire? Once you have the basics working here, here is what I would suggest:
- Place in
registeredTemplates
only those that should “trigger” your rule. If that is “none”, then the very template you are creating contracts of in this rule is probably a good choice.
- Before you create your contract, check whether the contract you mean to create is already there, perhaps using
query
or queryContractKey
. A trigger rule should always be written in a way that immediately running it again won’t have any effect. (Using “the current day” alongside the party as a key might be a good choice.)
- If possible, arrange for your contract creation to be the correction to ledger state, rather than a heartbeat interaction. That is what triggers are best at, and it is very easy to naturally satisfy (2) if you take this design approach.
- If you cannot arrange for this to be a reaction to ledger events instead, run your heartbeat significantly more frequently than daily. Perhaps every 15 or 30 minutes will do. Following (2) you should already be checking that “today’s” contract has been created, and this handily does away with the “server restart” problem.
- Thinking in ledger state also solves a different problem, which you will otherwise have to think about: what if you take the server down for a couple days? In most cases, you will want to “catch up” with multiple contract creation, but I can’t say whether that is what your goal is. It is the natural result implied by the “correction to ledger state” model, because what you do is check for contracts you haven’t “dealt with” in your trigger, and just deal with all of them. In the “server down for a couple days” scenario, this would mean a backlog of contracts that haven’t been “dealt with”, in which case your trigger just handles all of them in one go. Consider an “expiry” trigger, for example. You would not merely expire things that have expired in the past day; you would expire all items whose expiration time has passed, no matter how long ago.
The heartbeat mechanism is not meant to be as complicated as cron
, with all its options and sophisticated configuration features that solve the above questions in different ways; it is merely a simple complement to what triggers are really good at, which is dealing with ever-changing ledger state.
If it turns out that you have more complicated wallclock-based semantics in mind, and cannot deal with them by means of examining ledger state changes, then a different tool might be called for, be it cron
or something more “modern”, so to speak. If I have accounted for all potential issues with whatever you have in mind in my suggestions above, then triggers are probably sufficient to solve your problem.