@alepha/bucket-vercel 0.13.0 → 0.13.2
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 +82 -82
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/providers/VercelFileStorageProvider.ts +1 -1
- package/dist/chunk-MC1wKe0N.js +0 -27
- package/dist/index.cjs +0 -135
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -2002
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import "node:fs";
|
|
|
11
11
|
|
|
12
12
|
//#region ../alepha/src/core/constants/KIND.d.ts
|
|
13
13
|
/**
|
|
14
|
-
* Used for identifying
|
|
14
|
+
* Used for identifying primitives.
|
|
15
15
|
*
|
|
16
16
|
* @internal
|
|
17
17
|
*/
|
|
@@ -64,34 +64,50 @@ interface ServiceSubstitution<T extends object = any> {
|
|
|
64
64
|
*/
|
|
65
65
|
type ServiceEntry<T extends object = any> = Service<T> | ServiceSubstitution<T>;
|
|
66
66
|
//#endregion
|
|
67
|
-
//#region ../alepha/src/core/helpers/
|
|
68
|
-
interface
|
|
67
|
+
//#region ../alepha/src/core/helpers/primitive.d.ts
|
|
68
|
+
interface PrimitiveArgs<T extends object = {}> {
|
|
69
69
|
options: T;
|
|
70
70
|
alepha: Alepha;
|
|
71
71
|
service: InstantiableClass<Service>;
|
|
72
72
|
module?: Service;
|
|
73
73
|
}
|
|
74
|
-
interface
|
|
74
|
+
interface PrimitiveConfig {
|
|
75
75
|
propertyKey: string;
|
|
76
76
|
service: InstantiableClass<Service>;
|
|
77
77
|
module?: Service;
|
|
78
78
|
}
|
|
79
|
-
declare abstract class
|
|
79
|
+
declare abstract class Primitive<T extends object = {}> {
|
|
80
80
|
protected readonly alepha: Alepha;
|
|
81
81
|
readonly options: T;
|
|
82
|
-
readonly config:
|
|
83
|
-
constructor(args:
|
|
82
|
+
readonly config: PrimitiveConfig;
|
|
83
|
+
constructor(args: PrimitiveArgs<T>);
|
|
84
84
|
/**
|
|
85
|
-
* Called automatically by Alepha after the
|
|
85
|
+
* Called automatically by Alepha after the primitive is created.
|
|
86
86
|
*/
|
|
87
87
|
protected onInit(): void;
|
|
88
88
|
}
|
|
89
|
-
type
|
|
89
|
+
type PrimitiveFactoryLike<T extends object = any> = {
|
|
90
90
|
(options: T): any;
|
|
91
91
|
[KIND]: any;
|
|
92
92
|
};
|
|
93
93
|
//#endregion
|
|
94
|
-
//#region ../alepha/src/core/
|
|
94
|
+
//#region ../alepha/src/core/interfaces/Async.d.ts
|
|
95
|
+
/**
|
|
96
|
+
* Represents a value that can be either a value or a promise of value.
|
|
97
|
+
*/
|
|
98
|
+
type Async<T> = T | Promise<T>;
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region ../alepha/src/core/interfaces/LoggerInterface.d.ts
|
|
101
|
+
type LogLevel = "ERROR" | "WARN" | "INFO" | "DEBUG" | "TRACE" | "SILENT";
|
|
102
|
+
interface LoggerInterface {
|
|
103
|
+
trace(message: string, data?: unknown): void;
|
|
104
|
+
debug(message: string, data?: unknown): void;
|
|
105
|
+
info(message: string, data?: unknown): void;
|
|
106
|
+
warn(message: string, data?: unknown): void;
|
|
107
|
+
error(message: string, data?: unknown): void;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region ../alepha/src/core/primitives/$inject.d.ts
|
|
95
111
|
interface InjectOptions<T extends object = any> {
|
|
96
112
|
/**
|
|
97
113
|
* - 'transient' → Always a new instance on every inject. Zero caching.
|
|
@@ -116,8 +132,8 @@ interface InjectOptions<T extends object = any> {
|
|
|
116
132
|
parent?: Service | null;
|
|
117
133
|
}
|
|
118
134
|
//#endregion
|
|
119
|
-
//#region ../alepha/src/core/
|
|
120
|
-
interface
|
|
135
|
+
//#region ../alepha/src/core/primitives/$module.d.ts
|
|
136
|
+
interface ModulePrimitiveOptions {
|
|
121
137
|
/**
|
|
122
138
|
* Name of the module.
|
|
123
139
|
*
|
|
@@ -132,9 +148,9 @@ interface ModuleDescriptorOptions {
|
|
|
132
148
|
*/
|
|
133
149
|
services?: Array<Service>;
|
|
134
150
|
/**
|
|
135
|
-
* List of $
|
|
151
|
+
* List of $primitives to register in the module.
|
|
136
152
|
*/
|
|
137
|
-
|
|
153
|
+
primitives?: Array<PrimitiveFactoryLike>;
|
|
138
154
|
/**
|
|
139
155
|
* By default, module will register ALL services.
|
|
140
156
|
* You can override this behavior by providing a register function.
|
|
@@ -148,7 +164,7 @@ interface ModuleDescriptorOptions {
|
|
|
148
164
|
* Base class for all modules.
|
|
149
165
|
*/
|
|
150
166
|
declare abstract class Module {
|
|
151
|
-
abstract readonly options:
|
|
167
|
+
abstract readonly options: ModulePrimitiveOptions;
|
|
152
168
|
abstract register(alepha: Alepha): void;
|
|
153
169
|
static NAME_REGEX: RegExp;
|
|
154
170
|
/**
|
|
@@ -163,22 +179,6 @@ declare abstract class Module {
|
|
|
163
179
|
static of(ctor: Service): Service<Module> | undefined;
|
|
164
180
|
}
|
|
165
181
|
//#endregion
|
|
166
|
-
//#region ../alepha/src/core/interfaces/Async.d.ts
|
|
167
|
-
/**
|
|
168
|
-
* Represents a value that can be either a value or a promise of value.
|
|
169
|
-
*/
|
|
170
|
-
type Async<T> = T | Promise<T>;
|
|
171
|
-
//#endregion
|
|
172
|
-
//#region ../alepha/src/core/interfaces/LoggerInterface.d.ts
|
|
173
|
-
type LogLevel = "ERROR" | "WARN" | "INFO" | "DEBUG" | "TRACE" | "SILENT";
|
|
174
|
-
interface LoggerInterface {
|
|
175
|
-
trace(message: string, data?: unknown): void;
|
|
176
|
-
debug(message: string, data?: unknown): void;
|
|
177
|
-
info(message: string, data?: unknown): void;
|
|
178
|
-
warn(message: string, data?: unknown): void;
|
|
179
|
-
error(message: string, data?: unknown): void;
|
|
180
|
-
}
|
|
181
|
-
//#endregion
|
|
182
182
|
//#region ../alepha/src/core/providers/AlsProvider.d.ts
|
|
183
183
|
type AsyncLocalStorageData = any;
|
|
184
184
|
declare class AlsProvider {
|
|
@@ -440,7 +440,7 @@ declare class EventManager {
|
|
|
440
440
|
}): Promise<void>;
|
|
441
441
|
}
|
|
442
442
|
//#endregion
|
|
443
|
-
//#region ../alepha/src/core/
|
|
443
|
+
//#region ../alepha/src/core/primitives/$atom.d.ts
|
|
444
444
|
type AtomOptions<T extends TAtomObject, N extends string> = {
|
|
445
445
|
name: N;
|
|
446
446
|
schema: T;
|
|
@@ -569,7 +569,7 @@ type OnlyArray<T extends object> = { [K in keyof T]: NonNullable<T[K]> extends A
|
|
|
569
569
|
* // You can access the environment variables using alepha.env
|
|
570
570
|
* console.log(alepha.env.MY_VAR); // "value"
|
|
571
571
|
*
|
|
572
|
-
* // But you should use $env()
|
|
572
|
+
* // But you should use $env() primitive to get typed values from the environment.
|
|
573
573
|
* class App {
|
|
574
574
|
* env = $env(
|
|
575
575
|
* t.object({
|
|
@@ -582,7 +582,7 @@ type OnlyArray<T extends object> = { [K in keyof T]: NonNullable<T[K]> extends A
|
|
|
582
582
|
* ### Modules
|
|
583
583
|
*
|
|
584
584
|
* Modules are a way to group services together.
|
|
585
|
-
* You can register a module using the `$module`
|
|
585
|
+
* You can register a module using the `$module` primitive.
|
|
586
586
|
*
|
|
587
587
|
* ```ts
|
|
588
588
|
* import { $module } from "alepha";
|
|
@@ -600,7 +600,7 @@ type OnlyArray<T extends object> = { [K in keyof T]: NonNullable<T[K]> extends A
|
|
|
600
600
|
* ### Hooks
|
|
601
601
|
*
|
|
602
602
|
* Hooks are a way to run async functions from all registered providers/services.
|
|
603
|
-
* You can register a hook using the `$hook`
|
|
603
|
+
* You can register a hook using the `$hook` primitive.
|
|
604
604
|
*
|
|
605
605
|
* ```ts
|
|
606
606
|
* import { $hook } from "alepha";
|
|
@@ -697,9 +697,9 @@ declare class Alepha {
|
|
|
697
697
|
use: Service;
|
|
698
698
|
}>;
|
|
699
699
|
/**
|
|
700
|
-
* Registry of
|
|
700
|
+
* Registry of primitives.
|
|
701
701
|
*/
|
|
702
|
-
protected
|
|
702
|
+
protected primitiveRegistry: Map<Service<Primitive<{}>>, Primitive<{}>[]>;
|
|
703
703
|
/**
|
|
704
704
|
* List of all services + how they are provided.
|
|
705
705
|
*/
|
|
@@ -719,7 +719,7 @@ declare class Alepha {
|
|
|
719
719
|
/**
|
|
720
720
|
* State manager to store arbitrary values.
|
|
721
721
|
*/
|
|
722
|
-
get
|
|
722
|
+
get store(): StateManager<State>;
|
|
723
723
|
/**
|
|
724
724
|
* Codec manager for encoding and decoding data with different formats.
|
|
725
725
|
*
|
|
@@ -792,7 +792,7 @@ declare class Alepha {
|
|
|
792
792
|
* Starts the App.
|
|
793
793
|
*
|
|
794
794
|
* - Lock any further changes to the container.
|
|
795
|
-
* - Run "configure" hook for all services.
|
|
795
|
+
* - Run "configure" hook for all services. Primitives will be processed.
|
|
796
796
|
* - Run "start" hook for all services. Providers will connect/listen/...
|
|
797
797
|
* - Run "ready" hook for all services. This is the point where the App is ready to serve requests.
|
|
798
798
|
*
|
|
@@ -897,13 +897,13 @@ declare class Alepha {
|
|
|
897
897
|
}>;
|
|
898
898
|
services<T extends object>(base: Service<T>): Array<T>;
|
|
899
899
|
/**
|
|
900
|
-
* Get all
|
|
900
|
+
* Get all primitives of the specified type.
|
|
901
901
|
*/
|
|
902
|
-
|
|
903
|
-
[KIND]: InstantiableClass<
|
|
904
|
-
} | string): Array<
|
|
902
|
+
primitives<TPrimitive extends Primitive>(factory: {
|
|
903
|
+
[KIND]: InstantiableClass<TPrimitive>;
|
|
904
|
+
} | string): Array<TPrimitive>;
|
|
905
905
|
protected new<T extends object>(service: Service<T>, args?: any[]): T;
|
|
906
|
-
protected
|
|
906
|
+
protected processPrimitive(value: Primitive, propertyKey?: string): void;
|
|
907
907
|
}
|
|
908
908
|
interface Hook<T extends keyof Hooks = any> {
|
|
909
909
|
caller?: Service;
|
|
@@ -1030,34 +1030,6 @@ interface Hooks {
|
|
|
1030
1030
|
};
|
|
1031
1031
|
}
|
|
1032
1032
|
//#endregion
|
|
1033
|
-
//#region ../alepha/src/core/descriptors/$hook.d.ts
|
|
1034
|
-
interface HookOptions<T extends keyof Hooks> {
|
|
1035
|
-
/**
|
|
1036
|
-
* The name of the hook. "configure", "start", "ready", "stop", ...
|
|
1037
|
-
*/
|
|
1038
|
-
on: T;
|
|
1039
|
-
/**
|
|
1040
|
-
* The handler to run when the hook is triggered.
|
|
1041
|
-
*/
|
|
1042
|
-
handler: (args: Hooks[T]) => Async<any>;
|
|
1043
|
-
/**
|
|
1044
|
-
* Force the hook to run first or last on the list of hooks.
|
|
1045
|
-
*/
|
|
1046
|
-
priority?: "first" | "last";
|
|
1047
|
-
/**
|
|
1048
|
-
* Empty placeholder, not implemented yet. :-)
|
|
1049
|
-
*/
|
|
1050
|
-
before?: object | Array<object>;
|
|
1051
|
-
/**
|
|
1052
|
-
* Empty placeholder, not implemented yet. :-)
|
|
1053
|
-
*/
|
|
1054
|
-
after?: object | Array<object>;
|
|
1055
|
-
}
|
|
1056
|
-
declare class HookDescriptor<T extends keyof Hooks> extends Descriptor<HookOptions<T>> {
|
|
1057
|
-
called: number;
|
|
1058
|
-
protected onInit(): void;
|
|
1059
|
-
}
|
|
1060
|
-
//#endregion
|
|
1061
1033
|
//#region ../alepha/src/core/schemas/pageSchema.d.ts
|
|
1062
1034
|
declare const pageMetadataSchema: TObject$1<{
|
|
1063
1035
|
number: TInteger;
|
|
@@ -1090,6 +1062,34 @@ declare module "alepha" {
|
|
|
1090
1062
|
}
|
|
1091
1063
|
}
|
|
1092
1064
|
//#endregion
|
|
1065
|
+
//#region ../alepha/src/core/primitives/$hook.d.ts
|
|
1066
|
+
interface HookOptions<T extends keyof Hooks> {
|
|
1067
|
+
/**
|
|
1068
|
+
* The name of the hook. "configure", "start", "ready", "stop", ...
|
|
1069
|
+
*/
|
|
1070
|
+
on: T;
|
|
1071
|
+
/**
|
|
1072
|
+
* The handler to run when the hook is triggered.
|
|
1073
|
+
*/
|
|
1074
|
+
handler: (args: Hooks[T]) => Async<any>;
|
|
1075
|
+
/**
|
|
1076
|
+
* Force the hook to run first or last on the list of hooks.
|
|
1077
|
+
*/
|
|
1078
|
+
priority?: "first" | "last";
|
|
1079
|
+
/**
|
|
1080
|
+
* Empty placeholder, not implemented yet. :-)
|
|
1081
|
+
*/
|
|
1082
|
+
before?: object | Array<object>;
|
|
1083
|
+
/**
|
|
1084
|
+
* Empty placeholder, not implemented yet. :-)
|
|
1085
|
+
*/
|
|
1086
|
+
after?: object | Array<object>;
|
|
1087
|
+
}
|
|
1088
|
+
declare class HookPrimitive<T extends keyof Hooks> extends Primitive<HookOptions<T>> {
|
|
1089
|
+
called: number;
|
|
1090
|
+
protected onInit(): void;
|
|
1091
|
+
}
|
|
1092
|
+
//#endregion
|
|
1093
1093
|
//#region ../alepha/src/logger/schemas/logEntrySchema.d.ts
|
|
1094
1094
|
declare const logEntrySchema: TObject$1<{
|
|
1095
1095
|
level: TUnsafe<"SILENT" | "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR">;
|
|
@@ -1114,8 +1114,8 @@ declare class DateTimeProvider {
|
|
|
1114
1114
|
protected readonly timeouts: Timeout[];
|
|
1115
1115
|
protected readonly intervals: Interval[];
|
|
1116
1116
|
constructor();
|
|
1117
|
-
protected readonly onStart:
|
|
1118
|
-
protected readonly onStop:
|
|
1117
|
+
protected readonly onStart: HookPrimitive<"start">;
|
|
1118
|
+
protected readonly onStop: HookPrimitive<"stop">;
|
|
1119
1119
|
setLocale(locale: string): void;
|
|
1120
1120
|
isDateTime(value: unknown): value is DateTime;
|
|
1121
1121
|
/**
|
|
@@ -1733,8 +1733,8 @@ declare class MemoryFileStorageProvider implements FileStorageProvider {
|
|
|
1733
1733
|
protected createId(): string;
|
|
1734
1734
|
}
|
|
1735
1735
|
//#endregion
|
|
1736
|
-
//#region ../alepha/src/bucket/
|
|
1737
|
-
interface
|
|
1736
|
+
//#region ../alepha/src/bucket/primitives/$bucket.d.ts
|
|
1737
|
+
interface BucketPrimitiveOptions extends BucketFileOptions {
|
|
1738
1738
|
/**
|
|
1739
1739
|
* File storage provider configuration for the bucket.
|
|
1740
1740
|
*
|
|
@@ -1866,7 +1866,7 @@ interface BucketFileOptions {
|
|
|
1866
1866
|
*/
|
|
1867
1867
|
maxSize?: number;
|
|
1868
1868
|
}
|
|
1869
|
-
declare class
|
|
1869
|
+
declare class BucketPrimitive extends Primitive<BucketPrimitiveOptions> {
|
|
1870
1870
|
readonly provider: FileStorageProvider | MemoryFileStorageProvider;
|
|
1871
1871
|
private readonly fileSystem;
|
|
1872
1872
|
get name(): string;
|
|
@@ -1929,7 +1929,7 @@ declare module "alepha" {
|
|
|
1929
1929
|
"bucket:file:uploaded": {
|
|
1930
1930
|
id: string;
|
|
1931
1931
|
file: FileLike;
|
|
1932
|
-
bucket:
|
|
1932
|
+
bucket: BucketPrimitive;
|
|
1933
1933
|
options: BucketFileOptions;
|
|
1934
1934
|
};
|
|
1935
1935
|
/**
|
|
@@ -1937,14 +1937,14 @@ declare module "alepha" {
|
|
|
1937
1937
|
*/
|
|
1938
1938
|
"bucket:file:deleted": {
|
|
1939
1939
|
id: string;
|
|
1940
|
-
bucket:
|
|
1940
|
+
bucket: BucketPrimitive;
|
|
1941
1941
|
};
|
|
1942
1942
|
}
|
|
1943
1943
|
}
|
|
1944
1944
|
/**
|
|
1945
|
-
* Provides file storage capabilities through declarative bucket
|
|
1945
|
+
* Provides file storage capabilities through declarative bucket primitives with support for multiple storage backends.
|
|
1946
1946
|
*
|
|
1947
|
-
* The bucket module enables unified file operations across different storage systems using the `$bucket`
|
|
1947
|
+
* The bucket module enables unified file operations across different storage systems using the `$bucket` primitive
|
|
1948
1948
|
* on class properties. It abstracts storage provider differences, offering consistent APIs for local filesystem,
|
|
1949
1949
|
* cloud storage, or in-memory storage for testing environments.
|
|
1950
1950
|
*
|
|
@@ -1981,7 +1981,7 @@ declare class VercelFileStorageProvider implements FileStorageProvider {
|
|
|
1981
1981
|
protected readonly fileDetector: FileDetector;
|
|
1982
1982
|
protected readonly stores: Set<string>;
|
|
1983
1983
|
protected readonly vercelBlobApi: VercelBlobApi;
|
|
1984
|
-
protected readonly onStart:
|
|
1984
|
+
protected readonly onStart: HookPrimitive<"start">;
|
|
1985
1985
|
convertName(name: string): string;
|
|
1986
1986
|
protected createId(mimeType: string): string;
|
|
1987
1987
|
upload(bucketName: string, file: FileLike, fileId?: string): Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ var VercelFileStorageProvider = class {
|
|
|
30
30
|
onStart = $hook({
|
|
31
31
|
on: "start",
|
|
32
32
|
handler: async () => {
|
|
33
|
-
for (const bucket of this.alepha.
|
|
33
|
+
for (const bucket of this.alepha.primitives($bucket)) {
|
|
34
34
|
if (bucket.provider !== this) continue;
|
|
35
35
|
const storeName = this.convertName(bucket.name);
|
|
36
36
|
this.log.debug(`Prepare store '${storeName}' ...`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 type { Readable } from \"node:stream\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\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 { FileDetector, FileSystemProvider } 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\" {\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(FileSystemProvider);\n protected readonly fileDetector = $inject(FileDetector);\n protected readonly stores: Set<string> = new Set();\n protected readonly vercelBlobApi = $inject(VercelBlobApi);\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(`Blob storage '${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(mimeType: string): string {\n const ext = this.fileDetector.getExtensionFromMimeType(mimeType);\n return `${crypto.randomUUID()}.${ext}`;\n }\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n fileId ??= this.createId(file.type);\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 const contentBuffer = Buffer.from(await file.arrayBuffer());\n\n const result = await this.vercelBlobApi.put(\n pathname,\n contentBuffer 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 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 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 const mimeType = this.fileDetector.getContentType(fileId);\n\n return this.fileSystem.createFile({\n buffer: Buffer.from(arrayBuffer),\n name: fileId,\n type: mimeType,\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 { $module } from \"alepha\";\nimport { AlephaBucket, FileStorageProvider } from \"alepha/bucket\";\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":";;;;;;;;AAEA,IAAa,gBAAb,MAA2B;CACzB,MAAkB;CAClB,OAAoB;CACpB,MAAkB;;;;;ACgBpB,MAAM,YAAY,EAAE,OAAO,EACzB,uBAAuB,EAAE,KAAK,EAC5B,MAAM,QACP,CAAC,EACH,CAAC;;;;AASF,IAAa,4BAAb,MAAsE;CACpE,AAAmB,MAAM,SAAS;CAClC,AAAmB,MAAM,KAAK,UAAU;CACxC,AAAmB,SAAS,QAAQ,OAAO;CAC3C,AAAmB,OAAO,QAAQ,iBAAiB;CACnD,AAAmB,aAAa,QAAQ,mBAAmB;CAC3D,AAAmB,eAAe,QAAQ,aAAa;CACvD,AAAmB,yBAAsB,IAAI,KAAK;CAClD,AAAmB,gBAAgB,QAAQ,cAAc;CAEzD,AAAmB,UAAU,MAAM;EACjC,IAAI;EACJ,SAAS,YAAY;AACnB,QAAK,MAAM,UAAU,KAAK,OAAO,YAAY,QAAQ,EAAE;AACrD,QAAI,OAAO,aAAa,KACtB;IAGF,MAAM,YAAY,KAAK,YAAY,OAAO,KAAK;AAE/C,SAAK,IAAI,MAAM,kBAAkB,UAAU,OAAO;AAIlD,SAAK,OAAO,IAAI,UAAU;AAE1B,SAAK,IAAI,KAAK,iBAAiB,OAAO,KAAK,MAAM;;;EAGtD,CAAC;CAEF,AAAO,YAAY,MAAsB;AAEvC,SAAO,KAAK,WAAW,KAAK,IAAI,CAAC,aAAa;;CAGhD,AAAU,SAAS,UAA0B;EAC3C,MAAM,MAAM,KAAK,aAAa,yBAAyB,SAAS;AAChE,SAAO,GAAG,OAAO,YAAY,CAAC,GAAG;;CAGnC,MAAa,OACX,YACA,MACA,QACiB;AACjB,aAAW,KAAK,SAAS,KAAK,KAAK;AAEnC,OAAK,IAAI,MACP,mBAAmB,KAAK,KAAK,eAAe,WAAW,aAAa,OAAO,MAC5E;EAGD,MAAM,WAAW,GADC,KAAK,YAAY,WAAW,CAChB,GAAG;AAEjC,MAAI;GACF,MAAM,gBAAgB,OAAO,KAAK,MAAM,KAAK,aAAa,CAAC;GAE3D,MAAM,SAAS,MAAM,KAAK,cAAc,IACtC,UACA,eACA;IACE,QAAQ;IACR,aAAa,KAAK,QAAQ;IAC1B,OAAO,KAAK,IAAI;IAChB,gBAAgB;IACjB,CACF;AAED,QAAK,IAAI,MAAM,+BAA+B,OAAO,MAAM;AAC3D,UAAO;WACA,OAAO;AACd,QAAK,IAAI,MAAM,0BAA0B,QAAQ;AACjD,OAAI,iBAAiB,MACnB,OAAM,IAAI,YAAY,kBAAkB,MAAM,WAAW,EACvD,OAAO,OACR,CAAC;AAGJ,SAAM;;;CAIV,MAAa,SAAS,YAAoB,QAAmC;AAC3E,OAAK,IAAI,MACP,qBAAqB,OAAO,iBAAiB,WAAW,MACzD;EAGD,MAAM,WAAW,GADC,KAAK,YAAY,WAAW,CAChB,GAAG;AAEjC,MAAI;GACF,MAAM,aAAa,MAAM,KAAK,cAAc,KAAK,UAAU,EACzD,OAAO,KAAK,IAAI,uBACjB,CAAC;AAEF,OAAI,CAAC,WACH,OAAM,IAAI,kBACR,SAAS,OAAO,yBAAyB,WAAW,GACrD;GAGH,MAAM,WAAW,MAAM,MAAM,WAAW,IAAI;AAE5C,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,kBACR,yBAAyB,SAAS,aACnC;GAGH,MAAM,cAAc,MAAM,SAAS,aAAa;AAChD,OAAI,CAAC,YACH,OAAM,IAAI,kBAAkB,uCAAuC;GAGrE,MAAM,WAAW,KAAK,aAAa,eAAe,OAAO;AAEzD,UAAO,KAAK,WAAW,WAAW;IAChC,QAAQ,OAAO,KAAK,YAAY;IAChC,MAAM;IACN,MAAM;IACP,CAAC;WACK,OAAO;AACd,OAAI,iBAAiB,kBACnB,OAAM;AAGR,QAAK,IAAI,MAAM,4BAA4B,QAAQ;AACnD,OAAI,iBAAiB,MACnB,OAAM,IAAI,kBAAkB,0BAA0B,EAAE,OAAO,OAAO,CAAC;AAGzE,SAAM;;;CAIV,MAAa,OAAO,YAAoB,QAAkC;AACxE,OAAK,IAAI,MACP,+BAA+B,OAAO,eAAe,WAAW,MACjE;EAGD,MAAM,WAAW,GADC,KAAK,YAAY,WAAW,CAChB,GAAG;AAEjC,MAAI;AAIF,UAHe,MAAM,KAAK,cAAc,KAAK,UAAU,EACrD,OAAO,KAAK,IAAI,uBACjB,CAAC,KACgB;WACX,OAAO;AAEd,UAAO;;;CAIX,MAAa,OAAO,YAAoB,QAA+B;AACrE,OAAK,IAAI,MAAM,kBAAkB,OAAO,iBAAiB,WAAW,MAAM;EAG1E,MAAM,WAAW,GADC,KAAK,YAAY,WAAW,CAChB,GAAG;AAEjC,MAAI;AACF,SAAM,KAAK,cAAc,IAAI,UAAU,EACrC,OAAO,KAAK,IAAI,uBACjB,CAAC;WACK,OAAO;AACd,QAAK,IAAI,MAAM,0BAA0B,QAAQ;AACjD,OAAI,iBAAiB,MACnB,OAAM,IAAI,kBAAkB,uBAAuB,EAAE,OAAO,OAAO,CAAC;AAEtE,SAAM;;;;;;;;;;;;;AC5LZ,MAAa,qBAAqB,QAAQ;CACxC,MAAM;CACN,UAAU,CAAC,0BAA0B;CACrC,WAAW,WACT,OACG,KAAK;EACJ,UAAU;EACV,SAAS;EACT,KAAK;EACN,CAAC,CACD,KAAK,aAAa;CACxB,CAAC"}
|
|
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 type { Readable } from \"node:stream\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\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 { FileDetector, FileSystemProvider } 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\" {\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(FileSystemProvider);\n protected readonly fileDetector = $inject(FileDetector);\n protected readonly stores: Set<string> = new Set();\n protected readonly vercelBlobApi = $inject(VercelBlobApi);\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 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(`Blob storage '${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(mimeType: string): string {\n const ext = this.fileDetector.getExtensionFromMimeType(mimeType);\n return `${crypto.randomUUID()}.${ext}`;\n }\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n fileId ??= this.createId(file.type);\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 const contentBuffer = Buffer.from(await file.arrayBuffer());\n\n const result = await this.vercelBlobApi.put(\n pathname,\n contentBuffer 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 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 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 const mimeType = this.fileDetector.getContentType(fileId);\n\n return this.fileSystem.createFile({\n buffer: Buffer.from(arrayBuffer),\n name: fileId,\n type: mimeType,\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 { $module } from \"alepha\";\nimport { AlephaBucket, FileStorageProvider } from \"alepha/bucket\";\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": "Nicolas Foures",
|
|
13
|
-
"version": "0.13.
|
|
13
|
+
"version": "0.13.2",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=22.0.0"
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^24.10.1",
|
|
30
|
-
"alepha": "0.13.
|
|
30
|
+
"alepha": "0.13.2",
|
|
31
31
|
"tsdown": "^0.16.7",
|
|
32
32
|
"vitest": "^4.0.14"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"alepha": "0.13.
|
|
35
|
+
"alepha": "0.13.2"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"lint": "alepha lint",
|
|
@@ -45,7 +45,7 @@ export class VercelFileStorageProvider implements FileStorageProvider {
|
|
|
45
45
|
protected readonly onStart = $hook({
|
|
46
46
|
on: "start",
|
|
47
47
|
handler: async () => {
|
|
48
|
-
for (const bucket of this.alepha.
|
|
48
|
+
for (const bucket of this.alepha.primitives($bucket)) {
|
|
49
49
|
if (bucket.provider !== this) {
|
|
50
50
|
continue;
|
|
51
51
|
}
|
package/dist/chunk-MC1wKe0N.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
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") {
|
|
10
|
-
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
-
__defProp(to, key, {
|
|
14
|
-
get: ((k) => from[k]).bind(null, key),
|
|
15
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return to;
|
|
21
|
-
};
|
|
22
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
-
value: mod,
|
|
24
|
-
enumerable: true
|
|
25
|
-
}) : target, mod));
|
|
26
|
-
|
|
27
|
-
//#endregion
|
package/dist/index.cjs
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
let alepha = require("alepha");
|
|
2
|
-
let alepha_bucket = require("alepha/bucket");
|
|
3
|
-
let alepha_datetime = require("alepha/datetime");
|
|
4
|
-
let alepha_file = require("alepha/file");
|
|
5
|
-
let alepha_logger = require("alepha/logger");
|
|
6
|
-
let __vercel_blob = require("@vercel/blob");
|
|
7
|
-
|
|
8
|
-
//#region src/providers/VercelBlobProvider.ts
|
|
9
|
-
var VercelBlobApi = class {
|
|
10
|
-
put = __vercel_blob.put;
|
|
11
|
-
head = __vercel_blob.head;
|
|
12
|
-
del = __vercel_blob.del;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region src/providers/VercelFileStorageProvider.ts
|
|
17
|
-
const envSchema = alepha.t.object({ BLOB_READ_WRITE_TOKEN: alepha.t.text({ size: "long" }) });
|
|
18
|
-
/**
|
|
19
|
-
* Vercel Blob Storage implementation of File Storage Provider.
|
|
20
|
-
*/
|
|
21
|
-
var VercelFileStorageProvider = class {
|
|
22
|
-
log = (0, alepha_logger.$logger)();
|
|
23
|
-
env = (0, alepha.$env)(envSchema);
|
|
24
|
-
alepha = (0, alepha.$inject)(alepha.Alepha);
|
|
25
|
-
time = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
|
|
26
|
-
fileSystem = (0, alepha.$inject)(alepha_file.FileSystemProvider);
|
|
27
|
-
fileDetector = (0, alepha.$inject)(alepha_file.FileDetector);
|
|
28
|
-
stores = /* @__PURE__ */ new Set();
|
|
29
|
-
vercelBlobApi = (0, alepha.$inject)(VercelBlobApi);
|
|
30
|
-
onStart = (0, alepha.$hook)({
|
|
31
|
-
on: "start",
|
|
32
|
-
handler: async () => {
|
|
33
|
-
for (const bucket of this.alepha.descriptors(alepha_bucket.$bucket)) {
|
|
34
|
-
if (bucket.provider !== this) continue;
|
|
35
|
-
const storeName = this.convertName(bucket.name);
|
|
36
|
-
this.log.debug(`Prepare store '${storeName}' ...`);
|
|
37
|
-
this.stores.add(storeName);
|
|
38
|
-
this.log.info(`Blob storage '${bucket.name}' OK`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
convertName(name) {
|
|
43
|
-
return name.replaceAll("/", "-").toLowerCase();
|
|
44
|
-
}
|
|
45
|
-
createId(mimeType) {
|
|
46
|
-
const ext = this.fileDetector.getExtensionFromMimeType(mimeType);
|
|
47
|
-
return `${crypto.randomUUID()}.${ext}`;
|
|
48
|
-
}
|
|
49
|
-
async upload(bucketName, file, fileId) {
|
|
50
|
-
fileId ??= this.createId(file.type);
|
|
51
|
-
this.log.trace(`Uploading file '${file.name}' to bucket '${bucketName}' with id '${fileId}'...`);
|
|
52
|
-
const pathname = `${this.convertName(bucketName)}/${fileId}`;
|
|
53
|
-
try {
|
|
54
|
-
const contentBuffer = Buffer.from(await file.arrayBuffer());
|
|
55
|
-
const result = await this.vercelBlobApi.put(pathname, contentBuffer, {
|
|
56
|
-
access: "public",
|
|
57
|
-
contentType: file.type || "application/octet-stream",
|
|
58
|
-
token: this.env.BLOB_READ_WRITE_TOKEN,
|
|
59
|
-
allowOverwrite: true
|
|
60
|
-
});
|
|
61
|
-
this.log.trace(`File uploaded successfully: ${result.url}`);
|
|
62
|
-
return fileId;
|
|
63
|
-
} catch (error) {
|
|
64
|
-
this.log.error(`Failed to upload file: ${error}`);
|
|
65
|
-
if (error instanceof Error) throw new alepha.AlephaError(`Upload failed: ${error.message}`, { cause: error });
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
async download(bucketName, fileId) {
|
|
70
|
-
this.log.trace(`Downloading file '${fileId}' from bucket '${bucketName}'...`);
|
|
71
|
-
const pathname = `${this.convertName(bucketName)}/${fileId}`;
|
|
72
|
-
try {
|
|
73
|
-
const headResult = await this.vercelBlobApi.head(pathname, { token: this.env.BLOB_READ_WRITE_TOKEN });
|
|
74
|
-
if (!headResult) throw new alepha_bucket.FileNotFoundError(`File '${fileId}' not found in bucket '${bucketName}'`);
|
|
75
|
-
const response = await fetch(headResult.url);
|
|
76
|
-
if (!response.ok) throw new alepha_bucket.FileNotFoundError(`Failed to fetch file: ${response.statusText}`);
|
|
77
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
78
|
-
if (!arrayBuffer) throw new alepha_bucket.FileNotFoundError("File not found - empty response body");
|
|
79
|
-
const mimeType = this.fileDetector.getContentType(fileId);
|
|
80
|
-
return this.fileSystem.createFile({
|
|
81
|
-
buffer: Buffer.from(arrayBuffer),
|
|
82
|
-
name: fileId,
|
|
83
|
-
type: mimeType
|
|
84
|
-
});
|
|
85
|
-
} catch (error) {
|
|
86
|
-
if (error instanceof alepha_bucket.FileNotFoundError) throw error;
|
|
87
|
-
this.log.error(`Failed to download file: ${error}`);
|
|
88
|
-
if (error instanceof Error) throw new alepha_bucket.FileNotFoundError("Error downloading file", { cause: error });
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async exists(bucketName, fileId) {
|
|
93
|
-
this.log.trace(`Checking existence of file '${fileId}' in bucket '${bucketName}'...`);
|
|
94
|
-
const pathname = `${this.convertName(bucketName)}/${fileId}`;
|
|
95
|
-
try {
|
|
96
|
-
return await this.vercelBlobApi.head(pathname, { token: this.env.BLOB_READ_WRITE_TOKEN }) !== null;
|
|
97
|
-
} catch (error) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async delete(bucketName, fileId) {
|
|
102
|
-
this.log.trace(`Deleting file '${fileId}' from bucket '${bucketName}'...`);
|
|
103
|
-
const pathname = `${this.convertName(bucketName)}/${fileId}`;
|
|
104
|
-
try {
|
|
105
|
-
await this.vercelBlobApi.del(pathname, { token: this.env.BLOB_READ_WRITE_TOKEN });
|
|
106
|
-
} catch (error) {
|
|
107
|
-
this.log.error(`Failed to delete file: ${error}`);
|
|
108
|
-
if (error instanceof Error) throw new alepha_bucket.FileNotFoundError("Error deleting file", { cause: error });
|
|
109
|
-
throw error;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
//#endregion
|
|
115
|
-
//#region src/index.ts
|
|
116
|
-
/**
|
|
117
|
-
* Plugin for Alepha Bucket that provides Vercel Blob Storage capabilities.
|
|
118
|
-
*
|
|
119
|
-
* @see {@link VercelFileStorageProvider}
|
|
120
|
-
* @module alepha.bucket.vercel
|
|
121
|
-
*/
|
|
122
|
-
const AlephaBucketVercel = (0, alepha.$module)({
|
|
123
|
-
name: "alepha.bucket.vercel",
|
|
124
|
-
services: [VercelFileStorageProvider],
|
|
125
|
-
register: (alepha$1) => alepha$1.with({
|
|
126
|
-
optional: true,
|
|
127
|
-
provide: alepha_bucket.FileStorageProvider,
|
|
128
|
-
use: VercelFileStorageProvider
|
|
129
|
-
}).with(alepha_bucket.AlephaBucket)
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
//#endregion
|
|
133
|
-
exports.AlephaBucketVercel = AlephaBucketVercel;
|
|
134
|
-
exports.VercelFileStorageProvider = VercelFileStorageProvider;
|
|
135
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["put","head","del","t","Alepha","DateTimeProvider","FileSystemProvider","FileDetector","$bucket","AlephaError","FileNotFoundError","alepha","FileStorageProvider","AlephaBucket"],"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 type { Readable } from \"node:stream\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\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 { FileDetector, FileSystemProvider } 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\" {\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(FileSystemProvider);\n protected readonly fileDetector = $inject(FileDetector);\n protected readonly stores: Set<string> = new Set();\n protected readonly vercelBlobApi = $inject(VercelBlobApi);\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(`Blob storage '${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(mimeType: string): string {\n const ext = this.fileDetector.getExtensionFromMimeType(mimeType);\n return `${crypto.randomUUID()}.${ext}`;\n }\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n fileId ??= this.createId(file.type);\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 const contentBuffer = Buffer.from(await file.arrayBuffer());\n\n const result = await this.vercelBlobApi.put(\n pathname,\n contentBuffer 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 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 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 const mimeType = this.fileDetector.getContentType(fileId);\n\n return this.fileSystem.createFile({\n buffer: Buffer.from(arrayBuffer),\n name: fileId,\n type: mimeType,\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 { $module } from \"alepha\";\nimport { AlephaBucket, FileStorageProvider } from \"alepha/bucket\";\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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"}
|