@emeryld/rrroutes-client 2.5.7 → 2.5.9
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.
- package/README.md +18 -2
- package/dist/index.cjs +36 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +36 -20
- package/dist/index.mjs.map +1 -1
- package/dist/routesV3.client.shared.d.ts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -222,7 +222,9 @@ const updateUser = buildRoute('updateUser', {}, { name: 'profile' }) // debug na
|
|
|
222
222
|
|
|
223
223
|
### 8) File uploads (FormData)
|
|
224
224
|
|
|
225
|
-
If a leaf has `bodyFiles` set in its contract, the client automatically converts the body to `FormData
|
|
225
|
+
If a leaf has `bodyFiles` set in its contract, the client automatically converts the body to `FormData`.
|
|
226
|
+
For each declared file field name, pass files using `file${name}` in the input body.
|
|
227
|
+
The raw field name is also accepted for backward compatibility.
|
|
226
228
|
|
|
227
229
|
```ts
|
|
228
230
|
const uploadAvatar = client.build(
|
|
@@ -231,7 +233,21 @@ const uploadAvatar = client.build(
|
|
|
231
233
|
|
|
232
234
|
await uploadAvatar.fetch(
|
|
233
235
|
{ params: { userId: 'u_1' } },
|
|
234
|
-
{
|
|
236
|
+
{
|
|
237
|
+
// bodyFiles: [{ name: 'avatar', maxCount: 1 }]
|
|
238
|
+
fileavatar: new File([blob], 'avatar.png', { type: 'image/png' }),
|
|
239
|
+
// any non-file body fields still go here and are validated by bodySchema
|
|
240
|
+
note: 'profile image',
|
|
241
|
+
},
|
|
242
|
+
)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
For multi-file fields (`maxCount > 1`), pass `Blob[]` or `FileList`:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
await uploadAvatar.fetch(
|
|
249
|
+
{ params: { userId: 'u_1' } },
|
|
250
|
+
{ filephotos: [fileA, fileB] }, // bodyFiles: [{ name: 'photos', maxCount: 5 }]
|
|
235
251
|
)
|
|
236
252
|
```
|
|
237
253
|
|
package/dist/index.cjs
CHANGED
|
@@ -166,6 +166,7 @@ function isBlobLike(value) {
|
|
|
166
166
|
function assertFileFieldValue(sourceKey, targetField, value) {
|
|
167
167
|
if (value == null) return;
|
|
168
168
|
if (isBlobLike(value)) return;
|
|
169
|
+
if (isReactNativeFile(value)) return;
|
|
169
170
|
if (typeof FileList !== "undefined" && value instanceof FileList) {
|
|
170
171
|
for (const item of Array.from(value)) {
|
|
171
172
|
if (!isBlobLike(item)) {
|
|
@@ -178,7 +179,7 @@ function assertFileFieldValue(sourceKey, targetField, value) {
|
|
|
178
179
|
}
|
|
179
180
|
if (Array.isArray(value)) {
|
|
180
181
|
for (const item of value) {
|
|
181
|
-
if (!isBlobLike(item)) {
|
|
182
|
+
if (!isBlobLike(item) && !isReactNativeFile(item)) {
|
|
182
183
|
throw new Error(
|
|
183
184
|
`Multipart field "${sourceKey}" must contain Blob/File values for "${targetField}".`
|
|
184
185
|
);
|
|
@@ -187,7 +188,7 @@ function assertFileFieldValue(sourceKey, targetField, value) {
|
|
|
187
188
|
return;
|
|
188
189
|
}
|
|
189
190
|
throw new Error(
|
|
190
|
-
`Multipart field "${sourceKey}" must be Blob/File, Blob[] or FileList.`
|
|
191
|
+
`Multipart field "${sourceKey}" must be Blob/File, ReactNativeFile, Blob[]/ReactNativeFile[] or FileList. ReactNativeFile = { uri: string, name: string, type: string }.`
|
|
191
192
|
);
|
|
192
193
|
}
|
|
193
194
|
function splitMultipartBody(body, fields) {
|
|
@@ -199,7 +200,7 @@ function splitMultipartBody(body, fields) {
|
|
|
199
200
|
}
|
|
200
201
|
const fieldByInputKey = /* @__PURE__ */ new Map();
|
|
201
202
|
for (const field of fields) {
|
|
202
|
-
fieldByInputKey.set(`
|
|
203
|
+
fieldByInputKey.set(`file_${field.name}`, field.name);
|
|
203
204
|
fieldByInputKey.set(field.name, field.name);
|
|
204
205
|
}
|
|
205
206
|
const regularBody = {};
|
|
@@ -215,29 +216,44 @@ function splitMultipartBody(body, fields) {
|
|
|
215
216
|
}
|
|
216
217
|
return { regularBody, multipartFiles };
|
|
217
218
|
}
|
|
218
|
-
|
|
219
|
-
|
|
219
|
+
var isReactNativeFile = (v) => v && typeof v === "object" && typeof v.uri === "string" && typeof v.name === "string" && typeof v.type === "string";
|
|
220
|
+
var isFile = (v) => typeof File !== "undefined" && v instanceof File;
|
|
221
|
+
var isBlob = (v) => typeof Blob !== "undefined" && v instanceof Blob;
|
|
222
|
+
function toFormData(body, bodyFiles = []) {
|
|
220
223
|
const fd = new FormData();
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (
|
|
224
|
-
|
|
224
|
+
const fileKeys = new Set(bodyFiles.flatMap((f) => [f.name, `file_${f.name}`]));
|
|
225
|
+
const appendFile = (fieldName, value) => {
|
|
226
|
+
if (value == null) return;
|
|
227
|
+
if (isReactNativeFile(value)) {
|
|
228
|
+
fd.append(fieldName, value);
|
|
229
|
+
return;
|
|
225
230
|
}
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
fd.append(k, item);
|
|
230
|
-
}
|
|
231
|
-
continue;
|
|
231
|
+
if (isFile(value)) {
|
|
232
|
+
fd.append(fieldName, value, value.name);
|
|
233
|
+
return;
|
|
232
234
|
}
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
if (isBlob(value)) {
|
|
236
|
+
fd.append(fieldName, value, "upload");
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
throw new Error(`Invalid upload value for ${fieldName}`);
|
|
240
|
+
};
|
|
241
|
+
for (const [key, value] of Object.entries(body)) {
|
|
242
|
+
if (fileKeys.has(key)) {
|
|
243
|
+
if (Array.isArray(value)) {
|
|
244
|
+
for (const v of value) appendFile(key, v);
|
|
245
|
+
} else {
|
|
246
|
+
appendFile(key, value);
|
|
237
247
|
}
|
|
238
248
|
continue;
|
|
239
249
|
}
|
|
240
|
-
fd.append(
|
|
250
|
+
if (typeof value === "string") fd.append(key, value);
|
|
251
|
+
else if (typeof value === "number" || typeof value === "boolean")
|
|
252
|
+
fd.append(key, String(value));
|
|
253
|
+
else if (value == null) {
|
|
254
|
+
} else {
|
|
255
|
+
fd.append(key, JSON.stringify(value));
|
|
256
|
+
}
|
|
241
257
|
}
|
|
242
258
|
return fd;
|
|
243
259
|
}
|