Skip to content

Conversation

@ardatan
Copy link
Owner

@ardatan ardatan commented Sep 22, 2025

Closes #8757
Ref GW-506
Support polling in an interval by milliseconds

plugins:
  - live-query:
      invalidations:
        - pollingInterval: 10000 # Polling interval in milliseconds
          invalidate:
            - Query.products

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 22, 2025

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added polling-based invalidation with configurable millisecond intervals for the live-query plugin, allowing automatic query refreshes on a schedule.
    • Made the field parameter optional in invalidation configuration.
  • Documentation

    • Updated live queries documentation with examples of configuring polling-based invalidation.

Walkthrough

This PR implements polling-based invalidation for the Live Query plugin, complementing the existing mutation-based approach. The feature adds an optional pollingInterval property to LiveQueryInvalidation configuration, enabling subscribers to refresh at specified millisecond intervals. Changes include config schema updates, polling logic in the plugin, a new disposable stack dependency, and documentation.

Changes

Cohort / File(s) Summary
Changesets
.changeset/@graphql-mesh_plugin-live-query-8793-dependencies.md, .changeset/social-camels-mate.md
Added changeset entries documenting the patch for @graphql-mesh/plugin-live-query and @graphql-mesh/types with polling interval feature support and new dependency on @whatwg-node/disposablestack@^0.0.6
Config Schema & Types
packages/legacy/types/src/config-schema.json, packages/legacy/types/src/config.ts, packages/plugins/live-query/yaml-config.graphql
Updated LiveQueryInvalidation interface: made field optional, added new optional pollingInterval property (integer in milliseconds), and added @md annotations to GraphQL type definitions
Plugin Implementation
packages/plugins/live-query/package.json, packages/plugins/live-query/src/useInvalidateByResult.ts
Added @whatwg-node/disposablestack dependency; updated plugin to support polling-based invalidation with interval scheduling, timer lifecycle management, and Disposable interface implementation
Documentation
website/src/pages/docs/plugins/live-queries.mdx
Added new "Polling Based Invalidation" section with YAML configuration examples demonstrating pollingInterval usage
Build Configuration
package.json
Updated generate-config-schema script to include cleanup step removing generated markdown directory before schema generation
Examples
examples/v1-next/integrations/fastify/supergraph.graphql
Minor whitespace and formatting adjustments with no semantic changes

Sequence Diagram

sequenceDiagram
    participant Config as Configuration
    participant Plugin as Live Query Plugin
    participant Timer as Interval Timer
    participant PubSub as Mesh PubSub
    participant Subscriber as Subscriber

    Config->>Plugin: Load LiveQueryInvalidation config
    
    alt pollingInterval provided
        Plugin->>Timer: Schedule setInterval(pollingInterval)
        loop Every pollingInterval ms
            Timer->>Plugin: Trigger invalidation check
            Plugin->>PubSub: Publish invalidation paths
            PubSub->>Subscriber: Push updated data
        end
    else field provided (existing behavior)
        Plugin->>Plugin: Track mutation factories by field
        Plugin->>PubSub: Subscribe to mutation results
        Note over PubSub: On mutation match
        PubSub->>Subscriber: Push updated data
    end
    
    Plugin->>Plugin: On destroy: clear timers & unsubscribe
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Focus areas for review:
    • Polling interval validation and timer lifecycle management in useInvalidateByResult.ts
    • Proper cleanup of intervals on plugin destruction (Disposable interface)
    • Schema changes maintaining backward compatibility (field now optional)
    • Dependency on @whatwg-node/disposablestack and its correct usage

Possibly related PRs

  • #8793 — Implements the same polling interval feature for the Live Query plugin with identical config schema and implementation changes

Suggested reviewers

  • dotansimha

Poem

🐰 A rabbit hops through polling times,
With intervals in perfect rhymes,
No more just mutations to rely,
Now queries refresh as time goes by!
Disposables disposed with care,
Fresh data floats through the air!

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title feat(live-query): pollingInterval`` directly and clearly summarizes the main change—adding support for a pollingInterval configuration option to the live-query plugin. The title uses appropriate conventional commit formatting (feat), specifies the affected component (live-query), and names the specific feature being introduced. It is concise, specific enough for someone scanning history to understand the core change, and avoids vague terminology.
Linked Issues Check ✅ Passed The code changes align with the primary requirements from issue #8757. The PR implements a polling-based invalidation mechanism via the new pollingInterval configuration option (added to LiveQueryInvalidation in config schema, types, and GraphQL definitions). The implementation retains field-based mutation invalidation alongside polling support, enabling coexistence of both mechanisms as required. The useInvalidateByResult.ts file has been modified to schedule polling intervals using setInterval and publish invalidations on a timer. Documentation and generated markdown files have been added to describe the new feature. The optional caching optimization mentioned in the issue is not implemented, though it was explicitly marked as optional ("could consider").
Out of Scope Changes Check ✅ Passed The changes are appropriately scoped to the feature objectives. All meaningful code modifications directly support adding pollingInterval support: type definitions and config schemas are updated to include the new field, the plugin implementation is extended to handle polling intervals, necessary dependencies are added (@whatwg-node/disposablestack), and documentation is updated accordingly. Minor whitespace and formatting adjustments in examples/v1-next/integrations/fastify/supergraph.graphql are incidental and do not constitute meaningful out-of-scope work.
Description check ✅ Passed The pull request description clearly describes the feature being added: polling-based invalidation for live queries with a millisecond interval, including a specific YAML configuration example.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfde1e2 and 348b2d9.

📒 Files selected for processing (9)
  • .changeset/@graphql-mesh_plugin-live-query-8793-dependencies.md (1 hunks)
  • .changeset/social-camels-mate.md (1 hunks)
  • examples/v1-next/integrations/fastify/supergraph.graphql (3 hunks)
  • package.json (1 hunks)
  • packages/legacy/types/src/config-schema.json (2 hunks)
  • packages/legacy/types/src/config.ts (1 hunks)
  • packages/plugins/live-query/package.json (1 hunks)
  • packages/plugins/live-query/src/useInvalidateByResult.ts (3 hunks)
  • packages/plugins/live-query/yaml-config.graphql (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Apollo Federation Subgraph Compatibility Results

Federation 1 Support Federation 2 Support
_service🟢
@key (single)🟢
@key (multi)🟢
@key (composite)🟢
repeatable @key🟢
@requires🟢
@provides🟢
federated tracing🟢
@link🟢
@shareable🟢
@tag🟢
@override🟢
@inaccessible🟢
@composeDirective🟢
@interfaceObject🟢

Learn more:

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@graphql-mesh/cache-cfw-kv 0.105.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-file 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-inmemory-lru 0.8.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-localforage 0.105.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-redis 0.105.1-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-upstash-redis 0.1.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/compose-cli 1.5.2-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/fusion-composition 0.8.19-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/include 0.3.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/apollo-link 0.106.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/cli 0.100.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/config 0.108.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/graphql 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/grpc 0.108.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/json-schema 0.109.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/mongoose 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/mysql 0.105.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/neo4j 0.107.13-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/odata 0.106.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/openapi 0.109.22-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/postgraphile 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/raml 0.109.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/soap 0.107.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/supergraph 0.10.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/thrift 0.106.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/tuql 0.105.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/http 0.106.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/merger-bare 0.105.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/merger-stitching 0.105.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/migrate-config-cli 1.7.2-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/runtime 0.106.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/store 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-cache 0.105.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-encapsulate 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-extend 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-federation 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-filter-schema 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-hive 0.104.17-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-hoist-field 0.105.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-naming-convention 0.105.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-prefix 0.105.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-prune 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-rate-limit 0.105.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-rename 0.105.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-replace-field 0.105.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-resolvers-composition 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-transfer-schema 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-type-merging 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/types 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/urql-exchange 0.106.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/utils 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@omnigraph/json-schema 0.109.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@omnigraph/mysql 0.9.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@omnigraph/neo4j 0.11.13-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@omnigraph/odata 0.2.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@omnigraph/openapi 0.109.22-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@omnigraph/raml 0.109.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@omnigraph/soap 0.107.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@omnigraph/sqlite 0.8.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@omnigraph/thrift 0.9.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-deduplicate-request 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-hive 0.104.17-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-http-cache 0.105.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-http-details-extensions 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-jit 0.2.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-live-query 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-mock 0.105.17-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-newrelic 0.104.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-operation-field-permissions 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-operation-headers 1.4.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-rate-limit 0.105.3-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-response-cache 0.104.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-snapshot 0.104.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-statsd 0.104.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-grpc 0.3.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-mysql 0.9.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-neo4j 0.10.13-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-odata 0.2.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-rest 0.9.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-soap 0.10.16-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-sqlite 0.9.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-thrift 0.9.15-alpha-20251105205224-348b2d91bcf32f0c52abafc3535a9a638e0a1eb8 npm ↗︎ unpkg ↗︎

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

💻 Website Preview

The latest changes are available as preview in: https://d8cfb014.graphql-mesh.pages.dev

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (10)
.changeset/@graphql-mesh_plugin-live-query-8793-dependencies.md (1)

5-5: Fix markdownlint MD007 (unordered list indentation).

Unindent the bullet to top level. While here, small copy edit for clarity.

 --- 
 "@graphql-mesh/plugin-live-query": patch
 --- 
-dependencies updates:
-  - Added dependency [`@whatwg-node/disposablestack@^0.0.6` ↗︎](https://www.npmjs.com/package/@whatwg-node/disposablestack/v/0.0.6) (to `dependencies`)
+Dependency updates:
+- Added dependency [`@whatwg-node/disposablestack@^0.0.6` ↗︎](https://www.npmjs.com/package/@whatwg-node/disposablestack/v/0.0.6) (to `dependencies`)
packages/legacy/types/src/config-schema.json (4)

2464-2476: Update description to reflect union (mutation or polling).

Current text implies only mutation-based invalidation.

Apply:

-          "description": "Invalidate a query or queries when a specific operation is done without an error (Any of: LiveQueryInvalidationByMutation, LiveQueryInvalidationByPolling)"
+          "description": "Invalidate queries either after a mutation completes without error or on a fixed polling interval (Any of: LiveQueryInvalidationByMutation, LiveQueryInvalidationByPolling)"

2504-2516: Grammar: “affect” not “effect”.

Fix description for clarity.

-          "description": "Path to the operation that could effect it. In a form: Mutation.something. Note that wildcard is not supported in this field."
+          "description": "Path to the operation that could affect it. In a form: Mutation.something. Note that wildcard is not supported in this field."

2519-2535: Constrain and document pollingInterval; describe invalidate targets.

Add units and a sane minimum to prevent accidental hot loops; clarify target format.

     "LiveQueryInvalidationByPolling": {
       "additionalProperties": false,
       "type": "object",
       "title": "LiveQueryInvalidationByPolling",
       "properties": {
         "pollingInterval": {
-          "type": "integer"
+          "type": "integer",
+          "minimum": 1,
+          "description": "Polling interval in milliseconds (must be >= 1)."
         },
         "invalidate": {
           "type": "array",
           "items": {
             "type": "string"
           },
-          "additionalItems": false
+          "additionalItems": false,
+          "description": "Schema coordinates to invalidate on each poll (e.g. \"Query.products\")."
         }
       },
       "required": ["pollingInterval", "invalidate"]
     },

2536-2536: Back-compat alias for renamed type.

If external tooling referenced #/definitions/LiveQueryInvalidation, keep a deprecated alias to avoid breakage.

     },
+    "LiveQueryInvalidation": {
+      "description": "Deprecated alias. Use LiveQueryInvalidationByMutation.",
+      "allOf": [{ "$ref": "#/definitions/LiveQueryInvalidationByMutation" }]
+    },
     "LiveQueryIndexBy": {
website/src/generated-markdown/LiveQueryInvalidationByPolling.generated.md (1)

2-3: Clarify units in docs (milliseconds).

Reflect schema units to reduce confusion.

-* `pollingInterval` (type: `Int`, required)
+* `pollingInterval` (type: `Int`, required) — interval in milliseconds
 * `invalidate` (type: `Array of String`, required)

Note: this file is generated—please update the JSON schema descriptions and re-generate.

website/src/generated-markdown/LiveQueryInvalidationByMutation.generated.md (1)

2-3: Grammar: “affect” not “effect”.

Consistency with schema text.

-* `field` (type: `String`, required) - Path to the operation that could effect it. In a form: Mutation.something. Note that wildcard is not supported in this field.
+* `field` (type: `String`, required) - Path to the operation that could affect it. In a form: Mutation.something. Note that wildcard is not supported in this field.

Note: update the schema description so regenerated docs pick this up.

packages/plugins/live-query/yaml-config.graphql (2)

43-49: Doc fix: “affect”, not “effect”.

Small wording tweak for clarity.

Apply:

-  Path to the operation that could effect it. In a form: Mutation.something. Note that wildcard is not supported in this field.
+  Path to the operation that could affect it. Use the form "Mutation.something". Wildcards are not supported.

51-60: Clarify semantics and constraints for polling invalidation.

Explicitly require a positive interval and pluralize the description of coordinates.

Apply:

-  """
-  Polling interval in milliseconds
-  """
+  """
+  Polling interval in milliseconds. Must be a positive integer (> 0).
+  """
...
-  """
-  Schema coordinate of the query to be polled
-  """
-  invalidate: [String!]!
+  """
+  Schema coordinates of the queries to invalidate on each interval (e.g., "Query.products").
+  """
+  invalidate: [String!]!

Also consider stating that the list must be non-empty to avoid no-op timers.

packages/plugins/live-query/src/useInvalidateByResult.ts (1)

42-47: Unsubscribe destroy listener on dispose, not only when destroy fires.

Eliminates a latent subscription leak if consumers dispose without publishing destroy.

Apply:

-  const id = pubsub.subscribe('destroy', () => {
+  let destroySubId: number | undefined;
+  destroySubId = pubsub.subscribe('destroy', () => {
     for (const timer of timers) {
       clearInterval(timer);
     }
-    pubsub.unsubscribe(id);
+    if (destroySubId != null) {
+      pubsub.unsubscribe(destroySubId);
+      destroySubId = undefined;
+    }
   });
...
-    [DisposableSymbols.dispose]() {
-      for (const timer of timers) {
-        clearInterval(timer);
-      }
-    },
+    [DisposableSymbols.dispose]() {
+      for (const timer of timers) {
+        clearInterval(timer);
+      }
+      if (destroySubId != null) {
+        pubsub.unsubscribe(destroySubId);
+        destroySubId = undefined;
+      }
+    },

Also applies to: 86-90

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc36e2e and 65f3b47.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • .changeset/@graphql-mesh_plugin-live-query-8793-dependencies.md (1 hunks)
  • .changeset/social-camels-mate.md (1 hunks)
  • packages/legacy/types/src/config-schema.json (3 hunks)
  • packages/legacy/types/src/config.ts (2 hunks)
  • packages/plugins/live-query/package.json (1 hunks)
  • packages/plugins/live-query/src/useInvalidateByResult.ts (3 hunks)
  • packages/plugins/live-query/yaml-config.graphql (1 hunks)
  • website/src/generated-markdown/LiveQueryIndexBy.generated.md (1 hunks)
  • website/src/generated-markdown/LiveQueryInvalidationByMutation.generated.md (1 hunks)
  • website/src/generated-markdown/LiveQueryInvalidationByPolling.generated.md (1 hunks)
  • website/src/pages/docs/plugins/live-queries.mdx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/plugins/live-query/src/useInvalidateByResult.ts (4)
packages/legacy/types/src/pubsub.ts (4)
  • MeshPubSub (14-24)
  • HivePubSub (6-6)
  • toMeshPubSub (164-175)
  • DisposableSymbols (135-137)
packages/legacy/types/src/config.ts (2)
  • LiveQueryInvalidationByMutation (2187-2193)
  • LiveQueryInvalidationByPolling (2194-2197)
packages/cache/inmemory-lru/src/index.ts (1)
  • DisposableSymbols (68-74)
packages/cache/redis/src/index.ts (1)
  • DisposableSymbols (132-134)
🪛 markdownlint-cli2 (0.18.1)
.changeset/@graphql-mesh_plugin-live-query-8793-dependencies.md

5-5: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)

🔇 Additional comments (6)
.changeset/@graphql-mesh_plugin-live-query-8793-dependencies.md (1)

5-5: Sanity check the dependency range and module format.

^0.0.6 effectively pins to 0.0.6 only; confirm that’s intended, and that the package’s module format (ESM/CJS) aligns with how the plugin imports it.

website/src/generated-markdown/LiveQueryIndexBy.generated.md (1)

2-3: LGTM. Clear and consistent with schema.

packages/legacy/types/src/config.ts (1)

2156-2159: No action required — JSDoc already includes polling

packages/legacy/types/src/config.ts (≈ lines 2156–2159) already documents LiveQueryInvalidationByMutation and LiveQueryInvalidationByPolling, so regeneration is not required.

Likely an incorrect or invalid review comment.

packages/plugins/live-query/yaml-config.graphql (2)

62-65: LGTM: annotate LiveQueryIndexBy with @md.

Public docs visibility improvement looks good.


41-41: Config mismatch: examples use invalidateByPolling but schema exposes invalidations (union).

Repo search returned no matches for invalidateByPolling/invalidations — unable to verify. Verify where polling is configured and either:

  • expose invalidateByPolling on LiveQueryConfig, or
  • update docs/examples to use the invalidations union shape (see packages/plugins/live-query/yaml-config.graphql:41).

Re-run locally: rg -n --glob '!/node_modules/' -S -e '\binvalidateByPolling\b' -e '\binvalidations\b' -e 'LiveQueryConfig' -e 'LiveQueryInvalidation'

packages/plugins/live-query/src/useInvalidateByResult.ts (1)

25-25: Nit: timer type is fine; keep it.

Set<ReturnType<typeof setInterval>> is portable across Node/DOM. No change needed.

Comment on lines 21 to 43
params.invalidations.forEach(liveQueryInvalidation => {
const rawInvalidationPaths = liveQueryInvalidation.invalidate;
const factories = rawInvalidationPaths.map(rawInvalidationPath =>
getInterpolatedStringFactory(rawInvalidationPath),
);
liveQueryInvalidationFactoryMap.set(liveQueryInvalidation.field, factories);
if ('pollingInterval' in liveQueryInvalidation) {
timers.add(
setInterval(() => {
pubsub.publish('live-query:invalidate', liveQueryInvalidation.invalidate);
}, liveQueryInvalidation.pollingInterval),
);
} else if ('field' in liveQueryInvalidation) {
liveQueryInvalidationFactoryMap.set(liveQueryInvalidation.field, factories);
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t overwrite same‑field invalidations; validate interval; avoid unnecessary factory creation.

  • Current set overwrites earlier entries for the same field. Merge factories instead.
  • Reject non‑positive pollingInterval to prevent hot loops.
  • Only build factories for field‑based rules.

Apply:

-  params.invalidations.forEach(liveQueryInvalidation => {
-    const rawInvalidationPaths = liveQueryInvalidation.invalidate;
-    const factories = rawInvalidationPaths.map(rawInvalidationPath =>
-      getInterpolatedStringFactory(rawInvalidationPath),
-    );
-    if ('pollingInterval' in liveQueryInvalidation) {
-      timers.add(
-        setInterval(() => {
-          pubsub.publish('live-query:invalidate', liveQueryInvalidation.invalidate);
-        }, liveQueryInvalidation.pollingInterval),
-      );
-    } else if ('field' in liveQueryInvalidation) {
-      liveQueryInvalidationFactoryMap.set(liveQueryInvalidation.field, factories);
-    }
-  });
+  for (const inv of params.invalidations) {
+    if ('pollingInterval' in inv) {
+      if (inv.pollingInterval <= 0) {
+        throw new Error('pollingInterval must be a positive integer (> 0).');
+      }
+      if (inv.invalidate.length === 0) {
+        params.logger.warn('Skipping polling invalidation with empty "invalidate" list.');
+        continue;
+      }
+      timers.add(
+        setInterval(() => {
+          pubsub.publish('live-query:invalidate', inv.invalidate);
+        }, inv.pollingInterval),
+      );
+    } else if ('field' in inv) {
+      const factories = inv.invalidate.map(p => getInterpolatedStringFactory(p));
+      const existing = liveQueryInvalidationFactoryMap.get(inv.field) ?? [];
+      existing.push(...factories);
+      liveQueryInvalidationFactoryMap.set(inv.field, existing);
+    }
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
params.invalidations.forEach(liveQueryInvalidation => {
const rawInvalidationPaths = liveQueryInvalidation.invalidate;
const factories = rawInvalidationPaths.map(rawInvalidationPath =>
getInterpolatedStringFactory(rawInvalidationPath),
);
liveQueryInvalidationFactoryMap.set(liveQueryInvalidation.field, factories);
if ('pollingInterval' in liveQueryInvalidation) {
timers.add(
setInterval(() => {
pubsub.publish('live-query:invalidate', liveQueryInvalidation.invalidate);
}, liveQueryInvalidation.pollingInterval),
);
} else if ('field' in liveQueryInvalidation) {
liveQueryInvalidationFactoryMap.set(liveQueryInvalidation.field, factories);
}
});
for (const inv of params.invalidations) {
if ('pollingInterval' in inv) {
if (inv.pollingInterval <= 0) {
throw new Error('pollingInterval must be a positive integer (> 0).');
}
if (inv.invalidate.length === 0) {
params.logger.warn('Skipping polling invalidation with empty "invalidate" list.');
continue;
}
timers.add(
setInterval(() => {
pubsub.publish('live-query:invalidate', inv.invalidate);
}, inv.pollingInterval),
);
} else if ('field' in inv) {
const factories = inv.invalidate.map(p => getInterpolatedStringFactory(p));
const existing = liveQueryInvalidationFactoryMap.get(inv.field) ?? [];
existing.push(...factories);
liveQueryInvalidationFactoryMap.set(inv.field, existing);
}
}
🤖 Prompt for AI Agents
In packages/plugins/live-query/src/useInvalidateByResult.ts around lines 27 to
41, the current loop always creates factories for every invalidation, overwrites
entries in liveQueryInvalidationFactoryMap when multiple rules target the same
field, and accepts non‑positive pollingInterval values; change it so that
factories are only created for field‑based rules (do not call
getInterpolatedStringFactory for polling‑type invalidations), when a rule has a
'field' merge the new factories with any existing array in
liveQueryInvalidationFactoryMap instead of replacing it, and when a rule has a
'pollingInterval' validate that pollingInterval is a positive number (reject or
throw if <= 0) before creating the timer; ensure timers.add only receives the
setInterval id and preserve existing behavior for publishing invalidation
values.

@LeftoversTodayAppAdmin
Copy link
Contributor

Hey @ardatan

I think I found an issue.

  • If I dont have any polling interval defined then have the mutation defined as field: 'Mutation.strapi_updateProduct' then the live query refreshes the payload on instantly when the mutation is called
  • If I have pollingInterval defined then the query updates every X milliseconds whether or not the mutation is called
  • However if I have both pollingInterval and field for mutation defined then it does not update when the mutation is called, it only updates on the polling interval

Repro:

  • Set polling interval to around 15000 (15 seconds)
  • Make an update directly in the DB, after 15 seconds the live query updates as expected
  • Now make a update by calling the mutation strapi_updateProduct, the live query does not update until the 15 second polling interval is complete

Expected outcome:

  • The query updates every X milliseconds or immediately when the invalidate mutation is called.
  • Either one of the conditions should trigger an update of the live query to maintain backwards compatibility in terms of behavior. Otherwise if the polling interval is long, say 60 seconds, then the live query wont update for a long time after the mutation to invalidate is called.

Please let me know if you need any more information from my side.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
packages/plugins/live-query/src/useInvalidateByResult.ts (3)

12-12: Import the Disposable type explicitly.

The return type at line 26 uses Disposable, but only DisposableSymbols is imported. Add an explicit type import to ensure type safety.

Apply:

-import { DisposableSymbols } from '@whatwg-node/disposablestack';
+import { DisposableSymbols, type Disposable } from '@whatwg-node/disposablestack';

29-29: Guard against pubsub being undefined.

The toMeshPubSub function may return undefined, but the result is used without a guard. Add a check to prevent runtime errors.

Apply:

 const pubsub = toMeshPubSub(params.pubsub);
+if (!pubsub) {
+  throw new Error('Live Query plugin requires a valid pubsub instance.');
+}

30-49: Multiple issues with invalidation processing.

Several problems exist in this loop:

  1. Factories are created for all invalidations (line 32-34) but only used for mutation-based ones
  2. No validation that pollingInterval is positive
  3. Using set() at line 47 overwrites previous entries for the same field instead of merging
  4. No check for empty invalidate arrays

Apply:

-  params.invalidations.forEach(liveQueryInvalidation => {
-    const rawInvalidationPaths = liveQueryInvalidation.invalidate;
-    const factories = rawInvalidationPaths.map(rawInvalidationPath =>
-      getInterpolatedStringFactory(rawInvalidationPath),
-    );
-
-    // Set up polling-based invalidation if pollingInterval is provided
-    if ('pollingInterval' in liveQueryInvalidation && liveQueryInvalidation.pollingInterval) {
-      timers.add(
-        setInterval(() => {
-          pubsub.publish('live-query:invalidate', liveQueryInvalidation.invalidate);
-        }, liveQueryInvalidation.pollingInterval),
-      );
-    }
-
-    // Set up mutation-based invalidation if field is provided
-    if ('field' in liveQueryInvalidation && liveQueryInvalidation.field) {
-      liveQueryInvalidationFactoryMap.set(liveQueryInvalidation.field, factories);
-    }
-  });
+  for (const inv of params.invalidations) {
+    if (inv.invalidate.length === 0) {
+      params.logger.warn('Skipping invalidation with empty "invalidate" list.');
+      continue;
+    }
+
+    // Set up polling-based invalidation if pollingInterval is provided
+    if ('pollingInterval' in inv) {
+      if (inv.pollingInterval <= 0) {
+        throw new Error('pollingInterval must be a positive integer (> 0).');
+      }
+      timers.add(
+        setInterval(() => {
+          pubsub.publish('live-query:invalidate', inv.invalidate);
+        }, inv.pollingInterval),
+      );
+    } else if ('field' in inv) {
+      // Set up mutation-based invalidation - only create factories when needed
+      const factories = inv.invalidate.map(p => getInterpolatedStringFactory(p));
+      const existing = liveQueryInvalidationFactoryMap.get(inv.field) ?? [];
+      existing.push(...factories);
+      liveQueryInvalidationFactoryMap.set(inv.field, existing);
+    }
+  }
🧹 Nitpick comments (1)
packages/plugins/live-query/src/useInvalidateByResult.ts (1)

14-18: Simplify the hybrid type definition.

The intersection with Partial<> types doesn't add meaningful constraints since all values satisfy a partial. The type can be simplified to a plain union.

Apply:

-// Hybrid type that supports both polling and mutation-based invalidation
-type LiveQueryInvalidationHybrid = (
-  | YamlConfig.LiveQueryInvalidationByMutation
-  | YamlConfig.LiveQueryInvalidationByPolling
-) & Partial<YamlConfig.LiveQueryInvalidationByMutation> & Partial<YamlConfig.LiveQueryInvalidationByPolling>;
+// Union type that supports both polling and mutation-based invalidation
+type LiveQueryInvalidationHybrid =
+  | YamlConfig.LiveQueryInvalidationByMutation
+  | YamlConfig.LiveQueryInvalidationByPolling;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65f3b47 and 1d5c978.

📒 Files selected for processing (5)
  • examples/v1-next/integrations/fastify/supergraph.graphql (2 hunks)
  • packages/legacy/types/src/config-schema.json (3 hunks)
  • packages/legacy/types/src/config.ts (2 hunks)
  • packages/plugins/live-query/src/useInvalidateByResult.ts (3 hunks)
  • website/src/generated-markdown/LiveQueryInvalidationByPolling.generated.md (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • website/src/generated-markdown/LiveQueryInvalidationByPolling.generated.md
  • examples/v1-next/integrations/fastify/supergraph.graphql
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-22T17:11:35.160Z
Learnt from: enisdenjo
Repo: ardatan/graphql-mesh PR: 8716
File: packages/legacy/types/src/pubsub.ts:64-79
Timestamp: 2025-08-22T17:11:35.160Z
Learning: In the GraphQL Mesh codebase, when implementing MeshPubSub interfaces, the optional `options` parameter in the subscribe method may be intentionally omitted if it's unused and irrelevant for the specific implementation, even if it causes a minor TypeScript interface mismatch.

Applied to files:

  • packages/plugins/live-query/src/useInvalidateByResult.ts
🧬 Code graph analysis (1)
packages/plugins/live-query/src/useInvalidateByResult.ts (4)
packages/legacy/types/src/config.ts (2)
  • LiveQueryInvalidationByMutation (2187-2193)
  • LiveQueryInvalidationByPolling (2194-2203)
packages/legacy/types/src/pubsub.ts (4)
  • MeshPubSub (14-24)
  • HivePubSub (6-6)
  • toMeshPubSub (164-175)
  • DisposableSymbols (135-137)
packages/cache/inmemory-lru/src/index.ts (1)
  • DisposableSymbols (68-74)
packages/cache/redis/src/index.ts (1)
  • DisposableSymbols (132-134)
🔇 Additional comments (2)
packages/legacy/types/src/config-schema.json (1)

2464-2537: LGTM! Schema correctly models the union of invalidation types.

The JSON schema properly uses anyOf to represent the union of mutation-based and polling-based invalidations. Both types have clear descriptions and appropriate required fields.

packages/legacy/types/src/config.ts (1)

2154-2203: LGTM! TypeScript types correctly reflect the schema changes.

The union type for invalidations properly models both mutation-based and polling-based strategies. The interface definitions are clear and type-safe.

@LeftoversTodayAppAdmin
Copy link
Contributor

Hey @ardatan I just tested it with 0.104.15-alpha-20251103000320-0eb10bb65fedaf33990c08ccc9ba69fe2dd6d6a5 and I am able to validate both polling interval and mutation based invalidation work simultaneously!
Polling interval waits until the time has elapsed and mutation based invalidates instantly as expected.
These were all manual tests btw, please let me know if there is any additional testing you would like me to perform.

@ardatan ardatan force-pushed the polling-interval-live-query branch from 0eb10bb to 348b2d9 Compare November 5, 2025 20:50
@ardatan ardatan merged commit b077f20 into master Nov 5, 2025
16 of 17 checks passed
@ardatan ardatan deleted the polling-interval-live-query branch November 5, 2025 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request - Live queries - support polling interval based refresh

3 participants