@alepha/bucket-azure 0.8.1 → 0.9.0
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/dist/index.cjs +33 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -20
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +22 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -27
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +8 -8
- package/src/providers/AzureFileStorageProvider.ts +45 -33
package/dist/index.cjs
CHANGED
|
@@ -22,8 +22,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
24
|
const __alepha_bucket = __toESM(require("@alepha/bucket"));
|
|
25
|
-
const node_crypto = __toESM(require("node:crypto"));
|
|
26
25
|
const __alepha_core = __toESM(require("@alepha/core"));
|
|
26
|
+
const node_crypto = __toESM(require("node:crypto"));
|
|
27
|
+
const node_stream = __toESM(require("node:stream"));
|
|
27
28
|
const __alepha_datetime = __toESM(require("@alepha/datetime"));
|
|
28
29
|
const __alepha_file = __toESM(require("@alepha/file"));
|
|
29
30
|
const __azure_storage_blob = __toESM(require("@azure/storage-blob"));
|
|
@@ -35,8 +36,8 @@ const envSchema = __alepha_core.t.object({ AZ_STORAGE_CONNECTION_STRING: __aleph
|
|
|
35
36
|
*/
|
|
36
37
|
var AzureFileStorageProvider = class {
|
|
37
38
|
log = (0, __alepha_core.$logger)();
|
|
38
|
-
env = (0, __alepha_core.$
|
|
39
|
-
|
|
39
|
+
env = (0, __alepha_core.$env)(envSchema);
|
|
40
|
+
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
40
41
|
time = (0, __alepha_core.$inject)(__alepha_datetime.DateTimeProvider);
|
|
41
42
|
containers = {};
|
|
42
43
|
blobServiceClient;
|
|
@@ -44,6 +45,18 @@ var AzureFileStorageProvider = class {
|
|
|
44
45
|
constructor() {
|
|
45
46
|
this.blobServiceClient = __azure_storage_blob.BlobServiceClient.fromConnectionString(this.env.AZ_STORAGE_CONNECTION_STRING, this.options);
|
|
46
47
|
}
|
|
48
|
+
onStart = (0, __alepha_core.$hook)({
|
|
49
|
+
on: "start",
|
|
50
|
+
handler: async () => {
|
|
51
|
+
for (const bucket of this.alepha.descriptors(__alepha_bucket.$bucket)) {
|
|
52
|
+
if (bucket.provider !== this) continue;
|
|
53
|
+
const containerName = bucket.name.replaceAll("/", "-").toLowerCase();
|
|
54
|
+
this.log.debug(`Prepare container ${containerName}...`);
|
|
55
|
+
if (!this.containers[containerName]) this.containers[containerName] = await this.createContainerClient(containerName);
|
|
56
|
+
this.log.info(`Container ${bucket} OK`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
47
60
|
async createContainer(containerName) {
|
|
48
61
|
if (this.containers[containerName]) return this.containers[containerName];
|
|
49
62
|
const container = await this.createContainerClient(containerName);
|
|
@@ -65,7 +78,10 @@ var AzureFileStorageProvider = class {
|
|
|
65
78
|
metadata,
|
|
66
79
|
blobHTTPHeaders: { blobContentType: file.type }
|
|
67
80
|
});
|
|
68
|
-
else
|
|
81
|
+
else await block.uploadStream(node_stream.Readable.from(file.stream()), file.size || void 0, 5, {
|
|
82
|
+
metadata,
|
|
83
|
+
blobHTTPHeaders: { blobContentType: file.type }
|
|
84
|
+
});
|
|
69
85
|
return fileId;
|
|
70
86
|
}
|
|
71
87
|
async download(bucketName, fileId) {
|
|
@@ -75,7 +91,10 @@ var AzureFileStorageProvider = class {
|
|
|
75
91
|
throw error;
|
|
76
92
|
});
|
|
77
93
|
if (!blob.readableStreamBody) throw new __alepha_bucket.FileNotFoundError("File not found - empty stream body");
|
|
78
|
-
return (0, __alepha_file.createFile)(blob.readableStreamBody,
|
|
94
|
+
return (0, __alepha_file.createFile)(blob.readableStreamBody, {
|
|
95
|
+
...blob.metadata,
|
|
96
|
+
size: blob.contentLength
|
|
97
|
+
});
|
|
79
98
|
}
|
|
80
99
|
async exists(bucketName, fileId) {
|
|
81
100
|
return await this.getBlock(bucketName, fileId).exists();
|
|
@@ -92,17 +111,6 @@ var AzureFileStorageProvider = class {
|
|
|
92
111
|
if (!this.containers[container]) throw new __alepha_bucket.FileNotFoundError(`File '${fileId}' not found - container '${container}' does not exists`);
|
|
93
112
|
return this.containers[container].getBlockBlobClient(fileId);
|
|
94
113
|
}
|
|
95
|
-
onStart = (0, __alepha_core.$hook)({
|
|
96
|
-
on: "start",
|
|
97
|
-
handler: async () => {
|
|
98
|
-
for (const bucket of this.bucket.getBuckets()) {
|
|
99
|
-
const containerName = bucket.name.replaceAll("/", "-").toLowerCase();
|
|
100
|
-
this.log.debug(`Prepare container ${containerName}...`);
|
|
101
|
-
if (!this.containers[containerName]) this.containers[containerName] = await this.createContainerClient(containerName);
|
|
102
|
-
this.log.info(`Container ${bucket} OK`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
114
|
async createContainerClient(name) {
|
|
107
115
|
const container = this.blobServiceClient.getContainerClient(name);
|
|
108
116
|
await this.time.deadline((abortSignal) => container.createIfNotExists({ abortSignal }), [5, "seconds"]);
|
|
@@ -121,16 +129,15 @@ var AzureFileStorageProvider = class {
|
|
|
121
129
|
* @see {@link AzureFileStorageProvider}
|
|
122
130
|
* @module alepha.bucket.azure
|
|
123
131
|
*/
|
|
124
|
-
|
|
125
|
-
name
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
};
|
|
132
|
+
const AlephaBucketAzure = (0, __alepha_core.$module)({
|
|
133
|
+
name: "alepha.bucket.azure",
|
|
134
|
+
services: [AzureFileStorageProvider],
|
|
135
|
+
register: (alepha) => alepha.with({
|
|
136
|
+
optional: true,
|
|
137
|
+
provide: __alepha_bucket.FileStorageProvider,
|
|
138
|
+
use: AzureFileStorageProvider
|
|
139
|
+
}).with(__alepha_bucket.AlephaBucket)
|
|
140
|
+
});
|
|
134
141
|
|
|
135
142
|
//#endregion
|
|
136
143
|
exports.AlephaBucketAzure = AlephaBucketAzure;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Alepha","DateTimeProvider","$bucket","containerName: string","bucketName: string","file: FileLike","fileId?: string","fileId: string","FileNotFoundError","container: string","name: string","FileStorageProvider","AlephaBucket"],"sources":["../src/providers/AzureFileStorageProvider.ts","../src/index.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { Readable } from \"node:stream\";\nimport {\n\t$bucket,\n\tFileNotFoundError,\n\ttype FileStorageProvider,\n} from \"@alepha/bucket\";\nimport {\n\t$env,\n\t$hook,\n\t$inject,\n\t$logger,\n\tAlepha,\n\ttype FileLike,\n\ttype Static,\n\tt,\n} from \"@alepha/core\";\nimport { DateTimeProvider } from \"@alepha/datetime\";\nimport { createFile } from \"@alepha/file\";\nimport {\n\tBlobServiceClient,\n\ttype BlockBlobClient,\n\ttype ContainerClient,\n\ttype StoragePipelineOptions,\n} from \"@azure/storage-blob\";\n\nconst envSchema = t.object({\n\tAZ_STORAGE_CONNECTION_STRING: t.string({\n\t\tsize: \"long\",\n\t}),\n});\n\ndeclare module \"@alepha/core\" {\n\tinterface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n/**\n * Azure Blog Storage implementation of File Storage Provider.\n */\nexport class AzureFileStorageProvider implements FileStorageProvider {\n\tprotected readonly log = $logger();\n\tprotected readonly env = $env(envSchema);\n\tprotected readonly alepha = $inject(Alepha);\n\tprotected readonly time = $inject(DateTimeProvider);\n\tprotected readonly containers: Record<string, ContainerClient> = {};\n\tprotected readonly blobServiceClient: BlobServiceClient;\n\n\tpublic readonly options: StoragePipelineOptions = {};\n\n\tconstructor() {\n\t\tthis.blobServiceClient = BlobServiceClient.fromConnectionString(\n\t\t\tthis.env.AZ_STORAGE_CONNECTION_STRING,\n\t\t\tthis.options,\n\t\t);\n\t}\n\n\tprotected readonly onStart = $hook({\n\t\ton: \"start\",\n\t\thandler: async () => {\n\t\t\tfor (const bucket of this.alepha.descriptors($bucket)) {\n\t\t\t\tif (bucket.provider !== this) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst containerName = bucket.name.replaceAll(\"/\", \"-\").toLowerCase();\n\t\t\t\tthis.log.debug(`Prepare container ${containerName}...`);\n\n\t\t\t\tif (!this.containers[containerName]) {\n\t\t\t\t\tthis.containers[containerName] =\n\t\t\t\t\t\tawait this.createContainerClient(containerName);\n\t\t\t\t}\n\n\t\t\t\tthis.log.info(`Container ${bucket} OK`);\n\t\t\t}\n\t\t},\n\t});\n\n\tpublic async createContainer(\n\t\tcontainerName: string,\n\t): Promise<ContainerClient> {\n\t\tif (this.containers[containerName]) {\n\t\t\treturn this.containers[containerName];\n\t\t}\n\t\tconst container = await this.createContainerClient(containerName);\n\t\tthis.containers[containerName] = container;\n\t\treturn container;\n\t}\n\n\tpublic async upload(\n\t\tbucketName: string,\n\t\tfile: FileLike,\n\t\tfileId?: string,\n\t): Promise<string> {\n\t\tfileId ??= this.createId();\n\t\tconst block = this.getBlock(bucketName, fileId);\n\n\t\tconst metadata = {\n\t\t\tname: file.name,\n\t\t\ttype: file.type,\n\t\t};\n\n\t\tif (file.filepath) {\n\t\t\tawait block.uploadFile(file.filepath, {\n\t\t\t\tmetadata,\n\t\t\t\tblobHTTPHeaders: {\n\t\t\t\t\tblobContentType: file.type,\n\t\t\t\t},\n\t\t\t});\n\t\t} else if (file.size > 0) {\n\t\t\tawait block.uploadData(await file.arrayBuffer(), {\n\t\t\t\tmetadata,\n\t\t\t\tblobHTTPHeaders: {\n\t\t\t\t\tblobContentType: file.type,\n\t\t\t\t},\n\t\t\t});\n\t\t} else {\n\t\t\tawait block.uploadStream(\n\t\t\t\tReadable.from(file.stream()),\n\t\t\t\tfile.size || undefined,\n\t\t\t\t5,\n\t\t\t\t{\n\t\t\t\t\tmetadata,\n\t\t\t\t\tblobHTTPHeaders: {\n\t\t\t\t\t\tblobContentType: file.type,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\treturn fileId;\n\t}\n\n\tpublic async download(bucketName: string, fileId: string): Promise<FileLike> {\n\t\tconst block = this.getBlock(bucketName, fileId);\n\n\t\tconst blob = await block.download().catch((error) => {\n\t\t\tif (error instanceof Error) {\n\t\t\t\tthrow new FileNotFoundError(\"Error downloading file\", { cause: error });\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t});\n\n\t\tif (!blob.readableStreamBody) {\n\t\t\tthrow new FileNotFoundError(\"File not found - empty stream body\");\n\t\t}\n\n\t\treturn createFile(blob.readableStreamBody, {\n\t\t\t...blob.metadata,\n\t\t\tsize: blob.contentLength,\n\t\t});\n\t}\n\n\tpublic async exists(bucketName: string, fileId: string): Promise<boolean> {\n\t\treturn await this.getBlock(bucketName, fileId).exists();\n\t}\n\n\tpublic async delete(bucketName: string, fileId: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.getBlock(bucketName, fileId).delete();\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error) {\n\t\t\t\tthrow new FileNotFoundError(\"Error deleting file\", { cause: error });\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tpublic getBlock(container: string, fileId: string): BlockBlobClient {\n\t\tif (!this.containers[container]) {\n\t\t\tthrow new FileNotFoundError(\n\t\t\t\t`File '${fileId}' not found - container '${container}' does not exists`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.containers[container].getBlockBlobClient(fileId);\n\t}\n\n\tprotected async createContainerClient(\n\t\tname: string,\n\t): Promise<ContainerClient> {\n\t\tconst container = this.blobServiceClient.getContainerClient(name);\n\n\t\tawait this.time.deadline(\n\t\t\t(abortSignal) => container.createIfNotExists({ abortSignal }),\n\t\t\t[5, \"seconds\"],\n\t\t);\n\n\t\treturn container;\n\t}\n\n\tprotected createId(): string {\n\t\treturn randomUUID();\n\t}\n}\n","import { AlephaBucket, FileStorageProvider } from \"@alepha/bucket\";\nimport { $module } from \"@alepha/core\";\nimport { AzureFileStorageProvider } from \"./providers/AzureFileStorageProvider.ts\";\n\nexport * from \"./providers/AzureFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.\n *\n * @see {@link AzureFileStorageProvider}\n * @module alepha.bucket.azure\n */\nexport const AlephaBucketAzure = $module({\n\tname: \"alepha.bucket.azure\",\n\tservices: [AzureFileStorageProvider],\n\tregister: (alepha) =>\n\t\talepha\n\t\t\t.with({\n\t\t\t\toptional: true,\n\t\t\t\tprovide: FileStorageProvider,\n\t\t\t\tuse: AzureFileStorageProvider,\n\t\t\t})\n\t\t\t.with(AlephaBucket),\n});\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,50 +1,52 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as _alepha_core0$1 from "@alepha/core";
|
|
2
|
+
import * as _alepha_core0 from "@alepha/core";
|
|
3
|
+
import { Alepha, FileLike, Static } from "@alepha/core";
|
|
4
|
+
import { FileStorageProvider } from "@alepha/bucket";
|
|
3
5
|
import { DateTimeProvider } from "@alepha/datetime";
|
|
4
6
|
import { BlobServiceClient, BlockBlobClient, ContainerClient, StoragePipelineOptions } from "@azure/storage-blob";
|
|
5
7
|
|
|
6
8
|
//#region src/providers/AzureFileStorageProvider.d.ts
|
|
7
|
-
declare const envSchema: TObject<{
|
|
8
|
-
AZ_STORAGE_CONNECTION_STRING: TString;
|
|
9
|
+
declare const envSchema: _alepha_core0$1.TObject<{
|
|
10
|
+
AZ_STORAGE_CONNECTION_STRING: _alepha_core0$1.TString;
|
|
9
11
|
}>;
|
|
10
12
|
declare module "@alepha/core" {
|
|
11
13
|
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
|
-
* Azure Blog Storage implementation of File Storage Provider.
|
|
15
|
-
*/
|
|
16
|
+
* Azure Blog Storage implementation of File Storage Provider.
|
|
17
|
+
*/
|
|
16
18
|
declare class AzureFileStorageProvider implements FileStorageProvider {
|
|
17
|
-
protected readonly log: Logger;
|
|
18
|
-
protected readonly env:
|
|
19
|
-
|
|
19
|
+
protected readonly log: _alepha_core0$1.Logger;
|
|
20
|
+
protected readonly env: {
|
|
21
|
+
AZ_STORAGE_CONNECTION_STRING: string;
|
|
22
|
+
};
|
|
23
|
+
protected readonly alepha: Alepha;
|
|
20
24
|
protected readonly time: DateTimeProvider;
|
|
21
25
|
protected readonly containers: Record<string, ContainerClient>;
|
|
22
26
|
protected readonly blobServiceClient: BlobServiceClient;
|
|
23
27
|
readonly options: StoragePipelineOptions;
|
|
24
28
|
constructor();
|
|
29
|
+
protected readonly onStart: _alepha_core0$1.HookDescriptor<"start">;
|
|
25
30
|
createContainer(containerName: string): Promise<ContainerClient>;
|
|
26
31
|
upload(bucketName: string, file: FileLike, fileId?: string): Promise<string>;
|
|
27
32
|
download(bucketName: string, fileId: string): Promise<FileLike>;
|
|
28
33
|
exists(bucketName: string, fileId: string): Promise<boolean>;
|
|
29
34
|
delete(bucketName: string, fileId: string): Promise<void>;
|
|
30
35
|
getBlock(container: string, fileId: string): BlockBlobClient;
|
|
31
|
-
readonly onStart: HookDescriptor<"start">;
|
|
32
36
|
protected createContainerClient(name: string): Promise<ContainerClient>;
|
|
33
37
|
protected createId(): string;
|
|
34
38
|
}
|
|
35
39
|
//#endregion
|
|
36
40
|
//#region src/index.d.ts
|
|
37
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
38
41
|
/**
|
|
39
|
-
* Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.
|
|
40
|
-
*
|
|
41
|
-
* @see {@link AzureFileStorageProvider}
|
|
42
|
-
* @module alepha.bucket.azure
|
|
43
|
-
*/
|
|
44
|
-
declare
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
42
|
+
* Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.
|
|
43
|
+
*
|
|
44
|
+
* @see {@link AzureFileStorageProvider}
|
|
45
|
+
* @module alepha.bucket.azure
|
|
46
|
+
*/
|
|
47
|
+
declare const AlephaBucketAzure: _alepha_core0.ModuleDescriptor;
|
|
48
|
+
//# sourceMappingURL=index.d.ts.map
|
|
49
|
+
|
|
48
50
|
//#endregion
|
|
49
51
|
export { AlephaBucketAzure, AzureFileStorageProvider };
|
|
50
52
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/providers/AzureFileStorageProvider.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;cA0BM,2BAAS;gCAIb,eAAA,CAAA;;;EAJI,UAAA,GAIJ,SAGqB,OAHrB,CAG6B,MAH7B,CAAA,OAG2C,SAH3C,CAAA,CAAA,CAAA,CAAA;;;AAJa;AAAA;AAAA,cAaF,wBAAA,YAAoC,mBAblC,CAAA;EAAA,mBAO8B,GAAA,EAMP,eAAA,CACf,MAPsB;EAAS,mBAAvB,GAAA,EAAA;IAAR,4BAAA,EAAA,MAAA;EAAO,CAAA;EAAA,mBAAA,MAAA,EASJ,MATI;EAMjB,mBAAA,IAAA,EAIW,gBAJc;EAAA,mBAAA,UAAA,EAKN,MALM,CAAA,MAAA,EAKS,eALT,CAAA;EAAA,mBACf,iBAAA,EAKgB,iBALhB;EAAA,SAEG,OAAA,EAKA,sBALA;EAAA,WACF,CAAA;EAAA,mBACuB,OAAA,EAGC,eAAA,CASrB,cAZoB,CAAA,OAAA,CAAA;EAAe,eAA9B,CAAA,aAAA,EAAA,MAAA,CAAA,EAmC5B,OAnC4B,CAmCpB,eAnCoB,CAAA;EAAM,MACC,CAAA,UAAA,EAAA,MAAA,EAAA,IAAA,EA6C/B,QA7C+B,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EA+CnC,OA/CmC,CAAA,MAAA,CAAA;EAAiB,QAE9B,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAqFkC,OArFlC,CAqF0C,QArF1C,CAAA;EAAsB,MAAA,CAAA,UAAA,EASrB,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAiG+B,OAjG/B,CAAA,OAAA,CAAA;EAAA,MAuBf,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EA8E8C,OA9E9C,CAAA,IAAA,CAAA;EAAe,QAAvB,CAAA,SAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAyFiD,eAzFjD;EAAO,UAWH,qBAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EA0FJ,OA1FI,CA0FI,eA1FJ,CAAA;EAAQ,UAEZ,QAAA,CAAA,CAAA,EAAA,MAAA;;;;;;;;;;cC9ES,mBAWX,aAAA,CAX4B;ADUD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,50 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { FileStorageProvider } from "@alepha/bucket";
|
|
2
|
+
import * as _alepha_core0$1 from "@alepha/core";
|
|
3
|
+
import * as _alepha_core0 from "@alepha/core";
|
|
4
|
+
import { Alepha, FileLike, Static } from "@alepha/core";
|
|
3
5
|
import { DateTimeProvider } from "@alepha/datetime";
|
|
4
6
|
import { BlobServiceClient, BlockBlobClient, ContainerClient, StoragePipelineOptions } from "@azure/storage-blob";
|
|
5
7
|
|
|
6
8
|
//#region src/providers/AzureFileStorageProvider.d.ts
|
|
7
|
-
declare const envSchema: TObject<{
|
|
8
|
-
AZ_STORAGE_CONNECTION_STRING: TString;
|
|
9
|
+
declare const envSchema: _alepha_core0$1.TObject<{
|
|
10
|
+
AZ_STORAGE_CONNECTION_STRING: _alepha_core0$1.TString;
|
|
9
11
|
}>;
|
|
10
12
|
declare module "@alepha/core" {
|
|
11
13
|
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
|
-
* Azure Blog Storage implementation of File Storage Provider.
|
|
15
|
-
*/
|
|
16
|
+
* Azure Blog Storage implementation of File Storage Provider.
|
|
17
|
+
*/
|
|
16
18
|
declare class AzureFileStorageProvider implements FileStorageProvider {
|
|
17
|
-
protected readonly log: Logger;
|
|
18
|
-
protected readonly env:
|
|
19
|
-
|
|
19
|
+
protected readonly log: _alepha_core0$1.Logger;
|
|
20
|
+
protected readonly env: {
|
|
21
|
+
AZ_STORAGE_CONNECTION_STRING: string;
|
|
22
|
+
};
|
|
23
|
+
protected readonly alepha: Alepha;
|
|
20
24
|
protected readonly time: DateTimeProvider;
|
|
21
25
|
protected readonly containers: Record<string, ContainerClient>;
|
|
22
26
|
protected readonly blobServiceClient: BlobServiceClient;
|
|
23
27
|
readonly options: StoragePipelineOptions;
|
|
24
28
|
constructor();
|
|
29
|
+
protected readonly onStart: _alepha_core0$1.HookDescriptor<"start">;
|
|
25
30
|
createContainer(containerName: string): Promise<ContainerClient>;
|
|
26
31
|
upload(bucketName: string, file: FileLike, fileId?: string): Promise<string>;
|
|
27
32
|
download(bucketName: string, fileId: string): Promise<FileLike>;
|
|
28
33
|
exists(bucketName: string, fileId: string): Promise<boolean>;
|
|
29
34
|
delete(bucketName: string, fileId: string): Promise<void>;
|
|
30
35
|
getBlock(container: string, fileId: string): BlockBlobClient;
|
|
31
|
-
readonly onStart: HookDescriptor<"start">;
|
|
32
36
|
protected createContainerClient(name: string): Promise<ContainerClient>;
|
|
33
37
|
protected createId(): string;
|
|
34
38
|
}
|
|
35
39
|
//#endregion
|
|
36
40
|
//#region src/index.d.ts
|
|
37
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
38
41
|
/**
|
|
39
|
-
* Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.
|
|
40
|
-
*
|
|
41
|
-
* @see {@link AzureFileStorageProvider}
|
|
42
|
-
* @module alepha.bucket.azure
|
|
43
|
-
*/
|
|
44
|
-
declare
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
42
|
+
* Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.
|
|
43
|
+
*
|
|
44
|
+
* @see {@link AzureFileStorageProvider}
|
|
45
|
+
* @module alepha.bucket.azure
|
|
46
|
+
*/
|
|
47
|
+
declare const AlephaBucketAzure: _alepha_core0.ModuleDescriptor;
|
|
48
|
+
//# sourceMappingURL=index.d.ts.map
|
|
49
|
+
|
|
48
50
|
//#endregion
|
|
49
51
|
export { AlephaBucketAzure, AzureFileStorageProvider };
|
|
50
52
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/providers/AzureFileStorageProvider.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;cA0BM,2BAAS;gCAIb,eAAA,CAAA;;;EAJI,UAAA,GAIJ,SAGqB,OAHrB,CAG6B,MAH7B,CAAA,OAG2C,SAH3C,CAAA,CAAA,CAAA,CAAA;;;AAJa;AAAA;AAAA,cAaF,wBAAA,YAAoC,mBAblC,CAAA;EAAA,mBAO8B,GAAA,EAMP,eAAA,CACf,MAPsB;EAAS,mBAAvB,GAAA,EAAA;IAAR,4BAAA,EAAA,MAAA;EAAO,CAAA;EAAA,mBAAA,MAAA,EASJ,MATI;EAMjB,mBAAA,IAAA,EAIW,gBAJc;EAAA,mBAAA,UAAA,EAKN,MALM,CAAA,MAAA,EAKS,eALT,CAAA;EAAA,mBACf,iBAAA,EAKgB,iBALhB;EAAA,SAEG,OAAA,EAKA,sBALA;EAAA,WACF,CAAA;EAAA,mBACuB,OAAA,EAGC,eAAA,CASrB,cAZoB,CAAA,OAAA,CAAA;EAAe,eAA9B,CAAA,aAAA,EAAA,MAAA,CAAA,EAmC5B,OAnC4B,CAmCpB,eAnCoB,CAAA;EAAM,MACC,CAAA,UAAA,EAAA,MAAA,EAAA,IAAA,EA6C/B,QA7C+B,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EA+CnC,OA/CmC,CAAA,MAAA,CAAA;EAAiB,QAE9B,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAqFkC,OArFlC,CAqF0C,QArF1C,CAAA;EAAsB,MAAA,CAAA,UAAA,EASrB,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAiG+B,OAjG/B,CAAA,OAAA,CAAA;EAAA,MAuBf,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EA8E8C,OA9E9C,CAAA,IAAA,CAAA;EAAe,QAAvB,CAAA,SAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAyFiD,eAzFjD;EAAO,UAWH,qBAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EA0FJ,OA1FI,CA0FI,eA1FJ,CAAA;EAAQ,UAEZ,QAAA,CAAA,CAAA,EAAA,MAAA;;;;;;;;;;cC9ES,mBAWX,aAAA,CAX4B;ADUD"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $bucket, AlephaBucket, FileNotFoundError, FileStorageProvider } from "@alepha/bucket";
|
|
2
|
+
import { $env, $hook, $inject, $logger, $module, Alepha, t } from "@alepha/core";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
4
|
+
import { Readable } from "node:stream";
|
|
4
5
|
import { DateTimeProvider } from "@alepha/datetime";
|
|
5
6
|
import { createFile } from "@alepha/file";
|
|
6
7
|
import { BlobServiceClient } from "@azure/storage-blob";
|
|
@@ -12,8 +13,8 @@ const envSchema = t.object({ AZ_STORAGE_CONNECTION_STRING: t.string({ size: "lon
|
|
|
12
13
|
*/
|
|
13
14
|
var AzureFileStorageProvider = class {
|
|
14
15
|
log = $logger();
|
|
15
|
-
env = $
|
|
16
|
-
|
|
16
|
+
env = $env(envSchema);
|
|
17
|
+
alepha = $inject(Alepha);
|
|
17
18
|
time = $inject(DateTimeProvider);
|
|
18
19
|
containers = {};
|
|
19
20
|
blobServiceClient;
|
|
@@ -21,6 +22,18 @@ var AzureFileStorageProvider = class {
|
|
|
21
22
|
constructor() {
|
|
22
23
|
this.blobServiceClient = BlobServiceClient.fromConnectionString(this.env.AZ_STORAGE_CONNECTION_STRING, this.options);
|
|
23
24
|
}
|
|
25
|
+
onStart = $hook({
|
|
26
|
+
on: "start",
|
|
27
|
+
handler: async () => {
|
|
28
|
+
for (const bucket of this.alepha.descriptors($bucket)) {
|
|
29
|
+
if (bucket.provider !== this) continue;
|
|
30
|
+
const containerName = bucket.name.replaceAll("/", "-").toLowerCase();
|
|
31
|
+
this.log.debug(`Prepare container ${containerName}...`);
|
|
32
|
+
if (!this.containers[containerName]) this.containers[containerName] = await this.createContainerClient(containerName);
|
|
33
|
+
this.log.info(`Container ${bucket} OK`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
24
37
|
async createContainer(containerName) {
|
|
25
38
|
if (this.containers[containerName]) return this.containers[containerName];
|
|
26
39
|
const container = await this.createContainerClient(containerName);
|
|
@@ -42,7 +55,10 @@ var AzureFileStorageProvider = class {
|
|
|
42
55
|
metadata,
|
|
43
56
|
blobHTTPHeaders: { blobContentType: file.type }
|
|
44
57
|
});
|
|
45
|
-
else
|
|
58
|
+
else await block.uploadStream(Readable.from(file.stream()), file.size || void 0, 5, {
|
|
59
|
+
metadata,
|
|
60
|
+
blobHTTPHeaders: { blobContentType: file.type }
|
|
61
|
+
});
|
|
46
62
|
return fileId;
|
|
47
63
|
}
|
|
48
64
|
async download(bucketName, fileId) {
|
|
@@ -52,7 +68,10 @@ var AzureFileStorageProvider = class {
|
|
|
52
68
|
throw error;
|
|
53
69
|
});
|
|
54
70
|
if (!blob.readableStreamBody) throw new FileNotFoundError("File not found - empty stream body");
|
|
55
|
-
return createFile(blob.readableStreamBody,
|
|
71
|
+
return createFile(blob.readableStreamBody, {
|
|
72
|
+
...blob.metadata,
|
|
73
|
+
size: blob.contentLength
|
|
74
|
+
});
|
|
56
75
|
}
|
|
57
76
|
async exists(bucketName, fileId) {
|
|
58
77
|
return await this.getBlock(bucketName, fileId).exists();
|
|
@@ -69,17 +88,6 @@ var AzureFileStorageProvider = class {
|
|
|
69
88
|
if (!this.containers[container]) throw new FileNotFoundError(`File '${fileId}' not found - container '${container}' does not exists`);
|
|
70
89
|
return this.containers[container].getBlockBlobClient(fileId);
|
|
71
90
|
}
|
|
72
|
-
onStart = $hook({
|
|
73
|
-
on: "start",
|
|
74
|
-
handler: async () => {
|
|
75
|
-
for (const bucket of this.bucket.getBuckets()) {
|
|
76
|
-
const containerName = bucket.name.replaceAll("/", "-").toLowerCase();
|
|
77
|
-
this.log.debug(`Prepare container ${containerName}...`);
|
|
78
|
-
if (!this.containers[containerName]) this.containers[containerName] = await this.createContainerClient(containerName);
|
|
79
|
-
this.log.info(`Container ${bucket} OK`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
91
|
async createContainerClient(name) {
|
|
84
92
|
const container = this.blobServiceClient.getContainerClient(name);
|
|
85
93
|
await this.time.deadline((abortSignal) => container.createIfNotExists({ abortSignal }), [5, "seconds"]);
|
|
@@ -98,16 +106,15 @@ var AzureFileStorageProvider = class {
|
|
|
98
106
|
* @see {@link AzureFileStorageProvider}
|
|
99
107
|
* @module alepha.bucket.azure
|
|
100
108
|
*/
|
|
101
|
-
|
|
102
|
-
name
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
};
|
|
109
|
+
const AlephaBucketAzure = $module({
|
|
110
|
+
name: "alepha.bucket.azure",
|
|
111
|
+
services: [AzureFileStorageProvider],
|
|
112
|
+
register: (alepha) => alepha.with({
|
|
113
|
+
optional: true,
|
|
114
|
+
provide: FileStorageProvider,
|
|
115
|
+
use: AzureFileStorageProvider
|
|
116
|
+
}).with(AlephaBucket)
|
|
117
|
+
});
|
|
111
118
|
|
|
112
119
|
//#endregion
|
|
113
120
|
export { AlephaBucketAzure, AzureFileStorageProvider };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["
|
|
1
|
+
{"version":3,"file":"index.js","names":["containerName: string","bucketName: string","file: FileLike","fileId?: string","fileId: string","container: string","name: string"],"sources":["../src/providers/AzureFileStorageProvider.ts","../src/index.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { Readable } from \"node:stream\";\nimport {\n\t$bucket,\n\tFileNotFoundError,\n\ttype FileStorageProvider,\n} from \"@alepha/bucket\";\nimport {\n\t$env,\n\t$hook,\n\t$inject,\n\t$logger,\n\tAlepha,\n\ttype FileLike,\n\ttype Static,\n\tt,\n} from \"@alepha/core\";\nimport { DateTimeProvider } from \"@alepha/datetime\";\nimport { createFile } from \"@alepha/file\";\nimport {\n\tBlobServiceClient,\n\ttype BlockBlobClient,\n\ttype ContainerClient,\n\ttype StoragePipelineOptions,\n} from \"@azure/storage-blob\";\n\nconst envSchema = t.object({\n\tAZ_STORAGE_CONNECTION_STRING: t.string({\n\t\tsize: \"long\",\n\t}),\n});\n\ndeclare module \"@alepha/core\" {\n\tinterface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n/**\n * Azure Blog Storage implementation of File Storage Provider.\n */\nexport class AzureFileStorageProvider implements FileStorageProvider {\n\tprotected readonly log = $logger();\n\tprotected readonly env = $env(envSchema);\n\tprotected readonly alepha = $inject(Alepha);\n\tprotected readonly time = $inject(DateTimeProvider);\n\tprotected readonly containers: Record<string, ContainerClient> = {};\n\tprotected readonly blobServiceClient: BlobServiceClient;\n\n\tpublic readonly options: StoragePipelineOptions = {};\n\n\tconstructor() {\n\t\tthis.blobServiceClient = BlobServiceClient.fromConnectionString(\n\t\t\tthis.env.AZ_STORAGE_CONNECTION_STRING,\n\t\t\tthis.options,\n\t\t);\n\t}\n\n\tprotected readonly onStart = $hook({\n\t\ton: \"start\",\n\t\thandler: async () => {\n\t\t\tfor (const bucket of this.alepha.descriptors($bucket)) {\n\t\t\t\tif (bucket.provider !== this) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst containerName = bucket.name.replaceAll(\"/\", \"-\").toLowerCase();\n\t\t\t\tthis.log.debug(`Prepare container ${containerName}...`);\n\n\t\t\t\tif (!this.containers[containerName]) {\n\t\t\t\t\tthis.containers[containerName] =\n\t\t\t\t\t\tawait this.createContainerClient(containerName);\n\t\t\t\t}\n\n\t\t\t\tthis.log.info(`Container ${bucket} OK`);\n\t\t\t}\n\t\t},\n\t});\n\n\tpublic async createContainer(\n\t\tcontainerName: string,\n\t): Promise<ContainerClient> {\n\t\tif (this.containers[containerName]) {\n\t\t\treturn this.containers[containerName];\n\t\t}\n\t\tconst container = await this.createContainerClient(containerName);\n\t\tthis.containers[containerName] = container;\n\t\treturn container;\n\t}\n\n\tpublic async upload(\n\t\tbucketName: string,\n\t\tfile: FileLike,\n\t\tfileId?: string,\n\t): Promise<string> {\n\t\tfileId ??= this.createId();\n\t\tconst block = this.getBlock(bucketName, fileId);\n\n\t\tconst metadata = {\n\t\t\tname: file.name,\n\t\t\ttype: file.type,\n\t\t};\n\n\t\tif (file.filepath) {\n\t\t\tawait block.uploadFile(file.filepath, {\n\t\t\t\tmetadata,\n\t\t\t\tblobHTTPHeaders: {\n\t\t\t\t\tblobContentType: file.type,\n\t\t\t\t},\n\t\t\t});\n\t\t} else if (file.size > 0) {\n\t\t\tawait block.uploadData(await file.arrayBuffer(), {\n\t\t\t\tmetadata,\n\t\t\t\tblobHTTPHeaders: {\n\t\t\t\t\tblobContentType: file.type,\n\t\t\t\t},\n\t\t\t});\n\t\t} else {\n\t\t\tawait block.uploadStream(\n\t\t\t\tReadable.from(file.stream()),\n\t\t\t\tfile.size || undefined,\n\t\t\t\t5,\n\t\t\t\t{\n\t\t\t\t\tmetadata,\n\t\t\t\t\tblobHTTPHeaders: {\n\t\t\t\t\t\tblobContentType: file.type,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\treturn fileId;\n\t}\n\n\tpublic async download(bucketName: string, fileId: string): Promise<FileLike> {\n\t\tconst block = this.getBlock(bucketName, fileId);\n\n\t\tconst blob = await block.download().catch((error) => {\n\t\t\tif (error instanceof Error) {\n\t\t\t\tthrow new FileNotFoundError(\"Error downloading file\", { cause: error });\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t});\n\n\t\tif (!blob.readableStreamBody) {\n\t\t\tthrow new FileNotFoundError(\"File not found - empty stream body\");\n\t\t}\n\n\t\treturn createFile(blob.readableStreamBody, {\n\t\t\t...blob.metadata,\n\t\t\tsize: blob.contentLength,\n\t\t});\n\t}\n\n\tpublic async exists(bucketName: string, fileId: string): Promise<boolean> {\n\t\treturn await this.getBlock(bucketName, fileId).exists();\n\t}\n\n\tpublic async delete(bucketName: string, fileId: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.getBlock(bucketName, fileId).delete();\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error) {\n\t\t\t\tthrow new FileNotFoundError(\"Error deleting file\", { cause: error });\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tpublic getBlock(container: string, fileId: string): BlockBlobClient {\n\t\tif (!this.containers[container]) {\n\t\t\tthrow new FileNotFoundError(\n\t\t\t\t`File '${fileId}' not found - container '${container}' does not exists`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.containers[container].getBlockBlobClient(fileId);\n\t}\n\n\tprotected async createContainerClient(\n\t\tname: string,\n\t): Promise<ContainerClient> {\n\t\tconst container = this.blobServiceClient.getContainerClient(name);\n\n\t\tawait this.time.deadline(\n\t\t\t(abortSignal) => container.createIfNotExists({ abortSignal }),\n\t\t\t[5, \"seconds\"],\n\t\t);\n\n\t\treturn container;\n\t}\n\n\tprotected createId(): string {\n\t\treturn randomUUID();\n\t}\n}\n","import { AlephaBucket, FileStorageProvider } from \"@alepha/bucket\";\nimport { $module } from \"@alepha/core\";\nimport { AzureFileStorageProvider } from \"./providers/AzureFileStorageProvider.ts\";\n\nexport * from \"./providers/AzureFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.\n *\n * @see {@link AzureFileStorageProvider}\n * @module alepha.bucket.azure\n */\nexport const AlephaBucketAzure = $module({\n\tname: \"alepha.bucket.azure\",\n\tservices: [AzureFileStorageProvider],\n\tregister: (alepha) =>\n\t\talepha\n\t\t\t.with({\n\t\t\t\toptional: true,\n\t\t\t\tprovide: FileStorageProvider,\n\t\t\t\tuse: AzureFileStorageProvider,\n\t\t\t})\n\t\t\t.with(AlephaBucket),\n});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"storage-blob"
|
|
11
11
|
],
|
|
12
12
|
"author": "Feunard",
|
|
13
|
-
"version": "0.
|
|
13
|
+
"version": "0.9.0",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=22.0.0"
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
"src"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@alepha/bucket": "0.
|
|
27
|
-
"@alepha/core": "0.
|
|
28
|
-
"@alepha/datetime": "0.
|
|
29
|
-
"@alepha/file": "0.
|
|
30
|
-
"@azure/storage-blob": "
|
|
26
|
+
"@alepha/bucket": "0.9.0",
|
|
27
|
+
"@alepha/core": "0.9.0",
|
|
28
|
+
"@alepha/datetime": "0.9.0",
|
|
29
|
+
"@alepha/file": "0.9.0",
|
|
30
|
+
"@azure/storage-blob": "12.27.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"tsdown": "^0.
|
|
33
|
+
"tsdown": "^0.13.0",
|
|
34
34
|
"vitest": "^3.2.4"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AlephaBucket, FileStorageProvider } from "@alepha/bucket";
|
|
2
|
-
import
|
|
2
|
+
import { $module } from "@alepha/core";
|
|
3
3
|
import { AzureFileStorageProvider } from "./providers/AzureFileStorageProvider.ts";
|
|
4
4
|
|
|
5
5
|
export * from "./providers/AzureFileStorageProvider.ts";
|
|
@@ -12,15 +12,15 @@ export * from "./providers/AzureFileStorageProvider.ts";
|
|
|
12
12
|
* @see {@link AzureFileStorageProvider}
|
|
13
13
|
* @module alepha.bucket.azure
|
|
14
14
|
*/
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
export const AlephaBucketAzure = $module({
|
|
16
|
+
name: "alepha.bucket.azure",
|
|
17
|
+
services: [AzureFileStorageProvider],
|
|
18
|
+
register: (alepha) =>
|
|
18
19
|
alepha
|
|
19
20
|
.with({
|
|
21
|
+
optional: true,
|
|
20
22
|
provide: FileStorageProvider,
|
|
21
23
|
use: AzureFileStorageProvider,
|
|
22
|
-
optional: true,
|
|
23
24
|
})
|
|
24
|
-
.with(AlephaBucket)
|
|
25
|
-
|
|
26
|
-
}
|
|
25
|
+
.with(AlephaBucket),
|
|
26
|
+
});
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
+
$bucket,
|
|
4
5
|
FileNotFoundError,
|
|
5
6
|
type FileStorageProvider,
|
|
6
7
|
} from "@alepha/bucket";
|
|
7
8
|
import {
|
|
9
|
+
$env,
|
|
8
10
|
$hook,
|
|
9
11
|
$inject,
|
|
10
12
|
$logger,
|
|
13
|
+
Alepha,
|
|
11
14
|
type FileLike,
|
|
12
|
-
type HookDescriptor,
|
|
13
|
-
type Logger,
|
|
14
15
|
type Static,
|
|
15
|
-
type TObject,
|
|
16
|
-
type TString,
|
|
17
16
|
t,
|
|
18
17
|
} from "@alepha/core";
|
|
19
18
|
import { DateTimeProvider } from "@alepha/datetime";
|
|
@@ -25,9 +24,7 @@ import {
|
|
|
25
24
|
type StoragePipelineOptions,
|
|
26
25
|
} from "@azure/storage-blob";
|
|
27
26
|
|
|
28
|
-
const envSchema
|
|
29
|
-
AZ_STORAGE_CONNECTION_STRING: TString;
|
|
30
|
-
}> = t.object({
|
|
27
|
+
const envSchema = t.object({
|
|
31
28
|
AZ_STORAGE_CONNECTION_STRING: t.string({
|
|
32
29
|
size: "long",
|
|
33
30
|
}),
|
|
@@ -41,12 +38,10 @@ declare module "@alepha/core" {
|
|
|
41
38
|
* Azure Blog Storage implementation of File Storage Provider.
|
|
42
39
|
*/
|
|
43
40
|
export class AzureFileStorageProvider implements FileStorageProvider {
|
|
44
|
-
protected readonly log
|
|
45
|
-
protected readonly env
|
|
46
|
-
protected readonly
|
|
47
|
-
|
|
48
|
-
);
|
|
49
|
-
protected readonly time: DateTimeProvider = $inject(DateTimeProvider);
|
|
41
|
+
protected readonly log = $logger();
|
|
42
|
+
protected readonly env = $env(envSchema);
|
|
43
|
+
protected readonly alepha = $inject(Alepha);
|
|
44
|
+
protected readonly time = $inject(DateTimeProvider);
|
|
50
45
|
protected readonly containers: Record<string, ContainerClient> = {};
|
|
51
46
|
protected readonly blobServiceClient: BlobServiceClient;
|
|
52
47
|
|
|
@@ -59,6 +54,27 @@ export class AzureFileStorageProvider implements FileStorageProvider {
|
|
|
59
54
|
);
|
|
60
55
|
}
|
|
61
56
|
|
|
57
|
+
protected readonly onStart = $hook({
|
|
58
|
+
on: "start",
|
|
59
|
+
handler: async () => {
|
|
60
|
+
for (const bucket of this.alepha.descriptors($bucket)) {
|
|
61
|
+
if (bucket.provider !== this) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const containerName = bucket.name.replaceAll("/", "-").toLowerCase();
|
|
66
|
+
this.log.debug(`Prepare container ${containerName}...`);
|
|
67
|
+
|
|
68
|
+
if (!this.containers[containerName]) {
|
|
69
|
+
this.containers[containerName] =
|
|
70
|
+
await this.createContainerClient(containerName);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.log.info(`Container ${bucket} OK`);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
62
78
|
public async createContainer(
|
|
63
79
|
containerName: string,
|
|
64
80
|
): Promise<ContainerClient> {
|
|
@@ -98,7 +114,17 @@ export class AzureFileStorageProvider implements FileStorageProvider {
|
|
|
98
114
|
},
|
|
99
115
|
});
|
|
100
116
|
} else {
|
|
101
|
-
|
|
117
|
+
await block.uploadStream(
|
|
118
|
+
Readable.from(file.stream()),
|
|
119
|
+
file.size || undefined,
|
|
120
|
+
5,
|
|
121
|
+
{
|
|
122
|
+
metadata,
|
|
123
|
+
blobHTTPHeaders: {
|
|
124
|
+
blobContentType: file.type,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
);
|
|
102
128
|
}
|
|
103
129
|
|
|
104
130
|
return fileId;
|
|
@@ -119,7 +145,10 @@ export class AzureFileStorageProvider implements FileStorageProvider {
|
|
|
119
145
|
throw new FileNotFoundError("File not found - empty stream body");
|
|
120
146
|
}
|
|
121
147
|
|
|
122
|
-
return createFile(blob.readableStreamBody,
|
|
148
|
+
return createFile(blob.readableStreamBody, {
|
|
149
|
+
...blob.metadata,
|
|
150
|
+
size: blob.contentLength,
|
|
151
|
+
});
|
|
123
152
|
}
|
|
124
153
|
|
|
125
154
|
public async exists(bucketName: string, fileId: string): Promise<boolean> {
|
|
@@ -147,23 +176,6 @@ export class AzureFileStorageProvider implements FileStorageProvider {
|
|
|
147
176
|
return this.containers[container].getBlockBlobClient(fileId);
|
|
148
177
|
}
|
|
149
178
|
|
|
150
|
-
public readonly onStart: HookDescriptor<"start"> = $hook({
|
|
151
|
-
on: "start",
|
|
152
|
-
handler: async () => {
|
|
153
|
-
for (const bucket of this.bucket.getBuckets()) {
|
|
154
|
-
const containerName = bucket.name.replaceAll("/", "-").toLowerCase();
|
|
155
|
-
this.log.debug(`Prepare container ${containerName}...`);
|
|
156
|
-
|
|
157
|
-
if (!this.containers[containerName]) {
|
|
158
|
-
this.containers[containerName] =
|
|
159
|
-
await this.createContainerClient(containerName);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
this.log.info(`Container ${bucket} OK`);
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
|
|
167
179
|
protected async createContainerClient(
|
|
168
180
|
name: string,
|
|
169
181
|
): Promise<ContainerClient> {
|