diff --git a/internal/clients/live_query.go b/internal/clients/live_query.go index d929db1..4e65d34 100644 --- a/internal/clients/live_query.go +++ b/internal/clients/live_query.go @@ -142,6 +142,8 @@ func (c *liveQueryCache) trackObjectList(ctx context.Context, lq *runtime.LiveQu return err } +// Get implements cache.Cache. It wraps an underlying cache.Cache and sets up an Informer +// event handler that marks current live query as dirty if the current context has a live query. func (c *liveQueryCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { if err := c.Cache.Get(ctx, key, obj, opts...); err != nil { return err @@ -152,6 +154,8 @@ func (c *liveQueryCache) Get(ctx context.Context, key client.ObjectKey, obj clie return nil } +// List implements cache.Cache. It wraps an underlying cache.Cache and sets up an Informer +// event handler that marks current live query as dirty if the current context has a live query. func (c *liveQueryCache) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { if err := c.Cache.List(ctx, list, opts...); err != nil { return err diff --git a/internal/graph/extensions/live_query/json_patch.go b/internal/graph/extensions/live_query/json_patch.go index 78794b4..1fedd4b 100644 --- a/internal/graph/extensions/live_query/json_patch.go +++ b/internal/graph/extensions/live_query/json_patch.go @@ -172,6 +172,7 @@ func parseJSON(in []byte) (out any) { return out } +// CreateJSONPatch creates a JSON patch between two json values. func CreateJSONPatch(x, y []byte) ([]Operation, error) { if !json.Valid(x) || !json.Valid(y) { return nil, fmt.Errorf("invalid JSON") diff --git a/internal/graph/extensions/live_query/runtime/runtime.go b/internal/graph/extensions/live_query/runtime/runtime.go index 22b5a06..b2217c0 100644 --- a/internal/graph/extensions/live_query/runtime/runtime.go +++ b/internal/graph/extensions/live_query/runtime/runtime.go @@ -25,25 +25,40 @@ import ( type QueryResolveFn func(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler type CancelFn func() +// LiveQuery is set in context in generated liveQuery resolver. +// Resolvers that need to refresh the query can use GetLiveQuery() to +// check if the current response is a live query. +// They can then use LiveQuery.IsDone() and LiveQuery.Notify() to +// check if the live query is still running and trigger a live query refresh +// accordingly. type LiveQuery struct { ctx context.Context isDirty uint32 } +// IsDone returns true if live query finished. This is useful for stopping +// async listeners for discarded queries. func (l *LiveQuery) IsDone() bool { return l.ctx.Err() != nil } +// Notify marks live query as dirty, triggering a live query refresh on next +// throttle interval. func (l *LiveQuery) Notify() { atomic.StoreUint32(&l.isDirty, 1) } +// NeedsRefresh is a func that can be used to check if live query needs to be +// refreshed. It is used in generated live query resolver. type NeedsRefresh func() bool type liveQueryKey struct{} var liveQueryCtxKey = liveQueryKey{} +// WithLiveQuery sets LiveQuery on derived context and returns a callable for +// checking if live query needs to be refreshed. This is used in generated +// live query resolver to set up periodic live query refresh if changes occured. func WithLiveQuery(ctx context.Context) (context.Context, NeedsRefresh) { lq := &LiveQuery{ctx: ctx} return context.WithValue(ctx, liveQueryCtxKey, lq), func() bool { @@ -51,6 +66,7 @@ func WithLiveQuery(ctx context.Context) (context.Context, NeedsRefresh) { } } +// GetLiveQuery returns live query from context or nil if isn't set. func GetLiveQuery(ctx context.Context) *LiveQuery { if lq, ok := ctx.Value(liveQueryCtxKey).(*LiveQuery); ok { return lq