alepha 0.14.4 → 0.15.0

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 (277) hide show
  1. package/README.md +1 -4
  2. package/dist/api/audits/index.d.ts +619 -731
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +185 -298
  5. package/dist/api/files/index.d.ts.map +1 -1
  6. package/dist/api/files/index.js +0 -1
  7. package/dist/api/files/index.js.map +1 -1
  8. package/dist/api/jobs/index.d.ts +245 -356
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/notifications/index.d.ts +238 -350
  11. package/dist/api/notifications/index.d.ts.map +1 -1
  12. package/dist/api/parameters/index.d.ts +499 -611
  13. package/dist/api/parameters/index.d.ts.map +1 -1
  14. package/dist/api/users/index.browser.js +1 -2
  15. package/dist/api/users/index.browser.js.map +1 -1
  16. package/dist/api/users/index.d.ts +1697 -1804
  17. package/dist/api/users/index.d.ts.map +1 -1
  18. package/dist/api/users/index.js +178 -151
  19. package/dist/api/users/index.js.map +1 -1
  20. package/dist/api/verifications/index.d.ts +132 -132
  21. package/dist/api/verifications/index.d.ts.map +1 -1
  22. package/dist/batch/index.d.ts +122 -122
  23. package/dist/batch/index.d.ts.map +1 -1
  24. package/dist/batch/index.js +1 -2
  25. package/dist/batch/index.js.map +1 -1
  26. package/dist/bucket/index.d.ts +163 -163
  27. package/dist/bucket/index.d.ts.map +1 -1
  28. package/dist/cache/core/index.d.ts +46 -46
  29. package/dist/cache/core/index.d.ts.map +1 -1
  30. package/dist/cache/redis/index.d.ts.map +1 -1
  31. package/dist/cli/index.d.ts +302 -299
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +966 -564
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/command/index.d.ts +303 -299
  36. package/dist/command/index.d.ts.map +1 -1
  37. package/dist/command/index.js +11 -7
  38. package/dist/command/index.js.map +1 -1
  39. package/dist/core/index.browser.js +419 -99
  40. package/dist/core/index.browser.js.map +1 -1
  41. package/dist/core/index.d.ts +718 -625
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +420 -99
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/index.native.js +419 -99
  46. package/dist/core/index.native.js.map +1 -1
  47. package/dist/datetime/index.d.ts +44 -44
  48. package/dist/datetime/index.d.ts.map +1 -1
  49. package/dist/datetime/index.js +4 -4
  50. package/dist/datetime/index.js.map +1 -1
  51. package/dist/email/index.d.ts +97 -50
  52. package/dist/email/index.d.ts.map +1 -1
  53. package/dist/email/index.js +129 -33
  54. package/dist/email/index.js.map +1 -1
  55. package/dist/fake/index.d.ts +7981 -14
  56. package/dist/fake/index.d.ts.map +1 -1
  57. package/dist/file/index.d.ts +523 -390
  58. package/dist/file/index.d.ts.map +1 -1
  59. package/dist/file/index.js +253 -1
  60. package/dist/file/index.js.map +1 -1
  61. package/dist/lock/core/index.d.ts +208 -208
  62. package/dist/lock/core/index.d.ts.map +1 -1
  63. package/dist/lock/redis/index.d.ts.map +1 -1
  64. package/dist/logger/index.d.ts +25 -26
  65. package/dist/logger/index.d.ts.map +1 -1
  66. package/dist/mcp/index.d.ts +197 -197
  67. package/dist/mcp/index.d.ts.map +1 -1
  68. package/dist/orm/chunk-DtkW-qnP.js +38 -0
  69. package/dist/orm/index.browser.js.map +1 -1
  70. package/dist/orm/index.bun.js +2814 -0
  71. package/dist/orm/index.bun.js.map +1 -0
  72. package/dist/orm/index.d.ts +1205 -1057
  73. package/dist/orm/index.d.ts.map +1 -1
  74. package/dist/orm/index.js +2056 -1753
  75. package/dist/orm/index.js.map +1 -1
  76. package/dist/queue/core/index.d.ts +248 -248
  77. package/dist/queue/core/index.d.ts.map +1 -1
  78. package/dist/queue/redis/index.d.ts.map +1 -1
  79. package/dist/redis/index.bun.js +285 -0
  80. package/dist/redis/index.bun.js.map +1 -0
  81. package/dist/redis/index.d.ts +118 -136
  82. package/dist/redis/index.d.ts.map +1 -1
  83. package/dist/redis/index.js +18 -38
  84. package/dist/redis/index.js.map +1 -1
  85. package/dist/retry/index.d.ts +69 -69
  86. package/dist/retry/index.d.ts.map +1 -1
  87. package/dist/router/index.d.ts +6 -6
  88. package/dist/router/index.d.ts.map +1 -1
  89. package/dist/scheduler/index.d.ts +25 -25
  90. package/dist/scheduler/index.d.ts.map +1 -1
  91. package/dist/security/index.browser.js +5 -1
  92. package/dist/security/index.browser.js.map +1 -1
  93. package/dist/security/index.d.ts +417 -254
  94. package/dist/security/index.d.ts.map +1 -1
  95. package/dist/security/index.js +386 -86
  96. package/dist/security/index.js.map +1 -1
  97. package/dist/server/auth/index.d.ts +277 -277
  98. package/dist/server/auth/index.d.ts.map +1 -1
  99. package/dist/server/auth/index.js +20 -20
  100. package/dist/server/auth/index.js.map +1 -1
  101. package/dist/server/cache/index.d.ts +60 -57
  102. package/dist/server/cache/index.d.ts.map +1 -1
  103. package/dist/server/cache/index.js +1 -1
  104. package/dist/server/cache/index.js.map +1 -1
  105. package/dist/server/compress/index.d.ts +3 -3
  106. package/dist/server/compress/index.d.ts.map +1 -1
  107. package/dist/server/cookies/index.d.ts +6 -6
  108. package/dist/server/cookies/index.d.ts.map +1 -1
  109. package/dist/server/cookies/index.js +3 -3
  110. package/dist/server/cookies/index.js.map +1 -1
  111. package/dist/server/core/index.d.ts +242 -150
  112. package/dist/server/core/index.d.ts.map +1 -1
  113. package/dist/server/core/index.js +288 -122
  114. package/dist/server/core/index.js.map +1 -1
  115. package/dist/server/cors/index.d.ts +11 -12
  116. package/dist/server/cors/index.d.ts.map +1 -1
  117. package/dist/server/health/index.d.ts +0 -1
  118. package/dist/server/health/index.d.ts.map +1 -1
  119. package/dist/server/helmet/index.d.ts +2 -2
  120. package/dist/server/helmet/index.d.ts.map +1 -1
  121. package/dist/server/links/index.browser.js.map +1 -1
  122. package/dist/server/links/index.d.ts +84 -85
  123. package/dist/server/links/index.d.ts.map +1 -1
  124. package/dist/server/links/index.js +1 -2
  125. package/dist/server/links/index.js.map +1 -1
  126. package/dist/server/metrics/index.d.ts.map +1 -1
  127. package/dist/server/multipart/index.d.ts +6 -6
  128. package/dist/server/multipart/index.d.ts.map +1 -1
  129. package/dist/server/proxy/index.d.ts +102 -103
  130. package/dist/server/proxy/index.d.ts.map +1 -1
  131. package/dist/server/rate-limit/index.d.ts +16 -16
  132. package/dist/server/rate-limit/index.d.ts.map +1 -1
  133. package/dist/server/static/index.d.ts +44 -44
  134. package/dist/server/static/index.d.ts.map +1 -1
  135. package/dist/server/swagger/index.d.ts +48 -49
  136. package/dist/server/swagger/index.d.ts.map +1 -1
  137. package/dist/server/swagger/index.js +1 -2
  138. package/dist/server/swagger/index.js.map +1 -1
  139. package/dist/sms/index.d.ts +13 -11
  140. package/dist/sms/index.d.ts.map +1 -1
  141. package/dist/sms/index.js +7 -7
  142. package/dist/sms/index.js.map +1 -1
  143. package/dist/thread/index.d.ts +71 -72
  144. package/dist/thread/index.d.ts.map +1 -1
  145. package/dist/topic/core/index.d.ts +318 -318
  146. package/dist/topic/core/index.d.ts.map +1 -1
  147. package/dist/topic/redis/index.d.ts +6 -6
  148. package/dist/topic/redis/index.d.ts.map +1 -1
  149. package/dist/vite/index.d.ts +5720 -159
  150. package/dist/vite/index.d.ts.map +1 -1
  151. package/dist/vite/index.js +41 -18
  152. package/dist/vite/index.js.map +1 -1
  153. package/dist/websocket/index.browser.js +6 -6
  154. package/dist/websocket/index.browser.js.map +1 -1
  155. package/dist/websocket/index.d.ts +247 -247
  156. package/dist/websocket/index.d.ts.map +1 -1
  157. package/dist/websocket/index.js +6 -6
  158. package/dist/websocket/index.js.map +1 -1
  159. package/package.json +9 -14
  160. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  161. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  162. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  163. package/src/api/users/entities/users.ts +1 -1
  164. package/src/api/users/index.ts +8 -8
  165. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  166. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  167. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  168. package/src/api/users/services/CredentialService.ts +7 -7
  169. package/src/api/users/services/IdentityService.ts +4 -4
  170. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  171. package/src/api/users/services/RegistrationService.ts +38 -27
  172. package/src/api/users/services/SessionCrudService.ts +3 -3
  173. package/src/api/users/services/SessionService.spec.ts +3 -3
  174. package/src/api/users/services/SessionService.ts +28 -9
  175. package/src/api/users/services/UserService.ts +7 -7
  176. package/src/batch/providers/BatchProvider.ts +1 -2
  177. package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
  178. package/src/cli/assets/apiHelloControllerTs.ts +18 -0
  179. package/src/cli/assets/apiIndexTs.ts +16 -0
  180. package/src/cli/assets/claudeMd.ts +303 -0
  181. package/src/cli/assets/mainBrowserTs.ts +2 -2
  182. package/src/cli/assets/mainServerTs.ts +24 -0
  183. package/src/cli/assets/webAppRouterTs.ts +15 -0
  184. package/src/cli/assets/webHelloComponentTsx.ts +16 -0
  185. package/src/cli/assets/webIndexTs.ts +16 -0
  186. package/src/cli/commands/build.ts +41 -21
  187. package/src/cli/commands/db.ts +21 -18
  188. package/src/cli/commands/deploy.ts +17 -5
  189. package/src/cli/commands/dev.ts +13 -17
  190. package/src/cli/commands/format.ts +8 -2
  191. package/src/cli/commands/init.ts +74 -29
  192. package/src/cli/commands/lint.ts +8 -2
  193. package/src/cli/commands/test.ts +8 -2
  194. package/src/cli/commands/typecheck.ts +5 -1
  195. package/src/cli/commands/verify.ts +4 -2
  196. package/src/cli/services/AlephaCliUtils.ts +39 -600
  197. package/src/cli/services/PackageManagerUtils.ts +301 -0
  198. package/src/cli/services/ProjectScaffolder.ts +306 -0
  199. package/src/command/helpers/Runner.ts +15 -3
  200. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  201. package/src/core/index.shared.ts +1 -0
  202. package/src/core/index.ts +2 -0
  203. package/src/core/primitives/$hook.ts +6 -2
  204. package/src/core/primitives/$module.spec.ts +4 -0
  205. package/src/core/providers/AlsProvider.ts +1 -1
  206. package/src/core/providers/CodecManager.spec.ts +12 -6
  207. package/src/core/providers/CodecManager.ts +26 -6
  208. package/src/core/providers/EventManager.ts +169 -13
  209. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
  210. package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
  211. package/src/core/providers/StateManager.spec.ts +27 -16
  212. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  213. package/src/email/providers/LocalEmailProvider.ts +52 -15
  214. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  215. package/src/file/errors/FileError.ts +7 -0
  216. package/src/file/index.ts +9 -1
  217. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  218. package/src/orm/index.browser.ts +1 -19
  219. package/src/orm/index.bun.ts +77 -0
  220. package/src/orm/index.shared-server.ts +22 -0
  221. package/src/orm/index.shared.ts +15 -0
  222. package/src/orm/index.ts +19 -39
  223. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  224. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  225. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  226. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  227. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  228. package/src/orm/services/Repository.ts +8 -0
  229. package/src/redis/index.bun.ts +35 -0
  230. package/src/redis/providers/BunRedisProvider.ts +12 -43
  231. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  232. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  233. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  234. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  235. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  236. package/src/security/index.browser.ts +5 -0
  237. package/src/security/index.ts +90 -7
  238. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  239. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  240. package/src/security/primitives/$role.ts +5 -5
  241. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  242. package/src/security/primitives/$serviceAccount.ts +3 -3
  243. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  244. package/src/server/auth/primitives/$auth.ts +10 -10
  245. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  246. package/src/server/auth/primitives/$authGithub.ts +3 -3
  247. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  248. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  249. package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
  250. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  251. package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
  252. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  253. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  254. package/src/server/core/providers/ServerProvider.ts +144 -21
  255. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  256. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  257. package/src/server/links/index.ts +1 -1
  258. package/src/server/links/providers/LinkProvider.ts +1 -1
  259. package/src/server/swagger/index.ts +1 -1
  260. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  261. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  262. package/src/vite/helpers/boot.ts +28 -17
  263. package/src/vite/tasks/buildServer.ts +12 -1
  264. package/src/vite/tasks/devServer.ts +3 -1
  265. package/src/vite/tasks/generateCloudflare.ts +7 -0
  266. package/dist/server/security/index.browser.js +0 -13
  267. package/dist/server/security/index.browser.js.map +0 -1
  268. package/dist/server/security/index.d.ts +0 -173
  269. package/dist/server/security/index.d.ts.map +0 -1
  270. package/dist/server/security/index.js +0 -311
  271. package/dist/server/security/index.js.map +0 -1
  272. package/src/cli/assets/appRouterTs.ts +0 -9
  273. package/src/cli/assets/mainTs.ts +0 -13
  274. package/src/server/security/index.browser.ts +0 -10
  275. package/src/server/security/index.ts +0 -94
  276. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  277. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
@@ -10,7 +10,7 @@ import { BadRequestError, ConflictError, HttpError } from "alepha/server";
10
10
  import { $client } from "alepha/server/links";
11
11
  import type { UserEntity } from "../entities/users.ts";
12
12
  import { UserNotifications } from "../notifications/UserNotifications.ts";
13
- import { UserRealmProvider } from "../providers/UserRealmProvider.ts";
13
+ import { RealmProvider } from "../providers/RealmProvider.ts";
14
14
  import type { CompleteRegistrationRequest } from "../schemas/completeRegistrationRequestSchema.ts";
15
15
  import type { RegisterRequest } from "../schemas/registerRequestSchema.ts";
16
16
  import type { RegistrationIntentResponse } from "../schemas/registrationIntentResponseSchema.ts";
@@ -45,7 +45,7 @@ export class RegistrationService {
45
45
  protected readonly cryptoProvider = $inject(CryptoProvider);
46
46
  protected readonly verificationController = $client<VerificationController>();
47
47
  protected readonly userNotifications = $inject(UserNotifications);
48
- protected readonly userRealmProvider = $inject(UserRealmProvider);
48
+ protected readonly realmProvider = $inject(RealmProvider);
49
49
  protected readonly auditService = $inject(AuditService);
50
50
 
51
51
  protected readonly intentCache = $cache<RegistrationIntent>({
@@ -69,9 +69,7 @@ export class RegistrationService {
69
69
  userRealmName,
70
70
  });
71
71
 
72
- const realmSettings =
73
- this.userRealmProvider.getRealm(userRealmName).settings;
74
- const userRepository = this.userRealmProvider.userRepository(userRealmName);
72
+ const realmSettings = this.realmProvider.getRealm(userRealmName).settings;
75
73
 
76
74
  // Check if registration is allowed
77
75
  if (realmSettings?.registrationAllowed === false) {
@@ -87,6 +85,22 @@ export class RegistrationService {
87
85
  throw new BadRequestError("Username is required");
88
86
  }
89
87
 
88
+ if (body.username) {
89
+ const usernameRegExp = realmSettings?.usernameRegExp;
90
+ if (usernameRegExp) {
91
+ const regex = new RegExp(usernameRegExp);
92
+ if (!regex.test(body.username)) {
93
+ this.log.debug("Registration rejected: username regex mismatch", {
94
+ userRealmName,
95
+ username: body.username,
96
+ });
97
+ throw new BadRequestError(
98
+ "Username does not meet the required format",
99
+ );
100
+ }
101
+ }
102
+ }
103
+
90
104
  if (realmSettings?.emailRequired !== false && !body.email) {
91
105
  this.log.debug("Registration rejected: email required", {
92
106
  userRealmName,
@@ -189,9 +203,9 @@ export class RegistrationService {
189
203
  }
190
204
 
191
205
  const userRealmName = intent.realmName;
192
- const userRepository = this.userRealmProvider.userRepository(userRealmName);
206
+ const userRepository = this.realmProvider.userRepository(userRealmName);
193
207
  const identityRepository =
194
- this.userRealmProvider.identityRepository(userRealmName);
208
+ this.realmProvider.identityRepository(userRealmName);
195
209
 
196
210
  // Validate email verification if required
197
211
  if (intent.requirements.email) {
@@ -275,7 +289,7 @@ export class RegistrationService {
275
289
  username: user.username,
276
290
  });
277
291
 
278
- const realm = this.userRealmProvider.getRealm(userRealmName);
292
+ const realm = this.realmProvider.getRealm(userRealmName);
279
293
 
280
294
  await this.auditService.recordUser("create", {
281
295
  userId: user.id,
@@ -301,7 +315,7 @@ export class RegistrationService {
301
315
  body: Pick<RegisterRequest, "username" | "email" | "phoneNumber">,
302
316
  userRealmName?: string,
303
317
  ): Promise<void> {
304
- const userRepository = this.userRealmProvider.userRepository(userRealmName);
318
+ const userRepository = this.realmProvider.userRepository(userRealmName);
305
319
 
306
320
  if (body.username) {
307
321
  const existingUser = await userRepository
@@ -341,26 +355,23 @@ export class RegistrationService {
341
355
  */
342
356
  protected async sendEmailVerification(email: string): Promise<void> {
343
357
  this.log.debug("Sending email verification code", { email });
344
- try {
345
- const verification =
346
- await this.verificationController.requestVerificationCode({
347
- params: { type: "code" },
348
- body: { target: email },
349
- });
350
358
 
351
- await this.userNotifications.emailVerification.push({
352
- contact: email,
353
- variables: {
354
- email,
355
- code: verification.token,
356
- expiresInMinutes: Math.floor(verification.codeExpiration / 60),
357
- },
359
+ const verification =
360
+ await this.verificationController.requestVerificationCode({
361
+ params: { type: "code" },
362
+ body: { target: email },
358
363
  });
359
- this.log.debug("Email verification code sent", { email });
360
- } catch (error) {
361
- // Silent fail - verification service may have rate limiting
362
- this.log.warn("Failed to send email verification code", error);
363
- }
364
+
365
+ await this.userNotifications.emailVerification.push({
366
+ contact: email,
367
+ variables: {
368
+ email,
369
+ code: verification.token,
370
+ expiresInMinutes: Math.floor(verification.codeExpiration / 60),
371
+ },
372
+ });
373
+
374
+ this.log.debug("Email verification code sent", { email });
364
375
  }
365
376
 
366
377
  /**
@@ -2,15 +2,15 @@ import { $inject } from "alepha";
2
2
  import { $logger } from "alepha/logger";
3
3
  import type { Page } from "alepha/orm";
4
4
  import type { SessionEntity } from "../entities/sessions.ts";
5
- import { UserRealmProvider } from "../providers/UserRealmProvider.ts";
5
+ import { RealmProvider } from "../providers/RealmProvider.ts";
6
6
  import type { SessionQuery } from "../schemas/sessionQuerySchema.ts";
7
7
 
8
8
  export class SessionCrudService {
9
9
  protected readonly log = $logger();
10
- protected readonly userRealmProvider = $inject(UserRealmProvider);
10
+ protected readonly realmProvider = $inject(RealmProvider);
11
11
 
12
12
  public sessions(userRealmName?: string) {
13
- return this.userRealmProvider.sessionRepository(userRealmName);
13
+ return this.realmProvider.sessionRepository(userRealmName);
14
14
  }
15
15
 
16
16
  /**
@@ -7,8 +7,8 @@ import {
7
7
  import { describe, it } from "vitest";
8
8
  import {
9
9
  AlephaApiUsers,
10
+ RealmProvider,
10
11
  SessionService,
11
- UserRealmProvider,
12
12
  UserService,
13
13
  } from "../index.ts";
14
14
 
@@ -26,8 +26,8 @@ const setup = async (options?: { usernameEnabled?: boolean }) => {
26
26
 
27
27
  // Configure realm settings if provided
28
28
  if (options?.usernameEnabled) {
29
- const userRealmProvider = alepha.inject(UserRealmProvider);
30
- userRealmProvider.register("default", {
29
+ const realmProvider = alepha.inject(RealmProvider);
30
+ realmProvider.register("default", {
31
31
  settings: {
32
32
  usernameEnabled: true,
33
33
  } as never,
@@ -14,7 +14,7 @@ import { type ServerRequest, UnauthorizedError } from "alepha/server";
14
14
  import type { OAuth2Profile } from "alepha/server/auth";
15
15
  import { $client } from "alepha/server/links";
16
16
  import type { UserEntity } from "../entities/users.ts";
17
- import { UserRealmProvider } from "../providers/UserRealmProvider.ts";
17
+ import { RealmProvider } from "../providers/RealmProvider.ts";
18
18
 
19
19
  export class SessionService {
20
20
  protected readonly alepha = $inject(Alepha);
@@ -22,20 +22,20 @@ export class SessionService {
22
22
  protected readonly dateTimeProvider = $inject(DateTimeProvider);
23
23
  protected readonly cryptoProvider = $inject(CryptoProvider);
24
24
  protected readonly log = $logger();
25
- protected readonly userRealmProvider = $inject(UserRealmProvider);
25
+ protected readonly realmProvider = $inject(RealmProvider);
26
26
  protected readonly fileController = $client<FileController>();
27
27
  protected readonly auditService = $inject(AuditService);
28
28
 
29
29
  public users(userRealmName?: string) {
30
- return this.userRealmProvider.userRepository(userRealmName);
30
+ return this.realmProvider.userRepository(userRealmName);
31
31
  }
32
32
 
33
33
  public sessions(userRealmName?: string) {
34
- return this.userRealmProvider.sessionRepository(userRealmName);
34
+ return this.realmProvider.sessionRepository(userRealmName);
35
35
  }
36
36
 
37
37
  public identities(userRealmName?: string) {
38
- return this.userRealmProvider.identityRepository(userRealmName);
38
+ return this.realmProvider.identityRepository(userRealmName);
39
39
  }
40
40
 
41
41
  /**
@@ -55,7 +55,7 @@ export class SessionService {
55
55
  password: string,
56
56
  userRealmName?: string,
57
57
  ): Promise<UserEntity> {
58
- const { settings, name } = this.userRealmProvider.getRealm(userRealmName);
58
+ const { settings, name } = this.realmProvider.getRealm(userRealmName);
59
59
  const isEmail = username.includes("@");
60
60
  const isPhone = /^[+\d][\d\s()-]+$/.test(username);
61
61
  const isUsername = !isEmail && !isPhone;
@@ -70,6 +70,25 @@ export class SessionService {
70
70
  where.realm = name;
71
71
 
72
72
  if (settings.usernameEnabled !== false && isUsername) {
73
+ // validate username format if regex is provided
74
+ if (settings.usernameRegExp) {
75
+ const regex = new RegExp(settings.usernameRegExp);
76
+ if (!regex.test(username)) {
77
+ this.log.warn("Username does not match required format", {
78
+ provider,
79
+ username,
80
+ realm: name,
81
+ });
82
+
83
+ await this.auditService.recordAuth("login_failed", {
84
+ userRealm: name,
85
+ description: "Username does not match required format",
86
+ metadata: { provider, username },
87
+ });
88
+
89
+ throw new InvalidCredentialsError();
90
+ }
91
+ }
73
92
  where.username = username;
74
93
  } else if (settings.emailEnabled !== false && isEmail) {
75
94
  where.email = username;
@@ -237,7 +256,7 @@ export class SessionService {
237
256
  userId: session.userId,
238
257
  });
239
258
 
240
- const { name } = this.userRealmProvider.getRealm(userRealmName);
259
+ const { name } = this.realmProvider.getRealm(userRealmName);
241
260
 
242
261
  await this.auditService.recordAuth("token_refresh", {
243
262
  userId: user.id,
@@ -270,7 +289,7 @@ export class SessionService {
270
289
  this.log.debug("Session deleted");
271
290
 
272
291
  if (session) {
273
- const { name } = this.userRealmProvider.getRealm(userRealmName);
292
+ const { name } = this.realmProvider.getRealm(userRealmName);
274
293
 
275
294
  await this.auditService.recordAuth("logout", {
276
295
  userId: session.userId,
@@ -292,7 +311,7 @@ export class SessionService {
292
311
  email: profile.email,
293
312
  });
294
313
 
295
- const realm = this.userRealmProvider.getRealm(userRealmName);
314
+ const realm = this.realmProvider.getRealm(userRealmName);
296
315
  const identities = this.identities(userRealmName);
297
316
  const users = this.users(userRealmName);
298
317
 
@@ -7,7 +7,7 @@ import { BadRequestError } from "alepha/server";
7
7
  import { $client } from "alepha/server/links";
8
8
  import type { UserEntity } from "../entities/users.ts";
9
9
  import { UserNotifications } from "../notifications/UserNotifications.ts";
10
- import { UserRealmProvider } from "../providers/UserRealmProvider.ts";
10
+ import { RealmProvider } from "../providers/RealmProvider.ts";
11
11
  import type { CreateUser } from "../schemas/createUserSchema.ts";
12
12
  import type { UpdateUser } from "../schemas/updateUserSchema.ts";
13
13
  import type { UserQuery } from "../schemas/userQuerySchema.ts";
@@ -16,11 +16,11 @@ export class UserService {
16
16
  protected readonly log = $logger();
17
17
  protected readonly verificationController = $client<VerificationController>();
18
18
  protected readonly userNotifications = $inject(UserNotifications);
19
- protected readonly userRealmProvider = $inject(UserRealmProvider);
19
+ protected readonly realmProvider = $inject(RealmProvider);
20
20
  protected readonly auditService = $inject(AuditService);
21
21
 
22
22
  public users(userRealmName?: string) {
23
- return this.userRealmProvider.userRepository(userRealmName);
23
+ return this.realmProvider.userRepository(userRealmName);
24
24
  }
25
25
 
26
26
  /**
@@ -156,7 +156,7 @@ export class UserService {
156
156
 
157
157
  this.log.info("Email verified", { email, userId: user.id, type });
158
158
 
159
- const realm = this.userRealmProvider.getRealm(userRealmName);
159
+ const realm = this.realmProvider.getRealm(userRealmName);
160
160
 
161
161
  await this.auditService.recordUser("update", {
162
162
  userId: user.id,
@@ -256,7 +256,7 @@ export class UserService {
256
256
  userRealmName,
257
257
  });
258
258
 
259
- const realm = this.userRealmProvider.getRealm(userRealmName);
259
+ const realm = this.realmProvider.getRealm(userRealmName);
260
260
 
261
261
  // TODO: one query instead of 3
262
262
 
@@ -342,7 +342,7 @@ export class UserService {
342
342
  const user = await this.users(userRealmName).updateById(id, data);
343
343
  this.log.debug("User updated", { userId: id });
344
344
 
345
- const realm = this.userRealmProvider.getRealm(userRealmName);
345
+ const realm = this.realmProvider.getRealm(userRealmName);
346
346
 
347
347
  // Build changes object showing what was updated
348
348
  const changes: Record<string, { from: unknown; to: unknown }> = {};
@@ -382,7 +382,7 @@ export class UserService {
382
382
  await this.users(userRealmName).deleteById(id);
383
383
  this.log.info("User deleted", { userId: id });
384
384
 
385
- const realm = this.userRealmProvider.getRealm(userRealmName);
385
+ const realm = this.realmProvider.getRealm(userRealmName);
386
386
 
387
387
  await this.auditService.recordUser("delete", {
388
388
  userRealm: realm.name,
@@ -1,4 +1,3 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { $inject, type Alepha } from "alepha";
3
2
  import { DateTimeProvider, type DurationLike } from "alepha/datetime";
4
3
  import { $logger } from "alepha/logger";
@@ -188,7 +187,7 @@ export class BatchProvider {
188
187
  item: TItem,
189
188
  ): string {
190
189
  // 1. Generate unique ID
191
- const id = randomUUID();
190
+ const id = crypto.randomUUID();
192
191
 
193
192
  // 2. Determine the partition key (with error handling)
194
193
  let partitionKey: string;
@@ -1,7 +1,7 @@
1
1
  import { access, readdir, readFile } from "node:fs/promises";
2
2
  import * as os from "node:os";
3
3
  import { join } from "node:path";
4
- import { $inject } from "alepha";
4
+ import { $inject, AlephaError } from "alepha";
5
5
  import { $command } from "alepha/command";
6
6
  import { FileSystemProvider } from "alepha/file";
7
7
  import type { InlineConfig } from "tsdown";
@@ -11,6 +11,7 @@ interface Module {
11
11
  dependencies: string[];
12
12
  native?: boolean;
13
13
  browser?: boolean;
14
+ bun?: boolean;
14
15
  node?: boolean;
15
16
  }
16
17
 
@@ -24,13 +25,13 @@ export class AlephaPackageBuilderCli {
24
25
  handler: async ({ run, root }) => {
25
26
  const modules: Array<Module> = [];
26
27
 
27
- const pkg = await readFile("package.json", "utf-8");
28
- const pkgData = JSON.parse(pkg);
28
+ const pkgBuffer = await this.fs.readFile("package.json");
29
+ const pkgData = JSON.parse(pkgBuffer.toString("utf-8"));
29
30
  const packageName = pkgData.name as string;
30
31
 
31
32
  await run("analyze modules", async () => {
32
33
  modules.push(
33
- ...(await analyzeModules(join(root, this.src), packageName)),
34
+ ...(await analyzeModules(this.fs.join(root, this.src), packageName)),
34
35
  );
35
36
  });
36
37
 
@@ -56,6 +57,10 @@ export class AlephaPackageBuilderCli {
56
57
  pkgData.exports[path].browser = `./src/${item.name}/index.browser.ts`;
57
58
  }
58
59
 
60
+ if (item.bun) {
61
+ pkgData.exports[path].bun = `./src/${item.name}/index.bun.ts`;
62
+ }
63
+
59
64
  pkgData.exports[path].import = `./src/${item.name}/index.ts`;
60
65
  pkgData.exports[path].default = `./src/${item.name}/index.ts`;
61
66
  }
@@ -72,21 +77,20 @@ export class AlephaPackageBuilderCli {
72
77
 
73
78
  await this.fs.writeFile("package.json", JSON.stringify(pkgData, null, 2));
74
79
 
75
- const tmpDir = join(root, "node_modules/.alepha");
80
+ const tmpDir = this.fs.join(root, "node_modules/.alepha");
76
81
  await this.fs.mkdir(tmpDir, { recursive: true }).catch(() => {});
77
82
 
78
83
  await this.fs.writeFile(
79
- join(tmpDir, "module-dependencies.json"),
84
+ this.fs.join(tmpDir, "module-dependencies.json"),
80
85
  JSON.stringify(modules, null, 2),
81
86
  );
82
87
 
83
- const tsconfig = await readFile(
84
- join(root, "../../tsconfig.json"),
85
- "utf-8",
88
+ const tsconfigBuffer = await this.fs.readFile(
89
+ this.fs.join(root, "../../tsconfig.json"),
86
90
  );
87
91
 
88
92
  const external: string[] = Object.keys(
89
- JSON.parse(tsconfig).compilerOptions.paths,
93
+ JSON.parse(tsconfigBuffer.toString("utf-8")).compilerOptions.paths,
90
94
  );
91
95
 
92
96
  external.push("bun");
@@ -96,11 +100,11 @@ export class AlephaPackageBuilderCli {
96
100
 
97
101
  const build = async (item: Module) => {
98
102
  const entries: InlineConfig[] = [];
99
- const src = join(root, this.src, item.name);
100
- const dest = join(root, this.dist, item.name);
103
+ const src = this.fs.join(root, this.src, item.name);
104
+ const dest = this.fs.join(root, this.dist, item.name);
101
105
 
102
106
  entries.push({
103
- entry: join(src, "index.ts"),
107
+ entry: this.fs.join(src, "index.ts"),
104
108
  outDir: dest,
105
109
  format: ["esm"],
106
110
  sourcemap: true,
@@ -109,13 +113,12 @@ export class AlephaPackageBuilderCli {
109
113
  external,
110
114
  dts: {
111
115
  sourcemap: true,
112
- resolve: false,
113
116
  },
114
117
  });
115
118
 
116
119
  if (item.native) {
117
120
  entries.push({
118
- entry: join(src, "index.native.ts"),
121
+ entry: this.fs.join(src, "index.native.ts"),
119
122
  outDir: dest,
120
123
  platform: "neutral",
121
124
  sourcemap: true,
@@ -126,7 +129,7 @@ export class AlephaPackageBuilderCli {
126
129
 
127
130
  if (item.browser) {
128
131
  entries.push({
129
- entry: join(src, "index.browser.ts"),
132
+ entry: this.fs.join(src, "index.browser.ts"),
130
133
  outDir: dest,
131
134
  platform: "browser",
132
135
  sourcemap: true,
@@ -135,7 +138,19 @@ export class AlephaPackageBuilderCli {
135
138
  });
136
139
  }
137
140
 
138
- const config = join(
141
+ if (item.bun) {
142
+ entries.push({
143
+ entry: this.fs.join(src, "index.bun.ts"),
144
+ outDir: dest,
145
+ platform: "node",
146
+ sourcemap: true,
147
+ fixedExtension: false,
148
+ dts: false,
149
+ external,
150
+ });
151
+ }
152
+
153
+ const config = this.fs.join(
139
154
  tmpDir,
140
155
  `tsdown-${item.name.replace("/", "-")}.config.js`,
141
156
  );
@@ -261,7 +276,9 @@ function detectCircularDependencies(modules: Module[]): void {
261
276
  for (const module of modules) {
262
277
  const cycle = hasCycle(module.name);
263
278
  if (cycle) {
264
- throw new Error(`Circular dependency detected: ${cycle.join(" -> ")}`);
279
+ throw new AlephaError(
280
+ `Circular dependency detected: ${cycle.join(" -> ")}`,
281
+ );
265
282
  }
266
283
  }
267
284
  }
@@ -287,13 +304,14 @@ export async function analyzeModules(
287
304
  // This is a module
288
305
  const dependencies = new Set<string>();
289
306
 
290
- // Check for browser/node entry points
307
+ // Check for browser/node/bun entry points
291
308
  const hasBrowser = await fileExists(
292
309
  join(modulePath, "index.browser.ts"),
293
310
  );
294
311
  const hasNative = await fileExists(
295
312
  join(modulePath, "index.native.ts"),
296
313
  );
314
+ const hasBun = await fileExists(join(modulePath, "index.bun.ts"));
297
315
  const hasNode = await fileExists(join(modulePath, "index.node.ts"));
298
316
 
299
317
  // Get all .ts/.tsx files in this module
@@ -328,6 +346,7 @@ export async function analyzeModules(
328
346
 
329
347
  if (hasNative) module.native = true;
330
348
  if (hasBrowser) module.browser = true;
349
+ if (hasBun) module.bun = true;
331
350
  if (hasNode) module.node = true;
332
351
 
333
352
  modules.push(module);
@@ -0,0 +1,18 @@
1
+ export const apiHelloControllerTs = () => `
2
+ import { t } from "alepha";
3
+ import { $action } from "alepha/server";
4
+
5
+ export class HelloController {
6
+ hello = $action({
7
+ path: "/hello",
8
+ schema: {
9
+ response: t.object({
10
+ message: t.string(),
11
+ }),
12
+ },
13
+ handler: () => ({
14
+ message: "Hello, Alepha!",
15
+ }),
16
+ });
17
+ }
18
+ `.trim();
@@ -0,0 +1,16 @@
1
+ export interface ApiIndexTsOptions {
2
+ appName?: string;
3
+ }
4
+
5
+ export const apiIndexTs = (options: ApiIndexTsOptions = {}) => {
6
+ const { appName = "app" } = options;
7
+ return `
8
+ import { $module } from "alepha";
9
+ import { HelloController } from "./controllers/HelloController.ts";
10
+
11
+ export const ApiModule = $module({
12
+ name: "${appName}.api",
13
+ services: [HelloController],
14
+ });
15
+ `.trim();
16
+ };