@alepha/bucket-vercel 0.11.3 → 0.11.4
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.d.ts → index.d.mts} +3 -1
- package/dist/index.d.mts.map +1 -0
- package/dist/{index.js → index.mjs} +6 -5
- package/dist/index.mjs.map +1 -0
- package/package.json +10 -10
- package/src/providers/VercelFileStorageProvider.ts +4 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -2,6 +2,7 @@ import { FileMetadataService, FileStorageProvider } from "@alepha/bucket";
|
|
|
2
2
|
import * as _alepha_core1 from "@alepha/core";
|
|
3
3
|
import { Alepha, FileLike, Static } from "@alepha/core";
|
|
4
4
|
import { DateTimeProvider } from "@alepha/datetime";
|
|
5
|
+
import { FileSystem } from "@alepha/file";
|
|
5
6
|
import * as _alepha_logger0 from "@alepha/logger";
|
|
6
7
|
import { del, head, put } from "@vercel/blob";
|
|
7
8
|
|
|
@@ -29,6 +30,7 @@ declare class VercelFileStorageProvider implements FileStorageProvider {
|
|
|
29
30
|
};
|
|
30
31
|
protected readonly alepha: Alepha;
|
|
31
32
|
protected readonly time: DateTimeProvider;
|
|
33
|
+
protected readonly fileSystem: FileSystem;
|
|
32
34
|
protected readonly stores: Set<string>;
|
|
33
35
|
protected readonly vercelBlobApi: VercelBlobApi;
|
|
34
36
|
protected readonly metadataService: FileMetadataService;
|
|
@@ -51,4 +53,4 @@ declare class VercelFileStorageProvider implements FileStorageProvider {
|
|
|
51
53
|
declare const AlephaBucketVercel: _alepha_core1.Service<_alepha_core1.Module<{}>>;
|
|
52
54
|
//#endregion
|
|
53
55
|
export { AlephaBucketVercel, VercelFileStorageProvider };
|
|
54
|
-
//# sourceMappingURL=index.d.
|
|
56
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/providers/VercelBlobProvider.ts","../src/providers/VercelFileStorageProvider.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;cAEa,aAAA;cACC;eACC;cACD;;;;cCkBR,WAIJ,aAAA,CAJa;yBAIb,aAAA,CAAA;;;EDzBW,UAAA,GAAA,SC4BW,OD5BE,CC4BM,MD5BN,CAAA,OC4BoB,SD5BpB,CAAA,CAAA,CAAA,CACZ;;;;;cCiCD,yBAAA,YAAqC;0BAAX,eAAA,CACf;EAdlB,mBAIJ,GAAA,EAAA;IAJa,qBAAA,EAAA,MAAA;EAAA,CAAA;qBAAA,MAAA,EAgBY,MAhBZ;EAO+B,mBAAA,IAAA,EAUrB,gBAVqB;EAAd,mBAAA,UAAA,EAWD,UAXC;EAAR,mBAAA,MAAA,EAYK,GAZL,CAAA,MAAA,CAAA;EAAO,mBAAA,aAAA,EAaG,aAbH;EAAA,mBAAA,eAAA,EAcK,mBAdL;EAAA,mBAAA,OAAA,EAcK,aAAA,CAER,cAhBG,CAAA,OAAA,CAAA;EAMlB,WAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EAAA,MAA0B;EAAA,UAAA,QACf,CAAA,CAAA,EAAA,MAAA;EAEG,MAAA,CAAA,UAAA,EAAA,MAAA,EAAA,IAAA,EAuCjB,QAvCiB,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EAyCtB,OAzCsB,CAAA,MAAA,CAAA;EACF,QAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAoFoC,OApFpC,CAoF4C,QApF5C,CAAA;EACM,MAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAgJ4B,OAhJ5B,CAAA,OAAA,CAAA;EACF,MAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAkK8B,OAlK9B,CAAA,IAAA,CAAA;;;;;;;;;;cC1BhB,oBAAkB,aAAA,CAAA,QAW7B,aAAA,CAX6B"}
|
|
@@ -2,7 +2,7 @@ import { $bucket, AlephaBucket, FileMetadataService, FileNotFoundError, FileStor
|
|
|
2
2
|
import { $env, $hook, $inject, $module, Alepha, AlephaError, t } from "@alepha/core";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { DateTimeProvider } from "@alepha/datetime";
|
|
5
|
-
import {
|
|
5
|
+
import { FileSystem } from "@alepha/file";
|
|
6
6
|
import { $logger } from "@alepha/logger";
|
|
7
7
|
import { del, head, put } from "@vercel/blob";
|
|
8
8
|
|
|
@@ -24,6 +24,7 @@ var VercelFileStorageProvider = class {
|
|
|
24
24
|
env = $env(envSchema);
|
|
25
25
|
alepha = $inject(Alepha);
|
|
26
26
|
time = $inject(DateTimeProvider);
|
|
27
|
+
fileSystem = $inject(FileSystem);
|
|
27
28
|
stores = /* @__PURE__ */ new Set();
|
|
28
29
|
vercelBlobApi = $inject(VercelBlobApi);
|
|
29
30
|
metadataService = $inject(FileMetadataService);
|
|
@@ -79,10 +80,10 @@ var VercelFileStorageProvider = class {
|
|
|
79
80
|
const buffer = Buffer.from(arrayBuffer);
|
|
80
81
|
const { metadata, contentStart } = this.metadataService.decodeMetadataFromBuffer(buffer);
|
|
81
82
|
const content = buffer.subarray(contentStart);
|
|
82
|
-
return createFile(
|
|
83
|
+
return this.fileSystem.createFile({
|
|
84
|
+
buffer: content,
|
|
83
85
|
name: metadata.name,
|
|
84
|
-
type: metadata.type
|
|
85
|
-
size: content.length
|
|
86
|
+
type: metadata.type
|
|
86
87
|
});
|
|
87
88
|
} catch (error) {
|
|
88
89
|
if (error instanceof FileNotFoundError) throw error;
|
|
@@ -133,4 +134,4 @@ const AlephaBucketVercel = $module({
|
|
|
133
134
|
|
|
134
135
|
//#endregion
|
|
135
136
|
export { AlephaBucketVercel, VercelFileStorageProvider };
|
|
136
|
-
//# sourceMappingURL=index.
|
|
137
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/providers/VercelBlobProvider.ts","../src/providers/VercelFileStorageProvider.ts","../src/index.ts"],"sourcesContent":["import { del, head, put } from \"@vercel/blob\";\n\nexport class VercelBlobApi {\n put: typeof put = put;\n head: typeof head = head;\n del: typeof del = del;\n}\n","import { randomUUID } from \"node:crypto\";\nimport type { Readable } from \"node:stream\";\nimport {\n $bucket,\n FileMetadataService,\n FileNotFoundError,\n type FileStorageProvider,\n} from \"@alepha/bucket\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\n type FileLike,\n type Static,\n t,\n} from \"@alepha/core\";\nimport { DateTimeProvider } from \"@alepha/datetime\";\nimport { FileSystem } from \"@alepha/file\";\nimport { $logger } from \"@alepha/logger\";\nimport { VercelBlobApi } from \"./VercelBlobProvider.ts\";\n\nconst envSchema = t.object({\n BLOB_READ_WRITE_TOKEN: t.text({\n size: \"long\",\n }),\n});\n\ndeclare module \"@alepha/core\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n/**\n * Vercel Blob Storage implementation of File Storage Provider.\n */\nexport class VercelFileStorageProvider 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(FileSystem);\n protected readonly stores: Set<string> = new Set();\n protected readonly vercelBlobApi = $inject(VercelBlobApi);\n protected readonly metadataService = $inject(FileMetadataService);\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n for (const bucket of this.alepha.descriptors($bucket)) {\n if (bucket.provider !== this) {\n continue;\n }\n\n const storeName = this.convertName(bucket.name);\n\n this.log.debug(`Prepare store '${storeName}' ...`);\n\n // Vercel Blob doesn't require explicit store/container creation\n // We just track the store names for reference\n this.stores.add(storeName);\n\n this.log.info(`Store '${bucket.name}' OK`);\n }\n },\n });\n\n public convertName(name: string): string {\n // Convert to a valid path-like name for Vercel Blob\n return name.replaceAll(\"/\", \"-\").toLowerCase();\n }\n\n protected createId(): string {\n return randomUUID();\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 storeName = this.convertName(bucketName);\n const pathname = `${storeName}/${fileId}`;\n\n try {\n // Create a buffer with metadata and content\n const contentBuffer = Buffer.from(await file.arrayBuffer());\n const fileBuffer = this.metadataService.createFileBuffer(\n file,\n contentBuffer,\n );\n\n // Upload the complete buffer (metadata + content) to Vercel Blob\n const result = await this.vercelBlobApi.put(\n pathname,\n fileBuffer as unknown as Readable,\n {\n access: \"public\",\n contentType: file.type || \"application/octet-stream\",\n token: this.env.BLOB_READ_WRITE_TOKEN,\n allowOverwrite: true,\n },\n );\n\n this.log.trace(`File uploaded successfully: ${result.url}`);\n return fileId;\n } catch (error) {\n this.log.error(`Failed to upload file: ${error}`);\n if (error instanceof Error) {\n throw new AlephaError(`Upload failed: ${error.message}`, {\n cause: error,\n });\n }\n\n throw error;\n }\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\n const storeName = this.convertName(bucketName);\n const pathname = `${storeName}/${fileId}`;\n\n try {\n // check if the file exists and get metadata\n const headResult = await this.vercelBlobApi.head(pathname, {\n token: this.env.BLOB_READ_WRITE_TOKEN,\n });\n\n if (!headResult) {\n throw new FileNotFoundError(\n `File '${fileId}' not found in bucket '${bucketName}'`,\n );\n }\n\n // fetch the actual file content (with metadata)\n const response = await fetch(headResult.url);\n\n if (!response.ok) {\n throw new FileNotFoundError(\n `Failed to fetch file: ${response.statusText}`,\n );\n }\n\n const arrayBuffer = await response.arrayBuffer();\n if (!arrayBuffer) {\n throw new FileNotFoundError(\"File not found - empty response body\");\n }\n\n // Decode metadata from the buffer\n const buffer = Buffer.from(arrayBuffer);\n const { metadata, contentStart } =\n this.metadataService.decodeMetadataFromBuffer(buffer);\n\n // Extract the actual content\n const content = buffer.subarray(contentStart);\n\n return this.fileSystem.createFile({\n buffer: content,\n name: metadata.name,\n type: metadata.type,\n });\n } catch (error) {\n if (error instanceof FileNotFoundError) {\n throw error;\n }\n\n this.log.error(`Failed to download file: ${error}`);\n if (error instanceof Error) {\n throw new FileNotFoundError(\"Error downloading file\", { cause: error });\n }\n\n throw error;\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\n const storeName = this.convertName(bucketName);\n const pathname = `${storeName}/${fileId}`;\n\n try {\n const result = await this.vercelBlobApi.head(pathname, {\n token: this.env.BLOB_READ_WRITE_TOKEN,\n });\n return result !== null;\n } catch (error) {\n // Vercel Blob head() throws for non-existent files\n return false;\n }\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n this.log.trace(`Deleting file '${fileId}' from bucket '${bucketName}'...`);\n\n const storeName = this.convertName(bucketName);\n const pathname = `${storeName}/${fileId}`;\n\n try {\n await this.vercelBlobApi.del(pathname, {\n token: this.env.BLOB_READ_WRITE_TOKEN,\n });\n } catch (error) {\n this.log.error(`Failed to delete file: ${error}`);\n if (error instanceof Error) {\n throw new FileNotFoundError(\"Error deleting file\", { cause: error });\n }\n throw error;\n }\n }\n}\n","import { AlephaBucket, FileStorageProvider } from \"@alepha/bucket\";\nimport { $module } from \"@alepha/core\";\nimport { VercelFileStorageProvider } from \"./providers/VercelFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/VercelFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Bucket that provides Vercel Blob Storage capabilities.\n *\n * @see {@link VercelFileStorageProvider}\n * @module alepha.bucket.vercel\n */\nexport const AlephaBucketVercel = $module({\n name: \"alepha.bucket.vercel\",\n services: [VercelFileStorageProvider],\n register: (alepha) =>\n alepha\n .with({\n optional: true,\n provide: FileStorageProvider,\n use: VercelFileStorageProvider,\n })\n .with(AlephaBucket),\n});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"blob"
|
|
11
11
|
],
|
|
12
12
|
"author": "Feunard",
|
|
13
|
-
"version": "0.11.
|
|
13
|
+
"version": "0.11.4",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=22.0.0"
|
|
@@ -23,19 +23,19 @@
|
|
|
23
23
|
"src"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@alepha/bucket": "0.11.
|
|
27
|
-
"@alepha/core": "0.11.
|
|
28
|
-
"@alepha/datetime": "0.11.
|
|
29
|
-
"@alepha/file": "0.11.
|
|
30
|
-
"@alepha/logger": "0.11.
|
|
26
|
+
"@alepha/bucket": "0.11.4",
|
|
27
|
+
"@alepha/core": "0.11.4",
|
|
28
|
+
"@alepha/datetime": "0.11.4",
|
|
29
|
+
"@alepha/file": "0.11.4",
|
|
30
|
+
"@alepha/logger": "0.11.4",
|
|
31
31
|
"@vercel/blob": "^2.0.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@biomejs/biome": "^2.3.
|
|
35
|
-
"@types/node": "^24.
|
|
36
|
-
"tsdown": "^0.
|
|
34
|
+
"@biomejs/biome": "^2.3.3",
|
|
35
|
+
"@types/node": "^24.10.0",
|
|
36
|
+
"tsdown": "^0.16.0",
|
|
37
37
|
"typescript": "^5.9.3",
|
|
38
|
-
"vitest": "^
|
|
38
|
+
"vitest": "^4.0.6"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"test": "vitest run",
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
t,
|
|
18
18
|
} from "@alepha/core";
|
|
19
19
|
import { DateTimeProvider } from "@alepha/datetime";
|
|
20
|
-
import {
|
|
20
|
+
import { FileSystem } from "@alepha/file";
|
|
21
21
|
import { $logger } from "@alepha/logger";
|
|
22
22
|
import { VercelBlobApi } from "./VercelBlobProvider.ts";
|
|
23
23
|
|
|
@@ -39,6 +39,7 @@ export class VercelFileStorageProvider implements FileStorageProvider {
|
|
|
39
39
|
protected readonly env = $env(envSchema);
|
|
40
40
|
protected readonly alepha = $inject(Alepha);
|
|
41
41
|
protected readonly time = $inject(DateTimeProvider);
|
|
42
|
+
protected readonly fileSystem = $inject(FileSystem);
|
|
42
43
|
protected readonly stores: Set<string> = new Set();
|
|
43
44
|
protected readonly vercelBlobApi = $inject(VercelBlobApi);
|
|
44
45
|
protected readonly metadataService = $inject(FileMetadataService);
|
|
@@ -163,10 +164,10 @@ export class VercelFileStorageProvider implements FileStorageProvider {
|
|
|
163
164
|
// Extract the actual content
|
|
164
165
|
const content = buffer.subarray(contentStart);
|
|
165
166
|
|
|
166
|
-
return createFile(
|
|
167
|
+
return this.fileSystem.createFile({
|
|
168
|
+
buffer: content,
|
|
167
169
|
name: metadata.name,
|
|
168
170
|
type: metadata.type,
|
|
169
|
-
size: content.length,
|
|
170
171
|
});
|
|
171
172
|
} catch (error) {
|
|
172
173
|
if (error instanceof FileNotFoundError) {
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/providers/VercelBlobProvider.ts","../src/providers/VercelFileStorageProvider.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;cAEa,aAAA;cACC;eACC;cACD;;;;cCkBR,WAIJ,aAAA,CAJa;yBAIb,aAAA,CAAA;;;EDzBW,UAAA,GAAA,SC4BW,OD5BE,CC4BM,MD5BN,CAAA,OC4BoB,SD5BpB,CAAA,CAAA,CAAA,CACZ;;;;;cCiCD,yBAAA,YAAqC;0BAAX,eAAA,CACf;EAdlB,mBAIJ,GAAA,EAAA;IAJa,qBAAA,EAAA,MAAA;EAAA,CAAA;qBAAA,MAAA,EAgBY,MAhBZ;EAO+B,mBAAA,IAAA,EAUrB,gBAVqB;EAAd,mBAAA,MAAA,EAWH,GAXG,CAAA,MAAA,CAAA;EAAR,mBAAA,aAAA,EAYU,aAZV;EAAO,mBAAA,eAAA,EAaK,mBAbL;EAAA,mBAAA,OAAA,EAaK,aAAA,CAER,cAfG,CAAA,OAAA,CAAA;EAAA,WAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EAAA,MAAA;EAMlB,UAAA,QAAA,CAAA,CAAA,EAAA,MAA0B;EAAA,MAAA,CAAA,UACf,EAAA,MAAA,EAAA,IAAA,EAwCd,QAxCc,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,EA0CnB,OA1CmB,CAAA,MAAA,CAAA;EAEG,QAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAoFkC,OApFlC,CAoF0C,QApF1C,CAAA;EACF,MAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAgJkC,OAhJlC,CAAA,OAAA,CAAA;EACI,MAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAkK8B,OAlK9B,CAAA,IAAA,CAAA;;;;;;;;;;cCzBhB,oBAAkB,aAAA,CAAA,QAW7B,aAAA,CAX6B"}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/providers/VercelBlobProvider.ts","../src/providers/VercelFileStorageProvider.ts","../src/index.ts"],"sourcesContent":["import { del, head, put } from \"@vercel/blob\";\n\nexport class VercelBlobApi {\n put: typeof put = put;\n head: typeof head = head;\n del: typeof del = del;\n}\n","import { randomUUID } from \"node:crypto\";\nimport type { Readable } from \"node:stream\";\nimport {\n $bucket,\n FileMetadataService,\n FileNotFoundError,\n type FileStorageProvider,\n} from \"@alepha/bucket\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\n type FileLike,\n type Static,\n t,\n} from \"@alepha/core\";\nimport { DateTimeProvider } from \"@alepha/datetime\";\nimport { createFile } from \"@alepha/file\";\nimport { $logger } from \"@alepha/logger\";\nimport { VercelBlobApi } from \"./VercelBlobProvider.ts\";\n\nconst envSchema = t.object({\n BLOB_READ_WRITE_TOKEN: t.text({\n size: \"long\",\n }),\n});\n\ndeclare module \"@alepha/core\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n/**\n * Vercel Blob Storage implementation of File Storage Provider.\n */\nexport class VercelFileStorageProvider 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 stores: Set<string> = new Set();\n protected readonly vercelBlobApi = $inject(VercelBlobApi);\n protected readonly metadataService = $inject(FileMetadataService);\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n for (const bucket of this.alepha.descriptors($bucket)) {\n if (bucket.provider !== this) {\n continue;\n }\n\n const storeName = this.convertName(bucket.name);\n\n this.log.debug(`Prepare store '${storeName}' ...`);\n\n // Vercel Blob doesn't require explicit store/container creation\n // We just track the store names for reference\n this.stores.add(storeName);\n\n this.log.info(`Store '${bucket.name}' OK`);\n }\n },\n });\n\n public convertName(name: string): string {\n // Convert to a valid path-like name for Vercel Blob\n return name.replaceAll(\"/\", \"-\").toLowerCase();\n }\n\n protected createId(): string {\n return randomUUID();\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 storeName = this.convertName(bucketName);\n const pathname = `${storeName}/${fileId}`;\n\n try {\n // Create a buffer with metadata and content\n const contentBuffer = Buffer.from(await file.arrayBuffer());\n const fileBuffer = this.metadataService.createFileBuffer(\n file,\n contentBuffer,\n );\n\n // Upload the complete buffer (metadata + content) to Vercel Blob\n const result = await this.vercelBlobApi.put(\n pathname,\n fileBuffer as unknown as Readable,\n {\n access: \"public\",\n contentType: file.type || \"application/octet-stream\",\n token: this.env.BLOB_READ_WRITE_TOKEN,\n allowOverwrite: true,\n },\n );\n\n this.log.trace(`File uploaded successfully: ${result.url}`);\n return fileId;\n } catch (error) {\n this.log.error(`Failed to upload file: ${error}`);\n if (error instanceof Error) {\n throw new AlephaError(`Upload failed: ${error.message}`, {\n cause: error,\n });\n }\n\n throw error;\n }\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\n const storeName = this.convertName(bucketName);\n const pathname = `${storeName}/${fileId}`;\n\n try {\n // check if the file exists and get metadata\n const headResult = await this.vercelBlobApi.head(pathname, {\n token: this.env.BLOB_READ_WRITE_TOKEN,\n });\n\n if (!headResult) {\n throw new FileNotFoundError(\n `File '${fileId}' not found in bucket '${bucketName}'`,\n );\n }\n\n // fetch the actual file content (with metadata)\n const response = await fetch(headResult.url);\n\n if (!response.ok) {\n throw new FileNotFoundError(\n `Failed to fetch file: ${response.statusText}`,\n );\n }\n\n const arrayBuffer = await response.arrayBuffer();\n if (!arrayBuffer) {\n throw new FileNotFoundError(\"File not found - empty response body\");\n }\n\n // Decode metadata from the buffer\n const buffer = Buffer.from(arrayBuffer);\n const { metadata, contentStart } =\n this.metadataService.decodeMetadataFromBuffer(buffer);\n\n // Extract the actual content\n const content = buffer.subarray(contentStart);\n\n return createFile(content, {\n name: metadata.name,\n type: metadata.type,\n size: content.length,\n });\n } catch (error) {\n if (error instanceof FileNotFoundError) {\n throw error;\n }\n\n this.log.error(`Failed to download file: ${error}`);\n if (error instanceof Error) {\n throw new FileNotFoundError(\"Error downloading file\", { cause: error });\n }\n\n throw error;\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\n const storeName = this.convertName(bucketName);\n const pathname = `${storeName}/${fileId}`;\n\n try {\n const result = await this.vercelBlobApi.head(pathname, {\n token: this.env.BLOB_READ_WRITE_TOKEN,\n });\n return result !== null;\n } catch (error) {\n // Vercel Blob head() throws for non-existent files\n return false;\n }\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n this.log.trace(`Deleting file '${fileId}' from bucket '${bucketName}'...`);\n\n const storeName = this.convertName(bucketName);\n const pathname = `${storeName}/${fileId}`;\n\n try {\n await this.vercelBlobApi.del(pathname, {\n token: this.env.BLOB_READ_WRITE_TOKEN,\n });\n } catch (error) {\n this.log.error(`Failed to delete file: ${error}`);\n if (error instanceof Error) {\n throw new FileNotFoundError(\"Error deleting file\", { cause: error });\n }\n throw error;\n }\n }\n}\n","import { AlephaBucket, FileStorageProvider } from \"@alepha/bucket\";\nimport { $module } from \"@alepha/core\";\nimport { VercelFileStorageProvider } from \"./providers/VercelFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/VercelFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Bucket that provides Vercel Blob Storage capabilities.\n *\n * @see {@link VercelFileStorageProvider}\n * @module alepha.bucket.vercel\n */\nexport const AlephaBucketVercel = $module({\n name: \"alepha.bucket.vercel\",\n services: [VercelFileStorageProvider],\n register: (alepha) =>\n alepha\n .with({\n optional: true,\n provide: FileStorageProvider,\n use: VercelFileStorageProvider,\n })\n .with(AlephaBucket),\n});\n"],"mappings":"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"}
|