@alepha/bucket-azure 0.13.3 → 0.13.5
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.
|
@@ -2,12 +2,11 @@ import { Readable } from "node:stream";
|
|
|
2
2
|
import { BlobServiceClient, BlockBlobClient, ContainerClient, StoragePipelineOptions } from "@azure/storage-blob";
|
|
3
3
|
import * as typebox0 from "typebox";
|
|
4
4
|
import { Static, StaticDecode as Static$1, StaticEncode, TAny, TArray, TArray as TArray$1, TBoolean, TInteger, TNumber, TObject, TObject as TObject$1, TOptional, TOptionalAdd, TRecord, TSchema, TSchema as TSchema$1, TString, TUnsafe } from "typebox";
|
|
5
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
5
|
import { ReadableStream as ReadableStream$1 } from "node:stream/web";
|
|
6
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { Validator } from "typebox/compile";
|
|
8
8
|
import dayjsDuration from "dayjs/plugin/duration.js";
|
|
9
9
|
import DayjsApi, { Dayjs, ManipulateType, PluginFunc } from "dayjs";
|
|
10
|
-
import "node:fs";
|
|
11
10
|
|
|
12
11
|
//#region ../alepha/src/core/constants/KIND.d.ts
|
|
13
12
|
/**
|
|
@@ -107,6 +106,93 @@ interface LoggerInterface {
|
|
|
107
106
|
error(message: string, data?: unknown): void;
|
|
108
107
|
}
|
|
109
108
|
//#endregion
|
|
109
|
+
//#region ../alepha/src/core/helpers/FileLike.d.ts
|
|
110
|
+
interface FileLike {
|
|
111
|
+
/**
|
|
112
|
+
* Filename.
|
|
113
|
+
* @default "file"
|
|
114
|
+
*/
|
|
115
|
+
name: string;
|
|
116
|
+
/**
|
|
117
|
+
* Mandatory MIME type of the file.
|
|
118
|
+
* @default "application/octet-stream"
|
|
119
|
+
*/
|
|
120
|
+
type: string;
|
|
121
|
+
/**
|
|
122
|
+
* Size of the file in bytes.
|
|
123
|
+
*
|
|
124
|
+
* Always 0 for streams, as the size is not known until the stream is fully read.
|
|
125
|
+
*
|
|
126
|
+
* @default 0
|
|
127
|
+
*/
|
|
128
|
+
size: number;
|
|
129
|
+
/**
|
|
130
|
+
* Last modified timestamp in milliseconds since epoch.
|
|
131
|
+
*
|
|
132
|
+
* Always the current timestamp for streams, as the last modified time is not known.
|
|
133
|
+
* We use this field to ensure compatibility with File API.
|
|
134
|
+
*
|
|
135
|
+
* @default Date.now()
|
|
136
|
+
*/
|
|
137
|
+
lastModified: number;
|
|
138
|
+
/**
|
|
139
|
+
* Returns a ReadableStream or Node.js Readable stream of the file content.
|
|
140
|
+
*
|
|
141
|
+
* For streams, this is the original stream.
|
|
142
|
+
*/
|
|
143
|
+
stream(): StreamLike;
|
|
144
|
+
/**
|
|
145
|
+
* Returns the file content as an ArrayBuffer.
|
|
146
|
+
*
|
|
147
|
+
* For streams, this reads the entire stream into memory.
|
|
148
|
+
*/
|
|
149
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
150
|
+
/**
|
|
151
|
+
* Returns the file content as a string.
|
|
152
|
+
*
|
|
153
|
+
* For streams, this reads the entire stream into memory and converts it to a string.
|
|
154
|
+
*/
|
|
155
|
+
text(): Promise<string>;
|
|
156
|
+
/**
|
|
157
|
+
* Optional file path, if the file is stored on disk.
|
|
158
|
+
*
|
|
159
|
+
* This is not from the File API, but rather a custom field to indicate where the file is stored.
|
|
160
|
+
*/
|
|
161
|
+
filepath?: string;
|
|
162
|
+
}
|
|
163
|
+
type StreamLike = ReadableStream | ReadableStream$1 | Readable | NodeJS.ReadableStream;
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region ../alepha/src/core/providers/TypeProvider.d.ts
|
|
166
|
+
declare module "typebox" {
|
|
167
|
+
interface TString {
|
|
168
|
+
format?: string;
|
|
169
|
+
minLength?: number;
|
|
170
|
+
maxLength?: number;
|
|
171
|
+
}
|
|
172
|
+
interface TNumber {
|
|
173
|
+
format?: "int64";
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region ../alepha/src/core/primitives/$atom.d.ts
|
|
178
|
+
type AtomOptions<T extends TAtomObject, N extends string> = {
|
|
179
|
+
name: N;
|
|
180
|
+
schema: T;
|
|
181
|
+
description?: string;
|
|
182
|
+
} & (T extends TOptionalAdd<T> ? {
|
|
183
|
+
default?: Static$1<T>;
|
|
184
|
+
} : {
|
|
185
|
+
default: Static$1<T>;
|
|
186
|
+
});
|
|
187
|
+
declare class Atom<T extends TAtomObject = TObject$1, N extends string = string> {
|
|
188
|
+
readonly options: AtomOptions<T, N>;
|
|
189
|
+
get schema(): T;
|
|
190
|
+
get key(): N;
|
|
191
|
+
constructor(options: AtomOptions<T, N>);
|
|
192
|
+
}
|
|
193
|
+
type TAtomObject = TObject$1<any> | TArray;
|
|
194
|
+
type AtomStatic<T extends TAtomObject> = T extends TOptionalAdd<T> ? Static$1<T> | undefined : Static$1<T>;
|
|
195
|
+
//#endregion
|
|
110
196
|
//#region ../alepha/src/core/primitives/$inject.d.ts
|
|
111
197
|
interface InjectOptions<T extends object = any> {
|
|
112
198
|
/**
|
|
@@ -203,74 +289,6 @@ declare class Json {
|
|
|
203
289
|
parse(text: string, reviver?: (this: any, key: string, value: any) => any): any;
|
|
204
290
|
}
|
|
205
291
|
//#endregion
|
|
206
|
-
//#region ../alepha/src/core/helpers/FileLike.d.ts
|
|
207
|
-
interface FileLike {
|
|
208
|
-
/**
|
|
209
|
-
* Filename.
|
|
210
|
-
* @default "file"
|
|
211
|
-
*/
|
|
212
|
-
name: string;
|
|
213
|
-
/**
|
|
214
|
-
* Mandatory MIME type of the file.
|
|
215
|
-
* @default "application/octet-stream"
|
|
216
|
-
*/
|
|
217
|
-
type: string;
|
|
218
|
-
/**
|
|
219
|
-
* Size of the file in bytes.
|
|
220
|
-
*
|
|
221
|
-
* Always 0 for streams, as the size is not known until the stream is fully read.
|
|
222
|
-
*
|
|
223
|
-
* @default 0
|
|
224
|
-
*/
|
|
225
|
-
size: number;
|
|
226
|
-
/**
|
|
227
|
-
* Last modified timestamp in milliseconds since epoch.
|
|
228
|
-
*
|
|
229
|
-
* Always the current timestamp for streams, as the last modified time is not known.
|
|
230
|
-
* We use this field to ensure compatibility with File API.
|
|
231
|
-
*
|
|
232
|
-
* @default Date.now()
|
|
233
|
-
*/
|
|
234
|
-
lastModified: number;
|
|
235
|
-
/**
|
|
236
|
-
* Returns a ReadableStream or Node.js Readable stream of the file content.
|
|
237
|
-
*
|
|
238
|
-
* For streams, this is the original stream.
|
|
239
|
-
*/
|
|
240
|
-
stream(): StreamLike;
|
|
241
|
-
/**
|
|
242
|
-
* Returns the file content as an ArrayBuffer.
|
|
243
|
-
*
|
|
244
|
-
* For streams, this reads the entire stream into memory.
|
|
245
|
-
*/
|
|
246
|
-
arrayBuffer(): Promise<ArrayBuffer>;
|
|
247
|
-
/**
|
|
248
|
-
* Returns the file content as a string.
|
|
249
|
-
*
|
|
250
|
-
* For streams, this reads the entire stream into memory and converts it to a string.
|
|
251
|
-
*/
|
|
252
|
-
text(): Promise<string>;
|
|
253
|
-
/**
|
|
254
|
-
* Optional file path, if the file is stored on disk.
|
|
255
|
-
*
|
|
256
|
-
* This is not from the File API, but rather a custom field to indicate where the file is stored.
|
|
257
|
-
*/
|
|
258
|
-
filepath?: string;
|
|
259
|
-
}
|
|
260
|
-
type StreamLike = ReadableStream | ReadableStream$1 | Readable | NodeJS.ReadableStream;
|
|
261
|
-
//#endregion
|
|
262
|
-
//#region ../alepha/src/core/providers/TypeProvider.d.ts
|
|
263
|
-
declare module "typebox" {
|
|
264
|
-
interface TString {
|
|
265
|
-
format?: string;
|
|
266
|
-
minLength?: number;
|
|
267
|
-
maxLength?: number;
|
|
268
|
-
}
|
|
269
|
-
interface TNumber {
|
|
270
|
-
format?: "int64";
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
//#endregion
|
|
274
292
|
//#region ../alepha/src/core/providers/SchemaCodec.d.ts
|
|
275
293
|
declare abstract class SchemaCodec {
|
|
276
294
|
/**
|
|
@@ -440,25 +458,6 @@ declare class EventManager {
|
|
|
440
458
|
}): Promise<void>;
|
|
441
459
|
}
|
|
442
460
|
//#endregion
|
|
443
|
-
//#region ../alepha/src/core/primitives/$atom.d.ts
|
|
444
|
-
type AtomOptions<T extends TAtomObject, N extends string> = {
|
|
445
|
-
name: N;
|
|
446
|
-
schema: T;
|
|
447
|
-
description?: string;
|
|
448
|
-
} & (T extends TOptionalAdd<T> ? {
|
|
449
|
-
default?: Static$1<T>;
|
|
450
|
-
} : {
|
|
451
|
-
default: Static$1<T>;
|
|
452
|
-
});
|
|
453
|
-
declare class Atom<T extends TAtomObject = TObject$1, N extends string = string> {
|
|
454
|
-
readonly options: AtomOptions<T, N>;
|
|
455
|
-
get schema(): T;
|
|
456
|
-
get key(): N;
|
|
457
|
-
constructor(options: AtomOptions<T, N>);
|
|
458
|
-
}
|
|
459
|
-
type TAtomObject = TObject$1<any> | TArray;
|
|
460
|
-
type AtomStatic<T extends TAtomObject> = T extends TOptionalAdd<T> ? Static$1<T> | undefined : Static$1<T>;
|
|
461
|
-
//#endregion
|
|
462
461
|
//#region ../alepha/src/core/providers/StateManager.d.ts
|
|
463
462
|
interface AtomWithValue {
|
|
464
463
|
atom: Atom;
|
|
@@ -735,6 +734,8 @@ declare class Alepha {
|
|
|
735
734
|
*/
|
|
736
735
|
get env(): Readonly<Env>;
|
|
737
736
|
constructor(init?: Partial<State>);
|
|
737
|
+
set<T extends TAtomObject>(target: Atom<T>, value: AtomStatic<T>): this;
|
|
738
|
+
set<Key extends keyof State>(target: Key, value: State[Key] | undefined): this;
|
|
738
739
|
/**
|
|
739
740
|
* True when start() is called.
|
|
740
741
|
*
|
|
@@ -1995,5 +1996,4 @@ declare class AzureFileStorageProvider implements FileStorageProvider {
|
|
|
1995
1996
|
*/
|
|
1996
1997
|
declare const AlephaBucketAzure: Service<Module>;
|
|
1997
1998
|
//#endregion
|
|
1998
|
-
export { AlephaBucketAzure, AzureFileStorageProvider };
|
|
1999
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1999
|
+
export { AlephaBucketAzure, AzureFileStorageProvider };
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"storage-blob"
|
|
11
11
|
],
|
|
12
12
|
"author": "Nicolas Foures",
|
|
13
|
-
"version": "0.13.
|
|
13
|
+
"version": "0.13.5",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=22.0.0"
|
|
@@ -26,18 +26,18 @@
|
|
|
26
26
|
"@azure/storage-blob": "^12.29.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"alepha": "0.13.
|
|
30
|
-
"tsdown": "^0.
|
|
29
|
+
"alepha": "0.13.5",
|
|
30
|
+
"tsdown": "^0.17.0",
|
|
31
31
|
"vitest": "^4.0.15"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"alepha": "0.13.
|
|
34
|
+
"alepha": "0.13.5"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"lint": "alepha lint",
|
|
38
38
|
"typecheck": "alepha typecheck",
|
|
39
39
|
"test": "alepha test",
|
|
40
|
-
"build": "tsdown
|
|
40
|
+
"build": "tsdown"
|
|
41
41
|
},
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/providers/AzureFileStorageProvider.ts","../src/index.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { Readable } from \"node:stream\";\nimport {\n BlobServiceClient,\n type BlockBlobClient,\n type ContainerClient,\n type StoragePipelineOptions,\n} from \"@azure/storage-blob\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n type FileLike,\n type Static,\n t,\n} from \"alepha\";\nimport {\n $bucket,\n FileNotFoundError,\n type FileStorageProvider,\n} from \"alepha/bucket\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { FileSystemProvider } from \"alepha/file\";\nimport { $logger } from \"alepha/logger\";\n\nconst envSchema = t.object({\n AZ_STORAGE_CONNECTION_STRING: t.string(),\n});\n\ndeclare module \"alepha\" {\n interface 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 protected readonly log = $logger();\n protected readonly env = $env(envSchema);\n protected readonly alepha = $inject(Alepha);\n protected readonly time = $inject(DateTimeProvider);\n protected readonly fileSystem = $inject(FileSystemProvider);\n protected readonly containers: Record<string, ContainerClient> = {};\n protected readonly blobServiceClient: BlobServiceClient;\n\n public readonly options: StoragePipelineOptions = {};\n\n constructor() {\n this.blobServiceClient = BlobServiceClient.fromConnectionString(\n this.env.AZ_STORAGE_CONNECTION_STRING,\n this.options,\n );\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n for (const bucket of this.alepha.primitives($bucket)) {\n if (bucket.provider !== this) {\n continue;\n }\n\n const containerName = this.convertName(bucket.name);\n\n this.log.debug(`Prepare container '${containerName}' ...`);\n\n if (!this.containers[containerName]) {\n this.containers[containerName] =\n await this.createContainerClient(containerName);\n }\n\n this.log.info(`Container '${bucket.name}' OK`);\n }\n },\n });\n\n public convertName(name: string): string {\n // Azure Blob Storage does not allow uppercase letters in container names\n return name.replaceAll(\"/\", \"-\").toLowerCase();\n }\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n fileId ??= this.createId();\n\n this.log.trace(\n `Uploading file '${file.name}' to bucket '${bucketName}' with id '${fileId}'...`,\n );\n\n const block = this.getBlock(bucketName, fileId);\n\n const metadata = {\n name: file.name,\n type: file.type,\n };\n\n if (file.filepath) {\n await block.uploadFile(file.filepath, {\n metadata,\n blobHTTPHeaders: {\n blobContentType: file.type,\n },\n });\n } else if (file.size > 0) {\n await block.uploadData(await file.arrayBuffer(), {\n metadata,\n blobHTTPHeaders: {\n blobContentType: file.type,\n },\n });\n } else {\n await block.uploadStream(\n Readable.from(file.stream()),\n file.size || undefined,\n 5,\n {\n metadata,\n blobHTTPHeaders: {\n blobContentType: file.type,\n },\n },\n );\n }\n\n return fileId;\n }\n\n public async download(bucketName: string, fileId: string): Promise<FileLike> {\n this.log.trace(\n `Downloading file '${fileId}' from bucket '${bucketName}'...`,\n );\n const block = this.getBlock(bucketName, fileId);\n\n const blob = await block.download().catch((error) => {\n if (error instanceof Error) {\n throw new FileNotFoundError(\"Error downloading file\", { cause: error });\n }\n\n throw error;\n });\n\n if (!blob.readableStreamBody) {\n throw new FileNotFoundError(\"File not found - empty stream body\");\n }\n\n return this.fileSystem.createFile({\n stream: blob.readableStreamBody,\n ...blob.metadata,\n size: blob.contentLength,\n });\n }\n\n public async exists(bucketName: string, fileId: string): Promise<boolean> {\n this.log.trace(\n `Checking existence of file '${fileId}' in bucket '${bucketName}'...`,\n );\n return await this.getBlock(bucketName, fileId).exists();\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n this.log.trace(`Deleting file '${fileId}' from bucket '${bucketName}'...`);\n try {\n await this.getBlock(bucketName, fileId).delete();\n } catch (error) {\n if (error instanceof Error) {\n throw new FileNotFoundError(\"Error deleting file\", { cause: error });\n }\n throw error;\n }\n }\n\n public getBlock(container: string, fileId: string): BlockBlobClient {\n const containerName = this.convertName(container);\n\n if (!this.containers[containerName]) {\n throw new FileNotFoundError(\n `File '${fileId}' not found - container '${container}' does not exists`,\n );\n }\n\n return this.containers[containerName].getBlockBlobClient(fileId);\n }\n\n protected async createContainerClient(\n name: string,\n ): Promise<ContainerClient> {\n const container = this.blobServiceClient.getContainerClient(name);\n\n await this.time.deadline(\n (abortSignal) => container.createIfNotExists({ abortSignal }),\n [5, \"seconds\"],\n );\n\n return container;\n }\n\n protected createId(): string {\n return randomUUID();\n }\n}\n","import { $module } from \"alepha\";\nimport { AlephaBucket, FileStorageProvider } from \"alepha/bucket\";\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 name: \"alepha.bucket.azure\",\n services: [AzureFileStorageProvider],\n register: (alepha) =>\n alepha\n .with({\n optional: true,\n provide: FileStorageProvider,\n use: AzureFileStorageProvider,\n })\n .with(AlephaBucket),\n});\n"],"mappings":"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"}
|