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
@@ -0,0 +1,44 @@
1
+ import { $module } from "alepha";
2
+
3
+ // ---------------------------------------------------------------------------------------------------------------------
4
+
5
+ export { default as FormState } from "./components/FormState.tsx";
6
+ export * from "./errors/FormValidationError.ts";
7
+ export * from "./hooks/useForm.ts";
8
+ export * from "./hooks/useFormState.ts";
9
+ export * from "./services/FormModel.ts";
10
+
11
+ // ---------------------------------------------------------------------------------------------------------------------
12
+
13
+ declare module "alepha" {
14
+ interface Hooks {
15
+ "form:change": { id: string; path: string; value: any };
16
+ "form:reset": { id: string; values: Record<string, any> };
17
+ "form:submit:begin": { id: string };
18
+ "form:submit:success": { id: string; values: Record<string, any> };
19
+ "form:submit:error": { id: string; error: Error };
20
+ "form:submit:end": { id: string };
21
+ }
22
+ }
23
+
24
+ // ---------------------------------------------------------------------------------------------------------------------
25
+
26
+ /**
27
+ * | type | quality | stability |
28
+ * |------|---------|-----------|
29
+ * | frontend | rare | stable |
30
+ *
31
+ * Type-safe forms with validation.
32
+ *
33
+ * **Features:**
34
+ * - Form state management
35
+ * - TypeBox schema validation
36
+ * - Field-level error handling
37
+ * - Submit handling with loading state
38
+ * - Form reset
39
+ *
40
+ * @module alepha.react.form
41
+ */
42
+ export const AlephaReactForm = $module({
43
+ name: "alepha.react.form",
44
+ });
@@ -0,0 +1,614 @@
1
+ import type { TArray } from "alepha";
2
+ import {
3
+ $inject,
4
+ Alepha,
5
+ type Static,
6
+ type TObject,
7
+ type TSchema,
8
+ t,
9
+ } from "alepha";
10
+ import { $logger } from "alepha/logger";
11
+ import type { ChangeEvent, InputHTMLAttributes } from "react";
12
+
13
+ /**
14
+ * FormModel is a dynamic form handler that generates form inputs based on a provided TypeBox schema.
15
+ * It manages form state, handles input changes, and processes form submissions with validation.
16
+ *
17
+ * It means to be injected and used within React components to provide a structured way to create and manage forms.
18
+ *
19
+ * @see {@link useForm}
20
+ */
21
+ export class FormModel<T extends TObject> {
22
+ protected readonly log = $logger();
23
+ protected readonly alepha = $inject(Alepha);
24
+ protected readonly values: Record<string, any> = {};
25
+ protected submitInProgress = false;
26
+
27
+ public input: SchemaToInput<T>;
28
+
29
+ public get submitting(): boolean {
30
+ return this.submitInProgress;
31
+ }
32
+
33
+ constructor(
34
+ public readonly id: string,
35
+ public readonly options: FormCtrlOptions<T>,
36
+ ) {
37
+ this.options = options;
38
+
39
+ if (options.initialValues) {
40
+ this.values = this.alepha.codec.decode(
41
+ options.schema,
42
+ options.initialValues,
43
+ ) as Record<string, any>;
44
+ }
45
+
46
+ this.input = this.createProxyFromSchema(options, options.schema, {
47
+ store: this.values,
48
+ parent: "",
49
+ });
50
+ }
51
+
52
+ public get element(): HTMLFormElement {
53
+ return window.document.getElementById(this.id)! as HTMLFormElement;
54
+ }
55
+
56
+ public get currentValues(): Record<string, any> {
57
+ return this.restructureValues(this.values);
58
+ }
59
+
60
+ public get props() {
61
+ return {
62
+ id: this.id,
63
+ noValidate: true,
64
+ onSubmit: (ev?: FormEventLike) => {
65
+ ev?.preventDefault?.();
66
+ this.submit();
67
+ },
68
+ onReset: (event: FormEventLike) => this.reset(event),
69
+ };
70
+ }
71
+
72
+ public readonly reset = (event: FormEventLike) => {
73
+ // clear values in place to maintain proxy reference
74
+ for (const key in this.values) {
75
+ delete this.values[key];
76
+ }
77
+
78
+ this.options.onReset?.();
79
+
80
+ return this.alepha.events.emit(
81
+ "form:reset",
82
+ {
83
+ id: this.id,
84
+ values: this.values,
85
+ },
86
+ {
87
+ catch: true,
88
+ },
89
+ );
90
+ };
91
+
92
+ public readonly submit = async () => {
93
+ if (this.submitInProgress) {
94
+ this.log.warn(
95
+ "Form submission already in progress, ignoring duplicate submit.",
96
+ );
97
+ return;
98
+ }
99
+
100
+ // emit both action and form events
101
+ await this.alepha.events.emit("react:action:begin", {
102
+ type: "form",
103
+ id: this.id,
104
+ });
105
+ await this.alepha.events.emit("form:submit:begin", {
106
+ id: this.id,
107
+ });
108
+
109
+ this.submitInProgress = true;
110
+
111
+ const options = this.options;
112
+ const form = this.element;
113
+ const args = {
114
+ form,
115
+ };
116
+
117
+ try {
118
+ let values: Record<string, any> = this.restructureValues(this.values);
119
+
120
+ if (t.schema.isSchema(options.schema)) {
121
+ values = this.alepha.codec.decode(options.schema, values) as Record<
122
+ string,
123
+ any
124
+ >;
125
+ }
126
+
127
+ await options.handler(values as any, args);
128
+
129
+ await this.alepha.events.emit("react:action:success", {
130
+ type: "form",
131
+ id: this.id,
132
+ });
133
+ await this.alepha.events.emit("form:submit:success", {
134
+ id: this.id,
135
+ values,
136
+ });
137
+ } catch (error) {
138
+ this.log.error("Form submission error:", error);
139
+
140
+ options.onError?.(error as Error, args);
141
+
142
+ await this.alepha.events.emit("react:action:error", {
143
+ type: "form",
144
+ id: this.id,
145
+ error: error as Error,
146
+ });
147
+ await this.alepha.events.emit("form:submit:error", {
148
+ error: error as Error,
149
+ id: this.id,
150
+ });
151
+ } finally {
152
+ this.submitInProgress = false;
153
+ }
154
+
155
+ await this.alepha.events.emit("react:action:end", {
156
+ type: "form",
157
+ id: this.id,
158
+ });
159
+ await this.alepha.events.emit("form:submit:end", {
160
+ id: this.id,
161
+ });
162
+ };
163
+
164
+ /**
165
+ * Restructures flat keys like "address.city" into nested objects like { address: { city: ... } }
166
+ * Values are already typed from onChange, so no conversion is needed.
167
+ */
168
+ protected restructureValues(store: Record<string, any>): Record<string, any> {
169
+ const values: Record<string, any> = {};
170
+
171
+ for (const [key, value] of Object.entries(store)) {
172
+ if (key.includes(".")) {
173
+ // nested object: restructure flat key to nested structure
174
+ this.restructureNestedValue(values, key, value);
175
+ } else {
176
+ // value is already typed, just copy it
177
+ values[key] = value;
178
+ }
179
+ }
180
+
181
+ return values;
182
+ }
183
+
184
+ /**
185
+ * Helper to restructure a flat key like "address.city" into nested object structure.
186
+ * The value is already typed, so we just assign it to the nested path.
187
+ */
188
+ protected restructureNestedValue(
189
+ values: Record<string, any>,
190
+ key: string,
191
+ value: any,
192
+ ) {
193
+ const pathSegments = key.split(".");
194
+ const finalPropertyKey = pathSegments.pop();
195
+ if (!finalPropertyKey) {
196
+ return;
197
+ }
198
+
199
+ let currentObjectLevel = values;
200
+
201
+ // traverse/create the nested structure
202
+ for (const segment of pathSegments) {
203
+ currentObjectLevel[segment] ??= {};
204
+ currentObjectLevel = currentObjectLevel[segment];
205
+ }
206
+
207
+ // value is already typed from onChange, just assign it
208
+ currentObjectLevel[finalPropertyKey] = value;
209
+ }
210
+
211
+ protected createProxyFromSchema<T extends TObject>(
212
+ options: FormCtrlOptions<T>,
213
+ schema: TSchema,
214
+ context: {
215
+ parent: string;
216
+ store: Record<string, any>;
217
+ },
218
+ ): SchemaToInput<T> {
219
+ const parent = context.parent || "";
220
+ return new Proxy<SchemaToInput<T>>({} as SchemaToInput<T>, {
221
+ get: (_, prop: string) => {
222
+ if (!options.schema || !t.schema.isObject(schema)) {
223
+ return {};
224
+ }
225
+
226
+ if (prop in schema.properties) {
227
+ // // it's a nested object, create another proxy
228
+ // if (t.schema.isObject(schema.properties[prop])) {
229
+ // return this.createProxyFromSchema(
230
+ // options,
231
+ // schema.properties[prop],
232
+ // {
233
+ // parent: parent ? `${parent}.${prop}` : prop,
234
+ // store: context.store,
235
+ // },
236
+ // );
237
+ // }
238
+
239
+ return this.createInputFromSchema<T>(
240
+ prop as keyof Static<T> & string,
241
+ options,
242
+ schema,
243
+ schema.required?.includes(prop as string) || false,
244
+ context,
245
+ );
246
+ }
247
+ },
248
+ });
249
+ }
250
+
251
+ protected createInputFromSchema<T extends TObject>(
252
+ name: keyof Static<T> & string,
253
+ options: FormCtrlOptions<T>,
254
+ schema: TObject,
255
+ required: boolean,
256
+ context: {
257
+ parent: string;
258
+ store: Record<string, any>;
259
+ },
260
+ ): BaseInputField {
261
+ const parent = context.parent || "";
262
+ const field = schema.properties?.[name];
263
+ if (!field) {
264
+ return {
265
+ path: "",
266
+ required,
267
+ props: {} as InputHTMLAttributes<unknown>,
268
+ schema: schema,
269
+ set: () => {},
270
+ form: this,
271
+ };
272
+ }
273
+
274
+ const isRequired = schema.required?.includes(name) ?? false;
275
+ const key = parent ? `${parent}.${name}` : name;
276
+ const path = `/${key.replaceAll(".", "/")}`;
277
+
278
+ const set = (value: any, sync = true) => {
279
+ // Convert to typed value immediately based on schema
280
+ const typedValue = this.getValueFromInput(value, field);
281
+
282
+ if (context.store[key] === typedValue) {
283
+ // no change, do not update
284
+ // return; <- disabled for now, as some inputs may need to sync even if value is same
285
+ }
286
+
287
+ context.store[key] = typedValue;
288
+
289
+ if (options.onChange) {
290
+ options.onChange(key, typedValue, context.store);
291
+ }
292
+
293
+ this.alepha.events.emit(
294
+ "form:change",
295
+ {
296
+ id: this.id,
297
+ path: path,
298
+ value: typedValue,
299
+ },
300
+ {
301
+ catch: true,
302
+ },
303
+ );
304
+
305
+ if (sync) {
306
+ const inputElement = window.document.querySelector(
307
+ `[data-path="${path}"]`,
308
+ );
309
+ if (inputElement instanceof HTMLInputElement) {
310
+ if (t.schema.isBoolean(field)) {
311
+ inputElement.value = value;
312
+ inputElement.checked = Boolean(value);
313
+ } else {
314
+ inputElement.value = value;
315
+ }
316
+ }
317
+ }
318
+ };
319
+
320
+ const attr: InputHTMLAttributesLike = {
321
+ name: key,
322
+ autoComplete: "off",
323
+ onChange: (event: ChangeEvent<HTMLInputElement> | string | number) => {
324
+ if (typeof event === "string") {
325
+ // If the event is a string, it means it's a direct value change
326
+ set(event, false);
327
+ return;
328
+ }
329
+
330
+ if (typeof event === "number") {
331
+ // Some inputs might return number directly
332
+ set(event, false);
333
+ return;
334
+ }
335
+
336
+ if (t.schema.isBoolean(field)) {
337
+ if (event.target.value === "true") {
338
+ set(true, false);
339
+ } else if (event.target.value === "false") {
340
+ set(false, false);
341
+ } else if (event.target.value === "") {
342
+ set(undefined, false);
343
+ } else {
344
+ set(event.target.checked, false);
345
+ }
346
+ } else {
347
+ set(event.target.value, false);
348
+ }
349
+ },
350
+ };
351
+
352
+ (attr as any)["data-path"] = path;
353
+
354
+ if (options.id) {
355
+ attr.id = `${options.id}-${key}`;
356
+ (attr as any)["data-testid"] = attr.id;
357
+ }
358
+
359
+ if (t.schema.isString(field)) {
360
+ if (field.maxLength != null) {
361
+ attr.maxLength = Number(field.maxLength);
362
+ }
363
+
364
+ if (field.minLength != null) {
365
+ attr.minLength = Number(field.minLength);
366
+ }
367
+ }
368
+
369
+ if (options.initialValues?.[name] != null) {
370
+ attr.defaultValue = this.valueToInputEntry(options.initialValues[name]);
371
+ } else if ("default" in field && field.default != null) {
372
+ attr.defaultValue = this.valueToInputEntry(field.default);
373
+ }
374
+
375
+ if (isRequired) {
376
+ attr.required = true;
377
+ }
378
+
379
+ if ("description" in field && typeof field.description === "string") {
380
+ attr["aria-label"] = field.description;
381
+ }
382
+
383
+ if (t.schema.isInteger(field) || t.schema.isNumber(field)) {
384
+ attr.type = "number";
385
+ } else if (name === "password") {
386
+ attr.type = "password";
387
+ } else if (name === "email") {
388
+ attr.type = "email";
389
+ } else if (name === "url") {
390
+ attr.type = "url";
391
+ } else if (t.schema.isString(field)) {
392
+ if (field.format === "binary") {
393
+ attr.type = "file";
394
+ } else if (field.format === "date") {
395
+ attr.type = "date";
396
+ } else if (field.format === "time") {
397
+ attr.type = "time";
398
+ } else if (field.format === "date-time") {
399
+ attr.type = "datetime-local";
400
+ } else {
401
+ attr.type = "text";
402
+ }
403
+ } else if (t.schema.isBoolean(field)) {
404
+ attr.type = "checkbox";
405
+ }
406
+
407
+ if (options.onCreateField) {
408
+ const customAttr = options.onCreateField(name, field);
409
+ Object.assign(attr, customAttr);
410
+ }
411
+
412
+ // if type = object, add items: { [key: string]: InputField }
413
+ if (t.schema.isObject(field)) {
414
+ return {
415
+ path,
416
+ props: attr,
417
+ schema: field,
418
+ set,
419
+ form: this,
420
+ required,
421
+ items: this.createProxyFromSchema(options, field, {
422
+ parent: key,
423
+ store: context.store,
424
+ }),
425
+ } as ObjectInputField<any>;
426
+ }
427
+
428
+ // if type = array, add items: InputField[]
429
+ if (t.schema.isArray(field)) {
430
+ return {
431
+ path,
432
+ props: attr,
433
+ schema: field,
434
+ set,
435
+ form: this,
436
+ required,
437
+ items: [], // <- will be populated dynamically in the UI
438
+ } as ArrayInputField<any>;
439
+ }
440
+
441
+ return {
442
+ path,
443
+ props: attr,
444
+ schema: field,
445
+ set,
446
+ form: this,
447
+ required,
448
+ };
449
+ }
450
+
451
+ /**
452
+ * Convert an input value to the correct type based on the schema.
453
+ * Handles raw DOM values (strings, booleans from checkboxes, Files, etc.)
454
+ */
455
+ protected getValueFromInput(input: any, schema: TSchema): any {
456
+ if (input instanceof File) {
457
+ // for file inputs, return the File object directly
458
+ if (t.schema.isString(schema) && schema.format === "binary") {
459
+ return input;
460
+ }
461
+ // for now, ignore other formats
462
+ return null;
463
+ }
464
+
465
+ if (t.schema.isBoolean(schema)) {
466
+ // Handle string representations from Select components (Yes/No dropdown)
467
+ if (input === "true") return true;
468
+ if (input === "false") return false;
469
+ if (input === "" || input === null || input === undefined)
470
+ return undefined;
471
+ // Handle actual boolean values
472
+ return !!input;
473
+ }
474
+
475
+ if (t.schema.isNumber(schema)) {
476
+ const num = Number(input);
477
+ return Number.isNaN(num) ? null : num;
478
+ }
479
+
480
+ if (t.schema.isString(schema)) {
481
+ if (schema.format === "date") {
482
+ return new Date(input).toISOString().slice(0, 10); // For date input
483
+ }
484
+ if (schema.format === "time") {
485
+ return new Date(`1970-01-01T${input}`).toISOString().slice(11, 16); // For time input
486
+ }
487
+ if (schema.format === "date-time") {
488
+ return new Date(input).toISOString(); // For datetime-local input
489
+ }
490
+ return String(input);
491
+ }
492
+
493
+ return input; // fallback for other types
494
+ }
495
+
496
+ protected valueToInputEntry(value: any): string | number | boolean {
497
+ if (value === null || value === undefined) {
498
+ return "";
499
+ }
500
+
501
+ if (typeof value === "boolean") {
502
+ return value;
503
+ }
504
+
505
+ if (typeof value === "number") {
506
+ return value;
507
+ }
508
+
509
+ if (typeof value === "string") {
510
+ return value;
511
+ }
512
+
513
+ if (value instanceof Date) {
514
+ return value.toISOString().slice(0, 16); // For datetime-local input
515
+ }
516
+
517
+ return value;
518
+ }
519
+ }
520
+
521
+ export type SchemaToInput<T extends TObject> = {
522
+ [K in keyof T["properties"]]: InputField<T["properties"][K]>;
523
+ };
524
+
525
+ export interface FormEventLike {
526
+ preventDefault?: () => void;
527
+ stopPropagation?: () => void;
528
+ }
529
+
530
+ export type InputField<T extends TSchema> = T extends TObject
531
+ ? ObjectInputField<T>
532
+ : T extends TArray<infer U>
533
+ ? ArrayInputField<U>
534
+ : BaseInputField;
535
+
536
+ export interface BaseInputField {
537
+ path: string;
538
+ required: boolean;
539
+ props: InputHTMLAttributesLike;
540
+ schema: TSchema;
541
+ set: (value: any) => void;
542
+ form: FormModel<any>;
543
+ items?: any;
544
+ }
545
+
546
+ export interface ObjectInputField<T extends TObject> extends BaseInputField {
547
+ items: SchemaToInput<T>;
548
+ }
549
+
550
+ export interface ArrayInputField<T extends TSchema> extends BaseInputField {
551
+ items: Array<InputField<T>>;
552
+ }
553
+
554
+ export type InputHTMLAttributesLike = Pick<
555
+ InputHTMLAttributes<unknown>,
556
+ | "id"
557
+ | "name"
558
+ | "type"
559
+ | "value"
560
+ | "defaultValue"
561
+ | "required"
562
+ | "maxLength"
563
+ | "minLength"
564
+ | "aria-label"
565
+ | "autoComplete"
566
+ > & {
567
+ value?: any;
568
+ defaultValue?: any;
569
+ onChange?: (event: any) => void;
570
+ };
571
+
572
+ export type FormCtrlOptions<T extends TObject> = {
573
+ /**
574
+ * The schema defining the structure and validation rules for the form.
575
+ * This should be a TypeBox schema object.
576
+ */
577
+ schema: T;
578
+
579
+ /**
580
+ * Callback function to handle form submission.
581
+ * This function will receive the parsed and validated form values.
582
+ */
583
+ handler: (values: Static<T>, args: { form: HTMLFormElement }) => unknown;
584
+
585
+ /**
586
+ * Optional initial values for the form fields.
587
+ * This can be used to pre-populate the form with existing data.
588
+ */
589
+ initialValues?: Partial<Static<T>>;
590
+
591
+ /**
592
+ * Optional function to create custom field attributes.
593
+ * This can be used to add custom validation, styles, or other attributes.
594
+ */
595
+ onCreateField?: (
596
+ name: keyof Static<T> & string,
597
+ schema: TSchema,
598
+ ) => InputHTMLAttributes<unknown>;
599
+
600
+ /**
601
+ * If defined, this will generate a unique ID for each field, prefixed with this string.
602
+ *
603
+ * > "username" with id="form-123" will become "form-123-username".
604
+ *
605
+ * If omitted, IDs will not be generated.
606
+ */
607
+ id?: string;
608
+
609
+ onError?: (error: Error, args: { form: HTMLFormElement }) => void;
610
+
611
+ onChange?: (key: string, value: any, store: Record<string, any>) => void;
612
+
613
+ onReset?: () => void;
614
+ };