feat: Reinitialize frontend with SvelteKit and TypeScript

- Delete old Vite+Svelte frontend
- Initialize new SvelteKit project with TypeScript
- Configure Tailwind CSS v4 + DaisyUI
- Implement JWT authentication with auto-refresh
- Create login page with form validation (Zod)
- Add protected route guards
- Update Docker configuration for single-stage build
- Add E2E tests with Playwright (6/11 passing)
- Fix Svelte 5 reactivity with $state() runes

Known issues:
- 5 E2E tests failing (timing/async issues)
- Token refresh implementation needs debugging
- Validation error display timing
This commit is contained in:
2026-02-17 16:19:59 -05:00
parent 54df6018f5
commit de2d83092e
28274 changed files with 3816354 additions and 90 deletions

View File

@@ -0,0 +1,45 @@
import FormData from '../../form-data/FormData.js';
import { ReadableStream } from 'stream/web';
import { Buffer } from 'buffer';
import BrowserWindow from '../../window/BrowserWindow.js';
/**
* Multipart form data factory.
*
* Based on:
* https://github.com/node-fetch/node-fetch/blob/main/src/utils/multipart-parser.js (MIT)
*/
export default class MultipartFormDataParser {
/**
* Returns form data.
*
* @param window Window.
* @param body Body.
* @param contentType Content type header value.
* @returns Form data.
*/
static streamToFormData(window: BrowserWindow, body: ReadableStream, contentType: string): Promise<{
formData: FormData;
buffer: Buffer;
}>;
/**
* Converts a FormData object to a ReadableStream.
*
* @param formData FormData.
* @returns Stream and type.
*/
static formDataToStream(formData: FormData): {
contentType: string;
contentLength: number;
buffer: Buffer;
stream: ReadableStream;
};
/**
* Escapes a form data entry name.
*
* @param name Name.
* @param filename Whether it is a filename.
* @returns Escaped name.
*/
private static escapeName;
}
//# sourceMappingURL=MultipartFormDataParser.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MultipartFormDataParser.d.ts","sourceRoot":"","sources":["../../../src/fetch/multipart/MultipartFormDataParser.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,6BAA6B,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAI5C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,aAAa,MAAM,+BAA+B,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,CAAC,OAAO,OAAO,uBAAuB;IAC3C;;;;;;;OAOG;WACiB,gBAAgB,CACnC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,cAAc,EACpB,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAuDlD;;;;;OAKG;WACW,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG;QACnD,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,cAAc,CAAC;KACvB;IA+CD;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,UAAU;CAMzB"}

View File

@@ -0,0 +1,106 @@
import { ReadableStream } from 'stream/web';
import * as PropertySymbol from '../../PropertySymbol.js';
import MultipartReader from './MultipartReader.js';
import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js';
import { Buffer } from 'buffer';
/**
* Multipart form data factory.
*
* Based on:
* https://github.com/node-fetch/node-fetch/blob/main/src/utils/multipart-parser.js (MIT)
*/
export default class MultipartFormDataParser {
/**
* Returns form data.
*
* @param window Window.
* @param body Body.
* @param contentType Content type header value.
* @returns Form data.
*/
static async streamToFormData(window, body, contentType) {
if (!/multipart/i.test(contentType)) {
throw new window.DOMException(`Failed to build FormData object: The "content-type" header isn't of type "multipart/form-data".`, DOMExceptionNameEnum.invalidStateError);
}
const match = contentType.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
if (!match) {
throw new window.DOMException(`Failed to build FormData object: The "content-type" header doesn't contain any multipart boundary.`, DOMExceptionNameEnum.invalidStateError);
}
const bodyReader = body.getReader();
const reader = new MultipartReader(window, match[1] || match[2]);
const chunks = [];
let buffer;
const bytes = 0;
let readResult = await bodyReader.read();
while (!readResult.done) {
if (body[PropertySymbol.error]) {
throw body[PropertySymbol.error];
}
if (body[PropertySymbol.aborted]) {
throw new window.DOMException('Failed to read response body: The stream was aborted.', DOMExceptionNameEnum.abortError);
}
reader.write(readResult.value);
readResult = await bodyReader.read();
}
try {
buffer =
typeof chunks[0] === 'string' ? Buffer.from(chunks.join('')) : Buffer.concat(chunks, bytes);
}
catch (error) {
throw new window.DOMException(`Could not create Buffer from response body. Error: ${error.message}.`, DOMExceptionNameEnum.invalidStateError);
}
return {
formData: reader.end(),
buffer
};
}
/**
* Converts a FormData object to a ReadableStream.
*
* @param formData FormData.
* @returns Stream and type.
*/
static formDataToStream(formData) {
const boundary = '----HappyDOMFormDataBoundary' + Math.random().toString(36);
const chunks = [];
const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`;
for (const [name, value] of formData) {
if (typeof value === 'string') {
chunks.push(Buffer.from(`${prefix}${this.escapeName(name)}"\r\n\r\n${value.replace(/\r(?!\n)|(?<!\r)\n/g, '\r\n')}\r\n`));
}
else {
chunks.push(Buffer.from(`${prefix}${this.escapeName(name)}"; filename="${this.escapeName(value.name, true)}"\r\nContent-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`));
chunks.push(value[PropertySymbol.buffer]);
chunks.push(Buffer.from('\r\n'));
}
}
// add end boundary
chunks.push(Buffer.from(`--${boundary}--\r\n`));
const buffer = Buffer.concat(chunks);
return {
contentType: `multipart/form-data; boundary=${boundary}`,
contentLength: buffer.length,
buffer,
stream: new ReadableStream({
start(controller) {
controller.enqueue(buffer);
controller.close();
}
})
};
}
/**
* Escapes a form data entry name.
*
* @param name Name.
* @param filename Whether it is a filename.
* @returns Escaped name.
*/
static escapeName(name, filename = false) {
return (filename ? name : name.replace(/\r?\n|\r/g, '\r\n'))
.replace(/\n/g, '%0A')
.replace(/\r/g, '%0D')
.replace(/"/g, '%22');
}
}
//# sourceMappingURL=MultipartFormDataParser.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MultipartFormDataParser.js","sourceRoot":"","sources":["../../../src/fetch/multipart/MultipartFormDataParser.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,52 @@
import FormData from '../../form-data/FormData.js';
import BrowserWindow from '../../window/BrowserWindow.js';
/**
* Multipart reader.
*
* Based on:
* https://github.com/node-fetch/node-fetch/blob/main/src/utils/multipart-parser.js (MIT)
*/
export default class MultipartReader {
private formData;
private boundary;
private boundaryIndex;
private state;
private data;
/**
* Constructor.
*
* @param window Window.
* @param formData Form data.
* @param boundary Boundary.
*/
constructor(window: BrowserWindow, boundary: string);
/**
* Appends data.
*
* @param data Data.
*/
write(data: Uint8Array): void;
/**
* Ends the stream.
*
* @returns Form data.
*/
end(): FormData;
/**
* Appends data.
*
* @param key Key.
* @param value value.
* @param filename Filename.
* @param type Type.
*/
private appendFormData;
/**
* Returns content disposition.
*
* @param headerValue Header value.
* @returns Content disposition.
*/
private getContentDisposition;
}
//# sourceMappingURL=MultipartReader.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MultipartReader.d.ts","sourceRoot":"","sources":["../../../src/fetch/multipart/MultipartReader.ts"],"names":[],"mappings":"AACA,OAAO,QAAQ,MAAM,6BAA6B,CAAC;AACnD,OAAO,aAAa,MAAM,+BAA+B,CAAC;AAc1D;;;;;GAKG;AACH,MAAM,CAAC,OAAO,OAAO,eAAe;IACnC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,IAAI,CAUV;IAEF;;;;;;OAMG;gBACS,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM;IAUnD;;;;OAIG;IACI,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IA6FpC;;;;OAIG;IACI,GAAG,IAAI,QAAQ;IActB;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAiBtB;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;CAW7B"}

View File

@@ -0,0 +1,174 @@
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

View File

@@ -0,0 +1 @@
{"version":3,"file":"MultipartReader.js","sourceRoot":"","sources":["../../../src/fetch/multipart/MultipartReader.ts"],"names":[],"mappings":"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"}