alepha 0.15.1 → 0.15.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 (523) hide show
  1. package/README.md +68 -80
  2. package/dist/api/audits/index.d.ts +10 -33
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/audits/index.js +10 -33
  5. package/dist/api/audits/index.js.map +1 -1
  6. package/dist/api/files/index.d.ts +10 -3
  7. package/dist/api/files/index.d.ts.map +1 -1
  8. package/dist/api/files/index.js +10 -3
  9. package/dist/api/files/index.js.map +1 -1
  10. package/dist/api/jobs/index.d.ts +162 -155
  11. package/dist/api/jobs/index.d.ts.map +1 -1
  12. package/dist/api/jobs/index.js +10 -3
  13. package/dist/api/jobs/index.js.map +1 -1
  14. package/dist/api/keys/index.d.ts +413 -0
  15. package/dist/api/keys/index.d.ts.map +1 -0
  16. package/dist/api/keys/index.js +476 -0
  17. package/dist/api/keys/index.js.map +1 -0
  18. package/dist/api/notifications/index.d.ts +10 -4
  19. package/dist/api/notifications/index.d.ts.map +1 -1
  20. package/dist/api/notifications/index.js +10 -4
  21. package/dist/api/notifications/index.js.map +1 -1
  22. package/dist/api/parameters/index.d.ts +43 -50
  23. package/dist/api/parameters/index.d.ts.map +1 -1
  24. package/dist/api/parameters/index.js +30 -37
  25. package/dist/api/parameters/index.js.map +1 -1
  26. package/dist/api/users/index.d.ts +1081 -760
  27. package/dist/api/users/index.d.ts.map +1 -1
  28. package/dist/api/users/index.js +2539 -218
  29. package/dist/api/users/index.js.map +1 -1
  30. package/dist/api/verifications/index.d.ts +138 -132
  31. package/dist/api/verifications/index.d.ts.map +1 -1
  32. package/dist/api/verifications/index.js +12 -4
  33. package/dist/api/verifications/index.js.map +1 -1
  34. package/dist/batch/index.d.ts +20 -40
  35. package/dist/batch/index.d.ts.map +1 -1
  36. package/dist/batch/index.js +31 -44
  37. package/dist/batch/index.js.map +1 -1
  38. package/dist/bucket/index.d.ts +440 -8
  39. package/dist/bucket/index.d.ts.map +1 -1
  40. package/dist/bucket/index.js +1861 -12
  41. package/dist/bucket/index.js.map +1 -1
  42. package/dist/cache/core/index.d.ts +179 -7
  43. package/dist/cache/core/index.d.ts.map +1 -1
  44. package/dist/cache/core/index.js +213 -7
  45. package/dist/cache/core/index.js.map +1 -1
  46. package/dist/cache/redis/index.d.ts +1 -0
  47. package/dist/cache/redis/index.d.ts.map +1 -1
  48. package/dist/cache/redis/index.js +4 -0
  49. package/dist/cache/redis/index.js.map +1 -1
  50. package/dist/cli/index.d.ts +638 -5645
  51. package/dist/cli/index.d.ts.map +1 -1
  52. package/dist/cli/index.js +2550 -368
  53. package/dist/cli/index.js.map +1 -1
  54. package/dist/command/index.d.ts +203 -45
  55. package/dist/command/index.d.ts.map +1 -1
  56. package/dist/command/index.js +2060 -71
  57. package/dist/command/index.js.map +1 -1
  58. package/dist/core/index.browser.js +70 -40
  59. package/dist/core/index.browser.js.map +1 -1
  60. package/dist/core/index.d.ts +34 -13
  61. package/dist/core/index.d.ts.map +1 -1
  62. package/dist/core/index.js +90 -40
  63. package/dist/core/index.js.map +1 -1
  64. package/dist/core/index.native.js +70 -40
  65. package/dist/core/index.native.js.map +1 -1
  66. package/dist/datetime/index.d.ts +15 -0
  67. package/dist/datetime/index.d.ts.map +1 -1
  68. package/dist/datetime/index.js +15 -0
  69. package/dist/datetime/index.js.map +1 -1
  70. package/dist/email/index.d.ts +323 -20
  71. package/dist/email/index.d.ts.map +1 -1
  72. package/dist/email/index.js +1857 -7
  73. package/dist/email/index.js.map +1 -1
  74. package/dist/fake/index.d.ts +90 -8
  75. package/dist/fake/index.d.ts.map +1 -1
  76. package/dist/fake/index.js +91 -20
  77. package/dist/fake/index.js.map +1 -1
  78. package/dist/lock/core/index.d.ts +11 -4
  79. package/dist/lock/core/index.d.ts.map +1 -1
  80. package/dist/lock/core/index.js +11 -4
  81. package/dist/lock/core/index.js.map +1 -1
  82. package/dist/logger/index.d.ts +17 -66
  83. package/dist/logger/index.d.ts.map +1 -1
  84. package/dist/logger/index.js +14 -63
  85. package/dist/logger/index.js.map +1 -1
  86. package/dist/mcp/index.d.ts +10 -30
  87. package/dist/mcp/index.d.ts.map +1 -1
  88. package/dist/mcp/index.js +12 -35
  89. package/dist/mcp/index.js.map +1 -1
  90. package/dist/orm/index.browser.js +3 -3
  91. package/dist/orm/index.browser.js.map +1 -1
  92. package/dist/orm/index.bun.js +39 -20
  93. package/dist/orm/index.bun.js.map +1 -1
  94. package/dist/orm/index.d.ts +517 -540
  95. package/dist/orm/index.d.ts.map +1 -1
  96. package/dist/orm/index.js +58 -71
  97. package/dist/orm/index.js.map +1 -1
  98. package/dist/queue/core/index.d.ts +18 -10
  99. package/dist/queue/core/index.d.ts.map +1 -1
  100. package/dist/queue/core/index.js +14 -6
  101. package/dist/queue/core/index.js.map +1 -1
  102. package/dist/react/auth/index.browser.js +108 -0
  103. package/dist/react/auth/index.browser.js.map +1 -0
  104. package/dist/react/auth/index.d.ts +100 -0
  105. package/dist/react/auth/index.d.ts.map +1 -0
  106. package/dist/react/auth/index.js +145 -0
  107. package/dist/react/auth/index.js.map +1 -0
  108. package/dist/react/core/index.d.ts +469 -0
  109. package/dist/react/core/index.d.ts.map +1 -0
  110. package/dist/react/core/index.js +464 -0
  111. package/dist/react/core/index.js.map +1 -0
  112. package/dist/react/form/index.d.ts +232 -0
  113. package/dist/react/form/index.d.ts.map +1 -0
  114. package/dist/react/form/index.js +432 -0
  115. package/dist/react/form/index.js.map +1 -0
  116. package/dist/react/head/index.browser.js +423 -0
  117. package/dist/react/head/index.browser.js.map +1 -0
  118. package/dist/react/head/index.d.ts +288 -0
  119. package/dist/react/head/index.d.ts.map +1 -0
  120. package/dist/react/head/index.js +465 -0
  121. package/dist/react/head/index.js.map +1 -0
  122. package/dist/react/i18n/index.d.ts +175 -0
  123. package/dist/react/i18n/index.d.ts.map +1 -0
  124. package/dist/react/i18n/index.js +224 -0
  125. package/dist/react/i18n/index.js.map +1 -0
  126. package/dist/react/router/index.browser.js +1974 -0
  127. package/dist/react/router/index.browser.js.map +1 -0
  128. package/dist/react/router/index.d.ts +1956 -0
  129. package/dist/react/router/index.d.ts.map +1 -0
  130. package/dist/react/router/index.js +4722 -0
  131. package/dist/react/router/index.js.map +1 -0
  132. package/dist/react/websocket/index.d.ts +117 -0
  133. package/dist/react/websocket/index.d.ts.map +1 -0
  134. package/dist/react/websocket/index.js +107 -0
  135. package/dist/react/websocket/index.js.map +1 -0
  136. package/dist/redis/index.bun.js +4 -0
  137. package/dist/redis/index.bun.js.map +1 -1
  138. package/dist/redis/index.d.ts +41 -44
  139. package/dist/redis/index.d.ts.map +1 -1
  140. package/dist/redis/index.js +16 -25
  141. package/dist/redis/index.js.map +1 -1
  142. package/dist/retry/index.d.ts +11 -2
  143. package/dist/retry/index.d.ts.map +1 -1
  144. package/dist/retry/index.js +11 -2
  145. package/dist/retry/index.js.map +1 -1
  146. package/dist/scheduler/index.d.ts +11 -2
  147. package/dist/scheduler/index.d.ts.map +1 -1
  148. package/dist/scheduler/index.js +11 -2
  149. package/dist/scheduler/index.js.map +1 -1
  150. package/dist/security/index.d.ts +140 -49
  151. package/dist/security/index.d.ts.map +1 -1
  152. package/dist/security/index.js +164 -32
  153. package/dist/security/index.js.map +1 -1
  154. package/dist/server/auth/index.d.ts +12 -7
  155. package/dist/server/auth/index.d.ts.map +1 -1
  156. package/dist/server/auth/index.js +12 -7
  157. package/dist/server/auth/index.js.map +1 -1
  158. package/dist/server/cache/index.d.ts +7 -22
  159. package/dist/server/cache/index.d.ts.map +1 -1
  160. package/dist/server/cache/index.js +7 -22
  161. package/dist/server/cache/index.js.map +1 -1
  162. package/dist/server/compress/index.d.ts +10 -2
  163. package/dist/server/compress/index.d.ts.map +1 -1
  164. package/dist/server/compress/index.js +10 -2
  165. package/dist/server/compress/index.js.map +1 -1
  166. package/dist/server/cookies/index.d.ts +40 -16
  167. package/dist/server/cookies/index.d.ts.map +1 -1
  168. package/dist/server/cookies/index.js +7 -5
  169. package/dist/server/cookies/index.js.map +1 -1
  170. package/dist/server/core/index.d.ts +124 -23
  171. package/dist/server/core/index.d.ts.map +1 -1
  172. package/dist/server/core/index.js +231 -14
  173. package/dist/server/core/index.js.map +1 -1
  174. package/dist/server/cors/index.d.ts +13 -23
  175. package/dist/server/cors/index.d.ts.map +1 -1
  176. package/dist/server/cors/index.js +7 -21
  177. package/dist/server/cors/index.js.map +1 -1
  178. package/dist/server/health/index.d.ts +8 -2
  179. package/dist/server/health/index.d.ts.map +1 -1
  180. package/dist/server/health/index.js +8 -2
  181. package/dist/server/health/index.js.map +1 -1
  182. package/dist/server/helmet/index.d.ts +11 -3
  183. package/dist/server/helmet/index.d.ts.map +1 -1
  184. package/dist/server/helmet/index.js +11 -3
  185. package/dist/server/helmet/index.js.map +1 -1
  186. package/dist/server/links/index.d.ts +11 -6
  187. package/dist/server/links/index.d.ts.map +1 -1
  188. package/dist/server/links/index.js +11 -6
  189. package/dist/server/links/index.js.map +1 -1
  190. package/dist/server/metrics/index.d.ts +10 -3
  191. package/dist/server/metrics/index.d.ts.map +1 -1
  192. package/dist/server/metrics/index.js +10 -3
  193. package/dist/server/metrics/index.js.map +1 -1
  194. package/dist/server/multipart/index.d.ts +9 -3
  195. package/dist/server/multipart/index.d.ts.map +1 -1
  196. package/dist/server/multipart/index.js +9 -3
  197. package/dist/server/multipart/index.js.map +1 -1
  198. package/dist/server/proxy/index.d.ts +8 -2
  199. package/dist/server/proxy/index.d.ts.map +1 -1
  200. package/dist/server/proxy/index.js +8 -2
  201. package/dist/server/proxy/index.js.map +1 -1
  202. package/dist/server/rate-limit/index.d.ts +30 -35
  203. package/dist/server/rate-limit/index.d.ts.map +1 -1
  204. package/dist/server/rate-limit/index.js +18 -55
  205. package/dist/server/rate-limit/index.js.map +1 -1
  206. package/dist/server/static/index.d.ts +137 -4
  207. package/dist/server/static/index.d.ts.map +1 -1
  208. package/dist/server/static/index.js +1853 -5
  209. package/dist/server/static/index.js.map +1 -1
  210. package/dist/server/swagger/index.d.ts +309 -6
  211. package/dist/server/swagger/index.d.ts.map +1 -1
  212. package/dist/server/swagger/index.js +1854 -6
  213. package/dist/server/swagger/index.js.map +1 -1
  214. package/dist/sms/index.d.ts +309 -7
  215. package/dist/sms/index.d.ts.map +1 -1
  216. package/dist/sms/index.js +1856 -7
  217. package/dist/sms/index.js.map +1 -1
  218. package/dist/system/index.browser.js +1218 -0
  219. package/dist/system/index.browser.js.map +1 -0
  220. package/dist/{file → system}/index.d.ts +343 -16
  221. package/dist/system/index.d.ts.map +1 -0
  222. package/dist/{file → system}/index.js +419 -22
  223. package/dist/system/index.js.map +1 -0
  224. package/dist/thread/index.d.ts +11 -2
  225. package/dist/thread/index.d.ts.map +1 -1
  226. package/dist/thread/index.js +11 -2
  227. package/dist/thread/index.js.map +1 -1
  228. package/dist/topic/core/index.d.ts +12 -5
  229. package/dist/topic/core/index.d.ts.map +1 -1
  230. package/dist/topic/core/index.js +12 -5
  231. package/dist/topic/core/index.js.map +1 -1
  232. package/dist/vite/index.d.ts +5 -6272
  233. package/dist/vite/index.d.ts.map +1 -1
  234. package/dist/vite/index.js +23 -10
  235. package/dist/vite/index.js.map +1 -1
  236. package/dist/websocket/index.d.ts +12 -8
  237. package/dist/websocket/index.d.ts.map +1 -1
  238. package/dist/websocket/index.js +12 -8
  239. package/dist/websocket/index.js.map +1 -1
  240. package/package.json +82 -11
  241. package/src/api/audits/index.ts +10 -33
  242. package/src/api/files/__tests__/$bucket.spec.ts +1 -1
  243. package/src/api/files/controllers/AdminFileStatsController.spec.ts +1 -1
  244. package/src/api/files/controllers/FileController.spec.ts +1 -1
  245. package/src/api/files/index.ts +10 -3
  246. package/src/api/files/jobs/FileJobs.spec.ts +1 -1
  247. package/src/api/files/services/FileService.spec.ts +1 -1
  248. package/src/api/jobs/index.ts +10 -3
  249. package/src/api/keys/controllers/AdminApiKeyController.ts +75 -0
  250. package/src/api/keys/controllers/ApiKeyController.ts +103 -0
  251. package/src/api/keys/entities/apiKeyEntity.ts +41 -0
  252. package/src/api/keys/index.ts +49 -0
  253. package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +7 -0
  254. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +17 -0
  255. package/src/api/keys/schemas/createApiKeyBodySchema.ts +7 -0
  256. package/src/api/keys/schemas/createApiKeyResponseSchema.ts +11 -0
  257. package/src/api/keys/schemas/listApiKeyResponseSchema.ts +15 -0
  258. package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +5 -0
  259. package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +5 -0
  260. package/src/api/keys/services/ApiKeyService.spec.ts +553 -0
  261. package/src/api/keys/services/ApiKeyService.ts +306 -0
  262. package/src/api/logs/TODO.md +55 -0
  263. package/src/api/notifications/index.ts +10 -4
  264. package/src/api/parameters/index.ts +9 -30
  265. package/src/api/parameters/primitives/$config.ts +12 -4
  266. package/src/api/parameters/services/ConfigStore.ts +9 -3
  267. package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1035 -0
  268. package/src/api/users/__tests__/ApiKeys.spec.ts +401 -0
  269. package/src/api/users/index.ts +14 -3
  270. package/src/api/users/primitives/$realm.ts +33 -5
  271. package/src/api/users/providers/RealmProvider.ts +1 -12
  272. package/src/api/users/services/SessionService.ts +1 -1
  273. package/src/api/verifications/controllers/VerificationController.ts +2 -0
  274. package/src/api/verifications/index.ts +10 -4
  275. package/src/batch/index.ts +9 -36
  276. package/src/batch/primitives/$batch.ts +0 -8
  277. package/src/batch/providers/BatchProvider.ts +29 -2
  278. package/src/bucket/__tests__/shared.ts +1 -1
  279. package/src/bucket/index.ts +13 -6
  280. package/src/bucket/primitives/$bucket.ts +1 -1
  281. package/src/bucket/providers/LocalFileStorageProvider.ts +1 -1
  282. package/src/bucket/providers/MemoryFileStorageProvider.ts +1 -1
  283. package/src/cache/core/__tests__/shared.ts +30 -0
  284. package/src/cache/core/index.ts +11 -6
  285. package/src/cache/core/primitives/$cache.spec.ts +5 -0
  286. package/src/cache/core/providers/CacheProvider.ts +17 -0
  287. package/src/cache/core/providers/MemoryCacheProvider.ts +300 -1
  288. package/src/cache/redis/__tests__/cache-redis.spec.ts +5 -0
  289. package/src/cache/redis/providers/RedisCacheProvider.ts +9 -0
  290. package/src/cli/apps/AlephaCli.ts +1 -14
  291. package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -1
  292. package/src/cli/atoms/buildOptions.ts +99 -9
  293. package/src/cli/commands/build.ts +150 -37
  294. package/src/cli/commands/db.ts +22 -18
  295. package/src/cli/commands/deploy.ts +1 -1
  296. package/src/cli/commands/dev.ts +1 -20
  297. package/src/cli/commands/gen/env.ts +5 -2
  298. package/src/cli/commands/gen/openapi.ts +5 -2
  299. package/src/cli/commands/init.spec.ts +588 -0
  300. package/src/cli/commands/init.ts +115 -58
  301. package/src/cli/commands/lint.ts +7 -1
  302. package/src/cli/commands/typecheck.ts +11 -0
  303. package/src/cli/providers/AppEntryProvider.ts +1 -1
  304. package/src/cli/providers/ViteBuildProvider.ts +8 -50
  305. package/src/cli/providers/ViteDevServerProvider.ts +35 -16
  306. package/src/cli/services/AlephaCliUtils.ts +52 -121
  307. package/src/cli/services/PackageManagerUtils.ts +129 -11
  308. package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
  309. package/src/cli/services/ProjectScaffolder.ts +148 -81
  310. package/src/cli/services/ViteUtils.ts +82 -0
  311. package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +37 -24
  312. package/src/cli/templates/apiAppSecurityTs.ts +11 -0
  313. package/src/cli/templates/apiIndexTs.ts +30 -0
  314. package/src/cli/templates/gitignore.ts +39 -0
  315. package/src/cli/{assets → templates}/mainCss.ts +11 -2
  316. package/src/cli/templates/mainServerTs.ts +33 -0
  317. package/src/cli/templates/webAppRouterTs.ts +74 -0
  318. package/src/cli/templates/webHelloComponentTsx.ts +30 -0
  319. package/src/command/helpers/Runner.spec.ts +139 -0
  320. package/src/command/helpers/Runner.ts +7 -22
  321. package/src/command/index.ts +12 -4
  322. package/src/command/providers/CliProvider.spec.ts +1392 -0
  323. package/src/command/providers/CliProvider.ts +320 -47
  324. package/src/core/Alepha.ts +34 -27
  325. package/src/core/__tests__/Alepha-start.spec.ts +4 -4
  326. package/src/core/helpers/jsonSchemaToTypeBox.spec.ts +771 -0
  327. package/src/core/helpers/jsonSchemaToTypeBox.ts +62 -10
  328. package/src/core/index.shared.ts +1 -0
  329. package/src/core/index.ts +20 -0
  330. package/src/core/providers/EventManager.spec.ts +0 -71
  331. package/src/core/providers/EventManager.ts +3 -15
  332. package/src/core/providers/Json.ts +2 -14
  333. package/src/datetime/index.ts +15 -0
  334. package/src/email/index.ts +10 -5
  335. package/src/email/providers/LocalEmailProvider.spec.ts +1 -1
  336. package/src/email/providers/LocalEmailProvider.ts +1 -1
  337. package/src/fake/__tests__/keyName.example.ts +1 -1
  338. package/src/fake/__tests__/keyName.spec.ts +5 -5
  339. package/src/fake/index.ts +9 -6
  340. package/src/fake/providers/FakeProvider.spec.ts +258 -40
  341. package/src/fake/providers/FakeProvider.ts +133 -19
  342. package/src/lock/core/index.ts +11 -4
  343. package/src/logger/index.ts +17 -66
  344. package/src/mcp/index.ts +10 -27
  345. package/src/mcp/transports/SseMcpTransport.ts +0 -11
  346. package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
  347. package/src/orm/index.browser.ts +2 -2
  348. package/src/orm/index.bun.ts +5 -3
  349. package/src/orm/index.ts +23 -53
  350. package/src/orm/providers/drivers/BunSqliteProvider.ts +5 -1
  351. package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
  352. package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
  353. package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
  354. package/src/orm/services/Repository.ts +7 -3
  355. package/src/queue/core/index.ts +14 -6
  356. package/src/react/auth/__tests__/$auth.spec.ts +202 -0
  357. package/src/react/auth/hooks/useAuth.ts +32 -0
  358. package/src/react/auth/index.browser.ts +13 -0
  359. package/src/react/auth/index.shared.ts +2 -0
  360. package/src/react/auth/index.ts +48 -0
  361. package/src/react/auth/providers/ReactAuthProvider.ts +16 -0
  362. package/src/react/auth/services/ReactAuth.ts +135 -0
  363. package/src/react/core/__tests__/Router.spec.tsx +169 -0
  364. package/src/react/core/components/ClientOnly.tsx +49 -0
  365. package/src/react/core/components/ErrorBoundary.tsx +73 -0
  366. package/src/react/core/contexts/AlephaContext.ts +7 -0
  367. package/src/react/core/contexts/AlephaProvider.tsx +42 -0
  368. package/src/react/core/hooks/useAction.browser.spec.tsx +569 -0
  369. package/src/react/core/hooks/useAction.ts +480 -0
  370. package/src/react/core/hooks/useAlepha.ts +26 -0
  371. package/src/react/core/hooks/useClient.ts +17 -0
  372. package/src/react/core/hooks/useEvents.ts +51 -0
  373. package/src/react/core/hooks/useInject.ts +12 -0
  374. package/src/react/core/hooks/useStore.ts +52 -0
  375. package/src/react/core/index.ts +90 -0
  376. package/src/react/form/components/FormState.tsx +17 -0
  377. package/src/react/form/errors/FormValidationError.ts +18 -0
  378. package/src/react/form/hooks/useForm.browser.spec.tsx +366 -0
  379. package/src/react/form/hooks/useForm.ts +47 -0
  380. package/src/react/form/hooks/useFormState.ts +130 -0
  381. package/src/react/form/index.ts +44 -0
  382. package/src/react/form/services/FormModel.ts +614 -0
  383. package/src/react/head/helpers/SeoExpander.spec.ts +203 -0
  384. package/src/react/head/helpers/SeoExpander.ts +142 -0
  385. package/src/react/head/hooks/useHead.spec.tsx +288 -0
  386. package/src/react/head/hooks/useHead.ts +62 -0
  387. package/src/react/head/index.browser.ts +26 -0
  388. package/src/react/head/index.ts +44 -0
  389. package/src/react/head/interfaces/Head.ts +105 -0
  390. package/src/react/head/primitives/$head.ts +25 -0
  391. package/src/react/head/providers/BrowserHeadProvider.browser.spec.ts +196 -0
  392. package/src/react/head/providers/BrowserHeadProvider.ts +212 -0
  393. package/src/react/head/providers/HeadProvider.ts +168 -0
  394. package/src/react/head/providers/ServerHeadProvider.ts +31 -0
  395. package/src/react/i18n/__tests__/integration.spec.tsx +239 -0
  396. package/src/react/i18n/components/Localize.spec.tsx +357 -0
  397. package/src/react/i18n/components/Localize.tsx +35 -0
  398. package/src/react/i18n/hooks/useI18n.browser.spec.tsx +438 -0
  399. package/src/react/i18n/hooks/useI18n.ts +18 -0
  400. package/src/react/i18n/index.ts +41 -0
  401. package/src/react/i18n/primitives/$dictionary.ts +69 -0
  402. package/src/react/i18n/providers/I18nProvider.spec.ts +389 -0
  403. package/src/react/i18n/providers/I18nProvider.ts +278 -0
  404. package/src/react/router/__tests__/page-head-browser.browser.spec.ts +95 -0
  405. package/src/react/router/__tests__/page-head.spec.ts +48 -0
  406. package/src/react/router/__tests__/seo-head.spec.ts +125 -0
  407. package/src/react/router/atoms/ssrManifestAtom.ts +58 -0
  408. package/src/react/router/components/ErrorViewer.tsx +872 -0
  409. package/src/react/router/components/Link.tsx +23 -0
  410. package/src/react/router/components/NestedView.tsx +223 -0
  411. package/src/react/router/components/NotFound.tsx +30 -0
  412. package/src/react/router/constants/PAGE_PRELOAD_KEY.ts +6 -0
  413. package/src/react/router/contexts/RouterLayerContext.ts +12 -0
  414. package/src/react/router/errors/Redirection.ts +28 -0
  415. package/src/react/router/hooks/useActive.ts +52 -0
  416. package/src/react/router/hooks/useQueryParams.ts +63 -0
  417. package/src/react/router/hooks/useRouter.ts +20 -0
  418. package/src/react/router/hooks/useRouterState.ts +11 -0
  419. package/src/react/router/index.browser.ts +45 -0
  420. package/src/react/router/index.shared.ts +19 -0
  421. package/src/react/router/index.ts +146 -0
  422. package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
  423. package/src/react/router/primitives/$page.spec.tsx +676 -0
  424. package/src/react/router/primitives/$page.ts +489 -0
  425. package/src/react/router/providers/ReactBrowserProvider.ts +312 -0
  426. package/src/react/router/providers/ReactBrowserRendererProvider.ts +25 -0
  427. package/src/react/router/providers/ReactBrowserRouterProvider.ts +168 -0
  428. package/src/react/router/providers/ReactPageProvider.ts +726 -0
  429. package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
  430. package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
  431. package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
  432. package/src/react/router/providers/ReactServerProvider.ts +487 -0
  433. package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
  434. package/src/react/router/providers/ReactServerTemplateProvider.ts +542 -0
  435. package/src/react/router/providers/SSRManifestProvider.ts +334 -0
  436. package/src/react/router/services/ReactPageServerService.ts +48 -0
  437. package/src/react/router/services/ReactPageService.ts +27 -0
  438. package/src/react/router/services/ReactRouter.ts +262 -0
  439. package/src/react/websocket/hooks/useRoom.tsx +242 -0
  440. package/src/react/websocket/index.ts +7 -0
  441. package/src/redis/__tests__/redis.spec.ts +13 -0
  442. package/src/redis/index.ts +9 -25
  443. package/src/redis/providers/BunRedisProvider.ts +9 -0
  444. package/src/redis/providers/NodeRedisProvider.ts +8 -0
  445. package/src/redis/providers/RedisProvider.ts +16 -0
  446. package/src/retry/index.ts +11 -2
  447. package/src/router/index.ts +15 -0
  448. package/src/scheduler/index.ts +11 -2
  449. package/src/security/__tests__/BasicAuth.spec.ts +2 -0
  450. package/src/security/__tests__/ServerSecurityProvider.spec.ts +90 -5
  451. package/src/security/index.ts +15 -10
  452. package/src/security/interfaces/IssuerResolver.ts +27 -0
  453. package/src/security/primitives/$issuer.ts +55 -0
  454. package/src/security/providers/SecurityProvider.ts +179 -0
  455. package/src/security/providers/ServerBasicAuthProvider.ts +6 -2
  456. package/src/security/providers/ServerSecurityProvider.ts +63 -41
  457. package/src/server/auth/index.ts +12 -7
  458. package/src/server/cache/index.ts +7 -22
  459. package/src/server/compress/index.ts +10 -2
  460. package/src/server/cookies/index.ts +7 -5
  461. package/src/server/cookies/primitives/$cookie.ts +33 -11
  462. package/src/server/core/index.ts +16 -6
  463. package/src/server/core/interfaces/ServerRequest.ts +83 -1
  464. package/src/server/core/primitives/$action.spec.ts +1 -1
  465. package/src/server/core/primitives/$action.ts +8 -3
  466. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
  467. package/src/server/core/providers/NodeHttpServerProvider.ts +9 -3
  468. package/src/server/core/services/ServerRequestParser.spec.ts +520 -0
  469. package/src/server/core/services/ServerRequestParser.ts +306 -13
  470. package/src/server/cors/index.ts +7 -21
  471. package/src/server/cors/primitives/$cors.ts +6 -2
  472. package/src/server/health/index.ts +8 -2
  473. package/src/server/helmet/index.ts +11 -3
  474. package/src/server/links/index.ts +11 -6
  475. package/src/server/metrics/index.ts +10 -3
  476. package/src/server/multipart/index.ts +9 -3
  477. package/src/server/proxy/index.ts +8 -2
  478. package/src/server/rate-limit/index.ts +21 -25
  479. package/src/server/rate-limit/primitives/$rateLimit.ts +6 -2
  480. package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +38 -14
  481. package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +22 -56
  482. package/src/server/static/index.ts +8 -2
  483. package/src/server/static/providers/ServerStaticProvider.ts +1 -1
  484. package/src/server/swagger/index.ts +9 -4
  485. package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
  486. package/src/sms/index.ts +9 -5
  487. package/src/sms/providers/LocalSmsProvider.spec.ts +1 -1
  488. package/src/sms/providers/LocalSmsProvider.ts +1 -1
  489. package/src/system/index.browser.ts +36 -0
  490. package/src/system/index.ts +62 -0
  491. package/src/system/index.workerd.ts +1 -0
  492. package/src/{file → system}/providers/FileSystemProvider.ts +24 -0
  493. package/src/{file → system}/providers/MemoryFileSystemProvider.ts +116 -3
  494. package/src/system/providers/MemoryShellProvider.ts +164 -0
  495. package/src/{file → system}/providers/NodeFileSystemProvider.spec.ts +2 -2
  496. package/src/{file → system}/providers/NodeFileSystemProvider.ts +47 -2
  497. package/src/system/providers/NodeShellProvider.ts +184 -0
  498. package/src/system/providers/ShellProvider.ts +74 -0
  499. package/src/{file → system}/services/FileDetector.spec.ts +2 -2
  500. package/src/thread/index.ts +11 -2
  501. package/src/topic/core/index.ts +12 -5
  502. package/src/vite/tasks/buildClient.ts +2 -7
  503. package/src/vite/tasks/buildServer.ts +19 -13
  504. package/src/vite/tasks/generateCloudflare.ts +10 -7
  505. package/src/vite/tasks/generateDocker.ts +4 -0
  506. package/src/websocket/index.ts +12 -8
  507. package/dist/file/index.d.ts.map +0 -1
  508. package/dist/file/index.js.map +0 -1
  509. package/src/cli/assets/apiIndexTs.ts +0 -16
  510. package/src/cli/assets/mainServerTs.ts +0 -24
  511. package/src/cli/assets/webAppRouterTs.ts +0 -16
  512. package/src/cli/assets/webHelloComponentTsx.ts +0 -20
  513. package/src/cli/providers/ViteTemplateProvider.ts +0 -27
  514. package/src/file/index.ts +0 -43
  515. /package/src/cli/{assets → templates}/apiHelloControllerTs.ts +0 -0
  516. /package/src/cli/{assets → templates}/biomeJson.ts +0 -0
  517. /package/src/cli/{assets → templates}/dummySpecTs.ts +0 -0
  518. /package/src/cli/{assets → templates}/editorconfig.ts +0 -0
  519. /package/src/cli/{assets → templates}/mainBrowserTs.ts +0 -0
  520. /package/src/cli/{assets → templates}/tsconfigJson.ts +0 -0
  521. /package/src/cli/{assets → templates}/webIndexTs.ts +0 -0
  522. /package/src/{file → system}/errors/FileError.ts +0 -0
  523. /package/src/{file → system}/services/FileDetector.ts +0 -0
@@ -83,29 +83,53 @@ describe("ServerRateLimitProvider", () => {
83
83
  expect(result2.allowed).toBe(true);
84
84
  });
85
85
 
86
- it("should extract IP from x-forwarded-for header", async () => {
86
+ it("should use req.ip for rate limiting (trust proxy handled by ServerRequestParser)", async () => {
87
87
  const options = { max: 1, windowMs: 60000 };
88
88
 
89
- // First request with x-forwarded-for: 203.0.113.1
90
- const req1 = createMockRequest("127.0.0.1");
91
- req1.headers["x-forwarded-for"] = "203.0.113.1, 192.168.1.1";
92
-
89
+ // Rate limit uses req.ip which is resolved by ServerRequestParser
90
+ // Trust proxy configuration is at server level via TRUST_PROXY env var
91
+ const req1 = createMockRequest("192.168.1.100");
93
92
  const result1 = await provider.checkLimit(req1, options);
94
93
  expect(result1.allowed).toBe(true);
95
94
 
96
- // Second request with same x-forwarded-for should be blocked
97
- const req2 = createMockRequest("127.0.0.1");
98
- req2.headers["x-forwarded-for"] = "203.0.113.1, 192.168.1.1";
99
-
95
+ // Same IP should be blocked
96
+ const req2 = createMockRequest("192.168.1.100");
100
97
  const result2 = await provider.checkLimit(req2, options);
101
98
  expect(result2.allowed).toBe(false);
99
+ });
100
+
101
+ it("should handle truly concurrent requests atomically", async () => {
102
+ const options = { max: 3, windowMs: 60000 };
103
+
104
+ // Fire 5 requests concurrently from the same IP
105
+ const requests = Array.from({ length: 5 }, () =>
106
+ createMockRequest("10.0.0.1"),
107
+ );
108
+ const results = await Promise.all(
109
+ requests.map((req) => provider.checkLimit(req, options)),
110
+ );
111
+
112
+ // Exactly 3 should be allowed, 2 should be blocked
113
+ const allowed = results.filter((r) => r.allowed).length;
114
+ const blocked = results.filter((r) => !r.allowed).length;
115
+
116
+ expect(allowed).toBe(3);
117
+ expect(blocked).toBe(2);
118
+ });
119
+
120
+ it("should return correct resetTime within the fixed window", async () => {
121
+ const windowMs = 60000;
122
+ const options = { max: 10, windowMs };
123
+ const req = createMockRequest();
124
+
125
+ const result = await provider.checkLimit(req, options);
102
126
 
103
- // Request with different x-forwarded-for should be allowed
104
- const req3 = createMockRequest("127.0.0.1");
105
- req3.headers["x-forwarded-for"] = "198.51.100.1, 192.168.1.1";
127
+ // resetTime should be at the end of the current window
128
+ const now = Date.now();
129
+ const windowStart = Math.floor(now / windowMs) * windowMs;
130
+ const expectedResetTime = windowStart + windowMs;
106
131
 
107
- const result3 = await provider.checkLimit(req3, options);
108
- expect(result3.allowed).toBe(true);
132
+ expect(result.resetTime).toBe(expectedResetTime);
109
133
  });
110
134
  });
111
135
 
@@ -1,5 +1,5 @@
1
1
  import { $atom, $env, $hook, $inject, $use, type Static, t } from "alepha";
2
- import { $cache } from "alepha/cache";
2
+ import { CacheProvider } from "alepha/cache";
3
3
  import { $logger } from "alepha/logger";
4
4
  import {
5
5
  HttpError,
@@ -74,14 +74,11 @@ export class ServerRateLimitProvider {
74
74
  protected readonly log = $logger();
75
75
  protected readonly serverRouterProvider = $inject(ServerRouterProvider);
76
76
  protected readonly env = $env(envSchema);
77
-
78
- protected readonly cache = $cache<RateLimitData>({
79
- name: "server-rate-limit",
80
- ttl: [this.env.RATE_LIMIT_WINDOW_MS, "milliseconds"],
81
- });
82
-
77
+ protected readonly cacheProvider = $inject(CacheProvider);
83
78
  protected readonly globalOptions = $use(rateLimitOptions);
84
79
 
80
+ protected static readonly CACHE_NAME = "rate-limit";
81
+
85
82
  /**
86
83
  * Registered rate limit configurations with their path patterns
87
84
  */
@@ -210,42 +207,30 @@ export class ServerRateLimitProvider {
210
207
  ): Promise<RateLimitResult> {
211
208
  const windowMs = options.windowMs ?? this.env.RATE_LIMIT_WINDOW_MS;
212
209
  const max = options.max ?? this.env.RATE_LIMIT_MAX_REQUESTS;
213
- const key = this.generateKey(req);
210
+ const baseKey = this.generateKey(req);
214
211
 
215
212
  const now = Date.now();
216
- const windowStart = now - windowMs;
217
-
218
- // Get current rate limit data
219
- const currentData = (await this.cache.get(key)) || {
220
- count: 0,
221
- windowStart: now,
222
- hits: [],
223
- };
224
-
225
- // Clean old hits outside the current window
226
- const validHits = currentData.hits.filter(
227
- (hit: number) => hit >= windowStart,
213
+ // Fixed window: round down to nearest window boundary
214
+ const windowStart = Math.floor(now / windowMs) * windowMs;
215
+ const resetTime = windowStart + windowMs;
216
+
217
+ // Include window timestamp in key for automatic expiration of old windows
218
+ const key = `${baseKey}:${windowStart}`;
219
+
220
+ // Atomic increment - returns the new count after incrementing
221
+ const count = await this.cacheProvider.incr(
222
+ ServerRateLimitProvider.CACHE_NAME,
223
+ key,
224
+ 1,
228
225
  );
229
226
 
230
- // Check if limit exceeded
231
- const allowed = validHits.length < max;
232
- const remaining = Math.max(0, max - validHits.length);
233
- const resetTime = Math.max(...validHits, windowStart) + windowMs;
234
-
235
- // If allowed, record this request
236
- if (allowed) {
237
- validHits.push(now);
238
- await this.cache.set(key, {
239
- count: validHits.length,
240
- windowStart: Math.min(currentData.windowStart, windowStart),
241
- hits: validHits,
242
- });
243
- }
227
+ const allowed = count <= max;
228
+ const remaining = Math.max(0, max - count);
244
229
 
245
230
  const result: RateLimitResult = {
246
231
  allowed,
247
232
  limit: max,
248
- remaining: allowed ? remaining - 1 : remaining,
233
+ remaining,
249
234
  resetTime,
250
235
  };
251
236
 
@@ -257,26 +242,7 @@ export class ServerRateLimitProvider {
257
242
  }
258
243
 
259
244
  protected generateKey(req: ServerRequest): string {
260
- // Default to IP-based rate limiting
261
- const ip = this.getClientIP(req);
262
- return `ip:${ip}`;
245
+ // Use req.ip which is resolved by ServerRequestParser with proper trust proxy handling
246
+ return `ip:${req.ip || "unknown"}`;
263
247
  }
264
-
265
- protected getClientIP(req: ServerRequest): string {
266
- // Check x-forwarded-for header first (for proxies/load balancers)
267
- const forwarded = req.headers?.["x-forwarded-for"];
268
- if (forwarded) {
269
- // x-forwarded-for can contain multiple IPs, get the first one (original client)
270
- const firstIp = forwarded.split(",")[0].trim();
271
- if (firstIp) return firstIp;
272
- }
273
-
274
- return req.ip || "unknown";
275
- }
276
- }
277
-
278
- interface RateLimitData {
279
- count: number;
280
- windowStart: number;
281
- hits: number[];
282
248
  }
@@ -11,9 +11,15 @@ export * from "./providers/ServerStaticProvider.ts";
11
11
  // ---------------------------------------------------------------------------------------------------------------------
12
12
 
13
13
  /**
14
- * Create static file server with `$static()`.
14
+ * | type | quality | stability |
15
+ * |------|---------|-----------|
16
+ * | backend | standard | stable |
17
+ *
18
+ * Static file serving.
19
+ *
20
+ * **Features:**
21
+ * - Serve static files from directory
15
22
  *
16
- * @see {@link ServerStaticProvider}
17
23
  * @module alepha.server.static
18
24
  */
19
25
  export const AlephaServerStatic = $module({
@@ -4,9 +4,9 @@ import { basename, isAbsolute, join } from "node:path";
4
4
  import type { Readable as NodeStream } from "node:stream";
5
5
  import { $hook, $inject, Alepha } from "alepha";
6
6
  import { DateTimeProvider } from "alepha/datetime";
7
- import { FileDetector } from "alepha/file";
8
7
  import { $logger } from "alepha/logger";
9
8
  import { type ServerHandler, ServerRouterProvider } from "alepha/server";
9
+ import { FileDetector } from "alepha/system";
10
10
  import { $serve, type ServePrimitiveOptions } from "../primitives/$serve.ts";
11
11
 
12
12
  export class ServerStaticProvider {
@@ -30,11 +30,16 @@ declare module "alepha/server" {
30
30
  // ---------------------------------------------------------------------------------------------------------------------
31
31
 
32
32
  /**
33
- * Plugin for Alepha Server that provides Swagger documentation capabilities.
34
- * It generates OpenAPI v3 documentation for the server's endpoints ($action).
35
- * It also provides a Swagger UI for interactive API documentation.
33
+ * | type | quality | stability |
34
+ * |------|---------|-----------|
35
+ * | backend | standard | stable |
36
+ *
37
+ * Automatic API documentation generation.
38
+ *
39
+ * **Features:**
40
+ * - Swagger/OpenAPI configuration
41
+ * - Routes: `GET /swagger/ui`, `GET /swagger.json`
36
42
  *
37
- * @see {@link ServerSwaggerProvider}
38
43
  * @module alepha.server.swagger
39
44
  */
40
45
  export const AlephaServerSwagger = $module({
@@ -12,7 +12,6 @@ import {
12
12
  type TSchema,
13
13
  t,
14
14
  } from "alepha";
15
- import { FileSystemProvider } from "alepha/file";
16
15
  import { $logger } from "alepha/logger";
17
16
  import { AlephaSecurity } from "alepha/security";
18
17
  import {
@@ -23,6 +22,7 @@ import {
23
22
  ServerRouterProvider,
24
23
  } from "alepha/server";
25
24
  import { ServerStaticProvider } from "alepha/server/static";
25
+ import { FileSystemProvider } from "alepha/system";
26
26
  import {
27
27
  $swagger,
28
28
  type OpenApiDocument,
package/src/sms/index.ts CHANGED
@@ -34,13 +34,17 @@ declare module "alepha" {
34
34
  // ---------------------------------------------------------------------------------------------------------------------
35
35
 
36
36
  /**
37
- * Provides SMS sending capabilities for Alepha applications with multiple provider backends.
37
+ * | type | quality | stability |
38
+ * |------|---------|-----------|
39
+ * | backend | rare | stable |
38
40
  *
39
- * The SMS module enables declarative SMS sending through the `$sms` primitive, allowing you to send
40
- * text messages through different providers: memory (for testing) or local file system.
41
- * It supports automatic provider selection based on environment configuration.
41
+ * SMS delivery with multiple provider support.
42
+ *
43
+ * **Features:**
44
+ * - Send SMS with templates
45
+ * - Multiple recipients
46
+ * - Provider abstraction
42
47
  *
43
- * @see {@link SmsProvider}
44
48
  * @module alepha.sms
45
49
  */
46
50
  export const AlephaSms = $module({
@@ -1,5 +1,5 @@
1
1
  import { Alepha } from "alepha";
2
- import { FileSystemProvider, MemoryFileSystemProvider } from "alepha/file";
2
+ import { FileSystemProvider, MemoryFileSystemProvider } from "alepha/system";
3
3
  import { beforeEach, describe, expect, test, vi } from "vitest";
4
4
  import { SmsError } from "../errors/SmsError.ts";
5
5
  import { LocalSmsProvider } from "./LocalSmsProvider.ts";
@@ -1,6 +1,6 @@
1
1
  import { $inject } from "alepha";
2
- import { FileSystemProvider } from "alepha/file";
3
2
  import { $logger } from "alepha/logger";
3
+ import { FileSystemProvider } from "alepha/system";
4
4
  import { SmsError } from "../errors/SmsError.ts";
5
5
  import type { SmsProvider, SmsSendOptions } from "./SmsProvider.ts";
6
6
 
@@ -0,0 +1,36 @@
1
+ import { $module } from "alepha";
2
+ import { FileSystemProvider } from "./providers/FileSystemProvider.ts";
3
+ import { MemoryFileSystemProvider } from "./providers/MemoryFileSystemProvider.ts";
4
+ import { MemoryShellProvider } from "./providers/MemoryShellProvider.ts";
5
+ import { ShellProvider } from "./providers/ShellProvider.ts";
6
+ import { FileDetector } from "./services/FileDetector.ts";
7
+
8
+ export * from "./errors/FileError.ts";
9
+ export * from "./providers/FileSystemProvider.ts";
10
+ export * from "./providers/MemoryFileSystemProvider.ts";
11
+ export * from "./providers/MemoryShellProvider.ts";
12
+ export * from "./providers/ShellProvider.ts";
13
+ export * from "./services/FileDetector.ts";
14
+
15
+ export const AlephaSystem = $module({
16
+ name: "alepha.system",
17
+ services: [
18
+ FileDetector,
19
+ FileSystemProvider,
20
+ MemoryFileSystemProvider,
21
+ ShellProvider,
22
+ MemoryShellProvider,
23
+ ],
24
+ register: (alepha) =>
25
+ alepha
26
+ .with({
27
+ optional: true,
28
+ provide: FileSystemProvider,
29
+ use: MemoryFileSystemProvider,
30
+ })
31
+ .with({
32
+ optional: true,
33
+ provide: ShellProvider,
34
+ use: MemoryShellProvider,
35
+ }),
36
+ });
@@ -0,0 +1,62 @@
1
+ import { $module } from "alepha";
2
+ import { FileSystemProvider } from "./providers/FileSystemProvider.ts";
3
+ import { MemoryFileSystemProvider } from "./providers/MemoryFileSystemProvider.ts";
4
+ import { MemoryShellProvider } from "./providers/MemoryShellProvider.ts";
5
+ import { NodeFileSystemProvider } from "./providers/NodeFileSystemProvider.ts";
6
+ import { NodeShellProvider } from "./providers/NodeShellProvider.ts";
7
+ import { ShellProvider } from "./providers/ShellProvider.ts";
8
+ import { FileDetector } from "./services/FileDetector.ts";
9
+
10
+ // ---------------------------------------------------------------------------------------------------------------------
11
+
12
+ export * from "./errors/FileError.ts";
13
+ export * from "./providers/FileSystemProvider.ts";
14
+ export * from "./providers/MemoryFileSystemProvider.ts";
15
+ export * from "./providers/MemoryShellProvider.ts";
16
+ export * from "./providers/NodeFileSystemProvider.ts";
17
+ export * from "./providers/NodeShellProvider.ts";
18
+ export * from "./providers/ShellProvider.ts";
19
+ export * from "./services/FileDetector.ts";
20
+
21
+ // ---------------------------------------------------------------------------------------------------------------------
22
+
23
+ /**
24
+ * | type | quality | stability |
25
+ * |------|---------|-----------|
26
+ * | tooling | standard | stable |
27
+ *
28
+ * System-level abstractions for portable code across runtimes.
29
+ *
30
+ * **Features:**
31
+ * - File system operations (read, write, exists, etc.)
32
+ * - Shell command execution
33
+ * - File type detection and MIME utilities
34
+ * - Memory implementations for testing
35
+ *
36
+ * @module alepha.system
37
+ */
38
+ export const AlephaSystem = $module({
39
+ name: "alepha.system",
40
+ primitives: [],
41
+ services: [
42
+ FileDetector,
43
+ FileSystemProvider,
44
+ MemoryFileSystemProvider,
45
+ NodeFileSystemProvider,
46
+ ShellProvider,
47
+ MemoryShellProvider,
48
+ NodeShellProvider,
49
+ ],
50
+ register: (alepha) =>
51
+ alepha
52
+ .with({
53
+ optional: true,
54
+ provide: FileSystemProvider,
55
+ use: NodeFileSystemProvider,
56
+ })
57
+ .with({
58
+ optional: true,
59
+ provide: ShellProvider,
60
+ use: alepha.isTest() ? MemoryShellProvider : NodeShellProvider,
61
+ }),
62
+ });
@@ -0,0 +1 @@
1
+ export * from "./index.browser.ts";
@@ -196,8 +196,16 @@ export interface CpOptions {
196
196
  export interface MkdirOptions {
197
197
  /**
198
198
  * If true, creates parent directories as needed
199
+ *
200
+ * @default true
199
201
  */
200
202
  recursive?: boolean;
203
+ /**
204
+ * If true, does not throw an error if the directory already exists
205
+ *
206
+ * @default true
207
+ */
208
+ force?: boolean;
201
209
  /**
202
210
  * File mode (permission and sticky bits)
203
211
  */
@@ -306,4 +314,20 @@ export abstract class FileSystemProvider {
306
314
  path: string,
307
315
  data: Uint8Array | Buffer | string | FileLike,
308
316
  ): Promise<void>;
317
+
318
+ /**
319
+ * Reads the content of a file as a string.
320
+ *
321
+ * @param path - The file path to read
322
+ * @returns The file content as a string
323
+ */
324
+ abstract readTextFile(path: string): Promise<string>;
325
+
326
+ /**
327
+ * Reads the content of a file as JSON.
328
+ *
329
+ * @param path - The file path to read
330
+ * @returns The parsed JSON content
331
+ */
332
+ abstract readJsonFile<T = unknown>(path: string): Promise<T>;
309
333
  }
@@ -1,4 +1,5 @@
1
- import type { FileLike } from "alepha";
1
+ import { join as nodeJoin } from "node:path";
2
+ import { $inject, type FileLike, Json } from "alepha";
2
3
  import type {
3
4
  CpOptions,
4
5
  CreateFileOptions,
@@ -51,6 +52,8 @@ export interface MemoryFileSystemProviderOptions {
51
52
  * ```
52
53
  */
53
54
  export class MemoryFileSystemProvider implements FileSystemProvider {
55
+ protected json = $inject(Json);
56
+
54
57
  /**
55
58
  * In-memory storage for files (path -> content)
56
59
  */
@@ -71,6 +74,16 @@ export class MemoryFileSystemProvider implements FileSystemProvider {
71
74
  */
72
75
  public writeFileCalls: Array<{ path: string; data: string }> = [];
73
76
 
77
+ /**
78
+ * Track readFile calls for test assertions
79
+ */
80
+ public readFileCalls: Array<string> = [];
81
+
82
+ /**
83
+ * Track rm calls for test assertions
84
+ */
85
+ public rmCalls: Array<{ path: string; options?: RmOptions }> = [];
86
+
74
87
  /**
75
88
  * Track join calls for test assertions
76
89
  */
@@ -99,17 +112,42 @@ export class MemoryFileSystemProvider implements FileSystemProvider {
99
112
 
100
113
  /**
101
114
  * Join path segments using forward slashes.
115
+ * Uses Node's path.join for proper normalization (handles .. and .)
102
116
  */
103
117
  public join(...paths: string[]): string {
104
118
  this.joinCalls.push(paths);
105
- // Simple path join - normalize and join with /
106
- return paths.filter(Boolean).join("/").replace(/\/+/g, "/");
119
+ return nodeJoin(...paths);
107
120
  }
108
121
 
109
122
  /**
110
123
  * Create a FileLike object from various sources.
111
124
  */
112
125
  public createFile(options: CreateFileOptions): FileLike {
126
+ if ("path" in options) {
127
+ const filePath = options.path;
128
+ const buffer = this.files.get(filePath);
129
+ if (buffer === undefined) {
130
+ throw new Error(
131
+ `ENOENT: no such file or directory, open '${filePath}'`,
132
+ );
133
+ }
134
+ return {
135
+ name: options.name ?? filePath.split("/").pop() ?? "file",
136
+ type: options.type ?? "application/octet-stream",
137
+ size: buffer.byteLength,
138
+ lastModified: Date.now(),
139
+ stream: () => {
140
+ throw new Error("Stream not implemented in MemoryFileSystemProvider");
141
+ },
142
+ arrayBuffer: async (): Promise<ArrayBuffer> =>
143
+ buffer.buffer.slice(
144
+ buffer.byteOffset,
145
+ buffer.byteOffset + buffer.byteLength,
146
+ ) as ArrayBuffer,
147
+ text: async () => buffer.toString("utf-8"),
148
+ };
149
+ }
150
+
113
151
  if ("buffer" in options) {
114
152
  const buffer = options.buffer;
115
153
  return {
@@ -157,6 +195,8 @@ export class MemoryFileSystemProvider implements FileSystemProvider {
157
195
  * Remove a file or directory from memory.
158
196
  */
159
197
  public async rm(path: string, options?: RmOptions): Promise<void> {
198
+ this.rmCalls.push({ path, options });
199
+
160
200
  const exists = this.files.has(path) || this.directories.has(path);
161
201
 
162
202
  if (!exists && !options?.force) {
@@ -327,6 +367,8 @@ export class MemoryFileSystemProvider implements FileSystemProvider {
327
367
  * Read a file from memory.
328
368
  */
329
369
  public async readFile(path: string): Promise<Buffer> {
370
+ this.readFileCalls.push(path);
371
+
330
372
  if (this.readFileError) {
331
373
  throw this.readFileError;
332
374
  }
@@ -338,6 +380,22 @@ export class MemoryFileSystemProvider implements FileSystemProvider {
338
380
  return content;
339
381
  }
340
382
 
383
+ /**
384
+ * Read a file from memory as text.
385
+ */
386
+ public async readTextFile(path: string): Promise<string> {
387
+ const buffer = await this.readFile(path);
388
+ return buffer.toString("utf-8");
389
+ }
390
+
391
+ /**
392
+ * Read a file from memory as JSON.
393
+ */
394
+ public async readJsonFile<T = unknown>(path: string): Promise<T> {
395
+ const text = await this.readTextFile(path);
396
+ return this.json.parse(text) as T;
397
+ }
398
+
341
399
  /**
342
400
  * Write a file to memory.
343
401
  */
@@ -378,12 +436,67 @@ export class MemoryFileSystemProvider implements FileSystemProvider {
378
436
  this.directories.clear();
379
437
  this.mkdirCalls = [];
380
438
  this.writeFileCalls = [];
439
+ this.readFileCalls = [];
440
+ this.rmCalls = [];
381
441
  this.joinCalls = [];
382
442
  this.mkdirError = null;
383
443
  this.writeFileError = null;
384
444
  this.readFileError = null;
385
445
  }
386
446
 
447
+ // ─────────────────────────────────────────────────────────────────────────────
448
+ // Test assertion helpers
449
+ // ─────────────────────────────────────────────────────────────────────────────
450
+
451
+ /**
452
+ * Check if a file was written during the test.
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * expect(fs.wasWritten("/project/tsconfig.json")).toBe(true);
457
+ * ```
458
+ */
459
+ public wasWritten(path: string): boolean {
460
+ return this.writeFileCalls.some((call) => call.path === path);
461
+ }
462
+
463
+ /**
464
+ * Check if a file was written with content matching a pattern.
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * expect(fs.wasWrittenMatching("/project/tsconfig.json", /extends/)).toBe(true);
469
+ * ```
470
+ */
471
+ public wasWrittenMatching(path: string, pattern: RegExp): boolean {
472
+ const call = this.writeFileCalls.find((c) => c.path === path);
473
+ return call ? pattern.test(call.data) : false;
474
+ }
475
+
476
+ /**
477
+ * Check if a file was read during the test.
478
+ *
479
+ * @example
480
+ * ```typescript
481
+ * expect(fs.wasRead("/project/package.json")).toBe(true);
482
+ * ```
483
+ */
484
+ public wasRead(path: string): boolean {
485
+ return this.readFileCalls.includes(path);
486
+ }
487
+
488
+ /**
489
+ * Check if a file was deleted during the test.
490
+ *
491
+ * @example
492
+ * ```typescript
493
+ * expect(fs.wasDeleted("/project/old-file.txt")).toBe(true);
494
+ * ```
495
+ */
496
+ public wasDeleted(path: string): boolean {
497
+ return this.rmCalls.some((call) => call.path === path);
498
+ }
499
+
387
500
  /**
388
501
  * Get the content of a file as a string (convenience method for testing).
389
502
  */