@payloadcms/storage-azure 3.0.0-alpha.62
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/LICENSE.md +22 -0
- package/README.md +1 -0
- package/dist/generateURL.d.ts +8 -0
- package/dist/generateURL.d.ts.map +1 -0
- package/dist/generateURL.js +6 -0
- package/dist/generateURL.js.map +1 -0
- package/dist/handleDelete.d.ts +10 -0
- package/dist/handleDelete.d.ts.map +1 -0
- package/dist/handleDelete.js +9 -0
- package/dist/handleDelete.js.map +1 -0
- package/dist/handleUpload.d.ts +11 -0
- package/dist/handleUpload.d.ts.map +1 -0
- package/dist/handleUpload.js +28 -0
- package/dist/handleUpload.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/staticHandler.d.ts +10 -0
- package/dist/staticHandler.d.ts.map +1 -0
- package/dist/staticHandler.js +48 -0
- package/dist/staticHandler.js.map +1 -0
- package/dist/utils/getRangeFromHeader.d.ts +6 -0
- package/dist/utils/getRangeFromHeader.d.ts.map +1 -0
- package/dist/utils/getRangeFromHeader.js +21 -0
- package/dist/utils/getRangeFromHeader.js.map +1 -0
- package/package.json +49 -0
- package/src/index.ts +106 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018-2022 Payload CMS, LLC <info@payloadcms.com>
|
|
4
|
+
Portions Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Azure Storage
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { GenerateURL } from '@payloadcms/plugin-cloud-storage/types';
|
|
2
|
+
interface Args {
|
|
3
|
+
baseURL: string;
|
|
4
|
+
containerName: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const getGenerateURL: ({ baseURL, containerName }: Args) => GenerateURL;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=generateURL.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateURL.d.ts","sourceRoot":"","sources":["../src/generateURL.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAIzE,UAAU,IAAI;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,cAAc,+BACI,IAAI,KAAG,WAGnC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generateURL.ts"],"sourcesContent":["import type { GenerateURL } from '@payloadcms/plugin-cloud-storage/types'\n\nimport path from 'path'\n\ninterface Args {\n baseURL: string\n containerName: string\n}\n\nexport const getGenerateURL =\n ({ baseURL, containerName }: Args): GenerateURL =>\n ({ filename, prefix = '' }) => {\n return `${baseURL}/${containerName}/${path.posix.join(prefix, filename)}`\n }\n"],"names":["path","getGenerateURL","baseURL","containerName","filename","prefix","posix","join"],"rangeMappings":";;;","mappings":"AAEA,OAAOA,UAAU,OAAM;AAOvB,OAAO,MAAMC,iBACX,CAAC,EAAEC,OAAO,EAAEC,aAAa,EAAQ,GACjC,CAAC,EAAEC,QAAQ,EAAEC,SAAS,EAAE,EAAE;QACxB,OAAO,CAAC,EAAEH,QAAQ,CAAC,EAAEC,cAAc,CAAC,EAAEH,KAAKM,KAAK,CAACC,IAAI,CAACF,QAAQD,UAAU,CAAC;IAC3E,EAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ContainerClient } from '@azure/storage-blob';
|
|
2
|
+
import type { HandleDelete } from '@payloadcms/plugin-cloud-storage/types';
|
|
3
|
+
import type { CollectionConfig } from 'payload/types';
|
|
4
|
+
interface Args {
|
|
5
|
+
collection: CollectionConfig;
|
|
6
|
+
getStorageClient: () => ContainerClient;
|
|
7
|
+
}
|
|
8
|
+
export declare const getHandleDelete: ({ getStorageClient }: Args) => HandleDelete;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=handleDelete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handleDelete.d.ts","sourceRoot":"","sources":["../src/handleDelete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAIrD,UAAU,IAAI;IACZ,UAAU,EAAE,gBAAgB,CAAA;IAC5B,gBAAgB,EAAE,MAAM,eAAe,CAAA;CACxC;AAED,eAAO,MAAM,eAAe,yBAA0B,IAAI,KAAG,YAK5D,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
export const getHandleDelete = ({ getStorageClient })=>{
|
|
3
|
+
return async ({ doc: { prefix = '' }, filename })=>{
|
|
4
|
+
const blockBlobClient = getStorageClient().getBlockBlobClient(path.posix.join(prefix, filename));
|
|
5
|
+
await blockBlobClient.deleteIfExists();
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=handleDelete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/handleDelete.ts"],"sourcesContent":["import type { ContainerClient } from '@azure/storage-blob'\nimport type { HandleDelete } from '@payloadcms/plugin-cloud-storage/types'\nimport type { CollectionConfig } from 'payload/types'\n\nimport path from 'path'\n\ninterface Args {\n collection: CollectionConfig\n getStorageClient: () => ContainerClient\n}\n\nexport const getHandleDelete = ({ getStorageClient }: Args): HandleDelete => {\n return async ({ doc: { prefix = '' }, filename }) => {\n const blockBlobClient = getStorageClient().getBlockBlobClient(path.posix.join(prefix, filename))\n await blockBlobClient.deleteIfExists()\n }\n}\n"],"names":["path","getHandleDelete","getStorageClient","doc","prefix","filename","blockBlobClient","getBlockBlobClient","posix","join","deleteIfExists"],"rangeMappings":";;;;;;","mappings":"AAIA,OAAOA,UAAU,OAAM;AAOvB,OAAO,MAAMC,kBAAkB,CAAC,EAAEC,gBAAgB,EAAQ;IACxD,OAAO,OAAO,EAAEC,KAAK,EAAEC,SAAS,EAAE,EAAE,EAAEC,QAAQ,EAAE;QAC9C,MAAMC,kBAAkBJ,mBAAmBK,kBAAkB,CAACP,KAAKQ,KAAK,CAACC,IAAI,CAACL,QAAQC;QACtF,MAAMC,gBAAgBI,cAAc;IACtC;AACF,EAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ContainerClient } from '@azure/storage-blob';
|
|
2
|
+
import type { HandleUpload } from '@payloadcms/plugin-cloud-storage/types';
|
|
3
|
+
import type { CollectionConfig } from 'payload/types';
|
|
4
|
+
interface Args {
|
|
5
|
+
collection: CollectionConfig;
|
|
6
|
+
getStorageClient: () => ContainerClient;
|
|
7
|
+
prefix?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const getHandleUpload: ({ getStorageClient, prefix }: Args) => HandleUpload;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=handleUpload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handleUpload.d.ts","sourceRoot":"","sources":["../src/handleUpload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAOrD,UAAU,IAAI;IACZ,UAAU,EAAE,gBAAgB,CAAA;IAC5B,gBAAgB,EAAE,MAAM,eAAe,CAAA;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAGD,eAAO,MAAM,eAAe,iCAAuC,IAAI,KAAG,YAyBzE,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AbortController } from '@azure/abort-controller';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { Readable } from 'stream';
|
|
5
|
+
const multipartThreshold = 1024 * 1024 * 50 // 50MB
|
|
6
|
+
;
|
|
7
|
+
export const getHandleUpload = ({ getStorageClient, prefix = '' })=>{
|
|
8
|
+
return async ({ data, file })=>{
|
|
9
|
+
const fileKey = path.posix.join(data.prefix || prefix, file.filename);
|
|
10
|
+
const blockBlobClient = getStorageClient().getBlockBlobClient(fileKey);
|
|
11
|
+
// when there are no temp files, or the upload is less than the threshold size, do not stream files
|
|
12
|
+
if (!file.tempFilePath && file.buffer.length > 0 && file.buffer.length < multipartThreshold) {
|
|
13
|
+
await blockBlobClient.upload(file.buffer, file.buffer.byteLength, {
|
|
14
|
+
blobHTTPHeaders: {
|
|
15
|
+
blobContentType: file.mimeType
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
return data;
|
|
19
|
+
}
|
|
20
|
+
const fileBufferOrStream = file.tempFilePath ? fs.createReadStream(file.tempFilePath) : Readable.from(file.buffer);
|
|
21
|
+
await blockBlobClient.uploadStream(fileBufferOrStream, 4 * 1024 * 1024, 4, {
|
|
22
|
+
abortSignal: AbortController.timeout(30 * 60 * 1000)
|
|
23
|
+
});
|
|
24
|
+
return data;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=handleUpload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/handleUpload.ts"],"sourcesContent":["import type { ContainerClient } from '@azure/storage-blob'\nimport type { HandleUpload } from '@payloadcms/plugin-cloud-storage/types'\nimport type { CollectionConfig } from 'payload/types'\n\nimport { AbortController } from '@azure/abort-controller'\nimport fs from 'fs'\nimport path from 'path'\nimport { Readable } from 'stream'\n\ninterface Args {\n collection: CollectionConfig\n getStorageClient: () => ContainerClient\n prefix?: string\n}\n\nconst multipartThreshold = 1024 * 1024 * 50 // 50MB\nexport const getHandleUpload = ({ getStorageClient, prefix = '' }: Args): HandleUpload => {\n return async ({ data, file }) => {\n const fileKey = path.posix.join(data.prefix || prefix, file.filename)\n\n const blockBlobClient = getStorageClient().getBlockBlobClient(fileKey)\n\n // when there are no temp files, or the upload is less than the threshold size, do not stream files\n if (!file.tempFilePath && file.buffer.length > 0 && file.buffer.length < multipartThreshold) {\n await blockBlobClient.upload(file.buffer, file.buffer.byteLength, {\n blobHTTPHeaders: { blobContentType: file.mimeType },\n })\n\n return data\n }\n\n const fileBufferOrStream: Readable = file.tempFilePath\n ? fs.createReadStream(file.tempFilePath)\n : Readable.from(file.buffer)\n\n await blockBlobClient.uploadStream(fileBufferOrStream, 4 * 1024 * 1024, 4, {\n abortSignal: AbortController.timeout(30 * 60 * 1000),\n })\n\n return data\n }\n}\n"],"names":["AbortController","fs","path","Readable","multipartThreshold","getHandleUpload","getStorageClient","prefix","data","file","fileKey","posix","join","filename","blockBlobClient","getBlockBlobClient","tempFilePath","buffer","length","upload","byteLength","blobHTTPHeaders","blobContentType","mimeType","fileBufferOrStream","createReadStream","from","uploadStream","abortSignal","timeout"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAIA,SAASA,eAAe,QAAQ,0BAAyB;AACzD,OAAOC,QAAQ,KAAI;AACnB,OAAOC,UAAU,OAAM;AACvB,SAASC,QAAQ,QAAQ,SAAQ;AAQjC,MAAMC,qBAAqB,OAAO,OAAO,GAAG,OAAO;;AACnD,OAAO,MAAMC,kBAAkB,CAAC,EAAEC,gBAAgB,EAAEC,SAAS,EAAE,EAAQ;IACrE,OAAO,OAAO,EAAEC,IAAI,EAAEC,IAAI,EAAE;QAC1B,MAAMC,UAAUR,KAAKS,KAAK,CAACC,IAAI,CAACJ,KAAKD,MAAM,IAAIA,QAAQE,KAAKI,QAAQ;QAEpE,MAAMC,kBAAkBR,mBAAmBS,kBAAkB,CAACL;QAE9D,mGAAmG;QACnG,IAAI,CAACD,KAAKO,YAAY,IAAIP,KAAKQ,MAAM,CAACC,MAAM,GAAG,KAAKT,KAAKQ,MAAM,CAACC,MAAM,GAAGd,oBAAoB;YAC3F,MAAMU,gBAAgBK,MAAM,CAACV,KAAKQ,MAAM,EAAER,KAAKQ,MAAM,CAACG,UAAU,EAAE;gBAChEC,iBAAiB;oBAAEC,iBAAiBb,KAAKc,QAAQ;gBAAC;YACpD;YAEA,OAAOf;QACT;QAEA,MAAMgB,qBAA+Bf,KAAKO,YAAY,GAClDf,GAAGwB,gBAAgB,CAAChB,KAAKO,YAAY,IACrCb,SAASuB,IAAI,CAACjB,KAAKQ,MAAM;QAE7B,MAAMH,gBAAgBa,YAAY,CAACH,oBAAoB,IAAI,OAAO,MAAM,GAAG;YACzEI,aAAa5B,gBAAgB6B,OAAO,CAAC,KAAK,KAAK;QACjD;QAEA,OAAOrB;IACT;AACF,EAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CollectionOptions } from '@payloadcms/plugin-cloud-storage/types';
|
|
2
|
+
import type { Plugin } from 'payload/config';
|
|
3
|
+
export type AzureStorageOptions = {
|
|
4
|
+
allowContainerCreate: boolean;
|
|
5
|
+
baseURL: string;
|
|
6
|
+
/**
|
|
7
|
+
* Collection options to apply the Azure Blob adapter to.
|
|
8
|
+
*/
|
|
9
|
+
collections: Record<string, Omit<CollectionOptions, 'adapter'> | true>;
|
|
10
|
+
/**
|
|
11
|
+
* Azure Blob storage connection string
|
|
12
|
+
*/
|
|
13
|
+
connectionString: string;
|
|
14
|
+
/**
|
|
15
|
+
* Azure Blob storage container name
|
|
16
|
+
*/
|
|
17
|
+
containerName: string;
|
|
18
|
+
/**
|
|
19
|
+
* Whether or not to enable the plugin
|
|
20
|
+
*
|
|
21
|
+
* Default: true
|
|
22
|
+
*/
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
};
|
|
25
|
+
type AzureStoragePlugin = (azureStorageArgs: AzureStorageOptions) => Plugin;
|
|
26
|
+
export declare const azureStorage: AzureStoragePlugin;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,iBAAiB,EAElB,MAAM,wCAAwC,CAAA;AAC/C,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAUpD,MAAM,MAAM,mBAAmB,GAAG;IAChC,oBAAoB,EAAE,OAAO,CAAA;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,CAAA;IAEtE;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAA;IAExB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAA;IAErB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,KAAK,kBAAkB,GAAG,CAAC,gBAAgB,EAAE,mBAAmB,KAAK,MAAM,CAAA;AAE3E,eAAO,MAAM,YAAY,EAAE,kBA0BxB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { BlobServiceClient } from '@azure/storage-blob';
|
|
2
|
+
import { cloudStorage } from '@payloadcms/plugin-cloud-storage';
|
|
3
|
+
import { getGenerateURL } from './generateURL.js';
|
|
4
|
+
import { getHandleDelete } from './handleDelete.js';
|
|
5
|
+
import { getHandleUpload } from './handleUpload.js';
|
|
6
|
+
import { getHandler } from './staticHandler.js';
|
|
7
|
+
export const azureStorage = (azureStorageOptions)=>(incomingConfig)=>{
|
|
8
|
+
if (azureStorageOptions.enabled === false) {
|
|
9
|
+
return incomingConfig;
|
|
10
|
+
}
|
|
11
|
+
const adapter = azureStorageInternal(azureStorageOptions);
|
|
12
|
+
// Add adapter to each collection option object
|
|
13
|
+
const collectionsWithAdapter = Object.entries(azureStorageOptions.collections).reduce((acc, [slug, collOptions])=>({
|
|
14
|
+
...acc,
|
|
15
|
+
[slug]: {
|
|
16
|
+
...collOptions === true ? {} : collOptions,
|
|
17
|
+
adapter
|
|
18
|
+
}
|
|
19
|
+
}), {});
|
|
20
|
+
return cloudStorage({
|
|
21
|
+
collections: collectionsWithAdapter
|
|
22
|
+
})(incomingConfig);
|
|
23
|
+
};
|
|
24
|
+
function azureStorageInternal({ allowContainerCreate, baseURL, connectionString, containerName }) {
|
|
25
|
+
let storageClient = null;
|
|
26
|
+
const getStorageClient = ()=>{
|
|
27
|
+
if (storageClient) return storageClient;
|
|
28
|
+
const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
|
|
29
|
+
storageClient = blobServiceClient.getContainerClient(containerName);
|
|
30
|
+
return storageClient;
|
|
31
|
+
};
|
|
32
|
+
const createContainerIfNotExists = ()=>{
|
|
33
|
+
void getStorageClient().createIfNotExists({
|
|
34
|
+
access: 'blob'
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
return ({ collection, prefix })=>{
|
|
38
|
+
return {
|
|
39
|
+
generateURL: getGenerateURL({
|
|
40
|
+
baseURL,
|
|
41
|
+
containerName
|
|
42
|
+
}),
|
|
43
|
+
handleDelete: getHandleDelete({
|
|
44
|
+
collection,
|
|
45
|
+
getStorageClient
|
|
46
|
+
}),
|
|
47
|
+
handleUpload: getHandleUpload({
|
|
48
|
+
collection,
|
|
49
|
+
getStorageClient,
|
|
50
|
+
prefix
|
|
51
|
+
}),
|
|
52
|
+
staticHandler: getHandler({
|
|
53
|
+
collection,
|
|
54
|
+
getStorageClient
|
|
55
|
+
}),
|
|
56
|
+
...allowContainerCreate && {
|
|
57
|
+
onInit: createContainerIfNotExists
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { ContainerClient } from '@azure/storage-blob'\nimport type {\n Adapter,\n PluginOptions as CloudStoragePluginOptions,\n CollectionOptions,\n GeneratedAdapter,\n} from '@payloadcms/plugin-cloud-storage/types'\nimport type { Config, Plugin } from 'payload/config'\n\nimport { BlobServiceClient } from '@azure/storage-blob'\nimport { cloudStorage } from '@payloadcms/plugin-cloud-storage'\n\nimport { getGenerateURL } from './generateURL.js'\nimport { getHandleDelete } from './handleDelete.js'\nimport { getHandleUpload } from './handleUpload.js'\nimport { getHandler } from './staticHandler.js'\n\nexport type AzureStorageOptions = {\n allowContainerCreate: boolean\n baseURL: string\n /**\n * Collection options to apply the Azure Blob adapter to.\n */\n collections: Record<string, Omit<CollectionOptions, 'adapter'> | true>\n\n /**\n * Azure Blob storage connection string\n */\n connectionString: string\n\n /**\n * Azure Blob storage container name\n */\n containerName: string\n\n /**\n * Whether or not to enable the plugin\n *\n * Default: true\n */\n enabled?: boolean\n}\n\ntype AzureStoragePlugin = (azureStorageArgs: AzureStorageOptions) => Plugin\n\nexport const azureStorage: AzureStoragePlugin =\n (azureStorageOptions: AzureStorageOptions) =>\n (incomingConfig: Config): Config => {\n if (azureStorageOptions.enabled === false) {\n return incomingConfig\n }\n\n const adapter = azureStorageInternal(azureStorageOptions)\n\n // Add adapter to each collection option object\n const collectionsWithAdapter: CloudStoragePluginOptions['collections'] = Object.entries(\n azureStorageOptions.collections,\n ).reduce(\n (acc, [slug, collOptions]) => ({\n ...acc,\n [slug]: {\n ...(collOptions === true ? {} : collOptions),\n adapter,\n },\n }),\n {} as Record<string, CollectionOptions>,\n )\n\n return cloudStorage({\n collections: collectionsWithAdapter,\n })(incomingConfig)\n }\n\nfunction azureStorageInternal({\n allowContainerCreate,\n baseURL,\n connectionString,\n containerName,\n}: AzureStorageOptions): Adapter {\n let storageClient: ContainerClient | null = null\n const getStorageClient = () => {\n if (storageClient) return storageClient\n\n const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString)\n storageClient = blobServiceClient.getContainerClient(containerName)\n return storageClient\n }\n\n const createContainerIfNotExists = () => {\n void getStorageClient().createIfNotExists({ access: 'blob' })\n }\n\n return ({ collection, prefix }): GeneratedAdapter => {\n return {\n generateURL: getGenerateURL({ baseURL, containerName }),\n handleDelete: getHandleDelete({ collection, getStorageClient }),\n handleUpload: getHandleUpload({\n collection,\n getStorageClient,\n prefix,\n }),\n staticHandler: getHandler({ collection, getStorageClient }),\n ...(allowContainerCreate && { onInit: createContainerIfNotExists }),\n }\n }\n}\n"],"names":["BlobServiceClient","cloudStorage","getGenerateURL","getHandleDelete","getHandleUpload","getHandler","azureStorage","azureStorageOptions","incomingConfig","enabled","adapter","azureStorageInternal","collectionsWithAdapter","Object","entries","collections","reduce","acc","slug","collOptions","allowContainerCreate","baseURL","connectionString","containerName","storageClient","getStorageClient","blobServiceClient","fromConnectionString","getContainerClient","createContainerIfNotExists","createIfNotExists","access","collection","prefix","generateURL","handleDelete","handleUpload","staticHandler","onInit"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AASA,SAASA,iBAAiB,QAAQ,sBAAqB;AACvD,SAASC,YAAY,QAAQ,mCAAkC;AAE/D,SAASC,cAAc,QAAQ,mBAAkB;AACjD,SAASC,eAAe,QAAQ,oBAAmB;AACnD,SAASC,eAAe,QAAQ,oBAAmB;AACnD,SAASC,UAAU,QAAQ,qBAAoB;AA8B/C,OAAO,MAAMC,eACX,CAACC,sBACD,CAACC;QACC,IAAID,oBAAoBE,OAAO,KAAK,OAAO;YACzC,OAAOD;QACT;QAEA,MAAME,UAAUC,qBAAqBJ;QAErC,+CAA+C;QAC/C,MAAMK,yBAAmEC,OAAOC,OAAO,CACrFP,oBAAoBQ,WAAW,EAC/BC,MAAM,CACN,CAACC,KAAK,CAACC,MAAMC,YAAY,GAAM,CAAA;gBAC7B,GAAGF,GAAG;gBACN,CAACC,KAAK,EAAE;oBACN,GAAIC,gBAAgB,OAAO,CAAC,IAAIA,WAAW;oBAC3CT;gBACF;YACF,CAAA,GACA,CAAC;QAGH,OAAOT,aAAa;YAClBc,aAAaH;QACf,GAAGJ;IACL,EAAC;AAEH,SAASG,qBAAqB,EAC5BS,oBAAoB,EACpBC,OAAO,EACPC,gBAAgB,EAChBC,aAAa,EACO;IACpB,IAAIC,gBAAwC;IAC5C,MAAMC,mBAAmB;QACvB,IAAID,eAAe,OAAOA;QAE1B,MAAME,oBAAoB1B,kBAAkB2B,oBAAoB,CAACL;QACjEE,gBAAgBE,kBAAkBE,kBAAkB,CAACL;QACrD,OAAOC;IACT;IAEA,MAAMK,6BAA6B;QACjC,KAAKJ,mBAAmBK,iBAAiB,CAAC;YAAEC,QAAQ;QAAO;IAC7D;IAEA,OAAO,CAAC,EAAEC,UAAU,EAAEC,MAAM,EAAE;QAC5B,OAAO;YACLC,aAAahC,eAAe;gBAAEmB;gBAASE;YAAc;YACrDY,cAAchC,gBAAgB;gBAAE6B;gBAAYP;YAAiB;YAC7DW,cAAchC,gBAAgB;gBAC5B4B;gBACAP;gBACAQ;YACF;YACAI,eAAehC,WAAW;gBAAE2B;gBAAYP;YAAiB;YACzD,GAAIL,wBAAwB;gBAAEkB,QAAQT;YAA2B,CAAC;QACpE;IACF;AACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ContainerClient } from '@azure/storage-blob';
|
|
2
|
+
import type { StaticHandler } from '@payloadcms/plugin-cloud-storage/types';
|
|
3
|
+
import type { CollectionConfig } from 'payload/types';
|
|
4
|
+
interface Args {
|
|
5
|
+
collection: CollectionConfig;
|
|
6
|
+
getStorageClient: () => ContainerClient;
|
|
7
|
+
}
|
|
8
|
+
export declare const getHandler: ({ collection, getStorageClient }: Args) => StaticHandler;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=staticHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staticHandler.d.ts","sourceRoot":"","sources":["../src/staticHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAC3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAOrD,UAAU,IAAI;IACZ,UAAU,EAAE,gBAAgB,CAAA;IAC5B,gBAAgB,EAAE,MAAM,eAAe,CAAA;CACxC;AAED,eAAO,MAAM,UAAU,qCAAsC,IAAI,KAAG,aA8CnE,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getFilePrefix } from '@payloadcms/plugin-cloud-storage/utilities';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getRangeFromHeader } from './utils/getRangeFromHeader.js';
|
|
4
|
+
export const getHandler = ({ collection, getStorageClient })=>{
|
|
5
|
+
return async (req, { params: { filename } })=>{
|
|
6
|
+
try {
|
|
7
|
+
const prefix = await getFilePrefix({
|
|
8
|
+
collection,
|
|
9
|
+
filename,
|
|
10
|
+
req
|
|
11
|
+
});
|
|
12
|
+
const blockBlobClient = getStorageClient().getBlockBlobClient(path.posix.join(prefix, filename));
|
|
13
|
+
const { end, start } = await getRangeFromHeader(blockBlobClient, String(req.headers.get('range')));
|
|
14
|
+
const blob = await blockBlobClient.download(start, end);
|
|
15
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
16
|
+
const response = blob._response;
|
|
17
|
+
// Manually create a ReadableStream for the web from a Node.js stream.
|
|
18
|
+
const readableStream = new ReadableStream({
|
|
19
|
+
start (controller) {
|
|
20
|
+
const nodeStream = blob.readableStreamBody;
|
|
21
|
+
if (!nodeStream) {
|
|
22
|
+
throw new Error('No readable stream body');
|
|
23
|
+
}
|
|
24
|
+
nodeStream.on('data', (chunk)=>{
|
|
25
|
+
controller.enqueue(new Uint8Array(chunk));
|
|
26
|
+
});
|
|
27
|
+
nodeStream.on('end', ()=>{
|
|
28
|
+
controller.close();
|
|
29
|
+
});
|
|
30
|
+
nodeStream.on('error', (err)=>{
|
|
31
|
+
controller.error(err);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return new Response(readableStream, {
|
|
36
|
+
headers: response.headers.rawHeaders(),
|
|
37
|
+
status: response.status
|
|
38
|
+
});
|
|
39
|
+
} catch (err) {
|
|
40
|
+
req.payload.logger.error(err);
|
|
41
|
+
return new Response('Internal Server Error', {
|
|
42
|
+
status: 500
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=staticHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/staticHandler.ts"],"sourcesContent":["import type { ContainerClient } from '@azure/storage-blob'\nimport type { StaticHandler } from '@payloadcms/plugin-cloud-storage/types'\nimport type { CollectionConfig } from 'payload/types'\n\nimport { getFilePrefix } from '@payloadcms/plugin-cloud-storage/utilities'\nimport path from 'path'\n\nimport { getRangeFromHeader } from './utils/getRangeFromHeader.js'\n\ninterface Args {\n collection: CollectionConfig\n getStorageClient: () => ContainerClient\n}\n\nexport const getHandler = ({ collection, getStorageClient }: Args): StaticHandler => {\n return async (req, { params: { filename } }) => {\n try {\n const prefix = await getFilePrefix({ collection, filename, req })\n const blockBlobClient = getStorageClient().getBlockBlobClient(\n path.posix.join(prefix, filename),\n )\n\n const { end, start } = await getRangeFromHeader(\n blockBlobClient,\n String(req.headers.get('range')),\n )\n\n const blob = await blockBlobClient.download(start, end)\n // eslint-disable-next-line no-underscore-dangle\n const response = blob._response\n\n // Manually create a ReadableStream for the web from a Node.js stream.\n const readableStream = new ReadableStream({\n start(controller) {\n const nodeStream = blob.readableStreamBody\n if (!nodeStream) {\n throw new Error('No readable stream body')\n }\n\n nodeStream.on('data', (chunk) => {\n controller.enqueue(new Uint8Array(chunk))\n })\n nodeStream.on('end', () => {\n controller.close()\n })\n nodeStream.on('error', (err) => {\n controller.error(err)\n })\n },\n })\n\n return new Response(readableStream, {\n headers: response.headers.rawHeaders(),\n status: response.status,\n })\n } catch (err: unknown) {\n req.payload.logger.error(err)\n return new Response('Internal Server Error', { status: 500 })\n }\n }\n}\n"],"names":["getFilePrefix","path","getRangeFromHeader","getHandler","collection","getStorageClient","req","params","filename","prefix","blockBlobClient","getBlockBlobClient","posix","join","end","start","String","headers","get","blob","download","response","_response","readableStream","ReadableStream","controller","nodeStream","readableStreamBody","Error","on","chunk","enqueue","Uint8Array","close","err","error","Response","rawHeaders","status","payload","logger"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAIA,SAASA,aAAa,QAAQ,6CAA4C;AAC1E,OAAOC,UAAU,OAAM;AAEvB,SAASC,kBAAkB,QAAQ,gCAA+B;AAOlE,OAAO,MAAMC,aAAa,CAAC,EAAEC,UAAU,EAAEC,gBAAgB,EAAQ;IAC/D,OAAO,OAAOC,KAAK,EAAEC,QAAQ,EAAEC,QAAQ,EAAE,EAAE;QACzC,IAAI;YACF,MAAMC,SAAS,MAAMT,cAAc;gBAAEI;gBAAYI;gBAAUF;YAAI;YAC/D,MAAMI,kBAAkBL,mBAAmBM,kBAAkB,CAC3DV,KAAKW,KAAK,CAACC,IAAI,CAACJ,QAAQD;YAG1B,MAAM,EAAEM,GAAG,EAAEC,KAAK,EAAE,GAAG,MAAMb,mBAC3BQ,iBACAM,OAAOV,IAAIW,OAAO,CAACC,GAAG,CAAC;YAGzB,MAAMC,OAAO,MAAMT,gBAAgBU,QAAQ,CAACL,OAAOD;YACnD,gDAAgD;YAChD,MAAMO,WAAWF,KAAKG,SAAS;YAE/B,sEAAsE;YACtE,MAAMC,iBAAiB,IAAIC,eAAe;gBACxCT,OAAMU,UAAU;oBACd,MAAMC,aAAaP,KAAKQ,kBAAkB;oBAC1C,IAAI,CAACD,YAAY;wBACf,MAAM,IAAIE,MAAM;oBAClB;oBAEAF,WAAWG,EAAE,CAAC,QAAQ,CAACC;wBACrBL,WAAWM,OAAO,CAAC,IAAIC,WAAWF;oBACpC;oBACAJ,WAAWG,EAAE,CAAC,OAAO;wBACnBJ,WAAWQ,KAAK;oBAClB;oBACAP,WAAWG,EAAE,CAAC,SAAS,CAACK;wBACtBT,WAAWU,KAAK,CAACD;oBACnB;gBACF;YACF;YAEA,OAAO,IAAIE,SAASb,gBAAgB;gBAClCN,SAASI,SAASJ,OAAO,CAACoB,UAAU;gBACpCC,QAAQjB,SAASiB,MAAM;YACzB;QACF,EAAE,OAAOJ,KAAc;YACrB5B,IAAIiC,OAAO,CAACC,MAAM,CAACL,KAAK,CAACD;YACzB,OAAO,IAAIE,SAAS,yBAAyB;gBAAEE,QAAQ;YAAI;QAC7D;IACF;AACF,EAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BlockBlobClient } from '@azure/storage-blob';
|
|
2
|
+
export declare const getRangeFromHeader: (blockBlobClient: BlockBlobClient, rangeHeader?: string) => Promise<{
|
|
3
|
+
end: number | undefined;
|
|
4
|
+
start: number;
|
|
5
|
+
}>;
|
|
6
|
+
//# sourceMappingURL=getRangeFromHeader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getRangeFromHeader.d.ts","sourceRoot":"","sources":["../../src/utils/getRangeFromHeader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAI1D,eAAO,MAAM,kBAAkB,oBACZ,eAAe,gBAClB,MAAM,KACnB,QAAQ;IAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAkBpD,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import parseRange from 'range-parser';
|
|
2
|
+
export const getRangeFromHeader = async (blockBlobClient, rangeHeader)=>{
|
|
3
|
+
const fullRange = {
|
|
4
|
+
end: undefined,
|
|
5
|
+
start: 0
|
|
6
|
+
};
|
|
7
|
+
if (!rangeHeader) {
|
|
8
|
+
return fullRange;
|
|
9
|
+
}
|
|
10
|
+
const size = await blockBlobClient.getProperties().then((props)=>props.contentLength);
|
|
11
|
+
if (size === undefined) {
|
|
12
|
+
return fullRange;
|
|
13
|
+
}
|
|
14
|
+
const range = parseRange(size, rangeHeader);
|
|
15
|
+
if (range === -1 || range === -2 || range.type !== 'bytes' || range.length !== 1) {
|
|
16
|
+
return fullRange;
|
|
17
|
+
}
|
|
18
|
+
return range[0];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=getRangeFromHeader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/getRangeFromHeader.ts"],"sourcesContent":["import type { BlockBlobClient } from '@azure/storage-blob'\n\nimport parseRange from 'range-parser'\n\nexport const getRangeFromHeader = async (\n blockBlobClient: BlockBlobClient,\n rangeHeader?: string,\n): Promise<{ end: number | undefined; start: number }> => {\n const fullRange = { end: undefined, start: 0 }\n\n if (!rangeHeader) {\n return fullRange\n }\n\n const size = await blockBlobClient.getProperties().then((props) => props.contentLength)\n if (size === undefined) {\n return fullRange\n }\n\n const range = parseRange(size, rangeHeader)\n if (range === -1 || range === -2 || range.type !== 'bytes' || range.length !== 1) {\n return fullRange\n }\n\n return range[0]\n}\n"],"names":["parseRange","getRangeFromHeader","blockBlobClient","rangeHeader","fullRange","end","undefined","start","size","getProperties","then","props","contentLength","range","type","length"],"rangeMappings":";;;;;;;;;;;;;;;;;;","mappings":"AAEA,OAAOA,gBAAgB,eAAc;AAErC,OAAO,MAAMC,qBAAqB,OAChCC,iBACAC;IAEA,MAAMC,YAAY;QAAEC,KAAKC;QAAWC,OAAO;IAAE;IAE7C,IAAI,CAACJ,aAAa;QAChB,OAAOC;IACT;IAEA,MAAMI,OAAO,MAAMN,gBAAgBO,aAAa,GAAGC,IAAI,CAAC,CAACC,QAAUA,MAAMC,aAAa;IACtF,IAAIJ,SAASF,WAAW;QACtB,OAAOF;IACT;IAEA,MAAMS,QAAQb,WAAWQ,MAAML;IAC/B,IAAIU,UAAU,CAAC,KAAKA,UAAU,CAAC,KAAKA,MAAMC,IAAI,KAAK,WAAWD,MAAME,MAAM,KAAK,GAAG;QAChF,OAAOX;IACT;IAEA,OAAOS,KAAK,CAAC,EAAE;AACjB,EAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@payloadcms/storage-azure",
|
|
3
|
+
"version": "3.0.0-alpha.62",
|
|
4
|
+
"description": "Payload storage adapter for Azure Blob Storage",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/payloadcms/payload.git",
|
|
8
|
+
"directory": "packages/storage-azure"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"homepage": "https://payloadcms.com",
|
|
12
|
+
"author": "Payload CMS, Inc.",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"require": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@azure/abort-controller": "^1.1.0",
|
|
25
|
+
"@azure/storage-blob": "^12.11.0",
|
|
26
|
+
"range-parser": "^1.2.1",
|
|
27
|
+
"@payloadcms/plugin-cloud-storage": "3.0.0-alpha.62"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/range-parser": "^1.2.7",
|
|
31
|
+
"payload": "3.0.0-alpha.62"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"payload": "3.0.0-alpha.62"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.20.2"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "pnpm build:swc && pnpm build:types",
|
|
44
|
+
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
|
45
|
+
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
|
46
|
+
"build:clean": "find . \\( -type d \\( -name build -o -name dist -o -name .cache \\) -o -type f -name tsconfig.tsbuildinfo \\) -exec rm -rf {} + && pnpm build",
|
|
47
|
+
"clean": "rimraf {dist,*.tsbuildinfo}"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ContainerClient } from '@azure/storage-blob'
|
|
2
|
+
import type {
|
|
3
|
+
Adapter,
|
|
4
|
+
PluginOptions as CloudStoragePluginOptions,
|
|
5
|
+
CollectionOptions,
|
|
6
|
+
GeneratedAdapter,
|
|
7
|
+
} from '@payloadcms/plugin-cloud-storage/types'
|
|
8
|
+
import type { Config, Plugin } from 'payload/config'
|
|
9
|
+
|
|
10
|
+
import { BlobServiceClient } from '@azure/storage-blob'
|
|
11
|
+
import { cloudStorage } from '@payloadcms/plugin-cloud-storage'
|
|
12
|
+
|
|
13
|
+
import { getGenerateURL } from './generateURL.js'
|
|
14
|
+
import { getHandleDelete } from './handleDelete.js'
|
|
15
|
+
import { getHandleUpload } from './handleUpload.js'
|
|
16
|
+
import { getHandler } from './staticHandler.js'
|
|
17
|
+
|
|
18
|
+
export type AzureStorageOptions = {
|
|
19
|
+
allowContainerCreate: boolean
|
|
20
|
+
baseURL: string
|
|
21
|
+
/**
|
|
22
|
+
* Collection options to apply the Azure Blob adapter to.
|
|
23
|
+
*/
|
|
24
|
+
collections: Record<string, Omit<CollectionOptions, 'adapter'> | true>
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Azure Blob storage connection string
|
|
28
|
+
*/
|
|
29
|
+
connectionString: string
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Azure Blob storage container name
|
|
33
|
+
*/
|
|
34
|
+
containerName: string
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether or not to enable the plugin
|
|
38
|
+
*
|
|
39
|
+
* Default: true
|
|
40
|
+
*/
|
|
41
|
+
enabled?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type AzureStoragePlugin = (azureStorageArgs: AzureStorageOptions) => Plugin
|
|
45
|
+
|
|
46
|
+
export const azureStorage: AzureStoragePlugin =
|
|
47
|
+
(azureStorageOptions: AzureStorageOptions) =>
|
|
48
|
+
(incomingConfig: Config): Config => {
|
|
49
|
+
if (azureStorageOptions.enabled === false) {
|
|
50
|
+
return incomingConfig
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const adapter = azureStorageInternal(azureStorageOptions)
|
|
54
|
+
|
|
55
|
+
// Add adapter to each collection option object
|
|
56
|
+
const collectionsWithAdapter: CloudStoragePluginOptions['collections'] = Object.entries(
|
|
57
|
+
azureStorageOptions.collections,
|
|
58
|
+
).reduce(
|
|
59
|
+
(acc, [slug, collOptions]) => ({
|
|
60
|
+
...acc,
|
|
61
|
+
[slug]: {
|
|
62
|
+
...(collOptions === true ? {} : collOptions),
|
|
63
|
+
adapter,
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
{} as Record<string, CollectionOptions>,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return cloudStorage({
|
|
70
|
+
collections: collectionsWithAdapter,
|
|
71
|
+
})(incomingConfig)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function azureStorageInternal({
|
|
75
|
+
allowContainerCreate,
|
|
76
|
+
baseURL,
|
|
77
|
+
connectionString,
|
|
78
|
+
containerName,
|
|
79
|
+
}: AzureStorageOptions): Adapter {
|
|
80
|
+
let storageClient: ContainerClient | null = null
|
|
81
|
+
const getStorageClient = () => {
|
|
82
|
+
if (storageClient) return storageClient
|
|
83
|
+
|
|
84
|
+
const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString)
|
|
85
|
+
storageClient = blobServiceClient.getContainerClient(containerName)
|
|
86
|
+
return storageClient
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const createContainerIfNotExists = () => {
|
|
90
|
+
void getStorageClient().createIfNotExists({ access: 'blob' })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return ({ collection, prefix }): GeneratedAdapter => {
|
|
94
|
+
return {
|
|
95
|
+
generateURL: getGenerateURL({ baseURL, containerName }),
|
|
96
|
+
handleDelete: getHandleDelete({ collection, getStorageClient }),
|
|
97
|
+
handleUpload: getHandleUpload({
|
|
98
|
+
collection,
|
|
99
|
+
getStorageClient,
|
|
100
|
+
prefix,
|
|
101
|
+
}),
|
|
102
|
+
staticHandler: getHandler({ collection, getStorageClient }),
|
|
103
|
+
...(allowContainerCreate && { onInit: createContainerIfNotExists }),
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|