alepha 0.14.2 → 0.14.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 (361) hide show
  1. package/dist/api/audits/index.browser.js +5 -5
  2. package/dist/api/audits/index.browser.js.map +1 -1
  3. package/dist/api/audits/index.d.ts +784 -784
  4. package/dist/api/audits/index.d.ts.map +1 -1
  5. package/dist/api/audits/index.js +13 -13
  6. package/dist/api/audits/index.js.map +1 -1
  7. package/dist/api/files/index.browser.js +5 -5
  8. package/dist/api/files/index.browser.js.map +1 -1
  9. package/dist/api/files/index.d.ts +57 -57
  10. package/dist/api/files/index.d.ts.map +1 -1
  11. package/dist/api/files/index.js +71 -71
  12. package/dist/api/files/index.js.map +1 -1
  13. package/dist/api/jobs/index.browser.js +5 -5
  14. package/dist/api/jobs/index.browser.js.map +1 -1
  15. package/dist/api/jobs/index.d.ts +165 -165
  16. package/dist/api/jobs/index.d.ts.map +1 -1
  17. package/dist/api/jobs/index.js +10 -10
  18. package/dist/api/jobs/index.js.map +1 -1
  19. package/dist/api/notifications/index.browser.js +10 -10
  20. package/dist/api/notifications/index.browser.js.map +1 -1
  21. package/dist/api/notifications/index.d.ts +583 -171
  22. package/dist/api/notifications/index.d.ts.map +1 -1
  23. package/dist/api/notifications/index.js +12 -12
  24. package/dist/api/notifications/index.js.map +1 -1
  25. package/dist/api/parameters/index.browser.js +163 -10
  26. package/dist/api/parameters/index.browser.js.map +1 -1
  27. package/dist/api/parameters/index.d.ts +281 -276
  28. package/dist/api/parameters/index.d.ts.map +1 -1
  29. package/dist/api/parameters/index.js +196 -91
  30. package/dist/api/parameters/index.js.map +1 -1
  31. package/dist/api/users/index.browser.js +19 -19
  32. package/dist/api/users/index.browser.js.map +1 -1
  33. package/dist/api/users/index.d.ts +1137 -1123
  34. package/dist/api/users/index.d.ts.map +1 -1
  35. package/dist/api/users/index.js +827 -596
  36. package/dist/api/users/index.js.map +1 -1
  37. package/dist/api/verifications/index.browser.js +6 -6
  38. package/dist/api/verifications/index.browser.js.map +1 -1
  39. package/dist/api/verifications/index.d.ts +13 -13
  40. package/dist/api/verifications/index.d.ts.map +1 -1
  41. package/dist/api/verifications/index.js +6 -6
  42. package/dist/api/verifications/index.js.map +1 -1
  43. package/dist/bin/index.d.ts +1 -2
  44. package/dist/bin/index.js +0 -1
  45. package/dist/bin/index.js.map +1 -1
  46. package/dist/cli/index.d.ts +137 -112
  47. package/dist/cli/index.d.ts.map +1 -1
  48. package/dist/cli/index.js +371 -259
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/command/index.d.ts +45 -5
  51. package/dist/command/index.d.ts.map +1 -1
  52. package/dist/command/index.js +97 -17
  53. package/dist/command/index.js.map +1 -1
  54. package/dist/core/index.browser.js +14 -18
  55. package/dist/core/index.browser.js.map +1 -1
  56. package/dist/core/index.d.ts +29 -0
  57. package/dist/core/index.d.ts.map +1 -1
  58. package/dist/core/index.js +14 -18
  59. package/dist/core/index.js.map +1 -1
  60. package/dist/core/index.native.js +14 -18
  61. package/dist/core/index.native.js.map +1 -1
  62. package/dist/fake/index.js +195 -168
  63. package/dist/fake/index.js.map +1 -1
  64. package/dist/file/index.d.ts +8 -0
  65. package/dist/file/index.d.ts.map +1 -1
  66. package/dist/file/index.js +3 -0
  67. package/dist/file/index.js.map +1 -1
  68. package/dist/mcp/index.d.ts.map +1 -1
  69. package/dist/orm/index.d.ts +32 -32
  70. package/dist/orm/index.d.ts.map +1 -1
  71. package/dist/orm/index.js +12 -12
  72. package/dist/orm/index.js.map +1 -1
  73. package/dist/security/index.d.ts +1 -1
  74. package/dist/security/index.d.ts.map +1 -1
  75. package/dist/security/index.js +1 -1
  76. package/dist/security/index.js.map +1 -1
  77. package/dist/server/auth/index.d.ts +171 -155
  78. package/dist/server/auth/index.d.ts.map +1 -1
  79. package/dist/server/auth/index.js +0 -1
  80. package/dist/server/auth/index.js.map +1 -1
  81. package/dist/server/compress/index.d.ts.map +1 -1
  82. package/dist/server/compress/index.js +2 -0
  83. package/dist/server/compress/index.js.map +1 -1
  84. package/dist/server/core/index.d.ts.map +1 -1
  85. package/dist/server/core/index.js +1 -1
  86. package/dist/server/core/index.js.map +1 -1
  87. package/dist/server/links/index.browser.js +22 -6
  88. package/dist/server/links/index.browser.js.map +1 -1
  89. package/dist/server/links/index.d.ts +46 -44
  90. package/dist/server/links/index.d.ts.map +1 -1
  91. package/dist/server/links/index.js +24 -41
  92. package/dist/server/links/index.js.map +1 -1
  93. package/dist/server/security/index.d.ts +9 -9
  94. package/dist/server/swagger/index.d.ts +2 -1
  95. package/dist/server/swagger/index.d.ts.map +1 -1
  96. package/dist/server/swagger/index.js +8 -3
  97. package/dist/server/swagger/index.js.map +1 -1
  98. package/dist/vite/index.d.ts.map +1 -1
  99. package/dist/vite/index.js +12 -4
  100. package/dist/vite/index.js.map +1 -1
  101. package/dist/websocket/index.d.ts +7 -7
  102. package/package.json +7 -7
  103. package/src/api/audits/controllers/{AuditController.ts → AdminAuditController.ts} +5 -6
  104. package/src/api/audits/entities/audits.ts +5 -5
  105. package/src/api/audits/index.browser.ts +1 -1
  106. package/src/api/audits/index.ts +3 -3
  107. package/src/api/audits/primitives/$audit.spec.ts +276 -0
  108. package/src/api/audits/services/AuditService.spec.ts +495 -0
  109. package/src/api/files/__tests__/$bucket.spec.ts +91 -0
  110. package/src/api/files/controllers/AdminFileStatsController.spec.ts +166 -0
  111. package/src/api/files/controllers/{StorageStatsController.ts → AdminFileStatsController.ts} +2 -2
  112. package/src/api/files/controllers/FileController.spec.ts +558 -0
  113. package/src/api/files/controllers/FileController.ts +4 -5
  114. package/src/api/files/entities/files.ts +5 -5
  115. package/src/api/files/index.browser.ts +1 -1
  116. package/src/api/files/index.ts +4 -4
  117. package/src/api/files/jobs/FileJobs.spec.ts +52 -0
  118. package/src/api/files/services/FileService.spec.ts +109 -0
  119. package/src/api/jobs/__tests__/JobController.spec.ts +343 -0
  120. package/src/api/jobs/controllers/{JobController.ts → AdminJobController.ts} +2 -2
  121. package/src/api/jobs/entities/jobExecutions.ts +5 -5
  122. package/src/api/jobs/index.ts +3 -3
  123. package/src/api/jobs/primitives/$job.spec.ts +476 -0
  124. package/src/api/notifications/controllers/{NotificationController.ts → AdminNotificationController.ts} +4 -5
  125. package/src/api/notifications/entities/notifications.ts +5 -5
  126. package/src/api/notifications/index.browser.ts +1 -1
  127. package/src/api/notifications/index.ts +4 -4
  128. package/src/api/parameters/controllers/{ConfigController.ts → AdminConfigController.ts} +46 -107
  129. package/src/api/parameters/entities/parameters.ts +7 -17
  130. package/src/api/parameters/index.ts +3 -3
  131. package/src/api/parameters/primitives/$config.spec.ts +356 -0
  132. package/src/api/parameters/schemas/activateConfigBodySchema.ts +12 -0
  133. package/src/api/parameters/schemas/checkScheduledResponseSchema.ts +8 -0
  134. package/src/api/parameters/schemas/configCurrentResponseSchema.ts +13 -0
  135. package/src/api/parameters/schemas/configHistoryResponseSchema.ts +9 -0
  136. package/src/api/parameters/schemas/configNameParamSchema.ts +10 -0
  137. package/src/api/parameters/schemas/configNamesResponseSchema.ts +8 -0
  138. package/src/api/parameters/schemas/configTreeNodeSchema.ts +13 -0
  139. package/src/api/parameters/schemas/configVersionParamSchema.ts +9 -0
  140. package/src/api/parameters/schemas/configVersionResponseSchema.ts +9 -0
  141. package/src/api/parameters/schemas/configsByStatusResponseSchema.ts +9 -0
  142. package/src/api/parameters/schemas/createConfigVersionBodySchema.ts +24 -0
  143. package/src/api/parameters/schemas/index.ts +15 -0
  144. package/src/api/parameters/schemas/parameterResponseSchema.ts +26 -0
  145. package/src/api/parameters/schemas/parameterStatusSchema.ts +13 -0
  146. package/src/api/parameters/schemas/rollbackConfigBodySchema.ts +15 -0
  147. package/src/api/parameters/schemas/statusParamSchema.ts +9 -0
  148. package/src/api/users/__tests__/EmailVerification.spec.ts +369 -0
  149. package/src/api/users/__tests__/PasswordReset.spec.ts +550 -0
  150. package/src/api/users/controllers/AdminIdentityController.spec.ts +365 -0
  151. package/src/api/users/controllers/{IdentityController.ts → AdminIdentityController.ts} +3 -4
  152. package/src/api/users/controllers/AdminSessionController.spec.ts +274 -0
  153. package/src/api/users/controllers/{SessionController.ts → AdminSessionController.ts} +3 -4
  154. package/src/api/users/controllers/AdminUserController.spec.ts +372 -0
  155. package/src/api/users/controllers/AdminUserController.ts +116 -0
  156. package/src/api/users/controllers/UserController.ts +4 -107
  157. package/src/api/users/controllers/UserRealmController.ts +3 -0
  158. package/src/api/users/entities/identities.ts +6 -6
  159. package/src/api/users/entities/sessions.ts +6 -6
  160. package/src/api/users/entities/users.ts +9 -9
  161. package/src/api/users/index.ts +9 -6
  162. package/src/api/users/primitives/$userRealm.ts +13 -8
  163. package/src/api/users/services/CredentialService.spec.ts +509 -0
  164. package/src/api/users/services/CredentialService.ts +46 -0
  165. package/src/api/users/services/IdentityService.ts +15 -0
  166. package/src/api/users/services/RegistrationService.spec.ts +630 -0
  167. package/src/api/users/services/RegistrationService.ts +18 -0
  168. package/src/api/users/services/SessionService.spec.ts +301 -0
  169. package/src/api/users/services/SessionService.ts +110 -1
  170. package/src/api/users/services/UserService.ts +67 -2
  171. package/src/api/verifications/__tests__/CodeVerification.spec.ts +318 -0
  172. package/src/api/verifications/__tests__/LinkVerification.spec.ts +279 -0
  173. package/src/api/verifications/entities/verifications.ts +6 -6
  174. package/src/api/verifications/jobs/VerificationJobs.spec.ts +50 -0
  175. package/src/batch/__tests__/startup-buffering.spec.ts +458 -0
  176. package/src/batch/primitives/$batch.spec.ts +766 -0
  177. package/src/batch/providers/BatchProvider.spec.ts +786 -0
  178. package/src/bin/index.ts +0 -1
  179. package/src/bucket/__tests__/shared.ts +194 -0
  180. package/src/bucket/primitives/$bucket.spec.ts +104 -0
  181. package/src/bucket/providers/FileStorageProvider.spec.ts +13 -0
  182. package/src/bucket/providers/LocalFileStorageProvider.spec.ts +77 -0
  183. package/src/bucket/providers/MemoryFileStorageProvider.spec.ts +82 -0
  184. package/src/cache/core/__tests__/shared.ts +377 -0
  185. package/src/cache/core/primitives/$cache.spec.ts +111 -0
  186. package/src/cache/redis/__tests__/cache-redis.spec.ts +70 -0
  187. package/src/cli/apps/AlephaCli.ts +25 -4
  188. package/src/cli/commands/dev.ts +19 -7
  189. package/src/cli/commands/gen/changelog.spec.ts +315 -0
  190. package/src/cli/commands/{changelog.ts → gen/changelog.ts} +9 -9
  191. package/src/cli/commands/gen/openapi.ts +71 -0
  192. package/src/cli/commands/gen.ts +18 -0
  193. package/src/cli/commands/init.ts +2 -0
  194. package/src/cli/commands/root.ts +12 -3
  195. package/src/cli/commands/typecheck.ts +5 -0
  196. package/src/cli/index.ts +2 -1
  197. package/src/cli/services/AlephaCliUtils.ts +71 -32
  198. package/src/cli/services/GitMessageParser.ts +1 -1
  199. package/src/command/helpers/Asker.spec.ts +127 -0
  200. package/src/command/helpers/Runner.spec.ts +126 -0
  201. package/src/command/primitives/$command.spec.ts +1588 -0
  202. package/src/command/providers/CliProvider.ts +74 -24
  203. package/src/core/Alepha.ts +45 -0
  204. package/src/core/__tests__/Alepha-emit.spec.ts +22 -0
  205. package/src/core/__tests__/Alepha-graph.spec.ts +93 -0
  206. package/src/core/__tests__/Alepha-has.spec.ts +41 -0
  207. package/src/core/__tests__/Alepha-inject.spec.ts +93 -0
  208. package/src/core/__tests__/Alepha-register.spec.ts +81 -0
  209. package/src/core/__tests__/Alepha-start.spec.ts +176 -0
  210. package/src/core/__tests__/Alepha-with.spec.ts +14 -0
  211. package/src/core/__tests__/TypeBox-usecases.spec.ts +35 -0
  212. package/src/core/__tests__/TypeBoxLocale.spec.ts +15 -0
  213. package/src/core/__tests__/descriptor.spec.ts +34 -0
  214. package/src/core/__tests__/fixtures/A.ts +5 -0
  215. package/src/core/__tests__/pagination.spec.ts +77 -0
  216. package/src/core/helpers/jsonSchemaToTypeBox.ts +2 -2
  217. package/src/core/primitives/$atom.spec.ts +43 -0
  218. package/src/core/primitives/$hook.spec.ts +130 -0
  219. package/src/core/primitives/$inject.spec.ts +175 -0
  220. package/src/core/primitives/$module.spec.ts +115 -0
  221. package/src/core/providers/CodecManager.spec.ts +740 -0
  222. package/src/core/providers/EventManager.spec.ts +762 -0
  223. package/src/core/providers/EventManager.ts +4 -0
  224. package/src/core/providers/StateManager.spec.ts +365 -0
  225. package/src/core/providers/TypeProvider.spec.ts +1607 -0
  226. package/src/core/providers/TypeProvider.ts +20 -26
  227. package/src/datetime/primitives/$interval.spec.ts +103 -0
  228. package/src/datetime/providers/DateTimeProvider.spec.ts +86 -0
  229. package/src/email/primitives/$email.spec.ts +175 -0
  230. package/src/email/providers/LocalEmailProvider.spec.ts +341 -0
  231. package/src/fake/__tests__/keyName.example.ts +40 -0
  232. package/src/fake/__tests__/keyName.spec.ts +152 -0
  233. package/src/fake/__tests__/module.example.ts +32 -0
  234. package/src/fake/providers/FakeProvider.spec.ts +438 -0
  235. package/src/file/providers/FileSystemProvider.ts +8 -0
  236. package/src/file/providers/NodeFileSystemProvider.spec.ts +418 -0
  237. package/src/file/providers/NodeFileSystemProvider.ts +5 -0
  238. package/src/file/services/FileDetector.spec.ts +591 -0
  239. package/src/lock/core/__tests__/shared.ts +190 -0
  240. package/src/lock/core/providers/MemoryLockProvider.spec.ts +25 -0
  241. package/src/lock/redis/providers/RedisLockProvider.spec.ts +25 -0
  242. package/src/logger/__tests__/SimpleFormatterProvider.spec.ts +109 -0
  243. package/src/logger/primitives/$logger.spec.ts +108 -0
  244. package/src/logger/services/Logger.spec.ts +295 -0
  245. package/src/mcp/__tests__/errors.spec.ts +175 -0
  246. package/src/mcp/__tests__/integration.spec.ts +450 -0
  247. package/src/mcp/helpers/jsonrpc.spec.ts +380 -0
  248. package/src/mcp/primitives/$prompt.spec.ts +468 -0
  249. package/src/mcp/primitives/$resource.spec.ts +390 -0
  250. package/src/mcp/primitives/$tool.spec.ts +406 -0
  251. package/src/mcp/providers/McpServerProvider.spec.ts +797 -0
  252. package/src/orm/__tests__/$repository-crud.spec.ts +276 -0
  253. package/src/orm/__tests__/$repository-hooks.spec.ts +325 -0
  254. package/src/orm/__tests__/$repository-orderBy.spec.ts +128 -0
  255. package/src/orm/__tests__/$repository-pagination-sort.spec.ts +149 -0
  256. package/src/orm/__tests__/$repository-save.spec.ts +37 -0
  257. package/src/orm/__tests__/ModelBuilder-integration.spec.ts +490 -0
  258. package/src/orm/__tests__/ModelBuilder-types.spec.ts +186 -0
  259. package/src/orm/__tests__/PostgresProvider.spec.ts +46 -0
  260. package/src/orm/__tests__/delete-returning.spec.ts +256 -0
  261. package/src/orm/__tests__/deletedAt.spec.ts +80 -0
  262. package/src/orm/__tests__/enums.spec.ts +315 -0
  263. package/src/orm/__tests__/execute.spec.ts +72 -0
  264. package/src/orm/__tests__/fixtures/bigEntitySchema.ts +65 -0
  265. package/src/orm/__tests__/fixtures/userEntitySchema.ts +27 -0
  266. package/src/orm/__tests__/joins.spec.ts +1114 -0
  267. package/src/orm/__tests__/page.spec.ts +287 -0
  268. package/src/orm/__tests__/primaryKey.spec.ts +87 -0
  269. package/src/orm/__tests__/query-date-encoding.spec.ts +402 -0
  270. package/src/orm/__tests__/ref-auto-onDelete.spec.ts +156 -0
  271. package/src/orm/__tests__/references.spec.ts +102 -0
  272. package/src/orm/__tests__/security.spec.ts +710 -0
  273. package/src/orm/__tests__/sqlite.spec.ts +111 -0
  274. package/src/orm/__tests__/string-operators.spec.ts +429 -0
  275. package/src/orm/__tests__/timestamps.spec.ts +388 -0
  276. package/src/orm/__tests__/validation.spec.ts +183 -0
  277. package/src/orm/__tests__/version.spec.ts +64 -0
  278. package/src/orm/helpers/parseQueryString.spec.ts +196 -0
  279. package/src/orm/primitives/$repository.spec.ts +137 -0
  280. package/src/orm/primitives/$sequence.spec.ts +29 -0
  281. package/src/orm/primitives/$transaction.spec.ts +82 -0
  282. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -3
  283. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  284. package/src/orm/providers/drivers/CloudflareD1Provider.ts +1 -1
  285. package/src/orm/providers/drivers/DatabaseProvider.ts +1 -1
  286. package/src/orm/providers/drivers/NodePostgresProvider.ts +3 -3
  287. package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
  288. package/src/orm/providers/drivers/PglitePostgresProvider.ts +2 -2
  289. package/src/orm/services/ModelBuilder.spec.ts +575 -0
  290. package/src/orm/services/Repository.spec.ts +137 -0
  291. package/src/queue/core/__tests__/shared.ts +143 -0
  292. package/src/queue/core/providers/MemoryQueueProvider.spec.ts +23 -0
  293. package/src/queue/core/providers/WorkerProvider.spec.ts +378 -0
  294. package/src/queue/redis/providers/RedisQueueProvider.spec.ts +23 -0
  295. package/src/redis/__tests__/redis.spec.ts +58 -0
  296. package/src/retry/primitives/$retry.spec.ts +234 -0
  297. package/src/retry/providers/RetryProvider.spec.ts +438 -0
  298. package/src/router/__tests__/match.spec.ts +252 -0
  299. package/src/router/providers/RouterProvider.spec.ts +197 -0
  300. package/src/scheduler/__tests__/$scheduler-cron.spec.ts +25 -0
  301. package/src/scheduler/__tests__/$scheduler-interval.spec.ts +25 -0
  302. package/src/scheduler/__tests__/shared.ts +77 -0
  303. package/src/security/__tests__/bug-1-wildcard-after-start.spec.ts +229 -0
  304. package/src/security/__tests__/bug-2-password-validation.spec.ts +245 -0
  305. package/src/security/__tests__/bug-3-regex-vulnerability.spec.ts +407 -0
  306. package/src/security/__tests__/bug-4-oauth2-validation.spec.ts +439 -0
  307. package/src/security/__tests__/multi-layer-permissions.spec.ts +522 -0
  308. package/src/security/primitives/$permission.spec.ts +30 -0
  309. package/src/security/primitives/$permission.ts +2 -2
  310. package/src/security/primitives/$realm.spec.ts +101 -0
  311. package/src/security/primitives/$role.spec.ts +52 -0
  312. package/src/security/primitives/$serviceAccount.spec.ts +61 -0
  313. package/src/security/providers/SecurityProvider.spec.ts +350 -0
  314. package/src/server/auth/providers/ServerAuthProvider.ts +0 -2
  315. package/src/server/cache/providers/ServerCacheProvider.spec.ts +942 -0
  316. package/src/server/compress/providers/ServerCompressProvider.spec.ts +31 -0
  317. package/src/server/compress/providers/ServerCompressProvider.ts +2 -0
  318. package/src/server/cookies/providers/ServerCookiesProvider.spec.ts +253 -0
  319. package/src/server/core/__tests__/ServerRouterProvider-getRoutes.spec.ts +334 -0
  320. package/src/server/core/__tests__/ServerRouterProvider-requestId.spec.ts +129 -0
  321. package/src/server/core/primitives/$action.spec.ts +191 -0
  322. package/src/server/core/primitives/$route.spec.ts +65 -0
  323. package/src/server/core/providers/ServerBodyParserProvider.spec.ts +93 -0
  324. package/src/server/core/providers/ServerLoggerProvider.spec.ts +100 -0
  325. package/src/server/core/providers/ServerProvider.ts +3 -1
  326. package/src/server/core/services/HttpClient.spec.ts +123 -0
  327. package/src/server/core/services/UserAgentParser.spec.ts +111 -0
  328. package/src/server/cors/providers/ServerCorsProvider.spec.ts +481 -0
  329. package/src/server/health/providers/ServerHealthProvider.spec.ts +22 -0
  330. package/src/server/helmet/providers/ServerHelmetProvider.spec.ts +105 -0
  331. package/src/server/links/__tests__/$action.spec.ts +238 -0
  332. package/src/server/links/__tests__/fixtures/CrudApp.ts +122 -0
  333. package/src/server/links/__tests__/requestId.spec.ts +120 -0
  334. package/src/server/links/primitives/$remote.spec.ts +228 -0
  335. package/src/server/links/providers/LinkProvider.spec.ts +54 -0
  336. package/src/server/links/providers/LinkProvider.ts +49 -3
  337. package/src/server/links/providers/ServerLinksProvider.ts +1 -53
  338. package/src/server/links/schemas/apiLinksResponseSchema.ts +7 -0
  339. package/src/server/metrics/providers/ServerMetricsProvider.spec.ts +25 -0
  340. package/src/server/multipart/providers/ServerMultipartProvider.spec.ts +528 -0
  341. package/src/server/proxy/primitives/$proxy.spec.ts +87 -0
  342. package/src/server/rate-limit/__tests__/ActionRateLimit.spec.ts +211 -0
  343. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +344 -0
  344. package/src/server/security/__tests__/BasicAuth.spec.ts +684 -0
  345. package/src/server/security/__tests__/ServerSecurityProvider-realm.spec.ts +388 -0
  346. package/src/server/security/providers/ServerSecurityProvider.spec.ts +123 -0
  347. package/src/server/static/primitives/$serve.spec.ts +193 -0
  348. package/src/server/swagger/__tests__/ui.spec.ts +52 -0
  349. package/src/server/swagger/primitives/$swagger.spec.ts +193 -0
  350. package/src/server/swagger/providers/ServerSwaggerProvider.ts +18 -8
  351. package/src/sms/primitives/$sms.spec.ts +165 -0
  352. package/src/sms/providers/LocalSmsProvider.spec.ts +224 -0
  353. package/src/sms/providers/MemorySmsProvider.spec.ts +193 -0
  354. package/src/thread/primitives/$thread.spec.ts +186 -0
  355. package/src/topic/core/__tests__/shared.ts +144 -0
  356. package/src/topic/core/providers/MemoryTopicProvider.spec.ts +23 -0
  357. package/src/topic/redis/providers/RedisTopicProvider.spec.ts +23 -0
  358. package/src/vite/plugins/viteAlephaDev.ts +16 -4
  359. package/src/vite/tasks/runAlepha.ts +7 -1
  360. package/src/websocket/__tests__/$websocket-new.spec.ts +195 -0
  361. package/src/websocket/primitives/$channel.spec.ts +30 -0
@@ -0,0 +1,1114 @@
1
+ import { Alepha, t } from "alepha";
2
+ import { sql } from "drizzle-orm";
3
+ import { describe, test } from "vitest";
4
+ import { $entity, $repository, pg } from "../index.ts";
5
+
6
+ describe("Joins - Comprehensive Tests", () => {
7
+ // ===================================================================================================================
8
+ // SCHEMA DEFINITIONS
9
+ // ===================================================================================================================
10
+
11
+ const countries = $entity({
12
+ name: "countries",
13
+ schema: t.object({
14
+ id: pg.primaryKey(),
15
+ name: t.text(),
16
+ code: t.text(),
17
+ }),
18
+ });
19
+
20
+ const cities = $entity({
21
+ name: "cities",
22
+ schema: t.object({
23
+ id: pg.primaryKey(),
24
+ countryId: pg.ref(t.integer(), () => countries.cols.id),
25
+ name: t.text(),
26
+ population: t.optional(t.integer()),
27
+ }),
28
+ });
29
+
30
+ const users = $entity({
31
+ name: "users",
32
+ schema: t.object({
33
+ id: pg.primaryKey(),
34
+ name: t.text(),
35
+ email: t.text(),
36
+ cityId: pg.ref(t.optional(t.integer()), () => cities.cols.id),
37
+ managerId: pg.ref(t.optional(t.integer()), () => users.cols.id),
38
+ }),
39
+ });
40
+
41
+ const profiles = $entity({
42
+ name: "profiles",
43
+ schema: t.object({
44
+ id: pg.primaryKey(),
45
+ userId: pg.ref(t.integer(), () => users.cols.id),
46
+ bio: t.text(),
47
+ website: t.optional(t.text()),
48
+ }),
49
+ });
50
+
51
+ const posts = $entity({
52
+ name: "posts",
53
+ schema: t.object({
54
+ id: pg.primaryKey(),
55
+ authorId: pg.ref(t.integer(), () => users.cols.id),
56
+ title: t.text(),
57
+ content: t.text(),
58
+ publishedAt: pg.createdAt(),
59
+ }),
60
+ });
61
+
62
+ const comments = $entity({
63
+ name: "comments",
64
+ schema: t.object({
65
+ id: pg.primaryKey(),
66
+ postId: pg.ref(t.integer(), () => posts.cols.id),
67
+ authorId: pg.ref(t.integer(), () => users.cols.id),
68
+ content: t.text(),
69
+ createdAt: pg.createdAt(),
70
+ }),
71
+ });
72
+
73
+ const tags = $entity({
74
+ name: "tags",
75
+ schema: t.object({
76
+ id: pg.primaryKey(),
77
+ name: t.text(),
78
+ }),
79
+ });
80
+
81
+ const postTags = $entity({
82
+ name: "post_tags",
83
+ schema: t.object({
84
+ id: pg.primaryKey(),
85
+ postId: pg.ref(t.integer(), () => posts.cols.id),
86
+ tagId: pg.ref(t.integer(), () => tags.cols.id),
87
+ }),
88
+ });
89
+
90
+ class App {
91
+ countries = $repository(countries);
92
+ cities = $repository(cities);
93
+ users = $repository(users);
94
+ profiles = $repository(profiles);
95
+ posts = $repository(posts);
96
+ comments = $repository(comments);
97
+ tags = $repository(tags);
98
+ postTags = $repository(postTags);
99
+ }
100
+
101
+ // ===================================================================================================================
102
+ // TEST SETUP HELPERS
103
+ // ===================================================================================================================
104
+
105
+ async function setupTestData(app: App) {
106
+ // Create countries
107
+ const usa = await app.countries.create({
108
+ name: "United States",
109
+ code: "US",
110
+ });
111
+ const canada = await app.countries.create({ name: "Canada", code: "CA" });
112
+
113
+ // Create cities
114
+ const nyc = await app.cities.create({
115
+ countryId: usa.id,
116
+ name: "New York",
117
+ population: 8_000_000,
118
+ });
119
+ const toronto = await app.cities.create({
120
+ countryId: canada.id,
121
+ name: "Toronto",
122
+ population: 3_000_000,
123
+ });
124
+ const vancouver = await app.cities.create({
125
+ countryId: canada.id,
126
+ name: "Vancouver",
127
+ population: 700_000,
128
+ });
129
+
130
+ // Create users
131
+ const alice = await app.users.create({
132
+ name: "Alice",
133
+ email: "alice@example.com",
134
+ cityId: nyc.id,
135
+ });
136
+ const bob = await app.users.create({
137
+ name: "Bob",
138
+ email: "bob@example.com",
139
+ cityId: toronto.id,
140
+ managerId: alice.id,
141
+ });
142
+ const charlie = await app.users.create({
143
+ name: "Charlie",
144
+ email: "charlie@example.com",
145
+ cityId: vancouver.id,
146
+ managerId: alice.id,
147
+ });
148
+ const diana = await app.users.create({
149
+ name: "Diana",
150
+ email: "diana@example.com",
151
+ cityId: toronto.id,
152
+ managerId: bob.id,
153
+ });
154
+
155
+ // Create profiles
156
+ await app.profiles.create({
157
+ userId: alice.id,
158
+ bio: "Tech lead and mentor",
159
+ website: "https://alice.dev",
160
+ });
161
+ await app.profiles.create({
162
+ userId: bob.id,
163
+ bio: "Senior developer",
164
+ });
165
+ await app.profiles.create({
166
+ userId: charlie.id,
167
+ bio: "Frontend specialist",
168
+ website: "https://charlie.io",
169
+ });
170
+
171
+ // Create posts
172
+ const post1 = await app.posts.create({
173
+ authorId: alice.id,
174
+ title: "Introduction to TypeScript",
175
+ content: "TypeScript is great...",
176
+ });
177
+ const post2 = await app.posts.create({
178
+ authorId: alice.id,
179
+ title: "Advanced PostgreSQL",
180
+ content: "Let's dive deep into Postgres...",
181
+ });
182
+ const post3 = await app.posts.create({
183
+ authorId: bob.id,
184
+ title: "React Best Practices",
185
+ content: "Here are some tips...",
186
+ });
187
+
188
+ // Create tags
189
+ const typescript = await app.tags.create({ name: "TypeScript" });
190
+ const postgres = await app.tags.create({ name: "PostgreSQL" });
191
+ const react = await app.tags.create({ name: "React" });
192
+
193
+ // Create post-tag associations
194
+ await app.postTags.create({ postId: post1.id, tagId: typescript.id });
195
+ await app.postTags.create({ postId: post2.id, tagId: postgres.id });
196
+ await app.postTags.create({ postId: post3.id, tagId: react.id });
197
+ await app.postTags.create({ postId: post3.id, tagId: typescript.id });
198
+
199
+ // Create comments
200
+ await app.comments.create({
201
+ postId: post1.id,
202
+ authorId: bob.id,
203
+ content: "Great article!",
204
+ });
205
+ await app.comments.create({
206
+ postId: post1.id,
207
+ authorId: charlie.id,
208
+ content: "Very helpful, thanks!",
209
+ });
210
+ await app.comments.create({
211
+ postId: post2.id,
212
+ authorId: diana.id,
213
+ content: "Could you explain more about indexes?",
214
+ });
215
+
216
+ return {
217
+ countries: { usa, canada },
218
+ cities: { nyc, toronto, vancouver },
219
+ users: { alice, bob, charlie, diana },
220
+ posts: { post1, post2, post3 },
221
+ tags: { typescript, postgres, react },
222
+ };
223
+ }
224
+
225
+ // ===================================================================================================================
226
+ // BASIC JOIN TESTS
227
+ // ===================================================================================================================
228
+
229
+ test("simple left join - user with profile", async ({ expect }) => {
230
+ const alepha = Alepha.create();
231
+ const app = alepha.inject(App);
232
+ await alepha.start();
233
+
234
+ const { users: testUsers } = await setupTestData(app);
235
+
236
+ const result = await app.users.findOne({
237
+ where: { id: { eq: testUsers.alice.id } },
238
+ with: {
239
+ profile: {
240
+ join: profiles,
241
+ on: ["id", profiles.cols.userId],
242
+ },
243
+ },
244
+ });
245
+
246
+ expect(result.id).toBe(testUsers.alice.id);
247
+ expect(result.name).toBe("Alice");
248
+ expect(result.profile).toBeDefined();
249
+ expect(result.profile.bio).toBe("Tech lead and mentor");
250
+ expect(result.profile.website).toBe("https://alice.dev");
251
+ });
252
+
253
+ test("left join with tuple syntax - simple foreign key", async ({
254
+ expect,
255
+ }) => {
256
+ const alepha = Alepha.create();
257
+ const app = alepha.inject(App);
258
+ await alepha.start();
259
+
260
+ const { cities: testCities } = await setupTestData(app);
261
+
262
+ const result = await app.cities.findOne({
263
+ where: { id: { eq: testCities.nyc.id } },
264
+ with: {
265
+ country: {
266
+ join: countries,
267
+ on: ["countryId", countries.cols.id],
268
+ },
269
+ },
270
+ });
271
+
272
+ expect(result.name).toBe("New York");
273
+ expect(result.country).toBeDefined();
274
+ expect(result.country.name).toBe("United States");
275
+ expect(result.country.code).toBe("US");
276
+ });
277
+
278
+ test("left join with sql wrapper - custom join condition", async ({
279
+ expect,
280
+ }) => {
281
+ const alepha = Alepha.create();
282
+ const app = alepha.inject(App);
283
+ await alepha.start();
284
+
285
+ const { users: testUsers } = await setupTestData(app);
286
+
287
+ const result = await app.users.findOne({
288
+ where: { id: { eq: testUsers.alice.id } },
289
+ with: {
290
+ profile: {
291
+ join: profiles,
292
+ on: sql`${users.cols.id} = ${profiles.cols.userId}`,
293
+ },
294
+ },
295
+ });
296
+
297
+ expect(result.name).toBe("Alice");
298
+ expect(result.profile).toBeDefined();
299
+ expect(result.profile.bio).toBe("Tech lead and mentor");
300
+ });
301
+
302
+ test("inner join - only returns rows with matching relation", async ({
303
+ expect,
304
+ }) => {
305
+ const alepha = Alepha.create();
306
+ const app = alepha.inject(App);
307
+ await alepha.start();
308
+
309
+ await setupTestData(app);
310
+
311
+ // Diana has no profile, so should be excluded with inner join
312
+ const results = await app.users.findMany({
313
+ with: {
314
+ profile: {
315
+ type: "inner",
316
+ join: profiles,
317
+ on: ["id", profiles.cols.userId],
318
+ },
319
+ },
320
+ });
321
+
322
+ expect(results.length).toBe(3); // Alice, Bob, Charlie have profiles
323
+ expect(results.every((u) => u.profile !== null)).toBe(true);
324
+ expect(results.find((u) => u.name === "Diana")).toBeUndefined();
325
+ });
326
+
327
+ // ===================================================================================================================
328
+ // SELF-REFERENCING JOINS
329
+ // ===================================================================================================================
330
+
331
+ test("self-referencing join - user with manager", async ({ expect }) => {
332
+ const alepha = Alepha.create();
333
+ const app = alepha.inject(App);
334
+ await alepha.start();
335
+
336
+ const { users: testUsers } = await setupTestData(app);
337
+
338
+ const manager = users.alias("manager");
339
+
340
+ const result = await app.users.findOne({
341
+ where: { id: { eq: testUsers.bob.id } },
342
+ with: {
343
+ manager: {
344
+ join: manager,
345
+ on: ["managerId", manager.cols.id],
346
+ },
347
+ },
348
+ });
349
+
350
+ expect(result.name).toBe("Bob");
351
+ expect(result.manager).toBeDefined();
352
+ expect(result.manager.name).toBe("Alice");
353
+ expect(result.manager.email).toBe("alice@example.com");
354
+ });
355
+
356
+ test("self-referencing join - find users without manager", async ({
357
+ expect,
358
+ }) => {
359
+ const alepha = Alepha.create();
360
+ const app = alepha.inject(App);
361
+ await alepha.start();
362
+
363
+ await setupTestData(app);
364
+
365
+ const results = await app.users.findMany({
366
+ where: {
367
+ managerId: { isNull: true },
368
+ },
369
+ });
370
+
371
+ expect(results.length).toBe(1);
372
+ expect(results[0].name).toBe("Alice");
373
+ });
374
+
375
+ // ===================================================================================================================
376
+ // MULTIPLE JOINS
377
+ // ===================================================================================================================
378
+
379
+ test("multiple joins - user with profile and city", async ({ expect }) => {
380
+ const alepha = Alepha.create();
381
+ const app = alepha.inject(App);
382
+ await alepha.start();
383
+
384
+ const { users: testUsers } = await setupTestData(app);
385
+
386
+ const result = await app.users.findOne({
387
+ where: { id: { eq: testUsers.bob.id } },
388
+ with: {
389
+ profile: {
390
+ join: profiles,
391
+ on: ["id", profiles.cols.userId],
392
+ },
393
+ city: {
394
+ join: cities,
395
+ on: ["cityId", cities.cols.id],
396
+ },
397
+ },
398
+ });
399
+
400
+ expect(result.name).toBe("Bob");
401
+ expect(result.profile).toBeDefined();
402
+ expect(result.profile.bio).toBe("Senior developer");
403
+ expect(result.city).toBeDefined();
404
+ expect(result.city.name).toBe("Toronto");
405
+ expect(result.city.population).toBe(3_000_000);
406
+ });
407
+
408
+ test("multiple joins with different join types", async ({ expect }) => {
409
+ const alepha = Alepha.create();
410
+ const app = alepha.inject(App);
411
+ await alepha.start();
412
+
413
+ const { users: testUsers } = await setupTestData(app);
414
+
415
+ const result = await app.users.findOne({
416
+ where: { id: { eq: testUsers.alice.id } },
417
+ with: {
418
+ profile: {
419
+ type: "inner", // Must have profile
420
+ join: profiles,
421
+ on: ["id", profiles.cols.userId],
422
+ },
423
+ city: {
424
+ type: "left", // May not have city
425
+ join: cities,
426
+ on: ["cityId", cities.cols.id],
427
+ },
428
+ },
429
+ });
430
+
431
+ expect(result.name).toBe("Alice");
432
+ expect(result.profile).toBeDefined();
433
+ expect(result.city).toBeDefined();
434
+ });
435
+
436
+ // ===================================================================================================================
437
+ // NESTED JOINS (2 LEVELS)
438
+ // ===================================================================================================================
439
+
440
+ test("nested join 2 levels - user -> city -> country", async ({ expect }) => {
441
+ const alepha = Alepha.create();
442
+ const app = alepha.inject(App);
443
+ await alepha.start();
444
+
445
+ const { users: testUsers } = await setupTestData(app);
446
+
447
+ const result = await app.users.findOne({
448
+ where: { id: { eq: testUsers.bob.id } },
449
+ with: {
450
+ city: {
451
+ join: cities,
452
+ on: ["cityId", cities.cols.id],
453
+ with: {
454
+ country: {
455
+ join: countries,
456
+ on: ["countryId", countries.cols.id],
457
+ },
458
+ },
459
+ },
460
+ },
461
+ });
462
+
463
+ expect(result.name).toBe("Bob");
464
+ expect(result.city).toBeDefined();
465
+ expect(result.city.name).toBe("Toronto");
466
+ expect(result.city.country).toBeDefined();
467
+ expect(result.city.country.name).toBe("Canada");
468
+ expect(result.city.country.code).toBe("CA");
469
+ });
470
+
471
+ test("nested join 2 levels - post -> author -> city", async ({ expect }) => {
472
+ const alepha = Alepha.create();
473
+ const app = alepha.inject(App);
474
+ await alepha.start();
475
+
476
+ const { posts: testPosts } = await setupTestData(app);
477
+
478
+ const result = await app.posts.findOne({
479
+ where: { id: { eq: testPosts.post1.id } },
480
+ with: {
481
+ author: {
482
+ join: users,
483
+ on: ["authorId", users.cols.id],
484
+ with: {
485
+ city: {
486
+ join: cities,
487
+ on: ["cityId", cities.cols.id],
488
+ },
489
+ },
490
+ },
491
+ },
492
+ });
493
+
494
+ expect(result.title).toBe("Introduction to TypeScript");
495
+ expect(result.author).toBeDefined();
496
+ expect(result.author.name).toBe("Alice");
497
+ expect(result.author.city).toBeDefined();
498
+ expect(result.author.city.name).toBe("New York");
499
+ });
500
+
501
+ test("multiple nested joins - user with manager and cities", async ({
502
+ expect,
503
+ }) => {
504
+ const alepha = Alepha.create();
505
+ const app = alepha.inject(App);
506
+ await alepha.start();
507
+
508
+ const { users: testUsers } = await setupTestData(app);
509
+
510
+ const manager = users.alias("manager");
511
+ const managerCity = cities.alias("manager_city");
512
+
513
+ const result = await app.users.findOne({
514
+ where: { id: { eq: testUsers.bob.id } },
515
+ with: {
516
+ city: {
517
+ join: cities,
518
+ on: ["cityId", cities.cols.id],
519
+ with: {
520
+ country: {
521
+ join: countries,
522
+ on: ["countryId", countries.cols.id],
523
+ },
524
+ },
525
+ },
526
+ manager: {
527
+ join: manager,
528
+ on: ["managerId", manager.cols.id],
529
+ with: {
530
+ city: {
531
+ join: managerCity,
532
+ on: ["cityId", managerCity.cols.id],
533
+ },
534
+ },
535
+ },
536
+ },
537
+ });
538
+
539
+ expect(result.name).toBe("Bob");
540
+ expect(result.city).toBeDefined();
541
+ expect(result.city.name).toBe("Toronto");
542
+ expect(result.city.country).toBeDefined();
543
+ expect(result.city.country.name).toBe("Canada");
544
+ expect(result.manager).toBeDefined();
545
+ expect(result.manager.name).toBe("Alice");
546
+ expect(result.manager.city).toBeDefined();
547
+ expect(result.manager.city.name).toBe("New York");
548
+ });
549
+
550
+ // ===================================================================================================================
551
+ // NESTED JOINS (3 LEVELS)
552
+ // ===================================================================================================================
553
+
554
+ test("nested join 3 levels - post -> author -> city -> country", async ({
555
+ expect,
556
+ }) => {
557
+ const alepha = Alepha.create();
558
+ const app = alepha.inject(App);
559
+ await alepha.start();
560
+
561
+ const { posts: testPosts } = await setupTestData(app);
562
+
563
+ const result = await app.posts.findOne({
564
+ where: { id: { eq: testPosts.post3.id } },
565
+ with: {
566
+ author: {
567
+ join: users,
568
+ on: ["authorId", users.cols.id],
569
+ with: {
570
+ city: {
571
+ join: cities,
572
+ on: ["cityId", cities.cols.id],
573
+ with: {
574
+ country: {
575
+ join: countries,
576
+ on: ["countryId", countries.cols.id],
577
+ },
578
+ },
579
+ },
580
+ },
581
+ },
582
+ },
583
+ });
584
+
585
+ expect(result.title).toBe("React Best Practices");
586
+ expect(result.author).toBeDefined();
587
+ expect(result.author.name).toBe("Bob");
588
+ expect(result.author.city).toBeDefined();
589
+ expect(result.author.city.name).toBe("Toronto");
590
+ expect(result.author.city.country).toBeDefined();
591
+ expect(result.author.city.country.name).toBe("Canada");
592
+ expect(result.author.city.country.code).toBe("CA");
593
+ });
594
+
595
+ test("deeply nested with self-reference - user -> manager -> manager", async ({
596
+ expect,
597
+ }) => {
598
+ const alepha = Alepha.create();
599
+ const app = alepha.inject(App);
600
+ await alepha.start();
601
+
602
+ const { users: testUsers } = await setupTestData(app);
603
+
604
+ const manager = users.alias("manager");
605
+ const managerManager = users.alias("manager_manager");
606
+
607
+ const result = await app.users.findOne({
608
+ where: { id: { eq: testUsers.diana.id } },
609
+ with: {
610
+ manager: {
611
+ join: manager,
612
+ on: ["managerId", manager.cols.id],
613
+ with: {
614
+ manager: {
615
+ join: managerManager,
616
+ on: ["managerId", managerManager.cols.id],
617
+ },
618
+ },
619
+ },
620
+ },
621
+ });
622
+
623
+ expect(result.name).toBe("Diana");
624
+ expect(result.manager).toBeDefined();
625
+ expect(result.manager.name).toBe("Bob");
626
+ expect(result.manager.manager).toBeDefined();
627
+ expect(result.manager.manager.name).toBe("Alice");
628
+ });
629
+
630
+ // ===================================================================================================================
631
+ // WHERE CLAUSE ON JOINED TABLES
632
+ // ===================================================================================================================
633
+
634
+ test("filter by joined table column", async ({ expect }) => {
635
+ const alepha = Alepha.create();
636
+ const app = alepha.inject(App);
637
+ await alepha.start();
638
+
639
+ await setupTestData(app);
640
+
641
+ const results = await app.users.findMany({
642
+ with: {
643
+ city: {
644
+ join: cities,
645
+ on: ["cityId", cities.cols.id],
646
+ },
647
+ },
648
+ where: {
649
+ city: {
650
+ name: { eq: "Toronto" },
651
+ },
652
+ },
653
+ });
654
+
655
+ expect(results.length).toBe(2); // Bob and Diana
656
+ expect(results.every((u) => u.city.name === "Toronto")).toBe(true);
657
+ expect(results.map((u) => u.name).sort()).toEqual(["Bob", "Diana"]);
658
+ });
659
+
660
+ test("filter by nested joined table column", async ({ expect }) => {
661
+ const alepha = Alepha.create();
662
+ const app = alepha.inject(App);
663
+ await alepha.start();
664
+
665
+ await setupTestData(app);
666
+
667
+ const results = await app.users.findMany({
668
+ with: {
669
+ city: {
670
+ join: cities,
671
+ on: ["cityId", cities.cols.id],
672
+ with: {
673
+ country: {
674
+ join: countries,
675
+ on: ["countryId", countries.cols.id],
676
+ },
677
+ },
678
+ },
679
+ },
680
+ where: {
681
+ city: {
682
+ country: {
683
+ code: { eq: "CA" },
684
+ },
685
+ },
686
+ },
687
+ });
688
+
689
+ expect(results.length).toBe(3); // Bob, Charlie, Diana in Canadian cities
690
+ expect(results.every((u) => u.city.country.code === "CA")).toBe(true);
691
+ });
692
+
693
+ test("combine base and joined table filters", async ({ expect }) => {
694
+ const alepha = Alepha.create();
695
+ const app = alepha.inject(App);
696
+ await alepha.start();
697
+
698
+ await setupTestData(app);
699
+
700
+ const results = await app.users.findMany({
701
+ with: {
702
+ city: {
703
+ join: cities,
704
+ on: ["cityId", cities.cols.id],
705
+ },
706
+ },
707
+ where: {
708
+ and: [
709
+ { name: { contains: "ar" } }, // Charlie
710
+ {
711
+ city: {
712
+ population: { gt: 500_000 },
713
+ },
714
+ },
715
+ ],
716
+ },
717
+ });
718
+
719
+ expect(results.length).toBe(1);
720
+ expect(results[0].name).toBe("Charlie");
721
+ expect(results[0].city.name).toBe("Vancouver");
722
+ });
723
+
724
+ test("filter by self-referencing join", async ({ expect }) => {
725
+ const alepha = Alepha.create();
726
+ const app = alepha.inject(App);
727
+ await alepha.start();
728
+
729
+ await setupTestData(app);
730
+
731
+ const manager = users.alias("manager");
732
+
733
+ const results = await app.users.findMany({
734
+ with: {
735
+ manager: {
736
+ join: manager,
737
+ on: ["managerId", manager.cols.id],
738
+ },
739
+ },
740
+ where: {
741
+ manager: {
742
+ name: { eq: "Alice" },
743
+ },
744
+ },
745
+ });
746
+
747
+ expect(results.length).toBe(2); // Bob and Charlie report to Alice
748
+ expect(results.map((u) => u.name).sort()).toEqual(["Bob", "Charlie"]);
749
+ expect(results.every((u) => u.manager?.name === "Alice")).toBe(true);
750
+ });
751
+
752
+ // ===================================================================================================================
753
+ // FIND MULTIPLE WITH JOINS
754
+ // ===================================================================================================================
755
+
756
+ test("find multiple entities with joins", async ({ expect }) => {
757
+ const alepha = Alepha.create();
758
+ const app = alepha.inject(App);
759
+ await alepha.start();
760
+
761
+ await setupTestData(app);
762
+
763
+ const results = await app.users.findMany({
764
+ with: {
765
+ profile: {
766
+ join: profiles,
767
+ on: ["id", profiles.cols.userId],
768
+ },
769
+ city: {
770
+ join: cities,
771
+ on: ["cityId", cities.cols.id],
772
+ },
773
+ },
774
+ orderBy: { column: "name", direction: "asc" },
775
+ });
776
+
777
+ expect(results.length).toBeGreaterThan(0);
778
+ expect(results[0].name).toBe("Alice");
779
+ expect(results[0].profile).toBeDefined();
780
+ expect(results[0].city).toBeDefined();
781
+ });
782
+
783
+ test("find with joins and limit", async ({ expect }) => {
784
+ const alepha = Alepha.create();
785
+ const app = alepha.inject(App);
786
+ await alepha.start();
787
+
788
+ await setupTestData(app);
789
+
790
+ const results = await app.users.findMany({
791
+ with: {
792
+ city: {
793
+ join: cities,
794
+ on: ["cityId", cities.cols.id],
795
+ },
796
+ },
797
+ limit: 2,
798
+ orderBy: { column: "name", direction: "asc" },
799
+ });
800
+
801
+ expect(results.length).toBe(2);
802
+ expect(results[0].name).toBe("Alice");
803
+ expect(results[1].name).toBe("Bob");
804
+ });
805
+
806
+ test("find with joins and offset", async ({ expect }) => {
807
+ const alepha = Alepha.create();
808
+ const app = alepha.inject(App);
809
+ await alepha.start();
810
+
811
+ await setupTestData(app);
812
+
813
+ const results = await app.users.findMany({
814
+ with: {
815
+ profile: {
816
+ join: profiles,
817
+ on: ["id", profiles.cols.userId],
818
+ },
819
+ },
820
+ offset: 1,
821
+ limit: 2,
822
+ orderBy: { column: "name", direction: "asc" },
823
+ });
824
+
825
+ expect(results.length).toBe(2);
826
+ expect(results[0].name).toBe("Bob");
827
+ expect(results[1].name).toBe("Charlie");
828
+ });
829
+
830
+ // ===================================================================================================================
831
+ // COMPLEX SCENARIOS
832
+ // ===================================================================================================================
833
+
834
+ test("post with author, comments, and comment authors", async ({
835
+ expect,
836
+ }) => {
837
+ const alepha = Alepha.create();
838
+ const app = alepha.inject(App);
839
+ await alepha.start();
840
+
841
+ const { posts: testPosts } = await setupTestData(app);
842
+
843
+ // This requires multiple separate queries since we don't support one-to-many yet
844
+ const post = await app.posts.findOne({
845
+ where: { id: { eq: testPosts.post1.id } },
846
+ with: {
847
+ author: {
848
+ join: users,
849
+ on: ["authorId", users.cols.id],
850
+ },
851
+ },
852
+ });
853
+
854
+ const postComments = await app.comments.findMany({
855
+ where: { postId: { eq: testPosts.post1.id } },
856
+ with: {
857
+ author: {
858
+ join: users,
859
+ on: ["authorId", users.cols.id],
860
+ },
861
+ },
862
+ });
863
+
864
+ expect(post.title).toBe("Introduction to TypeScript");
865
+ expect(post.author.name).toBe("Alice");
866
+ expect(postComments.length).toBe(2);
867
+ expect(postComments[0].author).toBeDefined();
868
+ expect(postComments[1].author).toBeDefined();
869
+ });
870
+
871
+ test("join with null foreign key", async ({ expect }) => {
872
+ const alepha = Alepha.create();
873
+ const app = alepha.inject(App);
874
+ await alepha.start();
875
+
876
+ await setupTestData(app);
877
+
878
+ // Create user without city
879
+ const userWithoutCity = await app.users.create({
880
+ name: "Eve",
881
+ email: "eve@example.com",
882
+ });
883
+
884
+ const result = await app.users.findOne({
885
+ where: { id: { eq: userWithoutCity.id } },
886
+ with: {
887
+ city: {
888
+ join: cities,
889
+ on: ["cityId", cities.cols.id],
890
+ },
891
+ },
892
+ });
893
+
894
+ expect(result.name).toBe("Eve");
895
+ expect(result.city).toBeUndefined();
896
+ });
897
+
898
+ test("complex where with multiple join filters and operators", async ({
899
+ expect,
900
+ }) => {
901
+ const alepha = Alepha.create();
902
+ const app = alepha.inject(App);
903
+ await alepha.start();
904
+
905
+ await setupTestData(app);
906
+
907
+ const manager = users.alias("manager");
908
+
909
+ const results = await app.users.findMany({
910
+ with: {
911
+ city: {
912
+ join: cities,
913
+ on: ["cityId", cities.cols.id],
914
+ with: {
915
+ country: {
916
+ join: countries,
917
+ on: ["countryId", countries.cols.id],
918
+ },
919
+ },
920
+ },
921
+ manager: {
922
+ join: manager,
923
+ on: ["managerId", manager.cols.id],
924
+ },
925
+ },
926
+ where: {
927
+ and: [
928
+ { email: { endsWith: "example.com" } },
929
+ {
930
+ or: [
931
+ {
932
+ city: {
933
+ population: { gte: 1_000_000 },
934
+ },
935
+ },
936
+ {
937
+ manager: {
938
+ name: { eq: "Alice" },
939
+ },
940
+ },
941
+ ],
942
+ },
943
+ ],
944
+ },
945
+ });
946
+
947
+ expect(results.length).toBeGreaterThan(0);
948
+ expect(results.every((u) => u.email.endsWith("example.com"))).toBe(true);
949
+ });
950
+
951
+ // ===================================================================================================================
952
+ // PAGINATION WITH JOINS
953
+ // ===================================================================================================================
954
+
955
+ test("paginate with joins", async ({ expect }) => {
956
+ const alepha = Alepha.create();
957
+ const app = alepha.inject(App);
958
+ await alepha.start();
959
+
960
+ await setupTestData(app);
961
+
962
+ const page = await app.users.paginate(
963
+ { page: 0, size: 2 },
964
+ {
965
+ with: {
966
+ city: {
967
+ join: cities,
968
+ on: ["cityId", cities.cols.id],
969
+ },
970
+ },
971
+ orderBy: { column: "name", direction: "asc" },
972
+ },
973
+ );
974
+
975
+ expect(page.content.length).toBe(2);
976
+ expect(page.content[0].name).toBe("Alice");
977
+ expect(page.content[0].city).toBeDefined();
978
+ expect(page.page.isFirst).toBe(true);
979
+ expect(page.page.isLast).toBe(false);
980
+ });
981
+
982
+ test("paginate with nested joins", async ({ expect }) => {
983
+ const alepha = Alepha.create();
984
+ const app = alepha.inject(App);
985
+ await alepha.start();
986
+
987
+ await setupTestData(app);
988
+
989
+ const page = await app.users.paginate(
990
+ { page: 0, size: 3 },
991
+ {
992
+ with: {
993
+ city: {
994
+ join: cities,
995
+ on: ["cityId", cities.cols.id],
996
+ with: {
997
+ country: {
998
+ join: countries,
999
+ on: ["countryId", countries.cols.id],
1000
+ },
1001
+ },
1002
+ },
1003
+ },
1004
+ },
1005
+ );
1006
+
1007
+ expect(page.content.length).toBe(3);
1008
+ expect(page.content[0].city).toBeDefined();
1009
+ expect(page.content[0].city.country).toBeDefined();
1010
+ });
1011
+
1012
+ // ===================================================================================================================
1013
+ // EDGE CASES
1014
+ // ===================================================================================================================
1015
+
1016
+ test("join with no matching records returns empty result for inner join", async ({
1017
+ expect,
1018
+ }) => {
1019
+ const alepha = Alepha.create();
1020
+ const app = alepha.inject(App);
1021
+ await alepha.start();
1022
+
1023
+ await setupTestData(app);
1024
+
1025
+ const results = await app.users.findMany({
1026
+ where: { name: { eq: "Diana" } }, // Diana has no profile
1027
+ with: {
1028
+ profile: {
1029
+ type: "inner",
1030
+ join: profiles,
1031
+ on: ["id", profiles.cols.userId],
1032
+ },
1033
+ },
1034
+ });
1035
+
1036
+ expect(results.length).toBe(0);
1037
+ });
1038
+
1039
+ test("same table joined multiple times with aliases", async ({ expect }) => {
1040
+ const alepha = Alepha.create();
1041
+ const app = alepha.inject(App);
1042
+ await alepha.start();
1043
+
1044
+ const { posts: testPosts } = await setupTestData(app);
1045
+
1046
+ const author = users.alias("author");
1047
+ const authorCity = cities.alias("author_city");
1048
+
1049
+ // Get first comment
1050
+ const comment = await app.comments.findOne({
1051
+ where: { postId: { eq: testPosts.post1.id } },
1052
+ });
1053
+
1054
+ const result = await app.comments.findOne({
1055
+ where: { id: { eq: comment.id } },
1056
+ with: {
1057
+ post: {
1058
+ join: posts,
1059
+ on: ["postId", posts.cols.id],
1060
+ with: {
1061
+ author: {
1062
+ join: author,
1063
+ on: ["authorId", author.cols.id],
1064
+ with: {
1065
+ city: {
1066
+ join: authorCity,
1067
+ on: ["cityId", authorCity.cols.id],
1068
+ },
1069
+ },
1070
+ },
1071
+ },
1072
+ },
1073
+ author: {
1074
+ join: users,
1075
+ on: ["authorId", users.cols.id],
1076
+ with: {
1077
+ city: {
1078
+ join: cities,
1079
+ on: ["cityId", cities.cols.id],
1080
+ },
1081
+ },
1082
+ },
1083
+ },
1084
+ });
1085
+
1086
+ expect(result.content).toBeDefined();
1087
+ expect(result.post).toBeDefined();
1088
+ expect(result.post.author).toBeDefined();
1089
+ expect(result.author).toBeDefined();
1090
+ // Both should have cities
1091
+ expect(result.post.author.city).toBeDefined();
1092
+ expect(result.author.city).toBeDefined();
1093
+ });
1094
+
1095
+ test("empty result set with joins", async ({ expect }) => {
1096
+ const alepha = Alepha.create();
1097
+ const app = alepha.inject(App);
1098
+ await alepha.start();
1099
+
1100
+ await setupTestData(app);
1101
+
1102
+ const results = await app.users.findMany({
1103
+ where: { name: { eq: "NonExistentUser" } },
1104
+ with: {
1105
+ profile: {
1106
+ join: profiles,
1107
+ on: ["id", profiles.cols.userId],
1108
+ },
1109
+ },
1110
+ });
1111
+
1112
+ expect(results.length).toBe(0);
1113
+ });
1114
+ });