@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,368 @@
|
|
|
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 fs from 'fs/promises';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
/**
|
|
14
|
+
* Local filesystem storage strategy with intelligent file management
|
|
15
|
+
*/
|
|
16
|
+
export class LocalStrategy {
|
|
17
|
+
config;
|
|
18
|
+
baseDir;
|
|
19
|
+
baseUrl;
|
|
20
|
+
maxFileSize;
|
|
21
|
+
allowedTypes;
|
|
22
|
+
/**
|
|
23
|
+
* Creates local strategy with direct environment access (like auth pattern)
|
|
24
|
+
* @llm-rule WHEN: Storage initialization without cloud env vars - automatic fallback
|
|
25
|
+
* @llm-rule AVOID: Manual local configuration - environment detection handles this
|
|
26
|
+
*/
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.config = config;
|
|
29
|
+
if (!config.local) {
|
|
30
|
+
throw new Error('Local storage configuration missing');
|
|
31
|
+
}
|
|
32
|
+
this.baseDir = path.resolve(config.local.dir);
|
|
33
|
+
this.baseUrl = config.local.baseUrl;
|
|
34
|
+
this.maxFileSize = config.local.maxFileSize;
|
|
35
|
+
this.allowedTypes = config.local.allowedTypes;
|
|
36
|
+
// Ensure base directory exists on initialization
|
|
37
|
+
this.ensureDirectoryExists(this.baseDir);
|
|
38
|
+
if (this.config.environment.isDevelopment) {
|
|
39
|
+
console.log(`✅ [AppKit] Local storage initialized (dir: ${this.baseDir}, maxSize: ${Math.round(this.maxFileSize / 1048576)}MB)`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Stores file to local filesystem with automatic directory creation
|
|
44
|
+
* @llm-rule WHEN: Saving files to local storage for development or single-server apps
|
|
45
|
+
* @llm-rule AVOID: Manual directory management - this handles nested paths automatically
|
|
46
|
+
*/
|
|
47
|
+
async put(key, data, options) {
|
|
48
|
+
try {
|
|
49
|
+
const filePath = this.getFilePath(key);
|
|
50
|
+
// Ensure parent directory exists
|
|
51
|
+
const parentDir = path.dirname(filePath);
|
|
52
|
+
await this.ensureDirectoryExists(parentDir);
|
|
53
|
+
// Write file to disk
|
|
54
|
+
await fs.writeFile(filePath, data);
|
|
55
|
+
// Set file metadata if supported (extended attributes not available on all systems)
|
|
56
|
+
if (options?.metadata) {
|
|
57
|
+
try {
|
|
58
|
+
await this.setFileMetadata(filePath, options.metadata);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// Metadata setting is optional, don't fail the operation
|
|
62
|
+
if (this.config.environment.isDevelopment) {
|
|
63
|
+
console.warn(`[AppKit] Could not set metadata for ${key}:`, error.message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (this.config.environment.isDevelopment) {
|
|
68
|
+
console.log(`📁 [AppKit] Local file stored: ${key} (${data.length} bytes)`);
|
|
69
|
+
}
|
|
70
|
+
return key;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new Error(`Failed to store file locally: ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Retrieves file from local filesystem with error handling
|
|
78
|
+
* @llm-rule WHEN: Loading files from local storage
|
|
79
|
+
* @llm-rule AVOID: Direct fs operations - this handles errors and validation
|
|
80
|
+
*/
|
|
81
|
+
async get(key) {
|
|
82
|
+
try {
|
|
83
|
+
const filePath = this.getFilePath(key);
|
|
84
|
+
// Check if file exists before reading
|
|
85
|
+
if (!existsSync(filePath)) {
|
|
86
|
+
throw new Error(`File not found: ${key}`);
|
|
87
|
+
}
|
|
88
|
+
// Read file from disk
|
|
89
|
+
const data = await fs.readFile(filePath);
|
|
90
|
+
if (this.config.environment.isDevelopment) {
|
|
91
|
+
console.log(`📁 [AppKit] Local file retrieved: ${key} (${data.length} bytes)`);
|
|
92
|
+
}
|
|
93
|
+
return data;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
throw new Error(`Failed to retrieve file locally: ${error.message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Deletes file from local filesystem with confirmation
|
|
101
|
+
* @llm-rule WHEN: Removing files from local storage
|
|
102
|
+
* @llm-rule AVOID: Silent failures - this confirms deletion or reports issues
|
|
103
|
+
*/
|
|
104
|
+
async delete(key) {
|
|
105
|
+
try {
|
|
106
|
+
const filePath = this.getFilePath(key);
|
|
107
|
+
// Check if file exists
|
|
108
|
+
if (!existsSync(filePath)) {
|
|
109
|
+
return false; // File doesn't exist, consider it "deleted"
|
|
110
|
+
}
|
|
111
|
+
// Delete file
|
|
112
|
+
await fs.unlink(filePath);
|
|
113
|
+
// Clean up empty directories
|
|
114
|
+
await this.cleanupEmptyDirectories(path.dirname(filePath));
|
|
115
|
+
if (this.config.environment.isDevelopment) {
|
|
116
|
+
console.log(`🗑️ [AppKit] Local file deleted: ${key}`);
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`[AppKit] Local delete error for "${key}":`, error.message);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Lists files with prefix filtering and metadata
|
|
127
|
+
* @llm-rule WHEN: Browsing local files or implementing file managers
|
|
128
|
+
* @llm-rule AVOID: Loading all files - this efficiently scans directories
|
|
129
|
+
*/
|
|
130
|
+
async list(prefix = '') {
|
|
131
|
+
try {
|
|
132
|
+
const searchDir = prefix ? path.join(this.baseDir, path.dirname(prefix)) : this.baseDir;
|
|
133
|
+
const searchPattern = prefix ? path.basename(prefix) : '';
|
|
134
|
+
if (!existsSync(searchDir)) {
|
|
135
|
+
return []; // Directory doesn't exist, return empty list
|
|
136
|
+
}
|
|
137
|
+
const files = [];
|
|
138
|
+
await this.scanDirectory(searchDir, files, searchPattern, prefix);
|
|
139
|
+
// Sort by key for consistent ordering
|
|
140
|
+
files.sort((a, b) => a.key.localeCompare(b.key));
|
|
141
|
+
if (this.config.environment.isDevelopment) {
|
|
142
|
+
console.log(`📋 [AppKit] Local files listed: ${prefix}* (${files.length} files)`);
|
|
143
|
+
}
|
|
144
|
+
return files;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
console.error(`[AppKit] Local list error for prefix "${prefix}":`, error.message);
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Gets public URL for local file access
|
|
153
|
+
* @llm-rule WHEN: Generating URLs for local file serving
|
|
154
|
+
* @llm-rule AVOID: Hardcoded paths - this handles base URL configuration
|
|
155
|
+
*/
|
|
156
|
+
url(key) {
|
|
157
|
+
// Normalize key to use forward slashes for URLs
|
|
158
|
+
const normalizedKey = key.replace(/\\/g, '/');
|
|
159
|
+
// Ensure base URL ends with / and key doesn't start with /
|
|
160
|
+
const baseUrl = this.baseUrl.endsWith('/') ? this.baseUrl : this.baseUrl + '/';
|
|
161
|
+
const cleanKey = normalizedKey.startsWith('/') ? normalizedKey.slice(1) : normalizedKey;
|
|
162
|
+
return baseUrl + cleanKey;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Checks if file exists without reading content
|
|
166
|
+
* @llm-rule WHEN: Validating file existence efficiently
|
|
167
|
+
* @llm-rule AVOID: Reading entire file just to check existence
|
|
168
|
+
*/
|
|
169
|
+
async exists(key) {
|
|
170
|
+
try {
|
|
171
|
+
const filePath = this.getFilePath(key);
|
|
172
|
+
return existsSync(filePath);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Generates signed URL for temporary local file access (not applicable for local storage)
|
|
180
|
+
* @llm-rule WHEN: API compatibility - local storage doesn't support signed URLs
|
|
181
|
+
* @llm-rule AVOID: Using signed URLs with local storage - use regular URLs instead
|
|
182
|
+
*/
|
|
183
|
+
async signedUrl(key, expiresIn = 3600) {
|
|
184
|
+
// Local storage doesn't support signed URLs, return regular URL
|
|
185
|
+
console.warn('[AppKit] Signed URLs not supported with local storage, returning public URL');
|
|
186
|
+
return this.url(key);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Copies file within local filesystem efficiently
|
|
190
|
+
* @llm-rule WHEN: Duplicating files within local storage
|
|
191
|
+
* @llm-rule AVOID: Read/write operations - this uses efficient copy operations
|
|
192
|
+
*/
|
|
193
|
+
async copy(sourceKey, destKey) {
|
|
194
|
+
try {
|
|
195
|
+
const sourcePath = this.getFilePath(sourceKey);
|
|
196
|
+
const destPath = this.getFilePath(destKey);
|
|
197
|
+
// Check source exists
|
|
198
|
+
if (!existsSync(sourcePath)) {
|
|
199
|
+
throw new Error(`Source file not found: ${sourceKey}`);
|
|
200
|
+
}
|
|
201
|
+
// Ensure destination directory exists
|
|
202
|
+
const destDir = path.dirname(destPath);
|
|
203
|
+
await this.ensureDirectoryExists(destDir);
|
|
204
|
+
// Copy file
|
|
205
|
+
await fs.copyFile(sourcePath, destPath);
|
|
206
|
+
if (this.config.environment.isDevelopment) {
|
|
207
|
+
console.log(`📁 [AppKit] Local file copied: ${sourceKey} → ${destKey}`);
|
|
208
|
+
}
|
|
209
|
+
return destKey;
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
throw new Error(`Failed to copy file locally: ${error.message}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Disconnects local strategy gracefully
|
|
217
|
+
* @llm-rule WHEN: App shutdown or storage cleanup
|
|
218
|
+
* @llm-rule AVOID: Leaving temp files - this cleans up if configured
|
|
219
|
+
*/
|
|
220
|
+
async disconnect() {
|
|
221
|
+
// Local storage doesn't need explicit disconnection
|
|
222
|
+
// Could implement cleanup of temp files here if needed
|
|
223
|
+
if (this.config.environment.isDevelopment) {
|
|
224
|
+
console.log(`👋 [AppKit] Local storage strategy disconnected`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Private helper methods
|
|
228
|
+
/**
|
|
229
|
+
* Gets absolute file path from storage key
|
|
230
|
+
*/
|
|
231
|
+
getFilePath(key) {
|
|
232
|
+
// Normalize path separators and prevent directory traversal
|
|
233
|
+
const normalizedKey = key.replace(/\\/g, '/').replace(/\.\.+/g, '');
|
|
234
|
+
return path.join(this.baseDir, normalizedKey);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Ensures directory exists, creating it if necessary
|
|
238
|
+
*/
|
|
239
|
+
async ensureDirectoryExists(dirPath) {
|
|
240
|
+
try {
|
|
241
|
+
if (!existsSync(dirPath)) {
|
|
242
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
throw new Error(`Failed to create directory: ${error.message}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Recursively scans directory for files matching pattern
|
|
251
|
+
*/
|
|
252
|
+
async scanDirectory(dirPath, files, pattern = '', prefix = '') {
|
|
253
|
+
try {
|
|
254
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
255
|
+
for (const entry of entries) {
|
|
256
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
257
|
+
if (entry.isDirectory()) {
|
|
258
|
+
// Recursively scan subdirectories
|
|
259
|
+
await this.scanDirectory(fullPath, files, pattern, prefix);
|
|
260
|
+
}
|
|
261
|
+
else if (entry.isFile()) {
|
|
262
|
+
// Check if file matches pattern
|
|
263
|
+
if (!pattern || entry.name.startsWith(pattern)) {
|
|
264
|
+
const relativePath = path.relative(this.baseDir, fullPath);
|
|
265
|
+
const key = relativePath.replace(/\\/g, '/'); // Normalize to forward slashes
|
|
266
|
+
// Get file stats
|
|
267
|
+
const stats = await fs.stat(fullPath);
|
|
268
|
+
files.push({
|
|
269
|
+
key,
|
|
270
|
+
size: stats.size,
|
|
271
|
+
lastModified: stats.mtime,
|
|
272
|
+
contentType: this.getContentTypeFromExtension(key),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
// Directory access error, skip silently
|
|
280
|
+
if (this.config.environment.isDevelopment) {
|
|
281
|
+
console.warn(`[AppKit] Error scanning directory ${dirPath}:`, error.message);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Cleans up empty directories after file deletion
|
|
287
|
+
*/
|
|
288
|
+
async cleanupEmptyDirectories(dirPath) {
|
|
289
|
+
try {
|
|
290
|
+
// Don't clean up the base directory
|
|
291
|
+
if (dirPath === this.baseDir) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const entries = await fs.readdir(dirPath);
|
|
295
|
+
// If directory is empty, remove it and check parent
|
|
296
|
+
if (entries.length === 0) {
|
|
297
|
+
await fs.rmdir(dirPath);
|
|
298
|
+
// Recursively check parent directory
|
|
299
|
+
const parentDir = path.dirname(dirPath);
|
|
300
|
+
if (parentDir !== dirPath && parentDir !== this.baseDir) {
|
|
301
|
+
await this.cleanupEmptyDirectories(parentDir);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
// Cleanup is optional, don't fail if it doesn't work
|
|
307
|
+
if (this.config.environment.isDevelopment) {
|
|
308
|
+
console.warn(`[AppKit] Could not cleanup directory ${dirPath}:`, error.message);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Sets file metadata using extended attributes (where supported)
|
|
314
|
+
*/
|
|
315
|
+
async setFileMetadata(filePath, metadata) {
|
|
316
|
+
// Extended attributes are not universally supported
|
|
317
|
+
// This is a placeholder for future metadata support
|
|
318
|
+
// Could implement using file naming conventions or sidecar files
|
|
319
|
+
// For now, we'll store metadata in a separate .meta file
|
|
320
|
+
const metaPath = filePath + '.meta';
|
|
321
|
+
const metaContent = JSON.stringify(metadata, null, 2);
|
|
322
|
+
try {
|
|
323
|
+
await fs.writeFile(metaPath, metaContent);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
// Metadata is optional
|
|
327
|
+
throw new Error(`Failed to write metadata: ${error.message}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Gets content type from file extension
|
|
332
|
+
*/
|
|
333
|
+
getContentTypeFromExtension(key) {
|
|
334
|
+
const ext = path.extname(key).toLowerCase();
|
|
335
|
+
const mimeTypes = {
|
|
336
|
+
'.jpg': 'image/jpeg',
|
|
337
|
+
'.jpeg': 'image/jpeg',
|
|
338
|
+
'.png': 'image/png',
|
|
339
|
+
'.gif': 'image/gif',
|
|
340
|
+
'.webp': 'image/webp',
|
|
341
|
+
'.svg': 'image/svg+xml',
|
|
342
|
+
'.pdf': 'application/pdf',
|
|
343
|
+
'.txt': 'text/plain',
|
|
344
|
+
'.json': 'application/json',
|
|
345
|
+
'.csv': 'text/csv',
|
|
346
|
+
'.zip': 'application/zip',
|
|
347
|
+
'.mp4': 'video/mp4',
|
|
348
|
+
'.webm': 'video/webm',
|
|
349
|
+
'.mp3': 'audio/mpeg',
|
|
350
|
+
'.wav': 'audio/wav',
|
|
351
|
+
};
|
|
352
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Gets detailed local storage statistics
|
|
356
|
+
*/
|
|
357
|
+
getDetailedStats() {
|
|
358
|
+
return {
|
|
359
|
+
strategy: 'local',
|
|
360
|
+
baseDir: this.baseDir,
|
|
361
|
+
totalFiles: 0, // Would need to scan to get accurate count
|
|
362
|
+
totalSize: 0, // Would need to scan to get accurate size
|
|
363
|
+
maxFileSize: this.maxFileSize,
|
|
364
|
+
allowedTypes: this.allowedTypes,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../../src/storage/strategies/local.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare R2 storage strategy with zero egress fees and automatic CDN integration
|
|
3
|
+
* @module @bloomneo/appkit/storage
|
|
4
|
+
* @file src/storage/strategies/r2.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: App has CLOUDFLARE_R2_BUCKET env var for cost-effective distributed storage
|
|
7
|
+
* @llm-rule AVOID: Manual R2 setup - this handles connection, CDN integration, and S3-compatible API
|
|
8
|
+
* @llm-rule NOTE: Production-ready with zero egress fees, automatic CDN, S3-compatible operations
|
|
9
|
+
*/
|
|
10
|
+
import type { StorageStrategy, StorageFile, PutOptions } from '../storage.js';
|
|
11
|
+
import type { StorageConfig } from '../defaults.js';
|
|
12
|
+
/**
|
|
13
|
+
* Cloudflare R2 storage strategy with cost optimization and CDN integration
|
|
14
|
+
*/
|
|
15
|
+
export declare class R2Strategy implements StorageStrategy {
|
|
16
|
+
private config;
|
|
17
|
+
private r2Client;
|
|
18
|
+
private connected;
|
|
19
|
+
private bucket;
|
|
20
|
+
private accountId;
|
|
21
|
+
private cdnUrl?;
|
|
22
|
+
private endpoint;
|
|
23
|
+
/**
|
|
24
|
+
* Creates R2 strategy with direct environment access (like auth pattern)
|
|
25
|
+
* @llm-rule WHEN: Storage initialization with Cloudflare R2 environment variables detected
|
|
26
|
+
* @llm-rule AVOID: Manual R2 configuration - environment detection handles Cloudflare specifics
|
|
27
|
+
*/
|
|
28
|
+
constructor(config: StorageConfig);
|
|
29
|
+
/**
|
|
30
|
+
* Connects to Cloudflare R2 with automatic account validation
|
|
31
|
+
* @llm-rule WHEN: Storage initialization or reconnection after failure
|
|
32
|
+
* @llm-rule AVOID: Manual R2 client setup - this handles Cloudflare-specific configuration
|
|
33
|
+
*/
|
|
34
|
+
connect(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Tests R2 connection by checking bucket access
|
|
37
|
+
*/
|
|
38
|
+
private testConnection;
|
|
39
|
+
/**
|
|
40
|
+
* Stores file to R2 with automatic content type detection and zero egress cost
|
|
41
|
+
* @llm-rule WHEN: Uploading files to Cloudflare R2 for cost-effective storage
|
|
42
|
+
* @llm-rule AVOID: Manual R2 operations - this handles R2-specific optimizations
|
|
43
|
+
*/
|
|
44
|
+
put(key: string, data: Buffer, options?: PutOptions): Promise<string>;
|
|
45
|
+
/**
|
|
46
|
+
* Retrieves file from R2 with streaming support and zero egress cost
|
|
47
|
+
* @llm-rule WHEN: Downloading files from Cloudflare R2
|
|
48
|
+
* @llm-rule AVOID: Manual R2 operations - this handles streaming and cost optimization
|
|
49
|
+
*/
|
|
50
|
+
get(key: string): Promise<Buffer>;
|
|
51
|
+
/**
|
|
52
|
+
* Deletes file from R2 with confirmation
|
|
53
|
+
* @llm-rule WHEN: Removing files from Cloudflare R2 storage
|
|
54
|
+
* @llm-rule AVOID: Silent failures - this confirms deletion success
|
|
55
|
+
*/
|
|
56
|
+
delete(key: string): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Lists files with prefix filtering and R2-optimized pagination
|
|
59
|
+
* @llm-rule WHEN: Browsing R2 files or implementing file managers
|
|
60
|
+
* @llm-rule AVOID: Loading all objects - R2 has same limits as S3
|
|
61
|
+
*/
|
|
62
|
+
list(prefix?: string): Promise<StorageFile[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Gets CDN or public URL for R2 object with automatic CDN detection
|
|
65
|
+
* @llm-rule WHEN: Generating URLs for R2 file access with CDN optimization
|
|
66
|
+
* @llm-rule AVOID: Hardcoded URLs - this handles CDN and R2-specific URLs
|
|
67
|
+
*/
|
|
68
|
+
url(key: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Generates R2 public URL hash (simplified for demo)
|
|
71
|
+
*/
|
|
72
|
+
private generatePublicHash;
|
|
73
|
+
/**
|
|
74
|
+
* Generates signed URL for temporary R2 object access
|
|
75
|
+
* @llm-rule WHEN: Creating temporary download/upload links for R2 objects
|
|
76
|
+
* @llm-rule AVOID: Public URLs for private files - use signed URLs with expiration
|
|
77
|
+
*/
|
|
78
|
+
signedUrl(key: string, expiresIn?: number): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Checks if R2 object exists without downloading
|
|
81
|
+
* @llm-rule WHEN: Validating R2 object existence efficiently
|
|
82
|
+
* @llm-rule AVOID: Downloading objects just to check existence
|
|
83
|
+
*/
|
|
84
|
+
exists(key: string): Promise<boolean>;
|
|
85
|
+
/**
|
|
86
|
+
* Copies R2 object efficiently using server-side copy (zero egress cost)
|
|
87
|
+
* @llm-rule WHEN: Duplicating R2 objects without bandwidth costs
|
|
88
|
+
* @llm-rule AVOID: Download and upload - R2 server-side copy has zero egress fees
|
|
89
|
+
*/
|
|
90
|
+
copy(sourceKey: string, destKey: string): Promise<string>;
|
|
91
|
+
/**
|
|
92
|
+
* Disconnects R2 strategy gracefully
|
|
93
|
+
* @llm-rule WHEN: App shutdown or storage cleanup
|
|
94
|
+
* @llm-rule AVOID: Abrupt disconnection - graceful shutdown prevents connection issues
|
|
95
|
+
*/
|
|
96
|
+
disconnect(): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Converts readable stream to buffer
|
|
99
|
+
*/
|
|
100
|
+
private streamToBuffer;
|
|
101
|
+
/**
|
|
102
|
+
* Detects content type from file extension and buffer
|
|
103
|
+
*/
|
|
104
|
+
private detectContentType;
|
|
105
|
+
/**
|
|
106
|
+
* Gets content type for existing R2 object
|
|
107
|
+
*/
|
|
108
|
+
private getObjectContentType;
|
|
109
|
+
/**
|
|
110
|
+
* Gets R2 connection info for debugging
|
|
111
|
+
*/
|
|
112
|
+
getConnectionInfo(): {
|
|
113
|
+
connected: boolean;
|
|
114
|
+
bucket: string;
|
|
115
|
+
accountId: string;
|
|
116
|
+
endpoint: string;
|
|
117
|
+
cdnEnabled: boolean;
|
|
118
|
+
zeroEgressFees: boolean;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Gets R2-specific cost optimization info
|
|
122
|
+
*/
|
|
123
|
+
getCostInfo(): {
|
|
124
|
+
egressFees: string;
|
|
125
|
+
storageClass: string;
|
|
126
|
+
cdnIntegration: boolean;
|
|
127
|
+
recommendedFor: string[];
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=r2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2.d.ts","sourceRoot":"","sources":["../../../src/storage/strategies/r2.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,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,UAAW,YAAW,eAAe;IAChD,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;OAIG;gBACS,MAAM,EAAE,aAAa;IAejC;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA0C9B;;OAEG;YACW,cAAc;IAgB5B;;;;OAIG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IA6C3E;;;;OAIG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmCvC;;;;OAIG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA2B3C;;;;OAIG;IACG,IAAI,CAAC,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA8CvD;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAYxB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;;;OAIG;IACG,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,GAAE,MAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IA4BvE;;;;OAIG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwB3C;;;;OAIG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA0B/D;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBjC;;OAEG;YACW,cAAc;IAU5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkDzB;;OAEG;YACW,oBAAoB;IAiBlC;;OAEG;IACH,iBAAiB,IAAI;QACnB,SAAS,EAAE,OAAO,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,OAAO,CAAC;QACpB,cAAc,EAAE,OAAO,CAAC;KACzB;IAWD;;OAEG;IACH,WAAW,IAAI;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,OAAO,CAAC;QACxB,cAAc,EAAE,MAAM,EAAE,CAAC;KAC1B;CAaF"}
|