@@ -6,8 +6,8 @@ An interoperable [multipart form](https://tools.ietf.org/html/rfc7578) field str
6
6
7
7
It’s possible to implement:
8
8
9
- * Files nested anywhere within operations (typically in ` variables ` ).
10
- * Batched operations .
9
+ * Nesting files anywhere within operations (usually in ` variables ` ).
10
+ * Operation batching .
11
11
* File deduplication.
12
12
* File upload streams in resolvers.
13
13
* Aborting file uploads in resolvers.
@@ -26,82 +26,190 @@ So operations can be resolved while the files are still uploading, the fields ar
26
26
27
27
## Examples
28
28
29
- ### Avatar mutation
29
+ ### Single file
30
30
31
31
#### Operations
32
32
33
33
``` js
34
34
{
35
- query: ' …' ,
36
- operationName: ' updateAvatar' ,
35
+ query: `
36
+ mutation($file: Upload!) {
37
+ uploadFile(file: $file) {
38
+ id
39
+ }
40
+ }
41
+ ` ,
37
42
variables: {
38
- userId: ' …' ,
39
- image: File
43
+ file: File // a.txt
40
44
}
41
45
}
42
46
```
43
47
44
- #### Multipart form fields
48
+ #### cURL request
49
+
50
+ ``` shell
51
+ curl localhost:3001/graphql \
52
+ -F operations=' { "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }' \
53
+ -F map=' { "0": ["variables.file"] }' \
54
+ -F 0=@a.txt
55
+ ```
56
+
57
+ #### Request payload
58
+
59
+ ```
60
+ --------------------------cec8e8123c05ba25
61
+ Content-Disposition: form-data; name="operations"
62
+
63
+ { "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }
64
+ --------------------------cec8e8123c05ba25
65
+ Content-Disposition: form-data; name="map"
66
+
67
+ { "0": ["variables.file"] }
68
+ --------------------------cec8e8123c05ba25
69
+ Content-Disposition: form-data; name="0"; filename="a.txt"
70
+ Content-Type: text/plain
71
+
72
+ Alpha file content.
45
73
46
- 1 . ` operations ` : ` {"query": "…", "operationName": "updateAvatar", "variables": {"userId": "…", image: null}} `
47
- 2 . ` map ` : ` {"1": ["variables.image"]} `
48
- 3 . ` 1 ` : File
74
+ --------------------------cec8e8123c05ba25--
75
+ ```
49
76
50
- ### Gallery mutation
77
+ ### File list
51
78
52
79
#### Operations
53
80
54
81
``` js
55
82
{
56
- query: ' …' ,
57
- operationName: ' addToGallery' ,
83
+ query: `
84
+ mutation($files: [Upload!]!) {
85
+ multipleUpload(files: $files) {
86
+ id
87
+ }
88
+ }
89
+ ` ,
58
90
variables: {
59
- galleryId: ' …' ,
60
- images: [File , File , File ]
91
+ files: [
92
+ File , // b.txt
93
+ File // c.txt
94
+ ]
61
95
}
62
96
}
63
97
```
64
98
65
- #### Multipart form fields
99
+ #### cURL request
100
+
101
+ ``` shell
102
+ curl localhost:3001/graphql \
103
+ -F operations=' { "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }' \
104
+ -F map=' { "0": ["variables.files.0"], "1": ["variables.files.1"] }' \
105
+ -F 0=@b.txt \
106
+ -F 1=@c.txt
107
+ ```
108
+
109
+ #### Request payload
110
+
111
+ ```
112
+ --------------------------ec62457de6331cad
113
+ Content-Disposition: form-data; name="operations"
114
+
115
+ { "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }
116
+ --------------------------ec62457de6331cad
117
+ Content-Disposition: form-data; name="map"
118
+
119
+ { "0": ["variables.files.0"], "1": ["variables.files.1"] }
120
+ --------------------------ec62457de6331cad
121
+ Content-Disposition: form-data; name="0"; filename="b.txt"
122
+ Content-Type: text/plain
66
123
67
- 1 . ` operations ` : ` {"query": "…", "operationName": "addToGallery", "variables": {"galleryId": "…", images: [null, null, null]}} `
68
- 2 . ` map ` : ` {"1": ["variables.images.0"], "2": ["variables.images.1"], "3": ["variables.images.2"]} `
69
- 3 . ` 1 ` : File
70
- 4 . ` 2 ` : File
71
- 5 . ` 3 ` : File
124
+ Bravo file content.
72
125
73
- ### Batched mutations
126
+ --------------------------ec62457de6331cad
127
+ Content-Disposition: form-data; name="1"; filename="c.txt"
128
+ Content-Type: text/plain
129
+
130
+ Charlie file content.
131
+
132
+ --------------------------ec62457de6331cad--
133
+ ```
134
+
135
+ ### Batching
74
136
75
137
#### Operations
76
138
77
139
``` js
78
140
[
79
141
{
80
- query: ' …' ,
81
- operationName: ' updateAvatar' ,
142
+ query: `
143
+ mutation($file: Upload!) {
144
+ uploadFile(file: $file) {
145
+ id
146
+ }
147
+ }
148
+ ` ,
82
149
variables: {
83
- userId: ' …' ,
84
- image: File
150
+ file: File // a.txt
85
151
}
86
152
},
87
153
{
88
- query: ' …' ,
89
- operationName: ' addToGallery' ,
154
+ query: `
155
+ mutation($files: [Upload!]!) {
156
+ multipleUpload(files: $files) {
157
+ id
158
+ }
159
+ }
160
+ ` ,
90
161
variables: {
91
- galleryId: ' …' ,
92
- images: [File , File , File ]
162
+ files: [
163
+ File , // b.txt
164
+ File // c.txt
165
+ ]
93
166
}
94
167
}
95
168
]
96
169
```
97
170
98
- #### Multipart form fields
171
+ #### cURL request
172
+
173
+ ``` shell
174
+ curl localhost:3001/graphql \
175
+ -F operations=' [{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }, { "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }]' \
176
+ -F map=' { "0": ["0.variables.file"], "1": ["1.variables.files.0"], "2": ["1.variables.files.1"] }' \
177
+ -F 0=@a.txt \
178
+ -F 1=@b.txt \
179
+ -F 2=@c.txt
180
+ ```
181
+
182
+ #### Request payload
183
+
184
+ ```
185
+ --------------------------627436eaefdbc285
186
+ Content-Disposition: form-data; name="operations"
99
187
100
- 1 . ` operations ` : ` [{"query": "…", "operationName": "updateAvatar", "variables": {"userId": "…", image: null}}, {"query": "…", "operationName": "addToGallery", "variables": {"galleryId": "…", images: [null, null, null]}}] `
101
- 2 . ` map ` : ` {"1": ["0.variables.image", "1.variables.images.0"], "2": ["1.variables.images.1"], "3": ["1.variables.images.2"]} `
102
- 3 . ` 1 ` : File
103
- 4 . ` 2 ` : File
104
- 5 . ` 3 ` : File
188
+ [{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }, { "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }]
189
+ --------------------------627436eaefdbc285
190
+ Content-Disposition: form-data; name="map"
191
+
192
+ { "0": ["0.variables.file"], "1": ["1.variables.files.0"], "2": ["1.variables.files.1"] }
193
+ --------------------------627436eaefdbc285
194
+ Content-Disposition: form-data; name="0"; filename="a.txt"
195
+ Content-Type: text/plain
196
+
197
+ Alpha file content.
198
+
199
+ --------------------------627436eaefdbc285
200
+ Content-Disposition: form-data; name="1"; filename="b.txt"
201
+ Content-Type: text/plain
202
+
203
+ Bravo file content.
204
+
205
+ --------------------------627436eaefdbc285
206
+ Content-Disposition: form-data; name="2"; filename="c.txt"
207
+ Content-Type: text/plain
208
+
209
+ Charlie file content.
210
+
211
+ --------------------------627436eaefdbc285--
212
+ ```
105
213
106
214
## Implementations
107
215
0 commit comments