@plasius/storage 1.0.0 → 1.0.1
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/CHANGELOG.md +16 -1
- package/dist/index.cjs +100 -0
- package/dist/index.cjs.map +1 -0
- package/{dist-cjs/index.d.ts → dist/index.d.cts} +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +72 -81
- package/dist/index.js.map +1 -0
- package/docs/adrs/index.md +4 -0
- package/package.json +6 -7
- package/dist/index.d.ts.map +0 -1
- package/dist-cjs/index.d.ts.map +0 -1
- package/dist-cjs/index.js +0 -87
package/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,20 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
|
|
|
20
20
|
- **Security**
|
|
21
21
|
- (placeholder)
|
|
22
22
|
|
|
23
|
+
## [1.0.1] - 2026-02-13
|
|
24
|
+
|
|
25
|
+
- **Added**
|
|
26
|
+
- (placeholder)
|
|
27
|
+
|
|
28
|
+
- **Changed**
|
|
29
|
+
- Replace dual-`tsc` build steps with `tsup` to emit ESM + CJS + types side-by-side in `dist/` (`index.js`, `index.cjs`, `index.d.ts`).
|
|
30
|
+
|
|
31
|
+
- **Fixed**
|
|
32
|
+
- (placeholder)
|
|
33
|
+
|
|
34
|
+
- **Security**
|
|
35
|
+
- (placeholder)
|
|
36
|
+
|
|
23
37
|
## [1.0.0] - 2026-02-12
|
|
24
38
|
|
|
25
39
|
- **Added**
|
|
@@ -48,7 +62,7 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
|
|
|
48
62
|
|
|
49
63
|
---
|
|
50
64
|
|
|
51
|
-
[Unreleased]: https://github.com/Plasius-LTD/storage/compare/v1.0.
|
|
65
|
+
[Unreleased]: https://github.com/Plasius-LTD/storage/compare/v1.0.1...HEAD
|
|
52
66
|
|
|
53
67
|
## [1.0.0] - 2026-02-11
|
|
54
68
|
|
|
@@ -64,3 +78,4 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
|
|
|
64
78
|
- **Security**
|
|
65
79
|
- (placeholder)
|
|
66
80
|
[1.0.0]: https://github.com/Plasius-LTD/storage/releases/tag/v1.0.0
|
|
81
|
+
[1.0.1]: https://github.com/Plasius-LTD/storage/releases/tag/v1.0.1
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
uploadUserImageShare: () => uploadUserImageShare
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var import_storage_file_share = require("@azure/storage-file-share");
|
|
27
|
+
async function uploadUserImageShare(userId, version, buffer, contentType, options = {}) {
|
|
28
|
+
if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
"AZURE_STORAGE_CONNECTION_STRING is not set in environment variables."
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
if (!userId || typeof userId !== "string") {
|
|
34
|
+
throw new Error("userId is required and must be a string.");
|
|
35
|
+
}
|
|
36
|
+
if (typeof version !== "number" || isNaN(version)) {
|
|
37
|
+
throw new Error("version is required and must be a number.");
|
|
38
|
+
}
|
|
39
|
+
if (!buffer || !(buffer instanceof Buffer)) {
|
|
40
|
+
throw new Error("buffer is required and must be a Buffer.");
|
|
41
|
+
}
|
|
42
|
+
if (!contentType || typeof contentType !== "string") {
|
|
43
|
+
throw new Error("contentType is required and must be a string.");
|
|
44
|
+
}
|
|
45
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
46
|
+
const baseDelayMs = options.baseDelayMs ?? 500;
|
|
47
|
+
const shareName = "avatars";
|
|
48
|
+
const directoryName = userId;
|
|
49
|
+
const fileName = `${version}.jpg`;
|
|
50
|
+
const serviceClient = import_storage_file_share.ShareServiceClient.fromConnectionString(
|
|
51
|
+
process.env.AZURE_STORAGE_CONNECTION_STRING
|
|
52
|
+
);
|
|
53
|
+
const shareClient = serviceClient.getShareClient(shareName);
|
|
54
|
+
const directoryClient = shareClient.getDirectoryClient(directoryName);
|
|
55
|
+
const fileClient = directoryClient.getFileClient(fileName);
|
|
56
|
+
let attempt = 0;
|
|
57
|
+
let lastError = null;
|
|
58
|
+
while (attempt < maxRetries) {
|
|
59
|
+
try {
|
|
60
|
+
await shareClient.createIfNotExists();
|
|
61
|
+
await directoryClient.createIfNotExists();
|
|
62
|
+
await fileClient.create(buffer.length, {
|
|
63
|
+
fileHttpHeaders: { fileContentType: contentType }
|
|
64
|
+
});
|
|
65
|
+
await fileClient.uploadRange(buffer, 0, buffer.length);
|
|
66
|
+
return fileClient.url;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
lastError = err;
|
|
69
|
+
if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
|
|
70
|
+
console.error(
|
|
71
|
+
`Attempt ${attempt + 1} to upload user image failed: ${err.message}`
|
|
72
|
+
);
|
|
73
|
+
} else {
|
|
74
|
+
console.error(
|
|
75
|
+
`Attempt ${attempt + 1} to upload user image failed:`,
|
|
76
|
+
err
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (attempt < maxRetries - 1) {
|
|
80
|
+
const baseDelay = Math.pow(2, attempt) * baseDelayMs;
|
|
81
|
+
const jitter = Math.random() * baseDelay;
|
|
82
|
+
const delay = baseDelay / 2 + jitter;
|
|
83
|
+
console.warn(
|
|
84
|
+
`Backing off for ${Math.round(delay)} ms before retrying...`
|
|
85
|
+
);
|
|
86
|
+
await new Promise((res) => setTimeout(res, delay));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
attempt++;
|
|
90
|
+
}
|
|
91
|
+
const lastErrorMsg = lastError && typeof lastError === "object" && "message" in lastError && typeof lastError.message === "string" ? lastError.message : String(lastError ?? "");
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Failed to upload user image after ${maxRetries} attempts: ${lastErrorMsg}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
97
|
+
0 && (module.exports = {
|
|
98
|
+
uploadUserImageShare
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { ShareServiceClient } from \"@azure/storage-file-share\";\n\ninterface UploadOptions {\n maxRetries?: number;\n baseDelayMs?: number;\n}\n\n/**\n * Uploads a user image to Azure File Share storage with retries and error handling.\n * @param userId - The user's ID (used as directory name)\n * @param version - The version number (used as file name)\n * @param buffer - The file data as a Buffer\n * @param contentType - The MIME type of the file\n * @param options - Optional settings for retries and backoff\n * @returns URL to the uploaded file\n */\nexport async function uploadUserImageShare(\n userId: string,\n version: number,\n buffer: Buffer,\n contentType: string,\n options: UploadOptions = {}\n): Promise<string> {\n // Parameter validation\n if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {\n throw new Error(\n \"AZURE_STORAGE_CONNECTION_STRING is not set in environment variables.\"\n );\n }\n if (!userId || typeof userId !== \"string\") {\n throw new Error(\"userId is required and must be a string.\");\n }\n if (typeof version !== \"number\" || isNaN(version)) {\n throw new Error(\"version is required and must be a number.\");\n }\n if (!buffer || !(buffer instanceof Buffer)) {\n throw new Error(\"buffer is required and must be a Buffer.\");\n }\n if (!contentType || typeof contentType !== \"string\") {\n throw new Error(\"contentType is required and must be a string.\");\n }\n\n const maxRetries = options.maxRetries ?? 3;\n const baseDelayMs = options.baseDelayMs ?? 500;\n\n const shareName = \"avatars\";\n const directoryName = userId;\n const fileName = `${version}.jpg`;\n\n const serviceClient = ShareServiceClient.fromConnectionString(\n process.env.AZURE_STORAGE_CONNECTION_STRING\n );\n const shareClient = serviceClient.getShareClient(shareName);\n const directoryClient = shareClient.getDirectoryClient(directoryName);\n const fileClient = directoryClient.getFileClient(fileName);\n\n // Retry logic with exponential backoff\n let attempt = 0;\n let lastError: unknown = null;\n while (attempt < maxRetries) {\n try {\n // Ensure share exists\n await shareClient.createIfNotExists();\n // Ensure directory exists\n await directoryClient.createIfNotExists();\n // Create file (set size)\n await fileClient.create(buffer.length, {\n fileHttpHeaders: { fileContentType: contentType },\n });\n // Upload content\n await fileClient.uploadRange(buffer, 0, buffer.length);\n // Return file URL\n return fileClient.url;\n } catch (err: unknown) {\n lastError = err;\n if (\n err &&\n typeof err === \"object\" &&\n \"message\" in err &&\n typeof (err as { message: unknown }).message === \"string\"\n ) {\n console.error(\n `Attempt ${attempt + 1} to upload user image failed: ${\n (err as { message: string }).message\n }`\n );\n } else {\n console.error(\n `Attempt ${attempt + 1} to upload user image failed:`,\n err\n );\n }\n // Exponential backoff with jitter\n if (attempt < maxRetries - 1) {\n const baseDelay = Math.pow(2, attempt) * baseDelayMs;\n const jitter = Math.random() * baseDelay;\n const delay = baseDelay / 2 + jitter; // Between 0.5x and 1.5x base delay\n console.warn(\n `Backing off for ${Math.round(delay)} ms before retrying...`\n );\n await new Promise((res) => setTimeout(res, delay));\n }\n }\n attempt++;\n }\n const lastErrorMsg =\n lastError &&\n typeof lastError === \"object\" &&\n \"message\" in lastError &&\n typeof (lastError as { message: unknown }).message === \"string\"\n ? (lastError as { message: string }).message\n : String((lastError as Error) ?? \"\");\n throw new Error(\n `Failed to upload user image after ${maxRetries} attempts: ${lastErrorMsg}`\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAAmC;AAgBnC,eAAsB,qBACpB,QACA,SACA,QACA,aACA,UAAyB,CAAC,GACT;AAEjB,MAAI,CAAC,QAAQ,IAAI,iCAAiC;AAChD,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,MAAI,OAAO,YAAY,YAAY,MAAM,OAAO,GAAG;AACjD,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,MAAI,CAAC,UAAU,EAAE,kBAAkB,SAAS;AAC1C,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,MAAI,CAAC,eAAe,OAAO,gBAAgB,UAAU;AACnD,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AAEA,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,cAAc,QAAQ,eAAe;AAE3C,QAAM,YAAY;AAClB,QAAM,gBAAgB;AACtB,QAAM,WAAW,GAAG,OAAO;AAE3B,QAAM,gBAAgB,6CAAmB;AAAA,IACvC,QAAQ,IAAI;AAAA,EACd;AACA,QAAM,cAAc,cAAc,eAAe,SAAS;AAC1D,QAAM,kBAAkB,YAAY,mBAAmB,aAAa;AACpE,QAAM,aAAa,gBAAgB,cAAc,QAAQ;AAGzD,MAAI,UAAU;AACd,MAAI,YAAqB;AACzB,SAAO,UAAU,YAAY;AAC3B,QAAI;AAEF,YAAM,YAAY,kBAAkB;AAEpC,YAAM,gBAAgB,kBAAkB;AAExC,YAAM,WAAW,OAAO,OAAO,QAAQ;AAAA,QACrC,iBAAiB,EAAE,iBAAiB,YAAY;AAAA,MAClD,CAAC;AAED,YAAM,WAAW,YAAY,QAAQ,GAAG,OAAO,MAAM;AAErD,aAAO,WAAW;AAAA,IACpB,SAAS,KAAc;AACrB,kBAAY;AACZ,UACE,OACA,OAAO,QAAQ,YACf,aAAa,OACb,OAAQ,IAA6B,YAAY,UACjD;AACA,gBAAQ;AAAA,UACN,WAAW,UAAU,CAAC,iCACnB,IAA4B,OAC/B;AAAA,QACF;AAAA,MACF,OAAO;AACL,gBAAQ;AAAA,UACN,WAAW,UAAU,CAAC;AAAA,UACtB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,UAAU,aAAa,GAAG;AAC5B,cAAM,YAAY,KAAK,IAAI,GAAG,OAAO,IAAI;AACzC,cAAM,SAAS,KAAK,OAAO,IAAI;AAC/B,cAAM,QAAQ,YAAY,IAAI;AAC9B,gBAAQ;AAAA,UACN,mBAAmB,KAAK,MAAM,KAAK,CAAC;AAAA,QACtC;AACA,cAAM,IAAI,QAAQ,CAAC,QAAQ,WAAW,KAAK,KAAK,CAAC;AAAA,MACnD;AAAA,IACF;AACA;AAAA,EACF;AACA,QAAM,eACJ,aACA,OAAO,cAAc,YACrB,aAAa,aACb,OAAQ,UAAmC,YAAY,WAClD,UAAkC,UACnC,OAAQ,aAAuB,EAAE;AACvC,QAAM,IAAI;AAAA,IACR,qCAAqC,UAAU,cAAc,YAAY;AAAA,EAC3E;AACF;","names":[]}
|
|
@@ -11,6 +11,6 @@ interface UploadOptions {
|
|
|
11
11
|
* @param options - Optional settings for retries and backoff
|
|
12
12
|
* @returns URL to the uploaded file
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
declare function uploadUserImageShare(userId: string, version: number, buffer: Buffer, contentType: string, options?: UploadOptions): Promise<string>;
|
|
15
|
+
|
|
16
|
+
export { uploadUserImageShare };
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,6 @@ interface UploadOptions {
|
|
|
11
11
|
* @param options - Optional settings for retries and backoff
|
|
12
12
|
* @returns URL to the uploaded file
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
declare function uploadUserImageShare(userId: string, version: number, buffer: Buffer, contentType: string, options?: UploadOptions): Promise<string>;
|
|
15
|
+
|
|
16
|
+
export { uploadUserImageShare };
|
package/dist/index.js
CHANGED
|
@@ -1,84 +1,75 @@
|
|
|
1
|
+
// src/index.ts
|
|
1
2
|
import { ShareServiceClient } from "@azure/storage-file-share";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
async function uploadUserImageShare(userId, version, buffer, contentType, options = {}) {
|
|
4
|
+
if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
"AZURE_STORAGE_CONNECTION_STRING is not set in environment variables."
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
if (!userId || typeof userId !== "string") {
|
|
10
|
+
throw new Error("userId is required and must be a string.");
|
|
11
|
+
}
|
|
12
|
+
if (typeof version !== "number" || isNaN(version)) {
|
|
13
|
+
throw new Error("version is required and must be a number.");
|
|
14
|
+
}
|
|
15
|
+
if (!buffer || !(buffer instanceof Buffer)) {
|
|
16
|
+
throw new Error("buffer is required and must be a Buffer.");
|
|
17
|
+
}
|
|
18
|
+
if (!contentType || typeof contentType !== "string") {
|
|
19
|
+
throw new Error("contentType is required and must be a string.");
|
|
20
|
+
}
|
|
21
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
22
|
+
const baseDelayMs = options.baseDelayMs ?? 500;
|
|
23
|
+
const shareName = "avatars";
|
|
24
|
+
const directoryName = userId;
|
|
25
|
+
const fileName = `${version}.jpg`;
|
|
26
|
+
const serviceClient = ShareServiceClient.fromConnectionString(
|
|
27
|
+
process.env.AZURE_STORAGE_CONNECTION_STRING
|
|
28
|
+
);
|
|
29
|
+
const shareClient = serviceClient.getShareClient(shareName);
|
|
30
|
+
const directoryClient = shareClient.getDirectoryClient(directoryName);
|
|
31
|
+
const fileClient = directoryClient.getFileClient(fileName);
|
|
32
|
+
let attempt = 0;
|
|
33
|
+
let lastError = null;
|
|
34
|
+
while (attempt < maxRetries) {
|
|
35
|
+
try {
|
|
36
|
+
await shareClient.createIfNotExists();
|
|
37
|
+
await directoryClient.createIfNotExists();
|
|
38
|
+
await fileClient.create(buffer.length, {
|
|
39
|
+
fileHttpHeaders: { fileContentType: contentType }
|
|
40
|
+
});
|
|
41
|
+
await fileClient.uploadRange(buffer, 0, buffer.length);
|
|
42
|
+
return fileClient.url;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
lastError = err;
|
|
45
|
+
if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
|
|
46
|
+
console.error(
|
|
47
|
+
`Attempt ${attempt + 1} to upload user image failed: ${err.message}`
|
|
48
|
+
);
|
|
49
|
+
} else {
|
|
50
|
+
console.error(
|
|
51
|
+
`Attempt ${attempt + 1} to upload user image failed:`,
|
|
52
|
+
err
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (attempt < maxRetries - 1) {
|
|
56
|
+
const baseDelay = Math.pow(2, attempt) * baseDelayMs;
|
|
57
|
+
const jitter = Math.random() * baseDelay;
|
|
58
|
+
const delay = baseDelay / 2 + jitter;
|
|
59
|
+
console.warn(
|
|
60
|
+
`Backing off for ${Math.round(delay)} ms before retrying...`
|
|
61
|
+
);
|
|
62
|
+
await new Promise((res) => setTimeout(res, delay));
|
|
63
|
+
}
|
|
15
64
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (!buffer || !(buffer instanceof Buffer)) {
|
|
23
|
-
throw new Error("buffer is required and must be a Buffer.");
|
|
24
|
-
}
|
|
25
|
-
if (!contentType || typeof contentType !== "string") {
|
|
26
|
-
throw new Error("contentType is required and must be a string.");
|
|
27
|
-
}
|
|
28
|
-
const maxRetries = options.maxRetries ?? 3;
|
|
29
|
-
const baseDelayMs = options.baseDelayMs ?? 500;
|
|
30
|
-
const shareName = "avatars";
|
|
31
|
-
const directoryName = userId;
|
|
32
|
-
const fileName = `${version}.jpg`;
|
|
33
|
-
const serviceClient = ShareServiceClient.fromConnectionString(process.env.AZURE_STORAGE_CONNECTION_STRING);
|
|
34
|
-
const shareClient = serviceClient.getShareClient(shareName);
|
|
35
|
-
const directoryClient = shareClient.getDirectoryClient(directoryName);
|
|
36
|
-
const fileClient = directoryClient.getFileClient(fileName);
|
|
37
|
-
// Retry logic with exponential backoff
|
|
38
|
-
let attempt = 0;
|
|
39
|
-
let lastError = null;
|
|
40
|
-
while (attempt < maxRetries) {
|
|
41
|
-
try {
|
|
42
|
-
// Ensure share exists
|
|
43
|
-
await shareClient.createIfNotExists();
|
|
44
|
-
// Ensure directory exists
|
|
45
|
-
await directoryClient.createIfNotExists();
|
|
46
|
-
// Create file (set size)
|
|
47
|
-
await fileClient.create(buffer.length, {
|
|
48
|
-
fileHttpHeaders: { fileContentType: contentType },
|
|
49
|
-
});
|
|
50
|
-
// Upload content
|
|
51
|
-
await fileClient.uploadRange(buffer, 0, buffer.length);
|
|
52
|
-
// Return file URL
|
|
53
|
-
return fileClient.url;
|
|
54
|
-
}
|
|
55
|
-
catch (err) {
|
|
56
|
-
lastError = err;
|
|
57
|
-
if (err &&
|
|
58
|
-
typeof err === "object" &&
|
|
59
|
-
"message" in err &&
|
|
60
|
-
typeof err.message === "string") {
|
|
61
|
-
console.error(`Attempt ${attempt + 1} to upload user image failed: ${err.message}`);
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
console.error(`Attempt ${attempt + 1} to upload user image failed:`, err);
|
|
65
|
-
}
|
|
66
|
-
// Exponential backoff with jitter
|
|
67
|
-
if (attempt < maxRetries - 1) {
|
|
68
|
-
const baseDelay = Math.pow(2, attempt) * baseDelayMs;
|
|
69
|
-
const jitter = Math.random() * baseDelay;
|
|
70
|
-
const delay = baseDelay / 2 + jitter; // Between 0.5x and 1.5x base delay
|
|
71
|
-
console.warn(`Backing off for ${Math.round(delay)} ms before retrying...`);
|
|
72
|
-
await new Promise((res) => setTimeout(res, delay));
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
attempt++;
|
|
76
|
-
}
|
|
77
|
-
const lastErrorMsg = lastError &&
|
|
78
|
-
typeof lastError === "object" &&
|
|
79
|
-
"message" in lastError &&
|
|
80
|
-
typeof lastError.message === "string"
|
|
81
|
-
? lastError.message
|
|
82
|
-
: String(lastError ?? "");
|
|
83
|
-
throw new Error(`Failed to upload user image after ${maxRetries} attempts: ${lastErrorMsg}`);
|
|
65
|
+
attempt++;
|
|
66
|
+
}
|
|
67
|
+
const lastErrorMsg = lastError && typeof lastError === "object" && "message" in lastError && typeof lastError.message === "string" ? lastError.message : String(lastError ?? "");
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Failed to upload user image after ${maxRetries} attempts: ${lastErrorMsg}`
|
|
70
|
+
);
|
|
84
71
|
}
|
|
72
|
+
export {
|
|
73
|
+
uploadUserImageShare
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { ShareServiceClient } from \"@azure/storage-file-share\";\n\ninterface UploadOptions {\n maxRetries?: number;\n baseDelayMs?: number;\n}\n\n/**\n * Uploads a user image to Azure File Share storage with retries and error handling.\n * @param userId - The user's ID (used as directory name)\n * @param version - The version number (used as file name)\n * @param buffer - The file data as a Buffer\n * @param contentType - The MIME type of the file\n * @param options - Optional settings for retries and backoff\n * @returns URL to the uploaded file\n */\nexport async function uploadUserImageShare(\n userId: string,\n version: number,\n buffer: Buffer,\n contentType: string,\n options: UploadOptions = {}\n): Promise<string> {\n // Parameter validation\n if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {\n throw new Error(\n \"AZURE_STORAGE_CONNECTION_STRING is not set in environment variables.\"\n );\n }\n if (!userId || typeof userId !== \"string\") {\n throw new Error(\"userId is required and must be a string.\");\n }\n if (typeof version !== \"number\" || isNaN(version)) {\n throw new Error(\"version is required and must be a number.\");\n }\n if (!buffer || !(buffer instanceof Buffer)) {\n throw new Error(\"buffer is required and must be a Buffer.\");\n }\n if (!contentType || typeof contentType !== \"string\") {\n throw new Error(\"contentType is required and must be a string.\");\n }\n\n const maxRetries = options.maxRetries ?? 3;\n const baseDelayMs = options.baseDelayMs ?? 500;\n\n const shareName = \"avatars\";\n const directoryName = userId;\n const fileName = `${version}.jpg`;\n\n const serviceClient = ShareServiceClient.fromConnectionString(\n process.env.AZURE_STORAGE_CONNECTION_STRING\n );\n const shareClient = serviceClient.getShareClient(shareName);\n const directoryClient = shareClient.getDirectoryClient(directoryName);\n const fileClient = directoryClient.getFileClient(fileName);\n\n // Retry logic with exponential backoff\n let attempt = 0;\n let lastError: unknown = null;\n while (attempt < maxRetries) {\n try {\n // Ensure share exists\n await shareClient.createIfNotExists();\n // Ensure directory exists\n await directoryClient.createIfNotExists();\n // Create file (set size)\n await fileClient.create(buffer.length, {\n fileHttpHeaders: { fileContentType: contentType },\n });\n // Upload content\n await fileClient.uploadRange(buffer, 0, buffer.length);\n // Return file URL\n return fileClient.url;\n } catch (err: unknown) {\n lastError = err;\n if (\n err &&\n typeof err === \"object\" &&\n \"message\" in err &&\n typeof (err as { message: unknown }).message === \"string\"\n ) {\n console.error(\n `Attempt ${attempt + 1} to upload user image failed: ${\n (err as { message: string }).message\n }`\n );\n } else {\n console.error(\n `Attempt ${attempt + 1} to upload user image failed:`,\n err\n );\n }\n // Exponential backoff with jitter\n if (attempt < maxRetries - 1) {\n const baseDelay = Math.pow(2, attempt) * baseDelayMs;\n const jitter = Math.random() * baseDelay;\n const delay = baseDelay / 2 + jitter; // Between 0.5x and 1.5x base delay\n console.warn(\n `Backing off for ${Math.round(delay)} ms before retrying...`\n );\n await new Promise((res) => setTimeout(res, delay));\n }\n }\n attempt++;\n }\n const lastErrorMsg =\n lastError &&\n typeof lastError === \"object\" &&\n \"message\" in lastError &&\n typeof (lastError as { message: unknown }).message === \"string\"\n ? (lastError as { message: string }).message\n : String((lastError as Error) ?? \"\");\n throw new Error(\n `Failed to upload user image after ${maxRetries} attempts: ${lastErrorMsg}`\n );\n}\n"],"mappings":";AAAA,SAAS,0BAA0B;AAgBnC,eAAsB,qBACpB,QACA,SACA,QACA,aACA,UAAyB,CAAC,GACT;AAEjB,MAAI,CAAC,QAAQ,IAAI,iCAAiC;AAChD,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,MAAI,OAAO,YAAY,YAAY,MAAM,OAAO,GAAG;AACjD,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,MAAI,CAAC,UAAU,EAAE,kBAAkB,SAAS;AAC1C,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,MAAI,CAAC,eAAe,OAAO,gBAAgB,UAAU;AACnD,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AAEA,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,cAAc,QAAQ,eAAe;AAE3C,QAAM,YAAY;AAClB,QAAM,gBAAgB;AACtB,QAAM,WAAW,GAAG,OAAO;AAE3B,QAAM,gBAAgB,mBAAmB;AAAA,IACvC,QAAQ,IAAI;AAAA,EACd;AACA,QAAM,cAAc,cAAc,eAAe,SAAS;AAC1D,QAAM,kBAAkB,YAAY,mBAAmB,aAAa;AACpE,QAAM,aAAa,gBAAgB,cAAc,QAAQ;AAGzD,MAAI,UAAU;AACd,MAAI,YAAqB;AACzB,SAAO,UAAU,YAAY;AAC3B,QAAI;AAEF,YAAM,YAAY,kBAAkB;AAEpC,YAAM,gBAAgB,kBAAkB;AAExC,YAAM,WAAW,OAAO,OAAO,QAAQ;AAAA,QACrC,iBAAiB,EAAE,iBAAiB,YAAY;AAAA,MAClD,CAAC;AAED,YAAM,WAAW,YAAY,QAAQ,GAAG,OAAO,MAAM;AAErD,aAAO,WAAW;AAAA,IACpB,SAAS,KAAc;AACrB,kBAAY;AACZ,UACE,OACA,OAAO,QAAQ,YACf,aAAa,OACb,OAAQ,IAA6B,YAAY,UACjD;AACA,gBAAQ;AAAA,UACN,WAAW,UAAU,CAAC,iCACnB,IAA4B,OAC/B;AAAA,QACF;AAAA,MACF,OAAO;AACL,gBAAQ;AAAA,UACN,WAAW,UAAU,CAAC;AAAA,UACtB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,UAAU,aAAa,GAAG;AAC5B,cAAM,YAAY,KAAK,IAAI,GAAG,OAAO,IAAI;AACzC,cAAM,SAAS,KAAK,OAAO,IAAI;AAC/B,cAAM,QAAQ,YAAY,IAAI;AAC9B,gBAAQ;AAAA,UACN,mBAAmB,KAAK,MAAM,KAAK,CAAC;AAAA,QACtC;AACA,cAAM,IAAI,QAAQ,CAAC,QAAQ,WAAW,KAAK,KAAK,CAAC;AAAA,MACnD;AAAA,IACF;AACA;AAAA,EACF;AACA,QAAM,eACJ,aACA,OAAO,cAAc,YACrB,aAAa,aACb,OAAQ,UAAmC,YAAY,WAClD,UAAkC,UACnC,OAAQ,aAAuB,EAAE;AACvC,QAAM,IAAI;AAAA,IACR,qCAAqC,UAAU,cAAc,YAAY;AAAA,EAC3E;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plasius/storage",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"main": "./dist
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"main": "./dist/index.cjs",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"private": false,
|
|
7
7
|
"type": "module",
|
|
8
8
|
"description": "Azure storage for Plasius projects",
|
|
9
9
|
"scripts": {
|
|
10
|
-
"build": "
|
|
10
|
+
"build": "tsup",
|
|
11
11
|
"test": "vitest run",
|
|
12
12
|
"test:watch": "vitest",
|
|
13
13
|
"test:coverage": "vitest run --coverage",
|
|
14
14
|
"test:coverage:watch": "vitest --coverage",
|
|
15
|
-
"clean": "rimraf dist-cjs
|
|
15
|
+
"clean": "rimraf dist dist-cjs tsconfig.tsbuildinfo",
|
|
16
16
|
"reset:clean": "rm -rf node_modules package-lock.json && npm run clean",
|
|
17
17
|
"audit:ts": "tsc --noEmit --pretty",
|
|
18
18
|
"audit:eslint": "eslint \"{src,apps,packages}/**/*.{ts,tsx}\" --max-warnings=0 --ext .ts,.tsx",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"audit:npm": "npm audit --audit-level=moderate || true",
|
|
21
21
|
"audit:test": "vitest run --coverage",
|
|
22
22
|
"audit:all": "npm-run-all -l audit:ts audit:eslint audit:deps audit:npm audit:test",
|
|
23
|
-
"build:cjs": "tsc -p tsconfig.json --module commonjs --moduleResolution node --outDir dist-cjs --tsBuildInfoFile dist-cjs/tsconfig.tsbuildinfo",
|
|
24
23
|
"lint": "eslint .",
|
|
25
24
|
"prepare": "npm run build"
|
|
26
25
|
},
|
|
@@ -50,6 +49,7 @@
|
|
|
50
49
|
"npm-run-all": "^4.1.5",
|
|
51
50
|
"react": "^19.1.0",
|
|
52
51
|
"tsx": "^4.20.3",
|
|
52
|
+
"tsup": "^8.5.0",
|
|
53
53
|
"typescript": "^5.8.3",
|
|
54
54
|
"vitest": "^3.2.4",
|
|
55
55
|
"zod": "^4.0.17"
|
|
@@ -58,14 +58,13 @@
|
|
|
58
58
|
".": {
|
|
59
59
|
"types": "./dist/index.d.ts",
|
|
60
60
|
"import": "./dist/index.js",
|
|
61
|
-
"require": "./dist
|
|
61
|
+
"require": "./dist/index.cjs"
|
|
62
62
|
},
|
|
63
63
|
"./package.json": "./package.json"
|
|
64
64
|
},
|
|
65
65
|
"module": "./dist/index.js",
|
|
66
66
|
"files": [
|
|
67
67
|
"dist",
|
|
68
|
-
"dist-cjs",
|
|
69
68
|
"src",
|
|
70
69
|
"README.md",
|
|
71
70
|
"CHANGELOG.md",
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,UAAU,aAAa;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA6FjB"}
|
package/dist-cjs/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,UAAU,aAAa;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA6FjB"}
|
package/dist-cjs/index.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.uploadUserImageShare = uploadUserImageShare;
|
|
4
|
-
const storage_file_share_1 = require("@azure/storage-file-share");
|
|
5
|
-
/**
|
|
6
|
-
* Uploads a user image to Azure File Share storage with retries and error handling.
|
|
7
|
-
* @param userId - The user's ID (used as directory name)
|
|
8
|
-
* @param version - The version number (used as file name)
|
|
9
|
-
* @param buffer - The file data as a Buffer
|
|
10
|
-
* @param contentType - The MIME type of the file
|
|
11
|
-
* @param options - Optional settings for retries and backoff
|
|
12
|
-
* @returns URL to the uploaded file
|
|
13
|
-
*/
|
|
14
|
-
async function uploadUserImageShare(userId, version, buffer, contentType, options = {}) {
|
|
15
|
-
// Parameter validation
|
|
16
|
-
if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
|
|
17
|
-
throw new Error("AZURE_STORAGE_CONNECTION_STRING is not set in environment variables.");
|
|
18
|
-
}
|
|
19
|
-
if (!userId || typeof userId !== "string") {
|
|
20
|
-
throw new Error("userId is required and must be a string.");
|
|
21
|
-
}
|
|
22
|
-
if (typeof version !== "number" || isNaN(version)) {
|
|
23
|
-
throw new Error("version is required and must be a number.");
|
|
24
|
-
}
|
|
25
|
-
if (!buffer || !(buffer instanceof Buffer)) {
|
|
26
|
-
throw new Error("buffer is required and must be a Buffer.");
|
|
27
|
-
}
|
|
28
|
-
if (!contentType || typeof contentType !== "string") {
|
|
29
|
-
throw new Error("contentType is required and must be a string.");
|
|
30
|
-
}
|
|
31
|
-
const maxRetries = options.maxRetries ?? 3;
|
|
32
|
-
const baseDelayMs = options.baseDelayMs ?? 500;
|
|
33
|
-
const shareName = "avatars";
|
|
34
|
-
const directoryName = userId;
|
|
35
|
-
const fileName = `${version}.jpg`;
|
|
36
|
-
const serviceClient = storage_file_share_1.ShareServiceClient.fromConnectionString(process.env.AZURE_STORAGE_CONNECTION_STRING);
|
|
37
|
-
const shareClient = serviceClient.getShareClient(shareName);
|
|
38
|
-
const directoryClient = shareClient.getDirectoryClient(directoryName);
|
|
39
|
-
const fileClient = directoryClient.getFileClient(fileName);
|
|
40
|
-
// Retry logic with exponential backoff
|
|
41
|
-
let attempt = 0;
|
|
42
|
-
let lastError = null;
|
|
43
|
-
while (attempt < maxRetries) {
|
|
44
|
-
try {
|
|
45
|
-
// Ensure share exists
|
|
46
|
-
await shareClient.createIfNotExists();
|
|
47
|
-
// Ensure directory exists
|
|
48
|
-
await directoryClient.createIfNotExists();
|
|
49
|
-
// Create file (set size)
|
|
50
|
-
await fileClient.create(buffer.length, {
|
|
51
|
-
fileHttpHeaders: { fileContentType: contentType },
|
|
52
|
-
});
|
|
53
|
-
// Upload content
|
|
54
|
-
await fileClient.uploadRange(buffer, 0, buffer.length);
|
|
55
|
-
// Return file URL
|
|
56
|
-
return fileClient.url;
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
lastError = err;
|
|
60
|
-
if (err &&
|
|
61
|
-
typeof err === "object" &&
|
|
62
|
-
"message" in err &&
|
|
63
|
-
typeof err.message === "string") {
|
|
64
|
-
console.error(`Attempt ${attempt + 1} to upload user image failed: ${err.message}`);
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
console.error(`Attempt ${attempt + 1} to upload user image failed:`, err);
|
|
68
|
-
}
|
|
69
|
-
// Exponential backoff with jitter
|
|
70
|
-
if (attempt < maxRetries - 1) {
|
|
71
|
-
const baseDelay = Math.pow(2, attempt) * baseDelayMs;
|
|
72
|
-
const jitter = Math.random() * baseDelay;
|
|
73
|
-
const delay = baseDelay / 2 + jitter; // Between 0.5x and 1.5x base delay
|
|
74
|
-
console.warn(`Backing off for ${Math.round(delay)} ms before retrying...`);
|
|
75
|
-
await new Promise((res) => setTimeout(res, delay));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
attempt++;
|
|
79
|
-
}
|
|
80
|
-
const lastErrorMsg = lastError &&
|
|
81
|
-
typeof lastError === "object" &&
|
|
82
|
-
"message" in lastError &&
|
|
83
|
-
typeof lastError.message === "string"
|
|
84
|
-
? lastError.message
|
|
85
|
-
: String(lastError ?? "");
|
|
86
|
-
throw new Error(`Failed to upload user image after ${maxRetries} attempts: ${lastErrorMsg}`);
|
|
87
|
-
}
|