- ✅ Ticket 1.1: Estructura Clean Architecture en backend - ✅ Ticket 1.2: Schemas Zod compartidos - ✅ Ticket 1.3: Refactorización drugs.ts (1362 → 8 archivos modulares) - ✅ Ticket 1.4: Refactorización procedures.ts (3583 → 6 archivos modulares) - ✅ Ticket 1.5: Eliminación de duplicidades (~50 líneas) Cambios principales: - Creada estructura Clean Architecture en backend/src/ - Schemas Zod compartidos en backend/src/shared/schemas/ - Refactorización modular de drugs y procedures - Utilidades genéricas en src/utils/ (filter, validation) - Eliminados scripts obsoletos y documentación antigua - Corregidos errores: QueryClient, import test-error-handling - Build verificado y funcionando correctamente
174 lines
6.4 KiB
JavaScript
174 lines
6.4 KiB
JavaScript
import File from '../../file/File.js';
|
|
var MultipartParserStateEnum;
|
|
(function (MultipartParserStateEnum) {
|
|
MultipartParserStateEnum[MultipartParserStateEnum["boundary"] = 0] = "boundary";
|
|
MultipartParserStateEnum[MultipartParserStateEnum["headerStart"] = 2] = "headerStart";
|
|
MultipartParserStateEnum[MultipartParserStateEnum["header"] = 3] = "header";
|
|
MultipartParserStateEnum[MultipartParserStateEnum["data"] = 5] = "data";
|
|
})(MultipartParserStateEnum || (MultipartParserStateEnum = {}));
|
|
const CHARACTER_CODE = {
|
|
lf: 10,
|
|
cr: 13
|
|
};
|
|
/**
|
|
* Multipart reader.
|
|
*
|
|
* Based on:
|
|
* https://github.com/node-fetch/node-fetch/blob/main/src/utils/multipart-parser.js (MIT)
|
|
*/
|
|
export default class MultipartReader {
|
|
formData;
|
|
boundary;
|
|
boundaryIndex = 0;
|
|
state = MultipartParserStateEnum.boundary;
|
|
data = {
|
|
contentDisposition: null,
|
|
value: [],
|
|
contentType: null,
|
|
header: ''
|
|
};
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param window Window.
|
|
* @param formData Form data.
|
|
* @param boundary Boundary.
|
|
*/
|
|
constructor(window, boundary) {
|
|
const boundaryHeader = `--${boundary}`;
|
|
this.boundary = new Uint8Array(boundaryHeader.length);
|
|
this.formData = new window.FormData();
|
|
for (let i = 0, max = boundaryHeader.length; i < max; i++) {
|
|
this.boundary[i] = boundaryHeader.charCodeAt(i);
|
|
}
|
|
}
|
|
/**
|
|
* Appends data.
|
|
*
|
|
* @param data Data.
|
|
*/
|
|
write(data) {
|
|
let char;
|
|
let nextChar;
|
|
for (let i = 0, max = data.length; i < max; i++) {
|
|
char = data[i];
|
|
nextChar = data[i + 1];
|
|
switch (this.state) {
|
|
case MultipartParserStateEnum.boundary:
|
|
if (char === this.boundary[this.boundaryIndex]) {
|
|
this.boundaryIndex++;
|
|
}
|
|
else {
|
|
this.boundaryIndex = 0;
|
|
}
|
|
if (this.boundaryIndex === this.boundary.length) {
|
|
this.state = MultipartParserStateEnum.headerStart;
|
|
this.boundaryIndex = 0;
|
|
}
|
|
break;
|
|
case MultipartParserStateEnum.headerStart:
|
|
if (nextChar !== CHARACTER_CODE.cr && nextChar !== CHARACTER_CODE.lf) {
|
|
this.data.header = '';
|
|
this.state =
|
|
data[i - 2] === CHARACTER_CODE.lf
|
|
? MultipartParserStateEnum.data
|
|
: MultipartParserStateEnum.header;
|
|
}
|
|
break;
|
|
case MultipartParserStateEnum.header:
|
|
if (char === CHARACTER_CODE.cr) {
|
|
if (this.data.header) {
|
|
const headerParts = this.data.header.split(':');
|
|
if (headerParts.length > 1) {
|
|
const headerName = headerParts[0].toLowerCase();
|
|
const headerValue = headerParts[1].trim();
|
|
switch (headerName) {
|
|
case 'content-disposition':
|
|
this.data.contentDisposition = this.getContentDisposition(headerValue);
|
|
break;
|
|
case 'content-type':
|
|
this.data.contentType = headerValue;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
this.state = MultipartParserStateEnum.headerStart;
|
|
}
|
|
else {
|
|
this.data.header += String.fromCharCode(char);
|
|
}
|
|
break;
|
|
case MultipartParserStateEnum.data:
|
|
if (char === this.boundary[this.boundaryIndex]) {
|
|
this.boundaryIndex++;
|
|
}
|
|
else {
|
|
this.boundaryIndex = 0;
|
|
}
|
|
if (this.boundaryIndex === this.boundary.length) {
|
|
this.state = MultipartParserStateEnum.headerStart;
|
|
if (this.data.value.length) {
|
|
this.appendFormData(this.data.contentDisposition.name, Buffer.from(this.data.value.slice(0, -(this.boundary.length + 1))), this.data.contentDisposition.filename, this.data.contentType);
|
|
this.data.value = [];
|
|
this.data.contentDisposition = null;
|
|
this.data.contentType = null;
|
|
}
|
|
this.boundaryIndex = 0;
|
|
}
|
|
else {
|
|
this.data.value.push(char);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Ends the stream.
|
|
*
|
|
* @returns Form data.
|
|
*/
|
|
end() {
|
|
// If we are missing an end boundary, but we have data, we should append it.
|
|
if (this.data.contentDisposition && this.data.value.length) {
|
|
this.appendFormData(this.data.contentDisposition.name, Buffer.from(this.data.value.slice(0, -2)), this.data.contentDisposition.filename, this.data.contentType);
|
|
}
|
|
return this.formData;
|
|
}
|
|
/**
|
|
* Appends data.
|
|
*
|
|
* @param key Key.
|
|
* @param value value.
|
|
* @param filename Filename.
|
|
* @param type Type.
|
|
*/
|
|
appendFormData(key, value, filename, type) {
|
|
if (!value.length) {
|
|
return;
|
|
}
|
|
if (filename) {
|
|
this.formData.append(key, new File([value], filename, {
|
|
type
|
|
}));
|
|
}
|
|
else {
|
|
this.formData.append(key, value.toString());
|
|
}
|
|
}
|
|
/**
|
|
* Returns content disposition.
|
|
*
|
|
* @param headerValue Header value.
|
|
* @returns Content disposition.
|
|
*/
|
|
getContentDisposition(headerValue) {
|
|
const regex = /([a-z]+) *= *"([^"]+)"/g;
|
|
const contentDisposition = {};
|
|
let match;
|
|
while ((match = regex.exec(headerValue))) {
|
|
contentDisposition[match[1]] = match[2];
|
|
}
|
|
return contentDisposition;
|
|
}
|
|
}
|
|
//# sourceMappingURL=MultipartReader.js.map
|