Open kitten opened 1 year ago
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
@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 😄
@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
Using the old multipartFetchExchange
solves the problem. However, it has already been deprecated.
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
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
.
@hawkeye64 your issue was not closed, just fyi, as stated there it was converted to a Q&A discussion: https://github.com/urql-graphql/urql/discussions/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 :v:
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 😸
@kitten Fair enough on converting it to a discussion. I guess GitHub closes it and then recreates it as a discussion.
Thanks for the info. I'll have to read up more on the exchanges.
Hi, previously I was doing something like this.
/// This is a bit hard to understand, but it's basically a custom exchange that
// will send all operations to the websocket if it's open, and to the HTTP
// endpoint if it's not. Websockets take some time to connect
// so we dont want to delay the user.
const operationExchange: Exchange = (input) => {
const wsExchange = wsSink(input);
const httpExchange = fetchExchange(input);
return (ops$) => {
// We have to share the operations here because if the kind is teardown we forward it to both streams.
// Teardowns are used to cancel requests, so we need to forward them to both streams.
const sharedOps$ = pipe(ops$, share);
// Here we filter if the websocket is open or if the kind is subscription or teardown.
// We want to use the websocket as much as possible so we send all operations if it is open.
// We also need to forward all teardowns and subscriptions regardless of the websocket state.
const wsPipe$ = pipe(
sharedOps$,
filter((op) => get(websocketOpen) || op.kind === "subscription" || op.kind === "teardown"),
wsExchange,
);
// Here we use it as a fallback if the websocket is not open, however we still need to forward teardowns even if the websocket is open.
const httpPipe$ = pipe(
sharedOps$,
filter((op) => !get(websocketOpen) || op.kind === "teardown"),
httpExchange,
);
// At the end we need to merge both streams together and return a single result stream.
return merge([wsPipe$, httpPipe$]);
};
};
On the new release I get
app.js:20 Error: forward() must only be called once in each Exchange.
Is there a way to achieve similar functionality with the new release?
I see that it's basically like a "terminating" exchange, but from urql
's perspective, no exchange ever has to handle all operations, so it's expected they'll always pass on what they don't handle.
You can either call forward(never)
, with wonka
's never
export, or what you can actually create a pass-through case.
What I mean is, for instance, even the subscriptionExchange
has a passthrough case: https://github.com/urql-graphql/urql/blob/76ad6191dd193a6e9433acc7ccb1b4a1ac6461b3/packages/core/src/exchanges/subscription.ts#L219-L227
So you could split the fetchExchange
out again and leave it as a separate exchange.
However, if you need a quick fix forward(never)
is definitely the quickest, and essentially the same as:
return merge([
wsPipe$,
httpPipe$,
forward(pipe(operations, filter(() => false)),
]);
You could also do:
forward(
pipe(operations,
filter((operation) => operation.kind === 'teardown'),
)
That's of course unnecessary in your case, but typically, the built-in exchanges will always pass those on, in case there's more exchanges in the chain receiving future operations — however unlikely that may be
Well, not exactly a terminating exchange. Essentially I was conditionally toggling the fetch exchange based on if the websocket was open and ready.
exchanges after this exchange would still be called.
Ah, I misread the part where you're instantiating the exchanges. That's indeed where forward
is used twice. So, the only way to stop that is to wrap the forward
function you're receiving, call it yourself once and share
it, then return it in a new function that those exchanges receive
Edit: i.e.
const result$ = share(forward(never));
const wsExchange = wsSink({ ...input, forward: () => result$ });
const httpExchange = fetchExchange({ ...input, forward: () => result$ });
I suppose, we didn't yet look at whether we need to add a splitExchange
helper for this, but we didn't really deem it important enough, since it's still possible
const wsExchange: Exchange = ({ client, dispatchDebug, forward}) => {
const wsExchange = wsSink({
client,
dispatchDebug,
forward: () => never,
});
return (ops$) => {
// Here we filter if the websocket is open or if the kind is subscription or teardown.
// We want to use the websocket as much as possible so we send all operations if it is open.
// We also need to forward all teardowns and subscriptions regardless of the websocket state.
const wsPipe$ = pipe(
ops$,
filter((op) => get(websocketOpen) || op.kind === "subscription" || op.kind === "teardown"),
wsExchange,
);
// Here we use it as a fallback if the websocket is not open, however we still need to forward teardowns even if the websocket is open.
const forward$ = pipe(
ops$,
filter((op) => (!get(websocketOpen) || op.kind === "teardown") && op.kind !== 'subscription'),
forward,
);
// At the end we need to merge both streams together and return a single result stream.
return merge([wsPipe$, forward$]);
};
};
ended up with this thanks!
Subject: Missing "__typename" in query results after migrating from urql v3 to v4.0.5
Description: I recently updated the urql package from version 3 to the latest version (v4.0.5) as per the migration steps mentioned in the documentation. However, I'm facing an issue where the "__typename" field is no longer present in the query results. This missing field is causing problems in my application, as I rely on it for certain functionalities.
Steps to Reproduce:
Updated urql package from v3 to v4.0.5 using the provided migration steps. Executed the GraphQL queries that previously returned the "typename" field. Expected Behavior: I expected the "typename" field to be included in the query results, just like it was in urql v3.
Actual Behavior: The "__typename" field is missing from the query results in urql v4.0.5.
Additional Information:
OS: MacOS Node.js version: v20.5.5 urql version: 4.0.5 I have verified that the issue is not due to any changes in my GraphQL schema or queries since it worked correctly in urql v3. The "__typename" field is crucial for my application's functionality, and its absence is causing unexpected behavior.
Please let me know if there's any additional information I can provide or if there's a workaround to bring back the "__typename" field in urql v4.0.5.
Thank you for your help!
@samavati Are you querying the __typename
field in the selections where you need them? If you are would you mind creating a codesandbox where this is issue is reproduced, if you are not it can be fixed by querying the __typename
field as before we had a few bugs where this was erroneously returned when not queried.
@JoviDeCroock Thank you :) This resolved my problem 🎉
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
### 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 firsturql
?Expand for a quick summary of changes made in the prior urql v3 releases.
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!
Table of Contents
defaultExchanges
(💥)subscriptionExchange
(💥)hasNext
,stale
, and multiple results (💥)@urql/core
ditches thegraphql
package (💥)graphql
library?graphql
myself?dedupExchange
(🦋)urql
and@urql/preact
no longer create a default Client (💥)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 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
andgraphql-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:
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 adopturql
.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:
A rewrite of our default fetch/HTTP transport (✨)
urql
's defaultfetchExchange
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:@urql/exchange-multipart-fetch
(a now deprecated package)text/event-stream
protocol (also known as GraphQL SSE for Incremental Delivery responses.multipart/mixed
response protocol is still supported as before.With
multipart/mixed
andtext/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 tourql
.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 thesubscriptionExchange
, 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 aFile
or aBlob
.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 howOperationResult
s are defined and delivered in response toOperation
s.In this version we're tightening our guarantees around results that are marked as
hasNext: true
and when results are marked asstale: true
.Any API transport that delivers multiple results may mark its
ExecutionResult
s withhasNext: true
. This for instance happens when:@defer
-ed or@streasm
-ed results.@live
query is delivering updated results.When the
Client
seeshasNext
, it nows consistently knows to expect updated results.Furthermore,
stale: true
, as before, indicates that theClient
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 amakeFetchBody
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
'sforwardSubscription
function was instead accepting anOperation
-like structure. That's why this has changed to now accept aFetchBody
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 ourFetchBody
structure, ready to be accepted by GraphQL APIs.If you were previously relying on this first argument having a
context
property — theOperationContext
— we're now instead passing the entireOperation
as a second argument toforwardSubscription
code>@urql/core</code ditches the
graphql
package (💥)@urql/core
doesn’t rely on a peer dependency ongraphql
anymore.When you installed
@urql/core@^3.2.2
, you will at least add17.3kB
(gzip) to your bundlesize, since it not only includes its own code but also parts ofgraphql
(andwonka
, 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 ofgraphql
we used to use (which includesparse
,print
, andGraphQLError
). 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 most9.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 thegraphql
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 fromgraphql
(e.g.import('graphql').DocumentNode
) and@0no-co/graphql.web
will additionally switch to usinggraphql
’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 of2kB
(gzip).You can use the
graphql-web-lite
package and aliasgraphql
imports to it. Thegraphql-web-lite
repostory contains instructions using which you can alias thegraphql
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 ofgraphql
, and is built against thegraphql
library, but also uses@0no-co/graphql.web
internally. Once you’ve aliasedgraphql
tographql-web-lite
, you’ll continue to be able to use its API, but keep size to a minimum.@0no-co/graphql.web
graphql-web-lite
Easier core-usage (🦋)
Sources now have
.then()
set which means that you can leverageawait
by default on sources returned by urql, this enable you to performawait client.mutation()
.Sources also have
.subscribe()
now which accepts a callback that allows you to see all values passed back from theexchanges
.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 code>@urql/svelte</code'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.urql
and code>@urql/preact</code no longer create a default Client (💥)In prior versions,
urql
and@urql/preact
would create a default Client when noProvider
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 aProvider
anyway when you went to production. We have removed this in this major and instead throw an error when we miss aProvider
.