alepha 0.14.4 → 0.15.0

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 (277) hide show
  1. package/README.md +1 -4
  2. package/dist/api/audits/index.d.ts +619 -731
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +185 -298
  5. package/dist/api/files/index.d.ts.map +1 -1
  6. package/dist/api/files/index.js +0 -1
  7. package/dist/api/files/index.js.map +1 -1
  8. package/dist/api/jobs/index.d.ts +245 -356
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/notifications/index.d.ts +238 -350
  11. package/dist/api/notifications/index.d.ts.map +1 -1
  12. package/dist/api/parameters/index.d.ts +499 -611
  13. package/dist/api/parameters/index.d.ts.map +1 -1
  14. package/dist/api/users/index.browser.js +1 -2
  15. package/dist/api/users/index.browser.js.map +1 -1
  16. package/dist/api/users/index.d.ts +1697 -1804
  17. package/dist/api/users/index.d.ts.map +1 -1
  18. package/dist/api/users/index.js +178 -151
  19. package/dist/api/users/index.js.map +1 -1
  20. package/dist/api/verifications/index.d.ts +132 -132
  21. package/dist/api/verifications/index.d.ts.map +1 -1
  22. package/dist/batch/index.d.ts +122 -122
  23. package/dist/batch/index.d.ts.map +1 -1
  24. package/dist/batch/index.js +1 -2
  25. package/dist/batch/index.js.map +1 -1
  26. package/dist/bucket/index.d.ts +163 -163
  27. package/dist/bucket/index.d.ts.map +1 -1
  28. package/dist/cache/core/index.d.ts +46 -46
  29. package/dist/cache/core/index.d.ts.map +1 -1
  30. package/dist/cache/redis/index.d.ts.map +1 -1
  31. package/dist/cli/index.d.ts +302 -299
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +966 -564
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/command/index.d.ts +303 -299
  36. package/dist/command/index.d.ts.map +1 -1
  37. package/dist/command/index.js +11 -7
  38. package/dist/command/index.js.map +1 -1
  39. package/dist/core/index.browser.js +419 -99
  40. package/dist/core/index.browser.js.map +1 -1
  41. package/dist/core/index.d.ts +718 -625
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +420 -99
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/index.native.js +419 -99
  46. package/dist/core/index.native.js.map +1 -1
  47. package/dist/datetime/index.d.ts +44 -44
  48. package/dist/datetime/index.d.ts.map +1 -1
  49. package/dist/datetime/index.js +4 -4
  50. package/dist/datetime/index.js.map +1 -1
  51. package/dist/email/index.d.ts +97 -50
  52. package/dist/email/index.d.ts.map +1 -1
  53. package/dist/email/index.js +129 -33
  54. package/dist/email/index.js.map +1 -1
  55. package/dist/fake/index.d.ts +7981 -14
  56. package/dist/fake/index.d.ts.map +1 -1
  57. package/dist/file/index.d.ts +523 -390
  58. package/dist/file/index.d.ts.map +1 -1
  59. package/dist/file/index.js +253 -1
  60. package/dist/file/index.js.map +1 -1
  61. package/dist/lock/core/index.d.ts +208 -208
  62. package/dist/lock/core/index.d.ts.map +1 -1
  63. package/dist/lock/redis/index.d.ts.map +1 -1
  64. package/dist/logger/index.d.ts +25 -26
  65. package/dist/logger/index.d.ts.map +1 -1
  66. package/dist/mcp/index.d.ts +197 -197
  67. package/dist/mcp/index.d.ts.map +1 -1
  68. package/dist/orm/chunk-DtkW-qnP.js +38 -0
  69. package/dist/orm/index.browser.js.map +1 -1
  70. package/dist/orm/index.bun.js +2814 -0
  71. package/dist/orm/index.bun.js.map +1 -0
  72. package/dist/orm/index.d.ts +1205 -1057
  73. package/dist/orm/index.d.ts.map +1 -1
  74. package/dist/orm/index.js +2056 -1753
  75. package/dist/orm/index.js.map +1 -1
  76. package/dist/queue/core/index.d.ts +248 -248
  77. package/dist/queue/core/index.d.ts.map +1 -1
  78. package/dist/queue/redis/index.d.ts.map +1 -1
  79. package/dist/redis/index.bun.js +285 -0
  80. package/dist/redis/index.bun.js.map +1 -0
  81. package/dist/redis/index.d.ts +118 -136
  82. package/dist/redis/index.d.ts.map +1 -1
  83. package/dist/redis/index.js +18 -38
  84. package/dist/redis/index.js.map +1 -1
  85. package/dist/retry/index.d.ts +69 -69
  86. package/dist/retry/index.d.ts.map +1 -1
  87. package/dist/router/index.d.ts +6 -6
  88. package/dist/router/index.d.ts.map +1 -1
  89. package/dist/scheduler/index.d.ts +25 -25
  90. package/dist/scheduler/index.d.ts.map +1 -1
  91. package/dist/security/index.browser.js +5 -1
  92. package/dist/security/index.browser.js.map +1 -1
  93. package/dist/security/index.d.ts +417 -254
  94. package/dist/security/index.d.ts.map +1 -1
  95. package/dist/security/index.js +386 -86
  96. package/dist/security/index.js.map +1 -1
  97. package/dist/server/auth/index.d.ts +277 -277
  98. package/dist/server/auth/index.d.ts.map +1 -1
  99. package/dist/server/auth/index.js +20 -20
  100. package/dist/server/auth/index.js.map +1 -1
  101. package/dist/server/cache/index.d.ts +60 -57
  102. package/dist/server/cache/index.d.ts.map +1 -1
  103. package/dist/server/cache/index.js +1 -1
  104. package/dist/server/cache/index.js.map +1 -1
  105. package/dist/server/compress/index.d.ts +3 -3
  106. package/dist/server/compress/index.d.ts.map +1 -1
  107. package/dist/server/cookies/index.d.ts +6 -6
  108. package/dist/server/cookies/index.d.ts.map +1 -1
  109. package/dist/server/cookies/index.js +3 -3
  110. package/dist/server/cookies/index.js.map +1 -1
  111. package/dist/server/core/index.d.ts +242 -150
  112. package/dist/server/core/index.d.ts.map +1 -1
  113. package/dist/server/core/index.js +288 -122
  114. package/dist/server/core/index.js.map +1 -1
  115. package/dist/server/cors/index.d.ts +11 -12
  116. package/dist/server/cors/index.d.ts.map +1 -1
  117. package/dist/server/health/index.d.ts +0 -1
  118. package/dist/server/health/index.d.ts.map +1 -1
  119. package/dist/server/helmet/index.d.ts +2 -2
  120. package/dist/server/helmet/index.d.ts.map +1 -1
  121. package/dist/server/links/index.browser.js.map +1 -1
  122. package/dist/server/links/index.d.ts +84 -85
  123. package/dist/server/links/index.d.ts.map +1 -1
  124. package/dist/server/links/index.js +1 -2
  125. package/dist/server/links/index.js.map +1 -1
  126. package/dist/server/metrics/index.d.ts.map +1 -1
  127. package/dist/server/multipart/index.d.ts +6 -6
  128. package/dist/server/multipart/index.d.ts.map +1 -1
  129. package/dist/server/proxy/index.d.ts +102 -103
  130. package/dist/server/proxy/index.d.ts.map +1 -1
  131. package/dist/server/rate-limit/index.d.ts +16 -16
  132. package/dist/server/rate-limit/index.d.ts.map +1 -1
  133. package/dist/server/static/index.d.ts +44 -44
  134. package/dist/server/static/index.d.ts.map +1 -1
  135. package/dist/server/swagger/index.d.ts +48 -49
  136. package/dist/server/swagger/index.d.ts.map +1 -1
  137. package/dist/server/swagger/index.js +1 -2
  138. package/dist/server/swagger/index.js.map +1 -1
  139. package/dist/sms/index.d.ts +13 -11
  140. package/dist/sms/index.d.ts.map +1 -1
  141. package/dist/sms/index.js +7 -7
  142. package/dist/sms/index.js.map +1 -1
  143. package/dist/thread/index.d.ts +71 -72
  144. package/dist/thread/index.d.ts.map +1 -1
  145. package/dist/topic/core/index.d.ts +318 -318
  146. package/dist/topic/core/index.d.ts.map +1 -1
  147. package/dist/topic/redis/index.d.ts +6 -6
  148. package/dist/topic/redis/index.d.ts.map +1 -1
  149. package/dist/vite/index.d.ts +5720 -159
  150. package/dist/vite/index.d.ts.map +1 -1
  151. package/dist/vite/index.js +41 -18
  152. package/dist/vite/index.js.map +1 -1
  153. package/dist/websocket/index.browser.js +6 -6
  154. package/dist/websocket/index.browser.js.map +1 -1
  155. package/dist/websocket/index.d.ts +247 -247
  156. package/dist/websocket/index.d.ts.map +1 -1
  157. package/dist/websocket/index.js +6 -6
  158. package/dist/websocket/index.js.map +1 -1
  159. package/package.json +9 -14
  160. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  161. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  162. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  163. package/src/api/users/entities/users.ts +1 -1
  164. package/src/api/users/index.ts +8 -8
  165. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  166. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  167. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  168. package/src/api/users/services/CredentialService.ts +7 -7
  169. package/src/api/users/services/IdentityService.ts +4 -4
  170. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  171. package/src/api/users/services/RegistrationService.ts +38 -27
  172. package/src/api/users/services/SessionCrudService.ts +3 -3
  173. package/src/api/users/services/SessionService.spec.ts +3 -3
  174. package/src/api/users/services/SessionService.ts +28 -9
  175. package/src/api/users/services/UserService.ts +7 -7
  176. package/src/batch/providers/BatchProvider.ts +1 -2
  177. package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
  178. package/src/cli/assets/apiHelloControllerTs.ts +18 -0
  179. package/src/cli/assets/apiIndexTs.ts +16 -0
  180. package/src/cli/assets/claudeMd.ts +303 -0
  181. package/src/cli/assets/mainBrowserTs.ts +2 -2
  182. package/src/cli/assets/mainServerTs.ts +24 -0
  183. package/src/cli/assets/webAppRouterTs.ts +15 -0
  184. package/src/cli/assets/webHelloComponentTsx.ts +16 -0
  185. package/src/cli/assets/webIndexTs.ts +16 -0
  186. package/src/cli/commands/build.ts +41 -21
  187. package/src/cli/commands/db.ts +21 -18
  188. package/src/cli/commands/deploy.ts +17 -5
  189. package/src/cli/commands/dev.ts +13 -17
  190. package/src/cli/commands/format.ts +8 -2
  191. package/src/cli/commands/init.ts +74 -29
  192. package/src/cli/commands/lint.ts +8 -2
  193. package/src/cli/commands/test.ts +8 -2
  194. package/src/cli/commands/typecheck.ts +5 -1
  195. package/src/cli/commands/verify.ts +4 -2
  196. package/src/cli/services/AlephaCliUtils.ts +39 -600
  197. package/src/cli/services/PackageManagerUtils.ts +301 -0
  198. package/src/cli/services/ProjectScaffolder.ts +306 -0
  199. package/src/command/helpers/Runner.ts +15 -3
  200. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  201. package/src/core/index.shared.ts +1 -0
  202. package/src/core/index.ts +2 -0
  203. package/src/core/primitives/$hook.ts +6 -2
  204. package/src/core/primitives/$module.spec.ts +4 -0
  205. package/src/core/providers/AlsProvider.ts +1 -1
  206. package/src/core/providers/CodecManager.spec.ts +12 -6
  207. package/src/core/providers/CodecManager.ts +26 -6
  208. package/src/core/providers/EventManager.ts +169 -13
  209. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
  210. package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
  211. package/src/core/providers/StateManager.spec.ts +27 -16
  212. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  213. package/src/email/providers/LocalEmailProvider.ts +52 -15
  214. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  215. package/src/file/errors/FileError.ts +7 -0
  216. package/src/file/index.ts +9 -1
  217. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  218. package/src/orm/index.browser.ts +1 -19
  219. package/src/orm/index.bun.ts +77 -0
  220. package/src/orm/index.shared-server.ts +22 -0
  221. package/src/orm/index.shared.ts +15 -0
  222. package/src/orm/index.ts +19 -39
  223. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  224. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  225. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  226. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  227. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  228. package/src/orm/services/Repository.ts +8 -0
  229. package/src/redis/index.bun.ts +35 -0
  230. package/src/redis/providers/BunRedisProvider.ts +12 -43
  231. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  232. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  233. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  234. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  235. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  236. package/src/security/index.browser.ts +5 -0
  237. package/src/security/index.ts +90 -7
  238. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  239. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  240. package/src/security/primitives/$role.ts +5 -5
  241. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  242. package/src/security/primitives/$serviceAccount.ts +3 -3
  243. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  244. package/src/server/auth/primitives/$auth.ts +10 -10
  245. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  246. package/src/server/auth/primitives/$authGithub.ts +3 -3
  247. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  248. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  249. package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
  250. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  251. package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
  252. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  253. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  254. package/src/server/core/providers/ServerProvider.ts +144 -21
  255. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  256. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  257. package/src/server/links/index.ts +1 -1
  258. package/src/server/links/providers/LinkProvider.ts +1 -1
  259. package/src/server/swagger/index.ts +1 -1
  260. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  261. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  262. package/src/vite/helpers/boot.ts +28 -17
  263. package/src/vite/tasks/buildServer.ts +12 -1
  264. package/src/vite/tasks/devServer.ts +3 -1
  265. package/src/vite/tasks/generateCloudflare.ts +7 -0
  266. package/dist/server/security/index.browser.js +0 -13
  267. package/dist/server/security/index.browser.js.map +0 -1
  268. package/dist/server/security/index.d.ts +0 -173
  269. package/dist/server/security/index.d.ts.map +0 -1
  270. package/dist/server/security/index.js +0 -311
  271. package/dist/server/security/index.js.map +0 -1
  272. package/src/cli/assets/appRouterTs.ts +0 -9
  273. package/src/cli/assets/mainTs.ts +0 -13
  274. package/src/server/security/index.browser.ts +0 -10
  275. package/src/server/security/index.ts +0 -94
  276. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  277. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
@@ -0,0 +1,303 @@
1
+ export interface ClaudeMdOptions {
2
+ react?: boolean;
3
+ ui?: boolean;
4
+ projectName?: string;
5
+ }
6
+
7
+ export const claudeMd = (options: ClaudeMdOptions = {}) => {
8
+ const { react = false, projectName = "my-app" } = options;
9
+
10
+ const reactSection = react
11
+ ? `
12
+ ## React & Frontend
13
+
14
+ ### Pages with \`$page\`
15
+ \`\`\`tsx
16
+ import { $page } from "@alepha/react/router";
17
+ import { $client } from "alepha/server/links";
18
+ import type { UserController } from "./UserController.ts";
19
+
20
+ class AppRouter {
21
+ api = $client<UserController>();
22
+
23
+ users = $page({
24
+ path: "/users",
25
+ loader: async () => ({ users: await this.api.listUsers() }),
26
+ component: ({ users }) => (
27
+ <ul>{users.map(u => <li key={u.id}>{u.email}</li>)}</ul>
28
+ ),
29
+ });
30
+
31
+ userDetail = $page({
32
+ path: "/users/:id",
33
+ schema: { params: t.object({ id: t.uuid() }) },
34
+ loader: async ({ params }) => ({ user: await this.api.getUser({ params }) }),
35
+ lazy: () => import("./UserDetail.tsx"), // Code splitting
36
+ });
37
+ }
38
+ \`\`\`
39
+
40
+ ### React Hooks
41
+ \`\`\`typescript
42
+ import { useAlepha, useClient, useStore, useAction, useInject } from "@alepha/react";
43
+ import { useRouter, useActive } from "@alepha/react/router";
44
+ import { useForm } from "@alepha/react/form";
45
+ \`\`\`
46
+
47
+ - \`useClient<Controller>()\` - Type-safe API calls
48
+ - \`useStore(atom)\` - Global state (returns \`[value, setValue]\`)
49
+ - \`useAction({ handler })\` - Async operations with loading/error state
50
+ - \`useRouter<AppRouter>()\` - Type-safe navigation
51
+ - \`useForm({ schema, handler })\` - Type-safe forms with validation
52
+ `
53
+ : "";
54
+
55
+ const projectStructure = react
56
+ ? `
57
+ \`\`\`
58
+ ${projectName}/
59
+ ├── src/
60
+ │ ├── api/ # Backend
61
+ │ │ ├── controllers/ # API controllers with $action
62
+ │ │ ├── services/ # Business logic
63
+ │ │ ├── entities/ # Database entities with $entity
64
+ │ │ ├── providers/ # External service wrappers
65
+ │ │ └── index.ts # API module definition with $module
66
+ │ ├── web/ # Frontend (React only)
67
+ │ │ ├── components/ # React components
68
+ │ │ ├── atoms/ # State atoms with $atom
69
+ │ │ ├── AppRouter.ts # Routes with $page
70
+ │ │ └── index.ts # Web module definition with $module
71
+ │ ├── main.server.ts # Server entry
72
+ │ └── main.browser.ts # Browser entry (React only)
73
+ ├── index.html # (React only)
74
+ ├── package.json
75
+ └── tsconfig.json
76
+ \`\`\`
77
+ `
78
+ : `
79
+ \`\`\`
80
+ ${projectName}/
81
+ ├── src/
82
+ │ ├── api/ # Backend
83
+ │ │ ├── controllers/ # API controllers with $action
84
+ │ │ ├── services/ # Business logic
85
+ │ │ ├── entities/ # Database entities with $entity
86
+ │ │ ├── providers/ # External service wrappers
87
+ │ │ └── index.ts # API module definition with $module
88
+ │ └── main.server.ts # Server entry (always use main.server.ts)
89
+ ├── package.json
90
+ └── tsconfig.json
91
+ \`\`\`
92
+ `;
93
+
94
+ return `# CLAUDE.md
95
+
96
+ This file provides guidance to Claude Code when working with this Alepha project.
97
+
98
+ ## Overview
99
+
100
+ This is an **Alepha** project - a convention-driven TypeScript framework for type-safe full-stack applications.
101
+
102
+ **Key Concepts:**
103
+ - **Primitives**: Features defined with \`$\`-prefixed functions (\`$action\`, \`$entity\`, \`$page\`)
104
+ - **Class-Based**: Services are classes, primitives are class properties
105
+ - **Zero-Config**: Code structure IS the configuration
106
+ - **End-to-End Types**: Types flow from database → API → ${react ? "React" : "client"}
107
+
108
+ ## Rules
109
+
110
+ - Use TypeScript strict mode
111
+ - Use Biome for formatting (\`alepha lint\`)
112
+ - Use Vitest for testing
113
+ - One file = one class
114
+ - Primitives are class properties (except \`$entity\`, \`$atom\`)
115
+ - No decorators, no Express/Fastify patterns
116
+ - No manual instantiation - use dependency injection
117
+ - Use \`protected\` instead of \`private\` for class members
118
+ - Import with file extensions: \`import { User } from "./User.ts"\`
119
+ - Use \`t\` from Alepha for schemas (not Zod)
120
+
121
+ ## Project Structure
122
+ ${projectStructure}
123
+ ## Core Primitives
124
+
125
+ ### API with \`$action\`
126
+ \`\`\`typescript
127
+ import { t } from "alepha";
128
+ import { $action } from "alepha/server";
129
+
130
+ class UserController {
131
+ getUser = $action({
132
+ path: "/users/:id", // → GET /api/users/:id
133
+ schema: {
134
+ params: t.object({ id: t.uuid() }),
135
+ response: t.object({ id: t.uuid(), email: t.email() }),
136
+ },
137
+ handler: async ({ params }) => this.userRepo.findById(params.id),
138
+ });
139
+
140
+ createUser = $action({
141
+ // POST inferred from body schema
142
+ schema: {
143
+ body: t.object({ email: t.email() }),
144
+ response: userEntity.schema,
145
+ },
146
+ handler: async ({ body }) => this.userRepo.create(body),
147
+ });
148
+ }
149
+ \`\`\`
150
+
151
+ ### Database with \`$entity\` and \`$repository\`
152
+ \`\`\`typescript
153
+ import { $entity, $repository, db } from "alepha/orm";
154
+
155
+ // Entity defined at module level (for drizzle-kit compatibility)
156
+ export const userEntity = $entity({
157
+ name: "users",
158
+ schema: t.object({
159
+ id: db.primaryKey(),
160
+ email: t.email(),
161
+ createdAt: db.createdAt(),
162
+ updatedAt: db.updatedAt(),
163
+ }),
164
+ indexes: [{ column: "email", unique: true }],
165
+ });
166
+
167
+ class UserService {
168
+ repo = $repository(userEntity);
169
+
170
+ async findById(id: string) {
171
+ return this.repo.findById(id);
172
+ }
173
+ }
174
+ \`\`\`
175
+
176
+ ### Dependency Injection
177
+ \`\`\`typescript
178
+ import { $inject } from "alepha";
179
+
180
+ class OrderService {
181
+ userService = $inject(UserService); // Within same module
182
+
183
+ async createOrder(userId: string) {
184
+ const user = await this.userService.findById(userId);
185
+ // ...
186
+ }
187
+ }
188
+
189
+ // Cross-module: use $client instead of $inject
190
+ class AppRouter {
191
+ api = $client<OrderController>(); // Type-safe HTTP client
192
+ }
193
+ \`\`\`
194
+
195
+ ### Modules with \`$module\`
196
+ \`\`\`typescript
197
+ // src/api/index.ts - Groups all API services
198
+ import { $module } from "alepha";
199
+
200
+ export const ApiModule = $module({
201
+ name: "app.api",
202
+ services: [
203
+ UserController,
204
+ OrderController,
205
+ UserService,
206
+ ],
207
+ });
208
+
209
+ // src/web/index.ts - Groups all web services (React only)
210
+ export const WebModule = $module({
211
+ name: "app.web",
212
+ services: [AppRouter, Toaster],
213
+ register(alepha) {
214
+ // Optional: configure additional services
215
+ alepha.with(SomeLibrary);
216
+ },
217
+ });
218
+ \`\`\`
219
+
220
+ ### Environment Variables
221
+ \`\`\`typescript
222
+ import { $env, t } from "alepha";
223
+
224
+ class AppConfig {
225
+ env = $env(t.object({
226
+ DATABASE_URL: t.string(),
227
+ API_KEY: t.optional(t.string()),
228
+ }));
229
+ }
230
+ \`\`\`
231
+ ${reactSection}
232
+ ## Quick Reference
233
+
234
+ | Primitive | Import | Purpose |
235
+ |-----------|--------|---------|
236
+ | \`$inject\` | \`alepha\` | Dependency injection |
237
+ | \`$env\` | \`alepha\` | Environment variables |
238
+ | \`$hook\` | \`alepha\` | Lifecycle hooks |
239
+ | \`$logger\` | \`alepha/logger\` | Structured logging |
240
+ | \`$action\` | \`alepha/server\` | REST API endpoints |
241
+ | \`$route\` | \`alepha/server\` | Low-level HTTP routes |
242
+ | \`$entity\` | \`alepha/orm\` | Database tables |
243
+ | \`$repository\` | \`alepha/orm\` | Type-safe data access |
244
+ | \`$queue\` | \`alepha/queue\` | Background jobs |
245
+ | \`$scheduler\` | \`alepha/scheduler\` | Cron tasks |
246
+ | \`$cache\` | \`alepha/cache\` | Cached computations |
247
+ | \`$bucket\` | \`alepha/bucket\` | File storage |
248
+ | \`$issuer\` | \`alepha/security\` | JWT tokens |
249
+ | \`$command\` | \`alepha/command\` | CLI commands |${react ? `
250
+ | \`$page\` | \`@alepha/react/router\` | React pages with SSR |
251
+ | \`$atom\` | \`alepha\` | Global state |` : ""}
252
+
253
+ ## Testing
254
+
255
+ \`\`\`typescript
256
+ import { describe, it, expect } from "vitest";
257
+ import { Alepha } from "alepha";
258
+
259
+ describe("UserService", () => {
260
+ it("should create user", async () => {
261
+ const alepha = Alepha.create().with(UserService);
262
+ const service = alepha.inject(UserService);
263
+
264
+ const user = await service.create({ email: "test@example.com" });
265
+ expect(user.email).toBe("test@example.com");
266
+ });
267
+
268
+ it("should mock dependencies", async () => {
269
+ const alepha = Alepha.create()
270
+ .with(OrderService)
271
+ .with({ provide: PaymentGateway, use: MockPaymentGateway });
272
+
273
+ const service = alepha.inject(OrderService);
274
+ // PaymentGateway is now mocked
275
+ });
276
+ });
277
+ \`\`\`
278
+
279
+ ## Common Mistakes
280
+
281
+ 1. **DON'T use decorators** - Use primitives (\`$action\`, not \`@Get()\`)
282
+ 2. **DON'T use Zod** - Use TypeBox via \`t\` from Alepha
283
+ 3. **DON'T use Express patterns** - No \`app.get()\`, \`router.use()\`
284
+ 4. **DON'T inject across modules** - Use \`$client\` for cross-module calls
285
+ 5. **DON'T use async constructors** - Use \`$hook({ on: "start" })\`
286
+ 6. **DON'T instantiate manually** - Let DI container manage instances
287
+
288
+ ## After Code Changes
289
+
290
+ Always run:
291
+ \`\`\`bash
292
+ alepha lint # Format and lint
293
+ alepha typecheck # Type checking
294
+ alepha test # Run tests (if applicable)
295
+ alepha build # Build the project
296
+ \`\`\`
297
+
298
+ ## Documentation
299
+
300
+ - Full docs: https://alepha.dev/llms.txt
301
+ - Detailed docs: https://alepha.dev/llms-full.txt
302
+ `.trim();
303
+ };
@@ -1,10 +1,10 @@
1
1
  export const mainBrowserTs = () => `
2
2
  import { Alepha, run } from "alepha";
3
- import { AppRouter } from "./AppRouter.ts";
3
+ import { WebModule } from "./web/index.ts";
4
4
 
5
5
  const alepha = Alepha.create();
6
6
 
7
- alepha.with(AppRouter);
7
+ alepha.with(WebModule);
8
8
 
9
9
  run(alepha);
10
10
  `.trim();
@@ -0,0 +1,24 @@
1
+ export interface MainServerTsOptions {
2
+ react?: boolean;
3
+ }
4
+
5
+ export const mainServerTs = (options: MainServerTsOptions = {}) => {
6
+ const { react = false } = options;
7
+
8
+ const webImport = react
9
+ ? `import { WebModule } from "./web/index.ts";\n`
10
+ : "";
11
+
12
+ const webWith = react ? `alepha.with(WebModule);\n` : "";
13
+
14
+ return `
15
+ import { Alepha, run } from "alepha";
16
+ import { ApiModule } from "./api/index.ts";
17
+ ${webImport}
18
+ const alepha = Alepha.create();
19
+
20
+ alepha.with(ApiModule);
21
+ ${webWith}
22
+ run(alepha);
23
+ `.trim();
24
+ };
@@ -0,0 +1,15 @@
1
+ export const webAppRouterTs = () => `
2
+ import { $page } from "@alepha/react/router";
3
+ import { $client } from "alepha/server/links";
4
+ import type { HelloController } from "../api/controllers/HelloController.ts";
5
+
6
+ export class AppRouter {
7
+ api = $client<HelloController>();
8
+
9
+ home = $page({
10
+ path: "/",
11
+ lazy: () => import("./components/Hello.tsx"),
12
+ loader: () => this.api.hello(),
13
+ });
14
+ }
15
+ `.trim();
@@ -0,0 +1,16 @@
1
+ export const webHelloComponentTsx = () => `
2
+ interface Props {
3
+ message: string;
4
+ }
5
+
6
+ const Hello = (props: Props) => {
7
+ return (
8
+ <div>
9
+ <h1>{props.message}</h1>
10
+ <p>Edit this component in src/web/components/Hello.tsx</p>
11
+ </div>
12
+ );
13
+ };
14
+
15
+ export default Hello;
16
+ `.trim();
@@ -0,0 +1,16 @@
1
+ export interface WebIndexTsOptions {
2
+ appName?: string;
3
+ }
4
+
5
+ export const webIndexTs = (options: WebIndexTsOptions = {}) => {
6
+ const { appName = "app" } = options;
7
+ return `
8
+ import { $module } from "alepha";
9
+ import { AppRouter } from "./AppRouter.ts";
10
+
11
+ export const WebModule = $module({
12
+ name: "${appName}.web",
13
+ services: [AppRouter],
14
+ });
15
+ `.trim();
16
+ };
@@ -1,7 +1,6 @@
1
- import { access, readFile, unlink, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
1
  import { $inject, $use, t } from "alepha";
4
2
  import { $command } from "alepha/command";
3
+ import { FileSystemProvider } from "alepha/file";
5
4
  import { $logger } from "alepha/logger";
6
5
  import {
7
6
  boot,
@@ -16,10 +15,15 @@ import {
16
15
  } from "alepha/vite";
17
16
  import { buildOptions } from "../atoms/buildOptions.ts";
18
17
  import { AlephaCliUtils } from "../services/AlephaCliUtils.ts";
18
+ import { PackageManagerUtils } from "../services/PackageManagerUtils.ts";
19
+ import { ProjectScaffolder } from "../services/ProjectScaffolder.ts";
19
20
 
20
21
  export class BuildCommand {
21
22
  protected readonly log = $logger();
23
+ protected readonly fs = $inject(FileSystemProvider);
22
24
  protected readonly utils = $inject(AlephaCliUtils);
25
+ protected readonly pm = $inject(PackageManagerUtils);
26
+ protected readonly scaffolder = $inject(ProjectScaffolder);
23
27
  protected readonly options = $use(buildOptions);
24
28
 
25
29
  public readonly build = $command({
@@ -55,18 +59,23 @@ export class BuildCommand {
55
59
  description: "Generate sitemap.xml with base URL",
56
60
  }),
57
61
  ),
62
+ bun: t.optional(
63
+ t.boolean({
64
+ description: "Prioritize .bun.ts entry files for Bun runtime",
65
+ }),
66
+ ),
58
67
  }),
59
68
  handler: async ({ flags, args, run, root }) => {
60
69
  // Tell viteAlephaBuild plugin to skip - CLI handles all tasks
61
70
  process.env.ALEPHA_BUILD_MODE = "cli";
62
71
  process.env.NODE_ENV = "production";
63
72
 
64
- if (await this.utils.hasExpo(root)) {
73
+ if (await this.pm.hasExpo(root)) {
65
74
  // will come soon
66
75
  return;
67
76
  }
68
77
 
69
- await this.utils.ensureConfig(root, {
78
+ await this.scaffolder.ensureConfig(root, {
70
79
  tsconfigJson: true,
71
80
  });
72
81
 
@@ -76,21 +85,17 @@ export class BuildCommand {
76
85
  const distDir = "dist";
77
86
  const clientDir = "public";
78
87
 
79
- await this.utils.ensureDependency(root, "vite", { run });
88
+ await this.pm.ensureDependency(root, "vite", {
89
+ run,
90
+ exec: (cmd, opts) => this.utils.exec(cmd, opts),
91
+ });
80
92
  await run.rm("dist", { alias: "clean dist" });
81
93
 
82
94
  const options = this.options;
83
95
  await this.utils.loadEnv(root, [".env", ".env.production"]);
84
96
 
85
97
  const stats = flags.stats ?? options.stats ?? false;
86
-
87
- let hasClient = false;
88
- try {
89
- await access(join(root, "index.html"));
90
- hasClient = true;
91
- } catch {
92
- // No index.html
93
- }
98
+ const hasClient = await this.fs.exists(this.fs.join(root, "index.html"));
94
99
 
95
100
  // Build client (precompress always enabled)
96
101
  if (hasClient) {
@@ -110,12 +115,26 @@ export class BuildCommand {
110
115
  await run({
111
116
  name: "vite build server",
112
117
  handler: async () => {
113
- let clientBuilt = false;
114
- try {
115
- await readFile(`${distDir}/${clientDir}/index.html`, "utf-8");
116
- clientBuilt = true;
117
- } catch {
118
- // No client build
118
+ const clientIndexPath = `${distDir}/${clientDir}/index.html`;
119
+ const clientBuilt = await this.fs.exists(clientIndexPath);
120
+
121
+ const conditions: string[] = [];
122
+
123
+ // bun:
124
+ // - alepha
125
+ // - react-dom
126
+
127
+ if (flags.bun) {
128
+ conditions.push("bun");
129
+ }
130
+
131
+ // workerd:
132
+ // - react-dom
133
+ // - postgres
134
+
135
+ // TODO: investigate if we have more conditions like 'edge' to add here
136
+ if (options.cloudflare) {
137
+ conditions.push("workerd");
119
138
  }
120
139
 
121
140
  await buildServer({
@@ -124,11 +143,12 @@ export class BuildCommand {
124
143
  distDir,
125
144
  clientDir: clientBuilt ? clientDir : undefined,
126
145
  stats,
146
+ conditions,
127
147
  });
128
148
 
129
149
  // Server will handle index.html if both client & server are built
130
150
  if (clientBuilt) {
131
- await unlink(`${distDir}/${clientDir}/index.html`);
151
+ await this.fs.rm(clientIndexPath);
132
152
  }
133
153
  },
134
154
  });
@@ -148,7 +168,7 @@ export class BuildCommand {
148
168
  await run({
149
169
  name: "add sitemap",
150
170
  handler: async () => {
151
- await writeFile(
171
+ await this.fs.writeFile(
152
172
  `${distDir}/${clientDir}/sitemap.xml`,
153
173
  await generateSitemap({
154
174
  entry: `${distDir}/index.js`,
@@ -1,7 +1,6 @@
1
- import { readFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
1
  import { $inject, AlephaError, t } from "alepha";
4
2
  import { $command } from "alepha/command";
3
+ import { FileSystemProvider } from "alepha/file";
5
4
  import { $logger } from "alepha/logger";
6
5
  import type {
7
6
  DatabaseProvider,
@@ -27,6 +26,7 @@ const drizzleCommandFlags = t.object({
27
26
 
28
27
  export class DbCommand {
29
28
  protected readonly log = $logger();
29
+ protected readonly fs = $inject(FileSystemProvider);
30
30
  protected readonly utils = $inject(AlephaCliUtils);
31
31
 
32
32
  /**
@@ -66,26 +66,23 @@ export class DbCommand {
66
66
 
67
67
  accepted.add(providerName);
68
68
 
69
- const migrationDir = join(rootDir, "migrations", providerName);
69
+ const migrationDir = this.fs.join(rootDir, "migrations", providerName);
70
70
 
71
- const journalFile = await readFile(
72
- `${migrationDir}/meta/_journal.json`,
73
- "utf-8",
74
- ).catch(() => null);
71
+ const journalBuffer = await this.fs
72
+ .readFile(`${migrationDir}/meta/_journal.json`)
73
+ .catch(() => null);
75
74
 
76
- if (!journalFile) {
75
+ if (!journalBuffer) {
77
76
  this.log.info("No migration journal found.");
78
77
  return;
79
78
  }
80
79
 
81
- const journal = JSON.parse(journalFile);
80
+ const journal = JSON.parse(journalBuffer.toString("utf-8"));
82
81
  const lastMigration = journal.entries[journal.entries.length - 1];
83
- const lastSnapshot = JSON.parse(
84
- await readFile(
85
- `${migrationDir}/meta/${String(lastMigration.idx).padStart(4, "0")}_snapshot.json`,
86
- "utf-8",
87
- ),
82
+ const snapshotBuffer = await this.fs.readFile(
83
+ `${migrationDir}/meta/${String(lastMigration.idx).padStart(4, "0")}_snapshot.json`,
88
84
  );
85
+ const lastSnapshot = JSON.parse(snapshotBuffer.toString("utf-8"));
89
86
 
90
87
  const models = drizzleKitProvider.getModels(provider);
91
88
  const kit = drizzleKitProvider.importDrizzleKit();
@@ -295,6 +292,10 @@ export class DbCommand {
295
292
  const providerName = provider.name;
296
293
  const dialect = provider.dialect;
297
294
 
295
+ if (providerName === "") {
296
+ continue;
297
+ }
298
+
298
299
  if (accepted.has(providerName)) {
299
300
  continue;
300
301
  }
@@ -316,6 +317,7 @@ export class DbCommand {
316
317
  provider,
317
318
  providerName,
318
319
  providerUrl: provider.url,
320
+ providerDriver: provider.driver,
319
321
  dialect,
320
322
  entry,
321
323
  rootDir,
@@ -341,6 +343,7 @@ export class DbCommand {
341
343
  provider: DatabaseProvider;
342
344
  providerName: string;
343
345
  providerUrl: string;
346
+ providerDriver: string;
344
347
  dialect: string;
345
348
  entry: string;
346
349
  rootDir: string;
@@ -371,16 +374,16 @@ export class DbCommand {
371
374
  config.schemaFilter = options.provider.schema;
372
375
  }
373
376
 
374
- if (options.providerName === "d1") {
377
+ if (options.providerDriver === "d1") {
375
378
  config.driver = "d1-http";
376
379
  }
377
380
 
378
- if (options.providerName === "pglite") {
381
+ if (options.providerDriver === "pglite") {
379
382
  config.driver = "pglite";
380
383
  }
381
384
 
382
385
  if (options.dialect === "sqlite") {
383
- if (options.providerName === "d1") {
386
+ if (options.providerDriver === "d1") {
384
387
  const token = process.env.CLOUDFLARE_API_TOKEN;
385
388
  if (!token) {
386
389
  throw new AlephaError(
@@ -421,7 +424,7 @@ export class DbCommand {
421
424
  } else {
422
425
  let url = options.providerUrl;
423
426
  url = url.replace("sqlite://", "").replace("file://", "");
424
- url = join(options.rootDir, url);
427
+ url = this.fs.join(options.rootDir, url);
425
428
 
426
429
  config.dbCredentials = {
427
430
  url,
@@ -1,12 +1,15 @@
1
- import { join } from "node:path";
2
1
  import { $inject, AlephaError, t } from "alepha";
3
2
  import { $command } from "alepha/command";
3
+ import { FileSystemProvider } from "alepha/file";
4
4
  import { $logger } from "alepha/logger";
5
5
  import { AlephaCliUtils } from "../services/AlephaCliUtils.ts";
6
+ import { PackageManagerUtils } from "../services/PackageManagerUtils.ts";
6
7
 
7
8
  export class DeployCommand {
8
9
  protected readonly log = $logger();
10
+ protected readonly fs = $inject(FileSystemProvider);
9
11
  protected readonly utils = $inject(AlephaCliUtils);
12
+ protected readonly pm = $inject(PackageManagerUtils);
10
13
 
11
14
  /**
12
15
  * Deploy the project to a hosting platform (e.g., Vercel, Cloudflare, Surge)
@@ -79,7 +82,10 @@ export class DeployCommand {
79
82
  this.log.debug("Running database migrations before deployment...");
80
83
  await this.utils.exec(`alepha db migrate --mode=${mode}`);
81
84
  }
82
- await this.utils.ensureDependency(root, "vercel", { dev: true });
85
+ await this.pm.ensureDependency(root, "vercel", {
86
+ dev: true,
87
+ exec: (cmd, opts) => this.utils.exec(cmd, opts),
88
+ });
83
89
  const command =
84
90
  `vercel . --cwd=dist ${mode === "production" ? "--prod" : ""}`.trim();
85
91
  this.log.debug(`Deploying to Vercel with command: ${command}`);
@@ -93,7 +99,10 @@ export class DeployCommand {
93
99
  this.log.debug("Running database migrations before deployment...");
94
100
  await this.utils.exec(`alepha db migrate --mode=${mode}`);
95
101
  }
96
- await this.utils.ensureDependency(root, "wrangler", { dev: true });
102
+ await this.pm.ensureDependency(root, "wrangler", {
103
+ dev: true,
104
+ exec: (cmd, opts) => this.utils.exec(cmd, opts),
105
+ });
97
106
  const command =
98
107
  `wrangler deploy ${mode === "production" ? "" : "--env preview"} --config=dist/wrangler.jsonc`.trim();
99
108
  this.log.info(`Deploying to Cloudflare with command: ${command}`);
@@ -103,8 +112,11 @@ export class DeployCommand {
103
112
 
104
113
  // Surge deployment
105
114
  if (await this.utils.exists(root, "dist/public/404.html")) {
106
- await this.utils.ensureDependency(root, "surge", { dev: true });
107
- const distPath = join(root, "dist/public");
115
+ await this.pm.ensureDependency(root, "surge", {
116
+ dev: true,
117
+ exec: (cmd, opts) => this.utils.exec(cmd, opts),
118
+ });
119
+ const distPath = this.fs.join(root, "dist/public");
108
120
  this.log.debug(`Deploying to Surge from directory: ${distPath}`);
109
121
  await this.utils.exec(`surge ${distPath}`);
110
122
  return;