@curless/shopify-storefront 0.4.1 → 0.6.2

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/tenant.js CHANGED
@@ -1,4 +1,45 @@
1
1
  import { isStorableShop } from './multi-token-store.js';
2
+ /**
3
+ * How long a request will wait for a catalogue that has NEVER loaded.
4
+ *
5
+ * ⚠️ BOUNDED, AND THAT IS NOT A DETAIL. Nothing between here and Shopify sets a
6
+ * timeout — not the adapter, not the catalogue — so awaiting a refresh outright
7
+ * would hand a buyer's request to whatever Shopify decides to do with it.
8
+ */
9
+ export const COLD_START_WAIT_MS = 5_000;
10
+ /**
11
+ * Wait for a cold catalogue's FIRST load, then answer with it either way.
12
+ *
13
+ * ⚠️ WITHOUT THIS, THE FIRST QUESTION EVER ASKED OF A SHOP IS ANSWERED WRONG.
14
+ * `registry.for` creates the catalogue and starts its first load; reading the
15
+ * snapshot in the same tick gets an empty one, and the buyer is told "the
16
+ * catalog is not loaded yet". Verified in production on 2026-09-10, one minute
17
+ * after a real install: ask once, get the error card; ask again, get 15
18
+ * products. That first question is exactly what a Shopify reviewer asks, and
19
+ * "try again in a moment" is what they would have written the rejection about.
20
+ *
21
+ * `refresh()` JOINS the load already in flight rather than starting a second —
22
+ * so this waits for the fetch that is happening anyway, and a warm catalogue
23
+ * does not wait at all. If the wait runs out, the refresh carries on in the
24
+ * background and the next request finds it done.
25
+ */
26
+ const warm = async (catalog, waitMs) => {
27
+ if (catalog.current.loadedAt !== null)
28
+ return catalog;
29
+ let timer;
30
+ await Promise.race([
31
+ catalog.refresh(),
32
+ new Promise((resolve) => {
33
+ timer = setTimeout(resolve, waitMs);
34
+ // Never hold the process open for a cold-start wait.
35
+ if (typeof timer.unref === 'function')
36
+ timer.unref();
37
+ }),
38
+ ]);
39
+ if (timer)
40
+ clearTimeout(timer);
41
+ return catalog;
42
+ };
2
43
  /**
3
44
  * The branded case. The shop is fixed, so a `shop` on the request is not a
4
45
  * question to answer — it is a claim to CHECK. A request naming a different
@@ -20,7 +61,7 @@ export const singleShopResolver = (deps) => async (requestedShop) => {
20
61
  ok: true,
21
62
  tenant: {
22
63
  shopDomain: configured,
23
- catalog: deps.catalog,
64
+ catalog: await warm(deps.catalog, deps.coldStartWaitMs ?? COLD_START_WAIT_MS),
24
65
  shopify: { shopDomain: configured, adminToken: stored.accessToken },
25
66
  },
26
67
  };
@@ -49,7 +90,7 @@ export const multiShopResolver = (deps) => async (requestedShop) => {
49
90
  ok: true,
50
91
  tenant: {
51
92
  shopDomain: shop,
52
- catalog: deps.registry.for(shop),
93
+ catalog: await warm(deps.registry.for(shop), deps.coldStartWaitMs ?? COLD_START_WAIT_MS),
53
94
  shopify: { shopDomain: shop, adminToken: stored.accessToken },
54
95
  },
55
96
  };
@@ -1 +1 @@
1
- {"version":3,"file":"tenant.js","sourceRoot":"","sources":["../src/tenant.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAgDxD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAC7B,CAAC,IAAoB,EAAE,EAAE,CACzB,KAAK,EAAE,aAAsB,EAAuB,EAAE;IACpD,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9E,IAAI,aAAa,IAAI,aAAa,CAAC,WAAW,EAAE,KAAK,UAAU,EAAE,CAAC;QAChE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;IACjE,CAAC;IACD,uEAAuE;IACvE,+DAA+D;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC;IAC5D,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;IAC9E,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC7E,OAAO;QACL,EAAE,EAAE,IAAI;QACR,MAAM,EAAE;YACN,UAAU,EAAE,UAAU;YACtB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC,WAAW,EAAE;SACpE;KACF,CAAC;AACJ,CAAC,CAAC;AAEJ;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAC5B,CAAC,IAAmB,EAAE,EAAE,CACxB,KAAK,EAAE,aAAsB,EAAuB,EAAE;IACpD,MAAM,IAAI,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxD,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACnD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACjF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;IACjE,OAAO;QACL,EAAE,EAAE,IAAI;QACR,MAAM,EAAE;YACN,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;YAChC,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,WAAW,EAAE;SAC9D;KACF,CAAC;AACJ,CAAC,CAAC;AAEJ,iEAAiE;AACjE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAW,EAAU,EAAE;IACrD,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC;QACjB,KAAK,SAAS;YACZ,OAAO,mFAAmF,CAAC;QAC7F,KAAK,UAAU;YACb,OAAO,GAAG,CAAC,CAAC,KAAK,wCAAwC,CAAC;QAC5D,KAAK,eAAe;YAClB,OAAO,GAAG,CAAC,CAAC,IAAI,kCAAkC,CAAC;IACvD,CAAC;AACH,CAAC,CAAC"}
1
+ {"version":3,"file":"tenant.js","sourceRoot":"","sources":["../src/tenant.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curless/shopify-storefront",
3
- "version": "0.4.1",
3
+ "version": "0.6.2",
4
4
  "type": "module",
5
5
  "description": "One Shopify storefront MCP connector, driven by a config file. Identity, tool names, vocabulary and shop wiring come from a StorefrontConfig, so a second merchant is a JSON file rather than a fork of 3,400 lines.",
6
6
  "keywords": [