@alepha/bucket-azure 0.7.5 → 0.7.7
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 +135 -131
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +53 -0
- package/dist/index.d.ts +37 -94
- package/dist/index.js +113 -130
- package/dist/index.js.map +1 -0
- package/package.json +10 -7
package/dist/index.cjs
CHANGED
|
@@ -1,137 +1,141 @@
|
|
|
1
|
-
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
2
22
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
23
|
+
//#endregion
|
|
24
|
+
const __alepha_bucket = __toESM(require("@alepha/bucket"));
|
|
25
|
+
const node_crypto = __toESM(require("node:crypto"));
|
|
26
|
+
const __alepha_core = __toESM(require("@alepha/core"));
|
|
27
|
+
const __alepha_datetime = __toESM(require("@alepha/datetime"));
|
|
28
|
+
const __alepha_file = __toESM(require("@alepha/file"));
|
|
29
|
+
const __azure_storage_blob = __toESM(require("@azure/storage-blob"));
|
|
9
30
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
return this.containers[container].getBlockBlobClient(fileId);
|
|
100
|
-
}
|
|
101
|
-
onStart = core.$hook({
|
|
102
|
-
name: "start",
|
|
103
|
-
handler: async () => {
|
|
104
|
-
for (const bucket of this.bucketProvider.getBuckets()) {
|
|
105
|
-
const containerName = bucket.name.replaceAll("/", "-").toLowerCase();
|
|
106
|
-
this.log.debug(`Prepare container ${containerName}...`);
|
|
107
|
-
if (!this.containers[containerName]) {
|
|
108
|
-
this.containers[containerName] = await this.createContainerClient(containerName);
|
|
109
|
-
}
|
|
110
|
-
this.log.info(`Container ${bucket} OK`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
async createContainerClient(name) {
|
|
115
|
-
const container = this.blobServiceClient.getContainerClient(name);
|
|
116
|
-
await this.dateTimeProvider.deadline(
|
|
117
|
-
(abortSignal) => container.createIfNotExists({ abortSignal }),
|
|
118
|
-
[5, "seconds"]
|
|
119
|
-
);
|
|
120
|
-
return container;
|
|
121
|
-
}
|
|
122
|
-
createId() {
|
|
123
|
-
return node_crypto.randomUUID();
|
|
124
|
-
}
|
|
125
|
-
}
|
|
31
|
+
//#region src/providers/AzureFileStorageProvider.ts
|
|
32
|
+
const envSchema = __alepha_core.t.object({ AZ_STORAGE_CONNECTION_STRING: __alepha_core.t.string({
|
|
33
|
+
size: "long",
|
|
34
|
+
default: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
|
|
35
|
+
}) });
|
|
36
|
+
var AzureFileStorageProvider = class {
|
|
37
|
+
log = (0, __alepha_core.$logger)();
|
|
38
|
+
env = (0, __alepha_core.$env)(envSchema);
|
|
39
|
+
bucketProvider = (0, __alepha_core.$inject)(__alepha_bucket.BucketDescriptorProvider);
|
|
40
|
+
dateTimeProvider = (0, __alepha_core.$inject)(__alepha_datetime.DateTimeProvider);
|
|
41
|
+
containers = {};
|
|
42
|
+
blobServiceClient;
|
|
43
|
+
options = {};
|
|
44
|
+
constructor() {
|
|
45
|
+
this.blobServiceClient = __azure_storage_blob.BlobServiceClient.fromConnectionString(this.env.AZ_STORAGE_CONNECTION_STRING, this.storagePipelineOptions());
|
|
46
|
+
}
|
|
47
|
+
storagePipelineOptions() {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
async createContainer(containerName) {
|
|
51
|
+
if (this.containers[containerName]) return this.containers[containerName];
|
|
52
|
+
const container = await this.createContainerClient(containerName);
|
|
53
|
+
this.containers[containerName] = container;
|
|
54
|
+
return container;
|
|
55
|
+
}
|
|
56
|
+
async upload(bucketName, file$1, fileId) {
|
|
57
|
+
fileId ??= this.createId();
|
|
58
|
+
const block = this.getBlock(bucketName, fileId);
|
|
59
|
+
const metadata = {
|
|
60
|
+
name: file$1.name,
|
|
61
|
+
type: file$1.type
|
|
62
|
+
};
|
|
63
|
+
if (file$1.filepath) await block.uploadFile(file$1.filepath, {
|
|
64
|
+
metadata,
|
|
65
|
+
blobHTTPHeaders: { blobContentType: file$1.type }
|
|
66
|
+
});
|
|
67
|
+
else if (file$1.size > 0) await block.uploadData(await file$1.arrayBuffer(), {
|
|
68
|
+
metadata,
|
|
69
|
+
blobHTTPHeaders: { blobContentType: file$1.type }
|
|
70
|
+
});
|
|
71
|
+
else throw new Error("Raw stream upload is not supported yet");
|
|
72
|
+
return fileId;
|
|
73
|
+
}
|
|
74
|
+
async download(bucketName, fileId) {
|
|
75
|
+
const block = this.getBlock(bucketName, fileId);
|
|
76
|
+
const blob = await block.download().catch((error) => {
|
|
77
|
+
if (error instanceof Error) throw new __alepha_bucket.FileNotFoundError("Error downloading file", { cause: error });
|
|
78
|
+
throw error;
|
|
79
|
+
});
|
|
80
|
+
if (!blob.readableStreamBody) throw new __alepha_bucket.FileNotFoundError("File not found - empty stream body");
|
|
81
|
+
return (0, __alepha_file.file)(blob.readableStreamBody, blob.metadata);
|
|
82
|
+
}
|
|
83
|
+
async exists(bucketName, fileId) {
|
|
84
|
+
return await this.getBlock(bucketName, fileId).exists();
|
|
85
|
+
}
|
|
86
|
+
async delete(bucketName, fileId) {
|
|
87
|
+
try {
|
|
88
|
+
await this.getBlock(bucketName, fileId).delete();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error instanceof Error) throw new __alepha_bucket.FileNotFoundError("Error deleting file", { cause: error });
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
getBlock(container, fileId) {
|
|
95
|
+
if (!this.containers[container]) throw new __alepha_bucket.FileNotFoundError(`File '${fileId}' not found - container '${container}' does not exists`);
|
|
96
|
+
return this.containers[container].getBlockBlobClient(fileId);
|
|
97
|
+
}
|
|
98
|
+
onStart = (0, __alepha_core.$hook)({
|
|
99
|
+
name: "start",
|
|
100
|
+
handler: async () => {
|
|
101
|
+
for (const bucket of this.bucketProvider.getBuckets()) {
|
|
102
|
+
const containerName = bucket.name.replaceAll("/", "-").toLowerCase();
|
|
103
|
+
this.log.debug(`Prepare container ${containerName}...`);
|
|
104
|
+
if (!this.containers[containerName]) this.containers[containerName] = await this.createContainerClient(containerName);
|
|
105
|
+
this.log.info(`Container ${bucket} OK`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
async createContainerClient(name) {
|
|
110
|
+
const container = this.blobServiceClient.getContainerClient(name);
|
|
111
|
+
await this.dateTimeProvider.deadline((abortSignal) => container.createIfNotExists({ abortSignal }), [5, "seconds"]);
|
|
112
|
+
return container;
|
|
113
|
+
}
|
|
114
|
+
createId() {
|
|
115
|
+
return (0, node_crypto.randomUUID)();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
126
118
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/index.ts
|
|
121
|
+
/**
|
|
122
|
+
* Alepha Bucket Azure Module
|
|
123
|
+
*
|
|
124
|
+
* Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.
|
|
125
|
+
*
|
|
126
|
+
* @see {@link AzureFileStorageProvider}
|
|
127
|
+
* @module alepha.bucket.azure
|
|
128
|
+
*/
|
|
129
|
+
var AlephaBucketAzure = class {
|
|
130
|
+
name = "alepha.bucket.azure";
|
|
131
|
+
$services = (alepha) => alepha.with({
|
|
132
|
+
provide: __alepha_bucket.FileStorageProvider,
|
|
133
|
+
use: AzureFileStorageProvider,
|
|
134
|
+
optional: true
|
|
135
|
+
}).with(__alepha_bucket.AlephaBucket);
|
|
136
|
+
};
|
|
135
137
|
|
|
138
|
+
//#endregion
|
|
136
139
|
exports.AlephaBucketAzure = AlephaBucketAzure;
|
|
137
140
|
exports.AzureFileStorageProvider = AzureFileStorageProvider;
|
|
141
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["BucketDescriptorProvider","DateTimeProvider","containerName: string","bucketName: string","file: FileLike","fileId?: string","file","fileId: string","FileNotFoundError","container: string","name: string","alepha: Alepha","FileStorageProvider","AlephaBucket"],"sources":["../src/providers/AzureFileStorageProvider.ts","../src/index.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n\tBucketDescriptorProvider,\n\tFileNotFoundError,\n\ttype FileStorageProvider,\n} from \"@alepha/bucket\";\nimport type { Static } from \"@alepha/core\";\nimport { $env, $hook, $inject, $logger, type FileLike, t } from \"@alepha/core\";\nimport { DateTimeProvider } from \"@alepha/datetime\";\nimport { file } 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\tdefault:\n\t\t\t\"DefaultEndpointsProtocol=http;\" +\n\t\t\t\"AccountName=devstoreaccount1;\" +\n\t\t\t\"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;\" +\n\t\t\t\"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;\",\n\t}),\n});\n\ndeclare module \"@alepha/core\" {\n\tinterface Env extends Partial<Static<typeof envSchema>> {}\n}\n\nexport class AzureFileStorageProvider implements FileStorageProvider {\n\tprotected readonly log = $logger();\n\tprotected readonly env = $env(envSchema);\n\tprotected readonly bucketProvider = $inject(BucketDescriptorProvider);\n\tprotected readonly dateTimeProvider = $inject(DateTimeProvider);\n\tprotected readonly containers: Record<string, ContainerClient> = {};\n\tprotected readonly blobServiceClient: BlobServiceClient;\n\tprotected 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.storagePipelineOptions(),\n\t\t);\n\t}\n\n\tpublic storagePipelineOptions(): StoragePipelineOptions {\n\t\treturn {};\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\tthrow new Error(\"Raw stream upload is not supported yet\");\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 file(blob.readableStreamBody, blob.metadata);\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\tpublic readonly onStart = $hook({\n\t\tname: \"start\",\n\t\thandler: async () => {\n\t\t\tfor (const bucket of this.bucketProvider.getBuckets()) {\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\tprotected async createContainerClient(\n\t\tname: string,\n\t): Promise<ContainerClient> {\n\t\tconst container = this.blobServiceClient.getContainerClient(name);\n\n\t\tawait this.dateTimeProvider.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 type { Alepha, Module } from \"@alepha/core\";\nimport { AzureFileStorageProvider } from \"./providers/AzureFileStorageProvider.ts\";\n\nexport * from \"./providers/AzureFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Alepha Bucket Azure Module\n *\n * Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.\n *\n * @see {@link AzureFileStorageProvider}\n * @module alepha.bucket.azure\n */\nexport class AlephaBucketAzure implements Module {\n\tpublic readonly name = \"alepha.bucket.azure\";\n\tpublic readonly $services = (alepha: Alepha) =>\n\t\talepha\n\t\t\t.with({\n\t\t\t\tprovide: FileStorageProvider,\n\t\t\t\tuse: AzureFileStorageProvider,\n\t\t\t\toptional: true,\n\t\t\t})\n\t\t\t.with(AlephaBucket);\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as _alepha_core1 from "@alepha/core";
|
|
2
|
+
import { Alepha, FileLike, Module, Static } from "@alepha/core";
|
|
3
|
+
import * as _sinclair_typebox0 from "@sinclair/typebox";
|
|
4
|
+
import { BucketDescriptorProvider, FileStorageProvider } from "@alepha/bucket";
|
|
5
|
+
import { DateTimeProvider } from "@alepha/datetime";
|
|
6
|
+
import { BlobServiceClient, BlockBlobClient, ContainerClient, StoragePipelineOptions } from "@azure/storage-blob";
|
|
7
|
+
|
|
8
|
+
//#region src/providers/AzureFileStorageProvider.d.ts
|
|
9
|
+
declare const envSchema: _alepha_core1.TObject<{
|
|
10
|
+
AZ_STORAGE_CONNECTION_STRING: _sinclair_typebox0.TString;
|
|
11
|
+
}>;
|
|
12
|
+
declare module "@alepha/core" {
|
|
13
|
+
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
14
|
+
}
|
|
15
|
+
declare class AzureFileStorageProvider implements FileStorageProvider {
|
|
16
|
+
protected readonly log: _alepha_core1.Logger;
|
|
17
|
+
protected readonly env: {
|
|
18
|
+
AZ_STORAGE_CONNECTION_STRING: string;
|
|
19
|
+
};
|
|
20
|
+
protected readonly bucketProvider: BucketDescriptorProvider;
|
|
21
|
+
protected readonly dateTimeProvider: DateTimeProvider;
|
|
22
|
+
protected readonly containers: Record<string, ContainerClient>;
|
|
23
|
+
protected readonly blobServiceClient: BlobServiceClient;
|
|
24
|
+
protected readonly options: StoragePipelineOptions;
|
|
25
|
+
constructor();
|
|
26
|
+
storagePipelineOptions(): StoragePipelineOptions;
|
|
27
|
+
createContainer(containerName: string): Promise<ContainerClient>;
|
|
28
|
+
upload(bucketName: string, file: FileLike, fileId?: string): Promise<string>;
|
|
29
|
+
download(bucketName: string, fileId: string): Promise<FileLike>;
|
|
30
|
+
exists(bucketName: string, fileId: string): Promise<boolean>;
|
|
31
|
+
delete(bucketName: string, fileId: string): Promise<void>;
|
|
32
|
+
getBlock(container: string, fileId: string): BlockBlobClient;
|
|
33
|
+
readonly onStart: _alepha_core1.HookDescriptor<"start">;
|
|
34
|
+
protected createContainerClient(name: string): Promise<ContainerClient>;
|
|
35
|
+
protected createId(): string;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/index.d.ts
|
|
39
|
+
/**
|
|
40
|
+
* Alepha Bucket Azure Module
|
|
41
|
+
*
|
|
42
|
+
* Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.
|
|
43
|
+
*
|
|
44
|
+
* @see {@link AzureFileStorageProvider}
|
|
45
|
+
* @module alepha.bucket.azure
|
|
46
|
+
*/
|
|
47
|
+
declare class AlephaBucketAzure implements Module {
|
|
48
|
+
readonly name = "alepha.bucket.azure";
|
|
49
|
+
readonly $services: (alepha: Alepha) => Alepha;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { AlephaBucketAzure, AzureFileStorageProvider };
|
|
53
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,99 +1,41 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { DateTimeProvider } from
|
|
5
|
-
import {
|
|
1
|
+
import { BucketDescriptorProvider, FileStorageProvider } from "@alepha/bucket";
|
|
2
|
+
import * as _alepha_core1 from "@alepha/core";
|
|
3
|
+
import { Alepha, FileLike, Module, Static } from "@alepha/core";
|
|
4
|
+
import { DateTimeProvider } from "@alepha/datetime";
|
|
5
|
+
import { BlobServiceClient, BlockBlobClient, ContainerClient, StoragePipelineOptions } from "@azure/storage-blob";
|
|
6
|
+
import * as _sinclair_typebox0 from "@sinclair/typebox";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
declare const
|
|
9
|
-
|
|
10
|
-
declare const OptionalKind: unique symbol;
|
|
11
|
-
/** Symbol key applied to types */
|
|
12
|
-
declare const Hint: unique symbol;
|
|
13
|
-
/** Symbol key applied to types */
|
|
14
|
-
declare const Kind: unique symbol;
|
|
15
|
-
|
|
16
|
-
type StringFormatOption = 'date-time' | 'time' | 'date' | 'email' | 'idn-email' | 'hostname' | 'idn-hostname' | 'ipv4' | 'ipv6' | 'uri' | 'uri-reference' | 'iri' | 'uuid' | 'iri-reference' | 'uri-template' | 'json-pointer' | 'relative-json-pointer' | 'regex' | ({} & string);
|
|
17
|
-
type StringContentEncodingOption = '7bit' | '8bit' | 'binary' | 'quoted-printable' | 'base64' | ({} & string);
|
|
18
|
-
interface StringOptions extends SchemaOptions {
|
|
19
|
-
/** The maximum string length */
|
|
20
|
-
maxLength?: number;
|
|
21
|
-
/** The minimum string length */
|
|
22
|
-
minLength?: number;
|
|
23
|
-
/** A regular expression pattern this string should match */
|
|
24
|
-
pattern?: string;
|
|
25
|
-
/** A format this string should match */
|
|
26
|
-
format?: StringFormatOption;
|
|
27
|
-
/** The content encoding for this string */
|
|
28
|
-
contentEncoding?: StringContentEncodingOption;
|
|
29
|
-
/** The content media type for this string */
|
|
30
|
-
contentMediaType?: string;
|
|
31
|
-
}
|
|
32
|
-
interface TString extends TSchema, StringOptions {
|
|
33
|
-
[Kind]: 'String';
|
|
34
|
-
static: string;
|
|
35
|
-
type: 'string';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface SchemaOptions {
|
|
39
|
-
$schema?: string;
|
|
40
|
-
/** Id for this schema */
|
|
41
|
-
$id?: string;
|
|
42
|
-
/** Title of this schema */
|
|
43
|
-
title?: string;
|
|
44
|
-
/** Description of this schema */
|
|
45
|
-
description?: string;
|
|
46
|
-
/** Default value for this schema */
|
|
47
|
-
default?: any;
|
|
48
|
-
/** Example values matching this schema */
|
|
49
|
-
examples?: any;
|
|
50
|
-
/** Optional annotation for readOnly */
|
|
51
|
-
readOnly?: boolean;
|
|
52
|
-
/** Optional annotation for writeOnly */
|
|
53
|
-
writeOnly?: boolean;
|
|
54
|
-
[prop: string]: any;
|
|
55
|
-
}
|
|
56
|
-
interface TKind {
|
|
57
|
-
[Kind]: string;
|
|
58
|
-
}
|
|
59
|
-
interface TSchema extends TKind, SchemaOptions {
|
|
60
|
-
[ReadonlyKind]?: string;
|
|
61
|
-
[OptionalKind]?: string;
|
|
62
|
-
[Hint]?: string;
|
|
63
|
-
params: unknown[];
|
|
64
|
-
static: unknown;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
declare const envSchema: _alepha_core.TObject<{
|
|
68
|
-
AZ_STORAGE_CONNECTION_STRING: TString;
|
|
8
|
+
//#region src/providers/AzureFileStorageProvider.d.ts
|
|
9
|
+
declare const envSchema: _alepha_core1.TObject<{
|
|
10
|
+
AZ_STORAGE_CONNECTION_STRING: _sinclair_typebox0.TString;
|
|
69
11
|
}>;
|
|
70
12
|
declare module "@alepha/core" {
|
|
71
|
-
|
|
72
|
-
}
|
|
13
|
+
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
73
14
|
}
|
|
74
15
|
declare class AzureFileStorageProvider implements FileStorageProvider {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
16
|
+
protected readonly log: _alepha_core1.Logger;
|
|
17
|
+
protected readonly env: {
|
|
18
|
+
AZ_STORAGE_CONNECTION_STRING: string;
|
|
19
|
+
};
|
|
20
|
+
protected readonly bucketProvider: BucketDescriptorProvider;
|
|
21
|
+
protected readonly dateTimeProvider: DateTimeProvider;
|
|
22
|
+
protected readonly containers: Record<string, ContainerClient>;
|
|
23
|
+
protected readonly blobServiceClient: BlobServiceClient;
|
|
24
|
+
protected readonly options: StoragePipelineOptions;
|
|
25
|
+
constructor();
|
|
26
|
+
storagePipelineOptions(): StoragePipelineOptions;
|
|
27
|
+
createContainer(containerName: string): Promise<ContainerClient>;
|
|
28
|
+
upload(bucketName: string, file: FileLike, fileId?: string): Promise<string>;
|
|
29
|
+
download(bucketName: string, fileId: string): Promise<FileLike>;
|
|
30
|
+
exists(bucketName: string, fileId: string): Promise<boolean>;
|
|
31
|
+
delete(bucketName: string, fileId: string): Promise<void>;
|
|
32
|
+
getBlock(container: string, fileId: string): BlockBlobClient;
|
|
33
|
+
readonly onStart: _alepha_core1.HookDescriptor<"start">;
|
|
34
|
+
protected createContainerClient(name: string): Promise<ContainerClient>;
|
|
35
|
+
protected createId(): string;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/index.d.ts
|
|
97
39
|
/**
|
|
98
40
|
* Alepha Bucket Azure Module
|
|
99
41
|
*
|
|
@@ -103,8 +45,9 @@ declare class AzureFileStorageProvider implements FileStorageProvider {
|
|
|
103
45
|
* @module alepha.bucket.azure
|
|
104
46
|
*/
|
|
105
47
|
declare class AlephaBucketAzure implements Module {
|
|
106
|
-
|
|
107
|
-
|
|
48
|
+
readonly name = "alepha.bucket.azure";
|
|
49
|
+
readonly $services: (alepha: Alepha) => Alepha;
|
|
108
50
|
}
|
|
109
|
-
|
|
51
|
+
//#endregion
|
|
110
52
|
export { AlephaBucketAzure, AzureFileStorageProvider };
|
|
53
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,134 +1,117 @@
|
|
|
1
|
-
import { BucketDescriptorProvider, FileNotFoundError, FileStorageProvider
|
|
2
|
-
import { randomUUID } from
|
|
3
|
-
import {
|
|
4
|
-
import { DateTimeProvider } from
|
|
5
|
-
import { file } from
|
|
6
|
-
import { BlobServiceClient } from
|
|
1
|
+
import { AlephaBucket, BucketDescriptorProvider, FileNotFoundError, FileStorageProvider } from "@alepha/bucket";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { $env, $hook, $inject, $logger, t } from "@alepha/core";
|
|
4
|
+
import { DateTimeProvider } from "@alepha/datetime";
|
|
5
|
+
import { file } from "@alepha/file";
|
|
6
|
+
import { BlobServiceClient } from "@azure/storage-blob";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
return this.containers[container].getBlockBlobClient(fileId);
|
|
98
|
-
}
|
|
99
|
-
onStart = $hook({
|
|
100
|
-
name: "start",
|
|
101
|
-
handler: async () => {
|
|
102
|
-
for (const bucket of this.bucketProvider.getBuckets()) {
|
|
103
|
-
const containerName = bucket.name.replaceAll("/", "-").toLowerCase();
|
|
104
|
-
this.log.debug(`Prepare container ${containerName}...`);
|
|
105
|
-
if (!this.containers[containerName]) {
|
|
106
|
-
this.containers[containerName] = await this.createContainerClient(containerName);
|
|
107
|
-
}
|
|
108
|
-
this.log.info(`Container ${bucket} OK`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
async createContainerClient(name) {
|
|
113
|
-
const container = this.blobServiceClient.getContainerClient(name);
|
|
114
|
-
await this.dateTimeProvider.deadline(
|
|
115
|
-
(abortSignal) => container.createIfNotExists({ abortSignal }),
|
|
116
|
-
[5, "seconds"]
|
|
117
|
-
);
|
|
118
|
-
return container;
|
|
119
|
-
}
|
|
120
|
-
createId() {
|
|
121
|
-
return randomUUID();
|
|
122
|
-
}
|
|
123
|
-
}
|
|
8
|
+
//#region src/providers/AzureFileStorageProvider.ts
|
|
9
|
+
const envSchema = t.object({ AZ_STORAGE_CONNECTION_STRING: t.string({
|
|
10
|
+
size: "long",
|
|
11
|
+
default: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
|
|
12
|
+
}) });
|
|
13
|
+
var AzureFileStorageProvider = class {
|
|
14
|
+
log = $logger();
|
|
15
|
+
env = $env(envSchema);
|
|
16
|
+
bucketProvider = $inject(BucketDescriptorProvider);
|
|
17
|
+
dateTimeProvider = $inject(DateTimeProvider);
|
|
18
|
+
containers = {};
|
|
19
|
+
blobServiceClient;
|
|
20
|
+
options = {};
|
|
21
|
+
constructor() {
|
|
22
|
+
this.blobServiceClient = BlobServiceClient.fromConnectionString(this.env.AZ_STORAGE_CONNECTION_STRING, this.storagePipelineOptions());
|
|
23
|
+
}
|
|
24
|
+
storagePipelineOptions() {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
async createContainer(containerName) {
|
|
28
|
+
if (this.containers[containerName]) return this.containers[containerName];
|
|
29
|
+
const container = await this.createContainerClient(containerName);
|
|
30
|
+
this.containers[containerName] = container;
|
|
31
|
+
return container;
|
|
32
|
+
}
|
|
33
|
+
async upload(bucketName, file$1, fileId) {
|
|
34
|
+
fileId ??= this.createId();
|
|
35
|
+
const block = this.getBlock(bucketName, fileId);
|
|
36
|
+
const metadata = {
|
|
37
|
+
name: file$1.name,
|
|
38
|
+
type: file$1.type
|
|
39
|
+
};
|
|
40
|
+
if (file$1.filepath) await block.uploadFile(file$1.filepath, {
|
|
41
|
+
metadata,
|
|
42
|
+
blobHTTPHeaders: { blobContentType: file$1.type }
|
|
43
|
+
});
|
|
44
|
+
else if (file$1.size > 0) await block.uploadData(await file$1.arrayBuffer(), {
|
|
45
|
+
metadata,
|
|
46
|
+
blobHTTPHeaders: { blobContentType: file$1.type }
|
|
47
|
+
});
|
|
48
|
+
else throw new Error("Raw stream upload is not supported yet");
|
|
49
|
+
return fileId;
|
|
50
|
+
}
|
|
51
|
+
async download(bucketName, fileId) {
|
|
52
|
+
const block = this.getBlock(bucketName, fileId);
|
|
53
|
+
const blob = await block.download().catch((error) => {
|
|
54
|
+
if (error instanceof Error) throw new FileNotFoundError("Error downloading file", { cause: error });
|
|
55
|
+
throw error;
|
|
56
|
+
});
|
|
57
|
+
if (!blob.readableStreamBody) throw new FileNotFoundError("File not found - empty stream body");
|
|
58
|
+
return file(blob.readableStreamBody, blob.metadata);
|
|
59
|
+
}
|
|
60
|
+
async exists(bucketName, fileId) {
|
|
61
|
+
return await this.getBlock(bucketName, fileId).exists();
|
|
62
|
+
}
|
|
63
|
+
async delete(bucketName, fileId) {
|
|
64
|
+
try {
|
|
65
|
+
await this.getBlock(bucketName, fileId).delete();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (error instanceof Error) throw new FileNotFoundError("Error deleting file", { cause: error });
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
getBlock(container, fileId) {
|
|
72
|
+
if (!this.containers[container]) throw new FileNotFoundError(`File '${fileId}' not found - container '${container}' does not exists`);
|
|
73
|
+
return this.containers[container].getBlockBlobClient(fileId);
|
|
74
|
+
}
|
|
75
|
+
onStart = $hook({
|
|
76
|
+
name: "start",
|
|
77
|
+
handler: async () => {
|
|
78
|
+
for (const bucket of this.bucketProvider.getBuckets()) {
|
|
79
|
+
const containerName = bucket.name.replaceAll("/", "-").toLowerCase();
|
|
80
|
+
this.log.debug(`Prepare container ${containerName}...`);
|
|
81
|
+
if (!this.containers[containerName]) this.containers[containerName] = await this.createContainerClient(containerName);
|
|
82
|
+
this.log.info(`Container ${bucket} OK`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
async createContainerClient(name) {
|
|
87
|
+
const container = this.blobServiceClient.getContainerClient(name);
|
|
88
|
+
await this.dateTimeProvider.deadline((abortSignal) => container.createIfNotExists({ abortSignal }), [5, "seconds"]);
|
|
89
|
+
return container;
|
|
90
|
+
}
|
|
91
|
+
createId() {
|
|
92
|
+
return randomUUID();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
124
95
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/index.ts
|
|
98
|
+
/**
|
|
99
|
+
* Alepha Bucket Azure Module
|
|
100
|
+
*
|
|
101
|
+
* Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.
|
|
102
|
+
*
|
|
103
|
+
* @see {@link AzureFileStorageProvider}
|
|
104
|
+
* @module alepha.bucket.azure
|
|
105
|
+
*/
|
|
106
|
+
var AlephaBucketAzure = class {
|
|
107
|
+
name = "alepha.bucket.azure";
|
|
108
|
+
$services = (alepha) => alepha.with({
|
|
109
|
+
provide: FileStorageProvider,
|
|
110
|
+
use: AzureFileStorageProvider,
|
|
111
|
+
optional: true
|
|
112
|
+
}).with(AlephaBucket);
|
|
113
|
+
};
|
|
133
114
|
|
|
115
|
+
//#endregion
|
|
134
116
|
export { AlephaBucketAzure, AzureFileStorageProvider };
|
|
117
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["containerName: string","bucketName: string","file: FileLike","fileId?: string","file","fileId: string","container: string","name: string","alepha: Alepha"],"sources":["../src/providers/AzureFileStorageProvider.ts","../src/index.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n\tBucketDescriptorProvider,\n\tFileNotFoundError,\n\ttype FileStorageProvider,\n} from \"@alepha/bucket\";\nimport type { Static } from \"@alepha/core\";\nimport { $env, $hook, $inject, $logger, type FileLike, t } from \"@alepha/core\";\nimport { DateTimeProvider } from \"@alepha/datetime\";\nimport { file } 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\tdefault:\n\t\t\t\"DefaultEndpointsProtocol=http;\" +\n\t\t\t\"AccountName=devstoreaccount1;\" +\n\t\t\t\"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;\" +\n\t\t\t\"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;\",\n\t}),\n});\n\ndeclare module \"@alepha/core\" {\n\tinterface Env extends Partial<Static<typeof envSchema>> {}\n}\n\nexport class AzureFileStorageProvider implements FileStorageProvider {\n\tprotected readonly log = $logger();\n\tprotected readonly env = $env(envSchema);\n\tprotected readonly bucketProvider = $inject(BucketDescriptorProvider);\n\tprotected readonly dateTimeProvider = $inject(DateTimeProvider);\n\tprotected readonly containers: Record<string, ContainerClient> = {};\n\tprotected readonly blobServiceClient: BlobServiceClient;\n\tprotected 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.storagePipelineOptions(),\n\t\t);\n\t}\n\n\tpublic storagePipelineOptions(): StoragePipelineOptions {\n\t\treturn {};\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\tthrow new Error(\"Raw stream upload is not supported yet\");\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 file(blob.readableStreamBody, blob.metadata);\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\tpublic readonly onStart = $hook({\n\t\tname: \"start\",\n\t\thandler: async () => {\n\t\t\tfor (const bucket of this.bucketProvider.getBuckets()) {\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\tprotected async createContainerClient(\n\t\tname: string,\n\t): Promise<ContainerClient> {\n\t\tconst container = this.blobServiceClient.getContainerClient(name);\n\n\t\tawait this.dateTimeProvider.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 type { Alepha, Module } from \"@alepha/core\";\nimport { AzureFileStorageProvider } from \"./providers/AzureFileStorageProvider.ts\";\n\nexport * from \"./providers/AzureFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Alepha Bucket Azure Module\n *\n * Plugin for Alepha Bucket that provides Azure Blob Storage capabilities.\n *\n * @see {@link AzureFileStorageProvider}\n * @module alepha.bucket.azure\n */\nexport class AlephaBucketAzure implements Module {\n\tpublic readonly name = \"alepha.bucket.azure\";\n\tpublic readonly $services = (alepha: Alepha) =>\n\t\talepha\n\t\t\t.with({\n\t\t\t\tprovide: FileStorageProvider,\n\t\t\t\tuse: AzureFileStorageProvider,\n\t\t\t\toptional: true,\n\t\t\t})\n\t\t\t.with(AlephaBucket);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
"storage-blob"
|
|
11
11
|
],
|
|
12
12
|
"author": "Feunard",
|
|
13
|
-
"version": "0.7.
|
|
13
|
+
"version": "0.7.7",
|
|
14
14
|
"type": "module",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22.0.0"
|
|
17
|
+
},
|
|
15
18
|
"license": "MIT",
|
|
16
19
|
"main": "./dist/index.js",
|
|
17
20
|
"types": "./dist/index.d.ts",
|
|
@@ -20,19 +23,19 @@
|
|
|
20
23
|
"src"
|
|
21
24
|
],
|
|
22
25
|
"dependencies": {
|
|
23
|
-
"@alepha/bucket": "0.7.
|
|
24
|
-
"@alepha/core": "0.7.
|
|
25
|
-
"@alepha/datetime": "0.7.
|
|
26
|
-
"@alepha/file": "0.7.
|
|
26
|
+
"@alepha/bucket": "0.7.7",
|
|
27
|
+
"@alepha/core": "0.7.7",
|
|
28
|
+
"@alepha/datetime": "0.7.7",
|
|
29
|
+
"@alepha/file": "0.7.7",
|
|
27
30
|
"@azure/storage-blob": "^12.27.0"
|
|
28
31
|
},
|
|
29
32
|
"devDependencies": {
|
|
30
|
-
"
|
|
33
|
+
"tsdown": "^0.12.9",
|
|
31
34
|
"vitest": "^3.2.4"
|
|
32
35
|
},
|
|
33
36
|
"scripts": {
|
|
34
37
|
"test": "vitest run",
|
|
35
|
-
"build": "
|
|
38
|
+
"build": "tsdown -c ../../tsdown.config.ts"
|
|
36
39
|
},
|
|
37
40
|
"repository": {
|
|
38
41
|
"type": "git",
|