alepha 0.21.2 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/README.md +0 -1
  2. package/dist/api/audits/index.browser.js.map +1 -1
  3. package/dist/api/audits/index.d.ts +393 -403
  4. package/dist/api/audits/index.d.ts.map +1 -1
  5. package/dist/api/audits/index.js +25 -56
  6. package/dist/api/audits/index.js.map +1 -1
  7. package/dist/api/files/index.browser.js +31 -1
  8. package/dist/api/files/index.browser.js.map +1 -1
  9. package/dist/api/files/index.d.ts +313 -208
  10. package/dist/api/files/index.d.ts.map +1 -1
  11. package/dist/api/files/index.js +152 -42
  12. package/dist/api/files/index.js.map +1 -1
  13. package/dist/api/jobs/index.browser.js +2 -2
  14. package/dist/api/jobs/index.browser.js.map +1 -1
  15. package/dist/api/jobs/index.d.ts +282 -285
  16. package/dist/api/jobs/index.d.ts.map +1 -1
  17. package/dist/api/jobs/index.js +39 -33
  18. package/dist/api/jobs/index.js.map +1 -1
  19. package/dist/api/keys/index.d.ts +217 -222
  20. package/dist/api/keys/index.d.ts.map +1 -1
  21. package/dist/api/keys/index.js.map +1 -1
  22. package/dist/api/notifications/index.browser.js.map +1 -1
  23. package/dist/api/notifications/index.d.ts +188 -195
  24. package/dist/api/notifications/index.d.ts.map +1 -1
  25. package/dist/api/notifications/index.js.map +1 -1
  26. package/dist/api/oauth/index.d.ts +71 -76
  27. package/dist/api/oauth/index.d.ts.map +1 -1
  28. package/dist/api/oauth/index.js.map +1 -1
  29. package/dist/api/organizations/index.browser.js.map +1 -1
  30. package/dist/api/organizations/index.d.ts +104 -109
  31. package/dist/api/organizations/index.d.ts.map +1 -1
  32. package/dist/api/organizations/index.js.map +1 -1
  33. package/dist/api/parameters/index.browser.js +43 -16
  34. package/dist/api/parameters/index.browser.js.map +1 -1
  35. package/dist/api/parameters/index.d.ts +488 -344
  36. package/dist/api/parameters/index.d.ts.map +1 -1
  37. package/dist/api/parameters/index.js +175 -35
  38. package/dist/api/parameters/index.js.map +1 -1
  39. package/dist/api/payments/index.d.ts +396 -402
  40. package/dist/api/payments/index.d.ts.map +1 -1
  41. package/dist/api/payments/index.js.map +1 -1
  42. package/dist/api/subscriptions/index.d.ts +644 -652
  43. package/dist/api/subscriptions/index.d.ts.map +1 -1
  44. package/dist/api/subscriptions/index.js +1 -1
  45. package/dist/api/subscriptions/index.js.map +1 -1
  46. package/dist/api/users/index.browser.js +7 -0
  47. package/dist/api/users/index.browser.js.map +1 -1
  48. package/dist/api/users/index.d.ts +1106 -1005
  49. package/dist/api/users/index.d.ts.map +1 -1
  50. package/dist/api/users/index.js +307 -64
  51. package/dist/api/users/index.js.map +1 -1
  52. package/dist/api/verifications/index.browser.js.map +1 -1
  53. package/dist/api/verifications/index.d.ts +137 -143
  54. package/dist/api/verifications/index.d.ts.map +1 -1
  55. package/dist/api/verifications/index.js.map +1 -1
  56. package/dist/background/index.d.ts +95 -0
  57. package/dist/background/index.d.ts.map +1 -0
  58. package/dist/background/index.js +121 -0
  59. package/dist/background/index.js.map +1 -0
  60. package/dist/background/index.workerd.js +110 -0
  61. package/dist/background/index.workerd.js.map +1 -0
  62. package/dist/batch/index.d.ts +5 -7
  63. package/dist/batch/index.d.ts.map +1 -1
  64. package/dist/batch/index.js.map +1 -1
  65. package/dist/bin/index.js.map +1 -1
  66. package/dist/bucket/index.d.ts +76 -54
  67. package/dist/bucket/index.d.ts.map +1 -1
  68. package/dist/bucket/index.js +58 -11
  69. package/dist/bucket/index.js.map +1 -1
  70. package/dist/bucket/index.workerd.js +200 -5
  71. package/dist/bucket/index.workerd.js.map +1 -1
  72. package/dist/cache/core/index.d.ts +7 -10
  73. package/dist/cache/core/index.d.ts.map +1 -1
  74. package/dist/cache/core/index.js.map +1 -1
  75. package/dist/cache/core/index.workerd.js.map +1 -1
  76. package/dist/cache/database/index.d.ts +22 -26
  77. package/dist/cache/database/index.d.ts.map +1 -1
  78. package/dist/cache/database/index.js.map +1 -1
  79. package/dist/cache/redis/index.d.ts +4 -7
  80. package/dist/cache/redis/index.d.ts.map +1 -1
  81. package/dist/cache/redis/index.js.map +1 -1
  82. package/dist/captcha/index.d.ts +3 -6
  83. package/dist/captcha/index.d.ts.map +1 -1
  84. package/dist/captcha/index.js.map +1 -1
  85. package/dist/cli/config/index.d.ts.map +1 -1
  86. package/dist/cli/config/index.js.map +1 -1
  87. package/dist/cli/core/index.d.ts +458 -249
  88. package/dist/cli/core/index.d.ts.map +1 -1
  89. package/dist/cli/core/index.js +372 -660
  90. package/dist/cli/core/index.js.map +1 -1
  91. package/dist/cli/devtools/index.d.ts +3 -5
  92. package/dist/cli/devtools/index.d.ts.map +1 -1
  93. package/dist/cli/devtools/index.js.map +1 -1
  94. package/dist/cli/i18n/index.d.ts +20 -17
  95. package/dist/cli/i18n/index.d.ts.map +1 -1
  96. package/dist/cli/i18n/index.js +45 -11
  97. package/dist/cli/i18n/index.js.map +1 -1
  98. package/dist/cli/platform/index.d.ts +126 -1342
  99. package/dist/cli/platform/index.d.ts.map +1 -1
  100. package/dist/cli/platform/index.js +136 -2374
  101. package/dist/cli/platform/index.js.map +1 -1
  102. package/dist/cli/platform-lib/index.d.ts +1472 -0
  103. package/dist/cli/platform-lib/index.d.ts.map +1 -0
  104. package/dist/cli/platform-lib/index.js +2660 -0
  105. package/dist/cli/platform-lib/index.js.map +1 -0
  106. package/dist/cli/vendor/index.d.ts +17 -21
  107. package/dist/cli/vendor/index.d.ts.map +1 -1
  108. package/dist/cli/vendor/index.js.map +1 -1
  109. package/dist/command/index.d.ts +20 -19
  110. package/dist/command/index.d.ts.map +1 -1
  111. package/dist/command/index.js +39 -10
  112. package/dist/command/index.js.map +1 -1
  113. package/dist/{containers → container}/core/index.d.ts +13 -15
  114. package/dist/container/core/index.d.ts.map +1 -0
  115. package/dist/{containers → container}/core/index.js +23 -14
  116. package/dist/container/core/index.js.map +1 -0
  117. package/dist/{containers → container}/core/index.workerd.js +37 -22
  118. package/dist/container/core/index.workerd.js.map +1 -0
  119. package/dist/core/index.browser.js +27 -1
  120. package/dist/core/index.browser.js.map +1 -1
  121. package/dist/core/index.d.ts +48 -24
  122. package/dist/core/index.d.ts.map +1 -1
  123. package/dist/core/index.js +27 -1
  124. package/dist/core/index.js.map +1 -1
  125. package/dist/core/index.native.js +27 -1
  126. package/dist/core/index.native.js.map +1 -1
  127. package/dist/core/index.workerd.js +27 -1
  128. package/dist/core/index.workerd.js.map +1 -1
  129. package/dist/crypto/index.browser.js.map +1 -1
  130. package/dist/crypto/index.d.ts +5 -8
  131. package/dist/crypto/index.d.ts.map +1 -1
  132. package/dist/crypto/index.js.map +1 -1
  133. package/dist/datetime/index.d.ts +3 -4
  134. package/dist/datetime/index.d.ts.map +1 -1
  135. package/dist/datetime/index.js.map +1 -1
  136. package/dist/email/brevo/index.d.ts +2 -4
  137. package/dist/email/brevo/index.d.ts.map +1 -1
  138. package/dist/email/brevo/index.js.map +1 -1
  139. package/dist/email/cloudflare/index.d.ts +20 -7
  140. package/dist/email/cloudflare/index.d.ts.map +1 -1
  141. package/dist/email/cloudflare/index.js +46 -9
  142. package/dist/email/cloudflare/index.js.map +1 -1
  143. package/dist/email/core/index.d.ts +6 -9
  144. package/dist/email/core/index.d.ts.map +1 -1
  145. package/dist/email/core/index.js.map +1 -1
  146. package/dist/email/core/index.workerd.js.map +1 -1
  147. package/dist/email/smtp/index.d.ts +10 -13
  148. package/dist/email/smtp/index.d.ts.map +1 -1
  149. package/dist/email/smtp/index.js +107 -32
  150. package/dist/email/smtp/index.js.map +1 -1
  151. package/dist/fake/index.d.ts +1 -2
  152. package/dist/fake/index.d.ts.map +1 -1
  153. package/dist/fake/index.js.map +1 -1
  154. package/dist/lock/core/index.d.ts +9 -14
  155. package/dist/lock/core/index.d.ts.map +1 -1
  156. package/dist/lock/core/index.js.map +1 -1
  157. package/dist/lock/redis/index.d.ts +2 -4
  158. package/dist/lock/redis/index.d.ts.map +1 -1
  159. package/dist/lock/redis/index.js.map +1 -1
  160. package/dist/logger/index.d.ts +105 -76
  161. package/dist/logger/index.d.ts.map +1 -1
  162. package/dist/logger/index.js +196 -174
  163. package/dist/logger/index.js.map +1 -1
  164. package/dist/mcp/index.d.ts +25 -20
  165. package/dist/mcp/index.d.ts.map +1 -1
  166. package/dist/mcp/index.js +23 -0
  167. package/dist/mcp/index.js.map +1 -1
  168. package/dist/orm/core/index.browser.js.map +1 -1
  169. package/dist/orm/core/index.bun.js +19 -1
  170. package/dist/orm/core/index.bun.js.map +1 -1
  171. package/dist/orm/core/index.d.ts +76 -62
  172. package/dist/orm/core/index.d.ts.map +1 -1
  173. package/dist/orm/core/index.js +20 -2
  174. package/dist/orm/core/index.js.map +1 -1
  175. package/dist/orm/postgres/index.bun.js.map +1 -1
  176. package/dist/orm/postgres/index.d.ts +28 -20
  177. package/dist/orm/postgres/index.d.ts.map +1 -1
  178. package/dist/orm/postgres/index.js.map +1 -1
  179. package/dist/queue/core/index.d.ts +12 -15
  180. package/dist/queue/core/index.d.ts.map +1 -1
  181. package/dist/queue/core/index.js.map +1 -1
  182. package/dist/queue/core/index.workerd.js.map +1 -1
  183. package/dist/queue/redis/index.d.ts +3 -5
  184. package/dist/queue/redis/index.d.ts.map +1 -1
  185. package/dist/queue/redis/index.js.map +1 -1
  186. package/dist/react/auth/index.browser.js +9 -2
  187. package/dist/react/auth/index.browser.js.map +1 -1
  188. package/dist/react/auth/index.d.ts +14 -9
  189. package/dist/react/auth/index.d.ts.map +1 -1
  190. package/dist/react/auth/index.js +9 -2
  191. package/dist/react/auth/index.js.map +1 -1
  192. package/dist/react/core/index.d.ts +7 -8
  193. package/dist/react/core/index.d.ts.map +1 -1
  194. package/dist/react/core/index.js +6 -3
  195. package/dist/react/core/index.js.map +1 -1
  196. package/dist/react/form/index.d.ts +2 -5
  197. package/dist/react/form/index.d.ts.map +1 -1
  198. package/dist/react/form/index.js +16 -15
  199. package/dist/react/form/index.js.map +1 -1
  200. package/dist/react/head/index.browser.js.map +1 -1
  201. package/dist/react/head/index.d.ts +2 -4
  202. package/dist/react/head/index.d.ts.map +1 -1
  203. package/dist/react/head/index.js.map +1 -1
  204. package/dist/react/i18n/index.d.ts +90 -11
  205. package/dist/react/i18n/index.d.ts.map +1 -1
  206. package/dist/react/i18n/index.js +147 -11
  207. package/dist/react/i18n/index.js.map +1 -1
  208. package/dist/react/intro/index.d.ts +1 -2
  209. package/dist/react/intro/index.d.ts.map +1 -1
  210. package/dist/react/intro/index.js +2 -2
  211. package/dist/react/intro/index.js.map +1 -1
  212. package/dist/react/router/index.browser.js +193 -24
  213. package/dist/react/router/index.browser.js.map +1 -1
  214. package/dist/react/router/index.d.ts +434 -222
  215. package/dist/react/router/index.d.ts.map +1 -1
  216. package/dist/react/router/index.js +249 -35
  217. package/dist/react/router/index.js.map +1 -1
  218. package/dist/react/sitemap/index.browser.js +35 -0
  219. package/dist/react/sitemap/index.browser.js.map +1 -0
  220. package/dist/react/sitemap/index.d.ts +92 -0
  221. package/dist/react/sitemap/index.d.ts.map +1 -0
  222. package/dist/react/sitemap/index.js +131 -0
  223. package/dist/react/sitemap/index.js.map +1 -0
  224. package/dist/react/testing/index.d.ts +1 -2
  225. package/dist/react/testing/index.d.ts.map +1 -1
  226. package/dist/react/testing/index.js +16 -17
  227. package/dist/react/testing/index.js.map +1 -1
  228. package/dist/react/ui/index.d.ts +20 -25
  229. package/dist/react/ui/index.d.ts.map +1 -1
  230. package/dist/react/ui/index.js.map +1 -1
  231. package/dist/redis/index.bun.js.map +1 -1
  232. package/dist/redis/index.d.ts +17 -19
  233. package/dist/redis/index.d.ts.map +1 -1
  234. package/dist/redis/index.js.map +1 -1
  235. package/dist/retry/index.d.ts +2 -4
  236. package/dist/retry/index.d.ts.map +1 -1
  237. package/dist/retry/index.js.map +1 -1
  238. package/dist/router/index.d.ts.map +1 -1
  239. package/dist/router/index.js.map +1 -1
  240. package/dist/scheduler/index.d.ts +10 -13
  241. package/dist/scheduler/index.d.ts.map +1 -1
  242. package/dist/scheduler/index.js.map +1 -1
  243. package/dist/scheduler/index.workerd.js.map +1 -1
  244. package/dist/security/index.browser.js.map +1 -1
  245. package/dist/security/index.d.ts +45 -48
  246. package/dist/security/index.d.ts.map +1 -1
  247. package/dist/security/index.js.map +1 -1
  248. package/dist/server/auth/index.browser.js.map +1 -1
  249. package/dist/server/auth/index.d.ts +272 -173
  250. package/dist/server/auth/index.d.ts.map +1 -1
  251. package/dist/server/auth/index.js +1608 -15
  252. package/dist/server/auth/index.js.map +1 -1
  253. package/dist/server/cookies/index.browser.js.map +1 -1
  254. package/dist/server/cookies/index.d.ts +20 -7
  255. package/dist/server/cookies/index.d.ts.map +1 -1
  256. package/dist/server/cookies/index.js +22 -3
  257. package/dist/server/cookies/index.js.map +1 -1
  258. package/dist/server/core/index.browser.js.map +1 -1
  259. package/dist/server/core/index.d.ts +106 -73
  260. package/dist/server/core/index.d.ts.map +1 -1
  261. package/dist/server/core/index.js +44 -0
  262. package/dist/server/core/index.js.map +1 -1
  263. package/dist/server/cors/index.d.ts +11 -14
  264. package/dist/server/cors/index.d.ts.map +1 -1
  265. package/dist/server/cors/index.js.map +1 -1
  266. package/dist/server/etag/index.d.ts +6 -9
  267. package/dist/server/etag/index.d.ts.map +1 -1
  268. package/dist/server/etag/index.js.map +1 -1
  269. package/dist/server/health/index.d.ts +18 -21
  270. package/dist/server/health/index.d.ts.map +1 -1
  271. package/dist/server/health/index.js.map +1 -1
  272. package/dist/server/links/index.browser.js +2 -0
  273. package/dist/server/links/index.browser.js.map +1 -1
  274. package/dist/server/links/index.d.ts +63 -67
  275. package/dist/server/links/index.d.ts.map +1 -1
  276. package/dist/server/links/index.js +2 -0
  277. package/dist/server/links/index.js.map +1 -1
  278. package/dist/server/metrics/index.d.ts +5 -7
  279. package/dist/server/metrics/index.d.ts.map +1 -1
  280. package/dist/server/metrics/index.js.map +1 -1
  281. package/dist/server/proxy/index.d.ts +3 -5
  282. package/dist/server/proxy/index.d.ts.map +1 -1
  283. package/dist/server/proxy/index.js.map +1 -1
  284. package/dist/server/rate-limit/index.d.ts +10 -13
  285. package/dist/server/rate-limit/index.d.ts.map +1 -1
  286. package/dist/server/rate-limit/index.js.map +1 -1
  287. package/dist/server/static/index.d.ts +3 -5
  288. package/dist/server/static/index.d.ts.map +1 -1
  289. package/dist/server/static/index.js.map +1 -1
  290. package/dist/server/swagger/index.d.ts +5 -8
  291. package/dist/server/swagger/index.d.ts.map +1 -1
  292. package/dist/server/swagger/index.js.map +1 -1
  293. package/dist/sms/index.d.ts +3 -5
  294. package/dist/sms/index.d.ts.map +1 -1
  295. package/dist/sms/index.js.map +1 -1
  296. package/dist/system/index.browser.js.map +1 -1
  297. package/dist/system/index.d.ts +2 -4
  298. package/dist/system/index.d.ts.map +1 -1
  299. package/dist/system/index.js.map +1 -1
  300. package/dist/system/index.workerd.js.map +1 -1
  301. package/dist/topic/core/index.d.ts +4 -6
  302. package/dist/topic/core/index.d.ts.map +1 -1
  303. package/dist/topic/core/index.js.map +1 -1
  304. package/dist/topic/redis/index.d.ts +5 -8
  305. package/dist/topic/redis/index.d.ts.map +1 -1
  306. package/dist/topic/redis/index.js.map +1 -1
  307. package/package.json +59 -23
  308. package/src/api/audits/__tests__/AuditService.spec.ts +18 -110
  309. package/src/api/audits/controllers/AdminAuditController.ts +14 -0
  310. package/src/api/audits/services/AuditService.ts +21 -88
  311. package/src/api/files/__tests__/FileService.spec.ts +207 -2
  312. package/src/api/files/index.ts +3 -0
  313. package/src/api/files/schemas/fileCreatorSummarySchema.ts +22 -0
  314. package/src/api/files/schemas/fileResourceSchema.ts +10 -1
  315. package/src/api/files/services/FileService.ts +170 -72
  316. package/src/api/jobs/__tests__/$job.spec.ts +24 -1
  317. package/src/api/jobs/index.ts +4 -3
  318. package/src/api/jobs/primitives/$job.ts +7 -3
  319. package/src/api/jobs/providers/DirectJobDispatcher.ts +17 -36
  320. package/src/api/jobs/providers/JobProvider.ts +53 -24
  321. package/src/api/jobs/schemas/jobConfigAtom.ts +1 -1
  322. package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +4 -1
  323. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +3 -1
  324. package/src/api/parameters/__tests__/$parameter.spec.ts +19 -2
  325. package/src/api/parameters/audits/ParameterAudits.ts +17 -0
  326. package/src/api/parameters/controllers/AdminParameterController.ts +95 -19
  327. package/src/api/parameters/index.ts +3 -0
  328. package/src/api/parameters/schemas/activateParameterBodySchema.ts +3 -3
  329. package/src/api/parameters/schemas/createParameterVersionBodySchema.ts +3 -2
  330. package/src/api/parameters/schemas/parameterCreatorSummarySchema.ts +25 -0
  331. package/src/api/parameters/schemas/parameterResponseSchema.ts +5 -0
  332. package/src/api/parameters/schemas/rollbackParameterBodySchema.ts +4 -2
  333. package/src/api/parameters/services/ParameterProvider.ts +69 -6
  334. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +1 -1
  335. package/src/api/users/__tests__/AdminSessionController.spec.ts +37 -0
  336. package/src/api/users/audits/SessionAudits.ts +33 -0
  337. package/src/api/users/audits/UserAudits.ts +19 -43
  338. package/src/api/users/controllers/AdminUserController.ts +66 -1
  339. package/src/api/users/controllers/RealmController.ts +1 -0
  340. package/src/api/users/entities/sessions.ts +6 -0
  341. package/src/api/users/entities/users.ts +2 -0
  342. package/src/api/users/index.ts +9 -1
  343. package/src/api/users/primitives/$realm.ts +29 -0
  344. package/src/api/users/providers/RealmProvider.ts +15 -0
  345. package/src/api/users/schemas/realmConfigSchema.ts +14 -0
  346. package/src/api/users/schemas/sessionResourceSchema.ts +16 -0
  347. package/src/api/users/schemas/updateUserSchema.ts +1 -8
  348. package/src/api/users/schemas/userQuerySchema.ts +7 -0
  349. package/src/api/users/services/CredentialService.ts +15 -6
  350. package/src/api/users/services/IdentityService.ts +2 -1
  351. package/src/api/users/services/RegistrationService.ts +2 -1
  352. package/src/api/users/services/SessionCrudService.ts +19 -2
  353. package/src/api/users/services/SessionService.ts +39 -19
  354. package/src/api/users/services/UserService.ts +106 -8
  355. package/src/background/__tests__/BackgroundTaskProvider.spec.ts +96 -0
  356. package/src/background/index.ts +37 -0
  357. package/src/background/index.workerd.ts +28 -0
  358. package/src/background/providers/BackgroundTaskProvider.ts +70 -0
  359. package/src/background/providers/WorkerdBackgroundTaskProvider.ts +43 -0
  360. package/src/bucket/__tests__/$bucket.spec.ts +18 -0
  361. package/src/bucket/__tests__/LocalFileStorageProvider.spec.ts +5 -0
  362. package/src/bucket/__tests__/MemoryFileStorageProvider.spec.ts +5 -0
  363. package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +23 -4
  364. package/src/bucket/__tests__/shared.ts +30 -0
  365. package/src/bucket/index.ts +5 -5
  366. package/src/bucket/index.workerd.ts +11 -4
  367. package/src/bucket/primitives/$bucket.ts +27 -0
  368. package/src/bucket/providers/FileStorageProvider.ts +13 -0
  369. package/src/bucket/providers/LocalFileStorageProvider.ts +17 -1
  370. package/src/bucket/providers/MemoryFileStorageProvider.ts +7 -0
  371. package/src/bucket/providers/{CloudflareR2Provider.ts → R2FileStorageProvider.ts} +10 -1
  372. package/src/bucket/providers/{NodeS3BucketProvider.ts → S3FileStorageProvider.ts} +27 -5
  373. package/src/cli/core/__tests__/BuildDockerTask.spec.ts +25 -1
  374. package/src/cli/core/__tests__/init.spec.ts +0 -219
  375. package/src/cli/core/atoms/buildOptions.ts +0 -12
  376. package/src/cli/core/commands/__tests__/BuildCommand.spec.ts +43 -0
  377. package/src/cli/core/commands/build.ts +105 -37
  378. package/src/cli/core/commands/init.ts +0 -12
  379. package/src/cli/core/commands/pack.ts +133 -0
  380. package/src/cli/core/index.ts +3 -3
  381. package/src/cli/core/providers/ViteDevServerProvider.ts +40 -16
  382. package/src/cli/core/services/PackageManagerUtils.ts +0 -16
  383. package/src/cli/core/services/ProjectScaffolder.ts +29 -291
  384. package/src/cli/core/tasks/BuildCloudflareTask.ts +382 -56
  385. package/src/cli/core/tasks/BuildDockerTask.ts +33 -3
  386. package/src/cli/core/tasks/BuildPrerenderTask.ts +44 -7
  387. package/src/cli/core/tasks/BuildTask.ts +34 -0
  388. package/src/cli/core/templates/apiIndexTs.ts +1 -22
  389. package/src/cli/core/templates/mainCss.ts +0 -1
  390. package/src/cli/core/templates/webAppRouterTs.ts +0 -99
  391. package/src/cli/core/templates/webIndexTs.ts +1 -22
  392. package/src/cli/i18n/__tests__/I18nCheckService.spec.ts +48 -0
  393. package/src/cli/i18n/services/I18nCheckService.ts +65 -11
  394. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +5 -3
  395. package/src/cli/platform/commands/SecretsCommand.ts +8 -6
  396. package/src/cli/platform/commands/platform.ts +192 -46
  397. package/src/cli/platform/index.ts +12 -52
  398. package/src/cli/{platform → platform-lib}/__tests__/CloudflareAdapter.spec.ts +426 -169
  399. package/src/cli/{platform → platform-lib}/__tests__/NamingService.spec.ts +91 -4
  400. package/src/cli/{platform → platform-lib}/__tests__/VercelAdapter.spec.ts +56 -85
  401. package/src/cli/{platform → platform-lib}/adapters/CloudflareAdapter.ts +519 -190
  402. package/src/cli/{platform → platform-lib}/adapters/PlatformAdapter.ts +62 -35
  403. package/src/cli/{platform → platform-lib}/adapters/VercelAdapter.ts +6 -10
  404. package/src/cli/{platform → platform-lib}/atoms/platformOptions.ts +34 -1
  405. package/src/cli/platform-lib/index.ts +67 -0
  406. package/src/cli/platform-lib/services/NamingService.ts +136 -0
  407. package/src/cli/{platform → platform-lib}/services/PlatformInspector.ts +60 -13
  408. package/src/cli/{platform → platform-lib}/services/PlatformOrchestrator.ts +54 -43
  409. package/src/cli/{platform → platform-lib}/services/WranglerApi.ts +4 -2
  410. package/src/command/__tests__/Runner.spec.ts +20 -0
  411. package/src/command/helpers/EnvUtils.ts +19 -3
  412. package/src/command/helpers/Runner.ts +12 -2
  413. package/src/command/providers/CliProvider.ts +34 -1
  414. package/src/{containers → container}/core/__tests__/$container.spec.ts +5 -5
  415. package/src/{containers → container}/core/index.ts +4 -4
  416. package/src/{containers → container}/core/index.workerd.ts +19 -3
  417. package/src/{containers → container}/core/primitives/$container.ts +1 -1
  418. package/src/{containers → container}/core/providers/CloudflareContainerProvider.ts +17 -19
  419. package/src/{containers → container}/core/providers/ContainerProvider.ts +16 -2
  420. package/src/{containers → container}/core/providers/MockContainerProvider.ts +1 -1
  421. package/src/core/Alepha.ts +49 -1
  422. package/src/core/__tests__/$env.spec.ts +42 -0
  423. package/src/core/__tests__/dump.spec.ts +47 -0
  424. package/src/email/cloudflare/__tests__/CloudflareEmailProvider.spec.ts +42 -10
  425. package/src/email/cloudflare/index.ts +14 -5
  426. package/src/email/cloudflare/providers/CloudflareEmailProvider.ts +54 -9
  427. package/src/logger/__tests__/Logger.spec.ts +55 -0
  428. package/src/logger/index.ts +13 -0
  429. package/src/logger/services/Logger.ts +31 -1
  430. package/src/mcp/__tests__/McpServerProvider.spec.ts +71 -0
  431. package/src/mcp/providers/McpServerProvider.ts +55 -0
  432. package/src/orm/__tests__/orm-showcase-tests.ts +27 -0
  433. package/src/orm/__tests__/orm-showcase.spec.ts +12 -0
  434. package/src/orm/core/interfaces/PgQuery.ts +4 -1
  435. package/src/orm/core/services/Repository.ts +27 -11
  436. package/src/react/auth/hooks/useAuth.ts +10 -5
  437. package/src/react/core/__tests__/useQuery.browser.spec.tsx +25 -0
  438. package/src/react/core/hooks/useAction.ts +14 -3
  439. package/src/react/core/hooks/useQuery.ts +24 -4
  440. package/src/react/form/__tests__/FormModel-submit-loading.spec.ts +71 -0
  441. package/src/react/form/__tests__/form-submitting-reactive.browser.spec.tsx +96 -0
  442. package/src/react/form/services/FormModel.ts +57 -39
  443. package/src/react/i18n/__tests__/I18nProvider.spec.ts +89 -0
  444. package/src/react/i18n/__tests__/locale-routing.spec.ts +107 -0
  445. package/src/react/i18n/components/Translate.tsx +47 -0
  446. package/src/react/i18n/index.ts +2 -0
  447. package/src/react/i18n/providers/I18nProvider.ts +171 -12
  448. package/src/react/intro/components/GettingStartedAdminSlide.tsx +2 -2
  449. package/src/react/router/__tests__/$page.spec.tsx +3 -2
  450. package/src/react/router/__tests__/RouterLocaleProvider.spec.ts +127 -0
  451. package/src/react/router/__tests__/page-can.spec.ts +18 -13
  452. package/src/react/router/hooks/useQueryParams.ts +114 -14
  453. package/src/react/router/index.browser.ts +4 -0
  454. package/src/react/router/index.shared.ts +1 -0
  455. package/src/react/router/index.ts +9 -0
  456. package/src/react/router/primitives/$page.ts +85 -4
  457. package/src/react/router/providers/ReactBrowserRouterProvider.ts +18 -8
  458. package/src/react/router/providers/ReactPageProvider.ts +12 -1
  459. package/src/react/router/providers/ReactServerProvider.ts +96 -14
  460. package/src/react/router/providers/RootComponentsProvider.ts +13 -0
  461. package/src/react/router/providers/RouterLocaleProvider.ts +125 -0
  462. package/src/react/router/providers/__tests__/RootComponentsProvider.spec.ts +15 -0
  463. package/src/react/router/providers/__tests__/rootComponents.ssr.browser.spec.tsx +67 -0
  464. package/src/react/sitemap/__tests__/$sitemap.spec.ts +131 -0
  465. package/src/react/sitemap/index.browser.ts +21 -0
  466. package/src/react/sitemap/index.ts +25 -0
  467. package/src/react/sitemap/primitives/$sitemap.browser.ts +26 -0
  468. package/src/react/sitemap/primitives/$sitemap.ts +196 -0
  469. package/src/react/ui/services/SchemaControl.ts +3 -4
  470. package/src/server/auth/__tests__/appleClientSecret.spec.ts +34 -0
  471. package/src/server/auth/__tests__/authFederationClient.spec.ts +40 -0
  472. package/src/server/auth/__tests__/federationAssertion.spec.ts +146 -0
  473. package/src/server/auth/__tests__/federationRedirectReplay.spec.ts +44 -0
  474. package/src/server/auth/helpers/appleClientSecret.ts +24 -0
  475. package/src/server/auth/helpers/federationAssertion.ts +74 -0
  476. package/src/server/auth/helpers/jtiReplayGuard.ts +41 -0
  477. package/src/server/auth/helpers/safeRedirectPath.ts +19 -0
  478. package/src/server/auth/index.ts +4 -0
  479. package/src/server/auth/primitives/$authFederationBroker.ts +273 -0
  480. package/src/server/auth/primitives/$authFederationClient.ts +89 -0
  481. package/src/server/auth/providers/ServerAuthProvider.ts +18 -4
  482. package/src/server/cookies/__tests__/ServerCookiesProvider.spec.ts +70 -0
  483. package/src/server/cookies/providers/ServerCookiesProvider.ts +23 -3
  484. package/src/server/core/interfaces/ServerRequest.ts +8 -0
  485. package/src/server/core/primitives/$route.ts +27 -0
  486. package/src/server/core/providers/ServerMultipartProvider.ts +19 -0
  487. package/src/server/links/providers/LinkProvider.ts +10 -0
  488. package/dist/containers/core/index.d.ts.map +0 -1
  489. package/dist/containers/core/index.js.map +0 -1
  490. package/dist/containers/core/index.workerd.js.map +0 -1
  491. package/src/cli/core/tasks/BuildSitemapTask.ts +0 -130
  492. package/src/cli/core/templates/componentsJsonTs.ts +0 -39
  493. package/src/cli/core/templates/saasAdminLayoutTsx.ts +0 -77
  494. package/src/cli/core/templates/saasAdminPagesTsx.ts +0 -26
  495. package/src/cli/core/templates/saasAuthLayoutTsx.ts +0 -22
  496. package/src/cli/core/templates/saasAuthPagesTsx.ts +0 -62
  497. package/src/cli/core/templates/saasRealmProviderTs.ts +0 -52
  498. package/src/cli/platform/services/NamingService.ts +0 -54
  499. /package/dist/orm/core/{chunk-o8xxKEmq.js → chunk-B4FMCO8f.js} +0 -0
  500. /package/dist/react/testing/{chunk-6Ep1yQYe.js → chunk-BpyX8vjI.js} +0 -0
  501. /package/src/cli/{platform → platform-lib}/__tests__/GitHubSecretStore.spec.ts +0 -0
  502. /package/src/cli/{platform → platform-lib}/__tests__/PlatformCacheProvider.spec.ts +0 -0
  503. /package/src/cli/{platform → platform-lib}/__tests__/PlatformInspector.spec.ts +0 -0
  504. /package/src/cli/{platform → platform-lib}/__tests__/PlatformOrchestrator.spec.ts +0 -0
  505. /package/src/cli/{platform → platform-lib}/__tests__/SecretFilterService.spec.ts +0 -0
  506. /package/src/cli/{platform → platform-lib}/__tests__/detectResources.spec.ts +0 -0
  507. /package/src/cli/{platform → platform-lib}/providers/GitHubSecretStore.ts +0 -0
  508. /package/src/cli/{platform → platform-lib}/providers/MemorySecretStore.ts +0 -0
  509. /package/src/cli/{platform → platform-lib}/providers/PlatformCacheProvider.ts +0 -0
  510. /package/src/cli/{platform → platform-lib}/providers/SecretStoreProvider.ts +0 -0
  511. /package/src/cli/{platform → platform-lib}/schemas/cloudflare.ts +0 -0
  512. /package/src/cli/{platform → platform-lib}/schemas/platform.ts +0 -0
  513. /package/src/cli/{platform → platform-lib}/schemas/vercel.ts +0 -0
  514. /package/src/cli/{platform → platform-lib}/services/CloudflareApi.ts +0 -0
  515. /package/src/cli/{platform → platform-lib}/services/SecretFilterService.ts +0 -0
  516. /package/src/cli/{platform → platform-lib}/services/VercelApi.ts +0 -0
  517. /package/src/cli/{platform → platform-lib}/services/VercelCli.ts +0 -0
  518. /package/src/{containers → container}/core/interfaces/ContainerOptions.ts +0 -0
  519. /package/src/{containers → container}/core/providers/NodeContainerProvider.ts +0 -0
@@ -1,8 +1,10 @@
1
1
  import { $hook, $inject, $module, Alepha, KIND, Primitive, TypeBoxError, TypeProvider, createPrimitive, t } from "alepha";
2
2
  import { DateTimeProvider } from "alepha/datetime";
3
3
  import { $logger } from "alepha/logger";
4
+ import { RouterLocaleProvider } from "alepha/react/router";
4
5
  import { $cookie } from "alepha/server/cookies";
5
6
  import { useInject, useStore } from "alepha/react";
7
+ import { Fragment, jsx } from "react/jsx-runtime";
6
8
  //#region ../../src/react/i18n/providers/I18nProvider.ts
7
9
  var I18nProvider = class {
8
10
  log = $logger();
@@ -14,7 +16,42 @@ var I18nProvider = class {
14
16
  ttl: [1, "year"]
15
17
  });
16
18
  registry = [];
17
- options = { fallbackLang: "en" };
19
+ options = {
20
+ fallbackLang: "en",
21
+ /**
22
+ * When true (the default), the UI language for a first-time visitor (one
23
+ * with no `lang` cookie) is detected server-side from the `Accept-Language`
24
+ * header. A manually-selected language (the cookie) always takes
25
+ * precedence, so this never overrides an explicit user choice. Set to false
26
+ * to always start in `fallbackLang` regardless of the browser's preferred
27
+ * language.
28
+ */
29
+ autoDetect: true,
30
+ /**
31
+ * URL strategy for languages:
32
+ * - `"none"` (default): language lives in a cookie; URLs are not localized.
33
+ * - `"prefix"`: each non-default language gets a path prefix (`/fr/about`),
34
+ * making every language a distinct, crawlable URL for SEO. The default
35
+ * language (`fallbackLang`) stays unprefixed. Requires the router module.
36
+ * The URL becomes the source of truth for language (it wins over the
37
+ * cookie / `Accept-Language`), and there is no automatic redirect.
38
+ */
39
+ routing: "none"
40
+ };
41
+ /**
42
+ * Lazily-resolved locale-prefix router integration. Present only when both
43
+ * the router module is registered AND `routing: "prefix"` was configured —
44
+ * otherwise i18n stays fully standalone.
45
+ */
46
+ localeProviderResolved = false;
47
+ localeProviderRef;
48
+ get localeProvider() {
49
+ if (!this.localeProviderResolved) {
50
+ this.localeProviderResolved = true;
51
+ if (this.alepha.has(RouterLocaleProvider)) this.localeProviderRef = this.alepha.inject(RouterLocaleProvider);
52
+ }
53
+ return this.localeProviderRef;
54
+ }
18
55
  dateFormat = new Intl.DateTimeFormat(this.lang);
19
56
  numberFormat = new Intl.NumberFormat(this.lang);
20
57
  get languages() {
@@ -25,19 +62,69 @@ var I18nProvider = class {
25
62
  constructor() {
26
63
  this.refreshLocale();
27
64
  }
65
+ /**
66
+ * Configure locale-prefix routing on the router, before the SSR routes are
67
+ * registered (`priority: "first"` runs ahead of the router's own `configure`
68
+ * hook). No-op unless `routing: "prefix"` and the router module is present.
69
+ */
70
+ onConfigure = $hook({
71
+ on: "configure",
72
+ priority: "first",
73
+ handler: () => {
74
+ const localeProvider = this.localeProvider;
75
+ if (this.options.routing === "prefix" && localeProvider) localeProvider.configure({
76
+ enabled: true,
77
+ defaultLocale: this.fallbackLang,
78
+ locales: this.languages
79
+ });
80
+ }
81
+ });
28
82
  onRender = $hook({
29
83
  on: "server:onRequest",
30
84
  priority: "last",
31
85
  handler: async ({ request }) => {
32
- this.alepha.store.set("alepha.react.i18n.lang", this.cookie.get(request));
86
+ this.alepha.store.set("alepha.react.i18n.lang", this.resolveRequestLang(this.cookie.get(request), request.language, this.detectUrlLocale(request.url?.pathname)));
33
87
  }
34
88
  });
89
+ /**
90
+ * Detects the language carried by the request URL when `routing: "prefix"` is
91
+ * active. Returns the locale for any URL (the prefixed one for `/fr/...`, the
92
+ * default for an unprefixed path), or `undefined` when prefix routing is off.
93
+ */
94
+ detectUrlLocale(pathname) {
95
+ const localeProvider = this.localeProvider;
96
+ if (localeProvider?.enabled && pathname) return localeProvider.detect(pathname).locale || void 0;
97
+ }
98
+ /**
99
+ * Resolves the UI language for an incoming server request.
100
+ *
101
+ * Priority:
102
+ * 0. the URL locale prefix (`routing: "prefix"`) — the URL is the source of
103
+ * truth and wins over everything, with no redirect;
104
+ * 1. the `lang` cookie — a language the user manually selected;
105
+ * 2. the `Accept-Language` header (when `autoDetect` is enabled) — but only
106
+ * when the detected language is actually registered, so we never switch to
107
+ * a locale we have no dictionary for. A region-qualified header (`en-US`)
108
+ * matches an exact registration first, then its base language (`en`);
109
+ * 3. `fallbackLang`.
110
+ */
111
+ resolveRequestLang(cookieLang, headerLang, urlLocale) {
112
+ if (urlLocale) return urlLocale;
113
+ if (cookieLang) return cookieLang;
114
+ if (this.options.autoDetect && headerLang) {
115
+ const registered = this.languages;
116
+ for (const candidate of [headerLang, headerLang.split("-")[0]]) if (registered.includes(candidate)) return candidate;
117
+ }
118
+ return this.fallbackLang;
119
+ }
35
120
  onStart = $hook({
36
121
  on: "start",
37
122
  handler: async () => {
38
123
  if (this.alepha.isBrowser()) {
39
- const cookieLang = this.cookie.get();
40
- if (cookieLang) this.alepha.store.set("alepha.react.i18n.lang", cookieLang);
124
+ if (!this.localeProvider?.enabled) {
125
+ const cookieLang = this.cookie.get();
126
+ if (cookieLang) this.alepha.store.set("alepha.react.i18n.lang", cookieLang);
127
+ }
41
128
  for (const item of this.registry) if (item.lang === this.lang || item.lang === this.fallbackLang) {
42
129
  this.log.trace("Loading language", {
43
130
  lang: item.lang,
@@ -57,20 +144,38 @@ var I18nProvider = class {
57
144
  this.dateTimeProvider.setLocale(this.lang);
58
145
  TypeProvider.setLocale(this.lang);
59
146
  }
60
- setLang = async (lang) => {
147
+ /**
148
+ * Activates a language: lazily loads its dictionaries (browser), updates the
149
+ * lang state, and refreshes the locale-bound formatters. Does NOT persist a
150
+ * cookie or navigate — that is the caller's concern.
151
+ */
152
+ applyLang = async (lang) => {
61
153
  if (this.alepha.isBrowser()) {
62
- for (const item of this.registry) if (lang === item.lang) {
63
- if (Object.keys(item.translations).length > 0) continue;
64
- item.translations = await item.loader();
65
- }
66
- this.cookie.set(lang);
154
+ for (const item of this.registry) if (lang === item.lang && Object.keys(item.translations).length === 0) item.translations = await item.loader();
67
155
  }
68
156
  this.alepha.store.set("alepha.react.i18n.lang", lang);
69
157
  this.refreshLocale();
70
158
  };
159
+ setLang = async (lang) => {
160
+ const localeProvider = this.localeProvider;
161
+ if (localeProvider?.enabled) {
162
+ const { ReactRouter } = await import("alepha/react/router");
163
+ const router = this.alepha.inject(ReactRouter);
164
+ const canonical = localeProvider.detect(router.pathname).pathname;
165
+ await router.push(localeProvider.withPrefix(canonical, lang));
166
+ return;
167
+ }
168
+ await this.applyLang(lang);
169
+ if (this.alepha.isBrowser()) this.cookie.set(lang);
170
+ };
71
171
  mutate = $hook({
72
172
  on: "state:mutate",
73
173
  handler: async ({ key, value }) => {
174
+ if (key === "alepha.react.router.locale" && this.localeProvider?.enabled) {
175
+ const lang = value || this.fallbackLang;
176
+ if (lang !== this.lang) await this.applyLang(lang);
177
+ return;
178
+ }
74
179
  if (key === "alepha.react.i18n.lang" && this.alepha.isBrowser()) {
75
180
  let hasChanged = false;
76
181
  for (const item of this.registry) if (value === item.lang) {
@@ -202,6 +307,37 @@ const Localize = (props) => {
202
307
  return useI18n().l(props.value, props);
203
308
  };
204
309
  //#endregion
310
+ //#region ../../src/react/i18n/components/Translate.tsx
311
+ /**
312
+ * Renders a translated dictionary entry as a reactive text node.
313
+ *
314
+ * `tr(...)` is a hook, so it can only be called inside a component body. Use
315
+ * `<Translate>` wherever a *node* is expected instead — a prop, a `children`
316
+ * slot, or route metadata such as a nav-shell `label`. Because it subscribes to
317
+ * {@link useI18n} it re-renders on a language switch, unlike a string resolved
318
+ * once at module/initialisation time.
319
+ *
320
+ * Sibling of {@link Localize}: `Localize` formats a value (number, date,
321
+ * error); `Translate` looks up a key.
322
+ *
323
+ * @example
324
+ * ```tsx
325
+ * nav: { label: <Translate k="admin.nav.users" /> }
326
+ * <Translate k="cart.items" args={[String(count)]} fallback="Items" />
327
+ * ```
328
+ */
329
+ const Translate = (props) => {
330
+ const { tr } = useI18n();
331
+ return /* @__PURE__ */ jsx(Fragment, { children: tr(props.k, {
332
+ args: props.args,
333
+ default: props.fallback
334
+ }) });
335
+ };
336
+ /**
337
+ * Terse alias for {@link Translate} — handy in dense JSX such as nav labels.
338
+ */
339
+ const Tr = Translate;
340
+ //#endregion
205
341
  //#region ../../src/react/i18n/index.ts
206
342
  /**
207
343
  * Multi-language support.
@@ -219,6 +355,6 @@ const AlephaReactI18n = $module({
219
355
  services: [I18nProvider]
220
356
  });
221
357
  //#endregion
222
- export { $dictionary, AlephaReactI18n, DictionaryPrimitive, I18nProvider, Localize, useI18n };
358
+ export { $dictionary, AlephaReactI18n, DictionaryPrimitive, I18nProvider, Localize, Tr, Translate, useI18n };
223
359
 
224
360
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/i18n/providers/I18nProvider.ts","../../../src/react/i18n/primitives/$dictionary.ts","../../../src/react/i18n/hooks/useI18n.ts","../../../src/react/i18n/components/Localize.tsx","../../../src/react/i18n/index.ts"],"sourcesContent":["import { $hook, $inject, Alepha, TypeBoxError, TypeProvider, t } from \"alepha\";\nimport { type DateTime, DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $cookie } from \"alepha/server/cookies\";\nimport type { ServiceDictionary } from \"../hooks/useI18n.ts\";\n\nexport class I18nProvider<\n S extends object,\n K extends keyof ServiceDictionary<S>,\n> {\n protected log = $logger();\n protected alepha = $inject(Alepha);\n protected dateTimeProvider = $inject(DateTimeProvider);\n\n protected cookie = $cookie({\n name: \"lang\",\n schema: t.text(),\n ttl: [1, \"year\"],\n });\n\n public readonly registry: Array<{\n target: string;\n name: string;\n lang: string;\n loader: () => Promise<Record<string, string>>;\n translations: Record<string, string>;\n }> = [];\n\n options = {\n fallbackLang: \"en\",\n };\n\n public dateFormat: { format: (value: Date) => string } =\n new Intl.DateTimeFormat(this.lang);\n\n public numberFormat: { format: (value: number) => string } =\n new Intl.NumberFormat(this.lang);\n\n public get languages() {\n const languages = new Set<string>();\n\n for (const item of this.registry) {\n languages.add(item.lang);\n }\n\n return Array.from(languages);\n }\n\n constructor() {\n this.refreshLocale();\n }\n\n protected readonly onRender = $hook({\n on: \"server:onRequest\",\n priority: \"last\",\n handler: async ({ request }) => {\n this.alepha.store.set(\"alepha.react.i18n.lang\", this.cookie.get(request));\n },\n });\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n if (this.alepha.isBrowser()) {\n // get cookie lang\n const cookieLang = this.cookie.get();\n if (cookieLang) {\n this.alepha.store.set(\"alepha.react.i18n.lang\", cookieLang);\n }\n\n for (const item of this.registry) {\n if (item.lang === this.lang || item.lang === this.fallbackLang) {\n this.log.trace(\"Loading language\", {\n lang: item.lang,\n name: item.name,\n target: item.target,\n });\n item.translations = await item.loader();\n }\n }\n return;\n }\n\n for (const item of this.registry) {\n item.translations = await item.loader();\n }\n },\n });\n\n protected refreshLocale() {\n this.numberFormat = new Intl.NumberFormat(this.lang);\n this.dateFormat = new Intl.DateTimeFormat(this.lang);\n this.dateTimeProvider.setLocale(this.lang);\n TypeProvider.setLocale(this.lang);\n }\n\n public setLang = async (lang: string) => {\n if (this.alepha.isBrowser()) {\n for (const item of this.registry) {\n if (lang === item.lang) {\n if (Object.keys(item.translations).length > 0) {\n continue; // already loaded\n }\n item.translations = await item.loader();\n }\n }\n this.cookie.set(lang);\n }\n\n this.alepha.store.set(\"alepha.react.i18n.lang\", lang);\n this.refreshLocale();\n };\n\n protected readonly mutate = $hook({\n on: \"state:mutate\",\n handler: async ({ key, value }) => {\n if (key === \"alepha.react.i18n.lang\" && this.alepha.isBrowser()) {\n let hasChanged = false;\n for (const item of this.registry) {\n if (value === item.lang) {\n if (Object.keys(item.translations).length > 0) {\n continue; // already loaded\n }\n item.translations = await item.loader();\n hasChanged = true;\n }\n }\n\n this.refreshLocale();\n\n if (hasChanged) {\n this.alepha.store.set(\"alepha.react.i18n.lang\", value);\n }\n }\n },\n });\n\n public get fallbackLang(): string {\n const configured = this.options.fallbackLang;\n const hasDict = this.registry.some((item) => item.lang === configured);\n if (hasDict) {\n return configured;\n }\n return this.registry[0]?.lang ?? configured;\n }\n\n public get lang(): string {\n return this.alepha.store.get(\"alepha.react.i18n.lang\") || this.fallbackLang;\n }\n\n public translate = (key: string, args: string[] = []) => {\n for (const item of this.registry) {\n if (item.lang === this.lang) {\n if (item.translations[key]) {\n return this.render(item.translations[key], args); // append lang for fallback\n }\n }\n }\n\n for (const item of this.registry) {\n if (item.lang === this.fallbackLang) {\n if (item.translations[key]) {\n return this.render(item.translations[key], args); // append lang for fallback\n }\n }\n }\n\n return key; // fallback to the key itself if not found\n };\n\n public readonly l = (\n value: I18nLocalizeType,\n options: I18nLocalizeOptions = {},\n ) => {\n // Handle numbers\n if (typeof value === \"number\" && !options.date) {\n return new Intl.NumberFormat(this.lang, options.number).format(value);\n }\n\n // Handle dates\n if (\n value instanceof Date ||\n this.dateTimeProvider.isDateTime(value) ||\n (typeof value === \"string\" && options.date) ||\n (typeof value === \"number\" && options.date)\n ) {\n // convert to DateTime with locale applied\n let dt = this.dateTimeProvider.of(value);\n\n // apply timezone if specified\n if (options.timezone) {\n dt = dt.tz(options.timezone);\n }\n\n // format using dayjs format string\n if (typeof options.date === \"string\") {\n if (options.date === \"fromNow\") {\n return dt.locale(this.lang).fromNow();\n }\n return dt.locale(this.lang).format(options.date);\n }\n\n // format using Intl.DateTimeFormatOptions\n if (options.date) {\n return new Intl.DateTimeFormat(\n this.lang,\n options.timezone\n ? { ...options.date, timeZone: options.timezone }\n : options.date,\n ).format(dt.toDate());\n }\n\n // default formatting with timezone\n if (options.timezone) {\n return new Intl.DateTimeFormat(this.lang, {\n timeZone: options.timezone,\n }).format(dt.toDate());\n }\n\n // default formatting\n return new Intl.DateTimeFormat(this.lang).format(dt.toDate());\n }\n\n // handle TypeBox errors\n if (value instanceof TypeBoxError) {\n return TypeProvider.translateError(value, this.lang);\n }\n\n // return string values as-is\n return value;\n };\n\n /**\n * Look up `key` in the registered dictionaries. The `(string & {})` arm\n * keeps autocomplete for the typed dictionary keys while allowing shared\n * library components to pass arbitrary string keys (with a `default`\n * fallback) without casting to `as never`.\n */\n public readonly tr = (\n key: keyof ServiceDictionary<S>[K] | (string & {}),\n options: {\n args?: string[];\n default?: string;\n } = {},\n ) => {\n const translation = this.translate(key as string, options.args || []);\n if (translation === (key as string) && options.default) {\n return options.default;\n }\n return translation;\n };\n\n protected render(item: string, args: string[]): string {\n let result = item;\n for (let i = 0; i < args.length; i++) {\n result = result.replace(`$${i + 1}`, args[i]);\n }\n return result;\n }\n}\n\nexport type I18nLocalizeType = string | number | Date | DateTime | TypeBoxError;\n\nexport interface I18nLocalizeOptions {\n /**\n * Options for number formatting (when value is a number)\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\n */\n number?: Intl.NumberFormatOptions;\n /**\n * Options for date formatting (when value is a Date or DateTime)\n * Can be:\n * - A dayjs format string (e.g., \"LLL\", \"YYYY-MM-DD\", \"dddd, MMMM D YYYY\")\n * - \"fromNow\" for relative time (e.g., \"2 hours ago\")\n * - Intl.DateTimeFormatOptions for native formatting\n * @see https://day.js.org/docs/en/display/format\n * @see https://day.js.org/docs/en/display/from-now\n */\n date?: string | \"fromNow\" | Intl.DateTimeFormatOptions;\n /**\n * Timezone to display dates in (when value is a Date or DateTime)\n * Uses IANA timezone names (e.g., \"America/New_York\", \"Europe/Paris\", \"Asia/Tokyo\")\n * @see https://day.js.org/docs/en/timezone/timezone\n */\n timezone?: string;\n}\n","import { $inject, type Async, createPrimitive, KIND, Primitive } from \"alepha\";\nimport { I18nProvider } from \"../providers/I18nProvider.ts\";\n\n/**\n * Register a dictionary entry for translations.\n *\n * It allows you to define a set of translations for a specific language.\n * Entry can be lazy-loaded, which is useful for large dictionaries or when translations are not needed immediately.\n *\n * @example\n * ```ts\n * import { $dictionary } from \"alepha/react/i18n\";\n *\n * const Example = () => {\n * const { tr } = useI18n<App, \"en\">();\n * return <div>{tr(\"hello\")}</div>; //\n * }\n *\n * class App {\n *\n * en = $dictionary({\n * // { default: { hello: \"Hey\" } }\n * lazy: () => import(\"./translations/en.ts\"),\n * });\n *\n * home = $page({\n * path: \"/\",\n * component: Example,\n * })\n * }\n *\n * run(App);\n * ```\n */\nexport const $dictionary = <T extends Record<string, string>>(\n options: DictionaryPrimitiveOptions<T>,\n): DictionaryPrimitive<T> => {\n return createPrimitive(DictionaryPrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface DictionaryPrimitiveOptions<T extends Record<string, string>> {\n lang?: string;\n name?: string;\n lazy: () => Async<{ default: T }>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class DictionaryPrimitive<\n T extends Record<string, string>,\n> extends Primitive<DictionaryPrimitiveOptions<T>> {\n protected provider = $inject(I18nProvider);\n protected onInit() {\n this.provider.registry.push({\n target: this.config.service.name,\n name: this.options.name ?? this.config.propertyKey,\n lang: this.options.lang ?? this.config.propertyKey,\n loader: async () => {\n const mod = await this.options.lazy();\n return mod.default;\n },\n translations: {},\n });\n }\n}\n\n$dictionary[KIND] = DictionaryPrimitive;\n","import { useInject, useStore } from \"alepha/react\";\nimport type { DictionaryPrimitive } from \"../primitives/$dictionary.ts\";\nimport { I18nProvider } from \"../providers/I18nProvider.ts\";\n\n/**\n * Hook to access the i18n service.\n */\nexport const useI18n = <\n S extends object,\n K extends keyof ServiceDictionary<S>,\n>(): I18nProvider<S, K> => {\n useStore(\"alepha.react.i18n.lang\");\n return useInject(I18nProvider<S, K>);\n};\n\nexport type ServiceDictionary<T extends object> = {\n [K in keyof T]: T[K] extends DictionaryPrimitive<infer U> ? U : never;\n};\n","import type { TypeBoxError } from \"alepha\";\nimport type { DateTime } from \"alepha/datetime\";\nimport { useI18n } from \"../hooks/useI18n.ts\";\n\nexport interface LocalizeProps {\n value: string | number | Date | DateTime | TypeBoxError;\n /**\n * Options for number formatting (when value is a number)\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\n */\n number?: Intl.NumberFormatOptions;\n /**\n * Options for date formatting (when value is a Date or DateTime)\n * Can be:\n * - A dayjs format string (e.g., \"LLL\", \"YYYY-MM-DD\", \"dddd, MMMM D YYYY\")\n * - \"fromNow\" for relative time (e.g., \"2 hours ago\")\n * - Intl.DateTimeFormatOptions for native formatting\n * @see https://day.js.org/docs/en/display/format\n * @see https://day.js.org/docs/en/display/from-now\n */\n date?: string | \"fromNow\" | Intl.DateTimeFormatOptions;\n /**\n * Timezone to display dates in (when value is a Date or DateTime)\n * Uses IANA timezone names (e.g., \"America/New_York\", \"Europe/Paris\", \"Asia/Tokyo\")\n * @see https://day.js.org/docs/en/timezone/timezone\n */\n timezone?: string;\n}\n\nconst Localize = (props: LocalizeProps) => {\n const i18n = useI18n();\n return i18n.l(props.value, props);\n};\n\nexport default Localize;\n","import { $module } from \"alepha\";\nimport { $dictionary } from \"./primitives/$dictionary.ts\";\nimport { I18nProvider } from \"./providers/I18nProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type { LocalizeProps } from \"./components/Localize.tsx\";\nexport { default as Localize } from \"./components/Localize.tsx\";\nexport * from \"./hooks/useI18n.ts\";\nexport * from \"./primitives/$dictionary.ts\";\nexport * from \"./providers/I18nProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n export interface State {\n \"alepha.react.i18n.lang\"?: string;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Multi-language support.\n *\n * **Features:**\n * - Translation loading\n * - Locale detection\n * - Pluralization\n *\n * @module alepha.react.i18n\n */\nexport const AlephaReactI18n = $module({\n name: \"alepha.react.i18n\",\n primitives: [$dictionary],\n services: [I18nProvider],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/i18n/providers/I18nProvider.ts","../../../src/react/i18n/primitives/$dictionary.ts","../../../src/react/i18n/hooks/useI18n.ts","../../../src/react/i18n/components/Localize.tsx","../../../src/react/i18n/components/Translate.tsx","../../../src/react/i18n/index.ts"],"sourcesContent":["import { $hook, $inject, Alepha, TypeBoxError, TypeProvider, t } from \"alepha\";\nimport { type DateTime, DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\n// Locale-prefix routing is optional: the router module (one-directional\n// dependency, `i18n → router`) is only consulted when it is also registered.\nimport { RouterLocaleProvider } from \"alepha/react/router\";\nimport { $cookie } from \"alepha/server/cookies\";\nimport type { ServiceDictionary } from \"../hooks/useI18n.ts\";\n\nexport class I18nProvider<\n S extends object,\n K extends keyof ServiceDictionary<S>,\n> {\n protected log = $logger();\n protected alepha = $inject(Alepha);\n protected dateTimeProvider = $inject(DateTimeProvider);\n\n protected cookie = $cookie({\n name: \"lang\",\n schema: t.text(),\n ttl: [1, \"year\"],\n });\n\n public readonly registry: Array<{\n target: string;\n name: string;\n lang: string;\n loader: () => Promise<Record<string, string>>;\n translations: Record<string, string>;\n }> = [];\n\n options: {\n fallbackLang: string;\n autoDetect: boolean;\n routing: \"none\" | \"prefix\";\n } = {\n fallbackLang: \"en\",\n /**\n * When true (the default), the UI language for a first-time visitor (one\n * with no `lang` cookie) is detected server-side from the `Accept-Language`\n * header. A manually-selected language (the cookie) always takes\n * precedence, so this never overrides an explicit user choice. Set to false\n * to always start in `fallbackLang` regardless of the browser's preferred\n * language.\n */\n autoDetect: true,\n /**\n * URL strategy for languages:\n * - `\"none\"` (default): language lives in a cookie; URLs are not localized.\n * - `\"prefix\"`: each non-default language gets a path prefix (`/fr/about`),\n * making every language a distinct, crawlable URL for SEO. The default\n * language (`fallbackLang`) stays unprefixed. Requires the router module.\n * The URL becomes the source of truth for language (it wins over the\n * cookie / `Accept-Language`), and there is no automatic redirect.\n */\n routing: \"none\",\n };\n\n /**\n * Lazily-resolved locale-prefix router integration. Present only when both\n * the router module is registered AND `routing: \"prefix\"` was configured —\n * otherwise i18n stays fully standalone.\n */\n protected localeProviderResolved = false;\n protected localeProviderRef?: RouterLocaleProvider;\n protected get localeProvider(): RouterLocaleProvider | undefined {\n if (!this.localeProviderResolved) {\n this.localeProviderResolved = true;\n if (this.alepha.has(RouterLocaleProvider)) {\n this.localeProviderRef = this.alepha.inject(RouterLocaleProvider);\n }\n }\n return this.localeProviderRef;\n }\n\n public dateFormat: { format: (value: Date) => string } =\n new Intl.DateTimeFormat(this.lang);\n\n public numberFormat: { format: (value: number) => string } =\n new Intl.NumberFormat(this.lang);\n\n public get languages() {\n const languages = new Set<string>();\n\n for (const item of this.registry) {\n languages.add(item.lang);\n }\n\n return Array.from(languages);\n }\n\n constructor() {\n this.refreshLocale();\n }\n\n /**\n * Configure locale-prefix routing on the router, before the SSR routes are\n * registered (`priority: \"first\"` runs ahead of the router's own `configure`\n * hook). No-op unless `routing: \"prefix\"` and the router module is present.\n */\n protected readonly onConfigure = $hook({\n on: \"configure\",\n priority: \"first\",\n handler: () => {\n const localeProvider = this.localeProvider;\n if (this.options.routing === \"prefix\" && localeProvider) {\n localeProvider.configure({\n enabled: true,\n defaultLocale: this.fallbackLang,\n locales: this.languages,\n });\n }\n },\n });\n\n protected readonly onRender = $hook({\n on: \"server:onRequest\",\n priority: \"last\",\n handler: async ({ request }) => {\n this.alepha.store.set(\n \"alepha.react.i18n.lang\",\n this.resolveRequestLang(\n this.cookie.get(request),\n request.language,\n this.detectUrlLocale(request.url?.pathname),\n ),\n );\n },\n });\n\n /**\n * Detects the language carried by the request URL when `routing: \"prefix\"` is\n * active. Returns the locale for any URL (the prefixed one for `/fr/...`, the\n * default for an unprefixed path), or `undefined` when prefix routing is off.\n */\n protected detectUrlLocale(pathname: string | undefined): string | undefined {\n const localeProvider = this.localeProvider;\n if (localeProvider?.enabled && pathname) {\n return localeProvider.detect(pathname).locale || undefined;\n }\n return undefined;\n }\n\n /**\n * Resolves the UI language for an incoming server request.\n *\n * Priority:\n * 0. the URL locale prefix (`routing: \"prefix\"`) — the URL is the source of\n * truth and wins over everything, with no redirect;\n * 1. the `lang` cookie — a language the user manually selected;\n * 2. the `Accept-Language` header (when `autoDetect` is enabled) — but only\n * when the detected language is actually registered, so we never switch to\n * a locale we have no dictionary for. A region-qualified header (`en-US`)\n * matches an exact registration first, then its base language (`en`);\n * 3. `fallbackLang`.\n */\n protected resolveRequestLang(\n cookieLang: string | undefined,\n headerLang: string | undefined,\n urlLocale?: string,\n ): string {\n if (urlLocale) {\n return urlLocale;\n }\n\n if (cookieLang) {\n return cookieLang;\n }\n\n if (this.options.autoDetect && headerLang) {\n const registered = this.languages;\n for (const candidate of [headerLang, headerLang.split(\"-\")[0]]) {\n if (registered.includes(candidate)) {\n return candidate;\n }\n }\n }\n\n return this.fallbackLang;\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n if (this.alepha.isBrowser()) {\n // In prefix mode the URL (hydrated into the lang state by the server)\n // is the source of truth, so the cookie must not override it.\n if (!this.localeProvider?.enabled) {\n const cookieLang = this.cookie.get();\n if (cookieLang) {\n this.alepha.store.set(\"alepha.react.i18n.lang\", cookieLang);\n }\n }\n\n for (const item of this.registry) {\n if (item.lang === this.lang || item.lang === this.fallbackLang) {\n this.log.trace(\"Loading language\", {\n lang: item.lang,\n name: item.name,\n target: item.target,\n });\n item.translations = await item.loader();\n }\n }\n return;\n }\n\n for (const item of this.registry) {\n item.translations = await item.loader();\n }\n },\n });\n\n protected refreshLocale() {\n this.numberFormat = new Intl.NumberFormat(this.lang);\n this.dateFormat = new Intl.DateTimeFormat(this.lang);\n this.dateTimeProvider.setLocale(this.lang);\n TypeProvider.setLocale(this.lang);\n }\n\n /**\n * Activates a language: lazily loads its dictionaries (browser), updates the\n * lang state, and refreshes the locale-bound formatters. Does NOT persist a\n * cookie or navigate — that is the caller's concern.\n */\n protected applyLang = async (lang: string) => {\n if (this.alepha.isBrowser()) {\n for (const item of this.registry) {\n if (lang === item.lang && Object.keys(item.translations).length === 0) {\n item.translations = await item.loader();\n }\n }\n }\n\n this.alepha.store.set(\"alepha.react.i18n.lang\", lang);\n this.refreshLocale();\n };\n\n public setLang = async (lang: string) => {\n const localeProvider = this.localeProvider;\n if (localeProvider?.enabled) {\n // The URL is the source of truth: switching language means navigating to\n // the same page under the new locale prefix. Dictionaries and formatters\n // are then activated via the resulting `alepha.react.router.locale`\n // change (see the `mutate` hook). No cookie is written in prefix mode.\n const { ReactRouter } = await import(\"alepha/react/router\");\n const router = this.alepha.inject(ReactRouter);\n const canonical = localeProvider.detect(router.pathname).pathname;\n await router.push(localeProvider.withPrefix(canonical, lang));\n return;\n }\n\n await this.applyLang(lang);\n if (this.alepha.isBrowser()) {\n this.cookie.set(lang);\n }\n };\n\n protected readonly mutate = $hook({\n on: \"state:mutate\",\n handler: async ({ key, value }) => {\n // Prefix-mode navigation changed the active locale (set by the router) →\n // activate the matching language (loads dictionaries, refreshes\n // formatters, and re-renders consumers via the lang state).\n if (\n key === \"alepha.react.router.locale\" &&\n this.localeProvider?.enabled\n ) {\n const lang = (value as string) || this.fallbackLang;\n if (lang !== this.lang) {\n await this.applyLang(lang);\n }\n return;\n }\n\n if (key === \"alepha.react.i18n.lang\" && this.alepha.isBrowser()) {\n let hasChanged = false;\n for (const item of this.registry) {\n if (value === item.lang) {\n if (Object.keys(item.translations).length > 0) {\n continue; // already loaded\n }\n item.translations = await item.loader();\n hasChanged = true;\n }\n }\n\n this.refreshLocale();\n\n if (hasChanged) {\n this.alepha.store.set(\"alepha.react.i18n.lang\", value);\n }\n }\n },\n });\n\n public get fallbackLang(): string {\n const configured = this.options.fallbackLang;\n const hasDict = this.registry.some((item) => item.lang === configured);\n if (hasDict) {\n return configured;\n }\n return this.registry[0]?.lang ?? configured;\n }\n\n public get lang(): string {\n return this.alepha.store.get(\"alepha.react.i18n.lang\") || this.fallbackLang;\n }\n\n public translate = (key: string, args: string[] = []) => {\n for (const item of this.registry) {\n if (item.lang === this.lang) {\n if (item.translations[key]) {\n return this.render(item.translations[key], args); // append lang for fallback\n }\n }\n }\n\n for (const item of this.registry) {\n if (item.lang === this.fallbackLang) {\n if (item.translations[key]) {\n return this.render(item.translations[key], args); // append lang for fallback\n }\n }\n }\n\n return key; // fallback to the key itself if not found\n };\n\n public readonly l = (\n value: I18nLocalizeType,\n options: I18nLocalizeOptions = {},\n ) => {\n // Handle numbers\n if (typeof value === \"number\" && !options.date) {\n return new Intl.NumberFormat(this.lang, options.number).format(value);\n }\n\n // Handle dates\n if (\n value instanceof Date ||\n this.dateTimeProvider.isDateTime(value) ||\n (typeof value === \"string\" && options.date) ||\n (typeof value === \"number\" && options.date)\n ) {\n // convert to DateTime with locale applied\n let dt = this.dateTimeProvider.of(value);\n\n // apply timezone if specified\n if (options.timezone) {\n dt = dt.tz(options.timezone);\n }\n\n // format using dayjs format string\n if (typeof options.date === \"string\") {\n if (options.date === \"fromNow\") {\n return dt.locale(this.lang).fromNow();\n }\n return dt.locale(this.lang).format(options.date);\n }\n\n // format using Intl.DateTimeFormatOptions\n if (options.date) {\n return new Intl.DateTimeFormat(\n this.lang,\n options.timezone\n ? { ...options.date, timeZone: options.timezone }\n : options.date,\n ).format(dt.toDate());\n }\n\n // default formatting with timezone\n if (options.timezone) {\n return new Intl.DateTimeFormat(this.lang, {\n timeZone: options.timezone,\n }).format(dt.toDate());\n }\n\n // default formatting\n return new Intl.DateTimeFormat(this.lang).format(dt.toDate());\n }\n\n // handle TypeBox errors\n if (value instanceof TypeBoxError) {\n return TypeProvider.translateError(value, this.lang);\n }\n\n // return string values as-is\n return value;\n };\n\n /**\n * Look up `key` in the registered dictionaries. The `(string & {})` arm\n * keeps autocomplete for the typed dictionary keys while allowing shared\n * library components to pass arbitrary string keys (with a `default`\n * fallback) without casting to `as never`.\n */\n public readonly tr = (\n key: keyof ServiceDictionary<S>[K] | (string & {}),\n options: {\n args?: string[];\n default?: string;\n } = {},\n ) => {\n const translation = this.translate(key as string, options.args || []);\n if (translation === (key as string) && options.default) {\n return options.default;\n }\n return translation;\n };\n\n protected render(item: string, args: string[]): string {\n let result = item;\n for (let i = 0; i < args.length; i++) {\n result = result.replace(`$${i + 1}`, args[i]);\n }\n return result;\n }\n}\n\nexport type I18nLocalizeType = string | number | Date | DateTime | TypeBoxError;\n\nexport interface I18nLocalizeOptions {\n /**\n * Options for number formatting (when value is a number)\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\n */\n number?: Intl.NumberFormatOptions;\n /**\n * Options for date formatting (when value is a Date or DateTime)\n * Can be:\n * - A dayjs format string (e.g., \"LLL\", \"YYYY-MM-DD\", \"dddd, MMMM D YYYY\")\n * - \"fromNow\" for relative time (e.g., \"2 hours ago\")\n * - Intl.DateTimeFormatOptions for native formatting\n * @see https://day.js.org/docs/en/display/format\n * @see https://day.js.org/docs/en/display/from-now\n */\n date?: string | \"fromNow\" | Intl.DateTimeFormatOptions;\n /**\n * Timezone to display dates in (when value is a Date or DateTime)\n * Uses IANA timezone names (e.g., \"America/New_York\", \"Europe/Paris\", \"Asia/Tokyo\")\n * @see https://day.js.org/docs/en/timezone/timezone\n */\n timezone?: string;\n}\n","import { $inject, type Async, createPrimitive, KIND, Primitive } from \"alepha\";\nimport { I18nProvider } from \"../providers/I18nProvider.ts\";\n\n/**\n * Register a dictionary entry for translations.\n *\n * It allows you to define a set of translations for a specific language.\n * Entry can be lazy-loaded, which is useful for large dictionaries or when translations are not needed immediately.\n *\n * @example\n * ```ts\n * import { $dictionary } from \"alepha/react/i18n\";\n *\n * const Example = () => {\n * const { tr } = useI18n<App, \"en\">();\n * return <div>{tr(\"hello\")}</div>; //\n * }\n *\n * class App {\n *\n * en = $dictionary({\n * // { default: { hello: \"Hey\" } }\n * lazy: () => import(\"./translations/en.ts\"),\n * });\n *\n * home = $page({\n * path: \"/\",\n * component: Example,\n * })\n * }\n *\n * run(App);\n * ```\n */\nexport const $dictionary = <T extends Record<string, string>>(\n options: DictionaryPrimitiveOptions<T>,\n): DictionaryPrimitive<T> => {\n return createPrimitive(DictionaryPrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface DictionaryPrimitiveOptions<T extends Record<string, string>> {\n lang?: string;\n name?: string;\n lazy: () => Async<{ default: T }>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class DictionaryPrimitive<\n T extends Record<string, string>,\n> extends Primitive<DictionaryPrimitiveOptions<T>> {\n protected provider = $inject(I18nProvider);\n protected onInit() {\n this.provider.registry.push({\n target: this.config.service.name,\n name: this.options.name ?? this.config.propertyKey,\n lang: this.options.lang ?? this.config.propertyKey,\n loader: async () => {\n const mod = await this.options.lazy();\n return mod.default;\n },\n translations: {},\n });\n }\n}\n\n$dictionary[KIND] = DictionaryPrimitive;\n","import { useInject, useStore } from \"alepha/react\";\nimport type { DictionaryPrimitive } from \"../primitives/$dictionary.ts\";\nimport { I18nProvider } from \"../providers/I18nProvider.ts\";\n\n/**\n * Hook to access the i18n service.\n */\nexport const useI18n = <\n S extends object,\n K extends keyof ServiceDictionary<S>,\n>(): I18nProvider<S, K> => {\n useStore(\"alepha.react.i18n.lang\");\n return useInject(I18nProvider<S, K>);\n};\n\nexport type ServiceDictionary<T extends object> = {\n [K in keyof T]: T[K] extends DictionaryPrimitive<infer U> ? U : never;\n};\n","import type { TypeBoxError } from \"alepha\";\nimport type { DateTime } from \"alepha/datetime\";\nimport { useI18n } from \"../hooks/useI18n.ts\";\n\nexport interface LocalizeProps {\n value: string | number | Date | DateTime | TypeBoxError;\n /**\n * Options for number formatting (when value is a number)\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\n */\n number?: Intl.NumberFormatOptions;\n /**\n * Options for date formatting (when value is a Date or DateTime)\n * Can be:\n * - A dayjs format string (e.g., \"LLL\", \"YYYY-MM-DD\", \"dddd, MMMM D YYYY\")\n * - \"fromNow\" for relative time (e.g., \"2 hours ago\")\n * - Intl.DateTimeFormatOptions for native formatting\n * @see https://day.js.org/docs/en/display/format\n * @see https://day.js.org/docs/en/display/from-now\n */\n date?: string | \"fromNow\" | Intl.DateTimeFormatOptions;\n /**\n * Timezone to display dates in (when value is a Date or DateTime)\n * Uses IANA timezone names (e.g., \"America/New_York\", \"Europe/Paris\", \"Asia/Tokyo\")\n * @see https://day.js.org/docs/en/timezone/timezone\n */\n timezone?: string;\n}\n\nconst Localize = (props: LocalizeProps) => {\n const i18n = useI18n();\n return i18n.l(props.value, props);\n};\n\nexport default Localize;\n","import { useI18n } from \"../hooks/useI18n.ts\";\n\nexport interface TranslateProps {\n /**\n * Dictionary key to look up — the same key you would pass to `tr(...)`.\n */\n k: string;\n /**\n * Positional substitutions for `$1`, `$2`, … placeholders in the entry.\n */\n args?: string[];\n /**\n * Shown when the key is missing from every registered dictionary (otherwise\n * the raw key is rendered, matching `tr`'s behaviour).\n */\n fallback?: string;\n}\n\n/**\n * Renders a translated dictionary entry as a reactive text node.\n *\n * `tr(...)` is a hook, so it can only be called inside a component body. Use\n * `<Translate>` wherever a *node* is expected instead — a prop, a `children`\n * slot, or route metadata such as a nav-shell `label`. Because it subscribes to\n * {@link useI18n} it re-renders on a language switch, unlike a string resolved\n * once at module/initialisation time.\n *\n * Sibling of {@link Localize}: `Localize` formats a value (number, date,\n * error); `Translate` looks up a key.\n *\n * @example\n * ```tsx\n * nav: { label: <Translate k=\"admin.nav.users\" /> }\n * <Translate k=\"cart.items\" args={[String(count)]} fallback=\"Items\" />\n * ```\n */\nexport const Translate = (props: TranslateProps) => {\n const { tr } = useI18n();\n return <>{tr(props.k, { args: props.args, default: props.fallback })}</>;\n};\n\n/**\n * Terse alias for {@link Translate} — handy in dense JSX such as nav labels.\n */\nexport const Tr = Translate;\n\nexport default Translate;\n","import { $module } from \"alepha\";\nimport { $dictionary } from \"./primitives/$dictionary.ts\";\nimport { I18nProvider } from \"./providers/I18nProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type { LocalizeProps } from \"./components/Localize.tsx\";\nexport { default as Localize } from \"./components/Localize.tsx\";\nexport type { TranslateProps } from \"./components/Translate.tsx\";\nexport { default as Translate, Tr } from \"./components/Translate.tsx\";\nexport * from \"./hooks/useI18n.ts\";\nexport * from \"./primitives/$dictionary.ts\";\nexport * from \"./providers/I18nProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n export interface State {\n \"alepha.react.i18n.lang\"?: string;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Multi-language support.\n *\n * **Features:**\n * - Translation loading\n * - Locale detection\n * - Pluralization\n *\n * @module alepha.react.i18n\n */\nexport const AlephaReactI18n = $module({\n name: \"alepha.react.i18n\",\n primitives: [$dictionary],\n services: [I18nProvider],\n});\n"],"mappings":";;;;;;;;AASA,IAAa,eAAb,MAGE;CACA,MAAgB,QAAQ;CACxB,SAAmB,QAAQ,MAAM;CACjC,mBAA6B,QAAQ,gBAAgB;CAErD,SAAmB,QAAQ;EACzB,MAAM;EACN,QAAQ,EAAE,KAAK;EACf,KAAK,CAAC,GAAG,MAAM;CACjB,CAAC;CAED,WAMK,CAAC;CAEN,UAII;EACF,cAAc;;;;;;;;;EASd,YAAY;;;;;;;;;;EAUZ,SAAS;CACX;;;;;;CAOA,yBAAmC;CACnC;CACA,IAAc,iBAAmD;EAC/D,IAAI,CAAC,KAAK,wBAAwB;GAChC,KAAK,yBAAyB;GAC9B,IAAI,KAAK,OAAO,IAAI,oBAAoB,GACtC,KAAK,oBAAoB,KAAK,OAAO,OAAO,oBAAoB;EAEpE;EACA,OAAO,KAAK;CACd;CAEA,aACE,IAAI,KAAK,eAAe,KAAK,IAAI;CAEnC,eACE,IAAI,KAAK,aAAa,KAAK,IAAI;CAEjC,IAAW,YAAY;EACrB,MAAM,4BAAY,IAAI,IAAY;EAElC,KAAK,MAAM,QAAQ,KAAK,UACtB,UAAU,IAAI,KAAK,IAAI;EAGzB,OAAO,MAAM,KAAK,SAAS;CAC7B;CAEA,cAAc;EACZ,KAAK,cAAc;CACrB;;;;;;CAOA,cAAiC,MAAM;EACrC,IAAI;EACJ,UAAU;EACV,eAAe;GACb,MAAM,iBAAiB,KAAK;GAC5B,IAAI,KAAK,QAAQ,YAAY,YAAY,gBACvC,eAAe,UAAU;IACvB,SAAS;IACT,eAAe,KAAK;IACpB,SAAS,KAAK;GAChB,CAAC;EAEL;CACF,CAAC;CAED,WAA8B,MAAM;EAClC,IAAI;EACJ,UAAU;EACV,SAAS,OAAO,EAAE,cAAc;GAC9B,KAAK,OAAO,MAAM,IAChB,0BACA,KAAK,mBACH,KAAK,OAAO,IAAI,OAAO,GACvB,QAAQ,UACR,KAAK,gBAAgB,QAAQ,KAAK,QAAQ,CAC5C,CACF;EACF;CACF,CAAC;;;;;;CAOD,gBAA0B,UAAkD;EAC1E,MAAM,iBAAiB,KAAK;EAC5B,IAAI,gBAAgB,WAAW,UAC7B,OAAO,eAAe,OAAO,QAAQ,EAAE,UAAU,KAAA;CAGrD;;;;;;;;;;;;;;CAeA,mBACE,YACA,YACA,WACQ;EACR,IAAI,WACF,OAAO;EAGT,IAAI,YACF,OAAO;EAGT,IAAI,KAAK,QAAQ,cAAc,YAAY;GACzC,MAAM,aAAa,KAAK;GACxB,KAAK,MAAM,aAAa,CAAC,YAAY,WAAW,MAAM,GAAG,EAAE,EAAE,GAC3D,IAAI,WAAW,SAAS,SAAS,GAC/B,OAAO;EAGb;EAEA,OAAO,KAAK;CACd;CAEA,UAA6B,MAAM;EACjC,IAAI;EACJ,SAAS,YAAY;GACnB,IAAI,KAAK,OAAO,UAAU,GAAG;IAG3B,IAAI,CAAC,KAAK,gBAAgB,SAAS;KACjC,MAAM,aAAa,KAAK,OAAO,IAAI;KACnC,IAAI,YACF,KAAK,OAAO,MAAM,IAAI,0BAA0B,UAAU;IAE9D;IAEA,KAAK,MAAM,QAAQ,KAAK,UACtB,IAAI,KAAK,SAAS,KAAK,QAAQ,KAAK,SAAS,KAAK,cAAc;KAC9D,KAAK,IAAI,MAAM,oBAAoB;MACjC,MAAM,KAAK;MACX,MAAM,KAAK;MACX,QAAQ,KAAK;KACf,CAAC;KACD,KAAK,eAAe,MAAM,KAAK,OAAO;IACxC;IAEF;GACF;GAEA,KAAK,MAAM,QAAQ,KAAK,UACtB,KAAK,eAAe,MAAM,KAAK,OAAO;EAE1C;CACF,CAAC;CAED,gBAA0B;EACxB,KAAK,eAAe,IAAI,KAAK,aAAa,KAAK,IAAI;EACnD,KAAK,aAAa,IAAI,KAAK,eAAe,KAAK,IAAI;EACnD,KAAK,iBAAiB,UAAU,KAAK,IAAI;EACzC,aAAa,UAAU,KAAK,IAAI;CAClC;;;;;;CAOA,YAAsB,OAAO,SAAiB;EAC5C,IAAI,KAAK,OAAO,UAAU;QACnB,MAAM,QAAQ,KAAK,UACtB,IAAI,SAAS,KAAK,QAAQ,OAAO,KAAK,KAAK,YAAY,EAAE,WAAW,GAClE,KAAK,eAAe,MAAM,KAAK,OAAO;EAAA;EAK5C,KAAK,OAAO,MAAM,IAAI,0BAA0B,IAAI;EACpD,KAAK,cAAc;CACrB;CAEA,UAAiB,OAAO,SAAiB;EACvC,MAAM,iBAAiB,KAAK;EAC5B,IAAI,gBAAgB,SAAS;GAK3B,MAAM,EAAE,gBAAgB,MAAM,OAAO;GACrC,MAAM,SAAS,KAAK,OAAO,OAAO,WAAW;GAC7C,MAAM,YAAY,eAAe,OAAO,OAAO,QAAQ,EAAE;GACzD,MAAM,OAAO,KAAK,eAAe,WAAW,WAAW,IAAI,CAAC;GAC5D;EACF;EAEA,MAAM,KAAK,UAAU,IAAI;EACzB,IAAI,KAAK,OAAO,UAAU,GACxB,KAAK,OAAO,IAAI,IAAI;CAExB;CAEA,SAA4B,MAAM;EAChC,IAAI;EACJ,SAAS,OAAO,EAAE,KAAK,YAAY;GAIjC,IACE,QAAQ,gCACR,KAAK,gBAAgB,SACrB;IACA,MAAM,OAAQ,SAAoB,KAAK;IACvC,IAAI,SAAS,KAAK,MAChB,MAAM,KAAK,UAAU,IAAI;IAE3B;GACF;GAEA,IAAI,QAAQ,4BAA4B,KAAK,OAAO,UAAU,GAAG;IAC/D,IAAI,aAAa;IACjB,KAAK,MAAM,QAAQ,KAAK,UACtB,IAAI,UAAU,KAAK,MAAM;KACvB,IAAI,OAAO,KAAK,KAAK,YAAY,EAAE,SAAS,GAC1C;KAEF,KAAK,eAAe,MAAM,KAAK,OAAO;KACtC,aAAa;IACf;IAGF,KAAK,cAAc;IAEnB,IAAI,YACF,KAAK,OAAO,MAAM,IAAI,0BAA0B,KAAK;GAEzD;EACF;CACF,CAAC;CAED,IAAW,eAAuB;EAChC,MAAM,aAAa,KAAK,QAAQ;EAEhC,IADgB,KAAK,SAAS,MAAM,SAAS,KAAK,SAAS,UACjD,GACR,OAAO;EAET,OAAO,KAAK,SAAS,IAAI,QAAQ;CACnC;CAEA,IAAW,OAAe;EACxB,OAAO,KAAK,OAAO,MAAM,IAAI,wBAAwB,KAAK,KAAK;CACjE;CAEA,aAAoB,KAAa,OAAiB,CAAC,MAAM;EACvD,KAAK,MAAM,QAAQ,KAAK,UACtB,IAAI,KAAK,SAAS,KAAK;OACjB,KAAK,aAAa,MACpB,OAAO,KAAK,OAAO,KAAK,aAAa,MAAM,IAAI;EAAA;EAKrD,KAAK,MAAM,QAAQ,KAAK,UACtB,IAAI,KAAK,SAAS,KAAK;OACjB,KAAK,aAAa,MACpB,OAAO,KAAK,OAAO,KAAK,aAAa,MAAM,IAAI;EAAA;EAKrD,OAAO;CACT;CAEA,KACE,OACA,UAA+B,CAAC,MAC7B;EAEH,IAAI,OAAO,UAAU,YAAY,CAAC,QAAQ,MACxC,OAAO,IAAI,KAAK,aAAa,KAAK,MAAM,QAAQ,MAAM,EAAE,OAAO,KAAK;EAItE,IACE,iBAAiB,QACjB,KAAK,iBAAiB,WAAW,KAAK,KACrC,OAAO,UAAU,YAAY,QAAQ,QACrC,OAAO,UAAU,YAAY,QAAQ,MACtC;GAEA,IAAI,KAAK,KAAK,iBAAiB,GAAG,KAAK;GAGvC,IAAI,QAAQ,UACV,KAAK,GAAG,GAAG,QAAQ,QAAQ;GAI7B,IAAI,OAAO,QAAQ,SAAS,UAAU;IACpC,IAAI,QAAQ,SAAS,WACnB,OAAO,GAAG,OAAO,KAAK,IAAI,EAAE,QAAQ;IAEtC,OAAO,GAAG,OAAO,KAAK,IAAI,EAAE,OAAO,QAAQ,IAAI;GACjD;GAGA,IAAI,QAAQ,MACV,OAAO,IAAI,KAAK,eACd,KAAK,MACL,QAAQ,WACJ;IAAE,GAAG,QAAQ;IAAM,UAAU,QAAQ;GAAS,IAC9C,QAAQ,IACd,EAAE,OAAO,GAAG,OAAO,CAAC;GAItB,IAAI,QAAQ,UACV,OAAO,IAAI,KAAK,eAAe,KAAK,MAAM,EACxC,UAAU,QAAQ,SACpB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;GAIvB,OAAO,IAAI,KAAK,eAAe,KAAK,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;EAC9D;EAGA,IAAI,iBAAiB,cACnB,OAAO,aAAa,eAAe,OAAO,KAAK,IAAI;EAIrD,OAAO;CACT;;;;;;;CAQA,MACE,KACA,UAGI,CAAC,MACF;EACH,MAAM,cAAc,KAAK,UAAU,KAAe,QAAQ,QAAQ,CAAC,CAAC;EACpE,IAAI,gBAAiB,OAAkB,QAAQ,SAC7C,OAAO,QAAQ;EAEjB,OAAO;CACT;CAEA,OAAiB,MAAc,MAAwB;EACrD,IAAI,SAAS;EACb,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAC/B,SAAS,OAAO,QAAQ,IAAI,IAAI,KAAK,KAAK,EAAE;EAE9C,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChYA,MAAa,eACX,YAC2B;CAC3B,OAAO,gBAAgB,qBAAwB,OAAO;AACxD;AAYA,IAAa,sBAAb,cAEU,UAAyC;CACjD,WAAqB,QAAQ,YAAY;CACzC,SAAmB;EACjB,KAAK,SAAS,SAAS,KAAK;GAC1B,QAAQ,KAAK,OAAO,QAAQ;GAC5B,MAAM,KAAK,QAAQ,QAAQ,KAAK,OAAO;GACvC,MAAM,KAAK,QAAQ,QAAQ,KAAK,OAAO;GACvC,QAAQ,YAAY;IAElB,QAAO,MADW,KAAK,QAAQ,KAAK,GACzB;GACb;GACA,cAAc,CAAC;EACjB,CAAC;CACH;AACF;AAEA,YAAY,QAAQ;;;;;;AC7DpB,MAAa,gBAGc;CACzB,SAAS,wBAAwB;CACjC,OAAO,UAAU,YAAkB;AACrC;;;ACgBA,MAAM,YAAY,UAAyB;CAEzC,OADa,QACH,EAAE,EAAE,MAAM,OAAO,KAAK;AAClC;;;;;;;;;;;;;;;;;;;;;ACIA,MAAa,aAAa,UAA0B;CAClD,MAAM,EAAE,OAAO,QAAQ;CACvB,OAAO,oBAAA,UAAA,EAAA,UAAG,GAAG,MAAM,GAAG;EAAE,MAAM,MAAM;EAAM,SAAS,MAAM;CAAS,CAAC,EAAI,CAAA;AACzE;;;;AAKA,MAAa,KAAK;;;;;;;;;;;;;ACVlB,MAAa,kBAAkB,QAAQ;CACrC,MAAM;CACN,YAAY,CAAC,WAAW;CACxB,UAAU,CAAC,YAAY;AACzB,CAAC"}
@@ -1,5 +1,4 @@
1
1
  import { ReactNode } from "react";
2
- import * as _$react_jsx_runtime0 from "react/jsx-runtime";
3
2
 
4
3
  //#region ../../src/react/intro/components/GettingStarted.d.ts
5
4
  type GettingStartedStep = {
@@ -34,7 +33,7 @@ type GettingStartedProps = {
34
33
  */
35
34
  declare const GettingStarted: ({
36
35
  welcome
37
- }: GettingStartedProps) => _$react_jsx_runtime0.JSX.Element;
36
+ }: GettingStartedProps) => import("react/jsx-runtime").JSX.Element;
38
37
  //#endregion
39
38
  export { GettingStarted, type GettingStartedSlide, type GettingStartedStep };
40
39
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/react/intro/components/GettingStarted.tsx"],"mappings":";;;;KAMY,kBAAA;EACV,GAAA;EACA,IAAA,EAAM,SAAA;AAAA;AAAA,KAGI,mBAAA;EACV,IAAA;EACA,GAAA;EACA,MAAA;EACA,KAAA,GAAQ,kBAAA;EACR,KAAA;IAAU,KAAA;IAAe,IAAA;EAAA;AAAA;AAAA,KAGf,qBAAA;EACV,OAAA;EACA,UAAA;AAAA;AAAA,KAGU,mBAAA;EATV;;;;;;EAgBA,OAAA,GAAU,qBAAA;AAAA;;;;cAmCN,cAAA;EAAkB;AAAA,GAAa,mBAAA,KAAmB,oBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/react/intro/components/GettingStarted.tsx"],"mappings":";;;KAMY,kBAAA;EACV,GAAA;EACA,IAAA,EAAM,SAAS;AAAA;AAAA,KAGL,mBAAA;EACV,IAAA;EACA,GAAA;EACA,MAAA;EACA,KAAA,GAAQ,kBAAkB;EAC1B,KAAA;IAAU,KAAA;IAAe,IAAA;EAAA;AAAA;AAAA,KAGf,qBAAA;EACV,OAAA;EACA,UAAU;AAAA;AAAA,KAGA,mBAAA;EATF;;;;;AACqB;EAe7B,OAAA,GAAU,qBAAqB;AAAA;;;AAVrB;cA6CN,cAAA;EAAkB;AAAA,GAAa,mBAAmB,iCAAA,GAAA,CAAA,OAAA"}
@@ -9,11 +9,11 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  * Returns undefined if admin routes are not configured.
10
10
  */
11
11
  const useAdminSlide = () => {
12
- const { user, can } = useAuth();
12
+ const { user, has } = useAuth();
13
13
  const router = useRouter();
14
14
  if (!router.pages.find((it) => it.name === "adminLayout")) return;
15
15
  const adminAnchorProps = router.anchor(router.path("adminLayout"));
16
- if (can("admin:*")) return {
16
+ if (has("admin:*")) return {
17
17
  text: "You're in control.",
18
18
  sub: "Admin access granted.",
19
19
  steps: [{
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/intro/components/GettingStartedAdminSlide.tsx","../../../src/react/intro/components/GettingStartedAuthSlide.tsx","../../../src/react/intro/components/GettingStartedDevtoolsSlide.tsx","../../../src/react/intro/components/GettingStarted.tsx"],"sourcesContent":["import { useAuth } from \"alepha/react/auth\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { GettingStartedSlide } from \"./GettingStarted.tsx\";\n\n/**\n * Hook that provides the admin slide content.\n * Content changes based on user status and admin permissions.\n * Returns undefined if admin routes are not configured.\n */\nexport const useAdminSlide = (): GettingStartedSlide | undefined => {\n const { user, can } = useAuth();\n const router = useRouter();\n\n // Check if admin routes exist\n const hasAdmin = router.pages.find((it) => it.name === \"adminLayout\");\n if (!hasAdmin) {\n return undefined;\n }\n\n const adminAnchorProps = router.anchor(router.path(\"adminLayout\"));\n const canAccessAdmin = can(\"admin:*\");\n\n // User is admin - show success message\n if (canAccessAdmin) {\n return {\n text: \"You're in control.\",\n sub: \"Admin access granted.\",\n steps: [\n {\n num: \"✓\",\n text: \"You have admin privileges\",\n },\n {\n num: \"→\",\n text: (\n <>\n Go to <a {...adminAnchorProps}>/admin</a> to manage your\n application\n </>\n ),\n },\n ],\n };\n }\n\n // User is logged in but not admin - show how to become admin\n if (user) {\n return {\n text: \"Take the wheel.\",\n sub: \"Become admin in two steps.\",\n steps: [\n {\n num: \"1\",\n text: (\n <>\n Add your email to <code>adminEmails</code> in{\" \"}\n <code>AppSecurity.ts</code>\n </>\n ),\n },\n {\n num: \"2\",\n text: (\n <>\n Go to <a {...adminAnchorProps}>/admin</a>\n </>\n ),\n },\n ],\n };\n }\n\n // User is not logged in - show full instructions\n return {\n text: \"Take the wheel.\",\n sub: \"Become admin in three steps.\",\n steps: [\n {\n num: \"1\",\n text: (\n <>\n Add your email to <code>adminEmails</code> in{\" \"}\n <code>AppSecurity.ts</code>\n </>\n ),\n },\n { num: \"2\", text: \"Create a user account with that email\" },\n {\n num: \"3\",\n text: (\n <>\n Go to <a {...adminAnchorProps}>/admin</a>\n </>\n ),\n },\n ],\n };\n};\n","import { useAuth } from \"alepha/react/auth\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { GettingStartedSlide } from \"./GettingStarted.tsx\";\n\n/**\n * Hook that provides the auth slide content.\n * Content changes based on whether user is logged in.\n * Returns undefined if auth routes are not configured.\n */\nexport const useAuthSlide = (): GettingStartedSlide | undefined => {\n const { user, logout } = useAuth();\n const router = useRouter();\n\n // Check if auth routes exist\n const hasAuth = router.pages.find((it) => it.name === \"authLayout\");\n if (!hasAuth) {\n return undefined;\n }\n\n // User is logged in - show user info and logout option\n if (user) {\n return {\n text: \"Welcome back!\",\n sub: `You're signed in as ${user.email || user.username || \"user\"}.`,\n steps: [\n {\n num: \"✓\",\n text: \"Authentication is working correctly\",\n },\n {\n num: \"→\",\n text: (\n <>\n <a\n href=\"#\"\n onClick={(e) => {\n e.preventDefault();\n logout();\n }}\n >\n Sign out\n </a>{\" \"}\n to test the login flow\n </>\n ),\n },\n ],\n };\n }\n\n // User is not logged in - show signup instructions\n const authAnchorProps = router.anchor(router.path(\"login\"));\n\n return {\n text: \"Who are you?\",\n sub: \"Create your first account.\",\n steps: [\n {\n num: \"1\",\n text: (\n <>\n Sign up at <a {...authAnchorProps}>/auth/login</a>\n </>\n ),\n },\n {\n num: \"2\",\n text: (\n <>\n Customize in <code>src/api/AppSecurity.ts</code>\n </>\n ),\n },\n ],\n };\n};\n","import type { GettingStartedSlide } from \"./GettingStarted.tsx\";\n\n/**\n * Hook that provides the devtools slide content.\n * Only shown when @alepha/devtools is installed and enabled.\n * Returns undefined if devtools are not available.\n */\nexport const useDevtoolsSlide = (): GettingStartedSlide | undefined => {\n if (!import.meta.env?.VITE_ALEPHA_DEVTOOLS) {\n return undefined;\n }\n\n return {\n text: \"Inspect everything.\",\n sub: \"DevTools are built in.\",\n steps: [\n {\n num: \"→\",\n text: (\n <>\n Open{\" \"}\n <a href=\"/__devtools/\" target=\"_blank\" rel=\"noopener noreferrer\">\n /__devtools\n </a>{\" \"}\n to explore your app\n </>\n ),\n },\n {\n num: \"✓\",\n text: \"Browse entities, logs, configuration and dependencies\",\n },\n ],\n };\n};\n","import type { ReactNode } from \"react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useAdminSlide } from \"./GettingStartedAdminSlide.tsx\";\nimport { useAuthSlide } from \"./GettingStartedAuthSlide.tsx\";\nimport { useDevtoolsSlide } from \"./GettingStartedDevtoolsSlide.tsx\";\n\nexport type GettingStartedStep = {\n num: string;\n text: ReactNode;\n};\n\nexport type GettingStartedSlide = {\n text: string;\n sub: string;\n detail?: string;\n steps?: GettingStartedStep[];\n links?: { label: string; href: string }[];\n};\n\nexport type GettingStartedWelcome = {\n appName: string;\n serverTime: string;\n};\n\nexport type GettingStartedProps = {\n /**\n * Welcome data loaded from the server via SSR.\n *\n * When provided, displays app name and server timestamp on the first slide,\n * demonstrating the full SSR → hydration data flow.\n */\n welcome?: GettingStartedWelcome;\n};\n\nconst defaultFirstSlide: GettingStartedSlide = {\n text: \"Let's begin.\",\n sub: \"Every story starts with a blank page.\",\n detail: \"This one is yours.\",\n};\n\nconst helpSlide: GettingStartedSlide = {\n text: \"Need help?\",\n sub: \"We've got you covered.\",\n detail: \"Even our AI friends can read the docs.\",\n links: [\n { label: \"alepha.dev\", href: \"https://alepha.dev\" },\n { label: \"llms.txt\", href: \"https://alepha.dev/llms.txt\" },\n ],\n};\n\nconst formatServerTime = (isoString: string): string => {\n try {\n const date = new Date(isoString);\n return date.toLocaleTimeString(undefined, {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n } catch {\n return isoString;\n }\n};\n\n/**\n * A welcome component displayed when creating a new Alepha application.\n */\nconst GettingStarted = ({ welcome }: GettingStartedProps) => {\n const [index, setIndex] = useState(0);\n const [direction, setDirection] = useState<\"next\" | \"prev\">(\"next\");\n\n // Get auth-aware slide content (hooks return undefined if routes don't exist)\n const authSlide = useAuthSlide();\n const adminSlide = useAdminSlide();\n const devtoolsSlide = useDevtoolsSlide();\n\n const filteredMessages = useMemo(() => {\n const result: GettingStartedSlide[] = [];\n\n // First slide: use welcome data if provided, otherwise default\n if (welcome) {\n result.push({\n ...defaultFirstSlide,\n detail: `Server time: ${formatServerTime(welcome.serverTime)} - App: ${welcome.appName}`,\n });\n } else {\n result.push(defaultFirstSlide);\n }\n\n // Add auth slide if auth routes exist\n if (authSlide) {\n result.push(authSlide);\n }\n\n // Add admin slide if admin routes exist\n if (adminSlide) {\n result.push(adminSlide);\n }\n\n // Add devtools slide in non-production environments\n if (devtoolsSlide) {\n result.push(devtoolsSlide);\n }\n\n // Add \"Need help?\" message\n result.push(helpSlide);\n\n return result;\n }, [welcome, authSlide, adminSlide, devtoolsSlide]);\n\n const current = filteredMessages[index];\n\n const prev = useCallback(() => {\n setDirection(\"prev\");\n setIndex(\n (i) => (i - 1 + filteredMessages.length) % filteredMessages.length,\n );\n }, [filteredMessages.length]);\n\n const next = useCallback(() => {\n setDirection(\"next\");\n setIndex((i) => (i + 1) % filteredMessages.length);\n }, [filteredMessages.length]);\n\n const goTo = useCallback(\n (i: number) => {\n setDirection(i > index ? \"next\" : \"prev\");\n setIndex(i);\n },\n [index],\n );\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowLeft\") {\n prev();\n } else if (e.key === \"ArrowRight\") {\n next();\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [prev, next]);\n\n return (\n <>\n <style>{styles}</style>\n <main className=\"alepha-blank\">\n <div className=\"alepha-blank-content\">\n <div className=\"alepha-blank-text-block\">\n <h1\n className={`alepha-blank-message alepha-blank-slide-${direction}`}\n key={index}\n >\n {current.text}\n </h1>\n <p\n className={`alepha-blank-sub alepha-blank-slide-${direction}`}\n key={`sub-${index}`}\n >\n {current.sub}\n </p>\n {current.detail && (\n <p\n className={`alepha-blank-detail alepha-blank-slide-${direction}`}\n key={`detail-${index}`}\n >\n {current.detail}\n </p>\n )}\n {current.steps && (\n <div\n className={`alepha-blank-steps alepha-blank-slide-${direction}`}\n key={`steps-${index}`}\n >\n {current.steps.map((step, i) => (\n <div\n key={i}\n className=\"alepha-blank-step\"\n style={{ animationDelay: `${0.15 + i * 0.08}s` }}\n >\n <span className=\"alepha-blank-step-num\">{step.num}</span>\n <span className=\"alepha-blank-step-text\">{step.text}</span>\n </div>\n ))}\n </div>\n )}\n {current.links && (\n <div\n className={`alepha-blank-links alepha-blank-slide-${direction}`}\n key={`links-${index}`}\n >\n {current.links.map((link, i) => (\n <a\n key={link.href}\n href={link.href}\n target={link.href.startsWith(\"http\") ? \"_blank\" : undefined}\n rel={\n link.href.startsWith(\"http\")\n ? \"noopener noreferrer\"\n : undefined\n }\n style={{ animationDelay: `${0.1 + i * 0.05}s` }}\n >\n {link.label}\n </a>\n ))}\n </div>\n )}\n </div>\n\n <div className=\"alepha-blank-slider\">\n <button\n className=\"alepha-blank-nav-btn\"\n onClick={prev}\n aria-label=\"Previous\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path\n d=\"M9 3L5 7L9 11\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n <div className=\"alepha-blank-dots\">\n {filteredMessages.map((_, i) => (\n <button\n key={i}\n className={`alepha-blank-dot ${i === index ? \"active\" : \"\"}`}\n onClick={() => goTo(i)}\n aria-label={`Go to message ${i + 1}`}\n />\n ))}\n </div>\n <button\n className=\"alepha-blank-nav-btn\"\n onClick={next}\n aria-label=\"Next\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path\n d=\"M5 3L9 7L5 11\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n\n <div className=\"alepha-blank-hint\">\n <kbd>←</kbd> <kbd>→</kbd> to navigate\n </div>\n </div>\n </main>\n </>\n );\n};\n\nexport default GettingStarted;\n\nconst styles = `\n.alepha-blank {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100svh;\n background: #fafafa;\n font-family: system-ui, -apple-system, sans-serif;\n color: #171717;\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n width: 100%;\n}\n\n.alepha-blank-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n max-width: 640px;\n}\n\n.alepha-blank-text-block {\n min-height: 320px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n}\n\n.alepha-blank-message {\n font-size: clamp(2rem, 8vw, 3rem);\n font-weight: 600;\n letter-spacing: -0.02em;\n margin: 0;\n line-height: 1.1;\n}\n\n.alepha-blank-sub {\n font-size: 1.0625rem;\n color: #525252;\n margin: 1rem 0 0;\n font-weight: 400;\n line-height: 1.5;\n}\n\n.alepha-blank-detail {\n font-size: 0.875rem;\n color: #a3a3a3;\n margin: 0.5rem 0 0;\n font-weight: 400;\n line-height: 1.5;\n}\n\n.alepha-blank-slide-next {\n animation: alepha-blank-slideNext 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n\n.alepha-blank-slide-prev {\n animation: alepha-blank-slidePrev 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n\n@keyframes alepha-blank-slideNext {\n from {\n opacity: 0;\n transform: translateX(20px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n@keyframes alepha-blank-slidePrev {\n from {\n opacity: 0;\n transform: translateX(-20px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n.alepha-blank-steps {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n margin-top: 1.25rem;\n text-align: left;\n}\n\n.alepha-blank-step {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.25rem 0;\n opacity: 0;\n animation: alepha-blank-stepIn 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards;\n}\n\n.alepha-blank-step-num {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: #171717;\n color: #fff;\n font-size: 0.6875rem;\n font-weight: 600;\n flex-shrink: 0;\n}\n\n.alepha-blank-step-text {\n font-size: 0.8125rem;\n color: #525252;\n line-height: 1.5;\n}\n\n.alepha-blank-step-text code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.75rem;\n background: #f5f5f5;\n color: #171717;\n padding: 0.0625rem 0.375rem;\n border-radius: 3px;\n border: 1px solid #e5e5e5;\n}\n\n.alepha-blank-step-text a {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n color: #525252;\n text-decoration: underline;\n text-underline-offset: 2px;\n transition: color 0.15s ease;\n}\n\n.alepha-blank-step-text a:hover {\n color: #171717;\n}\n\n@keyframes alepha-blank-stepIn {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.alepha-blank-links {\n display: flex;\n gap: 1.5rem;\n margin-top: 1.25rem;\n}\n\n.alepha-blank-links a {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.875rem;\n color: #525252;\n text-decoration: none;\n position: relative;\n opacity: 0;\n animation: alepha-blank-fadeIn 0.3s ease-out forwards;\n transition: color 0.2s ease;\n}\n\n.alepha-blank-links a::after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n bottom: -2px;\n height: 1px;\n background: #a3a3a3;\n transform: scaleX(1);\n transform-origin: left;\n transition:\n transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),\n background 0.2s ease;\n}\n\n.alepha-blank-links a:hover {\n color: #171717;\n}\n\n.alepha-blank-links a:hover::after {\n background: #171717;\n transform: scaleX(1.05);\n}\n\n.alepha-blank-slider {\n display: flex;\n align-items: center;\n gap: 1rem;\n margin-top: 2.5rem;\n}\n\n.alepha-blank-dots {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.alepha-blank-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: transparent;\n border: 1.5px solid #d4d4d4;\n padding: 0;\n cursor: pointer;\n transition: all 0.25s cubic-bezier(0.22, 1, 0.36, 1);\n}\n\n.alepha-blank-dot:hover {\n border-color: #a3a3a3;\n transform: scale(1.2);\n}\n\n.alepha-blank-dot:focus-visible {\n outline: 2px solid #737373;\n outline-offset: 2px;\n}\n\n.alepha-blank-dot.active {\n background: #737373;\n border-color: #737373;\n transform: scale(1.1);\n}\n\n.alepha-blank-nav-btn {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n font-family: inherit;\n background: transparent;\n color: #a3a3a3;\n border: 1.5px solid transparent;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.22, 1, 0.36, 1);\n}\n\n.alepha-blank-nav-btn:hover {\n color: #525252;\n background: #f0f0f0;\n}\n\n.alepha-blank-nav-btn:focus-visible {\n outline: none;\n border-color: #737373;\n color: #525252;\n}\n\n.alepha-blank-nav-btn:active {\n transform: scale(0.92);\n background: #e5e5e5;\n}\n\n.alepha-blank-hint {\n margin-top: 2rem;\n font-size: 0.75rem;\n color: #a3a3a3;\n display: flex;\n align-items: center;\n gap: 0.375rem;\n opacity: 0;\n animation: alepha-blank-fadeIn 0.5s ease-out 0.5s forwards;\n}\n\n.alepha-blank-hint kbd {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.6875rem;\n background: #f0f0f0;\n border: 1px solid #e5e5e5;\n border-radius: 4px;\n padding: 0.125rem 0.375rem;\n box-shadow: 0 1px 0 #d4d4d4;\n}\n\n@keyframes alepha-blank-fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .alepha-blank-slide-next,\n .alepha-blank-slide-prev,\n .alepha-blank-links a,\n .alepha-blank-hint,\n .alepha-blank-step {\n animation: none;\n opacity: 1;\n }\n\n .alepha-blank-dot,\n .alepha-blank-nav-btn,\n .alepha-blank-links a,\n .alepha-blank-links a::after {\n transition: none;\n }\n}\n`;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/react/intro/components/GettingStartedAdminSlide.tsx","../../../src/react/intro/components/GettingStartedAuthSlide.tsx","../../../src/react/intro/components/GettingStartedDevtoolsSlide.tsx","../../../src/react/intro/components/GettingStarted.tsx"],"sourcesContent":["import { useAuth } from \"alepha/react/auth\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { GettingStartedSlide } from \"./GettingStarted.tsx\";\n\n/**\n * Hook that provides the admin slide content.\n * Content changes based on user status and admin permissions.\n * Returns undefined if admin routes are not configured.\n */\nexport const useAdminSlide = (): GettingStartedSlide | undefined => {\n const { user, has } = useAuth();\n const router = useRouter();\n\n // Check if admin routes exist\n const hasAdmin = router.pages.find((it) => it.name === \"adminLayout\");\n if (!hasAdmin) {\n return undefined;\n }\n\n const adminAnchorProps = router.anchor(router.path(\"adminLayout\"));\n const canAccessAdmin = has(\"admin:*\");\n\n // User is admin - show success message\n if (canAccessAdmin) {\n return {\n text: \"You're in control.\",\n sub: \"Admin access granted.\",\n steps: [\n {\n num: \"✓\",\n text: \"You have admin privileges\",\n },\n {\n num: \"→\",\n text: (\n <>\n Go to <a {...adminAnchorProps}>/admin</a> to manage your\n application\n </>\n ),\n },\n ],\n };\n }\n\n // User is logged in but not admin - show how to become admin\n if (user) {\n return {\n text: \"Take the wheel.\",\n sub: \"Become admin in two steps.\",\n steps: [\n {\n num: \"1\",\n text: (\n <>\n Add your email to <code>adminEmails</code> in{\" \"}\n <code>AppSecurity.ts</code>\n </>\n ),\n },\n {\n num: \"2\",\n text: (\n <>\n Go to <a {...adminAnchorProps}>/admin</a>\n </>\n ),\n },\n ],\n };\n }\n\n // User is not logged in - show full instructions\n return {\n text: \"Take the wheel.\",\n sub: \"Become admin in three steps.\",\n steps: [\n {\n num: \"1\",\n text: (\n <>\n Add your email to <code>adminEmails</code> in{\" \"}\n <code>AppSecurity.ts</code>\n </>\n ),\n },\n { num: \"2\", text: \"Create a user account with that email\" },\n {\n num: \"3\",\n text: (\n <>\n Go to <a {...adminAnchorProps}>/admin</a>\n </>\n ),\n },\n ],\n };\n};\n","import { useAuth } from \"alepha/react/auth\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { GettingStartedSlide } from \"./GettingStarted.tsx\";\n\n/**\n * Hook that provides the auth slide content.\n * Content changes based on whether user is logged in.\n * Returns undefined if auth routes are not configured.\n */\nexport const useAuthSlide = (): GettingStartedSlide | undefined => {\n const { user, logout } = useAuth();\n const router = useRouter();\n\n // Check if auth routes exist\n const hasAuth = router.pages.find((it) => it.name === \"authLayout\");\n if (!hasAuth) {\n return undefined;\n }\n\n // User is logged in - show user info and logout option\n if (user) {\n return {\n text: \"Welcome back!\",\n sub: `You're signed in as ${user.email || user.username || \"user\"}.`,\n steps: [\n {\n num: \"✓\",\n text: \"Authentication is working correctly\",\n },\n {\n num: \"→\",\n text: (\n <>\n <a\n href=\"#\"\n onClick={(e) => {\n e.preventDefault();\n logout();\n }}\n >\n Sign out\n </a>{\" \"}\n to test the login flow\n </>\n ),\n },\n ],\n };\n }\n\n // User is not logged in - show signup instructions\n const authAnchorProps = router.anchor(router.path(\"login\"));\n\n return {\n text: \"Who are you?\",\n sub: \"Create your first account.\",\n steps: [\n {\n num: \"1\",\n text: (\n <>\n Sign up at <a {...authAnchorProps}>/auth/login</a>\n </>\n ),\n },\n {\n num: \"2\",\n text: (\n <>\n Customize in <code>src/api/AppSecurity.ts</code>\n </>\n ),\n },\n ],\n };\n};\n","import type { GettingStartedSlide } from \"./GettingStarted.tsx\";\n\n/**\n * Hook that provides the devtools slide content.\n * Only shown when @alepha/devtools is installed and enabled.\n * Returns undefined if devtools are not available.\n */\nexport const useDevtoolsSlide = (): GettingStartedSlide | undefined => {\n if (!import.meta.env?.VITE_ALEPHA_DEVTOOLS) {\n return undefined;\n }\n\n return {\n text: \"Inspect everything.\",\n sub: \"DevTools are built in.\",\n steps: [\n {\n num: \"→\",\n text: (\n <>\n Open{\" \"}\n <a href=\"/__devtools/\" target=\"_blank\" rel=\"noopener noreferrer\">\n /__devtools\n </a>{\" \"}\n to explore your app\n </>\n ),\n },\n {\n num: \"✓\",\n text: \"Browse entities, logs, configuration and dependencies\",\n },\n ],\n };\n};\n","import type { ReactNode } from \"react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useAdminSlide } from \"./GettingStartedAdminSlide.tsx\";\nimport { useAuthSlide } from \"./GettingStartedAuthSlide.tsx\";\nimport { useDevtoolsSlide } from \"./GettingStartedDevtoolsSlide.tsx\";\n\nexport type GettingStartedStep = {\n num: string;\n text: ReactNode;\n};\n\nexport type GettingStartedSlide = {\n text: string;\n sub: string;\n detail?: string;\n steps?: GettingStartedStep[];\n links?: { label: string; href: string }[];\n};\n\nexport type GettingStartedWelcome = {\n appName: string;\n serverTime: string;\n};\n\nexport type GettingStartedProps = {\n /**\n * Welcome data loaded from the server via SSR.\n *\n * When provided, displays app name and server timestamp on the first slide,\n * demonstrating the full SSR → hydration data flow.\n */\n welcome?: GettingStartedWelcome;\n};\n\nconst defaultFirstSlide: GettingStartedSlide = {\n text: \"Let's begin.\",\n sub: \"Every story starts with a blank page.\",\n detail: \"This one is yours.\",\n};\n\nconst helpSlide: GettingStartedSlide = {\n text: \"Need help?\",\n sub: \"We've got you covered.\",\n detail: \"Even our AI friends can read the docs.\",\n links: [\n { label: \"alepha.dev\", href: \"https://alepha.dev\" },\n { label: \"llms.txt\", href: \"https://alepha.dev/llms.txt\" },\n ],\n};\n\nconst formatServerTime = (isoString: string): string => {\n try {\n const date = new Date(isoString);\n return date.toLocaleTimeString(undefined, {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n } catch {\n return isoString;\n }\n};\n\n/**\n * A welcome component displayed when creating a new Alepha application.\n */\nconst GettingStarted = ({ welcome }: GettingStartedProps) => {\n const [index, setIndex] = useState(0);\n const [direction, setDirection] = useState<\"next\" | \"prev\">(\"next\");\n\n // Get auth-aware slide content (hooks return undefined if routes don't exist)\n const authSlide = useAuthSlide();\n const adminSlide = useAdminSlide();\n const devtoolsSlide = useDevtoolsSlide();\n\n const filteredMessages = useMemo(() => {\n const result: GettingStartedSlide[] = [];\n\n // First slide: use welcome data if provided, otherwise default\n if (welcome) {\n result.push({\n ...defaultFirstSlide,\n detail: `Server time: ${formatServerTime(welcome.serverTime)} - App: ${welcome.appName}`,\n });\n } else {\n result.push(defaultFirstSlide);\n }\n\n // Add auth slide if auth routes exist\n if (authSlide) {\n result.push(authSlide);\n }\n\n // Add admin slide if admin routes exist\n if (adminSlide) {\n result.push(adminSlide);\n }\n\n // Add devtools slide in non-production environments\n if (devtoolsSlide) {\n result.push(devtoolsSlide);\n }\n\n // Add \"Need help?\" message\n result.push(helpSlide);\n\n return result;\n }, [welcome, authSlide, adminSlide, devtoolsSlide]);\n\n const current = filteredMessages[index];\n\n const prev = useCallback(() => {\n setDirection(\"prev\");\n setIndex(\n (i) => (i - 1 + filteredMessages.length) % filteredMessages.length,\n );\n }, [filteredMessages.length]);\n\n const next = useCallback(() => {\n setDirection(\"next\");\n setIndex((i) => (i + 1) % filteredMessages.length);\n }, [filteredMessages.length]);\n\n const goTo = useCallback(\n (i: number) => {\n setDirection(i > index ? \"next\" : \"prev\");\n setIndex(i);\n },\n [index],\n );\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowLeft\") {\n prev();\n } else if (e.key === \"ArrowRight\") {\n next();\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [prev, next]);\n\n return (\n <>\n <style>{styles}</style>\n <main className=\"alepha-blank\">\n <div className=\"alepha-blank-content\">\n <div className=\"alepha-blank-text-block\">\n <h1\n className={`alepha-blank-message alepha-blank-slide-${direction}`}\n key={index}\n >\n {current.text}\n </h1>\n <p\n className={`alepha-blank-sub alepha-blank-slide-${direction}`}\n key={`sub-${index}`}\n >\n {current.sub}\n </p>\n {current.detail && (\n <p\n className={`alepha-blank-detail alepha-blank-slide-${direction}`}\n key={`detail-${index}`}\n >\n {current.detail}\n </p>\n )}\n {current.steps && (\n <div\n className={`alepha-blank-steps alepha-blank-slide-${direction}`}\n key={`steps-${index}`}\n >\n {current.steps.map((step, i) => (\n <div\n key={i}\n className=\"alepha-blank-step\"\n style={{ animationDelay: `${0.15 + i * 0.08}s` }}\n >\n <span className=\"alepha-blank-step-num\">{step.num}</span>\n <span className=\"alepha-blank-step-text\">{step.text}</span>\n </div>\n ))}\n </div>\n )}\n {current.links && (\n <div\n className={`alepha-blank-links alepha-blank-slide-${direction}`}\n key={`links-${index}`}\n >\n {current.links.map((link, i) => (\n <a\n key={link.href}\n href={link.href}\n target={link.href.startsWith(\"http\") ? \"_blank\" : undefined}\n rel={\n link.href.startsWith(\"http\")\n ? \"noopener noreferrer\"\n : undefined\n }\n style={{ animationDelay: `${0.1 + i * 0.05}s` }}\n >\n {link.label}\n </a>\n ))}\n </div>\n )}\n </div>\n\n <div className=\"alepha-blank-slider\">\n <button\n className=\"alepha-blank-nav-btn\"\n onClick={prev}\n aria-label=\"Previous\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path\n d=\"M9 3L5 7L9 11\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n <div className=\"alepha-blank-dots\">\n {filteredMessages.map((_, i) => (\n <button\n key={i}\n className={`alepha-blank-dot ${i === index ? \"active\" : \"\"}`}\n onClick={() => goTo(i)}\n aria-label={`Go to message ${i + 1}`}\n />\n ))}\n </div>\n <button\n className=\"alepha-blank-nav-btn\"\n onClick={next}\n aria-label=\"Next\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n <path\n d=\"M5 3L9 7L5 11\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n\n <div className=\"alepha-blank-hint\">\n <kbd>←</kbd> <kbd>→</kbd> to navigate\n </div>\n </div>\n </main>\n </>\n );\n};\n\nexport default GettingStarted;\n\nconst styles = `\n.alepha-blank {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100svh;\n background: #fafafa;\n font-family: system-ui, -apple-system, sans-serif;\n color: #171717;\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n width: 100%;\n}\n\n.alepha-blank-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n max-width: 640px;\n}\n\n.alepha-blank-text-block {\n min-height: 320px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n}\n\n.alepha-blank-message {\n font-size: clamp(2rem, 8vw, 3rem);\n font-weight: 600;\n letter-spacing: -0.02em;\n margin: 0;\n line-height: 1.1;\n}\n\n.alepha-blank-sub {\n font-size: 1.0625rem;\n color: #525252;\n margin: 1rem 0 0;\n font-weight: 400;\n line-height: 1.5;\n}\n\n.alepha-blank-detail {\n font-size: 0.875rem;\n color: #a3a3a3;\n margin: 0.5rem 0 0;\n font-weight: 400;\n line-height: 1.5;\n}\n\n.alepha-blank-slide-next {\n animation: alepha-blank-slideNext 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n\n.alepha-blank-slide-prev {\n animation: alepha-blank-slidePrev 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n\n@keyframes alepha-blank-slideNext {\n from {\n opacity: 0;\n transform: translateX(20px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n@keyframes alepha-blank-slidePrev {\n from {\n opacity: 0;\n transform: translateX(-20px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n.alepha-blank-steps {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n margin-top: 1.25rem;\n text-align: left;\n}\n\n.alepha-blank-step {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.25rem 0;\n opacity: 0;\n animation: alepha-blank-stepIn 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards;\n}\n\n.alepha-blank-step-num {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: #171717;\n color: #fff;\n font-size: 0.6875rem;\n font-weight: 600;\n flex-shrink: 0;\n}\n\n.alepha-blank-step-text {\n font-size: 0.8125rem;\n color: #525252;\n line-height: 1.5;\n}\n\n.alepha-blank-step-text code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.75rem;\n background: #f5f5f5;\n color: #171717;\n padding: 0.0625rem 0.375rem;\n border-radius: 3px;\n border: 1px solid #e5e5e5;\n}\n\n.alepha-blank-step-text a {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n color: #525252;\n text-decoration: underline;\n text-underline-offset: 2px;\n transition: color 0.15s ease;\n}\n\n.alepha-blank-step-text a:hover {\n color: #171717;\n}\n\n@keyframes alepha-blank-stepIn {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.alepha-blank-links {\n display: flex;\n gap: 1.5rem;\n margin-top: 1.25rem;\n}\n\n.alepha-blank-links a {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.875rem;\n color: #525252;\n text-decoration: none;\n position: relative;\n opacity: 0;\n animation: alepha-blank-fadeIn 0.3s ease-out forwards;\n transition: color 0.2s ease;\n}\n\n.alepha-blank-links a::after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n bottom: -2px;\n height: 1px;\n background: #a3a3a3;\n transform: scaleX(1);\n transform-origin: left;\n transition:\n transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),\n background 0.2s ease;\n}\n\n.alepha-blank-links a:hover {\n color: #171717;\n}\n\n.alepha-blank-links a:hover::after {\n background: #171717;\n transform: scaleX(1.05);\n}\n\n.alepha-blank-slider {\n display: flex;\n align-items: center;\n gap: 1rem;\n margin-top: 2.5rem;\n}\n\n.alepha-blank-dots {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.alepha-blank-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: transparent;\n border: 1.5px solid #d4d4d4;\n padding: 0;\n cursor: pointer;\n transition: all 0.25s cubic-bezier(0.22, 1, 0.36, 1);\n}\n\n.alepha-blank-dot:hover {\n border-color: #a3a3a3;\n transform: scale(1.2);\n}\n\n.alepha-blank-dot:focus-visible {\n outline: 2px solid #737373;\n outline-offset: 2px;\n}\n\n.alepha-blank-dot.active {\n background: #737373;\n border-color: #737373;\n transform: scale(1.1);\n}\n\n.alepha-blank-nav-btn {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n font-family: inherit;\n background: transparent;\n color: #a3a3a3;\n border: 1.5px solid transparent;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.22, 1, 0.36, 1);\n}\n\n.alepha-blank-nav-btn:hover {\n color: #525252;\n background: #f0f0f0;\n}\n\n.alepha-blank-nav-btn:focus-visible {\n outline: none;\n border-color: #737373;\n color: #525252;\n}\n\n.alepha-blank-nav-btn:active {\n transform: scale(0.92);\n background: #e5e5e5;\n}\n\n.alepha-blank-hint {\n margin-top: 2rem;\n font-size: 0.75rem;\n color: #a3a3a3;\n display: flex;\n align-items: center;\n gap: 0.375rem;\n opacity: 0;\n animation: alepha-blank-fadeIn 0.5s ease-out 0.5s forwards;\n}\n\n.alepha-blank-hint kbd {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.6875rem;\n background: #f0f0f0;\n border: 1px solid #e5e5e5;\n border-radius: 4px;\n padding: 0.125rem 0.375rem;\n box-shadow: 0 1px 0 #d4d4d4;\n}\n\n@keyframes alepha-blank-fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .alepha-blank-slide-next,\n .alepha-blank-slide-prev,\n .alepha-blank-links a,\n .alepha-blank-hint,\n .alepha-blank-step {\n animation: none;\n opacity: 1;\n }\n\n .alepha-blank-dot,\n .alepha-blank-nav-btn,\n .alepha-blank-links a,\n .alepha-blank-links a::after {\n transition: none;\n }\n}\n`;\n"],"mappings":"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"}