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
@@ -532,9 +532,9 @@ declare class ConsumerPrimitive<T extends TSchema> extends Primitive<ConsumerPri
532
532
  //#endregion
533
533
  //#region ../../src/queue/core/index.d.ts
534
534
  /**
535
- * | type | quality | stability |
536
- * |------|---------|-----------|
537
- * | backend | epic | stable |
535
+ * | Stability | Since | Runtime |
536
+ * |-----------|-------|---------|
537
+ * | 3 - stable | 0.6.0 | node, bun|
538
538
  *
539
539
  * Asynchronous message processing with automatic worker management.
540
540
  *
@@ -361,9 +361,9 @@ $queue[KIND] = QueuePrimitive;
361
361
  //#endregion
362
362
  //#region ../../src/queue/core/index.ts
363
363
  /**
364
- * | type | quality | stability |
365
- * |------|---------|-----------|
366
- * | backend | epic | stable |
364
+ * | Stability | Since | Runtime |
365
+ * |-----------|-------|---------|
366
+ * | 3 - stable | 0.6.0 | node, bun|
367
367
  *
368
368
  * Asynchronous message processing with automatic worker management.
369
369
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/queue/core/primitives/$consumer.ts","../../../src/queue/core/providers/MemoryQueueProvider.ts","../../../src/queue/core/providers/QueueProvider.ts","../../../src/queue/core/providers/WorkerProvider.ts","../../../src/queue/core/primitives/$queue.ts","../../../src/queue/core/index.ts"],"sourcesContent":["import {\n createPrimitive,\n KIND,\n Primitive,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { QueuePrimitive } from \"./$queue.ts\";\n\n/**\n * Creates a consumer primitive to process messages from a specific queue.\n *\n * Provides a dedicated message consumer that connects to a queue and processes messages\n * with custom handler logic, enabling scalable architectures where multiple consumers\n * can process messages from the same queue.\n *\n * **Key Features**\n * - Seamless integration with any $queue primitive\n * - Full type safety inherited from queue schema\n * - Automatic worker management for background processing\n * - Built-in error handling and retry mechanisms\n * - Support for multiple consumers per queue for horizontal scaling\n *\n * **Common Use Cases**\n * - Email sending and notification services\n * - Image and media processing workers\n * - Data synchronization and background jobs\n *\n * @example\n * ```ts\n * class EmailService {\n * emailQueue = $queue({\n * name: \"emails\",\n * schema: t.object({\n * to: t.text(),\n * subject: t.text(),\n * body: t.text()\n * })\n * });\n *\n * emailConsumer = $consumer({\n * queue: this.emailQueue,\n * handler: async (message) => {\n * const { to, subject, body } = message.payload;\n * await this.sendEmail(to, subject, body);\n * }\n * });\n *\n * async sendWelcomeEmail(userEmail: string) {\n * await this.emailQueue.push({\n * to: userEmail,\n * subject: \"Welcome!\",\n * body: \"Thanks for joining.\"\n * });\n * }\n * }\n * ```\n */\nexport const $consumer = <T extends TSchema>(\n options: ConsumerPrimitiveOptions<T>,\n): ConsumerPrimitive<T> => {\n return createPrimitive(ConsumerPrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface ConsumerPrimitiveOptions<T extends TSchema> {\n /**\n * The queue primitive that this consumer will process messages from.\n *\n * This establishes the connection between the consumer and its source queue:\n * - The consumer inherits the queue's message schema for type safety\n * - Messages pushed to the queue will be automatically routed to this consumer\n * - Multiple consumers can be attached to the same queue for parallel processing\n * - The consumer will use the queue's provider and configuration settings\n *\n * **Queue Integration Benefits**:\n * - Type safety: Consumer handler gets fully typed message payloads\n * - Schema validation: Messages are validated before reaching the consumer\n * - Error handling: Failed messages can be retried or moved to dead letter queues\n * - Monitoring: Queue metrics include consumer processing statistics\n *\n * @example\n * ```ts\n * // First, define a queue\n * emailQueue = $queue({\n * name: \"emails\",\n * schema: t.object({ to: t.text(), subject: t.text() })\n * });\n *\n * // Then, create a consumer for that queue\n * emailConsumer = $consumer({\n * queue: this.emailQueue, // Reference the queue primitive\n * handler: async (message) => { } // process email\n * });\n * ```\n */\n queue: QueuePrimitive<T>;\n\n /**\n * Message handler function that processes individual messages from the queue.\n *\n * This function:\n * - Receives fully typed and validated message payloads from the connected queue\n * - Runs in the background worker system for non-blocking operation\n * - Should implement the core business logic for processing this message type\n * - Can throw errors to trigger the queue's retry mechanisms\n * - Has access to the full Alepha dependency injection container\n * - Should be idempotent to handle potential duplicate deliveries\n *\n * **Handler Design Guidelines**:\n * - Keep handlers focused on a single responsibility\n * - Use proper error handling and meaningful error messages\n * - Log important processing steps for debugging and monitoring\n * - Consider transaction boundaries for data consistency\n * - Make operations idempotent when possible\n * - Validate business rules within the handler logic\n *\n * **Error Handling Strategy**:\n * - Throw errors for temporary failures that should be retried\n * - Log and handle permanent failures gracefully\n * - Use specific error types to control retry behavior\n * - Consider implementing circuit breakers for external service calls\n *\n * @param message - The queue message containing the validated payload\n * @param message.payload - The typed message data based on the queue's schema\n * @returns Promise that resolves when processing is complete\n *\n * @example\n * ```ts\n * handler: async (message) => {\n * const { userId, action, data } = message.payload;\n *\n * try {\n * // Log processing start\n * this.logger.info(`Processing ${action} for user ${userId}`);\n *\n * // Validate business rules\n * if (!await this.userService.exists(userId)) {\n * throw new Error(`User ${userId} not found`);\n * }\n *\n * // Perform the main processing logic\n * switch (action) {\n * case \"create\":\n * await this.processCreation(userId, data);\n * break;\n * case \"update\":\n * await this.processUpdate(userId, data);\n * break;\n * default:\n * throw new Error(`Unknown action: ${action}`);\n * }\n *\n * // Log successful completion\n * this.logger.info(`Successfully processed ${action} for user ${userId}`);\n *\n * } catch (error) {\n * // Log error with context\n * this.logger.error(`Failed to process ${action} for user ${userId}`, {\n * error: error.message,\n * userId,\n * action,\n * data\n * });\n *\n * // Re-throw to trigger queue retry mechanism\n * throw error;\n * }\n * }\n * ```\n */\n handler: (message: { payload: Static<T> }) => Promise<void>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ConsumerPrimitive<T extends TSchema> extends Primitive<\n ConsumerPrimitiveOptions<T>\n> {}\n\n$consumer[KIND] = ConsumerPrimitive;\n","import { $logger } from \"alepha/logger\";\nimport type { QueueProvider } from \"./QueueProvider.ts\";\n\nexport class MemoryQueueProvider implements QueueProvider {\n protected readonly log = $logger();\n protected queueList: Record<string, string[]> = {};\n\n public async push(queue: string, ...messages: string[]): Promise<void> {\n if (this.queueList[queue] == null) {\n this.queueList[queue] = [];\n }\n\n this.queueList[queue].push(...messages);\n }\n\n public async pop(queue: string): Promise<string | undefined> {\n return this.queueList[queue]?.shift();\n }\n}\n","/**\n * Minimalist Queue interface.\n *\n * Will be probably enhanced in the future to support more advanced features. But for now, it's enough!\n */\nexport abstract class QueueProvider {\n /**\n * Push a message to the queue.\n *\n * @param queue Name of the queue to push the message to.\n * @param message String message to be pushed to the queue. Buffer messages are not supported for now.\n */\n public abstract push(queue: string, message: string): Promise<void>;\n\n /**\n * Pop a message from the queue.\n *\n * @param queue Name of the queue to pop the message from.\n *\n * @returns The message popped or `undefined` if the queue is empty.\n */\n public abstract pop(queue: string): Promise<string | undefined>;\n}\n","import {\n $env,\n $hook,\n $inject,\n Alepha,\n type Static,\n type TSchema,\n t,\n} from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $consumer } from \"../primitives/$consumer.ts\";\nimport {\n $queue,\n type QueueMessage,\n type QueuePrimitive,\n} from \"../primitives/$queue.ts\";\nimport { QueueProvider } from \"./QueueProvider.ts\";\n\nconst envSchema = t.object({\n /**\n * The interval in milliseconds to wait before checking for new messages.\n */\n QUEUE_WORKER_INTERVAL: t.integer({\n default: 1000,\n }),\n /**\n * The maximum interval in milliseconds to wait before checking for new messages.\n */\n QUEUE_WORKER_MAX_INTERVAL: t.integer({\n default: 32000,\n }),\n /**\n * The number of workers to run concurrently. Defaults to 1.\n * Useful only if you are doing a lot of I/O.\n */\n QUEUE_WORKER_CONCURRENCY: t.integer({\n default: 1,\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\nexport class WorkerProvider {\n protected readonly log = $logger();\n protected readonly env = $env(envSchema);\n protected readonly alepha = $inject(Alepha);\n protected readonly queueProvider = $inject(QueueProvider);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n\n protected workerPromises: Array<Promise<void>> = [];\n protected workersRunning = 0;\n protected abortController = new AbortController();\n protected workerIntervals: Record<number, number> = {};\n protected consumers: Array<Consumer> = [];\n\n public get isRunning(): boolean {\n return this.workersRunning > 0;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n priority: \"last\",\n handler: () => {\n for (const queue of this.alepha.primitives($queue)) {\n const handler = queue.options.handler;\n if (handler) {\n this.consumers.push({\n handler,\n queue,\n });\n }\n }\n\n for (const consumer of this.alepha.primitives($consumer)) {\n this.consumers.push(consumer.options);\n }\n\n if (this.consumers.length > 0) {\n this.startWorkers();\n this.log.debug(\n `Watching for ${this.consumers.length} queue${this.consumers.length > 1 ? \"s\" : \"\"} with ${this.env.QUEUE_WORKER_CONCURRENCY} worker${\n this.env.QUEUE_WORKER_CONCURRENCY > 1 ? \"s\" : \"\"\n }.`,\n );\n }\n },\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Engine part - this is the part that will run the workers and process the messages\n\n /**\n * Start the workers.\n * This method will create an endless loop that will check for new messages!\n */\n protected startWorkers(): void {\n const workerToStart =\n this.env.QUEUE_WORKER_CONCURRENCY - this.workersRunning;\n\n for (let i = 0; i < workerToStart; i++) {\n this.workersRunning += 1;\n this.log.debug(`Starting worker n-${i}`);\n\n const workerLoop = async () => {\n while (this.workersRunning > 0) {\n this.log.trace(`Worker n-${i} is checking for new messages`);\n const next = await this.getNextMessage();\n if (next) {\n this.workerIntervals[i] = 0;\n await this.processMessage(next);\n } else {\n await this.waitForNextMessage(i);\n }\n }\n this.log.info(`Worker n-${i} has stopped`);\n // Only decrement if we're not already at 0 (shutdown case)\n if (this.workersRunning > 0) {\n this.workersRunning -= 1;\n }\n };\n\n this.workerPromises.push(\n workerLoop().catch((e) => {\n this.log.error(`Worker n-${i} has crashed`, e);\n // Always decrement on crash, regardless of shutdown state\n this.workersRunning -= 1;\n }),\n );\n }\n }\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: async () => {\n if (this.consumers.length > 0) {\n await this.stopWorkers();\n }\n },\n });\n\n /**\n * Wait for the next message, where `n` is the worker number.\n *\n * This method will wait for a certain amount of time, increasing the wait time again if no message is found.\n */\n protected async waitForNextMessage(n: number): Promise<void> {\n const intervals = this.workerIntervals;\n const milliseconds = intervals[n] || this.env.QUEUE_WORKER_INTERVAL;\n\n this.log.trace(`Worker n-${n} is waiting for ${milliseconds}ms.`);\n\n if (this.abortController.signal.aborted) {\n this.log.warn(`Worker n-${n} aborted.`);\n return;\n }\n\n await this.dateTimeProvider.wait(milliseconds, {\n signal: this.abortController.signal,\n });\n\n if (intervals[n]) {\n if (intervals[n] < this.env.QUEUE_WORKER_MAX_INTERVAL) {\n intervals[n] = intervals[n] * 2;\n }\n } else {\n intervals[n] = milliseconds;\n }\n }\n\n /**\n * Get the next message.\n */\n protected async getNextMessage(): Promise<undefined | NextMessage> {\n for (const consumer of this.consumers) {\n const provider = consumer.queue.provider;\n const message = await provider.pop(consumer.queue.name);\n if (message) {\n return { message, consumer };\n }\n }\n }\n\n /**\n * Process a message from a queue.\n */\n protected async processMessage(response: {\n message: any;\n consumer: Consumer;\n }) {\n const { message, consumer } = response;\n\n try {\n const json = JSON.parse(message);\n const payload = this.alepha.codec.decode(\n consumer.queue.options.schema,\n json.payload,\n );\n await this.alepha.context.run(() => consumer.handler({ payload }));\n } catch (e) {\n this.log.error(\"Failed to process message\", e);\n }\n }\n\n /**\n * Stop the workers.\n *\n * This method will stop the workers and wait for them to finish processing.\n */\n protected async stopWorkers() {\n this.workersRunning = 0;\n\n this.log.trace(\"Stopping workers...\");\n this.abortController.abort();\n\n this.log.trace(\"Waiting for workers to finish...\");\n await Promise.all(this.workerPromises);\n }\n\n /**\n * Force the workers to get back to work.\n */\n public wakeUp(): void {\n this.log.debug(\"Waking up workers...\");\n this.abortController.abort();\n this.abortController = new AbortController();\n\n // if no workers are running, start them, (should not happen, but just in case)\n this.startWorkers();\n }\n}\n\nexport interface Consumer<T extends TSchema = TSchema> {\n queue: QueuePrimitive<T>;\n handler: (message: QueueMessage<T>) => Promise<void>;\n}\n\nexport interface NextMessage {\n consumer: Consumer;\n message: string;\n}\n","import {\n $inject,\n createPrimitive,\n KIND,\n Primitive,\n type Service,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { MemoryQueueProvider } from \"../providers/MemoryQueueProvider.ts\";\nimport { QueueProvider } from \"../providers/QueueProvider.ts\";\nimport { WorkerProvider } from \"../providers/WorkerProvider.ts\";\n\n/**\n * Creates a queue primitive for asynchronous message processing with background workers.\n *\n * The $queue primitive enables powerful asynchronous communication patterns in your application.\n * It provides type-safe message queuing with automatic worker processing, making it perfect for\n * decoupling components and handling background tasks efficiently.\n *\n * **Background Processing**\n * - Automatic worker threads for non-blocking message processing\n * - Built-in retry mechanisms and error handling\n * - Dead letter queues for failed message handling\n * - Graceful shutdown and worker lifecycle management\n *\n * **Type Safety**\n * - Full TypeScript support with schema validation using TypeBox\n * - Type-safe message payloads with automatic inference\n * - Runtime validation of all queued messages\n * - Compile-time errors for invalid message structures\n *\n * **Storage Flexibility**\n * - Memory provider for development and testing\n * - Redis provider for production scalability and persistence\n * - Custom provider support for specialized backends\n * - Automatic failover and connection pooling\n *\n * **Performance & Scalability**\n * - Batch processing support for high-throughput scenarios\n * - Horizontal scaling with distributed queue backends\n * - Configurable concurrency and worker pools\n * - Efficient serialization and message routing\n *\n * **Reliability**\n * - Message persistence across application restarts\n * - Automatic retry with exponential backoff\n * - Dead letter handling for permanently failed messages\n * - Comprehensive logging and monitoring integration\n *\n * @example Basic notification queue\n * ```typescript\n * const emailQueue = $queue({\n * name: \"email-notifications\",\n * schema: t.object({\n * to: t.text(),\n * subject: t.text(),\n * body: t.text(),\n * priority: t.optional(t.enum([\"high\", \"normal\"]))\n * }),\n * handler: async (message) => {\n * await emailService.send(message.payload);\n * console.log(`Email sent to ${message.payload.to}`);\n * }\n * });\n *\n * // Push messages for background processing\n * await emailQueue.push({\n * to: \"user@example.com\",\n * subject: \"Welcome!\",\n * body: \"Welcome to our platform\",\n * priority: \"high\"\n * });\n * ```\n *\n * @example Batch processing with Redis\n * ```typescript\n * const imageQueue = $queue({\n * name: \"image-processing\",\n * provider: RedisQueueProvider,\n * schema: t.object({\n * imageId: t.text(),\n * operations: t.array(t.enum([\"resize\", \"compress\", \"thumbnail\"]))\n * }),\n * handler: async (message) => {\n * for (const op of message.payload.operations) {\n * await processImage(message.payload.imageId, op);\n * }\n * }\n * });\n *\n * // Batch processing multiple images\n * await imageQueue.push(\n * { imageId: \"img1\", operations: [\"resize\", \"thumbnail\"] },\n * { imageId: \"img2\", operations: [\"compress\"] },\n * { imageId: \"img3\", operations: [\"resize\", \"compress\", \"thumbnail\"] }\n * );\n * ```\n *\n * @example Development with memory provider\n * ```typescript\n * const taskQueue = $queue({\n * name: \"dev-tasks\",\n * provider: \"memory\",\n * schema: t.object({\n * taskType: t.enum([\"cleanup\", \"backup\", \"report\"]),\n * data: t.record(t.text(), t.any())\n * }),\n * handler: async (message) => {\n * switch (message.payload.taskType) {\n * case \"cleanup\":\n * await performCleanup(message.payload.data);\n * break;\n * case \"backup\":\n * await createBackup(message.payload.data);\n * break;\n * case \"report\":\n * await generateReport(message.payload.data);\n * break;\n * }\n * }\n * });\n * ```\n */\nexport const $queue = <T extends TSchema>(\n options: QueuePrimitiveOptions<T>,\n): QueuePrimitive<T> => {\n return createPrimitive(QueuePrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface QueuePrimitiveOptions<T extends TSchema> {\n /**\n * Unique name for the queue.\n *\n * This name is used for:\n * - Queue identification across the system\n * - Storage backend key generation\n * - Logging and monitoring\n * - Worker assignment and routing\n *\n * If not provided, defaults to the property key where the queue is declared.\n *\n * @example \"email-notifications\"\n * @example \"image-processing\"\n * @example \"order-fulfillment\"\n */\n name?: string;\n\n /**\n * Human-readable description of the queue's purpose.\n *\n * Used for:\n * - Documentation generation\n * - Monitoring dashboards\n * - Development team communication\n * - Queue management interfaces\n *\n * @example \"Process user registration emails and welcome sequences\"\n * @example \"Handle image uploads, resizing, and thumbnail generation\"\n * @example \"Manage order processing, payment, and shipping workflows\"\n */\n description?: string;\n\n /**\n * Queue storage provider configuration.\n *\n * Options:\n * - **\"memory\"**: In-memory queue (default for development, lost on restart)\n * - **Service<QueueProvider>**: Custom provider class (e.g., RedisQueueProvider)\n * - **undefined**: Uses the default queue provider from dependency injection\n *\n * **Provider Selection Guidelines**:\n * - Development: Use \"memory\" for fast, simple testing\n * - Production: Use Redis or database-backed providers for persistence\n * - High-throughput: Use specialized providers with connection pooling\n * - Distributed systems: Use Redis or message brokers for scalability\n *\n * @default Uses injected QueueProvider\n * @example \"memory\"\n * @example RedisQueueProvider\n * @example DatabaseQueueProvider\n */\n provider?: \"memory\" | Service<QueueProvider>;\n\n /**\n * TypeBox schema defining the structure of messages in this queue.\n *\n * This schema:\n * - Validates all messages pushed to the queue\n * - Provides full TypeScript type inference\n * - Ensures type safety between producers and consumers\n * - Enables automatic serialization/deserialization\n *\n * **Schema Design Best Practices**:\n * - Keep schemas simple and focused on the specific task\n * - Use optional fields for data that might not always be available\n * - Include version fields for schema evolution\n * - Use union types for different message types in the same queue\n *\n * @example\n * ```ts\n * t.object({\n * userId: t.text(),\n * action: t.enum([\"create\", \"update\"]),\n * data: t.record(t.text(), t.any()),\n * timestamp: t.optional(t.number())\n * })\n * ```\n */\n schema: T;\n\n /**\n * Message handler function that processes queue messages.\n *\n * This function:\n * - Runs in background worker threads for non-blocking processing\n * - Receives type-safe message payloads based on the schema\n * - Should be idempotent to handle potential retries\n * - Can throw errors to trigger retry mechanisms\n * - Has access to the full Alepha dependency injection container\n *\n * **Handler Best Practices**:\n * - Keep handlers focused on a single responsibility\n * - Use proper error handling and logging\n * - Make operations idempotent when possible\n * - Validate critical business logic within handlers\n * - Consider using transactions for data consistency\n *\n * @param message - The queue message with validated payload\n * @returns Promise that resolves when processing is complete\n *\n * @example\n * ```ts\n * handler: async (message) => {\n * const { userId, email, template } = message.payload;\n *\n * try {\n * await this.emailService.send({\n * to: email,\n * template,\n * data: { userId }\n * });\n *\n * await this.userService.markEmailSent(userId, template);\n * } catch (error) {\n * // Log error and let the queue system handle retries\n * this.logger.error(`Failed to send email to ${email}`, error);\n * throw error;\n * }\n * }\n * ```\n */\n handler?: (message: QueueMessage<T>) => Promise<void>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class QueuePrimitive<T extends TSchema> extends Primitive<\n QueuePrimitiveOptions<T>\n> {\n protected readonly log = $logger();\n protected readonly workerProvider = $inject(WorkerProvider);\n public readonly provider = this.$provider();\n\n public async push(...payloads: Array<Static<T>>) {\n await Promise.all(\n payloads.map((payload) =>\n this.provider.push(\n this.name,\n JSON.stringify({\n headers: {},\n payload: this.alepha.codec.decode(this.options.schema, payload),\n }),\n ),\n ),\n );\n\n this.log.debug(`Pushed to queue ${this.name}`, payloads);\n this.workerProvider.wakeUp();\n }\n\n public get name() {\n return this.options.name || this.config.propertyKey;\n }\n\n protected $provider() {\n if (!this.options.provider) {\n return this.alepha.inject(QueueProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryQueueProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n$queue[KIND] = QueuePrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface QueueMessageSchema {\n payload: TSchema;\n}\n\nexport interface QueueMessage<T extends TSchema> {\n payload: Static<T>;\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { $consumer } from \"./primitives/$consumer.ts\";\nimport { $queue } from \"./primitives/$queue.ts\";\nimport { MemoryQueueProvider } from \"./providers/MemoryQueueProvider.ts\";\nimport { QueueProvider } from \"./providers/QueueProvider.ts\";\nimport { WorkerProvider } from \"./providers/WorkerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$consumer.ts\";\nexport * from \"./primitives/$queue.ts\";\nexport * from \"./providers/MemoryQueueProvider.ts\";\nexport * from \"./providers/QueueProvider.ts\";\nexport * from \"./providers/WorkerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * | type | quality | stability |\n * |------|---------|-----------|\n * | backend | epic | stable |\n *\n * Asynchronous message processing with automatic worker management.\n *\n * **Features:**\n * - Background job queues with type-safe payloads\n * - Queue consumer handlers\n * - Automatic worker threads for non-blocking processing\n * - Retry mechanisms with exponential backoff\n * - Dead letter queues for failed messages\n * - Batch processing support\n * - Configurable concurrency and worker pools\n * - Providers: Memory (dev), Redis (production)\n *\n * @module alepha.queue\n */\nexport const AlephaQueue = $module({\n name: \"alepha.queue\",\n primitives: [$queue, $consumer],\n services: [QueueProvider, MemoryQueueProvider, WorkerProvider],\n register: (alepha: Alepha) =>\n alepha\n .with({\n optional: true,\n provide: QueueProvider,\n use: MemoryQueueProvider,\n })\n .with(WorkerProvider),\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/queue/core/primitives/$consumer.ts","../../../src/queue/core/providers/MemoryQueueProvider.ts","../../../src/queue/core/providers/QueueProvider.ts","../../../src/queue/core/providers/WorkerProvider.ts","../../../src/queue/core/primitives/$queue.ts","../../../src/queue/core/index.ts"],"sourcesContent":["import {\n createPrimitive,\n KIND,\n Primitive,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { QueuePrimitive } from \"./$queue.ts\";\n\n/**\n * Creates a consumer primitive to process messages from a specific queue.\n *\n * Provides a dedicated message consumer that connects to a queue and processes messages\n * with custom handler logic, enabling scalable architectures where multiple consumers\n * can process messages from the same queue.\n *\n * **Key Features**\n * - Seamless integration with any $queue primitive\n * - Full type safety inherited from queue schema\n * - Automatic worker management for background processing\n * - Built-in error handling and retry mechanisms\n * - Support for multiple consumers per queue for horizontal scaling\n *\n * **Common Use Cases**\n * - Email sending and notification services\n * - Image and media processing workers\n * - Data synchronization and background jobs\n *\n * @example\n * ```ts\n * class EmailService {\n * emailQueue = $queue({\n * name: \"emails\",\n * schema: t.object({\n * to: t.text(),\n * subject: t.text(),\n * body: t.text()\n * })\n * });\n *\n * emailConsumer = $consumer({\n * queue: this.emailQueue,\n * handler: async (message) => {\n * const { to, subject, body } = message.payload;\n * await this.sendEmail(to, subject, body);\n * }\n * });\n *\n * async sendWelcomeEmail(userEmail: string) {\n * await this.emailQueue.push({\n * to: userEmail,\n * subject: \"Welcome!\",\n * body: \"Thanks for joining.\"\n * });\n * }\n * }\n * ```\n */\nexport const $consumer = <T extends TSchema>(\n options: ConsumerPrimitiveOptions<T>,\n): ConsumerPrimitive<T> => {\n return createPrimitive(ConsumerPrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface ConsumerPrimitiveOptions<T extends TSchema> {\n /**\n * The queue primitive that this consumer will process messages from.\n *\n * This establishes the connection between the consumer and its source queue:\n * - The consumer inherits the queue's message schema for type safety\n * - Messages pushed to the queue will be automatically routed to this consumer\n * - Multiple consumers can be attached to the same queue for parallel processing\n * - The consumer will use the queue's provider and configuration settings\n *\n * **Queue Integration Benefits**:\n * - Type safety: Consumer handler gets fully typed message payloads\n * - Schema validation: Messages are validated before reaching the consumer\n * - Error handling: Failed messages can be retried or moved to dead letter queues\n * - Monitoring: Queue metrics include consumer processing statistics\n *\n * @example\n * ```ts\n * // First, define a queue\n * emailQueue = $queue({\n * name: \"emails\",\n * schema: t.object({ to: t.text(), subject: t.text() })\n * });\n *\n * // Then, create a consumer for that queue\n * emailConsumer = $consumer({\n * queue: this.emailQueue, // Reference the queue primitive\n * handler: async (message) => { } // process email\n * });\n * ```\n */\n queue: QueuePrimitive<T>;\n\n /**\n * Message handler function that processes individual messages from the queue.\n *\n * This function:\n * - Receives fully typed and validated message payloads from the connected queue\n * - Runs in the background worker system for non-blocking operation\n * - Should implement the core business logic for processing this message type\n * - Can throw errors to trigger the queue's retry mechanisms\n * - Has access to the full Alepha dependency injection container\n * - Should be idempotent to handle potential duplicate deliveries\n *\n * **Handler Design Guidelines**:\n * - Keep handlers focused on a single responsibility\n * - Use proper error handling and meaningful error messages\n * - Log important processing steps for debugging and monitoring\n * - Consider transaction boundaries for data consistency\n * - Make operations idempotent when possible\n * - Validate business rules within the handler logic\n *\n * **Error Handling Strategy**:\n * - Throw errors for temporary failures that should be retried\n * - Log and handle permanent failures gracefully\n * - Use specific error types to control retry behavior\n * - Consider implementing circuit breakers for external service calls\n *\n * @param message - The queue message containing the validated payload\n * @param message.payload - The typed message data based on the queue's schema\n * @returns Promise that resolves when processing is complete\n *\n * @example\n * ```ts\n * handler: async (message) => {\n * const { userId, action, data } = message.payload;\n *\n * try {\n * // Log processing start\n * this.logger.info(`Processing ${action} for user ${userId}`);\n *\n * // Validate business rules\n * if (!await this.userService.exists(userId)) {\n * throw new Error(`User ${userId} not found`);\n * }\n *\n * // Perform the main processing logic\n * switch (action) {\n * case \"create\":\n * await this.processCreation(userId, data);\n * break;\n * case \"update\":\n * await this.processUpdate(userId, data);\n * break;\n * default:\n * throw new Error(`Unknown action: ${action}`);\n * }\n *\n * // Log successful completion\n * this.logger.info(`Successfully processed ${action} for user ${userId}`);\n *\n * } catch (error) {\n * // Log error with context\n * this.logger.error(`Failed to process ${action} for user ${userId}`, {\n * error: error.message,\n * userId,\n * action,\n * data\n * });\n *\n * // Re-throw to trigger queue retry mechanism\n * throw error;\n * }\n * }\n * ```\n */\n handler: (message: { payload: Static<T> }) => Promise<void>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ConsumerPrimitive<T extends TSchema> extends Primitive<\n ConsumerPrimitiveOptions<T>\n> {}\n\n$consumer[KIND] = ConsumerPrimitive;\n","import { $logger } from \"alepha/logger\";\nimport type { QueueProvider } from \"./QueueProvider.ts\";\n\nexport class MemoryQueueProvider implements QueueProvider {\n protected readonly log = $logger();\n protected queueList: Record<string, string[]> = {};\n\n public async push(queue: string, ...messages: string[]): Promise<void> {\n if (this.queueList[queue] == null) {\n this.queueList[queue] = [];\n }\n\n this.queueList[queue].push(...messages);\n }\n\n public async pop(queue: string): Promise<string | undefined> {\n return this.queueList[queue]?.shift();\n }\n}\n","/**\n * Minimalist Queue interface.\n *\n * Will be probably enhanced in the future to support more advanced features. But for now, it's enough!\n */\nexport abstract class QueueProvider {\n /**\n * Push a message to the queue.\n *\n * @param queue Name of the queue to push the message to.\n * @param message String message to be pushed to the queue. Buffer messages are not supported for now.\n */\n public abstract push(queue: string, message: string): Promise<void>;\n\n /**\n * Pop a message from the queue.\n *\n * @param queue Name of the queue to pop the message from.\n *\n * @returns The message popped or `undefined` if the queue is empty.\n */\n public abstract pop(queue: string): Promise<string | undefined>;\n}\n","import {\n $env,\n $hook,\n $inject,\n Alepha,\n type Static,\n type TSchema,\n t,\n} from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $consumer } from \"../primitives/$consumer.ts\";\nimport {\n $queue,\n type QueueMessage,\n type QueuePrimitive,\n} from \"../primitives/$queue.ts\";\nimport { QueueProvider } from \"./QueueProvider.ts\";\n\nconst envSchema = t.object({\n /**\n * The interval in milliseconds to wait before checking for new messages.\n */\n QUEUE_WORKER_INTERVAL: t.integer({\n default: 1000,\n }),\n /**\n * The maximum interval in milliseconds to wait before checking for new messages.\n */\n QUEUE_WORKER_MAX_INTERVAL: t.integer({\n default: 32000,\n }),\n /**\n * The number of workers to run concurrently. Defaults to 1.\n * Useful only if you are doing a lot of I/O.\n */\n QUEUE_WORKER_CONCURRENCY: t.integer({\n default: 1,\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\nexport class WorkerProvider {\n protected readonly log = $logger();\n protected readonly env = $env(envSchema);\n protected readonly alepha = $inject(Alepha);\n protected readonly queueProvider = $inject(QueueProvider);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n\n protected workerPromises: Array<Promise<void>> = [];\n protected workersRunning = 0;\n protected abortController = new AbortController();\n protected workerIntervals: Record<number, number> = {};\n protected consumers: Array<Consumer> = [];\n\n public get isRunning(): boolean {\n return this.workersRunning > 0;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n priority: \"last\",\n handler: () => {\n for (const queue of this.alepha.primitives($queue)) {\n const handler = queue.options.handler;\n if (handler) {\n this.consumers.push({\n handler,\n queue,\n });\n }\n }\n\n for (const consumer of this.alepha.primitives($consumer)) {\n this.consumers.push(consumer.options);\n }\n\n if (this.consumers.length > 0) {\n this.startWorkers();\n this.log.debug(\n `Watching for ${this.consumers.length} queue${this.consumers.length > 1 ? \"s\" : \"\"} with ${this.env.QUEUE_WORKER_CONCURRENCY} worker${\n this.env.QUEUE_WORKER_CONCURRENCY > 1 ? \"s\" : \"\"\n }.`,\n );\n }\n },\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Engine part - this is the part that will run the workers and process the messages\n\n /**\n * Start the workers.\n * This method will create an endless loop that will check for new messages!\n */\n protected startWorkers(): void {\n const workerToStart =\n this.env.QUEUE_WORKER_CONCURRENCY - this.workersRunning;\n\n for (let i = 0; i < workerToStart; i++) {\n this.workersRunning += 1;\n this.log.debug(`Starting worker n-${i}`);\n\n const workerLoop = async () => {\n while (this.workersRunning > 0) {\n this.log.trace(`Worker n-${i} is checking for new messages`);\n const next = await this.getNextMessage();\n if (next) {\n this.workerIntervals[i] = 0;\n await this.processMessage(next);\n } else {\n await this.waitForNextMessage(i);\n }\n }\n this.log.info(`Worker n-${i} has stopped`);\n // Only decrement if we're not already at 0 (shutdown case)\n if (this.workersRunning > 0) {\n this.workersRunning -= 1;\n }\n };\n\n this.workerPromises.push(\n workerLoop().catch((e) => {\n this.log.error(`Worker n-${i} has crashed`, e);\n // Always decrement on crash, regardless of shutdown state\n this.workersRunning -= 1;\n }),\n );\n }\n }\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: async () => {\n if (this.consumers.length > 0) {\n await this.stopWorkers();\n }\n },\n });\n\n /**\n * Wait for the next message, where `n` is the worker number.\n *\n * This method will wait for a certain amount of time, increasing the wait time again if no message is found.\n */\n protected async waitForNextMessage(n: number): Promise<void> {\n const intervals = this.workerIntervals;\n const milliseconds = intervals[n] || this.env.QUEUE_WORKER_INTERVAL;\n\n this.log.trace(`Worker n-${n} is waiting for ${milliseconds}ms.`);\n\n if (this.abortController.signal.aborted) {\n this.log.warn(`Worker n-${n} aborted.`);\n return;\n }\n\n await this.dateTimeProvider.wait(milliseconds, {\n signal: this.abortController.signal,\n });\n\n if (intervals[n]) {\n if (intervals[n] < this.env.QUEUE_WORKER_MAX_INTERVAL) {\n intervals[n] = intervals[n] * 2;\n }\n } else {\n intervals[n] = milliseconds;\n }\n }\n\n /**\n * Get the next message.\n */\n protected async getNextMessage(): Promise<undefined | NextMessage> {\n for (const consumer of this.consumers) {\n const provider = consumer.queue.provider;\n const message = await provider.pop(consumer.queue.name);\n if (message) {\n return { message, consumer };\n }\n }\n }\n\n /**\n * Process a message from a queue.\n */\n protected async processMessage(response: {\n message: any;\n consumer: Consumer;\n }) {\n const { message, consumer } = response;\n\n try {\n const json = JSON.parse(message);\n const payload = this.alepha.codec.decode(\n consumer.queue.options.schema,\n json.payload,\n );\n await this.alepha.context.run(() => consumer.handler({ payload }));\n } catch (e) {\n this.log.error(\"Failed to process message\", e);\n }\n }\n\n /**\n * Stop the workers.\n *\n * This method will stop the workers and wait for them to finish processing.\n */\n protected async stopWorkers() {\n this.workersRunning = 0;\n\n this.log.trace(\"Stopping workers...\");\n this.abortController.abort();\n\n this.log.trace(\"Waiting for workers to finish...\");\n await Promise.all(this.workerPromises);\n }\n\n /**\n * Force the workers to get back to work.\n */\n public wakeUp(): void {\n this.log.debug(\"Waking up workers...\");\n this.abortController.abort();\n this.abortController = new AbortController();\n\n // if no workers are running, start them, (should not happen, but just in case)\n this.startWorkers();\n }\n}\n\nexport interface Consumer<T extends TSchema = TSchema> {\n queue: QueuePrimitive<T>;\n handler: (message: QueueMessage<T>) => Promise<void>;\n}\n\nexport interface NextMessage {\n consumer: Consumer;\n message: string;\n}\n","import {\n $inject,\n createPrimitive,\n KIND,\n Primitive,\n type Service,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { MemoryQueueProvider } from \"../providers/MemoryQueueProvider.ts\";\nimport { QueueProvider } from \"../providers/QueueProvider.ts\";\nimport { WorkerProvider } from \"../providers/WorkerProvider.ts\";\n\n/**\n * Creates a queue primitive for asynchronous message processing with background workers.\n *\n * The $queue primitive enables powerful asynchronous communication patterns in your application.\n * It provides type-safe message queuing with automatic worker processing, making it perfect for\n * decoupling components and handling background tasks efficiently.\n *\n * **Background Processing**\n * - Automatic worker threads for non-blocking message processing\n * - Built-in retry mechanisms and error handling\n * - Dead letter queues for failed message handling\n * - Graceful shutdown and worker lifecycle management\n *\n * **Type Safety**\n * - Full TypeScript support with schema validation using TypeBox\n * - Type-safe message payloads with automatic inference\n * - Runtime validation of all queued messages\n * - Compile-time errors for invalid message structures\n *\n * **Storage Flexibility**\n * - Memory provider for development and testing\n * - Redis provider for production scalability and persistence\n * - Custom provider support for specialized backends\n * - Automatic failover and connection pooling\n *\n * **Performance & Scalability**\n * - Batch processing support for high-throughput scenarios\n * - Horizontal scaling with distributed queue backends\n * - Configurable concurrency and worker pools\n * - Efficient serialization and message routing\n *\n * **Reliability**\n * - Message persistence across application restarts\n * - Automatic retry with exponential backoff\n * - Dead letter handling for permanently failed messages\n * - Comprehensive logging and monitoring integration\n *\n * @example Basic notification queue\n * ```typescript\n * const emailQueue = $queue({\n * name: \"email-notifications\",\n * schema: t.object({\n * to: t.text(),\n * subject: t.text(),\n * body: t.text(),\n * priority: t.optional(t.enum([\"high\", \"normal\"]))\n * }),\n * handler: async (message) => {\n * await emailService.send(message.payload);\n * console.log(`Email sent to ${message.payload.to}`);\n * }\n * });\n *\n * // Push messages for background processing\n * await emailQueue.push({\n * to: \"user@example.com\",\n * subject: \"Welcome!\",\n * body: \"Welcome to our platform\",\n * priority: \"high\"\n * });\n * ```\n *\n * @example Batch processing with Redis\n * ```typescript\n * const imageQueue = $queue({\n * name: \"image-processing\",\n * provider: RedisQueueProvider,\n * schema: t.object({\n * imageId: t.text(),\n * operations: t.array(t.enum([\"resize\", \"compress\", \"thumbnail\"]))\n * }),\n * handler: async (message) => {\n * for (const op of message.payload.operations) {\n * await processImage(message.payload.imageId, op);\n * }\n * }\n * });\n *\n * // Batch processing multiple images\n * await imageQueue.push(\n * { imageId: \"img1\", operations: [\"resize\", \"thumbnail\"] },\n * { imageId: \"img2\", operations: [\"compress\"] },\n * { imageId: \"img3\", operations: [\"resize\", \"compress\", \"thumbnail\"] }\n * );\n * ```\n *\n * @example Development with memory provider\n * ```typescript\n * const taskQueue = $queue({\n * name: \"dev-tasks\",\n * provider: \"memory\",\n * schema: t.object({\n * taskType: t.enum([\"cleanup\", \"backup\", \"report\"]),\n * data: t.record(t.text(), t.any())\n * }),\n * handler: async (message) => {\n * switch (message.payload.taskType) {\n * case \"cleanup\":\n * await performCleanup(message.payload.data);\n * break;\n * case \"backup\":\n * await createBackup(message.payload.data);\n * break;\n * case \"report\":\n * await generateReport(message.payload.data);\n * break;\n * }\n * }\n * });\n * ```\n */\nexport const $queue = <T extends TSchema>(\n options: QueuePrimitiveOptions<T>,\n): QueuePrimitive<T> => {\n return createPrimitive(QueuePrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface QueuePrimitiveOptions<T extends TSchema> {\n /**\n * Unique name for the queue.\n *\n * This name is used for:\n * - Queue identification across the system\n * - Storage backend key generation\n * - Logging and monitoring\n * - Worker assignment and routing\n *\n * If not provided, defaults to the property key where the queue is declared.\n *\n * @example \"email-notifications\"\n * @example \"image-processing\"\n * @example \"order-fulfillment\"\n */\n name?: string;\n\n /**\n * Human-readable description of the queue's purpose.\n *\n * Used for:\n * - Documentation generation\n * - Monitoring dashboards\n * - Development team communication\n * - Queue management interfaces\n *\n * @example \"Process user registration emails and welcome sequences\"\n * @example \"Handle image uploads, resizing, and thumbnail generation\"\n * @example \"Manage order processing, payment, and shipping workflows\"\n */\n description?: string;\n\n /**\n * Queue storage provider configuration.\n *\n * Options:\n * - **\"memory\"**: In-memory queue (default for development, lost on restart)\n * - **Service<QueueProvider>**: Custom provider class (e.g., RedisQueueProvider)\n * - **undefined**: Uses the default queue provider from dependency injection\n *\n * **Provider Selection Guidelines**:\n * - Development: Use \"memory\" for fast, simple testing\n * - Production: Use Redis or database-backed providers for persistence\n * - High-throughput: Use specialized providers with connection pooling\n * - Distributed systems: Use Redis or message brokers for scalability\n *\n * @default Uses injected QueueProvider\n * @example \"memory\"\n * @example RedisQueueProvider\n * @example DatabaseQueueProvider\n */\n provider?: \"memory\" | Service<QueueProvider>;\n\n /**\n * TypeBox schema defining the structure of messages in this queue.\n *\n * This schema:\n * - Validates all messages pushed to the queue\n * - Provides full TypeScript type inference\n * - Ensures type safety between producers and consumers\n * - Enables automatic serialization/deserialization\n *\n * **Schema Design Best Practices**:\n * - Keep schemas simple and focused on the specific task\n * - Use optional fields for data that might not always be available\n * - Include version fields for schema evolution\n * - Use union types for different message types in the same queue\n *\n * @example\n * ```ts\n * t.object({\n * userId: t.text(),\n * action: t.enum([\"create\", \"update\"]),\n * data: t.record(t.text(), t.any()),\n * timestamp: t.optional(t.number())\n * })\n * ```\n */\n schema: T;\n\n /**\n * Message handler function that processes queue messages.\n *\n * This function:\n * - Runs in background worker threads for non-blocking processing\n * - Receives type-safe message payloads based on the schema\n * - Should be idempotent to handle potential retries\n * - Can throw errors to trigger retry mechanisms\n * - Has access to the full Alepha dependency injection container\n *\n * **Handler Best Practices**:\n * - Keep handlers focused on a single responsibility\n * - Use proper error handling and logging\n * - Make operations idempotent when possible\n * - Validate critical business logic within handlers\n * - Consider using transactions for data consistency\n *\n * @param message - The queue message with validated payload\n * @returns Promise that resolves when processing is complete\n *\n * @example\n * ```ts\n * handler: async (message) => {\n * const { userId, email, template } = message.payload;\n *\n * try {\n * await this.emailService.send({\n * to: email,\n * template,\n * data: { userId }\n * });\n *\n * await this.userService.markEmailSent(userId, template);\n * } catch (error) {\n * // Log error and let the queue system handle retries\n * this.logger.error(`Failed to send email to ${email}`, error);\n * throw error;\n * }\n * }\n * ```\n */\n handler?: (message: QueueMessage<T>) => Promise<void>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class QueuePrimitive<T extends TSchema> extends Primitive<\n QueuePrimitiveOptions<T>\n> {\n protected readonly log = $logger();\n protected readonly workerProvider = $inject(WorkerProvider);\n public readonly provider = this.$provider();\n\n public async push(...payloads: Array<Static<T>>) {\n await Promise.all(\n payloads.map((payload) =>\n this.provider.push(\n this.name,\n JSON.stringify({\n headers: {},\n payload: this.alepha.codec.decode(this.options.schema, payload),\n }),\n ),\n ),\n );\n\n this.log.debug(`Pushed to queue ${this.name}`, payloads);\n this.workerProvider.wakeUp();\n }\n\n public get name() {\n return this.options.name || this.config.propertyKey;\n }\n\n protected $provider() {\n if (!this.options.provider) {\n return this.alepha.inject(QueueProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryQueueProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n$queue[KIND] = QueuePrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface QueueMessageSchema {\n payload: TSchema;\n}\n\nexport interface QueueMessage<T extends TSchema> {\n payload: Static<T>;\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { $consumer } from \"./primitives/$consumer.ts\";\nimport { $queue } from \"./primitives/$queue.ts\";\nimport { MemoryQueueProvider } from \"./providers/MemoryQueueProvider.ts\";\nimport { QueueProvider } from \"./providers/QueueProvider.ts\";\nimport { WorkerProvider } from \"./providers/WorkerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$consumer.ts\";\nexport * from \"./primitives/$queue.ts\";\nexport * from \"./providers/MemoryQueueProvider.ts\";\nexport * from \"./providers/QueueProvider.ts\";\nexport * from \"./providers/WorkerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * | Stability | Since | Runtime |\n * |-----------|-------|---------|\n * | 3 - stable | 0.6.0 | node, bun|\n *\n * Asynchronous message processing with automatic worker management.\n *\n * **Features:**\n * - Background job queues with type-safe payloads\n * - Queue consumer handlers\n * - Automatic worker threads for non-blocking processing\n * - Retry mechanisms with exponential backoff\n * - Dead letter queues for failed messages\n * - Batch processing support\n * - Configurable concurrency and worker pools\n * - Providers: Memory (dev), Redis (production)\n *\n * @module alepha.queue\n */\nexport const AlephaQueue = $module({\n name: \"alepha.queue\",\n primitives: [$queue, $consumer],\n services: [QueueProvider, MemoryQueueProvider, WorkerProvider],\n register: (alepha: Alepha) =>\n alepha\n .with({\n optional: true,\n provide: QueueProvider,\n use: MemoryQueueProvider,\n })\n .with(WorkerProvider),\n});\n"],"mappings":"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"}
@@ -73,7 +73,8 @@ var ReactAuth = class {
73
73
  throw new Redirection(`${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${options.redirect || "/"}`);
74
74
  }
75
75
  logout() {
76
- window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;
76
+ const cacheBuster = Date.now();
77
+ window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}&_=${cacheBuster}`;
77
78
  }
78
79
  };
79
80
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.js","names":[],"sources":["../../../src/react/auth/services/ReactAuth.ts","../../../src/react/auth/hooks/useAuth.ts","../../../src/react/auth/index.browser.ts"],"sourcesContent":["import { $hook, $inject, Alepha } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { ReactBrowserProvider, Redirection } from \"alepha/react/router\";\nimport type { UserAccountToken } from \"alepha/security\";\nimport { HttpClient } from \"alepha/server\";\nimport {\n alephaServerAuthRoutes,\n type Tokens,\n tokenResponseSchema,\n userinfoResponseSchema,\n} from \"alepha/server/auth\";\nimport { LinkProvider } from \"alepha/server/links\";\n\n/**\n * Browser, SSR friendly, service to handle authentication.\n */\nexport class ReactAuth {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly httpClient = $inject(HttpClient);\n protected readonly linkProvider = $inject(LinkProvider);\n\n protected readonly onBeginTransition = $hook({\n on: \"react:transition:begin\",\n handler: async (event) => {\n if (this.alepha.isBrowser()) {\n Object.defineProperty(event.state, \"user\", {\n get: () => this.user,\n });\n }\n },\n });\n\n protected readonly onFetchRequest = $hook({\n on: \"client:onRequest\",\n handler: async ({ request }) => {\n if (this.alepha.isBrowser() && this.user) {\n // ensure cookies are sent with requests and refresh-able\n request.credentials ??= \"include\";\n }\n },\n });\n\n /**\n * Get the current authenticated user.\n *\n * Alias for `alepha.state.get(\"user\")`\n */\n public get user(): UserAccountToken | undefined {\n return this.alepha.store.get(\"alepha.server.request.user\");\n }\n\n public async ping() {\n const { data } = await this.httpClient.fetch(\n alephaServerAuthRoutes.userinfo,\n {\n schema: { response: userinfoResponseSchema },\n },\n );\n\n this.alepha.store.set(\"alepha.server.request.apiLinks\", data.api);\n this.alepha.store.set(\"alepha.server.request.user\", data.user);\n\n return data.user;\n }\n\n public can(action: string): boolean {\n if (!this.user) {\n return false;\n }\n\n return this.linkProvider.can(action);\n }\n\n public async login(\n provider: string,\n options: {\n hostname?: string;\n username?: string;\n password?: string;\n redirect?: string;\n realm?: string;\n [extra: string]: any;\n },\n ): Promise<Tokens> {\n const realmParam = options.realm\n ? `&realm=${encodeURIComponent(options.realm)}`\n : \"\";\n\n if (options.username || options.password) {\n const { data } = await this.httpClient.fetch(\n `${options.hostname || \"\"}${alephaServerAuthRoutes.token}?provider=${provider}${realmParam}`,\n {\n method: \"POST\",\n body: JSON.stringify({\n username: options.username,\n password: options.password,\n }),\n schema: { response: tokenResponseSchema },\n },\n );\n\n this.alepha.store.set(\"alepha.server.request.apiLinks\", data.api);\n this.alepha.store.set(\"alepha.server.request.user\", data.user);\n\n return data;\n }\n\n if (this.alepha.isBrowser()) {\n const browser = this.alepha.inject(ReactBrowserProvider);\n const redirect =\n options.redirect ||\n (browser.transitioning\n ? window.location.origin + browser.transitioning.to\n : window.location.href);\n\n const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${encodeURIComponent(redirect)}`;\n\n if (browser.transitioning) {\n throw new Redirection(href);\n } else {\n window.location.href = href;\n return {} as Tokens;\n }\n }\n\n throw new Redirection(\n `${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${options.redirect || \"/\"}`,\n );\n }\n\n public logout() {\n window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;\n }\n}\n","import { useAlepha, useStore } from \"alepha/react\";\nimport { type HttpVirtualClient, LinkProvider } from \"alepha/server/links\";\nimport { ReactAuth } from \"../services/ReactAuth.ts\";\n\nexport const useAuth = <T extends object = any>() => {\n const alepha = useAlepha();\n const [user] = useStore(\"alepha.server.request.user\");\n\n return {\n user,\n logout: () => {\n alepha.inject(ReactAuth).logout();\n },\n login: async (\n provider: keyof T,\n options: {\n username?: string;\n password?: string;\n redirect?: string;\n realm?: string;\n [extra: string]: any;\n } = {},\n ) => {\n await alepha.inject(ReactAuth).login(provider as string, options);\n },\n can: <Api extends object = any>(\n name: keyof HttpVirtualClient<Api>,\n ): boolean => {\n return alepha.inject(LinkProvider).can(name as string);\n },\n };\n};\n","import { $module } from \"alepha\";\nimport { ReactAuth } from \"./services/ReactAuth.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./index.shared.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaReactAuth = $module({\n name: \"alepha.react.auth\",\n services: [ReactAuth],\n});\n"],"mappings":";;;;;;;;;;;;AAgBA,IAAa,YAAb,MAAuB;CACrB,AAAmB,MAAM,SAAS;CAClC,AAAmB,SAAS,QAAQ,OAAO;CAC3C,AAAmB,aAAa,QAAQ,WAAW;CACnD,AAAmB,eAAe,QAAQ,aAAa;CAEvD,AAAmB,oBAAoB,MAAM;EAC3C,IAAI;EACJ,SAAS,OAAO,UAAU;AACxB,OAAI,KAAK,OAAO,WAAW,CACzB,QAAO,eAAe,MAAM,OAAO,QAAQ,EACzC,WAAW,KAAK,MACjB,CAAC;;EAGP,CAAC;CAEF,AAAmB,iBAAiB,MAAM;EACxC,IAAI;EACJ,SAAS,OAAO,EAAE,cAAc;AAC9B,OAAI,KAAK,OAAO,WAAW,IAAI,KAAK,KAElC,SAAQ,gBAAgB;;EAG7B,CAAC;;;;;;CAOF,IAAW,OAAqC;AAC9C,SAAO,KAAK,OAAO,MAAM,IAAI,6BAA6B;;CAG5D,MAAa,OAAO;EAClB,MAAM,EAAE,SAAS,MAAM,KAAK,WAAW,MACrC,uBAAuB,UACvB,EACE,QAAQ,EAAE,UAAU,wBAAwB,EAC7C,CACF;AAED,OAAK,OAAO,MAAM,IAAI,kCAAkC,KAAK,IAAI;AACjE,OAAK,OAAO,MAAM,IAAI,8BAA8B,KAAK,KAAK;AAE9D,SAAO,KAAK;;CAGd,AAAO,IAAI,QAAyB;AAClC,MAAI,CAAC,KAAK,KACR,QAAO;AAGT,SAAO,KAAK,aAAa,IAAI,OAAO;;CAGtC,MAAa,MACX,UACA,SAQiB;EACjB,MAAM,aAAa,QAAQ,QACvB,UAAU,mBAAmB,QAAQ,MAAM,KAC3C;AAEJ,MAAI,QAAQ,YAAY,QAAQ,UAAU;GACxC,MAAM,EAAE,SAAS,MAAM,KAAK,WAAW,MACrC,GAAG,QAAQ,YAAY,KAAK,uBAAuB,MAAM,YAAY,WAAW,cAChF;IACE,QAAQ;IACR,MAAM,KAAK,UAAU;KACnB,UAAU,QAAQ;KAClB,UAAU,QAAQ;KACnB,CAAC;IACF,QAAQ,EAAE,UAAU,qBAAqB;IAC1C,CACF;AAED,QAAK,OAAO,MAAM,IAAI,kCAAkC,KAAK,IAAI;AACjE,QAAK,OAAO,MAAM,IAAI,8BAA8B,KAAK,KAAK;AAE9D,UAAO;;AAGT,MAAI,KAAK,OAAO,WAAW,EAAE;GAC3B,MAAM,UAAU,KAAK,OAAO,OAAO,qBAAqB;GACxD,MAAM,WACJ,QAAQ,aACP,QAAQ,gBACL,OAAO,SAAS,SAAS,QAAQ,cAAc,KAC/C,OAAO,SAAS;GAEtB,MAAM,OAAO,GAAG,OAAO,SAAS,SAAS,uBAAuB,MAAM,YAAY,WAAW,WAAW,gBAAgB,mBAAmB,SAAS;AAEpJ,OAAI,QAAQ,cACV,OAAM,IAAI,YAAY,KAAK;QACtB;AACL,WAAO,SAAS,OAAO;AACvB,WAAO,EAAE;;;AAIb,QAAM,IAAI,YACR,GAAG,uBAAuB,MAAM,YAAY,WAAW,WAAW,gBAAgB,QAAQ,YAAY,MACvG;;CAGH,AAAO,SAAS;AACd,SAAO,SAAS,OAAO,GAAG,uBAAuB,OAAO,4BAA4B,mBAAmB,OAAO,SAAS,OAAO;;;;;;AChIlI,MAAa,gBAAwC;CACnD,MAAM,SAAS,WAAW;CAC1B,MAAM,CAAC,QAAQ,SAAS,6BAA6B;AAErD,QAAO;EACL;EACA,cAAc;AACZ,UAAO,OAAO,UAAU,CAAC,QAAQ;;EAEnC,OAAO,OACL,UACA,UAMI,EAAE,KACH;AACH,SAAM,OAAO,OAAO,UAAU,CAAC,MAAM,UAAoB,QAAQ;;EAEnE,MACE,SACY;AACZ,UAAO,OAAO,OAAO,aAAa,CAAC,IAAI,KAAe;;EAEzD;;;;;ACrBH,MAAa,kBAAkB,QAAQ;CACrC,MAAM;CACN,UAAU,CAAC,UAAU;CACtB,CAAC"}
1
+ {"version":3,"file":"index.browser.js","names":[],"sources":["../../../src/react/auth/services/ReactAuth.ts","../../../src/react/auth/hooks/useAuth.ts","../../../src/react/auth/index.browser.ts"],"sourcesContent":["import { $hook, $inject, Alepha } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { ReactBrowserProvider, Redirection } from \"alepha/react/router\";\nimport type { UserAccountToken } from \"alepha/security\";\nimport { HttpClient } from \"alepha/server\";\nimport {\n alephaServerAuthRoutes,\n type Tokens,\n tokenResponseSchema,\n userinfoResponseSchema,\n} from \"alepha/server/auth\";\nimport { LinkProvider } from \"alepha/server/links\";\n\n/**\n * Browser, SSR friendly, service to handle authentication.\n */\nexport class ReactAuth {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly httpClient = $inject(HttpClient);\n protected readonly linkProvider = $inject(LinkProvider);\n\n protected readonly onBeginTransition = $hook({\n on: \"react:transition:begin\",\n handler: async (event) => {\n if (this.alepha.isBrowser()) {\n Object.defineProperty(event.state, \"user\", {\n get: () => this.user,\n });\n }\n },\n });\n\n protected readonly onFetchRequest = $hook({\n on: \"client:onRequest\",\n handler: async ({ request }) => {\n if (this.alepha.isBrowser() && this.user) {\n // ensure cookies are sent with requests and refresh-able\n request.credentials ??= \"include\";\n }\n },\n });\n\n /**\n * Get the current authenticated user.\n *\n * Alias for `alepha.state.get(\"user\")`\n */\n public get user(): UserAccountToken | undefined {\n return this.alepha.store.get(\"alepha.server.request.user\");\n }\n\n public async ping() {\n const { data } = await this.httpClient.fetch(\n alephaServerAuthRoutes.userinfo,\n {\n schema: { response: userinfoResponseSchema },\n },\n );\n\n this.alepha.store.set(\"alepha.server.request.apiLinks\", data.api);\n this.alepha.store.set(\"alepha.server.request.user\", data.user);\n\n return data.user;\n }\n\n public can(action: string): boolean {\n if (!this.user) {\n return false;\n }\n\n return this.linkProvider.can(action);\n }\n\n public async login(\n provider: string,\n options: {\n hostname?: string;\n username?: string;\n password?: string;\n redirect?: string;\n realm?: string;\n [extra: string]: any;\n },\n ): Promise<Tokens> {\n const realmParam = options.realm\n ? `&realm=${encodeURIComponent(options.realm)}`\n : \"\";\n\n if (options.username || options.password) {\n const { data } = await this.httpClient.fetch(\n `${options.hostname || \"\"}${alephaServerAuthRoutes.token}?provider=${provider}${realmParam}`,\n {\n method: \"POST\",\n body: JSON.stringify({\n username: options.username,\n password: options.password,\n }),\n schema: { response: tokenResponseSchema },\n },\n );\n\n this.alepha.store.set(\"alepha.server.request.apiLinks\", data.api);\n this.alepha.store.set(\"alepha.server.request.user\", data.user);\n\n return data;\n }\n\n if (this.alepha.isBrowser()) {\n const browser = this.alepha.inject(ReactBrowserProvider);\n const redirect =\n options.redirect ||\n (browser.transitioning\n ? window.location.origin + browser.transitioning.to\n : window.location.href);\n\n const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${encodeURIComponent(redirect)}`;\n\n if (browser.transitioning) {\n throw new Redirection(href);\n } else {\n window.location.href = href;\n return {} as Tokens;\n }\n }\n\n throw new Redirection(\n `${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${options.redirect || \"/\"}`,\n );\n }\n\n public logout() {\n // Add cache-busting parameter to prevent browser from using cached redirect\n const cacheBuster = Date.now();\n window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}&_=${cacheBuster}`;\n }\n}\n","import { useAlepha, useStore } from \"alepha/react\";\nimport { type HttpVirtualClient, LinkProvider } from \"alepha/server/links\";\nimport { ReactAuth } from \"../services/ReactAuth.ts\";\n\nexport const useAuth = <T extends object = any>() => {\n const alepha = useAlepha();\n const [user] = useStore(\"alepha.server.request.user\");\n\n return {\n user,\n logout: () => {\n alepha.inject(ReactAuth).logout();\n },\n login: async (\n provider: keyof T,\n options: {\n username?: string;\n password?: string;\n redirect?: string;\n realm?: string;\n [extra: string]: any;\n } = {},\n ) => {\n await alepha.inject(ReactAuth).login(provider as string, options);\n },\n can: <Api extends object = any>(\n name: keyof HttpVirtualClient<Api>,\n ): boolean => {\n return alepha.inject(LinkProvider).can(name as string);\n },\n };\n};\n","import { $module } from \"alepha\";\nimport { ReactAuth } from \"./services/ReactAuth.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./index.shared.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaReactAuth = $module({\n name: \"alepha.react.auth\",\n services: [ReactAuth],\n});\n"],"mappings":"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"}
@@ -81,9 +81,9 @@ declare module "alepha/react/router" {
81
81
  }
82
82
  }
83
83
  /**
84
- * | type | quality | stability |
85
- * |------|---------|-----------|
86
- * | frontend | rare | stable |
84
+ * | Stability | Since | Runtime |
85
+ * |-----------|-------|---------|
86
+ * | 3 - stable | 0.8.0 | node, bun, browser|
87
87
  *
88
88
  * Auth-related React components and hooks.
89
89
  *
@@ -89,7 +89,8 @@ var ReactAuth = class {
89
89
  throw new Redirection(`${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${options.redirect || "/"}`);
90
90
  }
91
91
  logout() {
92
- window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;
92
+ const cacheBuster = Date.now();
93
+ window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}&_=${cacheBuster}`;
93
94
  }
94
95
  };
95
96
 
@@ -115,9 +116,9 @@ const useAuth = () => {
115
116
  //#endregion
116
117
  //#region ../../src/react/auth/index.ts
117
118
  /**
118
- * | type | quality | stability |
119
- * |------|---------|-----------|
120
- * | frontend | rare | stable |
119
+ * | Stability | Since | Runtime |
120
+ * |-----------|-------|---------|
121
+ * | 3 - stable | 0.8.0 | node, bun, browser|
121
122
  *
122
123
  * Auth-related React components and hooks.
123
124
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/auth/providers/ReactAuthProvider.ts","../../../src/react/auth/services/ReactAuth.ts","../../../src/react/auth/hooks/useAuth.ts","../../../src/react/auth/index.ts"],"sourcesContent":["import { $hook, $inject, Alepha } from \"alepha\";\n\nexport class ReactAuthProvider {\n protected readonly alepha = $inject(Alepha);\n\n public readonly onRender = $hook({\n on: \"react:server:render:begin\",\n handler: async ({ request, state }) => {\n if (request?.user) {\n const { token, realm, ...user } = request.user; // do not send token and realm to the client\n this.alepha.store.set(\"alepha.server.request.user\", user); // for hydration, browser, etc...\n state.user = user;\n }\n },\n });\n}\n","import { $hook, $inject, Alepha } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { ReactBrowserProvider, Redirection } from \"alepha/react/router\";\nimport type { UserAccountToken } from \"alepha/security\";\nimport { HttpClient } from \"alepha/server\";\nimport {\n alephaServerAuthRoutes,\n type Tokens,\n tokenResponseSchema,\n userinfoResponseSchema,\n} from \"alepha/server/auth\";\nimport { LinkProvider } from \"alepha/server/links\";\n\n/**\n * Browser, SSR friendly, service to handle authentication.\n */\nexport class ReactAuth {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly httpClient = $inject(HttpClient);\n protected readonly linkProvider = $inject(LinkProvider);\n\n protected readonly onBeginTransition = $hook({\n on: \"react:transition:begin\",\n handler: async (event) => {\n if (this.alepha.isBrowser()) {\n Object.defineProperty(event.state, \"user\", {\n get: () => this.user,\n });\n }\n },\n });\n\n protected readonly onFetchRequest = $hook({\n on: \"client:onRequest\",\n handler: async ({ request }) => {\n if (this.alepha.isBrowser() && this.user) {\n // ensure cookies are sent with requests and refresh-able\n request.credentials ??= \"include\";\n }\n },\n });\n\n /**\n * Get the current authenticated user.\n *\n * Alias for `alepha.state.get(\"user\")`\n */\n public get user(): UserAccountToken | undefined {\n return this.alepha.store.get(\"alepha.server.request.user\");\n }\n\n public async ping() {\n const { data } = await this.httpClient.fetch(\n alephaServerAuthRoutes.userinfo,\n {\n schema: { response: userinfoResponseSchema },\n },\n );\n\n this.alepha.store.set(\"alepha.server.request.apiLinks\", data.api);\n this.alepha.store.set(\"alepha.server.request.user\", data.user);\n\n return data.user;\n }\n\n public can(action: string): boolean {\n if (!this.user) {\n return false;\n }\n\n return this.linkProvider.can(action);\n }\n\n public async login(\n provider: string,\n options: {\n hostname?: string;\n username?: string;\n password?: string;\n redirect?: string;\n realm?: string;\n [extra: string]: any;\n },\n ): Promise<Tokens> {\n const realmParam = options.realm\n ? `&realm=${encodeURIComponent(options.realm)}`\n : \"\";\n\n if (options.username || options.password) {\n const { data } = await this.httpClient.fetch(\n `${options.hostname || \"\"}${alephaServerAuthRoutes.token}?provider=${provider}${realmParam}`,\n {\n method: \"POST\",\n body: JSON.stringify({\n username: options.username,\n password: options.password,\n }),\n schema: { response: tokenResponseSchema },\n },\n );\n\n this.alepha.store.set(\"alepha.server.request.apiLinks\", data.api);\n this.alepha.store.set(\"alepha.server.request.user\", data.user);\n\n return data;\n }\n\n if (this.alepha.isBrowser()) {\n const browser = this.alepha.inject(ReactBrowserProvider);\n const redirect =\n options.redirect ||\n (browser.transitioning\n ? window.location.origin + browser.transitioning.to\n : window.location.href);\n\n const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${encodeURIComponent(redirect)}`;\n\n if (browser.transitioning) {\n throw new Redirection(href);\n } else {\n window.location.href = href;\n return {} as Tokens;\n }\n }\n\n throw new Redirection(\n `${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${options.redirect || \"/\"}`,\n );\n }\n\n public logout() {\n window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;\n }\n}\n","import { useAlepha, useStore } from \"alepha/react\";\nimport { type HttpVirtualClient, LinkProvider } from \"alepha/server/links\";\nimport { ReactAuth } from \"../services/ReactAuth.ts\";\n\nexport const useAuth = <T extends object = any>() => {\n const alepha = useAlepha();\n const [user] = useStore(\"alepha.server.request.user\");\n\n return {\n user,\n logout: () => {\n alepha.inject(ReactAuth).logout();\n },\n login: async (\n provider: keyof T,\n options: {\n username?: string;\n password?: string;\n redirect?: string;\n realm?: string;\n [extra: string]: any;\n } = {},\n ) => {\n await alepha.inject(ReactAuth).login(provider as string, options);\n },\n can: <Api extends object = any>(\n name: keyof HttpVirtualClient<Api>,\n ): boolean => {\n return alepha.inject(LinkProvider).can(name as string);\n },\n };\n};\n","import { $module } from \"alepha\";\nimport { AlephaReact } from \"alepha/react\";\nimport type { UserAccount } from \"alepha/security\";\nimport { $auth, AlephaServerAuth } from \"alepha/server/auth\";\nimport { AlephaServerLinks } from \"alepha/server/links\";\nimport { ReactAuthProvider } from \"./providers/ReactAuthProvider.ts\";\nimport { ReactAuth } from \"./services/ReactAuth.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./index.shared.ts\";\nexport * from \"./providers/ReactAuthProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/react/router\" {\n interface ReactRouterState {\n user?: UserAccount;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * | type | quality | stability |\n * |------|---------|-----------|\n * | frontend | rare | stable |\n *\n * Auth-related React components and hooks.\n *\n * **Features:**\n * - Login/logout components\n * - Protected route wrappers\n * - Auth state hooks\n *\n * @module alepha.react.auth\n */\nexport const AlephaReactAuth = $module({\n name: \"alepha.react.auth\",\n primitives: [$auth],\n services: [\n AlephaReact,\n AlephaServerLinks,\n AlephaServerAuth,\n ReactAuthProvider,\n ReactAuth,\n ],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/auth/providers/ReactAuthProvider.ts","../../../src/react/auth/services/ReactAuth.ts","../../../src/react/auth/hooks/useAuth.ts","../../../src/react/auth/index.ts"],"sourcesContent":["import { $hook, $inject, Alepha } from \"alepha\";\n\nexport class ReactAuthProvider {\n protected readonly alepha = $inject(Alepha);\n\n public readonly onRender = $hook({\n on: \"react:server:render:begin\",\n handler: async ({ request, state }) => {\n if (request?.user) {\n const { token, realm, ...user } = request.user; // do not send token and realm to the client\n this.alepha.store.set(\"alepha.server.request.user\", user); // for hydration, browser, etc...\n state.user = user;\n }\n },\n });\n}\n","import { $hook, $inject, Alepha } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { ReactBrowserProvider, Redirection } from \"alepha/react/router\";\nimport type { UserAccountToken } from \"alepha/security\";\nimport { HttpClient } from \"alepha/server\";\nimport {\n alephaServerAuthRoutes,\n type Tokens,\n tokenResponseSchema,\n userinfoResponseSchema,\n} from \"alepha/server/auth\";\nimport { LinkProvider } from \"alepha/server/links\";\n\n/**\n * Browser, SSR friendly, service to handle authentication.\n */\nexport class ReactAuth {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly httpClient = $inject(HttpClient);\n protected readonly linkProvider = $inject(LinkProvider);\n\n protected readonly onBeginTransition = $hook({\n on: \"react:transition:begin\",\n handler: async (event) => {\n if (this.alepha.isBrowser()) {\n Object.defineProperty(event.state, \"user\", {\n get: () => this.user,\n });\n }\n },\n });\n\n protected readonly onFetchRequest = $hook({\n on: \"client:onRequest\",\n handler: async ({ request }) => {\n if (this.alepha.isBrowser() && this.user) {\n // ensure cookies are sent with requests and refresh-able\n request.credentials ??= \"include\";\n }\n },\n });\n\n /**\n * Get the current authenticated user.\n *\n * Alias for `alepha.state.get(\"user\")`\n */\n public get user(): UserAccountToken | undefined {\n return this.alepha.store.get(\"alepha.server.request.user\");\n }\n\n public async ping() {\n const { data } = await this.httpClient.fetch(\n alephaServerAuthRoutes.userinfo,\n {\n schema: { response: userinfoResponseSchema },\n },\n );\n\n this.alepha.store.set(\"alepha.server.request.apiLinks\", data.api);\n this.alepha.store.set(\"alepha.server.request.user\", data.user);\n\n return data.user;\n }\n\n public can(action: string): boolean {\n if (!this.user) {\n return false;\n }\n\n return this.linkProvider.can(action);\n }\n\n public async login(\n provider: string,\n options: {\n hostname?: string;\n username?: string;\n password?: string;\n redirect?: string;\n realm?: string;\n [extra: string]: any;\n },\n ): Promise<Tokens> {\n const realmParam = options.realm\n ? `&realm=${encodeURIComponent(options.realm)}`\n : \"\";\n\n if (options.username || options.password) {\n const { data } = await this.httpClient.fetch(\n `${options.hostname || \"\"}${alephaServerAuthRoutes.token}?provider=${provider}${realmParam}`,\n {\n method: \"POST\",\n body: JSON.stringify({\n username: options.username,\n password: options.password,\n }),\n schema: { response: tokenResponseSchema },\n },\n );\n\n this.alepha.store.set(\"alepha.server.request.apiLinks\", data.api);\n this.alepha.store.set(\"alepha.server.request.user\", data.user);\n\n return data;\n }\n\n if (this.alepha.isBrowser()) {\n const browser = this.alepha.inject(ReactBrowserProvider);\n const redirect =\n options.redirect ||\n (browser.transitioning\n ? window.location.origin + browser.transitioning.to\n : window.location.href);\n\n const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${encodeURIComponent(redirect)}`;\n\n if (browser.transitioning) {\n throw new Redirection(href);\n } else {\n window.location.href = href;\n return {} as Tokens;\n }\n }\n\n throw new Redirection(\n `${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${options.redirect || \"/\"}`,\n );\n }\n\n public logout() {\n // Add cache-busting parameter to prevent browser from using cached redirect\n const cacheBuster = Date.now();\n window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}&_=${cacheBuster}`;\n }\n}\n","import { useAlepha, useStore } from \"alepha/react\";\nimport { type HttpVirtualClient, LinkProvider } from \"alepha/server/links\";\nimport { ReactAuth } from \"../services/ReactAuth.ts\";\n\nexport const useAuth = <T extends object = any>() => {\n const alepha = useAlepha();\n const [user] = useStore(\"alepha.server.request.user\");\n\n return {\n user,\n logout: () => {\n alepha.inject(ReactAuth).logout();\n },\n login: async (\n provider: keyof T,\n options: {\n username?: string;\n password?: string;\n redirect?: string;\n realm?: string;\n [extra: string]: any;\n } = {},\n ) => {\n await alepha.inject(ReactAuth).login(provider as string, options);\n },\n can: <Api extends object = any>(\n name: keyof HttpVirtualClient<Api>,\n ): boolean => {\n return alepha.inject(LinkProvider).can(name as string);\n },\n };\n};\n","import { $module } from \"alepha\";\nimport { AlephaReact } from \"alepha/react\";\nimport type { UserAccount } from \"alepha/security\";\nimport { $auth, AlephaServerAuth } from \"alepha/server/auth\";\nimport { AlephaServerLinks } from \"alepha/server/links\";\nimport { ReactAuthProvider } from \"./providers/ReactAuthProvider.ts\";\nimport { ReactAuth } from \"./services/ReactAuth.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./index.shared.ts\";\nexport * from \"./providers/ReactAuthProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/react/router\" {\n interface ReactRouterState {\n user?: UserAccount;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * | Stability | Since | Runtime |\n * |-----------|-------|---------|\n * | 3 - stable | 0.8.0 | node, bun, browser|\n *\n * Auth-related React components and hooks.\n *\n * **Features:**\n * - Login/logout components\n * - Protected route wrappers\n * - Auth state hooks\n *\n * @module alepha.react.auth\n */\nexport const AlephaReactAuth = $module({\n name: \"alepha.react.auth\",\n primitives: [$auth],\n services: [\n AlephaReact,\n AlephaServerLinks,\n AlephaServerAuth,\n ReactAuthProvider,\n ReactAuth,\n ],\n});\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import * as alepha0 from "alepha";
2
2
  import { Alepha, Async, Atom, Hook, Hooks, Service, State, Static, TAtomObject } from "alepha";
3
- import * as react0 from "react";
3
+ import * as react2 from "react";
4
4
  import React, { DependencyList, ErrorInfo, PropsWithChildren, ReactNode } from "react";
5
5
  import * as react_jsx_runtime0 from "react/jsx-runtime";
6
6
  import { DurationLike } from "alepha/datetime";
@@ -82,7 +82,7 @@ declare class ErrorBoundary extends React.Component<PropsWithChildren<ErrorBound
82
82
  /**
83
83
  * React context to provide the Alepha instance throughout the component tree.
84
84
  */
85
- declare const AlephaContext: react0.Context<Alepha | undefined>;
85
+ declare const AlephaContext: react2.Context<Alepha | undefined>;
86
86
  //#endregion
87
87
  //#region ../../src/react/core/contexts/AlephaProvider.d.ts
88
88
  interface AlephaProviderProps {
@@ -95,7 +95,7 @@ interface AlephaProviderProps {
95
95
  *
96
96
  * This isn't recommended for apps using `alepha/react/router`, as Router will handle this for you.
97
97
  */
98
- declare const AlephaProvider: (props: AlephaProviderProps) => string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react0.ReactPortal | react0.ReactElement<unknown, string | react0.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react_jsx_runtime0.JSX.Element | null | undefined;
98
+ declare const AlephaProvider: (props: AlephaProviderProps) => string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react2.ReactPortal | react2.ReactElement<unknown, string | react2.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react_jsx_runtime0.JSX.Element | null | undefined;
99
99
  //#endregion
100
100
  //#region ../../src/react/core/hooks/useAction.d.ts
101
101
  /**
@@ -436,9 +436,9 @@ declare module "alepha" {
436
436
  }
437
437
  }
438
438
  /**
439
- * | type | quality | stability |
440
- * |------|---------|-----------|
441
- * | frontend | epic | stable |
439
+ * | Stability | Since | Runtime |
440
+ * |-----------|-------|---------|
441
+ * | 3 - stable | 0.2.0 | node, bun, workerd, browser, expo|
442
442
  *
443
443
  * Full-stack React framework with server-side rendering.
444
444
  *
@@ -430,9 +430,9 @@ function useStore(target, defaultValue) {
430
430
  //#endregion
431
431
  //#region ../../src/react/core/index.ts
432
432
  /**
433
- * | type | quality | stability |
434
- * |------|---------|-----------|
435
- * | frontend | epic | stable |
433
+ * | Stability | Since | Runtime |
434
+ * |-----------|-------|---------|
435
+ * | 3 - stable | 0.2.0 | node, bun, workerd, browser, expo|
436
436
  *
437
437
  * Full-stack React framework with server-side rendering.
438
438
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/core/components/ClientOnly.tsx","../../../src/react/core/components/ErrorBoundary.tsx","../../../src/react/core/contexts/AlephaContext.ts","../../../src/react/core/contexts/AlephaProvider.tsx","../../../src/react/core/hooks/useAlepha.ts","../../../src/react/core/hooks/useInject.ts","../../../src/react/core/hooks/useAction.ts","../../../src/react/core/hooks/useClient.ts","../../../src/react/core/hooks/useEvents.ts","../../../src/react/core/hooks/useStore.ts","../../../src/react/core/index.ts"],"sourcesContent":["import {\n type PropsWithChildren,\n type ReactNode,\n useEffect,\n useState,\n} from \"react\";\n\nexport interface ClientOnlyProps {\n fallback?: ReactNode;\n disabled?: boolean;\n}\n\n/**\n * A small utility component that renders its children only on the client side.\n *\n * Optionally, you can provide a fallback React node that will be rendered.\n *\n * You should use this component when\n * - you have code that relies on browser-specific APIs\n * - you want to avoid server-side rendering for a specific part of your application\n * - you want to prevent pre-rendering of a component\n *\n * @example\n * ```tsx\n * import { ClientOnly } from \"alepha/react\";\n *\n * const MyComponent = () => {\n * // Avoids SSR issues with Date API\n * return (\n * <ClientOnly>\n * {new Date().toLocaleTimeString()}\n * </ClientOnly>\n * );\n * }\n * ```\n */\nconst ClientOnly = (props: PropsWithChildren<ClientOnlyProps>) => {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => setMounted(true), []);\n\n if (props.disabled) {\n return props.children;\n }\n\n return mounted ? props.children : props.fallback;\n};\n\nexport default ClientOnly;\n","import React, {\n type ErrorInfo,\n type PropsWithChildren,\n type ReactNode,\n} from \"react\";\n\n/**\n * Props for the ErrorBoundary component.\n */\nexport interface ErrorBoundaryProps {\n /**\n * Fallback React node to render when an error is caught.\n * If not provided, a default error message will be shown.\n */\n fallback: (error: Error) => ReactNode;\n\n /**\n * Optional callback that receives the error and error info.\n * Use this to log errors to a monitoring service.\n */\n onError?: (error: Error, info: ErrorInfo) => void;\n}\n\n/**\n * State of the ErrorBoundary component.\n */\ninterface ErrorBoundaryState {\n error?: Error;\n}\n\n/**\n * A reusable error boundary for catching rendering errors in any part of the React component tree.\n *\n * It's already included in the Alepha React framework when using page or layout components.\n */\nexport class ErrorBoundary extends React.Component<\n PropsWithChildren<ErrorBoundaryProps>,\n ErrorBoundaryState\n> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = {};\n }\n\n /**\n * Update state so the next render shows the fallback UI.\n */\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return {\n error,\n };\n }\n\n /**\n * Lifecycle method called when an error is caught.\n * You can log the error or perform side effects here.\n */\n componentDidCatch(error: Error, info: ErrorInfo): void {\n if (this.props.onError) {\n this.props.onError(error, info);\n }\n }\n\n render(): ReactNode {\n if (this.state.error) {\n return this.props.fallback(this.state.error);\n }\n\n return this.props.children;\n }\n}\n\nexport default ErrorBoundary;\n","import type { Alepha } from \"alepha\";\nimport { createContext } from \"react\";\n\n/**\n * React context to provide the Alepha instance throughout the component tree.\n */\nexport const AlephaContext = createContext<Alepha | undefined>(undefined);\n","import { Alepha } from \"alepha\";\nimport { type ReactNode, useEffect, useMemo, useState } from \"react\";\nimport { AlephaContext } from \"./AlephaContext.ts\";\n\nexport interface AlephaProviderProps {\n children: ReactNode;\n onError: (error: Error) => ReactNode;\n onLoading: () => ReactNode;\n}\n\n/**\n * AlephaProvider component to initialize and provide Alepha instance to the app.\n *\n * This isn't recommended for apps using `alepha/react/router`, as Router will handle this for you.\n */\nexport const AlephaProvider = (props: AlephaProviderProps) => {\n const alepha = useMemo(() => Alepha.create(), []);\n\n const [started, setStarted] = useState(false);\n const [error, setError] = useState<Error | undefined>();\n\n useEffect(() => {\n alepha\n .start()\n .then(() => setStarted(true))\n .catch((err) => setError(err));\n }, [alepha]);\n\n if (error) {\n return props.onError(error);\n }\n\n if (!started) {\n return props.onLoading();\n }\n\n return (\n <AlephaContext.Provider value={alepha}>\n {props.children}\n </AlephaContext.Provider>\n );\n};\n","import { type Alepha, AlephaError } from \"alepha\";\nimport { useContext } from \"react\";\nimport { AlephaContext } from \"../contexts/AlephaContext.ts\";\n\n/**\n * Main Alepha hook.\n *\n * It provides access to the Alepha instance within a React component.\n *\n * With Alepha, you can access the core functionalities of the framework:\n *\n * - alepha.state() for state management\n * - alepha.inject() for dependency injection\n * - alepha.events.emit() for event handling\n * etc...\n */\nexport const useAlepha = (): Alepha => {\n const alepha = useContext(AlephaContext);\n if (!alepha) {\n throw new AlephaError(\n \"Hook 'useAlepha()' must be used within an AlephaContext.Provider\",\n );\n }\n\n return alepha;\n};\n","import type { Service } from \"alepha\";\nimport { useMemo } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Hook to inject a service instance.\n * It's a wrapper of `useAlepha().inject(service)` with a memoization.\n */\nexport const useInject = <T extends object>(service: Service<T>): T => {\n const alepha = useAlepha();\n return useMemo(() => alepha.inject(service), []);\n};\n","import type { Async } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n type Interval,\n type Timeout,\n} from \"alepha/datetime\";\nimport {\n type DependencyList,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\nimport { useInject } from \"./useInject.ts\";\n\n/**\n * Hook for handling async actions with automatic error handling and event emission.\n *\n * By default, prevents concurrent executions - if an action is running and you call it again,\n * the second call will be ignored. Use `debounce` option to delay execution instead.\n *\n * Emits lifecycle events:\n * - `react:action:begin` - When action starts\n * - `react:action:success` - When action completes successfully\n * - `react:action:error` - When action throws an error\n * - `react:action:end` - Always emitted at the end\n *\n * @example Basic usage\n * ```tsx\n * const action = useAction({\n * handler: async (data) => {\n * await api.save(data);\n * }\n * }, []);\n *\n * <button onClick={() => action.run(data)} disabled={action.loading}>\n * Save\n * </button>\n * ```\n *\n * @example With debounce (search input)\n * ```tsx\n * const search = useAction({\n * handler: async (query: string) => {\n * await api.search(query);\n * },\n * debounce: 300 // Wait 300ms after last call\n * }, []);\n *\n * <input onChange={(e) => search.run(e.target.value)} />\n * ```\n *\n * @example Run on component mount\n * ```tsx\n * const fetchData = useAction({\n * handler: async () => {\n * const data = await api.getData();\n * return data;\n * },\n * runOnInit: true // Runs once when component mounts\n * }, []);\n * ```\n *\n * @example Run periodically (polling)\n * ```tsx\n * const pollStatus = useAction({\n * handler: async () => {\n * const status = await api.getStatus();\n * return status;\n * },\n * runEvery: 5000 // Run every 5 seconds\n * }, []);\n *\n * // Or with duration tuple\n * const pollStatus = useAction({\n * handler: async () => {\n * const status = await api.getStatus();\n * return status;\n * },\n * runEvery: [30, 'seconds'] // Run every 30 seconds\n * }, []);\n * ```\n *\n * @example With AbortController\n * ```tsx\n * const fetch = useAction({\n * handler: async (url, { signal }) => {\n * const response = await fetch(url, { signal });\n * return response.json();\n * }\n * }, []);\n * // Automatically cancelled on unmount or when new request starts\n * ```\n *\n * @example With error handling\n * ```tsx\n * const deleteAction = useAction({\n * handler: async (id: string) => {\n * await api.delete(id);\n * },\n * onError: (error) => {\n * if (error.code === 'NOT_FOUND') {\n * // Custom error handling\n * }\n * }\n * }, []);\n *\n * {deleteAction.error && <div>Error: {deleteAction.error.message}</div>}\n * ```\n *\n * @example Global error handling\n * ```tsx\n * // In your root app setup\n * alepha.events.on(\"react:action:error\", ({ error }) => {\n * toast.danger(error.message);\n * Sentry.captureException(error);\n * });\n * ```\n */\nexport function useAction<Args extends any[], Result = void>(\n options: UseActionOptions<Args, Result>,\n deps: DependencyList,\n): UseActionReturn<Args, Result> {\n const alepha = useAlepha();\n const dateTimeProvider = useInject(DateTimeProvider);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>();\n const [result, setResult] = useState<Result | undefined>();\n const isExecutingRef = useRef(false);\n const debounceTimerRef = useRef<Timeout | undefined>(undefined);\n const abortControllerRef = useRef<AbortController | undefined>(undefined);\n const isMountedRef = useRef(true);\n const intervalRef = useRef<Interval | undefined>(undefined);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n isMountedRef.current = false;\n\n // clear debounce timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = undefined;\n }\n\n // clear interval\n if (intervalRef.current) {\n dateTimeProvider.clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n\n // abort in-flight request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = undefined;\n }\n };\n }, []);\n\n const executeAction = useCallback(\n async (...args: Args): Promise<Result | undefined> => {\n // Prevent concurrent executions\n if (isExecutingRef.current) {\n return;\n }\n\n // Abort previous request if still running\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n // Create new AbortController for this request\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n isExecutingRef.current = true;\n setLoading(true);\n setError(undefined);\n\n await alepha.events.emit(\"react:action:begin\", {\n type: \"custom\",\n id: options.id,\n });\n\n try {\n // Pass abort signal as last argument to handler\n const result = await options.handler(...args, {\n signal: abortController.signal,\n } as any);\n\n // TODO: it should be after onSuccess?\n setResult(result as Result);\n\n // Only update state if still mounted and not aborted\n if (!isMountedRef.current || abortController.signal.aborted) {\n return;\n }\n\n await alepha.events.emit(\"react:action:success\", {\n type: \"custom\",\n id: options.id,\n });\n\n if (options.onSuccess) {\n await options.onSuccess(result);\n }\n\n return result;\n } catch (err) {\n // Ignore abort errors\n if (err instanceof Error && err.name === \"AbortError\") {\n return;\n }\n\n // Only update state if still mounted\n if (!isMountedRef.current) {\n return;\n }\n\n const error = err as Error;\n setError(error);\n\n await alepha.events.emit(\"react:action:error\", {\n type: \"custom\",\n id: options.id,\n error,\n });\n\n if (options.onError) {\n await options.onError(error);\n } else {\n // Re-throw if no custom error handler\n throw error;\n }\n } finally {\n isExecutingRef.current = false;\n setLoading(false);\n\n await alepha.events.emit(\"react:action:end\", {\n type: \"custom\",\n id: options.id,\n });\n\n // Clean up abort controller\n if (abortControllerRef.current === abortController) {\n abortControllerRef.current = undefined;\n }\n }\n },\n [...deps, options.id, options.onError, options.onSuccess],\n );\n\n const handler = useCallback(\n async (...args: Args): Promise<Result | undefined> => {\n if (options.debounce) {\n // clear existing timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n }\n\n // Set new timer\n return new Promise((resolve) => {\n debounceTimerRef.current = dateTimeProvider.createTimeout(\n async () => {\n const result = await executeAction(...args);\n resolve(result);\n },\n options.debounce ?? 0,\n );\n });\n }\n\n return executeAction(...args);\n },\n [executeAction, options.debounce],\n );\n\n const cancel = useCallback(() => {\n // clear debounce timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = undefined;\n }\n\n // abort in-flight request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = undefined;\n }\n\n // reset state\n if (isMountedRef.current) {\n isExecutingRef.current = false;\n setLoading(false);\n }\n }, []);\n\n // Run action on mount if runOnInit is true\n useEffect(() => {\n if (options.runOnInit) {\n handler(...([] as any));\n }\n }, deps);\n\n // Run action periodically if runEvery is specified\n useEffect(() => {\n if (!options.runEvery) {\n return;\n }\n\n // Set up interval\n intervalRef.current = dateTimeProvider.createInterval(\n () => handler(...([] as any)),\n options.runEvery,\n true,\n );\n\n // cleanup on unmount or when runEvery changes\n return () => {\n if (intervalRef.current) {\n dateTimeProvider.clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n };\n }, [handler, options.runEvery]);\n\n return {\n run: handler,\n loading,\n error,\n cancel,\n result,\n };\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Context object passed as the last argument to action handlers.\n * Contains an AbortSignal that can be used to cancel the request.\n */\nexport interface ActionContext {\n /**\n * AbortSignal that can be passed to fetch or other async operations.\n * The signal will be aborted when:\n * - The component unmounts\n * - A new action is triggered (cancels previous)\n * - The cancel() method is called\n *\n * @example\n * ```tsx\n * const action = useAction({\n * handler: async (url, { signal }) => {\n * const response = await fetch(url, { signal });\n * return response.json();\n * }\n * }, []);\n * ```\n */\n signal: AbortSignal;\n}\n\nexport interface UseActionOptions<Args extends any[] = any[], Result = any> {\n /**\n * The async action handler function.\n * Receives the action arguments plus an ActionContext as the last parameter.\n */\n handler: (...args: [...Args, ActionContext]) => Async<Result>;\n\n /**\n * Custom error handler. If provided, prevents default error re-throw.\n */\n onError?: (error: Error) => void | Promise<void>;\n\n /**\n * Custom success handler.\n */\n onSuccess?: (result: Result) => void | Promise<void>;\n\n /**\n * Optional identifier for this action (useful for debugging/analytics)\n */\n id?: string;\n\n name?: string;\n\n /**\n * Debounce delay in milliseconds. If specified, the action will only execute\n * after the specified delay has passed since the last call. Useful for search inputs\n * or other high-frequency events.\n *\n * @example\n * ```tsx\n * // Execute search 300ms after user stops typing\n * const search = useAction({ handler: search, debounce: 300 }, [])\n * ```\n */\n debounce?: number;\n\n /**\n * If true, the action will be executed once when the component mounts.\n *\n * @example\n * ```tsx\n * const fetchData = useAction({\n * handler: async () => await api.getData(),\n * runOnInit: true\n * }, []);\n * ```\n */\n runOnInit?: boolean;\n\n /**\n * If specified, the action will be executed periodically at the given interval.\n * The interval is specified as a DurationLike value (number in ms, Duration object, or [number, unit] tuple).\n *\n * @example\n * ```tsx\n * // Run every 5 seconds\n * const poll = useAction({\n * handler: async () => await api.poll(),\n * runEvery: 5000\n * }, []);\n * ```\n *\n * @example\n * ```tsx\n * // Run every 1 minute\n * const poll = useAction({\n * handler: async () => await api.poll(),\n * runEvery: [1, 'minute']\n * }, []);\n * ```\n */\n runEvery?: DurationLike;\n}\n\nexport interface UseActionReturn<Args extends any[], Result> {\n /**\n * Execute the action with the provided arguments.\n *\n * @example\n * ```tsx\n * const action = useAction({ handler: async (data) => { ... } }, []);\n * action.run(data);\n * ```\n */\n run: (...args: Args) => Promise<Result | undefined>;\n\n /**\n * Loading state - true when action is executing.\n */\n loading: boolean;\n\n /**\n * Error state - contains error if action failed, undefined otherwise.\n */\n error?: Error;\n\n /**\n * Cancel any pending debounced action or abort the current in-flight request.\n *\n * @example\n * ```tsx\n * const action = useAction({ ... }, []);\n *\n * <button onClick={action.cancel} disabled={!action.loading}>\n * Cancel\n * </button>\n * ```\n */\n cancel: () => void;\n\n /**\n * The result data from the last successful action execution.\n */\n result?: Result;\n}\n","import {\n type ClientScope,\n type HttpVirtualClient,\n LinkProvider,\n} from \"alepha/server/links\";\nimport { useInject } from \"./useInject.ts\";\n\n/**\n * Hook to get a virtual client for the specified scope.\n *\n * It's the React-hook version of `$client()`, from `AlephaServerLinks` module.\n */\nexport const useClient = <T extends object>(\n scope?: ClientScope,\n): HttpVirtualClient<T> => {\n return useInject(LinkProvider).client<T>(scope);\n};\n","import type { Async, Hook, Hooks } from \"alepha\";\nimport { type DependencyList, useEffect } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Allow subscribing to multiple Alepha events. See {@link Hooks} for available events.\n *\n * useEvents is fully typed to ensure correct event callback signatures.\n *\n * @example\n * ```tsx\n * useEvents(\n * {\n * \"react:transition:begin\": (ev) => {\n * console.log(\"Transition began to:\", ev.to);\n * },\n * \"react:transition:error\": {\n * priority: \"first\",\n * callback: (ev) => {\n * console.error(\"Transition error:\", ev.error);\n * },\n * },\n * },\n * [],\n * );\n * ```\n */\nexport const useEvents = (opts: UseEvents, deps: DependencyList) => {\n const alepha = useAlepha();\n\n useEffect(() => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n const subs: Function[] = [];\n for (const [name, hook] of Object.entries(opts)) {\n subs.push(alepha.events.on(name as any, hook as any));\n }\n\n return () => {\n for (const clear of subs) {\n clear();\n }\n };\n }, deps);\n};\n\ntype UseEvents = {\n [T in keyof Hooks]?: Hook<T> | ((payload: Hooks[T]) => Async<void>);\n};\n","import type { State, Static, TAtomObject } from \"alepha\";\nimport { Atom } from \"alepha\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Hook to access and mutate the Alepha state.\n */\nfunction useStore<T extends TAtomObject>(\n target: Atom<T>,\n defaultValue?: Static<T>,\n): UseStoreReturn<Static<T>>;\nfunction useStore<Key extends keyof State>(\n target: Key,\n defaultValue?: State[Key],\n): UseStoreReturn<State[Key]>;\nfunction useStore(target: any, defaultValue?: any): any {\n const alepha = useAlepha();\n\n useMemo(() => {\n if (defaultValue != null && alepha.store.get(target) == null) {\n alepha.store.set(target, defaultValue);\n }\n }, [defaultValue]);\n\n const [state, setState] = useState(alepha.store.get(target));\n\n useEffect(() => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n const key = target instanceof Atom ? target.key : target;\n\n return alepha.events.on(\"state:mutate\", (ev) => {\n if (ev.key === key) {\n setState(ev.value);\n }\n });\n }, []);\n\n return [\n state,\n (value: any) => {\n alepha.store.set(target, value);\n },\n ] as const;\n}\n\nexport type UseStoreReturn<T> = [T, (value: T) => void];\n\nexport { useStore };\n","import { $module } from \"alepha\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type * from \"./components/ClientOnly.tsx\";\nexport { default as ClientOnly } from \"./components/ClientOnly.tsx\";\nexport type * from \"./components/ErrorBoundary.tsx\";\nexport { default as ErrorBoundary } from \"./components/ErrorBoundary.tsx\";\nexport * from \"./contexts/AlephaContext.ts\";\nexport * from \"./contexts/AlephaProvider.tsx\";\nexport * from \"./hooks/useAction.ts\";\nexport * from \"./hooks/useAlepha.ts\";\nexport * from \"./hooks/useClient.ts\";\nexport * from \"./hooks/useEvents.ts\";\nexport * from \"./hooks/useInject.ts\";\nexport * from \"./hooks/useStore.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n /**\n * Fires when a user action is starting.\n * Action can be a form submission, a route transition, or a custom action.\n */\n \"react:action:begin\": {\n type: string;\n id?: string;\n };\n /**\n * Fires when a user action has succeeded.\n * Action can be a form submission, a route transition, or a custom action.\n */\n \"react:action:success\": {\n type: string;\n id?: string;\n };\n /**\n * Fires when a user action has failed.\n * Action can be a form submission, a route transition, or a custom action.\n */\n \"react:action:error\": {\n type: string;\n id?: string;\n error: Error;\n };\n /**\n * Fires when a user action has completed, regardless of success or failure.\n * Action can be a form submission, a route transition, or a custom action.\n */\n \"react:action:end\": {\n type: string;\n id?: string;\n };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * | type | quality | stability |\n * |------|---------|-----------|\n * | frontend | epic | stable |\n *\n * Full-stack React framework with server-side rendering.\n *\n * **Features:**\n * - React page routes with type-safe params\n * - Async action handler with loading/error/cancel states\n * - Type-safe HTTP client access\n * - Dependency injection in components\n * - Global state management\n * - Router navigation methods\n * - Current route state access\n * - Check if path is active\n * - URL query parameters\n * - Access route schema\n * - Subscribe to Alepha events\n * - Type-safe form handling with validation\n * - Error handling wrapper component\n * - Client-side only rendering component\n * - Server-side rendering with hydration\n * - Automatic code splitting\n * - Event system for action tracking\n *\n * @module alepha.react\n */\nexport const AlephaReact = $module({\n name: \"alepha.react.core\",\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/core/components/ClientOnly.tsx","../../../src/react/core/components/ErrorBoundary.tsx","../../../src/react/core/contexts/AlephaContext.ts","../../../src/react/core/contexts/AlephaProvider.tsx","../../../src/react/core/hooks/useAlepha.ts","../../../src/react/core/hooks/useInject.ts","../../../src/react/core/hooks/useAction.ts","../../../src/react/core/hooks/useClient.ts","../../../src/react/core/hooks/useEvents.ts","../../../src/react/core/hooks/useStore.ts","../../../src/react/core/index.ts"],"sourcesContent":["import {\n type PropsWithChildren,\n type ReactNode,\n useEffect,\n useState,\n} from \"react\";\n\nexport interface ClientOnlyProps {\n fallback?: ReactNode;\n disabled?: boolean;\n}\n\n/**\n * A small utility component that renders its children only on the client side.\n *\n * Optionally, you can provide a fallback React node that will be rendered.\n *\n * You should use this component when\n * - you have code that relies on browser-specific APIs\n * - you want to avoid server-side rendering for a specific part of your application\n * - you want to prevent pre-rendering of a component\n *\n * @example\n * ```tsx\n * import { ClientOnly } from \"alepha/react\";\n *\n * const MyComponent = () => {\n * // Avoids SSR issues with Date API\n * return (\n * <ClientOnly>\n * {new Date().toLocaleTimeString()}\n * </ClientOnly>\n * );\n * }\n * ```\n */\nconst ClientOnly = (props: PropsWithChildren<ClientOnlyProps>) => {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => setMounted(true), []);\n\n if (props.disabled) {\n return props.children;\n }\n\n return mounted ? props.children : props.fallback;\n};\n\nexport default ClientOnly;\n","import React, {\n type ErrorInfo,\n type PropsWithChildren,\n type ReactNode,\n} from \"react\";\n\n/**\n * Props for the ErrorBoundary component.\n */\nexport interface ErrorBoundaryProps {\n /**\n * Fallback React node to render when an error is caught.\n * If not provided, a default error message will be shown.\n */\n fallback: (error: Error) => ReactNode;\n\n /**\n * Optional callback that receives the error and error info.\n * Use this to log errors to a monitoring service.\n */\n onError?: (error: Error, info: ErrorInfo) => void;\n}\n\n/**\n * State of the ErrorBoundary component.\n */\ninterface ErrorBoundaryState {\n error?: Error;\n}\n\n/**\n * A reusable error boundary for catching rendering errors in any part of the React component tree.\n *\n * It's already included in the Alepha React framework when using page or layout components.\n */\nexport class ErrorBoundary extends React.Component<\n PropsWithChildren<ErrorBoundaryProps>,\n ErrorBoundaryState\n> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = {};\n }\n\n /**\n * Update state so the next render shows the fallback UI.\n */\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return {\n error,\n };\n }\n\n /**\n * Lifecycle method called when an error is caught.\n * You can log the error or perform side effects here.\n */\n componentDidCatch(error: Error, info: ErrorInfo): void {\n if (this.props.onError) {\n this.props.onError(error, info);\n }\n }\n\n render(): ReactNode {\n if (this.state.error) {\n return this.props.fallback(this.state.error);\n }\n\n return this.props.children;\n }\n}\n\nexport default ErrorBoundary;\n","import type { Alepha } from \"alepha\";\nimport { createContext } from \"react\";\n\n/**\n * React context to provide the Alepha instance throughout the component tree.\n */\nexport const AlephaContext = createContext<Alepha | undefined>(undefined);\n","import { Alepha } from \"alepha\";\nimport { type ReactNode, useEffect, useMemo, useState } from \"react\";\nimport { AlephaContext } from \"./AlephaContext.ts\";\n\nexport interface AlephaProviderProps {\n children: ReactNode;\n onError: (error: Error) => ReactNode;\n onLoading: () => ReactNode;\n}\n\n/**\n * AlephaProvider component to initialize and provide Alepha instance to the app.\n *\n * This isn't recommended for apps using `alepha/react/router`, as Router will handle this for you.\n */\nexport const AlephaProvider = (props: AlephaProviderProps) => {\n const alepha = useMemo(() => Alepha.create(), []);\n\n const [started, setStarted] = useState(false);\n const [error, setError] = useState<Error | undefined>();\n\n useEffect(() => {\n alepha\n .start()\n .then(() => setStarted(true))\n .catch((err) => setError(err));\n }, [alepha]);\n\n if (error) {\n return props.onError(error);\n }\n\n if (!started) {\n return props.onLoading();\n }\n\n return (\n <AlephaContext.Provider value={alepha}>\n {props.children}\n </AlephaContext.Provider>\n );\n};\n","import { type Alepha, AlephaError } from \"alepha\";\nimport { useContext } from \"react\";\nimport { AlephaContext } from \"../contexts/AlephaContext.ts\";\n\n/**\n * Main Alepha hook.\n *\n * It provides access to the Alepha instance within a React component.\n *\n * With Alepha, you can access the core functionalities of the framework:\n *\n * - alepha.state() for state management\n * - alepha.inject() for dependency injection\n * - alepha.events.emit() for event handling\n * etc...\n */\nexport const useAlepha = (): Alepha => {\n const alepha = useContext(AlephaContext);\n if (!alepha) {\n throw new AlephaError(\n \"Hook 'useAlepha()' must be used within an AlephaContext.Provider\",\n );\n }\n\n return alepha;\n};\n","import type { Service } from \"alepha\";\nimport { useMemo } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Hook to inject a service instance.\n * It's a wrapper of `useAlepha().inject(service)` with a memoization.\n */\nexport const useInject = <T extends object>(service: Service<T>): T => {\n const alepha = useAlepha();\n return useMemo(() => alepha.inject(service), []);\n};\n","import type { Async } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n type Interval,\n type Timeout,\n} from \"alepha/datetime\";\nimport {\n type DependencyList,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\nimport { useInject } from \"./useInject.ts\";\n\n/**\n * Hook for handling async actions with automatic error handling and event emission.\n *\n * By default, prevents concurrent executions - if an action is running and you call it again,\n * the second call will be ignored. Use `debounce` option to delay execution instead.\n *\n * Emits lifecycle events:\n * - `react:action:begin` - When action starts\n * - `react:action:success` - When action completes successfully\n * - `react:action:error` - When action throws an error\n * - `react:action:end` - Always emitted at the end\n *\n * @example Basic usage\n * ```tsx\n * const action = useAction({\n * handler: async (data) => {\n * await api.save(data);\n * }\n * }, []);\n *\n * <button onClick={() => action.run(data)} disabled={action.loading}>\n * Save\n * </button>\n * ```\n *\n * @example With debounce (search input)\n * ```tsx\n * const search = useAction({\n * handler: async (query: string) => {\n * await api.search(query);\n * },\n * debounce: 300 // Wait 300ms after last call\n * }, []);\n *\n * <input onChange={(e) => search.run(e.target.value)} />\n * ```\n *\n * @example Run on component mount\n * ```tsx\n * const fetchData = useAction({\n * handler: async () => {\n * const data = await api.getData();\n * return data;\n * },\n * runOnInit: true // Runs once when component mounts\n * }, []);\n * ```\n *\n * @example Run periodically (polling)\n * ```tsx\n * const pollStatus = useAction({\n * handler: async () => {\n * const status = await api.getStatus();\n * return status;\n * },\n * runEvery: 5000 // Run every 5 seconds\n * }, []);\n *\n * // Or with duration tuple\n * const pollStatus = useAction({\n * handler: async () => {\n * const status = await api.getStatus();\n * return status;\n * },\n * runEvery: [30, 'seconds'] // Run every 30 seconds\n * }, []);\n * ```\n *\n * @example With AbortController\n * ```tsx\n * const fetch = useAction({\n * handler: async (url, { signal }) => {\n * const response = await fetch(url, { signal });\n * return response.json();\n * }\n * }, []);\n * // Automatically cancelled on unmount or when new request starts\n * ```\n *\n * @example With error handling\n * ```tsx\n * const deleteAction = useAction({\n * handler: async (id: string) => {\n * await api.delete(id);\n * },\n * onError: (error) => {\n * if (error.code === 'NOT_FOUND') {\n * // Custom error handling\n * }\n * }\n * }, []);\n *\n * {deleteAction.error && <div>Error: {deleteAction.error.message}</div>}\n * ```\n *\n * @example Global error handling\n * ```tsx\n * // In your root app setup\n * alepha.events.on(\"react:action:error\", ({ error }) => {\n * toast.danger(error.message);\n * Sentry.captureException(error);\n * });\n * ```\n */\nexport function useAction<Args extends any[], Result = void>(\n options: UseActionOptions<Args, Result>,\n deps: DependencyList,\n): UseActionReturn<Args, Result> {\n const alepha = useAlepha();\n const dateTimeProvider = useInject(DateTimeProvider);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>();\n const [result, setResult] = useState<Result | undefined>();\n const isExecutingRef = useRef(false);\n const debounceTimerRef = useRef<Timeout | undefined>(undefined);\n const abortControllerRef = useRef<AbortController | undefined>(undefined);\n const isMountedRef = useRef(true);\n const intervalRef = useRef<Interval | undefined>(undefined);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n isMountedRef.current = false;\n\n // clear debounce timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = undefined;\n }\n\n // clear interval\n if (intervalRef.current) {\n dateTimeProvider.clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n\n // abort in-flight request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = undefined;\n }\n };\n }, []);\n\n const executeAction = useCallback(\n async (...args: Args): Promise<Result | undefined> => {\n // Prevent concurrent executions\n if (isExecutingRef.current) {\n return;\n }\n\n // Abort previous request if still running\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n // Create new AbortController for this request\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n isExecutingRef.current = true;\n setLoading(true);\n setError(undefined);\n\n await alepha.events.emit(\"react:action:begin\", {\n type: \"custom\",\n id: options.id,\n });\n\n try {\n // Pass abort signal as last argument to handler\n const result = await options.handler(...args, {\n signal: abortController.signal,\n } as any);\n\n // TODO: it should be after onSuccess?\n setResult(result as Result);\n\n // Only update state if still mounted and not aborted\n if (!isMountedRef.current || abortController.signal.aborted) {\n return;\n }\n\n await alepha.events.emit(\"react:action:success\", {\n type: \"custom\",\n id: options.id,\n });\n\n if (options.onSuccess) {\n await options.onSuccess(result);\n }\n\n return result;\n } catch (err) {\n // Ignore abort errors\n if (err instanceof Error && err.name === \"AbortError\") {\n return;\n }\n\n // Only update state if still mounted\n if (!isMountedRef.current) {\n return;\n }\n\n const error = err as Error;\n setError(error);\n\n await alepha.events.emit(\"react:action:error\", {\n type: \"custom\",\n id: options.id,\n error,\n });\n\n if (options.onError) {\n await options.onError(error);\n } else {\n // Re-throw if no custom error handler\n throw error;\n }\n } finally {\n isExecutingRef.current = false;\n setLoading(false);\n\n await alepha.events.emit(\"react:action:end\", {\n type: \"custom\",\n id: options.id,\n });\n\n // Clean up abort controller\n if (abortControllerRef.current === abortController) {\n abortControllerRef.current = undefined;\n }\n }\n },\n [...deps, options.id, options.onError, options.onSuccess],\n );\n\n const handler = useCallback(\n async (...args: Args): Promise<Result | undefined> => {\n if (options.debounce) {\n // clear existing timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n }\n\n // Set new timer\n return new Promise((resolve) => {\n debounceTimerRef.current = dateTimeProvider.createTimeout(\n async () => {\n const result = await executeAction(...args);\n resolve(result);\n },\n options.debounce ?? 0,\n );\n });\n }\n\n return executeAction(...args);\n },\n [executeAction, options.debounce],\n );\n\n const cancel = useCallback(() => {\n // clear debounce timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = undefined;\n }\n\n // abort in-flight request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = undefined;\n }\n\n // reset state\n if (isMountedRef.current) {\n isExecutingRef.current = false;\n setLoading(false);\n }\n }, []);\n\n // Run action on mount if runOnInit is true\n useEffect(() => {\n if (options.runOnInit) {\n handler(...([] as any));\n }\n }, deps);\n\n // Run action periodically if runEvery is specified\n useEffect(() => {\n if (!options.runEvery) {\n return;\n }\n\n // Set up interval\n intervalRef.current = dateTimeProvider.createInterval(\n () => handler(...([] as any)),\n options.runEvery,\n true,\n );\n\n // cleanup on unmount or when runEvery changes\n return () => {\n if (intervalRef.current) {\n dateTimeProvider.clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n };\n }, [handler, options.runEvery]);\n\n return {\n run: handler,\n loading,\n error,\n cancel,\n result,\n };\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Context object passed as the last argument to action handlers.\n * Contains an AbortSignal that can be used to cancel the request.\n */\nexport interface ActionContext {\n /**\n * AbortSignal that can be passed to fetch or other async operations.\n * The signal will be aborted when:\n * - The component unmounts\n * - A new action is triggered (cancels previous)\n * - The cancel() method is called\n *\n * @example\n * ```tsx\n * const action = useAction({\n * handler: async (url, { signal }) => {\n * const response = await fetch(url, { signal });\n * return response.json();\n * }\n * }, []);\n * ```\n */\n signal: AbortSignal;\n}\n\nexport interface UseActionOptions<Args extends any[] = any[], Result = any> {\n /**\n * The async action handler function.\n * Receives the action arguments plus an ActionContext as the last parameter.\n */\n handler: (...args: [...Args, ActionContext]) => Async<Result>;\n\n /**\n * Custom error handler. If provided, prevents default error re-throw.\n */\n onError?: (error: Error) => void | Promise<void>;\n\n /**\n * Custom success handler.\n */\n onSuccess?: (result: Result) => void | Promise<void>;\n\n /**\n * Optional identifier for this action (useful for debugging/analytics)\n */\n id?: string;\n\n name?: string;\n\n /**\n * Debounce delay in milliseconds. If specified, the action will only execute\n * after the specified delay has passed since the last call. Useful for search inputs\n * or other high-frequency events.\n *\n * @example\n * ```tsx\n * // Execute search 300ms after user stops typing\n * const search = useAction({ handler: search, debounce: 300 }, [])\n * ```\n */\n debounce?: number;\n\n /**\n * If true, the action will be executed once when the component mounts.\n *\n * @example\n * ```tsx\n * const fetchData = useAction({\n * handler: async () => await api.getData(),\n * runOnInit: true\n * }, []);\n * ```\n */\n runOnInit?: boolean;\n\n /**\n * If specified, the action will be executed periodically at the given interval.\n * The interval is specified as a DurationLike value (number in ms, Duration object, or [number, unit] tuple).\n *\n * @example\n * ```tsx\n * // Run every 5 seconds\n * const poll = useAction({\n * handler: async () => await api.poll(),\n * runEvery: 5000\n * }, []);\n * ```\n *\n * @example\n * ```tsx\n * // Run every 1 minute\n * const poll = useAction({\n * handler: async () => await api.poll(),\n * runEvery: [1, 'minute']\n * }, []);\n * ```\n */\n runEvery?: DurationLike;\n}\n\nexport interface UseActionReturn<Args extends any[], Result> {\n /**\n * Execute the action with the provided arguments.\n *\n * @example\n * ```tsx\n * const action = useAction({ handler: async (data) => { ... } }, []);\n * action.run(data);\n * ```\n */\n run: (...args: Args) => Promise<Result | undefined>;\n\n /**\n * Loading state - true when action is executing.\n */\n loading: boolean;\n\n /**\n * Error state - contains error if action failed, undefined otherwise.\n */\n error?: Error;\n\n /**\n * Cancel any pending debounced action or abort the current in-flight request.\n *\n * @example\n * ```tsx\n * const action = useAction({ ... }, []);\n *\n * <button onClick={action.cancel} disabled={!action.loading}>\n * Cancel\n * </button>\n * ```\n */\n cancel: () => void;\n\n /**\n * The result data from the last successful action execution.\n */\n result?: Result;\n}\n","import {\n type ClientScope,\n type HttpVirtualClient,\n LinkProvider,\n} from \"alepha/server/links\";\nimport { useInject } from \"./useInject.ts\";\n\n/**\n * Hook to get a virtual client for the specified scope.\n *\n * It's the React-hook version of `$client()`, from `AlephaServerLinks` module.\n */\nexport const useClient = <T extends object>(\n scope?: ClientScope,\n): HttpVirtualClient<T> => {\n return useInject(LinkProvider).client<T>(scope);\n};\n","import type { Async, Hook, Hooks } from \"alepha\";\nimport { type DependencyList, useEffect } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Allow subscribing to multiple Alepha events. See {@link Hooks} for available events.\n *\n * useEvents is fully typed to ensure correct event callback signatures.\n *\n * @example\n * ```tsx\n * useEvents(\n * {\n * \"react:transition:begin\": (ev) => {\n * console.log(\"Transition began to:\", ev.to);\n * },\n * \"react:transition:error\": {\n * priority: \"first\",\n * callback: (ev) => {\n * console.error(\"Transition error:\", ev.error);\n * },\n * },\n * },\n * [],\n * );\n * ```\n */\nexport const useEvents = (opts: UseEvents, deps: DependencyList) => {\n const alepha = useAlepha();\n\n useEffect(() => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n const subs: Function[] = [];\n for (const [name, hook] of Object.entries(opts)) {\n subs.push(alepha.events.on(name as any, hook as any));\n }\n\n return () => {\n for (const clear of subs) {\n clear();\n }\n };\n }, deps);\n};\n\ntype UseEvents = {\n [T in keyof Hooks]?: Hook<T> | ((payload: Hooks[T]) => Async<void>);\n};\n","import type { State, Static, TAtomObject } from \"alepha\";\nimport { Atom } from \"alepha\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Hook to access and mutate the Alepha state.\n */\nfunction useStore<T extends TAtomObject>(\n target: Atom<T>,\n defaultValue?: Static<T>,\n): UseStoreReturn<Static<T>>;\nfunction useStore<Key extends keyof State>(\n target: Key,\n defaultValue?: State[Key],\n): UseStoreReturn<State[Key]>;\nfunction useStore(target: any, defaultValue?: any): any {\n const alepha = useAlepha();\n\n useMemo(() => {\n if (defaultValue != null && alepha.store.get(target) == null) {\n alepha.store.set(target, defaultValue);\n }\n }, [defaultValue]);\n\n const [state, setState] = useState(alepha.store.get(target));\n\n useEffect(() => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n const key = target instanceof Atom ? target.key : target;\n\n return alepha.events.on(\"state:mutate\", (ev) => {\n if (ev.key === key) {\n setState(ev.value);\n }\n });\n }, []);\n\n return [\n state,\n (value: any) => {\n alepha.store.set(target, value);\n },\n ] as const;\n}\n\nexport type UseStoreReturn<T> = [T, (value: T) => void];\n\nexport { useStore };\n","import { $module } from \"alepha\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type * from \"./components/ClientOnly.tsx\";\nexport { default as ClientOnly } from \"./components/ClientOnly.tsx\";\nexport type * from \"./components/ErrorBoundary.tsx\";\nexport { default as ErrorBoundary } from \"./components/ErrorBoundary.tsx\";\nexport * from \"./contexts/AlephaContext.ts\";\nexport * from \"./contexts/AlephaProvider.tsx\";\nexport * from \"./hooks/useAction.ts\";\nexport * from \"./hooks/useAlepha.ts\";\nexport * from \"./hooks/useClient.ts\";\nexport * from \"./hooks/useEvents.ts\";\nexport * from \"./hooks/useInject.ts\";\nexport * from \"./hooks/useStore.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n /**\n * Fires when a user action is starting.\n * Action can be a form submission, a route transition, or a custom action.\n */\n \"react:action:begin\": {\n type: string;\n id?: string;\n };\n /**\n * Fires when a user action has succeeded.\n * Action can be a form submission, a route transition, or a custom action.\n */\n \"react:action:success\": {\n type: string;\n id?: string;\n };\n /**\n * Fires when a user action has failed.\n * Action can be a form submission, a route transition, or a custom action.\n */\n \"react:action:error\": {\n type: string;\n id?: string;\n error: Error;\n };\n /**\n * Fires when a user action has completed, regardless of success or failure.\n * Action can be a form submission, a route transition, or a custom action.\n */\n \"react:action:end\": {\n type: string;\n id?: string;\n };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * | Stability | Since | Runtime |\n * |-----------|-------|---------|\n * | 3 - stable | 0.2.0 | node, bun, workerd, browser, expo|\n *\n * Full-stack React framework with server-side rendering.\n *\n * **Features:**\n * - React page routes with type-safe params\n * - Async action handler with loading/error/cancel states\n * - Type-safe HTTP client access\n * - Dependency injection in components\n * - Global state management\n * - Router navigation methods\n * - Current route state access\n * - Check if path is active\n * - URL query parameters\n * - Access route schema\n * - Subscribe to Alepha events\n * - Type-safe form handling with validation\n * - Error handling wrapper component\n * - Client-side only rendering component\n * - Server-side rendering with hydration\n * - Automatic code splitting\n * - Event system for action tracking\n *\n * @module alepha.react\n */\nexport const AlephaReact = $module({\n name: \"alepha.react.core\",\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,MAAM,cAAc,UAA8C;CAChE,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;AAE7C,iBAAgB,WAAW,KAAK,EAAE,EAAE,CAAC;AAErC,KAAI,MAAM,SACR,QAAO,MAAM;AAGf,QAAO,UAAU,MAAM,WAAW,MAAM;;AAG1C,yBAAe;;;;;;;;;ACbf,IAAa,gBAAb,cAAmC,MAAM,UAGvC;CACA,YAAY,OAA2B;AACrC,QAAM,MAAM;AACZ,OAAK,QAAQ,EAAE;;;;;CAMjB,OAAO,yBAAyB,OAAkC;AAChE,SAAO,EACL,OACD;;;;;;CAOH,kBAAkB,OAAc,MAAuB;AACrD,MAAI,KAAK,MAAM,QACb,MAAK,MAAM,QAAQ,OAAO,KAAK;;CAInC,SAAoB;AAClB,MAAI,KAAK,MAAM,MACb,QAAO,KAAK,MAAM,SAAS,KAAK,MAAM,MAAM;AAG9C,SAAO,KAAK,MAAM;;;AAItB,4BAAe;;;;;;;AClEf,MAAa,gBAAgB,cAAkC,OAAU;;;;;;;;;ACSzE,MAAa,kBAAkB,UAA+B;CAC5D,MAAM,SAAS,cAAc,OAAO,QAAQ,EAAE,EAAE,CAAC;CAEjD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,UAA6B;AAEvD,iBAAgB;AACd,SACG,OAAO,CACP,WAAW,WAAW,KAAK,CAAC,CAC5B,OAAO,QAAQ,SAAS,IAAI,CAAC;IAC/B,CAAC,OAAO,CAAC;AAEZ,KAAI,MACF,QAAO,MAAM,QAAQ,MAAM;AAG7B,KAAI,CAAC,QACH,QAAO,MAAM,WAAW;AAG1B,QACE,oBAAC,cAAc;EAAS,OAAO;YAC5B,MAAM;GACgB;;;;;;;;;;;;;;;;;ACvB7B,MAAa,kBAA0B;CACrC,MAAM,SAAS,WAAW,cAAc;AACxC,KAAI,CAAC,OACH,OAAM,IAAI,YACR,mEACD;AAGH,QAAO;;;;;;;;;AChBT,MAAa,aAA+B,YAA2B;CACrE,MAAM,SAAS,WAAW;AAC1B,QAAO,cAAc,OAAO,OAAO,QAAQ,EAAE,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC+GlD,SAAgB,UACd,SACA,MAC+B;CAC/B,MAAM,SAAS,WAAW;CAC1B,MAAM,mBAAmB,UAAU,iBAAiB;CACpD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,UAA6B;CACvD,MAAM,CAAC,QAAQ,aAAa,UAA8B;CAC1D,MAAM,iBAAiB,OAAO,MAAM;CACpC,MAAM,mBAAmB,OAA4B,OAAU;CAC/D,MAAM,qBAAqB,OAAoC,OAAU;CACzE,MAAM,eAAe,OAAO,KAAK;CACjC,MAAM,cAAc,OAA6B,OAAU;AAG3D,iBAAgB;AACd,eAAa;AACX,gBAAa,UAAU;AAGvB,OAAI,iBAAiB,SAAS;AAC5B,qBAAiB,aAAa,iBAAiB,QAAQ;AACvD,qBAAiB,UAAU;;AAI7B,OAAI,YAAY,SAAS;AACvB,qBAAiB,cAAc,YAAY,QAAQ;AACnD,gBAAY,UAAU;;AAIxB,OAAI,mBAAmB,SAAS;AAC9B,uBAAmB,QAAQ,OAAO;AAClC,uBAAmB,UAAU;;;IAGhC,EAAE,CAAC;CAEN,MAAM,gBAAgB,YACpB,OAAO,GAAG,SAA4C;AAEpD,MAAI,eAAe,QACjB;AAIF,MAAI,mBAAmB,QACrB,oBAAmB,QAAQ,OAAO;EAIpC,MAAM,kBAAkB,IAAI,iBAAiB;AAC7C,qBAAmB,UAAU;AAE7B,iBAAe,UAAU;AACzB,aAAW,KAAK;AAChB,WAAS,OAAU;AAEnB,QAAM,OAAO,OAAO,KAAK,sBAAsB;GAC7C,MAAM;GACN,IAAI,QAAQ;GACb,CAAC;AAEF,MAAI;GAEF,MAAM,SAAS,MAAM,QAAQ,QAAQ,GAAG,MAAM,EAC5C,QAAQ,gBAAgB,QACzB,CAAQ;AAGT,aAAU,OAAiB;AAG3B,OAAI,CAAC,aAAa,WAAW,gBAAgB,OAAO,QAClD;AAGF,SAAM,OAAO,OAAO,KAAK,wBAAwB;IAC/C,MAAM;IACN,IAAI,QAAQ;IACb,CAAC;AAEF,OAAI,QAAQ,UACV,OAAM,QAAQ,UAAU,OAAO;AAGjC,UAAO;WACA,KAAK;AAEZ,OAAI,eAAe,SAAS,IAAI,SAAS,aACvC;AAIF,OAAI,CAAC,aAAa,QAChB;GAGF,MAAM,QAAQ;AACd,YAAS,MAAM;AAEf,SAAM,OAAO,OAAO,KAAK,sBAAsB;IAC7C,MAAM;IACN,IAAI,QAAQ;IACZ;IACD,CAAC;AAEF,OAAI,QAAQ,QACV,OAAM,QAAQ,QAAQ,MAAM;OAG5B,OAAM;YAEA;AACR,kBAAe,UAAU;AACzB,cAAW,MAAM;AAEjB,SAAM,OAAO,OAAO,KAAK,oBAAoB;IAC3C,MAAM;IACN,IAAI,QAAQ;IACb,CAAC;AAGF,OAAI,mBAAmB,YAAY,gBACjC,oBAAmB,UAAU;;IAInC;EAAC,GAAG;EAAM,QAAQ;EAAI,QAAQ;EAAS,QAAQ;EAAU,CAC1D;CAED,MAAM,UAAU,YACd,OAAO,GAAG,SAA4C;AACpD,MAAI,QAAQ,UAAU;AAEpB,OAAI,iBAAiB,QACnB,kBAAiB,aAAa,iBAAiB,QAAQ;AAIzD,UAAO,IAAI,SAAS,YAAY;AAC9B,qBAAiB,UAAU,iBAAiB,cAC1C,YAAY;AAEV,aADe,MAAM,cAAc,GAAG,KAAK,CAC5B;OAEjB,QAAQ,YAAY,EACrB;KACD;;AAGJ,SAAO,cAAc,GAAG,KAAK;IAE/B,CAAC,eAAe,QAAQ,SAAS,CAClC;CAED,MAAM,SAAS,kBAAkB;AAE/B,MAAI,iBAAiB,SAAS;AAC5B,oBAAiB,aAAa,iBAAiB,QAAQ;AACvD,oBAAiB,UAAU;;AAI7B,MAAI,mBAAmB,SAAS;AAC9B,sBAAmB,QAAQ,OAAO;AAClC,sBAAmB,UAAU;;AAI/B,MAAI,aAAa,SAAS;AACxB,kBAAe,UAAU;AACzB,cAAW,MAAM;;IAElB,EAAE,CAAC;AAGN,iBAAgB;AACd,MAAI,QAAQ,UACV,SAAQ,GAAI,EAAE,CAAS;IAExB,KAAK;AAGR,iBAAgB;AACd,MAAI,CAAC,QAAQ,SACX;AAIF,cAAY,UAAU,iBAAiB,qBAC/B,QAAQ,GAAI,EAAE,CAAS,EAC7B,QAAQ,UACR,KACD;AAGD,eAAa;AACX,OAAI,YAAY,SAAS;AACvB,qBAAiB,cAAc,YAAY,QAAQ;AACnD,gBAAY,UAAU;;;IAGzB,CAAC,SAAS,QAAQ,SAAS,CAAC;AAE/B,QAAO;EACL,KAAK;EACL;EACA;EACA;EACA;EACD;;;;;;;;;;AClUH,MAAa,aACX,UACyB;AACzB,QAAO,UAAU,aAAa,CAAC,OAAU,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACYjD,MAAa,aAAa,MAAiB,SAAyB;CAClE,MAAM,SAAS,WAAW;AAE1B,iBAAgB;AACd,MAAI,CAAC,OAAO,WAAW,CACrB;EAGF,MAAM,OAAmB,EAAE;AAC3B,OAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,KAAK,CAC7C,MAAK,KAAK,OAAO,OAAO,GAAG,MAAa,KAAY,CAAC;AAGvD,eAAa;AACX,QAAK,MAAM,SAAS,KAClB,QAAO;;IAGV,KAAK;;;;;AC7BV,SAAS,SAAS,QAAa,cAAyB;CACtD,MAAM,SAAS,WAAW;AAE1B,eAAc;AACZ,MAAI,gBAAgB,QAAQ,OAAO,MAAM,IAAI,OAAO,IAAI,KACtD,QAAO,MAAM,IAAI,QAAQ,aAAa;IAEvC,CAAC,aAAa,CAAC;CAElB,MAAM,CAAC,OAAO,YAAY,SAAS,OAAO,MAAM,IAAI,OAAO,CAAC;AAE5D,iBAAgB;AACd,MAAI,CAAC,OAAO,WAAW,CACrB;EAGF,MAAM,MAAM,kBAAkB,OAAO,OAAO,MAAM;AAElD,SAAO,OAAO,OAAO,GAAG,iBAAiB,OAAO;AAC9C,OAAI,GAAG,QAAQ,IACb,UAAS,GAAG,MAAM;IAEpB;IACD,EAAE,CAAC;AAEN,QAAO,CACL,QACC,UAAe;AACd,SAAO,MAAM,IAAI,QAAQ,MAAM;GAElC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACyCH,MAAa,cAAc,QAAQ,EACjC,MAAM,qBACP,CAAC"}
@@ -211,9 +211,9 @@ declare module "alepha" {
211
211
  }
212
212
  }
213
213
  /**
214
- * | type | quality | stability |
215
- * |------|---------|-----------|
216
- * | frontend | rare | stable |
214
+ * | Stability | Since | Runtime |
215
+ * |-----------|-------|---------|
216
+ * | 3 - stable | 0.12.0 | node, bun, workerd, browser|
217
217
  *
218
218
  * Type-safe forms with validation.
219
219
  *
@@ -410,9 +410,9 @@ const useForm = (options, deps = []) => {
410
410
  //#endregion
411
411
  //#region ../../src/react/form/index.ts
412
412
  /**
413
- * | type | quality | stability |
414
- * |------|---------|-----------|
415
- * | frontend | rare | stable |
413
+ * | Stability | Since | Runtime |
414
+ * |-----------|-------|---------|
415
+ * | 3 - stable | 0.12.0 | node, bun, workerd, browser|
416
416
  *
417
417
  * Type-safe forms with validation.
418
418
  *