@ecomiq/storefront 2.0.19 → 2.0.20
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/package.json +4 -2
- package/src/auth/ecomiq-oauth-password-types.ts +0 -1
- package/src/auth/storefront-auth.ts +7 -0
- package/src/codegen.mjs +44 -0
- package/src/index.ts +4 -0
- package/src/pages/api.$.ts +20 -0
- package/src/pages/api.auth.$.ts +16 -0
- package/src/providers/auth.tsx +20 -7
- package/src/providers/cart.tsx +2 -2
- package/src/runtime/context.ts +1 -0
- package/src/server.ts +4 -2
- package/templates/__root.tsx +6 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecomiq/storefront",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.20",
|
|
4
4
|
"description": "Micro framework SSR de Ecomiq para storefronts. Páginas, datos y deploy sin configurar nada.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
"./internal/thank-you": "./src/pages/thank-you.tsx",
|
|
45
45
|
"./internal/cms-page": "./src/pages/pages.$pageSlug.tsx",
|
|
46
46
|
"./internal/account": "./src/pages/account.tsx",
|
|
47
|
+
"./internal/api-proxy": "./src/pages/api.$.ts",
|
|
48
|
+
"./internal/auth-api": "./src/pages/api.auth.$.ts",
|
|
47
49
|
"./package.json": "./package.json"
|
|
48
50
|
},
|
|
49
51
|
"files": [
|
|
@@ -80,7 +82,7 @@
|
|
|
80
82
|
"vite": "^8.1.0",
|
|
81
83
|
"wrangler": "^4.100.0",
|
|
82
84
|
"zod": "^3.25.0",
|
|
83
|
-
"@ecomiq/core": "^0.1.
|
|
85
|
+
"@ecomiq/core": "^0.1.7"
|
|
84
86
|
},
|
|
85
87
|
"peerDependencies": {
|
|
86
88
|
"react": "^19.0.0",
|
package/src/codegen.mjs
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* /checkout/$checkoutId ← flujo de checkout
|
|
18
18
|
* /thank-you ← post-compra
|
|
19
19
|
* /account/* ← área de cuenta (login/register en template: /login, /register)
|
|
20
|
+
* /api/$ ← proxy BFF same-origin
|
|
21
|
+
* /api/auth/$ ← Better Auth (handlers del framework)
|
|
20
22
|
*/
|
|
21
23
|
import {
|
|
22
24
|
mkdirSync,
|
|
@@ -90,6 +92,20 @@ const FRAMEWORK_LOCKED_PAGES = [
|
|
|
90
92
|
},
|
|
91
93
|
]
|
|
92
94
|
|
|
95
|
+
/** Server routes cerradas del framework (proxy BFF, Better Auth, etc.). */
|
|
96
|
+
const FRAMEWORK_LOCKED_API_ROUTES = [
|
|
97
|
+
{
|
|
98
|
+
rel: 'api/$.ts',
|
|
99
|
+
routeId: '/api/$',
|
|
100
|
+
importFrom: '@ecomiq/storefront/internal/api-proxy',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
rel: 'api/auth.$.ts',
|
|
104
|
+
routeId: '/api/auth/$',
|
|
105
|
+
importFrom: '@ecomiq/storefront/internal/auth-api',
|
|
106
|
+
},
|
|
107
|
+
]
|
|
108
|
+
|
|
93
109
|
export async function generateApp({ cwd, appDir, frameworkRoot, config }) {
|
|
94
110
|
const pagesDir = resolve(cwd, 'pages')
|
|
95
111
|
if (!existsSync(pagesDir)) {
|
|
@@ -209,11 +225,13 @@ export async function generateApp({ cwd, appDir, frameworkRoot, config }) {
|
|
|
209
225
|
}
|
|
210
226
|
|
|
211
227
|
assertNoFrameworkPageOverrides(pageEntries)
|
|
228
|
+
assertNoFrameworkApiOverrides(apiEntries)
|
|
212
229
|
|
|
213
230
|
const desiredRoutes = [
|
|
214
231
|
...pageEntries.map((entry) => entry.rel.replace(/\.jsx$/, '.tsx')),
|
|
215
232
|
...apiEntries.map((entry) => entry.rel),
|
|
216
233
|
...FRAMEWORK_LOCKED_PAGES.map((page) => page.rel),
|
|
234
|
+
...FRAMEWORK_LOCKED_API_ROUTES.map((route) => route.rel),
|
|
217
235
|
].sort()
|
|
218
236
|
|
|
219
237
|
for (const entry of pageEntries) {
|
|
@@ -244,6 +262,15 @@ export async function generateApp({ cwd, appDir, frameworkRoot, config }) {
|
|
|
244
262
|
)
|
|
245
263
|
}
|
|
246
264
|
|
|
265
|
+
for (const locked of FRAMEWORK_LOCKED_API_ROUTES) {
|
|
266
|
+
const outFile = join(routesDir, locked.rel)
|
|
267
|
+
mkdirSync(dirname(outFile), { recursive: true })
|
|
268
|
+
writeGeneratedFile(
|
|
269
|
+
outFile,
|
|
270
|
+
apiRouteFileSource(locked.routeId, locked.importFrom),
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
247
274
|
reconcileGeneratedRoutes(appDir, routesDir, desiredRoutes)
|
|
248
275
|
|
|
249
276
|
// ── 9. wrangler.jsonc para deploy en Cloudflare Workers ──
|
|
@@ -521,6 +548,23 @@ function assertNoFrameworkPageOverrides(pageEntries) {
|
|
|
521
548
|
}
|
|
522
549
|
}
|
|
523
550
|
|
|
551
|
+
function assertNoFrameworkApiOverrides(apiEntries) {
|
|
552
|
+
const reserved = new Map(
|
|
553
|
+
FRAMEWORK_LOCKED_API_ROUTES.map((route) => [route.routeId, route.rel]),
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
for (const entry of apiEntries) {
|
|
557
|
+
const routeId = fileToRouteId(entry.rel)
|
|
558
|
+
if (!reserved.has(routeId)) continue
|
|
559
|
+
throw new Error(
|
|
560
|
+
`La ruta ${routeId} es reservada por Ecomiq. ` +
|
|
561
|
+
`Elimina pages/${entry.rel}; ya la genera @ecomiq/storefront ` +
|
|
562
|
+
`(proxy BFF /api/$ y Better Auth /api/auth/$). ` +
|
|
563
|
+
`Puedes añadir otras rutas en pages/api/.`,
|
|
564
|
+
)
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
524
568
|
/** Server route: reexporta GET/POST/... del módulo del usuario. */
|
|
525
569
|
function apiRouteFileSource(routeId, importPath) {
|
|
526
570
|
return (
|
package/src/index.ts
CHANGED
|
@@ -31,6 +31,9 @@ export {
|
|
|
31
31
|
DepartmentsSchema,
|
|
32
32
|
DistrictSchema,
|
|
33
33
|
DistrictsSchema,
|
|
34
|
+
EntityIdSchema,
|
|
35
|
+
NullableEntityIdSchema,
|
|
36
|
+
OptionalEntityIdSchema,
|
|
34
37
|
GeoDivisionSchema,
|
|
35
38
|
GeoDivisionTypeSchema,
|
|
36
39
|
GetDepartmentsParamsSchema,
|
|
@@ -43,6 +46,7 @@ export {
|
|
|
43
46
|
StorefrontMenuItemSchema,
|
|
44
47
|
getMenuItems,
|
|
45
48
|
} from '@ecomiq/core'
|
|
49
|
+
export type { EntityId } from '@ecomiq/core'
|
|
46
50
|
export { getCountries, getDepartments, getDistricts, getProvinces } from '@ecomiq/core'
|
|
47
51
|
export {
|
|
48
52
|
AddCartItemParamsSchema,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy catch-all same-origin: `/api/*` → storefront `/v1/*`.
|
|
3
|
+
* Ruta cerrada del framework (no hace falta en pages/ del usuario).
|
|
4
|
+
*/
|
|
5
|
+
import { proxyStorefrontRequest } from '../api-proxy'
|
|
6
|
+
|
|
7
|
+
type ApiHandlerArgs = {
|
|
8
|
+
request: Request
|
|
9
|
+
params: { _splat?: string }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function handle({ request, params }: ApiHandlerArgs) {
|
|
13
|
+
return proxyStorefrontRequest(request, params._splat)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const GET = handle
|
|
17
|
+
export const POST = handle
|
|
18
|
+
export const PUT = handle
|
|
19
|
+
export const PATCH = handle
|
|
20
|
+
export const DELETE = handle
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth catch-all: `/api/auth/*`.
|
|
3
|
+
* Ruta cerrada del framework (no hace falta en pages/ del usuario).
|
|
4
|
+
*/
|
|
5
|
+
import { storefrontAuth } from '../auth/storefront-auth'
|
|
6
|
+
|
|
7
|
+
type ApiHandlerArgs = {
|
|
8
|
+
request: Request
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function handle({ request }: ApiHandlerArgs) {
|
|
12
|
+
return storefrontAuth.handler(request)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const GET = handle
|
|
16
|
+
export const POST = handle
|
package/src/providers/auth.tsx
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth storefront: Better Auth client + sesión via context.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* <AuthProvider client={client}>
|
|
7
|
-
* <App />
|
|
8
|
-
* </AuthProvider>
|
|
4
|
+
* El framework monta `<AuthProvider>` en `__root` (cliente por defecto).
|
|
5
|
+
* Puedes pasar un `client` propio si necesitas opciones custom.
|
|
9
6
|
*
|
|
7
|
+
* ```tsx
|
|
10
8
|
* const { session, signIn, signOut } = useAuth()
|
|
11
9
|
* ```
|
|
12
10
|
*/
|
|
@@ -39,14 +37,29 @@ export type StorefrontSession = StorefrontAuthClientInstance['$Infer']['Session'
|
|
|
39
37
|
|
|
40
38
|
const AuthContext = createContext<StorefrontAuthClientInstance | null>(null)
|
|
41
39
|
|
|
40
|
+
/** Cliente por defecto del framework (una instancia por bundle). */
|
|
41
|
+
let defaultAuthClient: StorefrontAuthClientInstance | null = null
|
|
42
|
+
|
|
43
|
+
function getDefaultAuthClient(): StorefrontAuthClientInstance {
|
|
44
|
+
if (!defaultAuthClient) {
|
|
45
|
+
defaultAuthClient = createStorefrontAuthClient()
|
|
46
|
+
}
|
|
47
|
+
return defaultAuthClient
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
export function AuthProvider({
|
|
43
51
|
client,
|
|
44
52
|
children,
|
|
45
53
|
}: {
|
|
46
|
-
|
|
54
|
+
/** Si se omite, usa el cliente por defecto del storefront. */
|
|
55
|
+
client?: StorefrontAuthClientInstance
|
|
47
56
|
children: ReactNode
|
|
48
57
|
}) {
|
|
49
|
-
return
|
|
58
|
+
return (
|
|
59
|
+
<AuthContext.Provider value={client ?? getDefaultAuthClient()}>
|
|
60
|
+
{children}
|
|
61
|
+
</AuthContext.Provider>
|
|
62
|
+
)
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
/** Acceso a sesión y acciones de auth. Requiere `<AuthProvider>` + `<AppEcomiqProvider>`. */
|
package/src/providers/cart.tsx
CHANGED
|
@@ -17,14 +17,14 @@ import {
|
|
|
17
17
|
useState,
|
|
18
18
|
type ReactNode,
|
|
19
19
|
} from 'react'
|
|
20
|
-
import { type StorefrontCart } from '@ecomiq/core'
|
|
20
|
+
import { type StorefrontCart, EntityIdSchema } from '@ecomiq/core'
|
|
21
21
|
import { z } from 'zod'
|
|
22
22
|
import { serverApi } from '../server'
|
|
23
23
|
|
|
24
24
|
const CartSessionSchema = z.object({
|
|
25
25
|
version: z.literal(1),
|
|
26
26
|
storeId: z.string().min(1),
|
|
27
|
-
cartId:
|
|
27
|
+
cartId: EntityIdSchema,
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
export type CartLoadStatus = 'loading' | 'ready' | 'error'
|
package/src/runtime/context.ts
CHANGED
package/src/server.ts
CHANGED
|
@@ -58,6 +58,8 @@ import {
|
|
|
58
58
|
type RemoveCartItemParams,
|
|
59
59
|
type UpdateCartItemQuantityParams,
|
|
60
60
|
type UploadPaymentReceiptParams,
|
|
61
|
+
EntityIdSchema,
|
|
62
|
+
OptionalEntityIdSchema,
|
|
61
63
|
} from '@ecomiq/core'
|
|
62
64
|
import { z } from 'zod'
|
|
63
65
|
import { getStorefrontClient } from './client'
|
|
@@ -133,7 +135,7 @@ const removeCartItemFn = createServerFn({ method: 'POST' })
|
|
|
133
135
|
.handler(({ data }) => submitCartItemRemoval(getStorefrontClient(), data))
|
|
134
136
|
|
|
135
137
|
const AddProductToCartServerSchema = z.object({
|
|
136
|
-
cartId:
|
|
138
|
+
cartId: OptionalEntityIdSchema,
|
|
137
139
|
currencyCode: z.string().length(3),
|
|
138
140
|
variantId: z.string().regex(/^[1-9]\d*$/),
|
|
139
141
|
quantity: z.number().int().positive(),
|
|
@@ -172,7 +174,7 @@ const checkoutCartFn = createServerFn({ method: 'POST' })
|
|
|
172
174
|
.handler(({ data }) => getStorefrontClient().checkoutCart(data))
|
|
173
175
|
|
|
174
176
|
const CartPaymentReceiptServerSchema = z.object({
|
|
175
|
-
cartId:
|
|
177
|
+
cartId: EntityIdSchema,
|
|
176
178
|
fileBase64: z.string().min(1),
|
|
177
179
|
fileName: z.string().min(1),
|
|
178
180
|
mimeType: z.string().min(1).optional(),
|
package/templates/__root.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useState, type ReactNode } from 'react'
|
|
3
3
|
import { createRootRoute, HeadContent, Scripts, Outlet, Link } from '@tanstack/react-router'
|
|
4
4
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
5
|
-
import { AppEcomiqProvider } from '@ecomiq/storefront/internal/context'
|
|
5
|
+
import { AppEcomiqProvider, AuthProvider } from '@ecomiq/storefront/internal/context'
|
|
6
6
|
import { buildRootHead } from '@ecomiq/storefront/internal/head'
|
|
7
7
|
import { serverApi } from '@ecomiq/storefront'
|
|
8
8
|
import { StorefrontError } from '@ecomiq/storefront/ui'
|
|
@@ -33,9 +33,11 @@ function RootComponent() {
|
|
|
33
33
|
<RootDocument language={settings.store.language}>
|
|
34
34
|
<QueryClientProvider client={queryClient}>
|
|
35
35
|
<AppEcomiqProvider config={publicConfig} settings={settings}>
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
|
|
36
|
+
<AuthProvider>
|
|
37
|
+
<Layout config={publicConfig} settings={settings}>
|
|
38
|
+
<Outlet />
|
|
39
|
+
</Layout>
|
|
40
|
+
</AuthProvider>
|
|
39
41
|
</AppEcomiqProvider>
|
|
40
42
|
</QueryClientProvider>
|
|
41
43
|
</RootDocument>
|