alepha 0.14.3 → 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 (317) hide show
  1. package/README.md +2 -5
  2. package/dist/api/audits/index.d.ts +620 -811
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +185 -377
  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 -435
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/notifications/index.d.ts +238 -429
  11. package/dist/api/notifications/index.d.ts.map +1 -1
  12. package/dist/api/parameters/index.d.ts +236 -427
  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 +1010 -1196
  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 +17 -17
  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 +384 -285
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +1113 -623
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/command/index.d.ts +299 -300
  36. package/dist/command/index.d.ts.map +1 -1
  37. package/dist/command/index.js +13 -9
  38. package/dist/command/index.js.map +1 -1
  39. package/dist/core/index.browser.js +445 -103
  40. package/dist/core/index.browser.js.map +1 -1
  41. package/dist/core/index.d.ts +733 -625
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +446 -103
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/index.native.js +445 -103
  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/logger/index.js +12 -2
  67. package/dist/logger/index.js.map +1 -1
  68. package/dist/mcp/index.d.ts +197 -197
  69. package/dist/mcp/index.d.ts.map +1 -1
  70. package/dist/mcp/index.js +1 -1
  71. package/dist/mcp/index.js.map +1 -1
  72. package/dist/orm/chunk-DtkW-qnP.js +38 -0
  73. package/dist/orm/index.browser.js.map +1 -1
  74. package/dist/orm/index.bun.js +2814 -0
  75. package/dist/orm/index.bun.js.map +1 -0
  76. package/dist/orm/index.d.ts +1228 -1216
  77. package/dist/orm/index.d.ts.map +1 -1
  78. package/dist/orm/index.js +2041 -1967
  79. package/dist/orm/index.js.map +1 -1
  80. package/dist/queue/core/index.d.ts +248 -248
  81. package/dist/queue/core/index.d.ts.map +1 -1
  82. package/dist/queue/redis/index.d.ts.map +1 -1
  83. package/dist/redis/index.bun.js +285 -0
  84. package/dist/redis/index.bun.js.map +1 -0
  85. package/dist/redis/index.d.ts +118 -136
  86. package/dist/redis/index.d.ts.map +1 -1
  87. package/dist/redis/index.js +18 -38
  88. package/dist/redis/index.js.map +1 -1
  89. package/dist/retry/index.d.ts +69 -69
  90. package/dist/retry/index.d.ts.map +1 -1
  91. package/dist/router/index.d.ts +6 -6
  92. package/dist/router/index.d.ts.map +1 -1
  93. package/dist/scheduler/index.d.ts +25 -25
  94. package/dist/scheduler/index.d.ts.map +1 -1
  95. package/dist/security/index.browser.js +5 -1
  96. package/dist/security/index.browser.js.map +1 -1
  97. package/dist/security/index.d.ts +417 -254
  98. package/dist/security/index.d.ts.map +1 -1
  99. package/dist/security/index.js +386 -86
  100. package/dist/security/index.js.map +1 -1
  101. package/dist/server/auth/index.d.ts +110 -110
  102. package/dist/server/auth/index.d.ts.map +1 -1
  103. package/dist/server/auth/index.js +20 -20
  104. package/dist/server/auth/index.js.map +1 -1
  105. package/dist/server/cache/index.d.ts +62 -47
  106. package/dist/server/cache/index.d.ts.map +1 -1
  107. package/dist/server/cache/index.js +56 -3
  108. package/dist/server/cache/index.js.map +1 -1
  109. package/dist/server/compress/index.d.ts +6 -0
  110. package/dist/server/compress/index.d.ts.map +1 -1
  111. package/dist/server/compress/index.js +36 -1
  112. package/dist/server/compress/index.js.map +1 -1
  113. package/dist/server/cookies/index.d.ts +6 -6
  114. package/dist/server/cookies/index.d.ts.map +1 -1
  115. package/dist/server/cookies/index.js +3 -3
  116. package/dist/server/cookies/index.js.map +1 -1
  117. package/dist/server/core/index.browser.js +2 -2
  118. package/dist/server/core/index.browser.js.map +1 -1
  119. package/dist/server/core/index.d.ts +242 -150
  120. package/dist/server/core/index.d.ts.map +1 -1
  121. package/dist/server/core/index.js +294 -125
  122. package/dist/server/core/index.js.map +1 -1
  123. package/dist/server/cors/index.d.ts +11 -12
  124. package/dist/server/cors/index.d.ts.map +1 -1
  125. package/dist/server/health/index.d.ts +0 -1
  126. package/dist/server/health/index.d.ts.map +1 -1
  127. package/dist/server/helmet/index.d.ts +2 -2
  128. package/dist/server/helmet/index.d.ts.map +1 -1
  129. package/dist/server/links/index.browser.js.map +1 -1
  130. package/dist/server/links/index.d.ts +123 -124
  131. package/dist/server/links/index.d.ts.map +1 -1
  132. package/dist/server/links/index.js +1 -2
  133. package/dist/server/links/index.js.map +1 -1
  134. package/dist/server/metrics/index.d.ts.map +1 -1
  135. package/dist/server/multipart/index.d.ts +6 -6
  136. package/dist/server/multipart/index.d.ts.map +1 -1
  137. package/dist/server/proxy/index.d.ts +102 -103
  138. package/dist/server/proxy/index.d.ts.map +1 -1
  139. package/dist/server/rate-limit/index.d.ts +16 -16
  140. package/dist/server/rate-limit/index.d.ts.map +1 -1
  141. package/dist/server/static/index.d.ts +44 -44
  142. package/dist/server/static/index.d.ts.map +1 -1
  143. package/dist/server/static/index.js +4 -0
  144. package/dist/server/static/index.js.map +1 -1
  145. package/dist/server/swagger/index.d.ts +48 -49
  146. package/dist/server/swagger/index.d.ts.map +1 -1
  147. package/dist/server/swagger/index.js +3 -5
  148. package/dist/server/swagger/index.js.map +1 -1
  149. package/dist/sms/index.d.ts +13 -11
  150. package/dist/sms/index.d.ts.map +1 -1
  151. package/dist/sms/index.js +7 -7
  152. package/dist/sms/index.js.map +1 -1
  153. package/dist/thread/index.d.ts +71 -72
  154. package/dist/thread/index.d.ts.map +1 -1
  155. package/dist/topic/core/index.d.ts +318 -318
  156. package/dist/topic/core/index.d.ts.map +1 -1
  157. package/dist/topic/redis/index.d.ts +6 -6
  158. package/dist/topic/redis/index.d.ts.map +1 -1
  159. package/dist/vite/index.d.ts +5805 -249
  160. package/dist/vite/index.d.ts.map +1 -1
  161. package/dist/vite/index.js +599 -513
  162. package/dist/vite/index.js.map +1 -1
  163. package/dist/websocket/index.browser.js +6 -6
  164. package/dist/websocket/index.browser.js.map +1 -1
  165. package/dist/websocket/index.d.ts +247 -247
  166. package/dist/websocket/index.d.ts.map +1 -1
  167. package/dist/websocket/index.js +6 -6
  168. package/dist/websocket/index.js.map +1 -1
  169. package/package.json +9 -14
  170. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  171. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  172. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  173. package/src/api/users/entities/users.ts +1 -1
  174. package/src/api/users/index.ts +8 -8
  175. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  176. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  177. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  178. package/src/api/users/services/CredentialService.ts +7 -7
  179. package/src/api/users/services/IdentityService.ts +4 -4
  180. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  181. package/src/api/users/services/RegistrationService.ts +38 -27
  182. package/src/api/users/services/SessionCrudService.ts +3 -3
  183. package/src/api/users/services/SessionService.spec.ts +3 -3
  184. package/src/api/users/services/SessionService.ts +28 -9
  185. package/src/api/users/services/UserService.ts +7 -7
  186. package/src/batch/providers/BatchProvider.ts +1 -2
  187. package/src/cli/apps/AlephaCli.ts +0 -2
  188. package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
  189. package/src/cli/assets/apiHelloControllerTs.ts +18 -0
  190. package/src/cli/assets/apiIndexTs.ts +16 -0
  191. package/src/cli/assets/claudeMd.ts +303 -0
  192. package/src/cli/assets/mainBrowserTs.ts +2 -2
  193. package/src/cli/assets/mainServerTs.ts +24 -0
  194. package/src/cli/assets/webAppRouterTs.ts +15 -0
  195. package/src/cli/assets/webHelloComponentTsx.ts +16 -0
  196. package/src/cli/assets/webIndexTs.ts +16 -0
  197. package/src/cli/atoms/buildOptions.ts +88 -0
  198. package/src/cli/commands/build.ts +70 -87
  199. package/src/cli/commands/db.ts +21 -22
  200. package/src/cli/commands/deploy.ts +17 -5
  201. package/src/cli/commands/dev.ts +22 -14
  202. package/src/cli/commands/format.ts +8 -2
  203. package/src/cli/commands/gen/env.ts +53 -0
  204. package/src/cli/commands/gen/openapi.ts +1 -1
  205. package/src/cli/commands/gen/resource.ts +15 -0
  206. package/src/cli/commands/gen.ts +7 -1
  207. package/src/cli/commands/init.ts +74 -30
  208. package/src/cli/commands/lint.ts +8 -2
  209. package/src/cli/commands/test.ts +8 -3
  210. package/src/cli/commands/typecheck.ts +5 -1
  211. package/src/cli/commands/verify.ts +5 -3
  212. package/src/cli/defineConfig.ts +49 -7
  213. package/src/cli/index.ts +0 -1
  214. package/src/cli/services/AlephaCliUtils.ts +39 -589
  215. package/src/cli/services/PackageManagerUtils.ts +301 -0
  216. package/src/cli/services/ProjectScaffolder.ts +306 -0
  217. package/src/command/helpers/Runner.spec.ts +2 -2
  218. package/src/command/helpers/Runner.ts +16 -4
  219. package/src/command/primitives/$command.ts +0 -6
  220. package/src/command/providers/CliProvider.ts +1 -3
  221. package/src/core/Alepha.ts +42 -0
  222. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  223. package/src/core/index.shared.ts +1 -0
  224. package/src/core/index.ts +2 -0
  225. package/src/core/primitives/$hook.ts +6 -2
  226. package/src/core/primitives/$module.spec.ts +4 -0
  227. package/src/core/providers/AlsProvider.ts +1 -1
  228. package/src/core/providers/CodecManager.spec.ts +12 -6
  229. package/src/core/providers/CodecManager.ts +26 -6
  230. package/src/core/providers/EventManager.ts +169 -13
  231. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
  232. package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
  233. package/src/core/providers/StateManager.spec.ts +27 -16
  234. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  235. package/src/email/providers/LocalEmailProvider.ts +52 -15
  236. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  237. package/src/file/errors/FileError.ts +7 -0
  238. package/src/file/index.ts +9 -1
  239. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  240. package/src/logger/index.ts +15 -3
  241. package/src/mcp/transports/StdioMcpTransport.ts +1 -1
  242. package/src/orm/index.browser.ts +1 -19
  243. package/src/orm/index.bun.ts +77 -0
  244. package/src/orm/index.shared-server.ts +22 -0
  245. package/src/orm/index.shared.ts +15 -0
  246. package/src/orm/index.ts +13 -39
  247. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  248. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  249. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  250. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  251. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  252. package/src/orm/services/Repository.ts +8 -0
  253. package/src/queue/core/providers/WorkerProvider.spec.ts +48 -32
  254. package/src/redis/index.bun.ts +35 -0
  255. package/src/redis/providers/BunRedisProvider.ts +12 -43
  256. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  257. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  258. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  259. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  260. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  261. package/src/security/index.browser.ts +5 -0
  262. package/src/security/index.ts +90 -7
  263. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  264. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  265. package/src/security/primitives/$role.ts +5 -5
  266. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  267. package/src/security/primitives/$serviceAccount.ts +3 -3
  268. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  269. package/src/server/auth/primitives/$auth.ts +10 -10
  270. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  271. package/src/server/auth/primitives/$authGithub.ts +3 -3
  272. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  273. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  274. package/src/server/cache/providers/ServerCacheProvider.spec.ts +183 -0
  275. package/src/server/cache/providers/ServerCacheProvider.ts +95 -10
  276. package/src/server/compress/providers/ServerCompressProvider.ts +61 -2
  277. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  278. package/src/server/core/helpers/ServerReply.ts +2 -2
  279. package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
  280. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  281. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  282. package/src/server/core/providers/ServerProvider.ts +155 -22
  283. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  284. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  285. package/src/server/links/index.ts +1 -1
  286. package/src/server/links/providers/LinkProvider.ts +1 -1
  287. package/src/server/static/providers/ServerStaticProvider.ts +10 -0
  288. package/src/server/swagger/index.ts +1 -1
  289. package/src/server/swagger/providers/ServerSwaggerProvider.ts +5 -8
  290. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  291. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  292. package/src/vite/helpers/boot.ts +28 -17
  293. package/src/vite/helpers/importViteReact.ts +13 -0
  294. package/src/vite/index.ts +1 -21
  295. package/src/vite/plugins/viteAlephaDev.ts +16 -1
  296. package/src/vite/plugins/viteAlephaSsrPreload.ts +222 -0
  297. package/src/vite/tasks/buildClient.ts +11 -0
  298. package/src/vite/tasks/buildServer.ts +59 -4
  299. package/src/vite/tasks/devServer.ts +71 -0
  300. package/src/vite/tasks/generateCloudflare.ts +7 -0
  301. package/src/vite/tasks/index.ts +2 -1
  302. package/dist/server/security/index.browser.js +0 -13
  303. package/dist/server/security/index.browser.js.map +0 -1
  304. package/dist/server/security/index.d.ts +0 -173
  305. package/dist/server/security/index.d.ts.map +0 -1
  306. package/dist/server/security/index.js +0 -311
  307. package/dist/server/security/index.js.map +0 -1
  308. package/src/cli/assets/appRouterTs.ts +0 -9
  309. package/src/cli/assets/mainTs.ts +0 -13
  310. package/src/cli/assets/viteConfigTs.ts +0 -14
  311. package/src/cli/commands/run.ts +0 -24
  312. package/src/server/security/index.browser.ts +0 -10
  313. package/src/server/security/index.ts +0 -94
  314. package/src/vite/plugins/viteAlepha.ts +0 -37
  315. package/src/vite/plugins/viteAlephaBuild.ts +0 -281
  316. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  317. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
@@ -1,7 +1,7 @@
1
1
  import { $inject, createPrimitive, KIND, Primitive } from "alepha";
2
2
  import { SecurityProvider } from "../providers/SecurityProvider.ts";
3
+ import type { IssuerPrimitive } from "./$issuer.ts";
3
4
  import type { PermissionPrimitive } from "./$permission.ts";
4
- import type { RealmPrimitive } from "./$realm.ts";
5
5
 
6
6
  /**
7
7
  * Create a new role.
@@ -23,7 +23,7 @@ export interface RolePrimitiveOptions {
23
23
  */
24
24
  description?: string;
25
25
 
26
- realm?: string | RealmPrimitive;
26
+ issuer?: string | IssuerPrimitive;
27
27
 
28
28
  permissions?: Array<
29
29
  | string
@@ -60,10 +60,10 @@ export class RolePrimitive extends Primitive<RolePrimitiveOptions> {
60
60
  }
61
61
 
62
62
  /**
63
- * Get the realm of the role.
63
+ * Get the issuer of the role.
64
64
  */
65
- public get realm(): string | RealmPrimitive | undefined {
66
- return this.options.realm;
65
+ public get issuer(): string | IssuerPrimitive | undefined {
66
+ return this.options.issuer;
67
67
  }
68
68
 
69
69
  public can(permission: string | PermissionPrimitive): boolean {
@@ -1,7 +1,7 @@
1
1
  import { Alepha } from "alepha";
2
2
  import { DateTimeProvider } from "alepha/datetime";
3
3
  import { describe, expect, it } from "vitest";
4
- import { $realm, $serviceAccount } from "../index.ts";
4
+ import { $issuer, $serviceAccount } from "../index.ts";
5
5
 
6
6
  class App {
7
7
  oauth2 = $serviceAccount({
@@ -16,12 +16,12 @@ class App {
16
16
  expiresIn: 300, // 5 minutes
17
17
  };
18
18
 
19
- realm = $realm({
20
- secret: "your-realm-secret",
19
+ issuer = $issuer({
20
+ secret: "your-issuer-secret",
21
21
  });
22
22
 
23
23
  jwt = $serviceAccount({
24
- realm: this.realm,
24
+ issuer: this.issuer,
25
25
  user: {
26
26
  id: "service-account",
27
27
  },
@@ -42,7 +42,7 @@ describe("$serviceAccount", () => {
42
42
  );
43
43
  expect(payload).toEqual({
44
44
  sub: expect.any(String),
45
- aud: "realm",
45
+ aud: "issuer",
46
46
  iat: expect.any(Number),
47
47
  exp: expect.any(Number),
48
48
  sid: expect.any(String),
@@ -1,7 +1,7 @@
1
1
  import { $context } from "alepha";
2
2
  import { DateTimeProvider } from "alepha/datetime";
3
3
  import type { UserAccount } from "../schemas/userAccountInfoSchema.ts";
4
- import type { AccessTokenResponse, RealmPrimitive } from "./$realm.ts";
4
+ import type { AccessTokenResponse, IssuerPrimitive } from "./$issuer.ts";
5
5
 
6
6
  /**
7
7
  * Allow to get an access token for a service account.
@@ -138,7 +138,7 @@ export const $serviceAccount = (
138
138
  return tokenFromCache;
139
139
  }
140
140
 
141
- const token = await options.realm.createToken(options.user);
141
+ const token = await options.issuer.createToken(options.user);
142
142
 
143
143
  cacheToken({
144
144
  ...token,
@@ -157,7 +157,7 @@ export type ServiceAccountPrimitiveOptions = {
157
157
  oauth2: Oauth2ServiceAccountPrimitiveOptions;
158
158
  }
159
159
  | {
160
- realm: RealmPrimitive;
160
+ issuer: IssuerPrimitive;
161
161
  user: UserAccount;
162
162
  }
163
163
  );
@@ -1,19 +1,17 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { $hook, $inject, Alepha } from "alepha";
3
3
  import { $logger } from "alepha/logger";
4
- import {
5
- JwtProvider,
6
- type Permission,
7
- SecurityProvider,
8
- type UserAccountToken,
9
- userAccountInfoSchema,
10
- } from "alepha/security";
11
4
  import {
12
5
  $action,
13
6
  ForbiddenError,
14
7
  type ServerRequest,
15
8
  UnauthorizedError,
16
9
  } from "alepha/server";
10
+ import type { UserAccountToken } from "../interfaces/UserAccountToken.ts";
11
+ import type { Permission } from "../schemas/permissionSchema.ts";
12
+ import { userAccountInfoSchema } from "../schemas/userAccountInfoSchema.ts";
13
+ import { JwtProvider } from "./JwtProvider.ts";
14
+ import { SecurityProvider } from "./SecurityProvider.ts";
17
15
  import {
18
16
  type BasicAuthOptions,
19
17
  isBasicAuth,
@@ -9,7 +9,7 @@ import {
9
9
  import { DateTimeProvider } from "alepha/datetime";
10
10
  import {
11
11
  type AccessTokenResponse,
12
- type RealmPrimitive,
12
+ type IssuerPrimitive,
13
13
  SecurityError,
14
14
  SecurityProvider,
15
15
  type UserAccount,
@@ -105,10 +105,10 @@ export type AuthExternal = {
105
105
  * When using your own authentication system, e.g. using a database to store user accounts.
106
106
  * This is usually used with a custom login form.
107
107
  *
108
- * This relies on the `realm`, which is used to create/verify the access token.
108
+ * This relies on the `issuer`, which is used to create/verify the access token.
109
109
  */
110
110
  export type AuthInternal = {
111
- realm: RealmPrimitive;
111
+ issuer: IssuerPrimitive;
112
112
  } & (
113
113
  | {
114
114
  /**
@@ -261,9 +261,9 @@ export class AuthPrimitive extends Primitive<AuthPrimitiveOptions> {
261
261
  return this.options.name ?? this.config.propertyKey;
262
262
  }
263
263
 
264
- public get realm(): RealmPrimitive | undefined {
265
- if ("realm" in this.options) {
266
- return this.options.realm;
264
+ public get issuer(): IssuerPrimitive | undefined {
265
+ if ("issuer" in this.options) {
266
+ return this.options.issuer;
267
267
  }
268
268
  return undefined;
269
269
  }
@@ -308,13 +308,13 @@ export class AuthPrimitive extends Primitive<AuthPrimitiveOptions> {
308
308
  refreshToken: string,
309
309
  accessToken?: string,
310
310
  ): Promise<AccessTokenResponse> {
311
- if ("realm" in this.options) {
312
- return this.options.realm
311
+ if ("issuer" in this.options) {
312
+ return this.options.issuer
313
313
  .refreshToken(refreshToken, accessToken)
314
314
  .then((it) => it.tokens)
315
315
  .catch((error) => {
316
316
  throw new SecurityError(
317
- "Failed to refresh access token using the refresh token (realm)",
317
+ "Failed to refresh access token using the refresh token (issuer)",
318
318
  {
319
319
  cause: error,
320
320
  },
@@ -337,7 +337,7 @@ export class AuthPrimitive extends Primitive<AuthPrimitiveOptions> {
337
337
  }
338
338
 
339
339
  throw new AlephaError(
340
- "No realm or OAuth2 configuration available for refreshing the access token",
340
+ "No issuer or OAuth2 configuration available for refreshing the access token",
341
341
  );
342
342
  }
343
343
 
@@ -1,5 +1,5 @@
1
1
  import { AlephaError } from "alepha";
2
- import type { RealmPrimitive } from "alepha/security";
2
+ import type { IssuerPrimitive } from "alepha/security";
3
3
  import {
4
4
  $auth,
5
5
  type CredentialsFn,
@@ -13,7 +13,7 @@ import {
13
13
  * Uses username and password to authenticate users.
14
14
  */
15
15
  export const $authCredentials = (
16
- realm: RealmPrimitive & WithLoginFn,
16
+ realm: IssuerPrimitive & WithLoginFn,
17
17
  options: Partial<CredentialsOptions> = {},
18
18
  ) => {
19
19
  const name = "credentials";
@@ -29,7 +29,7 @@ export const $authCredentials = (
29
29
  }
30
30
 
31
31
  return $auth({
32
- realm,
32
+ issuer: realm,
33
33
  name,
34
34
  credentials: {
35
35
  account,
@@ -1,5 +1,5 @@
1
1
  import { $context, AlephaError, t } from "alepha";
2
- import type { RealmPrimitive } from "alepha/security";
2
+ import type { IssuerPrimitive } from "alepha/security";
3
3
  import type { OAuth2Profile } from "../providers/ServerAuthProvider.ts";
4
4
  import {
5
5
  $auth,
@@ -19,7 +19,7 @@ import {
19
19
  * - `GITHUB_CLIENT_SECRET`: The client secret obtained from the GitHub Developer Settings.
20
20
  */
21
21
  export const $authGithub = (
22
- realm: RealmPrimitive & WithLinkFn,
22
+ realm: IssuerPrimitive & WithLinkFn,
23
23
  options: Partial<OidcOptions> = {},
24
24
  ) => {
25
25
  const { alepha } = $context();
@@ -45,7 +45,7 @@ export const $authGithub = (
45
45
  }
46
46
 
47
47
  return $auth({
48
- realm,
48
+ issuer: realm,
49
49
  name,
50
50
  oauth: {
51
51
  clientId: env.GITHUB_CLIENT_ID!,
@@ -1,5 +1,5 @@
1
1
  import { $context, AlephaError, t } from "alepha";
2
- import type { RealmPrimitive } from "alepha/security";
2
+ import type { IssuerPrimitive } from "alepha/security";
3
3
  import {
4
4
  $auth,
5
5
  type LinkAccountFn,
@@ -18,7 +18,7 @@ import {
18
18
  * - `GOOGLE_CLIENT_SECRET`: The client secret obtained from the Google Developer Console.
19
19
  */
20
20
  export const $authGoogle = (
21
- realm: RealmPrimitive & WithLinkFn,
21
+ realm: IssuerPrimitive & WithLinkFn,
22
22
  options: Partial<OidcOptions> = {},
23
23
  ) => {
24
24
  const { alepha } = $context();
@@ -44,7 +44,7 @@ export const $authGoogle = (
44
44
  }
45
45
 
46
46
  return $auth({
47
- realm,
47
+ issuer: realm,
48
48
  name,
49
49
  oidc: {
50
50
  issuer: "https://accounts.google.com",
@@ -71,8 +71,8 @@ export class ServerAuthProvider {
71
71
 
72
72
  for (const identity of this.identities) {
73
73
  if (filters.realmName) {
74
- const realm = "realm" in identity.options && identity.options.realm;
75
- if (!realm || realm.name !== filters.realmName) {
74
+ const issuer = identity.issuer;
75
+ if (!issuer || issuer.name !== filters.realmName) {
76
76
  continue;
77
77
  }
78
78
  }
@@ -145,7 +145,7 @@ export class ServerAuthProvider {
145
145
  // [feature] support for auth providers with fallback
146
146
  if (!request.headers.authorization) {
147
147
  for (const provider of this.identities) {
148
- if (!("realm" in provider.options) && !!provider.options.fallback) {
148
+ if ("fallback" in provider.options && !!provider.options.fallback) {
149
149
  const token = await provider.options.fallback();
150
150
  if (token) {
151
151
  request.headers.authorization = `Bearer ${token}`;
@@ -340,8 +340,8 @@ export class ServerAuthProvider {
340
340
  realm: query.realm,
341
341
  });
342
342
 
343
- const realm = "realm" in provider.options && provider.options.realm;
344
- if (!realm) {
343
+ const issuer = provider.issuer;
344
+ if (!issuer) {
345
345
  throw new SecurityError(
346
346
  `Auth provider '${query.provider}' does not support password grant`,
347
347
  );
@@ -373,7 +373,7 @@ export class ServerAuthProvider {
373
373
 
374
374
  const tokens = {
375
375
  provider: query.provider,
376
- ...(await realm.createToken(user)),
376
+ ...(await issuer.createToken(user)),
377
377
  };
378
378
 
379
379
  // for web applications, we store tokens in cookies
@@ -519,10 +519,10 @@ export class ServerAuthProvider {
519
519
 
520
520
  this.authorizationCode.del({ cookies });
521
521
 
522
- const realm = "realm" in provider.options && provider.options.realm;
522
+ const issuer = provider.issuer;
523
523
 
524
524
  // external, full OIDC System (e.g. Keycloak, Auth0)
525
- if (!realm) {
525
+ if (!issuer) {
526
526
  this.setTokens(externalTokens, cookies);
527
527
  reply.redirect(redirectUri);
528
528
  return;
@@ -531,7 +531,7 @@ export class ServerAuthProvider {
531
531
  // internal, we need to create our own tokens
532
532
 
533
533
  const user = await provider.user(externalTokens);
534
- const tokens = await realm.createToken(user);
534
+ const tokens = await issuer.createToken(user);
535
535
 
536
536
  this.setTokens(
537
537
  {
@@ -570,9 +570,9 @@ export class ServerAuthProvider {
570
570
  this.tokens.del({ cookies });
571
571
 
572
572
  // for internal providers, we can delete the session - if available
573
- if ("realm" in provider.options && tokens.refresh_token) {
573
+ if (provider.issuer && tokens.refresh_token) {
574
574
  const onDeleteSession =
575
- provider.options.realm.options.settings?.onDeleteSession;
575
+ provider.issuer.options.settings?.onDeleteSession;
576
576
  if (onDeleteSession) {
577
577
  try {
578
578
  await onDeleteSession(tokens.refresh_token);
@@ -635,8 +635,8 @@ export class ServerAuthProvider {
635
635
  return false;
636
636
  }
637
637
 
638
- // If realm filter is specified, match against provider's realm
639
- if (realmName && identity.realm?.name !== realmName) {
638
+ // If realm filter is specified, match against provider's issuer
639
+ if (realmName && identity.issuer?.name !== realmName) {
640
640
  return false;
641
641
  }
642
642
 
@@ -865,6 +865,189 @@ describe("ServerCacheProvider", () => {
865
865
  });
866
866
  });
867
867
 
868
+ describe("Stream caching support", () => {
869
+ test("should cache ReadableStream responses via tee", async ({
870
+ expect,
871
+ }) => {
872
+ // Create a simple ReadableStream that emits chunks
873
+ const chunks = ["Hello", " ", "World"];
874
+ const stream = new ReadableStream<Uint8Array>({
875
+ start(controller) {
876
+ const encoder = new TextEncoder();
877
+ for (const chunk of chunks) {
878
+ controller.enqueue(encoder.encode(chunk));
879
+ }
880
+ controller.close();
881
+ },
882
+ });
883
+
884
+ // Access the protected method via type assertion
885
+ const provider = cacheProvider as any;
886
+ const key = "test-stream-key";
887
+
888
+ // Collect the stream for cache
889
+ const hash = await provider.collectStreamForCache(
890
+ stream,
891
+ key,
892
+ 200,
893
+ "text/html",
894
+ true,
895
+ );
896
+
897
+ expect(hash).toBeDefined();
898
+ expect(hash).toMatch(/^"[a-f0-9]+"$/); // ETag format
899
+
900
+ // Verify the cache contains the collected content
901
+ const cached = await provider.cache.get(key);
902
+ expect(cached).toBeDefined();
903
+ expect(cached.body).toBe("Hello World");
904
+ expect(cached.status).toBe(200);
905
+ expect(cached.contentType).toBe("text/html");
906
+ });
907
+
908
+ test("should tee stream so client and cache both receive data", async ({
909
+ expect,
910
+ }) => {
911
+ const encoder = new TextEncoder();
912
+ const chunks = ["<html>", "<body>", "Content", "</body>", "</html>"];
913
+
914
+ const originalStream = new ReadableStream<Uint8Array>({
915
+ start(controller) {
916
+ for (const chunk of chunks) {
917
+ controller.enqueue(encoder.encode(chunk));
918
+ }
919
+ controller.close();
920
+ },
921
+ });
922
+
923
+ // Tee the stream like ServerCacheProvider does
924
+ const [clientStream, cacheStream] = originalStream.tee();
925
+
926
+ // Read from client stream (simulates client receiving data)
927
+ const clientReader = clientStream.getReader();
928
+ const clientChunks: string[] = [];
929
+ const decoder = new TextDecoder();
930
+
931
+ while (true) {
932
+ const { done, value } = await clientReader.read();
933
+ if (done) break;
934
+ clientChunks.push(decoder.decode(value, { stream: true }));
935
+ }
936
+ clientChunks.push(decoder.decode()); // flush
937
+
938
+ // Read from cache stream (simulates cache collection)
939
+ const cacheReader = cacheStream.getReader();
940
+ const cacheChunks: string[] = [];
941
+ const cacheDecoder = new TextDecoder();
942
+
943
+ while (true) {
944
+ const { done, value } = await cacheReader.read();
945
+ if (done) break;
946
+ cacheChunks.push(cacheDecoder.decode(value, { stream: true }));
947
+ }
948
+ cacheChunks.push(cacheDecoder.decode()); // flush
949
+
950
+ // Both should receive the same data
951
+ const clientData = clientChunks.join("");
952
+ const cacheData = cacheChunks.join("");
953
+
954
+ expect(clientData).toBe("<html><body>Content</body></html>");
955
+ expect(cacheData).toBe("<html><body>Content</body></html>");
956
+ expect(clientData).toBe(cacheData);
957
+ });
958
+
959
+ test("should handle empty stream gracefully", async ({ expect }) => {
960
+ const stream = new ReadableStream<Uint8Array>({
961
+ start(controller) {
962
+ controller.close();
963
+ },
964
+ });
965
+
966
+ const provider = cacheProvider as any;
967
+ const key = "empty-stream-key";
968
+
969
+ const hash = await provider.collectStreamForCache(
970
+ stream,
971
+ key,
972
+ 200,
973
+ "text/html",
974
+ true,
975
+ );
976
+
977
+ expect(hash).toBeDefined();
978
+
979
+ const cached = await provider.cache.get(key);
980
+ expect(cached).toBeDefined();
981
+ expect(cached.body).toBe("");
982
+ });
983
+
984
+ test("should handle large stream with multiple chunks", async ({
985
+ expect,
986
+ }) => {
987
+ const encoder = new TextEncoder();
988
+ const chunkCount = 100;
989
+ const chunkContent = "x".repeat(1000);
990
+
991
+ const stream = new ReadableStream<Uint8Array>({
992
+ start(controller) {
993
+ for (let i = 0; i < chunkCount; i++) {
994
+ controller.enqueue(encoder.encode(chunkContent));
995
+ }
996
+ controller.close();
997
+ },
998
+ });
999
+
1000
+ const provider = cacheProvider as any;
1001
+ const key = "large-stream-key";
1002
+
1003
+ const hash = await provider.collectStreamForCache(
1004
+ stream,
1005
+ key,
1006
+ 200,
1007
+ "text/html",
1008
+ false,
1009
+ );
1010
+
1011
+ // hash should be undefined when generateEtag is false
1012
+ expect(hash).toBeUndefined();
1013
+
1014
+ const cached = await provider.cache.get(key);
1015
+ expect(cached).toBeDefined();
1016
+ expect(cached.body.length).toBe(chunkCount * chunkContent.length);
1017
+ });
1018
+
1019
+ test("should generate correct ETag for streamed content", async ({
1020
+ expect,
1021
+ }) => {
1022
+ const content = "Test content for ETag";
1023
+ const encoder = new TextEncoder();
1024
+
1025
+ const stream = new ReadableStream<Uint8Array>({
1026
+ start(controller) {
1027
+ controller.enqueue(encoder.encode(content));
1028
+ controller.close();
1029
+ },
1030
+ });
1031
+
1032
+ const provider = cacheProvider as any;
1033
+
1034
+ // Generate ETag from stream
1035
+ const streamHash = await provider.collectStreamForCache(
1036
+ stream,
1037
+ "etag-test-key",
1038
+ 200,
1039
+ "text/html",
1040
+ true,
1041
+ );
1042
+
1043
+ // Generate ETag from string directly
1044
+ const stringHash = cacheProvider.generateETag(content);
1045
+
1046
+ // Both should produce the same ETag
1047
+ expect(streamHash).toBe(stringHash);
1048
+ });
1049
+ });
1050
+
868
1051
  describe("Error response caching", () => {
869
1052
  test("should NOT cache 500 error responses", async ({ expect }) => {
870
1053
  const response1 = await app.errorAction.fetch();
@@ -204,7 +204,7 @@ export class ServerCacheProvider {
204
204
 
205
205
  protected readonly onSend = $hook({
206
206
  on: "server:onSend",
207
- handler: async ({ route, request }) => {
207
+ handler: ({ route, request }) => {
208
208
  // before sending the response, check if the ETag matches
209
209
  // and if so, return a 304 Not Modified response
210
210
  // -> this is only relevant for etag-only routes, not cached routes <-
@@ -261,24 +261,55 @@ export class ServerCacheProvider {
261
261
  return;
262
262
  }
263
263
 
264
- // Only process string responses (text, html, json, etc.)
265
- // Buffer is not supported by alepha/cache for now
266
- if (typeof response.body !== "string") {
267
- return;
268
- }
269
-
270
264
  // Don't cache error responses (status >= 400)
271
265
  if (response.status && response.status >= 400) {
272
266
  return;
273
267
  }
274
268
 
269
+ // Initialize headers if not present
270
+ response.headers ??= {};
271
+
275
272
  const key = this.createCacheKey(route, request);
273
+
274
+ // Handle ReadableStream responses (e.g., SSR streaming)
275
+ if (response.body instanceof ReadableStream && shouldStore) {
276
+ // Tee the stream: one for client, one for cache collection
277
+ const [clientStream, cacheStream] = (
278
+ response.body as ReadableStream<Uint8Array>
279
+ ).tee();
280
+
281
+ // Replace response body with client stream (continues streaming to client)
282
+ response.body = clientStream as typeof response.body;
283
+
284
+ // Collect cache stream in background (non-blocking)
285
+ this.collectStreamForCache(
286
+ cacheStream,
287
+ key,
288
+ response.status,
289
+ response.headers?.["content-type"],
290
+ shouldUseEtag,
291
+ )
292
+ .then((hash) => {
293
+ if (shouldUseEtag && hash) {
294
+ // Note: headers already sent for streaming, etag only useful for future requests
295
+ this.log.trace("Stream cached with hash", { key, hash });
296
+ }
297
+ })
298
+ .catch((err) => {
299
+ this.log.warn("Failed to cache stream", { key, error: err });
300
+ });
301
+
302
+ return;
303
+ }
304
+
305
+ // Only process string responses (text, html, json, etc.)
306
+ if (typeof response.body !== "string") {
307
+ return;
308
+ }
309
+
276
310
  const generatedEtag = this.generateETag(response.body);
277
311
  const lastModified = this.time.toISOString();
278
312
 
279
- // Initialize headers if not present
280
- response.headers ??= {};
281
-
282
313
  // Store response if storing is enabled
283
314
  if (shouldStore) {
284
315
  this.log.trace("Storing response", {
@@ -404,6 +435,60 @@ export class ServerCacheProvider {
404
435
 
405
436
  return `${route.method}:${route.path.replaceAll(":", "")}:${params.join(",").replaceAll(":", "")}`;
406
437
  }
438
+
439
+ /**
440
+ * Collect a ReadableStream into a string and store it in the cache.
441
+ * This runs in the background while the original stream is sent to the client.
442
+ *
443
+ * @param stream - The stream to collect
444
+ * @param key - Cache key
445
+ * @param status - HTTP status code
446
+ * @param contentType - Content-Type header
447
+ * @param generateEtag - Whether to generate and return an ETag
448
+ * @returns The generated ETag hash, or undefined
449
+ */
450
+ protected async collectStreamForCache(
451
+ stream: ReadableStream<Uint8Array>,
452
+ key: string,
453
+ status: number | undefined,
454
+ contentType: string | undefined,
455
+ generateEtag: boolean,
456
+ ): Promise<string | undefined> {
457
+ const chunks: Uint8Array[] = [];
458
+ const reader = stream.getReader();
459
+
460
+ try {
461
+ while (true) {
462
+ const { done, value } = await reader.read();
463
+ if (done) break;
464
+ chunks.push(value);
465
+ }
466
+
467
+ // Combine chunks into a single string
468
+ const decoder = new TextDecoder();
469
+ const body =
470
+ chunks
471
+ .map((chunk) => decoder.decode(chunk, { stream: true }))
472
+ .join("") + decoder.decode(); // Flush remaining
473
+
474
+ const hash = this.generateETag(body);
475
+ const lastModified = this.time.toISOString();
476
+
477
+ this.log.trace("Storing streamed response", { key });
478
+
479
+ await this.cache.set(key, {
480
+ body,
481
+ status,
482
+ contentType,
483
+ lastModified,
484
+ hash,
485
+ });
486
+
487
+ return generateEtag ? hash : undefined;
488
+ } finally {
489
+ reader.releaseLock();
490
+ }
491
+ }
407
492
  }
408
493
 
409
494
  export type ServerRouteCache =