@shopify/cli-kit 3.65.3 → 3.66.1

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.
Files changed (66) hide show
  1. package/dist/private/node/api.d.ts +10 -2
  2. package/dist/private/node/api.js +151 -14
  3. package/dist/private/node/api.js.map +1 -1
  4. package/dist/private/node/conf-store.d.ts +27 -1
  5. package/dist/private/node/conf-store.js +39 -1
  6. package/dist/private/node/conf-store.js.map +1 -1
  7. package/dist/private/node/ui/components/SelectInput.js +1 -1
  8. package/dist/private/node/ui/components/SelectInput.js.map +1 -1
  9. package/dist/public/common/string.d.ts +7 -0
  10. package/dist/public/common/string.js +11 -0
  11. package/dist/public/common/string.js.map +1 -1
  12. package/dist/public/common/version.d.ts +1 -1
  13. package/dist/public/common/version.js +1 -1
  14. package/dist/public/common/version.js.map +1 -1
  15. package/dist/public/node/api/app-dev.js +3 -1
  16. package/dist/public/node/api/app-dev.js.map +1 -1
  17. package/dist/public/node/api/business-platform.d.ts +11 -1
  18. package/dist/public/node/api/business-platform.js +35 -8
  19. package/dist/public/node/api/business-platform.js.map +1 -1
  20. package/dist/public/node/api/graphql.js +2 -2
  21. package/dist/public/node/api/graphql.js.map +1 -1
  22. package/dist/public/node/api/rest-api-throttler.d.ts +23 -0
  23. package/dist/public/node/api/rest-api-throttler.js +239 -0
  24. package/dist/public/node/api/rest-api-throttler.js.map +1 -0
  25. package/dist/public/node/fs.d.ts +5 -0
  26. package/dist/public/node/fs.js +8 -1
  27. package/dist/public/node/fs.js.map +1 -1
  28. package/dist/public/node/hooks/prerun.d.ts +4 -0
  29. package/dist/public/node/hooks/prerun.js +27 -1
  30. package/dist/public/node/hooks/prerun.js.map +1 -1
  31. package/dist/public/node/http.d.ts +3 -3
  32. package/dist/public/node/http.js +6 -3
  33. package/dist/public/node/http.js.map +1 -1
  34. package/dist/public/node/ink.d.ts +1 -1
  35. package/dist/public/node/ink.js +1 -1
  36. package/dist/public/node/ink.js.map +1 -1
  37. package/dist/public/node/node-package-manager.d.ts +8 -1
  38. package/dist/public/node/node-package-manager.js +19 -3
  39. package/dist/public/node/node-package-manager.js.map +1 -1
  40. package/dist/public/node/output.d.ts +0 -8
  41. package/dist/public/node/output.js +0 -14
  42. package/dist/public/node/output.js.map +1 -1
  43. package/dist/public/node/themes/api.d.ts +1 -1
  44. package/dist/public/node/themes/api.js +16 -8
  45. package/dist/public/node/themes/api.js.map +1 -1
  46. package/dist/public/node/themes/theme-manager.d.ts +1 -0
  47. package/dist/public/node/themes/theme-manager.js +3 -0
  48. package/dist/public/node/themes/theme-manager.js.map +1 -1
  49. package/dist/public/node/themes/types.d.ts +4 -0
  50. package/dist/public/node/themes/types.js.map +1 -1
  51. package/dist/public/node/ui.d.ts +2 -2
  52. package/dist/public/node/ui.js.map +1 -1
  53. package/dist/public/node/upgrade.d.ts +14 -0
  54. package/dist/public/node/upgrade.js +38 -0
  55. package/dist/public/node/upgrade.js.map +1 -0
  56. package/dist/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +2 -3
  58. package/dist/private/node/themes/themes-api/headers.d.ts +0 -3
  59. package/dist/private/node/themes/themes-api/headers.js +0 -29
  60. package/dist/private/node/themes/themes-api/headers.js.map +0 -1
  61. package/dist/private/node/themes/themes-api/retry.d.ts +0 -1
  62. package/dist/private/node/themes/themes-api/retry.js +0 -6
  63. package/dist/private/node/themes/themes-api/retry.js.map +0 -1
  64. package/dist/private/node/themes/themes-api/throttler.d.ts +0 -2
  65. package/dist/private/node/themes/themes-api/throttler.js +0 -82
  66. package/dist/private/node/themes/themes-api/throttler.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { graphqlRequestDoc } from './graphql.js';
2
+ import { normalizeStoreFqdn } from '../context/fqdn.js';
2
3
  import Bottleneck from 'bottleneck';
3
4
  // API Rate limiter
4
5
  // Jobs are launched every 150ms
@@ -19,7 +20,8 @@ const limiter = new Bottleneck({
19
20
  */
20
21
  export async function appDevRequest(query, shopFqdn, token, variables) {
21
22
  const api = 'App Dev';
22
- const url = `https://${shopFqdn}/app_dev/unstable/graphql.json`;
23
+ const normalizedShopFqdn = await normalizeStoreFqdn(shopFqdn);
24
+ const url = `https://${normalizedShopFqdn}/app_dev/unstable/graphql.json`;
23
25
  const result = limiter.schedule(() => graphqlRequestDoc({
24
26
  query,
25
27
  api,
@@ -1 +1 @@
1
- {"version":3,"file":"app-dev.js","sourceRoot":"","sources":["../../../../src/public/node/api/app-dev.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAC,MAAM,cAAc,CAAA;AAC9C,OAAO,UAAU,MAAM,YAAY,CAAA;AAInC,mBAAmB;AACnB,gCAAgC;AAChC,iDAAiD;AACjD,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC;IAC7B,OAAO,EAAE,GAAG;IACZ,aAAa,EAAE,EAAE;CAClB,CAAC,CAAA;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAA6C,EAC7C,QAAgB,EAChB,KAAa,EACb,SAAsB;IAEtB,MAAM,GAAG,GAAG,SAAS,CAAA;IACrB,MAAM,GAAG,GAAG,WAAW,QAAQ,gCAAgC,CAAA;IAC/D,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAU,GAAG,EAAE,CAC5C,iBAAiB,CAAsB;QACrC,KAAK;QACL,GAAG;QACH,GAAG;QACH,KAAK;QACL,SAAS;KACV,CAAC,CACH,CAAA;IAED,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import {graphqlRequestDoc} from './graphql.js'\nimport Bottleneck from 'bottleneck'\nimport {Variables} from 'graphql-request'\nimport {TypedDocumentNode} from '@graphql-typed-document-node/core'\n\n// API Rate limiter\n// Jobs are launched every 150ms\n// Only 10 requests can be executed concurrently.\nconst limiter = new Bottleneck({\n minTime: 150,\n maxConcurrent: 10,\n})\n\n/**\n * Executes an org-scoped GraphQL query against the App Management API.\n * Uses typed documents.\n *\n * @param query - GraphQL query to execute.\n * @param shopFqdn - The shop fqdn.\n * @param token - Partners token.\n * @param variables - GraphQL variables to pass to the query.\n * @returns The response of the query of generic type <T>.\n */\nexport async function appDevRequest<TResult, TVariables extends Variables>(\n query: TypedDocumentNode<TResult, TVariables>,\n shopFqdn: string,\n token: string,\n variables?: TVariables,\n): Promise<TResult> {\n const api = 'App Dev'\n const url = `https://${shopFqdn}/app_dev/unstable/graphql.json`\n const result = limiter.schedule<TResult>(() =>\n graphqlRequestDoc<TResult, TVariables>({\n query,\n api,\n url,\n token,\n variables,\n }),\n )\n\n return result\n}\n"]}
1
+ {"version":3,"file":"app-dev.js","sourceRoot":"","sources":["../../../../src/public/node/api/app-dev.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAC,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAA;AACrD,OAAO,UAAU,MAAM,YAAY,CAAA;AAInC,mBAAmB;AACnB,gCAAgC;AAChC,iDAAiD;AACjD,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC;IAC7B,OAAO,EAAE,GAAG;IACZ,aAAa,EAAE,EAAE;CAClB,CAAC,CAAA;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAA6C,EAC7C,QAAgB,EAChB,KAAa,EACb,SAAsB;IAEtB,MAAM,GAAG,GAAG,SAAS,CAAA;IACrB,MAAM,kBAAkB,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAA;IAC7D,MAAM,GAAG,GAAG,WAAW,kBAAkB,gCAAgC,CAAA;IACzE,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAU,GAAG,EAAE,CAC5C,iBAAiB,CAAsB;QACrC,KAAK;QACL,GAAG;QACH,GAAG;QACH,KAAK;QACL,SAAS;KACV,CAAC,CACH,CAAA;IAED,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import {graphqlRequestDoc} from './graphql.js'\nimport {normalizeStoreFqdn} from '../context/fqdn.js'\nimport Bottleneck from 'bottleneck'\nimport {Variables} from 'graphql-request'\nimport {TypedDocumentNode} from '@graphql-typed-document-node/core'\n\n// API Rate limiter\n// Jobs are launched every 150ms\n// Only 10 requests can be executed concurrently.\nconst limiter = new Bottleneck({\n minTime: 150,\n maxConcurrent: 10,\n})\n\n/**\n * Executes an org-scoped GraphQL query against the App Management API.\n * Uses typed documents.\n *\n * @param query - GraphQL query to execute.\n * @param shopFqdn - The shop fqdn.\n * @param token - Partners token.\n * @param variables - GraphQL variables to pass to the query.\n * @returns The response of the query of generic type <T>.\n */\nexport async function appDevRequest<TResult, TVariables extends Variables>(\n query: TypedDocumentNode<TResult, TVariables>,\n shopFqdn: string,\n token: string,\n variables?: TVariables,\n): Promise<TResult> {\n const api = 'App Dev'\n const normalizedShopFqdn = await normalizeStoreFqdn(shopFqdn)\n const url = `https://${normalizedShopFqdn}/app_dev/unstable/graphql.json`\n const result = limiter.schedule<TResult>(() =>\n graphqlRequestDoc<TResult, TVariables>({\n query,\n api,\n url,\n token,\n variables,\n }),\n )\n\n return result\n}\n"]}
@@ -24,10 +24,20 @@ export declare function businessPlatformRequestDoc<TResult, TVariables extends V
24
24
  *
25
25
  * @param query - GraphQL query to execute.
26
26
  * @param token - Business Platform token.
27
+ * @param organizationId - Organization ID as a numeric (non-GID) value.
28
+ * @param variables - GraphQL variables to pass to the query.
29
+ * @returns The response of the query of generic type <T>.
30
+ */
31
+ export declare function businessPlatformOrganizationsRequest<T>(query: string, token: string, organizationId: string, variables?: GraphQLVariables): Promise<T>;
32
+ /**
33
+ * Executes a GraphQL query against the Business Platform Organizations API. Uses typed documents.
34
+ *
35
+ * @param query - GraphQL query to execute.
36
+ * @param token - Business Platform token.
27
37
  * @param organizationId - Organization ID as a numeric value.
28
38
  * @param variables - GraphQL variables to pass to the query.
29
39
  * @returns The response of the query of generic type <T>.
30
40
  */
31
- export declare function businessPlatformOrganizationsRequest<TResult>(query: TypedDocumentNode<TResult, GraphQLVariables> | TypedDocumentNode<TResult, Exact<{
41
+ export declare function businessPlatformOrganizationsRequestDoc<TResult>(query: TypedDocumentNode<TResult, GraphQLVariables> | TypedDocumentNode<TResult, Exact<{
32
42
  [key: string]: never;
33
43
  }>>, token: string, organizationId: string, variables?: GraphQLVariables): Promise<TResult>;
@@ -47,26 +47,53 @@ export async function businessPlatformRequestDoc(query, token, variables) {
47
47
  variables,
48
48
  });
49
49
  }
50
+ /**
51
+ * Sets up the request to the Business Platform Organizations API.
52
+ *
53
+ * @param token - Business Platform token.
54
+ * @param organizationId - Organization ID as a numeric (non-GID) value.
55
+ */
56
+ async function setupOrganizationsRequest(token, organizationId) {
57
+ const api = 'BusinessPlatform';
58
+ const fqdn = await businessPlatformFqdn();
59
+ const url = `https://${fqdn}/organizations/api/unstable/organization/${organizationId}/graphql`;
60
+ return {
61
+ token,
62
+ api,
63
+ url,
64
+ responseOptions: { onResponse: handleDeprecations },
65
+ };
66
+ }
50
67
  /**
51
68
  * Executes a GraphQL query against the Business Platform Organizations API.
52
69
  *
53
70
  * @param query - GraphQL query to execute.
54
71
  * @param token - Business Platform token.
55
- * @param organizationId - Organization ID as a numeric value.
72
+ * @param organizationId - Organization ID as a numeric (non-GID) value.
56
73
  * @param variables - GraphQL variables to pass to the query.
57
74
  * @returns The response of the query of generic type <T>.
58
75
  */
59
76
  export async function businessPlatformOrganizationsRequest(query, token, organizationId, variables) {
60
- const api = 'BusinessPlatform';
61
- const fqdn = await businessPlatformFqdn();
62
- const url = `https://${fqdn}/organizations/api/unstable/organization/${organizationId}/graphql`;
77
+ return graphqlRequest({
78
+ query,
79
+ ...(await setupOrganizationsRequest(token, organizationId)),
80
+ variables,
81
+ });
82
+ }
83
+ /**
84
+ * Executes a GraphQL query against the Business Platform Organizations API. Uses typed documents.
85
+ *
86
+ * @param query - GraphQL query to execute.
87
+ * @param token - Business Platform token.
88
+ * @param organizationId - Organization ID as a numeric value.
89
+ * @param variables - GraphQL variables to pass to the query.
90
+ * @returns The response of the query of generic type <T>.
91
+ */
92
+ export async function businessPlatformOrganizationsRequestDoc(query, token, organizationId, variables) {
63
93
  return graphqlRequestDoc({
64
94
  query,
65
- api,
66
- url,
67
- token,
95
+ ...(await setupOrganizationsRequest(token, organizationId)),
68
96
  variables,
69
- responseOptions: { onResponse: handleDeprecations },
70
97
  });
71
98
  }
72
99
  //# sourceMappingURL=business-platform.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"business-platform.js","sourceRoot":"","sources":["../../../../src/public/node/api/business-platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,cAAc,EAAE,iBAAiB,EAAC,MAAM,cAAc,CAAA;AACvF,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAA;AAChD,OAAO,EAAC,oBAAoB,EAAC,MAAM,oBAAoB,CAAA;AAIvD;;;;GAIG;AACH,KAAK,UAAU,YAAY,CAAC,KAAa;IACvC,MAAM,GAAG,GAAG,kBAAkB,CAAA;IAC9B,MAAM,IAAI,GAAG,MAAM,oBAAoB,EAAE,CAAA;IACzC,MAAM,GAAG,GAAG,WAAW,IAAI,mCAAmC,CAAA;IAC9D,OAAO;QACL,KAAK;QACL,GAAG;QACH,GAAG;QACH,eAAe,EAAE,EAAC,UAAU,EAAE,kBAAkB,EAAC;KAClD,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,KAAa,EACb,KAAa,EACb,SAA4B;IAE5B,OAAO,cAAc,CAAI;QACvB,GAAG,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;QAC9B,KAAK;QACL,SAAS;KACV,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,KAA6C,EAC7C,KAAa,EACb,SAAsB;IAEtB,OAAO,iBAAiB,CAAsB;QAC5C,GAAG,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;QAC9B,KAAK;QACL,SAAS;KACV,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,oCAAoC,CACxD,KAA+G,EAC/G,KAAa,EACb,cAAsB,EACtB,SAA4B;IAE5B,MAAM,GAAG,GAAG,kBAAkB,CAAA;IAC9B,MAAM,IAAI,GAAG,MAAM,oBAAoB,EAAE,CAAA;IACzC,MAAM,GAAG,GAAG,WAAW,IAAI,4CAA4C,cAAc,UAAU,CAAA;IAC/F,OAAO,iBAAiB,CAAC;QACvB,KAAK;QACL,GAAG;QACH,GAAG;QACH,KAAK;QACL,SAAS;QACT,eAAe,EAAE,EAAC,UAAU,EAAE,kBAAkB,EAAC;KAClD,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import {Exact, GraphQLVariables, graphqlRequest, graphqlRequestDoc} from './graphql.js'\nimport {handleDeprecations} from './partners.js'\nimport {businessPlatformFqdn} from '../context/fqdn.js'\nimport {TypedDocumentNode} from '@graphql-typed-document-node/core'\nimport {Variables} from 'graphql-request'\n\n/**\n * Sets up the request to the Business Platform Destinations API.\n *\n * @param token - Business Platform token.\n */\nasync function setupRequest(token: string) {\n const api = 'BusinessPlatform'\n const fqdn = await businessPlatformFqdn()\n const url = `https://${fqdn}/destinations/api/2020-07/graphql`\n return {\n token,\n api,\n url,\n responseOptions: {onResponse: handleDeprecations},\n }\n}\n\n/**\n * Executes a GraphQL query against the Business Platform Destinations API.\n *\n * @param query - GraphQL query to execute.\n * @param token - Business Platform token.\n * @param variables - GraphQL variables to pass to the query.\n * @returns The response of the query of generic type <T>.\n */\nexport async function businessPlatformRequest<T>(\n query: string,\n token: string,\n variables?: GraphQLVariables,\n): Promise<T> {\n return graphqlRequest<T>({\n ...(await setupRequest(token)),\n query,\n variables,\n })\n}\n\n/**\n * Executes a GraphQL query against the Business Platform Destinations API. Uses typed documents.\n *\n * @param query - GraphQL query to execute.\n * @param token - Business Platform token.\n * @param variables - GraphQL variables to pass to the query.\n * @returns The response of the query of generic type <TResult>.\n */\nexport async function businessPlatformRequestDoc<TResult, TVariables extends Variables>(\n query: TypedDocumentNode<TResult, TVariables>,\n token: string,\n variables?: TVariables,\n): Promise<TResult> {\n return graphqlRequestDoc<TResult, TVariables>({\n ...(await setupRequest(token)),\n query,\n variables,\n })\n}\n\n/**\n * Executes a GraphQL query against the Business Platform Organizations API.\n *\n * @param query - GraphQL query to execute.\n * @param token - Business Platform token.\n * @param organizationId - Organization ID as a numeric value.\n * @param variables - GraphQL variables to pass to the query.\n * @returns The response of the query of generic type <T>.\n */\nexport async function businessPlatformOrganizationsRequest<TResult>(\n query: TypedDocumentNode<TResult, GraphQLVariables> | TypedDocumentNode<TResult, Exact<{[key: string]: never}>>,\n token: string,\n organizationId: string,\n variables?: GraphQLVariables,\n): Promise<TResult> {\n const api = 'BusinessPlatform'\n const fqdn = await businessPlatformFqdn()\n const url = `https://${fqdn}/organizations/api/unstable/organization/${organizationId}/graphql`\n return graphqlRequestDoc({\n query,\n api,\n url,\n token,\n variables,\n responseOptions: {onResponse: handleDeprecations},\n })\n}\n"]}
1
+ {"version":3,"file":"business-platform.js","sourceRoot":"","sources":["../../../../src/public/node/api/business-platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,cAAc,EAAE,iBAAiB,EAAC,MAAM,cAAc,CAAA;AACvF,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAA;AAChD,OAAO,EAAC,oBAAoB,EAAC,MAAM,oBAAoB,CAAA;AAIvD;;;;GAIG;AACH,KAAK,UAAU,YAAY,CAAC,KAAa;IACvC,MAAM,GAAG,GAAG,kBAAkB,CAAA;IAC9B,MAAM,IAAI,GAAG,MAAM,oBAAoB,EAAE,CAAA;IACzC,MAAM,GAAG,GAAG,WAAW,IAAI,mCAAmC,CAAA;IAC9D,OAAO;QACL,KAAK;QACL,GAAG;QACH,GAAG;QACH,eAAe,EAAE,EAAC,UAAU,EAAE,kBAAkB,EAAC;KAClD,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,KAAa,EACb,KAAa,EACb,SAA4B;IAE5B,OAAO,cAAc,CAAI;QACvB,GAAG,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;QAC9B,KAAK;QACL,SAAS;KACV,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,KAA6C,EAC7C,KAAa,EACb,SAAsB;IAEtB,OAAO,iBAAiB,CAAsB;QAC5C,GAAG,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;QAC9B,KAAK;QACL,SAAS;KACV,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,yBAAyB,CAAC,KAAa,EAAE,cAAsB;IAC5E,MAAM,GAAG,GAAG,kBAAkB,CAAA;IAC9B,MAAM,IAAI,GAAG,MAAM,oBAAoB,EAAE,CAAA;IACzC,MAAM,GAAG,GAAG,WAAW,IAAI,4CAA4C,cAAc,UAAU,CAAA;IAC/F,OAAO;QACL,KAAK;QACL,GAAG;QACH,GAAG;QACH,eAAe,EAAE,EAAC,UAAU,EAAE,kBAAkB,EAAC;KAClD,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,oCAAoC,CACxD,KAAa,EACb,KAAa,EACb,cAAsB,EACtB,SAA4B;IAE5B,OAAO,cAAc,CAAI;QACvB,KAAK;QACL,GAAG,CAAC,MAAM,yBAAyB,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;QAC3D,SAAS;KACV,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,uCAAuC,CAC3D,KAA+G,EAC/G,KAAa,EACb,cAAsB,EACtB,SAA4B;IAE5B,OAAO,iBAAiB,CAAC;QACvB,KAAK;QACL,GAAG,CAAC,MAAM,yBAAyB,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;QAC3D,SAAS;KACV,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import {Exact, GraphQLVariables, graphqlRequest, graphqlRequestDoc} from './graphql.js'\nimport {handleDeprecations} from './partners.js'\nimport {businessPlatformFqdn} from '../context/fqdn.js'\nimport {TypedDocumentNode} from '@graphql-typed-document-node/core'\nimport {Variables} from 'graphql-request'\n\n/**\n * Sets up the request to the Business Platform Destinations API.\n *\n * @param token - Business Platform token.\n */\nasync function setupRequest(token: string) {\n const api = 'BusinessPlatform'\n const fqdn = await businessPlatformFqdn()\n const url = `https://${fqdn}/destinations/api/2020-07/graphql`\n return {\n token,\n api,\n url,\n responseOptions: {onResponse: handleDeprecations},\n }\n}\n\n/**\n * Executes a GraphQL query against the Business Platform Destinations API.\n *\n * @param query - GraphQL query to execute.\n * @param token - Business Platform token.\n * @param variables - GraphQL variables to pass to the query.\n * @returns The response of the query of generic type <T>.\n */\nexport async function businessPlatformRequest<T>(\n query: string,\n token: string,\n variables?: GraphQLVariables,\n): Promise<T> {\n return graphqlRequest<T>({\n ...(await setupRequest(token)),\n query,\n variables,\n })\n}\n\n/**\n * Executes a GraphQL query against the Business Platform Destinations API. Uses typed documents.\n *\n * @param query - GraphQL query to execute.\n * @param token - Business Platform token.\n * @param variables - GraphQL variables to pass to the query.\n * @returns The response of the query of generic type <TResult>.\n */\nexport async function businessPlatformRequestDoc<TResult, TVariables extends Variables>(\n query: TypedDocumentNode<TResult, TVariables>,\n token: string,\n variables?: TVariables,\n): Promise<TResult> {\n return graphqlRequestDoc<TResult, TVariables>({\n ...(await setupRequest(token)),\n query,\n variables,\n })\n}\n\n/**\n * Sets up the request to the Business Platform Organizations API.\n *\n * @param token - Business Platform token.\n * @param organizationId - Organization ID as a numeric (non-GID) value.\n */\nasync function setupOrganizationsRequest(token: string, organizationId: string) {\n const api = 'BusinessPlatform'\n const fqdn = await businessPlatformFqdn()\n const url = `https://${fqdn}/organizations/api/unstable/organization/${organizationId}/graphql`\n return {\n token,\n api,\n url,\n responseOptions: {onResponse: handleDeprecations},\n }\n}\n\n/**\n * Executes a GraphQL query against the Business Platform Organizations API.\n *\n * @param query - GraphQL query to execute.\n * @param token - Business Platform token.\n * @param organizationId - Organization ID as a numeric (non-GID) value.\n * @param variables - GraphQL variables to pass to the query.\n * @returns The response of the query of generic type <T>.\n */\nexport async function businessPlatformOrganizationsRequest<T>(\n query: string,\n token: string,\n organizationId: string,\n variables?: GraphQLVariables,\n): Promise<T> {\n return graphqlRequest<T>({\n query,\n ...(await setupOrganizationsRequest(token, organizationId)),\n variables,\n })\n}\n\n/**\n * Executes a GraphQL query against the Business Platform Organizations API. Uses typed documents.\n *\n * @param query - GraphQL query to execute.\n * @param token - Business Platform token.\n * @param organizationId - Organization ID as a numeric value.\n * @param variables - GraphQL variables to pass to the query.\n * @returns The response of the query of generic type <T>.\n */\nexport async function businessPlatformOrganizationsRequestDoc<TResult>(\n query: TypedDocumentNode<TResult, GraphQLVariables> | TypedDocumentNode<TResult, Exact<{[key: string]: never}>>,\n token: string,\n organizationId: string,\n variables?: GraphQLVariables,\n): Promise<TResult> {\n return graphqlRequestDoc({\n query,\n ...(await setupOrganizationsRequest(token, organizationId)),\n variables,\n })\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { buildHeaders, httpsAgent } from '../../../private/node/api/headers.js';
2
2
  import { debugLogRequestInfo, errorHandler } from '../../../private/node/api/graphql.js';
3
- import { debugLogResponseInfo } from '../../../private/node/api.js';
4
3
  import { runWithTimer } from '../metadata.js';
4
+ import { retryAwareRequest } from '../../../private/node/api.js';
5
5
  import { GraphQLClient, resolveRequestDocument } from 'graphql-request';
6
6
  /**
7
7
  * Handles execution of a GraphQL query.
@@ -18,7 +18,7 @@ async function performGraphQLRequest(options) {
18
18
  const clientOptions = { agent: await httpsAgent(), headers };
19
19
  const client = new GraphQLClient(url, clientOptions);
20
20
  return runWithTimer('cmd_all_timing_network_ms')(async () => {
21
- const response = await debugLogResponseInfo({ request: client.rawRequest(queryAsString, variables), url }, responseOptions?.handleErrors === false ? undefined : errorHandler(api));
21
+ const response = await retryAwareRequest({ request: () => client.rawRequest(queryAsString, variables), url }, responseOptions?.handleErrors === false ? undefined : errorHandler(api));
22
22
  if (responseOptions?.onResponse) {
23
23
  responseOptions.onResponse(response);
24
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"graphql.js","sourceRoot":"","sources":["../../../../src/public/node/api/graphql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,UAAU,EAAC,MAAM,sCAAsC,CAAA;AAC7E,OAAO,EAAC,mBAAmB,EAAE,YAAY,EAAC,MAAM,sCAAsC,CAAA;AACtF,OAAO,EAAC,oBAAoB,EAAC,MAAM,8BAA8B,CAAA;AACjE,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,aAAa,EAA+B,sBAAsB,EAAY,MAAM,iBAAiB,CAAA;AAyC7G;;;;GAIG;AACH,KAAK,UAAU,qBAAqB,CAAU,OAA8C;IAC1F,MAAM,EAAC,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,eAAe,EAAC,GAAG,OAAO,CAAA;IAC1F,MAAM,OAAO,GAAG;QACd,GAAG,YAAY;QACf,GAAG,YAAY,CAAC,KAAK,CAAC;KACvB,CAAA;IAED,mBAAmB,CAAC,GAAG,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,CAAC,CAAA;IAC3D,MAAM,aAAa,GAAG,EAAC,KAAK,EAAE,MAAM,UAAU,EAAE,EAAE,OAAO,EAAC,CAAA;IAC1D,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;IAEpD,OAAO,YAAY,CAAC,2BAA2B,CAAC,CAAC,KAAK,IAAI,EAAE;QAC1D,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CACzC,EAAC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAU,aAAa,EAAE,SAAS,CAAC,EAAE,GAAG,EAAC,EACpE,eAAe,EAAE,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CACxE,CAAA;QAED,IAAI,eAAe,EAAE,UAAU,EAAE;YAC/B,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;SACrC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAA;IACtB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAI,OAAiC;IACvE,OAAO,qBAAqB,CAAI;QAC9B,GAAG,OAAO;QACV,aAAa,EAAE,OAAO,CAAC,KAAe;KACvC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAsD;IAEtD,OAAO,qBAAqB,CAAU;QACpC,GAAG,OAAO;QACV,aAAa,EAAE,sBAAsB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK;KAC3D,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import {buildHeaders, httpsAgent} from '../../../private/node/api/headers.js'\nimport {debugLogRequestInfo, errorHandler} from '../../../private/node/api/graphql.js'\nimport {debugLogResponseInfo} from '../../../private/node/api.js'\nimport {runWithTimer} from '../metadata.js'\nimport {GraphQLClient, rawRequest, RequestDocument, resolveRequestDocument, Variables} from 'graphql-request'\nimport {TypedDocumentNode} from '@graphql-typed-document-node/core'\n\n// to replace TVariable type when there graphql query has no variables\nexport type Exact<T extends {[key: string]: unknown}> = {[K in keyof T]: T[K]}\n\nexport interface GraphQLVariables {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any\n}\n\nexport type GraphQLResponse<T> = Awaited<ReturnType<typeof rawRequest<T>>>\n\ninterface GraphQLRequestBaseOptions<TResult> {\n api: string\n url: string\n token?: string\n addedHeaders?: {[header: string]: string}\n responseOptions?: GraphQLResponseOptions<TResult>\n}\n\ntype PerformGraphQLRequestOptions<TResult> = GraphQLRequestBaseOptions<TResult> & {\n queryAsString: string\n variables?: Variables\n}\n\nexport type GraphQLRequestOptions<T> = GraphQLRequestBaseOptions<T> & {\n query: RequestDocument\n variables?: Variables\n}\n\nexport type GraphQLRequestDocOptions<TResult, TVariables> = GraphQLRequestBaseOptions<TResult> & {\n query: TypedDocumentNode<TResult, TVariables> | TypedDocumentNode<TResult, Exact<{[key: string]: never}>>\n variables?: TVariables\n}\n\nexport interface GraphQLResponseOptions<T> {\n handleErrors?: boolean\n onResponse?: (response: GraphQLResponse<T>) => void\n}\n\n/**\n * Handles execution of a GraphQL query.\n *\n * @param options - GraphQL request options.\n */\nasync function performGraphQLRequest<TResult>(options: PerformGraphQLRequestOptions<TResult>) {\n const {token, addedHeaders, queryAsString, variables, api, url, responseOptions} = options\n const headers = {\n ...addedHeaders,\n ...buildHeaders(token),\n }\n\n debugLogRequestInfo(api, queryAsString, variables, headers)\n const clientOptions = {agent: await httpsAgent(), headers}\n const client = new GraphQLClient(url, clientOptions)\n\n return runWithTimer('cmd_all_timing_network_ms')(async () => {\n const response = await debugLogResponseInfo(\n {request: client.rawRequest<TResult>(queryAsString, variables), url},\n responseOptions?.handleErrors === false ? undefined : errorHandler(api),\n )\n\n if (responseOptions?.onResponse) {\n responseOptions.onResponse(response)\n }\n\n return response.data\n })\n}\n\n/**\n * Executes a GraphQL query to an endpoint.\n *\n * @param options - GraphQL request options.\n * @returns The response of the query of generic type <T>.\n */\nexport async function graphqlRequest<T>(options: GraphQLRequestOptions<T>): Promise<T> {\n return performGraphQLRequest<T>({\n ...options,\n queryAsString: options.query as string,\n })\n}\n\n/**\n * Executes a GraphQL query to an endpoint. Uses typed documents.\n *\n * @param options - GraphQL request options.\n * @returns The response of the query of generic type <TResult>.\n */\nexport async function graphqlRequestDoc<TResult, TVariables extends Variables>(\n options: GraphQLRequestDocOptions<TResult, TVariables>,\n): Promise<TResult> {\n return performGraphQLRequest<TResult>({\n ...options,\n queryAsString: resolveRequestDocument(options.query).query,\n })\n}\n"]}
1
+ {"version":3,"file":"graphql.js","sourceRoot":"","sources":["../../../../src/public/node/api/graphql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,UAAU,EAAC,MAAM,sCAAsC,CAAA;AAC7E,OAAO,EAAC,mBAAmB,EAAE,YAAY,EAAC,MAAM,sCAAsC,CAAA;AACtF,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,iBAAiB,EAAC,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAC,aAAa,EAA+B,sBAAsB,EAAY,MAAM,iBAAiB,CAAA;AAyC7G;;;;GAIG;AACH,KAAK,UAAU,qBAAqB,CAAU,OAA8C;IAC1F,MAAM,EAAC,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,eAAe,EAAC,GAAG,OAAO,CAAA;IAC1F,MAAM,OAAO,GAAG;QACd,GAAG,YAAY;QACf,GAAG,YAAY,CAAC,KAAK,CAAC;KACvB,CAAA;IAED,mBAAmB,CAAC,GAAG,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,CAAC,CAAA;IAC3D,MAAM,aAAa,GAAG,EAAC,KAAK,EAAE,MAAM,UAAU,EAAE,EAAE,OAAO,EAAC,CAAA;IAC1D,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;IAEpD,OAAO,YAAY,CAAC,2BAA2B,CAAC,CAAC,KAAK,IAAI,EAAE;QAC1D,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CACtC,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAU,aAAa,EAAE,SAAS,CAAC,EAAE,GAAG,EAAC,EAC1E,eAAe,EAAE,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CACxE,CAAA;QAED,IAAI,eAAe,EAAE,UAAU,EAAE;YAC/B,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;SACrC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAA;IACtB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAI,OAAiC;IACvE,OAAO,qBAAqB,CAAI;QAC9B,GAAG,OAAO;QACV,aAAa,EAAE,OAAO,CAAC,KAAe;KACvC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAsD;IAEtD,OAAO,qBAAqB,CAAU;QACpC,GAAG,OAAO;QACV,aAAa,EAAE,sBAAsB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK;KAC3D,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import {buildHeaders, httpsAgent} from '../../../private/node/api/headers.js'\nimport {debugLogRequestInfo, errorHandler} from '../../../private/node/api/graphql.js'\nimport {runWithTimer} from '../metadata.js'\nimport {retryAwareRequest} from '../../../private/node/api.js'\nimport {GraphQLClient, rawRequest, RequestDocument, resolveRequestDocument, Variables} from 'graphql-request'\nimport {TypedDocumentNode} from '@graphql-typed-document-node/core'\n\n// to replace TVariable type when there graphql query has no variables\nexport type Exact<T extends {[key: string]: unknown}> = {[K in keyof T]: T[K]}\n\nexport interface GraphQLVariables {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any\n}\n\nexport type GraphQLResponse<T> = Awaited<ReturnType<typeof rawRequest<T>>>\n\ninterface GraphQLRequestBaseOptions<TResult> {\n api: string\n url: string\n token?: string\n addedHeaders?: {[header: string]: string}\n responseOptions?: GraphQLResponseOptions<TResult>\n}\n\ntype PerformGraphQLRequestOptions<TResult> = GraphQLRequestBaseOptions<TResult> & {\n queryAsString: string\n variables?: Variables\n}\n\nexport type GraphQLRequestOptions<T> = GraphQLRequestBaseOptions<T> & {\n query: RequestDocument\n variables?: Variables\n}\n\nexport type GraphQLRequestDocOptions<TResult, TVariables> = GraphQLRequestBaseOptions<TResult> & {\n query: TypedDocumentNode<TResult, TVariables> | TypedDocumentNode<TResult, Exact<{[key: string]: never}>>\n variables?: TVariables\n}\n\nexport interface GraphQLResponseOptions<T> {\n handleErrors?: boolean\n onResponse?: (response: GraphQLResponse<T>) => void\n}\n\n/**\n * Handles execution of a GraphQL query.\n *\n * @param options - GraphQL request options.\n */\nasync function performGraphQLRequest<TResult>(options: PerformGraphQLRequestOptions<TResult>) {\n const {token, addedHeaders, queryAsString, variables, api, url, responseOptions} = options\n const headers = {\n ...addedHeaders,\n ...buildHeaders(token),\n }\n\n debugLogRequestInfo(api, queryAsString, variables, headers)\n const clientOptions = {agent: await httpsAgent(), headers}\n const client = new GraphQLClient(url, clientOptions)\n\n return runWithTimer('cmd_all_timing_network_ms')(async () => {\n const response = await retryAwareRequest(\n {request: () => client.rawRequest<TResult>(queryAsString, variables), url},\n responseOptions?.handleErrors === false ? undefined : errorHandler(api),\n )\n\n if (responseOptions?.onResponse) {\n responseOptions.onResponse(response)\n }\n\n return response.data\n })\n}\n\n/**\n * Executes a GraphQL query to an endpoint.\n *\n * @param options - GraphQL request options.\n * @returns The response of the query of generic type <T>.\n */\nexport async function graphqlRequest<T>(options: GraphQLRequestOptions<T>): Promise<T> {\n return performGraphQLRequest<T>({\n ...options,\n queryAsString: options.query as string,\n })\n}\n\n/**\n * Executes a GraphQL query to an endpoint. Uses typed documents.\n *\n * @param options - GraphQL request options.\n * @returns The response of the query of generic type <TResult>.\n */\nexport async function graphqlRequestDoc<TResult, TVariables extends Variables>(\n options: GraphQLRequestDocOptions<TResult, TVariables>,\n): Promise<TResult> {\n return performGraphQLRequest<TResult>({\n ...options,\n queryAsString: resolveRequestDocument(options.query).query,\n })\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import { RestResponse } from './admin.js';
2
+ /**
3
+ * Throttles a provided action, limiting the number of globally parallel requests, or by the last seen API limit
4
+ * headers.
5
+ *
6
+ * @param request - A function performing a request.
7
+ * @returns - The result of the request, once it eventually runs.
8
+ */
9
+ export declare function throttle<T>(request: () => T): Promise<T>;
10
+ /**
11
+ * Keep track of the latest API call limit data from a response.
12
+ *
13
+ * @param response - The response object.
14
+ */
15
+ export declare function updateApiCallLimitFromResponse(response: RestResponse): void;
16
+ /**
17
+ * Retries an operation after a delay specified in the response headers.
18
+ *
19
+ * @param response - The response object.
20
+ * @param operation - The operation to retry.
21
+ * @returns - The response of the operation.
22
+ */
23
+ export declare function delayAwareRetry(response: RestResponse, operation: () => Promise<RestResponse>): Promise<RestResponse>;
@@ -0,0 +1,239 @@
1
+ import { tryParseInt } from '@shopify/cli-kit/common/string';
2
+ const MAX_NUMBER_OF_PARALLEL_REQUESTS = 5;
3
+ const MARGIN_TO_RATE_LIMIT = 5;
4
+ const DELAY_FOR_TOO_MANY_PARALLEL_REQUESTS = 1000;
5
+ const DELAY_FOR_TOO_CLOSE_TO_API_LIMIT = 4000;
6
+ const THEME_CONTEXT = 'theme';
7
+ /**
8
+ * Throttles a provided action, limiting the number of globally parallel requests, or by the last seen API limit
9
+ * headers.
10
+ *
11
+ * @param request - A function performing a request.
12
+ * @returns - The result of the request, once it eventually runs.
13
+ */
14
+ export async function throttle(request) {
15
+ return new Promise((resolve, _reject) => {
16
+ const performRequest = () => {
17
+ throttlingState(THEME_CONTEXT).requestCounter += 1;
18
+ resolve(request());
19
+ };
20
+ /**
21
+ * Performs the request taking into account the
22
+ * limit of parallel requests only when the API limit has not
23
+ * been reached.
24
+ *
25
+ * Otherwise, performs the request to get the updated API limit
26
+ * headers, so throttler parameters get updates.
27
+ */
28
+ const throttleByHeader = () => {
29
+ if (isReachingApiLimit()) {
30
+ setTimeout(() => throttleByParallelCounter(performRequest), DELAY_FOR_TOO_CLOSE_TO_API_LIMIT);
31
+ }
32
+ else {
33
+ throttleByParallelCounter(performRequest);
34
+ }
35
+ };
36
+ /**
37
+ * Performs the command only when the the limit
38
+ * of parallel request has not been reached.
39
+ *
40
+ * Otherwise, defers the execution to the throttle by rate-limit function,
41
+ * still respecting the limit of parallel requests.
42
+ *
43
+ * @param command - The action to execute.
44
+ */
45
+ const throttleByParallelCounter = (command) => {
46
+ if (hasTooManyRequests()) {
47
+ setTimeout(() => throttleByParallelCounter(throttleByHeader), DELAY_FOR_TOO_MANY_PARALLEL_REQUESTS);
48
+ }
49
+ else {
50
+ command();
51
+ }
52
+ };
53
+ /**
54
+ * Start throttling by counter to get the API limit headers.
55
+ */
56
+ throttleByParallelCounter(throttleByHeader);
57
+ }).finally(() => {
58
+ throttlingState(THEME_CONTEXT).requestCounter -= 1;
59
+ });
60
+ }
61
+ /**
62
+ * Keep track of the latest API call limit data from a response.
63
+ *
64
+ * @param response - The response object.
65
+ */
66
+ export function updateApiCallLimitFromResponse(response) {
67
+ const callLimit = extractApiCallLimitFromResponse(response);
68
+ if (!callLimit) {
69
+ return;
70
+ }
71
+ const [used, limit] = callLimit;
72
+ latestRequestInfo().apiCallLimit = { used, limit };
73
+ }
74
+ function hasTooManyRequests() {
75
+ return throttlingState(THEME_CONTEXT).requestCounter > MAX_NUMBER_OF_PARALLEL_REQUESTS;
76
+ }
77
+ function isReachingApiLimit() {
78
+ const { used, limit } = latestRequestInfo().apiCallLimit;
79
+ return used >= limit - MARGIN_TO_RATE_LIMIT;
80
+ }
81
+ function latestRequestInfo() {
82
+ return throttlingState(THEME_CONTEXT).latestRequestInfo;
83
+ }
84
+ /**
85
+ * Even considering the Stateless modules convention,
86
+ * tracking information about the latest request is
87
+ * critical to optimize the request throttler efficiently.
88
+ *
89
+ * Thus, in this case, this module deliberately avoids
90
+ * IO cost and uses the `_throttlingState` instance for
91
+ * that purpose.
92
+ *
93
+ * A context option is used if multiple APIs are using these capabilities.
94
+ *
95
+ * @param context - The context which we're tracking throttle state within.
96
+ */
97
+ function throttlingState(context) {
98
+ const stateForContext = _throttlingState[context];
99
+ if (stateForContext === undefined) {
100
+ const startingState = {
101
+ requestCounter: 0,
102
+ latestRequestInfo: {
103
+ apiCallLimit: { used: 0, limit: 40 },
104
+ },
105
+ };
106
+ _throttlingState[context] = startingState;
107
+ return startingState;
108
+ }
109
+ else {
110
+ return stateForContext;
111
+ }
112
+ }
113
+ const _throttlingState = {};
114
+ function extractRetryDelayMsFromResponse(response) {
115
+ const retryAfterStr = header(response, 'retry-after');
116
+ const retryAfter = tryParseInt(retryAfterStr);
117
+ if (!retryAfter) {
118
+ return 0;
119
+ }
120
+ return retryAfter;
121
+ }
122
+ /**
123
+ * Retries an operation after a delay specified in the response headers.
124
+ *
125
+ * @param response - The response object.
126
+ * @param operation - The operation to retry.
127
+ * @returns - The response of the operation.
128
+ */
129
+ export async function delayAwareRetry(response, operation) {
130
+ const retryDelay = extractRetryDelayMsFromResponse(response);
131
+ return new Promise((resolve, _reject) => {
132
+ setTimeout(() => resolve(operation()), retryDelay);
133
+ });
134
+ }
135
+ function extractApiCallLimitFromResponse(response) {
136
+ const apiCallLimit = header(response, 'x-shopify-shop-api-call-limit');
137
+ const [used, limit] = apiCallLimit
138
+ .split('/')
139
+ .map((num) => tryParseInt(num))
140
+ .filter(Boolean);
141
+ if (!used || !limit) {
142
+ return;
143
+ }
144
+ return [used, limit];
145
+ }
146
+ function header(response, name) {
147
+ const headers = response.headers;
148
+ const header = headers[name];
149
+ if (header?.length === 1) {
150
+ return header[0] ?? '';
151
+ }
152
+ return '';
153
+ }
154
+ if (import.meta.vitest) {
155
+ const { describe, test, expect, beforeEach } = import.meta.vitest;
156
+ let response;
157
+ beforeEach(() => {
158
+ response = {
159
+ json: {},
160
+ status: 200,
161
+ headers: {},
162
+ };
163
+ });
164
+ describe('retryAfter', () => {
165
+ test('when the "retry-after" header value is valid', async () => {
166
+ // Given
167
+ response.headers = {
168
+ 'retry-after': ['2.0'],
169
+ };
170
+ // When
171
+ const retryAfterDelay = extractRetryDelayMsFromResponse(response);
172
+ // Then
173
+ expect(retryAfterDelay).toBe(2);
174
+ });
175
+ test('when the "retry-after" header value is not present', async () => {
176
+ // Given
177
+ response.headers = {
178
+ 'retry-after': [],
179
+ };
180
+ // When
181
+ const retryAfterDelay = extractRetryDelayMsFromResponse(response);
182
+ // Then
183
+ expect(retryAfterDelay).toBe(0);
184
+ });
185
+ test('when the "retry-after" header value is valid', async () => {
186
+ // Given
187
+ response.headers = {
188
+ 'retry-after': ['invalid'],
189
+ };
190
+ // When
191
+ const retryAfterDelay = extractRetryDelayMsFromResponse(response);
192
+ // Then
193
+ expect(retryAfterDelay).toBe(0);
194
+ });
195
+ test('when the "retry-after" header is not present', async () => {
196
+ // Given
197
+ response.headers = {};
198
+ // When
199
+ const retryAfterDelay = extractRetryDelayMsFromResponse(response);
200
+ // Then
201
+ expect(retryAfterDelay).toBe(0);
202
+ });
203
+ });
204
+ describe('apiCallLimit', () => {
205
+ test('when the "x-shopify-shop-api-call-limit" header is valid', async () => {
206
+ // Given
207
+ response.headers = {
208
+ 'x-shopify-shop-api-call-limit': ['10/40'],
209
+ };
210
+ // When
211
+ const callLimit = extractApiCallLimitFromResponse(response);
212
+ const [used, limit] = callLimit;
213
+ // Then
214
+ expect(used).toBe(10);
215
+ expect(limit).toBe(40);
216
+ });
217
+ test('when the "x-shopify-shop-api-call-limit" header is invalid', async () => {
218
+ // Given
219
+ response.headers = {
220
+ 'x-shopify-shop-api-call-limit': ['foo/bar'],
221
+ };
222
+ // When
223
+ const callLimit = extractApiCallLimitFromResponse(response);
224
+ // Then
225
+ expect(callLimit).toBeUndefined();
226
+ });
227
+ test('when the "x-shopify-shop-api-call-limit" header is not formatted as expected', async () => {
228
+ // Given
229
+ response.headers = {
230
+ 'x-shopify-shop-api-call-limit': ['/10'],
231
+ };
232
+ // When
233
+ const callLimit = extractApiCallLimitFromResponse(response);
234
+ // Then
235
+ expect(callLimit).toBeUndefined();
236
+ });
237
+ });
238
+ }
239
+ //# sourceMappingURL=rest-api-throttler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rest-api-throttler.js","sourceRoot":"","sources":["../../../../src/public/node/api/rest-api-throttler.ts"],"names":[],"mappings":"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","sourcesContent":["import {RestResponse} from './admin.js'\nimport {tryParseInt} from '@shopify/cli-kit/common/string'\n\nconst MAX_NUMBER_OF_PARALLEL_REQUESTS = 5\nconst MARGIN_TO_RATE_LIMIT = 5\n\nconst DELAY_FOR_TOO_MANY_PARALLEL_REQUESTS = 1000\nconst DELAY_FOR_TOO_CLOSE_TO_API_LIMIT = 4000\n\nconst THEME_CONTEXT = 'theme'\n\n/**\n * Throttles a provided action, limiting the number of globally parallel requests, or by the last seen API limit\n * headers.\n *\n * @param request - A function performing a request.\n * @returns - The result of the request, once it eventually runs.\n */\nexport async function throttle<T>(request: () => T): Promise<T> {\n return new Promise<T>((resolve, _reject) => {\n const performRequest = () => {\n throttlingState(THEME_CONTEXT).requestCounter += 1\n resolve(request())\n }\n\n /**\n * Performs the request taking into account the\n * limit of parallel requests only when the API limit has not\n * been reached.\n *\n * Otherwise, performs the request to get the updated API limit\n * headers, so throttler parameters get updates.\n */\n const throttleByHeader = () => {\n if (isReachingApiLimit()) {\n setTimeout(() => throttleByParallelCounter(performRequest), DELAY_FOR_TOO_CLOSE_TO_API_LIMIT)\n } else {\n throttleByParallelCounter(performRequest)\n }\n }\n\n /**\n * Performs the command only when the the limit\n * of parallel request has not been reached.\n *\n * Otherwise, defers the execution to the throttle by rate-limit function,\n * still respecting the limit of parallel requests.\n *\n * @param command - The action to execute.\n */\n const throttleByParallelCounter = (command: () => void) => {\n if (hasTooManyRequests()) {\n setTimeout(() => throttleByParallelCounter(throttleByHeader), DELAY_FOR_TOO_MANY_PARALLEL_REQUESTS)\n } else {\n command()\n }\n }\n\n /**\n * Start throttling by counter to get the API limit headers.\n */\n throttleByParallelCounter(throttleByHeader)\n }).finally(() => {\n throttlingState(THEME_CONTEXT).requestCounter -= 1\n })\n}\n\n/**\n * Keep track of the latest API call limit data from a response.\n *\n * @param response - The response object.\n */\nexport function updateApiCallLimitFromResponse(response: RestResponse): void {\n const callLimit = extractApiCallLimitFromResponse(response)\n\n if (!callLimit) {\n return\n }\n\n const [used, limit] = callLimit\n\n latestRequestInfo().apiCallLimit = {used, limit}\n}\n\nfunction hasTooManyRequests() {\n return throttlingState(THEME_CONTEXT).requestCounter > MAX_NUMBER_OF_PARALLEL_REQUESTS\n}\n\nfunction isReachingApiLimit() {\n const {used, limit} = latestRequestInfo().apiCallLimit\n return used >= limit - MARGIN_TO_RATE_LIMIT\n}\n\nfunction latestRequestInfo() {\n return throttlingState(THEME_CONTEXT).latestRequestInfo\n}\n\n/**\n * Even considering the Stateless modules convention,\n * tracking information about the latest request is\n * critical to optimize the request throttler efficiently.\n *\n * Thus, in this case, this module deliberately avoids\n * IO cost and uses the `_throttlingState` instance for\n * that purpose.\n *\n * A context option is used if multiple APIs are using these capabilities.\n *\n * @param context - The context which we're tracking throttle state within.\n */\nfunction throttlingState(context: string): ThrottlingState {\n const stateForContext = _throttlingState[context]\n if (stateForContext === undefined) {\n const startingState = {\n requestCounter: 0,\n latestRequestInfo: {\n apiCallLimit: {used: 0, limit: 40},\n },\n }\n _throttlingState[context] = startingState\n return startingState\n } else {\n return stateForContext\n }\n}\n\ninterface ThrottlingState {\n /**\n * Number of parallel requests.\n */\n requestCounter: number\n\n /**\n * Latest request information.\n */\n latestRequestInfo: {\n apiCallLimit: {used: number; limit: number}\n }\n}\n\nconst _throttlingState: {\n [context: string]: ThrottlingState\n} = {}\n\nfunction extractRetryDelayMsFromResponse(response: RestResponse): number {\n const retryAfterStr = header(response, 'retry-after')\n const retryAfter = tryParseInt(retryAfterStr)\n\n if (!retryAfter) {\n return 0\n }\n\n return retryAfter\n}\n\n/**\n * Retries an operation after a delay specified in the response headers.\n *\n * @param response - The response object.\n * @param operation - The operation to retry.\n * @returns - The response of the operation.\n */\nexport async function delayAwareRetry(\n response: RestResponse,\n operation: () => Promise<RestResponse>,\n): Promise<RestResponse> {\n const retryDelay = extractRetryDelayMsFromResponse(response)\n return new Promise((resolve, _reject) => {\n setTimeout(() => resolve(operation()), retryDelay)\n })\n}\n\nfunction extractApiCallLimitFromResponse(response: RestResponse): [number, number] | undefined {\n const apiCallLimit = header(response, 'x-shopify-shop-api-call-limit')\n\n const [used, limit] = apiCallLimit\n .split('/')\n .map((num) => tryParseInt(num))\n .filter(Boolean)\n\n if (!used || !limit) {\n return\n }\n\n return [used, limit]\n}\n\nfunction header(response: RestResponse, name: string): string {\n const headers = response.headers\n const header = headers[name]\n\n if (header?.length === 1) {\n return header[0] ?? ''\n }\n\n return ''\n}\n\nif (import.meta.vitest) {\n const {describe, test, expect, beforeEach} = import.meta.vitest\n let response: RestResponse\n\n beforeEach(() => {\n response = {\n json: {},\n status: 200,\n headers: {},\n }\n })\n\n describe('retryAfter', () => {\n test('when the \"retry-after\" header value is valid', async () => {\n // Given\n response.headers = {\n 'retry-after': ['2.0'],\n }\n\n // When\n const retryAfterDelay = extractRetryDelayMsFromResponse(response)\n\n // Then\n expect(retryAfterDelay).toBe(2)\n })\n\n test('when the \"retry-after\" header value is not present', async () => {\n // Given\n response.headers = {\n 'retry-after': [],\n }\n\n // When\n const retryAfterDelay = extractRetryDelayMsFromResponse(response)\n\n // Then\n expect(retryAfterDelay).toBe(0)\n })\n\n test('when the \"retry-after\" header value is valid', async () => {\n // Given\n response.headers = {\n 'retry-after': ['invalid'],\n }\n\n // When\n const retryAfterDelay = extractRetryDelayMsFromResponse(response)\n\n // Then\n expect(retryAfterDelay).toBe(0)\n })\n\n test('when the \"retry-after\" header is not present', async () => {\n // Given\n response.headers = {}\n\n // When\n const retryAfterDelay = extractRetryDelayMsFromResponse(response)\n\n // Then\n expect(retryAfterDelay).toBe(0)\n })\n })\n\n describe('apiCallLimit', () => {\n test('when the \"x-shopify-shop-api-call-limit\" header is valid', async () => {\n // Given\n response.headers = {\n 'x-shopify-shop-api-call-limit': ['10/40'],\n }\n\n // When\n const callLimit = extractApiCallLimitFromResponse(response)\n const [used, limit] = callLimit!\n\n // Then\n expect(used).toBe(10)\n expect(limit).toBe(40)\n })\n\n test('when the \"x-shopify-shop-api-call-limit\" header is invalid', async () => {\n // Given\n response.headers = {\n 'x-shopify-shop-api-call-limit': ['foo/bar'],\n }\n\n // When\n const callLimit = extractApiCallLimitFromResponse(response)\n\n // Then\n expect(callLimit).toBeUndefined()\n })\n\n test('when the \"x-shopify-shop-api-call-limit\" header is not formatted as expected', async () => {\n // Given\n response.headers = {\n 'x-shopify-shop-api-call-limit': ['/10'],\n }\n\n // When\n const callLimit = extractApiCallLimitFromResponse(response)\n\n // Then\n expect(callLimit).toBeUndefined()\n })\n })\n}\n"]}
@@ -19,6 +19,11 @@ export declare function stripUpPath(path: string, strip: number): string;
19
19
  * @param callback - The callback that receives the temporary directory.
20
20
  */
21
21
  export declare function inTemporaryDirectory<T>(callback: (tmpDir: string) => T | Promise<T>): Promise<T>;
22
+ /**
23
+ * Return a temporary directory
24
+ * @returns - The path to the temporary directory.
25
+ */
26
+ export declare function tempDirectory(): string;
22
27
  /**
23
28
  * It reads a file and returns its content as a string using the
24
29
  * utf-8 encoding.
@@ -5,7 +5,7 @@ import { copy as fsCopy, ensureFile as fsEnsureFile, ensureFileSync as fsEnsureF
5
5
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
6
6
  // @ts-ignore
7
7
  } from 'fs-extra/esm';
8
- import { temporaryDirectoryTask } from 'tempy';
8
+ import { temporaryDirectory, temporaryDirectoryTask } from 'tempy';
9
9
  import { sep, join } from 'pathe';
10
10
  import { findUp as internalFindUp } from 'find-up';
11
11
  import { minimatch } from 'minimatch';
@@ -32,6 +32,13 @@ export function stripUpPath(path, strip) {
32
32
  export async function inTemporaryDirectory(callback) {
33
33
  return temporaryDirectoryTask(callback);
34
34
  }
35
+ /**
36
+ * Return a temporary directory
37
+ * @returns - The path to the temporary directory.
38
+ */
39
+ export function tempDirectory() {
40
+ return temporaryDirectory();
41
+ }
35
42
  /**
36
43
  * It reads a file and returns its content as a string.
37
44
  * Uses utf-8 encoding by default.