@cirrobio/sdk 0.12.11 → 0.12.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.
Files changed (56) hide show
  1. package/dist/index.esm.js.map +1 -1
  2. package/dist/index.js.map +1 -1
  3. package/package.json +3 -2
  4. package/src/api/config.ts +17 -0
  5. package/src/api/error-handler.ts +28 -0
  6. package/src/api/error.ts +8 -0
  7. package/src/api.ts +2 -0
  8. package/src/auth/authentication-provider.ts +26 -0
  9. package/src/auth/current-user.ts +26 -0
  10. package/src/auth/static-token-auth.ts +29 -0
  11. package/src/auth.ts +3 -0
  12. package/src/data/data.service.ts +128 -0
  13. package/src/data.ts +1 -0
  14. package/src/file/__test__/credentials-mutex.spec.ts +44 -0
  15. package/src/file/__test__/extensions.spec.ts +36 -0
  16. package/src/file/__test__/manifest-parser.spec.ts +67 -0
  17. package/src/file/__test__/s3-utils.spec.ts +17 -0
  18. package/src/file/__test__/utils.spec.ts +9 -0
  19. package/src/file/actions/delete.fn.ts +18 -0
  20. package/src/file/actions/sign-url.fn.ts +58 -0
  21. package/src/file/actions/upload.fn.ts +33 -0
  22. package/src/file/calculate-size.ts +14 -0
  23. package/src/file/extensions.fn.ts +88 -0
  24. package/src/file/file.service.ts +90 -0
  25. package/src/file/manifest-parser.ts +63 -0
  26. package/src/file/models/assets.ts +27 -0
  27. package/src/file/models/file-object.model.ts +61 -0
  28. package/src/file/models/file.ts +43 -0
  29. package/src/file/models/folder.ts +27 -0
  30. package/src/file/project-access-context.ts +26 -0
  31. package/src/file/shared.ts +9 -0
  32. package/src/file/util/credentials-mutex.so.ts +33 -0
  33. package/src/file/util/get-display.fn.ts +6 -0
  34. package/src/file/util/get-parent.fn.ts +7 -0
  35. package/src/file/util/s3-client.ts +43 -0
  36. package/src/file/util/s3-utils.ts +14 -0
  37. package/src/file.ts +16 -0
  38. package/src/formatters/__tests__/formatters.spec.ts +101 -0
  39. package/src/formatters/bytes-to-string.ts +32 -0
  40. package/src/formatters/json-pretty-print.ts +8 -0
  41. package/src/formatters/normalize-date.ts +10 -0
  42. package/src/formatters/normalize-string.ts +8 -0
  43. package/src/formatters/slash.ts +18 -0
  44. package/src/formatters/to-date-format.ts +12 -0
  45. package/src/formatters/to-friendly-name.ts +14 -0
  46. package/src/formatters/to-money.ts +13 -0
  47. package/src/formatters/to-pascal-case.ts +16 -0
  48. package/src/formatters/to-title-case.ts +9 -0
  49. package/src/formatters.ts +10 -0
  50. package/src/index.ts +6 -0
  51. package/src/util/__tests__/extract-from-object.spec.ts +29 -0
  52. package/src/util/download.ts +18 -0
  53. package/src/util/extract-from-object.ts +11 -0
  54. package/src/util/get-resource-name.ts +7 -0
  55. package/src/util/handle-promise.ts +7 -0
  56. package/src/util.ts +4 -0
@@ -0,0 +1,17 @@
1
+ import { PortalErrorHandler } from "./error-handler";
2
+ import { Configuration } from "@cirrobio/api-client";
3
+
4
+ interface GetConfigParams {
5
+ tokenGetter: () => Promise<string>;
6
+ basePath?: string;
7
+ }
8
+
9
+ export const generateApiConfig = ({ basePath = "/api", tokenGetter}: GetConfigParams) => {
10
+ return new Configuration({
11
+ basePath,
12
+ accessToken: tokenGetter,
13
+ middleware: [
14
+ new PortalErrorHandler()
15
+ ],
16
+ });
17
+ }
@@ -0,0 +1,28 @@
1
+ import { Middleware, ResponseContext } from "@cirrobio/api-client";
2
+ import { ApiError } from "./error";
3
+
4
+ export class PortalErrorHandler implements Middleware {
5
+ async post(context: ResponseContext): Promise<Response | void> {
6
+ const { response } = context;
7
+ if (response && (response.status >= 200 && response.status < 300)) {
8
+ return response;
9
+ }
10
+
11
+ // Handle Error
12
+ let errorMessage: string;
13
+ const errors = [];
14
+ try {
15
+ const err = await response.json();
16
+ console.warn(err);
17
+ if ('errorDetail' in err) {
18
+ errorMessage = err.errorDetail;
19
+ errors.push(err.errors.map((e: any) => e.message));
20
+ } else {
21
+ errorMessage = err.message;
22
+ }
23
+ } catch (ignore) {
24
+ errorMessage = "Unknown Error";
25
+ }
26
+ throw new ApiError(errorMessage, errors);
27
+ }
28
+ }
@@ -0,0 +1,8 @@
1
+ export class ApiError extends Error {
2
+ errors: string[];
3
+
4
+ constructor(message: string, errors: string[]) {
5
+ super(message);
6
+ this.errors = errors;
7
+ }
8
+ }
package/src/api.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { ApiError } from './api/error';
2
+ export { generateApiConfig } from './api/config';
@@ -0,0 +1,26 @@
1
+ import { CurrentUser } from "./current-user";
2
+ import { SystemInfoResponse } from "@cirrobio/api-client";
3
+
4
+
5
+ /**
6
+ * AuthenticationProvider defines how the application will authenticate users to Cirro.
7
+ */
8
+ export interface AuthenticationProvider {
9
+ /**
10
+ * Configures the authentication provider
11
+ */
12
+ configure: (config: SystemInfoResponse) => void;
13
+ /**
14
+ * Returns access token for the current user.
15
+ */
16
+ getAccessToken: () => Promise<string>;
17
+ /**
18
+ * Returns the current user information.
19
+ */
20
+ getCurrentUser: () => Promise<CurrentUser>;
21
+ /**
22
+ * Force a refresh of the authentication state.
23
+ * (i.e., pull a fresh access token to get the latest user info)
24
+ */
25
+ forceRefresh: () => Promise<void>;
26
+ }
@@ -0,0 +1,26 @@
1
+ export class CurrentUser {
2
+ username: string;
3
+ /**
4
+ * The unique identifier for the user
5
+ */
6
+ sub: string
7
+
8
+ /**
9
+ * The time the user authenticated
10
+ */
11
+ authTime: Date;
12
+
13
+ /**
14
+ * The groups the user belongs to
15
+ */
16
+ groups: string[];
17
+
18
+ static fromCognitoUser(idTokenPayload: Record<string, any>): CurrentUser {
19
+ return {
20
+ username: idTokenPayload["cognito:username"] ?? idTokenPayload.username,
21
+ sub: idTokenPayload.sub,
22
+ authTime: new Date(idTokenPayload.auth_time * 1000),
23
+ groups: idTokenPayload["cognito:groups"] ?? [],
24
+ };
25
+ }
26
+ }
@@ -0,0 +1,29 @@
1
+ import { SystemInfoResponse } from "@cirrobio/api-client";
2
+ import { AuthenticationProvider } from "./authentication-provider";
3
+ import { CurrentUser } from "./current-user";
4
+
5
+
6
+ export class StaticTokenAuthProvider implements AuthenticationProvider {
7
+ constructor(
8
+ readonly token: string
9
+ ) {
10
+ }
11
+
12
+ public configure(_ignore: SystemInfoResponse): void {
13
+ console.log('StaticTokenAuthProvider loaded');
14
+ }
15
+
16
+ public async getAccessToken() {
17
+ return this.token;
18
+ }
19
+
20
+ public async getCurrentUser() {
21
+ const accessToken = await this.getAccessToken();
22
+ const parsedToken = JSON.parse(atob(accessToken.split('.')[1]));
23
+ return CurrentUser.fromCognitoUser(parsedToken);
24
+ }
25
+
26
+ public forceRefresh(): Promise<void> {
27
+ return Promise.resolve();
28
+ }
29
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,3 @@
1
+ export type { AuthenticationProvider } from './auth/authentication-provider';
2
+ export { StaticTokenAuthProvider } from './auth/static-token-auth';
3
+ export { CurrentUser } from './auth/current-user';
@@ -0,0 +1,128 @@
1
+ import {
2
+ AuditApi,
3
+ BillingApi,
4
+ ComputeEnvironmentApi,
5
+ Configuration,
6
+ DatasetsApi,
7
+ ExecutionApi,
8
+ FileApi,
9
+ GovernanceApi,
10
+ MetadataApi,
11
+ NotebooksApi,
12
+ ProcessesApi,
13
+ ProjectRequestsApi,
14
+ ProjectsApi,
15
+ ReferencesApi,
16
+ SharingApi,
17
+ SystemApi,
18
+ ToolsApi,
19
+ UsersApi,
20
+ WorkspacesApi
21
+ } from "@cirrobio/api-client";
22
+ import { generateApiConfig } from "../api/config";
23
+
24
+ interface DataServiceParams {
25
+ tokenGetter: () => Promise<string>;
26
+ basePath?: string;
27
+ }
28
+
29
+ export class DataService {
30
+ private readonly apiConfig: Configuration;
31
+
32
+ private readonly _auditApi: AuditApi;
33
+ private readonly _billingApi: BillingApi;
34
+ private readonly _computeEnvironmentApi: ComputeEnvironmentApi;
35
+ private readonly _datasetsApi: DatasetsApi;
36
+ private readonly _executionApi: ExecutionApi;
37
+ private readonly _fileApi: FileApi;
38
+ private readonly _governanceApi: GovernanceApi;
39
+ private readonly _metadataApi: MetadataApi;
40
+ private readonly _notebooksApi: NotebooksApi;
41
+ private readonly _processesApi: ProcessesApi;
42
+ private readonly _projectRequestsApi: ProjectRequestsApi;
43
+ private readonly _projectsApi: ProjectsApi;
44
+ private readonly _referencesApi: ReferencesApi;
45
+ private readonly _sharingApi: SharingApi;
46
+ private readonly _systemApi: SystemApi;
47
+ private readonly _toolsApi: ToolsApi;
48
+ private readonly _usersApi: UsersApi;
49
+ private readonly _workspacesApi: WorkspacesApi;
50
+
51
+ constructor({ tokenGetter, basePath = "/api" }: DataServiceParams) {
52
+ this.apiConfig = generateApiConfig({ basePath, tokenGetter });
53
+
54
+ this._auditApi = new AuditApi(this.apiConfig);
55
+ this._billingApi = new BillingApi(this.apiConfig);
56
+ this._computeEnvironmentApi = new ComputeEnvironmentApi(this.apiConfig);
57
+ this._datasetsApi = new DatasetsApi(this.apiConfig);
58
+ this._executionApi = new ExecutionApi(this.apiConfig);
59
+ this._fileApi = new FileApi(this.apiConfig);
60
+ this._governanceApi = new GovernanceApi(this.apiConfig);
61
+ this._metadataApi = new MetadataApi(this.apiConfig);
62
+ this._notebooksApi = new NotebooksApi(this.apiConfig);
63
+ this._processesApi = new ProcessesApi(this.apiConfig);
64
+ this._projectRequestsApi = new ProjectRequestsApi(this.apiConfig);
65
+ this._projectsApi = new ProjectsApi(this.apiConfig);
66
+ this._referencesApi = new ReferencesApi(this.apiConfig);
67
+ this._sharingApi = new SharingApi(this.apiConfig);
68
+ this._systemApi = new SystemApi(this.apiConfig);
69
+ this._toolsApi = new ToolsApi(this.apiConfig);
70
+ this._usersApi = new UsersApi(this.apiConfig);
71
+ this._workspacesApi = new WorkspacesApi(this.apiConfig);
72
+ }
73
+
74
+ get audit(): AuditApi {
75
+ return this._auditApi;
76
+ }
77
+ get billing(): BillingApi {
78
+ return this._billingApi;
79
+ }
80
+ get computeEnvironments(): ComputeEnvironmentApi {
81
+ return this._computeEnvironmentApi;
82
+ }
83
+ get datasets(): DatasetsApi {
84
+ return this._datasetsApi;
85
+ }
86
+ get execution(): ExecutionApi {
87
+ return this._executionApi;
88
+ }
89
+ get file(): FileApi {
90
+ return this._fileApi;
91
+ }
92
+ get governance(): GovernanceApi {
93
+ return this._governanceApi;
94
+ }
95
+ get metadata(): MetadataApi {
96
+ return this._metadataApi;
97
+ }
98
+ get notebooks(): NotebooksApi {
99
+ return this._notebooksApi;
100
+ }
101
+ get processes(): ProcessesApi {
102
+ return this._processesApi;
103
+ }
104
+ get projectRequests(): ProjectRequestsApi {
105
+ return this._projectRequestsApi;
106
+ }
107
+ get projects(): ProjectsApi {
108
+ return this._projectsApi;
109
+ }
110
+ get references(): ReferencesApi {
111
+ return this._referencesApi;
112
+ }
113
+ get sharing(): SharingApi {
114
+ return this._sharingApi;
115
+ }
116
+ get system(): SystemApi {
117
+ return this._systemApi;
118
+ }
119
+ get tools(): ToolsApi {
120
+ return this._toolsApi;
121
+ }
122
+ get users(): UsersApi {
123
+ return this._usersApi;
124
+ }
125
+ get workspaces(): WorkspacesApi {
126
+ return this._workspacesApi;
127
+ }
128
+ }
package/src/data.ts ADDED
@@ -0,0 +1 @@
1
+ export { DataService } from './data/data.service';
@@ -0,0 +1,44 @@
1
+ import { Mutex } from "../util/credentials-mutex.so";
2
+
3
+ describe('integration: credentialsMutex', () => {
4
+ const cache: Map<string, string> = new Map<string, string>();
5
+
6
+ beforeEach(() => {
7
+ cache.clear();
8
+ });
9
+
10
+ it('should only have one call with concurrent requests', async () => {
11
+ let fetchCount = 0;
12
+ const mutex = new Mutex();
13
+
14
+ const fetchCredentials = async (userId: string): Promise<string> => {
15
+ const cached = cache.get(userId);
16
+ if (cached) {
17
+ return cached;
18
+ }
19
+
20
+ fetchCount++;
21
+ // Simulate API call
22
+ await new Promise(resolve => setTimeout(resolve, 50));
23
+ const resp = userId;
24
+ cache.set(userId, resp);
25
+ return resp;
26
+ };
27
+
28
+ // Multiple concurrent requests for same user
29
+ const promises = [
30
+ mutex.dispatch(() => fetchCredentials('user1')),
31
+ mutex.dispatch(() => fetchCredentials('user1')),
32
+ mutex.dispatch(() => fetchCredentials('user1')),
33
+ ];
34
+
35
+ const results = await Promise.all(promises);
36
+
37
+ // Should return the same credentials
38
+ expect(results[0]).toEqual(results[1]);
39
+ expect(results[1]).toEqual(results[2]);
40
+
41
+ // But only one fetch should have occurred
42
+ expect(fetchCount).toBe(1);
43
+ });
44
+ });
@@ -0,0 +1,36 @@
1
+ import { FILE_IMAGE_EXTENSIONS, FILE_TXT_GENOMICS_EXTENSIONS, matchesExtension } from "../extensions.fn";
2
+
3
+ describe("extensions", () => {
4
+ it("should return true when file extension matches", () => {
5
+ expect(matchesExtension("document.PDF", ["pdf"])).toBe(true);
6
+ expect(matchesExtension("image.jpg", FILE_IMAGE_EXTENSIONS)).toBe(true);
7
+ expect(matchesExtension("folder1/sample1.fastq", FILE_TXT_GENOMICS_EXTENSIONS)).toBe(true);
8
+ expect(matchesExtension("sample1.fastq.gz", FILE_TXT_GENOMICS_EXTENSIONS)).toBe(true);
9
+ });
10
+
11
+ it("should return false when file extension does not match", () => {
12
+ expect(matchesExtension("document.pdf", ["png"])).toBe(false);
13
+ });
14
+
15
+ it("should handle empty extensions array", () => {
16
+ expect(matchesExtension("file.txt", [])).toBe(false);
17
+ });
18
+
19
+ it("should handle files with multiple dots", () => {
20
+ expect(matchesExtension("my.file.name.txt", ["txt"])).toBe(true);
21
+ });
22
+
23
+ it("should handle files with no extension", () => {
24
+ expect(matchesExtension("README", ["txt"])).toBe(false);
25
+ });
26
+
27
+ it("should handle files with starting dot", () => {
28
+ expect(matchesExtension(".", [""])).toBe(true);
29
+ expect(matchesExtension(".gitignore", ["gitignore"])).toBe(true);
30
+ expect(matchesExtension(".bashrc", ["bashrc"])).toBe(true);
31
+ });
32
+
33
+ it("should handle empty string filename", () => {
34
+ expect(matchesExtension("", ["txt"])).toBe(false);
35
+ });
36
+ });
@@ -0,0 +1,67 @@
1
+ import { DatasetAssetsManifest } from "@cirrobio/api-client";
2
+ import { ManifestParser } from "../manifest-parser";
3
+ import { FileSystemObjectType } from "../models/file-object.model";
4
+
5
+
6
+ describe("manifestParser", () => {
7
+ const testManifest: DatasetAssetsManifest = {
8
+ domain: 's3://example-bucket/datasets',
9
+ files: [
10
+ {
11
+ path: 'file1.txt',
12
+ size: 1234,
13
+ metadata: {
14
+ description: 'Test file 1',
15
+ }
16
+ },
17
+ {
18
+ path: 'data/folder1/folder2/file2.txt',
19
+ size: 1234,
20
+ metadata: {
21
+ }
22
+ },
23
+ {
24
+ path: 'data/folder1/folder2/file3.txt',
25
+ size: 1234,
26
+ metadata: {
27
+ }
28
+ },
29
+ ],
30
+ }
31
+
32
+ it("should generate assets", () => {
33
+ const parser = new ManifestParser(testManifest);
34
+ const assets = parser.generateAssets();
35
+ expect(assets.length).toBe(3);
36
+ expect(assets[0].url).toBe('s3://example-bucket/datasets/file1.txt');
37
+ expect(assets[0].size).toBe(testManifest.files[0].size);
38
+ expect(assets[0].name).toBe('file1.txt');
39
+ expect(assets[0].path).toBe('');
40
+ expect(assets[0].displayPath).toBe('file1.txt');
41
+ expect(assets[0].id).toBeTruthy();
42
+ expect(assets[0].kind).toBe('txt');
43
+ expect(Object.keys(assets[0].metadata).length).toBe(1);
44
+ expect(assets[1].url).toBe('s3://example-bucket/datasets/data/folder1/folder2/file2.txt');
45
+ expect(assets[1].size).toBe(testManifest.files[0].size);
46
+ expect(assets[1].name).toBe('file2.txt');
47
+ expect(assets[1].path).toBe('data/folder1/folder2');
48
+ expect(assets[1].displayPath).toBe('folder1/folder2/file2.txt');
49
+ // calculate check
50
+ expect(assets.totalSizeBytes).toBe(3702);
51
+ expect(assets.totalSize).toBe("3.6 KiB");
52
+
53
+ });
54
+
55
+ it("should generate assets with folders", () => {
56
+ const parser = new ManifestParser(testManifest);
57
+ const assets = parser.generateAssets(true);
58
+ const folders = assets.filter(a => a.type === FileSystemObjectType.FOLDER);
59
+ expect(folders.length).toBe(3);
60
+ expect(folders[0].name).toBe('data');
61
+ expect(folders[1].name).toBe('folder1');
62
+ expect(folders[2].name).toBe('folder2');
63
+ expect(folders[2].displayPath).toBe('folder1/folder2');
64
+ const files = assets.filter(a => a.type === FileSystemObjectType.FILE);
65
+ expect(files.length).toBe(3);
66
+ });
67
+ })
@@ -0,0 +1,17 @@
1
+ import { s3UriToParams } from "../../file";
2
+
3
+
4
+ describe("s3-utils", () => {
5
+ describe("s3UriToParams", () => {
6
+ it("should parse a valid s3 URI", () => {
7
+ const uri = "s3://my-bucket/my-key/nested/folder";
8
+ const result = s3UriToParams(uri);
9
+ expect(result).toEqual({ Bucket: "my-bucket", Key: "my-key/nested/folder" });
10
+ });
11
+
12
+ it("should throw an error for an invalid URI", () => {
13
+ const uri = "invalid-uri";
14
+ expect(() => s3UriToParams(uri)).toThrow(`Received invalid uri: '${uri}'`);
15
+ });
16
+ });
17
+ })
@@ -0,0 +1,9 @@
1
+ import { getParentPath } from "../../file";
2
+
3
+ describe('getParentPath', () => {
4
+ it('should return the parent path of a given file path', () => {
5
+ const filePath = 's3://bucket/path/to/file.txt';
6
+ const parentPath = getParentPath(filePath);
7
+ expect(parentPath).toBe('s3://bucket/path/to');
8
+ })
9
+ })
@@ -0,0 +1,18 @@
1
+ import { AWSCredentials } from "@cirrobio/api-client";
2
+ import { DeleteObjectCommand } from "@aws-sdk/client-s3";
3
+ import { s3UriToParams } from "../util/s3-utils";
4
+ import { createS3Client } from "../util/s3-client";
5
+
6
+ export interface DeleteFileParams {
7
+ url: string;
8
+ credentials: AWSCredentials;
9
+ }
10
+
11
+ /**
12
+ * Delete a file from S3 given its URL and credentials.
13
+ */
14
+ export async function deleteFile({ url, credentials }: DeleteFileParams): Promise<void> {
15
+ const { Bucket, Key } = s3UriToParams(url);
16
+ const cmd = new DeleteObjectCommand({ Bucket, Key });
17
+ await createS3Client(credentials).send(cmd);
18
+ }
@@ -0,0 +1,58 @@
1
+ import { AWSCredentials } from "@cirrobio/api-client";
2
+ import { GetObjectCommand, GetObjectCommandInput } from "@aws-sdk/client-s3";
3
+ import { getSignedUrl as getSignedUrlInternal } from "@aws-sdk/s3-request-presigner";
4
+ import { createS3Client } from "../util/s3-client";
5
+ import { s3UriToParams } from "../util/s3-utils";
6
+ import { S3ClientConfigType } from "@aws-sdk/client-s3/dist-types/S3Client";
7
+
8
+ export interface GetFileUrlParams extends GetSignedUrlOptions {
9
+ url: string;
10
+ credentials?: AWSCredentials;
11
+ }
12
+
13
+ export interface GetSignedUrlOptions {
14
+ /**
15
+ * Whether to signal the browser to download this file on access
16
+ * (sets content disposition)
17
+ */
18
+ download?: boolean;
19
+ /**
20
+ * Whether to signal the browser to request gzipped content
21
+ */
22
+ gzip?: boolean;
23
+ /**
24
+ * Signed URL expiry timeout
25
+ * Defaults to 5 minutes
26
+ */
27
+ timeout?: number;
28
+ /**
29
+ * Override file name to download (when using download = true)
30
+ */
31
+ filename?: string;
32
+ /**
33
+ * Override the region of the bucket
34
+ */
35
+ region?: string;
36
+ }
37
+
38
+ /**
39
+ * Get a signed URL for a file in S3 given its S3 URI.
40
+ */
41
+ export function getSignedUrl({ url, credentials, ...params }: GetFileUrlParams): Promise<string> {
42
+ const clientParams: S3ClientConfigType = {};
43
+ if (params.region) {
44
+ clientParams.region = params.region;
45
+ }
46
+ const client = createS3Client(credentials, clientParams);
47
+ const { Bucket, Key } = s3UriToParams(url);
48
+ const args: GetObjectCommandInput = { Bucket, Key };
49
+ if (params?.download) {
50
+ const fileName = params.filename ?? Key.split('/').pop();
51
+ args.ResponseContentDisposition = `attachment; filename="${fileName}"`
52
+ }
53
+ if (params?.gzip) {
54
+ args.ResponseContentEncoding = 'gzip'
55
+ }
56
+ const command = new GetObjectCommand(args);
57
+ return getSignedUrlInternal(client, command, { expiresIn: 60 * (params?.timeout ?? 5) });
58
+ }
@@ -0,0 +1,33 @@
1
+ import { Upload } from "@aws-sdk/lib-storage";
2
+ import { AWSCredentials } from '@cirrobio/api-client';
3
+ import { createS3Client } from "../util/s3-client";
4
+ import { PutObjectCommandInput } from "@aws-sdk/client-s3";
5
+ import { CreateMultipartUploadRequest } from "@aws-sdk/client-s3/dist-types/models/models_0";
6
+
7
+ export interface UploadFileParams {
8
+ bucket: string;
9
+ path: string;
10
+ file: File;
11
+ credentials: AWSCredentials;
12
+ metadata?: Record<string, string>;
13
+ }
14
+
15
+ /**
16
+ * Upload a file to S3
17
+ */
18
+ export function uploadFile({ bucket, path, file, credentials, metadata }: UploadFileParams): Upload {
19
+ const params: PutObjectCommandInput & CreateMultipartUploadRequest = {
20
+ Bucket: bucket,
21
+ Key: path,
22
+ Body: file,
23
+ ContentType: file.type,
24
+ Metadata: metadata,
25
+ ChecksumType: "FULL_OBJECT"
26
+ };
27
+ return new Upload({
28
+ client: createS3Client(credentials),
29
+ queueSize: 4,
30
+ params,
31
+ });
32
+ }
33
+
@@ -0,0 +1,14 @@
1
+ import { FileSystemObject } from "./models/file-object.model";
2
+
3
+ /**
4
+ * Calculate the total size of all files in a directory.
5
+ * @param files - An array of FileSystemObject representing the files in the directory.
6
+ * @return The total size of all files in bytes.
7
+ */
8
+ export function calculateTotalSize(files: Array<FileSystemObject>): number {
9
+ let totalSize = 0;
10
+ for (const file of files) {
11
+ totalSize += file.size;
12
+ }
13
+ return totalSize;
14
+ }
@@ -0,0 +1,88 @@
1
+
2
+ /**
3
+ * An array of file extensions that can be rendered in a genome viewer
4
+ */
5
+ export const FILE_TRACK_ANNOTATION = ['bed', 'bed.gz', 'gtf', 'gtf.gz'];
6
+ export const FILE_TRACK_ALIGNMENTS = ['cram', 'cram.gz']; // TODO: put back bam
7
+ export const FILE_TRACK_VARIANT = ['vcf', 'vcf.gz'];
8
+ export const FILE_TRACK_WIG = ['wig', 'wig.gz', 'bw', 'bw.gz', 'bigwig', 'bigwig.gz'];
9
+ export const FILE_TRACK_SEG = ['seg', 'seg.gz'];
10
+ export const FILE_TRACK_INDEX_EXTENSIONS = ['tbi', 'bai', 'crai', 'csi'];
11
+ export const FILE_VITESSCE_EXTENSIONS = ['hdf5', 'h5ad', 'loom'];
12
+ export const FILE_TRACK_EXTENSIONS_NO_INDEX = [
13
+ ...FILE_TRACK_ANNOTATION,
14
+ ...FILE_TRACK_ALIGNMENTS,
15
+ ...FILE_TRACK_VARIANT,
16
+ ...FILE_TRACK_WIG,
17
+ ...FILE_TRACK_SEG
18
+ ];
19
+ export const FILE_TRACK_EXTENSIONS = [
20
+ ...FILE_TRACK_EXTENSIONS_NO_INDEX,
21
+ ...FILE_TRACK_INDEX_EXTENSIONS
22
+ ]
23
+
24
+
25
+ export const FILE_BROWSER_EXTENSIONS = ['html', 'pdf'];
26
+
27
+
28
+ /**
29
+ * An array of file extensions that are protein structure files.
30
+ */
31
+ export const FILE_PROTEIN_STRUCTURE_EXTENSIONS = ['pdb', 'pdb.gz', 'cif', 'cif.gz', 'ent', 'ent.gz', 'mmtf', 'mmtf.gz'];
32
+ /**
33
+ * An array of file extensions that are considered tabular files.
34
+ */
35
+ export const FILE_DSV_EXTENSIONS = ['tab', 'csv', 'tsv', 'dsv'];
36
+ /**
37
+ * An array of file extensions that contain genomic sequence content.
38
+ */
39
+ export const FILE_TXT_GENOMICS_EXTENSIONS = ['fasta', 'fna', 'fsa', 'fa', 'fastp', 'fastq', 'faa', 'gbk', 'gff', 'vcf', 'seq'];
40
+
41
+ /**
42
+ * An array of file extensions that are considered TXT files.
43
+ */
44
+ export const FILE_TXT_EXTENSIONS = ['txt', 'log', 'yml', 'cfg', 'config', 'xml', 'yaml', ...FILE_DSV_EXTENSIONS, ...FILE_TXT_GENOMICS_EXTENSIONS];
45
+
46
+
47
+ /**
48
+ * An array of file extensions that are considered image files.
49
+ */
50
+ export const FILE_IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'svg'];
51
+
52
+ /**
53
+ * An array of file extensions that are considered OME (Open Microscopy Environment) files.
54
+ */
55
+ export const FILE_OME_EXTENSIONS = ['tif', 'ome.tif', 'ome.tiff', 'ome.tif.gz', 'ome.tiff.gz'];
56
+
57
+ /**
58
+ * An array of file extensions that can be opened in the browser.
59
+ * Includes common document formats such as HTML, PDF, and JSON, as well as image, DSV, TXT, and OME file formats.
60
+ */
61
+ export const FILE_EXTENSIONS_TO_OPEN = ['html', 'pdf', 'json', 'fcs',
62
+ ...FILE_IMAGE_EXTENSIONS,
63
+ ...FILE_TXT_EXTENSIONS,
64
+ ...FILE_TRACK_EXTENSIONS,
65
+ ...FILE_PROTEIN_STRUCTURE_EXTENSIONS
66
+ ];
67
+
68
+
69
+ /**
70
+ * Checks if a file has an extension that matches one in the provided list.
71
+ * @param filePath The file to check the extension of.
72
+ * @param extensions A list of file endings to check the file against.
73
+ * @returns True or false if the file has an ending in the extensions list.
74
+ */
75
+ export function matchesExtension(filePath: string, extensions: string[]): boolean {
76
+ filePath = filePath.toLowerCase();
77
+
78
+ // Decompress the filePath if it ends with .gz
79
+ if (filePath.endsWith('.gz')) {
80
+ filePath = filePath.slice(0, -3); // remove the .gz extension
81
+ }
82
+
83
+ // Now, get the extension of the file
84
+ const fileExtension = filePath.slice(filePath.lastIndexOf('.') + 1);
85
+
86
+ // Check if the fileExtension is in the list of extensions
87
+ return extensions.includes(fileExtension);
88
+ }