Where my mind initially goes is to have a pure function going from id -> records, and then pass those records off to a separate procedure that persists them to the database. The same records can then also be passed around to the UI or whatever else.
> Often this would get called several times for the same declaration id, usually generating new records, so it's easier to have the function return the id's rather than trying to reconstruct what it did.
Are the "new" records meaningfully different in some way, or are they just new instances in memory of what is fundamentally the same value? In the former case, there's state somewhere that's causing each one to be different (there is not a pure relationship from id -> record set). In the latter case, you don't actually need multiple instances (unless you expect them to be mutated by other code down the line and are guarding against side-effects, in which case you could defensively clone the pure function's result as needed instead of calling it multiple times).
Edit: it's possible I misunderstood, and that what's happening is you're inserting data into a database which has its own auto-incrementing IDs that it then returns to you. If so, the coupling between value creation and state/mutation exists in the database's API itself, and so is outside of your control. There's not much you can do in that case because you don't own that code.