@bloomneo/appkit 1.2.9
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/LICENSE +21 -0
- package/README.md +902 -0
- package/bin/appkit.js +71 -0
- package/bin/commands/generate.js +1050 -0
- package/bin/templates/backend/README.md.template +39 -0
- package/bin/templates/backend/api.http.template +0 -0
- package/bin/templates/backend/docs/APPKIT_CLI.md +507 -0
- package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +61 -0
- package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +2539 -0
- package/bin/templates/backend/package.json.template +34 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.http.template +29 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.route.ts.template +36 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.service.ts.template +88 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.types.ts.template +18 -0
- package/bin/templates/backend/src/api/lib/api-router.ts.template +84 -0
- package/bin/templates/backend/src/api/server.ts.template +188 -0
- package/bin/templates/backend/tsconfig.api.json.template +24 -0
- package/bin/templates/backend/tsconfig.json.template +40 -0
- package/bin/templates/feature/feature.http.template +63 -0
- package/bin/templates/feature/feature.route.ts.template +36 -0
- package/bin/templates/feature/feature.service.ts.template +81 -0
- package/bin/templates/feature/feature.types.ts.template +23 -0
- package/bin/templates/feature-db/feature.http.template +63 -0
- package/bin/templates/feature-db/feature.model.ts.template +74 -0
- package/bin/templates/feature-db/feature.route.ts.template +58 -0
- package/bin/templates/feature-db/feature.service.ts.template +231 -0
- package/bin/templates/feature-db/feature.types.ts.template +25 -0
- package/bin/templates/feature-db/schema-addition.prisma.template +9 -0
- package/bin/templates/feature-db/seeding/README.md.template +57 -0
- package/bin/templates/feature-db/seeding/feature.seed.js.template +67 -0
- package/bin/templates/feature-user/schema-addition.prisma.template +19 -0
- package/bin/templates/feature-user/user.http.template +157 -0
- package/bin/templates/feature-user/user.model.ts.template +244 -0
- package/bin/templates/feature-user/user.route.ts.template +379 -0
- package/bin/templates/feature-user/user.seed.js.template +182 -0
- package/bin/templates/feature-user/user.service.ts.template +426 -0
- package/bin/templates/feature-user/user.types.ts.template +127 -0
- package/dist/auth/auth.d.ts +182 -0
- package/dist/auth/auth.d.ts.map +1 -0
- package/dist/auth/auth.js +477 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/defaults.d.ts +104 -0
- package/dist/auth/defaults.d.ts.map +1 -0
- package/dist/auth/defaults.js +374 -0
- package/dist/auth/defaults.js.map +1 -0
- package/dist/auth/index.d.ts +70 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +94 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cache/cache.d.ts +118 -0
- package/dist/cache/cache.d.ts.map +1 -0
- package/dist/cache/cache.js +249 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/cache/defaults.d.ts +63 -0
- package/dist/cache/defaults.d.ts.map +1 -0
- package/dist/cache/defaults.js +193 -0
- package/dist/cache/defaults.js.map +1 -0
- package/dist/cache/index.d.ts +101 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +203 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/strategies/memory.d.ts +138 -0
- package/dist/cache/strategies/memory.d.ts.map +1 -0
- package/dist/cache/strategies/memory.js +348 -0
- package/dist/cache/strategies/memory.js.map +1 -0
- package/dist/cache/strategies/redis.d.ts +105 -0
- package/dist/cache/strategies/redis.d.ts.map +1 -0
- package/dist/cache/strategies/redis.js +318 -0
- package/dist/cache/strategies/redis.js.map +1 -0
- package/dist/config/config.d.ts +62 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +107 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/defaults.d.ts +44 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +217 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +105 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +163 -0
- package/dist/config/index.js.map +1 -0
- package/dist/database/adapters/mongoose.d.ts +106 -0
- package/dist/database/adapters/mongoose.d.ts.map +1 -0
- package/dist/database/adapters/mongoose.js +480 -0
- package/dist/database/adapters/mongoose.js.map +1 -0
- package/dist/database/adapters/prisma.d.ts +106 -0
- package/dist/database/adapters/prisma.d.ts.map +1 -0
- package/dist/database/adapters/prisma.js +494 -0
- package/dist/database/adapters/prisma.js.map +1 -0
- package/dist/database/defaults.d.ts +87 -0
- package/dist/database/defaults.d.ts.map +1 -0
- package/dist/database/defaults.js +271 -0
- package/dist/database/defaults.js.map +1 -0
- package/dist/database/index.d.ts +137 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +490 -0
- package/dist/database/index.js.map +1 -0
- package/dist/email/defaults.d.ts +100 -0
- package/dist/email/defaults.d.ts.map +1 -0
- package/dist/email/defaults.js +400 -0
- package/dist/email/defaults.js.map +1 -0
- package/dist/email/email.d.ts +139 -0
- package/dist/email/email.d.ts.map +1 -0
- package/dist/email/email.js +316 -0
- package/dist/email/email.js.map +1 -0
- package/dist/email/index.d.ts +176 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +251 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/strategies/console.d.ts +90 -0
- package/dist/email/strategies/console.d.ts.map +1 -0
- package/dist/email/strategies/console.js +268 -0
- package/dist/email/strategies/console.js.map +1 -0
- package/dist/email/strategies/resend.d.ts +84 -0
- package/dist/email/strategies/resend.d.ts.map +1 -0
- package/dist/email/strategies/resend.js +266 -0
- package/dist/email/strategies/resend.js.map +1 -0
- package/dist/email/strategies/smtp.d.ts +77 -0
- package/dist/email/strategies/smtp.d.ts.map +1 -0
- package/dist/email/strategies/smtp.js +286 -0
- package/dist/email/strategies/smtp.js.map +1 -0
- package/dist/error/defaults.d.ts +40 -0
- package/dist/error/defaults.d.ts.map +1 -0
- package/dist/error/defaults.js +75 -0
- package/dist/error/defaults.js.map +1 -0
- package/dist/error/error.d.ts +140 -0
- package/dist/error/error.d.ts.map +1 -0
- package/dist/error/error.js +200 -0
- package/dist/error/error.js.map +1 -0
- package/dist/error/index.d.ts +145 -0
- package/dist/error/index.d.ts.map +1 -0
- package/dist/error/index.js +145 -0
- package/dist/error/index.js.map +1 -0
- package/dist/event/defaults.d.ts +111 -0
- package/dist/event/defaults.d.ts.map +1 -0
- package/dist/event/defaults.js +378 -0
- package/dist/event/defaults.js.map +1 -0
- package/dist/event/event.d.ts +171 -0
- package/dist/event/event.d.ts.map +1 -0
- package/dist/event/event.js +391 -0
- package/dist/event/event.js.map +1 -0
- package/dist/event/index.d.ts +173 -0
- package/dist/event/index.d.ts.map +1 -0
- package/dist/event/index.js +302 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/strategies/memory.d.ts +122 -0
- package/dist/event/strategies/memory.d.ts.map +1 -0
- package/dist/event/strategies/memory.js +331 -0
- package/dist/event/strategies/memory.js.map +1 -0
- package/dist/event/strategies/redis.d.ts +115 -0
- package/dist/event/strategies/redis.d.ts.map +1 -0
- package/dist/event/strategies/redis.js +434 -0
- package/dist/event/strategies/redis.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/defaults.d.ts +67 -0
- package/dist/logger/defaults.d.ts.map +1 -0
- package/dist/logger/defaults.js +213 -0
- package/dist/logger/defaults.js.map +1 -0
- package/dist/logger/index.d.ts +84 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +101 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/logger.d.ts +165 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +843 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/logger/transports/console.d.ts +102 -0
- package/dist/logger/transports/console.d.ts.map +1 -0
- package/dist/logger/transports/console.js +276 -0
- package/dist/logger/transports/console.js.map +1 -0
- package/dist/logger/transports/database.d.ts +153 -0
- package/dist/logger/transports/database.d.ts.map +1 -0
- package/dist/logger/transports/database.js +539 -0
- package/dist/logger/transports/database.js.map +1 -0
- package/dist/logger/transports/file.d.ts +146 -0
- package/dist/logger/transports/file.d.ts.map +1 -0
- package/dist/logger/transports/file.js +464 -0
- package/dist/logger/transports/file.js.map +1 -0
- package/dist/logger/transports/http.d.ts +128 -0
- package/dist/logger/transports/http.d.ts.map +1 -0
- package/dist/logger/transports/http.js +401 -0
- package/dist/logger/transports/http.js.map +1 -0
- package/dist/logger/transports/webhook.d.ts +152 -0
- package/dist/logger/transports/webhook.d.ts.map +1 -0
- package/dist/logger/transports/webhook.js +485 -0
- package/dist/logger/transports/webhook.js.map +1 -0
- package/dist/queue/defaults.d.ts +66 -0
- package/dist/queue/defaults.d.ts.map +1 -0
- package/dist/queue/defaults.js +205 -0
- package/dist/queue/defaults.js.map +1 -0
- package/dist/queue/index.d.ts +124 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +116 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/queue.d.ts +156 -0
- package/dist/queue/queue.d.ts.map +1 -0
- package/dist/queue/queue.js +387 -0
- package/dist/queue/queue.js.map +1 -0
- package/dist/queue/transports/database.d.ts +165 -0
- package/dist/queue/transports/database.d.ts.map +1 -0
- package/dist/queue/transports/database.js +595 -0
- package/dist/queue/transports/database.js.map +1 -0
- package/dist/queue/transports/memory.d.ts +143 -0
- package/dist/queue/transports/memory.d.ts.map +1 -0
- package/dist/queue/transports/memory.js +415 -0
- package/dist/queue/transports/memory.js.map +1 -0
- package/dist/queue/transports/redis.d.ts +203 -0
- package/dist/queue/transports/redis.d.ts.map +1 -0
- package/dist/queue/transports/redis.js +744 -0
- package/dist/queue/transports/redis.js.map +1 -0
- package/dist/security/defaults.d.ts +64 -0
- package/dist/security/defaults.d.ts.map +1 -0
- package/dist/security/defaults.js +159 -0
- package/dist/security/defaults.js.map +1 -0
- package/dist/security/index.d.ts +110 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +160 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/security.d.ts +138 -0
- package/dist/security/security.d.ts.map +1 -0
- package/dist/security/security.js +419 -0
- package/dist/security/security.js.map +1 -0
- package/dist/storage/defaults.d.ts +79 -0
- package/dist/storage/defaults.d.ts.map +1 -0
- package/dist/storage/defaults.js +358 -0
- package/dist/storage/defaults.js.map +1 -0
- package/dist/storage/index.d.ts +153 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +242 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/storage.d.ts +151 -0
- package/dist/storage/storage.d.ts.map +1 -0
- package/dist/storage/storage.js +439 -0
- package/dist/storage/storage.js.map +1 -0
- package/dist/storage/strategies/local.d.ts +117 -0
- package/dist/storage/strategies/local.d.ts.map +1 -0
- package/dist/storage/strategies/local.js +368 -0
- package/dist/storage/strategies/local.js.map +1 -0
- package/dist/storage/strategies/r2.d.ts +130 -0
- package/dist/storage/strategies/r2.d.ts.map +1 -0
- package/dist/storage/strategies/r2.js +470 -0
- package/dist/storage/strategies/r2.js.map +1 -0
- package/dist/storage/strategies/s3.d.ts +121 -0
- package/dist/storage/strategies/s3.d.ts.map +1 -0
- package/dist/storage/strategies/s3.js +461 -0
- package/dist/storage/strategies/s3.js.map +1 -0
- package/dist/util/defaults.d.ts +77 -0
- package/dist/util/defaults.d.ts.map +1 -0
- package/dist/util/defaults.js +193 -0
- package/dist/util/defaults.js.map +1 -0
- package/dist/util/index.d.ts +97 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +165 -0
- package/dist/util/index.js.map +1 -0
- package/dist/util/util.d.ts +145 -0
- package/dist/util/util.d.ts.map +1 -0
- package/dist/util/util.js +481 -0
- package/dist/util/util.js.map +1 -0
- package/package.json +234 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core storage class with automatic strategy selection and ultra-simple API
|
|
3
|
+
* @module @bloomneo/appkit/storage
|
|
4
|
+
* @file src/storage/storage.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Building apps that need file storage with automatic Local/S3/R2 selection
|
|
7
|
+
* @llm-rule AVOID: Using directly - always get instance via storageClass.get()
|
|
8
|
+
* @llm-rule NOTE: Auto-detects Local vs S3 vs R2 based on environment variables
|
|
9
|
+
*/
|
|
10
|
+
import { LocalStrategy } from './strategies/local.js';
|
|
11
|
+
import { S3Strategy } from './strategies/s3.js';
|
|
12
|
+
import { R2Strategy } from './strategies/r2.js';
|
|
13
|
+
/**
|
|
14
|
+
* Storage class with automatic strategy selection and ultra-simple API
|
|
15
|
+
*/
|
|
16
|
+
export class StorageClass {
|
|
17
|
+
config;
|
|
18
|
+
strategy;
|
|
19
|
+
connected = false;
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.strategy = this.createStrategy();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates appropriate strategy based on configuration
|
|
26
|
+
* @llm-rule WHEN: Storage initialization - selects Local, S3, or R2 based on environment
|
|
27
|
+
* @llm-rule AVOID: Manual strategy creation - configuration handles strategy selection
|
|
28
|
+
*/
|
|
29
|
+
createStrategy() {
|
|
30
|
+
switch (this.config.strategy) {
|
|
31
|
+
case 'local':
|
|
32
|
+
return new LocalStrategy(this.config);
|
|
33
|
+
case 's3':
|
|
34
|
+
return new S3Strategy(this.config);
|
|
35
|
+
case 'r2':
|
|
36
|
+
return new R2Strategy(this.config);
|
|
37
|
+
default:
|
|
38
|
+
throw new Error(`Unknown storage strategy: ${this.config.strategy}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Connects to storage backend with automatic setup
|
|
43
|
+
* @llm-rule WHEN: Storage initialization or reconnection after failure
|
|
44
|
+
* @llm-rule AVOID: Manual connection management - this handles connection state
|
|
45
|
+
*/
|
|
46
|
+
async connect() {
|
|
47
|
+
if (this.connected)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
// Strategy-specific connection (S3/R2 connect, Local creates dirs)
|
|
51
|
+
if ('connect' in this.strategy) {
|
|
52
|
+
await this.strategy.connect();
|
|
53
|
+
}
|
|
54
|
+
this.connected = true;
|
|
55
|
+
if (this.config.environment.isDevelopment) {
|
|
56
|
+
console.log(`✅ [AppKit] Storage system connected using ${this.config.strategy} strategy`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error(`❌ [AppKit] Storage connection failed:`, error.message);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Disconnects from storage backend gracefully
|
|
66
|
+
* @llm-rule WHEN: App shutdown or storage cleanup
|
|
67
|
+
* @llm-rule AVOID: Abrupt disconnection - graceful shutdown prevents data loss
|
|
68
|
+
*/
|
|
69
|
+
async disconnect() {
|
|
70
|
+
if (!this.connected)
|
|
71
|
+
return;
|
|
72
|
+
try {
|
|
73
|
+
await this.strategy.disconnect();
|
|
74
|
+
this.connected = false;
|
|
75
|
+
if (this.config.environment.isDevelopment) {
|
|
76
|
+
console.log(`👋 [AppKit] Storage system disconnected`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error(`⚠️ [AppKit] Storage disconnect error:`, error.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Stores file with automatic validation and type detection
|
|
85
|
+
* @llm-rule WHEN: Uploading files to storage backend
|
|
86
|
+
* @llm-rule AVOID: Manual file validation - this handles size, type, and path validation
|
|
87
|
+
* @llm-rule NOTE: Returns the key/path where file was stored
|
|
88
|
+
*/
|
|
89
|
+
async put(key, data, options) {
|
|
90
|
+
this.validateKey(key);
|
|
91
|
+
await this.ensureConnected();
|
|
92
|
+
try {
|
|
93
|
+
// Convert data to Buffer for consistency
|
|
94
|
+
const buffer = this.normalizeData(data);
|
|
95
|
+
// Validate file size
|
|
96
|
+
this.validateFileSize(buffer);
|
|
97
|
+
// Auto-detect content type if not provided
|
|
98
|
+
const contentType = options?.contentType || this.detectContentType(key, buffer);
|
|
99
|
+
// Validate file type
|
|
100
|
+
this.validateFileType(contentType);
|
|
101
|
+
// Store via strategy with enhanced options
|
|
102
|
+
const enhancedOptions = {
|
|
103
|
+
...options,
|
|
104
|
+
contentType,
|
|
105
|
+
};
|
|
106
|
+
const result = await this.strategy.put(key, buffer, enhancedOptions);
|
|
107
|
+
// Log in development
|
|
108
|
+
if (this.config.environment.isDevelopment) {
|
|
109
|
+
console.log(`📤 [AppKit] File stored: ${key} (${buffer.length} bytes, ${contentType})`);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error(`[AppKit] Storage put error for "${key}":`, error.message);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Retrieves file with automatic error handling
|
|
120
|
+
* @llm-rule WHEN: Downloading files from storage backend
|
|
121
|
+
* @llm-rule AVOID: Manual error handling - this provides consistent error messages
|
|
122
|
+
*/
|
|
123
|
+
async get(key) {
|
|
124
|
+
this.validateKey(key);
|
|
125
|
+
await this.ensureConnected();
|
|
126
|
+
try {
|
|
127
|
+
const result = await this.strategy.get(key);
|
|
128
|
+
// Log in development
|
|
129
|
+
if (this.config.environment.isDevelopment) {
|
|
130
|
+
console.log(`📥 [AppKit] File retrieved: ${key} (${result.length} bytes)`);
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.error(`[AppKit] Storage get error for "${key}":`, error.message);
|
|
136
|
+
throw new Error(`File not found: ${key}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Deletes file with confirmation
|
|
141
|
+
* @llm-rule WHEN: Removing files from storage backend
|
|
142
|
+
* @llm-rule AVOID: Silent failures - this confirms deletion success
|
|
143
|
+
*/
|
|
144
|
+
async delete(key) {
|
|
145
|
+
this.validateKey(key);
|
|
146
|
+
await this.ensureConnected();
|
|
147
|
+
try {
|
|
148
|
+
const result = await this.strategy.delete(key);
|
|
149
|
+
// Log in development
|
|
150
|
+
if (this.config.environment.isDevelopment) {
|
|
151
|
+
console.log(`🗑️ [AppKit] File deleted: ${key} (success: ${result})`);
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.error(`[AppKit] Storage delete error for "${key}":`, error.message);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Lists files with prefix filtering and metadata
|
|
162
|
+
* @llm-rule WHEN: Browsing files or implementing file managers
|
|
163
|
+
* @llm-rule AVOID: Loading all files - use prefix filtering for performance
|
|
164
|
+
*/
|
|
165
|
+
async list(prefix = '', limit) {
|
|
166
|
+
await this.ensureConnected();
|
|
167
|
+
try {
|
|
168
|
+
const files = await this.strategy.list(prefix);
|
|
169
|
+
// Apply limit if specified
|
|
170
|
+
const result = limit ? files.slice(0, limit) : files;
|
|
171
|
+
// Log in development
|
|
172
|
+
if (this.config.environment.isDevelopment) {
|
|
173
|
+
console.log(`📋 [AppKit] Files listed: ${prefix}* (${result.length} files)`);
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error(`[AppKit] Storage list error for prefix "${prefix}":`, error.message);
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Gets public URL for file access
|
|
184
|
+
* @llm-rule WHEN: Generating URLs for file access in web applications
|
|
185
|
+
* @llm-rule AVOID: Hardcoding URLs - this handles CDN and strategy-specific URLs
|
|
186
|
+
*/
|
|
187
|
+
url(key) {
|
|
188
|
+
this.validateKey(key);
|
|
189
|
+
try {
|
|
190
|
+
const url = this.strategy.url(key);
|
|
191
|
+
// Log in development
|
|
192
|
+
if (this.config.environment.isDevelopment) {
|
|
193
|
+
console.log(`🔗 [AppKit] URL generated: ${key} → ${url}`);
|
|
194
|
+
}
|
|
195
|
+
return url;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
console.error(`[AppKit] URL generation error for "${key}":`, error.message);
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Generates signed URL for temporary access
|
|
204
|
+
* @llm-rule WHEN: Creating temporary download links or private file access
|
|
205
|
+
* @llm-rule AVOID: Permanent URLs for private files - use signed URLs with expiration
|
|
206
|
+
*/
|
|
207
|
+
async signedUrl(key, expiresIn = 3600) {
|
|
208
|
+
this.validateKey(key);
|
|
209
|
+
await this.ensureConnected();
|
|
210
|
+
try {
|
|
211
|
+
if (!this.strategy.signedUrl) {
|
|
212
|
+
throw new Error(`Signed URLs not supported with ${this.config.strategy} strategy`);
|
|
213
|
+
}
|
|
214
|
+
const url = await this.strategy.signedUrl(key, expiresIn);
|
|
215
|
+
// Log in development
|
|
216
|
+
if (this.config.environment.isDevelopment) {
|
|
217
|
+
console.log(`🔐 [AppKit] Signed URL generated: ${key} (expires in ${expiresIn}s)`);
|
|
218
|
+
}
|
|
219
|
+
return url;
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
console.error(`[AppKit] Signed URL error for "${key}":`, error.message);
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Checks if file exists without downloading
|
|
228
|
+
* @llm-rule WHEN: Validating file existence before operations
|
|
229
|
+
* @llm-rule AVOID: Downloading files just to check existence - this is more efficient
|
|
230
|
+
*/
|
|
231
|
+
async exists(key) {
|
|
232
|
+
this.validateKey(key);
|
|
233
|
+
await this.ensureConnected();
|
|
234
|
+
try {
|
|
235
|
+
const result = await this.strategy.exists(key);
|
|
236
|
+
// Log in development
|
|
237
|
+
if (this.config.environment.isDevelopment) {
|
|
238
|
+
console.log(`🔍 [AppKit] File exists check: ${key} → ${result}`);
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
console.error(`[AppKit] Exists check error for "${key}":`, error.message);
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Copies file from one location to another
|
|
249
|
+
* @llm-rule WHEN: Duplicating files or moving between folders
|
|
250
|
+
* @llm-rule AVOID: Download and upload - this uses efficient copy operations when possible
|
|
251
|
+
*/
|
|
252
|
+
async copy(sourceKey, destKey) {
|
|
253
|
+
this.validateKey(sourceKey);
|
|
254
|
+
this.validateKey(destKey);
|
|
255
|
+
await this.ensureConnected();
|
|
256
|
+
try {
|
|
257
|
+
// Strategy-specific copy if available, otherwise download/upload
|
|
258
|
+
if ('copy' in this.strategy) {
|
|
259
|
+
return await this.strategy.copy(sourceKey, destKey);
|
|
260
|
+
}
|
|
261
|
+
// Fallback: download and upload
|
|
262
|
+
const data = await this.get(sourceKey);
|
|
263
|
+
return await this.put(destKey, data);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
console.error(`[AppKit] Copy error "${sourceKey}" → "${destKey}":`, error.message);
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Gets current storage strategy name for debugging
|
|
272
|
+
* @llm-rule WHEN: Debugging or health checks to see which strategy is active
|
|
273
|
+
* @llm-rule AVOID: Using for application logic - storage should be transparent
|
|
274
|
+
*/
|
|
275
|
+
getStrategy() {
|
|
276
|
+
return this.config.strategy;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Gets storage configuration summary for debugging
|
|
280
|
+
* @llm-rule WHEN: Health checks or debugging storage configuration
|
|
281
|
+
* @llm-rule AVOID: Exposing sensitive details - this only shows safe info
|
|
282
|
+
*/
|
|
283
|
+
getConfig() {
|
|
284
|
+
// Get config values based on strategy
|
|
285
|
+
let maxFileSize = 52428800; // 50MB default
|
|
286
|
+
let allowedTypes = [];
|
|
287
|
+
if (this.config.strategy === 'local' && this.config.local) {
|
|
288
|
+
maxFileSize = this.config.local.maxFileSize;
|
|
289
|
+
allowedTypes = this.config.local.allowedTypes;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
// S3 and R2 use global config or defaults
|
|
293
|
+
maxFileSize = 52428800; // 50MB default for cloud
|
|
294
|
+
allowedTypes = ['*']; // Allow all for cloud storage
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
strategy: this.config.strategy,
|
|
298
|
+
connected: this.connected,
|
|
299
|
+
maxFileSize,
|
|
300
|
+
allowedTypes,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// Private helper methods
|
|
304
|
+
/**
|
|
305
|
+
* Ensures storage system is connected before operations
|
|
306
|
+
*/
|
|
307
|
+
async ensureConnected() {
|
|
308
|
+
if (!this.connected) {
|
|
309
|
+
await this.connect();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Validates storage key format and security
|
|
314
|
+
*/
|
|
315
|
+
validateKey(key) {
|
|
316
|
+
if (!key || typeof key !== 'string') {
|
|
317
|
+
throw new Error('Storage key must be a non-empty string');
|
|
318
|
+
}
|
|
319
|
+
if (key.length > 1024) {
|
|
320
|
+
throw new Error('Storage key too long (max 1024 characters)');
|
|
321
|
+
}
|
|
322
|
+
// Security: prevent path traversal
|
|
323
|
+
if (key.includes('..') || key.includes('//')) {
|
|
324
|
+
throw new Error('Storage key contains invalid path components');
|
|
325
|
+
}
|
|
326
|
+
// Normalize separators
|
|
327
|
+
if (key.includes('\\')) {
|
|
328
|
+
throw new Error('Storage key must use forward slashes (/) as separators');
|
|
329
|
+
}
|
|
330
|
+
// Remove leading slash for consistency
|
|
331
|
+
if (key.startsWith('/')) {
|
|
332
|
+
throw new Error('Storage key should not start with forward slash');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Normalizes input data to Buffer
|
|
337
|
+
*/
|
|
338
|
+
normalizeData(data) {
|
|
339
|
+
if (Buffer.isBuffer(data)) {
|
|
340
|
+
return data;
|
|
341
|
+
}
|
|
342
|
+
if (data instanceof Uint8Array) {
|
|
343
|
+
return Buffer.from(data);
|
|
344
|
+
}
|
|
345
|
+
if (typeof data === 'string') {
|
|
346
|
+
return Buffer.from(data, 'utf8');
|
|
347
|
+
}
|
|
348
|
+
throw new Error('Data must be Buffer, Uint8Array, or string');
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Validates file size against configured limits
|
|
352
|
+
*/
|
|
353
|
+
validateFileSize(buffer) {
|
|
354
|
+
let maxSize = 52428800; // 50MB default
|
|
355
|
+
if (this.config.strategy === 'local' && this.config.local) {
|
|
356
|
+
maxSize = this.config.local.maxFileSize;
|
|
357
|
+
}
|
|
358
|
+
// S3 and R2 use default limit for now
|
|
359
|
+
if (buffer.length > maxSize) {
|
|
360
|
+
const maxMB = Math.round(maxSize / 1048576);
|
|
361
|
+
const fileMB = Math.round(buffer.length / 1048576);
|
|
362
|
+
throw new Error(`File too large: ${fileMB}MB (max: ${maxMB}MB)`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Detects content type from file extension and buffer
|
|
367
|
+
*/
|
|
368
|
+
detectContentType(key, buffer) {
|
|
369
|
+
// Get extension from key
|
|
370
|
+
const ext = key.split('.').pop()?.toLowerCase();
|
|
371
|
+
// Common MIME types
|
|
372
|
+
const mimeTypes = {
|
|
373
|
+
'jpg': 'image/jpeg',
|
|
374
|
+
'jpeg': 'image/jpeg',
|
|
375
|
+
'png': 'image/png',
|
|
376
|
+
'gif': 'image/gif',
|
|
377
|
+
'webp': 'image/webp',
|
|
378
|
+
'svg': 'image/svg+xml',
|
|
379
|
+
'pdf': 'application/pdf',
|
|
380
|
+
'txt': 'text/plain',
|
|
381
|
+
'json': 'application/json',
|
|
382
|
+
'csv': 'text/csv',
|
|
383
|
+
'zip': 'application/zip',
|
|
384
|
+
'mp4': 'video/mp4',
|
|
385
|
+
'webm': 'video/webm',
|
|
386
|
+
'mp3': 'audio/mpeg',
|
|
387
|
+
'wav': 'audio/wav',
|
|
388
|
+
};
|
|
389
|
+
if (ext && mimeTypes[ext]) {
|
|
390
|
+
return mimeTypes[ext];
|
|
391
|
+
}
|
|
392
|
+
// Simple buffer-based detection
|
|
393
|
+
const magic = buffer.subarray(0, 4);
|
|
394
|
+
if (magic[0] === 0xFF && magic[1] === 0xD8 && magic[2] === 0xFF) {
|
|
395
|
+
return 'image/jpeg';
|
|
396
|
+
}
|
|
397
|
+
if (magic[0] === 0x89 && magic[1] === 0x50 && magic[2] === 0x4E && magic[3] === 0x47) {
|
|
398
|
+
return 'image/png';
|
|
399
|
+
}
|
|
400
|
+
if (magic[0] === 0x47 && magic[1] === 0x49 && magic[2] === 0x46) {
|
|
401
|
+
return 'image/gif';
|
|
402
|
+
}
|
|
403
|
+
return 'application/octet-stream'; // Default binary
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Validates file type against allowed types
|
|
407
|
+
*/
|
|
408
|
+
validateFileType(contentType) {
|
|
409
|
+
let allowedTypes = [];
|
|
410
|
+
if (this.config.strategy === 'local' && this.config.local) {
|
|
411
|
+
allowedTypes = this.config.local.allowedTypes;
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
// S3 and R2 allow all types by default (filtering done at app level)
|
|
415
|
+
allowedTypes = ['*'];
|
|
416
|
+
}
|
|
417
|
+
// Allow all types if wildcard is present
|
|
418
|
+
if (allowedTypes.includes('*')) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// Check exact match or wildcard patterns
|
|
422
|
+
const isAllowed = allowedTypes.some(allowedType => {
|
|
423
|
+
if (allowedType === contentType) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
// Support wildcard patterns like "image/*"
|
|
427
|
+
if (allowedType.endsWith('/*')) {
|
|
428
|
+
const prefix = allowedType.slice(0, -2);
|
|
429
|
+
return contentType.startsWith(prefix + '/');
|
|
430
|
+
}
|
|
431
|
+
return false;
|
|
432
|
+
});
|
|
433
|
+
if (!isAllowed) {
|
|
434
|
+
throw new Error(`File type not allowed: ${contentType}. ` +
|
|
435
|
+
`Allowed types: ${allowedTypes.join(', ')}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/storage/storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local filesystem storage strategy with automatic directory management
|
|
3
|
+
* @module @bloomneo/appkit/storage
|
|
4
|
+
* @file src/storage/strategies/local.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: No cloud storage env vars - perfect for development and single-server apps
|
|
7
|
+
* @llm-rule AVOID: Production use across multiple servers - files don't sync across instances
|
|
8
|
+
* @llm-rule NOTE: Fast local storage, automatic directory creation, file serving support
|
|
9
|
+
*/
|
|
10
|
+
import type { StorageStrategy, StorageFile, PutOptions } from '../storage.js';
|
|
11
|
+
import type { StorageConfig } from '../defaults.js';
|
|
12
|
+
/**
|
|
13
|
+
* Local filesystem storage strategy with intelligent file management
|
|
14
|
+
*/
|
|
15
|
+
export declare class LocalStrategy implements StorageStrategy {
|
|
16
|
+
private config;
|
|
17
|
+
private baseDir;
|
|
18
|
+
private baseUrl;
|
|
19
|
+
private maxFileSize;
|
|
20
|
+
private allowedTypes;
|
|
21
|
+
/**
|
|
22
|
+
* Creates local strategy with direct environment access (like auth pattern)
|
|
23
|
+
* @llm-rule WHEN: Storage initialization without cloud env vars - automatic fallback
|
|
24
|
+
* @llm-rule AVOID: Manual local configuration - environment detection handles this
|
|
25
|
+
*/
|
|
26
|
+
constructor(config: StorageConfig);
|
|
27
|
+
/**
|
|
28
|
+
* Stores file to local filesystem with automatic directory creation
|
|
29
|
+
* @llm-rule WHEN: Saving files to local storage for development or single-server apps
|
|
30
|
+
* @llm-rule AVOID: Manual directory management - this handles nested paths automatically
|
|
31
|
+
*/
|
|
32
|
+
put(key: string, data: Buffer, options?: PutOptions): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Retrieves file from local filesystem with error handling
|
|
35
|
+
* @llm-rule WHEN: Loading files from local storage
|
|
36
|
+
* @llm-rule AVOID: Direct fs operations - this handles errors and validation
|
|
37
|
+
*/
|
|
38
|
+
get(key: string): Promise<Buffer>;
|
|
39
|
+
/**
|
|
40
|
+
* Deletes file from local filesystem with confirmation
|
|
41
|
+
* @llm-rule WHEN: Removing files from local storage
|
|
42
|
+
* @llm-rule AVOID: Silent failures - this confirms deletion or reports issues
|
|
43
|
+
*/
|
|
44
|
+
delete(key: string): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* Lists files with prefix filtering and metadata
|
|
47
|
+
* @llm-rule WHEN: Browsing local files or implementing file managers
|
|
48
|
+
* @llm-rule AVOID: Loading all files - this efficiently scans directories
|
|
49
|
+
*/
|
|
50
|
+
list(prefix?: string): Promise<StorageFile[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Gets public URL for local file access
|
|
53
|
+
* @llm-rule WHEN: Generating URLs for local file serving
|
|
54
|
+
* @llm-rule AVOID: Hardcoded paths - this handles base URL configuration
|
|
55
|
+
*/
|
|
56
|
+
url(key: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Checks if file exists without reading content
|
|
59
|
+
* @llm-rule WHEN: Validating file existence efficiently
|
|
60
|
+
* @llm-rule AVOID: Reading entire file just to check existence
|
|
61
|
+
*/
|
|
62
|
+
exists(key: string): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Generates signed URL for temporary local file access (not applicable for local storage)
|
|
65
|
+
* @llm-rule WHEN: API compatibility - local storage doesn't support signed URLs
|
|
66
|
+
* @llm-rule AVOID: Using signed URLs with local storage - use regular URLs instead
|
|
67
|
+
*/
|
|
68
|
+
signedUrl(key: string, expiresIn?: number): Promise<string>;
|
|
69
|
+
/**
|
|
70
|
+
* Copies file within local filesystem efficiently
|
|
71
|
+
* @llm-rule WHEN: Duplicating files within local storage
|
|
72
|
+
* @llm-rule AVOID: Read/write operations - this uses efficient copy operations
|
|
73
|
+
*/
|
|
74
|
+
copy(sourceKey: string, destKey: string): Promise<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Disconnects local strategy gracefully
|
|
77
|
+
* @llm-rule WHEN: App shutdown or storage cleanup
|
|
78
|
+
* @llm-rule AVOID: Leaving temp files - this cleans up if configured
|
|
79
|
+
*/
|
|
80
|
+
disconnect(): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Gets absolute file path from storage key
|
|
83
|
+
*/
|
|
84
|
+
private getFilePath;
|
|
85
|
+
/**
|
|
86
|
+
* Ensures directory exists, creating it if necessary
|
|
87
|
+
*/
|
|
88
|
+
private ensureDirectoryExists;
|
|
89
|
+
/**
|
|
90
|
+
* Recursively scans directory for files matching pattern
|
|
91
|
+
*/
|
|
92
|
+
private scanDirectory;
|
|
93
|
+
/**
|
|
94
|
+
* Cleans up empty directories after file deletion
|
|
95
|
+
*/
|
|
96
|
+
private cleanupEmptyDirectories;
|
|
97
|
+
/**
|
|
98
|
+
* Sets file metadata using extended attributes (where supported)
|
|
99
|
+
*/
|
|
100
|
+
private setFileMetadata;
|
|
101
|
+
/**
|
|
102
|
+
* Gets content type from file extension
|
|
103
|
+
*/
|
|
104
|
+
private getContentTypeFromExtension;
|
|
105
|
+
/**
|
|
106
|
+
* Gets detailed local storage statistics
|
|
107
|
+
*/
|
|
108
|
+
getDetailedStats(): {
|
|
109
|
+
strategy: string;
|
|
110
|
+
baseDir: string;
|
|
111
|
+
totalFiles: number;
|
|
112
|
+
totalSize: number;
|
|
113
|
+
maxFileSize: number;
|
|
114
|
+
allowedTypes: string[];
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../src/storage/strategies/local.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;GAEG;AACH,qBAAa,aAAc,YAAW,eAAe;IACnD,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAW;IAE/B;;;;OAIG;gBACS,MAAM,EAAE,aAAa;IAoBjC;;;;OAIG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAiC3E;;;;OAIG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsBvC;;;;OAIG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0B3C;;;;OAIG;IACG,IAAI,CAAC,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA0BvD;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAWxB;;;;OAIG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS3C;;;;OAIG;IACG,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,GAAE,MAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAMvE;;;;OAIG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA2B/D;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAWjC;;OAEG;IACH,OAAO,CAAC,WAAW;IAMnB;;OAEG;YACW,qBAAqB;IAUnC;;OAEG;YACW,aAAa;IAyC3B;;OAEG;YACW,uBAAuB;IA2BrC;;OAEG;YACW,eAAe;IAiB7B;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAwBnC;;OAEG;IACH,gBAAgB,IAAI;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,EAAE,CAAC;KACxB;CAUF"}
|