@tanstack/create 0.65.0 → 0.67.0

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 (130) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/frameworks/react/add-ons/powersync/README.md.ejs +26 -0
  3. package/dist/frameworks/react/add-ons/powersync/assets/_dot_env.local.append +3 -0
  4. package/dist/frameworks/react/add-ons/powersync/assets/powersync-vite-plugin.ts +17 -0
  5. package/dist/frameworks/react/add-ons/powersync/assets/src/integrations/powersync/provider.tsx +26 -0
  6. package/dist/frameworks/react/add-ons/powersync/assets/src/lib/powersync/AppSchema.ts +17 -0
  7. package/dist/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts +52 -0
  8. package/dist/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx +129 -0
  9. package/dist/frameworks/react/add-ons/powersync/info.json +46 -0
  10. package/dist/frameworks/react/add-ons/powersync/package.json.ejs +7 -0
  11. package/dist/frameworks/react/add-ons/powersync/small-logo.svg +6 -0
  12. package/dist/frameworks/react/add-ons/shopify/README.md +86 -0
  13. package/dist/frameworks/react/add-ons/shopify/assets/_dot_env.local.append +19 -0
  14. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/account-nav.tsx.ejs +41 -0
  15. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/add-to-cart-button.tsx +48 -0
  16. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-line-item.tsx +94 -0
  17. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-summary.tsx +111 -0
  18. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/empty-state.tsx +29 -0
  19. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/money.tsx +11 -0
  20. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/product-card.tsx +74 -0
  21. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/product-grid.tsx +24 -0
  22. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/shop-image.tsx +57 -0
  23. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/shop.css +58 -0
  24. package/dist/frameworks/react/add-ons/shopify/assets/src/components/shop/variant-selector.tsx +79 -0
  25. package/dist/frameworks/react/add-ons/shopify/assets/src/hooks/use-cart.ts +276 -0
  26. package/dist/frameworks/react/add-ons/shopify/assets/src/hooks/use-customer.ts.ejs +22 -0
  27. package/dist/frameworks/react/add-ons/shopify/assets/src/integrations/shopify/header-cart.tsx +37 -0
  28. package/dist/frameworks/react/add-ons/shopify/assets/src/lib/shopify/customer-queries.ts.ejs +228 -0
  29. package/dist/frameworks/react/add-ons/shopify/assets/src/lib/shopify/format.ts +33 -0
  30. package/dist/frameworks/react/add-ons/shopify/assets/src/lib/shopify/queries.ts +684 -0
  31. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.addresses.tsx.ejs +67 -0
  32. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.callback.tsx.ejs +45 -0
  33. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.index.tsx.ejs +70 -0
  34. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.login.tsx.ejs +59 -0
  35. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.logout.tsx.ejs +16 -0
  36. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.$id.tsx.ejs +126 -0
  37. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.tsx.ejs +50 -0
  38. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.tsx.ejs +34 -0
  39. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.cart.tsx +45 -0
  40. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.collections.$handle.tsx +66 -0
  41. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.index.tsx +36 -0
  42. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.pages.$handle.tsx +39 -0
  43. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.policies.$handle.tsx +30 -0
  44. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.products.$handle.tsx +106 -0
  45. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.search.tsx +75 -0
  46. package/dist/frameworks/react/add-ons/shopify/assets/src/routes/shop.tsx +78 -0
  47. package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/cart.functions.ts +207 -0
  48. package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/catalog.functions.ts +244 -0
  49. package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/cookies.ts +29 -0
  50. package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-client.ts.ejs +99 -0
  51. package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-cookies.ts.ejs +49 -0
  52. package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer.functions.ts.ejs +168 -0
  53. package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/env.ts +89 -0
  54. package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/oauth.ts.ejs +301 -0
  55. package/dist/frameworks/react/add-ons/shopify/assets/src/server/shopify/storefront-client.ts +101 -0
  56. package/dist/frameworks/react/add-ons/shopify/info.json +104 -0
  57. package/dist/frameworks/react/add-ons/shopify/package.json +6 -0
  58. package/dist/frameworks/react/add-ons/shopify/small-logo.svg +1 -0
  59. package/dist/frameworks/react/examples/shopify-storefront/README.md +39 -0
  60. package/dist/frameworks/react/examples/shopify-storefront/assets/src/components/FeaturedCollections.tsx +43 -0
  61. package/dist/frameworks/react/examples/shopify-storefront/assets/src/components/ShopHero.tsx +39 -0
  62. package/dist/frameworks/react/examples/shopify-storefront/assets/src/routes/index.tsx +65 -0
  63. package/dist/frameworks/react/examples/shopify-storefront/info.json +18 -0
  64. package/dist/frameworks/react/examples/shopify-storefront/package.json +3 -0
  65. package/dist/frameworks/react/project/base/src/components/Header.tsx.ejs +34 -34
  66. package/package.json +1 -1
  67. package/src/frameworks/react/add-ons/powersync/README.md.ejs +26 -0
  68. package/src/frameworks/react/add-ons/powersync/assets/_dot_env.local.append +3 -0
  69. package/src/frameworks/react/add-ons/powersync/assets/powersync-vite-plugin.ts +17 -0
  70. package/src/frameworks/react/add-ons/powersync/assets/src/integrations/powersync/provider.tsx +26 -0
  71. package/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/AppSchema.ts +17 -0
  72. package/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts +52 -0
  73. package/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx +129 -0
  74. package/src/frameworks/react/add-ons/powersync/info.json +46 -0
  75. package/src/frameworks/react/add-ons/powersync/package.json.ejs +7 -0
  76. package/src/frameworks/react/add-ons/powersync/small-logo.svg +6 -0
  77. package/src/frameworks/react/add-ons/shopify/README.md +86 -0
  78. package/src/frameworks/react/add-ons/shopify/assets/_dot_env.local.append +19 -0
  79. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/account-nav.tsx.ejs +41 -0
  80. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/add-to-cart-button.tsx +48 -0
  81. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-line-item.tsx +94 -0
  82. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/cart-summary.tsx +111 -0
  83. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/empty-state.tsx +29 -0
  84. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/money.tsx +11 -0
  85. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/product-card.tsx +74 -0
  86. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/product-grid.tsx +24 -0
  87. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/shop-image.tsx +57 -0
  88. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/shop.css +58 -0
  89. package/src/frameworks/react/add-ons/shopify/assets/src/components/shop/variant-selector.tsx +79 -0
  90. package/src/frameworks/react/add-ons/shopify/assets/src/hooks/use-cart.ts +276 -0
  91. package/src/frameworks/react/add-ons/shopify/assets/src/hooks/use-customer.ts.ejs +22 -0
  92. package/src/frameworks/react/add-ons/shopify/assets/src/integrations/shopify/header-cart.tsx +37 -0
  93. package/src/frameworks/react/add-ons/shopify/assets/src/lib/shopify/customer-queries.ts.ejs +228 -0
  94. package/src/frameworks/react/add-ons/shopify/assets/src/lib/shopify/format.ts +33 -0
  95. package/src/frameworks/react/add-ons/shopify/assets/src/lib/shopify/queries.ts +684 -0
  96. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.addresses.tsx.ejs +67 -0
  97. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.callback.tsx.ejs +45 -0
  98. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.index.tsx.ejs +70 -0
  99. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.login.tsx.ejs +59 -0
  100. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.logout.tsx.ejs +16 -0
  101. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.$id.tsx.ejs +126 -0
  102. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.orders.tsx.ejs +50 -0
  103. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.account.tsx.ejs +34 -0
  104. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.cart.tsx +45 -0
  105. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.collections.$handle.tsx +66 -0
  106. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.index.tsx +36 -0
  107. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.pages.$handle.tsx +39 -0
  108. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.policies.$handle.tsx +30 -0
  109. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.products.$handle.tsx +106 -0
  110. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.search.tsx +75 -0
  111. package/src/frameworks/react/add-ons/shopify/assets/src/routes/shop.tsx +78 -0
  112. package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/cart.functions.ts +207 -0
  113. package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/catalog.functions.ts +244 -0
  114. package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/cookies.ts +29 -0
  115. package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-client.ts.ejs +99 -0
  116. package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer-cookies.ts.ejs +49 -0
  117. package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/customer.functions.ts.ejs +168 -0
  118. package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/env.ts +89 -0
  119. package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/oauth.ts.ejs +301 -0
  120. package/src/frameworks/react/add-ons/shopify/assets/src/server/shopify/storefront-client.ts +101 -0
  121. package/src/frameworks/react/add-ons/shopify/info.json +104 -0
  122. package/src/frameworks/react/add-ons/shopify/package.json +6 -0
  123. package/src/frameworks/react/add-ons/shopify/small-logo.svg +1 -0
  124. package/src/frameworks/react/examples/shopify-storefront/README.md +39 -0
  125. package/src/frameworks/react/examples/shopify-storefront/assets/src/components/FeaturedCollections.tsx +43 -0
  126. package/src/frameworks/react/examples/shopify-storefront/assets/src/components/ShopHero.tsx +39 -0
  127. package/src/frameworks/react/examples/shopify-storefront/assets/src/routes/index.tsx +65 -0
  128. package/src/frameworks/react/examples/shopify-storefront/info.json +18 -0
  129. package/src/frameworks/react/examples/shopify-storefront/package.json +3 -0
  130. package/src/frameworks/react/project/base/src/components/Header.tsx.ejs +34 -34
package/CHANGELOG.md CHANGED
@@ -1,5 +1,77 @@
1
1
  # @tanstack/create
2
2
 
3
+ ## 0.67.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Auto-generated changeset from semantic commits on main.
8
+
9
+ - feat(create): add React PowerSync scaffolding add-on (#407) (8f24af5)
10
+
11
+ ## 0.66.0
12
+
13
+ ### Minor Changes
14
+
15
+ - feat(cli, create): add Shopify storefront add-on + storefront template ([`814d222`](https://github.com/TanStack/cli/commit/814d222ac04e839eabe56abce5dcbe66d751c5d8))
16
+
17
+ Headless Shopify support for TanStack Start apps, scaffold-ready and
18
+ runtime-portable. The pitch: prove that TanStack Start is a first-class
19
+ target for Shopify, not just Next.js Commerce or Hydrogen.
20
+
21
+ **`shopify` add-on** — additive. `tanstack add shopify` mounts `/shop/*`
22
+ routes alongside an existing app without touching the home page. Includes:
23
+
24
+ - Storefront API client (server-only fetch via `createServerFn`, public
25
+ token by default + optional private token for higher rate limits and
26
+ buyer-IP forwarding).
27
+ - Hand-written GraphQL queries with hydrogen-react types (type-only;
28
+ zero runtime weight).
29
+ - httpOnly cookie cart (`tanstack_cart_id`) + React Query single-key
30
+ cache + optimistic updates with module-level mutation counter to
31
+ batch invalidations during rapid clicks.
32
+ - Hydrogen-demo parity routes: shop landing, product detail (with
33
+ variants + availability), collections, cart, search, Shopify CMS
34
+ pages, policies.
35
+ - Hydrogen-stock UI components (ProductCard, VariantSelector,
36
+ AddToCartButton, CartLineItem, CartSummary, ShopImage with CDN
37
+ transforms, Money via Intl) themed with six CSS custom properties
38
+ for easy reskinning.
39
+ - Header cart-count badge via the `header-user` integration slot.
40
+ - Shopify-hosted checkout (redirect to `cart.checkoutUrl`).
41
+ - **Optional Customer Account API** behind a `customerAccount` select
42
+ option. Hand-rolled OAuth 2.1 PKCE with `.well-known` discovery
43
+ cached in module memory (no usable npm client exists yet),
44
+ HMAC-signed httpOnly session cookies (HS256), lazy token refresh,
45
+ account dashboard / orders / order detail / addresses routes — all
46
+ EJS-guarded so the files only emit when enabled.
47
+
48
+ **`shopify-storefront` template** — storefront-first.
49
+ `tanstack create my-shop --template shopify-storefront` cascades the
50
+ `shopify` add-on (which cascades `tanstack-query`) and replaces the
51
+ home route with a polished landing (hero + featured collections + best
52
+ sellers grid).
53
+
54
+ **Zero-config first run.** Defaults to Shopify's public Hydrogen demo
55
+ store (`hydrogen-preview.myshopify.com`) so the storefront renders
56
+ real products immediately. Override the four env vars in `.env.local`
57
+ (or your deploy target's dashboard) to point at your store. Demo
58
+ defaults are baked into source as fallbacks, so the experience doesn't
59
+ break when a runtime doesn't load `.env` files into `process.env`.
60
+
61
+ **Portable.** Cookie ops via `@tanstack/react-start/server`; crypto via
62
+ Web Crypto (`crypto.subtle`); generic `CDN-Cache-Control` for browse
63
+ (`s-maxage=300, stale-while-revalidate=600`) and `private, no-store`
64
+ for cart. Works on Node, Cloudflare Workers, Shopify Oxygen (just
65
+ Workers), Vercel, Netlify, Bun, Deno.
66
+
67
+ **Header layout fix.** While the cart-count badge is the new
68
+ right-aligned action, the base scaffold's `Header` was placing the
69
+ social icons left-of-center on `sm+`. Reordered the JSX so navigation
70
+ sits between the logo and the right-side actions in DOM order, with
71
+ one mobile-only `order-3` to keep `flex-wrap` putting nav on its own
72
+ row. Result: logo → nav → (auto-spaced) → cart/social/theme on every
73
+ breakpoint, and a more sensible reading order for screen readers.
74
+
3
75
  ## 0.65.0
4
76
 
5
77
  ### Minor Changes
@@ -0,0 +1,26 @@
1
+ # PowerSync
2
+
3
+ This project includes the PowerSync Web SDK and React hooks.
4
+
5
+ ## Environment
6
+
7
+ Set these variables in `.env.local`:
8
+
9
+ - `VITE_POWERSYNC_URL`
10
+ - `VITE_POWERSYNC_TOKEN` for local development only
11
+
12
+ ## What The Add-on Includes
13
+
14
+ - `src/lib/powersync/AppSchema.ts`
15
+ - `src/lib/powersync/BackendConnector.ts`
16
+ - `src/integrations/powersync/provider.tsx`
17
+ - `src/routes/demo/powersync.tsx`
18
+
19
+ ## Next Steps
20
+
21
+ 1. Replace the development token flow in `src/lib/powersync/BackendConnector.ts` with your real auth flow.
22
+ 2. Update the sample schema in `src/lib/powersync/AppSchema.ts` to match your synced tables.
23
+ 3. Implement the upload logic in `uploadData()` so local mutations are written back to your backend.
24
+
25
+ PowerSync setup guidance:
26
+ https://docs.powersync.com/client-sdk-references/js-web
@@ -0,0 +1,3 @@
1
+
2
+ VITE_POWERSYNC_URL=
3
+ VITE_POWERSYNC_TOKEN=
@@ -0,0 +1,17 @@
1
+ import type { Plugin } from 'vite'
2
+
3
+ export default function powersyncVite(): Plugin {
4
+ return {
5
+ name: 'powersync-vite',
6
+ config() {
7
+ return {
8
+ optimizeDeps: {
9
+ exclude: ['@powersync/web'],
10
+ },
11
+ worker: {
12
+ format: 'es',
13
+ },
14
+ }
15
+ },
16
+ }
17
+ }
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from 'react'
2
+ import { PowerSyncContext } from '@powersync/react'
3
+ import { PowerSyncDatabase, WASQLiteOpenFactory } from '@powersync/web'
4
+
5
+ import { AppSchema } from '#/lib/powersync/AppSchema'
6
+ import { BackendConnector } from '#/lib/powersync/BackendConnector'
7
+
8
+ const db = new PowerSyncDatabase({
9
+ database: new WASQLiteOpenFactory({
10
+ dbFilename: 'powersync.db',
11
+ }),
12
+ schema: AppSchema,
13
+ flags: {
14
+ disableSSRWarning: true,
15
+ },
16
+ })
17
+
18
+ void db.connect(new BackendConnector())
19
+
20
+ export default function PowerSyncProvider({
21
+ children,
22
+ }: {
23
+ children: ReactNode
24
+ }) {
25
+ return <PowerSyncContext.Provider value={db}>{children}</PowerSyncContext.Provider>
26
+ }
@@ -0,0 +1,17 @@
1
+ import { Schema, Table, column } from '@powersync/web'
2
+
3
+ const todos = new Table(
4
+ {
5
+ created_at: column.text,
6
+ description: column.text,
7
+ completed: column.integer,
8
+ },
9
+ { indexes: { created_at: ['created_at'] } },
10
+ )
11
+
12
+ export const AppSchema = new Schema({
13
+ todos,
14
+ })
15
+
16
+ export type Database = (typeof AppSchema)['types']
17
+ export type TodoRecord = Database['todos']
@@ -0,0 +1,52 @@
1
+ import {
2
+ type AbstractPowerSyncDatabase,
3
+ type PowerSyncBackendConnector,
4
+ UpdateType,
5
+ } from '@powersync/web'
6
+
7
+ export class BackendConnector implements PowerSyncBackendConnector {
8
+ private readonly powersyncUrl = import.meta.env.VITE_POWERSYNC_URL
9
+ private readonly powersyncToken = import.meta.env.VITE_POWERSYNC_TOKEN
10
+
11
+ async fetchCredentials() {
12
+ if (!this.powersyncUrl || !this.powersyncToken) {
13
+ return null
14
+ }
15
+
16
+ return {
17
+ endpoint: this.powersyncUrl,
18
+ token: this.powersyncToken,
19
+ }
20
+ }
21
+
22
+ async uploadData(database: AbstractPowerSyncDatabase): Promise<void> {
23
+ const transaction = await database.getNextCrudTransaction()
24
+
25
+ if (!transaction) {
26
+ return
27
+ }
28
+
29
+ try {
30
+ for (const op of transaction.crud) {
31
+ const record = { ...op.opData, id: op.id }
32
+
33
+ switch (op.op) {
34
+ case UpdateType.PUT:
35
+ console.info('TODO: create record remotely', record)
36
+ break
37
+ case UpdateType.PATCH:
38
+ console.info('TODO: patch record remotely', record)
39
+ break
40
+ case UpdateType.DELETE:
41
+ console.info('TODO: delete record remotely', record)
42
+ break
43
+ }
44
+ }
45
+
46
+ await transaction.complete()
47
+ } catch (error) {
48
+ console.error('PowerSync uploadData failed', error)
49
+ throw error
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,129 @@
1
+ import { useState } from 'react'
2
+ import { createFileRoute } from '@tanstack/react-router'
3
+ import { usePowerSync, useQuery, useStatus } from '@powersync/react'
4
+
5
+ export const Route = createFileRoute('/demo/powersync')({
6
+ component: PowerSyncDemo,
7
+ })
8
+
9
+ type TodoRow = {
10
+ id: string
11
+ created_at: string
12
+ description: string
13
+ completed: number
14
+ }
15
+
16
+ function PowerSyncDemo() {
17
+ const powerSync = usePowerSync()
18
+ const status = useStatus()
19
+ const { data } = useQuery(
20
+ 'SELECT id, created_at, description, completed FROM todos ORDER BY created_at DESC',
21
+ )
22
+ const todos = (data ?? []) as Array<TodoRow>
23
+ const [description, setDescription] = useState('')
24
+ const [error, setError] = useState<string | null>(null)
25
+
26
+ async function addTodo(event: React.FormEvent<HTMLFormElement>) {
27
+ event.preventDefault()
28
+
29
+ const nextDescription = description.trim()
30
+ if (!nextDescription) {
31
+ return
32
+ }
33
+
34
+ try {
35
+ setError(null)
36
+ await powerSync.execute(
37
+ 'INSERT INTO todos (id, created_at, description, completed) VALUES (?, ?, ?, ?)',
38
+ [crypto.randomUUID(), new Date().toISOString(), nextDescription, 0],
39
+ )
40
+
41
+ setDescription('')
42
+ } catch (error) {
43
+ console.error('Failed to insert PowerSync todo', error)
44
+ setError('Failed to insert row. Please try again.')
45
+ }
46
+ }
47
+
48
+ return (
49
+ <main className="page-wrap py-10">
50
+ <div className="max-w-3xl space-y-6">
51
+ <header className="space-y-2">
52
+ <p className="text-sm font-semibold uppercase tracking-[0.2em] text-[var(--sea-ink-soft)]">
53
+ Offline Sync
54
+ </p>
55
+ <h1 className="text-3xl font-semibold tracking-tight">PowerSync</h1>
56
+ <p className="text-sm text-[var(--sea-ink-soft)]">
57
+ This demo writes to the local SQLite database immediately. Replace the sample
58
+ schema and backend connector with your real PowerSync configuration.
59
+ </p>
60
+ </header>
61
+
62
+ <section className="rounded-3xl border border-[var(--line)] bg-white/70 p-5 shadow-sm">
63
+ <h2 className="text-sm font-semibold uppercase tracking-[0.16em] text-[var(--sea-ink-soft)]">
64
+ Connection State
65
+ </h2>
66
+ <pre className="mt-3 overflow-auto rounded-2xl bg-[var(--chip-bg)] p-4 text-xs leading-6 text-[var(--sea-ink)]">
67
+ {JSON.stringify(status, null, 2)}
68
+ </pre>
69
+ </section>
70
+
71
+ <section className="rounded-3xl border border-[var(--line)] bg-white/70 p-5 shadow-sm">
72
+ <h2 className="text-sm font-semibold uppercase tracking-[0.16em] text-[var(--sea-ink-soft)]">
73
+ Local Todos
74
+ </h2>
75
+ {error ? (
76
+ <p className="mt-3 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
77
+ {error}
78
+ </p>
79
+ ) : null}
80
+ <form className="mt-4 flex flex-col gap-3 sm:flex-row" onSubmit={addTodo}>
81
+ <label className="sr-only" htmlFor="powersync-todo-description">
82
+ Todo description
83
+ </label>
84
+ <input
85
+ id="powersync-todo-description"
86
+ className="min-w-0 flex-1 rounded-2xl border border-[var(--line)] bg-white px-4 py-3 text-sm text-[var(--sea-ink)] outline-none"
87
+ onChange={(event) => setDescription(event.target.value)}
88
+ placeholder="Write to the local PowerSync database"
89
+ value={description}
90
+ />
91
+ <button
92
+ className="rounded-2xl bg-[var(--sea-ink)] px-4 py-3 text-sm font-semibold text-white"
93
+ type="submit"
94
+ >
95
+ Insert Local Row
96
+ </button>
97
+ </form>
98
+
99
+ <ul className="mt-5 space-y-3">
100
+ {todos.length === 0 ? (
101
+ <li className="rounded-2xl border border-dashed border-[var(--line)] px-4 py-5 text-sm text-[var(--sea-ink-soft)]">
102
+ No rows yet. Insert one locally, then wire `uploadData()` to send it upstream.
103
+ </li>
104
+ ) : (
105
+ todos.map((todo) => (
106
+ <li
107
+ className="rounded-2xl border border-[var(--line)] bg-[var(--chip-bg)] px-4 py-4"
108
+ key={todo.id}
109
+ >
110
+ <div className="flex items-start justify-between gap-4">
111
+ <div>
112
+ <p className="font-medium text-[var(--sea-ink)]">{todo.description}</p>
113
+ <p className="mt-1 text-xs text-[var(--sea-ink-soft)]">
114
+ {todo.created_at}
115
+ </p>
116
+ </div>
117
+ <span className="rounded-full border border-[var(--line)] px-2 py-1 text-xs font-semibold text-[var(--sea-ink-soft)]">
118
+ {todo.completed ? 'done' : 'pending'}
119
+ </span>
120
+ </div>
121
+ </li>
122
+ ))
123
+ )}
124
+ </ul>
125
+ </section>
126
+ </div>
127
+ </main>
128
+ )
129
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "PowerSync",
3
+ "description": "Add PowerSync offline sync to your application.",
4
+ "phase": "add-on",
5
+ "type": "add-on",
6
+ "category": "database",
7
+ "color": "#2563EB",
8
+ "priority": 55,
9
+ "link": "https://docs.powersync.com/client-sdk-references/js-web",
10
+ "modes": ["file-router"],
11
+ "envVars": [
12
+ {
13
+ "name": "VITE_POWERSYNC_URL",
14
+ "description": "PowerSync instance URL",
15
+ "required": true,
16
+ "file": ".env.local"
17
+ },
18
+ {
19
+ "name": "VITE_POWERSYNC_TOKEN",
20
+ "description": "Client-visible development token for local testing; VITE_* env vars are exposed to the browser",
21
+ "required": false,
22
+ "file": ".env.local"
23
+ }
24
+ ],
25
+ "integrations": [
26
+ {
27
+ "type": "vite-plugin",
28
+ "path": "powersync-vite-plugin.ts",
29
+ "jsName": "powersyncVite",
30
+ "code": "powersyncVite()"
31
+ },
32
+ {
33
+ "type": "provider",
34
+ "path": "src/integrations/powersync/provider.tsx",
35
+ "jsName": "PowerSyncProvider"
36
+ }
37
+ ],
38
+ "routes": [
39
+ {
40
+ "url": "/demo/powersync",
41
+ "name": "PowerSync",
42
+ "path": "src/routes/demo/powersync.tsx",
43
+ "jsName": "PowerSyncDemo"
44
+ }
45
+ ]
46
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "dependencies": {
3
+ "@journeyapps/wa-sqlite": "^1.2.6",
4
+ "@powersync/react": "^1.7.4",
5
+ "@powersync/web": "^1.26.1"
6
+ }
7
+ }
@@ -0,0 +1,6 @@
1
+ <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="64" height="64" rx="16" fill="#2563EB"/>
3
+ <path d="M20 21C20 18.7909 21.7909 17 24 17H39.5C45.299 17 50 21.701 50 27.5C50 33.299 45.299 38 39.5 38H29V47H20V21Z" fill="white"/>
4
+ <path d="M29 29H38.5C40.9853 29 43 26.9853 43 24.5C43 22.0147 40.9853 20 38.5 20H29V29Z" fill="#2563EB"/>
5
+ <circle cx="43" cy="44" r="7" fill="#93C5FD"/>
6
+ </svg>
@@ -0,0 +1,86 @@
1
+ # Shopify
2
+
3
+ Headless Shopify storefront for TanStack Start. Mounts `/shop/*` routes
4
+ alongside your existing app — your home page stays untouched.
5
+
6
+ The default `.env.local` points at Shopify's public Hydrogen demo store, so the
7
+ storefront renders real products on first run with zero setup.
8
+
9
+ ## Routes
10
+
11
+ | Route | What it does |
12
+ |--------------------------------|---------------------------------------------|
13
+ | `/shop` | Shop landing — featured products + collections |
14
+ | `/shop/products/$handle` | Product detail (variants, images, options) |
15
+ | `/shop/collections/$handle` | Collection grid with sort + pagination |
16
+ | `/shop/cart` | Cart line items, discount codes, checkout |
17
+ | `/shop/search` | Product search |
18
+ | `/shop/pages/$handle` | Shopify CMS pages (about, etc.) |
19
+ | `/shop/policies/$handle` | Privacy, refund, terms, shipping |
20
+
21
+ If you opted into customer accounts during scaffold:
22
+
23
+ | Route | What it does |
24
+ |--------------------------------------|------------------------------------|
25
+ | `/shop/account/login` | Kick off Shopify OAuth |
26
+ | `/shop/account/callback` | OAuth callback handler |
27
+ | `/shop/account/logout` | End the customer session |
28
+ | `/shop/account` | Dashboard |
29
+ | `/shop/account/orders` | Order history |
30
+ | `/shop/account/orders/$id` | Order detail |
31
+ | `/shop/account/addresses` | Manage saved addresses |
32
+
33
+ ## Connect your store
34
+
35
+ 1. In Shopify admin, go to **Settings > Apps and sales channels > Develop apps**.
36
+ 2. Create a new app, enable the **Storefront API**, and copy the public access token.
37
+ 3. Set in `.env.local`:
38
+ ```
39
+ SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
40
+ SHOPIFY_PUBLIC_STOREFRONT_TOKEN=...
41
+ ```
42
+ 4. (Optional) For higher rate limits + buyer-IP forwarding, also create a private
43
+ token and set `SHOPIFY_PRIVATE_STOREFRONT_TOKEN`.
44
+
45
+ ## Enable customer accounts
46
+
47
+ If `customerAccount=enabled` was selected during scaffold:
48
+
49
+ 1. In Shopify admin, go to **Settings > Customer accounts > Headless**.
50
+ 2. Register a public client. Add `http://localhost:3000/shop/account/callback`
51
+ *and* your production callback URL to the redirect URIs.
52
+ 3. Copy the Client ID and Shop ID into `.env.local`:
53
+ ```
54
+ SHOPIFY_CUSTOMER_ACCOUNT_CLIENT_ID=...
55
+ SHOPIFY_CUSTOMER_ACCOUNT_SHOP_ID=...
56
+ SHOPIFY_SESSION_SECRET=$(openssl rand -hex 32)
57
+ ```
58
+
59
+ The Hydrogen demo store doesn't have customer accounts configured, so the
60
+ default demo creds won't work for `/shop/account/*` — you'll need a real store.
61
+
62
+ ## Architecture
63
+
64
+ - **Storefront API client** — server-only fetch in `src/server/shopify/storefront-client.ts`.
65
+ All product/cart reads go through the server (private token never reaches the browser).
66
+ - **Cart state** — Cart ID stored in an httpOnly cookie (`tanstack_cart_id`). React
67
+ Query owns the cache (single key `['shopify', 'cart']`); optimistic updates with
68
+ a module-level mutation counter to batch invalidations.
69
+ - **GraphQL queries** — hand-written strings in `src/lib/shopify/queries.ts`, types
70
+ sliced from `@shopify/hydrogen-react/storefront-api-types` (type-only import; zero runtime).
71
+ - **Customer accounts** — hand-rolled OAuth 2.1 PKCE with `.well-known` discovery
72
+ (no usable npm client exists yet). Tokens in a signed httpOnly cookie.
73
+ - **Checkout** — redirects to `cart.checkoutUrl` (Shopify-hosted).
74
+
75
+ ## Deployment
76
+
77
+ Works anywhere TanStack Start runs:
78
+
79
+ - **Node** — `npm run build && npm start`
80
+ - **Cloudflare Workers / Shopify Oxygen** — Oxygen is just Workers under the hood;
81
+ build with the Workers preset and deploy to either platform.
82
+ - **Vercel / Netlify** — set the env vars in the dashboard.
83
+ - **Bun, Deno** — supported via Start's adapters.
84
+
85
+ For the customer-account flow, register both your local *and* production
86
+ callback URLs in the Shopify admin's headless app config.
@@ -0,0 +1,19 @@
1
+ # ─── Shopify ─────────────────────────────────────────────────────────────────
2
+ # These defaults point to Shopify's public Hydrogen demo store so the storefront
3
+ # works on first run with zero config. Replace with your store's values to go live.
4
+ # Get tokens at: Shopify admin > Settings > Apps and sales channels > Develop apps.
5
+ SHOPIFY_STORE_DOMAIN=hydrogen-preview.myshopify.com
6
+ SHOPIFY_STOREFRONT_API_VERSION=2026-01
7
+ SHOPIFY_PUBLIC_STOREFRONT_TOKEN=3b580e70970c4528da70c98e097c2fa0
8
+ # Optional — set to a private storefront token for higher rate limits + buyer-IP forwarding.
9
+ SHOPIFY_PRIVATE_STOREFRONT_TOKEN=
10
+
11
+ # ─── Shopify Customer Account API ────────────────────────────────────────────
12
+ # Required only if you opted into customer accounts during scaffold.
13
+ # Register a public client in Shopify admin > Settings > Customer accounts > Headless,
14
+ # then paste the client ID and your numeric shop ID below.
15
+ SHOPIFY_CUSTOMER_ACCOUNT_CLIENT_ID=
16
+ SHOPIFY_CUSTOMER_ACCOUNT_SHOP_ID=
17
+ SHOPIFY_CUSTOMER_ACCOUNT_REDIRECT_URI=http://localhost:3000/shop/account/callback
18
+ # 32+ random chars, e.g. `openssl rand -hex 32`. Used to sign customer session cookies.
19
+ SHOPIFY_SESSION_SECRET=
@@ -0,0 +1,41 @@
1
+ <% if (addOnOption.shopify.customerAccount !== 'enabled') { ignoreFile(); return; } %>
2
+ import { Link } from '@tanstack/react-router'
3
+
4
+ const ITEMS = [
5
+ { to: '/shop/account', label: 'Overview', exact: true },
6
+ { to: '/shop/account/orders', label: 'Orders', exact: false },
7
+ { to: '/shop/account/addresses', label: 'Addresses', exact: false },
8
+ ] as const
9
+
10
+ export function AccountNav() {
11
+ return (
12
+ <nav className="flex flex-col gap-1 text-sm">
13
+ {ITEMS.map((item) => (
14
+ <Link
15
+ key={item.to}
16
+ to={item.to}
17
+ activeOptions={{ exact: item.exact }}
18
+ className="rounded-md px-2 py-1.5 text-[var(--storefront-fg-muted)] hover:bg-[var(--storefront-line)]/40 hover:text-[var(--storefront-fg)]"
19
+ activeProps={{
20
+ className:
21
+ 'rounded-md px-2 py-1.5 bg-[var(--storefront-line)]/60 text-[var(--storefront-fg)] font-medium',
22
+ }}
23
+ >
24
+ {item.label}
25
+ </Link>
26
+ ))}
27
+ <form
28
+ action="/shop/account/logout"
29
+ method="post"
30
+ className="mt-4 border-t border-[var(--storefront-line)] pt-4"
31
+ >
32
+ <button
33
+ type="submit"
34
+ className="rounded-md px-2 py-1.5 text-left text-sm text-[var(--storefront-fg-muted)] hover:text-[var(--storefront-fg)]"
35
+ >
36
+ Sign out
37
+ </button>
38
+ </form>
39
+ </nav>
40
+ )
41
+ }
@@ -0,0 +1,48 @@
1
+ import { useAddToCart } from '#/hooks/use-cart'
2
+ import type { ProductDetail, ProductDetailVariant } from '#/lib/shopify/queries'
3
+
4
+ type AddToCartButtonProps = {
5
+ product: ProductDetail
6
+ variant: ProductDetailVariant | undefined
7
+ quantity?: number
8
+ }
9
+
10
+ export function AddToCartButton({
11
+ product,
12
+ variant,
13
+ quantity = 1,
14
+ }: AddToCartButtonProps) {
15
+ const { mutate, isPending } = useAddToCart()
16
+ const disabled = !variant || !variant.availableForSale || isPending
17
+
18
+ return (
19
+ <button
20
+ type="button"
21
+ disabled={disabled}
22
+ onClick={() => {
23
+ if (!variant) return
24
+ mutate({
25
+ variantId: variant.id,
26
+ quantity,
27
+ line: {
28
+ productTitle: product.title,
29
+ productHandle: product.handle,
30
+ variantTitle: variant.title,
31
+ price: variant.price,
32
+ image: variant.image ?? product.images.nodes[0] ?? null,
33
+ selectedOptions: variant.selectedOptions,
34
+ },
35
+ })
36
+ }}
37
+ className="w-full rounded-full bg-[var(--storefront-accent)] px-6 py-3.5 text-sm font-medium text-[var(--storefront-accent-fg)] transition disabled:cursor-not-allowed disabled:opacity-50 hover:opacity-90"
38
+ >
39
+ {!variant
40
+ ? 'Select options'
41
+ : !variant.availableForSale
42
+ ? 'Sold out'
43
+ : isPending
44
+ ? 'Adding…'
45
+ : 'Add to cart'}
46
+ </button>
47
+ )
48
+ }