alepha 0.20.2 → 0.20.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/README.md +0 -1
  2. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  3. package/assets/swagger-ui/swagger-ui.css +1 -1
  4. package/dist/api/audits/index.browser.js +49 -0
  5. package/dist/api/audits/index.browser.js.map +1 -1
  6. package/dist/api/audits/index.js +49 -0
  7. package/dist/api/audits/index.js.map +1 -1
  8. package/dist/api/files/index.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts +2 -61
  10. package/dist/api/jobs/index.d.ts.map +1 -1
  11. package/dist/api/jobs/index.js.map +1 -1
  12. package/dist/api/keys/index.d.ts +4 -4
  13. package/dist/api/keys/index.js.map +1 -1
  14. package/dist/api/notifications/index.d.ts +1 -10
  15. package/dist/api/notifications/index.d.ts.map +1 -1
  16. package/dist/api/parameters/index.browser.js +37 -0
  17. package/dist/api/parameters/index.browser.js.map +1 -1
  18. package/dist/api/parameters/index.d.ts +12 -68
  19. package/dist/api/parameters/index.d.ts.map +1 -1
  20. package/dist/api/parameters/index.js +57 -4
  21. package/dist/api/parameters/index.js.map +1 -1
  22. package/dist/api/payments/index.js.map +1 -1
  23. package/dist/api/users/index.browser.js +6 -0
  24. package/dist/api/users/index.browser.js.map +1 -1
  25. package/dist/api/users/index.d.ts +148 -227
  26. package/dist/api/users/index.d.ts.map +1 -1
  27. package/dist/api/users/index.js +60 -14
  28. package/dist/api/users/index.js.map +1 -1
  29. package/dist/api/verifications/index.d.ts.map +1 -1
  30. package/dist/api/verifications/index.js +2 -1
  31. package/dist/api/verifications/index.js.map +1 -1
  32. package/dist/bucket/index.d.ts +77 -107
  33. package/dist/bucket/index.d.ts.map +1 -1
  34. package/dist/bucket/index.js +153 -5
  35. package/dist/bucket/index.js.map +1 -1
  36. package/dist/bucket/index.workerd.js +12 -2
  37. package/dist/bucket/index.workerd.js.map +1 -1
  38. package/dist/cache/core/index.d.ts +26 -0
  39. package/dist/cache/core/index.d.ts.map +1 -1
  40. package/dist/cache/core/index.js +11 -1
  41. package/dist/cache/core/index.js.map +1 -1
  42. package/dist/cache/core/index.workerd.js +11 -1
  43. package/dist/cache/core/index.workerd.js.map +1 -1
  44. package/dist/captcha/index.js.map +1 -1
  45. package/dist/cli/config/index.d.ts +7 -5
  46. package/dist/cli/config/index.d.ts.map +1 -1
  47. package/dist/cli/config/index.js +2 -3
  48. package/dist/cli/config/index.js.map +1 -1
  49. package/dist/cli/core/index.d.ts +637 -11660
  50. package/dist/cli/core/index.d.ts.map +1 -1
  51. package/dist/cli/core/index.js +707 -532
  52. package/dist/cli/core/index.js.map +1 -1
  53. package/dist/cli/devtools/index.d.ts +4 -8
  54. package/dist/cli/devtools/index.d.ts.map +1 -1
  55. package/dist/cli/devtools/index.js +20 -16
  56. package/dist/cli/devtools/index.js.map +1 -1
  57. package/dist/cli/platform/index.d.ts +51 -77
  58. package/dist/cli/platform/index.d.ts.map +1 -1
  59. package/dist/cli/platform/index.js +65 -15
  60. package/dist/cli/platform/index.js.map +1 -1
  61. package/dist/cli/vendor/index.d.ts +10 -13
  62. package/dist/cli/vendor/index.d.ts.map +1 -1
  63. package/dist/cli/vendor/index.js +30 -12
  64. package/dist/cli/vendor/index.js.map +1 -1
  65. package/dist/command/index.js +1 -1
  66. package/dist/command/index.js.map +1 -1
  67. package/dist/core/index.browser.js +27 -3
  68. package/dist/core/index.browser.js.map +1 -1
  69. package/dist/core/index.d.ts +8 -11
  70. package/dist/core/index.d.ts.map +1 -1
  71. package/dist/core/index.js +27 -3
  72. package/dist/core/index.js.map +1 -1
  73. package/dist/core/index.native.js +27 -3
  74. package/dist/core/index.native.js.map +1 -1
  75. package/dist/core/index.workerd.js +27 -3
  76. package/dist/core/index.workerd.js.map +1 -1
  77. package/dist/crypto/index.js.map +1 -1
  78. package/dist/datetime/index.d.ts +69 -10
  79. package/dist/datetime/index.d.ts.map +1 -1
  80. package/dist/datetime/index.js +135 -13
  81. package/dist/datetime/index.js.map +1 -1
  82. package/dist/email/core/index.js.map +1 -1
  83. package/dist/email/smtp/index.js +130 -16
  84. package/dist/email/smtp/index.js.map +1 -1
  85. package/dist/fake/index.js.map +1 -1
  86. package/dist/lock/core/index.d.ts +30 -2
  87. package/dist/lock/core/index.d.ts.map +1 -1
  88. package/dist/lock/core/index.js +35 -12
  89. package/dist/lock/core/index.js.map +1 -1
  90. package/dist/lock/redis/index.js.map +1 -1
  91. package/dist/logger/index.js +32 -1
  92. package/dist/logger/index.js.map +1 -1
  93. package/dist/mcp/index.d.ts +238 -31
  94. package/dist/mcp/index.d.ts.map +1 -1
  95. package/dist/mcp/index.js +198 -67
  96. package/dist/mcp/index.js.map +1 -1
  97. package/dist/orm/core/index.browser.js +2 -362
  98. package/dist/orm/core/index.browser.js.map +1 -1
  99. package/dist/orm/core/index.bun.js +18 -409
  100. package/dist/orm/core/index.bun.js.map +1 -1
  101. package/dist/orm/core/index.d.ts +41 -194
  102. package/dist/orm/core/index.d.ts.map +1 -1
  103. package/dist/orm/core/index.js +27 -422
  104. package/dist/orm/core/index.js.map +1 -1
  105. package/dist/orm/postgres/index.bun.js +17 -20
  106. package/dist/orm/postgres/index.bun.js.map +1 -1
  107. package/dist/orm/postgres/index.d.ts +1 -5
  108. package/dist/orm/postgres/index.d.ts.map +1 -1
  109. package/dist/orm/postgres/index.js +17 -20
  110. package/dist/orm/postgres/index.js.map +1 -1
  111. package/dist/react/core/index.d.ts +102 -1
  112. package/dist/react/core/index.d.ts.map +1 -1
  113. package/dist/react/core/index.js +65 -1
  114. package/dist/react/core/index.js.map +1 -1
  115. package/dist/react/form/index.d.ts +6 -0
  116. package/dist/react/form/index.d.ts.map +1 -1
  117. package/dist/react/form/index.js +7 -7
  118. package/dist/react/form/index.js.map +1 -1
  119. package/dist/react/i18n/index.d.ts +7 -1
  120. package/dist/react/i18n/index.d.ts.map +1 -1
  121. package/dist/react/i18n/index.js +6 -0
  122. package/dist/react/i18n/index.js.map +1 -1
  123. package/dist/react/intro/index.js +22 -17
  124. package/dist/react/intro/index.js.map +1 -1
  125. package/dist/react/router/index.browser.js +98 -4
  126. package/dist/react/router/index.browser.js.map +1 -1
  127. package/dist/react/router/index.d.ts +58 -5
  128. package/dist/react/router/index.d.ts.map +1 -1
  129. package/dist/react/router/index.js +122 -6
  130. package/dist/react/router/index.js.map +1 -1
  131. package/dist/react/testing/{chunk-DBEY4PJZ.js → chunk-6Ep1yQYe.js} +1 -1
  132. package/dist/react/testing/index.js +1 -1
  133. package/dist/react/testing/index.js.map +1 -1
  134. package/dist/react/ui/index.d.ts +195 -1
  135. package/dist/react/ui/index.d.ts.map +1 -1
  136. package/dist/react/ui/index.js +64 -1
  137. package/dist/react/ui/index.js.map +1 -1
  138. package/dist/react/websocket/index.js.map +1 -1
  139. package/dist/redis/index.js.map +1 -1
  140. package/dist/scheduler/index.d.ts +1 -2
  141. package/dist/scheduler/index.d.ts.map +1 -1
  142. package/dist/scheduler/index.js +1 -1
  143. package/dist/scheduler/index.js.map +1 -1
  144. package/dist/scheduler/index.workerd.js +1 -1
  145. package/dist/scheduler/index.workerd.js.map +1 -1
  146. package/dist/security/index.browser.js.map +1 -1
  147. package/dist/security/index.d.ts.map +1 -1
  148. package/dist/security/index.js +2 -2
  149. package/dist/security/index.js.map +1 -1
  150. package/dist/server/auth/index.d.ts.map +1 -1
  151. package/dist/server/auth/index.js +24 -10
  152. package/dist/server/auth/index.js.map +1 -1
  153. package/dist/server/cookies/index.js.map +1 -1
  154. package/dist/server/core/index.browser.js +10 -3
  155. package/dist/server/core/index.browser.js.map +1 -1
  156. package/dist/server/core/index.d.ts +1 -4
  157. package/dist/server/core/index.d.ts.map +1 -1
  158. package/dist/server/core/index.js +47 -9
  159. package/dist/server/core/index.js.map +1 -1
  160. package/dist/server/links/index.browser.js.map +1 -1
  161. package/dist/server/links/index.js.map +1 -1
  162. package/dist/server/metrics/index.js +19 -1
  163. package/dist/server/metrics/index.js.map +1 -1
  164. package/dist/server/rate-limit/index.js.map +1 -1
  165. package/dist/server/static/index.js.map +1 -1
  166. package/dist/server/swagger/index.d.ts.map +1 -1
  167. package/dist/server/swagger/index.js +4 -5
  168. package/dist/server/swagger/index.js.map +1 -1
  169. package/dist/sms/index.js.map +1 -1
  170. package/dist/system/index.browser.js.map +1 -1
  171. package/dist/system/index.js.map +1 -1
  172. package/dist/system/index.workerd.js.map +1 -1
  173. package/dist/topic/core/index.js.map +1 -1
  174. package/dist/websocket/index.browser.js +32 -5
  175. package/dist/websocket/index.browser.js.map +1 -1
  176. package/dist/websocket/index.d.ts +3 -1
  177. package/dist/websocket/index.d.ts.map +1 -1
  178. package/dist/websocket/index.js +42 -6
  179. package/dist/websocket/index.js.map +1 -1
  180. package/package.json +685 -274
  181. package/src/api/files/__tests__/FileController.spec.ts +1 -1
  182. package/src/api/jobs/__tests__/$job.spec.ts +5 -1
  183. package/src/api/parameters/services/ParameterProvider.ts +21 -4
  184. package/src/api/users/__tests__/SessionService.spec.ts +99 -0
  185. package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
  186. package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
  187. package/src/api/users/entities/sessions.ts +6 -0
  188. package/src/api/users/jobs/UserJobs.ts +44 -17
  189. package/src/api/users/providers/RealmProvider.ts +4 -0
  190. package/src/api/users/schemas/userQuerySchema.ts +0 -1
  191. package/src/api/users/services/SessionService.ts +27 -0
  192. package/src/api/users/services/UserService.ts +1 -5
  193. package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
  194. package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
  195. package/src/api/verifications/services/VerificationService.ts +1 -0
  196. package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
  197. package/src/bucket/index.ts +19 -2
  198. package/src/bucket/primitives/$bucket.ts +9 -1
  199. package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
  200. package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
  201. package/src/cache/core/index.ts +29 -0
  202. package/src/cache/core/primitives/$cache.ts +14 -1
  203. package/src/cli/config/defineConfig.ts +13 -15
  204. package/src/cli/core/__tests__/init.spec.ts +214 -7
  205. package/src/cli/core/commands/init.ts +12 -0
  206. package/src/cli/core/services/PackageManagerUtils.ts +23 -6
  207. package/src/cli/core/services/ProjectScaffolder.ts +315 -33
  208. package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
  209. package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
  210. package/src/cli/core/tasks/BuildServerTask.ts +8 -0
  211. package/src/cli/core/templates/agentMd.ts +2 -10
  212. package/src/cli/core/templates/apiIndexTs.ts +23 -1
  213. package/src/cli/core/templates/componentsJsonTs.ts +39 -0
  214. package/src/cli/core/templates/mainCss.ts +1 -0
  215. package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
  216. package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
  217. package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
  218. package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
  219. package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
  220. package/src/cli/core/templates/webAppRouterTs.ts +104 -1
  221. package/src/cli/core/templates/webIndexTs.ts +23 -1
  222. package/src/cli/devtools/index.ts +12 -26
  223. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
  224. package/src/cli/platform/index.ts +15 -24
  225. package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
  226. package/src/cli/vendor/index.ts +14 -23
  227. package/src/command/providers/CliProvider.ts +1 -1
  228. package/src/core/Alepha.ts +11 -1
  229. package/src/core/helpers/ref.ts +18 -0
  230. package/src/core/index.shared.ts +1 -0
  231. package/src/core/interfaces/Service.ts +3 -1
  232. package/src/core/providers/SchemaValidator.ts +9 -1
  233. package/src/core/providers/TypeProvider.ts +2 -3
  234. package/src/datetime/REFACTORING.md +118 -0
  235. package/src/datetime/providers/DateTimeProvider.ts +203 -24
  236. package/src/lock/core/index.ts +31 -0
  237. package/src/lock/core/primitives/$lock.ts +14 -1
  238. package/src/logger/services/Logger.ts +1 -1
  239. package/src/mcp/__tests__/$resource.spec.ts +1 -1
  240. package/src/mcp/__tests__/$tool.spec.ts +1 -1
  241. package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
  242. package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
  243. package/src/mcp/helpers/jsonrpc.ts +26 -1
  244. package/src/mcp/index.ts +10 -5
  245. package/src/mcp/interfaces/McpTypes.ts +83 -6
  246. package/src/mcp/primitives/$prompt.ts +18 -1
  247. package/src/mcp/primitives/$resource.ts +18 -1
  248. package/src/mcp/primitives/$tool.ts +83 -7
  249. package/src/mcp/providers/McpServerProvider.ts +74 -16
  250. package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
  251. package/src/orm/REFACTORING.md +330 -0
  252. package/src/orm/__tests__/$repository-tests.ts +1 -0
  253. package/src/orm/__tests__/orm-next-tests.ts +2 -67
  254. package/src/orm/__tests__/orm-next.spec.ts +0 -21
  255. package/src/orm/core/index.shared.ts +0 -2
  256. package/src/orm/core/index.ts +1 -2
  257. package/src/orm/core/primitives/$repository.ts +3 -6
  258. package/src/orm/core/primitives/$transactional.ts +11 -0
  259. package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
  260. package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
  261. package/src/orm/core/schemas/updateSchema.ts +1 -1
  262. package/src/orm/core/services/ModelBuilder.ts +1 -13
  263. package/src/orm/core/services/PgRelationManager.ts +4 -2
  264. package/src/orm/core/services/Repository.ts +1 -42
  265. package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
  266. package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
  267. package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
  268. package/src/react/core/hooks/useQuery.ts +153 -0
  269. package/src/react/core/index.ts +1 -0
  270. package/src/react/form/services/FormModel.ts +15 -6
  271. package/src/react/form/services/parseField.ts +8 -0
  272. package/src/react/i18n/providers/I18nProvider.ts +8 -2
  273. package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
  274. package/src/react/router/__tests__/$page.spec.tsx +0 -16
  275. package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
  276. package/src/react/router/__tests__/ssr.spec.tsx +339 -0
  277. package/src/react/router/primitives/$page.ts +28 -4
  278. package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
  279. package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
  280. package/src/react/router/providers/ReactPageProvider.ts +27 -9
  281. package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
  282. package/src/react/router/providers/ReactServerProvider.ts +1 -0
  283. package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
  284. package/src/react/ui/index.ts +6 -0
  285. package/src/react/ui/services/SchemaControl.ts +209 -0
  286. package/src/scheduler/providers/CronProvider.ts +1 -1
  287. package/src/security/primitives/$basicAuth.ts +1 -1
  288. package/src/security/primitives/$issuer.ts +6 -3
  289. package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
  290. package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
  291. package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
  292. package/src/server/core/errors/ValidationError.ts +13 -1
  293. package/src/server/core/interfaces/ServerRequest.ts +1 -0
  294. package/src/server/core/primitives/$action.ts +16 -5
  295. package/src/server/core/providers/ServerProvider.ts +1 -1
  296. package/src/server/core/providers/ServerRouterProvider.ts +28 -6
  297. package/src/server/core/services/HttpClient.ts +1 -1
  298. package/src/server/swagger/providers/ServerSwaggerProvider.ts +6 -8
  299. package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
  300. package/src/websocket/services/WebSocketClient.ts +11 -5
  301. package/src/mcp/transports/SseMcpTransport.ts +0 -182
  302. package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
  303. package/src/orm/core/helpers/parseQueryString.ts +0 -502
  304. package/src/orm/core/primitives/$view.ts +0 -88
@@ -7,6 +7,7 @@ import {
7
7
  import { FileStorageProvider } from "./providers/FileStorageProvider.ts";
8
8
  import { LocalFileStorageProvider } from "./providers/LocalFileStorageProvider.ts";
9
9
  import { MemoryFileStorageProvider } from "./providers/MemoryFileStorageProvider.ts";
10
+ import { NodeS3BucketProvider } from "./providers/NodeS3BucketProvider.ts";
10
11
 
11
12
  // ---------------------------------------------------------------------------------------------------------------------
12
13
 
@@ -16,6 +17,7 @@ export * from "./providers/CloudflareR2Provider.ts";
16
17
  export * from "./providers/FileStorageProvider.ts";
17
18
  export * from "./providers/LocalFileStorageProvider.ts";
18
19
  export * from "./providers/MemoryFileStorageProvider.ts";
20
+ export * from "./providers/NodeS3BucketProvider.ts";
19
21
 
20
22
  // ---------------------------------------------------------------------------------------------------------------------
21
23
 
@@ -38,6 +40,14 @@ declare module "alepha" {
38
40
  id: string;
39
41
  bucket: BucketPrimitive;
40
42
  };
43
+ /**
44
+ * Triggered when a file is downloaded from a bucket.
45
+ */
46
+ "bucket:file:downloaded": {
47
+ id: string;
48
+ file: FileLike;
49
+ bucket: BucketPrimitive;
50
+ };
41
51
  }
42
52
  }
43
53
 
@@ -61,15 +71,22 @@ export const AlephaBucket = $module({
61
71
  name: "alepha.bucket",
62
72
  primitives: [$bucket],
63
73
  services: [FileStorageProvider],
64
- variants: [MemoryFileStorageProvider, LocalFileStorageProvider],
74
+ variants: [
75
+ MemoryFileStorageProvider,
76
+ LocalFileStorageProvider,
77
+ NodeS3BucketProvider,
78
+ ],
65
79
  register: (alepha) => {
80
+ const useS3 = !!alepha.env.S3_ENDPOINT;
66
81
  alepha.with({
67
82
  optional: true,
68
83
  provide: FileStorageProvider,
69
84
  use:
70
85
  alepha.isTest() || alepha.isServerless()
71
86
  ? MemoryFileStorageProvider
72
- : LocalFileStorageProvider,
87
+ : useS3
88
+ ? NodeS3BucketProvider
89
+ : LocalFileStorageProvider,
73
90
  });
74
91
  },
75
92
  });
@@ -288,7 +288,15 @@ export class BucketPrimitive extends Primitive<BucketPrimitiveOptions> {
288
288
  * Downloads a file from the bucket.
289
289
  */
290
290
  public async download(fileId: string): Promise<FileLike> {
291
- return this.provider.download(this.name, fileId);
291
+ const file = await this.provider.download(this.name, fileId);
292
+
293
+ await this.alepha.events.emit("bucket:file:downloaded", {
294
+ id: fileId,
295
+ bucket: this,
296
+ file,
297
+ });
298
+
299
+ return file;
292
300
  }
293
301
 
294
302
  protected $provider() {
@@ -1,3 +1,4 @@
1
+ import type { R2Bucket } from "@cloudflare/workers-types";
1
2
  import {
2
3
  $env,
3
4
  $hook,
@@ -14,142 +15,6 @@ import type { FileStorageProvider } from "./FileStorageProvider.ts";
14
15
 
15
16
  // ---------------------------------------------------------------------------------------------------------------------
16
17
 
17
- /**
18
- * R2Bucket interface matching Cloudflare's R2 API.
19
- */
20
- export interface R2Bucket {
21
- put(
22
- key: string,
23
- value:
24
- | ReadableStream
25
- | ArrayBuffer
26
- | ArrayBufferView
27
- | string
28
- | Blob
29
- | null,
30
- options?: R2PutOptions,
31
- ): Promise<R2Object | null>;
32
- get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>;
33
- head(key: string): Promise<R2Object | null>;
34
- delete(keys: string | string[]): Promise<void>;
35
- list(options?: R2ListOptions): Promise<R2Objects>;
36
- createMultipartUpload(
37
- key: string,
38
- options?: R2MultipartOptions,
39
- ): Promise<R2MultipartUpload>;
40
- }
41
-
42
- export interface R2Object {
43
- key: string;
44
- version: string;
45
- size: number;
46
- etag: string;
47
- httpEtag: string;
48
- checksums: R2Checksums;
49
- uploaded: Date;
50
- httpMetadata?: R2HTTPMetadata;
51
- customMetadata?: Record<string, string>;
52
- range?: R2Range;
53
- storageClass: string;
54
- }
55
-
56
- export interface R2ObjectBody extends R2Object {
57
- body: ReadableStream;
58
- bodyUsed: boolean;
59
- arrayBuffer(): Promise<ArrayBuffer>;
60
- text(): Promise<string>;
61
- json<T>(): Promise<T>;
62
- blob(): Promise<Blob>;
63
- }
64
-
65
- export interface R2PutOptions {
66
- onlyIf?: R2Conditional;
67
- httpMetadata?: R2HTTPMetadata;
68
- customMetadata?: Record<string, string>;
69
- md5?: ArrayBuffer | string;
70
- sha1?: ArrayBuffer | string;
71
- sha256?: ArrayBuffer | string;
72
- sha384?: ArrayBuffer | string;
73
- sha512?: ArrayBuffer | string;
74
- storageClass?: string;
75
- }
76
-
77
- export interface R2GetOptions {
78
- onlyIf?: R2Conditional;
79
- range?: R2Range;
80
- }
81
-
82
- export interface R2ListOptions {
83
- limit?: number;
84
- prefix?: string;
85
- cursor?: string;
86
- delimiter?: string;
87
- startAfter?: string;
88
- include?: ("httpMetadata" | "customMetadata")[];
89
- }
90
-
91
- export interface R2Objects {
92
- objects: R2Object[];
93
- truncated: boolean;
94
- cursor?: string;
95
- delimitedPrefixes: string[];
96
- }
97
-
98
- export interface R2Checksums {
99
- md5?: ArrayBuffer;
100
- sha1?: ArrayBuffer;
101
- sha256?: ArrayBuffer;
102
- sha384?: ArrayBuffer;
103
- sha512?: ArrayBuffer;
104
- }
105
-
106
- export interface R2HTTPMetadata {
107
- contentType?: string;
108
- contentLanguage?: string;
109
- contentDisposition?: string;
110
- contentEncoding?: string;
111
- cacheControl?: string;
112
- cacheExpiry?: Date;
113
- }
114
-
115
- export interface R2Conditional {
116
- etagMatches?: string;
117
- etagDoesNotMatch?: string;
118
- uploadedBefore?: Date;
119
- uploadedAfter?: Date;
120
- secondsGranularity?: boolean;
121
- }
122
-
123
- export interface R2Range {
124
- offset?: number;
125
- length?: number;
126
- suffix?: number;
127
- }
128
-
129
- export interface R2MultipartOptions {
130
- httpMetadata?: R2HTTPMetadata;
131
- customMetadata?: Record<string, string>;
132
- storageClass?: string;
133
- }
134
-
135
- export interface R2MultipartUpload {
136
- key: string;
137
- uploadId: string;
138
- uploadPart(
139
- partNumber: number,
140
- value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob,
141
- ): Promise<R2UploadedPart>;
142
- abort(): Promise<void>;
143
- complete(uploadedParts: R2UploadedPart[]): Promise<R2Object>;
144
- }
145
-
146
- export interface R2UploadedPart {
147
- partNumber: number;
148
- etag: string;
149
- }
150
-
151
- // ---------------------------------------------------------------------------------------------------------------------
152
-
153
18
  /**
154
19
  * Cloudflare R2 storage provider.
155
20
  *
@@ -311,7 +176,7 @@ export class CloudflareR2Provider implements FileStorageProvider {
311
176
  type: contentType,
312
177
  size: object.size,
313
178
  lastModified: object.uploaded.getTime(),
314
- stream: () => object.body,
179
+ stream: () => object.body as unknown as ReadableStream,
315
180
  arrayBuffer: () => object.arrayBuffer(),
316
181
  text: () => object.text(),
317
182
  };
@@ -0,0 +1,218 @@
1
+ import { Buffer } from "node:buffer";
2
+ import {
3
+ $env,
4
+ $hook,
5
+ $inject,
6
+ Alepha,
7
+ AlephaError,
8
+ type FileLike,
9
+ type Static,
10
+ t,
11
+ } from "alepha";
12
+ import { $logger } from "alepha/logger";
13
+ import { FileDetector, FileSystemProvider } from "alepha/system";
14
+ import { S3mini } from "s3mini";
15
+ import { FileNotFoundError } from "../errors/FileNotFoundError.ts";
16
+ import { $bucket } from "../primitives/$bucket.ts";
17
+ import type { FileStorageProvider } from "./FileStorageProvider.ts";
18
+
19
+ const envSchema = t.object({
20
+ /**
21
+ * S3 endpoint URL. The bucket name is appended (path-style) per request.
22
+ *
23
+ * Examples:
24
+ * - AWS S3: `https://s3.us-east-1.amazonaws.com`
25
+ * - Cloudflare R2: `https://<account-id>.r2.cloudflarestorage.com`
26
+ * - MinIO: `http://localhost:9000`
27
+ * - DigitalOcean Spaces: `https://<region>.digitaloceanspaces.com`
28
+ */
29
+ S3_ENDPOINT: t.string(),
30
+
31
+ /**
32
+ * AWS region or "auto" for R2.
33
+ *
34
+ * @default "auto"
35
+ */
36
+ S3_REGION: t.optional(t.string()),
37
+
38
+ /**
39
+ * Access key ID for S3 authentication.
40
+ */
41
+ S3_ACCESS_KEY_ID: t.string(),
42
+
43
+ /**
44
+ * Secret access key for S3 authentication.
45
+ */
46
+ S3_SECRET_ACCESS_KEY: t.string(),
47
+ });
48
+
49
+ declare module "alepha" {
50
+ interface Env extends Partial<Static<typeof envSchema>> {}
51
+ }
52
+
53
+ /**
54
+ * S3-compatible file storage provider for Node.js.
55
+ *
56
+ * Backed by `s3mini` (zero-dep, ~20 KB). Works with AWS S3, Cloudflare R2,
57
+ * MinIO, DigitalOcean Spaces, Backblaze B2, and any other S3-compatible service.
58
+ *
59
+ * Uses path-style addressing (`<endpoint>/<bucket>`).
60
+ */
61
+ export class NodeS3BucketProvider implements FileStorageProvider {
62
+ protected readonly log = $logger();
63
+ protected readonly env = $env(envSchema);
64
+ protected readonly alepha = $inject(Alepha);
65
+ protected readonly fileSystem = $inject(FileSystemProvider);
66
+ protected readonly fileDetector = $inject(FileDetector);
67
+ protected readonly clients: Map<string, S3mini> = new Map();
68
+
69
+ /**
70
+ * Convert bucket name to S3-compatible format.
71
+ * S3 bucket names must be lowercase, 3-63 characters, no underscores.
72
+ */
73
+ public convertName(name: string): string {
74
+ return name.replaceAll("/", "-").replaceAll("_", "-").toLowerCase();
75
+ }
76
+
77
+ protected getClient(bucketName: string): S3mini {
78
+ const name = this.convertName(bucketName);
79
+ let client = this.clients.get(name);
80
+ if (!client) {
81
+ const endpoint = this.env.S3_ENDPOINT.replace(/\/+$/, "");
82
+ client = new S3mini({
83
+ accessKeyId: this.env.S3_ACCESS_KEY_ID,
84
+ secretAccessKey: this.env.S3_SECRET_ACCESS_KEY,
85
+ region: this.env.S3_REGION || "auto",
86
+ endpoint: `${endpoint}/${name}`,
87
+ });
88
+ this.clients.set(name, client);
89
+ }
90
+ return client;
91
+ }
92
+
93
+ protected readonly onStart = $hook({
94
+ on: "start",
95
+ handler: async () => {
96
+ for (const bucket of this.alepha.primitives($bucket)) {
97
+ if (bucket.provider !== this) {
98
+ continue;
99
+ }
100
+
101
+ const name = this.convertName(bucket.name);
102
+ const client = this.getClient(bucket.name);
103
+
104
+ this.log.debug(`Preparing S3 bucket '${name}'...`);
105
+
106
+ const exists = await client.bucketExists();
107
+ if (!exists) {
108
+ this.log.debug(`Creating S3 bucket '${name}'...`);
109
+ const created = await client.createBucket();
110
+ if (!created) {
111
+ throw new AlephaError(`Failed to create S3 bucket '${name}'`);
112
+ }
113
+ }
114
+
115
+ this.log.info(`S3 bucket '${bucket.name}' OK`);
116
+ }
117
+ },
118
+ });
119
+
120
+ protected createId(mimeType: string): string {
121
+ const ext = this.fileDetector.getExtensionFromMimeType(mimeType);
122
+ return `${crypto.randomUUID()}.${ext}`;
123
+ }
124
+
125
+ public async upload(
126
+ bucketName: string,
127
+ file: FileLike,
128
+ fileId?: string,
129
+ ): Promise<string> {
130
+ fileId ??= this.createId(file.type);
131
+
132
+ this.log.trace(
133
+ `Uploading file '${file.name}' to bucket '${bucketName}' with id '${fileId}'...`,
134
+ );
135
+
136
+ const client = this.getClient(bucketName);
137
+
138
+ try {
139
+ const buffer = new Uint8Array(await file.arrayBuffer());
140
+ await client.putObject(
141
+ fileId,
142
+ buffer,
143
+ file.type || "application/octet-stream",
144
+ undefined,
145
+ { "x-amz-meta-name": encodeURIComponent(file.name) },
146
+ file.size,
147
+ );
148
+
149
+ this.log.trace(`File uploaded successfully: ${fileId}`);
150
+ return fileId;
151
+ } catch (error) {
152
+ this.log.error(`Failed to upload file: ${error}`);
153
+ if (error instanceof Error) {
154
+ throw new AlephaError(`Upload failed: ${error.message}`, {
155
+ cause: error,
156
+ });
157
+ }
158
+ throw error;
159
+ }
160
+ }
161
+
162
+ public async download(bucketName: string, fileId: string): Promise<FileLike> {
163
+ this.log.trace(
164
+ `Downloading file '${fileId}' from bucket '${bucketName}'...`,
165
+ );
166
+
167
+ const client = this.getClient(bucketName);
168
+ const response = await client.getObjectResponse(fileId);
169
+
170
+ if (!response) {
171
+ throw new FileNotFoundError(
172
+ `File '${fileId}' not found in bucket '${bucketName}'`,
173
+ );
174
+ }
175
+
176
+ const buffer = Buffer.from(await response.arrayBuffer());
177
+
178
+ const mimeType =
179
+ response.headers.get("content-type") ||
180
+ this.fileDetector.getContentType(fileId);
181
+
182
+ const metaName = response.headers.get("x-amz-meta-name");
183
+ const name = metaName ? decodeURIComponent(metaName) : fileId;
184
+
185
+ return this.fileSystem.createFile({
186
+ buffer,
187
+ name,
188
+ type: mimeType,
189
+ size: buffer.length,
190
+ });
191
+ }
192
+
193
+ public async exists(bucketName: string, fileId: string): Promise<boolean> {
194
+ this.log.trace(
195
+ `Checking existence of file '${fileId}' in bucket '${bucketName}'...`,
196
+ );
197
+
198
+ const client = this.getClient(bucketName);
199
+ const result = await client.objectExists(fileId);
200
+ return result === true;
201
+ }
202
+
203
+ public async delete(bucketName: string, fileId: string): Promise<void> {
204
+ this.log.trace(`Deleting file '${fileId}' from bucket '${bucketName}'...`);
205
+
206
+ const client = this.getClient(bucketName);
207
+
208
+ try {
209
+ await client.deleteObject(fileId);
210
+ } catch (error) {
211
+ this.log.error(`Failed to delete file: ${error}`);
212
+ if (error instanceof Error) {
213
+ throw new FileNotFoundError("Error deleting file", { cause: error });
214
+ }
215
+ throw error;
216
+ }
217
+ }
218
+ }
@@ -12,6 +12,35 @@ export * from "./providers/MemoryCacheProvider.ts";
12
12
 
13
13
  // ---------------------------------------------------------------------------------------------------------------------
14
14
 
15
+ declare module "alepha" {
16
+ interface Hooks {
17
+ /**
18
+ * Fires when a cache lookup finds a value.
19
+ */
20
+ "cache:hit": {
21
+ container: string;
22
+ key: string;
23
+ };
24
+ /**
25
+ * Fires when a cache lookup does not find a value.
26
+ */
27
+ "cache:miss": {
28
+ container: string;
29
+ key: string;
30
+ };
31
+ /**
32
+ * Fires when a value is written to the cache.
33
+ */
34
+ "cache:set": {
35
+ container: string;
36
+ key: string;
37
+ ttlMs?: number;
38
+ };
39
+ }
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------------------------------------------------
43
+
15
44
  /**
16
45
  * Type-safe caching with TTL support.
17
46
  *
@@ -239,6 +239,12 @@ export class CachePrimitive<
239
239
  ttl: px > 0 ? px : undefined,
240
240
  compress: this.options.compress,
241
241
  });
242
+
243
+ await this.alepha.events.emit("cache:set", {
244
+ container: this.container,
245
+ key,
246
+ ttlMs: px > 0 ? px : undefined,
247
+ });
242
248
  }
243
249
 
244
250
  public async get(key: string): Promise<TReturn | undefined> {
@@ -250,7 +256,14 @@ export class CachePrimitive<
250
256
  return undefined;
251
257
  }
252
258
 
253
- return this.provider.getTyped<TReturn>(this.container, key);
259
+ const value = await this.provider.getTyped<TReturn>(this.container, key);
260
+
261
+ await this.alepha.events.emit(
262
+ value === undefined ? "cache:miss" : "cache:hit",
263
+ { container: this.container, key },
264
+ );
265
+
266
+ return value;
254
267
  }
255
268
 
256
269
  protected $provider(): CacheProvider {
@@ -1,4 +1,4 @@
1
- import type { Alepha } from "alepha";
1
+ import type { Alepha, Service } from "alepha";
2
2
  import {
3
3
  type AppEntryOptions,
4
4
  appEntryOptions,
@@ -10,15 +10,6 @@ import {
10
10
 
11
11
  // ---------------------------------------------------------------------------------------------------------------------
12
12
 
13
- export type AlephaCliConfigPlugin = (
14
- config: AlephaCliConfig,
15
- alepha: Alepha,
16
- ) => void;
17
-
18
- export const cliConfigPlugins: AlephaCliConfigPlugin[] = [];
19
-
20
- // ---------------------------------------------------------------------------------------------------------------------
21
-
22
13
  export interface AlephaCliConfig {
23
14
  /**
24
15
  * Override entry paths.
@@ -28,7 +19,12 @@ export interface AlephaCliConfig {
28
19
  /**
29
20
  * Register more services to the Alepha CLI (enhancements, commands, etc.).
30
21
  */
31
- services?: Array<any>;
22
+ services?: Array<Service>;
23
+
24
+ /**
25
+ * @alias services Register more services to the Alepha CLI (enhancements, commands, etc.).
26
+ */
27
+ plugins?: Array<Service>;
32
28
 
33
29
  /**
34
30
  * Configure Alepha build command.
@@ -58,6 +54,12 @@ export const defineConfig = (config: AlephaCliConfig) => {
58
54
  }
59
55
  }
60
56
 
57
+ if (config.plugins) {
58
+ for (const it of config.plugins) {
59
+ alepha.with(it);
60
+ }
61
+ }
62
+
61
63
  if (config.env) {
62
64
  for (const [key, value] of Object.entries(config.env)) {
63
65
  process.env[key] = String(value);
@@ -76,10 +78,6 @@ export const defineConfig = (config: AlephaCliConfig) => {
76
78
  alepha.set(appEntryOptions, config.entry);
77
79
  }
78
80
 
79
- for (const plugin of cliConfigPlugins) {
80
- plugin(config, alepha);
81
- }
82
-
83
81
  return {};
84
82
  };
85
83
  };