-
Notifications
You must be signed in to change notification settings - Fork 291
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
Create createHydrogenContext
that combine createStorefrontClient
,createCustomerAccountClient
and createCartHandler
#2333
Conversation
This comment has been minimized.
This comment has been minimized.
Oxygen deployed a preview of your
Learn more about Hydrogen's GitHub integration. |
c163a20
to
f3d5790
Compare
851271b
to
16598b1
Compare
fe5cfc9
to
d8d45cb
Compare
createShopifyHandler
that combine options from storefront, customerAccount and cartcreateShopifyHandler
that combine options from createStorefrontClient
,createCustomerAccountClient
and createCartHandler
createShopifyHandler
that combine options from createStorefrontClient
,createCustomerAccountClient
and createCartHandler
createShopifyHandler
that combine createStorefrontClient
,createCustomerAccountClient
and createCartHandler
b75bc76
to
1d9ed1c
Compare
b8c6f0e
to
0cb50f9
Compare
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.
Great job Michelle, looking much simpler 🔥
Just a partial review for now with quick things I saw.
export function getStorefrontHeaders( | ||
request: Request | CrossRuntimeRequest, | ||
): StorefrontHeaders { | ||
const headers = request.headers; | ||
return { | ||
requestGroupId: (headers.get ? headers.get('request-id') : null) || null, | ||
buyerIp: (headers.get ? headers.get('oxygen-buyer-ip') : null) || null, | ||
cookie: (headers.get ? headers.get('cookie') : null) || null, | ||
purpose: (headers.get ? headers.get('purpose') : null) || null, | ||
}; | ||
} |
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.
The idea of having getStorefrontHeaders
in remix-oxygen
package is because it's unique to the Oxygen platform. In Node or somewhere else, you'd get buyerIp, cookie, etc. in other ways.
Therefore, if we want to move this function here, at the very least I think we should make it obvious this only works on Oxygen or it will be misleading. Perhaps getStorefrontFromOxygenHeaders
?
--
In fact, I'm not even sure about this because within Hydrogen we don't know if we're running on Oxygen or not. So it's the user the one who should be on charge of passing buyerIp
etc. 🤔
E.g. say we are on Express and we try internally to access 'oxygen-buyer-ip'
but that simply returns null
. We should let the user know we couldn't find the IP address. We used to do this by forcing the user to pass this parameter manually. I guess we could just warn/throw now... not sure :/
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.
The main reason why I was moving getStorefrontHeaders into @Shopify/hydrogen was because I can't import @shopify/remix-oxygen. It created circular import and won't build.
Maybe what I can do is just a local method that account for both without exporting it at all?
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.
Looking through the storefront code, looks like we generate a UUID for requestGroupId
, buyerIp
and cookie
gets default to empty string, and purpose
us just left as null as default.
I dont think we should throw since we currently dont, maybe warn? Not sure if we really need to thou.
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.
Looking through the storefront code, looks like we generate a UUID for requestGroupId, buyerIp and cookie gets default to empty string, and purpose us just left as null as default.
I dont think we should throw since we currently dont, maybe warn? Not sure if we really need to thou.
True, I thought these params were required in TS at least, but they are actually optional. In that case we probably don't need to throw/warn, although we should probably consider warning eventually...
That said, it would still be better to use the getHeader
utility so that we know how to get headers from Node requests (those that are present, like purpose
).
requestHeaders: Headers | CrossRuntimeRequest['headers'], | ||
) => { | ||
const cookies = parse( | ||
(requestHeaders.get ? requestHeaders.get('Cookie') : undefined) || '', |
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.
You can use getHeader
from utils/request.ts
, it understands CrossRuntimeRequest.headers
already.
We might need to change the type here to be requestHeaders: CrossRuntimeRequest['headers']
though 🤔
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.
We might need to change the type here to be requestHeaders: CrossRuntimeRequest['headers'] though 🤔
You mean instead of both types of requestHeaders: Headers | CrossRuntimeRequest['headers'],
just account for one? I thought the point is we will try to cover both shape of headers.
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.
The idea is that CrossRuntimeRequest['headers']
already contains both types. It basically requests only the minimum type that work in both runtimes. Am I wrong? 🤔
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 change the type to use CrossRuntimeRequest['headers']
only. It looks like it does contain both so we are good!
However, I dont think I can use the getHeader
function here since whats being pass in is the header and getHeader
uses the request. (If I change that it will be breaking change)
I end up creating getHeaderValue
for this purpose.
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 you would just need to pass is in an object, like getHeader({headers}, key)
, because the rest of the keys in CrossRuntimeRequest are optional. That said, getHeaderValue
is OK as well.
createShopifyHandler
that combine createStorefrontClient
,createCustomerAccountClient
and createCartHandler
createHydrogenContext
that combine createStorefrontClient
,createCustomerAccountClient
and createCartHandler
f868146
to
0c722ae
Compare
/snapit |
🫰✨ Thanks @michenly! Your snapshots have been published to npm. Test the snapshots by updating your "@shopify/cli-hydrogen": "0.0.0-snapshot-20240730204555",
"@shopify/create-hydrogen": "0.0.0-snapshot-20240730204555",
"@shopify/hydrogen": "0.0.0-snapshot-20240730204555" |
@frandiox I managed to create a JS project using the current template with Not exactly sure how to verify this, but here it the output |
c4d25ce
to
e362948
Compare
I think that The problem is that the typedef is not correct, so -- We can either fix the |
@frandiox I went with your second suggestion as this will be a much easier maintenance going forward. Thank you for the suggestion! |
e673a5d
to
b9a66e2
Compare
b9a66e2
to
8536f08
Compare
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.
Great work Michelle. Just a few minor things.
'@shopify/hydrogen': patch | ||
--- | ||
|
||
Create `createHydrogenContext` that combined `createStorefrontClient`, `createCustomerAccountClient` and `createCartHandler`. |
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.
We can fix right before release, but in my opinion all of the above changeset information about setting it up should be here. The feature is a new feature to hydrogen, so we should be highlighting how to adopt and use it here. We need to trigger a new @shopify/create-hydrogen
release, but beyond that, I think it's confusing spread release notes between the two. But maybe we should align with @juanpprieto and @frandiox.
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 the long description example should be in the skeleton
changeset, no? That's where we put changes to the skeleton so that devs with existing projects can update their code.
This one is good for hydrogen
, and create-hydrogen
can just have a small description as well, or just "starter template updated"
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 can see the point for both.
I think it makes a lot of sense to keep the changset minimal and relevant, written in the tone for the package at hand. But for a new feature we want the folks migrate to, we do need a way to highlight it as well.
To combined the two opinions, how about we keep the changeset minimal. But use the upgrade
steps and the blog post to hight light new features.
getCartId: cartOptions.getId || cartGetIdDefault(request.headers), | ||
setCartId: cartOptions.setId || cartSetIdDefault(), | ||
cartQueryFragment: cartOptions.queryFragment, | ||
cartMutateFragment: cartOptions.mutateFragment, |
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.
🥳 the default getCartId
and setCartId
defaults. Should we also have a default for cartQueryFragment
?
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.
cartQueryFragment
wont be included in code gen type if the defaults move to @shopify/hydrogen
it was left in the user code for this reason.
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.
That seems unfortunate, I wonder if we can fix codegen to do that? But probably a different PR.
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.
@frandiox wondering if you know this is a simple thing to fix or not?
Co-authored-by: Bret Little <[email protected]>
Co-authored-by: Bret Little <[email protected]>
Co-authored-by: Bret Little <[email protected]>
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 it's looking good 🎉 Minor comments below:
'@shopify/hydrogen': patch | ||
--- | ||
|
||
Create `createHydrogenContext` that combined `createStorefrontClient`, `createCustomerAccountClient` and `createCartHandler`. |
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 the long description example should be in the skeleton
changeset, no? That's where we put changes to the skeleton so that devs with existing projects can update their code.
This one is good for hydrogen
, and create-hydrogen
can just have a small description as well, or just "starter template updated"
// update imported type from mock-i18n-types to @shopify/hydrogen with TS file | ||
const importFromNode = root.find({ |
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 is such a simple replacement that I think it's better to simply use RegExp instead of Ast-Grep: content.replace(/'./mock-api-types'/, '@shopify/hydrogen')`
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.
FAIR
class AppSession { | ||
isPending = false; |
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 we just show a permalink to the session implementation file on GitHub? 🙈 (it's not the main thing being showcased here).
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.
We actually do this in a few other places too.
Without the local implementation of AppSession, the type wont pass.
But it was noted to be a smelly since it does indicate a non-complete example.
I can use @ts-ignore and just leave the example in-complete. Let me know how this feel, if folks are ok with it I will make this change in all the examples.
//@ts-ignore See example implementation in https://github.com/Shopify/hydrogen/blob/main/templates/skeleton/app/lib/session.ts
import {AppSession} from '~/lib/session';
export interface HydrogenContext< | ||
TSession extends HydrogenSession = HydrogenSession, | ||
TCustomMethods extends CustomMethodsBase | undefined = undefined, | ||
TI18n extends I18nBase = I18nBase, | ||
TEnv extends HydrogenEnv = Env, | ||
> { | ||
/** A GraphQL client for querying the [Storefront API](https://shopify.dev/docs/api/storefront). */ | ||
storefront: StorefrontClient<TI18n>['storefront']; |
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.
Now that we have this context, we could bubble up some properties in storefront like cache
and i18n
. But we can do this later.
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.
as in return cache and i18n in context as well?
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.
Yeah, but we can think more about this I guess.
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.
@blittle what do you think?
I can add cache fairly quickly but I dont really have a pulse on when we will need to reach for it outside of storefrontClient
i18n would be great to have in context, but would like to do that in a separate PR as it require more testing to ensure changing locale works properly with demo store.
requestHeaders: Headers | CrossRuntimeRequest['headers'], | ||
) => { | ||
const cookies = parse( | ||
(requestHeaders.get ? requestHeaders.get('Cookie') : undefined) || '', |
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 you would just need to pass is in an object, like getHeader({headers}, key)
, because the rest of the keys in CrossRuntimeRequest are optional. That said, getHeaderValue
is OK as well.
createHydrogenContext
that combine options from storefront, customerAccount and cart. This is to prepared for an combined context object as well.createHydrogenContext
andcartGetIdDefault
are compatible with bothRequest
andCrossRuntimeRequest
. Thus compatible with express.createHydrogenContext
does type inference and you never need to pass in generic. However, in global type fileenv.d.ts
, generics are required since there is nothing to infer from.Default usage
Usage in
env.d.ts
With customized cart methods