@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 +254 -51
- package/dist/..d.ts +30 -0
- package/dist/index-C-xqLr1g.d.ts +6551 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +103 -0
- package/dist/index.js.map +1 -0
- package/dist/nextjs/api.d.ts +10 -14
- package/dist/nextjs/api.js +74 -41
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs.d.ts +22 -3186
- package/dist/nextjs.js +1723 -259
- package/dist/nextjs.js.map +1 -1
- package/dist/store-proxy-handler-CNzKlTFA.d.ts +10 -0
- package/dist/tanstack/api.d.ts +41 -0
- package/dist/tanstack/api.js +196 -0
- package/dist/tanstack/api.js.map +1 -0
- package/dist/tanstack.d.ts +30 -0
- package/dist/{react.js → tanstack.js} +2369 -1971
- package/dist/tanstack.js.map +1 -0
- package/package.json +24 -12
- package/dist/core.d.ts +0 -3391
- package/dist/core.js +0 -1127
- package/dist/core.js.map +0 -1
- package/dist/find-storefront-cms-page-c8CktSz7.d.ts +0 -164
- package/dist/react.d.ts +0 -963
- package/dist/react.js.map +0 -1
- package/dist/store-to_vuWRF.d.ts +0 -4333
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# @ecomiq/storefront
|
|
2
2
|
|
|
3
|
-
SDK **headless** para construir tu
|
|
3
|
+
SDK **headless** para construir tu tienda con **Next.js** o **TanStack Start** y **React** sobre la API de Ecomiq.
|
|
4
4
|
|
|
5
|
-
Incluye
|
|
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
|
-
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
|
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.
|
|
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}
|
|
138
|
+
<EcomiqProvider store={store}>
|
|
67
139
|
{children}
|
|
68
140
|
</EcomiqProvider>
|
|
69
141
|
);
|
|
70
142
|
}
|
|
71
143
|
```
|
|
72
144
|
|
|
73
|
-
`EcomiqProvider`
|
|
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
|
-
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Inicio rápido — TanStack Start
|
|
109
183
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
202
|
+
### 2. Tienda en root route
|
|
116
203
|
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
224
|
+
### 3. Hooks (idénticos a Next)
|
|
131
225
|
|
|
132
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
} from "@ecomiq/storefront/
|
|
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
|
-
|
|
249
|
+
**Ruta API (Next.js):**
|
|
142
250
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
256
|
+
export const { GET, POST } = toNextJsHandler(auth);
|
|
257
|
+
```
|
|
149
258
|
|
|
150
|
-
|
|
259
|
+
**Cliente:**
|
|
151
260
|
|
|
152
|
-
|
|
261
|
+
```ts
|
|
262
|
+
// lib/auth-client.ts
|
|
263
|
+
"use client";
|
|
153
264
|
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
268
|
+
export const authClient = createAuthClient({
|
|
269
|
+
plugins: [ecomiqOAuthPasswordClient()],
|
|
270
|
+
});
|
|
271
|
+
```
|
|
161
272
|
|
|
162
|
-
|
|
273
|
+
**Layout** — pasa `authClient` al provider:
|
|
163
274
|
|
|
164
|
-
|
|
275
|
+
```tsx
|
|
276
|
+
<EcomiqProvider store={store} authClient={authClient}>
|
|
277
|
+
{children}
|
|
278
|
+
</EcomiqProvider>
|
|
279
|
+
```
|
|
165
280
|
|
|
166
|
-
|
|
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
|
-
|
|
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 };
|