@tmlmobilidade/go-clients-oci-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/client.d.ts +134 -0
- package/dist/client.js +322 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/storage-error.d.ts +55 -0
- package/dist/storage-error.js +62 -0
- package/package.json +52 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { Region } from 'oci-common';
|
|
3
|
+
import { ObjectStorageClient } from 'oci-objectstorage';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for an OCI Object Storage client.
|
|
6
|
+
*
|
|
7
|
+
* Every OCI Storage client follows the same env var naming convention,
|
|
8
|
+
* scoped by `prefix`:
|
|
9
|
+
* `{PREFIX}_BUCKET_NAME` / `{PREFIX}_NAMESPACE` — bucket identity
|
|
10
|
+
* `{PREFIX}_REGION` — region identifier (e.g. `eu-frankfurt-1`)
|
|
11
|
+
* `{PREFIX}_TENANCY` / `{PREFIX}_USER` / `{PREFIX}_FINGERPRINT` — auth
|
|
12
|
+
* `{PREFIX}_PRIVATE_KEY` / `{PREFIX}_PRIVATE_KEY_PATH` — key (one required)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const client = await OCIStorageClient.getClient({ prefix: 'OCI_STORAGE' })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export interface OCIStorageConfig {
|
|
20
|
+
/** Env var prefix (e.g. `"OCI_STORAGE"`). */
|
|
21
|
+
prefix: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Internal bookkeeping for an active OCI Storage client.
|
|
25
|
+
*/
|
|
26
|
+
interface OCIStorageEntry {
|
|
27
|
+
bucketName: string;
|
|
28
|
+
client: ObjectStorageClient;
|
|
29
|
+
namespace: string;
|
|
30
|
+
region: Region;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* High-level wrapper around an OCI Object Storage client.
|
|
34
|
+
*
|
|
35
|
+
* Binds a bucket name, namespace, and region so callers don't repeat them
|
|
36
|
+
* on every call. Provides the same convenience methods as the original
|
|
37
|
+
* `OCIStorageProvider` for a drop-in migration path.
|
|
38
|
+
*/
|
|
39
|
+
export declare class OCIStorageClientWrapper {
|
|
40
|
+
private readonly entry;
|
|
41
|
+
constructor(entry: OCIStorageEntry);
|
|
42
|
+
/**
|
|
43
|
+
* Copies a file from one object key to another within the same bucket.
|
|
44
|
+
* @param source - The source object key.
|
|
45
|
+
* @param destination - The destination object key.
|
|
46
|
+
*/
|
|
47
|
+
copyFile(source: string, destination: string): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Deletes a single object by key.
|
|
50
|
+
* @param key - The object key to delete.
|
|
51
|
+
*/
|
|
52
|
+
deleteFile(key: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Deletes multiple objects by key. Rejects on the first error.
|
|
55
|
+
* @param keys - The object keys to delete.
|
|
56
|
+
*/
|
|
57
|
+
deleteFiles(keys: string[]): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Checks whether an object exists in the bucket.
|
|
60
|
+
* @param key - The object key to check.
|
|
61
|
+
* @returns `true` if the object exists, `false` if a 404 is returned.
|
|
62
|
+
*/
|
|
63
|
+
fileExists(key: string): Promise<boolean>;
|
|
64
|
+
/**
|
|
65
|
+
* Generates a pre-authenticated download URL for an object, valid for 1 hour.
|
|
66
|
+
* @param key - The object key.
|
|
67
|
+
* @returns A public HTTPS URL to download the object.
|
|
68
|
+
* @throws {HttpException} 404 if the object does not exist.
|
|
69
|
+
*/
|
|
70
|
+
getFileUrl(key: string): Promise<string>;
|
|
71
|
+
/**
|
|
72
|
+
* Lists object keys in the bucket, optionally filtered by a prefix.
|
|
73
|
+
* @param prefix - Optional prefix to filter by.
|
|
74
|
+
* @returns An array of object keys.
|
|
75
|
+
*/
|
|
76
|
+
listFiles(prefix?: string): Promise<string[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Uploads a file to the bucket.
|
|
79
|
+
*
|
|
80
|
+
* Uses the OCI `UploadManager` for resumable uploads with MD5 enforcement.
|
|
81
|
+
* Images are stored with `Content-Disposition: inline`; everything else
|
|
82
|
+
* uses `attachment`.
|
|
83
|
+
* @param key - The destination object key.
|
|
84
|
+
* @param body - The file contents as a Buffer, Readable, or ReadableStream.
|
|
85
|
+
* @param mimeType - Optional MIME type (e.g. `image/png`).
|
|
86
|
+
*/
|
|
87
|
+
uploadFile(key: string, body: Buffer | Readable | ReadableStream, mimeType?: string): Promise<void>;
|
|
88
|
+
private throwBlobStoreError;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Singleton-per-prefix factory for OCI Object Storage client wrappers.
|
|
92
|
+
*
|
|
93
|
+
* Each `prefix` maps to one client wrapper, created once and cached. Env vars
|
|
94
|
+
* are resolved at creation time using the prefix.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const client = await OCIStorageClient.getClient({ prefix: 'OCI_STORAGE' })
|
|
99
|
+
* await client.uploadFile('path/to/file.pdf', buffer)
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare class OCIStorageClient {
|
|
103
|
+
private static entries;
|
|
104
|
+
/**
|
|
105
|
+
* Gracefully tear down all active OCI Storage clients and clear the
|
|
106
|
+
* internal cache so subsequent `getClient` calls re-connect.
|
|
107
|
+
*
|
|
108
|
+
* Note: The OCI SDK's `ObjectStorageClient` has no explicit close method,
|
|
109
|
+
* so this primarily clears the cache and logs disconnection.
|
|
110
|
+
*/
|
|
111
|
+
static disconnectAll(): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Get (or create) an OCI Storage client wrapper for the given config.
|
|
114
|
+
*
|
|
115
|
+
* Each unique `prefix` produces a singleton wrapper. The first call
|
|
116
|
+
* validates required environment variables, creates the OCI SDK client,
|
|
117
|
+
* and caches it. Subsequent calls return the cached wrapper immediately.
|
|
118
|
+
*
|
|
119
|
+
* @param config - Storage configuration (env var prefix).
|
|
120
|
+
* @returns A wrapper with convenience methods bound to the resolved bucket.
|
|
121
|
+
* @throws If required env vars are missing or OCI auth fails.
|
|
122
|
+
*/
|
|
123
|
+
static getClient(config: OCIStorageConfig): Promise<OCIStorageClientWrapper>;
|
|
124
|
+
/**
|
|
125
|
+
* Create a new `OCIStorageEntry` by resolving environment variables and
|
|
126
|
+
* instantiating the OCI SDK `ObjectStorageClient`.
|
|
127
|
+
*
|
|
128
|
+
* Validates that all required env vars for the chosen auth mode (inline
|
|
129
|
+
* key or key file path) are set. On failure the cache entry is removed so
|
|
130
|
+
* retries work.
|
|
131
|
+
*/
|
|
132
|
+
private static createClient;
|
|
133
|
+
}
|
|
134
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/* * */
|
|
2
|
+
import { HTTP_STATUS, HttpException, mimeTypes } from '@tmlmobilidade/consts';
|
|
3
|
+
import { Logger } from '@tmlmobilidade/logger';
|
|
4
|
+
import { withRetry } from '@tmlmobilidade/utils';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { OciError, Region, SimpleAuthenticationDetailsProvider } from 'oci-common';
|
|
7
|
+
import { ObjectStorageClient, UploadManager } from 'oci-objectstorage';
|
|
8
|
+
import { CreatePreauthenticatedRequestDetails } from 'oci-objectstorage/lib/model/create-preauthenticated-request-details.js';
|
|
9
|
+
import { BlobStoreError, NotFoundError } from './storage-error.js';
|
|
10
|
+
/**
|
|
11
|
+
* High-level wrapper around an OCI Object Storage client.
|
|
12
|
+
*
|
|
13
|
+
* Binds a bucket name, namespace, and region so callers don't repeat them
|
|
14
|
+
* on every call. Provides the same convenience methods as the original
|
|
15
|
+
* `OCIStorageProvider` for a drop-in migration path.
|
|
16
|
+
*/
|
|
17
|
+
export class OCIStorageClientWrapper {
|
|
18
|
+
entry;
|
|
19
|
+
constructor(entry) {
|
|
20
|
+
this.entry = entry;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Copies a file from one object key to another within the same bucket.
|
|
24
|
+
* @param source - The source object key.
|
|
25
|
+
* @param destination - The destination object key.
|
|
26
|
+
*/
|
|
27
|
+
async copyFile(source, destination) {
|
|
28
|
+
try {
|
|
29
|
+
await withRetry(() => this.entry.client.copyObject({
|
|
30
|
+
bucketName: this.entry.bucketName,
|
|
31
|
+
copyObjectDetails: {
|
|
32
|
+
destinationBucket: this.entry.bucketName,
|
|
33
|
+
destinationNamespace: this.entry.namespace,
|
|
34
|
+
destinationObjectName: destination,
|
|
35
|
+
destinationRegion: this.entry.region.regionId,
|
|
36
|
+
sourceObjectName: source,
|
|
37
|
+
},
|
|
38
|
+
namespaceName: this.entry.namespace,
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
this.throwBlobStoreError(error, { destination, source });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Deletes a single object by key.
|
|
47
|
+
* @param key - The object key to delete.
|
|
48
|
+
*/
|
|
49
|
+
async deleteFile(key) {
|
|
50
|
+
try {
|
|
51
|
+
await withRetry(() => this.entry.client.deleteObject({
|
|
52
|
+
bucketName: this.entry.bucketName,
|
|
53
|
+
namespaceName: this.entry.namespace,
|
|
54
|
+
objectName: key,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
this.throwBlobStoreError(error, { key });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Deletes multiple objects by key. Rejects on the first error.
|
|
63
|
+
* @param keys - The object keys to delete.
|
|
64
|
+
*/
|
|
65
|
+
async deleteFiles(keys) {
|
|
66
|
+
try {
|
|
67
|
+
await Promise.all(keys.map(key => this.entry.client.deleteObject({
|
|
68
|
+
bucketName: this.entry.bucketName,
|
|
69
|
+
namespaceName: this.entry.namespace,
|
|
70
|
+
objectName: key,
|
|
71
|
+
})));
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
this.throwBlobStoreError(error, { keys });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Checks whether an object exists in the bucket.
|
|
79
|
+
* @param key - The object key to check.
|
|
80
|
+
* @returns `true` if the object exists, `false` if a 404 is returned.
|
|
81
|
+
*/
|
|
82
|
+
async fileExists(key) {
|
|
83
|
+
try {
|
|
84
|
+
return await withRetry(async () => {
|
|
85
|
+
try {
|
|
86
|
+
await this.entry.client.headObject({
|
|
87
|
+
bucketName: this.entry.bucketName,
|
|
88
|
+
namespaceName: this.entry.namespace,
|
|
89
|
+
objectName: key,
|
|
90
|
+
});
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof OciError && error.statusCode === 404)
|
|
95
|
+
return false;
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.throwBlobStoreError(error, { key });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Generates a pre-authenticated download URL for an object, valid for 1 hour.
|
|
106
|
+
* @param key - The object key.
|
|
107
|
+
* @returns A public HTTPS URL to download the object.
|
|
108
|
+
* @throws {HttpException} 404 if the object does not exist.
|
|
109
|
+
*/
|
|
110
|
+
async getFileUrl(key) {
|
|
111
|
+
try {
|
|
112
|
+
return await withRetry(async () => {
|
|
113
|
+
try {
|
|
114
|
+
await this.entry.client.headObject({
|
|
115
|
+
bucketName: this.entry.bucketName,
|
|
116
|
+
namespaceName: this.entry.namespace,
|
|
117
|
+
objectName: key,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof OciError && error.statusCode === 404) {
|
|
122
|
+
throw new HttpException(HTTP_STATUS.NOT_FOUND, `File ${key} does not exist in bucket ${this.entry.bucketName}`);
|
|
123
|
+
}
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
const response = await this.entry.client.createPreauthenticatedRequest({
|
|
127
|
+
bucketName: this.entry.bucketName,
|
|
128
|
+
createPreauthenticatedRequestDetails: {
|
|
129
|
+
accessType: CreatePreauthenticatedRequestDetails.AccessType.ObjectRead,
|
|
130
|
+
name: 'public-download-link',
|
|
131
|
+
objectName: key,
|
|
132
|
+
timeExpires: new Date(Date.now() + 1000 * 60 * 60),
|
|
133
|
+
},
|
|
134
|
+
namespaceName: this.entry.namespace,
|
|
135
|
+
});
|
|
136
|
+
return `https://objectstorage.${this.entry.region.regionId}.oraclecloud.com${response.preauthenticatedRequest.accessUri}`;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
this.throwBlobStoreError(error, { key });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Lists object keys in the bucket, optionally filtered by a prefix.
|
|
145
|
+
* @param prefix - Optional prefix to filter by.
|
|
146
|
+
* @returns An array of object keys.
|
|
147
|
+
*/
|
|
148
|
+
async listFiles(prefix) {
|
|
149
|
+
try {
|
|
150
|
+
return await withRetry(async () => {
|
|
151
|
+
const result = await this.entry.client.listObjects({
|
|
152
|
+
bucketName: this.entry.bucketName,
|
|
153
|
+
namespaceName: this.entry.namespace,
|
|
154
|
+
prefix,
|
|
155
|
+
});
|
|
156
|
+
return result.listObjects?.objects?.map(obj => obj.name) ?? [];
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
this.throwBlobStoreError(error, { prefix });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Uploads a file to the bucket.
|
|
165
|
+
*
|
|
166
|
+
* Uses the OCI `UploadManager` for resumable uploads with MD5 enforcement.
|
|
167
|
+
* Images are stored with `Content-Disposition: inline`; everything else
|
|
168
|
+
* uses `attachment`.
|
|
169
|
+
* @param key - The destination object key.
|
|
170
|
+
* @param body - The file contents as a Buffer, Readable, or ReadableStream.
|
|
171
|
+
* @param mimeType - Optional MIME type (e.g. `image/png`).
|
|
172
|
+
*/
|
|
173
|
+
async uploadFile(key, body, mimeType) {
|
|
174
|
+
const isImage = mimeType === mimeTypes.png || mimeType === mimeTypes.jpg || mimeType === mimeTypes.jpeg || mimeType === mimeTypes.gif || mimeType === mimeTypes.svg;
|
|
175
|
+
const uploadManager = new UploadManager(this.entry.client, { enforceMD5: true });
|
|
176
|
+
try {
|
|
177
|
+
// Uploads are not safely retried (streams may be consumed).
|
|
178
|
+
await uploadManager.upload({
|
|
179
|
+
content: body instanceof Buffer
|
|
180
|
+
? { blob: new Blob([new Uint8Array(body)], { type: mimeType }) }
|
|
181
|
+
: { stream: body },
|
|
182
|
+
requestDetails: {
|
|
183
|
+
bucketName: this.entry.bucketName,
|
|
184
|
+
contentDisposition: isImage ? 'inline' : 'attachment',
|
|
185
|
+
contentType: mimeType,
|
|
186
|
+
namespaceName: this.entry.namespace,
|
|
187
|
+
objectName: key,
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.throwBlobStoreError(error, { key, mimeType });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
throwBlobStoreError(error, context) {
|
|
196
|
+
if (error instanceof HttpException && error.statusCode === HTTP_STATUS.NOT_FOUND) {
|
|
197
|
+
throw new NotFoundError(error.message, { cause: error, context });
|
|
198
|
+
}
|
|
199
|
+
throw new BlobStoreError(error instanceof Error ? error.message : String(error), {
|
|
200
|
+
cause: error,
|
|
201
|
+
context,
|
|
202
|
+
retryable: true,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Singleton-per-prefix factory for OCI Object Storage client wrappers.
|
|
208
|
+
*
|
|
209
|
+
* Each `prefix` maps to one client wrapper, created once and cached. Env vars
|
|
210
|
+
* are resolved at creation time using the prefix.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* const client = await OCIStorageClient.getClient({ prefix: 'OCI_STORAGE' })
|
|
215
|
+
* await client.uploadFile('path/to/file.pdf', buffer)
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
export class OCIStorageClient {
|
|
219
|
+
static entries = new Map();
|
|
220
|
+
/**
|
|
221
|
+
* Gracefully tear down all active OCI Storage clients and clear the
|
|
222
|
+
* internal cache so subsequent `getClient` calls re-connect.
|
|
223
|
+
*
|
|
224
|
+
* Note: The OCI SDK's `ObjectStorageClient` has no explicit close method,
|
|
225
|
+
* so this primarily clears the cache and logs disconnection.
|
|
226
|
+
*/
|
|
227
|
+
static async disconnectAll() {
|
|
228
|
+
const settlements = await Promise.allSettled(this.entries.values());
|
|
229
|
+
for (const settlement of settlements) {
|
|
230
|
+
if (settlement.status === 'fulfilled') {
|
|
231
|
+
// OCISDK ObjectStorageClient has no explicit close/disconnect.
|
|
232
|
+
Logger.info({ message: `[OCIStorage] Disconnected client for namespace ${settlement.value.namespace}.` });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
this.entries.clear();
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get (or create) an OCI Storage client wrapper for the given config.
|
|
239
|
+
*
|
|
240
|
+
* Each unique `prefix` produces a singleton wrapper. The first call
|
|
241
|
+
* validates required environment variables, creates the OCI SDK client,
|
|
242
|
+
* and caches it. Subsequent calls return the cached wrapper immediately.
|
|
243
|
+
*
|
|
244
|
+
* @param config - Storage configuration (env var prefix).
|
|
245
|
+
* @returns A wrapper with convenience methods bound to the resolved bucket.
|
|
246
|
+
* @throws If required env vars are missing or OCI auth fails.
|
|
247
|
+
*/
|
|
248
|
+
static async getClient(config) {
|
|
249
|
+
const key = config.prefix;
|
|
250
|
+
if (!this.entries.has(key)) {
|
|
251
|
+
const promise = this.createClient(config).catch((error) => {
|
|
252
|
+
this.entries.delete(key);
|
|
253
|
+
throw error;
|
|
254
|
+
});
|
|
255
|
+
this.entries.set(key, promise);
|
|
256
|
+
}
|
|
257
|
+
const entry = await this.entries.get(key);
|
|
258
|
+
if (!entry)
|
|
259
|
+
throw new Error(`OCI Storage client not found for prefix ${key}`);
|
|
260
|
+
return new OCIStorageClientWrapper(entry);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Create a new `OCIStorageEntry` by resolving environment variables and
|
|
264
|
+
* instantiating the OCI SDK `ObjectStorageClient`.
|
|
265
|
+
*
|
|
266
|
+
* Validates that all required env vars for the chosen auth mode (inline
|
|
267
|
+
* key or key file path) are set. On failure the cache entry is removed so
|
|
268
|
+
* retries work.
|
|
269
|
+
*/
|
|
270
|
+
static async createClient(config) {
|
|
271
|
+
//
|
|
272
|
+
//
|
|
273
|
+
// Setup a function to get the environment variable by prefix
|
|
274
|
+
const env = (name) => process.env[`${config.prefix}_${name}`];
|
|
275
|
+
//
|
|
276
|
+
// Check if all required environment variables are set
|
|
277
|
+
Logger.info({ message: `[${config.prefix}] Creating OCI Storage client...` });
|
|
278
|
+
const bucketName = env('BUCKET_NAME');
|
|
279
|
+
if (!bucketName)
|
|
280
|
+
throw new Error(`${config.prefix}_BUCKET_NAME is not set`);
|
|
281
|
+
const namespace = env('NAMESPACE');
|
|
282
|
+
if (!namespace)
|
|
283
|
+
throw new Error(`${config.prefix}_NAMESPACE is not set`);
|
|
284
|
+
const region = env('REGION');
|
|
285
|
+
if (!region)
|
|
286
|
+
throw new Error(`${config.prefix}_REGION is not set`);
|
|
287
|
+
const tenancy = env('TENANCY');
|
|
288
|
+
if (!tenancy)
|
|
289
|
+
throw new Error(`${config.prefix}_TENANCY is not set`);
|
|
290
|
+
const user = env('USER');
|
|
291
|
+
if (!user)
|
|
292
|
+
throw new Error(`${config.prefix}_USER is not set`);
|
|
293
|
+
const fingerprint = env('FINGERPRINT');
|
|
294
|
+
if (!fingerprint)
|
|
295
|
+
throw new Error(`${config.prefix}_FINGERPRINT is not set`);
|
|
296
|
+
//
|
|
297
|
+
// Resolve private key
|
|
298
|
+
const privateKeyPath = env('PRIVATE_KEY_PATH');
|
|
299
|
+
const privateKeyValue = env('PRIVATE_KEY');
|
|
300
|
+
if (!privateKeyPath && !privateKeyValue)
|
|
301
|
+
throw new Error(`${config.prefix}_PRIVATE_KEY or ${config.prefix}_PRIVATE_KEY_PATH is not set`);
|
|
302
|
+
let privateKey;
|
|
303
|
+
if (privateKeyPath)
|
|
304
|
+
privateKey = readFileSync(privateKeyPath, 'utf8');
|
|
305
|
+
else if (privateKeyValue)
|
|
306
|
+
privateKey = privateKeyValue.replace(/\\n/g, '\n');
|
|
307
|
+
else
|
|
308
|
+
throw new Error(`Could not resolve private key for ${config.prefix} (missing ${config.prefix}_PRIVATE_KEY or ${config.prefix}_PRIVATE_KEY_PATH)`);
|
|
309
|
+
//
|
|
310
|
+
// Create OCI Storage client
|
|
311
|
+
const client = new ObjectStorageClient({
|
|
312
|
+
authenticationDetailsProvider: new SimpleAuthenticationDetailsProvider(tenancy, user, fingerprint, privateKey, null, Region.fromRegionId(region)),
|
|
313
|
+
});
|
|
314
|
+
Logger.info({ message: `[${config.prefix}] OCI Storage client created.` });
|
|
315
|
+
return {
|
|
316
|
+
bucketName,
|
|
317
|
+
client,
|
|
318
|
+
namespace,
|
|
319
|
+
region: Region.fromRegionId(region),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export declare abstract class StorageError extends Error {
|
|
2
|
+
abstract readonly code: string;
|
|
3
|
+
abstract readonly retryable: boolean;
|
|
4
|
+
readonly cause?: unknown;
|
|
5
|
+
readonly context: Record<string, unknown>;
|
|
6
|
+
constructor(message: string, options?: {
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
context?: Record<string, unknown>;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export declare class NotFoundError extends StorageError {
|
|
12
|
+
readonly code = "STORAGE_NOT_FOUND";
|
|
13
|
+
readonly retryable = false;
|
|
14
|
+
}
|
|
15
|
+
export declare class ValidationError extends StorageError {
|
|
16
|
+
readonly code = "STORAGE_VALIDATION";
|
|
17
|
+
readonly retryable = false;
|
|
18
|
+
}
|
|
19
|
+
export declare class ConflictError extends StorageError {
|
|
20
|
+
readonly code = "STORAGE_CONFLICT";
|
|
21
|
+
readonly retryable = false;
|
|
22
|
+
}
|
|
23
|
+
export declare class BlobStoreError extends StorageError {
|
|
24
|
+
readonly code = "STORAGE_BLOB";
|
|
25
|
+
readonly retryable: boolean;
|
|
26
|
+
constructor(message: string, options?: {
|
|
27
|
+
cause?: unknown;
|
|
28
|
+
context?: Record<string, unknown>;
|
|
29
|
+
retryable?: boolean;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export declare class MetadataError extends StorageError {
|
|
33
|
+
readonly code = "STORAGE_METADATA";
|
|
34
|
+
readonly retryable: boolean;
|
|
35
|
+
constructor(message: string, options?: {
|
|
36
|
+
cause?: unknown;
|
|
37
|
+
context?: Record<string, unknown>;
|
|
38
|
+
retryable?: boolean;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export declare class CompensationError extends StorageError {
|
|
42
|
+
readonly code = "STORAGE_COMPENSATION";
|
|
43
|
+
readonly retryable = false;
|
|
44
|
+
readonly suppressed: unknown[];
|
|
45
|
+
constructor(message: string, options: {
|
|
46
|
+
cause?: unknown;
|
|
47
|
+
context?: Record<string, unknown>;
|
|
48
|
+
suppressed: unknown[];
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export declare class TimeoutError extends StorageError {
|
|
52
|
+
readonly code = "STORAGE_TIMEOUT";
|
|
53
|
+
readonly retryable = true;
|
|
54
|
+
}
|
|
55
|
+
export declare function toStorageError(error: unknown, context?: Record<string, unknown>): StorageError;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* * */
|
|
2
|
+
export class StorageError extends Error {
|
|
3
|
+
cause;
|
|
4
|
+
context;
|
|
5
|
+
constructor(message, options) {
|
|
6
|
+
super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);
|
|
7
|
+
this.name = new.target.name;
|
|
8
|
+
this.context = options?.context ?? {};
|
|
9
|
+
this.cause = options?.cause;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class NotFoundError extends StorageError {
|
|
13
|
+
code = 'STORAGE_NOT_FOUND';
|
|
14
|
+
retryable = false;
|
|
15
|
+
}
|
|
16
|
+
export class ValidationError extends StorageError {
|
|
17
|
+
code = 'STORAGE_VALIDATION';
|
|
18
|
+
retryable = false;
|
|
19
|
+
}
|
|
20
|
+
export class ConflictError extends StorageError {
|
|
21
|
+
code = 'STORAGE_CONFLICT';
|
|
22
|
+
retryable = false;
|
|
23
|
+
}
|
|
24
|
+
export class BlobStoreError extends StorageError {
|
|
25
|
+
code = 'STORAGE_BLOB';
|
|
26
|
+
retryable;
|
|
27
|
+
constructor(message, options) {
|
|
28
|
+
super(message, options);
|
|
29
|
+
this.retryable = options?.retryable ?? true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class MetadataError extends StorageError {
|
|
33
|
+
code = 'STORAGE_METADATA';
|
|
34
|
+
retryable;
|
|
35
|
+
constructor(message, options) {
|
|
36
|
+
super(message, options);
|
|
37
|
+
this.retryable = options?.retryable ?? false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class CompensationError extends StorageError {
|
|
41
|
+
code = 'STORAGE_COMPENSATION';
|
|
42
|
+
retryable = false;
|
|
43
|
+
suppressed;
|
|
44
|
+
constructor(message, options) {
|
|
45
|
+
super(message, options);
|
|
46
|
+
this.suppressed = options.suppressed;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export class TimeoutError extends StorageError {
|
|
50
|
+
code = 'STORAGE_TIMEOUT';
|
|
51
|
+
retryable = true;
|
|
52
|
+
}
|
|
53
|
+
export function toStorageError(error, context) {
|
|
54
|
+
if (error instanceof StorageError) {
|
|
55
|
+
if (context && Object.keys(context).length > 0) {
|
|
56
|
+
return Object.assign(error, { context: { ...error.context, ...context } });
|
|
57
|
+
}
|
|
58
|
+
return error;
|
|
59
|
+
}
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
return new BlobStoreError(message, { cause: error, context, retryable: true });
|
|
62
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tmlmobilidade/go-clients-oci-storage",
|
|
3
|
+
"version": "20260723.1350.16",
|
|
4
|
+
"author": {
|
|
5
|
+
"email": "iso@tmlmobilidade.pt",
|
|
6
|
+
"name": "TML-ISO"
|
|
7
|
+
},
|
|
8
|
+
"license": "AGPL-3.0-or-later",
|
|
9
|
+
"homepage": "https://go.tmlmobilidade.pt",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/tmlmobilidade/go/issues"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/tmlmobilidade/go.git"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"public transit",
|
|
19
|
+
"tml",
|
|
20
|
+
"transportes metropolitanos de lisboa",
|
|
21
|
+
"go"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"main": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc && resolve-tspaths",
|
|
34
|
+
"lint": "eslint ./src/ && tsc --noEmit",
|
|
35
|
+
"lint:fix": "eslint ./src/ --fix",
|
|
36
|
+
"watch": "tsc-watch --onSuccess 'resolve-tspaths'"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@tmlmobilidade/consts": "*",
|
|
40
|
+
"@tmlmobilidade/logger": "*",
|
|
41
|
+
"@tmlmobilidade/utils": "*",
|
|
42
|
+
"oci-common": "2.137.0",
|
|
43
|
+
"oci-objectstorage": "2.137.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@tmlmobilidade/tsconfig": "*",
|
|
47
|
+
"@types/node": "26.1.1",
|
|
48
|
+
"resolve-tspaths": "0.8.23",
|
|
49
|
+
"tsc-watch": "7.2.1",
|
|
50
|
+
"typescript": "6.0.3"
|
|
51
|
+
}
|
|
52
|
+
}
|