@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 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,6 @@
1
+ import path from 'path';
2
+ export const getGenerateURL = ({ baseURL, containerName })=>({ filename, prefix = '' })=>{
3
+ return `${baseURL}/${containerName}/${path.posix.join(prefix, filename)}`;
4
+ };
5
+
6
+ //# sourceMappingURL=generateURL.js.map
@@ -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"}
@@ -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
+ }