-
Notifications
You must be signed in to change notification settings - Fork 10.3k
/
index.ts
143 lines (139 loc) · 4.32 KB
/
index.ts
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
import { MachineConfig, assign, createMachine } from "xstate"
import { IWaitingContext } from "./types"
import { waitingActions } from "./actions"
import { waitingServices } from "./services"
const NODE_MUTATION_BATCH_SIZE = 100
const NODE_MUTATION_BATCH_TIMEOUT = 500
const FILE_CHANGE_AGGREGATION_TIMEOUT = 200
export type WaitingResult = Pick<IWaitingContext, "nodeMutationBatch">
/**
* This idle state also handles batching of node mutations and running of
* mutations when we first start it
*/
export const waitingStates: MachineConfig<IWaitingContext, any, any> = {
predictableActionArguments: true,
id: `waitingMachine`,
initial: `idle`,
context: {
nodeMutationBatch: [],
runningBatch: [],
},
states: {
idle: {
always: [
{
// If we already have queued node mutations, move
// immediately to batching
cond: (ctx): boolean => !!ctx.nodeMutationBatch.length,
target: `batchingNodeMutations`,
},
{
// If source files are dirty upon entering this state,
// move immediately to aggregatingFileChanges to force re-compilation
// See https://github.com/gatsbyjs/gatsby/issues/27609
target: `aggregatingFileChanges`,
cond: ({ sourceFilesDirty }): boolean => Boolean(sourceFilesDirty),
},
],
on: {
ADD_NODE_MUTATION: {
actions: `addNodeMutation`,
target: `batchingNodeMutations`,
},
// We only listen for this when idling because if we receive it at any
// other point we're already going to create pages etc
SOURCE_FILE_CHANGED: {
target: `aggregatingFileChanges`,
},
},
},
aggregatingFileChanges: {
// Sigh. This is because webpack doesn't expose the Watchpack
// aggregated file invalidation events. If we compile immediately,
// we won't pick up the changed files
after: {
// The aggregation timeout
[FILE_CHANGE_AGGREGATION_TIMEOUT]: {
actions: `extractQueries`,
target: `idle`,
},
},
on: {
ADD_NODE_MUTATION: {
actions: `addNodeMutation`,
target: `batchingNodeMutations`,
},
SOURCE_FILE_CHANGED: {
target: undefined,
// External self-transition reset the timer
internal: false,
},
},
},
batchingNodeMutations: {
// Check if the batch is already full on entry
always: {
cond: (ctx): boolean =>
ctx.nodeMutationBatch.length >= NODE_MUTATION_BATCH_SIZE,
target: `committingBatch`,
},
on: {
// More mutations added to batch
ADD_NODE_MUTATION: [
// You know the score: only run the first matching transition
{
// If this fills the batch then commit it
actions: `addNodeMutation`,
cond: (ctx): boolean =>
ctx.nodeMutationBatch.length >= NODE_MUTATION_BATCH_SIZE,
target: `committingBatch`,
},
{
// ...otherwise just add it to the batch
actions: `addNodeMutation`,
},
],
},
after: {
// Time's up
[NODE_MUTATION_BATCH_TIMEOUT]: `committingBatch`,
},
},
committingBatch: {
entry: assign<IWaitingContext>(({ nodeMutationBatch }) => {
return {
nodeMutationBatch: [],
runningBatch: nodeMutationBatch,
}
}),
on: {
// While we're running the batch we will also run new actions, as these may be cascades
ADD_NODE_MUTATION: {
actions: `callApi`,
},
},
invoke: {
src: `runMutationBatch`,
// When we're done, clear the running batch ready for next time
onDone: {
actions: assign<IWaitingContext, any>({
runningBatch: [],
}),
target: `rebuild`,
},
},
},
rebuild: {
type: `final`,
// This is returned to the parent. The batch includes
// any mutations that arrived while we were running the other batch
data: ({ nodeMutationBatch }): WaitingResult => {
return { nodeMutationBatch }
},
},
},
}
export const waitingMachine = createMachine(waitingStates, {
actions: waitingActions,
services: waitingServices,
})