8000 fix: support file uploads as form-data content type · manchenkoff/nuxt-sanctum-precognition@9e97780 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Commit 9e97780

Browse files
committed
fix: support file uploads as form-data content type
1 parent 0714237 commit 9e97780

File tree

5 files changed

+52
-31
lines changed

5 files changed

+52
-31
lines changed

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
},
3535
"dependencies": {
3636
"defu": "^6.1.4",
37-
"nuxt-auth-sanctum": "^0.6.0"
37+
"nuxt-auth-sanctum": "^0.6.1",
38+
"object-form-encoder": "^0.0.6"
3839
},
3940
"devDependencies": {
4041
"@nuxt/devtools": "^2.3.1",
@@ -43,15 +44,15 @@
4344
"@nuxt/module-builder": "^0.8.4",
4445
"@nuxt/schema": "^3.16.1",
4546
"@nuxt/test-utils": "^3.17.2",
47+
"@types/lodash": "^4.17.16",
4648
"@types/node": "^22.13.13",
4749
"changelogen": "^0.6.1",
4850
"eslint": "^9.23.0",
51+
"lodash": "^4.17.21",
4952
"nuxt": "^3.16.1",
5053
"typescript": "^5.8.2",
5154
"vitest": "^3.0.9",
52-
"vue-tsc": "^2.2.8",
53-
"@types/lodash": "^4.17.16",
54-
"lodash": "^4.17.21"
55+
"vue-tsc": "^2.2.8"
5556
},
5657
"packageManager": "pnpm@10.6.5+sha512.cdf928fca20832cd59ec53826492b7dc25dc524d4370b6b4adbf65803d32efaa6c1c88147c0ae4e8d579a6c9eec715757b50d4fa35eea179d868eada4ed043af",
5758
"pnpm": {

pnpm-lock.yaml

Lines changed: 13 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/runtime/composables/usePrecognitionForm.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { computed, reactive, type Ref, ref, toRaw } from 'vue'
22
import { cloneDeep, debounce, isEqual } from 'lodash'
3+
import { objectToFormData } from 'object-form-encoder'
34
import type {
45
Payload,
56
PayloadData,
@@ -12,14 +13,21 @@ import type {
1213
} from '../types'
1314
import { usePrecognitionConfig } from '../composables/usePrecognitionConfig'
1415
import {
15-
CONTENT_TYPE_HEADER,
1616
PRECOGNITION_HEADER,
1717
PRECOGNITION_ONLY_HEADER,
1818
PRECOGNITION_SUCCESS_HEADER,
19+
STATUS_NO_CONTENT,
20+
STATUS_VALIDATION_ERROR,
1921
} from '../utils/constants'
2022
import { clearFiles, hasFiles } from '../utils/files'
2123
import { useSanctumClient } from '#imports'
2224

25+
type FormProcessParams<T extends Payload> = {
26+
precognitive: boolean
27+
fields: PayloadKey<T>[]
28+
options?: ValidationOptions
29+
}
30+
2331
// TODO: implement a Nuxt UI compatible version of this composable (via decorator?)
2432
export const usePrecognitionForm = <T extends Payload>(
2533
method: RequestMethod,
@@ -39,23 +47,24 @@ export const usePrecognitionForm = <T extends Payload>(
3947
const _client = useSanctumClient()
4048

4149
// TODO: refactor and decompose this function (separate current state and arguments)
42-
async function process(params: { precognitive: boolean, fields: PayloadKey<T>[], options?: ValidationOptions } = { precognitive: false, fields: [], options: {} }): Promise<ResponseType> {
43-
// TODO: use object-form-encoder (and test with files attached)
44-
let payload = form.data()
50+
async function process(params: FormProcessParams<T> = {
51+
precognitive: false,
52+
fields: [],
53+
options: {},
54+
}): Promise<ResponseType> {
55+
let payload = form.data() as object
4556

4657
const headers = new Headers()
4758
const includeFiles = params.options?.validateFiles ?? _config.validateFiles
4859

4960
if (hasFiles(payload)) {
50-
if (includeFiles) {
51-
headers.set(CONTENT_TYPE_HEADER, 'multipart/form-data')
61+
if (params.precognitive && !includeFiles) {
62+
console.warn('Files were detected in the payload but will not be sent. '
63+
+ 'To include files, set `validateFiles` to `true` in the validation options or module config.')
64+
payload = clearFiles(payload)
5265
}
5366
else {
54-
if (params.precognitive) {
55-
console.warn('Files were detected in the payload but will not be sent. '
56-
+ 'To include files, set `validateFiles` to `true` in the validation options or module config.')
57-
payload = clearFiles(payload)
58-
}
67+
payload = objectToFormData(payload)
5968
}
6069
}
6170

@@ -83,7 +92,10 @@ export const usePrecognitionForm = <T extends Payload>(
8392
console.warn('Did not receive a Precognition response. Ensure you have the Precognition middleware in place for the route.')
8493
}
8594

86-
if (response.status === 204 && response.headers.get(PRECOGNITION_SUCCESS_HEADER) === 'true') {
95+
if (
96+
response.status === STATUS_NO_CONTENT
97+
&& response.headers.get(PRECOGNITION_SUCCESS_HEADER) === 'true'
98+
) {
8799
if (params.fields.length > 0) {
88100
const validatedNew = new Set(_validated.value)
89101

@@ -105,7 +117,7 @@ export const usePrecognitionForm = <T extends Payload>(
105117
return Promise.resolve(response)
106118
}
107119

108-
if (response.status === 422) {
120+
if (response.status === STATUS_VALIDATION_ERROR) {
109121
form.setErrors(response._data.errors as PayloadErrors<T>)
110122
}
111123

@@ -226,7 +238,7 @@ export const usePrecognitionForm = <T extends Payload>(
226238
options.onSuccess(response)
227239
})
228240
.catch((response: ResponseType) => {
229-
if (response.status === 422) {
241+
if (response.status === STATUS_VALIDATION_ERROR) {
230242
if (!options?.onValidationError) {
231243
return
232244
}

src/runtime/utils/constants.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
export const
22
PRECOGNITION_HEADER = 'Precognition',
33
PRECOGNITION_ONLY_HEADER = 'Precognition-Validate-Only',
4-
PRECOGNITION_SUCCESS_HEADER = 'Precognition-Success',
5-
CONTENT_TYPE_HEADER = 'Content-Type'
4+
PRECOGNITION_SUCCESS_HEADER = 'Precognition-Success'
5+
6+
export const
7+
STATUS_NO_CONTENT = 204,
8+
STATUS_VALIDATION_ERROR = 422

src/runtime/utils/files.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
import type { Payload } from '../types'
2-
31
export const isFile = (value: unknown): boolean => (typeof File !== 'undefined' && value instanceof File)
42
|| value instanceof Blob
53
|| (typeof FileList !== 'undefined' && value instanceof FileList && value.length > 0)
64

75
export const hasFiles = (data: unknown): boolean => isFile(data)
86
|| (typeof data === 'object' && data !== null && Object.values(data).some(value => hasFiles(value)))
97

10-
export const clearFiles = <T extends Payload>(data: T): T => {
11-
let newData = { ...data }
8+
export const clearFiles = (data: object): object => {
9+
let newData = { ...data } as Record<string, unknown>
1210

1311
Object
1412
.keys(newData)
@@ -22,14 +20,13 @@ export const clearFiles = <T extends Payload>(data: T): T => {
2220
// drop the file from the payload
2321
if (isFile(value)) {
2422
const { [name]: _, ...fields } = newData
25-
newData = fields as T
23+
newData = fields
2624

2725
return
2826
}
2927

3028
// recursively clear files from nested arrays
3129
if (Array.isArray(value)) {
32-
// @ts-expect-error: assign property value on reactive object
3330
newData[name] = Object.values(clearFiles({ ...value }))
3431

3532
return

0 commit comments

Comments
 (0)
0