-
Notifications
You must be signed in to change notification settings - Fork 10.3k
/
api-runner-node.js
197 lines (175 loc) · 5.68 KB
/
api-runner-node.js
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
const Promise = require(`bluebird`)
const glob = require(`glob`)
const _ = require(`lodash`)
const mapSeries = require(`async/mapSeries`)
const reporter = require(`gatsby-cli/lib/reporter`)
const cache = require(`./cache`)
const apiList = require(`./api-node-docs`)
const createNodeId = require(`./create-node-id`)
// Bind action creators per plugin so we can auto-add
// metadata to actions they create.
const boundPluginActionCreators = {}
const doubleBind = (boundActionCreators, api, plugin, { traceId }) => {
if (boundPluginActionCreators[plugin.name + api + traceId]) {
return boundPluginActionCreators[plugin.name + api + traceId]
} else {
const keys = Object.keys(boundActionCreators)
const doubleBoundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const boundActionCreator = boundActionCreators[key]
if (typeof boundActionCreator === `function`) {
doubleBoundActionCreators[key] = (...args) => {
// Let action callers override who the plugin is. Shouldn't be used
// that often.
if (args.length === 1) {
boundActionCreator(args[0], plugin, traceId)
} else if (args.length === 2) {
boundActionCreator(args[0], args[1], traceId)
}
}
}
}
boundPluginActionCreators[
plugin.name + api + traceId
] = doubleBoundActionCreators
return doubleBoundActionCreators
}
}
const runAPI = (plugin, api, args) => {
let pathPrefix = ``
const {
store,
loadNodeContent,
getNodes,
getNode,
hasNodeChanged,
getNodeAndSavePathDependency,
} = require(`../redux`)
const { boundActionCreators } = require(`../redux/actions`)
const doubleBoundActionCreators = doubleBind(
boundActionCreators,
api,
plugin,
args
)
if (store.getState().program.prefixPaths) {
pathPrefix = store.getState().config.pathPrefix
}
const namespacedCreateNodeId = id => createNodeId(id, plugin.name)
const gatsbyNode = require(`${plugin.resolve}/gatsby-node`)
if (gatsbyNode[api]) {
const apiCallArgs = [
{
...args,
pathPrefix,
boundActionCreators: doubleBoundActionCreators,
loadNodeContent,
store,
getNodes,
getNode,
hasNodeChanged,
reporter,
getNodeAndSavePathDependency,
cache,
createNodeId: namespacedCreateNodeId,
},
plugin.pluginOptions,
]
// If the plugin is using a callback use that otherwise
// expect a Promise to be returned.
if (gatsbyNode[api].length === 3) {
return Promise.fromCallback(callback =>
gatsbyNode[api](...apiCallArgs, callback)
)
} else {
const result = gatsbyNode[api](...apiCallArgs)
return Promise.resolve(result)
}
}
return null
}
let filteredPlugins
const hasAPIFile = plugin => glob.sync(`${plugin.resolve}/gatsby-node*`)[0]
let apisRunning = []
let waitingForCasacadeToFinish = []
module.exports = async (api, args = {}, pluginSource) =>
new Promise(resolve => {
// Check that the API is documented.
if (!apiList[api]) {
reporter.error(`api: "${api}" is not a valid Gatsby api`)
process.exit()
}
const { store } = require(`../redux`)
const plugins = store.getState().flattenedPlugins
// Get the list of plugins that implement gatsby-node
if (!filteredPlugins) {
filteredPlugins = plugins.filter(plugin => hasAPIFile(plugin))
}
// Break infinite loops.
// Sometimes a plugin will implement an API and call an
// action which will trigger the same API being called.
// "onCreatePage" is the only example right now.
// In these cases, we should avoid calling the originating plugin
// again.
let noSourcePluginPlugins = filteredPlugins
if (pluginSource) {
noSourcePluginPlugins = filteredPlugins.filter(
p => p.name !== pluginSource
)
}
const apiRunInstance = {
api,
args,
pluginSource,
resolve,
startTime: new Date().toJSON(),
traceId: args.traceId,
}
if (args.waitForCascadingActions) {
waitingForCasacadeToFinish.push(apiRunInstance)
}
apisRunning.push(apiRunInstance)
let pluginName = null
mapSeries(
noSourcePluginPlugins,
(plugin, callback) => {
if (plugin.name === `default-site-plugin`) {
pluginName = `gatsby-node.js`
} else {
pluginName = `Plugin ${plugin.name}`
}
Promise.resolve(runAPI(plugin, api, args)).asCallback(callback)
},
(err, results) => {
if (err) {
reporter.error(`${pluginName} returned an error`, err)
}
// Remove runner instance
apisRunning = apisRunning.filter(runner => runner !== apiRunInstance)
if (apisRunning.length === 0) {
const { emitter } = require(`../redux`)
emitter.emit(`API_RUNNING_QUEUE_EMPTY`)
}
// Filter empty results
apiRunInstance.results = results.filter(result => !_.isEmpty(result))
// Filter out empty responses and return if the
// api caller isn't waiting for cascading actions to finish.
if (!args.waitForCascadingActions) {
resolve(apiRunInstance.results)
}
// Check if any of our waiters are done.
waitingForCasacadeToFinish = waitingForCasacadeToFinish.filter(
instance => {
// If none of its trace IDs are running, it's done.
if (!_.some(apisRunning, a => a.traceId === instance.traceId)) {
instance.resolve(instance.results)
return false
} else {
return true
}
}
)
}
)
})