shopify-nuxt 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +362 -23
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +25 -6
  4. package/dist/runtime/components/ShopifyAppProvider.d.vue.ts +18 -0
  5. package/dist/runtime/components/ShopifyAppProvider.vue +22 -0
  6. package/dist/runtime/components/ShopifyAppProvider.vue.d.ts +18 -0
  7. package/dist/runtime/components/polaris/ShCheckbox.d.vue.ts +2 -2
  8. package/dist/runtime/components/polaris/ShCheckbox.vue.d.ts +2 -2
  9. package/dist/runtime/components/polaris/ShChoiceList.d.vue.ts +2 -2
  10. package/dist/runtime/components/polaris/ShChoiceList.vue.d.ts +2 -2
  11. package/dist/runtime/components/polaris/ShColorField.d.vue.ts +2 -2
  12. package/dist/runtime/components/polaris/ShColorField.vue.d.ts +2 -2
  13. package/dist/runtime/components/polaris/ShColorPicker.d.vue.ts +2 -2
  14. package/dist/runtime/components/polaris/ShColorPicker.vue.d.ts +2 -2
  15. package/dist/runtime/components/polaris/ShDateField.d.vue.ts +2 -2
  16. package/dist/runtime/components/polaris/ShDateField.vue.d.ts +2 -2
  17. package/dist/runtime/components/polaris/ShDatePicker.d.vue.ts +2 -2
  18. package/dist/runtime/components/polaris/ShDatePicker.vue.d.ts +2 -2
  19. package/dist/runtime/components/polaris/ShDropZone.d.vue.ts +2 -2
  20. package/dist/runtime/components/polaris/ShDropZone.vue.d.ts +2 -2
  21. package/dist/runtime/components/polaris/ShEmailField.d.vue.ts +2 -2
  22. package/dist/runtime/components/polaris/ShEmailField.vue.d.ts +2 -2
  23. package/dist/runtime/components/polaris/ShModal.d.vue.ts +7 -2
  24. package/dist/runtime/components/polaris/ShModal.vue +4 -1
  25. package/dist/runtime/components/polaris/ShModal.vue.d.ts +7 -2
  26. package/dist/runtime/components/polaris/ShMoneyField.d.vue.ts +2 -2
  27. package/dist/runtime/components/polaris/ShMoneyField.vue.d.ts +2 -2
  28. package/dist/runtime/components/polaris/ShNumberField.d.vue.ts +2 -2
  29. package/dist/runtime/components/polaris/ShNumberField.vue.d.ts +2 -2
  30. package/dist/runtime/components/polaris/ShPasswordField.d.vue.ts +2 -2
  31. package/dist/runtime/components/polaris/ShPasswordField.vue.d.ts +2 -2
  32. package/dist/runtime/components/polaris/ShSearchField.d.vue.ts +2 -2
  33. package/dist/runtime/components/polaris/ShSearchField.vue.d.ts +2 -2
  34. package/dist/runtime/components/polaris/ShSelect.d.vue.ts +2 -2
  35. package/dist/runtime/components/polaris/ShSelect.vue.d.ts +2 -2
  36. package/dist/runtime/components/polaris/ShSwitch.d.vue.ts +2 -2
  37. package/dist/runtime/components/polaris/ShSwitch.vue.d.ts +2 -2
  38. package/dist/runtime/components/polaris/ShTextArea.d.vue.ts +2 -2
  39. package/dist/runtime/components/polaris/ShTextArea.vue.d.ts +2 -2
  40. package/dist/runtime/components/polaris/ShTextField.d.vue.ts +2 -2
  41. package/dist/runtime/components/polaris/ShTextField.vue.d.ts +2 -2
  42. package/dist/runtime/components/polaris/ShUrlField.d.vue.ts +2 -2
  43. package/dist/runtime/components/polaris/ShUrlField.vue.d.ts +2 -2
  44. package/dist/runtime/composables/useAppBridge.d.ts +4 -1
  45. package/dist/runtime/composables/useAppBridge.js +24 -13
  46. package/dist/runtime/middleware/shopify-auth.js +10 -4
  47. package/dist/runtime/pages/auth-login.d.vue.ts +3 -0
  48. package/dist/runtime/pages/auth-login.vue +90 -0
  49. package/dist/runtime/pages/auth-login.vue.d.ts +3 -0
  50. package/dist/runtime/server/index.d.ts +2 -0
  51. package/dist/runtime/server/index.js +4 -0
  52. package/dist/runtime/server/plugins/shopify-defaults.d.ts +8 -0
  53. package/dist/runtime/server/plugins/shopify-defaults.js +12 -0
  54. package/dist/runtime/server/routes/auth-callback.d.ts +1 -1
  55. package/dist/runtime/server/routes/auth-exit-iframe.d.ts +1 -1
  56. package/dist/runtime/server/routes/auth-session-token.d.ts +1 -1
  57. package/dist/runtime/server/routes/auth.d.ts +1 -1
  58. package/dist/runtime/server/services/shopify.js +3 -3
  59. package/dist/runtime/server/utils/clients.d.ts +24 -5
  60. package/dist/runtime/server/utils/clients.js +21 -2
  61. package/dist/runtime/server/utils/helpers.js +13 -11
  62. package/dist/runtime/server/utils/unauthenticated-storefront.d.ts +1 -5
  63. package/dist/runtime/server/utils/unauthenticated-storefront.js +2 -8
  64. package/dist/runtime/types.d.ts +48 -2
  65. package/package.json +9 -5
  66. package/dist/runtime/plugins/polaris.d.ts +0 -1
  67. package/dist/runtime/plugins/polaris.js +0 -0
package/README.md CHANGED
@@ -47,22 +47,34 @@ export default defineNuxtConfig({
47
47
  shopify: {
48
48
  apiKey: 'ApiKeyFromPartnersDashboard',
49
49
  apiSecretKey: 'ApiSecretKeyFromPartnersDashboard',
50
- scopes: ['read_products', 'write_products'],
51
50
  appUrl: 'https://your-app-url.com'
52
51
  }
53
52
  })
54
53
  ```
55
54
 
56
- For session storage and post-auth hooks, create a Nitro server plugin:
55
+ All values can also be set via environment variables:
56
+
57
+ | Option | Env Variable |
58
+ | -------------- | ----------------------------- |
59
+ | `apiKey` | `NUXT_SHOPIFY_API_KEY` |
60
+ | `apiSecretKey` | `NUXT_SHOPIFY_API_SECRET_KEY` |
61
+ | `appUrl` | `NUXT_SHOPIFY_APP_URL` |
62
+
63
+ ### Session storage
64
+
65
+ By default, the module provides an in-memory session storage (`MemorySessionStorage`) that works out of the box — no configuration needed. For production use, you should use a persistent session storage adapter.
66
+
67
+ To override the default session storage or configure lifecycle hooks, create a Nitro server plugin:
57
68
 
58
69
  ```ts
59
70
  // server/plugins/shopify.ts
60
71
  import { configureShopify } from '#shopify/server'
61
- import { MemorySessionStorage } from '@shopify/shopify-app-session-storage-memory'
72
+ import { PrismaSessionStorage } from '@shopify/shopify-app-session-storage-prisma'
73
+ import { prisma } from '~/server/utils/prisma'
62
74
 
63
75
  export default defineNitroPlugin(() => {
64
76
  configureShopify({
65
- sessionStorage: new MemorySessionStorage(),
77
+ sessionStorage: new PrismaSessionStorage(prisma),
66
78
  hooks: {
67
79
  afterAuth: async ({ session, admin }) => {
68
80
  // Register webhooks, seed data, etc.
@@ -72,14 +84,31 @@ export default defineNitroPlugin(() => {
72
84
  })
73
85
  ```
74
86
 
75
- ### Authenticating admin requests
87
+ Any config passed to `configureShopify()` is merged with the defaults — you only need to specify what you want to override.
88
+
89
+ ### Module options
76
90
 
77
- Use `useShopifyAdmin()` in your server API routes to authenticate requests from the Shopify admin:
91
+ | Option | Type | Default | Description |
92
+ | ----------------- | ----------------- | ---------------- | ----------------------------------------------------------------------- |
93
+ | `apiKey` | `string` | — | Your Shopify API key from the Partners dashboard |
94
+ | `apiSecretKey` | `string` | — | Your Shopify API secret key |
95
+ | `scopes` | `string[]` | `[]` | OAuth scopes (optional — Shopify reads from `shopify.app.toml`) |
96
+ | `appUrl` | `string` | — | Your app's public URL (tunnel URL in development) |
97
+ | `apiVersion` | `ApiVersion` | Latest stable | The Shopify API version to use |
98
+ | `authPathPrefix` | `string` | `/_shopify/auth` | URL prefix for OAuth endpoints |
99
+ | `distribution` | `AppDistribution` | `app_store` | App distribution type (`app_store`, `single_merchant`, `shopify_admin`) |
100
+ | `useOnlineTokens` | `boolean` | `false` | Use online (per-user) tokens in addition to offline (per-shop) tokens |
101
+ | `authPage` | `string \| false` | built-in page | Custom auth page component path, or `false` to disable |
102
+ | `navLinks` | `NavLink[]` | `[]` | Navigation links for the app sidebar (used by `<ShopifyAppProvider>`) |
103
+
104
+ ## Authenticating admin requests
105
+
106
+ Use `useShopifyAdmin()` in your server API routes to authenticate requests from the Shopify admin. The returned `admin` object provides **typed GraphQL** and REST clients powered by `@shopify/admin-api-client`:
78
107
 
79
108
  ```ts
80
109
  // server/api/products.ts
81
110
  export default defineEventHandler(async (event) => {
82
- const { admin } = await useShopifyAdmin(event)
111
+ const { admin, session } = await useShopifyAdmin(event)
83
112
 
84
113
  const response = await admin.graphql(`{
85
114
  products(first: 5) {
@@ -92,13 +121,57 @@ export default defineEventHandler(async (event) => {
92
121
  }
93
122
  }`)
94
123
 
95
- return response
124
+ return await response.json()
96
125
  })
97
126
  ```
98
127
 
99
- ### Handling webhooks
128
+ #### GraphQL with variables
100
129
 
101
- Use `useShopifyWebhook()` to validate and process incoming webhooks:
130
+ ```ts
131
+ const response = await admin.graphql(
132
+ `#graphql
133
+ mutation populateProduct($input: ProductInput!) {
134
+ productCreate(input: $input) {
135
+ product {
136
+ id
137
+ title
138
+ }
139
+ }
140
+ }`,
141
+ {
142
+ variables: {
143
+ input: { title: 'New Product' }
144
+ }
145
+ }
146
+ )
147
+
148
+ const { data } = await response.json()
149
+ ```
150
+
151
+ #### REST API
152
+
153
+ ```ts
154
+ const products = await admin.rest.get({ path: 'products' })
155
+ await admin.rest.post({
156
+ path: 'products',
157
+ data: { product: { title: 'New Product' } }
158
+ })
159
+ ```
160
+
161
+ #### Full `AdminContext` return type
162
+
163
+ | Property | Type | Description |
164
+ | -------------- | -------------------------- | -------------------------------------------------- |
165
+ | `session` | `Session` | The authenticated Shopify session |
166
+ | `admin` | `AdminApiContext` | GraphQL and REST API clients |
167
+ | `sessionToken` | `JwtPayload \| undefined` | Decoded session token (embedded apps only) |
168
+ | `billing` | `BillingContext` | Helpers for `require()`, `check()`, `request()` |
169
+ | `cors` | `(response) => Response` | Add CORS headers to a response |
170
+ | `redirect` | `(url, init?) => Response` | Redirect helper that works inside embedded iframes |
171
+
172
+ ## Handling webhooks
173
+
174
+ Use `useShopifyWebhook()` to validate and process incoming webhooks. HMAC verification is handled automatically:
102
175
 
103
176
  ```ts
104
177
  // server/api/webhooks.ts
@@ -118,7 +191,81 @@ export default defineEventHandler(async (event) => {
118
191
  })
119
192
  ```
120
193
 
121
- ### Using App Bridge on the client
194
+ #### Registering webhooks programmatically
195
+
196
+ ```ts
197
+ // server/plugins/shopify.ts
198
+ import { configureShopify, registerShopifyWebhooks } from '#shopify/server'
199
+
200
+ export default defineNitroPlugin(() => {
201
+ configureShopify({
202
+ hooks: {
203
+ afterAuth: async ({ session }) => {
204
+ await registerShopifyWebhooks(session)
205
+ }
206
+ }
207
+ })
208
+ })
209
+ ```
210
+
211
+ > **Tip**: For most apps, registering webhooks via `shopify.app.toml` is sufficient. Use `registerShopifyWebhooks()` only when you need dynamic, per-shop webhook registration.
212
+
213
+ ## Authenticating other request types
214
+
215
+ ### Shopify Flow
216
+
217
+ ```ts
218
+ // server/api/flow.ts
219
+ export default defineEventHandler(async (event) => {
220
+ const { session, admin, payload } = await useShopifyFlow(event)
221
+ // Handle Flow trigger/action
222
+ })
223
+ ```
224
+
225
+ ### Public requests (checkout extensions, etc.)
226
+
227
+ ```ts
228
+ // server/api/public/widget.ts
229
+ export default defineEventHandler(async (event) => {
230
+ const { sessionToken, cors } = await useShopifyPublic(event)
231
+ // sessionToken contains the decoded JWT payload
232
+ // Use cors() to wrap your response with CORS headers
233
+ })
234
+ ```
235
+
236
+ ### Unauthenticated access (background jobs)
237
+
238
+ For accessing the Shopify API without an incoming request (cron jobs, background tasks):
239
+
240
+ ```ts
241
+ // server/api/cron/sync.ts
242
+ export default defineEventHandler(async () => {
243
+ const { admin } = await useShopifyUnauthenticatedAdmin(
244
+ 'my-shop.myshopify.com'
245
+ )
246
+
247
+ const response = await admin.graphql(`{
248
+ products(first: 10) { edges { node { id title } } }
249
+ }`)
250
+
251
+ return await response.json()
252
+ })
253
+ ```
254
+
255
+ ```ts
256
+ // Storefront API access
257
+ const { storefront } = await useShopifyUnauthenticatedStorefront(
258
+ 'my-shop.myshopify.com'
259
+ )
260
+
261
+ const response = await storefront.graphql(`{
262
+ products(first: 10) { edges { node { id title } } }
263
+ }`)
264
+ ```
265
+
266
+ Both the admin and storefront GraphQL clients are typed via `@shopify/admin-api-client` and `@shopify/storefront-api-client` respectively.
267
+
268
+ ## Using App Bridge on the client
122
269
 
123
270
  The module automatically injects the Shopify App Bridge CDN script and provides typed access via composables:
124
271
 
@@ -127,15 +274,72 @@ The module automatically injects the Shopify App Bridge CDN script and provides
127
274
  // Access the App Bridge instance (typed as ShopifyGlobal)
128
275
  const shopify = useAppBridge()
129
276
 
130
- // Or use the authenticated fetch wrapper
277
+ // Get the current shop
278
+ const shop = shopify.config.shop
279
+ </script>
280
+ ```
281
+
282
+ ### Authenticated fetch
283
+
284
+ Use `useShopifyFetch()` for client-side API calls that automatically include the session token:
285
+
286
+ ```vue
287
+ <script setup>
131
288
  const shopifyFetch = useShopifyFetch()
132
- const { data } = await shopifyFetch('/api/products')
289
+
290
+ const { data: products } = await useAsyncData(
291
+ 'products',
292
+ () => shopifyFetch('/api/products'),
293
+ { server: false }
294
+ )
133
295
  </script>
134
296
  ```
135
297
 
136
- ### Using Polaris components
298
+ > **Important**: Always use `server: false` with `useShopifyFetch()` — session tokens are only available on the client side within the Shopify admin iframe.
299
+
300
+ ## Using Polaris components
137
301
 
138
- This module provides Vue wrapper components for [Shopify Polaris web components](https://shopify.dev/docs/api/app-home/web-components). Use the `Sh`-prefixed wrappers instead of the raw `s-*` web components — they provide typed props with autocomplete and work seamlessly with Vue's reactivity system.
302
+ ### `<ShopifyAppProvider>`
303
+
304
+ Wrap your app pages with `<ShopifyAppProvider>` to automatically render the [App Bridge navigation menu](https://shopify.dev/docs/api/app-home/app-bridge-web-components/app-nav) from your module config:
305
+
306
+ ```ts
307
+ // nuxt.config.ts
308
+ export default defineNuxtConfig({
309
+ modules: ['shopify-nuxt'],
310
+ shopify: {
311
+ // ...
312
+ navLinks: [
313
+ { label: 'Home', href: '/', rel: 'home' },
314
+ { label: 'Products', href: '/products' },
315
+ { label: 'Settings', href: '/settings' }
316
+ ]
317
+ }
318
+ })
319
+ ```
320
+
321
+ ```vue
322
+ <!-- layouts/default.vue -->
323
+ <template>
324
+ <ShopifyAppProvider>
325
+ <slot />
326
+ </ShopifyAppProvider>
327
+ </template>
328
+ ```
329
+
330
+ You can also override the links per-component via the `links` prop:
331
+
332
+ ```vue
333
+ <ShopifyAppProvider :links="[{ label: 'Home', href: '/', rel: 'home' }]">
334
+ <slot />
335
+ </ShopifyAppProvider>
336
+ ```
337
+
338
+ Each `NavLink` has: `label` (string), `href` (string), and optionally `rel: 'home'` to set the default landing page.
339
+
340
+ ### Polaris web components
341
+
342
+ This module provides Vue wrapper components for [Shopify Polaris web components](https://shopify.dev/docs/api/app-home/web-components). Use the `Sh`-prefixed wrappers instead of the raw `s-*` web components — they provide typed props with autocomplete, v-model support for form components, and work seamlessly with Vue's reactivity system.
139
343
 
140
344
  ```vue
141
345
  <template>
@@ -150,17 +354,85 @@ This module provides Vue wrapper components for [Shopify Polaris web components]
150
354
  </ShBanner>
151
355
 
152
356
  <ShTextField
357
+ v-model="title"
153
358
  label="Product title"
154
- :value="title"
155
359
  placeholder="Enter product title"
156
360
  />
361
+
362
+ <ShSelect
363
+ v-model="status"
364
+ label="Status"
365
+ :options="[
366
+ { label: 'Active', value: 'active' },
367
+ { label: 'Draft', value: 'draft' }
368
+ ]"
369
+ />
157
370
  </ShPage>
158
371
  </template>
159
372
  ```
160
373
 
161
374
  All `Sh*` components are auto-imported — no manual imports needed. Each component maps directly to its Polaris web component counterpart (e.g., `<ShButton>` → `<s-button>`, `<ShTextField>` → `<s-text-field>`).
162
375
 
163
- ### Loading your app in Shopify Admin
376
+ ### Available components
377
+
378
+ Layout & structure: `ShPage`, `ShBox`, `ShStack`, `ShGrid`, `ShGridItem`, `ShSection`, `ShDivider`
379
+
380
+ Actions: `ShButton`, `ShButtonGroup`, `ShClickable`, `ShLink`
381
+
382
+ Forms: `ShTextField`, `ShNumberField`, `ShEmailField`, `ShPasswordField`, `ShUrlField`, `ShMoneyField`, `ShColorField`, `ShDateField`, `ShTextArea`, `ShSelect`, `ShCheckbox`, `ShSwitch`, `ShChoiceList`, `ShChoice`, `ShSearchField`, `ShDropZone`, `ShColorPicker`, `ShDatePicker`
383
+
384
+ Feedback: `ShBanner`, `ShBadge`, `ShSpinner`, `ShTooltip`
385
+
386
+ Navigation: `ShAppNav`, `ShMenu`, `ShOption`, `ShOptionGroup`, `ShPopover`
387
+
388
+ Data: `ShTable`, `ShTableHeader`, `ShTableHeaderRow`, `ShTableBody`, `ShTableRow`, `ShTableCell`
389
+
390
+ Content: `ShText`, `ShHeading`, `ShParagraph`, `ShIcon`, `ShImage`, `ShThumbnail`, `ShAvatar`, `ShChip`, `ShClickableChip`, `ShListItem`, `ShOrderedList`, `ShUnorderedList`
391
+
392
+ Other: `ShModal`, `ShQueryContainer`
393
+
394
+ ### Using `ShModal`
395
+
396
+ `ShModal` wraps the [Polaris `<s-modal>`](https://shopify.dev/docs/api/app-home/web-components/overlays/modal) component — it renders **inside** your app's iframe. Open and close it using `commandFor` / `command` attributes:
397
+
398
+ ```vue
399
+ <template>
400
+ <ShButton command-for="my-modal" command="--show">Open</ShButton>
401
+
402
+ <ShModal id="my-modal" heading="Confirm action">
403
+ <ShParagraph>Are you sure?</ShParagraph>
404
+
405
+ <ShButton slot="secondary-actions" command-for="my-modal" command="--hide">
406
+ Cancel
407
+ </ShButton>
408
+ <ShButton
409
+ slot="primary-action"
410
+ variant="primary"
411
+ command-for="my-modal"
412
+ command="--hide"
413
+ >
414
+ Confirm
415
+ </ShButton>
416
+ </ShModal>
417
+ </template>
418
+ ```
419
+
420
+ > **Note**: `ShModal` is **not** the same as the [App Bridge `<ui-modal>`](https://shopify.dev/docs/api/app-bridge-library/apis/modal) which renders outside the iframe and is controlled via `shopify.modal.show(id)`. If you need the App Bridge modal, use `<ui-modal>` directly.
421
+
422
+ ## OAuth routes
423
+
424
+ The module automatically registers these routes:
425
+
426
+ | Route | Purpose |
427
+ | ---------------------------------- | -------------------------------------- |
428
+ | `GET /_shopify/auth` | Start the OAuth flow |
429
+ | `GET /_shopify/auth/callback` | Handle the OAuth callback from Shopify |
430
+ | `GET /_shopify/auth/exit-iframe` | App Bridge iframe escape page |
431
+ | `GET /_shopify/auth/session-token` | Session token bounce page |
432
+
433
+ The prefix `/_shopify/auth` is configurable via the `authPathPrefix` option.
434
+
435
+ ## Loading your app in Shopify Admin
164
436
 
165
437
  To load your app within the Shopify Admin, you need to:
166
438
 
@@ -175,11 +447,15 @@ To load your app within the Shopify Admin, you need to:
175
447
  | **Authentication** | OAuth flow, session tokens, token exchange — all handled automatically |
176
448
  | **App Bridge** | CDN-based App Bridge with full TypeScript types via `@shopify/app-bridge-types` |
177
449
  | **Polaris** | Vue wrapper components (`Sh*`) for all Polaris web components with typed props |
450
+ | **Typed GraphQL** | Admin and Storefront API clients typed via `@shopify/admin-api-client` |
178
451
  | **Webhooks** | HMAC validation, payload parsing, and webhook registration |
179
452
  | **Admin API** | GraphQL and REST clients with automatic session management |
453
+ | **Storefront API** | Typed GraphQL client for Storefront API via `@shopify/storefront-api-client` |
180
454
  | **Billing** | Billing context for subscription and usage-based charges |
181
- | **Session storage** | Pluggable session storage via `@shopify/shopify-app-session-storage` |
182
- | **Auto-imports** | Server utilities and client composables are auto-imported |
455
+ | **Session storage** | Built-in `MemorySessionStorage` default, pluggable via `configureShopify()` |
456
+ | **Auto-imports** | Server utilities, client composables, and components are auto-imported |
457
+ | **Bot detection** | Admin auth automatically detects bots and returns 410 to avoid unnecessary auth |
458
+ | **CORS** | Built-in CORS helpers for public/checkout extension endpoints |
183
459
 
184
460
  ## Server utilities
185
461
 
@@ -198,14 +474,39 @@ These are auto-imported in your `server/` directory:
198
474
  | `useShopifyUnauthenticatedStorefront(shop)` | Offline session storefront API access |
199
475
  | `registerShopifyWebhooks(session)` | Register webhooks for a shop |
200
476
 
477
+ ### `#shopify/server` exports
478
+
479
+ For use in Nitro plugins and advanced server-side configuration:
480
+
481
+ ```ts
482
+ import {
483
+ configureShopify,
484
+ getShopifyApi,
485
+ getResolvedConfig,
486
+ getSessionStorage,
487
+ registerShopifyWebhooks,
488
+ createAdminApiContext,
489
+ createStorefrontApiContext
490
+ } from '#shopify/server'
491
+
492
+ // Types
493
+ import type {
494
+ AdminApiContext,
495
+ StorefrontApiContext,
496
+ GraphQLClient,
497
+ GraphQLQueryOptions,
498
+ GraphQLResponse
499
+ } from '#shopify/server'
500
+ ```
501
+
201
502
  ## Client composables
202
503
 
203
504
  These are auto-imported in your Vue components:
204
505
 
205
- | Composable | Purpose |
206
- | ------------------- | ------------------------------------------------- |
207
- | `useAppBridge()` | Returns typed `ShopifyGlobal` from App Bridge CDN |
208
- | `useShopifyFetch()` | Fetch wrapper with automatic session token auth |
506
+ | Composable | Purpose |
507
+ | ------------------- | -------------------------------------------------------------------- |
508
+ | `useAppBridge()` | Returns typed `ShopifyGlobal` from App Bridge CDN |
509
+ | `useShopifyFetch()` | Fetch wrapper with automatic session token in `Authorization` header |
209
510
 
210
511
  ## Testing your app
211
512
 
@@ -219,8 +520,38 @@ const config = testConfig()
219
520
 
220
521
  // testSession() returns a mock Shopify session
221
522
  const session = testSession()
523
+
524
+ // Both accept overrides
525
+ const customConfig = testConfig({ apiKey: 'custom-key' })
526
+ const customSession = testSession({ shop: 'custom-shop.myshopify.com' })
527
+ ```
528
+
529
+ ## TypeScript
530
+
531
+ The module augments Nuxt's `RuntimeConfig` types so you get full autocomplete and type safety when accessing config:
532
+
533
+ ```ts
534
+ // Server — all Shopify config fields are typed
535
+ const config = useRuntimeConfig()
536
+ config.shopify.apiKey // string
537
+ config.shopify.apiSecretKey // string
538
+ config.shopify.scopes // string[]
539
+ config.shopify.appUrl // string
540
+
541
+ // Client — only public fields
542
+ const publicConfig = useRuntimeConfig().public
543
+ publicConfig.shopify.apiKey // string
544
+ publicConfig.shopify.authPagePath // string
545
+ publicConfig.shopify.authPathPrefix // string
222
546
  ```
223
547
 
548
+ The types are declared via module augmentation in `nuxt/schema`:
549
+
550
+ | Interface | Key | Fields |
551
+ | --------------------- | --------- | --------------------------------------------------------------------------------------------------------------- |
552
+ | `RuntimeConfig` | `shopify` | `apiKey`, `apiSecretKey`, `scopes`, `appUrl`, `apiVersion`, `authPathPrefix`, `distribution`, `useOnlineTokens` |
553
+ | `PublicRuntimeConfig` | `shopify` | `apiKey`, `authPagePath`, `authPathPrefix` |
554
+
224
555
  ## Resources
225
556
 
226
557
  Getting started:
@@ -237,6 +568,14 @@ Shopify:
237
568
  - [App extensions](https://shopify.dev/docs/apps/app-extensions/list)
238
569
  - [Shopify Functions](https://shopify.dev/docs/api/functions)
239
570
 
571
+ Session storage adapters:
572
+
573
+ - [`@shopify/shopify-app-session-storage-prisma`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-prisma)
574
+ - [`@shopify/shopify-app-session-storage-drizzle`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-drizzle)
575
+ - [`@shopify/shopify-app-session-storage-redis`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-redis)
576
+ - [`@shopify/shopify-app-session-storage-mongodb`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-mongodb)
577
+ - [`@shopify/shopify-app-session-storage-memory`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-memory) (default, not for production)
578
+
240
579
  ## Contributing
241
580
 
242
581
  <details>
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shopify-nuxt",
3
3
  "configKey": "shopify",
4
- "version": "0.0.2",
4
+ "version": "0.0.4",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addServerImportsDir, addImportsDir, addComponentsDir, addTypeTemplate, addPlugin, addServerHandler, addRouteMiddleware } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addServerImportsDir, addImportsDir, addComponentsDir, addTypeTemplate, addPlugin, addServerHandler, addRouteMiddleware, extendPages } from '@nuxt/kit';
2
2
  import { ApiVersion } from '@shopify/shopify-api';
3
3
 
4
4
  const module$1 = defineNuxtModule({
@@ -9,7 +9,6 @@ const module$1 = defineNuxtModule({
9
9
  defaults: {
10
10
  apiKey: "",
11
11
  apiSecretKey: "",
12
- scopes: [],
13
12
  appUrl: "",
14
13
  apiVersion: ApiVersion.January26,
15
14
  authPathPrefix: "/_shopify/auth",
@@ -21,7 +20,7 @@ const module$1 = defineNuxtModule({
21
20
  nuxt.options.runtimeConfig.shopify = {
22
21
  apiKey: options.apiKey,
23
22
  apiSecretKey: options.apiSecretKey,
24
- scopes: options.scopes,
23
+ scopes: options.scopes || [],
25
24
  appUrl: options.appUrl,
26
25
  apiVersion: options.apiVersion || ApiVersion.January26,
27
26
  authPathPrefix: options.authPathPrefix || "/_shopify/auth",
@@ -29,7 +28,10 @@ const module$1 = defineNuxtModule({
29
28
  useOnlineTokens: options.useOnlineTokens || false
30
29
  };
31
30
  nuxt.options.runtimeConfig.public.shopify = {
32
- apiKey: options.apiKey
31
+ apiKey: options.apiKey,
32
+ authPagePath: options.authPage !== false ? "/auth" : "",
33
+ authPathPrefix: options.authPathPrefix || "/_shopify/auth",
34
+ navLinks: options.navLinks || []
33
35
  };
34
36
  nuxt.options.alias["#shopify/server"] = resolver.resolve("./runtime/server");
35
37
  addServerImportsDir(resolver.resolve("./runtime/server/utils"));
@@ -68,7 +70,7 @@ const module$1 = defineNuxtModule({
68
70
  ...nuxt.options.app.head.script || []
69
71
  ];
70
72
  nuxt.options.vue.compilerOptions.isCustomElement = (tag) => {
71
- return tag.startsWith("s-");
73
+ return tag.startsWith("s-") || tag.startsWith("ui-");
72
74
  };
73
75
  });
74
76
  addTypeTemplate({
@@ -115,13 +117,30 @@ export {}
115
117
  path: resolver.resolve("./runtime/middleware/shopify-auth"),
116
118
  global: false
117
119
  });
120
+ if (options.authPage !== false) {
121
+ extendPages((pages) => {
122
+ pages.push({
123
+ name: "shopify-auth-login",
124
+ path: "/auth",
125
+ file: options.authPage || resolver.resolve("./runtime/pages/auth-login.vue")
126
+ });
127
+ });
128
+ }
129
+ nuxt.hook("nitro:config", (nitroConfig) => {
130
+ nitroConfig.plugins = nitroConfig.plugins || [];
131
+ nitroConfig.plugins.push(
132
+ resolver.resolve("./runtime/server/plugins/shopify-defaults")
133
+ );
134
+ });
118
135
  nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
119
136
  nuxt.hook("nitro:config", (nitroConfig) => {
120
137
  nitroConfig.externals = nitroConfig.externals || {};
121
138
  nitroConfig.externals.inline = nitroConfig.externals.inline || [];
122
139
  nitroConfig.externals.inline.push(
123
140
  "@shopify/shopify-api",
124
- "@shopify/shopify-api/adapters/node"
141
+ "@shopify/shopify-api/adapters/node",
142
+ "@shopify/shopify-app-session-storage-memory",
143
+ "isbot"
125
144
  );
126
145
  });
127
146
  nuxt.hook("prepare:types", ({ references }) => {
@@ -0,0 +1,18 @@
1
+ import type { NavLink } from '../types.js';
2
+ type __VLS_Props = {
3
+ /** Override the nav links from module config */
4
+ links?: NavLink[];
5
+ };
6
+ declare var __VLS_1: {};
7
+ type __VLS_Slots = {} & {
8
+ default?: (props: typeof __VLS_1) => any;
9
+ };
10
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
14
+ type __VLS_WithSlots<T, S> = T & {
15
+ new (): {
16
+ $slots: S;
17
+ };
18
+ };
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <slot />
3
+ <ShAppNav v-if="navLinks.length">
4
+ <ShLink
5
+ v-for="link in navLinks"
6
+ :key="link.href"
7
+ :href="link.href"
8
+ :rel="link.rel"
9
+ >
10
+ {{ link.label }}
11
+ </ShLink>
12
+ </ShAppNav>
13
+ </template>
14
+
15
+ <script setup>
16
+ import { useRuntimeConfig } from "#app";
17
+ const props = defineProps({
18
+ links: { type: Array, required: false }
19
+ });
20
+ const config = useRuntimeConfig().public.shopify;
21
+ const navLinks = props.links || config.navLinks || [];
22
+ </script>
@@ -0,0 +1,18 @@
1
+ import type { NavLink } from '../types.js';
2
+ type __VLS_Props = {
3
+ /** Override the nav links from module config */
4
+ links?: NavLink[];
5
+ };
6
+ declare var __VLS_1: {};
7
+ type __VLS_Slots = {} & {
8
+ default?: (props: typeof __VLS_1) => any;
9
+ };
10
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
14
+ type __VLS_WithSlots<T, S> = T & {
15
+ new (): {
16
+ $slots: S;
17
+ };
18
+ };
@@ -18,11 +18,11 @@ type __VLS_Slots = {} & {
18
18
  };
19
19
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
20
20
  "update:modelValue": (v: boolean) => void;
21
- change: (event: Event) => void;
22
21
  input: (event: Event) => void;
22
+ change: (event: Event) => void;
23
23
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
24
- onChange?: ((event: Event) => any) | undefined;
25
24
  onInput?: ((event: Event) => any) | undefined;
25
+ onChange?: ((event: Event) => any) | undefined;
26
26
  "onUpdate:modelValue"?: ((v: boolean) => any) | undefined;
27
27
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
28
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -18,11 +18,11 @@ type __VLS_Slots = {} & {
18
18
  };
19
19
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
20
20
  "update:modelValue": (v: boolean) => void;
21
- change: (event: Event) => void;
22
21
  input: (event: Event) => void;
22
+ change: (event: Event) => void;
23
23
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
24
- onChange?: ((event: Event) => any) | undefined;
25
24
  onInput?: ((event: Event) => any) | undefined;
25
+ onChange?: ((event: Event) => any) | undefined;
26
26
  "onUpdate:modelValue"?: ((v: boolean) => any) | undefined;
27
27
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
28
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;