alepha 0.15.3 → 0.15.5

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 (318) hide show
  1. package/README.md +26 -11
  2. package/dist/api/audits/index.d.ts +335 -335
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +11 -3
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +3 -3
  7. package/dist/api/files/index.js +4 -3
  8. package/dist/api/files/index.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts +198 -155
  10. package/dist/api/jobs/index.d.ts.map +1 -1
  11. package/dist/api/jobs/index.js +103 -5
  12. package/dist/api/jobs/index.js.map +1 -1
  13. package/dist/api/keys/index.d.ts +198 -198
  14. package/dist/api/keys/index.d.ts.map +1 -1
  15. package/dist/api/keys/index.js +3 -3
  16. package/dist/api/keys/index.js.map +1 -1
  17. package/dist/api/notifications/index.browser.js +1 -0
  18. package/dist/api/notifications/index.browser.js.map +1 -1
  19. package/dist/api/notifications/index.d.ts +3 -3
  20. package/dist/api/notifications/index.js +4 -3
  21. package/dist/api/notifications/index.js.map +1 -1
  22. package/dist/api/parameters/index.d.ts +263 -263
  23. package/dist/api/parameters/index.d.ts.map +1 -1
  24. package/dist/api/parameters/index.js +41 -30
  25. package/dist/api/parameters/index.js.map +1 -1
  26. package/dist/api/users/index.d.ts +383 -77
  27. package/dist/api/users/index.d.ts.map +1 -1
  28. package/dist/api/users/index.js +284 -72
  29. package/dist/api/users/index.js.map +1 -1
  30. package/dist/api/verifications/index.d.ts +131 -131
  31. package/dist/api/verifications/index.d.ts.map +1 -1
  32. package/dist/api/verifications/index.js +3 -3
  33. package/dist/api/verifications/index.js.map +1 -1
  34. package/dist/batch/index.d.ts +3 -3
  35. package/dist/batch/index.js +3 -3
  36. package/dist/batch/index.js.map +1 -1
  37. package/dist/bucket/index.d.ts +3 -3
  38. package/dist/bucket/index.js +6 -6
  39. package/dist/bucket/index.js.map +1 -1
  40. package/dist/cache/core/index.d.ts +3 -3
  41. package/dist/cache/core/index.js +3 -3
  42. package/dist/cache/core/index.js.map +1 -1
  43. package/dist/cli/index.d.ts +5612 -20
  44. package/dist/cli/index.d.ts.map +1 -1
  45. package/dist/cli/index.js +122 -91
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/command/index.d.ts +11 -4
  48. package/dist/command/index.d.ts.map +1 -1
  49. package/dist/command/index.js +8 -6
  50. package/dist/command/index.js.map +1 -1
  51. package/dist/core/index.browser.js.map +1 -1
  52. package/dist/core/index.d.ts +4 -8
  53. package/dist/core/index.d.ts.map +1 -1
  54. package/dist/core/index.js +3 -3
  55. package/dist/core/index.js.map +1 -1
  56. package/dist/core/index.native.js.map +1 -1
  57. package/dist/datetime/index.d.ts +3 -3
  58. package/dist/datetime/index.js +3 -3
  59. package/dist/datetime/index.js.map +1 -1
  60. package/dist/email/index.d.ts +16 -16
  61. package/dist/email/index.d.ts.map +1 -1
  62. package/dist/email/index.js +10562 -10
  63. package/dist/email/index.js.map +1 -1
  64. package/dist/fake/index.d.ts +3 -3
  65. package/dist/fake/index.js +3 -3
  66. package/dist/fake/index.js.map +1 -1
  67. package/dist/lock/core/index.d.ts +9 -4
  68. package/dist/lock/core/index.d.ts.map +1 -1
  69. package/dist/lock/core/index.js +12 -4
  70. package/dist/lock/core/index.js.map +1 -1
  71. package/dist/logger/index.d.ts +3 -3
  72. package/dist/logger/index.js +6 -3
  73. package/dist/logger/index.js.map +1 -1
  74. package/dist/mcp/index.d.ts +3 -3
  75. package/dist/mcp/index.js +3 -3
  76. package/dist/mcp/index.js.map +1 -1
  77. package/dist/orm/index.d.ts +12 -12
  78. package/dist/orm/index.js +4 -4
  79. package/dist/orm/index.js.map +1 -1
  80. package/dist/queue/core/index.d.ts +3 -3
  81. package/dist/queue/core/index.js +3 -3
  82. package/dist/queue/core/index.js.map +1 -1
  83. package/dist/react/auth/index.browser.js +2 -1
  84. package/dist/react/auth/index.browser.js.map +1 -1
  85. package/dist/react/auth/index.d.ts +3 -3
  86. package/dist/react/auth/index.js +5 -4
  87. package/dist/react/auth/index.js.map +1 -1
  88. package/dist/react/core/index.d.ts +6 -6
  89. package/dist/react/core/index.js +3 -3
  90. package/dist/react/core/index.js.map +1 -1
  91. package/dist/react/form/index.d.ts +3 -3
  92. package/dist/react/form/index.js +3 -3
  93. package/dist/react/form/index.js.map +1 -1
  94. package/dist/react/head/index.d.ts +3 -3
  95. package/dist/react/head/index.js +3 -3
  96. package/dist/react/head/index.js.map +1 -1
  97. package/dist/react/i18n/index.d.ts +3 -3
  98. package/dist/react/i18n/index.js +3 -3
  99. package/dist/react/i18n/index.js.map +1 -1
  100. package/dist/react/intro/index.css +337 -0
  101. package/dist/react/intro/index.css.map +1 -0
  102. package/dist/react/intro/index.d.ts +10 -0
  103. package/dist/react/intro/index.d.ts.map +1 -0
  104. package/dist/react/intro/index.js +222 -0
  105. package/dist/react/intro/index.js.map +1 -0
  106. package/dist/react/router/index.browser.js +2 -2
  107. package/dist/react/router/index.browser.js.map +1 -1
  108. package/dist/react/router/index.d.ts +11 -1
  109. package/dist/react/router/index.d.ts.map +1 -1
  110. package/dist/react/router/index.js +21 -11
  111. package/dist/react/router/index.js.map +1 -1
  112. package/dist/redis/index.d.ts +22 -22
  113. package/dist/redis/index.js +3 -3
  114. package/dist/redis/index.js.map +1 -1
  115. package/dist/retry/index.d.ts +3 -3
  116. package/dist/retry/index.js +3 -3
  117. package/dist/retry/index.js.map +1 -1
  118. package/dist/scheduler/index.d.ts +16 -4
  119. package/dist/scheduler/index.d.ts.map +1 -1
  120. package/dist/scheduler/index.js +45 -7
  121. package/dist/scheduler/index.js.map +1 -1
  122. package/dist/security/index.d.ts +3 -3
  123. package/dist/security/index.js +5 -5
  124. package/dist/security/index.js.map +1 -1
  125. package/dist/server/auth/index.d.ts +3 -3
  126. package/dist/server/auth/index.js +3 -3
  127. package/dist/server/auth/index.js.map +1 -1
  128. package/dist/server/cache/index.d.ts +3 -3
  129. package/dist/server/cache/index.js +3 -3
  130. package/dist/server/cache/index.js.map +1 -1
  131. package/dist/server/compress/index.d.ts +3 -3
  132. package/dist/server/compress/index.d.ts.map +1 -1
  133. package/dist/server/compress/index.js +4 -3
  134. package/dist/server/compress/index.js.map +1 -1
  135. package/dist/server/cookies/index.d.ts +3 -3
  136. package/dist/server/cookies/index.js +3 -3
  137. package/dist/server/cookies/index.js.map +1 -1
  138. package/dist/server/core/index.d.ts +14 -25
  139. package/dist/server/core/index.d.ts.map +1 -1
  140. package/dist/server/core/index.js +13 -29
  141. package/dist/server/core/index.js.map +1 -1
  142. package/dist/server/cors/index.d.ts +3 -3
  143. package/dist/server/cors/index.js +3 -3
  144. package/dist/server/cors/index.js.map +1 -1
  145. package/dist/server/health/index.d.ts +20 -20
  146. package/dist/server/health/index.js +3 -3
  147. package/dist/server/health/index.js.map +1 -1
  148. package/dist/server/helmet/index.d.ts +3 -3
  149. package/dist/server/helmet/index.js +3 -3
  150. package/dist/server/helmet/index.js.map +1 -1
  151. package/dist/server/links/index.d.ts +42 -42
  152. package/dist/server/links/index.d.ts.map +1 -1
  153. package/dist/server/links/index.js +4 -4
  154. package/dist/server/links/index.js.map +1 -1
  155. package/dist/server/metrics/index.d.ts +3 -3
  156. package/dist/server/metrics/index.js +3 -3
  157. package/dist/server/metrics/index.js.map +1 -1
  158. package/dist/server/multipart/index.d.ts +3 -3
  159. package/dist/server/multipart/index.js +3 -3
  160. package/dist/server/multipart/index.js.map +1 -1
  161. package/dist/server/proxy/index.d.ts +3 -3
  162. package/dist/server/proxy/index.js +3 -3
  163. package/dist/server/proxy/index.js.map +1 -1
  164. package/dist/server/rate-limit/index.d.ts +3 -3
  165. package/dist/server/rate-limit/index.js +3 -3
  166. package/dist/server/rate-limit/index.js.map +1 -1
  167. package/dist/server/static/index.d.ts +3 -3
  168. package/dist/server/static/index.js +6 -6
  169. package/dist/server/static/index.js.map +1 -1
  170. package/dist/server/swagger/index.d.ts +3 -3
  171. package/dist/server/swagger/index.js +6 -6
  172. package/dist/server/swagger/index.js.map +1 -1
  173. package/dist/sms/index.d.ts +3 -3
  174. package/dist/sms/index.js +6 -6
  175. package/dist/sms/index.js.map +1 -1
  176. package/dist/system/index.d.ts +3 -3
  177. package/dist/system/index.js +3 -3
  178. package/dist/system/index.js.map +1 -1
  179. package/dist/thread/index.d.ts +3 -3
  180. package/dist/thread/index.js +3 -3
  181. package/dist/thread/index.js.map +1 -1
  182. package/dist/topic/core/index.d.ts +3 -3
  183. package/dist/topic/core/index.js +3 -3
  184. package/dist/topic/core/index.js.map +1 -1
  185. package/dist/vite/index.d.ts +6286 -4
  186. package/dist/vite/index.d.ts.map +1 -1
  187. package/dist/vite/index.js +28 -2
  188. package/dist/vite/index.js.map +1 -1
  189. package/dist/websocket/index.d.ts +37 -37
  190. package/dist/websocket/index.d.ts.map +1 -1
  191. package/dist/websocket/index.js +3 -3
  192. package/dist/websocket/index.js.map +1 -1
  193. package/package.json +12 -4
  194. package/src/api/audits/controllers/AdminAuditController.ts +8 -0
  195. package/src/api/audits/index.ts +3 -3
  196. package/src/api/files/controllers/AdminFileStatsController.ts +1 -0
  197. package/src/api/files/index.ts +3 -3
  198. package/src/api/jobs/controllers/AdminJobController.ts +18 -2
  199. package/src/api/jobs/index.ts +4 -3
  200. package/src/api/jobs/services/JobAudits.spec.ts +89 -0
  201. package/src/api/jobs/services/JobAudits.ts +101 -0
  202. package/src/api/keys/index.ts +3 -3
  203. package/src/api/notifications/controllers/AdminNotificationController.ts +1 -0
  204. package/src/api/notifications/index.ts +3 -3
  205. package/src/api/parameters/controllers/AdminConfigController.ts +10 -0
  206. package/src/api/parameters/index.ts +5 -3
  207. package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1 -1
  208. package/src/api/users/__tests__/ApiKeys.spec.ts +1 -1
  209. package/src/api/users/__tests__/EmailVerification.spec.ts +16 -1
  210. package/src/api/users/__tests__/PasswordReset.spec.ts +11 -0
  211. package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -0
  212. package/src/api/users/controllers/AdminIdentityController.ts +3 -0
  213. package/src/api/users/controllers/AdminSessionController.ts +3 -0
  214. package/src/api/users/controllers/AdminUserController.ts +5 -0
  215. package/src/api/users/index.ts +8 -9
  216. package/src/api/users/primitives/$realm.ts +117 -19
  217. package/src/api/users/providers/RealmProvider.ts +15 -7
  218. package/src/api/users/services/CredentialService.spec.ts +11 -0
  219. package/src/api/users/services/CredentialService.ts +47 -24
  220. package/src/api/users/services/IdentityService.ts +12 -4
  221. package/src/api/users/services/RegistrationService.spec.ts +11 -0
  222. package/src/api/users/services/RegistrationService.ts +33 -12
  223. package/src/api/users/services/SessionService.ts +83 -12
  224. package/src/api/users/services/UserAudits.ts +47 -0
  225. package/src/api/users/services/UserFiles.ts +19 -0
  226. package/src/api/users/services/UserJobs.spec.ts +107 -0
  227. package/src/api/users/services/UserJobs.ts +62 -0
  228. package/src/api/users/services/UserParameters.ts +23 -0
  229. package/src/api/users/services/UserService.ts +34 -17
  230. package/src/api/verifications/index.ts +3 -3
  231. package/src/batch/index.ts +3 -3
  232. package/src/bucket/index.ts +3 -3
  233. package/src/cache/core/index.ts +3 -3
  234. package/src/cli/commands/build.ts +1 -0
  235. package/src/cli/commands/db.ts +9 -0
  236. package/src/cli/commands/init.spec.ts +2 -17
  237. package/src/cli/commands/init.ts +37 -1
  238. package/src/cli/providers/ViteDevServerProvider.ts +36 -2
  239. package/src/cli/services/AlephaCliUtils.ts +17 -0
  240. package/src/cli/services/PackageManagerUtils.ts +15 -1
  241. package/src/cli/services/ProjectScaffolder.ts +8 -13
  242. package/src/cli/templates/agentMd.ts +2 -25
  243. package/src/cli/templates/apiAppSecurityTs.ts +37 -2
  244. package/src/cli/templates/mainCss.ts +2 -32
  245. package/src/cli/templates/webAppRouterTs.ts +5 -5
  246. package/src/cli/templates/webHomeComponentTsx.ts +10 -0
  247. package/src/command/helpers/Runner.ts +14 -1
  248. package/src/command/index.ts +3 -3
  249. package/src/core/helpers/primitive.ts +0 -5
  250. package/src/core/index.ts +3 -3
  251. package/src/datetime/index.ts +3 -3
  252. package/src/email/index.ts +3 -3
  253. package/src/email/index.workerd.ts +36 -0
  254. package/src/email/providers/LocalEmailProvider.ts +2 -2
  255. package/src/email/providers/WorkermailerEmailProvider.ts +221 -0
  256. package/src/fake/index.ts +3 -3
  257. package/src/lock/core/index.ts +3 -3
  258. package/src/lock/core/primitives/$lock.ts +13 -1
  259. package/src/logger/index.ts +3 -3
  260. package/src/logger/providers/PrettyFormatterProvider.ts +7 -0
  261. package/src/mcp/index.ts +3 -3
  262. package/src/orm/index.ts +3 -3
  263. package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
  264. package/src/queue/core/index.ts +3 -3
  265. package/src/react/auth/index.ts +3 -3
  266. package/src/react/auth/services/ReactAuth.ts +3 -1
  267. package/src/react/core/index.ts +3 -3
  268. package/src/react/form/index.ts +3 -3
  269. package/src/react/head/index.ts +3 -3
  270. package/src/react/i18n/index.ts +3 -3
  271. package/src/react/intro/components/GettingStarted.css +334 -0
  272. package/src/react/intro/components/GettingStarted.tsx +276 -0
  273. package/src/react/intro/index.ts +1 -0
  274. package/src/react/router/atoms/ssrManifestAtom.ts +7 -0
  275. package/src/react/router/index.browser.ts +2 -0
  276. package/src/react/router/index.ts +2 -0
  277. package/src/react/router/providers/ReactServerProvider.ts +14 -4
  278. package/src/react/router/providers/SSRManifestProvider.ts +7 -0
  279. package/src/redis/index.ts +3 -3
  280. package/src/retry/index.ts +3 -3
  281. package/src/router/index.ts +3 -3
  282. package/src/scheduler/index.ts +3 -3
  283. package/src/scheduler/index.workerd.ts +43 -0
  284. package/src/scheduler/providers/CronProvider.ts +53 -6
  285. package/src/scheduler/providers/WorkerdCronProvider.ts +102 -0
  286. package/src/security/index.ts +3 -3
  287. package/src/security/providers/JwtProvider.ts +2 -2
  288. package/src/server/auth/index.ts +3 -3
  289. package/src/server/cache/index.ts +3 -3
  290. package/src/server/compress/index.ts +3 -3
  291. package/src/server/compress/providers/ServerCompressProvider.ts +6 -0
  292. package/src/server/cookies/index.ts +3 -3
  293. package/src/server/core/index.ts +3 -3
  294. package/src/server/core/primitives/$action.spec.ts +3 -2
  295. package/src/server/core/primitives/$action.ts +6 -2
  296. package/src/server/core/providers/NodeHttpServerProvider.ts +2 -15
  297. package/src/server/core/providers/ServerProvider.ts +4 -2
  298. package/src/server/core/providers/ServerRouterProvider.ts +5 -27
  299. package/src/server/cors/index.ts +3 -3
  300. package/src/server/health/index.ts +3 -3
  301. package/src/server/helmet/index.ts +3 -3
  302. package/src/server/links/index.ts +3 -3
  303. package/src/server/links/providers/ServerLinksProvider.spec.ts +332 -0
  304. package/src/server/links/providers/ServerLinksProvider.ts +1 -1
  305. package/src/server/metrics/index.ts +3 -3
  306. package/src/server/multipart/index.ts +3 -3
  307. package/src/server/proxy/index.ts +3 -3
  308. package/src/server/rate-limit/index.ts +3 -3
  309. package/src/server/static/index.ts +3 -3
  310. package/src/server/swagger/index.ts +3 -3
  311. package/src/sms/index.ts +3 -3
  312. package/src/system/index.ts +3 -3
  313. package/src/thread/index.ts +3 -3
  314. package/src/topic/core/index.ts +3 -3
  315. package/src/vite/tasks/generateCloudflare.ts +38 -2
  316. package/src/websocket/index.ts +3 -3
  317. package/src/cli/templates/webHelloComponentTsx.ts +0 -30
  318. /package/src/api/users/{notifications → services}/UserNotifications.ts +0 -0
@@ -1,8 +1,6 @@
1
1
  import { $context } from "alepha";
2
- import { AlephaApiAudits } from "alepha/api/audits";
3
- import { AlephaApiFiles } from "alepha/api/files";
4
- import { AlephaApiJobs } from "alepha/api/jobs";
5
2
  import { AlephaApiKeys, ApiKeyService } from "alepha/api/keys";
3
+ import { AlephaApiVerification } from "alepha/api/verifications";
6
4
  import type { Repository } from "alepha/orm";
7
5
  import {
8
6
  $issuer,
@@ -27,6 +25,11 @@ import type { sessions } from "../entities/sessions.ts";
27
25
  import { DEFAULT_USER_REALM_NAME, type users } from "../entities/users.ts";
28
26
  import { RealmProvider } from "../providers/RealmProvider.ts";
29
27
  import { SessionService } from "../services/SessionService.ts";
28
+ import { UserAudits } from "../services/UserAudits.ts";
29
+ import { UserFiles } from "../services/UserFiles.ts";
30
+ import { UserJobs } from "../services/UserJobs.ts";
31
+ import { UserNotifications } from "../services/UserNotifications.ts";
32
+ import { UserParameters } from "../services/UserParameters.ts";
30
33
 
31
34
  export type RealmPrimitive = IssuerPrimitive & WithLinkFn & WithLoginFn;
32
35
 
@@ -66,12 +69,50 @@ export const $realm = (options: RealmOptions = {}): RealmPrimitive => {
66
69
  options.settings.phoneEnabled = true;
67
70
  }
68
71
 
72
+ // Merge features with defaults
73
+ const features: RealmFeatures = {
74
+ jobs: false,
75
+ notifications: false,
76
+ apiKeys: false,
77
+ parameters: false,
78
+ files: false,
79
+ audits: false,
80
+ organizations: false,
81
+ ...options.features,
82
+ };
83
+
84
+ // When notifications are disabled, force verification-dependent settings to false
85
+ // These features require sending codes via email/SMS which won't work without notifications
86
+ if (!features.notifications) {
87
+ options.settings.verifyEmailRequired = false;
88
+ options.settings.verifyPhoneRequired = false;
89
+ options.settings.resetPasswordAllowed = false;
90
+ }
91
+
69
92
  const realmRegistration = realmProvider.register(name, options);
70
93
 
71
- // For now, all modules are enabled
72
- alepha.with(AlephaApiFiles);
73
- alepha.with(AlephaApiAudits);
74
- alepha.with(AlephaApiJobs);
94
+ // Enable features based on configuration
95
+ // Each feature registers its wrapper service which internally uses the module primitives
96
+ if (features.files) {
97
+ alepha.with(UserFiles);
98
+ }
99
+
100
+ if (features.audits) {
101
+ alepha.with(UserAudits);
102
+ }
103
+
104
+ if (features.jobs) {
105
+ alepha.with(UserJobs);
106
+ }
107
+
108
+ if (features.notifications) {
109
+ alepha.with(AlephaApiVerification);
110
+ alepha.with(UserNotifications);
111
+ }
112
+
113
+ if (features.parameters) {
114
+ alepha.with(UserParameters);
115
+ }
75
116
 
76
117
  // Collect custom resolvers that will be registered during $issuer.onInit()
77
118
  // This ensures they are registered AFTER the realm is created (not on the default test realm)
@@ -80,7 +121,7 @@ export const $realm = (options: RealmOptions = {}): RealmPrimitive => {
80
121
  ];
81
122
 
82
123
  // Enable API key authentication - must be added to customResolvers before $issuer() call
83
- if (options.apiKeys) {
124
+ if (features.apiKeys) {
84
125
  alepha.with(AlephaApiKeys);
85
126
  const apiKeyService = alepha.inject(ApiKeyService);
86
127
  customResolvers.push(apiKeyService.createResolver());
@@ -176,6 +217,71 @@ export const $realm = (options: RealmOptions = {}): RealmPrimitive => {
176
217
 
177
218
  // ---------------------------------------------------------------------------------------------------------------------
178
219
 
220
+ export interface RealmFeatures {
221
+ /**
222
+ * Enable job execution tracking and purge functionality.
223
+ *
224
+ * @default false
225
+ */
226
+ jobs?: boolean;
227
+
228
+ /**
229
+ * Enable notification system for password reset, verification emails, etc.
230
+ *
231
+ * @default false
232
+ */
233
+ notifications?: boolean;
234
+
235
+ /**
236
+ * Enable API key authentication for programmatic access.
237
+ *
238
+ * When enabled, users can create API keys to access protected endpoints
239
+ * without using JWT tokens. API keys are useful for:
240
+ * - Programmatic access (CLI tools, scripts)
241
+ * - Long-lived authentication tokens
242
+ * - Third-party integrations (MCP servers)
243
+ *
244
+ * API keys can be passed via:
245
+ * - Query parameter: `?api_key=ak_xxx`
246
+ * - Bearer header: `Authorization: Bearer ak_xxx`
247
+ *
248
+ * @default false
249
+ */
250
+ apiKeys?: boolean;
251
+
252
+ /**
253
+ * Enable runtime configuration management.
254
+ *
255
+ * Allows configuring realm settings at runtime with versioning and scheduled activation.
256
+ *
257
+ * @default false
258
+ */
259
+ parameters?: boolean;
260
+
261
+ /**
262
+ * Enable file management for avatar uploads and attachments.
263
+ *
264
+ * @default false
265
+ */
266
+ files?: boolean;
267
+
268
+ /**
269
+ * Enable audit trail for compliance and event logging.
270
+ *
271
+ * @default false
272
+ */
273
+ audits?: boolean;
274
+
275
+ /**
276
+ * Enable organization management to group users.
277
+ *
278
+ * @default false
279
+ */
280
+ organizations?: boolean;
281
+ }
282
+
283
+ // ---------------------------------------------------------------------------------------------------------------------
284
+
179
285
  export interface RealmOptions {
180
286
  /**
181
287
  * Secret key for signing tokens.
@@ -209,17 +315,9 @@ export interface RealmOptions {
209
315
  };
210
316
 
211
317
  /**
212
- * Enable API key authentication.
213
- *
214
- * When enabled, users can create API keys to access protected endpoints
215
- * without using JWT tokens. API keys are useful for:
216
- * - Programmatic access (CLI tools, scripts)
217
- * - Long-lived authentication tokens
218
- * - Third-party integrations
318
+ * Enable or disable realm features.
219
319
  *
220
- * API keys can be passed via:
221
- * - Query parameter: `?api_key=ak_xxx`
222
- * - Bearer header: `Authorization: Bearer ak_xxx`
320
+ * Features control which modules are loaded with the realm.
223
321
  */
224
- apiKeys?: boolean;
322
+ features?: Partial<RealmFeatures>;
225
323
  }
@@ -1,5 +1,4 @@
1
1
  import { $inject, Alepha, AlephaError } from "alepha";
2
- import { $bucket } from "alepha/bucket";
3
2
  import { $repository, type Repository } from "alepha/orm";
4
3
  import {
5
4
  type RealmAuthSettings,
@@ -8,7 +7,7 @@ import {
8
7
  import { identities } from "../entities/identities.ts";
9
8
  import { sessions } from "../entities/sessions.ts";
10
9
  import { DEFAULT_USER_REALM_NAME, users } from "../entities/users.ts";
11
- import type { RealmOptions } from "../primitives/$realm.ts";
10
+ import type { RealmFeatures, RealmOptions } from "../primitives/$realm.ts";
12
11
 
13
12
  export interface RealmRepositories {
14
13
  identities: Repository<typeof identities.schema>;
@@ -20,6 +19,7 @@ export interface Realm {
20
19
  name: string;
21
20
  repositories: RealmRepositories;
22
21
  settings: RealmAuthSettings;
22
+ features: RealmFeatures;
23
23
  }
24
24
 
25
25
  export class RealmProvider {
@@ -31,12 +31,19 @@ export class RealmProvider {
31
31
 
32
32
  protected realms = new Map<string, Realm>();
33
33
 
34
- public avatars = $bucket({
35
- maxSize: 5 * 1024 * 1024, // 5 MB
36
- mimeTypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
37
- });
38
-
39
34
  public register(realmName: string, realmOptions: RealmOptions = {}) {
35
+ // Merge features with defaults
36
+ const features: RealmFeatures = {
37
+ jobs: false,
38
+ notifications: false,
39
+ apiKeys: false,
40
+ parameters: false,
41
+ files: false,
42
+ audits: false,
43
+ organizations: false,
44
+ ...realmOptions.features,
45
+ };
46
+
40
47
  this.realms.set(realmName, {
41
48
  name: realmName,
42
49
  repositories: {
@@ -53,6 +60,7 @@ export class RealmProvider {
53
60
  ...realmOptions.settings?.passwordPolicy,
54
61
  },
55
62
  },
63
+ features,
56
64
  });
57
65
  return this.getRealm(realmName);
58
66
  }
@@ -8,7 +8,9 @@ import { describe, it } from "vitest";
8
8
  import {
9
9
  AlephaApiUsers,
10
10
  CredentialService,
11
+ RealmProvider,
11
12
  SessionService,
13
+ UserNotifications,
12
14
  UserService,
13
15
  } from "../index.ts";
14
16
 
@@ -21,9 +23,18 @@ const setup = async () => {
21
23
  alepha.with(AlephaEmail);
22
24
  alepha.with(AlephaApiVerification);
23
25
  alepha.with(AlephaApiUsers);
26
+ alepha.with(UserNotifications);
24
27
 
25
28
  await alepha.start();
26
29
 
30
+ // Enable notifications for the default realm
31
+ const realmProvider = alepha.inject(RealmProvider);
32
+ realmProvider.register("default", {
33
+ features: {
34
+ notifications: true,
35
+ },
36
+ });
37
+
27
38
  const emailProvider = alepha.inject(MemoryEmailProvider);
28
39
  emailProvider.records = [];
29
40
 
@@ -1,6 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { $inject } from "alepha";
3
- import { AuditService } from "alepha/api/audits";
2
+ import { $inject, Alepha } from "alepha";
4
3
  import type { VerificationController } from "alepha/api/verifications";
5
4
  import { $cache } from "alepha/cache";
6
5
  import { DateTimeProvider } from "alepha/datetime";
@@ -8,10 +7,11 @@ import { $logger } from "alepha/logger";
8
7
  import { CryptoProvider } from "alepha/security";
9
8
  import { BadRequestError, HttpError } from "alepha/server";
10
9
  import { $client } from "alepha/server/links";
11
- import { UserNotifications } from "../notifications/UserNotifications.ts";
12
10
  import { RealmProvider } from "../providers/RealmProvider.ts";
13
11
  import type { CompletePasswordResetRequest } from "../schemas/completePasswordResetRequestSchema.ts";
14
12
  import type { PasswordResetIntentResponse } from "../schemas/passwordResetIntentResponseSchema.ts";
13
+ import { UserAudits } from "./UserAudits.ts";
14
+ import { UserNotifications } from "./UserNotifications.ts";
15
15
 
16
16
  /**
17
17
  * Intent stored in cache during the password reset flow.
@@ -27,13 +27,28 @@ interface PasswordResetIntent {
27
27
  const INTENT_TTL_MINUTES = 10;
28
28
 
29
29
  export class CredentialService {
30
+ protected readonly alepha = $inject(Alepha);
30
31
  protected readonly log = $logger();
31
32
  protected readonly cryptoProvider = $inject(CryptoProvider);
32
33
  protected readonly dateTimeProvider = $inject(DateTimeProvider);
33
34
  protected readonly verificationController = $client<VerificationController>();
34
- protected readonly userNotifications = $inject(UserNotifications);
35
35
  protected readonly realmProvider = $inject(RealmProvider);
36
- protected readonly auditService = $inject(AuditService);
36
+
37
+ protected userAudits(realmName?: string) {
38
+ const realm = this.realmProvider.getRealm(realmName);
39
+ if (realm.features.audits) {
40
+ return this.alepha.inject(UserAudits);
41
+ }
42
+ return undefined;
43
+ }
44
+
45
+ protected userNotifications(realmName?: string) {
46
+ const realm = this.realmProvider.getRealm(realmName);
47
+ if (realm.features.notifications) {
48
+ return this.alepha.inject(UserNotifications);
49
+ }
50
+ return undefined;
51
+ }
37
52
 
38
53
  protected readonly intentCache = $cache<PasswordResetIntent>({
39
54
  name: "password-reset-intents",
@@ -118,7 +133,7 @@ export class CredentialService {
118
133
  });
119
134
 
120
135
  // Send password reset notification with the code
121
- await this.userNotifications.passwordReset.push({
136
+ await this.userNotifications(userRealmName)?.passwordReset.push({
122
137
  contact: email,
123
138
  variables: {
124
139
  email,
@@ -225,7 +240,7 @@ export class CredentialService {
225
240
  const realm = this.realmProvider.getRealm(intent.realmName);
226
241
 
227
242
  // Audit: password reset
228
- await this.auditService.recordUser("update", {
243
+ await this.userAudits(intent.realmName)?.recordUser("update", {
229
244
  userId: intent.userId,
230
245
  userEmail: intent.email,
231
246
  userRealm: realm.name,
@@ -235,14 +250,18 @@ export class CredentialService {
235
250
  });
236
251
 
237
252
  // Audit: sessions invalidated (security event)
238
- await this.auditService.record("security", "sessions_invalidated", {
239
- userId: intent.userId,
240
- userEmail: intent.email,
241
- userRealm: realm.name,
242
- resourceId: intent.userId,
243
- severity: "warning",
244
- description: "All sessions invalidated after password reset",
245
- });
253
+ await this.userAudits(intent.realmName)?.record(
254
+ "security",
255
+ "sessions_invalidated",
256
+ {
257
+ userId: intent.userId,
258
+ userEmail: intent.email,
259
+ userRealm: realm.name,
260
+ resourceId: intent.userId,
261
+ severity: "warning",
262
+ description: "All sessions invalidated after password reset",
263
+ },
264
+ );
246
265
  }
247
266
 
248
267
  // Legacy methods kept for backward compatibility
@@ -333,7 +352,7 @@ export class CredentialService {
333
352
  const realm = this.realmProvider.getRealm(userRealmName);
334
353
 
335
354
  // Audit: password reset (legacy method)
336
- await this.auditService.recordUser("update", {
355
+ await this.userAudits(userRealmName)?.recordUser("update", {
337
356
  userId: user.id,
338
357
  userEmail: email,
339
358
  userRealm: realm.name,
@@ -343,13 +362,17 @@ export class CredentialService {
343
362
  });
344
363
 
345
364
  // Audit: sessions invalidated
346
- await this.auditService.record("security", "sessions_invalidated", {
347
- userId: user.id,
348
- userEmail: email,
349
- userRealm: realm.name,
350
- resourceId: user.id,
351
- severity: "warning",
352
- description: "All sessions invalidated after password reset",
353
- });
365
+ await this.userAudits(userRealmName)?.record(
366
+ "security",
367
+ "sessions_invalidated",
368
+ {
369
+ userId: user.id,
370
+ userEmail: email,
371
+ userRealm: realm.name,
372
+ resourceId: user.id,
373
+ severity: "warning",
374
+ description: "All sessions invalidated after password reset",
375
+ },
376
+ );
354
377
  }
355
378
  }
@@ -1,15 +1,23 @@
1
- import { $inject } from "alepha";
2
- import { AuditService } from "alepha/api/audits";
1
+ import { $inject, Alepha } from "alepha";
3
2
  import { $logger } from "alepha/logger";
4
3
  import type { Page } from "alepha/orm";
5
4
  import type { IdentityEntity } from "../entities/identities.ts";
6
5
  import { RealmProvider } from "../providers/RealmProvider.ts";
7
6
  import type { IdentityQuery } from "../schemas/identityQuerySchema.ts";
7
+ import { UserAudits } from "./UserAudits.ts";
8
8
 
9
9
  export class IdentityService {
10
+ protected readonly alepha = $inject(Alepha);
10
11
  protected readonly log = $logger();
11
12
  protected readonly realmProvider = $inject(RealmProvider);
12
- protected readonly auditService = $inject(AuditService);
13
+
14
+ protected userAudits(realmName?: string) {
15
+ const realm = this.realmProvider.getRealm(realmName);
16
+ if (realm.features.audits) {
17
+ return this.alepha.inject(UserAudits);
18
+ }
19
+ return undefined;
20
+ }
13
21
 
14
22
  public identities(userRealmName?: string) {
15
23
  return this.realmProvider.identityRepository(userRealmName);
@@ -87,7 +95,7 @@ export class IdentityService {
87
95
 
88
96
  const realm = this.realmProvider.getRealm(userRealmName);
89
97
 
90
- await this.auditService.recordUser("update", {
98
+ await this.userAudits(userRealmName)?.recordUser("update", {
91
99
  userRealm: realm.name,
92
100
  resourceId: identity.userId,
93
101
  description: `Identity provider disconnected: ${identity.provider}`,
@@ -10,6 +10,7 @@ import {
10
10
  RealmProvider,
11
11
  RegistrationService,
12
12
  SessionService,
13
+ UserNotifications,
13
14
  UserService,
14
15
  } from "../index.ts";
15
16
 
@@ -20,6 +21,7 @@ const setup = async (realmSettings?: Record<string, unknown>) => {
20
21
  alepha.with(AlephaEmail);
21
22
  alepha.with(AlephaApiVerification);
22
23
  alepha.with(AlephaApiUsers);
24
+ alepha.with(UserNotifications);
23
25
 
24
26
  await alepha.start();
25
27
 
@@ -84,6 +86,9 @@ describe("alepha/api/users - RegistrationService", () => {
84
86
 
85
87
  // Register realm with email verification required
86
88
  realmProvider.register("verify-email-realm", {
89
+ features: {
90
+ notifications: true,
91
+ },
87
92
  settings: {
88
93
  verifyEmailRequired: true,
89
94
  } as never,
@@ -315,6 +320,9 @@ describe("alepha/api/users - RegistrationService", () => {
315
320
  await setup();
316
321
 
317
322
  realmProvider.register("email-verify-realm", {
323
+ features: {
324
+ notifications: true,
325
+ },
318
326
  settings: {
319
327
  verifyEmailRequired: true,
320
328
  } as never,
@@ -586,6 +594,9 @@ describe("alepha/api/users - RegistrationService", () => {
586
594
  } = await setup();
587
595
 
588
596
  realmProvider.register("full-verify-realm", {
597
+ features: {
598
+ notifications: true,
599
+ },
589
600
  settings: {
590
601
  verifyEmailRequired: true,
591
602
  } as never,
@@ -1,6 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { $inject } from "alepha";
3
- import { AuditService } from "alepha/api/audits";
2
+ import { $inject, Alepha } from "alepha";
4
3
  import type { VerificationController } from "alepha/api/verifications";
5
4
  import { $cache } from "alepha/cache";
6
5
  import { DateTimeProvider } from "alepha/datetime";
@@ -9,11 +8,12 @@ import { CryptoProvider } from "alepha/security";
9
8
  import { BadRequestError, ConflictError, HttpError } from "alepha/server";
10
9
  import { $client } from "alepha/server/links";
11
10
  import type { UserEntity } from "../entities/users.ts";
12
- import { UserNotifications } from "../notifications/UserNotifications.ts";
13
11
  import { RealmProvider } from "../providers/RealmProvider.ts";
14
12
  import type { CompleteRegistrationRequest } from "../schemas/completeRegistrationRequestSchema.ts";
15
13
  import type { RegisterRequest } from "../schemas/registerRequestSchema.ts";
16
14
  import type { RegistrationIntentResponse } from "../schemas/registrationIntentResponseSchema.ts";
15
+ import { UserAudits } from "./UserAudits.ts";
16
+ import { UserNotifications } from "./UserNotifications.ts";
17
17
 
18
18
  /**
19
19
  * Intent stored in cache during the registration flow.
@@ -40,19 +40,34 @@ interface RegistrationIntent {
40
40
  const INTENT_TTL_MINUTES = 10;
41
41
 
42
42
  export class RegistrationService {
43
+ protected readonly alepha = $inject(Alepha);
43
44
  protected readonly log = $logger();
44
45
  protected readonly dateTimeProvider = $inject(DateTimeProvider);
45
46
  protected readonly cryptoProvider = $inject(CryptoProvider);
46
47
  protected readonly verificationController = $client<VerificationController>();
47
- protected readonly userNotifications = $inject(UserNotifications);
48
48
  protected readonly realmProvider = $inject(RealmProvider);
49
- protected readonly auditService = $inject(AuditService);
50
49
 
51
50
  protected readonly intentCache = $cache<RegistrationIntent>({
52
51
  name: "registration-intents",
53
52
  ttl: [INTENT_TTL_MINUTES, "minutes"],
54
53
  });
55
54
 
55
+ protected userAudits(realmName?: string) {
56
+ const realm = this.realmProvider.getRealm(realmName);
57
+ if (realm.features.audits) {
58
+ return this.alepha.inject(UserAudits);
59
+ }
60
+ return undefined;
61
+ }
62
+
63
+ protected userNotifications(realmName?: string) {
64
+ const realm = this.realmProvider.getRealm(realmName);
65
+ if (realm.features.notifications) {
66
+ return this.alepha.inject(UserNotifications);
67
+ }
68
+ return undefined;
69
+ }
70
+
56
71
  /**
57
72
  * Phase 1: Create a registration intent.
58
73
  *
@@ -130,11 +145,11 @@ export class RegistrationService {
130
145
 
131
146
  // Create verification sessions and send codes
132
147
  if (requirements.email && body.email) {
133
- await this.sendEmailVerification(body.email);
148
+ await this.sendEmailVerification(body.email, userRealmName);
134
149
  }
135
150
 
136
151
  if (requirements.phone && body.phoneNumber) {
137
- await this.sendPhoneVerification(body.phoneNumber);
152
+ await this.sendPhoneVerification(body.phoneNumber, userRealmName);
138
153
  }
139
154
 
140
155
  // Generate intent ID and expiration
@@ -291,7 +306,7 @@ export class RegistrationService {
291
306
 
292
307
  const realm = this.realmProvider.getRealm(userRealmName);
293
308
 
294
- await this.auditService.recordUser("create", {
309
+ await this.userAudits(userRealmName)?.recordUser("create", {
295
310
  userId: user.id,
296
311
  userEmail: user.email ?? undefined,
297
312
  userRealm: realm.name,
@@ -353,7 +368,10 @@ export class RegistrationService {
353
368
  /**
354
369
  * Send email verification code.
355
370
  */
356
- protected async sendEmailVerification(email: string): Promise<void> {
371
+ protected async sendEmailVerification(
372
+ email: string,
373
+ realmName?: string,
374
+ ): Promise<void> {
357
375
  this.log.debug("Sending email verification code", { email });
358
376
 
359
377
  const verification =
@@ -362,7 +380,7 @@ export class RegistrationService {
362
380
  body: { target: email },
363
381
  });
364
382
 
365
- await this.userNotifications.emailVerification.push({
383
+ await this.userNotifications(realmName)?.emailVerification.push({
366
384
  contact: email,
367
385
  variables: {
368
386
  email,
@@ -377,7 +395,10 @@ export class RegistrationService {
377
395
  /**
378
396
  * Send phone verification code.
379
397
  */
380
- protected async sendPhoneVerification(phoneNumber: string): Promise<void> {
398
+ protected async sendPhoneVerification(
399
+ phoneNumber: string,
400
+ realmName?: string,
401
+ ): Promise<void> {
381
402
  this.log.debug("Sending phone verification code", { phoneNumber });
382
403
  try {
383
404
  const verification =
@@ -386,7 +407,7 @@ export class RegistrationService {
386
407
  body: { target: phoneNumber },
387
408
  });
388
409
 
389
- await this.userNotifications.phoneVerification.push({
410
+ await this.userNotifications(realmName)?.phoneVerification.push({
390
411
  contact: phoneNumber,
391
412
  variables: {
392
413
  phoneNumber,