@tmlmobilidade/go-providers-storage 20260723.1350.16
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.
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/operations/batch-delete.d.ts +14 -0
- package/dist/operations/batch-delete.js +45 -0
- package/dist/operations/batch-upload.d.ts +28 -0
- package/dist/operations/batch-upload.js +45 -0
- package/dist/operations/copy.d.ts +11 -0
- package/dist/operations/copy.js +71 -0
- package/dist/operations/delete.d.ts +17 -0
- package/dist/operations/delete.js +43 -0
- package/dist/operations/exists.d.ts +13 -0
- package/dist/operations/exists.js +30 -0
- package/dist/operations/find-by-id.d.ts +9 -0
- package/dist/operations/find-by-id.js +22 -0
- package/dist/operations/find-many.d.ts +13 -0
- package/dist/operations/find-many.js +16 -0
- package/dist/operations/get-signed-url.d.ts +9 -0
- package/dist/operations/get-signed-url.js +29 -0
- package/dist/operations/index.d.ts +12 -0
- package/dist/operations/index.js +12 -0
- package/dist/operations/move.d.ts +11 -0
- package/dist/operations/move.js +98 -0
- package/dist/operations/replace.d.ts +17 -0
- package/dist/operations/replace.js +99 -0
- package/dist/operations/upload.d.ts +13 -0
- package/dist/operations/upload.js +52 -0
- package/dist/operations/validate-upload.d.ts +15 -0
- package/dist/operations/validate-upload.js +28 -0
- package/dist/provider.d.ts +130 -0
- package/dist/provider.js +146 -0
- package/dist/types/blob-body.d.ts +2 -0
- package/dist/types/blob-body.js +1 -0
- package/dist/types/deps.d.ts +6 -0
- package/dist/types/deps.js +1 -0
- package/dist/types/hooks.d.ts +8 -0
- package/dist/types/hooks.js +1 -0
- package/dist/types/operation-context.d.ts +8 -0
- package/dist/types/operation-context.js +1 -0
- package/dist/utils/mime.d.ts +23 -0
- package/dist/utils/mime.js +41 -0
- package/dist/utils/observability.d.ts +35 -0
- package/dist/utils/observability.js +46 -0
- package/dist/utils/operation-runner.d.ts +51 -0
- package/dist/utils/operation-runner.js +124 -0
- package/dist/utils/storage-key.d.ts +22 -0
- package/dist/utils/storage-key.js +29 -0
- package/package.json +56 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* * */
|
|
2
|
+
import { getMimeTypeFromFileExtension } from '../utils/mime.js';
|
|
3
|
+
import { runSaga } from '../utils/operation-runner.js';
|
|
4
|
+
import { buildStorageKey, storageKey, tempStorageKey } from '../utils/storage-key.js';
|
|
5
|
+
import { ConflictError, MetadataError, NotFoundError } from '@tmlmobilidade/go-clients-oci-storage';
|
|
6
|
+
import { goDb } from '@tmlmobilidade/go-interfaces-godb';
|
|
7
|
+
import { CreateAttachmentSchema } from '@tmlmobilidade/types';
|
|
8
|
+
import { convertObject } from '@tmlmobilidade/utils';
|
|
9
|
+
export async function replace(deps, input) {
|
|
10
|
+
//
|
|
11
|
+
const { createAttachmentDto, file, hooks } = input;
|
|
12
|
+
const fileId = createAttachmentDto._id;
|
|
13
|
+
const mimeType = getMimeTypeFromFileExtension(createAttachmentDto.name);
|
|
14
|
+
const filePath = buildStorageKey(createAttachmentDto.scope, createAttachmentDto.resource_id, fileId, createAttachmentDto.name);
|
|
15
|
+
const context = { attachmentId: fileId, key: filePath, operation: 'replace', resourceId: createAttachmentDto.resource_id, scope: createAttachmentDto.scope };
|
|
16
|
+
let existing;
|
|
17
|
+
let asideKey = '';
|
|
18
|
+
let inserted;
|
|
19
|
+
let asideCreated = false;
|
|
20
|
+
let newBlobWritten = false;
|
|
21
|
+
return runSaga({
|
|
22
|
+
context,
|
|
23
|
+
hooks,
|
|
24
|
+
observability: deps.observability,
|
|
25
|
+
result: () => {
|
|
26
|
+
if (!inserted)
|
|
27
|
+
throw new MetadataError('Replace completed without inserted attachment');
|
|
28
|
+
return inserted;
|
|
29
|
+
},
|
|
30
|
+
steps: [
|
|
31
|
+
{
|
|
32
|
+
execute: async () => {
|
|
33
|
+
const found = await goDb.core.attachments.findOne({ _id: { $eq: fileId } });
|
|
34
|
+
if (!found)
|
|
35
|
+
throw new NotFoundError('File not found', { context: { fileId } });
|
|
36
|
+
existing = found;
|
|
37
|
+
const existingPath = storageKey(found);
|
|
38
|
+
if (existingPath !== filePath) {
|
|
39
|
+
throw new ConflictError('File ID is provided, but the file path is different from the existing file', {
|
|
40
|
+
context: { existingPath, filePath },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
asideKey = tempStorageKey(existingPath);
|
|
44
|
+
},
|
|
45
|
+
name: 'loadAndValidate',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
compensate: async () => {
|
|
49
|
+
if (asideCreated)
|
|
50
|
+
await deps.blobs.deleteFile(asideKey);
|
|
51
|
+
},
|
|
52
|
+
execute: async () => {
|
|
53
|
+
await deps.blobs.copyFile(filePath, asideKey);
|
|
54
|
+
asideCreated = true;
|
|
55
|
+
},
|
|
56
|
+
name: 'copyAside',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
compensate: async () => {
|
|
60
|
+
if (newBlobWritten) {
|
|
61
|
+
await deps.blobs.copyFile(asideKey, filePath);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
execute: async () => {
|
|
65
|
+
await deps.blobs.uploadFile(filePath, file, mimeType);
|
|
66
|
+
newBlobWritten = true;
|
|
67
|
+
},
|
|
68
|
+
name: 'putBlob',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
compensate: async () => {
|
|
72
|
+
if (inserted && existing) {
|
|
73
|
+
await goDb.core.attachments.deleteById(fileId, { forceIfLocked: true });
|
|
74
|
+
const restored = convertObject(existing, CreateAttachmentSchema);
|
|
75
|
+
await goDb.core.attachments.insertOne({ ...restored, _id: fileId });
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
execute: async () => {
|
|
79
|
+
await goDb.core.attachments.deleteById(fileId, { forceIfLocked: true });
|
|
80
|
+
inserted = await goDb.core.attachments.insertOne({
|
|
81
|
+
...createAttachmentDto,
|
|
82
|
+
_id: fileId,
|
|
83
|
+
type: mimeType,
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
name: 'replaceMetadata',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
execute: async () => {
|
|
90
|
+
if (asideCreated) {
|
|
91
|
+
await deps.blobs.deleteFile(asideKey);
|
|
92
|
+
asideCreated = false;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
name: 'cleanupAside',
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BlobBody } from '../types/blob-body.js';
|
|
2
|
+
import { type StorageDeps } from '../types/deps.js';
|
|
3
|
+
import { type OperationHooks } from '../types/hooks.js';
|
|
4
|
+
import { type OperationContext } from '../types/operation-context.js';
|
|
5
|
+
import { type Attachment, type CreateAttachmentDto } from '@tmlmobilidade/types';
|
|
6
|
+
export interface UploadInput {
|
|
7
|
+
createAttachmentDto: CreateAttachmentDto & {
|
|
8
|
+
_id?: string;
|
|
9
|
+
};
|
|
10
|
+
file: BlobBody;
|
|
11
|
+
hooks?: OperationHooks<OperationContext, Attachment>;
|
|
12
|
+
}
|
|
13
|
+
export declare function upload(deps: StorageDeps, input: UploadInput): Promise<Attachment>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* * */
|
|
2
|
+
import { getMimeTypeFromFileExtension } from '../utils/mime.js';
|
|
3
|
+
import { runSaga } from '../utils/operation-runner.js';
|
|
4
|
+
import { buildStorageKey } from '../utils/storage-key.js';
|
|
5
|
+
import { MetadataError } from '@tmlmobilidade/go-clients-oci-storage';
|
|
6
|
+
import { goDb } from '@tmlmobilidade/go-interfaces-godb';
|
|
7
|
+
import { generateRandomString } from '@tmlmobilidade/strings';
|
|
8
|
+
/* * */
|
|
9
|
+
export async function upload(deps, input) {
|
|
10
|
+
//
|
|
11
|
+
const { createAttachmentDto, file, hooks } = input;
|
|
12
|
+
const fileId = createAttachmentDto._id || generateRandomString({ length: 5 });
|
|
13
|
+
const mimeType = getMimeTypeFromFileExtension(createAttachmentDto.name);
|
|
14
|
+
const filePath = buildStorageKey(createAttachmentDto.scope, createAttachmentDto.resource_id, fileId, createAttachmentDto.name);
|
|
15
|
+
const context = { attachmentId: fileId, key: filePath, operation: 'upload', resourceId: createAttachmentDto.resource_id, scope: createAttachmentDto.scope };
|
|
16
|
+
let inserted;
|
|
17
|
+
return runSaga({
|
|
18
|
+
context,
|
|
19
|
+
hooks,
|
|
20
|
+
observability: deps.observability,
|
|
21
|
+
result: () => {
|
|
22
|
+
if (!inserted)
|
|
23
|
+
throw new MetadataError('Upload completed without inserted attachment');
|
|
24
|
+
return inserted;
|
|
25
|
+
},
|
|
26
|
+
steps: [
|
|
27
|
+
{
|
|
28
|
+
compensate: async () => {
|
|
29
|
+
await deps.blobs.deleteFile(filePath);
|
|
30
|
+
},
|
|
31
|
+
execute: async () => {
|
|
32
|
+
await deps.blobs.uploadFile(filePath, file, mimeType);
|
|
33
|
+
},
|
|
34
|
+
name: 'putBlob',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
compensate: async () => {
|
|
38
|
+
if (inserted)
|
|
39
|
+
await goDb.core.attachments.deleteById(inserted._id, { forceIfLocked: true });
|
|
40
|
+
},
|
|
41
|
+
execute: async () => {
|
|
42
|
+
inserted = await goDb.core.attachments.insertOne({
|
|
43
|
+
...createAttachmentDto,
|
|
44
|
+
_id: fileId,
|
|
45
|
+
type: mimeType,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
name: 'insertMetadata',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type OperationHooks } from '../types/hooks.js';
|
|
2
|
+
import { type OperationContext } from '../types/operation-context.js';
|
|
3
|
+
import { type Observability } from '../utils/observability.js';
|
|
4
|
+
import { type CreateAttachmentDto } from '@tmlmobilidade/types';
|
|
5
|
+
export interface ValidateUploadResult {
|
|
6
|
+
extension: string;
|
|
7
|
+
mimeType: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ValidateUploadInput {
|
|
10
|
+
createAttachmentDto: Pick<CreateAttachmentDto, 'name' | 'size'>;
|
|
11
|
+
hooks?: OperationHooks<OperationContext, ValidateUploadResult>;
|
|
12
|
+
maxSizeBytes?: number;
|
|
13
|
+
observability?: Observability;
|
|
14
|
+
}
|
|
15
|
+
export declare function validateUpload(input: ValidateUploadInput): Promise<ValidateUploadResult>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* * */
|
|
2
|
+
import { getFileExtension, getMimeTypeFromFileExtension } from '../utils/mime.js';
|
|
3
|
+
import { runOperation } from '../utils/operation-runner.js';
|
|
4
|
+
import { ValidationError } from '@tmlmobilidade/go-clients-oci-storage';
|
|
5
|
+
/* * */
|
|
6
|
+
export async function validateUpload(input) {
|
|
7
|
+
//
|
|
8
|
+
const { createAttachmentDto, hooks, maxSizeBytes, observability } = input;
|
|
9
|
+
const context = { operation: 'validateUpload' };
|
|
10
|
+
return runOperation({
|
|
11
|
+
context,
|
|
12
|
+
execute: async () => {
|
|
13
|
+
const extension = getFileExtension(createAttachmentDto.name);
|
|
14
|
+
const mimeType = getMimeTypeFromFileExtension(createAttachmentDto.name);
|
|
15
|
+
if (typeof createAttachmentDto.size === 'number' && createAttachmentDto.size < 0) {
|
|
16
|
+
throw new ValidationError('File size cannot be negative', { context: { size: createAttachmentDto.size } });
|
|
17
|
+
}
|
|
18
|
+
if (maxSizeBytes !== undefined && createAttachmentDto.size > maxSizeBytes) {
|
|
19
|
+
throw new ValidationError(`File size exceeds maximum of ${maxSizeBytes} bytes`, {
|
|
20
|
+
context: { maxSizeBytes, size: createAttachmentDto.size },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return { extension, mimeType };
|
|
24
|
+
},
|
|
25
|
+
hooks,
|
|
26
|
+
observability,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { BlobBody } from './types/blob-body.js';
|
|
2
|
+
import { type OperationHooks } from './types/hooks.js';
|
|
3
|
+
import { type OperationContext } from './types/operation-context.js';
|
|
4
|
+
import { type Filter, type FindOptions } from '@tmlmobilidade/go-clients-mongo';
|
|
5
|
+
import { type Attachment, type CreateAttachmentDto } from '@tmlmobilidade/types';
|
|
6
|
+
import * as operations from './operations/index.js';
|
|
7
|
+
declare class StorageProviderClass {
|
|
8
|
+
private readonly deps;
|
|
9
|
+
private static _instance;
|
|
10
|
+
private constructor();
|
|
11
|
+
static getInstance(): Promise<StorageProviderClass>;
|
|
12
|
+
/**
|
|
13
|
+
* Deletes multiple files by their IDs in a batch operation.
|
|
14
|
+
* @param fileIds - An array of file IDs to be deleted.
|
|
15
|
+
* @param hooks - Operation hooks for observability or side effects.
|
|
16
|
+
* @param options - Optional: Batch deletion settings such as concurrency.
|
|
17
|
+
* @returns A promise resolving to the batch result containing deleted fileIds.
|
|
18
|
+
*/
|
|
19
|
+
batchDelete(fileIds: string[], hooks?: OperationHooks<OperationContext, operations.BatchResult<{
|
|
20
|
+
fileId: string;
|
|
21
|
+
}>>, options?: Pick<operations.BatchDeleteInput, 'concurrency'>): Promise<operations.BatchResult<{
|
|
22
|
+
fileId: string;
|
|
23
|
+
}>>;
|
|
24
|
+
/**
|
|
25
|
+
* Uploads multiple items in a batch operation.
|
|
26
|
+
* @param items - List of items to be uploaded.
|
|
27
|
+
* @param hooks - Operation hooks for the batch upload.
|
|
28
|
+
* @param options - Optional: Control concurrency for batch uploads.
|
|
29
|
+
* @returns A promise resolving to the batch upload result with attachments.
|
|
30
|
+
*/
|
|
31
|
+
batchUpload(items: operations.BatchUploadItem[], hooks?: OperationHooks<OperationContext, operations.BatchResult<Attachment>>, options?: {
|
|
32
|
+
concurrency?: number;
|
|
33
|
+
}): Promise<operations.BatchResult<Attachment>>;
|
|
34
|
+
/**
|
|
35
|
+
* Copies a file to a new resource and scope.
|
|
36
|
+
* @param fileId - The ID of the file to copy.
|
|
37
|
+
* @param scope - The new scope for the copied file.
|
|
38
|
+
* @param resourceId - The ID of the new associated resource.
|
|
39
|
+
* @param hooks - Hooks for operation observability and behavior.
|
|
40
|
+
* @returns A promise resolving to the new copied Attachment.
|
|
41
|
+
*/
|
|
42
|
+
copy(fileId: string, scope: string, resourceId: string, hooks?: OperationHooks<OperationContext, Attachment>): Promise<Attachment>;
|
|
43
|
+
/**
|
|
44
|
+
* Deletes a single file by its ID.
|
|
45
|
+
* @param fileId - The ID of the file to delete.
|
|
46
|
+
* @param hooks - Hooks for operation side effects and observability.
|
|
47
|
+
* @returns A promise resolving with the deleted fileId.
|
|
48
|
+
*/
|
|
49
|
+
delete(fileId: string, hooks?: OperationHooks<OperationContext, {
|
|
50
|
+
fileId: string;
|
|
51
|
+
}>): Promise<{
|
|
52
|
+
fileId: string;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Checks for existence of a file by ID or key.
|
|
56
|
+
* @param params - An object containing fileId or key.
|
|
57
|
+
* @param hooks - Hooks for operation execution.
|
|
58
|
+
* @returns A promise resolving to the existence result.
|
|
59
|
+
*/
|
|
60
|
+
exists(params: {
|
|
61
|
+
fileId?: string;
|
|
62
|
+
key?: string;
|
|
63
|
+
}, hooks?: OperationHooks<OperationContext, operations.ExistsResult>): Promise<operations.ExistsResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Finds a file attachment by its unique ID.
|
|
66
|
+
* @param id - The ID of the attachment.
|
|
67
|
+
* @param hooks - Hooks for operation execution.
|
|
68
|
+
* @returns A promise resolving to the Attachment or null if not found.
|
|
69
|
+
*/
|
|
70
|
+
findById(id: string, hooks?: OperationHooks<OperationContext, Attachment | null>): Promise<Attachment | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Generates a signed URL for downloading or accessing a file.
|
|
73
|
+
* @param params - Parameters with fileId or key for which to generate the URL.
|
|
74
|
+
* @param hooks - Hooks for execution/observability.
|
|
75
|
+
* @returns A promise resolving to the signed URL as a string.
|
|
76
|
+
*/
|
|
77
|
+
getSignedUrl(params: {
|
|
78
|
+
fileId?: string;
|
|
79
|
+
key?: string;
|
|
80
|
+
}, hooks?: OperationHooks<OperationContext, string>): Promise<string>;
|
|
81
|
+
/**
|
|
82
|
+
* Moves a file to a different resource and/or scope.
|
|
83
|
+
* @param fileId - ID of the file to move.
|
|
84
|
+
* @param scope - Target scope.
|
|
85
|
+
* @param resourceId - Target resource ID.
|
|
86
|
+
* @param hooks - Hooks for observability or side effects.
|
|
87
|
+
* @returns A promise resolving to the updated Attachment.
|
|
88
|
+
*/
|
|
89
|
+
move(fileId: string, scope: string, resourceId: string, hooks?: OperationHooks<OperationContext, Attachment>): Promise<Attachment>;
|
|
90
|
+
/**
|
|
91
|
+
* Replaces an existing file's data while preserving its identifier.
|
|
92
|
+
* @param file - Blob body of the new file.
|
|
93
|
+
* @param createAttachmentDto - Data transfer object describing the attachment, must include _id.
|
|
94
|
+
* @param hooks - Hooks for operation execution.
|
|
95
|
+
* @returns A promise resolving to the updated Attachment.
|
|
96
|
+
*/
|
|
97
|
+
replace(file: BlobBody, createAttachmentDto: CreateAttachmentDto & {
|
|
98
|
+
_id: string;
|
|
99
|
+
}, hooks?: OperationHooks<OperationContext, Attachment>): Promise<Attachment>;
|
|
100
|
+
/**
|
|
101
|
+
* Uploads a new file and creates the corresponding attachment.
|
|
102
|
+
* @param file - Blob body of the file to upload.
|
|
103
|
+
* @param createAttachmentDto - Data transfer object for attachment creation. _id is optional.
|
|
104
|
+
* @param hooks - Hooks for operation execution.
|
|
105
|
+
* @returns A promise resolving to the created Attachment.
|
|
106
|
+
*/
|
|
107
|
+
upload(file: BlobBody, createAttachmentDto: CreateAttachmentDto & {
|
|
108
|
+
_id?: string;
|
|
109
|
+
}, hooks?: OperationHooks<OperationContext, Attachment>): Promise<Attachment>;
|
|
110
|
+
/**
|
|
111
|
+
* Validates whether a file upload can proceed, based on file size and name.
|
|
112
|
+
* @param createAttachmentDto - Contains file information to validate (name and size).
|
|
113
|
+
* @param hooks - Hooks for operation execution.
|
|
114
|
+
* @param options - Optional: Max allowed file size in bytes.
|
|
115
|
+
* @returns A promise resolving to the validation result.
|
|
116
|
+
*/
|
|
117
|
+
validateUpload(createAttachmentDto: Pick<CreateAttachmentDto, 'name' | 'size'>, hooks?: OperationHooks<OperationContext, operations.ValidateUploadResult>, options?: {
|
|
118
|
+
maxSizeBytes?: number;
|
|
119
|
+
}): Promise<operations.ValidateUploadResult>;
|
|
120
|
+
/**
|
|
121
|
+
* Finds multiple attachments matching the filter criteria.
|
|
122
|
+
* @param filter - Filter criteria to match attachments.
|
|
123
|
+
* @param options - Find options.
|
|
124
|
+
* @param hooks - Hooks for operation execution.
|
|
125
|
+
* @returns A promise resolving to an array of matching attachments.
|
|
126
|
+
*/
|
|
127
|
+
findMany(filter?: Filter<Attachment>, options?: FindOptions, hooks?: OperationHooks<OperationContext, Attachment[]>): Promise<Attachment[] | null>;
|
|
128
|
+
}
|
|
129
|
+
export declare const storageProvider: StorageProviderClass;
|
|
130
|
+
export {};
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/* * */
|
|
2
|
+
import { createLoggerObservability } from './utils/observability.js';
|
|
3
|
+
import { OCIStorageClient } from '@tmlmobilidade/go-clients-oci-storage';
|
|
4
|
+
import { asyncSingletonProxy } from '@tmlmobilidade/utils';
|
|
5
|
+
import * as operations from './operations/index.js';
|
|
6
|
+
/* * */
|
|
7
|
+
class StorageProviderClass {
|
|
8
|
+
deps;
|
|
9
|
+
//
|
|
10
|
+
static _instance;
|
|
11
|
+
constructor(deps) {
|
|
12
|
+
this.deps = deps;
|
|
13
|
+
}
|
|
14
|
+
static async getInstance() {
|
|
15
|
+
if (!StorageProviderClass._instance) {
|
|
16
|
+
const ociStorageClient = await OCIStorageClient.getClient({ prefix: 'OCI_STORAGE' });
|
|
17
|
+
StorageProviderClass._instance = new StorageProviderClass({ blobs: ociStorageClient, observability: createLoggerObservability() });
|
|
18
|
+
}
|
|
19
|
+
return StorageProviderClass._instance;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Deletes multiple files by their IDs in a batch operation.
|
|
23
|
+
* @param fileIds - An array of file IDs to be deleted.
|
|
24
|
+
* @param hooks - Operation hooks for observability or side effects.
|
|
25
|
+
* @param options - Optional: Batch deletion settings such as concurrency.
|
|
26
|
+
* @returns A promise resolving to the batch result containing deleted fileIds.
|
|
27
|
+
*/
|
|
28
|
+
async batchDelete(fileIds, hooks, options) {
|
|
29
|
+
return operations.batchDelete(this.deps, { concurrency: options?.concurrency, fileIds, hooks });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Uploads multiple items in a batch operation.
|
|
33
|
+
* @param items - List of items to be uploaded.
|
|
34
|
+
* @param hooks - Operation hooks for the batch upload.
|
|
35
|
+
* @param options - Optional: Control concurrency for batch uploads.
|
|
36
|
+
* @returns A promise resolving to the batch upload result with attachments.
|
|
37
|
+
*/
|
|
38
|
+
async batchUpload(items, hooks, options) {
|
|
39
|
+
return operations.batchUpload(this.deps, { concurrency: options?.concurrency, hooks, items });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Copies a file to a new resource and scope.
|
|
43
|
+
* @param fileId - The ID of the file to copy.
|
|
44
|
+
* @param scope - The new scope for the copied file.
|
|
45
|
+
* @param resourceId - The ID of the new associated resource.
|
|
46
|
+
* @param hooks - Hooks for operation observability and behavior.
|
|
47
|
+
* @returns A promise resolving to the new copied Attachment.
|
|
48
|
+
*/
|
|
49
|
+
async copy(fileId, scope, resourceId, hooks) {
|
|
50
|
+
return operations.copy(this.deps, { fileId, hooks, resourceId, scope });
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Deletes a single file by its ID.
|
|
54
|
+
* @param fileId - The ID of the file to delete.
|
|
55
|
+
* @param hooks - Hooks for operation side effects and observability.
|
|
56
|
+
* @returns A promise resolving with the deleted fileId.
|
|
57
|
+
*/
|
|
58
|
+
async delete(fileId, hooks) {
|
|
59
|
+
return operations.deleteAttachment(this.deps, { fileId, hooks });
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Checks for existence of a file by ID or key.
|
|
63
|
+
* @param params - An object containing fileId or key.
|
|
64
|
+
* @param hooks - Hooks for operation execution.
|
|
65
|
+
* @returns A promise resolving to the existence result.
|
|
66
|
+
*/
|
|
67
|
+
async exists(params, hooks) {
|
|
68
|
+
return operations.exists(this.deps, { ...params, hooks });
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Finds a file attachment by its unique ID.
|
|
72
|
+
* @param id - The ID of the attachment.
|
|
73
|
+
* @param hooks - Hooks for operation execution.
|
|
74
|
+
* @returns A promise resolving to the Attachment or null if not found.
|
|
75
|
+
*/
|
|
76
|
+
async findById(id, hooks) {
|
|
77
|
+
return operations.findById(this.deps, { hooks, id });
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Generates a signed URL for downloading or accessing a file.
|
|
81
|
+
* @param params - Parameters with fileId or key for which to generate the URL.
|
|
82
|
+
* @param hooks - Hooks for execution/observability.
|
|
83
|
+
* @returns A promise resolving to the signed URL as a string.
|
|
84
|
+
*/
|
|
85
|
+
async getSignedUrl(params, hooks) {
|
|
86
|
+
return operations.getSignedUrl(this.deps, { ...params, hooks });
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Moves a file to a different resource and/or scope.
|
|
90
|
+
* @param fileId - ID of the file to move.
|
|
91
|
+
* @param scope - Target scope.
|
|
92
|
+
* @param resourceId - Target resource ID.
|
|
93
|
+
* @param hooks - Hooks for observability or side effects.
|
|
94
|
+
* @returns A promise resolving to the updated Attachment.
|
|
95
|
+
*/
|
|
96
|
+
async move(fileId, scope, resourceId, hooks) {
|
|
97
|
+
return operations.move(this.deps, { fileId, hooks, resourceId, scope });
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Replaces an existing file's data while preserving its identifier.
|
|
101
|
+
* @param file - Blob body of the new file.
|
|
102
|
+
* @param createAttachmentDto - Data transfer object describing the attachment, must include _id.
|
|
103
|
+
* @param hooks - Hooks for operation execution.
|
|
104
|
+
* @returns A promise resolving to the updated Attachment.
|
|
105
|
+
*/
|
|
106
|
+
async replace(file, createAttachmentDto, hooks) {
|
|
107
|
+
return operations.replace(this.deps, { createAttachmentDto, file, hooks });
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Uploads a new file and creates the corresponding attachment.
|
|
111
|
+
* @param file - Blob body of the file to upload.
|
|
112
|
+
* @param createAttachmentDto - Data transfer object for attachment creation. _id is optional.
|
|
113
|
+
* @param hooks - Hooks for operation execution.
|
|
114
|
+
* @returns A promise resolving to the created Attachment.
|
|
115
|
+
*/
|
|
116
|
+
async upload(file, createAttachmentDto, hooks) {
|
|
117
|
+
return operations.upload(this.deps, { createAttachmentDto, file, hooks });
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Validates whether a file upload can proceed, based on file size and name.
|
|
121
|
+
* @param createAttachmentDto - Contains file information to validate (name and size).
|
|
122
|
+
* @param hooks - Hooks for operation execution.
|
|
123
|
+
* @param options - Optional: Max allowed file size in bytes.
|
|
124
|
+
* @returns A promise resolving to the validation result.
|
|
125
|
+
*/
|
|
126
|
+
async validateUpload(createAttachmentDto, hooks, options) {
|
|
127
|
+
return operations.validateUpload({
|
|
128
|
+
createAttachmentDto,
|
|
129
|
+
hooks,
|
|
130
|
+
maxSizeBytes: options?.maxSizeBytes,
|
|
131
|
+
observability: this.deps.observability,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Finds multiple attachments matching the filter criteria.
|
|
136
|
+
* @param filter - Filter criteria to match attachments.
|
|
137
|
+
* @param options - Find options.
|
|
138
|
+
* @param hooks - Hooks for operation execution.
|
|
139
|
+
* @returns A promise resolving to an array of matching attachments.
|
|
140
|
+
*/
|
|
141
|
+
async findMany(filter, options, hooks) {
|
|
142
|
+
return operations.findMany(this.deps, { filter, hooks, options });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/* * */
|
|
146
|
+
export const storageProvider = asyncSingletonProxy(StorageProviderClass);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type StorageError } from '@tmlmobilidade/go-clients-oci-storage';
|
|
2
|
+
export interface OperationHooks<TContext, TResult> {
|
|
3
|
+
onError?: (ctx: TContext, error: StorageError) => Promise<void> | void;
|
|
4
|
+
onFinally?: (ctx: TContext) => Promise<void> | void;
|
|
5
|
+
onRollback?: (ctx: TContext, error?: StorageError) => Promise<void> | void;
|
|
6
|
+
onStart?: (ctx: TContext) => Promise<void> | void;
|
|
7
|
+
onSuccess?: (ctx: TContext, result: TResult) => Promise<void> | void;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the file extension from a file name and validates it against supported MIME types.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} fileName - The name of the file.
|
|
5
|
+
* @returns {string} The lowercase file extension.
|
|
6
|
+
* @throws {ValidationError} If the file does not have an extension or the extension is unsupported.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getFileExtension(fileName: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Retrieves the MIME type associated with a file extension found in the file name.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} fileName - The name of the file.
|
|
13
|
+
* @returns {string} The MIME type corresponding to the file extension.
|
|
14
|
+
* @throws {ValidationError} If the extension is missing or unsupported.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getMimeTypeFromFileExtension(fileName: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Finds the file extension corresponding to the provided MIME type.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} mimeType - The MIME type to look up.
|
|
21
|
+
* @returns {string} The file extension matching the MIME type, or an empty string if not found.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getFileExtensionFromMimeType(mimeType: string): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* * */
|
|
2
|
+
import { mimeTypes } from '@tmlmobilidade/consts';
|
|
3
|
+
import { ValidationError } from '@tmlmobilidade/go-clients-oci-storage';
|
|
4
|
+
/* * */
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the file extension from a file name and validates it against supported MIME types.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} fileName - The name of the file.
|
|
9
|
+
* @returns {string} The lowercase file extension.
|
|
10
|
+
* @throws {ValidationError} If the file does not have an extension or the extension is unsupported.
|
|
11
|
+
*/
|
|
12
|
+
export function getFileExtension(fileName) {
|
|
13
|
+
const extension = fileName.split('.').pop()?.toLowerCase();
|
|
14
|
+
if (!extension)
|
|
15
|
+
throw new ValidationError('File has no extension', { context: { fileName } });
|
|
16
|
+
if (!(extension in mimeTypes)) {
|
|
17
|
+
throw new ValidationError(`Unsupported file extension: ${extension}`, { context: { extension, fileName } });
|
|
18
|
+
}
|
|
19
|
+
return extension;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Retrieves the MIME type associated with a file extension found in the file name.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} fileName - The name of the file.
|
|
25
|
+
* @returns {string} The MIME type corresponding to the file extension.
|
|
26
|
+
* @throws {ValidationError} If the extension is missing or unsupported.
|
|
27
|
+
*/
|
|
28
|
+
export function getMimeTypeFromFileExtension(fileName) {
|
|
29
|
+
return mimeTypes[getFileExtension(fileName)];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Finds the file extension corresponding to the provided MIME type.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} mimeType - The MIME type to look up.
|
|
35
|
+
* @returns {string} The file extension matching the MIME type, or an empty string if not found.
|
|
36
|
+
*/
|
|
37
|
+
export function getFileExtensionFromMimeType(mimeType) {
|
|
38
|
+
if (!mimeType)
|
|
39
|
+
return '';
|
|
40
|
+
return Object.keys(mimeTypes).find(key => mimeTypes[key] === mimeType) ?? '';
|
|
41
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type OperationContext } from '../types/operation-context.js';
|
|
2
|
+
/**
|
|
3
|
+
* Interface defining the contract for observability in storage operations.
|
|
4
|
+
*
|
|
5
|
+
* @interface Observability
|
|
6
|
+
* @property {function} onOperationEnd - Callback to be invoked when an operation ends.
|
|
7
|
+
* @property {function} onOperationStart - Callback to be invoked when an operation starts.
|
|
8
|
+
* @property {function} onStep - Callback to be invoked when a step in an operation is executed.
|
|
9
|
+
*/
|
|
10
|
+
export interface Observability {
|
|
11
|
+
onOperationEnd: (ctx: OperationContext & {
|
|
12
|
+
durationMs: number;
|
|
13
|
+
outcome: 'error' | 'success';
|
|
14
|
+
}) => void;
|
|
15
|
+
onOperationStart: (ctx: OperationContext) => void;
|
|
16
|
+
onStep: (ctx: OperationContext & {
|
|
17
|
+
phase: 'compensate' | 'execute';
|
|
18
|
+
step: string;
|
|
19
|
+
}) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* No-op implementation of the Observability interface.
|
|
23
|
+
*
|
|
24
|
+
* @type {Observability}
|
|
25
|
+
* @property {function} onOperationEnd - Does nothing.
|
|
26
|
+
* @property {function} onOperationStart - Does nothing.
|
|
27
|
+
* @property {function} onStep - Does nothing.
|
|
28
|
+
*/
|
|
29
|
+
export declare const noopObservability: Observability;
|
|
30
|
+
/**
|
|
31
|
+
* Creates an Observability implementation that logs to the console using the Logger.
|
|
32
|
+
*
|
|
33
|
+
* @returns {Observability} The logger-based observability implementation.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createLoggerObservability(): Observability;
|