@bacano/sdk 1.2.3 → 1.2.5

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.
@@ -2,9 +2,9 @@
2
2
 
3
3
  `@bacano/sdk/content-management` permite que una herramienta administrativa o
4
4
  un agente autorizado consulte y mantenga **tipos, componentes, grupos, listas
5
- de opciones, entradas e imágenes** de Bacano Content, y **formularios web**.
5
+ de opciones, entradas, imágenes y bloques adicionales de productos** de Bacano Content, y **formularios web**.
6
6
  Cada capacidad requiere
7
- un permiso explícito. El acceso no autoriza productos, ventas, clientes,
7
+ un permiso explícito. El acceso a bloques no autoriza modificar productos, ventas, clientes,
8
8
  configuración general, GraphQL, SQL ni acceso directo a Storage.
9
9
 
10
10
  Este contrato es independiente del cliente público del storefront. Debe usarse
@@ -38,6 +38,10 @@ Scopes disponibles:
38
38
  - `forms:publish`: publicar, pausar, reactivar o archivar formularios. Requiere
39
39
  `forms:manage`. También se exige para cambiar notificaciones de formularios
40
40
  que ya fueron publicados, porque afectan envíos en curso.
41
+ - `product_content:manage`: consultar, crear, actualizar, ordenar y retirar
42
+ bloques adicionales exclusivamente en los productos cuyos IDs se incluyeron
43
+ al generar el acceso. Requiere que el usuario mantenga `products.view` y
44
+ `websites.manage`; no modifica precio, inventario ni variantes.
41
45
 
42
46
  Los permisos de Content no conceden acceso a formularios ni viceversa. Los
43
47
  tokens existentes no se amplían; solicita uno nuevo con el alcance necesario.
@@ -89,6 +93,42 @@ El cliente ofrece:
89
93
  `applyOptionListLifecycle(idOrKey, operation)`.
90
94
  - `listMedia(filters)` y `uploadImage(input)` para reutilizar o cargar imágenes
91
95
  cuando el acceso incluya `content_media:upload`.
96
+ - `listProductBlocks(productId, filters)`, `createProductBlock(productId, input)`,
97
+ `updateProductBlock(productId, blockId, input)`,
98
+ `reorderProductBlocks(productId, input)` y
99
+ `retireProductBlock(productId, blockId, expectedUpdatedAt)`.
100
+
101
+ ## Bloques adicionales de un producto
102
+
103
+ Al generar un acceso con `product_content:manage`, indica de 1 a 50 IDs de
104
+ productos activos de la empresa. El token no habilita otros productos aunque
105
+ el usuario tenga acceso a ellos en el backoffice. Usa un tipo de contenido
106
+ activo de uso `product_additional`; los campos y las referencias de imágenes
107
+ se validan con las mismas reglas de guardado del administrador.
108
+
109
+ ```ts
110
+ const [block] = await content.listProductBlocks(productId, {
111
+ contentType: "landing_producto",
112
+ locale: "es-CO",
113
+ });
114
+ await content.updateProductBlock(productId, block.id, {
115
+ content: { ...block.content, titulo: "Nuevo título" },
116
+ sortOrder: block.sortOrder,
117
+ expectedUpdatedAt: block.updatedAt,
118
+ });
119
+ ```
120
+
121
+ `createProductBlock` requiere `contentType`, `locale`, `content` y `sortOrder`.
122
+ Para ordenar, envía **todos** los bloques del idioma en el orden deseado,
123
+ incluyendo el `id` y `expectedUpdatedAt` de cada uno. El servidor aplica el
124
+ cambio atómicamente; una versión obsoleta devuelve conflicto HTTP 409. Vuelve
125
+ a listar y pide revisar el conflicto antes de repetir. Retirar un bloque quita
126
+ su asociación con el producto, sin borrar el tipo, la entrada independiente ni
127
+ los archivos de imagen. Para builds estáticos, el cliente público ofrece
128
+ `client.content.getForProducts(productIds)` y devuelve un registro indexado por
129
+ ID normalizado en minúsculas, incluyendo listas vacías para productos sin
130
+ bloques visibles. Agrupa hasta 50 productos por solicitud; no revela bloques
131
+ de productos no publicados en el sitio. `getForProduct` sigue disponible.
92
132
 
93
133
  ## Idioma, nombres y keys
94
134
 
package/client.d.ts CHANGED
@@ -35,6 +35,7 @@ export interface BacanoClient {
35
35
  getByCategory(categoryKey: string, opts?: GetContentOptions): Promise<ContentEntry[]>;
36
36
  getByTag(tag: string, opts?: GetContentOptions): Promise<ContentEntry[]>;
37
37
  getForProduct(productId: string, opts?: GetContentOptions): Promise<ProductContentBlock[]>;
38
+ getForProducts(productIds: string[], opts?: GetContentOptions): Promise<Record<string, ProductContentBlock[]>>;
38
39
  };
39
40
  forms: {
40
41
  /** Returns the current published definition or null when it is unavailable. */
@@ -2,7 +2,7 @@ import type { FormManagementClient } from "./form-management-types";
2
2
  export type ContentManagementTypeKind = "entry" | "component";
3
3
  export type ContentManagementTypeStatus = "draft" | "active" | "archived";
4
4
  export type ContentManagementTypeUsage = "website_content" | "product_additional" | "product_specification";
5
- export type ContentManagementScope = "content_types:manage" | "content_entries:manage" | "content_media:upload" | "content_entries:publish" | "forms:manage" | "forms:publish";
5
+ export type ContentManagementScope = "content_types:manage" | "content_entries:manage" | "content_media:upload" | "content_entries:publish" | "forms:manage" | "forms:publish" | "product_content:manage";
6
6
  export type ContentManagementFieldType = "boolean" | "component" | "date" | "link" | "measure" | "media" | "number" | "reference" | "repeater" | "rich_text" | "rich_text_full" | "select" | "slug" | "text" | "textarea";
7
7
  export interface ContentManagementChildFieldInput {
8
8
  id?: string | null;
@@ -68,8 +68,46 @@ export interface ContentManagementContext {
68
68
  /** @deprecated Use scopes to inspect the complete grant. */
69
69
  scope: ContentManagementScope;
70
70
  scopes: ContentManagementScope[];
71
+ productIds: string[];
71
72
  expiresAt: string;
72
73
  }
74
+ export interface ContentManagementProductBlock {
75
+ id: string;
76
+ productId: string;
77
+ contentType: {
78
+ id: string;
79
+ key: string;
80
+ name: string;
81
+ };
82
+ locale: string;
83
+ content: Record<string, unknown>;
84
+ sortOrder: number;
85
+ createdAt: string;
86
+ updatedAt: string;
87
+ }
88
+ export interface ContentManagementProductBlockListInput {
89
+ contentType?: string;
90
+ locale?: string;
91
+ }
92
+ export interface ContentManagementProductBlockCreateInput {
93
+ contentType: string;
94
+ locale?: string;
95
+ content: Record<string, unknown>;
96
+ sortOrder: number;
97
+ }
98
+ export interface ContentManagementProductBlockUpdateInput {
99
+ content: Record<string, unknown>;
100
+ sortOrder: number;
101
+ expectedUpdatedAt: string;
102
+ }
103
+ export interface ContentManagementProductBlockReorderInput {
104
+ locale: string;
105
+ /** Complete ordered list for this product and locale. */
106
+ items: Array<{
107
+ id: string;
108
+ expectedUpdatedAt: string;
109
+ }>;
110
+ }
73
111
  export type ContentManagementEntryStatus = "draft" | "published" | "archived";
74
112
  export interface ContentManagementEntryInput {
75
113
  contentType: string;
@@ -262,6 +300,11 @@ export interface ContentManagementClientConfig {
262
300
  }
263
301
  export interface ContentManagementClient extends FormManagementClient {
264
302
  getContext(): Promise<ContentManagementContext>;
303
+ listProductBlocks(productId: string, input?: ContentManagementProductBlockListInput): Promise<ContentManagementProductBlock[]>;
304
+ createProductBlock(productId: string, input: ContentManagementProductBlockCreateInput): Promise<ContentManagementProductBlock>;
305
+ updateProductBlock(productId: string, blockId: string, input: ContentManagementProductBlockUpdateInput): Promise<ContentManagementProductBlock>;
306
+ reorderProductBlocks(productId: string, input: ContentManagementProductBlockReorderInput): Promise<ContentManagementProductBlock[]>;
307
+ retireProductBlock(productId: string, blockId: string, expectedUpdatedAt: string): Promise<ContentManagementProductBlock>;
265
308
  getReferences(): Promise<ContentManagementReferences>;
266
309
  listTypes(): Promise<ContentManagementType[]>;
267
310
  getType(identifier: string): Promise<ContentManagementType>;
@@ -112,6 +112,13 @@ function mediaListPath(input = {}) {
112
112
  const query = search.toString();
113
113
  return query ? `/media?${query}` : "/media";
114
114
  }
115
+ function productBlocksPath(productId, input = {}) {
116
+ const path = `/products/${encodeIdentifier(productId)}/blocks`;
117
+ const search = new URLSearchParams();
118
+ if (input.contentType) search.set("contentType", input.contentType);
119
+ if (input.locale) search.set("locale", input.locale);
120
+ return search.size ? `${path}?${search}` : path;
121
+ }
115
122
  function uploadBody(file) {
116
123
  if ("arrayBuffer" in file) return file;
117
124
  if (file instanceof ArrayBuffer) return file;
@@ -183,6 +190,25 @@ function createContentManagementClient(config) {
183
190
  return {
184
191
  ...createFormManagementMethods(request),
185
192
  getContext: () => request("/context"),
193
+ listProductBlocks: (productId, input) => request(
194
+ productBlocksPath(productId, input)
195
+ ),
196
+ createProductBlock: (productId, input) => request(productBlocksPath(productId), {
197
+ method: "POST",
198
+ body: JSON.stringify(input)
199
+ }),
200
+ updateProductBlock: (productId, blockId, input) => request(
201
+ `${productBlocksPath(productId)}/${encodeIdentifier(blockId)}`,
202
+ { method: "PUT", body: JSON.stringify(input) }
203
+ ),
204
+ reorderProductBlocks: (productId, input) => request(
205
+ `${productBlocksPath(productId)}/order`,
206
+ { method: "PUT", body: JSON.stringify(input) }
207
+ ),
208
+ retireProductBlock: (productId, blockId, expectedUpdatedAt) => request(
209
+ `${productBlocksPath(productId)}/${encodeIdentifier(blockId)}`,
210
+ { method: "DELETE", body: JSON.stringify({ expectedUpdatedAt }) }
211
+ ),
186
212
  getReferences: () => request("/references"),
187
213
  listTypes: () => request("/types"),
188
214
  getType: (identifier) => request(`/types/${encodeIdentifier(identifier)}`),
@@ -85,6 +85,13 @@ function mediaListPath(input = {}) {
85
85
  const query = search.toString();
86
86
  return query ? `/media?${query}` : "/media";
87
87
  }
88
+ function productBlocksPath(productId, input = {}) {
89
+ const path = `/products/${encodeIdentifier(productId)}/blocks`;
90
+ const search = new URLSearchParams();
91
+ if (input.contentType) search.set("contentType", input.contentType);
92
+ if (input.locale) search.set("locale", input.locale);
93
+ return search.size ? `${path}?${search}` : path;
94
+ }
88
95
  function uploadBody(file) {
89
96
  if ("arrayBuffer" in file) return file;
90
97
  if (file instanceof ArrayBuffer) return file;
@@ -156,6 +163,25 @@ function createContentManagementClient(config) {
156
163
  return {
157
164
  ...createFormManagementMethods(request),
158
165
  getContext: () => request("/context"),
166
+ listProductBlocks: (productId, input) => request(
167
+ productBlocksPath(productId, input)
168
+ ),
169
+ createProductBlock: (productId, input) => request(productBlocksPath(productId), {
170
+ method: "POST",
171
+ body: JSON.stringify(input)
172
+ }),
173
+ updateProductBlock: (productId, blockId, input) => request(
174
+ `${productBlocksPath(productId)}/${encodeIdentifier(blockId)}`,
175
+ { method: "PUT", body: JSON.stringify(input) }
176
+ ),
177
+ reorderProductBlocks: (productId, input) => request(
178
+ `${productBlocksPath(productId)}/order`,
179
+ { method: "PUT", body: JSON.stringify(input) }
180
+ ),
181
+ retireProductBlock: (productId, blockId, expectedUpdatedAt) => request(
182
+ `${productBlocksPath(productId)}/${encodeIdentifier(blockId)}`,
183
+ { method: "DELETE", body: JSON.stringify({ expectedUpdatedAt }) }
184
+ ),
159
185
  getReferences: () => request("/references"),
160
186
  listTypes: () => request("/types"),
161
187
  getType: (identifier) => request(`/types/${encodeIdentifier(identifier)}`),
package/index.cjs CHANGED
@@ -1414,6 +1414,35 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
1414
1414
  }
1415
1415
  `
1416
1416
  );
1417
+ var WEBSITE_GET_PRODUCT_CONTENT_BATCH = (
1418
+ /* GraphQL */
1419
+ `
1420
+ mutation WebsiteGetProductContentBatch(
1421
+ $websiteId: uuid!
1422
+ $productIds: [uuid!]!
1423
+ $locale: String
1424
+ ) {
1425
+ result: websiteGetProductContentBatch(
1426
+ website_id: $websiteId
1427
+ product_ids: $productIds
1428
+ locale: $locale
1429
+ ) {
1430
+ success
1431
+ errors
1432
+ blocks {
1433
+ id
1434
+ product_id
1435
+ locale
1436
+ content_json
1437
+ resolved_fields
1438
+ sort_order
1439
+ updated_at
1440
+ content_type { id key name kind }
1441
+ }
1442
+ }
1443
+ }
1444
+ `
1445
+ );
1417
1446
  var CHECK_AVAILABILITY = (
1418
1447
  /* GraphQL */
1419
1448
  `
@@ -5782,6 +5811,33 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
5782
5811
  return (data.result.blocks ?? []).map(
5783
5812
  (block) => mapProductContentBlock(block, storageUrl)
5784
5813
  );
5814
+ },
5815
+ async getForProducts(productIds, opts) {
5816
+ const uniqueIds = [...new Set(productIds.map((id) => id.toLowerCase()))];
5817
+ const blocksByProduct = Object.fromEntries(uniqueIds.map((id) => [id, []]));
5818
+ if (uniqueIds.length === 0) return blocksByProduct;
5819
+ const ctx = getContext();
5820
+ for (let start = 0; start < uniqueIds.length; start += 50) {
5821
+ const batch = uniqueIds.slice(start, start + 50);
5822
+ const data = await executeGraphQL(
5823
+ graphqlUrl,
5824
+ WEBSITE_GET_PRODUCT_CONTENT_BATCH,
5825
+ {
5826
+ websiteId: ctx.websiteId,
5827
+ productIds: batch,
5828
+ locale: opts?.locale ?? DEFAULT_LOCALE
5829
+ },
5830
+ getHeaders
5831
+ );
5832
+ if (!data.result?.success) continue;
5833
+ for (const block of data.result.blocks ?? []) {
5834
+ const id = block.product_id.toLowerCase();
5835
+ if (Object.prototype.hasOwnProperty.call(blocksByProduct, id)) {
5836
+ blocksByProduct[id].push(mapProductContentBlock(block, storageUrl));
5837
+ }
5838
+ }
5839
+ }
5840
+ return blocksByProduct;
5785
5841
  }
5786
5842
  };
5787
5843
  }
@@ -7147,7 +7203,8 @@ function createBacanoClient(config) {
7147
7203
  getByKey: contentModule.getByKey.bind(contentModule),
7148
7204
  getByCategory: contentModule.getByCategory.bind(contentModule),
7149
7205
  getByTag: contentModule.getByTag.bind(contentModule),
7150
- getForProduct: contentModule.getForProduct.bind(contentModule)
7206
+ getForProduct: contentModule.getForProduct.bind(contentModule),
7207
+ getForProducts: contentModule.getForProducts.bind(contentModule)
7151
7208
  },
7152
7209
  forms: {
7153
7210
  getDefinition: formsModule.getDefinition.bind(formsModule),
package/index.js CHANGED
@@ -1353,6 +1353,35 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
1353
1353
  }
1354
1354
  `
1355
1355
  );
1356
+ var WEBSITE_GET_PRODUCT_CONTENT_BATCH = (
1357
+ /* GraphQL */
1358
+ `
1359
+ mutation WebsiteGetProductContentBatch(
1360
+ $websiteId: uuid!
1361
+ $productIds: [uuid!]!
1362
+ $locale: String
1363
+ ) {
1364
+ result: websiteGetProductContentBatch(
1365
+ website_id: $websiteId
1366
+ product_ids: $productIds
1367
+ locale: $locale
1368
+ ) {
1369
+ success
1370
+ errors
1371
+ blocks {
1372
+ id
1373
+ product_id
1374
+ locale
1375
+ content_json
1376
+ resolved_fields
1377
+ sort_order
1378
+ updated_at
1379
+ content_type { id key name kind }
1380
+ }
1381
+ }
1382
+ }
1383
+ `
1384
+ );
1356
1385
  var CHECK_AVAILABILITY = (
1357
1386
  /* GraphQL */
1358
1387
  `
@@ -5721,6 +5750,33 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
5721
5750
  return (data.result.blocks ?? []).map(
5722
5751
  (block) => mapProductContentBlock(block, storageUrl)
5723
5752
  );
5753
+ },
5754
+ async getForProducts(productIds, opts) {
5755
+ const uniqueIds = [...new Set(productIds.map((id) => id.toLowerCase()))];
5756
+ const blocksByProduct = Object.fromEntries(uniqueIds.map((id) => [id, []]));
5757
+ if (uniqueIds.length === 0) return blocksByProduct;
5758
+ const ctx = getContext();
5759
+ for (let start = 0; start < uniqueIds.length; start += 50) {
5760
+ const batch = uniqueIds.slice(start, start + 50);
5761
+ const data = await executeGraphQL(
5762
+ graphqlUrl,
5763
+ WEBSITE_GET_PRODUCT_CONTENT_BATCH,
5764
+ {
5765
+ websiteId: ctx.websiteId,
5766
+ productIds: batch,
5767
+ locale: opts?.locale ?? DEFAULT_LOCALE
5768
+ },
5769
+ getHeaders
5770
+ );
5771
+ if (!data.result?.success) continue;
5772
+ for (const block of data.result.blocks ?? []) {
5773
+ const id = block.product_id.toLowerCase();
5774
+ if (Object.prototype.hasOwnProperty.call(blocksByProduct, id)) {
5775
+ blocksByProduct[id].push(mapProductContentBlock(block, storageUrl));
5776
+ }
5777
+ }
5778
+ }
5779
+ return blocksByProduct;
5724
5780
  }
5725
5781
  };
5726
5782
  }
@@ -7086,7 +7142,8 @@ function createBacanoClient(config) {
7086
7142
  getByKey: contentModule.getByKey.bind(contentModule),
7087
7143
  getByCategory: contentModule.getByCategory.bind(contentModule),
7088
7144
  getByTag: contentModule.getByTag.bind(contentModule),
7089
- getForProduct: contentModule.getForProduct.bind(contentModule)
7145
+ getForProduct: contentModule.getForProduct.bind(contentModule),
7146
+ getForProducts: contentModule.getForProducts.bind(contentModule)
7090
7147
  },
7091
7148
  forms: {
7092
7149
  getDefinition: formsModule.getDefinition.bind(formsModule),
@@ -5,5 +5,6 @@ export interface ContentModule {
5
5
  getByCategory(categoryKey: string, opts?: GetContentOptions): Promise<ContentEntry[]>;
6
6
  getByTag(tag: string, opts?: GetContentOptions): Promise<ContentEntry[]>;
7
7
  getForProduct(productId: string, opts?: GetContentOptions): Promise<ProductContentBlock[]>;
8
+ getForProducts(productIds: string[], opts?: GetContentOptions): Promise<Record<string, ProductContentBlock[]>>;
8
9
  }
9
10
  export declare function createContentModule(graphqlUrl: string, getHeaders: RequestHeadersProvider, getContext: () => BacanoClientContext, storageUrl?: string): ContentModule;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacano/sdk",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "TypeScript SDK for building websites on the Bacano ERP",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
package/react.cjs CHANGED
@@ -1122,6 +1122,35 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
1122
1122
  }
1123
1123
  `
1124
1124
  );
1125
+ var WEBSITE_GET_PRODUCT_CONTENT_BATCH = (
1126
+ /* GraphQL */
1127
+ `
1128
+ mutation WebsiteGetProductContentBatch(
1129
+ $websiteId: uuid!
1130
+ $productIds: [uuid!]!
1131
+ $locale: String
1132
+ ) {
1133
+ result: websiteGetProductContentBatch(
1134
+ website_id: $websiteId
1135
+ product_ids: $productIds
1136
+ locale: $locale
1137
+ ) {
1138
+ success
1139
+ errors
1140
+ blocks {
1141
+ id
1142
+ product_id
1143
+ locale
1144
+ content_json
1145
+ resolved_fields
1146
+ sort_order
1147
+ updated_at
1148
+ content_type { id key name kind }
1149
+ }
1150
+ }
1151
+ }
1152
+ `
1153
+ );
1125
1154
  var CHECK_AVAILABILITY = (
1126
1155
  /* GraphQL */
1127
1156
  `
@@ -6814,6 +6843,33 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
6814
6843
  return (data.result.blocks ?? []).map(
6815
6844
  (block) => mapProductContentBlock(block, storageUrl)
6816
6845
  );
6846
+ },
6847
+ async getForProducts(productIds, opts) {
6848
+ const uniqueIds = [...new Set(productIds.map((id) => id.toLowerCase()))];
6849
+ const blocksByProduct = Object.fromEntries(uniqueIds.map((id) => [id, []]));
6850
+ if (uniqueIds.length === 0) return blocksByProduct;
6851
+ const ctx = getContext();
6852
+ for (let start = 0; start < uniqueIds.length; start += 50) {
6853
+ const batch = uniqueIds.slice(start, start + 50);
6854
+ const data = await executeGraphQL(
6855
+ graphqlUrl,
6856
+ WEBSITE_GET_PRODUCT_CONTENT_BATCH,
6857
+ {
6858
+ websiteId: ctx.websiteId,
6859
+ productIds: batch,
6860
+ locale: opts?.locale ?? DEFAULT_LOCALE
6861
+ },
6862
+ getHeaders
6863
+ );
6864
+ if (!data.result?.success) continue;
6865
+ for (const block of data.result.blocks ?? []) {
6866
+ const id = block.product_id.toLowerCase();
6867
+ if (Object.prototype.hasOwnProperty.call(blocksByProduct, id)) {
6868
+ blocksByProduct[id].push(mapProductContentBlock(block, storageUrl));
6869
+ }
6870
+ }
6871
+ }
6872
+ return blocksByProduct;
6817
6873
  }
6818
6874
  };
6819
6875
  }
@@ -7561,7 +7617,8 @@ function createBacanoClient(config) {
7561
7617
  getByKey: contentModule.getByKey.bind(contentModule),
7562
7618
  getByCategory: contentModule.getByCategory.bind(contentModule),
7563
7619
  getByTag: contentModule.getByTag.bind(contentModule),
7564
- getForProduct: contentModule.getForProduct.bind(contentModule)
7620
+ getForProduct: contentModule.getForProduct.bind(contentModule),
7621
+ getForProducts: contentModule.getForProducts.bind(contentModule)
7565
7622
  },
7566
7623
  forms: {
7567
7624
  getDefinition: formsModule.getDefinition.bind(formsModule),
package/react.js CHANGED
@@ -1065,6 +1065,35 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
1065
1065
  }
1066
1066
  `
1067
1067
  );
1068
+ var WEBSITE_GET_PRODUCT_CONTENT_BATCH = (
1069
+ /* GraphQL */
1070
+ `
1071
+ mutation WebsiteGetProductContentBatch(
1072
+ $websiteId: uuid!
1073
+ $productIds: [uuid!]!
1074
+ $locale: String
1075
+ ) {
1076
+ result: websiteGetProductContentBatch(
1077
+ website_id: $websiteId
1078
+ product_ids: $productIds
1079
+ locale: $locale
1080
+ ) {
1081
+ success
1082
+ errors
1083
+ blocks {
1084
+ id
1085
+ product_id
1086
+ locale
1087
+ content_json
1088
+ resolved_fields
1089
+ sort_order
1090
+ updated_at
1091
+ content_type { id key name kind }
1092
+ }
1093
+ }
1094
+ }
1095
+ `
1096
+ );
1068
1097
  var CHECK_AVAILABILITY = (
1069
1098
  /* GraphQL */
1070
1099
  `
@@ -6761,6 +6790,33 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
6761
6790
  return (data.result.blocks ?? []).map(
6762
6791
  (block) => mapProductContentBlock(block, storageUrl)
6763
6792
  );
6793
+ },
6794
+ async getForProducts(productIds, opts) {
6795
+ const uniqueIds = [...new Set(productIds.map((id) => id.toLowerCase()))];
6796
+ const blocksByProduct = Object.fromEntries(uniqueIds.map((id) => [id, []]));
6797
+ if (uniqueIds.length === 0) return blocksByProduct;
6798
+ const ctx = getContext();
6799
+ for (let start = 0; start < uniqueIds.length; start += 50) {
6800
+ const batch = uniqueIds.slice(start, start + 50);
6801
+ const data = await executeGraphQL(
6802
+ graphqlUrl,
6803
+ WEBSITE_GET_PRODUCT_CONTENT_BATCH,
6804
+ {
6805
+ websiteId: ctx.websiteId,
6806
+ productIds: batch,
6807
+ locale: opts?.locale ?? DEFAULT_LOCALE
6808
+ },
6809
+ getHeaders
6810
+ );
6811
+ if (!data.result?.success) continue;
6812
+ for (const block of data.result.blocks ?? []) {
6813
+ const id = block.product_id.toLowerCase();
6814
+ if (Object.prototype.hasOwnProperty.call(blocksByProduct, id)) {
6815
+ blocksByProduct[id].push(mapProductContentBlock(block, storageUrl));
6816
+ }
6817
+ }
6818
+ }
6819
+ return blocksByProduct;
6764
6820
  }
6765
6821
  };
6766
6822
  }
@@ -7508,7 +7564,8 @@ function createBacanoClient(config) {
7508
7564
  getByKey: contentModule.getByKey.bind(contentModule),
7509
7565
  getByCategory: contentModule.getByCategory.bind(contentModule),
7510
7566
  getByTag: contentModule.getByTag.bind(contentModule),
7511
- getForProduct: contentModule.getForProduct.bind(contentModule)
7567
+ getForProduct: contentModule.getForProduct.bind(contentModule),
7568
+ getForProducts: contentModule.getForProducts.bind(contentModule)
7512
7569
  },
7513
7570
  forms: {
7514
7571
  getDefinition: formsModule.getDefinition.bind(formsModule),
@@ -13,6 +13,7 @@ export declare const WEBSITE_GET_PRODUCT_SPECIFICATIONS = "\n mutation WebsiteG
13
13
  export declare const GET_BRANDS = "\n query GetBrands($companyId: uuid!, $chainId: uuid!) {\n brands(\n where: {\n company_id: { _eq: $companyId }\n products: {\n chain_products: {\n chain_id: { _eq: $chainId }\n is_enabled: { _eq: true }\n status: { _eq: active }\n }\n }\n }\n order_by: { name: asc }\n ) {\n id\n name\n }\n }\n";
14
14
  export declare const WEBSITE_GET_CONTENT = "\n mutation WebsiteGetContent(\n $websiteId: uuid!\n $key: String\n $categoryKey: String\n $tag: String\n $locale: String\n ) {\n result: websiteGetContent(\n website_id: $websiteId\n key: $key\n category_key: $categoryKey\n tag: $tag\n locale: $locale\n ) {\n success\n errors\n entries {\n \n id\n key\n category_key\n name\n locale\n status\n tags\n content_json\n resolved_fields\n published_at\n updated_at\n content_type {\n id\n key\n name\n kind\n }\n\n }\n }\n }\n";
15
15
  export declare const WEBSITE_GET_PRODUCT_CONTENT = "\n mutation WebsiteGetProductContent(\n $websiteId: uuid!\n $productId: uuid!\n $locale: String\n ) {\n result: websiteGetProductContent(\n website_id: $websiteId\n product_id: $productId\n locale: $locale\n ) {\n success\n errors\n blocks {\n id\n product_id\n locale\n content_json\n resolved_fields\n sort_order\n updated_at\n content_type {\n id\n key\n name\n kind\n }\n }\n }\n }\n";
16
+ export declare const WEBSITE_GET_PRODUCT_CONTENT_BATCH = "\n mutation WebsiteGetProductContentBatch(\n $websiteId: uuid!\n $productIds: [uuid!]!\n $locale: String\n ) {\n result: websiteGetProductContentBatch(\n website_id: $websiteId\n product_ids: $productIds\n locale: $locale\n ) {\n success\n errors\n blocks {\n id\n product_id\n locale\n content_json\n resolved_fields\n sort_order\n updated_at\n content_type { id key name kind }\n }\n }\n }\n";
16
17
  export declare const CHECK_AVAILABILITY = "\n query CheckAvailability(\n $variantIds: [uuid!]!\n $chainId: uuid!\n $websiteId: uuid\n ) {\n websiteProductAvailability(\n product_variant_ids: $variantIds\n chain_id: $chainId\n website_id: $websiteId\n ) {\n product_variant_id\n in_stock\n label\n max_purchasable_quantity\n }\n }\n";
17
18
  export declare const GET_PRODUCT_LIVE_STATE = "\n query GetProductLiveState(\n $variantIds: [uuid!]!\n $companyId: uuid!\n $chainId: uuid!\n $websiteId: uuid!\n ) {\n product_variants(\n where: {\n id: { _in: $variantIds }\n is_disabled: { _eq: false }\n product: {\n company_id: { _eq: $companyId }\n product_website_publications: {\n website_id: { _eq: $websiteId }\n is_visible: { _eq: true }\n }\n chain_products: {\n chain_id: { _eq: $chainId }\n is_enabled: { _eq: true }\n status: { _eq: active }\n }\n }\n }\n ) {\n id\n product_variant_prices(\n where: {\n price_type: { _in: [CLIENT, SALE, WHOLESALE, WHOLESALE_2] }\n _or: [\n { chain_id: { _eq: $chainId } }\n { chain_id: { _is_null: true } }\n ]\n }\n ) {\n id\n price\n price_type\n chain_id\n }\n }\n websiteProductAvailability(\n product_variant_ids: $variantIds\n chain_id: $chainId\n website_id: $websiteId\n ) {\n product_variant_id\n in_stock\n label\n max_purchasable_quantity\n }\n }\n";
18
19
  export declare const CREATE_CART = "\n mutation CreateCart(\n $websiteId: uuid!\n $cartToken: String\n $contact: WebsiteGuestContactInput\n $fulfillmentFamily: String\n ) {\n websiteCreateCart(\n website_id: $websiteId\n cart_token: $cartToken\n contact: $contact\n fulfillment_family: $fulfillmentFamily\n ) {\n success\n order_id\n cart_token\n cart {\n id\n status\n discount\n tax\n total_without_tax\n total_with_tax\n total_with_discount\n created_at\n updated_at\n volume_pricing {\n enabled\n eligibleQuantity\n appliedPriceType\n appliedMinQuantity\n nextPriceType\n nextMinQuantity\n remainingQuantity\n totalSavings\n }\n items {\n id\n product_variant_id\n quantity\n unit_price\n line_total\n base_unit_price\n unit_savings\n line_savings\n volume_pricing_eligible\n resolved_price_type\n volume_pricing_min_quantity\n product_variant {\n id\n sku\n product {\n id\n name\n }\n prices {\n id\n price\n price_type\n chain_id\n }\n }\n }\n }\n errors\n }\n }\n";