Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Announcement: urql v4 Major Releases #3114

Open
kitten opened this issue Mar 31, 2023 · 15 comments
Open

Announcement: urql v4 Major Releases #3114

kitten opened this issue Mar 31, 2023 · 15 comments

Comments

@kitten
Copy link
Member

kitten commented Mar 31, 2023

It’s only been seven months since we’ve launched our urql v3 batch of major releases, but today, we’re even more excited to talk about our next batch of urql v4 releases!

What happened in the last episode of urql?
Expand for a quick summary of changes made in the prior urql v3 releases.

General changes

  • We dropped IE11 support and modernised our build output
  • We introduced stricter Variables generics typings across our bindings
  • Granular graphql imports have been removed since they caused build issues in rare cases

@urql/core

  • Not all ClientOptions are now reflects as properties on the Client
  • The createOperationContext method was removed from the Client
  • ClientOptions.url must now always be a string and defined

@urql/exchange-graphcache

  • Easier optimistic functions
    • The optimistic functions now may return objects that contain more, nested optimistic functions
    • We now use field names rather than aliases, so it’s easier to define reusable optimistic functions
  • Offline Mode Non-Blocking
    • The offline rehydration is now non-blocking and a network request would always be attempted first


Back in our last batch of major releases, little changed and little broke, relatively speaking. We chose to save up larger refactors and changes for when we'd be able to ship some big improvements along with them; and today's the day!

Symbol Legend:
We have a lot of changes to talk about, therefore, we’re not necessarily
grouping everything by whether it’s breaking or not, and will instead annotate
headings of sections according to whether they’re major, minor, or patch changes.

  • 💥: A major, breaking change, or an addition that may affect how urql used to work
  • ✨: A minor, feature change, which adds a new feature without breaking old code
  • 🦋: A patch, which often only improve what’s going on in the background

Table of Contents

After upgrading with your favourite package manager you may have duplicates of several packages, which you can fix by trying out the following command:

# for npm
npm dedupe
# for pnpm
pnpm dedupe
# for yarn
npx yarn-deduplicate

For the full release notes and changelogs, check the Release PR.


Sponsoring and Discord

In case you've missed it or don't come by the repository often, Hiya 👋 Welcome back!

We're also here to say that we now accept sponsorships on GitHub: https://github.com/sponsors/urql-graphql
Sponsorships are important to us of course, but they will take more of a central role in how this project sustains itself moving forward. We have more moving parts now than ever and want to dedicate as much time to the urql community as possible! 💖

We also now have an easier way to ask help and chat with us and other contributors, by joining our Discord: https://urql.dev/discord
Building a community on Discord allows us to also talk about and support you with our related projects and other libraries, like wonka and graphql-web-lite!

TSDocs... TSDocs everywhere ()

We’re replacing our API docs pages with TSDocs! 📚

Every one of our public API will now come with inline comments, called TSDocs, which give you a summary of what the API does, and they're in our opinion a big step forward from clunky, old API docs:

  • Any editor that supports them (typically via the Language Server Protocol) will show them on hover, alongside type hints. VSCode has especially nice visuals for them.
  • They give us space on every single library export to explain the API, deliver context, point out gotchas, and deliver the right information in the perfect place.
  • They easily link from API to API, so jumping between related types and functions, and reading multiple docs together becomes much easier.

We hope that this will ease the learning curve of having to learn urql's APIs, and reduces common misconceptions and questions from popping up early on, when you're starting to adopt urql.

We're preparing to launch a web app that allows you to browse the APIs of our packages as well. More news on this soon...

No more defaultExchanges (💥)

In prior versions we didn't need you to explicitly pass exchanges to the client we would automatically make these to be [dedupExchange, cacheExchange, fetchExchange].

We have now removed this and need you to explicitly pass us the exchanges, in doing so we have reduced the default budndle size for folks not using these exchanges. You can fix the missing exchanges by doing the following:

import { createClient, cacheExchange, fetchExchange } from '@urql/core'

const client = createClient({
  url: '',
  exchanges: [cacheExchange, fetchExchange],
})

A rewrite of our default fetch/HTTP transport ()

urql's default fetchExchange used to come with a humble but extensible set of features. In @urql/core@^3.0.0, we're moving and adding more features into the core package:

  • Multipart File Uploads are now supported without having to install @urql/exchange-multipart-fetch (a now deprecated package)
  • We now support the text/event-stream protocol (also known as GraphQL SSE for Incremental Delivery responses.
  • While we recently shipped fixes to our Incremental Delivery support, we continued to stabilise and refactor its support.
  • of course, the multipart/mixed response protocol is still supported as before.

With multipart/mixed and text/event-stream, we’re now supporting more protocol options for APIs, which enable APIs to use more transport methods for @defer, @stream, @live, and subscriptions and give API authors more options without having to add custom exchanges to urql.

Persisted Query Support is changing ()

As for, (Automatic) Persisted Queries support, this used to be implemented using the @urql/exchange-persisted-fetch library, which didn’t work for the subscriptionExchange, as it simply made a fetch/HTTP request on its own.

Instead, we’re deprecating the package and replacing it with @urql/exchange-persisted.

The API of this package will match the old one exactly, but it will only annotate query (and optionally, mutation) operations to have persisted query extensions on them.

Source Code

File Upload Support goes built-in ()

Multipart File Uploads are now supported in @urql/core and are activated when your variables contain a File or a Blob.

You won't have to install @urql/exchange-multipart-fetch anymore and this package has been deprecated.

hasNext, stale, and multiple results (💥)

@urql/core contains two subtle yet important changes to how OperationResults are defined and delivered in response to Operations.

In this version we're tightening our guarantees around results that are marked as hasNext: true and when results are marked as stale: true.

Any API transport that delivers multiple results may mark its ExecutionResults with hasNext: true. This for instance happens when:

  • A subscription, which hasn't been closed by your API, delivers new events.
  • A query or mutation are waiting for @defer-ed or @streasm-ed results.
  • A @live query is delivering updated results.

When the Client sees hasNext, it nows consistently knows to expect updated results.

Furthermore, stale: true, as before, indicates that the Client expects an updated API result for a given query soon. For instance, this may mean that an operation is being sent and will replace an existing result with a new one as soon as the API responds.

Knock-on changes to the subscriptionExchange (💥)

The subscriptionExchange had to be changed a little to accommodate the new @urql/exchange-persisted support above and to fix some issues we were seeing in how it's being used.

Internally, @urql/core/internal contains a makeFetchBody function (and other fetch utilities that are reusable) which constructs the JSON data that will be sent to the GraphQL API. This function is aware of how to handle persisted queries.

Unfortunately, our subscriptionExchange's forwardSubscription function was instead accepting an Operation-like structure. That's why this has changed to now accept a FetchBody as the first argument.

This helps transports like graphql-ws to function correctly, as they pass this input on 1:1 without filtering it, expecting it to basically be like our FetchBody structure, ready to be accepted by GraphQL APIs.

If you were previously relying on this first argument having a context property — the OperationContext — we're now instead passing the entire Operation as a second argument to forwardSubscription

@urql/core ditches the graphql package (💥)

@urql/core doesn’t rely on a peer dependency on graphql anymore.

When you installed @urql/core@^3.2.2, you will at least add 17.3kB (gzip) to your bundlesize, since it not only includes its own code but also parts of graphql (and wonka, our stream utilities.) We wouldn't consider this bad, but we can do better.

In @urql/core@^4.0.0, we’re replacing the parts of graphql we used to use (which includes parse, print, and GraphQLError). Instead, we’re now relying on a homegrown, spec-compliant implementation of the GraphQL query language, the @0no-co/graphql.web package.

This small change means @urql/core@^4.0.0 will only add at most 9.9kB (gzip) to your bundlesize!

How does this affect TypeScript and the standard graphql library?

Any output from @0no-co/graphql.web is tested (with 100% test coverage; if this puts you at ease) to be compatible with the reference output from the graphql library. This means all ASTs will remain compatible.

When you have the standard graphql library installed, all @urql/core APIs will also automatically accept types from graphql (e.g. import('graphql').DocumentNode) and @0no-co/graphql.web will additionally switch to using graphql’s AST types as well.

How do I reduce my bundlesize if I depend on graphql myself?

If any of the rest of your app uses graphql already, we do have another trick up our sleeves, if you want to avoid a slight increase in bundlesize of 2kB (gzip).

You can use the graphql-web-lite package and alias graphql imports to it.
The graphql-web-lite repostory contains instructions using which you can alias the graphql package in just a few minutes! ⏲️ We hope, even faster than it’d take you to make a coffee.

The graphql-web-lite package is built to slim down the default build of graphql, and is built against the graphql library, but also uses @0no-co/graphql.web internally. Once you’ve aliased graphql to graphql-web-lite, you’ll continue to be able to use its API, but keep size to a minimum.

Easier core-usage (🦋)

Sources now have .then() set which means that you can leverage await by default on sources returned by urql, this enable you to perform await client.mutation().

Sources also have .subscribe() now which accepts a callback that allows you to see all values passed back from the exchanges.

const { unsubscribe } = client.query().subscribe(operationResult => {
  console.log(operationResult)
})

No more need for the dedupExchange (🦋)

The dedupExchange has long been needed in @urql/core as a way to avoid sending multiple requests for operations subscribed to from multiple locations. This has now been added as default behavior to the client.

This means you can drop the dedupExchange all together, the exchange will be a noop in this major.

Integrations

Fixing @urql/svelte's missing subscription handler (💥)

When refactoring our Svelte bindings in a prior revision, we accidentally misplaced the handleSubscription support and it wasn't possible to merge subscription events like on our other bindings.

This has now been fixed and the subscriptionStore accepts it again as a second argument.

import { subscriptionStore, gql, getContextClient } from '@urql/svelte';

const todos = subscriptionStore({
  client: getContextClient(),
  query: gql`
    subscription {
      newNotification { id, text }
    }
  `,
}, function combineNotifications(notifications = [], data) {
  return [...notifications, data.newNotification];
});

urql and @urql/preact no longer create a default Client (💥)

In prior versions, urql and @urql/preact would create a default Client when no Provider was used in the tree. This by default would send GraphQL requests to /graphql. This wasn't really useful as you would need to add a Provider anyway when you went to production. We have removed this in this major and instead throw an error when we miss a Provider.

@kitten kitten pinned this issue Mar 31, 2023
@negezor
Copy link
Contributor

negezor commented Mar 31, 2023

File upload seems to be broken as I found the key {"__key":"<random>"} instead of null in operations.
https://github.com/jaydenseric/graphql-multipart-request-spec#multipart-form-field-structure

@kitten
Copy link
Member Author

kitten commented Mar 31, 2023

@negezor: I just checked this against our example for file uploads and it works just fine 😅 (although noting that the example API currently is down for uploads).
You can check the resulting request here: https://user-images.githubusercontent.com/2041385/229220689-6126358b-8bb8-4d74-bdc0-b27190d7eed2.png

The behaviour of variables serialisation hasn't changed, so File, Blob, and other non-serializable objects are still replaced by random (but identity stable) strings, i.e. {__key:"[string]"}, as you're seeing.

However, as long as a File and Blob is inside the request itself the actual GraphQL request will switch over to using a multipart request (as per the screenshot), which then specifies where in the variables the file should be inserted at, as per the spec.

(btw, If you'd like help, please open an issue, discussion or feel free to chat on Discord ✌️)

Edit: Ah, you know what; I didn't realise this but I believe the input file map should be a record of string: [string]. I have no idea why and I'm getting a little sick and tired of the spec being very handwavy, but I'll check whether that's the issue

Edit 2: Fix is out. I think it's pretty trivial, so I'll just yeet it out quickly 😄

@negezor
Copy link
Contributor

negezor commented Mar 31, 2023

@kitten Specifically, in my case, this does not work, I get a response from the server: Invalid files map: invalid type: string "variables.input.avatar", expected a sequence at line 1 column 29

If you look at the specification, null is expected there

Payload

Using the old multipartFetchExchange solves the problem. However, it has already been deprecated.

@kitten
Copy link
Member Author

kitten commented Mar 31, 2023

Again, please do stick to issues please for bug reports 😅
That said, while I didn't bother creating an issue for bug tracking for this, @urql/core@4.0.1 does fix this. This also really doesn't have anything to do with the non-null value in variables, but with the FormData's map value

@hawkeye64
Copy link

hawkeye64 commented Apr 11, 2023

My issue #3144 was closed and redirected here. I still don't understand what needs to be done to resolve the issue I am having. I don't think the docs have been updated. I'll be forced to roll back until I have a clear view of what needs to be done.

In the meantime, if anyone else has a similar issue (and using yarn), I resolved it with this in package.json

  "resolutions": {
    "@urql/core": "3.2.2"
  },

Be sure to remove node_modules and yarn.lock and then rerun yarn.

@kitten
Copy link
Member Author

kitten commented Apr 11, 2023

@hawkeye64 your issue was not closed, just fyi, as stated there it was converted to a Q&A discussion: #3145

The section there I've linked you is the "No More Default Exchanges" section on this page. The section up here will tell you what to do, namely to specify an explicit exchanges array option when creating the client, as the default has been removed for better treeshaking when the defaults aren't in use.

So, pass exchanges: [cacheExchange, fetchExchange] and that replaces the old default. More details in the original document in this issue. Generally these things are caugh]t by TypeScript, if it's used, which is why we only document these changes in migration docs. However, all of our docs on the docs site should be updated to reflect this change already. Do let me know though if you found one that doesn't ✌️

A resolution is not good practice in this case at least, and while it should work it will force the bindings to work with a core package that is out of their explicitly set range. Furthermore, even if your resolution is in range, you'll from then on often miss out on updates and many tools will fail to notify you about them or override the resolution. (Also for clarity's sake, this is Yarn's syntax; other package managers may have other solutions for resolutions, so careful if someone here copies this)

To also repeat this here, since it has come up in other issues. Each package manager supports upgrading and updating. When updating a bindings package (e.g. for React, Vue, or urql) with the "upgrade" command or "add/install" commands, @urql/core will usually also be updated automatically.

If not, all package managers have an "update" command, to update a transitive dependency like @urql/core. Alternatively, an explicit "add/install" will also work.

Afterwards, you may accidentally have old versions of @urql/core still stuck around, especially if you haven't upgraded all other exchanges, or are using Yarn v1. In any case, duplicates, e.g. of @urql/core, can be deduplicated using one of the following commands, depending on the package manager used:

# for npm
npm dedupe
# for pnpm
pnpm dedupe
# for yarn
npx yarn-deduplicate

This is always in the migration guides and we add these commands when we bump out of range and duplicates become more likely.

Hope this clarifies some FAQs 😸

@hawkeye64
Copy link

@kitten Fair enough on converting it to a discussion. I guess GitHub closes it and then recreates it as a discussion.

image

Thanks for the info. I'll have to read up more on the exchanges.

@TroyKomodo

This comment was marked as off-topic.

@kitten

This comment was marked as off-topic.

@TroyKomodo

This comment was marked as off-topic.

@kitten

This comment was marked as off-topic.

@TroyKomodo

This comment was marked as off-topic.

@samavati

This comment was marked as off-topic.

@JoviDeCroock

This comment was marked as off-topic.

@samavati

This comment was marked as off-topic.

@urql-graphql urql-graphql locked and limited conversation to collaborators Aug 3, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants