@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.
Files changed (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +902 -0
  3. package/bin/appkit.js +71 -0
  4. package/bin/commands/generate.js +1050 -0
  5. package/bin/templates/backend/README.md.template +39 -0
  6. package/bin/templates/backend/api.http.template +0 -0
  7. package/bin/templates/backend/docs/APPKIT_CLI.md +507 -0
  8. package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +61 -0
  9. package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +2539 -0
  10. package/bin/templates/backend/package.json.template +34 -0
  11. package/bin/templates/backend/src/api/features/welcome/welcome.http.template +29 -0
  12. package/bin/templates/backend/src/api/features/welcome/welcome.route.ts.template +36 -0
  13. package/bin/templates/backend/src/api/features/welcome/welcome.service.ts.template +88 -0
  14. package/bin/templates/backend/src/api/features/welcome/welcome.types.ts.template +18 -0
  15. package/bin/templates/backend/src/api/lib/api-router.ts.template +84 -0
  16. package/bin/templates/backend/src/api/server.ts.template +188 -0
  17. package/bin/templates/backend/tsconfig.api.json.template +24 -0
  18. package/bin/templates/backend/tsconfig.json.template +40 -0
  19. package/bin/templates/feature/feature.http.template +63 -0
  20. package/bin/templates/feature/feature.route.ts.template +36 -0
  21. package/bin/templates/feature/feature.service.ts.template +81 -0
  22. package/bin/templates/feature/feature.types.ts.template +23 -0
  23. package/bin/templates/feature-db/feature.http.template +63 -0
  24. package/bin/templates/feature-db/feature.model.ts.template +74 -0
  25. package/bin/templates/feature-db/feature.route.ts.template +58 -0
  26. package/bin/templates/feature-db/feature.service.ts.template +231 -0
  27. package/bin/templates/feature-db/feature.types.ts.template +25 -0
  28. package/bin/templates/feature-db/schema-addition.prisma.template +9 -0
  29. package/bin/templates/feature-db/seeding/README.md.template +57 -0
  30. package/bin/templates/feature-db/seeding/feature.seed.js.template +67 -0
  31. package/bin/templates/feature-user/schema-addition.prisma.template +19 -0
  32. package/bin/templates/feature-user/user.http.template +157 -0
  33. package/bin/templates/feature-user/user.model.ts.template +244 -0
  34. package/bin/templates/feature-user/user.route.ts.template +379 -0
  35. package/bin/templates/feature-user/user.seed.js.template +182 -0
  36. package/bin/templates/feature-user/user.service.ts.template +426 -0
  37. package/bin/templates/feature-user/user.types.ts.template +127 -0
  38. package/dist/auth/auth.d.ts +182 -0
  39. package/dist/auth/auth.d.ts.map +1 -0
  40. package/dist/auth/auth.js +477 -0
  41. package/dist/auth/auth.js.map +1 -0
  42. package/dist/auth/defaults.d.ts +104 -0
  43. package/dist/auth/defaults.d.ts.map +1 -0
  44. package/dist/auth/defaults.js +374 -0
  45. package/dist/auth/defaults.js.map +1 -0
  46. package/dist/auth/index.d.ts +70 -0
  47. package/dist/auth/index.d.ts.map +1 -0
  48. package/dist/auth/index.js +94 -0
  49. package/dist/auth/index.js.map +1 -0
  50. package/dist/cache/cache.d.ts +118 -0
  51. package/dist/cache/cache.d.ts.map +1 -0
  52. package/dist/cache/cache.js +249 -0
  53. package/dist/cache/cache.js.map +1 -0
  54. package/dist/cache/defaults.d.ts +63 -0
  55. package/dist/cache/defaults.d.ts.map +1 -0
  56. package/dist/cache/defaults.js +193 -0
  57. package/dist/cache/defaults.js.map +1 -0
  58. package/dist/cache/index.d.ts +101 -0
  59. package/dist/cache/index.d.ts.map +1 -0
  60. package/dist/cache/index.js +203 -0
  61. package/dist/cache/index.js.map +1 -0
  62. package/dist/cache/strategies/memory.d.ts +138 -0
  63. package/dist/cache/strategies/memory.d.ts.map +1 -0
  64. package/dist/cache/strategies/memory.js +348 -0
  65. package/dist/cache/strategies/memory.js.map +1 -0
  66. package/dist/cache/strategies/redis.d.ts +105 -0
  67. package/dist/cache/strategies/redis.d.ts.map +1 -0
  68. package/dist/cache/strategies/redis.js +318 -0
  69. package/dist/cache/strategies/redis.js.map +1 -0
  70. package/dist/config/config.d.ts +62 -0
  71. package/dist/config/config.d.ts.map +1 -0
  72. package/dist/config/config.js +107 -0
  73. package/dist/config/config.js.map +1 -0
  74. package/dist/config/defaults.d.ts +44 -0
  75. package/dist/config/defaults.d.ts.map +1 -0
  76. package/dist/config/defaults.js +217 -0
  77. package/dist/config/defaults.js.map +1 -0
  78. package/dist/config/index.d.ts +105 -0
  79. package/dist/config/index.d.ts.map +1 -0
  80. package/dist/config/index.js +163 -0
  81. package/dist/config/index.js.map +1 -0
  82. package/dist/database/adapters/mongoose.d.ts +106 -0
  83. package/dist/database/adapters/mongoose.d.ts.map +1 -0
  84. package/dist/database/adapters/mongoose.js +480 -0
  85. package/dist/database/adapters/mongoose.js.map +1 -0
  86. package/dist/database/adapters/prisma.d.ts +106 -0
  87. package/dist/database/adapters/prisma.d.ts.map +1 -0
  88. package/dist/database/adapters/prisma.js +494 -0
  89. package/dist/database/adapters/prisma.js.map +1 -0
  90. package/dist/database/defaults.d.ts +87 -0
  91. package/dist/database/defaults.d.ts.map +1 -0
  92. package/dist/database/defaults.js +271 -0
  93. package/dist/database/defaults.js.map +1 -0
  94. package/dist/database/index.d.ts +137 -0
  95. package/dist/database/index.d.ts.map +1 -0
  96. package/dist/database/index.js +490 -0
  97. package/dist/database/index.js.map +1 -0
  98. package/dist/email/defaults.d.ts +100 -0
  99. package/dist/email/defaults.d.ts.map +1 -0
  100. package/dist/email/defaults.js +400 -0
  101. package/dist/email/defaults.js.map +1 -0
  102. package/dist/email/email.d.ts +139 -0
  103. package/dist/email/email.d.ts.map +1 -0
  104. package/dist/email/email.js +316 -0
  105. package/dist/email/email.js.map +1 -0
  106. package/dist/email/index.d.ts +176 -0
  107. package/dist/email/index.d.ts.map +1 -0
  108. package/dist/email/index.js +251 -0
  109. package/dist/email/index.js.map +1 -0
  110. package/dist/email/strategies/console.d.ts +90 -0
  111. package/dist/email/strategies/console.d.ts.map +1 -0
  112. package/dist/email/strategies/console.js +268 -0
  113. package/dist/email/strategies/console.js.map +1 -0
  114. package/dist/email/strategies/resend.d.ts +84 -0
  115. package/dist/email/strategies/resend.d.ts.map +1 -0
  116. package/dist/email/strategies/resend.js +266 -0
  117. package/dist/email/strategies/resend.js.map +1 -0
  118. package/dist/email/strategies/smtp.d.ts +77 -0
  119. package/dist/email/strategies/smtp.d.ts.map +1 -0
  120. package/dist/email/strategies/smtp.js +286 -0
  121. package/dist/email/strategies/smtp.js.map +1 -0
  122. package/dist/error/defaults.d.ts +40 -0
  123. package/dist/error/defaults.d.ts.map +1 -0
  124. package/dist/error/defaults.js +75 -0
  125. package/dist/error/defaults.js.map +1 -0
  126. package/dist/error/error.d.ts +140 -0
  127. package/dist/error/error.d.ts.map +1 -0
  128. package/dist/error/error.js +200 -0
  129. package/dist/error/error.js.map +1 -0
  130. package/dist/error/index.d.ts +145 -0
  131. package/dist/error/index.d.ts.map +1 -0
  132. package/dist/error/index.js +145 -0
  133. package/dist/error/index.js.map +1 -0
  134. package/dist/event/defaults.d.ts +111 -0
  135. package/dist/event/defaults.d.ts.map +1 -0
  136. package/dist/event/defaults.js +378 -0
  137. package/dist/event/defaults.js.map +1 -0
  138. package/dist/event/event.d.ts +171 -0
  139. package/dist/event/event.d.ts.map +1 -0
  140. package/dist/event/event.js +391 -0
  141. package/dist/event/event.js.map +1 -0
  142. package/dist/event/index.d.ts +173 -0
  143. package/dist/event/index.d.ts.map +1 -0
  144. package/dist/event/index.js +302 -0
  145. package/dist/event/index.js.map +1 -0
  146. package/dist/event/strategies/memory.d.ts +122 -0
  147. package/dist/event/strategies/memory.d.ts.map +1 -0
  148. package/dist/event/strategies/memory.js +331 -0
  149. package/dist/event/strategies/memory.js.map +1 -0
  150. package/dist/event/strategies/redis.d.ts +115 -0
  151. package/dist/event/strategies/redis.d.ts.map +1 -0
  152. package/dist/event/strategies/redis.js +434 -0
  153. package/dist/event/strategies/redis.js.map +1 -0
  154. package/dist/index.d.ts +58 -0
  155. package/dist/index.d.ts.map +1 -0
  156. package/dist/index.js +72 -0
  157. package/dist/index.js.map +1 -0
  158. package/dist/logger/defaults.d.ts +67 -0
  159. package/dist/logger/defaults.d.ts.map +1 -0
  160. package/dist/logger/defaults.js +213 -0
  161. package/dist/logger/defaults.js.map +1 -0
  162. package/dist/logger/index.d.ts +84 -0
  163. package/dist/logger/index.d.ts.map +1 -0
  164. package/dist/logger/index.js +101 -0
  165. package/dist/logger/index.js.map +1 -0
  166. package/dist/logger/logger.d.ts +165 -0
  167. package/dist/logger/logger.d.ts.map +1 -0
  168. package/dist/logger/logger.js +843 -0
  169. package/dist/logger/logger.js.map +1 -0
  170. package/dist/logger/transports/console.d.ts +102 -0
  171. package/dist/logger/transports/console.d.ts.map +1 -0
  172. package/dist/logger/transports/console.js +276 -0
  173. package/dist/logger/transports/console.js.map +1 -0
  174. package/dist/logger/transports/database.d.ts +153 -0
  175. package/dist/logger/transports/database.d.ts.map +1 -0
  176. package/dist/logger/transports/database.js +539 -0
  177. package/dist/logger/transports/database.js.map +1 -0
  178. package/dist/logger/transports/file.d.ts +146 -0
  179. package/dist/logger/transports/file.d.ts.map +1 -0
  180. package/dist/logger/transports/file.js +464 -0
  181. package/dist/logger/transports/file.js.map +1 -0
  182. package/dist/logger/transports/http.d.ts +128 -0
  183. package/dist/logger/transports/http.d.ts.map +1 -0
  184. package/dist/logger/transports/http.js +401 -0
  185. package/dist/logger/transports/http.js.map +1 -0
  186. package/dist/logger/transports/webhook.d.ts +152 -0
  187. package/dist/logger/transports/webhook.d.ts.map +1 -0
  188. package/dist/logger/transports/webhook.js +485 -0
  189. package/dist/logger/transports/webhook.js.map +1 -0
  190. package/dist/queue/defaults.d.ts +66 -0
  191. package/dist/queue/defaults.d.ts.map +1 -0
  192. package/dist/queue/defaults.js +205 -0
  193. package/dist/queue/defaults.js.map +1 -0
  194. package/dist/queue/index.d.ts +124 -0
  195. package/dist/queue/index.d.ts.map +1 -0
  196. package/dist/queue/index.js +116 -0
  197. package/dist/queue/index.js.map +1 -0
  198. package/dist/queue/queue.d.ts +156 -0
  199. package/dist/queue/queue.d.ts.map +1 -0
  200. package/dist/queue/queue.js +387 -0
  201. package/dist/queue/queue.js.map +1 -0
  202. package/dist/queue/transports/database.d.ts +165 -0
  203. package/dist/queue/transports/database.d.ts.map +1 -0
  204. package/dist/queue/transports/database.js +595 -0
  205. package/dist/queue/transports/database.js.map +1 -0
  206. package/dist/queue/transports/memory.d.ts +143 -0
  207. package/dist/queue/transports/memory.d.ts.map +1 -0
  208. package/dist/queue/transports/memory.js +415 -0
  209. package/dist/queue/transports/memory.js.map +1 -0
  210. package/dist/queue/transports/redis.d.ts +203 -0
  211. package/dist/queue/transports/redis.d.ts.map +1 -0
  212. package/dist/queue/transports/redis.js +744 -0
  213. package/dist/queue/transports/redis.js.map +1 -0
  214. package/dist/security/defaults.d.ts +64 -0
  215. package/dist/security/defaults.d.ts.map +1 -0
  216. package/dist/security/defaults.js +159 -0
  217. package/dist/security/defaults.js.map +1 -0
  218. package/dist/security/index.d.ts +110 -0
  219. package/dist/security/index.d.ts.map +1 -0
  220. package/dist/security/index.js +160 -0
  221. package/dist/security/index.js.map +1 -0
  222. package/dist/security/security.d.ts +138 -0
  223. package/dist/security/security.d.ts.map +1 -0
  224. package/dist/security/security.js +419 -0
  225. package/dist/security/security.js.map +1 -0
  226. package/dist/storage/defaults.d.ts +79 -0
  227. package/dist/storage/defaults.d.ts.map +1 -0
  228. package/dist/storage/defaults.js +358 -0
  229. package/dist/storage/defaults.js.map +1 -0
  230. package/dist/storage/index.d.ts +153 -0
  231. package/dist/storage/index.d.ts.map +1 -0
  232. package/dist/storage/index.js +242 -0
  233. package/dist/storage/index.js.map +1 -0
  234. package/dist/storage/storage.d.ts +151 -0
  235. package/dist/storage/storage.d.ts.map +1 -0
  236. package/dist/storage/storage.js +439 -0
  237. package/dist/storage/storage.js.map +1 -0
  238. package/dist/storage/strategies/local.d.ts +117 -0
  239. package/dist/storage/strategies/local.d.ts.map +1 -0
  240. package/dist/storage/strategies/local.js +368 -0
  241. package/dist/storage/strategies/local.js.map +1 -0
  242. package/dist/storage/strategies/r2.d.ts +130 -0
  243. package/dist/storage/strategies/r2.d.ts.map +1 -0
  244. package/dist/storage/strategies/r2.js +470 -0
  245. package/dist/storage/strategies/r2.js.map +1 -0
  246. package/dist/storage/strategies/s3.d.ts +121 -0
  247. package/dist/storage/strategies/s3.d.ts.map +1 -0
  248. package/dist/storage/strategies/s3.js +461 -0
  249. package/dist/storage/strategies/s3.js.map +1 -0
  250. package/dist/util/defaults.d.ts +77 -0
  251. package/dist/util/defaults.d.ts.map +1 -0
  252. package/dist/util/defaults.js +193 -0
  253. package/dist/util/defaults.js.map +1 -0
  254. package/dist/util/index.d.ts +97 -0
  255. package/dist/util/index.d.ts.map +1 -0
  256. package/dist/util/index.js +165 -0
  257. package/dist/util/index.js.map +1 -0
  258. package/dist/util/util.d.ts +145 -0
  259. package/dist/util/util.d.ts.map +1 -0
  260. package/dist/util/util.js +481 -0
  261. package/dist/util/util.js.map +1 -0
  262. package/package.json +234 -0
@@ -0,0 +1,470 @@
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
+ /**
11
+ * Cloudflare R2 storage strategy with cost optimization and CDN integration
12
+ */
13
+ export class R2Strategy {
14
+ config;
15
+ r2Client = null;
16
+ connected = false;
17
+ bucket;
18
+ accountId;
19
+ cdnUrl;
20
+ endpoint;
21
+ /**
22
+ * Creates R2 strategy with direct environment access (like auth pattern)
23
+ * @llm-rule WHEN: Storage initialization with Cloudflare R2 environment variables detected
24
+ * @llm-rule AVOID: Manual R2 configuration - environment detection handles Cloudflare specifics
25
+ */
26
+ constructor(config) {
27
+ this.config = config;
28
+ if (!config.r2) {
29
+ throw new Error('R2 storage configuration missing');
30
+ }
31
+ this.bucket = config.r2.bucket;
32
+ this.accountId = config.r2.accountId;
33
+ this.cdnUrl = config.r2.cdnUrl;
34
+ // Build R2 endpoint URL
35
+ this.endpoint = `https://${this.accountId}.r2.cloudflarestorage.com`;
36
+ }
37
+ /**
38
+ * Connects to Cloudflare R2 with automatic account validation
39
+ * @llm-rule WHEN: Storage initialization or reconnection after failure
40
+ * @llm-rule AVOID: Manual R2 client setup - this handles Cloudflare-specific configuration
41
+ */
42
+ async connect() {
43
+ if (this.connected)
44
+ return;
45
+ try {
46
+ // Dynamic import for S3 client (R2 is S3-compatible)
47
+ const { S3Client } = await import('@aws-sdk/client-s3');
48
+ const r2Config = this.config.r2;
49
+ // Build R2 client configuration (S3-compatible)
50
+ const clientConfig = {
51
+ region: 'auto', // R2 uses 'auto' region
52
+ endpoint: this.endpoint,
53
+ credentials: {
54
+ accessKeyId: r2Config.accessKeyId,
55
+ secretAccessKey: r2Config.secretAccessKey,
56
+ },
57
+ forcePathStyle: false, // R2 uses virtual-hosted-style
58
+ maxAttempts: 3, // Built-in retry logic
59
+ };
60
+ // Create R2 client using S3 SDK
61
+ this.r2Client = new S3Client(clientConfig);
62
+ // Test connection by checking if bucket exists
63
+ await this.testConnection();
64
+ this.connected = true;
65
+ if (this.config.environment.isDevelopment) {
66
+ console.log(`✅ [AppKit] R2 storage connected (account: ${this.accountId}, bucket: ${this.bucket})`);
67
+ if (this.cdnUrl) {
68
+ console.log(`🚀 [AppKit] R2 CDN enabled: ${this.cdnUrl}`);
69
+ }
70
+ }
71
+ }
72
+ catch (error) {
73
+ this.connected = false;
74
+ this.r2Client = null;
75
+ throw new Error(`R2 storage connection failed: ${error.message}`);
76
+ }
77
+ }
78
+ /**
79
+ * Tests R2 connection by checking bucket access
80
+ */
81
+ async testConnection() {
82
+ const { HeadBucketCommand } = await import('@aws-sdk/client-s3');
83
+ try {
84
+ await this.r2Client.send(new HeadBucketCommand({ Bucket: this.bucket }));
85
+ }
86
+ catch (error) {
87
+ if (error.name === 'NotFound') {
88
+ throw new Error(`R2 bucket not found: ${this.bucket}`);
89
+ }
90
+ if (error.name === 'Forbidden') {
91
+ throw new Error(`R2 bucket access denied: ${this.bucket}. Check API token permissions.`);
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ /**
97
+ * Stores file to R2 with automatic content type detection and zero egress cost
98
+ * @llm-rule WHEN: Uploading files to Cloudflare R2 for cost-effective storage
99
+ * @llm-rule AVOID: Manual R2 operations - this handles R2-specific optimizations
100
+ */
101
+ async put(key, data, options) {
102
+ if (!this.connected || !this.r2Client) {
103
+ throw new Error('R2 storage not connected');
104
+ }
105
+ try {
106
+ const { PutObjectCommand } = await import('@aws-sdk/client-s3');
107
+ // Build R2 put parameters (S3-compatible)
108
+ const params = {
109
+ Bucket: this.bucket,
110
+ Key: key,
111
+ Body: data,
112
+ ContentType: options?.contentType || this.detectContentType(key, data),
113
+ };
114
+ // Add R2-optimized parameters
115
+ if (options?.cacheControl) {
116
+ params.CacheControl = options.cacheControl;
117
+ }
118
+ else {
119
+ // R2 default cache control for better CDN performance
120
+ params.CacheControl = 'public, max-age=31536000'; // 1 year for static assets
121
+ }
122
+ if (options?.expires) {
123
+ params.Expires = options.expires;
124
+ }
125
+ if (options?.metadata) {
126
+ params.Metadata = options.metadata;
127
+ }
128
+ // Execute upload to R2
129
+ await this.r2Client.send(new PutObjectCommand(params));
130
+ if (this.config.environment.isDevelopment) {
131
+ console.log(`☁️ [AppKit] R2 file uploaded: ${key} (${data.length} bytes, zero egress cost)`);
132
+ }
133
+ return key;
134
+ }
135
+ catch (error) {
136
+ throw new Error(`R2 upload failed: ${error.message}`);
137
+ }
138
+ }
139
+ /**
140
+ * Retrieves file from R2 with streaming support and zero egress cost
141
+ * @llm-rule WHEN: Downloading files from Cloudflare R2
142
+ * @llm-rule AVOID: Manual R2 operations - this handles streaming and cost optimization
143
+ */
144
+ async get(key) {
145
+ if (!this.connected || !this.r2Client) {
146
+ throw new Error('R2 storage not connected');
147
+ }
148
+ try {
149
+ const { GetObjectCommand } = await import('@aws-sdk/client-s3');
150
+ const params = {
151
+ Bucket: this.bucket,
152
+ Key: key,
153
+ };
154
+ const result = await this.r2Client.send(new GetObjectCommand(params));
155
+ if (!result.Body) {
156
+ throw new Error(`R2 object has no body: ${key}`);
157
+ }
158
+ // Convert stream to buffer
159
+ const buffer = await this.streamToBuffer(result.Body);
160
+ if (this.config.environment.isDevelopment) {
161
+ console.log(`☁️ [AppKit] R2 file downloaded: ${key} (${buffer.length} bytes, zero egress cost)`);
162
+ }
163
+ return buffer;
164
+ }
165
+ catch (error) {
166
+ if (error.name === 'NoSuchKey') {
167
+ throw new Error(`File not found: ${key}`);
168
+ }
169
+ throw new Error(`R2 download failed: ${error.message}`);
170
+ }
171
+ }
172
+ /**
173
+ * Deletes file from R2 with confirmation
174
+ * @llm-rule WHEN: Removing files from Cloudflare R2 storage
175
+ * @llm-rule AVOID: Silent failures - this confirms deletion success
176
+ */
177
+ async delete(key) {
178
+ if (!this.connected || !this.r2Client) {
179
+ console.error('R2 storage not connected');
180
+ return false;
181
+ }
182
+ try {
183
+ const { DeleteObjectCommand } = await import('@aws-sdk/client-s3');
184
+ const params = {
185
+ Bucket: this.bucket,
186
+ Key: key,
187
+ };
188
+ await this.r2Client.send(new DeleteObjectCommand(params));
189
+ if (this.config.environment.isDevelopment) {
190
+ console.log(`🗑️ [AppKit] R2 file deleted: ${key}`);
191
+ }
192
+ return true;
193
+ }
194
+ catch (error) {
195
+ console.error(`[AppKit] R2 delete error for "${key}":`, error.message);
196
+ return false;
197
+ }
198
+ }
199
+ /**
200
+ * Lists files with prefix filtering and R2-optimized pagination
201
+ * @llm-rule WHEN: Browsing R2 files or implementing file managers
202
+ * @llm-rule AVOID: Loading all objects - R2 has same limits as S3
203
+ */
204
+ async list(prefix = '') {
205
+ if (!this.connected || !this.r2Client) {
206
+ throw new Error('R2 storage not connected');
207
+ }
208
+ try {
209
+ const { ListObjectsV2Command } = await import('@aws-sdk/client-s3');
210
+ const params = {
211
+ Bucket: this.bucket,
212
+ MaxKeys: 1000, // R2 limit same as S3
213
+ };
214
+ if (prefix) {
215
+ params.Prefix = prefix;
216
+ }
217
+ const result = await this.r2Client.send(new ListObjectsV2Command(params));
218
+ const files = [];
219
+ if (result.Contents) {
220
+ for (const object of result.Contents) {
221
+ if (object.Key) {
222
+ files.push({
223
+ key: object.Key,
224
+ size: object.Size || 0,
225
+ lastModified: object.LastModified || new Date(),
226
+ etag: object.ETag?.replace(/"/g, ''), // Remove quotes from ETag
227
+ contentType: await this.getObjectContentType(object.Key),
228
+ });
229
+ }
230
+ }
231
+ }
232
+ if (this.config.environment.isDevelopment) {
233
+ console.log(`☁️ [AppKit] R2 files listed: ${prefix}* (${files.length} files)`);
234
+ }
235
+ return files;
236
+ }
237
+ catch (error) {
238
+ console.error(`[AppKit] R2 list error for prefix "${prefix}":`, error.message);
239
+ return [];
240
+ }
241
+ }
242
+ /**
243
+ * Gets CDN or public URL for R2 object with automatic CDN detection
244
+ * @llm-rule WHEN: Generating URLs for R2 file access with CDN optimization
245
+ * @llm-rule AVOID: Hardcoded URLs - this handles CDN and R2-specific URLs
246
+ */
247
+ url(key) {
248
+ // Use custom CDN URL if configured (recommended for production)
249
+ if (this.cdnUrl) {
250
+ const baseUrl = this.cdnUrl.endsWith('/') ? this.cdnUrl : this.cdnUrl + '/';
251
+ const cleanKey = key.startsWith('/') ? key.slice(1) : key;
252
+ return baseUrl + cleanKey;
253
+ }
254
+ // Use R2 public URL (has rate limits, CDN recommended)
255
+ return `https://pub-${this.generatePublicHash()}.r2.dev/${key}`;
256
+ }
257
+ /**
258
+ * Generates R2 public URL hash (simplified for demo)
259
+ */
260
+ generatePublicHash() {
261
+ // In production, this would be the actual R2 public URL hash
262
+ // For now, we'll use a placeholder that works with the R2 endpoint
263
+ return this.accountId.slice(0, 8);
264
+ }
265
+ /**
266
+ * Generates signed URL for temporary R2 object access
267
+ * @llm-rule WHEN: Creating temporary download/upload links for R2 objects
268
+ * @llm-rule AVOID: Public URLs for private files - use signed URLs with expiration
269
+ */
270
+ async signedUrl(key, expiresIn = 3600) {
271
+ if (!this.connected || !this.r2Client) {
272
+ throw new Error('R2 storage not connected');
273
+ }
274
+ try {
275
+ const { GetObjectCommand } = await import('@aws-sdk/client-s3');
276
+ const { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');
277
+ const command = new GetObjectCommand({
278
+ Bucket: this.bucket,
279
+ Key: key,
280
+ });
281
+ const signedUrl = await getSignedUrl(this.r2Client, command, {
282
+ expiresIn,
283
+ });
284
+ if (this.config.environment.isDevelopment) {
285
+ console.log(`🔐 [AppKit] R2 signed URL generated: ${key} (expires in ${expiresIn}s)`);
286
+ }
287
+ return signedUrl;
288
+ }
289
+ catch (error) {
290
+ throw new Error(`R2 signed URL generation failed: ${error.message}`);
291
+ }
292
+ }
293
+ /**
294
+ * Checks if R2 object exists without downloading
295
+ * @llm-rule WHEN: Validating R2 object existence efficiently
296
+ * @llm-rule AVOID: Downloading objects just to check existence
297
+ */
298
+ async exists(key) {
299
+ if (!this.connected || !this.r2Client) {
300
+ return false;
301
+ }
302
+ try {
303
+ const { HeadObjectCommand } = await import('@aws-sdk/client-s3');
304
+ const params = {
305
+ Bucket: this.bucket,
306
+ Key: key,
307
+ };
308
+ await this.r2Client.send(new HeadObjectCommand(params));
309
+ return true;
310
+ }
311
+ catch (error) {
312
+ if (error.name === 'NotFound' || error.name === 'NoSuchKey') {
313
+ return false;
314
+ }
315
+ console.error(`[AppKit] R2 exists check error for "${key}":`, error.message);
316
+ return false;
317
+ }
318
+ }
319
+ /**
320
+ * Copies R2 object efficiently using server-side copy (zero egress cost)
321
+ * @llm-rule WHEN: Duplicating R2 objects without bandwidth costs
322
+ * @llm-rule AVOID: Download and upload - R2 server-side copy has zero egress fees
323
+ */
324
+ async copy(sourceKey, destKey) {
325
+ if (!this.connected || !this.r2Client) {
326
+ throw new Error('R2 storage not connected');
327
+ }
328
+ try {
329
+ const { CopyObjectCommand } = await import('@aws-sdk/client-s3');
330
+ const params = {
331
+ Bucket: this.bucket,
332
+ Key: destKey,
333
+ CopySource: `${this.bucket}/${sourceKey}`,
334
+ };
335
+ await this.r2Client.send(new CopyObjectCommand(params));
336
+ if (this.config.environment.isDevelopment) {
337
+ console.log(`☁️ [AppKit] R2 file copied: ${sourceKey} → ${destKey} (zero egress cost)`);
338
+ }
339
+ return destKey;
340
+ }
341
+ catch (error) {
342
+ throw new Error(`R2 copy failed: ${error.message}`);
343
+ }
344
+ }
345
+ /**
346
+ * Disconnects R2 strategy gracefully
347
+ * @llm-rule WHEN: App shutdown or storage cleanup
348
+ * @llm-rule AVOID: Abrupt disconnection - graceful shutdown prevents connection issues
349
+ */
350
+ async disconnect() {
351
+ if (!this.connected)
352
+ return;
353
+ try {
354
+ // R2 client doesn't need explicit disconnection (uses S3 SDK)
355
+ this.connected = false;
356
+ this.r2Client = null;
357
+ if (this.config.environment.isDevelopment) {
358
+ console.log(`👋 [AppKit] R2 storage strategy disconnected`);
359
+ }
360
+ }
361
+ catch (error) {
362
+ console.error(`[AppKit] R2 disconnect error:`, error.message);
363
+ }
364
+ }
365
+ // Private helper methods
366
+ /**
367
+ * Converts readable stream to buffer
368
+ */
369
+ async streamToBuffer(stream) {
370
+ const chunks = [];
371
+ return new Promise((resolve, reject) => {
372
+ stream.on('data', (chunk) => chunks.push(chunk));
373
+ stream.on('error', reject);
374
+ stream.on('end', () => resolve(Buffer.concat(chunks)));
375
+ });
376
+ }
377
+ /**
378
+ * Detects content type from file extension and buffer
379
+ */
380
+ detectContentType(key, buffer) {
381
+ // Get extension from key
382
+ const ext = key.split('.').pop()?.toLowerCase();
383
+ // Common MIME types optimized for web delivery
384
+ const mimeTypes = {
385
+ 'jpg': 'image/jpeg',
386
+ 'jpeg': 'image/jpeg',
387
+ 'png': 'image/png',
388
+ 'gif': 'image/gif',
389
+ 'webp': 'image/webp',
390
+ 'avif': 'image/avif', // Modern format support
391
+ 'svg': 'image/svg+xml',
392
+ 'pdf': 'application/pdf',
393
+ 'txt': 'text/plain',
394
+ 'json': 'application/json',
395
+ 'csv': 'text/csv',
396
+ 'zip': 'application/zip',
397
+ 'mp4': 'video/mp4',
398
+ 'webm': 'video/webm',
399
+ 'mp3': 'audio/mpeg',
400
+ 'wav': 'audio/wav',
401
+ 'woff2': 'font/woff2',
402
+ 'woff': 'font/woff',
403
+ 'css': 'text/css',
404
+ 'js': 'text/javascript',
405
+ };
406
+ if (ext && mimeTypes[ext]) {
407
+ return mimeTypes[ext];
408
+ }
409
+ // Buffer-based detection for common formats
410
+ const magic = buffer.subarray(0, 4);
411
+ if (magic[0] === 0xFF && magic[1] === 0xD8 && magic[2] === 0xFF) {
412
+ return 'image/jpeg';
413
+ }
414
+ if (magic[0] === 0x89 && magic[1] === 0x50 && magic[2] === 0x4E && magic[3] === 0x47) {
415
+ return 'image/png';
416
+ }
417
+ if (magic[0] === 0x47 && magic[1] === 0x49 && magic[2] === 0x46) {
418
+ return 'image/gif';
419
+ }
420
+ return 'application/octet-stream';
421
+ }
422
+ /**
423
+ * Gets content type for existing R2 object
424
+ */
425
+ async getObjectContentType(key) {
426
+ try {
427
+ const { HeadObjectCommand } = await import('@aws-sdk/client-s3');
428
+ const params = {
429
+ Bucket: this.bucket,
430
+ Key: key,
431
+ };
432
+ const result = await this.r2Client.send(new HeadObjectCommand(params));
433
+ return result.ContentType;
434
+ }
435
+ catch (error) {
436
+ // If we can't get content type, return undefined
437
+ return undefined;
438
+ }
439
+ }
440
+ /**
441
+ * Gets R2 connection info for debugging
442
+ */
443
+ getConnectionInfo() {
444
+ return {
445
+ connected: this.connected,
446
+ bucket: this.bucket,
447
+ accountId: this.accountId,
448
+ endpoint: this.endpoint,
449
+ cdnEnabled: !!this.cdnUrl,
450
+ zeroEgressFees: true, // R2's key advantage
451
+ };
452
+ }
453
+ /**
454
+ * Gets R2-specific cost optimization info
455
+ */
456
+ getCostInfo() {
457
+ return {
458
+ egressFees: 'Zero egress fees',
459
+ storageClass: 'Hot storage with instant access',
460
+ cdnIntegration: !!this.cdnUrl,
461
+ recommendedFor: [
462
+ 'High-bandwidth applications',
463
+ 'Media streaming',
464
+ 'Global CDN delivery',
465
+ 'Cost-sensitive workloads'
466
+ ],
467
+ };
468
+ }
469
+ }
470
+ //# sourceMappingURL=r2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2.js","sourceRoot":"","sources":["../../../src/storage/strategies/r2.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * S3-compatible storage strategy with automatic connection management and multi-provider support
3
+ * @module @bloomneo/appkit/storage
4
+ * @file src/storage/strategies/s3.ts
5
+ *
6
+ * @llm-rule WHEN: App has AWS_S3_BUCKET or S3_ENDPOINT env vars for distributed cloud storage
7
+ * @llm-rule AVOID: Manual S3 setup - this handles AWS, Wasabi, MinIO, DigitalOcean Spaces automatically
8
+ * @llm-rule NOTE: Production-ready with retry logic, signed URLs, CDN support, automatic MIME detection
9
+ */
10
+ import type { StorageStrategy, StorageFile, PutOptions } from '../storage.js';
11
+ import type { StorageConfig } from '../defaults.js';
12
+ /**
13
+ * S3-compatible storage strategy with multi-provider support and reliability features
14
+ */
15
+ export declare class S3Strategy implements StorageStrategy {
16
+ private config;
17
+ private s3Client;
18
+ private connected;
19
+ private bucket;
20
+ private region;
21
+ private endpoint?;
22
+ private cdnUrl?;
23
+ /**
24
+ * Creates S3 strategy with direct environment access (like auth pattern)
25
+ * @llm-rule WHEN: Storage initialization with S3-compatible environment variables detected
26
+ * @llm-rule AVOID: Manual S3 configuration - environment detection handles AWS/Wasabi/MinIO
27
+ */
28
+ constructor(config: StorageConfig);
29
+ /**
30
+ * Connects to S3-compatible service with automatic retry and provider detection
31
+ * @llm-rule WHEN: Storage initialization or reconnection after failure
32
+ * @llm-rule AVOID: Manual S3 client setup - this handles all provider configurations
33
+ */
34
+ connect(): Promise<void>;
35
+ /**
36
+ * Tests S3 connection by checking bucket access
37
+ */
38
+ private testConnection;
39
+ /**
40
+ * Detects S3 provider from configuration
41
+ */
42
+ private detectProvider;
43
+ /**
44
+ * Stores file to S3 with automatic content type detection and metadata
45
+ * @llm-rule WHEN: Uploading files to S3-compatible cloud storage
46
+ * @llm-rule AVOID: Manual S3 operations - this handles multipart uploads and metadata
47
+ */
48
+ put(key: string, data: Buffer, options?: PutOptions): Promise<string>;
49
+ /**
50
+ * Retrieves file from S3 with streaming support
51
+ * @llm-rule WHEN: Downloading files from S3-compatible storage
52
+ * @llm-rule AVOID: Manual S3 operations - this handles streaming and errors
53
+ */
54
+ get(key: string): Promise<Buffer>;
55
+ /**
56
+ * Deletes file from S3 with confirmation
57
+ * @llm-rule WHEN: Removing files from S3-compatible storage
58
+ * @llm-rule AVOID: Silent failures - this confirms deletion success
59
+ */
60
+ delete(key: string): Promise<boolean>;
61
+ /**
62
+ * Lists files with prefix filtering and pagination
63
+ * @llm-rule WHEN: Browsing S3 files or implementing file managers
64
+ * @llm-rule AVOID: Loading all objects - use prefix filtering and pagination
65
+ */
66
+ list(prefix?: string): Promise<StorageFile[]>;
67
+ /**
68
+ * Gets public or CDN URL for S3 object
69
+ * @llm-rule WHEN: Generating URLs for S3 file access with CDN support
70
+ * @llm-rule AVOID: Hardcoded URLs - this handles CDN and region-specific URLs
71
+ */
72
+ url(key: string): string;
73
+ /**
74
+ * Generates signed URL for temporary S3 object access
75
+ * @llm-rule WHEN: Creating temporary download/upload links for S3 objects
76
+ * @llm-rule AVOID: Permanent URLs for private files - use signed URLs with expiration
77
+ */
78
+ signedUrl(key: string, expiresIn?: number): Promise<string>;
79
+ /**
80
+ * Checks if S3 object exists without downloading
81
+ * @llm-rule WHEN: Validating S3 object existence efficiently
82
+ * @llm-rule AVOID: Downloading objects just to check existence
83
+ */
84
+ exists(key: string): Promise<boolean>;
85
+ /**
86
+ * Copies S3 object efficiently using server-side copy
87
+ * @llm-rule WHEN: Duplicating S3 objects without downloading/uploading
88
+ * @llm-rule AVOID: Download and upload - S3 server-side copy is much faster
89
+ */
90
+ copy(sourceKey: string, destKey: string): Promise<string>;
91
+ /**
92
+ * Disconnects S3 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 S3 object
107
+ */
108
+ private getObjectContentType;
109
+ /**
110
+ * Gets S3 connection info for debugging
111
+ */
112
+ getConnectionInfo(): {
113
+ connected: boolean;
114
+ bucket: string;
115
+ region: string;
116
+ endpoint?: string;
117
+ provider: string;
118
+ cdnEnabled: boolean;
119
+ };
120
+ }
121
+ //# sourceMappingURL=s3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../../src/storage/strategies/s3.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,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAC,CAAS;IAExB;;;;OAIG;gBACS,MAAM,EAAE,aAAa;IAajC;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA4C9B;;OAEG;YACW,cAAc;IAgB5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB;;;;OAIG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IA0C3E;;;;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;IAmBxB;;;;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;IAoBjC;;OAEG;YACW,cAAc;IAU5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA6CzB;;OAEG;YACW,oBAAoB;IAiBlC;;OAEG;IACH,iBAAiB,IAAI;QACnB,SAAS,EAAE,OAAO,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,OAAO,CAAC;KACrB;CAUF"}