@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,37 +0,0 @@
1
- import { useRouterState } from '@tanstack/react-router'
2
- import {
3
- StorefrontDefinitionSchema,
4
- type StorefrontDefinition,
5
- } from '@ecomiq/storefront-definition'
6
-
7
- export function definitionForRoute(
8
- pathname: string,
9
- loaderData: unknown,
10
- ): StorefrontDefinition | null {
11
- if (
12
- pathname !== '/' ||
13
- loaderData === null ||
14
- typeof loaderData !== 'object'
15
- ) {
16
- return null
17
- }
18
- if (!('kind' in loaderData) || loaderData.kind !== 'definition') {
19
- return null
20
- }
21
- if (!('definition' in loaderData)) {
22
- return null
23
- }
24
-
25
- const definition = StorefrontDefinitionSchema.safeParse(loaderData.definition)
26
- return definition.success ? definition.data : null
27
- }
28
-
29
- export function useDefinition(): StorefrontDefinition | null {
30
- return useRouterState({
31
- select: (state) =>
32
- definitionForRoute(
33
- state.location.pathname,
34
- state.matches.at(-1)?.loaderData,
35
- ),
36
- })
37
- }
@@ -1,277 +0,0 @@
1
- import type {
2
- Collection,
3
- Collections,
4
- ProductsParamsInput,
5
- ProductsResponse,
6
- ProductSummary,
7
- } from '@ecomiq/core'
8
- import {
9
- CloudRuntimeStoreSlugSchema,
10
- getCloudRuntimeClient,
11
- type ActiveStorefrontRuntime,
12
- } from './cloud-runtime-client'
13
- import { getStorefrontClient } from './client'
14
- import {
15
- getServerConfig,
16
- type ServerEcomiqConfig,
17
- } from './server-config-store'
18
- import type {
19
- CollectionGridStorefrontSection,
20
- ProductGridStorefrontSection,
21
- StorefrontDefinition,
22
- } from '@ecomiq/storefront-definition'
23
- import type { DefinitionData } from './storefront-definition/renderer'
24
-
25
- const MAX_UNIQUE_PRODUCT_SOURCES = 12
26
- const MANUAL_PRODUCT_PAGE_SIZE = 100
27
- const MAX_MANUAL_PRODUCT_SCAN_PAGES = 5
28
-
29
- export interface StorefrontRuntimeCatalogClient {
30
- getProducts(params?: ProductsParamsInput): Promise<ProductsResponse>
31
- getCollections(): Promise<Collections>
32
- }
33
-
34
- export interface StorefrontRuntimeCloudClient {
35
- resolveActiveStorefront(
36
- storeSlug: string,
37
- ): Promise<ActiveStorefrontRuntime | null>
38
- }
39
-
40
- export interface ResolvedStorefrontDefinitionData
41
- extends DefinitionData {
42
- productsBySectionId: Record<string, ProductSummary[]>
43
- collectionsBySectionId: Record<string, Collection[]>
44
- }
45
-
46
- export type ActiveStorefrontHome =
47
- | {
48
- kind: 'legacy'
49
- }
50
- | {
51
- kind: 'definition'
52
- definition: StorefrontDefinition
53
- publication: ActiveStorefrontRuntime['publication']
54
- data: ResolvedStorefrontDefinitionData
55
- }
56
-
57
- function emptyRendererData(): ResolvedStorefrontDefinitionData {
58
- return {
59
- productsBySectionId: {},
60
- collectionsBySectionId: {},
61
- }
62
- }
63
-
64
- function productSourceKey(
65
- section: ProductGridStorefrontSection,
66
- ): string | null {
67
- if (section.source.kind === 'manual') return null
68
- if (section.source.kind === 'collection') {
69
- return `collection:${section.source.slug}`
70
- }
71
- return 'all'
72
- }
73
-
74
- async function loadProductSource(
75
- section: ProductGridStorefrontSection,
76
- client: StorefrontRuntimeCatalogClient,
77
- ): Promise<ProductSummary[]> {
78
- const params: ProductsParamsInput = {
79
- page: 1,
80
- pageSize: section.limit,
81
- sort: 'newest',
82
- }
83
- if (section.source.kind === 'collection') {
84
- params.collection = section.source.slug
85
- }
86
- const response = await client.getProducts(params)
87
- return response.products.slice(0, section.limit)
88
- }
89
-
90
- async function scanManualProducts(
91
- productIds: ReadonlySet<string>,
92
- client: StorefrontRuntimeCatalogClient,
93
- ): Promise<Map<string, ProductSummary>> {
94
- const found = new Map<string, ProductSummary>()
95
-
96
- for (let page = 1; page <= MAX_MANUAL_PRODUCT_SCAN_PAGES; page += 1) {
97
- const response = await client.getProducts({
98
- page,
99
- pageSize: MANUAL_PRODUCT_PAGE_SIZE,
100
- sort: 'newest',
101
- })
102
- for (const product of response.products) {
103
- if (productIds.has(product.id)) found.set(product.id, product)
104
- }
105
-
106
- if (
107
- found.size === productIds.size ||
108
- page >= response.totalPages ||
109
- response.products.length === 0
110
- ) {
111
- break
112
- }
113
- }
114
-
115
- return found
116
- }
117
-
118
- export async function resolveStorefrontDefinitionData(
119
- definition: StorefrontDefinition,
120
- client: StorefrontRuntimeCatalogClient,
121
- ): Promise<ResolvedStorefrontDefinitionData> {
122
- const data = emptyRendererData()
123
- const productSections = definition.sections.filter(
124
- (section): section is ProductGridStorefrontSection =>
125
- section.enabled && section.type === 'product-grid',
126
- )
127
- const collectionSections = definition.sections.filter(
128
- (section): section is CollectionGridStorefrontSection =>
129
- section.enabled && section.type === 'collection-grid',
130
- )
131
-
132
- const sourceSections = new Map<string, ProductGridStorefrontSection>()
133
- for (const section of productSections) {
134
- const sourceKey = productSourceKey(section)
135
- if (!sourceKey) continue
136
- const current = sourceSections.get(sourceKey)
137
- if (current) {
138
- if (section.limit > current.limit) sourceSections.set(sourceKey, section)
139
- continue
140
- }
141
- if (sourceSections.size >= MAX_UNIQUE_PRODUCT_SOURCES) continue
142
- sourceSections.set(sourceKey, section)
143
- }
144
-
145
- const sourceRequests = new Map<string, Promise<ProductSummary[]>>()
146
- for (const [sourceKey, section] of sourceSections) {
147
- sourceRequests.set(
148
- sourceKey,
149
- loadProductSource(section, client).catch(() => []),
150
- )
151
- }
152
-
153
- const manualIds = new Set(
154
- productSections.flatMap((section) =>
155
- section.source.kind === 'manual' ? section.source.productIds : [],
156
- ),
157
- )
158
- const manualProducts =
159
- manualIds.size > 0
160
- ? scanManualProducts(manualIds, client).catch(
161
- () => new Map<string, ProductSummary>(),
162
- )
163
- : Promise.resolve(new Map<string, ProductSummary>())
164
-
165
- await Promise.all(
166
- productSections.map(async (section) => {
167
- if (section.source.kind === 'manual') {
168
- const productsById = await manualProducts
169
- data.productsBySectionId[section.id] = section.source.productIds
170
- .map((productId) => productsById.get(productId))
171
- .filter(
172
- (product): product is ProductSummary => product !== undefined,
173
- )
174
- .slice(0, section.limit)
175
- return
176
- }
177
-
178
- const sourceKey = productSourceKey(section)
179
- const products = sourceKey
180
- ? await (sourceRequests.get(sourceKey) ?? Promise.resolve([]))
181
- : []
182
- data.productsBySectionId[section.id] = products.slice(0, section.limit)
183
- }),
184
- )
185
-
186
- if (collectionSections.length > 0) {
187
- const collections = await client.getCollections().catch(() => [])
188
- const collectionsBySlug = new Map(
189
- collections.map((collection) => [collection.slug, collection]),
190
- )
191
-
192
- for (const section of collectionSections) {
193
- if (section.source.kind === 'manual') {
194
- data.collectionsBySectionId[section.id] = section.source.slugs
195
- .map((slug) => collectionsBySlug.get(slug))
196
- .filter(
197
- (collection): collection is Collection =>
198
- collection !== undefined,
199
- )
200
- .slice(0, section.limit)
201
- continue
202
- }
203
- data.collectionsBySectionId[section.id] = collections.slice(
204
- 0,
205
- section.limit,
206
- )
207
- }
208
- }
209
-
210
- return data
211
- }
212
-
213
- export function deriveCloudStoreSlug(domain: string): string | null {
214
- const normalized = domain.trim().toLowerCase()
215
- if (!normalized) return null
216
-
217
- let hostname: string
218
- try {
219
- hostname = new URL(
220
- normalized.includes('://') ? normalized : `https://${normalized}`,
221
- ).hostname
222
- } catch {
223
- return null
224
- }
225
-
226
- const candidate = hostname.split('.')[0] ?? ''
227
- const parsed = CloudRuntimeStoreSlugSchema.safeParse(candidate)
228
- return parsed.success ? parsed.data : null
229
- }
230
-
231
- export interface LoadActiveStorefrontHomeDependencies {
232
- config?: ServerEcomiqConfig
233
- cloudClient?: StorefrontRuntimeCloudClient
234
- catalogClient?: StorefrontRuntimeCatalogClient
235
- }
236
-
237
- export async function loadActiveStorefrontHome(
238
- dependencies: LoadActiveStorefrontHomeDependencies = {},
239
- ): Promise<ActiveStorefrontHome> {
240
- const config = dependencies.config ?? getServerConfig()
241
- const cloudConfig = config.cloud
242
- if (!cloudConfig?.baseUrl) return { kind: 'legacy' }
243
-
244
- const storeSlug =
245
- cloudConfig.storeSlug ?? deriveCloudStoreSlug(config.domain)
246
- if (!storeSlug) return { kind: 'legacy' }
247
-
248
- const cloudClient =
249
- dependencies.cloudClient ??
250
- getCloudRuntimeClient({
251
- baseUrl: cloudConfig.baseUrl,
252
- timeoutMs: cloudConfig.timeoutMs,
253
- cacheTtlMs: cloudConfig.cacheTtlMs,
254
- })
255
-
256
- let runtime: ActiveStorefrontRuntime | null
257
- try {
258
- runtime = await cloudClient.resolveActiveStorefront(storeSlug)
259
- } catch {
260
- return { kind: 'legacy' }
261
- }
262
- if (!runtime) return { kind: 'legacy' }
263
-
264
- const catalogClient =
265
- dependencies.catalogClient ?? getStorefrontClient()
266
- const data = await resolveStorefrontDefinitionData(
267
- runtime.definition,
268
- catalogClient,
269
- ).catch(emptyRendererData)
270
-
271
- return {
272
- kind: 'definition',
273
- definition: runtime.definition,
274
- publication: runtime.publication,
275
- data,
276
- }
277
- }