@cirrobio/sdk 0.2.4 → 0.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 (50) hide show
  1. package/dist/api/error-handler.d.ts +8 -0
  2. package/dist/api/error-handler.js +37 -0
  3. package/dist/api.d.ts +1 -8
  4. package/dist/api.js +4 -103
  5. package/dist/file/__test__/s3-utils.test.d.ts +1 -0
  6. package/dist/file/__test__/s3-utils.test.js +16 -0
  7. package/dist/file/actions/delete.fn.d.ts +9 -0
  8. package/dist/file/actions/delete.fn.js +15 -0
  9. package/dist/file/actions/sign-url.fn.d.ts +15 -0
  10. package/dist/file/actions/sign-url.fn.js +26 -0
  11. package/dist/file/actions/upload.fn.d.ts +13 -0
  12. package/dist/file/actions/upload.fn.js +23 -0
  13. package/dist/{extensions.fn.js → file/extensions.fn.js} +21 -16
  14. package/dist/file/file-object.model.d.ts +54 -0
  15. package/dist/file/file-object.model.js +8 -0
  16. package/dist/file/file.service.d.ts +29 -0
  17. package/dist/file/file.service.js +75 -0
  18. package/dist/file/project-access-context.d.ts +14 -0
  19. package/dist/file/project-access-context.js +24 -0
  20. package/dist/file/shared.d.ts +5 -0
  21. package/dist/file/shared.js +11 -0
  22. package/dist/file/util/credentials-mutex.so.d.ts +15 -0
  23. package/dist/file/util/credentials-mutex.so.js +32 -0
  24. package/dist/file/util/s3-client.d.ts +6 -0
  25. package/dist/file/util/s3-client.js +19 -0
  26. package/dist/file/util/s3-utils.d.ts +7 -0
  27. package/dist/file/util/s3-utils.js +17 -0
  28. package/dist/file.d.ts +11 -0
  29. package/dist/file.js +38 -0
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +1 -1
  32. package/package.json +16 -3
  33. package/src/api/error-handler.ts +36 -0
  34. package/src/api.ts +1 -36
  35. package/src/file/__test__/s3-utils.test.ts +17 -0
  36. package/src/file/actions/delete.fn.ts +18 -0
  37. package/src/file/actions/sign-url.fn.ts +35 -0
  38. package/src/file/actions/upload.fn.ts +30 -0
  39. package/src/{extensions.fn.ts → file/extensions.fn.ts} +1 -2
  40. package/src/file/file-object.model.ts +57 -0
  41. package/src/file/file.service.ts +79 -0
  42. package/src/file/project-access-context.ts +26 -0
  43. package/src/file/shared.ts +7 -0
  44. package/src/file/util/credentials-mutex.so.ts +33 -0
  45. package/src/file/util/s3-client.ts +17 -0
  46. package/src/file/util/s3-utils.ts +14 -0
  47. package/src/file.ts +11 -0
  48. package/src/index.ts +2 -2
  49. package/tsconfig.json +3 -3
  50. /package/dist/{extensions.fn.d.ts → file/extensions.fn.d.ts} +0 -0
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createS3Client = void 0;
4
+ const client_s3_1 = require("@aws-sdk/client-s3");
5
+ /**
6
+ * Creates an S3 client using the provided credentials.
7
+ */
8
+ function createS3Client(credentials) {
9
+ return new client_s3_1.S3Client({
10
+ credentials: {
11
+ accessKeyId: credentials.accessKeyId,
12
+ secretAccessKey: credentials.secretAccessKey,
13
+ sessionToken: credentials.sessionToken,
14
+ },
15
+ region: credentials.region,
16
+ useDualstackEndpoint: true,
17
+ });
18
+ }
19
+ exports.createS3Client = createS3Client;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts an S3 URI to a bucket and key
3
+ */
4
+ export declare function s3UriToParams(uri: string): {
5
+ Bucket: string;
6
+ Key: string;
7
+ };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.s3UriToParams = void 0;
4
+ /**
5
+ * Converts an S3 URI to a bucket and key
6
+ */
7
+ function s3UriToParams(uri) {
8
+ const matches = /^s3:\/\/([^/]+)\/(.+)$/.exec(uri);
9
+ if (!matches) {
10
+ throw new Error(`Received invalid uri: '${uri}'`);
11
+ }
12
+ return {
13
+ Bucket: matches[1],
14
+ Key: matches[2],
15
+ };
16
+ }
17
+ exports.s3UriToParams = s3UriToParams;
package/dist/file.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export * from './file/extensions.fn';
2
+ export { FileService } from './file/file.service';
3
+ export { deleteFile, DeleteFileParams } from './file/actions/delete.fn';
4
+ export { getSignedUrl, GetSignedUrlOptions } from './file/actions/sign-url.fn';
5
+ export { uploadFile, UploadFileParams } from './file/actions/upload.fn';
6
+ export { ProjectFileAccessContext } from './file/project-access-context';
7
+ export { FileSystemObject, FileSystemObjectType } from './file/file-object.model';
8
+ export { s3UriToParams } from './file/util/s3-utils';
9
+ export { createS3Client } from './file/util/s3-client';
10
+ export { getProjectS3Bucket } from './file/shared';
11
+ export { Progress, Upload } from '@aws-sdk/lib-storage';
package/dist/file.js ADDED
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.Upload = exports.getProjectS3Bucket = exports.createS3Client = exports.s3UriToParams = exports.FileSystemObjectType = exports.ProjectFileAccessContext = exports.uploadFile = exports.getSignedUrl = exports.deleteFile = exports.FileService = void 0;
18
+ __exportStar(require("./file/extensions.fn"), exports);
19
+ var file_service_1 = require("./file/file.service");
20
+ Object.defineProperty(exports, "FileService", { enumerable: true, get: function () { return file_service_1.FileService; } });
21
+ var delete_fn_1 = require("./file/actions/delete.fn");
22
+ Object.defineProperty(exports, "deleteFile", { enumerable: true, get: function () { return delete_fn_1.deleteFile; } });
23
+ var sign_url_fn_1 = require("./file/actions/sign-url.fn");
24
+ Object.defineProperty(exports, "getSignedUrl", { enumerable: true, get: function () { return sign_url_fn_1.getSignedUrl; } });
25
+ var upload_fn_1 = require("./file/actions/upload.fn");
26
+ Object.defineProperty(exports, "uploadFile", { enumerable: true, get: function () { return upload_fn_1.uploadFile; } });
27
+ var project_access_context_1 = require("./file/project-access-context");
28
+ Object.defineProperty(exports, "ProjectFileAccessContext", { enumerable: true, get: function () { return project_access_context_1.ProjectFileAccessContext; } });
29
+ var file_object_model_1 = require("./file/file-object.model");
30
+ Object.defineProperty(exports, "FileSystemObjectType", { enumerable: true, get: function () { return file_object_model_1.FileSystemObjectType; } });
31
+ var s3_utils_1 = require("./file/util/s3-utils");
32
+ Object.defineProperty(exports, "s3UriToParams", { enumerable: true, get: function () { return s3_utils_1.s3UriToParams; } });
33
+ var s3_client_1 = require("./file/util/s3-client");
34
+ Object.defineProperty(exports, "createS3Client", { enumerable: true, get: function () { return s3_client_1.createS3Client; } });
35
+ var shared_1 = require("./file/shared");
36
+ Object.defineProperty(exports, "getProjectS3Bucket", { enumerable: true, get: function () { return shared_1.getProjectS3Bucket; } });
37
+ var lib_storage_1 = require("@aws-sdk/lib-storage");
38
+ Object.defineProperty(exports, "Upload", { enumerable: true, get: function () { return lib_storage_1.Upload; } });
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
+ export * from './file';
1
2
  export * from './api';
2
- export * from './extensions.fn';
package/dist/index.js CHANGED
@@ -14,5 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./file"), exports);
17
18
  __exportStar(require("./api"), exports);
18
- __exportStar(require("./extensions.fn"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cirrobio/sdk",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "SDK for Cirro",
5
5
  "author": "CirroBio",
6
6
  "repository": {
@@ -16,10 +16,23 @@
16
16
  "build": "tsc",
17
17
  "prepare": "npm run build"
18
18
  },
19
+ "jest": {
20
+ "coverageReporters": [
21
+ "lcov"
22
+ ],
23
+ "preset": "ts-jest"
24
+ },
19
25
  "devDependencies": {
20
- "typescript": "^4.0"
26
+ "typescript": "^4.0",
27
+ "@types/jest": "^29.5.5",
28
+ "jest": "^29.7.0",
29
+ "ts-jest": "^29.1.1",
30
+ "@types/node": "^22.13.1"
21
31
  },
22
32
  "dependencies": {
23
- "@cirrobio/api-client": "^0.1"
33
+ "@cirrobio/api-client": "^0.2.4",
34
+ "@aws-sdk/client-s3": "^3.34.0",
35
+ "@aws-sdk/lib-storage": "^3.34.0",
36
+ "@aws-sdk/s3-request-presigner": "^3.34.0"
24
37
  }
25
38
  }
@@ -0,0 +1,36 @@
1
+ import { Middleware, ResponseContext } from "@cirrobio/api-client";
2
+
3
+ export class ApiError extends Error {
4
+ errors: string[];
5
+
6
+ constructor(message: string, errors: string[]) {
7
+ super(message);
8
+ this.errors = errors;
9
+ }
10
+ }
11
+
12
+ export class PortalErrorHandler implements Middleware {
13
+ async post(context: ResponseContext): Promise<Response | void> {
14
+ const { response } = context;
15
+ if (response && (response.status >= 200 && response.status < 300)) {
16
+ return response;
17
+ }
18
+
19
+ // Handle Error
20
+ let errorMessage: string;
21
+ const errors = [];
22
+ try {
23
+ const err = await response.json();
24
+ console.warn(err);
25
+ if ('errorDetail' in err) {
26
+ errorMessage = err.errorDetail;
27
+ errors.push(err.errors.map((e: any) => e.message));
28
+ } else {
29
+ errorMessage = err.message;
30
+ }
31
+ } catch (ignore) {
32
+ errorMessage = "Unknown Error";
33
+ }
34
+ throw new ApiError(errorMessage, errors);
35
+ }
36
+ }
package/src/api.ts CHANGED
@@ -1,36 +1 @@
1
- import { Middleware, ResponseContext } from "@cirrobio/api-client";
2
-
3
- export class ApiError extends Error {
4
- errors: string[];
5
-
6
- constructor(message: string, errors: string[]) {
7
- super(message);
8
- this.errors = errors;
9
- }
10
- }
11
-
12
- export class PortalErrorHandler implements Middleware {
13
- async post(context: ResponseContext): Promise<Response | void> {
14
- const { response } = context;
15
- if (response && (response.status >= 200 && response.status < 300)) {
16
- return response;
17
- }
18
-
19
- // Handle Error
20
- let errorMessage: string;
21
- const errors = [];
22
- try {
23
- const err = await response.json();
24
- console.warn(err);
25
- if ('errorDetail' in err) {
26
- errorMessage = err.errorDetail;
27
- errors.push(err.errors.map((e: any) => e.message));
28
- } else {
29
- errorMessage = err.message;
30
- }
31
- } catch (ignore) {
32
- errorMessage = "Unknown Error";
33
- }
34
- throw new ApiError(errorMessage, errors);
35
- }
36
- }
1
+ export { PortalErrorHandler, ApiError } from './api/error-handler';
@@ -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)).toThrowError(`Received invalid uri: '${uri}'`);
15
+ });
16
+ });
17
+ })
@@ -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,35 @@
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
+
7
+ export interface GetFileUrlParams extends GetSignedUrlOptions {
8
+ url: string;
9
+ credentials?: AWSCredentials;
10
+ }
11
+
12
+ export interface GetSignedUrlOptions {
13
+ download?: boolean;
14
+ gzip?: boolean;
15
+ timeout?: number;
16
+ filename?: string;
17
+ }
18
+
19
+ /**
20
+ * Get a signed URL for a file in S3 given its S3 URI.
21
+ */
22
+ export function getSignedUrl({ url, credentials, ...params }: GetFileUrlParams): Promise<string> {
23
+ const client = createS3Client(credentials);
24
+ const { Bucket, Key } = s3UriToParams(url);
25
+ const fileName = params.filename ?? Key.split('/').pop();
26
+ const args: GetObjectCommandInput = { Bucket, Key };
27
+ if (params?.download) {
28
+ args.ResponseContentDisposition = `attachment; filename="${fileName}"`
29
+ }
30
+ if (params?.gzip) {
31
+ args.ResponseContentEncoding = 'gzip'
32
+ }
33
+ const command = new GetObjectCommand(args);
34
+ return getSignedUrlInternal(client, command, { expiresIn: 60 * (params?.timeout ?? 5) });
35
+ }
@@ -0,0 +1,30 @@
1
+ import { Upload } from "@aws-sdk/lib-storage";
2
+ import { AWSCredentials } from '@cirrobio/api-client';
3
+ import { createS3Client } from "../util/s3-client";
4
+
5
+ export interface UploadFileParams {
6
+ bucket: string;
7
+ path: string;
8
+ file: File;
9
+ credentials: AWSCredentials;
10
+ metadata?: Record<string, string>;
11
+ }
12
+
13
+ /**
14
+ * Upload a file to S3
15
+ */
16
+ export function uploadFile({ bucket, path, file, credentials, metadata }: UploadFileParams): Upload {
17
+ const params = {
18
+ Bucket: bucket,
19
+ Key: path,
20
+ Body: file,
21
+ ContentType: file.type,
22
+ Metadata: metadata,
23
+ };
24
+ return new Upload({
25
+ client: createS3Client(credentials),
26
+ queueSize: 4,
27
+ params,
28
+ });
29
+ }
30
+
@@ -2,7 +2,6 @@
2
2
  /**
3
3
  * An array of file extensions that can be rendered in a genome viewer
4
4
  */
5
-
6
5
  export const FILE_TRACK_ANNOTATION = ['bed', 'bed.gz', 'gtf', 'gtf.gz'];
7
6
  export const FILE_TRACK_ALIGNMENTS = ['cram', 'cram.gz']; // TODO: put back bam
8
7
  export const FILE_TRACK_VARIANT = ['vcf', 'vcf.gz'];
@@ -86,4 +85,4 @@ export function matchesExtension(filePath: string, extensions: string[]): boolea
86
85
 
87
86
  // Check if the fileExtension is in the list of extensions
88
87
  return extensions.includes(fileExtension);
89
- }
88
+ }
@@ -0,0 +1,57 @@
1
+ import { ProjectFileAccessContext } from "./project-access-context";
2
+
3
+ export enum FileSystemObjectType {
4
+ FILE = 'file',
5
+ FOLDER = 'folder'
6
+ }
7
+
8
+ /**
9
+ * Represents a file that can be downloaded
10
+ */
11
+ export interface DownloadableFile {
12
+ url: string;
13
+ name?: string;
14
+ fileAccessContext: ProjectFileAccessContext;
15
+ }
16
+
17
+ /**
18
+ * Represents a file in Cirro
19
+ */
20
+ export interface FileSystemObject extends DownloadableFile {
21
+ /**
22
+ * Unique Id For Object
23
+ */
24
+ id: string;
25
+ /**
26
+ * Object S3 Url
27
+ */
28
+ url: string;
29
+ /**
30
+ * Object Path
31
+ */
32
+ path: string;
33
+ /**
34
+ * Object Name
35
+ */
36
+ name: string;
37
+ /**
38
+ * Object Size
39
+ */
40
+ size: number;
41
+ /**
42
+ * Object Kind (PNG, TXT)
43
+ */
44
+ kind: string;
45
+ /**
46
+ * Object Type (File or Folder)
47
+ */
48
+ type: FileSystemObjectType;
49
+ /**
50
+ * Metadata
51
+ */
52
+ metadata?: Record<string, any>;
53
+ /**
54
+ * Access context
55
+ */
56
+ fileAccessContext: ProjectFileAccessContext;
57
+ }
@@ -0,0 +1,79 @@
1
+ import { AccessType, AWSCredentials, FileApi } from "@cirrobio/api-client";
2
+ import { ProjectFileAccessContext } from "./project-access-context";
3
+ import { DownloadableFile } from "./file-object.model";
4
+ import { credentialsCache, credentialsMutex } from "./util/credentials-mutex.so";
5
+ import { GetSignedUrlOptions, GetFileUrlParams, getSignedUrl } from "./actions/sign-url.fn";
6
+ import { getProjectS3Bucket } from "./shared";
7
+
8
+ /**
9
+ * Service for viewing files in Cirro
10
+ * currently this only operates on files within a project
11
+ */
12
+ export class FileService {
13
+ constructor(
14
+ private readonly fileApi: FileApi
15
+ ) {}
16
+
17
+ /**
18
+ * Get contents of a file
19
+ */
20
+ async getProjectFile(file: DownloadableFile): Promise<Response> {
21
+ const url = await this.getSignedUrlFromProjectFile(file);
22
+ return fetch(url);
23
+ }
24
+
25
+ /**
26
+ * Get a signed URL for a file
27
+ */
28
+ async getSignedUrlFromProjectFile(file: DownloadableFile, params?: GetSignedUrlOptions): Promise<string> {
29
+ const credentials = await this.getProjectAccessCredentials(file.fileAccessContext);
30
+ const _params: GetFileUrlParams = {
31
+ ...params,
32
+ filename: file.name,
33
+ url: file.url,
34
+ credentials,
35
+ };
36
+ return getSignedUrl(_params);
37
+ }
38
+
39
+ /**
40
+ * Get a signed URL for a file given a path
41
+ */
42
+ async getSignedUrlFromProjectPath(fileAccessContext: ProjectFileAccessContext, path: string, params?: GetFileUrlParams): Promise<string> {
43
+ const credentials = await this.getProjectAccessCredentials(fileAccessContext);
44
+ const _params: GetFileUrlParams = {
45
+ ...params,
46
+ filename: path.split('/').pop(),
47
+ url: `s3://${getProjectS3Bucket(fileAccessContext.project.id)}/${path}`,
48
+ credentials,
49
+ };
50
+ return getSignedUrl(_params);
51
+ }
52
+
53
+ /**
54
+ * Get credentials for accessing a project file
55
+ */
56
+ async getProjectAccessCredentials(fileAccessContext: ProjectFileAccessContext): Promise<AWSCredentials> {
57
+ // Special case for project download, since we can cache the credentials
58
+ if (fileAccessContext.fileAccessRequest.accessType === AccessType.ProjectDownload) {
59
+ return this.getProjectReadCredentials(fileAccessContext);
60
+ }
61
+ return this.fileApi.generateProjectFileAccessToken({ projectId: fileAccessContext.project.id, fileAccessRequest: fileAccessContext.fileAccessRequest });
62
+ }
63
+
64
+ private async getProjectReadCredentials(fileAccessContext: ProjectFileAccessContext): Promise<AWSCredentials> {
65
+ const projectId = fileAccessContext.project.id;
66
+ return credentialsMutex.dispatch(async () => {
67
+ const cachedCredentials = credentialsCache.get(projectId);
68
+ const expirationTime = cachedCredentials ? cachedCredentials?.expiration : null;
69
+ const fetchNewCredentials = !expirationTime || expirationTime < new Date();
70
+ if (fetchNewCredentials) {
71
+ const fileAccessRequest = fileAccessContext.fileAccessRequest;
72
+ const credentials = await this.fileApi.generateProjectFileAccessToken({ projectId, fileAccessRequest });
73
+ credentialsCache.set(projectId, credentials);
74
+ return credentials;
75
+ }
76
+ return cachedCredentials;
77
+ });
78
+ }
79
+ }
@@ -0,0 +1,26 @@
1
+ import { AccessType, DatasetDetail, FileAccessRequest, Project } from "@cirrobio/api-client";
2
+
3
+ type ProjectIdentifiable = Pick<Project, 'id'>;
4
+
5
+ /**
6
+ * Helper class to encapsulate the file access for a project.
7
+ */
8
+ export class ProjectFileAccessContext {
9
+ constructor(
10
+ readonly project: ProjectIdentifiable,
11
+ readonly dataset: DatasetDetail,
12
+ readonly fileAccessRequest: FileAccessRequest
13
+ ) {
14
+ }
15
+
16
+ static projectDownload(project: ProjectIdentifiable): ProjectFileAccessContext {
17
+ const request: FileAccessRequest = { accessType: AccessType.ProjectDownload };
18
+ return new ProjectFileAccessContext(project, null, request);
19
+ }
20
+
21
+ static datasetDownload(project: ProjectIdentifiable, dataset: DatasetDetail): ProjectFileAccessContext {
22
+ const accessType = dataset.share ? AccessType.SharedDatasetDownload : AccessType.ProjectDownload;
23
+ const request: FileAccessRequest = { accessType, datasetId: dataset.id };
24
+ return new ProjectFileAccessContext(project, dataset, request);
25
+ }
26
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Get the S3 bucket name for a project.
3
+ * Will be deprecated in the future.
4
+ */
5
+ export function getProjectS3Bucket(projectId: string): string {
6
+ return `project-${projectId}`;
7
+ }
@@ -0,0 +1,33 @@
1
+ import { AWSCredentials } from '@cirrobio/api-client';
2
+
3
+ class Mutex<T> {
4
+ private mutex = Promise.resolve();
5
+
6
+ lock(): PromiseLike<() => void> {
7
+ let begin: (unlock: () => void) => void = () => { /* Do nothing */ };
8
+
9
+ this.mutex = this.mutex.then(() => new Promise(begin));
10
+
11
+ return new Promise((res) => {
12
+ begin = res;
13
+ });
14
+ }
15
+
16
+ async dispatch(fn: (() => T) | (() => PromiseLike<T>)): Promise<T> {
17
+ const unlock = await this.lock();
18
+ try {
19
+ return await Promise.resolve(fn());
20
+ } finally {
21
+ unlock();
22
+ }
23
+ }
24
+ }
25
+
26
+ /**
27
+ * A mutex to ensure that only one request for credentials is made at a time.
28
+ */
29
+ export const credentialsMutex = new Mutex<AWSCredentials>();
30
+ /**
31
+ * A cache of credentials to avoid making multiple requests for the same credentials.
32
+ */
33
+ export const credentialsCache: Map<string, AWSCredentials> = new Map<string, AWSCredentials>();
@@ -0,0 +1,17 @@
1
+ import { S3Client } from "@aws-sdk/client-s3";
2
+ import { AWSCredentials } from "@cirrobio/api-client";
3
+
4
+ /**
5
+ * Creates an S3 client using the provided credentials.
6
+ */
7
+ export function createS3Client(credentials: AWSCredentials): S3Client {
8
+ return new S3Client({
9
+ credentials: {
10
+ accessKeyId: credentials.accessKeyId,
11
+ secretAccessKey: credentials.secretAccessKey,
12
+ sessionToken: credentials.sessionToken,
13
+ },
14
+ region: credentials.region,
15
+ useDualstackEndpoint: true,
16
+ });
17
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Converts an S3 URI to a bucket and key
3
+ */
4
+ export function s3UriToParams(uri: string): { Bucket: string, Key: string } {
5
+ const matches = /^s3:\/\/([^/]+)\/(.+)$/.exec(uri);
6
+ if (!matches) {
7
+ throw new Error(`Received invalid uri: '${uri}'`);
8
+ }
9
+ return {
10
+ Bucket: matches[1],
11
+ Key: matches[2],
12
+ };
13
+ }
14
+
package/src/file.ts ADDED
@@ -0,0 +1,11 @@
1
+ export * from './file/extensions.fn'
2
+ export { FileService } from './file/file.service'
3
+ export { deleteFile, DeleteFileParams } from './file/actions/delete.fn'
4
+ export { getSignedUrl, GetSignedUrlOptions } from './file/actions/sign-url.fn'
5
+ export { uploadFile, UploadFileParams } from './file/actions/upload.fn'
6
+ export { ProjectFileAccessContext } from './file/project-access-context'
7
+ export { FileSystemObject, FileSystemObjectType } from './file/file-object.model'
8
+ export { s3UriToParams } from './file/util/s3-utils'
9
+ export { createS3Client } from './file/util/s3-client'
10
+ export { getProjectS3Bucket } from './file/shared'
11
+ export { Progress, Upload } from '@aws-sdk/lib-storage';
package/src/index.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from './api'
2
- export * from './extensions.fn'
1
+ export * from './file';
2
+ export * from './api';
package/tsconfig.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "declaration": true,
4
- "target": "es5",
4
+ "target": "es2018",
5
5
  "module": "commonjs",
6
6
  "moduleResolution": "node",
7
7
  "outDir": "dist",
8
8
  "lib": [
9
- "es2016",
9
+ "es2018",
10
10
  "dom"
11
11
  ],
12
12
  "typeRoots": [
@@ -17,4 +17,4 @@
17
17
  "dist",
18
18
  "node_modules"
19
19
  ]
20
- }
20
+ }