-
Notifications
You must be signed in to change notification settings - Fork 255
Expand file tree
/
Copy pathpartners.ts
More file actions
166 lines (151 loc) · 4.91 KB
/
partners.ts
File metadata and controls
166 lines (151 loc) · 4.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import {
graphqlRequest,
GraphQLVariables,
GraphQLResponse,
graphqlRequestDoc,
CacheOptions,
UnauthorizedHandler,
} from './graphql.js'
import {addCursorAndFiltersToAppLogsUrl} from './utilities.js'
import {partnersFqdn} from '../context/fqdn.js'
import {setNextDeprecationDate} from '../../../private/node/context/deprecations-store.js'
import {getPackageManager} from '../node-package-manager.js'
import {cwd} from '../path.js'
import {AbortError, BugError} from '../error.js'
import {formatPackageManagerCommand} from '../output.js'
import {RequestModeInput} from '../http.js'
import {blockPartnersAccess} from '../environment.js'
import Bottleneck from 'bottleneck'
import {Variables} from 'graphql-request'
import {TypedDocumentNode} from '@graphql-typed-document-node/core'
// API Rate limiter for partners API (Limit is 10 requests per second)
// Jobs are launched every 150ms to add an extra 50ms margin per request.
// Only 10 requests can be executed concurrently.
const limiter = new Bottleneck({
minTime: 150,
maxConcurrent: 10,
})
/**
* Sets up the request to the Partners API.
*
* @param token - Partners token.
*/
async function setupRequest(token: string) {
if (blockPartnersAccess()) {
throw new BugError('Partners API is no longer available.')
}
const api = 'Partners'
const fqdn = await partnersFqdn()
const url = `https://${fqdn}/api/cli/graphql`
return {
token,
api,
url,
responseOptions: {onResponse: handleDeprecations},
}
}
/**
* Executes a GraphQL query against the Partners API.
*
* @param query - GraphQL query to execute.
* @param token - Partners token.
* @param variables - GraphQL variables to pass to the query.
* @param cacheOptions - Cache options.
* @param preferredBehaviour - Preferred behaviour for the request.
* @param unauthorizedHandler - Optional handler for unauthorized requests.
* @returns The response of the query of generic type <T>.
*/
export async function partnersRequest<T>(
query: string,
token: string,
variables?: GraphQLVariables,
cacheOptions?: CacheOptions,
preferredBehaviour?: RequestModeInput,
unauthorizedHandler?: UnauthorizedHandler,
): Promise<T> {
const opts = await setupRequest(token)
const result = limiter.schedule(() =>
graphqlRequest<T>({
...opts,
query,
variables,
cacheOptions,
preferredBehaviour,
unauthorizedHandler,
}),
)
return result
}
export const generateFetchAppLogUrl = async (
cursor?: string,
filters?: {
status?: string
source?: string
},
): Promise<string> => {
const fqdn = await partnersFqdn()
const url = `https://${fqdn}/app_logs/poll`
return addCursorAndFiltersToAppLogsUrl(url, cursor, filters)
}
/**
* Executes a GraphQL query against the Partners API. Uses typed documents.
*
* @param query - GraphQL query to execute.
* @param token - Partners token.
* @param variables - GraphQL variables to pass to the query.
* @param preferredBehaviour - Preferred behaviour for the request.
* @param unauthorizedHandler - Optional handler for unauthorized requests.
* @returns The response of the query of generic type <TResult>.
*/
export async function partnersRequestDoc<TResult, TVariables extends Variables>(
query: TypedDocumentNode<TResult, TVariables>,
token: string,
variables?: TVariables,
preferredBehaviour?: RequestModeInput,
unauthorizedHandler?: UnauthorizedHandler,
): Promise<TResult> {
try {
const opts = await setupRequest(token)
const result = limiter.schedule(() =>
graphqlRequestDoc<TResult, TVariables>({
...opts,
query,
variables,
preferredBehaviour,
unauthorizedHandler,
}),
)
return result
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.errors?.[0]?.extensions?.type === 'unsupported_client_version') {
const packageManager = await getPackageManager(cwd())
throw new AbortError(['Upgrade your CLI version to run this command.'], null, [
['Run', {command: formatPackageManagerCommand(packageManager, 'shopify upgrade')}],
])
}
throw error
}
}
interface Deprecation {
supportedUntilDate?: string
}
interface WithDeprecations {
deprecations: Deprecation[]
}
/**
* Sets the next deprecation date from [GraphQL response extensions](https://www.apollographql.com/docs/resources/graphql-glossary/#extensions)
* if `response.extensions.deprecations` objects contain a `supportedUntilDate` (ISO 8601-formatted string).
*
* @param response - The response of the query.
*/
export function handleDeprecations<T>(response: GraphQLResponse<T>): void {
if (!response.extensions) return
const deprecationDates: Date[] = []
for (const deprecation of (response.extensions as WithDeprecations).deprecations) {
if (deprecation.supportedUntilDate) {
deprecationDates.push(new Date(deprecation.supportedUntilDate))
}
}
setNextDeprecationDate(deprecationDates)
}