@quatrain/worker 1.2.10 → 1.2.12

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.
@@ -37,7 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.FileSystem = void 0;
40
- const node_fs_1 = __importStar(require("node:fs"));
40
+ const node_fs_1 = __importDefault(require("node:fs"));
41
41
  const node_path_1 = __importDefault(require("node:path"));
42
42
  const axios_1 = __importDefault(require("axios"));
43
43
  const Worker_1 = require("./Worker");
@@ -141,63 +141,26 @@ class FileSystem {
141
141
  meta = { ...meta, ...info };
142
142
  Worker_1.Worker.info(`Uploading file ${filename} to ${meta.uploadUrl}`);
143
143
  const { size } = node_fs_1.default.statSync(filename);
144
- if (size < 32 * 1024) {
145
- return new Promise((resolve, reject) => {
146
- (0, node_fetch_native_1.default)(meta.uploadUrl, {
147
- method: 'PUT',
148
- mode: 'cors',
149
- duplex: 'half',
150
- body: (0, node_fs_1.readFileSync)(filename),
151
- headers: {
152
- 'Content-Type': meta.contentType || mime,
153
- 'Content-length': String(size),
154
- },
155
- })
156
- .then(() => resolve({ ...meta, size, uploadUrl: undefined }))
157
- .catch((err) => {
158
- Worker_1.Worker.error(`An error occured while uploading ${filename}: ${err.message}`);
159
- reject(err);
160
- });
144
+ try {
145
+ Worker_1.Worker.info(`Uploading file ${filename} with size ${size} bytes`);
146
+ const fileBuffer = node_fs_1.default.readFileSync(filename);
147
+ const res = await (0, node_fetch_native_1.default)(meta.uploadUrl, {
148
+ method: 'PUT',
149
+ body: fileBuffer,
150
+ headers: {
151
+ 'Content-Type': meta.contentType || mime,
152
+ },
161
153
  });
162
- }
163
- return new Promise((resolve, reject) => {
164
- try {
165
- let done = 0; // total bytes uploaded
166
- let prev = 0; // latest value of done stored when % done was displayed
167
- const { size } = node_fs_1.default.statSync(filename);
168
- const readStream = node_fs_1.default.createReadStream(filename);
169
- const sizeMB = (size / (1024 * 1024)).toFixed(2);
170
- readStream.on('data', (data) => {
171
- done += data.length;
172
- if (done - prev >= size / 20) {
173
- // only display message when 5% more has been uploaded
174
- Worker_1.Worker.info(`${((done / size) * 100).toFixed(2)}% - ${(done /
175
- (1024 * 1024)).toFixed(2)}MB / ${sizeMB}MB`);
176
- prev = done;
177
- }
178
- });
179
- Worker_1.Worker.info(`Uploading file ${filename} with size ${size} bytes`);
180
- (0, node_fetch_native_1.default)(meta.uploadUrl, {
181
- method: 'PUT',
182
- mode: 'cors',
183
- duplex: 'half',
184
- body: readStream,
185
- headers: {
186
- 'Content-Type': meta.contentType || mime,
187
- 'Content-length': String(size),
188
- },
189
- })
190
- .then(() => resolve({ ...meta, size, uploadUrl: undefined }))
191
- .catch((err) => {
192
- Worker_1.Worker.error(`An error occured while uploading ${filename}: ${err.message}`);
193
- reject(err);
194
- });
154
+ if (res && res.ok === false) {
155
+ const errorText = typeof res.text === 'function' ? await res.text().catch(() => '') : '';
156
+ throw new Error(`HTTP upload failed with status ${res.status || 'unknown'}: ${errorText}`);
195
157
  }
196
- catch (err) {
197
- Worker_1.Worker.error(err);
198
- reject(err);
199
- }
200
- });
158
+ return { ...meta, size, uploadUrl: undefined };
159
+ }
160
+ catch (err) {
161
+ Worker_1.Worker.error(`An error occured while uploading ${filename}: ${err.message}`);
162
+ throw err;
163
+ }
201
164
  }
202
165
  /**
203
166
  * Return meta data on given file
@@ -94,6 +94,12 @@ describe('FileSystem Utilities (Worker)', () => {
94
94
  jest.spyOn(node_fs_1.default, 'createReadStream').mockImplementation(() => {
95
95
  return node_stream_1.Readable.from(Buffer.alloc(35 * 1024));
96
96
  });
97
+ jest.spyOn(node_fs_1.default, 'readFileSync').mockImplementation((p) => {
98
+ if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('small-fail.mp4') || p.endsWith('large-sync-fail.mp4'))) {
99
+ return Buffer.alloc(35 * 1024);
100
+ }
101
+ return (jest.requireActual('node:fs').readFileSync)(p);
102
+ });
97
103
  jest.spyOn(node_fs_1.default, 'existsSync').mockImplementation((p) => {
98
104
  if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('small-fail.mp4') || p.endsWith('large-sync-fail.mp4'))) {
99
105
  return true;
@@ -330,8 +336,8 @@ describe('FileSystem Utilities (Worker)', () => {
330
336
  ffmpeg.ffprobe.mockImplementation((file, callback) => {
331
337
  callback(null, mockMetadata);
332
338
  });
333
- // Make createReadStream throw a synchronous error
334
- jest.spyOn(node_fs_1.default, 'createReadStream').mockImplementationOnce(() => {
339
+ // Make readFileSync throw a synchronous error
340
+ jest.spyOn(node_fs_1.default, 'readFileSync').mockImplementationOnce(() => {
335
341
  throw new Error('Sync stream error');
336
342
  });
337
343
  const meta = {
package/dist/Helpers.d.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  * General utility class providing common static helpers for workers.
3
3
  */
4
4
  export declare class Helpers {
5
+ private static _ffmpegPathPromise;
5
6
  /** Default absolute path to the system's FFmpeg binary. */
6
- static FFMPEG: Promise<string>;
7
+ static get FFMPEG(): Promise<string>;
7
8
  /**
8
9
  * Generate a thubnail from a video file at given frame position
9
10
  * @param videoPath path to video file
package/dist/Helpers.js CHANGED
@@ -6,8 +6,14 @@ const Worker_1 = require("./Worker");
6
6
  * General utility class providing common static helpers for workers.
7
7
  */
8
8
  class Helpers {
9
+ static _ffmpegPathPromise = null;
9
10
  /** Default absolute path to the system's FFmpeg binary. */
10
- static FFMPEG = Worker_1.Worker.getSystemCommandPath('ffmpeg');
11
+ static get FFMPEG() {
12
+ if (!this._ffmpegPathPromise) {
13
+ this._ffmpegPathPromise = Worker_1.Worker.getSystemCommandPath('ffmpeg').catch(() => 'ffmpeg');
14
+ }
15
+ return this._ffmpegPathPromise;
16
+ }
11
17
  /**
12
18
  * Generate a thubnail from a video file at given frame position
13
19
  * @param videoPath path to video file
package/dist/Worker.d.ts CHANGED
@@ -25,7 +25,7 @@ export declare class Worker extends Core {
25
25
  * @param ts timestamp
26
26
  * @returns boolean
27
27
  */
28
- static pushEvent(event: string, data?: {}, ts?: number): false | undefined;
28
+ static pushEvent(event: string, data?: {}, ts?: number): void;
29
29
  /**
30
30
  * Async Push an event to the backend endpoint, if available
31
31
  * @param event string
@@ -33,7 +33,7 @@ export declare class Worker extends Core {
33
33
  * @param ts timestamp
34
34
  * @returns boolean
35
35
  */
36
- static pushEventAsync(event: string, data?: {}, ts?: number): Promise<boolean | undefined>;
36
+ static pushEventAsync(event: string, data?: {}, ts?: number): Promise<true | undefined>;
37
37
  /**
38
38
  * Global handling function to process received messages
39
39
  * @param messageHandler function
package/dist/Worker.js CHANGED
@@ -59,8 +59,8 @@ class Worker extends core_1.Core {
59
59
  */
60
60
  static pushEvent(event, data = {}, ts = 0) {
61
61
  if (!this.endpoint) {
62
- Worker.warn(`Events endpoint is not set, can't send update!`);
63
- return false;
62
+ Worker.error(`Events endpoint is mandatory but missing! Cannot report job status to source.`);
63
+ throw new Error(`Events endpoint is mandatory but missing`);
64
64
  }
65
65
  ts = ts === Date.now() ? Date.now() + 1 : Date.now();
66
66
  const payload = {
@@ -79,8 +79,7 @@ class Worker extends core_1.Core {
79
79
  return true;
80
80
  })
81
81
  .catch((err) => {
82
- Worker.error(`Failed to push event to backend: ${err}`);
83
- return false;
82
+ Worker.error(`Failed to push event to backend: ${err.message}`);
84
83
  });
85
84
  }
86
85
  /**
@@ -92,8 +91,8 @@ class Worker extends core_1.Core {
92
91
  */
93
92
  static async pushEventAsync(event, data = {}, ts = 0) {
94
93
  if (!this.endpoint) {
95
- Worker.warn(`Events endpoint is not set, can't send update!`);
96
- return false;
94
+ Worker.error(`Events endpoint is mandatory but missing! Cannot report job status to source.`);
95
+ throw new Error(`Events endpoint is mandatory but missing`);
97
96
  }
98
97
  try {
99
98
  ts = ts === Date.now() ? Date.now() + 1 : Date.now();
@@ -105,14 +104,14 @@ class Worker extends core_1.Core {
105
104
  ts,
106
105
  };
107
106
  const res = await axios_1.default.patch(Worker.endpoint, payload);
108
- if (res.statusText === 'OK') {
107
+ if (res.statusText === 'OK' || res.status === 200) {
109
108
  Worker.info(`Event pushed to backend: ${res.statusText}`);
110
109
  return true;
111
110
  }
112
111
  }
113
112
  catch (err) {
114
- Worker.error(`Failed to push event to backend: ${err}`);
115
- return false;
113
+ Worker.error(`Failed to push event to backend: ${err.message}`);
114
+ throw new Error(`Failed to push event to backend: ${err.message}`);
116
115
  }
117
116
  }
118
117
  /**
@@ -40,9 +40,8 @@ describe('Worker', () => {
40
40
  });
41
41
  });
42
42
  describe('pushEvent', () => {
43
- it('should return false when endpoint is not set', () => {
44
- const result = Worker_1.Worker.pushEvent('test-event');
45
- expect(result).toBe(false);
43
+ it('should throw error when endpoint is not set', () => {
44
+ expect(() => Worker_1.Worker.pushEvent('test-event')).toThrow('Events endpoint is mandatory but missing');
46
45
  expect(mockedAxios.patch).not.toHaveBeenCalled();
47
46
  });
48
47
  it('should send event when endpoint is set', () => {
@@ -110,9 +109,8 @@ describe('Worker', () => {
110
109
  });
111
110
  });
112
111
  describe('pushEventAsync', () => {
113
- it('should return false when endpoint is not set', async () => {
114
- const result = await Worker_1.Worker.pushEventAsync('test-event');
115
- expect(result).toBe(false);
112
+ it('should throw error when endpoint is not set', async () => {
113
+ await expect(Worker_1.Worker.pushEventAsync('test-event')).rejects.toThrow('Events endpoint is mandatory but missing');
116
114
  expect(mockedAxios.patch).not.toHaveBeenCalled();
117
115
  });
118
116
  it('should send event and return true on success', async () => {
@@ -130,11 +128,10 @@ describe('Worker', () => {
130
128
  foo: 'bar',
131
129
  }));
132
130
  });
133
- it('should return false on axios error', async () => {
131
+ it('should throw error on axios error', async () => {
134
132
  Worker_1.Worker.endpoint = 'https://api.example.com/events';
135
133
  mockedAxios.patch.mockRejectedValue(new Error('Network error'));
136
- const result = await Worker_1.Worker.pushEventAsync('test-event');
137
- expect(result).toBe(false);
134
+ await expect(Worker_1.Worker.pushEventAsync('test-event')).rejects.toThrow('Failed to push event to backend: Network error');
138
135
  });
139
136
  it('should handle non-OK status response', async () => {
140
137
  Worker_1.Worker.endpoint = 'https://api.example.com/events';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quatrain/worker",
3
- "version": "1.2.10",
3
+ "version": "1.2.12",
4
4
  "description": "Container Worker helpers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -63,6 +63,13 @@ describe('FileSystem Utilities (Worker)', () => {
63
63
  return Readable.from(Buffer.alloc(35 * 1024)) as any
64
64
  })
65
65
 
66
+ jest.spyOn(fs, 'readFileSync').mockImplementation((p) => {
67
+ if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('small-fail.mp4') || p.endsWith('large-sync-fail.mp4'))) {
68
+ return Buffer.alloc(35 * 1024)
69
+ }
70
+ return (jest.requireActual('node:fs').readFileSync)(p)
71
+ })
72
+
66
73
  jest.spyOn(fs, 'existsSync').mockImplementation((p) => {
67
74
  if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('small-fail.mp4') || p.endsWith('large-sync-fail.mp4'))) {
68
75
  return true
@@ -346,8 +353,8 @@ describe('FileSystem Utilities (Worker)', () => {
346
353
  callback(null, mockMetadata)
347
354
  });
348
355
 
349
- // Make createReadStream throw a synchronous error
350
- jest.spyOn(fs, 'createReadStream').mockImplementationOnce(() => {
356
+ // Make readFileSync throw a synchronous error
357
+ jest.spyOn(fs, 'readFileSync').mockImplementationOnce(() => {
351
358
  throw new Error('Sync stream error')
352
359
  })
353
360
 
package/src/FileSystem.ts CHANGED
@@ -116,74 +116,28 @@ export class FileSystem {
116
116
  Worker.info(`Uploading file ${filename} to ${meta.uploadUrl}`)
117
117
  const { size } = fs.statSync(filename)
118
118
 
119
- if (size < 32 * 1024) {
120
- return new Promise((resolve, reject) => {
121
- fetch(meta.uploadUrl, {
122
- method: 'PUT',
123
- mode: 'cors',
124
- duplex: 'half',
125
- body: readFileSync(filename),
126
- headers: {
127
- 'Content-Type': meta.contentType || mime,
128
- 'Content-length': String(size),
129
- },
130
- } as any)
131
- .then(() => resolve({ ...meta, size, uploadUrl: undefined }))
132
- .catch((err) => {
133
- Worker.error(
134
- `An error occured while uploading ${filename}: ${err.message}`
135
- )
136
- reject(err)
137
- })
119
+ try {
120
+ Worker.info(`Uploading file ${filename} with size ${size} bytes`)
121
+ const fileBuffer = fs.readFileSync(filename)
122
+
123
+ const res = await fetch(meta.uploadUrl, {
124
+ method: 'PUT',
125
+ body: fileBuffer,
126
+ headers: {
127
+ 'Content-Type': meta.contentType || mime,
128
+ },
138
129
  })
139
- }
140
-
141
- return new Promise((resolve, reject) => {
142
- try {
143
- let done = 0 // total bytes uploaded
144
- let prev = 0 // latest value of done stored when % done was displayed
145
- const { size } = fs.statSync(filename)
146
- const readStream = fs.createReadStream(filename)
147
130
 
148
- const sizeMB = (size / (1024 * 1024)).toFixed(2)
149
-
150
- readStream.on('data', (data) => {
151
- done += data.length
152
- if (done - prev >= size / 20) {
153
- // only display message when 5% more has been uploaded
154
- Worker.info(
155
- `${((done / size) * 100).toFixed(2)}% - ${(
156
- done /
157
- (1024 * 1024)
158
- ).toFixed(2)}MB / ${sizeMB}MB`
159
- )
160
- prev = done
161
- }
162
- })
163
-
164
- Worker.info(`Uploading file ${filename} with size ${size} bytes`)
165
- fetch(meta.uploadUrl, {
166
- method: 'PUT',
167
- mode: 'cors',
168
- duplex: 'half',
169
- body: readStream,
170
- headers: {
171
- 'Content-Type': meta.contentType || mime,
172
- 'Content-length': String(size),
173
- },
174
- } as any)
175
- .then(() => resolve({ ...meta, size, uploadUrl: undefined }))
176
- .catch((err) => {
177
- Worker.error(
178
- `An error occured while uploading ${filename}: ${err.message}`
179
- )
180
- reject(err)
181
- })
182
- } catch (err) {
183
- Worker.error(err)
184
- reject(err)
131
+ if (res && res.ok === false) {
132
+ const errorText = typeof res.text === 'function' ? await res.text().catch(() => '') : ''
133
+ throw new Error(`HTTP upload failed with status ${res.status || 'unknown'}: ${errorText}`)
185
134
  }
186
- })
135
+
136
+ return { ...meta, size, uploadUrl: undefined }
137
+ } catch (err: any) {
138
+ Worker.error(`An error occured while uploading ${filename}: ${err.message}`)
139
+ throw err
140
+ }
187
141
  }
188
142
 
189
143
  /**
package/src/Helpers.ts CHANGED
@@ -5,8 +5,15 @@ import path from 'node:path'
5
5
  * General utility class providing common static helpers for workers.
6
6
  */
7
7
  export class Helpers {
8
+ private static _ffmpegPathPromise: Promise<string> | null = null
9
+
8
10
  /** Default absolute path to the system's FFmpeg binary. */
9
- static FFMPEG = Worker.getSystemCommandPath('ffmpeg')
11
+ static get FFMPEG(): Promise<string> {
12
+ if (!this._ffmpegPathPromise) {
13
+ this._ffmpegPathPromise = Worker.getSystemCommandPath('ffmpeg').catch(() => 'ffmpeg')
14
+ }
15
+ return this._ffmpegPathPromise
16
+ }
10
17
 
11
18
  /**
12
19
  * Generate a thubnail from a video file at given frame position
@@ -42,9 +42,8 @@ describe('Worker', () => {
42
42
  })
43
43
 
44
44
  describe('pushEvent', () => {
45
- it('should return false when endpoint is not set', () => {
46
- const result = Worker.pushEvent('test-event')
47
- expect(result).toBe(false)
45
+ it('should throw error when endpoint is not set', () => {
46
+ expect(() => Worker.pushEvent('test-event')).toThrow('Events endpoint is mandatory but missing')
48
47
  expect(mockedAxios.patch).not.toHaveBeenCalled()
49
48
  })
50
49
 
@@ -145,9 +144,8 @@ describe('Worker', () => {
145
144
  })
146
145
 
147
146
  describe('pushEventAsync', () => {
148
- it('should return false when endpoint is not set', async () => {
149
- const result = await Worker.pushEventAsync('test-event')
150
- expect(result).toBe(false)
147
+ it('should throw error when endpoint is not set', async () => {
148
+ await expect(Worker.pushEventAsync('test-event')).rejects.toThrow('Events endpoint is mandatory but missing')
151
149
  expect(mockedAxios.patch).not.toHaveBeenCalled()
152
150
  })
153
151
 
@@ -173,14 +171,12 @@ describe('Worker', () => {
173
171
  )
174
172
  })
175
173
 
176
- it('should return false on axios error', async () => {
174
+ it('should throw error on axios error', async () => {
177
175
  Worker.endpoint = 'https://api.example.com/events'
178
176
 
179
177
  mockedAxios.patch.mockRejectedValue(new Error('Network error'))
180
178
 
181
- const result = await Worker.pushEventAsync('test-event')
182
-
183
- expect(result).toBe(false)
179
+ await expect(Worker.pushEventAsync('test-event')).rejects.toThrow('Failed to push event to backend: Network error')
184
180
  })
185
181
 
186
182
  it('should handle non-OK status response', async () => {
package/src/Worker.ts CHANGED
@@ -66,8 +66,8 @@ export class Worker extends Core {
66
66
  */
67
67
  static pushEvent(event: string, data = {}, ts = 0) {
68
68
  if (!this.endpoint) {
69
- Worker.warn(`Events endpoint is not set, can't send update!`)
70
- return false
69
+ Worker.error(`Events endpoint is mandatory but missing! Cannot report job status to source.`)
70
+ throw new Error(`Events endpoint is mandatory but missing`)
71
71
  }
72
72
 
73
73
  ts = ts === Date.now() ? Date.now() + 1 : Date.now()
@@ -89,8 +89,7 @@ export class Worker extends Core {
89
89
  return true
90
90
  })
91
91
  .catch((err) => {
92
- Worker.error(`Failed to push event to backend: ${err}`)
93
- return false
92
+ Worker.error(`Failed to push event to backend: ${err.message}`)
94
93
  })
95
94
  }
96
95
 
@@ -103,8 +102,8 @@ export class Worker extends Core {
103
102
  */
104
103
  static async pushEventAsync(event: string, data = {}, ts = 0) {
105
104
  if (!this.endpoint) {
106
- Worker.warn(`Events endpoint is not set, can't send update!`)
107
- return false
105
+ Worker.error(`Events endpoint is mandatory but missing! Cannot report job status to source.`)
106
+ throw new Error(`Events endpoint is mandatory but missing`)
108
107
  }
109
108
 
110
109
  try {
@@ -119,13 +118,13 @@ export class Worker extends Core {
119
118
 
120
119
  const res = await axios.patch(Worker.endpoint, payload)
121
120
 
122
- if (res.statusText === 'OK') {
121
+ if (res.statusText === 'OK' || res.status === 200) {
123
122
  Worker.info(`Event pushed to backend: ${res.statusText}`)
124
123
  return true
125
124
  }
126
- } catch (err) {
127
- Worker.error(`Failed to push event to backend: ${err}`)
128
- return false
125
+ } catch (err: any) {
126
+ Worker.error(`Failed to push event to backend: ${err.message}`)
127
+ throw new Error(`Failed to push event to backend: ${err.message}`)
129
128
  }
130
129
  }
131
130