diff --git a/package-lock.json b/package-lock.json index 64802ff..02bdc9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "@aws-sdk/client-s3": "^3.620.0", "@azure/storage-blob": "^12.24.0", - "@google-cloud/storage": "^6.11.0" + "@google-cloud/storage": "^6.11.0", + "minio": "^8.0.1" }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.6", @@ -3177,6 +3178,12 @@ "@types/estree": "^1.0.0" } }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://npmjs.teknologiumum.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -3292,6 +3299,11 @@ "node": ">=12" } }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://npmjs.teknologiumum.com/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, "node_modules/async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -3300,6 +3312,20 @@ "retry": "0.13.1" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://npmjs.teknologiumum.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3333,6 +3359,14 @@ "node": "*" } }, + "node_modules/block-stream2": { + "version": "2.1.0", + "resolved": "https://npmjs.teknologiumum.com/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, "node_modules/bowser": { "version": "2.11.0", "resolved": "https://npmjs.teknologiumum.com/bowser/-/bowser-2.11.0.tgz", @@ -3360,6 +3394,19 @@ "node": ">=8" } }, + "node_modules/browser-or-node": { + "version": "2.1.1", + "resolved": "https://npmjs.teknologiumum.com/browser-or-node/-/browser-or-node-2.1.1.tgz", + "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==" + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://npmjs.teknologiumum.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -3374,6 +3421,24 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://npmjs.teknologiumum.com/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://npmjs.teknologiumum.com/callsites/-/callsites-3.1.0.tgz", @@ -3498,6 +3563,14 @@ } } }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://npmjs.teknologiumum.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://npmjs.teknologiumum.com/deep-eql/-/deep-eql-5.0.2.tgz", @@ -3513,6 +3586,22 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://npmjs.teknologiumum.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://npmjs.teknologiumum.com/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3623,6 +3712,25 @@ "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==" }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://npmjs.teknologiumum.com/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://npmjs.teknologiumum.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://npmjs.teknologiumum.com/esbuild/-/esbuild-0.21.5.tgz", @@ -3938,6 +4046,11 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://npmjs.teknologiumum.com/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -4091,6 +4204,14 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://npmjs.teknologiumum.com/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -4126,6 +4247,14 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://npmjs.teknologiumum.com/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/foreground-child": { "version": "3.2.1", "resolved": "https://npmjs.teknologiumum.com/foreground-child/-/foreground-child-3.2.1.tgz", @@ -4166,7 +4295,6 @@ "version": "1.1.2", "resolved": "https://npmjs.teknologiumum.com/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4206,6 +4334,24 @@ "node": "*" } }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://npmjs.teknologiumum.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-stream": { "version": "8.0.1", "resolved": "https://npmjs.teknologiumum.com/get-stream/-/get-stream-8.0.1.tgz", @@ -4334,6 +4480,17 @@ "node": ">=12.0.0" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://npmjs.teknologiumum.com/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/grapheme-splitter": { "version": "1.0.4", "resolved": "https://npmjs.teknologiumum.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", @@ -4380,6 +4537,64 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://npmjs.teknologiumum.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://npmjs.teknologiumum.com/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://npmjs.teknologiumum.com/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://npmjs.teknologiumum.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://npmjs.teknologiumum.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://npmjs.teknologiumum.com/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4469,6 +4684,40 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://npmjs.teknologiumum.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://npmjs.teknologiumum.com/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://npmjs.teknologiumum.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", @@ -4499,6 +4748,20 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://npmjs.teknologiumum.com/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -4540,6 +4803,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://npmjs.teknologiumum.com/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4708,8 +4985,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -4874,6 +5150,30 @@ "node": "*" } }, + "node_modules/minio": { + "version": "8.0.1", + "resolved": "https://npmjs.teknologiumum.com/minio/-/minio-8.0.1.tgz", + "integrity": "sha512-FzDO6yGnqLtm8sp3mXafWtiRUOslJSSg/aI0v9YbN5vjw5KLoODKAROCyi766NIvTSxcfHBrbhCSGk1A+MOzDg==", + "dependencies": { + "async": "^3.2.4", + "block-stream2": "^2.1.0", + "browser-or-node": "^2.1.1", + "buffer-crc32": "^1.0.0", + "eventemitter3": "^5.0.1", + "fast-xml-parser": "^4.2.2", + "ipaddr.js": "^2.0.1", + "lodash": "^4.17.21", + "mime-types": "^2.1.35", + "query-string": "^7.1.3", + "stream-json": "^1.8.0", + "through2": "^4.0.2", + "web-encoding": "^1.1.5", + "xml2js": "^0.5.0" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://npmjs.teknologiumum.com/minipass/-/minipass-7.1.2.tgz", @@ -5150,6 +5450,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://npmjs.teknologiumum.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.4.40", "resolved": "https://npmjs.teknologiumum.com/postcss/-/postcss-8.4.40.tgz", @@ -5196,6 +5504,23 @@ "node": ">=6" } }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://npmjs.teknologiumum.com/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5377,6 +5702,11 @@ } ] }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://npmjs.teknologiumum.com/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + }, "node_modules/semver": { "version": "7.5.3", "resolved": "https://npmjs.teknologiumum.com/semver/-/semver-7.5.3.tgz", @@ -5410,6 +5740,22 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://npmjs.teknologiumum.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5467,6 +5813,14 @@ "node": ">=0.10.0" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://npmjs.teknologiumum.com/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "engines": { + "node": ">=6" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://npmjs.teknologiumum.com/stackback/-/stackback-0.0.2.tgz", @@ -5479,6 +5833,11 @@ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "dev": true }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://npmjs.teknologiumum.com/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==" + }, "node_modules/stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -5487,11 +5846,27 @@ "stubs": "^3.0.0" } }, + "node_modules/stream-json": { + "version": "1.8.0", + "resolved": "https://npmjs.teknologiumum.com/stream-json/-/stream-json-1.8.0.tgz", + "integrity": "sha512-HZfXngYHUAr1exT4fxlbc1IOce1RYxp2ldeaf97LYCOPSoOqY/1Psp7iGvpb+6JIOgkra9zDYnPX01hGAHzEPw==", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, "node_modules/stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://npmjs.teknologiumum.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5735,6 +6110,14 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://npmjs.teknologiumum.com/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dependencies": { + "readable-stream": "3" + } + }, "node_modules/tinybench": { "version": "2.8.0", "resolved": "https://npmjs.teknologiumum.com/tinybench/-/tinybench-2.8.0.tgz", @@ -5887,6 +6270,18 @@ "punycode": "^2.1.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://npmjs.teknologiumum.com/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6078,6 +6473,17 @@ "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==", "dev": true }, + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://npmjs.teknologiumum.com/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -6107,6 +6513,24 @@ "node": ">= 8" } }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://npmjs.teknologiumum.com/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://npmjs.teknologiumum.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -6231,6 +6655,26 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://npmjs.teknologiumum.com/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://npmjs.teknologiumum.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/yaml": { "version": "2.3.1", "resolved": "https://npmjs.teknologiumum.com/yaml/-/yaml-2.3.1.tgz", diff --git a/package.json b/package.json index cb00996..8273d70 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "dependencies": { "@aws-sdk/client-s3": "^3.620.0", "@azure/storage-blob": "^12.24.0", - "@google-cloud/storage": "^6.11.0" + "@google-cloud/storage": "^6.11.0", + "minio": "^8.0.1" }, "directories": { "lib": "./src", diff --git a/src/s3/minio.ts b/src/s3/minio.ts new file mode 100644 index 0000000..0188c23 --- /dev/null +++ b/src/s3/minio.ts @@ -0,0 +1,135 @@ +import { join } from "node:path"; +import { buffer } from "node:stream/consumers"; + +import * as Minio from "minio"; +import { Readable, Writable } from "stream"; + +import { ConnectionString } from "../connectionString"; +import type { IObjectStorage, PutOptions, StatResponse } from "../interface"; +import { UnimplementedError } from "../errors"; + +export class MinioStorage implements IObjectStorage { + private readonly client: Minio.Client; + private readonly bucketName: string; + + constructor(config: ConnectionString) { + const clientOptions: Minio.ClientOptions = { + endPoint: "", + accessKey: "", + secretKey: "" + }; + if (config.username !== undefined && config.username !== "" && config.password !== undefined && config.password !== "") { + clientOptions.accessKey = config.username; + clientOptions.secretKey = config.password; + } + + if (config.parameters !== undefined) { + if ("region" in config.parameters && typeof config.parameters.region === "string") { + clientOptions.region = config.parameters.region; + } else { + // See https://github.com/aws/aws-sdk-js-v3/issues/1845#issuecomment-754832210 + clientOptions.region = "us-east-1"; + clientOptions.pathStyle = true; + } + + if ("forcePathStyle" in config.parameters) { + clientOptions.pathStyle = Boolean(config.parameters.forcePathStyle); + } + + if ("useAccelerateEndpoint" in config.parameters) { + clientOptions.s3AccelerateEndpoint = config.parameters.useAccelerateEndpoint; + } + + if ("endpoint" in config.parameters && typeof config.parameters.endpoint === "string") { + clientOptions.endPoint = config.parameters.endpoint; + } + + if ("useSSL" in config.parameters) { + clientOptions.useSSL = Boolean(config.parameters.useSSL); + } + + if ("port" in config.parameters) { + clientOptions.port = Number.parseInt(config.parameters.port); + } + } + + this.client = new Minio.Client(clientOptions); + this.bucketName = config.bucketName; + } + + async put(path: string, content: string | Buffer, options?: PutOptions | undefined): Promise { + await this.client.putObject(this.bucketName, path, content, undefined, options?.metadata); + } + + putStream(path: string, options?: PutOptions | undefined): Promise { + throw new UnimplementedError(); + } + + async get(path: string, encoding?: string | undefined): Promise { + const response = await this.client.getObject(this.bucketName, path); + return buffer(response); + } + + getStream(path: string): Promise { + return this.client.getObject(this.bucketName, path); + } + + async stat(path: string): Promise { + const response = await this.client.statObject(this.bucketName, path); + return { + size: response.size, + lastModified: response.lastModified, + createdTime: new Date(0), + etag: response.etag, + metadata: response.metaData + }; + } + + list(path?: string | undefined): Promise> { + return new Promise((resolve, reject) => { + const listStream = this.client.listObjectsV2(this.bucketName, path, false); + const objects: string[] = []; + listStream.on("end", () => { + resolve(objects); + }); + + listStream.on("data", (item) => { + if (item.name !== undefined) { + objects.push(item.name); + } + }); + + listStream.on("error", (error) => { + reject(error); + }); + }); + } + + async exists(path: string): Promise { + try { + await this.client.statObject(this.bucketName, path); + return true; + } catch (error: unknown) { + if (error instanceof Minio.S3Error) { + if (error.code === "NoSuchKey") { + return false; + } + } + + throw error; + } + } + + async delete(path: string): Promise { + await this.client.removeObject(this.bucketName, path); + } + + async copy(sourcePath: string, destinationPath: string): Promise { + await this.client.copyObject(this.bucketName, destinationPath, join(this.bucketName, sourcePath)); + } + + async move(sourcePath: string, destinationPath: string): Promise { + await this.copy(sourcePath, destinationPath); + await this.delete(sourcePath); + } +} \ No newline at end of file diff --git a/src/storage.ts b/src/storage.ts index 337eb20..34373a5 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -3,6 +3,7 @@ import { type IObjectStorage, PutOptions, StatResponse } from "./interface"; import { parseConnectionString } from "./connectionString"; import { FileStorage } from "./file/file"; import { S3Storage } from "./s3/s3"; +import { MinioStorage } from "./s3/minio"; /** * The Storage class implements the `IObjectStorage` interface and provides a way to interact @@ -40,7 +41,11 @@ export class Storage implements IObjectStorage { this.#implementation = new FileStorage(parsedConnectionString.bucketName); break; case "s3": - this.#implementation = new S3Storage(parsedConnectionString); + if (parsedConnectionString.parameters?.useMinioSdk === "true") { + this.#implementation = new MinioStorage(parsedConnectionString); + } else { + this.#implementation = new S3Storage(parsedConnectionString); + } break; // case "azblob": // this.#implementation = new AzureBlobStorage(parsedConnectionString); diff --git a/tests/s3/integration.test.ts b/tests/s3/aws.integration.test.ts similarity index 97% rename from tests/s3/integration.test.ts rename to tests/s3/aws.integration.test.ts index cb6dfd3..eaac740 100644 --- a/tests/s3/integration.test.ts +++ b/tests/s3/aws.integration.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import { destroyBucket, removeAllObject, setupBucket } from "./util"; +import { destroyBucket, removeAllObject, setupBucket } from "./aws.util"; import { S3Client } from "@aws-sdk/client-s3"; import { loremIpsum } from "lorem-ipsum"; import { createHash } from "node:crypto"; diff --git a/tests/s3/util.ts b/tests/s3/aws.util.ts similarity index 100% rename from tests/s3/util.ts rename to tests/s3/aws.util.ts diff --git a/tests/s3/minio.integration.test.ts b/tests/s3/minio.integration.test.ts new file mode 100644 index 0000000..a89dae5 --- /dev/null +++ b/tests/s3/minio.integration.test.ts @@ -0,0 +1,71 @@ +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { destroyBucket, removeAllObject, setupBucket } from "./minio.util"; +import { loremIpsum } from "lorem-ipsum"; +import { createHash } from "node:crypto"; +import { ConnectionString } from "../../src/connectionString"; +import { MinioStorage } from "../../src/s3/minio"; +import { Client } from "minio"; + +describe("S3 Provider - Integration", () => { + const s3Host = process.env.S3_HOST ?? "http://localhost:9000"; + const s3Access = process.env.S3_ACCESS ?? "teknologi-umum"; + const s3Secret = process.env.S3_SECRET ?? "very-strong-password"; + const bucketName = "blob-js"; + const s3Client = new Client({ + endPoint: s3Host, + accessKey: s3Access, + secretKey: s3Secret, + useSSL: false, + pathStyle: true, + region: "us-east-1" + }); + + const connectionStringConfig: ConnectionString = { + provider: "s3", + username: s3Access, + password: s3Secret, + bucketName: bucketName, + parameters: { + useMinioSdk: "true", + endpoint: s3Host, + disableHostPrefix: "true", + forcePathStyle: "true" + } + }; + + beforeAll(async () => { + // Create S3 bucket + await setupBucket(s3Client, bucketName); + }); + + afterAll(async () => { + await removeAllObject(s3Client, bucketName); + await destroyBucket(s3Client, bucketName); + }); + + it("should be able to create, read and delete file", async () => { + const content = loremIpsum({count: 1024, units: "sentences"}); + const hashFunc = createHash("md5"); + hashFunc.update(content); + const checksum = hashFunc.digest("base64"); + + const s3Client = new MinioStorage(connectionStringConfig); + + await s3Client.put("lorem-ipsum.txt", content, {contentMD5: checksum}); + + expect(s3Client.exists("lorem-ipsum.txt")) + .resolves + .toStrictEqual(true); + + // GetObjectAttributes is not available on MinIO + // const fileStat = await s3Client.stat("lorem-ipsum.txt"); + // expect(fileStat.size).toStrictEqual(content.length); + + const fileContent = await s3Client.get("lorem-ipsum.txt"); + expect(fileContent.toString()).toStrictEqual(content); + + expect(s3Client.delete("lorem-ipsum.txt")) + .resolves + .ok; + }); +}); \ No newline at end of file diff --git a/tests/s3/minio.util.ts b/tests/s3/minio.util.ts new file mode 100644 index 0000000..18e3180 --- /dev/null +++ b/tests/s3/minio.util.ts @@ -0,0 +1,38 @@ +import { + BucketAlreadyExists, + BucketAlreadyOwnedByYou, + CreateBucketCommand, + DeleteBucketCommand, + DeleteObjectsCommand, + ListObjectsV2Command, + S3Client +} from "@aws-sdk/client-s3"; +import { Client } from "minio"; + +export async function setupBucket(client: Client, bucketName: string): Promise { + await client.makeBucket(bucketName); +} + +export async function removeAllObject(client: Client, bucketName: string): Promise { + const listObj = client.listObjectsV2(bucketName); + + return new Promise((resolve, reject) => { + listObj.on("data", async (obj) => { + if (obj.name !== undefined) { + await client.removeObject(bucketName, obj.name); + } + }); + + listObj.on("error", (error) => { + reject(error); + }); + + listObj.on("end", () => { + resolve(); + }); + }); +} + +export async function destroyBucket(client: Client, bucketName: string): Promise { + await client.removeBucket(bucketName); +} \ No newline at end of file diff --git a/tests/s3/unit.test.ts b/tests/s3/unit.test.ts index a58c362..0f91532 100644 --- a/tests/s3/unit.test.ts +++ b/tests/s3/unit.test.ts @@ -4,7 +4,7 @@ import { createHash } from "node:crypto"; import { BlobFileNotExistError, BlobMismatchedMD5IntegrityError } from "../../src/errors"; import { S3Client } from "@aws-sdk/client-s3"; import { ConnectionString } from "../../src/connectionString"; -import { destroyBucket, removeAllObject, setupBucket } from "./util"; +import { destroyBucket, removeAllObject, setupBucket } from "./aws.util"; import { S3Storage } from "../../src/s3/s3"; describe("S3 Provider - Unit", () => {