fix(11-05): sync single-statement and plain-transaction writes, type engine status errors

- pin the sync callbacks before gorm:commit_or_rollback_transaction: an
  After-only anchor is appended past the commit and lagoon's after-commit
  flush, so single-statement writes never synced
- give the gate and the document builder a clean session: Session with NewDB
  and a Context clones the write's statement, and a later WithContext queried
  through the written model's table
- typesense.StatusError carries method, path and status, never the body
- README: sync semantics, the three gates, delete on soft delete, and the
  SQL re-gate required of SearchIDs callers
This commit is contained in:
Jakub Zych
2026-09-30 13:05:10 +02:00
parent 3e1f3e6a1b
commit 9543e6508c
5 changed files with 63 additions and 9 deletions

View File

@@ -138,27 +138,33 @@ func (s *Service) Logger() *slog.Logger {
return s.log
}
// commitCallback is GORM's commit of a transaction it opened itself.
const commitCallback = "gorm:commit_or_rollback_transaction"
// installCallbacks registers the sync callbacks on gdb, replacing earlier
// ones so a handle shared by several apps syncs through the most recent
// service.
// service. Each runs after the model's own after hook and before GORM
// commits a single-statement write: GORM appends a callback that names only
// an After anchor to the end of the chain, past the commit and past
// lagoon's after-commit flush, where the registered work would never run.
func (s *Service) installCallbacks(gdb *gorm.DB) error {
cb := gdb.Callback()
if cb.Create().Get(CallbackAfterCreate) == nil {
if err := cb.Create().After("gorm:after_create").Register(CallbackAfterCreate, s.afterCreate); err != nil {
if err := cb.Create().After("gorm:after_create").Before(commitCallback).Register(CallbackAfterCreate, s.afterCreate); err != nil {
return err
}
} else if err := cb.Create().Replace(CallbackAfterCreate, s.afterCreate); err != nil {
return err
}
if cb.Update().Get(CallbackAfterUpdate) == nil {
if err := cb.Update().After("gorm:after_update").Register(CallbackAfterUpdate, s.afterUpdate); err != nil {
if err := cb.Update().After("gorm:after_update").Before(commitCallback).Register(CallbackAfterUpdate, s.afterUpdate); err != nil {
return err
}
} else if err := cb.Update().Replace(CallbackAfterUpdate, s.afterUpdate); err != nil {
return err
}
if cb.Delete().Get(CallbackAfterDelete) == nil {
if err := cb.Delete().After("gorm:after_delete").Register(CallbackAfterDelete, s.afterDelete); err != nil {
if err := cb.Delete().After("gorm:after_delete").Before(commitCallback).Register(CallbackAfterDelete, s.afterDelete); err != nil {
return err
}
} else if err := cb.Delete().Replace(CallbackAfterDelete, s.afterDelete); err != nil {