@ecomiq/storefront 0.1.1 → 1.0.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/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # @ecomiq/storefront
2
2
 
3
- SDK **headless** para construir tu propia tienda con **Next.js** y **React** sobre Ecomiq.
3
+ SDK **headless** para construir tu tienda con **Next.js** o **TanStack Start** y **React** sobre la API de Ecomiq.
4
4
 
5
- Incluye capa de **datos y API**: tipos, schemas Zod, cliente BFF (`/api/store`), hooks y utilidades server. **No** incluye UI precargada (checkout, CMS renderer, temas, `ShellRouter`, etc.): diseñas e implementas la interfaz en tu proyecto.
5
+ Incluye tipos, schemas Zod, cliente BFF (`/api/store`), hooks, `EcomiqProvider` y bootstrap server por framework. **No incluye UI de tienda** (CMS, temas, checkout, etc.): la implementas en tu proyecto.
6
6
 
7
7
  ## Requisitos
8
8
 
9
9
  - Node.js 20+
10
- - Next.js 15+ (App Router)
11
10
  - React 19+
12
- - Tailwind CSS 4+
11
+ - **Next.js 15+** (App Router) *o* **TanStack Start** (`@tanstack/react-start`)
12
+ - Tailwind CSS 4+ (opcional; ver `/nextjs/css`)
13
13
 
14
14
  ## Instalación
15
15
 
@@ -19,6 +19,18 @@ npm install @ecomiq/storefront
19
19
  pnpm add @ecomiq/storefront
20
20
  ```
21
21
 
22
+ Instala también el peer de tu stack:
23
+
24
+ ```bash
25
+ # Next.js
26
+ pnpm add next react react-dom @tanstack/react-query better-auth
27
+
28
+ # TanStack Start
29
+ pnpm add @tanstack/react-start react react-dom @tanstack/react-query better-auth
30
+ ```
31
+
32
+ **Next.js** — en `next.config`:
33
+
22
34
  ```js
23
35
  /** @type {import('next').NextConfig} */
24
36
  const nextConfig = {
@@ -28,16 +40,77 @@ const nextConfig = {
28
40
  export default nextConfig;
29
41
  ```
30
42
 
43
+ ## `ecomiq.config.js`
44
+
45
+ En la **raíz de tu app** (`ecomiq.config.js`, `.cjs` o `.mjs`):
46
+
47
+ ```js
48
+ // @ts-check
49
+
50
+ /** @type {import('@ecomiq/storefront').EcomiqConfig} */
51
+ const ecomiqConfig = {
52
+ slugStore: "mi-tienda",
53
+ };
54
+
55
+ module.exports = ecomiqConfig;
56
+ ```
57
+
58
+ Si defines `slugStore`, `getPublicStore` usa ese slug (útil en localhost). Si no, deriva del header `Host`. Plantilla: [ecomiq.config.example.cjs](./ecomiq.config.example.cjs).
59
+
60
+ ## Puntos de entrada (npm)
61
+
62
+ | Import | Uso |
63
+ |--------|-----|
64
+ | `@ecomiq/storefront` | Solo config: `EcomiqConfig`, `loadEcomiqConfig`, `resolvePublicStoreKey` |
65
+ | `@ecomiq/storefront/nextjs` | Adapter Next + capa React (hooks, provider, BFF, auth, tipos) |
66
+ | `@ecomiq/storefront/nextjs/api` | Proxy `/api/store/*` (App Router) |
67
+ | `@ecomiq/storefront/nextjs/css` | Tailwind mínimo (opcional) |
68
+ | `@ecomiq/storefront/nextjs/postcss` | PostCSS de referencia |
69
+ | `@ecomiq/storefront/tanstack` | Adapter TanStack + **misma** capa React que `/nextjs` |
70
+ | `@ecomiq/storefront/tanstack/api` | Proxy `/api/store/*` (server routes) |
71
+
72
+ Importa **todo desde el adapter de tu framework** (`/nextjs` o `/tanstack`). En npm no hay subpaths públicos `/core` ni `/react`.
73
+
74
+ Los hooks (`useStoreProducts`, `useStoreCart`, `useStoreCollections`, etc.) tienen **una sola implementación** reexportada por ambos adapters; solo cambia el bootstrap server (`getPublicStore`, handler del proxy).
75
+
76
+ ### Peers
77
+
78
+ | Paquete | Cuándo |
79
+ |---------|--------|
80
+ | `react`, `react-dom` | Siempre |
81
+ | `next` | Solo si usas `/nextjs` |
82
+ | `@tanstack/react-start` | Solo si usas `/tanstack` |
83
+ | `@opennextjs/cloudflare` | Opcional (settings vía KV) |
84
+
31
85
  ## Arquitectura
32
86
 
33
87
  ```
34
- Navegador → /api/store/* (proxy en tu Next.js) → API Ecomiq
35
- ↑ X-Store-Id (cookie / header tras getPublicStore)
88
+ ┌─────────────────────────────┐
89
+ │ @ecomiq/storefront │ ecomiq.config.js
90
+ └──────────────┬──────────────┘
91
+ │
92
+ ┌─────────────────────────┴─────────────────────────┐
93
+ ▼ ▼
94
+ /nextjs (+ /nextjs/api) /tanstack (+ /tanstack/api)
95
+ getPublicStore · toNextJsStoreApiHandler getPublicStore · createTanStackStoreApiHandler
96
+ │ │
97
+ └─────────────────────────┬─────────────────────────┘
98
+ ▼
99
+ Capa React compartida (hooks, EcomiqProvider, cliente BFF, auth)
100
+ │
101
+ ▼
102
+ core/server — fetchPublicStoreSettings · handleStoreProxyRequest
103
+ │
104
+ ▼
105
+ Navegador → /api/store/* (proxy en tu app) → API Ecomiq
106
+ ↑ contexto X-Store-Id (tras getPublicStore)
36
107
  ```
37
108
 
38
- El SDK **no** llama al upstream desde el browser; todas las peticiones van al proxy que tú montas con `toNextJsStoreApiHandler`.
109
+ El browser **no** llama al upstream directo; montas el proxy con el adapter de tu framework.
110
+
111
+ ---
39
112
 
40
- ## Inicio rápido
113
+ ## Inicio rápido — Next.js
41
114
 
42
115
  ### 1. Proxy BFF
43
116
 
@@ -50,12 +123,11 @@ export const { GET, POST, PUT, PATCH, DELETE } = toNextJsStoreApiHandler({
50
123
  });
51
124
  ```
52
125
 
53
- ### 2. Resolver la tienda (server)
126
+ ### 2. Layout y tienda
54
127
 
55
128
  ```tsx
56
129
  // app/layout.tsx (Server Component)
57
130
  import { getPublicStore, EcomiqProvider } from "@ecomiq/storefront/nextjs";
58
- import { authClient } from "./auth-client";
59
131
 
60
132
  export default async function RootLayout({ children }: { children: React.ReactNode }) {
61
133
  const { data: store, error } = await getPublicStore();
@@ -63,14 +135,14 @@ export default async function RootLayout({ children }: { children: React.ReactNo
63
135
  // tu página de error / tienda no encontrada
64
136
  }
65
137
  return (
66
- <EcomiqProvider store={store} authClient={authClient}>
138
+ <EcomiqProvider store={store}>
67
139
  {children}
68
140
  </EcomiqProvider>
69
141
  );
70
142
  }
71
143
  ```
72
144
 
73
- `EcomiqProvider` es un Client Component: puedes importarlo desde un layout servidor como en el ejemplo. Hidrata la tienda (`useStore`, `setClientStoreId`), React Query y la sesión (`authClient` de Better Auth).
145
+ `EcomiqProvider` hidrata `useStore`, configura React Query y el `storeId` del cliente BFF.
74
146
 
75
147
  ### 3. Catálogo con hooks
76
148
 
@@ -105,67 +177,183 @@ export default async function CatalogPage() {
105
177
  }
106
178
  ```
107
179
 
108
- ### 5. Estilos (opcional)
180
+ ---
181
+
182
+ ## Inicio rápido — TanStack Start
109
183
 
110
- ```css
111
- @import "tailwindcss";
112
- @import "@ecomiq/storefront/nextjs/css";
184
+ ### 1. Proxy BFF
185
+
186
+ Ruta splat: `/api/store/*` → `routes/api/store/$.ts`
187
+
188
+ ```ts
189
+ // src/routes/api/store/$.ts
190
+ import { createFileRoute } from "@tanstack/react-router";
191
+ import { createTanStackStoreApiHandler } from "@ecomiq/storefront/tanstack/api";
192
+
193
+ const storeApi = createTanStackStoreApiHandler({
194
+ apiBaseUrl: process.env.STOREFRONT_API_URL,
195
+ });
196
+
197
+ export const Route = createFileRoute("/api/store/$")({
198
+ server: { handlers: storeApi },
199
+ });
113
200
  ```
114
201
 
115
- El CSS publicado es **mínimo**. Tu design system y componentes viven en la app.
202
+ ### 2. Tienda en root route
116
203
 
117
- ## Puntos de entrada (npm)
204
+ ```tsx
205
+ // src/routes/__root.tsx
206
+ import { createRootRoute, Outlet } from "@tanstack/react-router";
207
+ import { getPublicStore, EcomiqProvider } from "@ecomiq/storefront/tanstack";
118
208
 
119
- | Import | Uso |
120
- |--------|-----|
121
- | `@ecomiq/storefront/core` | Tipos, Zod, utilidades, servicios axios legacy |
122
- | `@ecomiq/storefront/react` | `EcomiqProvider`, hooks, stores, auth OAuth, `findStorefrontCmsPageBySlug`, selector de variantes |
123
- | `@ecomiq/storefront/nextjs` | `getPublicStore`, `EcomiqProvider`, cliente BFF, hooks `useStore*` |
124
- | `@ecomiq/storefront/nextjs/api` | Handler del proxy `/api/store/*` |
125
- | `@ecomiq/storefront/nextjs/css` | Tailwind base opcional |
126
- | `@ecomiq/storefront/nextjs/postcss` | PostCSS de referencia |
209
+ export const Route = createRootRoute({
210
+ loader: async () => getPublicStore(),
211
+ component: RootComponent,
212
+ });
127
213
 
128
- No hay subpath `./client` en npm: las funciones del cliente BFF se importan desde **`/nextjs`**.
214
+ function RootComponent() {
215
+ const { data: store } = Route.useLoaderData();
216
+ return (
217
+ <EcomiqProvider store={store}>
218
+ <Outlet />
219
+ </EcomiqProvider>
220
+ );
221
+ }
222
+ ```
129
223
 
130
- ## Auth (Better Auth)
224
+ ### 3. Hooks (idénticos a Next)
131
225
 
132
- Plugins para OAuth password grant de Ecomiq:
226
+ ```tsx
227
+ import { useStoreProducts, useStoreCart } from "@ecomiq/storefront/tanstack";
228
+ ```
229
+
230
+ ---
231
+
232
+ ## Autenticación (Better Auth + SDK)
233
+
234
+ Instala [Better Auth](https://www.better-auth.com/).
235
+
236
+ **Servidor:**
133
237
 
134
238
  ```ts
135
- import {
136
- ecomiqOAuthPassword,
137
- ecomiqOAuthPasswordClient,
138
- } from "@ecomiq/storefront/react";
239
+ // lib/auth.ts
240
+ import { betterAuth } from "better-auth";
241
+ import { ecomiqOAuthPassword } from "@ecomiq/storefront/nextjs";
242
+ // TanStack: import { ecomiqOAuthPassword } from "@ecomiq/storefront/tanstack";
243
+
244
+ export const auth = betterAuth({
245
+ plugins: [ecomiqOAuthPassword()],
246
+ });
139
247
  ```
140
248
 
141
- ## Variables de entorno
249
+ **Ruta API (Next.js):**
142
250
 
143
- | Variable | Descripción |
144
- |----------|-------------|
145
- | `STOREFRONT_API_URL` | URL base de la API de tienda (proxy) |
146
- | `PUBLIC_STORE_SETTINGS_SOURCE` | `api` (default) o `kv` (OpenNext / Cloudflare) |
251
+ ```ts
252
+ // app/api/auth/[...all]/route.ts
253
+ import { toNextJsHandler } from "better-auth/next-js";
254
+ import { auth } from "@/lib/auth";
147
255
 
148
- Tras `getPublicStore`, el SDK fija `X-Store-Id` en el servidor para las llamadas al proxy.
256
+ export const { GET, POST } = toNextJsHandler(auth);
257
+ ```
149
258
 
150
- ## Qué no viene en el paquete npm
259
+ **Cliente:**
151
260
 
152
- Pensado para que **tú** construyas la UI:
261
+ ```ts
262
+ // lib/auth-client.ts
263
+ "use client";
153
264
 
154
- - `ShellRouter`, páginas exportadas
155
- - Checkout, carrito drawer, cuenta, formularios precargados
156
- - `CmsSectionsRenderer`, `DefaultCmsSectionsRenderer`, `SafeThemeInjector`
157
- - `ThemeComponentLoader` y temas `default` / `ana` / `sony`
158
- - Dependencia de `@ecomiq/ui`
265
+ import { createAuthClient } from "better-auth/react";
266
+ import { ecomiqOAuthPasswordClient } from "@ecomiq/storefront/nextjs";
159
267
 
160
- Esa capa completa existe en el **monorepo Ecomiq** (`workspace:*` sobre `src/`), no en el tarball npm.
268
+ export const authClient = createAuthClient({
269
+ plugins: [ecomiqOAuthPasswordClient()],
270
+ });
271
+ ```
161
272
 
162
- ## Desarrollo en el monorepo Ecomiq
273
+ **Layout** — pasa `authClient` al provider:
163
274
 
164
- Si trabajas dentro del repo `ecomiq`, el paquete se consume por fuente con exports amplios (`./react/components/*`, `./nextjs/export-pages/*`, etc.) y UI con `@ecomiq/ui`.
275
+ ```tsx
276
+ <EcomiqProvider store={store} authClient={authClient}>
277
+ {children}
278
+ </EcomiqProvider>
279
+ ```
165
280
 
166
- Para empaquetar o publicar en npm: ver [PUBLISHING.md](./PUBLISHING.md).
281
+ ### Login, registro y logout
282
+
283
+ ```tsx
284
+ "use client";
285
+
286
+ import {
287
+ useStore,
288
+ useStorefrontAuthClient,
289
+ useStorefrontSession,
290
+ registerCustomer,
291
+ signInWithEcomiqCredentials,
292
+ } from "@ecomiq/storefront/nextjs";
293
+
294
+ export function AccountActions() {
295
+ const { data: store } = useStore();
296
+ const authClient = useStorefrontAuthClient();
297
+ const { isAuthenticated, user, signOut, refetch } = useStorefrontSession();
298
+
299
+ async function handleLogin(email: string, password: string) {
300
+ await signInWithEcomiqCredentials(authClient, store, { email, password });
301
+ await refetch();
302
+ }
303
+
304
+ async function handleRegister(input: {
305
+ email: string;
306
+ password: string;
307
+ firstName: string;
308
+ lastName: string;
309
+ countryIsoCode: string;
310
+ phone: string;
311
+ }) {
312
+ await registerCustomer(input);
313
+ await handleLogin(input.email, input.password);
314
+ }
315
+
316
+ if (isAuthenticated) {
317
+ return (
318
+ <div>
319
+ <p>Hola, {user?.firstName ?? user?.email}</p>
320
+ <button type="button" onClick={() => signOut()}>Cerrar sesión</button>
321
+ </div>
322
+ );
323
+ }
324
+
325
+ return <div>{/* Tu UI */}</div>;
326
+ }
327
+ ```
167
328
 
168
- ## Dependencias recomendadas en tu app
329
+ En TanStack, cambia el import a `@ecomiq/storefront/tanstack`.
330
+
331
+ ---
332
+
333
+ ## Qué incluye cada adapter
334
+
335
+ Desde `/nextjs` o `/tanstack` obtienes:
336
+
337
+ - **Server:** `getPublicStore`, `getHostname`, `fetchPublicStoreSettings` (agnóstico)
338
+ - **Provider:** `EcomiqProvider`, `useStore`, `usePublicStore`
339
+ - **Hooks BFF:** `useStoreProducts`, `useStoreCollections`, `useStorePages`, `useStoreCart`
340
+ - **Cliente:** `getStorefrontProducts`, `registerCustomer`, servicios de carrito/cuenta, etc.
341
+ - **Auth:** `ecomiqOAuthPassword`, `ecomiqOAuthPasswordClient`, `signInWithEcomiqCredentials`, `useStorefrontSession`
342
+ - **Tipos y Zod:** `StorefrontSettings`, `StorefrontProduct`, schemas, `formatCurrency`
343
+ - **Utilidades:** `findStorefrontCmsPageBySlug`, selector de variantes de producto
344
+
345
+ ## Variables de entorno
346
+
347
+ | Variable | Descripción |
348
+ |----------|-------------|
349
+ | `STOREFRONT_API_URL` | URL base de la API de tienda (proxy) |
350
+ | `PUBLIC_STORE_SETTINGS_SOURCE` | `api` (default) o `kv` (Cloudflare KV vía `@opennextjs/cloudflare`) |
351
+
352
+ **Identificador de tienda:** `slugStore` en `ecomiq.config.*` si existe; si no, header `Host`.
353
+
354
+ ## Dependencias recomendadas
355
+
356
+ **Next.js:**
169
357
 
170
358
  ```json
171
359
  {
@@ -180,6 +368,21 @@ Para empaquetar o publicar en npm: ver [PUBLISHING.md](./PUBLISHING.md).
180
368
  }
181
369
  ```
182
370
 
371
+ **TanStack Start:**
372
+
373
+ ```json
374
+ {
375
+ "dependencies": {
376
+ "@ecomiq/storefront": "^0.1.0",
377
+ "@tanstack/react-query": "^5.66.0",
378
+ "@tanstack/react-start": "^1.0.0",
379
+ "better-auth": "^1.6.0",
380
+ "react": "^19.0.0",
381
+ "react-dom": "^19.0.0"
382
+ }
383
+ }
384
+ ```
385
+
183
386
  ## Soporte
184
387
 
185
388
  Documentación y credenciales: [ecomiq.pe](https://ecomiq.pe) (o el portal de tu organización).
package/dist/..d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+
3
+ declare const EcomiqConfigSchema: z.ZodObject<{
4
+ /**
5
+ * Slug de tienda en Ecomiq (p. ej. `mi-tienda` para `mi-tienda.ecomiq.app`).
6
+ * Si está definido, tiene prioridad sobre el `Host` de la request (útil en `localhost`).
7
+ */
8
+ slugStore: z.ZodOptional<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ slugStore?: string | undefined;
11
+ }, {
12
+ slugStore?: string | undefined;
13
+ }>;
14
+ type EcomiqConfig = z.infer<typeof EcomiqConfigSchema>;
15
+ /**
16
+ * Carga `ecomiq.config.js` (o `.cjs` / `.mjs`) desde la raíz del proyecto (`process.cwd()`).
17
+ * Resultado en caché por proceso.
18
+ */
19
+ declare function loadEcomiqConfig(cwd?: string): EcomiqConfig | null;
20
+ /** Limpia la caché (tests o recarga en dev). */
21
+ declare function clearEcomiqConfigCache(): void;
22
+ /**
23
+ * Identificador de tienda para settings (API / KV).
24
+ * 1. `slugStore` en `ecomiq.config.*` si existe.
25
+ * 2. Si no, deriva del header `Host` (`resolveStorefrontSettingsHost`).
26
+ */
27
+ declare function resolvePublicStoreKey(hostHeader: string | null | undefined, cwd?: string): string;
28
+ declare function hasEcomiqSlugStoreConfig(cwd?: string): boolean;
29
+
30
+ export { type EcomiqConfig, EcomiqConfigSchema, clearEcomiqConfigCache, hasEcomiqSlugStoreConfig, loadEcomiqConfig, resolvePublicStoreKey };