@decocms/start 2.21.0 → 2.23.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.
@@ -43,7 +43,7 @@ Each phase has entry/exit criteria. Follow in order. Automation % indicates how
43
43
  | [2](#phase-2--signals--state) | Signals & State | ~50% | `references/signals/` |
44
44
  | [3](#phase-3--deco-framework) | Deco Framework Elimination | ~80% | `references/deco-framework/` |
45
45
  | [4](#phase-4--commerce--types) | Commerce Types & UI | ~70% | `references/commerce/` |
46
- | [5](#phase-5--platform-hooks) | Platform Hooks | 0% | `references/platform-hooks/` |
46
+ | [5](#phase-5--platform-hooks) | Platform Hooks (factories, W12+) | template | `references/platform-hooks-factories.md` |
47
47
  | [6](#phase-6--islands-elimination) | Islands Elimination | ~60% | `references/islands.md` |
48
48
  | [7](#phase-7--section-registry) | Section Registry & Setup | 0% | `references/async-rendering.md` |
49
49
  | [8](#phase-8--routes--cms) | Routes & CMS | template | `references/navigation.md` |
@@ -152,14 +152,18 @@ See: `references/commerce/README.md`, `references/vtex-commerce.md`
152
152
 
153
153
  **Entry**: Phase 4 complete
154
154
 
155
- **Actions** (manual implementation):
156
- 1. Create `src/hooks/useCart.ts` — module-level singleton + listener pattern
157
- 2. Create `src/hooks/useUser.ts`, `src/hooks/useWishlist.ts` (stubs or real)
158
- 3. Wire VTEX API calls via `@decocms/apps` invoke functions
155
+ **Actions (Wave 12+ factory-based — current)**:
156
+ 1. `src/hooks/useCart.ts` — 5-line shim around `createUseCart` from `@decocms/apps/vtex/hooks/createUseCart`
157
+ 2. `src/hooks/useUser.ts` 5-line shim around `createUseUser`
158
+ 3. `src/hooks/useWishlist.ts` 5-line shim around `createUseWishlist`
159
+ 4. The migration template (`scripts/migrate/templates/hooks.ts`) emits all three for VTEX sites automatically.
160
+
161
+ For non-VTEX platforms, scaffold no-op stubs using `@decocms/start/sdk/signal` (see factories doc § "Non-VTEX platforms").
159
162
 
160
163
  **Exit**: Cart add/remove works, no `apps/{platform}/hooks` imports
161
164
 
162
- See: `references/platform-hooks/README.md`
165
+ See: `references/platform-hooks-factories.md` (canonical, Wave 12+).
166
+ Pre-W12 manual approach is preserved at `references/platform-hooks/README.md` for sites that haven't migrated to factories yet.
163
167
 
164
168
  ---
165
169
 
@@ -356,7 +360,8 @@ For sites with 100+ sections:
356
360
  | Signals → TanStack Store | `references/signals/` |
357
361
  | Deco framework elimination | `references/deco-framework/` |
358
362
  | Commerce & widget types | `references/commerce/` |
359
- | Platform hooks (VTEX) | `references/platform-hooks/` |
363
+ | Platform hooks (VTEX, factories — Wave 12+) | `references/platform-hooks-factories.md` |
364
+ | Platform hooks (manual, legacy pre-W12) | `references/platform-hooks/README.md` |
360
365
  | Vite configuration | `references/vite-config/` |
361
366
  | Automation commands | `references/codemod-commands.md` |
362
367
  | Islands elimination | `references/islands.md` |
@@ -62,6 +62,27 @@ Both rewrite to the same React `onClick` / `onChange` etc.
62
62
  The biggest bucket. Almost always a `useScript`-wrapped function
63
63
  attached as an event handler, with no fetch involved.
64
64
 
65
+ > **Codemod available** (since `@decocms/start >= 2.21.0`). The
66
+ > migration script's `transforms` pipeline now runs
67
+ > `htmx-on-event-rename`, which mechanically rewrites
68
+ > `hx-on:click={…}` → `onClick={…}` (and every other standard DOM
69
+ > event in the [STANDARD_EVENT_MAP](https://github.com/decocms/deco-start/blob/main/scripts/migrate/transforms/htmx-on-events.ts)
70
+ > table) for both colon and dash variants. Handler bodies are
71
+ > preserved verbatim; if the body references Fresh-only globals
72
+ > (`useScript(…)`, `globalThis.window.STOREFRONT`, `STOREFRONT.…`),
73
+ > the codemod injects a single MIGRATION TODO comment at the top
74
+ > of the file pointing back here. **htmx lifecycle events**
75
+ > (`hx-on:htmx-config-request`, `hx-on-htmx-before-request`, etc.)
76
+ > and unknown custom events (`hx-on:my-custom-thing`) are left
77
+ > alone — those need manual rewrite, and the `htmx-residue` audit
78
+ > rule catches them.
79
+ >
80
+ > Smoke result on als-storefront (754 files): codemod renames 98
81
+ > `hx-on:*` attributes across 71 files; 67 of those files (94 %)
82
+ > get the body-TODO. Engineers still own the body rewrite below;
83
+ > the codemod just removes the dead `hx-*` attribute name so the
84
+ > file compiles in React.
85
+
65
86
  ### Before
66
87
 
67
88
  ```tsx
@@ -1,16 +1,29 @@
1
- # Platform Hooks Migration
1
+ # Platform Hooks Migration (legacy reference)
2
2
 
3
- Platform hooks (useCart, useUser, useWishlist) are the most complex migration target because they have real business logic.
3
+ > **This document describes the pre-Wave-12 manual approach.** New
4
+ > migrations should follow
5
+ > [`platform-hooks-factories.md`](../platform-hooks-factories.md), which
6
+ > covers the `createUseCart` / `createUseUser` / `createUseWishlist`
7
+ > factories from `@decocms/apps/vtex/hooks`. The factories collapse
8
+ > everything below into a 5-line site shim per hook.
9
+ >
10
+ > This file is kept for sites that scaffolded before the factories
11
+ > existed — typically sites with `src/lib/vtex-cart-server.ts` or
12
+ > hand-rolled `createServerFn` calls to VTEX endpoints inside
13
+ > `src/hooks/useCart.ts`. See the **"Migrating off the manual approach"**
14
+ > section in the new doc for the cleanup playbook.
4
15
 
5
- ## Strategy
16
+ ---
17
+
18
+ ## Strategy (legacy)
6
19
 
7
20
  All hooks are **site-local**. No Vite alias tricks. No compat layers.
8
21
 
9
- - Active platform hooks (VTEX for this store) -> `~/hooks/useCart.ts` with real implementation
10
- - Inactive platform hooks (Wake, Shopify, etc.) -> `~/hooks/platform/{name}.ts` with no-op stubs
11
- - Auth hooks -> `~/hooks/useUser.ts`, `~/hooks/useWishlist.ts`
22
+ - Active platform hooks (VTEX for this store) `~/hooks/useCart.ts` with real implementation
23
+ - Inactive platform hooks (Wake, Shopify, etc.) `~/hooks/platform/{name}.ts` with no-op stubs
24
+ - Auth hooks `~/hooks/useUser.ts`, `~/hooks/useWishlist.ts`
12
25
 
13
- ## VTEX useCart (Real Implementation)
26
+ ## VTEX useCart (Manual Implementation)
14
27
 
15
28
  ### Why Server Functions Are Required
16
29
 
@@ -18,7 +31,7 @@ The storefront domain (e.g., `my-store.deco.site`) differs from the VTEX checkou
18
31
 
19
32
  Use TanStack Start `createServerFn` to create server-side proxy functions that the client hook calls transparently.
20
33
 
21
- ### Server Functions (~/lib/vtex-cart-server.ts)
34
+ ### Server Functions (`~/lib/vtex-cart-server.ts`)
22
35
 
23
36
  ```typescript
24
37
  import { createServerFn } from "@tanstack/react-start";
@@ -40,50 +53,46 @@ export const getOrCreateCart = createServerFn({ method: "GET" })
40
53
  "X-VTEX-API-AppKey": API_KEY,
41
54
  "X-VTEX-API-AppToken": API_TOKEN,
42
55
  },
43
- body: JSON.stringify({ expectedOrderFormSections: ["items", "totalizers", "shippingData", "clientPreferencesData", "storePreferencesData", "marketingData"] }),
56
+ body: JSON.stringify({
57
+ expectedOrderFormSections: [
58
+ "items",
59
+ "totalizers",
60
+ "shippingData",
61
+ "clientPreferencesData",
62
+ "storePreferencesData",
63
+ "marketingData",
64
+ ],
65
+ }),
44
66
  });
45
67
  return res.json();
46
68
  });
47
-
48
- export const addItemsToCart = createServerFn({ method: "POST" })
49
- .validator((data: { orderFormId: string; items: any[] }) => data)
50
- .handler(async ({ data }) => {
51
- const res = await fetch(
52
- `https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm/${data.orderFormId}/items`,
53
- {
54
- method: "POST",
55
- headers: { "Content-Type": "application/json", "X-VTEX-API-AppKey": API_KEY, "X-VTEX-API-AppToken": API_TOKEN },
56
- body: JSON.stringify({ orderItems: data.items }),
57
- },
58
- );
59
- return res.json();
60
- });
61
-
62
- export const updateCartItems = createServerFn({ method: "POST" })
63
- .validator((data: { orderFormId: string; items: any[] }) => data)
64
- .handler(async ({ data }) => {
65
- const res = await fetch(
66
- `https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm/${data.orderFormId}/items/update`,
67
- {
68
- method: "POST",
69
- headers: { "Content-Type": "application/json", "X-VTEX-API-AppKey": API_KEY, "X-VTEX-API-AppToken": API_TOKEN },
70
- body: JSON.stringify({ orderItems: data.items }),
71
- },
72
- );
73
- return res.json();
74
- });
75
69
  ```
76
70
 
77
- ### Hook (~/hooks/useCart.ts)
71
+ > **Don't write code like this in new sites.** The factories already wrap
72
+ > all canonical VTEX action endpoints (cart, session, masterdata,
73
+ > newsletter, checkout) in `@decocms/apps/vtex/actions/*`. The migration
74
+ > template scaffolds `src/server/invoke.gen.ts` which exposes them as
75
+ > typed server functions; `~/server/invoke.ts` then re-exports them
76
+ > under `invoke.vtex.actions.*`. The factory consumes that surface and
77
+ > returns the legacy hook shape.
78
78
 
79
- Key design decisions:
79
+ ### Hook (`~/hooks/useCart.ts`)
80
+
81
+ Key design decisions of the legacy manual hook:
80
82
  - **Module-level singleton state** shared across all component instances
81
83
  - **Pub/sub pattern** (`_listeners` Set) for notifying React components of changes
82
- - **Cookie-based session**: reads/writes `checkout.vtex.com__orderFormId` on the **client** side (not VTEX's domain cookie)
84
+ - **Cookie-based session**: reads/writes `checkout.vtex.com__orderFormId` on the **client** side
83
85
  - Returns `cart` and `loading` with `.value` getter/setter for backward compat with Preact-era components
84
86
  - Lazy initialization: cart is fetched on first component mount, not on module load
85
87
  - Exports `itemToAnalyticsItem` for cart-specific analytics mapping
86
88
 
89
+ The factory in `@decocms/apps/vtex/hooks/createUseCart` ships *exactly*
90
+ these semantics — that's the implementation behind the new shim. If your
91
+ site needs to extend behaviour (e.g. extra analytics events, custom
92
+ post-add hooks), prefer wrapping the factory's exports rather than
93
+ forking back to a manual hook; the factory leaves space for that
94
+ without giving up the upgrade path.
95
+
87
96
  ### Cross-Domain Checkout
88
97
 
89
98
  The minicart's "Finalizar Compra" button must link to the VTEX checkout domain with the `orderFormId` as a query parameter — the VTEX domain can't read the storefront's cookies:
@@ -92,10 +101,8 @@ The minicart's "Finalizar Compra" button must link to the VTEX checkout domain w
92
101
  const checkoutUrl = `https://secure.${STORE_DOMAIN}/checkout/?orderFormId=${orderFormId}`;
93
102
  ```
94
103
 
95
- ### VTEX Types (~/types/vtex.ts)
96
-
97
- Site-local types for VTEX-specific structures:
98
- - `OrderFormItem`, `SimulationOrderForm`, `Sla`, `SKU`, `VtexProduct`
104
+ This pattern is unchanged by the factory — it's a UI concern, not a hook
105
+ concern. Implement it in your minicart component as before.
99
106
 
100
107
  ## Inactive Platform Stubs
101
108
 
@@ -130,9 +137,13 @@ export function useWishlist() {
130
137
  }
131
138
  ```
132
139
 
133
- Create similar stubs for: `shopify.ts`, `linx.ts`, `vnda.ts`, `nuvemshop.ts`.
140
+ Create similar stubs for: `shopify.ts`, `linx.ts`, `vnda.ts`, `nuvemshop.ts`. Match the return shape to what each platform's AddToCartButton expects (some use `addItem`, others `addItems`).
134
141
 
135
- Match the return shape to what each platform's AddToCartButton expects (some use `addItem`, others `addItems`).
142
+ The factory equivalent for non-VTEX sites is documented in
143
+ [`platform-hooks-factories.md` § "Non-VTEX platforms"](../platform-hooks-factories.md#non-vtex-platforms).
144
+ Until each platform has its own factory in `@decocms/apps`, the stub
145
+ shape above is still correct — but use `@decocms/start/sdk/signal`
146
+ instead of hand-rolled `{ value: ... }` objects.
136
147
 
137
148
  ## Import Rewrites
138
149
 
@@ -143,7 +154,6 @@ sed -i '' 's|from "apps/vtex/hooks/useWishlist.ts"|from "~/hooks/useWishlist"|g'
143
154
  sed -i '' 's|from "apps/vtex/utils/types.ts"|from "~/types/vtex"|g'
144
155
  sed -i '' 's|from "apps/shopify/hooks/useCart.ts"|from "~/hooks/platform/shopify"|g'
145
156
  sed -i '' 's|from "apps/wake/hooks/useCart.ts"|from "~/hooks/platform/wake"|g'
146
- # etc. for all platforms
147
157
  ```
148
158
 
149
159
  ## Verification
@@ -0,0 +1,186 @@
1
+ # Platform Hooks — Factory Pattern
2
+
3
+ > **Canonical reference for `createUseCart` / `createUseUser` /
4
+ > `createUseWishlist` from `@decocms/apps/vtex/hooks`.** These factories
5
+ > ship the legacy invoke-based hook semantics that migrated Fresh sites
6
+ > depend on — module-level singleton state, listener-based re-render,
7
+ > awaitable async actions, signal-shaped accessors. Sites consume them
8
+ > as 5-line shims.
9
+
10
+ This doc replaces the pre-W12 "manual `createServerFn` per VTEX endpoint"
11
+ approach in
12
+ [`platform-hooks/README.md`](./platform-hooks/README.md). If you scaffolded
13
+ a site before Wave 12 (≤ `@decocms/apps@2.x` / `@decocms/start@2.18`), see
14
+ "Migrating off the manual approach" at the bottom.
15
+
16
+ ---
17
+
18
+ ## What the factories own
19
+
20
+ | Concern | Where it lives |
21
+ |---|---|
22
+ | Module-level singleton state (`cart`, `user`, `wishlist`) | Inside the factory closure |
23
+ | `useEffect` + `forceRender(c => c + 1)` re-render pattern | Factory |
24
+ | Signal-shaped accessors (`cart.value`, `user.value`) | Factory |
25
+ | Awaitable mutations (`await addItem(...)`) | Factory |
26
+ | `itemToAnalyticsItem` helper (cart) | Factory |
27
+ | Wishlist arg swap (`productId` ↔ `productGroupId`) | Factory |
28
+ | **VTEX HTTP calls** | NOT in the factory — provided by the `invoke` proxy you pass in |
29
+
30
+ The factory only wires state + listeners. The site provides an `invoke`
31
+ object whose shape is structurally typed against
32
+ `CreateUseCartInvoke` / `CreateUseUserInvoke` / `CreateUseWishlistInvoke`
33
+ (exported next to each factory). The migration template generates an
34
+ `invoke` proxy in `src/server/invoke.ts` that meets all three shapes
35
+ without any extra wiring.
36
+
37
+ ---
38
+
39
+ ## Site-local shim (the entire file)
40
+
41
+ ### `src/hooks/useCart.ts`
42
+
43
+ ```ts
44
+ import { createUseCart } from "@decocms/apps/vtex/hooks/createUseCart";
45
+ import { invoke } from "~/server/invoke";
46
+
47
+ export type { OrderForm, OrderFormItem } from "@decocms/apps/vtex/types";
48
+
49
+ export const { useCart, resetCart, itemToAnalyticsItem } = createUseCart({
50
+ invoke,
51
+ });
52
+ ```
53
+
54
+ ### `src/hooks/useUser.ts`
55
+
56
+ ```ts
57
+ import { createUseUser } from "@decocms/apps/vtex/hooks/createUseUser";
58
+ import { invoke } from "~/server/invoke";
59
+
60
+ export type { Person } from "@decocms/apps/vtex/loaders/user";
61
+
62
+ export const { useUser, resetUser } = createUseUser({ invoke });
63
+ ```
64
+
65
+ ### `src/hooks/useWishlist.ts`
66
+
67
+ ```ts
68
+ import { createUseWishlist } from "@decocms/apps/vtex/hooks/createUseWishlist";
69
+ import { invoke } from "~/server/invoke";
70
+
71
+ export type { WishlistItem } from "@decocms/apps/vtex/loaders/wishlist";
72
+
73
+ export const { useWishlist, resetWishlist } = createUseWishlist({ invoke });
74
+ ```
75
+
76
+ That's the whole hook — no `createServerFn`, no VTEX URLs, no `AppKey`
77
+ plumbing. `npm run migrate` scaffolds these three files automatically
78
+ when `--platform vtex` is set; if you regenerate, the migration template
79
+ in `scripts/migrate/templates/hooks.ts` is the source of truth.
80
+
81
+ ---
82
+
83
+ ## Why a factory and not a single hook?
84
+
85
+ > Two reasons that come up repeatedly when reviewing migration PRs.
86
+
87
+ 1. **`useCart` already exists in apps.** The canonical `vtex/hooks/useCart.ts`
88
+ is built on TanStack Query and exposes the `Minicart` shape — that is
89
+ the shape new code should target. The factory exists strictly so
90
+ already-migrated UIs keep working without a rewrite. Both can coexist
91
+ in one site.
92
+ 2. **Singletons can't live in a shared package without leaking across
93
+ sites.** The factory call instantiates a fresh module-level state per
94
+ site. Importing `useCart` directly from apps would share state across
95
+ any sites that ran in the same Worker (matters less in practice, but
96
+ it's the architectural reason the factory exists).
97
+
98
+ The factory boundary is also the seam where we'd later wire
99
+ `@tanstack/store` if we wanted to — the API shape is signal-compatible
100
+ already.
101
+
102
+ ---
103
+
104
+ ## Non-VTEX platforms
105
+
106
+ Sites that target Wake / Shopify / VNDA / Linx / Nuvemshop still need a
107
+ hook surface that AddToCartButtons can consume. Until each platform has
108
+ its own factory in `@decocms/apps`, scaffold a no-op shim:
109
+
110
+ ```ts
111
+ // src/hooks/useCart.ts (custom platform)
112
+ import { signal } from "@decocms/start/sdk/signal";
113
+
114
+ const cart = signal<unknown>(null);
115
+ const loading = signal(false);
116
+
117
+ export function useCart() {
118
+ return {
119
+ cart,
120
+ loading,
121
+ async getCart() {
122
+ // TODO: call your platform's cart API via ~/server/invoke
123
+ return null;
124
+ },
125
+ async addItems(_items: unknown[]) {
126
+ // TODO
127
+ },
128
+ async updateItems(_items: unknown[]) {
129
+ // TODO
130
+ },
131
+ setCart(next: unknown) {
132
+ cart.value = next;
133
+ },
134
+ };
135
+ }
136
+
137
+ export default useCart;
138
+ ```
139
+
140
+ `@decocms/start/sdk/signal` (re-exported via `~/sdk/signal` after
141
+ migration) gives you the same `.value` getter/setter the factory uses,
142
+ so AddToCart UI components don't need to know which platform is wired.
143
+
144
+ The migration template's `generateGenericUseCart()` emits this stub when
145
+ `--platform` is `custom` (or any non-VTEX value).
146
+
147
+ ---
148
+
149
+ ## Migrating off the manual approach (pre-W12 sites)
150
+
151
+ If your site contains files like `src/lib/vtex-cart-server.ts` or hand-rolled `createServerFn` blocks for VTEX endpoints in `src/hooks/useCart.ts`, the post-cleanup audit will not auto-fix them — the manual code accumulated 6+ months of site-specific edits and the per-site judgment call about "what's still needed?" is real. The mechanical part:
152
+
153
+ 1. Replace the entire body of `src/hooks/useCart.ts` with the 5-line factory shim above.
154
+ 2. Delete `src/lib/vtex-cart-server.ts` (the migration template's `src/server/invoke.gen.ts` provides equivalent server functions wrapping `@decocms/apps/vtex/actions/checkout`).
155
+ 3. Verify `src/server/invoke.ts` exports the proxy shape the factory needs (cart actions under `invoke.vtex.actions`). The migration template scaffolds this; older sites may need to add the missing entries by hand.
156
+ 4. Run `npm run typecheck` — TypeScript will surface any callsites that referenced removed helpers (e.g. local `getOrCreateCart` shims).
157
+ 5. Repeat for `useUser` / `useWishlist`.
158
+
159
+ For `useUser`, the analog of step 2 is removing any local `currentUser` /
160
+ `getUser` server functions in favor of `@decocms/apps/vtex/loaders/user`
161
+ exposed via `invoke.vtex.loaders.user()`. For `useWishlist`, the
162
+ canonical surface is `@decocms/apps/vtex/{actions,loaders}/wishlist`.
163
+
164
+ If you find yourself wanting to add behaviour to a factory (extra cart
165
+ actions, custom analytics events) rather than ripping out the factory and going back to a manual hook:
166
+
167
+ - **Extra read paths** → expose a new loader from
168
+ `@decocms/apps/vtex/loaders/*`, register it in `~/server/invoke.ts`,
169
+ call from your component (the factory doesn't need to know).
170
+ - **Extra write paths** → ditto for `@decocms/apps/vtex/actions/*`.
171
+ - **Cross-cutting business logic** (e.g. PIX-specific offer pricing) →
172
+ this is the kind of seam that justifies a parallel `useOffer` factory.
173
+ Talk to the apps maintainers; opening up a factory's plugin slots is a
174
+ one-PR change in apps, not a per-site rewrite.
175
+
176
+ ---
177
+
178
+ ## Related
179
+
180
+ - `scripts/migrate/templates/hooks.ts` — the template that emits the
181
+ shims above.
182
+ - `apps-start/vtex/hooks/createUseCart.ts` /
183
+ `createUseUser.ts` / `createUseWishlist.ts` — the factories
184
+ themselves; each docstring is the authoritative API reference.
185
+ - `references/platform-hooks/README.md` — historical reference for the
186
+ pre-W12 manual approach (kept for sites that haven't migrated yet).