@plasius/storage 1.0.0 → 1.0.2

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 CHANGED
@@ -20,6 +20,34 @@ 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.2] - 2026-02-22
24
+
25
+ - **Added**
26
+ - (placeholder)
27
+
28
+ - **Changed**
29
+ - (placeholder)
30
+
31
+ - **Fixed**
32
+ - (placeholder)
33
+
34
+ - **Security**
35
+ - (placeholder)
36
+
37
+ ## [1.0.1] - 2026-02-13
38
+
39
+ - **Added**
40
+ - (placeholder)
41
+
42
+ - **Changed**
43
+ - Replace dual-`tsc` build steps with `tsup` to emit ESM + CJS + types side-by-side in `dist/` (`index.js`, `index.cjs`, `index.d.ts`).
44
+
45
+ - **Fixed**
46
+ - (placeholder)
47
+
48
+ - **Security**
49
+ - (placeholder)
50
+
23
51
  ## [1.0.0] - 2026-02-12
24
52
 
25
53
  - **Added**
@@ -48,7 +76,7 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
48
76
 
49
77
  ---
50
78
 
51
- [Unreleased]: https://github.com/Plasius-LTD/storage/compare/v1.0.0...HEAD
79
+ [Unreleased]: https://github.com/Plasius-LTD/storage/compare/v1.0.2...HEAD
52
80
 
53
81
  ## [1.0.0] - 2026-02-11
54
82
 
@@ -64,3 +92,5 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
64
92
  - **Security**
65
93
  - (placeholder)
66
94
  [1.0.0]: https://github.com/Plasius-LTD/storage/releases/tag/v1.0.0
95
+ [1.0.1]: https://github.com/Plasius-LTD/storage/releases/tag/v1.0.1
96
+ [1.0.2]: https://github.com/Plasius-LTD/storage/releases/tag/v1.0.2
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
- export declare function uploadUserImageShare(userId: string, version: number, buffer: Buffer, contentType: string, options?: UploadOptions): Promise<string>;
15
- export {};
16
- //# sourceMappingURL=index.d.ts.map
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
- export declare function uploadUserImageShare(userId: string, version: number, buffer: Buffer, contentType: string, options?: UploadOptions): Promise<string>;
15
- export {};
16
- //# sourceMappingURL=index.d.ts.map
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
- * Uploads a user image to Azure File Share storage with retries and error handling.
4
- * @param userId - The user's ID (used as directory name)
5
- * @param version - The version number (used as file name)
6
- * @param buffer - The file data as a Buffer
7
- * @param contentType - The MIME type of the file
8
- * @param options - Optional settings for retries and backoff
9
- * @returns URL to the uploaded file
10
- */
11
- export async function uploadUserImageShare(userId, version, buffer, contentType, options = {}) {
12
- // Parameter validation
13
- if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
14
- throw new Error("AZURE_STORAGE_CONNECTION_STRING is not set in environment variables.");
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
- if (!userId || typeof userId !== "string") {
17
- throw new Error("userId is required and must be a string.");
18
- }
19
- if (typeof version !== "number" || isNaN(version)) {
20
- throw new Error("version is required and must be a number.");
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":[]}
@@ -0,0 +1,4 @@
1
+ # ADR Index
2
+
3
+ - [ADR-0001: Standalone @plasius/storage Package Scope](./adr-0001-storage-package-scope.md)
4
+ - [ADR-0002: Public Repository Governance Baseline](./adr-0002-public-repo-governance.md)
package/package.json CHANGED
@@ -1,26 +1,25 @@
1
1
  {
2
2
  "name": "@plasius/storage",
3
- "version": "1.0.0",
4
- "main": "./dist-cjs/index.js",
3
+ "version": "1.0.2",
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": "tsc --build && npm run build:cjs",
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 dist tsconfig.tsbuildinfo",
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",
19
- "audit:deps": "depcheck --skip-missing=true",
19
+ "audit:deps": "npm ls --all --omit=optional --omit=peer > /dev/null 2>&1 || true",
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
  },
@@ -37,35 +36,34 @@
37
36
  },
38
37
  "devDependencies": {
39
38
  "@azure/cosmos": "^4.4.1",
40
- "@types/node": "^24.3.1",
41
39
  "@testing-library/react": "^16.3.0",
40
+ "@types/node": "^24.3.1",
42
41
  "@types/react": "^19.1.8",
43
42
  "@types/uuid": "^10.0.0",
44
- "@typescript-eslint/eslint-plugin": "^8.38.0",
45
- "@typescript-eslint/parser": "^8.38.0",
46
- "@vitest/coverage-v8": "^3.2.4",
43
+ "@typescript-eslint/eslint-plugin": "^8.56.0",
44
+ "@typescript-eslint/parser": "^8.56.0",
45
+ "@vitest/coverage-v8": "^4.0.18",
47
46
  "ajv": "^6.12.6",
48
- "depcheck": "^1.4.7",
49
- "eslint": "^9.33.0",
50
- "npm-run-all": "^4.1.5",
47
+ "eslint": "^10.0.1",
48
+ "npm-run-all": "^1.1.3",
51
49
  "react": "^19.1.0",
50
+ "tsup": "^8.5.0",
52
51
  "tsx": "^4.20.3",
53
52
  "typescript": "^5.8.3",
54
- "vitest": "^3.2.4",
53
+ "vitest": "^4.0.18",
55
54
  "zod": "^4.0.17"
56
55
  },
57
56
  "exports": {
58
57
  ".": {
59
58
  "types": "./dist/index.d.ts",
60
59
  "import": "./dist/index.js",
61
- "require": "./dist-cjs/index.js"
60
+ "require": "./dist/index.cjs"
62
61
  },
63
62
  "./package.json": "./package.json"
64
63
  },
65
64
  "module": "./dist/index.js",
66
65
  "files": [
67
66
  "dist",
68
- "dist-cjs",
69
67
  "src",
70
68
  "README.md",
71
69
  "CHANGELOG.md",
@@ -99,5 +97,8 @@
99
97
  ],
100
98
  "engines": {
101
99
  "node": ">=22.12"
100
+ },
101
+ "overrides": {
102
+ "minimatch": "^10.2.1"
102
103
  }
103
104
  }
@@ -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"}
@@ -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
- }