alepha 0.19.3 → 0.19.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  2. package/dist/api/invitations/index.d.ts +790 -0
  3. package/dist/api/invitations/index.d.ts.map +1 -0
  4. package/dist/api/invitations/index.js +665 -0
  5. package/dist/api/invitations/index.js.map +1 -0
  6. package/dist/api/issues/index.d.ts +810 -0
  7. package/dist/api/issues/index.d.ts.map +1 -0
  8. package/dist/api/issues/index.js +447 -0
  9. package/dist/api/issues/index.js.map +1 -0
  10. package/dist/api/jobs/index.browser.js +8 -9
  11. package/dist/api/jobs/index.browser.js.map +1 -1
  12. package/dist/api/jobs/index.d.ts +99 -43
  13. package/dist/api/jobs/index.d.ts.map +1 -1
  14. package/dist/api/jobs/index.js +257 -40
  15. package/dist/api/jobs/index.js.map +1 -1
  16. package/dist/api/notifications/index.browser.js +0 -1
  17. package/dist/api/notifications/index.browser.js.map +1 -1
  18. package/dist/api/notifications/index.d.ts +3 -3
  19. package/dist/api/notifications/index.d.ts.map +1 -1
  20. package/dist/api/notifications/index.js +0 -1
  21. package/dist/api/notifications/index.js.map +1 -1
  22. package/dist/api/parameters/index.browser.js +112 -1
  23. package/dist/api/parameters/index.browser.js.map +1 -1
  24. package/dist/api/parameters/index.d.ts +90 -3
  25. package/dist/api/parameters/index.d.ts.map +1 -1
  26. package/dist/api/parameters/index.js +79 -12
  27. package/dist/api/parameters/index.js.map +1 -1
  28. package/dist/{billing → api/payments}/index.d.ts +67 -49
  29. package/dist/api/payments/index.d.ts.map +1 -0
  30. package/dist/{billing → api/payments}/index.js +108 -74
  31. package/dist/api/payments/index.js.map +1 -0
  32. package/dist/api/subscriptions/index.d.ts +1692 -0
  33. package/dist/api/subscriptions/index.d.ts.map +1 -0
  34. package/dist/api/subscriptions/index.js +1870 -0
  35. package/dist/api/subscriptions/index.js.map +1 -0
  36. package/dist/api/users/index.d.ts +24 -2
  37. package/dist/api/users/index.d.ts.map +1 -1
  38. package/dist/api/users/index.js +176 -36
  39. package/dist/api/users/index.js.map +1 -1
  40. package/dist/api/verifications/index.d.ts +13 -13
  41. package/dist/api/workflows/index.browser.js +246 -0
  42. package/dist/api/workflows/index.browser.js.map +1 -0
  43. package/dist/api/workflows/index.d.ts +1618 -0
  44. package/dist/api/workflows/index.d.ts.map +1 -0
  45. package/dist/api/workflows/index.js +1504 -0
  46. package/dist/api/workflows/index.js.map +1 -0
  47. package/dist/captcha/index.d.ts +142 -0
  48. package/dist/captcha/index.d.ts.map +1 -0
  49. package/dist/captcha/index.js +177 -0
  50. package/dist/captcha/index.js.map +1 -0
  51. package/dist/cli/core/index.d.ts +126 -30
  52. package/dist/cli/core/index.d.ts.map +1 -1
  53. package/dist/cli/core/index.js +106 -67
  54. package/dist/cli/core/index.js.map +1 -1
  55. package/dist/cli/platform/index.d.ts +84 -10
  56. package/dist/cli/platform/index.d.ts.map +1 -1
  57. package/dist/cli/platform/index.js +92 -4
  58. package/dist/cli/platform/index.js.map +1 -1
  59. package/dist/cli/vendor/index.d.ts +60 -10
  60. package/dist/cli/vendor/index.d.ts.map +1 -1
  61. package/dist/cli/vendor/index.js +177 -45
  62. package/dist/cli/vendor/index.js.map +1 -1
  63. package/dist/command/index.d.ts.map +1 -1
  64. package/dist/command/index.js +2 -3
  65. package/dist/command/index.js.map +1 -1
  66. package/dist/core/index.browser.js +21 -2
  67. package/dist/core/index.browser.js.map +1 -1
  68. package/dist/core/index.d.ts +33 -2
  69. package/dist/core/index.d.ts.map +1 -1
  70. package/dist/core/index.js +21 -2
  71. package/dist/core/index.js.map +1 -1
  72. package/dist/core/index.native.js +21 -2
  73. package/dist/core/index.native.js.map +1 -1
  74. package/dist/core/index.workerd.js +21 -2
  75. package/dist/core/index.workerd.js.map +1 -1
  76. package/dist/email/smtp/index.js +24 -8
  77. package/dist/email/smtp/index.js.map +1 -1
  78. package/dist/orm/core/index.browser.js +0 -18
  79. package/dist/orm/core/index.browser.js.map +1 -1
  80. package/dist/orm/core/index.bun.js +6 -23
  81. package/dist/orm/core/index.bun.js.map +1 -1
  82. package/dist/orm/core/index.d.ts +1 -13
  83. package/dist/orm/core/index.d.ts.map +1 -1
  84. package/dist/orm/core/index.js +6 -23
  85. package/dist/orm/core/index.js.map +1 -1
  86. package/dist/orm/postgres/index.bun.js +3 -3
  87. package/dist/orm/postgres/index.bun.js.map +1 -1
  88. package/dist/orm/postgres/index.d.ts.map +1 -1
  89. package/dist/orm/postgres/index.js +3 -3
  90. package/dist/orm/postgres/index.js.map +1 -1
  91. package/dist/react/i18n/index.d.ts +1 -0
  92. package/dist/react/i18n/index.d.ts.map +1 -1
  93. package/dist/react/i18n/index.js +8 -4
  94. package/dist/react/i18n/index.js.map +1 -1
  95. package/dist/react/router/index.browser.js +25 -3
  96. package/dist/react/router/index.browser.js.map +1 -1
  97. package/dist/react/router/index.d.ts +16 -1
  98. package/dist/react/router/index.d.ts.map +1 -1
  99. package/dist/react/router/index.js +25 -3
  100. package/dist/react/router/index.js.map +1 -1
  101. package/dist/security/index.d.ts +28 -0
  102. package/dist/security/index.d.ts.map +1 -1
  103. package/dist/security/index.js +28 -0
  104. package/dist/security/index.js.map +1 -1
  105. package/dist/server/auth/index.d.ts +145 -2
  106. package/dist/server/auth/index.d.ts.map +1 -1
  107. package/dist/server/auth/index.js +364 -63
  108. package/dist/server/auth/index.js.map +1 -1
  109. package/dist/server/cookies/index.d.ts.map +1 -1
  110. package/dist/server/cookies/index.js.map +1 -1
  111. package/dist/websocket/index.d.ts.map +1 -1
  112. package/dist/websocket/index.js.map +1 -1
  113. package/package.json +47 -20
  114. package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
  115. package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
  116. package/src/api/invitations/controllers/InvitationController.ts +84 -0
  117. package/src/api/invitations/entities/invitations.ts +33 -0
  118. package/src/api/invitations/index.ts +65 -0
  119. package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
  120. package/src/api/invitations/providers/InvitationProvider.ts +45 -0
  121. package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
  122. package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
  123. package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
  124. package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
  125. package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
  126. package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
  127. package/src/api/invitations/services/InvitationService.ts +556 -0
  128. package/src/api/issues/__tests__/IssueService.spec.ts +263 -0
  129. package/src/api/issues/controllers/AdminIssueController.ts +149 -0
  130. package/src/api/issues/controllers/IssueController.ts +44 -0
  131. package/src/api/issues/entities/issues.ts +49 -0
  132. package/src/api/issues/index.ts +53 -0
  133. package/src/api/issues/schemas/createIssueSchema.ts +13 -0
  134. package/src/api/issues/schemas/issueConfigAtom.ts +13 -0
  135. package/src/api/issues/schemas/issueQuerySchema.ts +18 -0
  136. package/src/api/issues/schemas/issueResourceSchema.ts +6 -0
  137. package/src/api/issues/schemas/myIssueQuerySchema.ts +10 -0
  138. package/src/api/issues/schemas/updateIssueSchema.ts +13 -0
  139. package/src/api/issues/services/IssueService.ts +264 -0
  140. package/src/api/jobs/__tests__/$job.spec.ts +876 -0
  141. package/src/api/jobs/controllers/AdminJobController.ts +44 -0
  142. package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
  143. package/src/api/jobs/index.ts +0 -3
  144. package/src/api/jobs/primitives/$job.ts +22 -11
  145. package/src/api/jobs/providers/JobProvider.ts +229 -19
  146. package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
  147. package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
  148. package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
  149. package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
  150. package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
  151. package/src/api/jobs/services/JobService.ts +51 -12
  152. package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
  153. package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
  154. package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
  155. package/src/api/parameters/index.browser.ts +12 -0
  156. package/src/api/parameters/primitives/$parameter.ts +20 -3
  157. package/src/api/parameters/services/ParameterProvider.ts +48 -7
  158. package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
  159. package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
  160. package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
  161. package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
  162. package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
  163. package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
  164. package/src/{billing → api/payments}/index.ts +31 -25
  165. package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
  166. package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
  167. package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
  168. package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
  169. package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
  170. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
  171. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
  172. package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
  173. package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
  174. package/src/api/subscriptions/entities/subscriptions.ts +68 -0
  175. package/src/api/subscriptions/index.ts +144 -0
  176. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
  177. package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
  178. package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
  179. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
  180. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
  181. package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
  182. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
  183. package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
  184. package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
  185. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
  186. package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
  187. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
  188. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
  189. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
  190. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
  191. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
  192. package/src/api/subscriptions/services/BillingService.ts +437 -0
  193. package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
  194. package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
  195. package/src/api/subscriptions/services/UsageService.ts +118 -0
  196. package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
  197. package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
  198. package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
  199. package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
  200. package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
  201. package/src/api/users/__tests__/SessionService.spec.ts +142 -1
  202. package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
  203. package/src/api/users/controllers/UserController.ts +3 -8
  204. package/src/api/users/notifications/UserNotifications.ts +23 -0
  205. package/src/api/users/primitives/$realm.ts +24 -0
  206. package/src/api/users/schemas/loginSchema.ts +1 -1
  207. package/src/api/users/services/CredentialService.ts +57 -7
  208. package/src/api/users/services/RegistrationService.ts +50 -11
  209. package/src/api/users/services/SessionService.ts +64 -9
  210. package/src/api/users/services/UserService.ts +21 -12
  211. package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
  212. package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
  213. package/src/api/workflows/entities/workflowExecutions.ts +74 -0
  214. package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
  215. package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
  216. package/src/api/workflows/index.browser.ts +22 -0
  217. package/src/api/workflows/index.ts +124 -0
  218. package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
  219. package/src/api/workflows/primitives/$workflow.ts +202 -0
  220. package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
  221. package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
  222. package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
  223. package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
  224. package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
  225. package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
  226. package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
  227. package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
  228. package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
  229. package/src/api/workflows/services/WorkflowService.ts +382 -0
  230. package/src/captcha/__tests__/MemoryCaptchaProvider.spec.ts +74 -0
  231. package/src/captcha/index.ts +33 -0
  232. package/src/captcha/providers/CaptchaProvider.ts +17 -0
  233. package/src/captcha/providers/MemoryCaptchaProvider.ts +65 -0
  234. package/src/captcha/providers/TurnstileCaptchaProvider.ts +125 -0
  235. package/src/cli/core/atoms/buildOptions.ts +57 -0
  236. package/src/cli/core/commands/build.ts +2 -0
  237. package/src/cli/core/providers/ViteDevServerProvider.ts +1 -1
  238. package/src/cli/core/services/ViteUtils.ts +5 -2
  239. package/src/cli/core/tasks/BuildClientTask.ts +3 -1
  240. package/src/cli/core/tasks/BuildCloudflareTask.ts +4 -0
  241. package/src/cli/core/tasks/BuildPwaTask.ts +81 -0
  242. package/src/cli/core/templates/webAppRouterTs.ts +5 -58
  243. package/src/cli/platform/adapters/CloudflareAdapter.ts +24 -0
  244. package/src/cli/platform/atoms/platformOptions.ts +19 -3
  245. package/src/cli/platform/hooks/PlatformHook.ts +51 -0
  246. package/src/cli/platform/index.ts +1 -0
  247. package/src/cli/platform/services/CloudflareApi.ts +22 -1
  248. package/src/cli/platform/services/PlatformOrchestrator.ts +67 -2
  249. package/src/cli/vendor/__tests__/VendorService.spec.ts +322 -178
  250. package/src/cli/vendor/commands/VendorCommand.ts +41 -38
  251. package/src/cli/vendor/services/VendorService.ts +234 -31
  252. package/src/command/__tests__/CliProvider.spec.ts +45 -0
  253. package/src/command/providers/CliProvider.ts +3 -4
  254. package/src/core/__tests__/TypeProvider.spec.ts +4 -2
  255. package/src/core/providers/SchemaValidator.ts +1 -1
  256. package/src/core/providers/TypeProvider.ts +46 -3
  257. package/src/orm/__tests__/enums.spec.ts +22 -29
  258. package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
  259. package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
  260. package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
  261. package/src/orm/core/services/Repository.ts +20 -6
  262. package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
  263. package/src/react/i18n/__tests__/I18nProvider.spec.ts +83 -0
  264. package/src/react/i18n/providers/I18nProvider.ts +12 -10
  265. package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
  266. package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
  267. package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
  268. package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
  269. package/src/security/primitives/$issuer.ts +3 -1
  270. package/src/security/primitives/$secure.ts +28 -0
  271. package/src/server/auth/index.ts +7 -0
  272. package/src/server/auth/primitives/$auth.ts +37 -3
  273. package/src/server/auth/primitives/$authApple.ts +114 -4
  274. package/src/server/auth/primitives/$authFacebook.ts +98 -0
  275. package/src/server/auth/primitives/$authFranceConnect.ts +105 -0
  276. package/src/server/auth/primitives/$authGithub.ts +22 -16
  277. package/src/server/auth/primitives/$authMicrosoft.ts +88 -0
  278. package/src/server/auth/providers/ServerAuthProvider.ts +197 -72
  279. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -0
  280. package/src/server/core/__tests__/ServerRouterProvider-errorHandler.spec.ts +1 -1
  281. package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -1
  282. package/dist/billing/index.d.ts.map +0 -1
  283. package/dist/billing/index.js.map +0 -1
  284. package/src/billing/__tests__/BillingService.spec.ts +0 -136
  285. /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
  286. /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
  287. /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
  288. /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
  289. /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
@@ -0,0 +1,167 @@
1
+ import { Alepha } from "alepha";
2
+ import { describe, it } from "vitest";
3
+ import { AlephaOrmPostgres } from "../postgres/index.ts";
4
+ import {
5
+ testCombinedFilters,
6
+ testEmptyResultWithJoins,
7
+ testFilterOnJoinedTable,
8
+ testFindOneWithJoinReturnsUndefined,
9
+ testGetOneWithJoinThrowsWhenNotFound,
10
+ testInnerJoinExcludesNulls,
11
+ testJoinWithOffset,
12
+ testJoinWithOrderByAndLimit,
13
+ testLeftJoinPlayerWithTeam,
14
+ testLeftJoinReturnsUndefinedWhenFkNull,
15
+ testMultipleOperatorsOnJoinedFilter,
16
+ testOrFilterAcrossTables,
17
+ testPaginationWithJoins,
18
+ } from "./orm-showcase-tests.ts";
19
+
20
+ describe("orm showcase: entities + joins", () => {
21
+ describe("basic left join", () => {
22
+ it("should join player with team (sqlite)", async () => {
23
+ await testLeftJoinPlayerWithTeam(
24
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
25
+ );
26
+ });
27
+ it("should join player with team (postgres)", async () => {
28
+ await testLeftJoinPlayerWithTeam(Alepha.create().with(AlephaOrmPostgres));
29
+ });
30
+ });
31
+
32
+ describe("left join with null FK", () => {
33
+ it("should return undefined when FK is null (sqlite)", async () => {
34
+ await testLeftJoinReturnsUndefinedWhenFkNull(
35
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
36
+ );
37
+ });
38
+ it("should return undefined when FK is null (postgres)", async () => {
39
+ await testLeftJoinReturnsUndefinedWhenFkNull(
40
+ Alepha.create().with(AlephaOrmPostgres),
41
+ );
42
+ });
43
+ });
44
+
45
+ describe("inner join", () => {
46
+ it("should exclude rows without match (sqlite)", async () => {
47
+ await testInnerJoinExcludesNulls(
48
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
49
+ );
50
+ });
51
+ it("should exclude rows without match (postgres)", async () => {
52
+ await testInnerJoinExcludesNulls(Alepha.create().with(AlephaOrmPostgres));
53
+ });
54
+ });
55
+
56
+ describe("filter on joined table", () => {
57
+ it("should filter by joined table column (sqlite)", async () => {
58
+ await testFilterOnJoinedTable(
59
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
60
+ );
61
+ });
62
+ it("should filter by joined table column (postgres)", async () => {
63
+ await testFilterOnJoinedTable(Alepha.create().with(AlephaOrmPostgres));
64
+ });
65
+ });
66
+
67
+ describe("combined base + join filters", () => {
68
+ it("should combine base and join table filters (sqlite)", async () => {
69
+ await testCombinedFilters(
70
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
71
+ );
72
+ });
73
+ it("should combine base and join table filters (postgres)", async () => {
74
+ await testCombinedFilters(Alepha.create().with(AlephaOrmPostgres));
75
+ });
76
+ });
77
+
78
+ describe("orderBy + limit with joins", () => {
79
+ it("should order and limit with joins (sqlite)", async () => {
80
+ await testJoinWithOrderByAndLimit(
81
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
82
+ );
83
+ });
84
+ it("should order and limit with joins (postgres)", async () => {
85
+ await testJoinWithOrderByAndLimit(
86
+ Alepha.create().with(AlephaOrmPostgres),
87
+ );
88
+ });
89
+ });
90
+
91
+ describe("pagination with joins", () => {
92
+ it("should paginate with joins (sqlite)", async () => {
93
+ await testPaginationWithJoins(
94
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
95
+ );
96
+ });
97
+ it("should paginate with joins (postgres)", async () => {
98
+ await testPaginationWithJoins(Alepha.create().with(AlephaOrmPostgres));
99
+ });
100
+ });
101
+
102
+ describe("OR filter across tables", () => {
103
+ it("should OR across base and joined table (sqlite)", async () => {
104
+ await testOrFilterAcrossTables(
105
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
106
+ );
107
+ });
108
+ it("should OR across base and joined table (postgres)", async () => {
109
+ await testOrFilterAcrossTables(Alepha.create().with(AlephaOrmPostgres));
110
+ });
111
+ });
112
+
113
+ describe("getOne / findOne with joins", () => {
114
+ it("should throw when not found via getOne (sqlite)", async () => {
115
+ await testGetOneWithJoinThrowsWhenNotFound(
116
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
117
+ );
118
+ });
119
+ it("should throw when not found via getOne (postgres)", async () => {
120
+ await testGetOneWithJoinThrowsWhenNotFound(
121
+ Alepha.create().with(AlephaOrmPostgres),
122
+ );
123
+ });
124
+
125
+ it("should return undefined via findOne (sqlite)", async () => {
126
+ await testFindOneWithJoinReturnsUndefined(
127
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
128
+ );
129
+ });
130
+ it("should return undefined via findOne (postgres)", async () => {
131
+ await testFindOneWithJoinReturnsUndefined(
132
+ Alepha.create().with(AlephaOrmPostgres),
133
+ );
134
+ });
135
+ });
136
+
137
+ describe("edge cases", () => {
138
+ it("should return empty array when no matches (sqlite)", async () => {
139
+ await testEmptyResultWithJoins(
140
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
141
+ );
142
+ });
143
+ it("should return empty array when no matches (postgres)", async () => {
144
+ await testEmptyResultWithJoins(Alepha.create().with(AlephaOrmPostgres));
145
+ });
146
+
147
+ it("should filter with operators on joined table (sqlite)", async () => {
148
+ await testMultipleOperatorsOnJoinedFilter(
149
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
150
+ );
151
+ });
152
+ it("should filter with operators on joined table (postgres)", async () => {
153
+ await testMultipleOperatorsOnJoinedFilter(
154
+ Alepha.create().with(AlephaOrmPostgres),
155
+ );
156
+ });
157
+
158
+ it("should handle offset with inner join (sqlite)", async () => {
159
+ await testJoinWithOffset(
160
+ Alepha.create({ env: { DATABASE_URL: "sqlite://:memory:" } }),
161
+ );
162
+ });
163
+ it("should handle offset with inner join (postgres)", async () => {
164
+ await testJoinWithOffset(Alepha.create().with(AlephaOrmPostgres));
165
+ });
166
+ });
167
+ });
@@ -12,7 +12,6 @@ import {
12
12
  type TSchema,
13
13
  type TString,
14
14
  type TStringOptions,
15
- type TUnsafe,
16
15
  t,
17
16
  } from "alepha";
18
17
  import type { UpdateDeleteAction } from "drizzle-orm/pg-core/foreign-keys";
@@ -20,7 +19,6 @@ import {
20
19
  PG_CREATED_AT,
21
20
  PG_DEFAULT,
22
21
  PG_DELETED_AT,
23
- PG_ENUM,
24
22
  PG_IDENTITY,
25
23
  PG_ORGANIZATION,
26
24
  PG_PRIMARY_KEY,
@@ -28,7 +26,6 @@ import {
28
26
  PG_UPDATED_AT,
29
27
  PG_VERSION,
30
28
  type PgDefault,
31
- type PgEnumOptions,
32
29
  type PgIdentityOptions,
33
30
  type PgPrimaryKey,
34
31
  type PgRef,
@@ -198,32 +195,6 @@ export class DatabaseTypeProvider {
198
195
  public readonly organization = () =>
199
196
  pgAttr(t.optional(t.uuid()), PG_ORGANIZATION);
200
197
 
201
- /**
202
- * Creates a Postgres ENUM type.
203
- *
204
- * > By default, `t.enum()` is mapped to a TEXT column in Postgres.
205
- * > Using this method, you can create a real ENUM type in the database.
206
- *
207
- * @example
208
- * ```ts
209
- * const statusEnum = pg.enum(["pending", "active", "archived"], { name: "status_enum" });
210
- * ```
211
- */
212
- public readonly enum = <T extends string[]>(
213
- values: [...T],
214
- pgEnumOptions?: PgEnumOptions,
215
- typeOptions?: TStringOptions,
216
- ): PgAttr<TUnsafe<T[number]>, typeof PG_ENUM> => {
217
- return pgAttr(
218
- t.enum(values, {
219
- description: pgEnumOptions?.description,
220
- ...typeOptions,
221
- }),
222
- PG_ENUM,
223
- pgEnumOptions,
224
- );
225
- };
226
-
227
198
  /**
228
199
  * Creates a reference to another table or schema. Basically a foreign key.
229
200
  */
@@ -533,6 +533,8 @@ export abstract class Repository<T extends TObject> {
533
533
  } = {},
534
534
  opts: StatementOptions & { count?: boolean } = {},
535
535
  ): Promise<Page<PgStatic<T, R>>> {
536
+ // Overflow-safe: pageQuerySchema constrains size to [1, 100] and page to >= 0.
537
+ // With max size=100, page would need to exceed 2^45 to overflow Number.MAX_SAFE_INTEGER.
536
538
  const limit = query.limit ?? pagination.size ?? 10;
537
539
  const page = pagination.page ?? 0;
538
540
  const offset = query.offset ?? page * limit;
@@ -674,7 +676,9 @@ export abstract class Repository<T extends TObject> {
674
676
  .returning(this.table)
675
677
  .then(([it]) => this.clean(it, this.entity.schema));
676
678
 
677
- this.dbCache.invalidateTable(this.tableName).catch(() => {});
679
+ this.dbCache
680
+ .invalidateTable(this.tableName)
681
+ .catch((err) => this.log.warn("Cache invalidation failed", err));
678
682
 
679
683
  await this.alepha.events.emit("repository:create:after", {
680
684
  tableName: this.tableName,
@@ -728,7 +732,9 @@ export abstract class Repository<T extends TObject> {
728
732
  allEntities.push(...entities);
729
733
  }
730
734
 
731
- this.dbCache.invalidateTable(this.tableName).catch(() => {});
735
+ this.dbCache
736
+ .invalidateTable(this.tableName)
737
+ .catch((err) => this.log.warn("Cache invalidation failed", err));
732
738
 
733
739
  await this.alepha.events.emit("repository:create:after", {
734
740
  tableName: this.tableName,
@@ -825,7 +831,9 @@ export abstract class Repository<T extends TObject> {
825
831
  .returning(this.table)
826
832
  .then(([it]) => this.clean(it, this.entity.schema));
827
833
 
828
- this.dbCache.invalidateTable(this.tableName).catch(() => {});
834
+ this.dbCache
835
+ .invalidateTable(this.tableName)
836
+ .catch((err) => this.log.warn("Cache invalidation failed", err));
829
837
 
830
838
  await this.alepha.events.emit("repository:create:after", {
831
839
  tableName: this.tableName,
@@ -889,7 +897,9 @@ export abstract class Repository<T extends TObject> {
889
897
  try {
890
898
  const entity = this.clean(response[0], this.entity.schema);
891
899
 
892
- this.dbCache.invalidateTable(this.tableName).catch(() => {});
900
+ this.dbCache
901
+ .invalidateTable(this.tableName)
902
+ .catch((err) => this.log.warn("Cache invalidation failed", err));
893
903
 
894
904
  await this.alepha.events.emit("repository:update:after", {
895
905
  tableName: this.tableName,
@@ -1041,7 +1051,9 @@ export abstract class Repository<T extends TObject> {
1041
1051
  .where(this.toSQL(where))
1042
1052
  .returning();
1043
1053
 
1044
- this.dbCache.invalidateTable(this.tableName).catch(() => {});
1054
+ this.dbCache
1055
+ .invalidateTable(this.tableName)
1056
+ .catch((err) => this.log.warn("Cache invalidation failed", err));
1045
1057
 
1046
1058
  await this.alepha.events.emit("repository:update:after", {
1047
1059
  tableName: this.tableName,
@@ -1089,7 +1101,9 @@ export abstract class Repository<T extends TObject> {
1089
1101
  .returning({ id: (this.table as any)[this.id.key] });
1090
1102
  const ids = result.map((row) => row.id);
1091
1103
 
1092
- this.dbCache.invalidateTable(this.tableName).catch(() => {});
1104
+ this.dbCache
1105
+ .invalidateTable(this.tableName)
1106
+ .catch((err) => this.log.warn("Cache invalidation failed", err));
1093
1107
 
1094
1108
  await this.alepha.events.emit("repository:delete:after", {
1095
1109
  tableName: this.tableName,
@@ -4,14 +4,12 @@ import {
4
4
  type FromSchema,
5
5
  ModelBuilder,
6
6
  PG_CREATED_AT,
7
- PG_ENUM,
8
7
  PG_GENERATED,
9
8
  PG_IDENTITY,
10
9
  PG_PRIMARY_KEY,
11
10
  PG_REF,
12
11
  PG_SERIAL,
13
12
  PG_UPDATED_AT,
14
- type PgEnumOptions,
15
13
  type PgGeneratedOptions,
16
14
  type PgIdentityOptions,
17
15
  type PgRefOptions,
@@ -384,10 +382,9 @@ export class PostgresModelBuilder extends ModelBuilder {
384
382
  );
385
383
  }
386
384
 
387
- // SQL Enum
388
- if (PG_ENUM in value && value[PG_ENUM]) {
389
- const options = value[PG_ENUM] as PgEnumOptions;
390
- const enumName = options.name ?? `${tableName}_${key}_enum`;
385
+ // SQL Enum (default for t.enum unless mode: "text")
386
+ if ((value as any).mode !== "text") {
387
+ const enumName = (value as any).enumName ?? `${tableName}_${key}_enum`;
391
388
 
392
389
  if (enums.has(enumName)) {
393
390
  const values = (
@@ -366,6 +366,89 @@ describe("I18nProvider", () => {
366
366
  );
367
367
  });
368
368
 
369
+ describe("when only non-English dictionaries are registered", () => {
370
+ test("should default to the first registered language", async ({
371
+ expect,
372
+ }) => {
373
+ class App {
374
+ fr = $dictionary({
375
+ lazy: async () => ({ default: { hello: "Bonjour" } }),
376
+ });
377
+ }
378
+
379
+ const alepha = Alepha.create().with(AlephaReactI18n);
380
+ const app = alepha.inject(App);
381
+ const i18n = alepha.inject(I18nProvider);
382
+
383
+ expect(i18n.lang).toBe("fr");
384
+ });
385
+
386
+ test("should translate using the first registered language", async ({
387
+ expect,
388
+ }) => {
389
+ class App {
390
+ fr = $dictionary({
391
+ lazy: async () => ({
392
+ default: { greeting: "Bonjour!", settings: "Paramètres" },
393
+ }),
394
+ });
395
+ }
396
+
397
+ const alepha = Alepha.create().with(AlephaReactI18n);
398
+ const app = alepha.inject(App);
399
+ const i18n = alepha.inject(I18nProvider);
400
+
401
+ await alepha.start();
402
+
403
+ expect(i18n.tr("greeting")).toBe("Bonjour!");
404
+ expect(i18n.tr("settings")).toBe("Paramètres");
405
+ });
406
+
407
+ test("should not include 'en' in languages list", async ({ expect }) => {
408
+ class App {
409
+ fr = $dictionary({
410
+ lazy: async () => ({ default: { hello: "Bonjour" } }),
411
+ });
412
+
413
+ de = $dictionary({
414
+ lazy: async () => ({ default: { hello: "Hallo" } }),
415
+ });
416
+ }
417
+
418
+ const alepha = Alepha.create().with(AlephaReactI18n);
419
+ const app = alepha.inject(App);
420
+ const i18n = alepha.inject(I18nProvider);
421
+
422
+ expect(i18n.languages).toEqual(["fr", "de"]);
423
+ });
424
+
425
+ test("should fallback to the first registered language for missing keys", async ({
426
+ expect,
427
+ }) => {
428
+ class App {
429
+ fr = $dictionary({
430
+ lazy: async () => ({
431
+ default: { hello: "Bonjour", world: "Monde" },
432
+ }),
433
+ });
434
+
435
+ de = $dictionary({
436
+ lazy: async () => ({ default: { hello: "Hallo" } }),
437
+ });
438
+ }
439
+
440
+ const alepha = Alepha.create().with(AlephaReactI18n);
441
+ const app = alepha.inject(App);
442
+ const i18n = alepha.inject(I18nProvider);
443
+
444
+ await alepha.start();
445
+ await i18n.setLang("de");
446
+
447
+ expect(i18n.tr("hello")).toBe("Hallo");
448
+ expect(i18n.tr("world")).toBe("Monde"); // Falls back to FR
449
+ });
450
+ });
451
+
369
452
  test("should handle partial interpolation args", async ({ expect }) => {
370
453
  class App {
371
454
  en = $dictionary({
@@ -42,7 +42,6 @@ export class I18nProvider<
42
42
  for (const item of this.registry) {
43
43
  languages.add(item.lang);
44
44
  }
45
- languages.add(this.options.fallbackLang);
46
45
 
47
46
  return Array.from(languages);
48
47
  }
@@ -70,10 +69,7 @@ export class I18nProvider<
70
69
  }
71
70
 
72
71
  for (const item of this.registry) {
73
- if (
74
- item.lang === this.lang ||
75
- item.lang === this.options.fallbackLang
76
- ) {
72
+ if (item.lang === this.lang || item.lang === this.fallbackLang) {
77
73
  this.log.trace("Loading language", {
78
74
  lang: item.lang,
79
75
  name: item.name,
@@ -139,11 +135,17 @@ export class I18nProvider<
139
135
  },
140
136
  });
141
137
 
138
+ public get fallbackLang(): string {
139
+ const configured = this.options.fallbackLang;
140
+ const hasDict = this.registry.some((item) => item.lang === configured);
141
+ if (hasDict) {
142
+ return configured;
143
+ }
144
+ return this.registry[0]?.lang ?? configured;
145
+ }
146
+
142
147
  public get lang(): string {
143
- return (
144
- this.alepha.store.get("alepha.react.i18n.lang") ||
145
- this.options.fallbackLang
146
- );
148
+ return this.alepha.store.get("alepha.react.i18n.lang") || this.fallbackLang;
147
149
  }
148
150
 
149
151
  public translate = (key: string, args: string[] = []) => {
@@ -156,7 +158,7 @@ export class I18nProvider<
156
158
  }
157
159
 
158
160
  for (const item of this.registry) {
159
- if (item.lang === this.options.fallbackLang) {
161
+ if (item.lang === this.fallbackLang) {
160
162
  if (item.translations[key]) {
161
163
  return this.render(item.translations[key], args); // append lang for fallback
162
164
  }
@@ -882,4 +882,161 @@ describe("$page browser tests", () => {
882
882
  });
883
883
  });
884
884
  });
885
+
886
+ describe("transition supersession", () => {
887
+ it("should not commit a stale slow transition when a newer navigation already won", async () => {
888
+ let resolvePageA: ((value: { data: string }) => void) | undefined;
889
+ const pageARendered = vi.fn();
890
+ const pageBRendered = vi.fn();
891
+
892
+ class App {
893
+ pageA = $page({
894
+ path: "/page-a",
895
+ loader: () =>
896
+ new Promise<{ data: string }>((resolve) => {
897
+ resolvePageA = resolve;
898
+ }),
899
+ component: ({ data }: { data: string }) => {
900
+ pageARendered();
901
+ return <div data-testid="page-a">A: {data}</div>;
902
+ },
903
+ });
904
+
905
+ pageB = $page({
906
+ path: "/page-b",
907
+ component: () => {
908
+ pageBRendered();
909
+ return <div data-testid="page-b">B</div>;
910
+ },
911
+ });
912
+ }
913
+
914
+ alepha = Alepha.create().with(AlephaReact).with(App);
915
+ await alepha.start();
916
+
917
+ const router = alepha.inject(ReactRouter);
918
+
919
+ // Start navigating to /page-a — its loader hangs, push() will not
920
+ // resolve until we manually resolve the loader below.
921
+ const pushAPromise = router.push("/page-a");
922
+
923
+ // Yield so the in-flight transition for /page-a actually starts and
924
+ // reaches the awaited loader before pushB enters the race.
925
+ await new Promise((r) => setTimeout(r, 0));
926
+
927
+ // Navigate to /page-b before /page-a finishes loading. This should
928
+ // supersede the in-flight /page-a transition.
929
+ await act(async () => {
930
+ await router.push("/page-b");
931
+ });
932
+
933
+ // /page-b should now be the committed router state.
934
+ expect(router.state.name).toBe("pageB");
935
+ expect(router.state.url.pathname).toBe("/page-b");
936
+
937
+ // Now resolve /page-a's loader: this is the race window where the
938
+ // stale /page-a transition could overwrite /page-b.
939
+ resolvePageA?.({ data: "loaded" });
940
+ await act(async () => {
941
+ await pushAPromise;
942
+ });
943
+
944
+ // Stale /page-a must NOT have committed.
945
+ expect(router.state.name).toBe("pageB");
946
+ expect(router.state.url.pathname).toBe("/page-b");
947
+ // /page-a's component must never have been instantiated.
948
+ expect(pageARendered).not.toHaveBeenCalled();
949
+ });
950
+
951
+ it("should not fire onEnter for a stale superseded page", async () => {
952
+ let resolvePageA: (() => void) | undefined;
953
+ const pageAOnEnter = vi.fn();
954
+ const pageBOnEnter = vi.fn();
955
+
956
+ class App {
957
+ pageA = $page({
958
+ path: "/page-a",
959
+ loader: () =>
960
+ new Promise<void>((resolve) => {
961
+ resolvePageA = resolve;
962
+ }),
963
+ onEnter: pageAOnEnter,
964
+ component: () => <div data-testid="page-a">A</div>,
965
+ });
966
+
967
+ pageB = $page({
968
+ path: "/page-b",
969
+ onEnter: pageBOnEnter,
970
+ component: () => <div data-testid="page-b">B</div>,
971
+ });
972
+ }
973
+
974
+ alepha = Alepha.create().with(AlephaReact).with(App);
975
+ await alepha.start();
976
+
977
+ const router = alepha.inject(ReactRouter);
978
+
979
+ const pushAPromise = router.push("/page-a");
980
+ await new Promise((r) => setTimeout(r, 0));
981
+
982
+ await act(async () => {
983
+ await router.push("/page-b");
984
+ });
985
+
986
+ expect(router.state.name).toBe("pageB");
987
+ expect(pageBOnEnter).toHaveBeenCalledTimes(1);
988
+ expect(pageAOnEnter).not.toHaveBeenCalled();
989
+
990
+ // Resolve the stale loader: it must remain a no-op.
991
+ resolvePageA?.();
992
+ await act(async () => {
993
+ await pushAPromise;
994
+ });
995
+
996
+ expect(router.state.name).toBe("pageB");
997
+ expect(pageAOnEnter).not.toHaveBeenCalled();
998
+ expect(pageBOnEnter).toHaveBeenCalledTimes(1);
999
+ });
1000
+
1001
+ it("should not call pushState for a stale superseded transition", async () => {
1002
+ let resolvePageA: (() => void) | undefined;
1003
+
1004
+ class App {
1005
+ pageA = $page({
1006
+ path: "/page-a",
1007
+ loader: () =>
1008
+ new Promise<void>((resolve) => {
1009
+ resolvePageA = resolve;
1010
+ }),
1011
+ component: () => <div data-testid="page-a">A</div>,
1012
+ });
1013
+
1014
+ pageB = $page({
1015
+ path: "/page-b",
1016
+ component: () => <div data-testid="page-b">B</div>,
1017
+ });
1018
+ }
1019
+
1020
+ alepha = Alepha.create().with(AlephaReact).with(App);
1021
+ await alepha.start();
1022
+
1023
+ const router = alepha.inject(ReactRouter);
1024
+
1025
+ const pushAPromise = router.push("/page-a");
1026
+ await new Promise((r) => setTimeout(r, 0));
1027
+
1028
+ await act(async () => {
1029
+ await router.push("/page-b");
1030
+ });
1031
+ expect(window.location.pathname).toBe("/page-b");
1032
+
1033
+ resolvePageA?.();
1034
+ await act(async () => {
1035
+ await pushAPromise;
1036
+ });
1037
+
1038
+ // The stale /page-a transition must not have rewritten the URL bar.
1039
+ expect(window.location.pathname).toBe("/page-b");
1040
+ });
1041
+ });
885
1042
  });