renovate 757958f2d2
subscriptions / test (push) Has been skipped
subscriptions / vulnerabilities (push) Has been skipped
Release / release (push) Successful in 2m8s
pre-commit / pre-commit (push) Successful in 6m25s
subscriptions / coverage-baseline (push) Failing after 10m1s
chore(deps): update actions/setup-go action to v7 (#8)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/setup-go](https://github.com/actions/setup-go) | action | major | `v6` → `v7` |

---

### Release Notes

<details>
<summary>actions/setup-go (actions/setup-go)</summary>

### [`v7.0.0`](https://github.com/actions/setup-go/releases/tag/v7.0.0)

[Compare Source](https://github.com/actions/setup-go/compare/v7.0.0...v7.0.0)

##### What's Changed

- Migrate to ESM and upgrade dependencies by [@&#8203;priyagupta108](https://github.com/priyagupta108) in [#&#8203;763](https://github.com/actions/setup-go/pull/763)
- chore(deps): bump [@&#8203;actions/cache](https://github.com/actions/cache) to 6.2.0 by [@&#8203;philip-gai](https://github.com/philip-gai) in [#&#8203;771](https://github.com/actions/setup-go/pull/771)

##### New Contributors

- [@&#8203;philip-gai](https://github.com/philip-gai) made their first contribution in [#&#8203;771](https://github.com/actions/setup-go/pull/771)

**Full Changelog**: <https://github.com/actions/setup-go/compare/v6...v7.0.0>

### [`v7`](https://github.com/actions/setup-go/compare/v6.5.0...v7.0.0)

[Compare Source](https://github.com/actions/setup-go/compare/v6.5.0...v7.0.0)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDMuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI0My4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #8
Co-authored-by: Renovate Bot <renovate@unbound.se>
Co-committed-by: Renovate Bot <renovate@unbound.se>
2026-07-19 18:43:07 +00:00

subscriptions

Shared core for Shiny's cross-service read-your-writes GraphQL subscriptions (ADR-0009 tier-3, ADR-0012).

An entity shown in the UI is frequently projected from another service's event, so the owning service exposes a GraphQL subscription, drives it from a per-replica transient AMQP consumer, and pushes a lightweight poke once the change is visible in its own read view — the client then refetches the authoritative query. This package is the reusable, type-generic, hardened core of that pattern, extracted from the hand-rolled copies in authz-service (availableCompanies) and accounting-service (entryBasesChanged).

import "gitea.unbound.se/shiny/subscriptions"

// One registry per subscription, parameterised by the GraphQL payload type.
reg := subscriptions.New[model.EntryBasisChange](subscriptions.WithLogger(logger))

// Resolver: register a websocket consumer (key by company, user, …).
ch, cleanup, _ := reg.AddReceiver(companyID)
go func() { <-ctx.Done(); cleanup() }()
return ch, nil

// AMQP handler: gate the push on the read view, off the delivery goroutine.
reg.Submit(ev.CompanyID, func(ctx context.Context) (*model.EntryBasisChange, bool) {
    basis, err := readView.FindEntryBasisById(ctx, id)
    if err != nil {
        return nil, false // transient read error — keep waiting
    }
    return &model.EntryBasisChange{ID: id, Removed: removed}, removed == (basis == nil)
})

What the registry owns (so services don't re-roll it): the keyed subscriber map, non-blocking buffered fan-out (sends under the read lock so a close can't race a send), a bounded worker pool that runs the read-view gate off the AMQP delivery goroutine, and the retry/timeout budget. What stays in the service: the event→(key, payload) mapping and the Producer read-view closure.

The poke is idempotent and drop-tolerant — the client refetches on any poke — so the worker acks immediately and a dropped/duplicated poke self-heals. Wire an Observer to surface dropped/skipped pushes as metrics.

S
Description
Shared core for cross-service read-your-writes GraphQL subscriptions (ADR-0012)
Readme
69 KiB
v0.1.1
Latest
2026-08-29 11:36:59 +00:00
Languages
Go 100%