brainerce 1.54.0 → 1.56.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/dist/index.mjs CHANGED
@@ -120,6 +120,24 @@ var SDK_VERSION = "1.54.0";
120
120
  // src/client.ts
121
121
  var DEFAULT_BASE_URL = "https://api.brainerce.com";
122
122
  var DEFAULT_TIMEOUT = 3e4;
123
+ var META_EVENT_NAMES = {
124
+ view_item: "ViewContent",
125
+ add_to_cart: "AddToCart",
126
+ begin_checkout: "InitiateCheckout",
127
+ add_payment_info: "AddPaymentInfo",
128
+ purchase: "Purchase",
129
+ search: "Search",
130
+ sign_up: "CompleteRegistration"
131
+ };
132
+ var TIKTOK_EVENT_NAMES = {
133
+ view_item: "ViewContent",
134
+ add_to_cart: "AddToCart",
135
+ begin_checkout: "InitiateCheckout",
136
+ add_payment_info: "AddPaymentInfo",
137
+ purchase: "CompletePayment",
138
+ search: "Search",
139
+ sign_up: "CompleteRegistration"
140
+ };
123
141
  var RTL_LOCALES = /* @__PURE__ */ new Set(["ar", "he", "fa", "ur", "yi"]);
124
142
  function getDirectionForLocale(locale) {
125
143
  if (!locale) return "ltr";
@@ -174,6 +192,12 @@ var _BrainerceClient = class _BrainerceClient {
174
192
  // GA4 stitch state (see `loadGoogleAnalytics()` in the Analytics section).
175
193
  this._ga4MeasurementId = null;
176
194
  this._ga4StitchPromise = null;
195
+ // Marketing tags booted by `initTracking()`. Each holds the id already
196
+ // loaded, which is what makes a repeat call a no-op instead of a second
197
+ // pixel install (and a doubled PageView).
198
+ this._gtmContainerId = null;
199
+ this._metaPixelId = null;
200
+ this._tiktokPixelId = null;
177
201
  /** One warning per client, not per keystroke-driven address submit. */
178
202
  this._warnedResolvedOnlyAddressFields = false;
179
203
  /** localStorage key for session cart reference (sessionToken + cartId) */
@@ -1195,6 +1219,225 @@ var _BrainerceClient = class _BrainerceClient {
1195
1219
  }
1196
1220
  });
1197
1221
  }
1222
+ /**
1223
+ * Boot every marketing tag the merchant has configured — GA4, Google Tag
1224
+ * Manager, the Meta pixel, the TikTok pixel — in one call.
1225
+ *
1226
+ * Pass `storeInfo.tracking` straight through. The ids in it are resolved
1227
+ * server-side from the marketplace apps the merchant already connected, so
1228
+ * for the common case nobody types an id anywhere and nobody redeploys the
1229
+ * storefront: connect the Google app in the dashboard and this call starts
1230
+ * loading GA4 on the next page render.
1231
+ *
1232
+ * Call it once, as early as possible (root layout / app entry). It is
1233
+ * idempotent, a no-op during SSR, and never throws — a blocked or missing
1234
+ * tag must never take a storefront down with it.
1235
+ *
1236
+ * GA4 goes through {@link loadGoogleAnalytics}, so the `client_id` /
1237
+ * `session_id` stitch ids keep flowing onto cart and checkout calls and the
1238
+ * server-side purchase conversion still lands in the right session.
1239
+ *
1240
+ * @example
1241
+ * ```typescript
1242
+ * const storeInfo = await client.getStoreInfo();
1243
+ * client.initTracking(storeInfo.tracking);
1244
+ * // …later, on the order confirmation page:
1245
+ * client.trackMarketingEvent('purchase', {
1246
+ * transactionId: order.id,
1247
+ * currency: order.currency,
1248
+ * value: order.totalAmount,
1249
+ * items: order.items.map((i) => ({ itemId: i.sku, itemName: i.name, price: i.price, quantity: i.quantity })),
1250
+ * });
1251
+ * ```
1252
+ */
1253
+ initTracking(tracking) {
1254
+ if (typeof window === "undefined" || !tracking) return;
1255
+ if (tracking.ga4MeasurementId) {
1256
+ this.loadGoogleAnalytics(tracking.ga4MeasurementId);
1257
+ }
1258
+ if (tracking.gtmContainerId) this.loadGtm(tracking.gtmContainerId);
1259
+ if (tracking.metaPixelId) this.loadMetaPixel(tracking.metaPixelId);
1260
+ if (tracking.tiktokPixelId) this.loadTikTokPixel(tracking.tiktokPixelId);
1261
+ }
1262
+ /**
1263
+ * Report one e-commerce event to every marketing tag that
1264
+ * {@link initTracking} loaded (GA4/GTM, Meta, TikTok).
1265
+ *
1266
+ * Distinct from {@link trackEvent}, which posts a cookieless pageview/beacon
1267
+ * to Brainerce's own storefront analytics. This one is about ad platforms —
1268
+ * call both; they answer different questions.
1269
+ *
1270
+ * You describe what happened once, in GA4's vocabulary, and the SDK
1271
+ * translates: a `dataLayer` push for GA4/GTM, the matching Meta standard
1272
+ * event via `fbq`, and the matching TikTok event via `ttq`. Tags that aren't
1273
+ * loaded are skipped silently, so the same call is correct whether the
1274
+ * merchant has connected none, one, or all of them.
1275
+ *
1276
+ * Why this matters for ad spend: a GTM container with no `dataLayer` events
1277
+ * is an empty container, and Meta cannot optimize a campaign it never sees a
1278
+ * `Purchase` for. The value/currency/item-id triple in {@link TrackingEventPayload}
1279
+ * is the whole input to that optimization.
1280
+ *
1281
+ * `purchase` is de-duplicated by the vendors on `transactionId` (GA4
1282
+ * `transaction_id`, Meta `eventID`, TikTok `event_id`), so a shopper
1283
+ * refreshing the confirmation page cannot double-count the order — pass the
1284
+ * order id and the safety is automatic.
1285
+ *
1286
+ * SSR-safe and never throws.
1287
+ */
1288
+ trackMarketingEvent(name, payload = {}) {
1289
+ if (typeof window === "undefined") return;
1290
+ try {
1291
+ const items = payload.items ?? [];
1292
+ window.dataLayer = window.dataLayer || [];
1293
+ window.dataLayer.push({ ecommerce: null });
1294
+ window.dataLayer.push({
1295
+ event: name,
1296
+ ecommerce: {
1297
+ ...payload.currency ? { currency: payload.currency } : {},
1298
+ ...payload.value !== void 0 ? { value: payload.value } : {},
1299
+ ...payload.transactionId ? { transaction_id: payload.transactionId } : {},
1300
+ ...payload.shipping !== void 0 ? { shipping: payload.shipping } : {},
1301
+ ...payload.tax !== void 0 ? { tax: payload.tax } : {},
1302
+ ...payload.coupon ? { coupon: payload.coupon } : {},
1303
+ items: items.map((item) => ({
1304
+ item_id: item.itemId,
1305
+ ...item.itemName ? { item_name: item.itemName } : {},
1306
+ ...item.price !== void 0 ? { price: item.price } : {},
1307
+ ...item.quantity !== void 0 ? { quantity: item.quantity } : {},
1308
+ ...item.itemVariant ? { item_variant: item.itemVariant } : {},
1309
+ ...item.itemCategory ? { item_category: item.itemCategory } : {}
1310
+ }))
1311
+ }
1312
+ });
1313
+ if (window.gtag && this._ga4MeasurementId) {
1314
+ window.gtag("event", name, {
1315
+ ...payload.currency ? { currency: payload.currency } : {},
1316
+ ...payload.value !== void 0 ? { value: payload.value } : {},
1317
+ ...payload.transactionId ? { transaction_id: payload.transactionId } : {},
1318
+ ...payload.shipping !== void 0 ? { shipping: payload.shipping } : {},
1319
+ ...payload.tax !== void 0 ? { tax: payload.tax } : {},
1320
+ ...payload.coupon ? { coupon: payload.coupon } : {},
1321
+ items: items.map((item) => ({
1322
+ item_id: item.itemId,
1323
+ item_name: item.itemName,
1324
+ price: item.price,
1325
+ quantity: item.quantity
1326
+ }))
1327
+ });
1328
+ }
1329
+ const metaEvent = META_EVENT_NAMES[name];
1330
+ if (window.fbq && metaEvent) {
1331
+ const contents = items.map((item) => ({
1332
+ id: item.itemId,
1333
+ quantity: item.quantity ?? 1,
1334
+ ...item.price !== void 0 ? { item_price: item.price } : {}
1335
+ }));
1336
+ window.fbq(
1337
+ "track",
1338
+ metaEvent,
1339
+ {
1340
+ ...payload.currency ? { currency: payload.currency } : {},
1341
+ ...payload.value !== void 0 ? { value: payload.value } : {},
1342
+ ...contents.length ? { contents, content_ids: contents.map((c) => c.id), content_type: "product" } : {}
1343
+ },
1344
+ // Meta de-dupes a browser event against a server (CAPI) event of the
1345
+ // same eventID, and against a repeat send of the same page.
1346
+ payload.transactionId ? { eventID: payload.transactionId } : void 0
1347
+ );
1348
+ }
1349
+ const tiktokEvent = TIKTOK_EVENT_NAMES[name];
1350
+ if (window.ttq?.track && tiktokEvent) {
1351
+ window.ttq.track(
1352
+ tiktokEvent,
1353
+ {
1354
+ ...payload.currency ? { currency: payload.currency } : {},
1355
+ ...payload.value !== void 0 ? { value: payload.value } : {},
1356
+ contents: items.map((item) => ({
1357
+ content_id: item.itemId,
1358
+ content_name: item.itemName,
1359
+ quantity: item.quantity ?? 1,
1360
+ price: item.price
1361
+ }))
1362
+ },
1363
+ payload.transactionId ? { event_id: payload.transactionId } : void 0
1364
+ );
1365
+ }
1366
+ } catch {
1367
+ }
1368
+ }
1369
+ /** Install the GTM container loader. Idempotent; no-op if already present. */
1370
+ loadGtm(containerId) {
1371
+ if (this._gtmContainerId === containerId) return;
1372
+ this._gtmContainerId = containerId;
1373
+ try {
1374
+ window.dataLayer = window.dataLayer || [];
1375
+ window.dataLayer.push({ "gtm.start": Date.now(), event: "gtm.js" });
1376
+ const script = document.createElement("script");
1377
+ script.async = true;
1378
+ script.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(containerId)}`;
1379
+ document.head.appendChild(script);
1380
+ } catch {
1381
+ }
1382
+ }
1383
+ /** Install the Meta pixel and fire its initial PageView. Idempotent. */
1384
+ loadMetaPixel(pixelId) {
1385
+ if (this._metaPixelId === pixelId) return;
1386
+ this._metaPixelId = pixelId;
1387
+ try {
1388
+ if (!window.fbq) {
1389
+ const queue = [];
1390
+ const fbq = ((...args) => {
1391
+ if (fbq.callMethod) fbq.callMethod(...args);
1392
+ else queue.push(args);
1393
+ });
1394
+ fbq.queue = queue;
1395
+ fbq.loaded = true;
1396
+ fbq.version = "2.0";
1397
+ window.fbq = fbq;
1398
+ window._fbq = fbq;
1399
+ const script = document.createElement("script");
1400
+ script.async = true;
1401
+ script.src = "https://connect.facebook.net/en_US/fbevents.js";
1402
+ document.head.appendChild(script);
1403
+ }
1404
+ window.fbq("init", pixelId);
1405
+ window.fbq("track", "PageView");
1406
+ } catch {
1407
+ }
1408
+ }
1409
+ /** Install the TikTok pixel and fire its initial page view. Idempotent. */
1410
+ loadTikTokPixel(pixelId) {
1411
+ if (this._tiktokPixelId === pixelId) return;
1412
+ this._tiktokPixelId = pixelId;
1413
+ try {
1414
+ const ttq = window.ttq ?? {};
1415
+ ttq._i = ttq._i ?? {};
1416
+ ttq._i[pixelId] = ttq._i[pixelId] ?? [];
1417
+ ttq._t = ttq._t ?? {};
1418
+ ttq._t[pixelId] = Date.now();
1419
+ ttq._o = ttq._o ?? {};
1420
+ ttq._o[pixelId] = {};
1421
+ const methods = ["page", "track", "identify", "instances", "ready"];
1422
+ ttq.methods = methods;
1423
+ for (const method of methods) {
1424
+ if (typeof ttq[method] !== "function") {
1425
+ ttq[method] = (...args) => {
1426
+ ttq._i?.[pixelId]?.push([method, ...args]);
1427
+ };
1428
+ }
1429
+ }
1430
+ window.ttq = ttq;
1431
+ const script = document.createElement("script");
1432
+ script.async = true;
1433
+ script.src = `https://analytics.tiktok.com/i18n/pixel/events.js?sdkid=${encodeURIComponent(
1434
+ pixelId
1435
+ )}&lib=ttq`;
1436
+ document.head.appendChild(script);
1437
+ ttq.page?.();
1438
+ } catch {
1439
+ }
1440
+ }
1198
1441
  /**
1199
1442
  * Merge the resolved GA4 stitch ids onto a request body — only for fields
1200
1443
  * the caller didn't already set explicitly (explicit values always win).
@@ -1324,6 +1567,50 @@ var _BrainerceClient = class _BrainerceClient {
1324
1567
  queryParamsWithRegion
1325
1568
  );
1326
1569
  }
1570
+ /**
1571
+ * Lightweight product rows for sitemap generation: `slug`, `updatedAt`, and
1572
+ * per-locale `localeSlugs` only — up to `limit` (max 5000) in ONE request,
1573
+ * with none of the 100-per-page clamp the full listing applies.
1574
+ *
1575
+ * Sales-channel (`salesChannelId`) mode only; other modes throw so
1576
+ * {@link getProductSitemapEntries} (sitemap.ts) can catch and fall back to
1577
+ * paginating `getProducts`. Prefer that helper over calling this directly.
1578
+ */
1579
+ async getSitemapProducts(limit = 5e3) {
1580
+ if (!this.isVibeCodedMode()) {
1581
+ throw new Error("getSitemapProducts is available in salesChannelId mode only");
1582
+ }
1583
+ const res = await this.vibeCodedRequest("GET", "/sitemap-products", void 0, { limit: Math.min(limit, 5e3) });
1584
+ return res.data;
1585
+ }
1586
+ /**
1587
+ * Resolve an old (renamed) slug to the entity's CURRENT slug, so the
1588
+ * storefront can issue a permanent (301/308) redirect instead of a 404.
1589
+ *
1590
+ * Call this in the catch/not-found path of a product or blog page:
1591
+ * the platform records every slug rename, so a URL that stopped matching
1592
+ * usually has a redirect. Returns `null` when there is no redirect (real
1593
+ * 404) or outside sales-channel mode — always fall through to notFound().
1594
+ *
1595
+ * @example
1596
+ * ```typescript
1597
+ * // app/products/[slug]/page.tsx — in the catch path:
1598
+ * const redirect = await client.resolveSlugRedirect('product', slug);
1599
+ * if (redirect) permanentRedirect(`/products/${redirect.currentSlug}`);
1600
+ * notFound();
1601
+ * ```
1602
+ */
1603
+ async resolveSlugRedirect(entityType, slug) {
1604
+ if (!this.isVibeCodedMode()) return null;
1605
+ try {
1606
+ return await this.vibeCodedRequest(
1607
+ "GET",
1608
+ `/slug-redirects/${entityType}/${encodePathSegment(slug)}`
1609
+ );
1610
+ } catch {
1611
+ return null;
1612
+ }
1613
+ }
1327
1614
  /**
1328
1615
  * Get a single product by ID
1329
1616
  * Works in vibe-coded, storefront (public), and admin mode
@@ -5115,9 +5402,7 @@ var _BrainerceClient = class _BrainerceClient {
5115
5402
  * ```
5116
5403
  */
5117
5404
  async setShippingAddress(checkoutId, address) {
5118
- const body = await this.withAnalyticsStitchIds(
5119
- this.stripResolvedOnlyAddressFields(address)
5120
- );
5405
+ const body = await this.withAnalyticsStitchIds(this.stripResolvedOnlyAddressFields(address));
5121
5406
  if (this.isVibeCodedMode()) {
5122
5407
  return this.vibeCodedRequest(
5123
5408
  "PATCH",
@@ -8618,6 +8903,41 @@ var _BrainerceClient = class _BrainerceClient {
8618
8903
  { salesChannelId }
8619
8904
  );
8620
8905
  }
8906
+ /**
8907
+ * Attach a customer to a sales channel (admin mode) — marks them as active in
8908
+ * that storefront. Accepts the sales-channel record ID or its public `vc_*`
8909
+ * connection ID.
8910
+ *
8911
+ * Rarely needed: the platform records a channel by itself whenever the
8912
+ * customer registers, signs in or checks out on it. Use this for migrations
8913
+ * from another system and for fixing up records you created yourself. A
8914
+ * customer belongs to one store but can be active in any number of its
8915
+ * channels, so calling this for several channels is normal and expected.
8916
+ *
8917
+ * This does not change where the customer CAME FROM — for that, pass
8918
+ * `acquisitionSalesChannelId` to {@link updateCustomer}.
8919
+ */
8920
+ async publishCustomerToSalesChannel(customerId, salesChannelId) {
8921
+ return this.adminRequest(
8922
+ "POST",
8923
+ `/api/v1/customers/${encodePathSegment(customerId)}/publish-sales-channel`,
8924
+ { salesChannelId }
8925
+ );
8926
+ }
8927
+ /**
8928
+ * Detach a customer from a sales channel (admin mode).
8929
+ *
8930
+ * A correction, NOT a block — it does not stop that person from buying on
8931
+ * that storefront, and the channel is recorded again the next time they sign
8932
+ * in or order there. There is no way to bar a customer from a channel.
8933
+ */
8934
+ async unpublishCustomerFromSalesChannel(customerId, salesChannelId) {
8935
+ return this.adminRequest(
8936
+ "POST",
8937
+ `/api/v1/customers/${encodePathSegment(customerId)}/unpublish-sales-channel`,
8938
+ { salesChannelId }
8939
+ );
8940
+ }
8621
8941
  /**
8622
8942
  * Publish a coupon to a sales channel (admin mode) — makes it redeemable on
8623
8943
  * that vibe-coded storefront. Accepts the sales-channel record ID or its
@@ -9809,7 +10129,8 @@ function buildProductJsonLd(product, opts) {
9809
10129
  const brand = opts.brandName ?? product.brands?.[0]?.name;
9810
10130
  const description = stripHtml(product.description).slice(0, 5e3);
9811
10131
  const effectivePrice = product.salePrice ?? product.basePrice;
9812
- const inStock = product.inventory ? (product.inventory.available ?? 0) > 0 : true;
10132
+ const inv = product.inventory;
10133
+ const availability = !inv ? "https://schema.org/InStock" : inv.inStock ?? (inv.available ?? 0) > 0 ? "https://schema.org/InStock" : inv.canPurchase ? "https://schema.org/BackOrder" : "https://schema.org/OutOfStock";
9813
10134
  const isVariable = product.type === "VARIABLE" && product.priceMin && product.priceMax;
9814
10135
  const itemCondition = "https://schema.org/NewCondition";
9815
10136
  const shippingDetails = (opts.shipping ?? []).filter((z) => z.amount !== null).map((z) => ({
@@ -9840,8 +10161,9 @@ function buildProductJsonLd(product, opts) {
9840
10161
  "@type": "AggregateOffer",
9841
10162
  lowPrice: product.priceMin,
9842
10163
  highPrice: product.priceMax,
10164
+ ...product.variants?.length ? { offerCount: product.variants.length } : {},
9843
10165
  priceCurrency: opts.currency,
9844
- availability: inStock ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
10166
+ availability,
9845
10167
  itemCondition,
9846
10168
  ...shippingDetails.length > 0 ? { shippingDetails } : {},
9847
10169
  ...url ? { url } : {}
@@ -9849,7 +10171,7 @@ function buildProductJsonLd(product, opts) {
9849
10171
  "@type": "Offer",
9850
10172
  price: effectivePrice,
9851
10173
  priceCurrency: opts.currency,
9852
- availability: inStock ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
10174
+ availability,
9853
10175
  itemCondition,
9854
10176
  // Only meaningful for an active sale price with a known end date —
9855
10177
  // a regular (non-sale) price has no expiry to declare.
@@ -9864,19 +10186,25 @@ function buildProductJsonLd(product, opts) {
9864
10186
  ...description ? { description } : {},
9865
10187
  ...images.length > 0 ? { image: images } : {},
9866
10188
  ...url ? { url } : {},
9867
- sku: product.sku,
10189
+ // Fall back to the product id so the offer always carries a stable SKU —
10190
+ // Merchant Center matching and review aggregation both key on it.
10191
+ sku: product.sku || product.id,
9868
10192
  // Identifiers Google uses to match a product to its catalog — key for
9869
10193
  // free merchant-listing eligibility without a Merchant Center feed.
9870
10194
  ...product.gtin ? { gtin: product.gtin } : {},
9871
10195
  ...product.mpn ? { mpn: product.mpn } : {},
9872
10196
  ...brand ? { brand: { "@type": "Brand", name: brand } } : {},
9873
10197
  offers: offer,
9874
- // Google policy: never emit an empty/zero rating block.
10198
+ // Google policy: never emit an empty/zero rating block. bestRating /
10199
+ // worstRating make the 1-5 scale explicit so aggregators can't misread
10200
+ // a 4.8 on an assumed 0-10 scale.
9875
10201
  ...product.reviewCount && product.reviewCount > 0 && product.avgRating ? {
9876
10202
  aggregateRating: {
9877
10203
  "@type": "AggregateRating",
9878
10204
  ratingValue: product.avgRating,
9879
- reviewCount: product.reviewCount
10205
+ reviewCount: product.reviewCount,
10206
+ bestRating: 5,
10207
+ worstRating: 1
9880
10208
  }
9881
10209
  } : {}
9882
10210
  };
@@ -9987,6 +10315,54 @@ async function getBlogSitemapEntries(client, opts) {
9987
10315
  }
9988
10316
  return entries;
9989
10317
  }
10318
+ async function getProductSitemapEntries(client, opts) {
10319
+ const base = opts.siteUrl.replace(/\/+$/, "");
10320
+ const basePath = opts.basePath ?? "/products";
10321
+ const pageSize = Math.min(opts.pageSize ?? 100, 100);
10322
+ const maxEntries = opts.maxEntries ?? 5e3;
10323
+ let rows = [];
10324
+ try {
10325
+ rows = await client.getSitemapProducts(maxEntries);
10326
+ } catch {
10327
+ let page = 1;
10328
+ for (; ; ) {
10329
+ const res = await client.getProducts({ page, limit: pageSize });
10330
+ rows.push(
10331
+ ...res.data.map((p) => ({
10332
+ slug: p.slug ?? null,
10333
+ id: p.id,
10334
+ updatedAt: p.updatedAt,
10335
+ localeSlugs: p.localeSlugs ?? null
10336
+ }))
10337
+ );
10338
+ if (page >= res.meta.totalPages || rows.length >= maxEntries) break;
10339
+ page += 1;
10340
+ }
10341
+ }
10342
+ const nonDefaultLocales = opts.locales?.filter((locale) => locale !== opts.defaultLocale) ?? [];
10343
+ const entries = [];
10344
+ for (const row of rows.slice(0, maxEntries)) {
10345
+ const baseSlug = row.slug || row.id;
10346
+ if (!baseSlug) continue;
10347
+ const lastModified = row.updatedAt ? new Date(row.updatedAt) : void 0;
10348
+ const localeSlugs = row.localeSlugs ?? {};
10349
+ entries.push({
10350
+ url: `${base}${basePath}/${baseSlug}`,
10351
+ ...lastModified ? { lastModified } : {},
10352
+ changeFrequency: "daily",
10353
+ priority: 0.8
10354
+ });
10355
+ for (const locale of nonDefaultLocales) {
10356
+ entries.push({
10357
+ url: `${base}/${locale}${basePath}/${localeSlugs[locale] || baseSlug}`,
10358
+ ...lastModified ? { lastModified } : {},
10359
+ changeFrequency: "daily",
10360
+ priority: 0.7
10361
+ });
10362
+ }
10363
+ }
10364
+ return entries;
10365
+ }
9990
10366
  async function getCategorySitemapEntries(client, opts) {
9991
10367
  const base = opts.siteUrl.replace(/\/+$/, "");
9992
10368
  const basePath = opts.basePath ?? "/category";
@@ -10291,6 +10667,7 @@ export {
10291
10667
  getProductMetafieldsByType,
10292
10668
  getProductPrice,
10293
10669
  getProductPriceInfo,
10670
+ getProductSitemapEntries,
10294
10671
  getProductSwatches,
10295
10672
  getStockStatus,
10296
10673
  getVariantOptions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainerce",
3
- "version": "1.54.0",
3
+ "version": "1.56.0",
4
4
  "description": "Official SDK for building e-commerce storefronts with Brainerce Platform. Perfect for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -28,6 +28,7 @@
28
28
  "lint": "eslint \"src/**/*.ts\"",
29
29
  "test": "vitest run",
30
30
  "test:watch": "vitest",
31
+ "release": "pnpm build && node ../../scripts/publish-workspace-package.js .",
31
32
  "prepublishOnly": "pnpm build"
32
33
  },
33
34
  "keywords": [