@tmlmobilidade/go-utils-files 20260904.1059.6

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.
@@ -0,0 +1,78 @@
1
+ import JSZip from 'jszip';
2
+ import { type ParseConfig } from 'papaparse';
3
+ interface UpdateCsvFieldParams {
4
+ column: string;
5
+ csvString: string;
6
+ rowIndex: number;
7
+ value: string;
8
+ }
9
+ export declare class Files {
10
+ /**
11
+ * Converts a Blob to a File.
12
+ * @param blob The Blob object to convert.
13
+ * @param fileName The name of the resulting File.
14
+ * @returns The resulting File.
15
+ */
16
+ static blobToFile(blob: Blob, fileName: string): File;
17
+ /**
18
+ * Returns the file extension from a file name.
19
+ * @param fileName The name of the file.
20
+ * @returns The file extension.
21
+ * @throws Error if the file has no extension or if the extension is not supported.
22
+ */
23
+ static getFileExtension(fileName: string): string;
24
+ /**
25
+ * Gets the file extension from a MIME type.
26
+ * @param mimeType The MIME type to get the file extension for.
27
+ * @returns The file extension, or an empty string if not found.
28
+ */
29
+ static getFileExtensionFromMimeType(mimeType: string): string;
30
+ /**
31
+ * Gets the MIME type from a file extension.
32
+ * @param fileName The name of the file to get the MIME type for.
33
+ * @returns The MIME type.
34
+ */
35
+ static getMimeTypeFromFileExtension(fileName: string): string;
36
+ /**
37
+ * Reads and extracts a single file from a ZIP archive.
38
+ * @param zipFilePath The zip file to read from, can be a File object (browser), string path (Node.js), or URL
39
+ * @param fileName The name of the file to extract from the ZIP
40
+ * @param encoding The encoding to use when reading the file. See JSZip documentation for supported formats.
41
+ * @returns A Promise resolving to the file contents in the specified encoding
42
+ * @throws Error if the file is not found in the ZIP
43
+ */
44
+ static readFileFromZip<T extends Parameters<JSZip.JSZipObject['async']>[0]>(zipFilePath: File | string | URL, fileName: string, encoding: T): Promise<ReturnType<JSZip.JSZipObject['async']> extends Promise<infer R> ? R : never>;
45
+ /**
46
+ * Unzips a ZIP file from a File (browser), string path (Node.js), or URL.
47
+ * @param zipFilePath The path, URL, or File object representing the ZIP file to extract.
48
+ * @returns A Promise that resolves to a JSZip instance representing the unzipped contents.
49
+ */
50
+ static unzip(zipFilePath: File | string | URL): Promise<JSZip>;
51
+ /**
52
+ * Updates a CSV string with an object.
53
+ * @param csvString The CSV string to update
54
+ * @param column The column name to update
55
+ * @param row The row index to update
56
+ * @param value The value to update the column with
57
+ * @returns A Promise resolving to the updated CSV string
58
+ */
59
+ static updateCsvField<T>(params: UpdateCsvFieldParams[]): Promise<string>;
60
+ /**
61
+ * Parses a CSV string into an array of objects using PapaParse.
62
+ * @param csvString The CSV string to parse
63
+ * @param options Parse configuration options
64
+ * @param options.header Whether to interpret first row as field names. Defaults to true.
65
+ * @param options.skipEmptyLines Whether to skip empty lines in the CSV. Defaults to true.
66
+ * @param options.rest Additional PapaParse configuration options
67
+ * @returns Promise resolving to array of parsed objects
68
+ * @throws Error if parsing fails with details of parsing errors
69
+ */
70
+ static parseCsv<T>(csvString: string, { header, skipEmptyLines, ...options }: ParseConfig<T>): Promise<T[]>;
71
+ /**
72
+ * Zips multiple files into a ZIP archive.
73
+ * @param files An object where keys are filenames and values are either File (browser) or Buffer/Uint8Array (Node.js).
74
+ * @returns A Promise resolving to a Uint8Array representing the ZIP file content.
75
+ */
76
+ static zip(files: Record<string, Buffer | File | Uint8Array>): Promise<Uint8Array>;
77
+ }
78
+ export {};
package/dist/files.js ADDED
@@ -0,0 +1,171 @@
1
+ /* * */
2
+ import { mimeTypes } from '@tmlmobilidade/consts';
3
+ import JSZip from 'jszip';
4
+ import papaparse from 'papaparse';
5
+ import { fetchZipFromUrl } from './helpers/fetch-zip-from-url.js';
6
+ import { isBrowser } from './helpers/is-browser.js';
7
+ import { normalizeFileContent } from './helpers/normalize-file-content.js';
8
+ import { readZipFromFile } from './helpers/read-zip-from-file.js';
9
+ /* * */
10
+ export class Files {
11
+ //
12
+ /**
13
+ * Converts a Blob to a File.
14
+ * @param blob The Blob object to convert.
15
+ * @param fileName The name of the resulting File.
16
+ * @returns The resulting File.
17
+ */
18
+ static blobToFile(blob, fileName) {
19
+ return new File([blob], fileName);
20
+ }
21
+ /**
22
+ * Returns the file extension from a file name.
23
+ * @param fileName The name of the file.
24
+ * @returns The file extension.
25
+ * @throws Error if the file has no extension or if the extension is not supported.
26
+ */
27
+ static getFileExtension(fileName) {
28
+ // Extract the file extension from the file name.
29
+ const extension = fileName.split('.').pop()?.toLowerCase();
30
+ // Throw an error if the file has no extension
31
+ // or if the extension is not supported.
32
+ if (!extension)
33
+ throw new Error('File has no extension');
34
+ // Get the MIME type for the extension.
35
+ const mimeType = mimeTypes[extension];
36
+ // Throw an error if the extension is not supported.
37
+ if (!mimeType)
38
+ throw new Error(`Unsupported file extension: ${extension}`);
39
+ // Return the file extension.
40
+ return extension;
41
+ }
42
+ /**
43
+ * Gets the file extension from a MIME type.
44
+ * @param mimeType The MIME type to get the file extension for.
45
+ * @returns The file extension, or an empty string if not found.
46
+ */
47
+ static getFileExtensionFromMimeType(mimeType) {
48
+ if (!mimeType)
49
+ return '';
50
+ const extension = Object.keys(mimeTypes).find(key => mimeTypes[key] === mimeType);
51
+ if (!extension)
52
+ return '';
53
+ return extension;
54
+ }
55
+ /**
56
+ * Gets the MIME type from a file extension.
57
+ * @param fileName The name of the file to get the MIME type for.
58
+ * @returns The MIME type.
59
+ */
60
+ static getMimeTypeFromFileExtension(fileName) {
61
+ const extension = Files.getFileExtension(fileName);
62
+ return mimeTypes[extension];
63
+ }
64
+ /**
65
+ * Reads and extracts a single file from a ZIP archive.
66
+ * @param zipFilePath The zip file to read from, can be a File object (browser), string path (Node.js), or URL
67
+ * @param fileName The name of the file to extract from the ZIP
68
+ * @param encoding The encoding to use when reading the file. See JSZip documentation for supported formats.
69
+ * @returns A Promise resolving to the file contents in the specified encoding
70
+ * @throws Error if the file is not found in the ZIP
71
+ */
72
+ static async readFileFromZip(zipFilePath, fileName, encoding) {
73
+ const zip = await Files.unzip(zipFilePath);
74
+ const file = zip.file(fileName);
75
+ if (!file)
76
+ throw new Error(`File ${fileName} not found in the zip archive.`);
77
+ return await file.async(encoding);
78
+ }
79
+ /**
80
+ * Unzips a ZIP file from a File (browser), string path (Node.js), or URL.
81
+ * @param zipFilePath The path, URL, or File object representing the ZIP file to extract.
82
+ * @returns A Promise that resolves to a JSZip instance representing the unzipped contents.
83
+ */
84
+ static async unzip(zipFilePath) {
85
+ try {
86
+ let data = null;
87
+ if (isBrowser && zipFilePath instanceof File) {
88
+ data = await zipFilePath.arrayBuffer();
89
+ }
90
+ if (typeof zipFilePath === 'string' || zipFilePath instanceof URL) {
91
+ const pathOrUrl = zipFilePath.toString();
92
+ if (isBrowser || pathOrUrl.startsWith('http')) {
93
+ data = await fetchZipFromUrl(pathOrUrl);
94
+ }
95
+ else {
96
+ data = await readZipFromFile(pathOrUrl);
97
+ }
98
+ }
99
+ if (!data || data.byteLength === 0) {
100
+ throw new Error('ZIP file is empty');
101
+ }
102
+ const zip = await JSZip.loadAsync(data);
103
+ if (Object.keys(zip.files).length === 0) {
104
+ throw new Error('ZIP file contains no files');
105
+ }
106
+ return zip;
107
+ }
108
+ catch (error) {
109
+ if (error instanceof Error && error.message.includes('Central Directory')) {
110
+ throw new Error('Invalid or corrupted ZIP file', error);
111
+ }
112
+ throw error;
113
+ }
114
+ }
115
+ /**
116
+ * Updates a CSV string with an object.
117
+ * @param csvString The CSV string to update
118
+ * @param column The column name to update
119
+ * @param row The row index to update
120
+ * @param value The value to update the column with
121
+ * @returns A Promise resolving to the updated CSV string
122
+ */
123
+ static async updateCsvField(params) {
124
+ let csv = params[0].csvString;
125
+ for (const param of params) {
126
+ const data = await this.parseCsv(csv, { header: true });
127
+ const updatedData = data.map((row, index) => (index === param.rowIndex ? { ...row, [param.column]: param.value } : row));
128
+ csv = papaparse.unparse(updatedData, { header: true });
129
+ }
130
+ return csv;
131
+ }
132
+ /**
133
+ * Parses a CSV string into an array of objects using PapaParse.
134
+ * @param csvString The CSV string to parse
135
+ * @param options Parse configuration options
136
+ * @param options.header Whether to interpret first row as field names. Defaults to true.
137
+ * @param options.skipEmptyLines Whether to skip empty lines in the CSV. Defaults to true.
138
+ * @param options.rest Additional PapaParse configuration options
139
+ * @returns Promise resolving to array of parsed objects
140
+ * @throws Error if parsing fails with details of parsing errors
141
+ */
142
+ static async parseCsv(csvString, { header = true, skipEmptyLines = true, ...options }) {
143
+ const parse = papaparse.parse(csvString, { header, skipEmptyLines, ...options });
144
+ if (parse.errors.length > 0) {
145
+ throw new Error(`Failed to parse CSV: ${parse.errors.map(error => `${error.message} [${error.code}]`).join(', ')}`);
146
+ }
147
+ return parse.data;
148
+ }
149
+ /**
150
+ * Zips multiple files into a ZIP archive.
151
+ * @param files An object where keys are filenames and values are either File (browser) or Buffer/Uint8Array (Node.js).
152
+ * @returns A Promise resolving to a Uint8Array representing the ZIP file content.
153
+ */
154
+ static async zip(files) {
155
+ try {
156
+ const zip = new JSZip();
157
+ await Promise.all(Object.entries(files).map(async ([filename, content]) => {
158
+ const fileData = await normalizeFileContent(content);
159
+ zip.file(filename, fileData);
160
+ }));
161
+ const zipContent = await zip.generateAsync({ type: 'uint8array' });
162
+ if (!zipContent || zipContent.length === 0) {
163
+ throw new Error('Failed to generate ZIP: output is empty');
164
+ }
165
+ return zipContent;
166
+ }
167
+ catch (error) {
168
+ throw new Error(`Failed to create ZIP archive: ${error.message}`, error);
169
+ }
170
+ }
171
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Fetches a ZIP file from a URL and returns it as an ArrayBuffer.
3
+ * @param url The URL of the ZIP file to fetch.
4
+ * @returns A Promise that resolves to an ArrayBuffer containing the ZIP file data.
5
+ * @throws If the HTTP request fails or returns a non-200 status code.
6
+ */
7
+ export declare function fetchZipFromUrl(url: string): Promise<ArrayBuffer>;
@@ -0,0 +1,13 @@
1
+ /* * */
2
+ /**
3
+ * Fetches a ZIP file from a URL and returns it as an ArrayBuffer.
4
+ * @param url The URL of the ZIP file to fetch.
5
+ * @returns A Promise that resolves to an ArrayBuffer containing the ZIP file data.
6
+ * @throws If the HTTP request fails or returns a non-200 status code.
7
+ */
8
+ export async function fetchZipFromUrl(url) {
9
+ const response = await fetch(url);
10
+ if (!response.ok)
11
+ throw new Error(`Failed to fetch ZIP file: HTTP ${response.status} - ${response.statusText}`);
12
+ return await response.arrayBuffer();
13
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Gets the temporary working directory path for a given ID.
3
+ * @param id The ID to get the temporary working directory path for.
4
+ * @returns The temporary working directory path.
5
+ */
6
+ export declare function getTmpWorkdirPath(id?: string, createIfNotExists?: boolean): string;
@@ -0,0 +1,33 @@
1
+ /* * */
2
+ import { generateRandomString } from '@tmlmobilidade/strings';
3
+ import fs from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { isBrowser } from './is-browser.js';
7
+ /**
8
+ * Gets the temporary working directory path for a given ID.
9
+ * @param id The ID to get the temporary working directory path for.
10
+ * @returns The temporary working directory path.
11
+ */
12
+ export function getTmpWorkdirPath(id, createIfNotExists) {
13
+ //
14
+ if (isBrowser) {
15
+ throw new Error('getTmpWorkdirPath is not supported in the browser');
16
+ }
17
+ //
18
+ // Use the system temporary directory and create
19
+ // a subdirectory based on the ID, if any was provided.
20
+ // Otherwise, use a random ID for the subdirectory.
21
+ const osTmpDir = tmpdir();
22
+ const workdirName = id ? id : generateRandomString();
23
+ const workdirPath = join(osTmpDir, encodeURIComponent(workdirName));
24
+ //
25
+ // If the createIfNotExists flag is set to true,
26
+ // create the directory if it doesn't exist.
27
+ if (createIfNotExists && !fs.existsSync(workdirPath)) {
28
+ fs.mkdirSync(workdirPath, { recursive: true });
29
+ }
30
+ //
31
+ // Return the temporary working directory path.
32
+ return workdirPath;
33
+ }
@@ -0,0 +1,5 @@
1
+ export * from './fetch-zip-from-url.js';
2
+ export * from './get-tmp-workdir-path.js';
3
+ export * from './is-browser.js';
4
+ export * from './normalize-file-content.js';
5
+ export * from './read-zip-from-file.js';
@@ -0,0 +1,5 @@
1
+ export * from './fetch-zip-from-url.js';
2
+ export * from './get-tmp-workdir-path.js';
3
+ export * from './is-browser.js';
4
+ export * from './normalize-file-content.js';
5
+ export * from './read-zip-from-file.js';
@@ -0,0 +1 @@
1
+ export declare const isBrowser: boolean;
@@ -0,0 +1,2 @@
1
+ /* * */
2
+ export const isBrowser = typeof globalThis === 'object' && 'window' in globalThis;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Converts the input file data into an ArrayBuffer or Uint8Array suitable for JSZip.
3
+ * @param content A File (browser) or Buffer/Uint8Array (Node.js)
4
+ */
5
+ export declare function normalizeFileContent(content: Buffer | File | Uint8Array): Promise<ArrayBuffer | Uint8Array>;
@@ -0,0 +1,15 @@
1
+ /* * */
2
+ import { isBrowser } from './is-browser.js';
3
+ /**
4
+ * Converts the input file data into an ArrayBuffer or Uint8Array suitable for JSZip.
5
+ * @param content A File (browser) or Buffer/Uint8Array (Node.js)
6
+ */
7
+ export async function normalizeFileContent(content) {
8
+ if (isBrowser && content instanceof File) {
9
+ return await content.arrayBuffer();
10
+ }
11
+ if (content instanceof Buffer || content instanceof Uint8Array) {
12
+ return content;
13
+ }
14
+ throw new TypeError('Unsupported file content type for zipping.');
15
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Reads a ZIP file from the local filesystem and returns it as an ArrayBuffer.
3
+ * @param path The file system path to the ZIP file.
4
+ * @returns A Promise that resolves to an ArrayBuffer containing the ZIP file data.
5
+ * @throws If there is an error reading the file or if the function is called in a browser environment.
6
+ */
7
+ export declare function readZipFromFile(path: string): Promise<ArrayBuffer>;
@@ -0,0 +1,24 @@
1
+ /* * */
2
+ import { isBrowser } from './is-browser.js';
3
+ /**
4
+ * Reads a ZIP file from the local filesystem and returns it as an ArrayBuffer.
5
+ * @param path The file system path to the ZIP file.
6
+ * @returns A Promise that resolves to an ArrayBuffer containing the ZIP file data.
7
+ * @throws If there is an error reading the file or if the function is called in a browser environment.
8
+ */
9
+ export async function readZipFromFile(path) {
10
+ //
11
+ if (isBrowser) {
12
+ throw new Error('readZipFromFile is not supported in the browser');
13
+ }
14
+ //
15
+ // Only require fs/promises in Node.js
16
+ const { readFile } = await (Function('return import("fs/promises")')());
17
+ try {
18
+ const buffer = await readFile(path);
19
+ return buffer.buffer;
20
+ }
21
+ catch (error) {
22
+ throw new Error(`Failed to read ZIP file: ${error.message}`, error);
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ export * from './files.js';
2
+ export * from './helpers/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './files.js';
2
+ export * from './helpers/index.js';
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@tmlmobilidade/go-utils-files",
3
+ "version": "20260904.1059.6",
4
+ "author": {
5
+ "email": "iso@tmlmobilidade.pt",
6
+ "name": "TML-ISO"
7
+ },
8
+ "license": "AGPL-3.0-or-later",
9
+ "homepage": "https://go.tmlmobilidade.pt",
10
+ "bugs": {
11
+ "url": "https://github.com/tmlmobilidade/go/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/tmlmobilidade/go.git"
16
+ },
17
+ "keywords": [
18
+ "public transit",
19
+ "tml",
20
+ "transportes metropolitanos de lisboa",
21
+ "go"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "module",
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "scripts": {
33
+ "build": "tsc && resolve-tspaths",
34
+ "lint": "eslint ./src/ && tsc --noEmit",
35
+ "lint:fix": "eslint ./src/ --fix",
36
+ "watch": "tsc-watch --onSuccess 'resolve-tspaths'"
37
+ },
38
+ "dependencies": {
39
+ "@tmlmobilidade/consts": "*",
40
+ "@tmlmobilidade/strings": "*",
41
+ "jszip": "3.10.1",
42
+ "papaparse": "5.7.0"
43
+ },
44
+ "devDependencies": {
45
+ "@tmlmobilidade/go-utils-tsconfig": "*",
46
+ "@types/node": "26.4.0",
47
+ "resolve-tspaths": "0.8.23",
48
+ "tsc-watch": "7.2.1",
49
+ "typescript": "6.0.3"
50
+ }
51
+ }