This is a bit theoretical, but it might be an alternative to using shared logic.
Let's first assume that business logic is needed to sanitize user requests and convert them into operations on the database. Now let's try to work around that assumption.
For some applications, it might be possible to make the DB store and the client store have the same structure. In that case, we don't need to convert requests into any other form, so we don't need to apply business logic.
For example we could imagine that the DB state is a tree of data, and a user action is simply an update to one or more parts of the tree.
In a simple implementation, we could only allow users to make updates to their own isolated tree. In this case there would be no need for any business logic at all. Clients would read their user tree, and the public part of other users trees, and then do their best to render what those trees provide. Sanitising invalid or undesirable data could be done on the client.
In a more complex implementation, we might allow users to make updates to any part of the tree (including shared branches, or the branches of other users), but with an access layer on the server that would reject unauthorised updates.
In that case, there would be some access control logic on the server, and the client UI should try to only offer actions which the server will classify as legal. That can be seen as a form of duplication, but I believe that form of duplication is already common in many apps, before we even reach the question of optimistic updates.
So to reiterate, the idea is that the client's data store and the server's data store should have the same structure. Then the update operation can be constructed on the client, and performed at both ends. The server could approve or reject updates, but it would never need to convert them into a different form.
(Another somewhat related idea might be to retain a list of all user actions, and assume that the state of the DB would then be the accumulation of all legal user action requests. The DB would have logic to reject illegal actions, but for optimistic updates, a client would trust its own actions. This might have some nice features, but an obvious concern here would be scalability as the number of actions grows.)
This answer would be better if it could provide some more concrete examples. (I will have to get back to you on that!) But I hope at least this might offer an alternative perspective.
I had these musings while thinking about "offline first" design and PouchDB, a client-side DB that can be used offline, and which syncs (catches up) with the server-side DB when online.