@ecomiq/storefront 2.0.22 → 2.1.4

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.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Página CMS cerrada (`/pages/$pageSlug`).
3
- * Resuelve contenido publicado vía GET /v1/pages/{slug}.
2
+ * Página de contenido CMS (`/pages/$pageSlug`).
3
+ * Las páginas Definition las sirve el template (`$.tsx`).
4
4
  */
5
5
  import type { CmsPageDetailsDto } from '@ecomiq/core'
6
6
  import {
@@ -8,7 +8,7 @@ import { definePage, Link } from '../index'
8
8
  import { buttonVariants, cn } from '../ui'
9
9
 
10
10
  const ThankYouSearchSchema = z.object({
11
- cartId: z.string().optional(),
11
+ cartToken: z.string().optional(),
12
12
  transactionId: z.string().optional(),
13
13
  })
14
14
 
@@ -17,14 +17,14 @@ import {
17
17
  useState,
18
18
  type ReactNode,
19
19
  } from 'react'
20
- import { type StorefrontCart, EntityIdSchema } from '@ecomiq/core'
20
+ import { CartTokenSchema, type StorefrontCart } 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: EntityIdSchema,
27
+ cartToken: CartTokenSchema,
28
28
  })
29
29
 
30
30
  export type CartLoadStatus = 'loading' | 'ready' | 'error'
@@ -36,7 +36,7 @@ export type CartMutation = {
36
36
 
37
37
  export interface CartContextValue {
38
38
  cart: StorefrontCart | null
39
- cartId: string | undefined
39
+ cartToken: string | undefined
40
40
  itemCount: number
41
41
  loadStatus: CartLoadStatus
42
42
  mutation: CartMutation | null
@@ -77,20 +77,20 @@ export function CartProvider({
77
77
  }) {
78
78
  const key = sessionKey(storeId)
79
79
  const [cart, setCart] = useState<StorefrontCart | null>(null)
80
- const [cartId, setCartId] = useState<string>()
80
+ const [cartToken, setCartToken] = useState<string>()
81
81
  const [loadStatus, setLoadStatus] = useState<CartLoadStatus>('loading')
82
82
  const [mutation, setMutation] = useState<CartMutation | null>(null)
83
83
  const [error, setError] = useState<string>()
84
84
  const mutationInFlight = useRef(false)
85
85
 
86
- const persist = (nextCartId: string) => {
87
- setCartId(nextCartId)
88
- localStorage.setItem(key, JSON.stringify({ version: 1, storeId, cartId: nextCartId }))
86
+ const persist = (nextCartToken: string) => {
87
+ setCartToken(nextCartToken)
88
+ localStorage.setItem(key, JSON.stringify({ version: 1, storeId, cartToken: nextCartToken }))
89
89
  }
90
90
 
91
91
  const clear = () => {
92
92
  setCart(null)
93
- setCartId(undefined)
93
+ setCartToken(undefined)
94
94
  localStorage.removeItem(key)
95
95
  }
96
96
 
@@ -104,9 +104,9 @@ export function CartProvider({
104
104
  return
105
105
  }
106
106
 
107
- setCartId(stored.cartId)
107
+ setCartToken(stored.cartToken)
108
108
  try {
109
- const result = await serverApi.loadCart({ cartId: stored.cartId })
109
+ const result = await serverApi.loadCart({ cartToken: stored.cartToken })
110
110
  if (!result.ok) {
111
111
  setError(result.error.message)
112
112
  setLoadStatus('error')
@@ -147,7 +147,7 @@ export function CartProvider({
147
147
  let result
148
148
  try {
149
149
  result = await serverApi.addProductToCart({
150
- cartId,
150
+ cartToken,
151
151
  currencyCode,
152
152
  variantId: input.variantId,
153
153
  quantity: input.quantity,
@@ -164,7 +164,7 @@ export function CartProvider({
164
164
  }
165
165
 
166
166
  setError(undefined)
167
- persist(result.value.cartId)
167
+ persist(result.value.cartToken)
168
168
  setCart(result.value.cart)
169
169
  return result.value.cart
170
170
  } finally {
@@ -174,7 +174,7 @@ export function CartProvider({
174
174
  }
175
175
 
176
176
  const setItemQuantity = async (itemId: string, quantity: number) => {
177
- if (!cartId) {
177
+ if (!cartToken) {
178
178
  setError('No hay un carrito activo.')
179
179
  return
180
180
  }
@@ -190,7 +190,7 @@ export function CartProvider({
190
190
  let result
191
191
  try {
192
192
  result = await serverApi.setCartItemQuantity({
193
- cartId,
193
+ cartToken,
194
194
  cartItemId: itemId,
195
195
  quantity,
196
196
  })
@@ -213,7 +213,7 @@ export function CartProvider({
213
213
  }
214
214
 
215
215
  const removeItem = async (itemId: string) => {
216
- if (!cartId) {
216
+ if (!cartToken) {
217
217
  setError('No hay un carrito activo.')
218
218
  return
219
219
  }
@@ -229,7 +229,7 @@ export function CartProvider({
229
229
  let result
230
230
  try {
231
231
  result = await serverApi.removeCartItem({
232
- cartId,
232
+ cartToken,
233
233
  cartItemId: itemId,
234
234
  })
235
235
  } catch {
@@ -252,7 +252,7 @@ export function CartProvider({
252
252
 
253
253
  const value: CartContextValue = {
254
254
  cart,
255
- cartId,
255
+ cartToken,
256
256
  itemCount: cart?.items.reduce((sum, item) => sum + item.quantity, 0) ?? 0,
257
257
  loadStatus,
258
258
  mutation,
@@ -3,6 +3,7 @@ import { createServerOnlyFn } from '@tanstack/react-start'
3
3
 
4
4
  export interface ServerEcomiqCloudConfig {
5
5
  baseUrl?: string
6
+ artifactBaseUrl?: string
6
7
  storeSlug?: string
7
8
  timeoutMs?: number
8
9
  cacheTtlMs?: number
package/src/server.ts CHANGED
@@ -58,8 +58,8 @@ import {
58
58
  type RemoveCartItemParams,
59
59
  type UpdateCartItemQuantityParams,
60
60
  type UploadPaymentReceiptParams,
61
+ CartTokenSchema,
61
62
  EntityIdSchema,
62
- OptionalEntityIdSchema,
63
63
  } from '@ecomiq/core'
64
64
  import { z } from 'zod'
65
65
  import { getStorefrontClient } from './client'
@@ -71,16 +71,11 @@ import {
71
71
  type AddProductToCartInput,
72
72
  } from './cart-session'
73
73
  import { submitProductReview } from './review-submission'
74
- import { loadActiveStorefrontHome } from './storefront-runtime'
75
74
 
76
75
  const getSettingsFn = createServerFn({ method: 'GET' }).handler(() =>
77
76
  getStorefrontClient().getSettings(),
78
77
  )
79
78
 
80
- const getActiveStorefrontHomeFn = createServerFn({ method: 'GET' }).handler(
81
- () => loadActiveStorefrontHome(),
82
- )
83
-
84
79
  const getStoreIdFn = createServerFn({ method: 'GET' }).handler(() =>
85
80
  getStorefrontClient().getStoreId(),
86
81
  )
@@ -140,9 +135,9 @@ const removeCartItemFn = createServerFn({ method: 'POST' })
140
135
  .handler(({ data }) => submitCartItemRemoval(getStorefrontClient(), data))
141
136
 
142
137
  const AddProductToCartServerSchema = z.object({
143
- cartId: OptionalEntityIdSchema,
138
+ cartToken: CartTokenSchema.optional(),
144
139
  currencyCode: z.string().length(3),
145
- variantId: z.string().regex(/^[1-9]\d*$/),
140
+ variantId: EntityIdSchema,
146
141
  quantity: z.number().int().positive(),
147
142
  })
148
143
 
@@ -179,7 +174,7 @@ const checkoutCartFn = createServerFn({ method: 'POST' })
179
174
  .handler(({ data }) => getStorefrontClient().checkoutCart(data))
180
175
 
181
176
  const CartPaymentReceiptServerSchema = z.object({
182
- cartId: EntityIdSchema,
177
+ cartToken: CartTokenSchema,
183
178
  fileBase64: z.string().min(1),
184
179
  fileName: z.string().min(1),
185
180
  mimeType: z.string().min(1).optional(),
@@ -191,7 +186,7 @@ const uploadPaymentReceiptFn = createServerFn({ method: 'POST' })
191
186
  const binary = Uint8Array.from(atob(data.fileBase64), (c) => c.charCodeAt(0))
192
187
  const file = new Blob([binary], { type: data.mimeType ?? 'application/octet-stream' })
193
188
  return getStorefrontClient().uploadPaymentReceipt({
194
- cartId: data.cartId,
189
+ cartToken: data.cartToken,
195
190
  file,
196
191
  fileName: data.fileName,
197
192
  })
@@ -227,7 +222,6 @@ const getDistrictsFn = createServerFn({ method: 'GET' })
227
222
  */
228
223
  export const serverApi = {
229
224
  getSettings: () => getSettingsFn(),
230
- getActiveStorefrontHome: () => getActiveStorefrontHomeFn(),
231
225
  getStoreId: () => getStoreIdFn(),
232
226
  getCollections: () => getCollectionsFn(),
233
227
  getCollectionBySlug: (slug: string) => getCollectionBySlugFn({ data: { slug } }),
@@ -271,7 +265,7 @@ export const serverApi = {
271
265
  for (const byte of bytes) binary += String.fromCharCode(byte)
272
266
  return uploadPaymentReceiptFn({
273
267
  data: {
274
- cartId: params.cartId,
268
+ cartToken: params.cartToken,
275
269
  fileBase64: btoa(binary),
276
270
  fileName:
277
271
  params.fileName ??
package/src/styles.css CHANGED
@@ -5,7 +5,6 @@
5
5
  /* Escanear los componentes UI del propio framework */
6
6
  @source './ui';
7
7
  @source './components';
8
- @source './storefront-definition';
9
8
  @source './runtime';
10
9
  @source './pages';
11
10
  @source '../templates';
@@ -75,15 +74,3 @@
75
74
  transition-duration: 0.01ms !important;
76
75
  }
77
76
  }
78
-
79
- .storefront-definition {
80
- font-family: var(--storefront-font-body);
81
- }
82
-
83
- .storefront-definition :where(h1, h2, h3, h4, h5, h6) {
84
- font-family: var(--storefront-font-heading);
85
- }
86
-
87
- .storefront-definition-section {
88
- padding-block: var(--storefront-section-padding);
89
- }
@@ -1,218 +0,0 @@
1
- import { z } from 'zod'
2
- import { StorefrontDefinitionSchema } from '@ecomiq/storefront-definition'
3
-
4
- export const CloudRuntimeStoreSlugSchema = z
5
- .string()
6
- .trim()
7
- .min(2)
8
- .max(100)
9
- .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
10
-
11
- export const ActiveStorefrontRuntimeSchema = z
12
- .object({
13
- schemaVersion: z.literal(1),
14
- store: z
15
- .object({
16
- slug: CloudRuntimeStoreSlugSchema,
17
- })
18
- .strict(),
19
- environment: z
20
- .object({
21
- name: z.literal('production'),
22
- status: z.literal('ready'),
23
- })
24
- .strict(),
25
- publication: z
26
- .object({
27
- sequence: z.number().int().nonnegative(),
28
- releaseSequence: z.number().int().nonnegative(),
29
- publishedAt: z.string().datetime({ offset: true }),
30
- })
31
- .strict(),
32
- definition: StorefrontDefinitionSchema,
33
- })
34
- .strict()
35
-
36
- export type ActiveStorefrontRuntime = z.infer<
37
- typeof ActiveStorefrontRuntimeSchema
38
- >
39
-
40
- export type CloudRuntimeFetch = (
41
- input: RequestInfo | URL,
42
- init?: RequestInit,
43
- ) => Promise<Response>
44
-
45
- export interface CloudRuntimeClientOptions {
46
- baseUrl: string
47
- timeoutMs?: number
48
- cacheTtlMs?: number
49
- fetch?: CloudRuntimeFetch
50
- now?: () => number
51
- }
52
-
53
- interface RuntimeCacheEntry {
54
- value: ActiveStorefrontRuntime | null
55
- etag?: string
56
- expiresAt: number
57
- }
58
-
59
- export class CloudRuntimeResponseError extends Error {
60
- readonly status: number
61
-
62
- constructor(status: number) {
63
- super(`Cloud runtime respondió HTTP ${status}`)
64
- this.name = 'CloudRuntimeResponseError'
65
- this.status = status
66
- }
67
- }
68
-
69
- const DEFAULT_TIMEOUT_MS = 3_000
70
- const DEFAULT_CACHE_TTL_MS = 15_000
71
- const MAX_CACHE_ENTRIES = 100
72
-
73
- export class CloudRuntimeClient {
74
- readonly baseUrl: string
75
- readonly timeoutMs: number
76
- readonly cacheTtlMs: number
77
-
78
- private readonly fetchRuntime: CloudRuntimeFetch
79
- private readonly now: () => number
80
- private readonly cache = new Map<string, RuntimeCacheEntry>()
81
- private readonly inFlight = new Map<
82
- string,
83
- Promise<ActiveStorefrontRuntime | null>
84
- >()
85
-
86
- constructor(options: CloudRuntimeClientOptions) {
87
- this.baseUrl = new URL(options.baseUrl).toString().replace(/\/$/, '')
88
- this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS
89
- this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS
90
- this.fetchRuntime = options.fetch ?? globalThis.fetch
91
- this.now = options.now ?? Date.now
92
- }
93
-
94
- async resolveActiveStorefront(
95
- storeSlugInput: string,
96
- ): Promise<ActiveStorefrontRuntime | null> {
97
- const storeSlug = CloudRuntimeStoreSlugSchema.parse(storeSlugInput)
98
- const cached = this.cache.get(storeSlug)
99
- if (cached && cached.expiresAt > this.now()) {
100
- return cached.value
101
- }
102
-
103
- const activeRequest = this.inFlight.get(storeSlug)
104
- if (activeRequest) return activeRequest
105
-
106
- const request = this.requestActiveStorefront(storeSlug, cached).finally(
107
- () => {
108
- this.inFlight.delete(storeSlug)
109
- },
110
- )
111
- this.inFlight.set(storeSlug, request)
112
- return request
113
- }
114
-
115
- clearCache(): void {
116
- this.cache.clear()
117
- this.inFlight.clear()
118
- }
119
-
120
- private setCache(storeSlug: string, entry: RuntimeCacheEntry): void {
121
- if (!this.cache.has(storeSlug) && this.cache.size >= MAX_CACHE_ENTRIES) {
122
- const oldestKey = this.cache.keys().next().value
123
- if (oldestKey !== undefined) this.cache.delete(oldestKey)
124
- }
125
- this.cache.set(storeSlug, entry)
126
- }
127
-
128
- private async requestActiveStorefront(
129
- storeSlug: string,
130
- cached?: RuntimeCacheEntry,
131
- ): Promise<ActiveStorefrontRuntime | null> {
132
- const controller = new AbortController()
133
- const timeout = setTimeout(() => controller.abort(), this.timeoutMs)
134
- const headers = new Headers({ accept: 'application/json' })
135
- if (cached?.etag) headers.set('if-none-match', cached.etag)
136
-
137
- try {
138
- const response = await this.fetchRuntime(
139
- new URL(
140
- `/v1/runtime/storefronts/${encodeURIComponent(storeSlug)}`,
141
- this.baseUrl,
142
- ),
143
- {
144
- method: 'GET',
145
- headers,
146
- signal: controller.signal,
147
- },
148
- )
149
-
150
- if (response.status === 304) {
151
- if (!cached) throw new CloudRuntimeResponseError(304)
152
- this.setCache(storeSlug, {
153
- ...cached,
154
- expiresAt: this.now() + this.cacheTtlMs,
155
- })
156
- return cached.value
157
- }
158
-
159
- if (response.status === 404) {
160
- this.setCache(storeSlug, {
161
- value: null,
162
- etag: response.headers.get('etag') ?? undefined,
163
- expiresAt: this.now() + this.cacheTtlMs,
164
- })
165
- return null
166
- }
167
-
168
- if (!response.ok) {
169
- throw new CloudRuntimeResponseError(response.status)
170
- }
171
-
172
- const runtime = ActiveStorefrontRuntimeSchema.parse(
173
- await response.json(),
174
- )
175
- if (runtime.store.slug !== storeSlug) {
176
- throw new Error(
177
- `Cloud runtime devolvió el store slug ${runtime.store.slug} para ${storeSlug}`,
178
- )
179
- }
180
-
181
- this.setCache(storeSlug, {
182
- value: runtime,
183
- etag: response.headers.get('etag') ?? undefined,
184
- expiresAt: this.now() + this.cacheTtlMs,
185
- })
186
- return runtime
187
- } finally {
188
- clearTimeout(timeout)
189
- }
190
- }
191
- }
192
-
193
- const sharedClients = new Map<string, CloudRuntimeClient>()
194
-
195
- export function getCloudRuntimeClient(
196
- options: Omit<CloudRuntimeClientOptions, 'fetch' | 'now'>,
197
- ): CloudRuntimeClient {
198
- const key = [
199
- new URL(options.baseUrl).toString(),
200
- options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
201
- options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,
202
- ].join('|')
203
- const current = sharedClients.get(key)
204
- if (current) return current
205
-
206
- const client = new CloudRuntimeClient(options)
207
- if (sharedClients.size >= 8) {
208
- const oldestKey = sharedClients.keys().next().value
209
- if (oldestKey !== undefined) sharedClients.delete(oldestKey)
210
- }
211
- sharedClients.set(key, client)
212
- return client
213
- }
214
-
215
- export function resetCloudRuntimeClients(): void {
216
- for (const client of sharedClients.values()) client.clearCache()
217
- sharedClients.clear()
218
- }
@@ -1 +0,0 @@
1
- export * from '@ecomiq/storefront-definition'
@@ -1,2 +0,0 @@
1
- export * from './contract'
2
- export * from './renderer'