@misiki/gocommerce-connector 0.27.0 → 0.29.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/README.md CHANGED
@@ -127,6 +127,7 @@ down stays best-effort: the storefront renders from its static record.
127
127
  | --- | --- |
128
128
  | product page, listings, search | `/api/products`, `/api/products/slug/{slug}` (an id or SKU also works) |
129
129
  | category pages and tree | `/api/categories`, `/api/categories/{slug}` — a category sells its descendants |
130
+ | the filter rail's categories | the counts from `/api/products?facets=1`, each with its ancestors from `/api/categories?flat=1`, in tree order and with its `depth`. A parent counts its whole subtree |
130
131
  | collections | `/api/collections`, `/api/collections/{slug}` |
131
132
  | featured / trending shelves | the collection with slug `featured` / `trending`; newest products when there is none |
132
133
  | product page extras | on the product from `/api/products/slug/{slug}` (D134): `highlights` and `freeGift` from the product's own metadata, `soldAtLeast` when the shop shows sold counts (null otherwise, never zero), and the product's videos after its pictures in `images` (and alone in `videos`), so the gallery plays them |
@@ -241,6 +242,10 @@ Each of these is a real gap, listed so nobody has to find it in production.
241
242
  vendor, product type, category attributes, price) need an engine that counts
242
243
  them (`GET /api/products?facets=1`, D130). Against an older one the filter
243
244
  rail is empty and a category page's attribute filters are not applied.
245
+ - **Category counts in a large shop.** The engine counts a listing's products
246
+ in at most 100 categories, the ones with the most products. The connector adds
247
+ those counts up to each parent, so in a shop with more categories a parent's
248
+ count misses the products of the categories the engine left out.
244
249
  - **Tax in the bag.** The engine's cart carries no tax or total; tax is computed
245
250
  when the order is placed. The bag shows subtotal − discount + delivery, and the
246
251
  order shows the real figures.
@@ -1536,7 +1536,9 @@ declare class BaseService {
1536
1536
  * Prices a cart at the signed-in shopper's customer-group prices. The
1537
1537
  * engine does this only for an account whose email is confirmed (403
1538
1538
  * otherwise), and the cart stays the shopper's either way — so a refusal
1539
- * is not an error, and neither is a build without ext/identity.
1539
+ * is not an error, and neither is a build without ext/identity. A refused
1540
+ * claim still gives the cart the account's email, which the claim would
1541
+ * have set, or the checkout asks the shopper for it again.
1540
1542
  */
1541
1543
  protected claimCartForAccount(cartId: string): Promise<void>;
1542
1544
  /**
@@ -2467,13 +2469,19 @@ declare class ProductService extends CatalogService {
2467
2469
  }
2468
2470
  declare const productService: ProductService;
2469
2471
 
2472
+ type RailCategory = {
2473
+ name: string;
2474
+ slug: string;
2475
+ count: number;
2476
+ depth: number;
2477
+ };
2470
2478
  /**
2471
2479
  * The engine's counts in the shape the storefront's filter rail reads. Each
2472
2480
  * `allFilters` key is also the URL parameter the rail writes when a value is
2473
2481
  * ticked, and `searchWithUrl` reads it back: `vendor`, `product_type` and
2474
2482
  * `attributes.<handle>`. Tags go to the rail's own tag group, written as `tags`.
2475
2483
  */
2476
- declare function toStorefrontFacets(f?: ProductFacets | null): {
2484
+ declare function toStorefrontFacets(f?: ProductFacets | null, tree?: Category[]): {
2477
2485
  priceStat: {
2478
2486
  min: number;
2479
2487
  max: number;
@@ -2481,11 +2489,7 @@ declare function toStorefrontFacets(f?: ProductFacets | null): {
2481
2489
  min?: undefined;
2482
2490
  max?: undefined;
2483
2491
  };
2484
- categories: {
2485
- name: string;
2486
- slug: string;
2487
- count: number;
2488
- }[];
2492
+ categories: RailCategory[];
2489
2493
  tags: {
2490
2494
  name: string;
2491
2495
  count: number;
@@ -2518,11 +2522,7 @@ declare class SearchService extends CatalogService {
2518
2522
  min?: undefined;
2519
2523
  max?: undefined;
2520
2524
  };
2521
- categories: {
2522
- name: string;
2523
- slug: string;
2524
- count: number;
2525
- }[];
2525
+ categories: RailCategory[];
2526
2526
  tags: {
2527
2527
  name: string;
2528
2528
  count: number;
@@ -2546,11 +2546,7 @@ declare class SearchService extends CatalogService {
2546
2546
  min?: undefined;
2547
2547
  max?: undefined;
2548
2548
  };
2549
- categories: {
2550
- name: string;
2551
- slug: string;
2552
- count: number;
2553
- }[];
2549
+ categories: RailCategory[];
2554
2550
  tags: {
2555
2551
  name: string;
2556
2552
  count: number;
@@ -2717,11 +2713,7 @@ declare class SearchService extends CatalogService {
2717
2713
  min?: undefined;
2718
2714
  max?: undefined;
2719
2715
  };
2720
- categories: {
2721
- name: string;
2722
- slug: string;
2723
- count: number;
2724
- }[];
2716
+ categories: RailCategory[];
2725
2717
  tags: {
2726
2718
  name: string;
2727
2719
  count: number;
@@ -1536,7 +1536,9 @@ declare class BaseService {
1536
1536
  * Prices a cart at the signed-in shopper's customer-group prices. The
1537
1537
  * engine does this only for an account whose email is confirmed (403
1538
1538
  * otherwise), and the cart stays the shopper's either way — so a refusal
1539
- * is not an error, and neither is a build without ext/identity.
1539
+ * is not an error, and neither is a build without ext/identity. A refused
1540
+ * claim still gives the cart the account's email, which the claim would
1541
+ * have set, or the checkout asks the shopper for it again.
1540
1542
  */
1541
1543
  protected claimCartForAccount(cartId: string): Promise<void>;
1542
1544
  /**
@@ -2467,13 +2469,19 @@ declare class ProductService extends CatalogService {
2467
2469
  }
2468
2470
  declare const productService: ProductService;
2469
2471
 
2472
+ type RailCategory = {
2473
+ name: string;
2474
+ slug: string;
2475
+ count: number;
2476
+ depth: number;
2477
+ };
2470
2478
  /**
2471
2479
  * The engine's counts in the shape the storefront's filter rail reads. Each
2472
2480
  * `allFilters` key is also the URL parameter the rail writes when a value is
2473
2481
  * ticked, and `searchWithUrl` reads it back: `vendor`, `product_type` and
2474
2482
  * `attributes.<handle>`. Tags go to the rail's own tag group, written as `tags`.
2475
2483
  */
2476
- declare function toStorefrontFacets(f?: ProductFacets | null): {
2484
+ declare function toStorefrontFacets(f?: ProductFacets | null, tree?: Category[]): {
2477
2485
  priceStat: {
2478
2486
  min: number;
2479
2487
  max: number;
@@ -2481,11 +2489,7 @@ declare function toStorefrontFacets(f?: ProductFacets | null): {
2481
2489
  min?: undefined;
2482
2490
  max?: undefined;
2483
2491
  };
2484
- categories: {
2485
- name: string;
2486
- slug: string;
2487
- count: number;
2488
- }[];
2492
+ categories: RailCategory[];
2489
2493
  tags: {
2490
2494
  name: string;
2491
2495
  count: number;
@@ -2518,11 +2522,7 @@ declare class SearchService extends CatalogService {
2518
2522
  min?: undefined;
2519
2523
  max?: undefined;
2520
2524
  };
2521
- categories: {
2522
- name: string;
2523
- slug: string;
2524
- count: number;
2525
- }[];
2525
+ categories: RailCategory[];
2526
2526
  tags: {
2527
2527
  name: string;
2528
2528
  count: number;
@@ -2546,11 +2546,7 @@ declare class SearchService extends CatalogService {
2546
2546
  min?: undefined;
2547
2547
  max?: undefined;
2548
2548
  };
2549
- categories: {
2550
- name: string;
2551
- slug: string;
2552
- count: number;
2553
- }[];
2549
+ categories: RailCategory[];
2554
2550
  tags: {
2555
2551
  name: string;
2556
2552
  count: number;
@@ -2717,11 +2713,7 @@ declare class SearchService extends CatalogService {
2717
2713
  min?: undefined;
2718
2714
  max?: undefined;
2719
2715
  };
2720
- categories: {
2721
- name: string;
2722
- slug: string;
2723
- count: number;
2724
- }[];
2716
+ categories: RailCategory[];
2725
2717
  tags: {
2726
2718
  name: string;
2727
2719
  count: number;
package/dist/index.cjs CHANGED
@@ -440,12 +440,19 @@ var BaseService = class _BaseService {
440
440
  * Prices a cart at the signed-in shopper's customer-group prices. The
441
441
  * engine does this only for an account whose email is confirmed (403
442
442
  * otherwise), and the cart stays the shopper's either way — so a refusal
443
- * is not an error, and neither is a build without ext/identity.
443
+ * is not an error, and neither is a build without ext/identity. A refused
444
+ * claim still gives the cart the account's email, which the claim would
445
+ * have set, or the checkout asks the shopper for it again.
444
446
  */
445
447
  async claimCartForAccount(cartId) {
446
448
  try {
447
449
  await this.engine("/x/identity/me/carts", { method: "POST", body: { cart_id: cartId }, auth: true });
448
450
  } catch {
451
+ const email = currentMe()?.email;
452
+ if (email)
453
+ await this.engine(`/api/carts/${encodeURIComponent(cartId)}/email`, { method: "PUT", body: { email } }).catch(
454
+ () => void 0
455
+ );
449
456
  }
450
457
  }
451
458
  // ------------------------------------------------------------ Litekart verbs
@@ -1586,7 +1593,23 @@ function attributesFrom(p) {
1586
1593
  return Object.keys(out).length ? out : void 0;
1587
1594
  }
1588
1595
  var counts = (values) => Object.fromEntries((values ?? []).map((v) => [v.value, v.count]));
1589
- function toStorefrontFacets(f) {
1596
+ function categoryFacets(counted, tree) {
1597
+ const byId = new Map(tree.map((c) => [c.id, c]));
1598
+ const totals = /* @__PURE__ */ new Map();
1599
+ for (const { id: id2, count } of counted) {
1600
+ for (let c = byId.get(id2); c; c = c.parent_id === null ? void 0 : byId.get(c.parent_id)) {
1601
+ totals.set(c.id, (totals.get(c.id) ?? 0) + count);
1602
+ }
1603
+ }
1604
+ const kept = tree.filter((c) => totals.has(c.id)).sort((a, b) => a.position - b.position || a.id - b.id);
1605
+ const parentOf = (c) => c.parent_id !== null && byId.has(c.parent_id) ? c.parent_id : null;
1606
+ const under = (parent) => kept.filter((c) => parentOf(c) === parent).flatMap((c) => [{ name: c.title, slug: c.slug, count: totals.get(c.id), depth: c.depth }, ...under(c.id)]);
1607
+ return [
1608
+ ...under(null),
1609
+ ...counted.filter((c) => !byId.has(c.id)).map((c) => ({ name: c.title, slug: c.slug, count: c.count, depth: 0 }))
1610
+ ];
1611
+ }
1612
+ function toStorefrontFacets(f, tree = []) {
1590
1613
  if (!f) return { priceStat: {}, categories: [], tags: [], allFilters: {} };
1591
1614
  const allFilters = {};
1592
1615
  const group = (key, values) => {
@@ -1597,7 +1620,7 @@ function toStorefrontFacets(f) {
1597
1620
  for (const [handle, values] of Object.entries(f.attributes ?? {})) group(`attributes.${handle}`, values);
1598
1621
  return {
1599
1622
  priceStat: f.price ? { min: toMajor(f.price.min), max: toMajor(f.price.max) } : {},
1600
- categories: (f.categories ?? []).map((c) => ({ name: c.title, slug: c.slug, count: c.count })),
1623
+ categories: categoryFacets(f.categories ?? [], tree),
1601
1624
  tags: (f.tags ?? []).map((t) => ({ name: t.value, count: t.count })),
1602
1625
  allFilters
1603
1626
  };
@@ -1630,13 +1653,14 @@ var SearchService = class extends CatalogService {
1630
1653
  productTypes: listParam(p, "product_type"),
1631
1654
  facets: true
1632
1655
  });
1656
+ const tree = res.facets?.categories?.length ? await this.facts.categories().catch(() => []) : [];
1633
1657
  return {
1634
1658
  data: res.data,
1635
1659
  count: res.count,
1636
1660
  totalPages: res.noOfPage,
1637
1661
  page: res.page,
1638
1662
  categoryHierarchy: res.categoryHierarchy,
1639
- facets: toStorefrontFacets(res.facets)
1663
+ facets: toStorefrontFacets(res.facets, tree)
1640
1664
  };
1641
1665
  } catch (err) {
1642
1666
  if (err?.status === 404) return this.emptyResult();