@decocms/start 0.19.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.
- package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
- package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
- package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
- package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
- package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
- package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
- package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
- package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
- package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
- package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
- package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
- package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
- package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
- package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
- package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
- package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
- package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
- package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
- package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
- package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
- package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
- package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
- package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
- package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
- package/.cursor/skills/deco-core-architecture/engine.md +220 -0
- package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
- package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
- package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
- package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
- package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
- package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
- package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
- package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
- package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
- package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
- package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
- package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
- package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
- package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
- package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
- package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
- package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
- package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
- package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
- package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
- package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
- package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
- package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
- package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
- package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
- package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
- package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
- package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
- package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
- package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
- package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
- package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
- package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
- package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
- package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
- package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
- package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
- package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
- package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
- package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
- package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
- package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
- package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
- package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
- package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
- package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
- package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
- package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
- package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
- package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
- package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
- package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
- package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
- package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
- package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
- package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
- package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
- package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
- package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
- package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
- package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
- package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
- package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
- package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
- package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
- package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
- package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
- package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
- package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
- package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
- package/.cursor/skills/find-skills/SKILL.md +133 -0
- package/.cursor/skills/incident-report/SKILL.md +179 -0
- package/.cursor/skills/incident-report/references/5-whys.md +75 -0
- package/.cursor/skills/incident-report/templates/client-report.md +187 -0
- package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
- package/.cursor/skills/template-skill/SKILL.md +38 -0
- package/.github/workflows/release.yml +32 -0
- package/.releaserc.json +25 -0
- package/CLAUDE.md +135 -0
- package/GAP_ANALYSIS.md +224 -0
- package/GAP_ANALYSIS_V2.md +1013 -0
- package/biome.json +39 -0
- package/knip.json +5 -0
- package/package.json +87 -0
- package/scripts/generate-blocks.ts +69 -0
- package/scripts/generate-invoke.ts +378 -0
- package/scripts/generate-schema.ts +657 -0
- package/src/admin/cors.ts +29 -0
- package/src/admin/decofile.ts +72 -0
- package/src/admin/index.ts +24 -0
- package/src/admin/invoke.ts +163 -0
- package/src/admin/liveControls.ts +29 -0
- package/src/admin/meta.ts +70 -0
- package/src/admin/render.ts +205 -0
- package/src/admin/schema.ts +686 -0
- package/src/admin/setup.ts +44 -0
- package/src/cms/index.ts +59 -0
- package/src/cms/loader.ts +180 -0
- package/src/cms/registry.ts +162 -0
- package/src/cms/resolve.ts +1005 -0
- package/src/cms/sectionLoaders.ts +294 -0
- package/src/hooks/DecoPageRenderer.tsx +444 -0
- package/src/hooks/LazySection.tsx +109 -0
- package/src/hooks/LiveControls.tsx +108 -0
- package/src/hooks/SectionErrorFallback.tsx +85 -0
- package/src/hooks/index.ts +8 -0
- package/src/index.ts +5 -0
- package/src/matchers/builtins.ts +184 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +131 -0
- package/src/middleware/index.ts +80 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.ts +205 -0
- package/src/routes/adminRoutes.ts +83 -0
- package/src/routes/cmsRoute.ts +302 -0
- package/src/routes/components.tsx +34 -0
- package/src/routes/index.ts +15 -0
- package/src/sdk/analytics.ts +72 -0
- package/src/sdk/cacheHeaders.ts +268 -0
- package/src/sdk/cachedLoader.ts +206 -0
- package/src/sdk/clx.ts +3 -0
- package/src/sdk/cookie.ts +39 -0
- package/src/sdk/createInvoke.ts +57 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/env.ts +27 -0
- package/src/sdk/index.ts +63 -0
- package/src/sdk/instrumentedFetch.ts +137 -0
- package/src/sdk/invoke.ts +133 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/redirects.ts +217 -0
- package/src/sdk/requestContext.ts +184 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +41 -0
- package/src/sdk/sitemap.ts +143 -0
- package/src/sdk/urlUtils.ts +117 -0
- package/src/sdk/useDevice.ts +82 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.ts +101 -0
- package/src/sdk/workerEntry.ts +703 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +13 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# The Deco App Pattern
|
|
2
|
+
|
|
3
|
+
Every Deco app follows the same structural pattern. This document describes each component.
|
|
4
|
+
|
|
5
|
+
## Directory Layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
{app-name}/
|
|
9
|
+
├── mod.ts # App factory function
|
|
10
|
+
├── manifest.gen.ts # Auto-generated block registry
|
|
11
|
+
├── runtime.ts # Client-side invoke proxy
|
|
12
|
+
├── middleware.ts # Per-request middleware (optional)
|
|
13
|
+
├── actions/ # Server-side mutations
|
|
14
|
+
├── loaders/ # Server-side data fetching
|
|
15
|
+
├── hooks/ # Client-side hooks (Preact signals)
|
|
16
|
+
│ └── context.ts # Shared reactive state
|
|
17
|
+
├── sections/ # CMS-editable UI sections
|
|
18
|
+
├── handlers/ # HTTP request handlers
|
|
19
|
+
├── components/ # Shared components
|
|
20
|
+
├── utils/ # Internal utilities
|
|
21
|
+
│ ├── types.ts # API types
|
|
22
|
+
│ ├── client.ts # Typed HTTP client interface
|
|
23
|
+
│ └── transform.ts # API → schema.org mapping
|
|
24
|
+
├── workflows/ # Background jobs
|
|
25
|
+
└── preview/ # Admin preview UI
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## `mod.ts` — App Factory
|
|
29
|
+
|
|
30
|
+
The entry point. Exports a function that receives `Props` and returns an `App`.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import manifest, { Manifest } from "./manifest.gen.ts";
|
|
34
|
+
import { middleware } from "./middleware.ts";
|
|
35
|
+
import { type App, type AppContext as AC } from "@deco/deco";
|
|
36
|
+
|
|
37
|
+
export type AppContext = AC<ReturnType<typeof MyApp>>;
|
|
38
|
+
|
|
39
|
+
export interface Props {
|
|
40
|
+
account: string;
|
|
41
|
+
apiKey?: Secret;
|
|
42
|
+
salesChannel?: string;
|
|
43
|
+
platform: "myplatform";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default function MyApp(props: Props) {
|
|
47
|
+
// Create typed HTTP clients
|
|
48
|
+
const api = createHttpClient<MyAPI>({ base: `https://${props.account}.api.com` });
|
|
49
|
+
const gql = createGraphqlClient({ endpoint: `https://${props.account}.api.com/graphql` });
|
|
50
|
+
|
|
51
|
+
const state = { ...props, api, gql };
|
|
52
|
+
|
|
53
|
+
const app: App<Manifest, typeof state> = {
|
|
54
|
+
state,
|
|
55
|
+
manifest,
|
|
56
|
+
middleware, // Optional
|
|
57
|
+
dependencies: [], // Other apps this depends on
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return app;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Key Concepts:
|
|
65
|
+
- **`state`** is available to all loaders/actions via `ctx` (AppContext)
|
|
66
|
+
- **`manifest`** registers all blocks (auto-generated)
|
|
67
|
+
- **`middleware`** runs before every request
|
|
68
|
+
- **`dependencies`** compose other apps (e.g., `website`, `workflows`)
|
|
69
|
+
|
|
70
|
+
## `manifest.gen.ts` — Block Registry
|
|
71
|
+
|
|
72
|
+
Auto-generated by `deno task start`. Registers all discoverable blocks:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// DO NOT EDIT
|
|
76
|
+
import * as $$$0 from "./actions/cart/addItems.ts";
|
|
77
|
+
import * as $$$1 from "./loaders/productDetailsPage.ts";
|
|
78
|
+
import * as $$$$0 from "./sections/Analytics/Vtex.tsx";
|
|
79
|
+
|
|
80
|
+
const manifest = {
|
|
81
|
+
"actions": { "myapp/actions/cart/addItems.ts": $$$0 },
|
|
82
|
+
"loaders": { "myapp/loaders/productDetailsPage.ts": $$$1 },
|
|
83
|
+
"sections": { "myapp/sections/Analytics/Vtex.tsx": $$$$0 },
|
|
84
|
+
"name": "myapp",
|
|
85
|
+
"baseUrl": import.meta.url,
|
|
86
|
+
};
|
|
87
|
+
export type Manifest = typeof manifest;
|
|
88
|
+
export default manifest;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## `runtime.ts` — Client Invoke Proxy
|
|
92
|
+
|
|
93
|
+
Provides typed client-side invocations:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { Manifest } from "./manifest.gen.ts";
|
|
97
|
+
import { proxy } from "@deco/deco/web";
|
|
98
|
+
export const invoke = proxy<Manifest>();
|
|
99
|
+
|
|
100
|
+
// Usage (client-side):
|
|
101
|
+
const cart = await invoke.myapp.loaders.cart();
|
|
102
|
+
const result = await invoke.myapp.actions.cart.addItems({ items: [...] });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Actions Pattern
|
|
106
|
+
|
|
107
|
+
Server-side write operations. Each action is a single function:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// actions/cart/addItems.ts
|
|
111
|
+
import { AppContext } from "../../mod.ts";
|
|
112
|
+
|
|
113
|
+
export interface Props {
|
|
114
|
+
items: Array<{ id: string; quantity: number; seller: string }>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const action = async (props: Props, req: Request, ctx: AppContext): Promise<OrderForm> => {
|
|
118
|
+
const { vcsDeprecated } = ctx;
|
|
119
|
+
// ... call VTEX API
|
|
120
|
+
return orderForm;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export default action;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Action conventions:
|
|
127
|
+
- Receives `(props, req, ctx)` — Props are CMS-configurable
|
|
128
|
+
- Returns the modified resource
|
|
129
|
+
- Placed in `actions/{domain}/{operation}.ts`
|
|
130
|
+
- Cart actions return `OrderForm`
|
|
131
|
+
- Auth actions return `AuthResponse`
|
|
132
|
+
|
|
133
|
+
## Loaders Pattern
|
|
134
|
+
|
|
135
|
+
Server-side read operations:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// loaders/productDetailsPage.ts
|
|
139
|
+
import { AppContext } from "../mod.ts";
|
|
140
|
+
import { ProductDetailsPage } from "../../commerce/types.ts";
|
|
141
|
+
|
|
142
|
+
export interface Props {
|
|
143
|
+
slug: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const loader = async (props: Props, req: Request, ctx: AppContext): Promise<ProductDetailsPage | null> => {
|
|
147
|
+
// Fetch from API, transform to schema.org
|
|
148
|
+
return { "@type": "ProductDetailsPage", product, breadcrumbList, seo };
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export default loader;
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Loader conventions:
|
|
155
|
+
- Receives `(props, req, ctx)`
|
|
156
|
+
- Returns `null` for 404 cases
|
|
157
|
+
- Commerce loaders return `schema.org` types
|
|
158
|
+
- Placed in `loaders/{domain}/{operation}.ts`
|
|
159
|
+
|
|
160
|
+
## Hooks Pattern (Preact/Signals)
|
|
161
|
+
|
|
162
|
+
Client-side reactive state using `@preact/signals`:
|
|
163
|
+
|
|
164
|
+
### `context.ts` — Central State
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { IS_BROWSER } from "$fresh/runtime.ts";
|
|
168
|
+
import { signal } from "@preact/signals";
|
|
169
|
+
import { invoke } from "../runtime.ts";
|
|
170
|
+
|
|
171
|
+
const loading = signal<boolean>(true);
|
|
172
|
+
const context = {
|
|
173
|
+
cart: IS_BROWSER ? signal<OrderForm | null>(null) : { value: null },
|
|
174
|
+
user: IS_BROWSER ? signal<Person | null>(null) : { value: null },
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Serial queue with abort support
|
|
178
|
+
let queue = Promise.resolve();
|
|
179
|
+
let abort = () => {};
|
|
180
|
+
const enqueue = (cb: (signal: AbortSignal) => Promise<Partial<Context>>) => {
|
|
181
|
+
abort();
|
|
182
|
+
loading.value = true;
|
|
183
|
+
const controller = new AbortController();
|
|
184
|
+
queue = queue.then(async () => {
|
|
185
|
+
const result = await cb(controller.signal);
|
|
186
|
+
context.cart.value = result.cart || context.cart.value;
|
|
187
|
+
loading.value = false;
|
|
188
|
+
});
|
|
189
|
+
abort = () => controller.abort();
|
|
190
|
+
return queue;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Initial load
|
|
194
|
+
if (IS_BROWSER) {
|
|
195
|
+
enqueue((signal) => invoke({ cart: invoke.myapp.loaders.cart() }, { signal }));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const state = { ...context, loading, enqueue };
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### `useCart.ts` — Domain Hook
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { state as storeState } from "./context.ts";
|
|
205
|
+
import { invoke } from "../runtime.ts";
|
|
206
|
+
|
|
207
|
+
const { cart, loading } = storeState;
|
|
208
|
+
|
|
209
|
+
const enqueue = (key) => (props) =>
|
|
210
|
+
storeState.enqueue((signal) =>
|
|
211
|
+
invoke({ cart: { key, props } }, { signal })
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const state = {
|
|
215
|
+
cart,
|
|
216
|
+
loading,
|
|
217
|
+
addItems: enqueue("myapp/actions/cart/addItems.ts"),
|
|
218
|
+
updateItems: enqueue("myapp/actions/cart/updateItems.ts"),
|
|
219
|
+
// ...
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const useCart = () => state;
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Key Hook Concepts:
|
|
226
|
+
- **Serial queue** — mutations execute one at a time, latest aborts previous
|
|
227
|
+
- **`enqueue`** pattern — wraps `invoke` with abort control
|
|
228
|
+
- **Signals** — reactive primitives from `@preact/signals`
|
|
229
|
+
- All state flows through `context.ts`
|
|
230
|
+
|
|
231
|
+
## Middleware Pattern
|
|
232
|
+
|
|
233
|
+
Runs before every request. Sets up shared context:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
// middleware.ts
|
|
237
|
+
import { AppMiddlewareContext } from "./mod.ts";
|
|
238
|
+
|
|
239
|
+
export const middleware = (_props: unknown, req: Request, ctx: AppMiddlewareContext) => {
|
|
240
|
+
// Parse cookies, set segment in bag, etc.
|
|
241
|
+
const cookies = getCookies(req.headers);
|
|
242
|
+
setSegmentBag(cookies, req, ctx);
|
|
243
|
+
setISCookiesBag(cookies, ctx);
|
|
244
|
+
return ctx.next!();
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Uses `ctx.bag` for per-request shared state (segment, cookies, orderFormId).
|
|
249
|
+
|
|
250
|
+
## Sections Pattern
|
|
251
|
+
|
|
252
|
+
CMS-renderable Preact components:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// sections/Analytics/Vtex.tsx
|
|
256
|
+
import { AppContext } from "../../mod.ts";
|
|
257
|
+
|
|
258
|
+
export interface Props {
|
|
259
|
+
trackingId?: string;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export default function VtexAnalytics(props: Props) {
|
|
263
|
+
return <script dangerouslySetInnerHTML={{ __html: `...` }} />;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## OpenAPI Codegen
|
|
268
|
+
|
|
269
|
+
For apps with OpenAPI specs:
|
|
270
|
+
|
|
271
|
+
1. Place `{name}.openapi.json` in `utils/openapi/`
|
|
272
|
+
2. Run `deno task start`
|
|
273
|
+
3. Generated types appear in `{name}.openapi.gen.ts`
|
|
274
|
+
4. Use with `createHttpClient<GeneratedType>()`
|
|
275
|
+
|
|
276
|
+
## Commerce App Checklist
|
|
277
|
+
|
|
278
|
+
When building a new commerce integration:
|
|
279
|
+
|
|
280
|
+
- [ ] `utils/types.ts` — Define all API response types
|
|
281
|
+
- [ ] `utils/client.ts` — Define typed HTTP interface
|
|
282
|
+
- [ ] `utils/transform.ts` — Map API types → schema.org types
|
|
283
|
+
- [ ] `loaders/` — PDP, PLP, ProductList, Suggestions, Cart, User, Wishlist
|
|
284
|
+
- [ ] `actions/cart/` — addItems, updateItems, removeItems, updateCoupons
|
|
285
|
+
- [ ] `actions/authentication/` — signIn, logout, etc.
|
|
286
|
+
- [ ] `hooks/` — useCart, useUser, useWishlist, context
|
|
287
|
+
- [ ] `middleware.ts` — Cookie/segment handling
|
|
288
|
+
- [ ] `mod.ts` — App factory with clients
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Commerce Types — Schema.org Reference
|
|
2
|
+
|
|
3
|
+
The `commerce/types.ts` file (786 lines) defines the canonical data model for all Deco commerce apps. Every platform (VTEX, Shopify, Wake, Nuvemshop) transforms its native API responses into these types.
|
|
4
|
+
|
|
5
|
+
## Core Hierarchy
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Thing
|
|
9
|
+
├── Product (extends ProductLeaf)
|
|
10
|
+
│ └── ProductGroup (has hasVariant: ProductLeaf[])
|
|
11
|
+
├── Offer
|
|
12
|
+
│ └── AggregateOffer (has offers: Offer[])
|
|
13
|
+
├── BreadcrumbList (has itemListElement: ListItem<string>[])
|
|
14
|
+
├── ImageObject
|
|
15
|
+
├── VideoObject
|
|
16
|
+
├── Brand
|
|
17
|
+
├── Review
|
|
18
|
+
├── Person
|
|
19
|
+
├── Organization
|
|
20
|
+
├── Place
|
|
21
|
+
└── ItemList
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Product Types
|
|
25
|
+
|
|
26
|
+
### ProductLeaf
|
|
27
|
+
```typescript
|
|
28
|
+
interface ProductLeaf extends Omit<Thing, "@type"> {
|
|
29
|
+
"@type": "Product";
|
|
30
|
+
category?: string; // "category > subcategory > ..."
|
|
31
|
+
productID: string; // Platform SKU ID
|
|
32
|
+
sku: string; // SKU code (same as productID in most cases)
|
|
33
|
+
gtin?: string; // EAN/GTIN barcode
|
|
34
|
+
releaseDate?: string; // ISO 8601
|
|
35
|
+
brand?: Brand;
|
|
36
|
+
offers?: AggregateOffer;
|
|
37
|
+
isVariantOf?: ProductGroup;
|
|
38
|
+
isSimilarTo?: Product[];
|
|
39
|
+
isAccessoryOrSparePartFor?: Product[];
|
|
40
|
+
isRelatedTo?: Product[];
|
|
41
|
+
additionalProperty?: PropertyValue[];
|
|
42
|
+
review?: Review[];
|
|
43
|
+
aggregateRating?: AggregateRating;
|
|
44
|
+
questions?: Question[];
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### ProductGroup
|
|
49
|
+
```typescript
|
|
50
|
+
interface ProductGroup extends Omit<Thing, "@type"> {
|
|
51
|
+
"@type": "ProductGroup";
|
|
52
|
+
productGroupID: string; // Platform product ID
|
|
53
|
+
hasVariant: ProductLeaf[];
|
|
54
|
+
model?: string;
|
|
55
|
+
additionalProperty?: PropertyValue[];
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Product = ProductLeaf & { isVariantOf?: ProductGroup }
|
|
60
|
+
|
|
61
|
+
## Offer / AggregateOffer
|
|
62
|
+
|
|
63
|
+
### Offer
|
|
64
|
+
```typescript
|
|
65
|
+
interface Offer {
|
|
66
|
+
"@type": "Offer";
|
|
67
|
+
seller: string; // Seller ID (NOT name!)
|
|
68
|
+
sellerName?: string;
|
|
69
|
+
price: number;
|
|
70
|
+
priceCurrency?: string;
|
|
71
|
+
priceSpecification: UnitPriceSpecification[];
|
|
72
|
+
priceValidUntil?: string;
|
|
73
|
+
availability: ItemAvailability;
|
|
74
|
+
inventoryLevel?: QuantitativeValue;
|
|
75
|
+
itemCondition?: OfferItemCondition;
|
|
76
|
+
teasers?: Teasers[];
|
|
77
|
+
giftSkuIds?: string[];
|
|
78
|
+
hasMerchantReturnPolicy?: MerchantReturnPolicy;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### AggregateOffer
|
|
83
|
+
```typescript
|
|
84
|
+
interface AggregateOffer {
|
|
85
|
+
"@type": "AggregateOffer";
|
|
86
|
+
priceCurrency: string;
|
|
87
|
+
highPrice: number;
|
|
88
|
+
lowPrice: number;
|
|
89
|
+
offerCount: number;
|
|
90
|
+
offers: Offer[];
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### UnitPriceSpecification
|
|
95
|
+
```typescript
|
|
96
|
+
interface UnitPriceSpecification {
|
|
97
|
+
"@type": "UnitPriceSpecification";
|
|
98
|
+
priceType: PriceTypeEnumeration; // "https://schema.org/ListPrice" | "https://schema.org/SalePrice" | etc.
|
|
99
|
+
priceComponentType?: PriceComponentTypeEnumeration;
|
|
100
|
+
price: number;
|
|
101
|
+
billingDuration?: number;
|
|
102
|
+
billingIncrement?: number;
|
|
103
|
+
name?: string;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Page Types
|
|
108
|
+
|
|
109
|
+
### ProductDetailsPage
|
|
110
|
+
```typescript
|
|
111
|
+
interface ProductDetailsPage {
|
|
112
|
+
"@type": "ProductDetailsPage";
|
|
113
|
+
breadcrumbList: BreadcrumbList;
|
|
114
|
+
product: Product;
|
|
115
|
+
seo?: Seo;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### ProductListingPage
|
|
120
|
+
```typescript
|
|
121
|
+
interface ProductListingPage {
|
|
122
|
+
"@type": "ProductListingPage";
|
|
123
|
+
breadcrumb: BreadcrumbList;
|
|
124
|
+
filters: Filter[];
|
|
125
|
+
products: Product[];
|
|
126
|
+
pageInfo: PageInfo;
|
|
127
|
+
sortOptions: SortOption[];
|
|
128
|
+
seo?: Seo;
|
|
129
|
+
pageTypes?: PageType[];
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### PageInfo
|
|
134
|
+
```typescript
|
|
135
|
+
interface PageInfo {
|
|
136
|
+
currentPage: number;
|
|
137
|
+
nextPage?: string;
|
|
138
|
+
previousPage?: string;
|
|
139
|
+
records?: number;
|
|
140
|
+
recordPerPage?: number;
|
|
141
|
+
pageTypes?: PageType[];
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Filter Types
|
|
146
|
+
|
|
147
|
+
### FilterToggle
|
|
148
|
+
```typescript
|
|
149
|
+
interface FilterToggle extends FilterBase {
|
|
150
|
+
"@type": "FilterToggle";
|
|
151
|
+
values: FilterToggleValue[];
|
|
152
|
+
quantity: number;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### FilterRange
|
|
157
|
+
```typescript
|
|
158
|
+
interface FilterRange extends FilterBase {
|
|
159
|
+
"@type": "FilterRange";
|
|
160
|
+
values: FilterRangeValue; // { min: number, max: number }
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Navigation
|
|
165
|
+
|
|
166
|
+
### SiteNavigationElement
|
|
167
|
+
```typescript
|
|
168
|
+
interface SiteNavigationElement {
|
|
169
|
+
"@type": "SiteNavigationElement";
|
|
170
|
+
name?: string;
|
|
171
|
+
url?: string;
|
|
172
|
+
image?: ImageObject[];
|
|
173
|
+
children?: SiteNavigationElement[];
|
|
174
|
+
additionalType?: string;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Analytics (GA4)
|
|
179
|
+
|
|
180
|
+
### AnalyticsItem
|
|
181
|
+
```typescript
|
|
182
|
+
interface AnalyticsItem {
|
|
183
|
+
item_id: string;
|
|
184
|
+
item_group_id?: string;
|
|
185
|
+
quantity: number;
|
|
186
|
+
coupon?: string;
|
|
187
|
+
price: number;
|
|
188
|
+
discount?: number;
|
|
189
|
+
index?: number;
|
|
190
|
+
item_name?: string;
|
|
191
|
+
item_variant?: string;
|
|
192
|
+
item_brand?: string;
|
|
193
|
+
item_url?: string;
|
|
194
|
+
affiliation?: string;
|
|
195
|
+
item_list_id?: string;
|
|
196
|
+
item_list_name?: string;
|
|
197
|
+
[key: `item_category${number | ""}`]: string;
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Event Types
|
|
202
|
+
- `AddToCartEvent` — items + value + currency
|
|
203
|
+
- `RemoveFromCartEvent` — items + value + currency
|
|
204
|
+
- `ViewItemEvent` — items + value + currency
|
|
205
|
+
- `ViewItemListEvent` — items + item_list_name
|
|
206
|
+
- `SelectItemEvent` — items + item_list_name
|
|
207
|
+
- `BeginCheckoutEvent` — items + value + coupon
|
|
208
|
+
- `AddShippingInfoEvent` — items + shipping_tier
|
|
209
|
+
- `ViewCartEvent` — items + value + currency
|
|
210
|
+
- `ViewPromotionEvent` — items + promotion_name
|
|
211
|
+
- `SelectPromotionEvent` — items + promotion_name
|
|
212
|
+
- `LoginEvent` — method
|
|
213
|
+
- `SearchEvent` — search_term
|
|
214
|
+
|
|
215
|
+
## Availability Enum
|
|
216
|
+
```typescript
|
|
217
|
+
type ItemAvailability =
|
|
218
|
+
| "https://schema.org/BackOrder"
|
|
219
|
+
| "https://schema.org/Discontinued"
|
|
220
|
+
| "https://schema.org/InStock"
|
|
221
|
+
| "https://schema.org/InStoreOnly"
|
|
222
|
+
| "https://schema.org/LimitedAvailability"
|
|
223
|
+
| "https://schema.org/OnlineOnly"
|
|
224
|
+
| "https://schema.org/OutOfStock"
|
|
225
|
+
| "https://schema.org/PreOrder"
|
|
226
|
+
| "https://schema.org/PreSale"
|
|
227
|
+
| "https://schema.org/SoldOut";
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Key Rules for Platform Implementors
|
|
231
|
+
|
|
232
|
+
1. **`Offer.seller` MUST be the seller ID** (e.g., "1"), never the seller name
|
|
233
|
+
2. **`Offer.sellerName`** is optional and holds the display name
|
|
234
|
+
3. **Prices are in decimal** (not cents) — e.g., `199.90` not `19990`
|
|
235
|
+
4. **`productID`** = platform SKU ID; **`ProductGroup.productGroupID`** = platform product ID
|
|
236
|
+
5. **`Product.sku`** should equal `productID` — used for cart operations
|
|
237
|
+
6. **Images** must be `ImageObject[]` with `url`, `alternateName`, `encodingFormat`
|
|
238
|
+
7. **Breadcrumbs** use `ListItem<string>` with `position` (1-indexed) and `item` (URL)
|
|
239
|
+
8. **Category strings** use ` > ` separator (e.g., `"Furniture > Chairs > Office"`)
|