node-server-engine 1.2.4 → 1.2.5

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.
Files changed (35) hide show
  1. package/.env.example +1 -4
  2. package/README.md +171 -0
  3. package/dist/entities/Server/Server.js +0 -10
  4. package/dist/entities/Server/Server.js.map +1 -1
  5. package/dist/middleware/geminiFileUploader/geminiFileUploader.js +21 -69
  6. package/dist/middleware/geminiFileUploader/geminiFileUploader.js.map +1 -1
  7. package/dist/middleware/index.d.ts +1 -0
  8. package/dist/middleware/index.js +11 -0
  9. package/dist/middleware/index.js.map +1 -1
  10. package/dist/middleware/multiPartFileUploader/index.d.ts +1 -0
  11. package/dist/middleware/multiPartFileUploader/index.js +17 -0
  12. package/dist/middleware/multiPartFileUploader/index.js.map +1 -0
  13. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.d.ts +3 -0
  14. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.js +156 -0
  15. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.js.map +1 -0
  16. package/dist/utils/createHttpServer/createHttpServer.js.map +1 -1
  17. package/dist/utils/geminiFileUpload/geminiFileUpload.d.ts +9 -0
  18. package/dist/utils/geminiFileUpload/geminiFileUpload.js +100 -0
  19. package/dist/utils/geminiFileUpload/geminiFileUpload.js.map +1 -0
  20. package/dist/utils/geminiFileUpload/index.d.ts +1 -0
  21. package/dist/utils/geminiFileUpload/index.js +17 -0
  22. package/dist/utils/geminiFileUpload/index.js.map +1 -0
  23. package/dist/utils/index.d.ts +1 -0
  24. package/dist/utils/index.js +11 -0
  25. package/dist/utils/index.js.map +1 -1
  26. package/package.json +3 -1
  27. package/src/entities/Server/Server.ts +0 -6
  28. package/src/middleware/geminiFileUploader/geminiFileUploader.ts +19 -68
  29. package/src/middleware/index.ts +1 -0
  30. package/src/middleware/multiPartFileUploader/index.ts +1 -0
  31. package/src/middleware/multiPartFileUploader/multiPartFileUploader.ts +184 -0
  32. package/src/utils/createHttpServer/createHttpServer.ts +2 -3
  33. package/src/utils/geminiFileUpload/geminiFileUpload.ts +98 -0
  34. package/src/utils/geminiFileUpload/index.ts +1 -0
  35. package/src/utils/index.ts +3 -0
@@ -0,0 +1,184 @@
1
+ import express from 'express';
2
+ import multer from 'multer';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ import { reportDebug, reportError } from 'utils';
6
+
7
+ const TEMP_DIR: string =
8
+ process.env.TEMP_CHUNK_FOLDER || './uploads/temp_chunks';
9
+
10
+ const namespace = 'engine:middleware:multiPartFileUploader';
11
+
12
+ fs.ensureDirSync(TEMP_DIR);
13
+
14
+ const storage = multer.memoryStorage();
15
+
16
+ async function multiPartFileUploader(
17
+ req: express.Request,
18
+ res: express.Response,
19
+ next: express.NextFunction
20
+ ): Promise<void> {
21
+ try {
22
+ const uploadSingle = multer({ storage }).single('file');
23
+
24
+ uploadSingle(req, res, async (err) => {
25
+ if (err) {
26
+ reportError('Error uploading chunk');
27
+ return next(err);
28
+ }
29
+
30
+ const { filename, totalChunks, chunkIndex, uniqueID } = req.body;
31
+
32
+ if (
33
+ !filename ||
34
+ !totalChunks ||
35
+ chunkIndex === undefined ||
36
+ !uniqueID ||
37
+ !req.file
38
+ ) {
39
+ req.body.uploadDetails = {
40
+ status: 'failed',
41
+ message: 'Missing required fields',
42
+ details: {
43
+ filename,
44
+ totalChunks,
45
+ chunkIndex,
46
+ uniqueID,
47
+ receivedChunk: !!req.file
48
+ }
49
+ };
50
+ reportDebug({
51
+ namespace,
52
+ message: `Missing required fields`,
53
+ data: {
54
+ status: 'failed',
55
+ details: {
56
+ filename,
57
+ totalChunks,
58
+ chunkIndex,
59
+ uniqueID
60
+ }
61
+ }
62
+ });
63
+ return next();
64
+ }
65
+
66
+ const sessionFolder = path.join(TEMP_DIR, uniqueID);
67
+ fs.ensureDirSync(sessionFolder);
68
+
69
+ const chunkPath = path.join(
70
+ sessionFolder,
71
+ `${filename}.part${chunkIndex}`
72
+ );
73
+
74
+ try {
75
+ await fs.writeFile(chunkPath, req.file.buffer);
76
+ } catch (writeErr) {
77
+ reportError(
78
+ `Failed to write chunk ${chunkIndex}: ${(writeErr as Error).message}`
79
+ );
80
+ return next(writeErr);
81
+ }
82
+
83
+ const uploadedChunks = await fs.readdir(sessionFolder);
84
+
85
+ if (uploadedChunks.length === parseInt(totalChunks, 10)) {
86
+ reportDebug({
87
+ namespace,
88
+ message: `All chunks uploaded. Merging file...`
89
+ });
90
+
91
+ try {
92
+ await mergeChunks(filename, totalChunks, sessionFolder);
93
+ } catch (mergeErr) {
94
+ reportError(`File merging failed: ${(mergeErr as Error).message}`);
95
+ return next(mergeErr);
96
+ }
97
+
98
+ const finalFilePath = path.join(sessionFolder, filename);
99
+
100
+ if (!fs.existsSync(finalFilePath)) {
101
+ reportError('File merging failed: File does not exist');
102
+ return next(new Error('File merging failed'));
103
+ }
104
+
105
+ req.body.uploadDetails = {
106
+ status: 'completed',
107
+ message: 'File uploaded and merged successfully',
108
+ details: { filename, uniqueID }
109
+ };
110
+ reportDebug({
111
+ namespace,
112
+ message: `File merged successfully`,
113
+ data: {
114
+ status: 'completed',
115
+ details: { filename, uniqueID }
116
+ }
117
+ });
118
+
119
+ return next();
120
+ }
121
+
122
+ reportDebug({
123
+ namespace,
124
+ message: `Chunk uploaded successfully`,
125
+ data: {
126
+ filename,
127
+ uniqueID,
128
+ chunkIndex,
129
+ totalChunks
130
+ }
131
+ });
132
+
133
+ req.body.uploadDetails = {
134
+ status: 'pending',
135
+ message: 'Chunk uploaded successfully',
136
+ details: { filename, uniqueID, chunkIndex, totalChunks }
137
+ };
138
+
139
+ return next();
140
+ });
141
+ } catch (error) {
142
+ reportError(
143
+ `Failed to upload or process chunk: ${(error as Error).message}`
144
+ );
145
+ next(error);
146
+ }
147
+ }
148
+
149
+ async function mergeChunks(
150
+ filename: string,
151
+ totalChunks: string | number,
152
+ sessionFolder: string
153
+ ): Promise<void> {
154
+ const finalFilePath = path.join(sessionFolder, filename);
155
+ const writeStream = fs.createWriteStream(finalFilePath);
156
+
157
+ for (let i = 0; i < parseInt(totalChunks as string, 10); i++) {
158
+ const partPath = path.join(sessionFolder, `${filename}.part${i}`);
159
+
160
+ try {
161
+ const chunkData = await fs.readFile(partPath);
162
+ writeStream.write(chunkData);
163
+ } catch (error) {
164
+ reportError(`Error reading chunk ${i}: ${(error as Error).message}`);
165
+ throw error;
166
+ }
167
+
168
+ fs.remove(partPath).catch((err: Error) =>
169
+ reportError(`Error removing part ${partPath}: ${err.message}`)
170
+ );
171
+ }
172
+
173
+ writeStream.end();
174
+
175
+ return new Promise<void>((resolve, reject) => {
176
+ writeStream.on('finish', () => resolve(undefined));
177
+ writeStream.on('error', (error) => {
178
+ reportError(`Error merging chunks: ${(error as Error).message}`);
179
+ reject(error);
180
+ });
181
+ });
182
+ }
183
+
184
+ export { multiPartFileUploader };
@@ -1,6 +1,5 @@
1
1
  import fs from 'fs';
2
- import { RequestListener, Server } from 'http';
3
- import { createServer as createServerHttp2 } from 'http';
2
+ import { createServer, RequestListener, Server } from 'http';
4
3
  import {
5
4
  createServer as createSecureServer,
6
5
  Server as SecureServer
@@ -30,6 +29,6 @@ export function createHttpServer(
30
29
  handler
31
30
  );
32
31
  } else {
33
- return createServerHttp2(handler);
32
+ return createServer(handler);
34
33
  }
35
34
  }
@@ -0,0 +1,98 @@
1
+ import { GoogleAIFileManager } from '@google/generative-ai/server';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import tmp from 'tmp';
5
+ import { reportError, reportDebug } from 'utils/report';
6
+
7
+ const namespace = 'engine:utils:geminiFileUpload';
8
+
9
+ const apiKey = process.env.GOOGLE_AI_KEY;
10
+ if (!apiKey) {
11
+ throw new Error('GOOGLE_AI_KEY is not set in environment variables');
12
+ }
13
+
14
+ const fileManager = new GoogleAIFileManager(apiKey);
15
+
16
+ export async function geminiFileUpload(
17
+ buffer: Buffer,
18
+ mimeType: string,
19
+ originalName: string
20
+ ): Promise<
21
+ | { success: true; originalname: string; fileUri: string; mimeType: string }
22
+ | { success: false; error: unknown }
23
+ > {
24
+ let tempFile;
25
+ try {
26
+ // 1. Create Temp File
27
+ tempFile = tmp.fileSync({ postfix: path.extname(originalName) });
28
+ fs.writeFileSync(tempFile.name, buffer);
29
+
30
+ // 2. Upload File
31
+ const uploadResult = await fileManager.uploadFile(tempFile.name, {
32
+ mimeType,
33
+ displayName: originalName
34
+ });
35
+
36
+ if (!uploadResult || !uploadResult.file) {
37
+ throw new Error('Upload failed: No file returned');
38
+ }
39
+
40
+ const file = uploadResult.file;
41
+ reportDebug({
42
+ namespace,
43
+ message: 'Uploaded file successfully',
44
+ data: { displayName: file.displayName, name: file.name }
45
+ });
46
+
47
+ reportDebug({
48
+ namespace,
49
+ message: 'Waiting for file processing...'
50
+ });
51
+
52
+ // 3. Poll Until File is Processed
53
+ let geminiFile = await fileManager.getFile(file.name);
54
+ while (geminiFile?.state === 'PROCESSING') {
55
+ process.stdout.write('.');
56
+ await new Promise((resolve) => setTimeout(resolve, 10000));
57
+ geminiFile = await fileManager.getFile(file.name);
58
+ }
59
+
60
+ if (!geminiFile || geminiFile.state !== 'ACTIVE') {
61
+ const errorMessage = `File processing failed for ${file.name}`;
62
+ reportError(errorMessage);
63
+ return { success: false, error: new Error(errorMessage) };
64
+ }
65
+
66
+ reportDebug({
67
+ namespace,
68
+ message: 'File is ready'
69
+ });
70
+
71
+ return {
72
+ success: true,
73
+ originalname: file.name,
74
+ fileUri: file.uri,
75
+ mimeType: file.mimeType
76
+ };
77
+ } catch (error) {
78
+ reportError({
79
+ namespace,
80
+ message: 'Error during Gemini file upload and processing',
81
+ error
82
+ });
83
+ return { success: false, error };
84
+ } finally {
85
+ // Ensure cleanup of temp file
86
+ if (tempFile) {
87
+ try {
88
+ tempFile.removeCallback();
89
+ } catch (cleanupError) {
90
+ reportError({
91
+ namespace,
92
+ message: 'Error cleaning up temp file',
93
+ error: cleanupError
94
+ });
95
+ }
96
+ }
97
+ }
98
+ }
@@ -0,0 +1 @@
1
+ export * from './geminiFileUpload';
@@ -23,3 +23,6 @@ export * from './sendEmail';
23
23
 
24
24
  // String manipulation
25
25
  export * from './trimPathSlash';
26
+
27
+ // File Upload
28
+ export * from './geminiFileUpload';