@dbos-inc/aws-s3-workflows 3.6.3-preview → 3.6.7-preview

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.
@@ -1,350 +1,350 @@
1
- import {
2
- FileRecord,
3
- S3WorkflowCallbacks,
4
- registerS3UploadWorkflow,
5
- registerS3PresignedUploadWorkflow,
6
- registerS3DeleteWorkflow,
7
- } from './s3_utils';
8
- import { DBOS } from '@dbos-inc/dbos-sdk';
9
-
10
- import { S3Client, DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
11
- import { createPresignedPost, PresignedPost } from '@aws-sdk/s3-presigned-post';
12
- import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
13
-
14
- import FormData from 'form-data';
15
- import axios, { AxiosResponse } from 'axios';
16
- import { Readable } from 'stream';
17
- import * as fs from 'fs';
18
- import { randomUUID } from 'node:crypto';
19
-
20
- enum FileStatus {
21
- PENDING = 'Pending',
22
- RECEIVED = 'Received',
23
- ACTIVE = 'Active',
24
- }
25
-
26
- interface FileDetails {
27
- user_id: string;
28
- file_type: string;
29
- file_name: string;
30
- file_id?: string;
31
- file_status?: string;
32
- file_time?: number;
33
- }
34
-
35
- interface UserFile extends FileDetails, FileRecord {
36
- user_id: string;
37
- file_type: string;
38
- file_name: string;
39
- file_id: string;
40
- file_status: string;
41
- file_time: number;
42
- }
43
-
44
- class TestUserFileTable {
45
- //////////
46
- //// Database table
47
- //////////
48
-
49
- // Pick a file ID
50
- @DBOS.step()
51
- static chooseFileRecord(details: FileDetails): Promise<UserFile> {
52
- const rec: UserFile = {
53
- user_id: details.user_id,
54
- file_status: FileStatus.PENDING,
55
- file_type: details.file_type,
56
- file_id: randomUUID(),
57
- file_name: details.file_name,
58
- file_time: new Date().getTime(),
59
- key: '',
60
- };
61
- rec.key = TestUserFileTable.createS3Key(rec);
62
- return Promise.resolve(rec);
63
- }
64
-
65
- static createS3Key(rec: UserFile) {
66
- const key = `${rec.file_type}/${rec.user_id}/${rec.file_id}/${rec.file_time}`;
67
- return key;
68
- }
69
-
70
- static toFileDetails(rec: UserFile) {
71
- return {
72
- user_id: rec.user_id,
73
- file_type: rec.file_type,
74
- file_name: rec.file_name,
75
- file_id: rec.file_id,
76
- file_status: rec.file_status,
77
- file_time: rec.file_time,
78
- };
79
- }
80
-
81
- // File table DML operations
82
- // Whole record is known
83
- @DBOS.transaction()
84
- static async insertFileRecord(rec: UserFile) {
85
- await DBOS.knexClient<FileDetails>('user_files').insert(TestUserFileTable.toFileDetails(rec));
86
- }
87
- @DBOS.transaction()
88
- static async updateFileRecord(rec: UserFile) {
89
- await DBOS.knexClient<FileDetails>('user_files')
90
- .update(TestUserFileTable.toFileDetails(rec))
91
- .where({ file_id: rec.file_id });
92
- }
93
- // Delete when part of record is known
94
- @DBOS.transaction()
95
- static async deleteFileRecordById(file_id: string) {
96
- await DBOS.knexClient<FileDetails>('user_files').delete().where({ file_id });
97
- }
98
-
99
- // Queries
100
- @DBOS.transaction({ readOnly: true })
101
- static async lookUpByFields(fields: FileDetails) {
102
- const rv = await DBOS.knexClient<FileDetails>('user_files')
103
- .select()
104
- .where({ ...fields, file_status: FileStatus.ACTIVE })
105
- .orderBy('file_time', 'desc')
106
- .first();
107
- return rv ? [rv] : [];
108
- }
109
- @DBOS.transaction({ readOnly: true })
110
- static async lookUpByName(user_id: string, file_type: string, file_name: string) {
111
- const rv = await DBOS.knexClient<FileDetails>('user_files')
112
- .select()
113
- .where({ user_id, file_type, file_name, file_status: FileStatus.ACTIVE })
114
- .orderBy('file_time', 'desc')
115
- .first();
116
- return rv ? [rv] : [];
117
- }
118
- @DBOS.transaction({ readOnly: true })
119
- static async lookUpByType(user_id: string, file_type: string) {
120
- const rv = await DBOS.knexClient<FileDetails>('user_files')
121
- .select()
122
- .where({ user_id, file_type, file_status: FileStatus.ACTIVE });
123
- return rv;
124
- }
125
- @DBOS.transaction({ readOnly: true })
126
- static async lookUpByUser(user_id: string) {
127
- const rv = await DBOS.knexClient<FileDetails>('user_files')
128
- .select()
129
- .where({ user_id, file_status: FileStatus.ACTIVE });
130
- return rv;
131
- }
132
- }
133
-
134
- const s3bucket = process.env['S3_BUCKET'];
135
- const s3region = process.env['AWS_REGION'];
136
- const s3accessKey = process.env['AWS_ACCESS_KEY_ID'];
137
- const s3accessSecret = process.env['AWS_SECRET_ACCESS_KEY'];
138
-
139
- let s3client: S3Client | undefined = undefined;
140
-
141
- interface Opts {
142
- contentType?: string;
143
- }
144
-
145
- const s3callback: S3WorkflowCallbacks<UserFile, Opts> = {
146
- // Database operations (these should be transactions)
147
- newActiveFile: async (rec: UserFile) => {
148
- rec.file_status = FileStatus.ACTIVE;
149
- return await TestUserFileTable.insertFileRecord(rec);
150
- },
151
- newPendingFile: async (rec: UserFile) => {
152
- rec.file_status = FileStatus.PENDING;
153
- return await TestUserFileTable.insertFileRecord(rec);
154
- },
155
- fileActivated: async (rec: UserFile) => {
156
- rec.file_status = FileStatus.ACTIVE;
157
- return await TestUserFileTable.updateFileRecord(rec);
158
- },
159
- fileDeleted: async (rec: UserFile) => {
160
- return await TestUserFileTable.deleteFileRecordById(rec.file_id);
161
- },
162
-
163
- // S3 interaction options, these will be run as steps
164
- putS3Contents: async (rec: UserFile, content: string, options?: Opts) => {
165
- return await s3client?.send(
166
- new PutObjectCommand({
167
- Bucket: s3bucket,
168
- Key: rec.key,
169
- ContentType: options?.contentType ?? 'text/plain',
170
- Body: content,
171
- }),
172
- );
173
- },
174
- createPresignedPost: async (rec: UserFile, timeout?: number, opts?: Opts) => {
175
- const postPresigned = await createPresignedPost(s3client!, {
176
- Conditions: [
177
- ['content-length-range', 1, 10000000], // 10MB
178
- ],
179
- Bucket: s3bucket!,
180
- Key: rec.key,
181
- Expires: timeout || 60,
182
- Fields: {
183
- 'Content-Type': opts?.contentType || '*',
184
- },
185
- });
186
- return { url: postPresigned.url, fields: postPresigned.fields };
187
- },
188
- validateS3Upload: undefined,
189
- deleteS3Object: async (rec: UserFile) => {
190
- return await s3client?.send(
191
- new DeleteObjectCommand({
192
- Bucket: s3bucket,
193
- Key: rec.key,
194
- }),
195
- );
196
- },
197
- };
198
-
199
- export const uploadWF = registerS3UploadWorkflow(s3callback, { className: 'UserFile', name: 'uploadWF' });
200
- export const uploadPWF = registerS3PresignedUploadWorkflow(s3callback, { className: 'UserFile', name: 'uploadPWF' });
201
- export const deleteWF = registerS3DeleteWorkflow(s3callback, { className: 'UserFile', name: 'deleteWF' });
202
-
203
- describe('ses-tests', () => {
204
- let s3IsAvailable = true;
205
-
206
- beforeAll(async () => {
207
- // Check if S3 is available and update app config, skip the test if it's not
208
- if (!s3region || !s3bucket || !s3accessKey || !s3accessSecret) {
209
- s3IsAvailable = false;
210
- console.log(
211
- 'S3 Test is not configured. To run, set AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and S3_BUCKET',
212
- );
213
- } else {
214
- s3client = new S3Client({
215
- region: s3region,
216
- credentials: {
217
- accessKeyId: s3accessKey,
218
- secretAccessKey: s3accessSecret,
219
- },
220
- });
221
-
222
- await DBOS.launch();
223
- }
224
- });
225
-
226
- afterAll(async () => {
227
- if (s3IsAvailable) {
228
- await DBOS.shutdown();
229
- }
230
- }, 10000);
231
-
232
- test('s3-simple-wfs', async () => {
233
- if (!s3IsAvailable) {
234
- console.log('S3 unavailable, skipping S3 tests');
235
- return;
236
- }
237
-
238
- const userid = randomUUID();
239
-
240
- // The simple workflows that will be performed are to:
241
- // Put file contents into DBOS (w/ table index)
242
- const myFile: FileDetails = { user_id: userid, file_type: 'text', file_name: 'mytextfile.txt' };
243
- const myFileRec = await TestUserFileTable.chooseFileRecord(myFile);
244
- await uploadWF(myFileRec, 'This is my file');
245
-
246
- // Get the file contents out of DBOS (using the table index)
247
- const mytxt = await getS3KeyContents(myFileRec.key);
248
- expect(mytxt).toBe('This is my file');
249
-
250
- // Delete the file contents out of DBOS (using the table index)
251
- const dfhandle = await deleteWF(myFileRec);
252
- expect(dfhandle).toBeDefined();
253
- }, 10000);
254
-
255
- test('s3-complex-wfs', async () => {
256
- if (!s3IsAvailable) {
257
- console.log('S3 unavailable, skipping S3 tests');
258
- return;
259
- }
260
-
261
- // The complex workflows that will be performed are to:
262
- // Put the file contents into DBOS with a presigned post
263
- const userid = randomUUID();
264
-
265
- // The simple workflows that will be performed are to:
266
- // Put file contents into DBOS (w/ table index)
267
- const myFile: FileDetails = { user_id: userid, file_type: 'text', file_name: 'mytextfile.txt' };
268
- const myFileRec = await TestUserFileTable.chooseFileRecord(myFile);
269
- const wfhandle = await DBOS.startWorkflow(uploadPWF)(myFileRec, 60, { contentType: 'text/plain' });
270
- // Get the presigned post
271
- const ppost = await DBOS.getEvent<PresignedPost>(wfhandle.workflowID, 'uploadkey');
272
- // Upload to the URL
273
- try {
274
- const res = await uploadToS3(ppost!, './src/s3_utils.test.ts');
275
- expect(res.status.toString()[0]).toBe('2');
276
- } catch (e) {
277
- // You do NOT want to accidentally serialize an AxiosError - they don't!
278
- console.log('Caught something awful!', e);
279
- expect(e).toBeUndefined();
280
- }
281
- // Notify WF
282
- await DBOS.send<boolean>(wfhandle.workflowID, true, 'uploadfinish');
283
-
284
- // Wait for WF complete
285
- const _myFileRecord = await wfhandle.getResult();
286
-
287
- // Get the file out of DBOS (using a signed URL)
288
- const myurl = await getS3KeyUrl(myFileRec.key, 60);
289
- expect(myurl).not.toBeNull();
290
- // Get the file contents out of S3
291
- await downloadFromS3(myurl, './deleteme.xxx');
292
- expect(fs.existsSync('./deleteme.xxx')).toBeTruthy();
293
- fs.rmSync('./deleteme.xxx');
294
-
295
- // Delete the file contents out of DBOS (using the table index)
296
- const dfhandle = await deleteWF(myFileRec);
297
- expect(dfhandle).toBeDefined();
298
- }, 10000);
299
-
300
- async function getS3KeyContents(key: string) {
301
- return (
302
- await s3client!.send(
303
- new GetObjectCommand({
304
- Bucket: s3bucket!,
305
- Key: key,
306
- }),
307
- )
308
- ).Body?.transformToString();
309
- }
310
-
311
- async function getS3KeyUrl(key: string, expirationSecs: number) {
312
- const getObjectCommand = new GetObjectCommand({
313
- Bucket: s3bucket!,
314
- Key: key,
315
- });
316
-
317
- const presignedUrl = await getSignedUrl(s3client!, getObjectCommand, { expiresIn: expirationSecs });
318
- return presignedUrl;
319
- }
320
-
321
- async function uploadToS3(presignedPostData: PresignedPost, filePath: string) {
322
- const formData = new FormData();
323
-
324
- // Append all the fields from the presigned post data
325
- Object.keys(presignedPostData.fields).forEach((key) => {
326
- formData.append(key, presignedPostData.fields[key]);
327
- });
328
-
329
- // Append the file you want to upload
330
- const fileStream = fs.createReadStream(filePath);
331
- formData.append('file', fileStream);
332
-
333
- return await axios.post(presignedPostData.url, formData);
334
- }
335
-
336
- async function downloadFromS3(presignedGetUrl: string, outputPath: string) {
337
- const response: AxiosResponse<Readable> = await axios.get(presignedGetUrl, {
338
- responseType: 'stream', // Important to handle large files
339
- });
340
-
341
- // Use a write stream to save the file to the desired path
342
- const writer = fs.createWriteStream(outputPath);
343
- response.data.pipe(writer);
344
-
345
- return new Promise<void>((resolve, reject) => {
346
- writer.on('finish', resolve);
347
- writer.on('error', reject);
348
- });
349
- }
350
- });
1
+ // import {
2
+ // FileRecord,
3
+ // S3WorkflowCallbacks,
4
+ // registerS3UploadWorkflow,
5
+ // registerS3PresignedUploadWorkflow,
6
+ // registerS3DeleteWorkflow,
7
+ // } from './s3_utils';
8
+ // import { DBOS } from '@dbos-inc/dbos-sdk';
9
+
10
+ // import { S3Client, DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
11
+ // import { createPresignedPost, PresignedPost } from '@aws-sdk/s3-presigned-post';
12
+ // import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
13
+
14
+ // import FormData from 'form-data';
15
+ // import axios, { AxiosResponse } from 'axios';
16
+ // import { Readable } from 'stream';
17
+ // import * as fs from 'fs';
18
+ // import { randomUUID } from 'node:crypto';
19
+
20
+ // enum FileStatus {
21
+ // PENDING = 'Pending',
22
+ // RECEIVED = 'Received',
23
+ // ACTIVE = 'Active',
24
+ // }
25
+
26
+ // interface FileDetails {
27
+ // user_id: string;
28
+ // file_type: string;
29
+ // file_name: string;
30
+ // file_id?: string;
31
+ // file_status?: string;
32
+ // file_time?: number;
33
+ // }
34
+
35
+ // interface UserFile extends FileDetails, FileRecord {
36
+ // user_id: string;
37
+ // file_type: string;
38
+ // file_name: string;
39
+ // file_id: string;
40
+ // file_status: string;
41
+ // file_time: number;
42
+ // }
43
+
44
+ // class TestUserFileTable {
45
+ // //////////
46
+ // //// Database table
47
+ // //////////
48
+
49
+ // // Pick a file ID
50
+ // @DBOS.step()
51
+ // static chooseFileRecord(details: FileDetails): Promise<UserFile> {
52
+ // const rec: UserFile = {
53
+ // user_id: details.user_id,
54
+ // file_status: FileStatus.PENDING,
55
+ // file_type: details.file_type,
56
+ // file_id: randomUUID(),
57
+ // file_name: details.file_name,
58
+ // file_time: new Date().getTime(),
59
+ // key: '',
60
+ // };
61
+ // rec.key = TestUserFileTable.createS3Key(rec);
62
+ // return Promise.resolve(rec);
63
+ // }
64
+
65
+ // static createS3Key(rec: UserFile) {
66
+ // const key = `${rec.file_type}/${rec.user_id}/${rec.file_id}/${rec.file_time}`;
67
+ // return key;
68
+ // }
69
+
70
+ // static toFileDetails(rec: UserFile) {
71
+ // return {
72
+ // user_id: rec.user_id,
73
+ // file_type: rec.file_type,
74
+ // file_name: rec.file_name,
75
+ // file_id: rec.file_id,
76
+ // file_status: rec.file_status,
77
+ // file_time: rec.file_time,
78
+ // };
79
+ // }
80
+
81
+ // // File table DML operations
82
+ // // Whole record is known
83
+ // @DBOS.transaction()
84
+ // static async insertFileRecord(rec: UserFile) {
85
+ // await DBOS.knexClient<FileDetails>('user_files').insert(TestUserFileTable.toFileDetails(rec));
86
+ // }
87
+ // @DBOS.transaction()
88
+ // static async updateFileRecord(rec: UserFile) {
89
+ // await DBOS.knexClient<FileDetails>('user_files')
90
+ // .update(TestUserFileTable.toFileDetails(rec))
91
+ // .where({ file_id: rec.file_id });
92
+ // }
93
+ // // Delete when part of record is known
94
+ // @DBOS.transaction()
95
+ // static async deleteFileRecordById(file_id: string) {
96
+ // await DBOS.knexClient<FileDetails>('user_files').delete().where({ file_id });
97
+ // }
98
+
99
+ // // Queries
100
+ // @DBOS.transaction({ readOnly: true })
101
+ // static async lookUpByFields(fields: FileDetails) {
102
+ // const rv = await DBOS.knexClient<FileDetails>('user_files')
103
+ // .select()
104
+ // .where({ ...fields, file_status: FileStatus.ACTIVE })
105
+ // .orderBy('file_time', 'desc')
106
+ // .first();
107
+ // return rv ? [rv] : [];
108
+ // }
109
+ // @DBOS.transaction({ readOnly: true })
110
+ // static async lookUpByName(user_id: string, file_type: string, file_name: string) {
111
+ // const rv = await DBOS.knexClient<FileDetails>('user_files')
112
+ // .select()
113
+ // .where({ user_id, file_type, file_name, file_status: FileStatus.ACTIVE })
114
+ // .orderBy('file_time', 'desc')
115
+ // .first();
116
+ // return rv ? [rv] : [];
117
+ // }
118
+ // @DBOS.transaction({ readOnly: true })
119
+ // static async lookUpByType(user_id: string, file_type: string) {
120
+ // const rv = await DBOS.knexClient<FileDetails>('user_files')
121
+ // .select()
122
+ // .where({ user_id, file_type, file_status: FileStatus.ACTIVE });
123
+ // return rv;
124
+ // }
125
+ // @DBOS.transaction({ readOnly: true })
126
+ // static async lookUpByUser(user_id: string) {
127
+ // const rv = await DBOS.knexClient<FileDetails>('user_files')
128
+ // .select()
129
+ // .where({ user_id, file_status: FileStatus.ACTIVE });
130
+ // return rv;
131
+ // }
132
+ // }
133
+
134
+ // const s3bucket = process.env['S3_BUCKET'];
135
+ // const s3region = process.env['AWS_REGION'];
136
+ // const s3accessKey = process.env['AWS_ACCESS_KEY_ID'];
137
+ // const s3accessSecret = process.env['AWS_SECRET_ACCESS_KEY'];
138
+
139
+ // let s3client: S3Client | undefined = undefined;
140
+
141
+ // interface Opts {
142
+ // contentType?: string;
143
+ // }
144
+
145
+ // const s3callback: S3WorkflowCallbacks<UserFile, Opts> = {
146
+ // // Database operations (these should be transactions)
147
+ // newActiveFile: async (rec: UserFile) => {
148
+ // rec.file_status = FileStatus.ACTIVE;
149
+ // return await TestUserFileTable.insertFileRecord(rec);
150
+ // },
151
+ // newPendingFile: async (rec: UserFile) => {
152
+ // rec.file_status = FileStatus.PENDING;
153
+ // return await TestUserFileTable.insertFileRecord(rec);
154
+ // },
155
+ // fileActivated: async (rec: UserFile) => {
156
+ // rec.file_status = FileStatus.ACTIVE;
157
+ // return await TestUserFileTable.updateFileRecord(rec);
158
+ // },
159
+ // fileDeleted: async (rec: UserFile) => {
160
+ // return await TestUserFileTable.deleteFileRecordById(rec.file_id);
161
+ // },
162
+
163
+ // // S3 interaction options, these will be run as steps
164
+ // putS3Contents: async (rec: UserFile, content: string, options?: Opts) => {
165
+ // return await s3client?.send(
166
+ // new PutObjectCommand({
167
+ // Bucket: s3bucket,
168
+ // Key: rec.key,
169
+ // ContentType: options?.contentType ?? 'text/plain',
170
+ // Body: content,
171
+ // }),
172
+ // );
173
+ // },
174
+ // createPresignedPost: async (rec: UserFile, timeout?: number, opts?: Opts) => {
175
+ // const postPresigned = await createPresignedPost(s3client!, {
176
+ // Conditions: [
177
+ // ['content-length-range', 1, 10000000], // 10MB
178
+ // ],
179
+ // Bucket: s3bucket!,
180
+ // Key: rec.key,
181
+ // Expires: timeout || 60,
182
+ // Fields: {
183
+ // 'Content-Type': opts?.contentType || '*',
184
+ // },
185
+ // });
186
+ // return { url: postPresigned.url, fields: postPresigned.fields };
187
+ // },
188
+ // validateS3Upload: undefined,
189
+ // deleteS3Object: async (rec: UserFile) => {
190
+ // return await s3client?.send(
191
+ // new DeleteObjectCommand({
192
+ // Bucket: s3bucket,
193
+ // Key: rec.key,
194
+ // }),
195
+ // );
196
+ // },
197
+ // };
198
+
199
+ // export const uploadWF = registerS3UploadWorkflow(s3callback, { className: 'UserFile', name: 'uploadWF' });
200
+ // export const uploadPWF = registerS3PresignedUploadWorkflow(s3callback, { className: 'UserFile', name: 'uploadPWF' });
201
+ // export const deleteWF = registerS3DeleteWorkflow(s3callback, { className: 'UserFile', name: 'deleteWF' });
202
+
203
+ // describe('ses-tests', () => {
204
+ // let s3IsAvailable = true;
205
+
206
+ // beforeAll(async () => {
207
+ // // Check if S3 is available and update app config, skip the test if it's not
208
+ // if (!s3region || !s3bucket || !s3accessKey || !s3accessSecret) {
209
+ // s3IsAvailable = false;
210
+ // console.log(
211
+ // 'S3 Test is not configured. To run, set AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and S3_BUCKET',
212
+ // );
213
+ // } else {
214
+ // s3client = new S3Client({
215
+ // region: s3region,
216
+ // credentials: {
217
+ // accessKeyId: s3accessKey,
218
+ // secretAccessKey: s3accessSecret,
219
+ // },
220
+ // });
221
+
222
+ // await DBOS.launch();
223
+ // }
224
+ // });
225
+
226
+ // afterAll(async () => {
227
+ // if (s3IsAvailable) {
228
+ // await DBOS.shutdown();
229
+ // }
230
+ // }, 10000);
231
+
232
+ // test('s3-simple-wfs', async () => {
233
+ // if (!s3IsAvailable) {
234
+ // console.log('S3 unavailable, skipping S3 tests');
235
+ // return;
236
+ // }
237
+
238
+ // const userid = randomUUID();
239
+
240
+ // // The simple workflows that will be performed are to:
241
+ // // Put file contents into DBOS (w/ table index)
242
+ // const myFile: FileDetails = { user_id: userid, file_type: 'text', file_name: 'mytextfile.txt' };
243
+ // const myFileRec = await TestUserFileTable.chooseFileRecord(myFile);
244
+ // await uploadWF(myFileRec, 'This is my file');
245
+
246
+ // // Get the file contents out of DBOS (using the table index)
247
+ // const mytxt = await getS3KeyContents(myFileRec.key);
248
+ // expect(mytxt).toBe('This is my file');
249
+
250
+ // // Delete the file contents out of DBOS (using the table index)
251
+ // const dfhandle = await deleteWF(myFileRec);
252
+ // expect(dfhandle).toBeDefined();
253
+ // }, 10000);
254
+
255
+ // test('s3-complex-wfs', async () => {
256
+ // if (!s3IsAvailable) {
257
+ // console.log('S3 unavailable, skipping S3 tests');
258
+ // return;
259
+ // }
260
+
261
+ // // The complex workflows that will be performed are to:
262
+ // // Put the file contents into DBOS with a presigned post
263
+ // const userid = randomUUID();
264
+
265
+ // // The simple workflows that will be performed are to:
266
+ // // Put file contents into DBOS (w/ table index)
267
+ // const myFile: FileDetails = { user_id: userid, file_type: 'text', file_name: 'mytextfile.txt' };
268
+ // const myFileRec = await TestUserFileTable.chooseFileRecord(myFile);
269
+ // const wfhandle = await DBOS.startWorkflow(uploadPWF)(myFileRec, 60, { contentType: 'text/plain' });
270
+ // // Get the presigned post
271
+ // const ppost = await DBOS.getEvent<PresignedPost>(wfhandle.workflowID, 'uploadkey');
272
+ // // Upload to the URL
273
+ // try {
274
+ // const res = await uploadToS3(ppost!, './src/s3_utils.test.ts');
275
+ // expect(res.status.toString()[0]).toBe('2');
276
+ // } catch (e) {
277
+ // // You do NOT want to accidentally serialize an AxiosError - they don't!
278
+ // console.log('Caught something awful!', e);
279
+ // expect(e).toBeUndefined();
280
+ // }
281
+ // // Notify WF
282
+ // await DBOS.send<boolean>(wfhandle.workflowID, true, 'uploadfinish');
283
+
284
+ // // Wait for WF complete
285
+ // const _myFileRecord = await wfhandle.getResult();
286
+
287
+ // // Get the file out of DBOS (using a signed URL)
288
+ // const myurl = await getS3KeyUrl(myFileRec.key, 60);
289
+ // expect(myurl).not.toBeNull();
290
+ // // Get the file contents out of S3
291
+ // await downloadFromS3(myurl, './deleteme.xxx');
292
+ // expect(fs.existsSync('./deleteme.xxx')).toBeTruthy();
293
+ // fs.rmSync('./deleteme.xxx');
294
+
295
+ // // Delete the file contents out of DBOS (using the table index)
296
+ // const dfhandle = await deleteWF(myFileRec);
297
+ // expect(dfhandle).toBeDefined();
298
+ // }, 10000);
299
+
300
+ // async function getS3KeyContents(key: string) {
301
+ // return (
302
+ // await s3client!.send(
303
+ // new GetObjectCommand({
304
+ // Bucket: s3bucket!,
305
+ // Key: key,
306
+ // }),
307
+ // )
308
+ // ).Body?.transformToString();
309
+ // }
310
+
311
+ // async function getS3KeyUrl(key: string, expirationSecs: number) {
312
+ // const getObjectCommand = new GetObjectCommand({
313
+ // Bucket: s3bucket!,
314
+ // Key: key,
315
+ // });
316
+
317
+ // const presignedUrl = await getSignedUrl(s3client!, getObjectCommand, { expiresIn: expirationSecs });
318
+ // return presignedUrl;
319
+ // }
320
+
321
+ // async function uploadToS3(presignedPostData: PresignedPost, filePath: string) {
322
+ // const formData = new FormData();
323
+
324
+ // // Append all the fields from the presigned post data
325
+ // Object.keys(presignedPostData.fields).forEach((key) => {
326
+ // formData.append(key, presignedPostData.fields[key]);
327
+ // });
328
+
329
+ // // Append the file you want to upload
330
+ // const fileStream = fs.createReadStream(filePath);
331
+ // formData.append('file', fileStream);
332
+
333
+ // return await axios.post(presignedPostData.url, formData);
334
+ // }
335
+
336
+ // async function downloadFromS3(presignedGetUrl: string, outputPath: string) {
337
+ // const response: AxiosResponse<Readable> = await axios.get(presignedGetUrl, {
338
+ // responseType: 'stream', // Important to handle large files
339
+ // });
340
+
341
+ // // Use a write stream to save the file to the desired path
342
+ // const writer = fs.createWriteStream(outputPath);
343
+ // response.data.pipe(writer);
344
+
345
+ // return new Promise<void>((resolve, reject) => {
346
+ // writer.on('finish', resolve);
347
+ // writer.on('error', reject);
348
+ // });
349
+ // }
350
+ // });