alepha 0.20.1 → 0.20.3

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 (393) hide show
  1. package/README.md +0 -1
  2. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  3. package/assets/swagger-ui/swagger-ui.css +1 -1
  4. package/dist/api/audits/index.browser.js +49 -0
  5. package/dist/api/audits/index.browser.js.map +1 -1
  6. package/dist/api/audits/index.d.ts.map +1 -1
  7. package/dist/api/audits/index.js +49 -0
  8. package/dist/api/audits/index.js.map +1 -1
  9. package/dist/api/files/index.d.ts.map +1 -1
  10. package/dist/api/files/index.js +2 -1
  11. package/dist/api/files/index.js.map +1 -1
  12. package/dist/api/jobs/index.browser.js +64 -148
  13. package/dist/api/jobs/index.browser.js.map +1 -1
  14. package/dist/api/jobs/index.d.ts +339 -600
  15. package/dist/api/jobs/index.d.ts.map +1 -1
  16. package/dist/api/jobs/index.js +605 -1012
  17. package/dist/api/jobs/index.js.map +1 -1
  18. package/dist/api/keys/index.js.map +1 -1
  19. package/dist/api/notifications/index.d.ts +79 -27
  20. package/dist/api/notifications/index.d.ts.map +1 -1
  21. package/dist/api/notifications/index.js +90 -23
  22. package/dist/api/notifications/index.js.map +1 -1
  23. package/dist/api/organizations/index.d.ts.map +1 -1
  24. package/dist/api/parameters/index.browser.js +37 -0
  25. package/dist/api/parameters/index.browser.js.map +1 -1
  26. package/dist/api/parameters/index.d.ts +4 -65
  27. package/dist/api/parameters/index.d.ts.map +1 -1
  28. package/dist/api/parameters/index.js +37 -0
  29. package/dist/api/parameters/index.js.map +1 -1
  30. package/dist/api/payments/index.d.ts +2 -1
  31. package/dist/api/payments/index.d.ts.map +1 -1
  32. package/dist/api/payments/index.js +4 -2
  33. package/dist/api/payments/index.js.map +1 -1
  34. package/dist/api/users/index.d.ts +225 -5199
  35. package/dist/api/users/index.d.ts.map +1 -1
  36. package/dist/api/users/index.js +15 -11
  37. package/dist/api/users/index.js.map +1 -1
  38. package/dist/api/verifications/index.d.ts.map +1 -1
  39. package/dist/api/verifications/index.js +4 -2
  40. package/dist/api/verifications/index.js.map +1 -1
  41. package/dist/bucket/index.js +5 -1
  42. package/dist/bucket/index.js.map +1 -1
  43. package/dist/bucket/index.workerd.js +5 -1
  44. package/dist/bucket/index.workerd.js.map +1 -1
  45. package/dist/cache/core/index.js.map +1 -1
  46. package/dist/cache/core/index.workerd.js.map +1 -1
  47. package/dist/captcha/index.js.map +1 -1
  48. package/dist/cli/core/index.d.ts +225 -11681
  49. package/dist/cli/core/index.d.ts.map +1 -1
  50. package/dist/cli/core/index.js +732 -257
  51. package/dist/cli/core/index.js.map +1 -1
  52. package/dist/cli/devtools/index.js +7 -1
  53. package/dist/cli/devtools/index.js.map +1 -1
  54. package/dist/cli/platform/index.d.ts +65 -63
  55. package/dist/cli/platform/index.d.ts.map +1 -1
  56. package/dist/cli/platform/index.js +140 -27
  57. package/dist/cli/platform/index.js.map +1 -1
  58. package/dist/cli/vendor/index.js +15 -0
  59. package/dist/cli/vendor/index.js.map +1 -1
  60. package/dist/command/index.d.ts +1 -1
  61. package/dist/command/index.js +1 -1
  62. package/dist/command/index.js.map +1 -1
  63. package/dist/core/index.browser.js +6 -0
  64. package/dist/core/index.browser.js.map +1 -1
  65. package/dist/core/index.d.ts +8 -8
  66. package/dist/core/index.d.ts.map +1 -1
  67. package/dist/core/index.js +6 -0
  68. package/dist/core/index.js.map +1 -1
  69. package/dist/core/index.native.js +6 -0
  70. package/dist/core/index.native.js.map +1 -1
  71. package/dist/core/index.workerd.js +6 -0
  72. package/dist/core/index.workerd.js.map +1 -1
  73. package/dist/crypto/index.js.map +1 -1
  74. package/dist/datetime/index.js.map +1 -1
  75. package/dist/email/core/index.js.map +1 -1
  76. package/dist/email/smtp/index.js +2 -10522
  77. package/dist/email/smtp/index.js.map +1 -1
  78. package/dist/fake/index.d.ts +4 -8085
  79. package/dist/fake/index.d.ts.map +1 -1
  80. package/dist/fake/index.js +3 -33554
  81. package/dist/fake/index.js.map +1 -1
  82. package/dist/lock/core/index.js.map +1 -1
  83. package/dist/lock/redis/index.js.map +1 -1
  84. package/dist/logger/index.js +32 -1
  85. package/dist/logger/index.js.map +1 -1
  86. package/dist/mcp/index.js +5 -1
  87. package/dist/mcp/index.js.map +1 -1
  88. package/dist/orm/core/index.browser.js +1 -361
  89. package/dist/orm/core/index.browser.js.map +1 -1
  90. package/dist/orm/core/index.bun.js +14 -406
  91. package/dist/orm/core/index.bun.js.map +1 -1
  92. package/dist/orm/core/index.d.ts +96 -5117
  93. package/dist/orm/core/index.d.ts.map +1 -1
  94. package/dist/orm/core/index.js +23 -419
  95. package/dist/orm/core/index.js.map +1 -1
  96. package/dist/orm/postgres/index.bun.js +17 -20
  97. package/dist/orm/postgres/index.bun.js.map +1 -1
  98. package/dist/orm/postgres/index.d.ts +2 -613
  99. package/dist/orm/postgres/index.d.ts.map +1 -1
  100. package/dist/orm/postgres/index.js +17 -20
  101. package/dist/orm/postgres/index.js.map +1 -1
  102. package/dist/react/core/index.js.map +1 -1
  103. package/dist/react/form/index.d.ts +60 -1
  104. package/dist/react/form/index.d.ts.map +1 -1
  105. package/dist/react/form/index.js +86 -1
  106. package/dist/react/form/index.js.map +1 -1
  107. package/dist/react/head/index.browser.js +16 -1
  108. package/dist/react/head/index.browser.js.map +1 -1
  109. package/dist/react/head/index.d.ts +6 -0
  110. package/dist/react/head/index.d.ts.map +1 -1
  111. package/dist/react/head/index.js +16 -1
  112. package/dist/react/head/index.js.map +1 -1
  113. package/dist/react/i18n/index.js.map +1 -1
  114. package/dist/react/intro/index.js +22 -17
  115. package/dist/react/intro/index.js.map +1 -1
  116. package/dist/react/router/index.browser.js +78 -12
  117. package/dist/react/router/index.browser.js.map +1 -1
  118. package/dist/react/router/index.d.ts +57 -13
  119. package/dist/react/router/index.d.ts.map +1 -1
  120. package/dist/react/router/index.js +102 -14
  121. package/dist/react/router/index.js.map +1 -1
  122. package/dist/react/testing/index.d.ts +1 -411
  123. package/dist/react/testing/index.d.ts.map +1 -1
  124. package/dist/react/testing/index.js +13 -12293
  125. package/dist/react/testing/index.js.map +1 -1
  126. package/dist/react/ui/index.d.ts +124 -0
  127. package/dist/react/ui/index.d.ts.map +1 -0
  128. package/dist/react/ui/index.js +209 -0
  129. package/dist/react/ui/index.js.map +1 -0
  130. package/dist/react/websocket/index.js.map +1 -1
  131. package/dist/redis/index.js.map +1 -1
  132. package/dist/router/index.d.ts +13 -13
  133. package/dist/router/index.d.ts.map +1 -1
  134. package/dist/router/index.js +45 -32
  135. package/dist/router/index.js.map +1 -1
  136. package/dist/scheduler/index.d.ts +1 -83
  137. package/dist/scheduler/index.d.ts.map +1 -1
  138. package/dist/scheduler/index.js +2 -391
  139. package/dist/scheduler/index.js.map +1 -1
  140. package/dist/scheduler/index.workerd.js +2 -391
  141. package/dist/scheduler/index.workerd.js.map +1 -1
  142. package/dist/security/index.browser.js.map +1 -1
  143. package/dist/security/index.d.ts +2 -325
  144. package/dist/security/index.d.ts.map +1 -1
  145. package/dist/security/index.js +3 -1362
  146. package/dist/security/index.js.map +1 -1
  147. package/dist/server/auth/index.d.ts +1 -1054
  148. package/dist/server/auth/index.d.ts.map +1 -1
  149. package/dist/server/auth/index.js +16 -1224
  150. package/dist/server/auth/index.js.map +1 -1
  151. package/dist/server/cookies/index.js.map +1 -1
  152. package/dist/server/core/index.browser.js.map +1 -1
  153. package/dist/server/core/index.d.ts +1 -4
  154. package/dist/server/core/index.d.ts.map +1 -1
  155. package/dist/server/core/index.js +19 -4
  156. package/dist/server/core/index.js.map +1 -1
  157. package/dist/server/links/index.browser.js.map +1 -1
  158. package/dist/server/links/index.js.map +1 -1
  159. package/dist/server/metrics/index.d.ts +1 -514
  160. package/dist/server/metrics/index.d.ts.map +1 -1
  161. package/dist/server/metrics/index.js +4 -4356
  162. package/dist/server/metrics/index.js.map +1 -1
  163. package/dist/server/rate-limit/index.js.map +1 -1
  164. package/dist/server/static/index.js.map +1 -1
  165. package/dist/server/swagger/index.js +1 -1
  166. package/dist/server/swagger/index.js.map +1 -1
  167. package/dist/sms/index.js.map +1 -1
  168. package/dist/system/index.browser.js.map +1 -1
  169. package/dist/system/index.d.ts.map +1 -1
  170. package/dist/system/index.js +1 -0
  171. package/dist/system/index.js.map +1 -1
  172. package/dist/system/index.workerd.js.map +1 -1
  173. package/dist/topic/core/index.js +1 -1
  174. package/dist/topic/core/index.js.map +1 -1
  175. package/dist/websocket/index.browser.js +21 -0
  176. package/dist/websocket/index.browser.js.map +1 -1
  177. package/dist/websocket/index.js +21 -0
  178. package/dist/websocket/index.js.map +1 -1
  179. package/package.json +23 -37
  180. package/src/api/files/__tests__/FileController.spec.ts +1 -1
  181. package/src/api/files/jobs/FileJobs.ts +2 -1
  182. package/src/api/jobs/__tests__/$job.spec.ts +320 -2867
  183. package/src/api/jobs/controllers/AdminJobController.ts +29 -138
  184. package/src/api/jobs/entities/jobExecutionEntity.ts +27 -19
  185. package/src/api/jobs/index.browser.ts +5 -7
  186. package/src/api/jobs/index.ts +23 -51
  187. package/src/api/jobs/primitives/$job.ts +66 -58
  188. package/src/api/jobs/providers/JobProvider.ts +561 -566
  189. package/src/api/jobs/providers/JobQueueProvider.ts +18 -19
  190. package/src/api/jobs/schemas/jobConfigAtom.ts +20 -23
  191. package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +3 -27
  192. package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +5 -7
  193. package/src/api/jobs/schemas/jobRegistrationSchema.ts +7 -4
  194. package/src/api/jobs/schemas/triggerJobSchema.ts +0 -1
  195. package/src/api/jobs/services/JobService.ts +90 -483
  196. package/src/api/notifications/controllers/AdminNotificationController.ts +19 -12
  197. package/src/api/notifications/index.ts +7 -4
  198. package/src/api/notifications/jobs/NotificationJobs.ts +83 -12
  199. package/src/api/payments/services/PaymentService.ts +4 -2
  200. package/src/api/users/__tests__/UserJobs.spec.ts +10 -49
  201. package/src/api/users/audits/UserAudits.ts +3 -1
  202. package/src/api/users/buckets/UserBuckets.ts +2 -1
  203. package/src/api/users/index.ts +1 -4
  204. package/src/api/users/jobs/UserJobs.ts +5 -4
  205. package/src/api/users/schemas/userQuerySchema.ts +0 -1
  206. package/src/api/users/services/UserService.ts +1 -5
  207. package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
  208. package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
  209. package/src/api/verifications/jobs/VerificationJobs.ts +2 -1
  210. package/src/api/verifications/services/VerificationService.ts +1 -0
  211. package/src/cli/core/__tests__/init.spec.ts +209 -1
  212. package/src/cli/core/commands/init.ts +9 -9
  213. package/src/cli/core/services/PackageManagerUtils.ts +22 -12
  214. package/src/cli/core/services/ProjectScaffolder.ts +300 -70
  215. package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
  216. package/src/cli/core/tasks/BuildServerTask.ts +8 -0
  217. package/src/cli/core/templates/agentMd.ts +2 -8
  218. package/src/cli/core/templates/apiIndexTs.ts +22 -14
  219. package/src/cli/core/templates/componentsJsonTs.ts +39 -0
  220. package/src/cli/core/templates/mainCss.ts +2 -36
  221. package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
  222. package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
  223. package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
  224. package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
  225. package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
  226. package/src/cli/core/templates/vitestConfigTs.ts +17 -0
  227. package/src/cli/core/templates/webAppRouterTs.ts +102 -82
  228. package/src/cli/core/templates/webIndexTs.ts +23 -1
  229. package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +22 -71
  230. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
  231. package/src/cli/platform/adapters/CloudflareAdapter.ts +12 -11
  232. package/src/cli/platform/atoms/platformOptions.ts +9 -0
  233. package/src/cli/platform/schemas/cloudflare.ts +3 -2
  234. package/src/cli/platform/services/CloudflareApi.ts +164 -25
  235. package/src/cli/platform/services/WranglerApi.ts +0 -17
  236. package/src/command/providers/CliProvider.ts +1 -1
  237. package/src/core/Alepha.ts +9 -0
  238. package/src/core/interfaces/Service.ts +3 -1
  239. package/src/core/providers/TypeProvider.ts +1 -1
  240. package/src/logger/services/Logger.ts +1 -1
  241. package/src/mcp/__tests__/$resource.spec.ts +1 -1
  242. package/src/mcp/__tests__/$tool.spec.ts +1 -1
  243. package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
  244. package/src/orm/__tests__/$repository-tests.ts +1 -0
  245. package/src/orm/__tests__/orm-next-tests.ts +2 -67
  246. package/src/orm/__tests__/orm-next.spec.ts +0 -21
  247. package/src/orm/core/index.shared.ts +0 -2
  248. package/src/orm/core/index.ts +1 -2
  249. package/src/orm/core/primitives/$repository.ts +3 -6
  250. package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
  251. package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
  252. package/src/orm/core/services/ModelBuilder.ts +1 -13
  253. package/src/orm/core/services/Repository.ts +1 -42
  254. package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
  255. package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
  256. package/src/react/form/index.ts +2 -0
  257. package/src/react/form/services/parseField.ts +163 -0
  258. package/src/react/form/services/prettyName.ts +19 -0
  259. package/src/react/head/providers/BrowserHeadProvider.ts +31 -10
  260. package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
  261. package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
  262. package/src/react/router/primitives/$page.ts +35 -12
  263. package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
  264. package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
  265. package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
  266. package/src/react/router/providers/ReactServerProvider.ts +1 -0
  267. package/src/react/ui/atoms/uiAtom.ts +28 -0
  268. package/src/react/ui/components/ColorScheme.tsx +36 -0
  269. package/src/react/ui/hooks/useColorMode.ts +49 -0
  270. package/src/react/ui/hooks/useSidebarState.ts +26 -0
  271. package/src/react/ui/hooks/useTheme.ts +22 -0
  272. package/src/react/ui/index.ts +35 -0
  273. package/src/react/ui/services/UiPersistence.ts +41 -0
  274. package/src/router/TemplatedPathParser.ts +50 -51
  275. package/src/router/__tests__/RouterProvider.spec.ts +62 -0
  276. package/src/router/__tests__/TemplatedPathParser.spec.ts +18 -0
  277. package/src/router/providers/RouterProvider.ts +10 -5
  278. package/src/scheduler/providers/CronProvider.ts +1 -1
  279. package/src/security/primitives/$basicAuth.ts +1 -1
  280. package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
  281. package/src/server/core/interfaces/ServerRequest.ts +1 -0
  282. package/src/server/core/providers/ServerProvider.ts +1 -1
  283. package/src/server/core/providers/ServerRouterProvider.ts +2 -2
  284. package/src/server/core/services/HttpClient.ts +1 -1
  285. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  286. package/src/system/providers/NodeShellProvider.ts +1 -0
  287. package/src/topic/core/providers/TopicProvider.ts +1 -1
  288. package/dist/api/invitations/index.d.ts +0 -790
  289. package/dist/api/invitations/index.d.ts.map +0 -1
  290. package/dist/api/invitations/index.js +0 -662
  291. package/dist/api/invitations/index.js.map +0 -1
  292. package/dist/api/issues/index.d.ts +0 -810
  293. package/dist/api/issues/index.d.ts.map +0 -1
  294. package/dist/api/issues/index.js +0 -444
  295. package/dist/api/issues/index.js.map +0 -1
  296. package/dist/api/subscriptions/index.d.ts +0 -1692
  297. package/dist/api/subscriptions/index.d.ts.map +0 -1
  298. package/dist/api/subscriptions/index.js +0 -1867
  299. package/dist/api/subscriptions/index.js.map +0 -1
  300. package/dist/api/workflows/index.browser.js +0 -246
  301. package/dist/api/workflows/index.browser.js.map +0 -1
  302. package/dist/api/workflows/index.d.ts +0 -1618
  303. package/dist/api/workflows/index.d.ts.map +0 -1
  304. package/dist/api/workflows/index.js +0 -1495
  305. package/dist/api/workflows/index.js.map +0 -1
  306. package/dist/react/testing/chunk-DBEY4PJZ.js +0 -16
  307. package/src/api/invitations/__tests__/InvitationService.spec.ts +0 -439
  308. package/src/api/invitations/controllers/AdminInvitationController.ts +0 -86
  309. package/src/api/invitations/controllers/InvitationController.ts +0 -84
  310. package/src/api/invitations/entities/invitations.ts +0 -33
  311. package/src/api/invitations/index.ts +0 -58
  312. package/src/api/invitations/jobs/InvitationJobs.ts +0 -37
  313. package/src/api/invitations/providers/InvitationProvider.ts +0 -45
  314. package/src/api/invitations/schemas/createInvitationSchema.ts +0 -12
  315. package/src/api/invitations/schemas/invitationConfigAtom.ts +0 -20
  316. package/src/api/invitations/schemas/invitationQuerySchema.ts +0 -15
  317. package/src/api/invitations/schemas/invitationResourceSchema.ts +0 -6
  318. package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +0 -22
  319. package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +0 -10
  320. package/src/api/invitations/services/InvitationService.ts +0 -556
  321. package/src/api/issues/__tests__/IssueService.spec.ts +0 -263
  322. package/src/api/issues/controllers/AdminIssueController.ts +0 -149
  323. package/src/api/issues/controllers/IssueController.ts +0 -44
  324. package/src/api/issues/entities/issues.ts +0 -49
  325. package/src/api/issues/index.ts +0 -50
  326. package/src/api/issues/schemas/createIssueSchema.ts +0 -13
  327. package/src/api/issues/schemas/issueConfigAtom.ts +0 -13
  328. package/src/api/issues/schemas/issueQuerySchema.ts +0 -18
  329. package/src/api/issues/schemas/issueResourceSchema.ts +0 -6
  330. package/src/api/issues/schemas/myIssueQuerySchema.ts +0 -10
  331. package/src/api/issues/schemas/updateIssueSchema.ts +0 -13
  332. package/src/api/issues/services/IssueService.ts +0 -264
  333. package/src/api/jobs/__tests__/$job-middleware.spec.ts +0 -126
  334. package/src/api/jobs/__tests__/JobService.spec.ts +0 -31
  335. package/src/api/jobs/entities/jobExecutionLogEntity.ts +0 -13
  336. package/src/api/jobs/schemas/jobActivitySchema.ts +0 -15
  337. package/src/api/jobs/schemas/jobCronInfoSchema.ts +0 -22
  338. package/src/api/jobs/schemas/jobExecutionDetailResourceSchema.ts +0 -20
  339. package/src/api/jobs/schemas/jobFailureSchema.ts +0 -9
  340. package/src/api/jobs/schemas/jobQueueDepthSchema.ts +0 -14
  341. package/src/api/jobs/schemas/jobStatsSchema.ts +0 -14
  342. package/src/api/jobs/services/JobService-tests.ts +0 -157
  343. package/src/api/subscriptions/__tests__/BillingService.spec.ts +0 -218
  344. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +0 -278
  345. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +0 -212
  346. package/src/api/subscriptions/controllers/SubscriptionController.ts +0 -189
  347. package/src/api/subscriptions/entities/subscriptionEvents.ts +0 -54
  348. package/src/api/subscriptions/entities/subscriptions.ts +0 -68
  349. package/src/api/subscriptions/index.ts +0 -133
  350. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +0 -382
  351. package/src/api/subscriptions/middleware/$requireLimit.ts +0 -50
  352. package/src/api/subscriptions/middleware/$requirePlan.ts +0 -49
  353. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +0 -110
  354. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +0 -8
  355. package/src/api/subscriptions/schemas/changePlanSchema.ts +0 -9
  356. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +0 -11
  357. package/src/api/subscriptions/schemas/entitlementsSchema.ts +0 -21
  358. package/src/api/subscriptions/schemas/mrrSchema.ts +0 -13
  359. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +0 -71
  360. package/src/api/subscriptions/schemas/planResourceSchema.ts +0 -25
  361. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +0 -8
  362. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +0 -19
  363. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +0 -6
  364. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +0 -32
  365. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +0 -23
  366. package/src/api/subscriptions/services/BillingService.ts +0 -437
  367. package/src/api/subscriptions/services/SubscriptionConfig.ts +0 -56
  368. package/src/api/subscriptions/services/SubscriptionService.ts +0 -867
  369. package/src/api/subscriptions/services/UsageService.ts +0 -118
  370. package/src/api/workflows/__tests__/$workflow.spec.ts +0 -616
  371. package/src/api/workflows/controllers/AdminWorkflowController.ts +0 -191
  372. package/src/api/workflows/entities/workflowExecutions.ts +0 -74
  373. package/src/api/workflows/entities/workflowStepExecutions.ts +0 -74
  374. package/src/api/workflows/entities/workflowStepLogs.ts +0 -13
  375. package/src/api/workflows/index.browser.ts +0 -22
  376. package/src/api/workflows/index.ts +0 -115
  377. package/src/api/workflows/jobs/WorkflowJobs.ts +0 -77
  378. package/src/api/workflows/primitives/$workflow.ts +0 -202
  379. package/src/api/workflows/providers/WorkflowProvider.ts +0 -1284
  380. package/src/api/workflows/schemas/workflowActivitySchema.ts +0 -15
  381. package/src/api/workflows/schemas/workflowConfigAtom.ts +0 -51
  382. package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +0 -18
  383. package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +0 -26
  384. package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +0 -30
  385. package/src/api/workflows/schemas/workflowRegistrationSchema.ts +0 -26
  386. package/src/api/workflows/schemas/workflowStatsSchema.ts +0 -16
  387. package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +0 -15
  388. package/src/api/workflows/services/WorkflowService.ts +0 -382
  389. package/src/cli/core/templates/apiAppSecurityTs.ts +0 -43
  390. package/src/cli/core/templates/webAdminDashboardTsx.ts +0 -17
  391. package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
  392. package/src/orm/core/helpers/parseQueryString.ts +0 -502
  393. package/src/orm/core/primitives/$view.ts +0 -88
@@ -0,0 +1,163 @@
1
+ import { type TSchema, TypeBoxError } from "alepha";
2
+ import type { BaseInputField } from "./FormModel.ts";
3
+ import { prettyName } from "./prettyName.ts";
4
+
5
+ /**
6
+ * Semantic icon hint derived from schema metadata. UI layers map this
7
+ * to their own icon set — this module is headless and ships no JSX.
8
+ */
9
+ export type IconHint =
10
+ | "email"
11
+ | "password"
12
+ | "phone"
13
+ | "url"
14
+ | "number"
15
+ | "calendar"
16
+ | "clock"
17
+ | "list"
18
+ | "text"
19
+ | "user"
20
+ | "file"
21
+ | "switch";
22
+
23
+ export interface FieldConstraints {
24
+ minLength?: number;
25
+ maxLength?: number;
26
+ minimum?: number;
27
+ maximum?: number;
28
+ pattern?: string;
29
+ }
30
+
31
+ export interface FieldMeta {
32
+ id?: string;
33
+ label: string;
34
+ description?: string;
35
+ error?: string;
36
+ required: boolean;
37
+ type?: string;
38
+ format?: string;
39
+ isEnum: boolean;
40
+ isArray: boolean;
41
+ isObject: boolean;
42
+ isArrayOfObjects: boolean;
43
+ enum?: readonly unknown[];
44
+ iconHint?: IconHint;
45
+ constraints: FieldConstraints;
46
+ testId?: string;
47
+ schema: TSchema;
48
+ }
49
+
50
+ export interface ParseFieldOptions {
51
+ label?: string;
52
+ description?: string;
53
+ error?: Error;
54
+ }
55
+
56
+ /**
57
+ * Derives a {@link FieldMeta} from an `InputField` (from `useForm`) plus
58
+ * optional overrides. Pure — no React, no JSX, no UI library coupling.
59
+ *
60
+ * UI components consume this metadata to render labels, descriptions,
61
+ * error messages, icons, and validation constraints.
62
+ */
63
+ export const parseField = (
64
+ input: BaseInputField,
65
+ options: ParseFieldOptions = {},
66
+ ): FieldMeta => {
67
+ const schema = input.schema as TSchema & {
68
+ type?: string;
69
+ format?: string;
70
+ title?: string;
71
+ description?: string;
72
+ enum?: readonly unknown[];
73
+ minLength?: number;
74
+ maxLength?: number;
75
+ minimum?: number;
76
+ maximum?: number;
77
+ pattern?: string;
78
+ properties?: unknown;
79
+ items?: { properties?: unknown };
80
+ };
81
+
82
+ const label =
83
+ options.label ??
84
+ (typeof schema.title === "string" ? schema.title : undefined) ??
85
+ prettyName(input.path);
86
+
87
+ const description =
88
+ options.description ??
89
+ (typeof schema.description === "string" ? schema.description : undefined);
90
+
91
+ const error =
92
+ options.error instanceof TypeBoxError
93
+ ? (options.error as TypeBoxError).value?.message
94
+ : undefined;
95
+
96
+ const type = schema.type;
97
+ const format = typeof schema.format === "string" ? schema.format : undefined;
98
+ const isEnum = Array.isArray(schema.enum);
99
+ const isArray = type === "array";
100
+ const isObject = type === "object" && Boolean(schema.properties);
101
+ const isArrayOfObjects =
102
+ isArray && Boolean(schema.items && (schema.items as any).properties);
103
+
104
+ const name = input.props.name;
105
+ const iconHint = inferIconHint({ type, format, name, isEnum, isArray });
106
+
107
+ const constraints: FieldConstraints = {};
108
+ if (typeof schema.minLength === "number")
109
+ constraints.minLength = schema.minLength;
110
+ if (typeof schema.maxLength === "number")
111
+ constraints.maxLength = schema.maxLength;
112
+ if (typeof schema.minimum === "number") constraints.minimum = schema.minimum;
113
+ if (typeof schema.maximum === "number") constraints.maximum = schema.maximum;
114
+ if (typeof schema.pattern === "string") constraints.pattern = schema.pattern;
115
+
116
+ return {
117
+ id: input.props.id,
118
+ label,
119
+ description,
120
+ error,
121
+ required: input.required,
122
+ type,
123
+ format,
124
+ isEnum,
125
+ isArray,
126
+ isObject,
127
+ isArrayOfObjects,
128
+ enum: schema.enum,
129
+ iconHint,
130
+ constraints,
131
+ testId: (input.props as Record<string, unknown>)["data-testid"] as
132
+ | string
133
+ | undefined,
134
+ schema: input.schema,
135
+ };
136
+ };
137
+
138
+ const inferIconHint = (params: {
139
+ type?: string;
140
+ format?: string;
141
+ name?: string;
142
+ isEnum: boolean;
143
+ isArray: boolean;
144
+ }): IconHint | undefined => {
145
+ const { type, format, name, isEnum, isArray } = params;
146
+
147
+ if (format === "email") return "email";
148
+ if (format === "url" || format === "uri") return "url";
149
+ if (format === "tel" || format === "phone") return "phone";
150
+ if (format === "date" || format === "date-time") return "calendar";
151
+ if (format === "time") return "clock";
152
+
153
+ if (name?.toLowerCase().includes("password")) return "password";
154
+ if (name?.toLowerCase().includes("email")) return "email";
155
+ if (name?.toLowerCase().includes("phone")) return "phone";
156
+
157
+ if (type === "boolean") return "switch";
158
+ if (type === "number" || type === "integer") return "number";
159
+ if (isEnum || isArray) return "list";
160
+ if (type === "string") return "text";
161
+
162
+ return undefined;
163
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Converts a path or identifier string into a pretty display name.
3
+ * For paths like "/contacts/0/name", extracts just the field name "Name".
4
+ * Handles camelCase and snake_case conversion to Title Case.
5
+ *
6
+ * @example
7
+ * prettyName("/userName") // "User Name"
8
+ * prettyName("/contacts/0/email") // "Email"
9
+ * prettyName("/address/streetName") // "Street Name"
10
+ * prettyName("first_name") // "First Name"
11
+ */
12
+ export const prettyName = (name: string): string => {
13
+ const segments = name.split("/").filter((s) => s && !/^\d+$/.test(s));
14
+ const fieldName = segments[segments.length - 1] || name.replaceAll("/", "");
15
+ return fieldName
16
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
17
+ .replace(/_/g, " ")
18
+ .replace(/\b\w/g, (c) => c.toUpperCase());
19
+ };
@@ -154,10 +154,12 @@ export class BrowserHeadProvider {
154
154
  | string
155
155
  | (Record<string, string | boolean | undefined> & { content?: string }),
156
156
  ): void {
157
- const el = document.createElement("script");
158
-
159
- // Handle plain string as inline script
157
+ // Plain string → inline script. Dedupe by exact content match against
158
+ // any existing inline script (handles SSR-emitted globals that would
159
+ // otherwise be re-appended on hydration).
160
160
  if (typeof script === "string") {
161
+ if (this.findInlineScriptByContent(document, script)) return;
162
+ const el = document.createElement("script");
161
163
  el.textContent = script;
162
164
  document.head.appendChild(el);
163
165
  return;
@@ -165,14 +167,18 @@ export class BrowserHeadProvider {
165
167
 
166
168
  const { content, ...attrs } = script;
167
169
 
168
- // For scripts with src, check if already exists
170
+ // src-based scripts: dedupe by src attribute (existing behaviour).
169
171
  if (attrs.src) {
170
- const existing = document.querySelector(`script[src="${attrs.src}"]`);
171
- if (existing) {
172
- return;
173
- }
172
+ if (document.querySelector(`script[src="${attrs.src}"]`)) return;
173
+ } else if (typeof attrs.id === "string") {
174
+ // id-based dedupe — single source of truth per id.
175
+ if (document.querySelector(`script#${CSS.escape(attrs.id)}`)) return;
176
+ } else if (content) {
177
+ // Inline scripts with `content` and no src/id: fall back to content match.
178
+ if (this.findInlineScriptByContent(document, content)) return;
174
179
  }
175
180
 
181
+ const el = document.createElement("script");
176
182
  for (const [key, value] of Object.entries(attrs)) {
177
183
  if (value === true) {
178
184
  el.setAttribute(key, "");
@@ -180,14 +186,29 @@ export class BrowserHeadProvider {
180
186
  el.setAttribute(key, String(value));
181
187
  }
182
188
  }
183
-
184
189
  if (content) {
185
190
  el.textContent = content;
186
191
  }
187
-
188
192
  document.head.appendChild(el);
189
193
  }
190
194
 
195
+ /**
196
+ * Find an existing inline `<script>` tag (no `src`) with matching textContent.
197
+ * Used to make `renderScriptTag` idempotent across hydration + navigation,
198
+ * so SSR-emitted global scripts aren't re-appended client-side.
199
+ */
200
+ protected findInlineScriptByContent(
201
+ document: Document,
202
+ content: string,
203
+ ): Element | null {
204
+ for (const existing of document.head.querySelectorAll(
205
+ "script:not([src])",
206
+ )) {
207
+ if (existing.textContent === content) return existing;
208
+ }
209
+ return null;
210
+ }
211
+
191
212
  protected renderMetaTag(document: Document, meta: HeadMeta): void {
192
213
  const { content } = meta;
193
214
 
@@ -8,7 +8,7 @@ import type { GettingStartedSlide } from "./GettingStarted.tsx";
8
8
  * Returns undefined if auth routes are not configured.
9
9
  */
10
10
  export const useAuthSlide = (): GettingStartedSlide | undefined => {
11
- const { user } = useAuth();
11
+ const { user, logout } = useAuth();
12
12
  const router = useRouter();
13
13
 
14
14
  // Check if auth routes exist
@@ -19,8 +19,6 @@ export const useAuthSlide = (): GettingStartedSlide | undefined => {
19
19
 
20
20
  // User is logged in - show user info and logout option
21
21
  if (user) {
22
- const logoutAnchorProps = router.anchor(router.path("logout"));
23
-
24
22
  return {
25
23
  text: "Welcome back!",
26
24
  sub: `You're signed in as ${user.email || user.username || "user"}.`,
@@ -33,7 +31,16 @@ export const useAuthSlide = (): GettingStartedSlide | undefined => {
33
31
  num: "→",
34
32
  text: (
35
33
  <>
36
- <a {...logoutAnchorProps}>Sign out</a> to test the login flow
34
+ <a
35
+ href="#"
36
+ onClick={(e) => {
37
+ e.preventDefault();
38
+ logout();
39
+ }}
40
+ >
41
+ Sign out
42
+ </a>{" "}
43
+ to test the login flow
37
44
  </>
38
45
  ),
39
46
  },
@@ -1,9 +1,22 @@
1
1
  import { Alepha } from "alepha";
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
- import { ReactBrowserProvider } from "../providers/ReactBrowserProvider.ts";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import {
4
+ ReactBrowserProvider,
5
+ type RouterPushOptions,
6
+ reactBrowserOptions,
7
+ } from "../providers/ReactBrowserProvider.ts";
4
8
 
5
9
  class TestReactBrowserProvider extends ReactBrowserProvider {
6
10
  public testGetHydrationState = this.getHydrationState.bind(this);
11
+ public testAttachAnchorInterceptor = this.attachAnchorInterceptor.bind(this);
12
+ public pushCalls: Array<{ url: string; options?: RouterPushOptions }> = [];
13
+
14
+ public override async push(
15
+ url: string,
16
+ options?: RouterPushOptions,
17
+ ): Promise<void> {
18
+ this.pushCalls.push({ url, options });
19
+ }
7
20
  }
8
21
 
9
22
  describe("ReactBrowserProvider", () => {
@@ -102,4 +115,202 @@ describe("ReactBrowserProvider", () => {
102
115
  expect(result?.["alepha.i18n.locale"]).toBe("en");
103
116
  });
104
117
  });
118
+
119
+ describe("attachAnchorInterceptor", () => {
120
+ let alepha: Alepha;
121
+ let provider: TestReactBrowserProvider;
122
+ let detach: () => void;
123
+ let container: HTMLDivElement;
124
+
125
+ const createAnchor = (
126
+ attrs: Record<string, string>,
127
+ inner?: HTMLElement,
128
+ ): HTMLAnchorElement => {
129
+ const a = document.createElement("a");
130
+ for (const [k, v] of Object.entries(attrs)) {
131
+ a.setAttribute(k, v);
132
+ }
133
+ if (inner) {
134
+ a.appendChild(inner);
135
+ } else {
136
+ a.textContent = "link";
137
+ }
138
+ container.appendChild(a);
139
+ return a;
140
+ };
141
+
142
+ const click = (
143
+ target: HTMLElement,
144
+ init: MouseEventInit = {},
145
+ ): MouseEvent => {
146
+ const ev = new MouseEvent("click", {
147
+ bubbles: true,
148
+ cancelable: true,
149
+ button: 0,
150
+ ...init,
151
+ });
152
+ target.dispatchEvent(ev);
153
+ return ev;
154
+ };
155
+
156
+ beforeEach(() => {
157
+ alepha = Alepha.create();
158
+ provider = alepha.inject(TestReactBrowserProvider);
159
+ container = document.createElement("div");
160
+ document.body.appendChild(container);
161
+ detach = provider.testAttachAnchorInterceptor();
162
+ });
163
+
164
+ afterEach(() => {
165
+ detach();
166
+ container.remove();
167
+ });
168
+
169
+ it("intercepts plain internal /foo anchor clicks", () => {
170
+ const a = createAnchor({ href: "/foo" });
171
+
172
+ const ev = click(a);
173
+
174
+ expect(provider.pushCalls).toHaveLength(1);
175
+ expect(provider.pushCalls[0].url).toBe("/foo");
176
+ expect(ev.defaultPrevented).toBe(true);
177
+ });
178
+
179
+ it("preserves query and hash when intercepting", () => {
180
+ const a = createAnchor({ href: "/foo?x=1#bar" });
181
+
182
+ click(a);
183
+
184
+ expect(provider.pushCalls[0].url).toBe("/foo?x=1#bar");
185
+ });
186
+
187
+ it("ignores cmd-click (metaKey)", () => {
188
+ const a = createAnchor({ href: "/foo" });
189
+
190
+ const ev = click(a, { metaKey: true });
191
+
192
+ expect(provider.pushCalls).toHaveLength(0);
193
+ expect(ev.defaultPrevented).toBe(false);
194
+ });
195
+
196
+ it("ignores ctrl-click", () => {
197
+ const a = createAnchor({ href: "/foo" });
198
+
199
+ click(a, { ctrlKey: true });
200
+
201
+ expect(provider.pushCalls).toHaveLength(0);
202
+ });
203
+
204
+ it("ignores shift-click", () => {
205
+ const a = createAnchor({ href: "/foo" });
206
+
207
+ click(a, { shiftKey: true });
208
+
209
+ expect(provider.pushCalls).toHaveLength(0);
210
+ });
211
+
212
+ it("ignores alt-click", () => {
213
+ const a = createAnchor({ href: "/foo" });
214
+
215
+ click(a, { altKey: true });
216
+
217
+ expect(provider.pushCalls).toHaveLength(0);
218
+ });
219
+
220
+ it("ignores non-primary mouse buttons", () => {
221
+ const a = createAnchor({ href: "/foo" });
222
+
223
+ click(a, { button: 1 });
224
+
225
+ expect(provider.pushCalls).toHaveLength(0);
226
+ });
227
+
228
+ it("ignores anchors with target='_blank'", () => {
229
+ const a = createAnchor({ href: "/foo", target: "_blank" });
230
+
231
+ click(a);
232
+
233
+ expect(provider.pushCalls).toHaveLength(0);
234
+ });
235
+
236
+ it("ignores anchors with download attribute", () => {
237
+ const a = createAnchor({ href: "/foo", download: "" });
238
+
239
+ click(a);
240
+
241
+ expect(provider.pushCalls).toHaveLength(0);
242
+ });
243
+
244
+ it("ignores anchors with data-no-router attribute", () => {
245
+ const a = createAnchor({ href: "/foo", "data-no-router": "" });
246
+
247
+ click(a);
248
+
249
+ expect(provider.pushCalls).toHaveLength(0);
250
+ });
251
+
252
+ it("ignores mailto: hrefs", () => {
253
+ const a = createAnchor({ href: "mailto:foo@bar.com" });
254
+
255
+ click(a);
256
+
257
+ expect(provider.pushCalls).toHaveLength(0);
258
+ });
259
+
260
+ it("ignores tel: hrefs", () => {
261
+ const a = createAnchor({ href: "tel:+15555555" });
262
+
263
+ click(a);
264
+
265
+ expect(provider.pushCalls).toHaveLength(0);
266
+ });
267
+
268
+ it("ignores hrefs to external origins", () => {
269
+ const a = createAnchor({ href: "https://example.com/foo" });
270
+
271
+ click(a);
272
+
273
+ expect(provider.pushCalls).toHaveLength(0);
274
+ });
275
+
276
+ it("ignores hash-only #section hrefs", () => {
277
+ const a = createAnchor({ href: "#section" });
278
+
279
+ click(a);
280
+
281
+ expect(provider.pushCalls).toHaveLength(0);
282
+ });
283
+
284
+ it("intercepts when click target is nested inside the anchor", () => {
285
+ const span = document.createElement("span");
286
+ span.textContent = "inner";
287
+ createAnchor({ href: "/foo" }, span);
288
+
289
+ click(span);
290
+
291
+ expect(provider.pushCalls).toHaveLength(1);
292
+ expect(provider.pushCalls[0].url).toBe("/foo");
293
+ });
294
+
295
+ it("skips when defaultPrevented is already true", () => {
296
+ const a = createAnchor({ href: "/foo" });
297
+ a.addEventListener("click", (ev) => ev.preventDefault());
298
+
299
+ click(a);
300
+
301
+ expect(provider.pushCalls).toHaveLength(0);
302
+ });
303
+
304
+ it("respects interceptAnchorClicks=false at runtime", () => {
305
+ alepha.store.set(reactBrowserOptions.key, {
306
+ ...alepha.store.get(reactBrowserOptions.key)!,
307
+ interceptAnchorClicks: false,
308
+ });
309
+ const a = createAnchor({ href: "/foo" });
310
+
311
+ click(a);
312
+
313
+ expect(provider.pushCalls).toHaveLength(0);
314
+ });
315
+ });
105
316
  });
@@ -50,16 +50,6 @@ import { ReactPageService } from "../services/ReactPageService.ts";
50
50
  * - Hierarchical error handling (child → parent)
51
51
  * - HTTP status code handling (404, 401, etc.)
52
52
  *
53
- * **Page Animations**
54
- * - CSS-based enter/exit animations
55
- * - Dynamic animations based on page state
56
- * - Custom timing and easing functions
57
- *
58
- * **Lifecycle Management**
59
- * - Server response hooks for headers and status codes
60
- * - Page leave handlers for cleanup (browser only)
61
- * - Permission-based access control
62
- *
63
53
  * @example Simple page with data fetching
64
54
  * ```typescript
65
55
  * const userProfile = $page({
@@ -202,13 +192,46 @@ export interface PagePrimitiveOptions<
202
192
  lazy?: () => Promise<{ default: FC<TProps & TPropsParent> }>;
203
193
 
204
194
  /**
205
- * Attach child pages to create nested routes.
206
- * This will make the page a parent route.
195
+ * Attach child pages to create nested routes, adopting them as children of
196
+ * this page.
197
+ *
198
+ * Use this when you want a parent to own children it cannot modify — most
199
+ * notably pages that come from an injected router in another package, whose
200
+ * `$page` definitions are frozen and cannot declare `parent` themselves.
201
+ *
202
+ * ```ts
203
+ * layout = $page({
204
+ * path: "/app",
205
+ * children: () => [
206
+ * this.productRouter.catalogPage, // from $inject(ProductRouter)
207
+ * this.productRouter.checkoutPage,
208
+ * ],
209
+ * });
210
+ * ```
211
+ *
212
+ * Use a thunk (`() => [...]`) when the children are defined later in the
213
+ * same class.
214
+ *
215
+ * **Declare each edge from one side only.** If a child already sets
216
+ * `parent: thisPage`, do NOT also add it to `children` — the link is
217
+ * already established, and declaring it on both sides creates a TypeScript
218
+ * circular dependency between the two class fields (each references the
219
+ * other before it is initialised).
207
220
  */
208
221
  children?: Array<PagePrimitive> | (() => Array<PagePrimitive>);
209
222
 
210
223
  /**
211
224
  * Define a parent page for nested routing.
225
+ *
226
+ * Use this when you own the child page and can edit its definition — it is
227
+ * the simplest way to nest routes and reads top-down. For pages you do NOT
228
+ * own (e.g. pages exposed by an injected router from another package), let
229
+ * the parent adopt them via its `children` option instead.
230
+ *
231
+ * **Declare each edge from one side only.** If you set `parent` here, do
232
+ * NOT also add this page to the parent's `children` array — the link is
233
+ * already established, and declaring it on both sides creates a TypeScript
234
+ * circular dependency between the two class fields.
212
235
  */
213
236
  parent?: PagePrimitive<PageConfigSchema, TPropsParent, any>;
214
237
 
@@ -28,9 +28,20 @@ export const reactBrowserOptions = $atom({
28
28
  name: "alepha.react.browser.options",
29
29
  schema: t.object({
30
30
  scrollRestoration: t.enum(["top", "manual"]), // TODO: must be per page?
31
+ /**
32
+ * Intercept clicks on plain `<a href="/...">` anchors and route them
33
+ * through the SPA router, so authors don't need `<Link>` everywhere
34
+ * (notably for SSR/Markdown HTML rendered as raw markup).
35
+ *
36
+ * Skips: modifier keys, non-primary mouse buttons, `target` other than
37
+ * `_self`, `download`, `data-no-router`, non-http(s) schemes, hash-only
38
+ * hrefs, external origins, and clicks already `defaultPrevented`.
39
+ */
40
+ interceptAnchorClicks: t.boolean({ default: true }),
31
41
  }),
32
42
  default: {
33
43
  scrollRestoration: "top" as const,
44
+ interceptAnchorClicks: true,
34
45
  },
35
46
  });
36
47
 
@@ -325,8 +336,70 @@ export class ReactBrowserProvider {
325
336
 
326
337
  this.render();
327
338
  });
339
+
340
+ this.attachAnchorInterceptor();
328
341
  },
329
342
  });
343
+
344
+ /**
345
+ * Attach a delegated click listener that routes plain `<a href="/...">`
346
+ * clicks through the SPA router. Returns a detach function (used in tests).
347
+ *
348
+ * Bails out on modifier keys, non-primary mouse buttons, `target`, `download`,
349
+ * `data-no-router`, hash-only/external/non-http hrefs, and already-prevented
350
+ * events. Honors the runtime `interceptAnchorClicks` flag.
351
+ */
352
+ protected attachAnchorInterceptor(): () => void {
353
+ const onClick = (ev: MouseEvent) => {
354
+ if (!this.options.interceptAnchorClicks) return;
355
+ if (ev.defaultPrevented) return;
356
+ if (ev.button !== 0) return;
357
+ if (ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) return;
358
+
359
+ const node = ev.target as Element | null;
360
+ const a = node?.closest?.("a");
361
+ if (!a) return;
362
+
363
+ if (a.hasAttribute("download")) return;
364
+ if (a.hasAttribute("data-no-router")) return;
365
+
366
+ const target = a.getAttribute("target");
367
+ if (target && target !== "_self") return;
368
+
369
+ const href = a.getAttribute("href");
370
+ if (!href) return;
371
+ if (href.startsWith("#")) return;
372
+ if (/^[a-z][a-z0-9+.-]*:/i.test(href)) {
373
+ // absolute scheme: only intercept if it points at our own origin
374
+ let url: URL;
375
+ try {
376
+ url = new URL(href);
377
+ } catch {
378
+ return;
379
+ }
380
+ if (url.origin !== this.location.origin) return;
381
+ ev.preventDefault();
382
+ const path = url.pathname + url.search + url.hash;
383
+ this.push(this.stripBase(path)).catch((e) => this.log.error(e));
384
+ return;
385
+ }
386
+
387
+ ev.preventDefault();
388
+ const url = new URL(href, this.location.href);
389
+ const path = url.pathname + url.search + url.hash;
390
+ this.push(this.stripBase(path)).catch((e) => this.log.error(e));
391
+ };
392
+
393
+ this.document.addEventListener("click", onClick);
394
+ return () => this.document.removeEventListener("click", onClick);
395
+ }
396
+
397
+ protected stripBase(path: string): string {
398
+ if (this.base && path.startsWith(this.base)) {
399
+ return path.slice(this.base.length) || "/";
400
+ }
401
+ return path;
402
+ }
330
403
  }
331
404
 
332
405
  // ---------------------------------------------------------------------------------------------------------------------
@@ -52,7 +52,7 @@ export class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
52
52
  previous: PreviousLayerData[] = [],
53
53
  meta = {},
54
54
  isStale: () => boolean = () => false,
55
- ): Promise<string | void> {
55
+ ): Promise<string | undefined> {
56
56
  const { pathname, search } = url;
57
57
 
58
58
  const entry: Partial<ReactRouterState> = {
@@ -65,7 +65,7 @@ export class ReactPreloadProvider {
65
65
  handler: ({ response }) => {
66
66
  // Only add to HTML responses (SSR pages)
67
67
  const contentType = response.headers["content-type"];
68
- if (!contentType || !contentType.includes("text/html")) {
68
+ if (!contentType?.includes("text/html")) {
69
69
  return;
70
70
  }
71
71