the-guild-org / the-guild-org/apollo-angular

Defer doesn't work with multipart/mixed or text/event-stream

Open
#2,069 14 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

help wanted
Dominant language
TypeScript
Stars
1.5k
Forks
309
Avg merge
1h 45m
Merged PRs (30d)
3

Description

Describe the bug

Schema:

schema {
  query: Query
}

type Query {
  loadFoo: Foo!
}

type Foo {
  name: String!
}


"The `@defer` directive may be provided for fragment spreads and inline fragments to inform the executor to delay the execution of the current fragment to indicate deprioritization of the current fragment. A query with `@defer` directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred is delivered in a subsequent response. `@include` and `@skip` take precedence over `@defer`."
directive @defer(
  "Deferred when true."
  if: Boolean,
  "If this argument label has a value other than null, it will be passed on to the result of this defer directive. This label is intended to give client applications a way to identify to which fragment a deferred result belongs to."
  label: String
) on FRAGMENT_SPREAD | INLINE_FRAGMENT

I use @graphql-codegen/typescript-apollo-angular for codegen.
When using this query:

query foos {
  loadFoo {
    ... @defer {
      name
    }
  }
}

, generate code (which works fine) and then try to run it, I get the following error:

ERROR ApolloError: Http failure during parsing for http://localhost:4200/graphql
    at new ApolloError (index.js:28:28)
    at QueryManager.js:673:19
    at both (asyncMap.js:16:53)
    at asyncMap.js:9:72
    at new ZoneAwarePromise (zone.js:1411:21)
    at Object.then (asyncMap.js:9:24)
    at Object.error (asyncMap.js:18:26)
    at notifySubscription (module.js:137:18)
    at onNotify (module.js:176:3)
    at SubscriptionObserver.error (module.js:229:5)

While digging deeper, you can find another error message:

{
    "headers": {
        "normalizedNames": {},
        "lazyUpdate": null
    },
    "status": 200,
    "statusText": "OK",
    "url": "http://localhost:4200/graphql",
    "ok": false,
    "name": "HttpErrorResponse",
    "message": "Http failure during parsing for http://localhost:4200/graphql",
    "error": {
        "error": {
           "message": "No number after minus sign in JSON at position 3"
           "stack: "SyntaxError: No number after minus sign in JSON at position 3
    at JSON.parse (<anonymous>)
    at XMLHttpRequest.onLoad (http://localhost:4200/vendor.js:22534:41)
    at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:8235:171)
    at http://localhost:4200/vendor.js:34143:49
    at AsyncStackTaggingZoneSpec.onInvokeTask (http://localhost:4200/vendor.js:34143:30)
    at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:8235:54)
    at Object.onInvokeTask (http://localhost:4200/vendor.js:34454:25)
    at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:8235:54)
    at Zone.runTask (http://localhost:4200/polyfills.js:8037:37)
    at ZoneTask.invokeTask [as invoke] (http://localhost:4200/polyfills.js:8312:26)"
         },
        "text": "\r\n---\r\nContent-Type: application/json; charset=utf-8\r\n\r\n{\"data\":{\"loadFoo\":{\"__typename\":\"Foo\"}},\"hasNext\":true}\r\n---\r\nContent-Type: application/json; charset=utf-8\r\n\r\n{\"incremental\":[{\"data\":{\"name\":\"Bar\",\"__typename\":\"Foo\"},\"path\":[\"loadFoo\"]}],\"hasNext\":false}\r\n-----\r\n"
    }
}

Which might make sense, because the response is incremental. The Backends response looks like this (this is the multipart/mixed format):

---
Content-Type: application/json; charset=utf-8

{"data":{"loadFoo":{"__typename":"Foo"}},"hasNext":true}
---
Content-Type: application/json; charset=utf-8

{"incremental":[{"data":{"name":"Bar","__typename":"Foo"},"path":["loadFoo"]}],"hasNext":false}
-----

If I try to use text/event-stream the backend anwers like this:

event: next
data: {"data":{"sanctuaryById":{"kennels":[{"id":"ccce9657-fec8-4ce0-9d7e-0160e9cec514","name":"Test","__typename":"Kennel"}],"__typename":"Sanctuary"}},"hasNext":true}

event: next
data: {"incremental":[{"data":{"name":"Dingens","__typename":"Sanctuary"},"path":["sanctuaryById"]}],"hasNext":false}

event: complete

but then the Apollo Error looks like this:

{
    "headers": {
        "normalizedNames": {},
        "lazyUpdate": null
    },
    "status": 200,
    "statusText": "OK",
    "url": "http://localhost:4200/graphql",
    "ok": false,
    "name": "HttpErrorResponse",
    "message": "Http failure during parsing for http://localhost:4200/graphql",
    "error": {
        "error": {
          "message": "Unexpected token 'e', "event: nex"... is not valid JSON"
          "stack":"SyntaxError: Unexpected token 'e', "event: nex"... is not valid JSON
    at JSON.parse (<anonymous>)
    at XMLHttpRequest.onLoad (http://localhost:4200/vendor.js:22534:41)
    at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:8235:171)
    at http://localhost:4200/vendor.js:34143:49
    at AsyncStackTaggingZoneSpec.onInvokeTask (http://localhost:4200/vendor.js:34143:30)
    at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:8235:54)
    at Object.onInvokeTask (http://localhost:4200/vendor.js:34454:25)
    at _ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:8235:54)
    at Zone.runTask (http://localhost:4200/polyfills.js:8037:37)
    at ZoneTask.invokeTask [as invoke] (http://localhost:4200/polyfills.js:8312:26)"
        },
        "text": "event: next\ndata: {\"data\":{\"loadFoo\":{\"__typename\":\"Foo\"}},\"hasNext\":true}\n\nevent: next\ndata: {\"incremental\":[{\"data\":{\"name\":\"Bar\",\"__typename\":\"Foo\"},\"path\":[\"loadFoo\"]}],\"hasNext\":false}\n\nevent: complete\n\n"
    }
}

I tested this with the react-client and it simply works. Maybe it's a bug in the codegen? I didn't use Codegen in the react test.

To Reproduce

Create a simple Backend with HotChocolate https://chillicream.com/docs/hotchocolate/v13 and defer any field. It doesn't work with the simplest case. If you want I can provide you one.

Expected behavior

Defer should work.

Environment:

├── @angular/cli@16.2.1
├── @angular/core@16.2.3
├── @apollo/client@3.8.2
├── apollo-angular@5.0.1
├── graphql@16.8.0
└── typescript@5.1.3
    "@graphql-codegen/add": "^5.0.0",
    "@graphql-codegen/cli": "^5.0.0",
    "@graphql-codegen/typescript-apollo-angular": "^3.5.6",
    "@graphql-codegen/typescript-operations": "^4.0.1",

Additional context

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the issue with the listed Angular, Apollo Client, and GraphQL versions, using the Hot Chocolate defer example and comparing it with the working React client. Inspect how the Angular client handles multipart/mixed and text/event-stream responses; done means deferred responses are consumed without JSON parsing errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
angular, graphql, typescript
Domain
api, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.