Commit 3d1d3c5
[template] align Shopify data layer (#306)
* [template] align Shopify operation error contract
Standardize how lib/shopify/operations/* signal failure: transport and
GraphQL errors always throw; missing resources return undefined/null/[].
Render-tolerant callers wrap with the new withFallback helper instead of
operations swallowing errors internally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [template] inspect userErrors on every cart mutation
Adds ShopifyUserError + unwrapCartMutation helpers so cart mutation
responses are validated uniformly. Previously the line mutations
(cartCreate, cartLinesAdd, cartLinesUpdate, cartLinesRemove) didn't
select userErrors at all; the address/note mutations selected them but
silently ignored them. Now any non-empty userErrors throws with the
Shopify-provided messages, which the action wrappers surface as
{ success: false, error }.
Also removes the dead if (!result) branches from lib/cart/action.ts
that the new contract makes unreachable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [template] unify Shopify operation locale calling convention
Convert the eight read operations that took positional (id, locale)
arguments to a single params object, matching the convention already
used by getCollections/getCatalogProducts/searchIndexProducts etc.
Drops the unused _locale param from getMenu while we're here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [template] consolidate Shopify fragment and query locations
Apply one rule: shared fragments live in fragments.ts; single-use
queries are named top-level consts in the operation file; nothing is
inlined inside shopifyFetch calls.
- Move CART_FRAGMENT from cart.ts into fragments.ts and rewrite its
inline money/image selections to reuse MoneyFields / ImageFields,
matching the composition pattern used by PRODUCT_CARD_FRAGMENT et al.
- Add COLLECTION_FIELDS_FRAGMENT shared by getCollection and
getCollections, replacing the duplicated inline field selection.
- Hoist all 11 inline cart queries/mutations and the 2 inline
collection queries to top-level SCREAMING_SNAKE_CASE consts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [template] centralize cart cookie reads/writes
Adds getCartIdFromCookie, setCartIdCookie, and
buildCartIdSetCookieHeader to lib/cart/server.ts so the cookie name and
attributes (HttpOnly, Secure, SameSite=Strict, 7-day Max-Age, Path=/)
live in one place. Replaces 11 inline reads and one inline write in
lib/shopify/operations/cart.ts and the manual Set-Cookie string
construction in the chat route.
While inlining, surfaced a latent type narrowing issue in the chat
route's cart-create path — Cart.id is typed as string | undefined
because optimistic client carts have no id, so the streaming
Set-Cookie path now guards explicitly instead of template-literal'ing
undefined.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [template] fix dead cache tag in getProductById
The "product-id-${id}" tag is never pushed by app/api/webhooks/shopify
or anywhere else — it invalidated nothing. Replace it with
"product-${handle}" added after the fetch resolves, matching the
format the webhook does push (so a Shopify product webhook now
actually invalidates the by-id cache entry).
The numeric-id tag via tagProducts already covered the
admin_graphql_api_id webhook path; this restores the handle path too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [template] symmetric transform layout
Move menu transforms (ShopifyMenuItem, ShopifyMenuResponse,
transformShopifyMenu) from lib/shopify/operations/menu.ts to a new
lib/shopify/transforms/menu.ts, matching every other resource (cart,
collection, product, search).
Push filter transformation inside getCollectionProducts and
getSearchFacets. Both now accept an activeFilters param and return
domain Filter[] + PriceRange? instead of raw ShopifyFilter[].
transformShopifyFilters loses its bogus default priceRange {0,1000}
when no PRICE_RANGE filter exists — the optional return type now
matches reality.
4 call sites stop running transformShopifyFilters themselves and stop
maintaining the hasPriceRange workaround they used to detect the
default. Filter sidebar and markdown generators already guard
priceRange with truthy checks, so the behavior shift is visible only
when Shopify returns no price filter at all: the price slider no
longer renders a placeholder 0-1000 range.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix: The Storefront API docs describe an operation error-handling contract that is the exact inverse of the actual code after this PR
This commit fixes the issue reported at apps/docs/content/docs/reference/storefront-api.mdx:163
BUG: The "Error handling" section of apps/docs/content/docs/reference/storefront-api.mdx (line ~163) states: "getCart() wraps the call in try-catch and returns undefined on failure, while getProduct() throws if the product isn't found." Both claims are now factually wrong:
1. getProduct() in apps/template/lib/shopify/operations/products.ts now does `if (!data.productByHandle) { return undefined; }` — it RETURNS undefined when the product is missing, it does not throw.
2. getCart() in apps/template/lib/shopify/operations/cart.ts no longer has any try/catch — it throws on transport/GraphQL failure and only returns undefined when there is genuinely no cart/cartId. Render-tolerant callers (components/nav/cart.tsx, app/cart/page.tsx, lib/cart/action.ts) now wrap the call in `withFallback(getCart(), undefined)` (defined in lib/shopify/errors.ts).
This documents the opposite of the PR's headline error contract: operations throw on transport/GraphQL failure and return undefined/null/[] on missing, with withFallback for render-tolerant callers. Per repo guidelines, template feature changes that are documented must have their docs updated. The trigger is concrete: any reader following the docs would implement/expect the inverse error behavior.
FIX: Rewrote the paragraph to describe the new contract — operations throw on transport/GraphQL failure and return undefined/null/[] when a resource is missing; getProduct() returns undefined when not found; getCart() returns undefined when there's no cart; and render paths use the withFallback(promise, fallback) helper to degrade gracefully.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: laugharn <laugharn@gmail.com>
* Fix: enable-shopify-menus skill shows broken getMenu copy-paste code using the old positional `getMenu("HANDLE", locale)` signature after getMenu was changed to a single object arg `getMenu({ handle })` with no locale
This commit fixes the issue reported at packages/plugin/skills/enable-shopify-menus/SKILL.md:41
BUG: This PR changed `getMenu` in apps/template/lib/shopify/operations/menu.ts from the old positional signature `getMenu(handle: string, _locale: string = defaultLocale)` to a single object parameter `getMenu({ handle }: { handle: string }): Promise<Menu | null>`, removing the locale parameter entirely. However the `enable-shopify-menus` skill (packages/plugin/skills/enable-shopify-menus/SKILL.md lines 41 and 62) and its mirrored docs (apps/docs/content/docs/skills/enable-shopify-menus.mdx lines 52 and 73) still instruct users to copy-paste `const menu = await getMenu("NAV_HANDLE", locale);` and `const menu = await getMenu("FOOTER_HANDLE", locale);`.
Concrete failure trigger: A user following the skill pastes `getMenu("NAV_HANDLE", locale)`. Under the new signature, this passes the string `"NAV_HANDLE"` as the destructured object argument (so `handle` becomes `undefined`), passes an unexpected second `locale` argument, and—in a TypeScript project—fails to compile because a string is not assignable to `{ handle: string }`. The resulting menu query runs with `handle: undefined`, returning no menu. So the skill produces broken, non-compiling code.
Additionally, the `enable-shopify-markets` skill/docs ("update getMenu to derive country and language from the active locale") was inconsistent because getMenu no longer accepts a locale at all.
FIX: Updated the four broken examples to the new object-arg form `getMenu({ handle: "NAV_HANDLE" })` / `getMenu({ handle: "FOOTER_HANDLE" })` in both the SKILL.md and the mirrored .mdx, keeping skill and docs in sync. Also reconciled the markets skill/docs guidance to state that getMenu currently takes only `{ handle }` and must be extended (e.g. `getMenu({ handle, locale })`) to add market scoping, rather than implying it already accepts a locale.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: laugharn <laugharn@gmail.com>
* [docs] update getProduct example to match current operation signature
Two "this is the canonical operation pattern" code blocks still
demonstrated the old positional getProduct(handle, locale) form.
Update them to the params-object form, show the Promise<X | undefined>
return type and the if (!data.X) return undefined missing-resource
branch, so a reader copying the example gets the current contract.
Also fix the @/lib/shopify/client import path in the REFERENCE.md
example — that file was renamed to lib/shopify/fetch.ts on 2026-04-25
but the example never got updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [docs] sync remaining lib/shopify/client references to fetch.ts
Three places still referenced the old lib/shopify/client.ts path
(renamed to fetch.ts on 2026-04-25). While in the CMS skill examples,
also bring the example signatures up to the params-object convention
from this PR — getHomepage({ locale }), getMarketingPage({ slug,
locale }).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>1 parent 9093c74 commit 3d1d3c5
33 files changed
Lines changed: 704 additions & 609 deletions
File tree
- apps
- docs/content/docs
- reference
- skills
- template
- app
- api/chat
- cart
- collections/[handle]
- md
- collections/[handle]
- products/[handle]
- search
- products/[handle]
- components
- product
- search
- lib
- agent/tools
- cart
- collections
- search
- shopify
- operations
- transforms
- packages/plugin/skills
- enable-shopify-cms
- enable-shopify-markets
- shopify-graphql-reference/references
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
92 | | - | |
93 | | - | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
94 | 101 | | |
95 | 102 | | |
96 | 103 | | |
| |||
105 | 112 | | |
106 | 113 | | |
107 | 114 | | |
| 115 | + | |
| 116 | + | |
108 | 117 | | |
109 | 118 | | |
110 | 119 | | |
| |||
160 | 169 | | |
161 | 170 | | |
162 | 171 | | |
163 | | - | |
| 172 | + | |
164 | 173 | | |
165 | 174 | | |
166 | 175 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
65 | | - | |
| 65 | + | |
| 66 | + | |
66 | 67 | | |
67 | 68 | | |
68 | | - | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
69 | 74 | | |
70 | 75 | | |
71 | 76 | | |
72 | 77 | | |
73 | 78 | | |
74 | 79 | | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
79 | 87 | | |
80 | 88 | | |
81 | 89 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
330 | 330 | | |
331 | 331 | | |
332 | 332 | | |
333 | | - | |
| 333 | + | |
334 | 334 | | |
335 | 335 | | |
336 | 336 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
| 52 | + | |
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
73 | | - | |
| 73 | + | |
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | | - | |
10 | 8 | | |
| 9 | + | |
11 | 10 | | |
12 | 11 | | |
| 12 | + | |
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
56 | 52 | | |
57 | 53 | | |
58 | 54 | | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
68 | 58 | | |
69 | 59 | | |
70 | 60 | | |
| |||
90 | 80 | | |
91 | 81 | | |
92 | 82 | | |
93 | | - | |
94 | 83 | | |
95 | 84 | | |
96 | 85 | | |
| |||
111 | 100 | | |
112 | 101 | | |
113 | 102 | | |
114 | | - | |
| 103 | + | |
115 | 104 | | |
116 | 105 | | |
117 | 106 | | |
118 | 107 | | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
123 | 112 | | |
124 | 113 | | |
125 | 114 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
| |||
41 | 42 | | |
42 | 43 | | |
43 | 44 | | |
44 | | - | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
45 | 49 | | |
46 | 50 | | |
47 | 51 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
| 99 | + | |
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | 8 | | |
10 | 9 | | |
11 | 10 | | |
| |||
29 | 28 | | |
30 | 29 | | |
31 | 30 | | |
32 | | - | |
| 31 | + | |
33 | 32 | | |
| 33 | + | |
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
54 | | - | |
55 | 53 | | |
56 | 54 | | |
57 | 55 | | |
58 | | - | |
59 | | - | |
| 56 | + | |
| 57 | + | |
60 | 58 | | |
61 | 59 | | |
62 | 60 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
12 | 28 | | |
13 | 29 | | |
14 | 30 | | |
| |||
19 | 35 | | |
20 | 36 | | |
21 | 37 | | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
| 38 | + | |
26 | 39 | | |
27 | | - | |
28 | | - | |
29 | | - | |
| 40 | + | |
30 | 41 | | |
31 | | - | |
| 42 | + | |
32 | 43 | | |
33 | 44 | | |
34 | | - | |
35 | | - | |
36 | | - | |
| 45 | + | |
37 | 46 | | |
38 | 47 | | |
39 | 48 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | 8 | | |
10 | 9 | | |
11 | 10 | | |
| |||
39 | 38 | | |
40 | 39 | | |
41 | 40 | | |
42 | | - | |
| 41 | + | |
43 | 42 | | |
44 | 43 | | |
45 | | - | |
46 | | - | |
47 | 44 | | |
48 | 45 | | |
49 | 46 | | |
50 | 47 | | |
51 | 48 | | |
52 | | - | |
53 | | - | |
| 49 | + | |
| 50 | + | |
54 | 51 | | |
55 | 52 | | |
56 | 53 | | |
| |||
0 commit comments