Skip to content

Conversation

@ardatan
Copy link
Owner

@ardatan ardatan commented Sep 22, 2025

Closes #8757
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

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Live Query now supports polling-based invalidation with millisecond intervals. Configure pollingInterval and invalidate targets.
    • Invalidations config accepts both mutation-based and polling-based strategies.
  • Documentation

    • Added “Polling Based Invalidation” section with configuration examples.
    • Generated reference pages for related configuration types.
  • Chores

    • Added a new dependency to improve disposal utilities.
    • Added changesets for patch releases of relevant packages.

Walkthrough

Adds polling-based invalidation to the live-query plugin alongside existing mutation-based invalidation. Updates type schemas (TS, JSON, YAML GraphQL) to model a union of invalidation strategies, introduces disposal lifecycle for timers, adds a new dependency, and updates docs and generated markdown. Two changeset entries record versioning.

Changes

Cohort / File(s) Summary of Changes
Changesets
.changeset/@graphql-mesh_plugin-live-query-8793-dependencies.md, .changeset/social-camels-mate.md
Adds patch releases; introduces @whatwg-node/disposablestack@^0.0.6; documents new polling invalidation with YAML example.
Legacy Types Config (Schema + TS)
packages/legacy/types/src/config-schema.json, packages/legacy/types/src/config.ts
Renames mutation invalidation type; adds polling invalidation type; updates LiveQueryConfig.invalidations to a union (mutation or polling); enhances descriptions.
Live Query Plugin Logic
packages/plugins/live-query/src/useInvalidateByResult.ts
Extends plugin to support polling-based invalidation via setInterval; tracks timers; adds disposal via DisposableSymbols.dispose; broadens invalidation param types; ensures cleanup on destroy.
Live Query Plugin Package
packages/plugins/live-query/package.json
Adds dependency @whatwg-node/disposablestack: ^0.0.6.
YAML Config Schema
packages/plugins/live-query/yaml-config.graphql
Replaces single type with union `LiveQueryInvalidation = LiveQueryInvalidationByMutation
Docs — Generated Markdown
website/src/generated-markdown/LiveQueryIndexBy.generated.md, website/src/generated-markdown/LiveQueryInvalidationByMutation.generated.md, website/src/generated-markdown/LiveQueryInvalidationByPolling.generated.md
Adds/updates generated docs for index-by and both invalidation types, listing required fields.
Docs — Guide Page
website/src/pages/docs/plugins/live-queries.mdx
Adds “Polling Based Invalidation” section with YAML example; retains existing examples.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Plugin as LiveQuery Plugin
  participant PubSub as PubSub
  participant Timer as Polling Timer
  participant Schema as GraphQL Schema

  rect rgba(200,240,255,0.2)
    note over Plugin: Initialization
    Plugin->>PubSub: create pubsub
    Plugin->>Timer: setInterval(pollingInterval) for each polling rule
    Plugin-->>Plugin: register dispose to clear timers
  end

  rect rgba(220,255,220,0.2)
    note over Timer,PubSub: Polling-based invalidation
    Timer-->>PubSub: publish("live-query:invalidate", paths)
  end

  rect rgba(255,245,200,0.2)
    note over Schema,PubSub: Mutation-based invalidation
    Client->>Schema: execute Mutation.field
    Schema-->>PubSub: publish("live-query:invalidate", paths)
  end

  PubSub-->>Client: notify subscribers to re-evaluate queries

  rect rgba(255,220,220,0.2)
    note over Plugin,Timer: Disposal / Destroy
    Client->>Plugin: destroy / [dispose]()
    Plugin->>Timer: clearInterval(all)
    Plugin->>PubSub: unsubscribe destroy listener
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I twitch my nose at ticking time, hop-hop!
New polls drum softly—invalidate, then stop.
Mutations squeak, “me too!” in tandem cheer,
Timers cleared—no leaks to fear.
With schemas aligned and docs in tow,
Live queries bloom where fresh winds blow. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat(live-query): pollingInterval" is concise, focuses on the live-query feature, and clearly highlights the main change (adding a pollingInterval configuration), so it accurately reflects the changeset and PR objectives.
Description Check ✅ Passed The PR description references the related issue, states the intent to add millisecond polling for invalidation, and includes a matching YAML example, so it is directly related to and descriptive of the changeset.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch polling-interval-live-query

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

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

🚀 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.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-file 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-inmemory-lru 0.8.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-localforage 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-redis 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/cache-upstash-redis 0.1.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/compose-cli 1.4.19-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/fusion-composition 0.8.18-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/include 0.3.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/apollo-link 0.106.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/cli 0.100.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/config 0.108.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/graphql 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/grpc 0.108.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/json-schema 0.109.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/mongoose 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/mysql 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/neo4j 0.107.12-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/odata 0.106.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/openapi 0.109.21-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/postgraphile 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/raml 0.109.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/soap 0.107.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/supergraph 0.10.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/thrift 0.106.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/tuql 0.105.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/http 0.106.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/merger-bare 0.105.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/merger-stitching 0.105.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/migrate-config-cli 1.6.20-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/runtime 0.106.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/store 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-cache 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-encapsulate 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-extend 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-federation 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-filter-schema 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-hive 0.104.16-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-hoist-field 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-naming-convention 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-prefix 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-prune 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-rate-limit 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-rename 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-replace-field 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-resolvers-composition 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-transfer-schema 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transform-type-merging 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/types 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/urql-exchange 0.106.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/utils 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@omnigraph/json-schema 0.109.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@omnigraph/mysql 0.9.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@omnigraph/neo4j 0.11.12-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@omnigraph/odata 0.2.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@omnigraph/openapi 0.109.21-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@omnigraph/raml 0.109.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@omnigraph/soap 0.107.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@omnigraph/sqlite 0.8.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@omnigraph/thrift 0.9.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-deduplicate-request 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-hive 0.104.16-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-http-cache 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-http-details-extensions 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-jit 0.2.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-live-query 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-mock 0.105.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-newrelic 0.104.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-operation-field-permissions 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-operation-headers 1.4.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-rate-limit 0.105.2-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-response-cache 0.104.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-snapshot 0.104.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/plugin-statsd 0.104.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-grpc 0.3.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-mysql 0.9.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-neo4j 0.10.12-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-odata 0.2.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-rest 0.9.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-soap 0.10.15-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-sqlite 0.9.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎
@graphql-mesh/transport-thrift 0.9.14-alpha-20250922151901-2100ea6ff399fc0053cc422e3cc6ab17fe79be1c npm ↗︎ unpkg ↗︎

@github-actions
Copy link
Contributor

💻 Website Preview

The latest changes are available as preview in: https://e4847730.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 +8 to +15
```yaml
plugins:
- live-query:
invalidations:
- pollingInterval: 10000 # Polling interval in milliseconds
invalidate:
- Query.products
```
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

Align example with actual plugin key.

Use liveQuery to match config docs and schema.

 plugins:
-  - live-query:
+  - liveQuery:
       invalidations:
         - pollingInterval: 10000 # Polling interval in milliseconds
           invalidate:
             - Query.products
📝 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
```yaml
plugins:
- live-query:
invalidations:
- pollingInterval: 10000 # Polling interval in milliseconds
invalidate:
- Query.products
```
plugins:
- liveQuery:
invalidations:
- pollingInterval: 10000 # Polling interval in milliseconds
invalidate:
- Query.products
🤖 Prompt for AI Agents
In .changeset/social-camels-mate.md around lines 8 to 15, the plugin key in the
example uses "live-query" but the actual config/schema expects "liveQuery";
update the YAML example to use the correct key "liveQuery" (preserve the nested
structure and options such as invalidations, pollingInterval and invalidate
entries) so the docs match the implementation.

"@graphql-mesh/types": "^0.104.13",
"@graphql-mesh/utils": "^0.104.13",
"@n1ru4l/in-memory-live-query-store": "^0.10.0",
"@whatwg-node/disposablestack": "^0.0.6",
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

🧩 Analysis chain

Verify new dependency compatibility (Node 16+) and licensing.

Please confirm @whatwg-node/disposablestack@^0.0.6 fully supports Node >=16 (your engine range) and add it to any third‑party allowlist/SBOM if required.

Run:


🏁 Script executed:

#!/bin/bash
# Check engine constraints and latest version
npm view @whatwg-node/disposablestack version engines

# Inspect license
npm view @whatwg-node/disposablestack license

Length of output: 52


Block merge — @whatwg-node/disposablestack@^0.0.6 requires Node >=18.0.0

  • File: packages/plugins/live-query/package.json (line 45): "@whatwg-node/disposablestack": "^0.0.6"
  • npm metadata: engines.node = '>=18.0.0'; license = MIT.

Remediation: either bump repo engines to ">=18.0.0", pin to a version that supports Node 16 (if available), or remove/replace the dependency; if accepted, add to SBOM/third‑party allowlist.

🤖 Prompt for AI Agents
packages/plugins/live-query/package.json around line 45: the dependency
"@whatwg-node/disposablestack": "^0.0.6" requires Node >=18.0.0; fix by either
(A) bump the repository engines field to ">=18.0.0" (update root and any
package-level package.json engines, adjust CI workflows to use Node 18, and run
tests), or (B) pin this dependency to a version that supports Node 16 (find the
latest compatible version, update this package.json, run install and tests), or
(C) remove or replace the dependency with a Node-16-compatible equivalent and
update code accordingly; whichever option you choose, update the
SBOM/third‑party allowlist and document the change in the PR.

type MeshPubSub,
type YamlConfig,
} from '@graphql-mesh/types';
import { DisposableSymbols } from '@whatwg-node/disposablestack';
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

Import Disposable type to match DisposableSymbols usage and return type.

Avoid relying on ambient types; ensures the returned object structurally satisfies the library’s Disposable.

Apply:

-import { DisposableSymbols } from '@whatwg-node/disposablestack';
+import { DisposableSymbols, type Disposable } from '@whatwg-node/disposablestack';
...
-export function useInvalidateByResult(params: InvalidateByResultParams): Plugin & Disposable {
+export function useInvalidateByResult(params: InvalidateByResultParams): Plugin & Disposable {

Also applies to: 23-23

🤖 Prompt for AI Agents
In packages/plugins/live-query/src/useInvalidateByResult.ts around lines 12 and
23, the file imports only DisposableSymbols but uses/returns a Disposable
structurally; add an explicit import for the Disposable type from
'@whatwg-node/disposablestack' and use that type annotation for the returned
object (and any local variables/parameters) so the return type matches the
library's Disposable interface rather than relying on ambient structural typing.

export function useInvalidateByResult(params: InvalidateByResultParams): Plugin & Disposable {
const liveQueryInvalidationFactoryMap = new Map<string, ResolverDataBasedFactory<string>[]>();
const timers = new Set<ReturnType<typeof setInterval>>();
const pubsub = toMeshPubSub(params.pubsub);
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

Guard toMeshPubSub result (typed as possibly undefined).

Prevents a runtime crash and TypeScript error if the conversion ever yields undefined.

Apply:

-  const pubsub = toMeshPubSub(params.pubsub);
+  const pubsub = toMeshPubSub(params.pubsub);
+  if (!pubsub) {
+    throw new Error('Live Query plugin requires a pubsub instance.');
+  }
📝 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
const pubsub = toMeshPubSub(params.pubsub);
const pubsub = toMeshPubSub(params.pubsub);
if (!pubsub) {
throw new Error('Live Query plugin requires a pubsub instance.');
}
🤖 Prompt for AI Agents
In packages/plugins/live-query/src/useInvalidateByResult.ts around line 26, the
call to toMeshPubSub(params.pubsub) returns a value typed as possibly undefined
but is used directly; add a guard to handle undefined (either throw a clear
error or return early) and update typing control flow so subsequent code treats
pubsub as non-undefined. Specifically, check if the result is falsy immediately
after the call, log or throw a descriptive error referencing params.pubsub, and
only proceed when pubsub is defined so TypeScript and runtime are safe.

Comment on lines 27 to +41
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.

Comment on lines +95 to +107
### Polling Based Invalidation

You can also invalidate queries in a polling interval by specifying the schema coordinate of the
query to be polled.

```yaml filename=".meshrc.yaml"
plugins:
- live-query:
invalidations:
- pollingInterval: 10000 # Polling interval in milliseconds
invalidate:
- Query.products
```
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

Plugin key mismatch: use liveQuery (camelCase), not live-query.

All other examples and the Plugin schema use liveQuery. The current snippet will not load the plugin.

-plugins:
-  - live-query:
+plugins:
+  - liveQuery:
       invalidations:
         - pollingInterval: 10000 # Polling interval in milliseconds
           invalidate:
             - Query.products
🤖 Prompt for AI Agents
In website/src/pages/docs/plugins/live-queries.mdx around lines 95 to 107, the
YAML example uses the wrong plugin key "live-query"; update it to the camelCase
key "liveQuery" so it matches the plugin schema and other examples; keep the
rest of the YAML (pollingInterval and invalidate block) unchanged.

@LeftoversTodayAppAdmin
Copy link

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.

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

2 participants