-
Notifications
You must be signed in to change notification settings - Fork 1
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
Simple layer #3
Simple layer #3
Conversation
what's the cost/benefit of having this logic in here vs using an upstream package, if they are functionally equivalent? |
The upstream package includes a lot more code/logic over what is essentially a fetch helper in an attempt to cover every possible use case. I think there is space for a something like this that is way more minimal, but covers the 80% case. RRMNL also is badly packaged/published. We are pinned to an old version b/c the author is using the babel runtime transform, but not including babel runtime or corejs as dependencies because "it bloats the bundle". i.e. is broken by default but it's being called working as intended so IDK |
also this is now better tested than the upstream one :P |
I think the API for a general package we could stick in relay-tools should probably be: import { NetworkLayer } from 'relay-runtime'
import { createFetch, createSubscribe } from 'relay-network-layer'
NetworkLayer.create(
createFetch(opts),
createSubscribe(otherOpts)
) |
thanks :) and having this as a separate network layer package? does it make sense? |
I was thinking of renaming this to 'relay-network-layer' and having the useAuthToken bit just be a side feature. WDT? |
might make more sense. I don't have big vesting on this so my suggestions are totally non binding :) I'll let you guys decide! |
did you not like my relay-fetch as a library name? https://github.com/4Catalyzer/relay-fetch |
It's also good :P i already had the work here. we can put it where ever. I mostly like that |
okay, relay-network-layer does in fact sound better |
gtg? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this might be simpler without observables. For basic batching, you could do something like:
function fetchFn(request) {
if (!canBatch(request)) {
return fetchGraphql(request);
}
return batcher.fetch(request);
}
class Batcher {
pendingRequests: Array<PendingRequest> | null = null;
fetch(request) {
if (!this.pendingRequests) {
this.pendingRequests = [];
setTimeout(this.executeBatch, timeoutMs);
}
return new Promise((resolve, reject) => {
this.pendingRequests.push({
request,
resolve,
reject,
});
});
}
executeBatch = async () => {
const results = await fetchData(
this.pendingRequests.map(({ request }) => request),
);
results.forEach((result, i) => {
if (result.errors) {
this.pendingRequests[i].reject(...);
} else {
this.pendingRequests[i].resolve(...);
}
});
}
}
this is obviously very sketch-quality, but it seems like we can generally avoid observables here?
}); | ||
}); | ||
|
||
export default (origin, opts) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this just a very small reimpl of an event emitter?
it works, i'm sure, but... could just use an event emitter :p
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i know, i just copied and tweaked from some medium post on mocking socket.io
src/NetworkLayer.ts
Outdated
batch?: boolean; | ||
|
||
/** An authorization token sent in a header with every request */ | ||
token?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
token?: string; | |
authorizationToken?: string; |
maybe? more explicit name
alternatively, maybe something like:
authorization?: string | {
headerName?: string;
scheme?: string;
token: string;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think this can be removed
src/createSubscribe.ts
Outdated
socket | ||
.on('connect', () => { | ||
if (token) { | ||
emitTransient('authenticate', token); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, huh, so we don't ever update auth?
i wonder if we can move to something like the apollo spec where we just send auth at connect time, then.
it's a little awkward, though, because i think in a few places we may subscribe before we auth? maybe just for telemed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually wondering if there is a (minor) bug around here. We tear down network layers before the environment has been replaced. Envs don't really have any concept of a closed network so presumably until the environment is not used any more it'd still be trying to subscribe...
Also tho, is it actually feasible to update auth for existing subscriptions? If you switch to another user without permission would existing sub's just fail indefinitely? Obviously this doesn't really ever occur since we teardown everything on user changes. But still seems kinda sharp, really you'd only want to update auth in a refresh token sort of situation which we don't currently have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, this was some holdover from the old q-si stuff where you could refresh your token expiration. that hasn't been possible for quite some time now, so maybe this use case has gone away
i mean, emitTransient
does nothing anyway if the subscription is closed, right? so it's not a big deal
* Any fetch API "init" details, this is based directly to the `fetch` call, see | ||
* [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) for API details. | ||
*/ | ||
init?: RequestInit; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this API is somewhat awkward because, as constructed, it doesn't let us add extra headers or whatever
perhaps just something that accepts a custom fn with the same signature as fetch
, to allow maximum flexibility?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why doesn't it allow extra headers? if you add headers in here they are used as the defaults.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, didn't see that you initialized headers with init?.headers
still... could be more flexible to accept a custom fetch fn that just defaults to window.fetch
? or do you think it's overkill?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, this is probably enough for now
src/NetworkLayer.ts
Outdated
* | ||
* **Requires a Graphql server that understands batching" | ||
*/ | ||
batch?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe:
batching?: boolean | {
enabled: boolean;
timeoutMs?: number;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meh, makes it a bit harder to handle, I'd so not worth it for one additional option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alternatively, could jsut collapse this into one option that is batch timeout: false | number
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, that works too. i got this pattern from apollo server config, where it seems... maybe fairly neat from the user pov?
I was thinking the observables were required but I guess if we are always using the builtin Network.create it'll coerce the Promise to an observable. The real reason for the observable is the abort logic tho. |
Co-Authored-By: Jimmy Jia <[email protected]>
Why do you need the observable for the abort logic? Can't you just reject the promise on any sort of error? |
idk how Relay converts it, the result from an abort is you complete the observable without having sent a value, with a Promise, that's a bit ambiguous |
okay, that makes sense. that's definitely worth adding a comment to note |
Co-Authored-By: Jimmy Jia <[email protected]>
notes addressed! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, nothing really big... just the notes here
README.md
Outdated
}; | ||
|
||
/** An authorization token sent in a header with every request */ | ||
token?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so remove this option and just use authorization
below?
and in the example above
src/NetworkLayer.ts
Outdated
batch?: boolean; | ||
|
||
/** An authorization token sent in a header with every request */ | ||
token?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think this can be removed
* Any fetch API "init" details, this is based directly to the `fetch` call, see | ||
* [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) for API details. | ||
*/ | ||
init?: RequestInit; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, this is probably enough for now
src/NetworkLayer.ts
Outdated
|
||
constructor({ | ||
const SimpleNetworkLayer = { | ||
create({ | ||
token, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
token, |
and just use authorization
below
src/NetworkLayer.ts
Outdated
throwErrors, | ||
init, | ||
batch, | ||
url = '/graphql', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sort these options to match their order in the docs above?
init?: RequestInit; | ||
throwErrors?: boolean; | ||
authorization?: | ||
| string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this accept some sort of boolean
or null
for an explicit false
?
|
||
export interface SubscriptionClientOptions { | ||
url?: string; | ||
token?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps this can take the same shape with authorization?: ...
as in the non-subscription version?
src/createSubscribe.ts
Outdated
new (...args: any): SubscriptionClient; | ||
} | ||
|
||
export class SocketioSubscriptionClient implements SubscriptionClient { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export class SocketioSubscriptionClient implements SubscriptionClient { | |
export class SocketIoSubscriptionClient implements SubscriptionClient { |
src/createSubscribe.ts
Outdated
close?(): void | Promise<void>; | ||
} | ||
|
||
interface SubscriptionClientClass { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could do Class<SubscriptionClient>
with Class
from utility-types
src/createSubscribe.ts
Outdated
} | ||
|
||
export default function createSubscribe({ | ||
subscriptionClientClass = SocketioSubscriptionClient, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you know if it's more standard for this to be called subscriptionClientClass
or should it be SubscriptionClient
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
im not aware of any consensus on that
rips out RRMNL for something much smaller. I should maybe keep the socket stuff split out to avoid including socketio unnecessarily