alepha 0.14.3 → 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 (317) hide show
  1. package/README.md +2 -5
  2. package/dist/api/audits/index.d.ts +620 -811
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +185 -377
  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 -435
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/notifications/index.d.ts +238 -429
  11. package/dist/api/notifications/index.d.ts.map +1 -1
  12. package/dist/api/parameters/index.d.ts +236 -427
  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 +1010 -1196
  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 +17 -17
  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 +384 -285
  32. package/dist/cli/index.d.ts.map +1 -1
  33. package/dist/cli/index.js +1113 -623
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/command/index.d.ts +299 -300
  36. package/dist/command/index.d.ts.map +1 -1
  37. package/dist/command/index.js +13 -9
  38. package/dist/command/index.js.map +1 -1
  39. package/dist/core/index.browser.js +445 -103
  40. package/dist/core/index.browser.js.map +1 -1
  41. package/dist/core/index.d.ts +733 -625
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +446 -103
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/index.native.js +445 -103
  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/logger/index.js +12 -2
  67. package/dist/logger/index.js.map +1 -1
  68. package/dist/mcp/index.d.ts +197 -197
  69. package/dist/mcp/index.d.ts.map +1 -1
  70. package/dist/mcp/index.js +1 -1
  71. package/dist/mcp/index.js.map +1 -1
  72. package/dist/orm/chunk-DtkW-qnP.js +38 -0
  73. package/dist/orm/index.browser.js.map +1 -1
  74. package/dist/orm/index.bun.js +2814 -0
  75. package/dist/orm/index.bun.js.map +1 -0
  76. package/dist/orm/index.d.ts +1228 -1216
  77. package/dist/orm/index.d.ts.map +1 -1
  78. package/dist/orm/index.js +2041 -1967
  79. package/dist/orm/index.js.map +1 -1
  80. package/dist/queue/core/index.d.ts +248 -248
  81. package/dist/queue/core/index.d.ts.map +1 -1
  82. package/dist/queue/redis/index.d.ts.map +1 -1
  83. package/dist/redis/index.bun.js +285 -0
  84. package/dist/redis/index.bun.js.map +1 -0
  85. package/dist/redis/index.d.ts +118 -136
  86. package/dist/redis/index.d.ts.map +1 -1
  87. package/dist/redis/index.js +18 -38
  88. package/dist/redis/index.js.map +1 -1
  89. package/dist/retry/index.d.ts +69 -69
  90. package/dist/retry/index.d.ts.map +1 -1
  91. package/dist/router/index.d.ts +6 -6
  92. package/dist/router/index.d.ts.map +1 -1
  93. package/dist/scheduler/index.d.ts +25 -25
  94. package/dist/scheduler/index.d.ts.map +1 -1
  95. package/dist/security/index.browser.js +5 -1
  96. package/dist/security/index.browser.js.map +1 -1
  97. package/dist/security/index.d.ts +417 -254
  98. package/dist/security/index.d.ts.map +1 -1
  99. package/dist/security/index.js +386 -86
  100. package/dist/security/index.js.map +1 -1
  101. package/dist/server/auth/index.d.ts +110 -110
  102. package/dist/server/auth/index.d.ts.map +1 -1
  103. package/dist/server/auth/index.js +20 -20
  104. package/dist/server/auth/index.js.map +1 -1
  105. package/dist/server/cache/index.d.ts +62 -47
  106. package/dist/server/cache/index.d.ts.map +1 -1
  107. package/dist/server/cache/index.js +56 -3
  108. package/dist/server/cache/index.js.map +1 -1
  109. package/dist/server/compress/index.d.ts +6 -0
  110. package/dist/server/compress/index.d.ts.map +1 -1
  111. package/dist/server/compress/index.js +36 -1
  112. package/dist/server/compress/index.js.map +1 -1
  113. package/dist/server/cookies/index.d.ts +6 -6
  114. package/dist/server/cookies/index.d.ts.map +1 -1
  115. package/dist/server/cookies/index.js +3 -3
  116. package/dist/server/cookies/index.js.map +1 -1
  117. package/dist/server/core/index.browser.js +2 -2
  118. package/dist/server/core/index.browser.js.map +1 -1
  119. package/dist/server/core/index.d.ts +242 -150
  120. package/dist/server/core/index.d.ts.map +1 -1
  121. package/dist/server/core/index.js +294 -125
  122. package/dist/server/core/index.js.map +1 -1
  123. package/dist/server/cors/index.d.ts +11 -12
  124. package/dist/server/cors/index.d.ts.map +1 -1
  125. package/dist/server/health/index.d.ts +0 -1
  126. package/dist/server/health/index.d.ts.map +1 -1
  127. package/dist/server/helmet/index.d.ts +2 -2
  128. package/dist/server/helmet/index.d.ts.map +1 -1
  129. package/dist/server/links/index.browser.js.map +1 -1
  130. package/dist/server/links/index.d.ts +123 -124
  131. package/dist/server/links/index.d.ts.map +1 -1
  132. package/dist/server/links/index.js +1 -2
  133. package/dist/server/links/index.js.map +1 -1
  134. package/dist/server/metrics/index.d.ts.map +1 -1
  135. package/dist/server/multipart/index.d.ts +6 -6
  136. package/dist/server/multipart/index.d.ts.map +1 -1
  137. package/dist/server/proxy/index.d.ts +102 -103
  138. package/dist/server/proxy/index.d.ts.map +1 -1
  139. package/dist/server/rate-limit/index.d.ts +16 -16
  140. package/dist/server/rate-limit/index.d.ts.map +1 -1
  141. package/dist/server/static/index.d.ts +44 -44
  142. package/dist/server/static/index.d.ts.map +1 -1
  143. package/dist/server/static/index.js +4 -0
  144. package/dist/server/static/index.js.map +1 -1
  145. package/dist/server/swagger/index.d.ts +48 -49
  146. package/dist/server/swagger/index.d.ts.map +1 -1
  147. package/dist/server/swagger/index.js +3 -5
  148. package/dist/server/swagger/index.js.map +1 -1
  149. package/dist/sms/index.d.ts +13 -11
  150. package/dist/sms/index.d.ts.map +1 -1
  151. package/dist/sms/index.js +7 -7
  152. package/dist/sms/index.js.map +1 -1
  153. package/dist/thread/index.d.ts +71 -72
  154. package/dist/thread/index.d.ts.map +1 -1
  155. package/dist/topic/core/index.d.ts +318 -318
  156. package/dist/topic/core/index.d.ts.map +1 -1
  157. package/dist/topic/redis/index.d.ts +6 -6
  158. package/dist/topic/redis/index.d.ts.map +1 -1
  159. package/dist/vite/index.d.ts +5805 -249
  160. package/dist/vite/index.d.ts.map +1 -1
  161. package/dist/vite/index.js +599 -513
  162. package/dist/vite/index.js.map +1 -1
  163. package/dist/websocket/index.browser.js +6 -6
  164. package/dist/websocket/index.browser.js.map +1 -1
  165. package/dist/websocket/index.d.ts +247 -247
  166. package/dist/websocket/index.d.ts.map +1 -1
  167. package/dist/websocket/index.js +6 -6
  168. package/dist/websocket/index.js.map +1 -1
  169. package/package.json +9 -14
  170. package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
  171. package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
  172. package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
  173. package/src/api/users/entities/users.ts +1 -1
  174. package/src/api/users/index.ts +8 -8
  175. package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
  176. package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
  177. package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
  178. package/src/api/users/services/CredentialService.ts +7 -7
  179. package/src/api/users/services/IdentityService.ts +4 -4
  180. package/src/api/users/services/RegistrationService.spec.ts +25 -27
  181. package/src/api/users/services/RegistrationService.ts +38 -27
  182. package/src/api/users/services/SessionCrudService.ts +3 -3
  183. package/src/api/users/services/SessionService.spec.ts +3 -3
  184. package/src/api/users/services/SessionService.ts +28 -9
  185. package/src/api/users/services/UserService.ts +7 -7
  186. package/src/batch/providers/BatchProvider.ts +1 -2
  187. package/src/cli/apps/AlephaCli.ts +0 -2
  188. package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
  189. package/src/cli/assets/apiHelloControllerTs.ts +18 -0
  190. package/src/cli/assets/apiIndexTs.ts +16 -0
  191. package/src/cli/assets/claudeMd.ts +303 -0
  192. package/src/cli/assets/mainBrowserTs.ts +2 -2
  193. package/src/cli/assets/mainServerTs.ts +24 -0
  194. package/src/cli/assets/webAppRouterTs.ts +15 -0
  195. package/src/cli/assets/webHelloComponentTsx.ts +16 -0
  196. package/src/cli/assets/webIndexTs.ts +16 -0
  197. package/src/cli/atoms/buildOptions.ts +88 -0
  198. package/src/cli/commands/build.ts +70 -87
  199. package/src/cli/commands/db.ts +21 -22
  200. package/src/cli/commands/deploy.ts +17 -5
  201. package/src/cli/commands/dev.ts +22 -14
  202. package/src/cli/commands/format.ts +8 -2
  203. package/src/cli/commands/gen/env.ts +53 -0
  204. package/src/cli/commands/gen/openapi.ts +1 -1
  205. package/src/cli/commands/gen/resource.ts +15 -0
  206. package/src/cli/commands/gen.ts +7 -1
  207. package/src/cli/commands/init.ts +74 -30
  208. package/src/cli/commands/lint.ts +8 -2
  209. package/src/cli/commands/test.ts +8 -3
  210. package/src/cli/commands/typecheck.ts +5 -1
  211. package/src/cli/commands/verify.ts +5 -3
  212. package/src/cli/defineConfig.ts +49 -7
  213. package/src/cli/index.ts +0 -1
  214. package/src/cli/services/AlephaCliUtils.ts +39 -589
  215. package/src/cli/services/PackageManagerUtils.ts +301 -0
  216. package/src/cli/services/ProjectScaffolder.ts +306 -0
  217. package/src/command/helpers/Runner.spec.ts +2 -2
  218. package/src/command/helpers/Runner.ts +16 -4
  219. package/src/command/primitives/$command.ts +0 -6
  220. package/src/command/providers/CliProvider.ts +1 -3
  221. package/src/core/Alepha.ts +42 -0
  222. package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
  223. package/src/core/index.shared.ts +1 -0
  224. package/src/core/index.ts +2 -0
  225. package/src/core/primitives/$hook.ts +6 -2
  226. package/src/core/primitives/$module.spec.ts +4 -0
  227. package/src/core/providers/AlsProvider.ts +1 -1
  228. package/src/core/providers/CodecManager.spec.ts +12 -6
  229. package/src/core/providers/CodecManager.ts +26 -6
  230. package/src/core/providers/EventManager.ts +169 -13
  231. package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
  232. package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
  233. package/src/core/providers/StateManager.spec.ts +27 -16
  234. package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
  235. package/src/email/providers/LocalEmailProvider.ts +52 -15
  236. package/src/email/providers/NodemailerEmailProvider.ts +167 -56
  237. package/src/file/errors/FileError.ts +7 -0
  238. package/src/file/index.ts +9 -1
  239. package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
  240. package/src/logger/index.ts +15 -3
  241. package/src/mcp/transports/StdioMcpTransport.ts +1 -1
  242. package/src/orm/index.browser.ts +1 -19
  243. package/src/orm/index.bun.ts +77 -0
  244. package/src/orm/index.shared-server.ts +22 -0
  245. package/src/orm/index.shared.ts +15 -0
  246. package/src/orm/index.ts +13 -39
  247. package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
  248. package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
  249. package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
  250. package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
  251. package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
  252. package/src/orm/services/Repository.ts +8 -0
  253. package/src/queue/core/providers/WorkerProvider.spec.ts +48 -32
  254. package/src/redis/index.bun.ts +35 -0
  255. package/src/redis/providers/BunRedisProvider.ts +12 -43
  256. package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
  257. package/src/redis/providers/NodeRedisProvider.ts +16 -34
  258. package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
  259. package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
  260. package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
  261. package/src/security/index.browser.ts +5 -0
  262. package/src/security/index.ts +90 -7
  263. package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
  264. package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
  265. package/src/security/primitives/$role.ts +5 -5
  266. package/src/security/primitives/$serviceAccount.spec.ts +5 -5
  267. package/src/security/primitives/$serviceAccount.ts +3 -3
  268. package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
  269. package/src/server/auth/primitives/$auth.ts +10 -10
  270. package/src/server/auth/primitives/$authCredentials.ts +3 -3
  271. package/src/server/auth/primitives/$authGithub.ts +3 -3
  272. package/src/server/auth/primitives/$authGoogle.ts +3 -3
  273. package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
  274. package/src/server/cache/providers/ServerCacheProvider.spec.ts +183 -0
  275. package/src/server/cache/providers/ServerCacheProvider.ts +95 -10
  276. package/src/server/compress/providers/ServerCompressProvider.ts +61 -2
  277. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
  278. package/src/server/core/helpers/ServerReply.ts +2 -2
  279. package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
  280. package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
  281. package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
  282. package/src/server/core/providers/ServerProvider.ts +155 -22
  283. package/src/server/core/providers/ServerRouterProvider.ts +259 -115
  284. package/src/server/core/providers/ServerTimingProvider.ts +2 -2
  285. package/src/server/links/index.ts +1 -1
  286. package/src/server/links/providers/LinkProvider.ts +1 -1
  287. package/src/server/static/providers/ServerStaticProvider.ts +10 -0
  288. package/src/server/swagger/index.ts +1 -1
  289. package/src/server/swagger/providers/ServerSwaggerProvider.ts +5 -8
  290. package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
  291. package/src/sms/providers/LocalSmsProvider.ts +8 -7
  292. package/src/vite/helpers/boot.ts +28 -17
  293. package/src/vite/helpers/importViteReact.ts +13 -0
  294. package/src/vite/index.ts +1 -21
  295. package/src/vite/plugins/viteAlephaDev.ts +16 -1
  296. package/src/vite/plugins/viteAlephaSsrPreload.ts +222 -0
  297. package/src/vite/tasks/buildClient.ts +11 -0
  298. package/src/vite/tasks/buildServer.ts +59 -4
  299. package/src/vite/tasks/devServer.ts +71 -0
  300. package/src/vite/tasks/generateCloudflare.ts +7 -0
  301. package/src/vite/tasks/index.ts +2 -1
  302. package/dist/server/security/index.browser.js +0 -13
  303. package/dist/server/security/index.browser.js.map +0 -1
  304. package/dist/server/security/index.d.ts +0 -173
  305. package/dist/server/security/index.d.ts.map +0 -1
  306. package/dist/server/security/index.js +0 -311
  307. package/dist/server/security/index.js.map +0 -1
  308. package/src/cli/assets/appRouterTs.ts +0 -9
  309. package/src/cli/assets/mainTs.ts +0 -13
  310. package/src/cli/assets/viteConfigTs.ts +0 -14
  311. package/src/cli/commands/run.ts +0 -24
  312. package/src/server/security/index.browser.ts +0 -10
  313. package/src/server/security/index.ts +0 -94
  314. package/src/vite/plugins/viteAlepha.ts +0 -37
  315. package/src/vite/plugins/viteAlephaBuild.ts +0 -281
  316. /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
  317. /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
@@ -136,12 +136,71 @@ export class ServerCompressProvider {
136
136
 
137
137
  if (typeof body === "object" && body instanceof ReadableStream) {
138
138
  this.setHeaders(response, encoding);
139
- response.body = Readable.fromWeb(body).pipe(
140
- compressor.stream({ params }),
139
+ // For streaming responses, use flush mode to avoid buffering
140
+ response.body = this.createFlushingCompressStream(
141
+ body,
142
+ compressor.stream,
143
+ encoding,
144
+ params,
141
145
  );
142
146
  }
143
147
  }
144
148
 
149
+ /**
150
+ * Create a compressed stream that flushes after each chunk.
151
+ * This is essential for streaming SSR - ensures each chunk is sent immediately.
152
+ */
153
+ protected createFlushingCompressStream(
154
+ input: ReadableStream,
155
+ createCompressor: (options?: any) => Transform,
156
+ encoding: string,
157
+ params: Record<number, any>,
158
+ ): ReadableStream<Uint8Array> {
159
+ const compressor = createCompressor({
160
+ params,
161
+ flush: zlib.constants.Z_SYNC_FLUSH,
162
+ });
163
+ const reader = Readable.fromWeb(input);
164
+
165
+ return new ReadableStream<Uint8Array>({
166
+ start(controller) {
167
+ compressor.on("data", (chunk: Buffer) => {
168
+ controller.enqueue(new Uint8Array(chunk));
169
+ });
170
+
171
+ compressor.on("end", () => {
172
+ controller.close();
173
+ });
174
+
175
+ compressor.on("error", (err) => {
176
+ controller.error(err);
177
+ });
178
+
179
+ reader.on("data", (chunk: Buffer) => {
180
+ compressor.write(chunk);
181
+ // Force flush after each chunk for streaming
182
+ // Cast to any because flush() exists on zlib streams but not in Transform type
183
+ const zlibStream = compressor as any;
184
+ if (encoding === "gzip") {
185
+ zlibStream.flush(zlib.constants.Z_SYNC_FLUSH);
186
+ } else if (encoding === "br") {
187
+ zlibStream.flush(zlib.constants.BROTLI_OPERATION_FLUSH);
188
+ } else if (encoding === "zstd") {
189
+ zlibStream.flush();
190
+ }
191
+ });
192
+
193
+ reader.on("end", () => {
194
+ compressor.end();
195
+ });
196
+
197
+ reader.on("error", (err) => {
198
+ controller.error(err);
199
+ });
200
+ },
201
+ });
202
+ }
203
+
145
204
  protected getParams(
146
205
  encoding: keyof typeof ServerCompressProvider.compressors,
147
206
  ): Record<number, any> {
@@ -48,7 +48,7 @@ export class ServerCookiesProvider {
48
48
 
49
49
  public readonly onRequest = $hook({
50
50
  on: "server:onRequest",
51
- handler: async ({ request }) => {
51
+ handler: ({ request }) => {
52
52
  request.cookies = {
53
53
  req: this.cookieParser.parseRequestCookies(
54
54
  request.headers.cookie ?? "",
@@ -60,7 +60,7 @@ export class ServerCookiesProvider {
60
60
 
61
61
  public readonly onAction = $hook({
62
62
  on: "action:onRequest",
63
- handler: async ({ request }) => {
63
+ handler: ({ request }) => {
64
64
  request.cookies = {
65
65
  req: this.cookieParser.parseRequestCookies(
66
66
  request.headers.cookie ?? "",
@@ -72,7 +72,7 @@ export class ServerCookiesProvider {
72
72
 
73
73
  public readonly onSend = $hook({
74
74
  on: "server:onSend",
75
- handler: async ({ request }) => {
75
+ handler: ({ request }) => {
76
76
  if (request.cookies && Object.keys(request.cookies.res).length > 0) {
77
77
  const setCookieHeaders = this.cookieParser.serializeResponseCookies(
78
78
  request.cookies.res,
@@ -12,9 +12,9 @@ export class ServerReply {
12
12
  public body?: any;
13
13
 
14
14
  /**
15
- * Redirect to a given URL with optional status code (default 302).
15
+ * Redirect to a given URL with optional status code (default 301).
16
16
  */
17
- public redirect(url: string, status: number = 302): void {
17
+ public redirect(url: string, status: number = 301): void {
18
18
  this.status = status;
19
19
  this.headers.location = url;
20
20
  }
@@ -44,13 +44,32 @@ export class NodeHttpServerProvider extends ServerProvider {
44
44
  return `http://${this.env.SERVER_HOST}:${this.env.SERVER_PORT}`;
45
45
  }
46
46
 
47
+ // Pre-bound error handler to avoid function allocation per request
48
+ protected readonly handleRequestError = (
49
+ res: import("node:http").ServerResponse,
50
+ err: Error,
51
+ ) => {
52
+ this.log.error("Error handling request", err);
53
+ res.statusCode = 500;
54
+ res.end("Internal Server Error");
55
+ };
56
+
57
+ // P3: Pre-allocated event object to avoid { req, res } allocation per request
58
+ // Safe because handleNodeRequest completes before the next request reuses this object
59
+ protected readonly nodeRequestEvent = {
60
+ req: null as unknown as IncomingMessage,
61
+ res: null as unknown as ServerResponse,
62
+ };
63
+
47
64
  public readonly server = this.createHttpServer((req, res) => {
48
- this.log.trace(`Incoming Node.js message -> ${req.url}`);
49
- this.handleNodeRequest({ req, res }).catch((err) => {
50
- this.log.error("Error handling request", err);
51
- res.statusCode = 500;
52
- res.end("Internal Server Error");
53
- });
65
+ // Reuse pre-allocated event object instead of creating { req, res } per request
66
+ const ev = this.nodeRequestEvent;
67
+ ev.req = req;
68
+ ev.res = res;
69
+ // Note: handleNodeRequest returns a promise that resolves after response is sent
70
+ this.handleNodeRequest(ev).catch((err) =>
71
+ this.handleRequestError(res, err),
72
+ );
54
73
  });
55
74
 
56
75
  public readonly start = $hook({
@@ -24,7 +24,7 @@ export class ServerBodyParserProvider {
24
24
 
25
25
  public readonly onRequest = $hook({
26
26
  on: "server:onRequest",
27
- handler: async ({ route, request }) => {
27
+ handler: ({ route, request }) => {
28
28
  if (request.body) {
29
29
  return; // already parsed
30
30
  }
@@ -46,28 +46,24 @@ export class ServerBodyParserProvider {
46
46
  }
47
47
 
48
48
  if (route.schema?.body) {
49
- try {
50
- const body = await this.parse(
51
- stream,
52
- request.headers,
53
- route.schema.body,
54
- );
55
- if (body) {
56
- request.body = body;
57
- }
58
- } catch (error) {
59
- if (error instanceof HttpError) {
60
- throw error;
61
- }
62
-
63
- throw new HttpError(
64
- {
65
- status: 400,
66
- message: "Failed to parse request body",
67
- },
68
- error,
69
- );
70
- }
49
+ return this.parse(stream, request.headers, route.schema.body)
50
+ .then((body) => {
51
+ if (body) {
52
+ request.body = body;
53
+ }
54
+ })
55
+ .catch((error) => {
56
+ if (error instanceof HttpError) {
57
+ throw error;
58
+ }
59
+ throw new HttpError(
60
+ {
61
+ status: 400,
62
+ message: "Failed to parse request body",
63
+ },
64
+ error,
65
+ );
66
+ });
71
67
  }
72
68
  },
73
69
  });
@@ -9,24 +9,26 @@ export class ServerLoggerProvider {
9
9
  on: "server:onRequest",
10
10
  priority: "first",
11
11
  handler: ({ route, request }) => {
12
- if (!route.silent) {
13
- request.metadata.now = Date.now();
14
-
15
- const data: Record<string, string> = {
16
- method: request.method,
17
- path: request.url.pathname,
18
- };
19
-
20
- if (this.alepha.isProduction()) {
21
- data.agent = request.headers["user-agent"];
22
- const ip = request.ip;
23
- if (ip) {
24
- data.ip = ip;
25
- }
26
- }
12
+ if (route.silent) {
13
+ return;
14
+ }
15
+
16
+ request.metadata.now = Date.now();
17
+
18
+ const data: Record<string, string> = {
19
+ method: request.method,
20
+ path: request.url.pathname,
21
+ };
27
22
 
28
- this.log.info("Incoming request", data);
23
+ if (this.alepha.isProduction()) {
24
+ data.agent = request.headers["user-agent"];
25
+ const ip = request.ip;
26
+ if (ip) {
27
+ data.ip = ip;
28
+ }
29
29
  }
30
+
31
+ this.log.info("Incoming request", data);
30
32
  },
31
33
  });
32
34
 
@@ -42,10 +44,12 @@ export class ServerLoggerProvider {
42
44
  on: "server:onResponse",
43
45
  priority: "last",
44
46
  handler: ({ route, request, response }) => {
45
- if (!route.silent) {
46
- const ms = Date.now() - request.metadata.now;
47
- this.log.info("Request completed", { status: response.status, ms });
47
+ if (route.silent) {
48
+ return;
48
49
  }
50
+
51
+ const ms = Date.now() - request.metadata.now;
52
+ this.log.info("Request completed", { status: response.status, ms });
49
53
  },
50
54
  });
51
55
  }
@@ -11,6 +11,21 @@ import type {
11
11
  } from "../interfaces/ServerRequest.ts";
12
12
  import { ServerRouterProvider } from "./ServerRouterProvider.ts";
13
13
 
14
+ // ============================================================================
15
+ // Performance Constants
16
+ // ============================================================================
17
+
18
+ // Note: We cannot use frozen/shared empty objects here because downstream code
19
+ // may mutate params/query (e.g., validation, React page rendering)
20
+
21
+ // Header constants for fast property access
22
+ const HEADER_X_FORWARDED_PROTO = "x-forwarded-proto";
23
+ const HEADER_HOST = "host";
24
+
25
+ // Protocol prefixes
26
+ const PROTO_HTTPS = "https://";
27
+ const PROTO_HTTP = "http://";
28
+
14
29
  /**
15
30
  * Base server provider to handle incoming requests and route them.
16
31
  *
@@ -26,7 +41,117 @@ export class ServerProvider {
26
41
 
27
42
  protected readonly internalServerErrorMessage = "Internal Server Error";
28
43
 
44
+ // Pre-allocated error response to avoid object creation per failed request
45
+ protected readonly internalErrorResponse = Object.freeze({
46
+ status: 500,
47
+ headers: Object.freeze({ "content-type": "text/plain" }),
48
+ body: this.internalServerErrorMessage,
49
+ });
50
+
51
+ // Pre-bound error handler to avoid function allocation per request
52
+ protected readonly handleInternalError = () => this.internalErrorResponse;
53
+
54
+ // ============================================================================
55
+ // P1: URL Base Cache - cache protocol+host per unique host header
56
+ // Avoids string concatenation on every request
57
+ // ============================================================================
58
+ protected readonly urlBaseCache = new Map<string, string>();
59
+
60
+ /**
61
+ * Get cached URL base (protocol + host) for a given host header.
62
+ * Caches the result to avoid repeated string concatenation.
63
+ */
64
+ protected getUrlBase(headers: Record<string, string>): string {
65
+ const host = headers[HEADER_HOST];
66
+ let base = this.urlBaseCache.get(host);
67
+ if (!base) {
68
+ const proto =
69
+ headers[HEADER_X_FORWARDED_PROTO] === "https"
70
+ ? PROTO_HTTPS
71
+ : PROTO_HTTP;
72
+ base = proto + host;
73
+ // Limit cache size to prevent memory leaks from many unique hosts
74
+ if (this.urlBaseCache.size < 100) {
75
+ this.urlBaseCache.set(host, base);
76
+ }
77
+ }
78
+ return base;
79
+ }
80
+
81
+ // ============================================================================
82
+ // P0: Manual Query String Parser - faster than URLSearchParams
83
+ // Parses query string without creating URL object
84
+ // ============================================================================
85
+
86
+ /**
87
+ * Parse query string manually - faster than new URL() + URLSearchParams.
88
+ * Returns empty object if no query string.
89
+ */
90
+ protected parseQueryString(rawUrl: string): Record<string, string> {
91
+ const qIndex = rawUrl.indexOf("?");
92
+ if (qIndex === -1) {
93
+ return {};
94
+ }
95
+
96
+ const qs = rawUrl.slice(qIndex + 1);
97
+ if (!qs) {
98
+ return {};
99
+ }
100
+
101
+ const query: Record<string, string> = {};
102
+ let start = 0;
103
+ let eqIdx = -1;
104
+
105
+ for (let i = 0; i <= qs.length; i++) {
106
+ const char = i < qs.length ? qs.charCodeAt(i) : 38; // '&' at end
107
+
108
+ if (char === 61) {
109
+ // '='
110
+ eqIdx = i;
111
+ } else if (char === 38) {
112
+ // '&'
113
+ if (eqIdx !== -1 && eqIdx > start) {
114
+ const key = qs.slice(start, eqIdx);
115
+ const value = qs.slice(eqIdx + 1, i);
116
+ // Only decode if necessary (contains % or +)
117
+ query[this.fastDecode(key)] = this.fastDecode(value);
118
+ }
119
+ start = i + 1;
120
+ eqIdx = -1;
121
+ }
122
+ }
123
+
124
+ return query;
125
+ }
126
+
127
+ /**
128
+ * Fast decode - only calls decodeURIComponent if needed.
129
+ */
130
+ protected fastDecode(str: string): string {
131
+ // Fast path: no encoding needed
132
+ if (str.indexOf("%") === -1 && str.indexOf("+") === -1) {
133
+ return str;
134
+ }
135
+ // Replace + with space before decoding
136
+ try {
137
+ return decodeURIComponent(str.replace(/\+/g, " "));
138
+ } catch {
139
+ return str;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Extract pathname from URL without creating URL object.
145
+ */
146
+ protected getPathname(rawUrl: string): string {
147
+ const qIndex = rawUrl.indexOf("?");
148
+ return qIndex === -1 ? rawUrl : rawUrl.slice(0, qIndex);
149
+ }
150
+
29
151
  public get hostname(): string {
152
+ if (this.alepha.isViteDev()) {
153
+ return `http://localhost:${this.alepha.env.SERVER_PORT}`;
154
+ }
30
155
  return ""; // no hostname in serverless mode
31
156
  }
32
157
 
@@ -51,15 +176,16 @@ export class ServerProvider {
51
176
  /**
52
177
  * Handle Node.js HTTP request event.
53
178
  *
54
- * Technically, we just convert Node.js request to Web Standard Request.
179
+ * Optimized to avoid expensive URL parsing when possible.
55
180
  */
56
181
  public async handleNodeRequest(
57
182
  nodeRequestEvent: NodeRequestEvent,
58
183
  ): Promise<void> {
59
184
  const { req, res } = nodeRequestEvent;
60
- const { route, params } = this.router.match(`/${req.method}${req.url}`);
185
+ const rawUrl = req.url!;
186
+ const { route, params } = this.router.match(`/${req.method}${rawUrl}`);
61
187
 
62
- if (this.isViteNotFound(req.url, route, params)) {
188
+ if (this.isViteNotFound(rawUrl, route, params)) {
63
189
  return;
64
190
  }
65
191
 
@@ -72,11 +198,16 @@ export class ServerProvider {
72
198
  }
73
199
 
74
200
  const headers = (req.headers ?? {}) as Record<string, string>;
75
- const proto = headers["x-forwarded-proto"] === "https" ? "https" : "http";
76
- const url = new URL(`${proto}://${headers.host}${req.url}`);
77
- const query = Object.fromEntries(url.searchParams.entries());
78
201
  const method = (req.method?.toUpperCase() ?? "GET") as RouteMethod;
79
202
 
203
+ // P0: Use manual query parsing - much faster than new URL() + URLSearchParams
204
+ const query = this.parseQueryString(rawUrl);
205
+
206
+ // P1: Use cached URL base - avoids string concat on every request
207
+ // Create URL object (still needed for downstream code that uses url.pathname, etc.)
208
+ const urlBase = this.getUrlBase(headers);
209
+ const url = new URL(rawUrl, urlBase);
210
+
80
211
  const request: ServerRequestData = {
81
212
  method,
82
213
  url,
@@ -86,13 +217,9 @@ export class ServerProvider {
86
217
  raw: { node: nodeRequestEvent },
87
218
  };
88
219
 
89
- const response = await route.handler(request).catch(() => {
90
- return {
91
- status: 500,
92
- headers: { "content-type": "text/plain" },
93
- body: this.internalServerErrorMessage,
94
- };
95
- });
220
+ const response = await route
221
+ .handler(request)
222
+ .catch(this.handleInternalError);
96
223
 
97
224
  // empty body - just send status & headers
98
225
  if (!response.body) {
@@ -116,9 +243,16 @@ export class ServerProvider {
116
243
  // if response.body is web stream
117
244
  if (response.body instanceof ReadableStream) {
118
245
  res.writeHead(response.status, response.headers);
246
+ // Flush headers immediately and disable Nagle's algorithm for streaming
247
+ res.flushHeaders();
248
+ res.socket?.setNoDelay(true);
119
249
  try {
120
250
  for await (const chunk of response.body) {
121
- res.write(chunk);
251
+ const canContinue = res.write(chunk);
252
+ // If the internal buffer is full, wait for it to drain
253
+ if (!canContinue) {
254
+ await new Promise<void>((resolve) => res.once("drain", resolve));
255
+ }
122
256
  }
123
257
  } catch (error) {
124
258
  this.log.error("Error piping proxy response stream", error);
@@ -165,7 +299,10 @@ export class ServerProvider {
165
299
  headers[key] = value;
166
300
  });
167
301
 
168
- const query = Object.fromEntries(url.searchParams.entries());
302
+ // Optimize: only parse query params if there are any
303
+ const query = url.search
304
+ ? Object.fromEntries(url.searchParams.entries())
305
+ : {};
169
306
  const method = (req.method.toUpperCase() ?? "GET") as RouteMethod;
170
307
  const request: ServerRequestData = {
171
308
  method,
@@ -176,13 +313,9 @@ export class ServerProvider {
176
313
  raw: { web: ev },
177
314
  };
178
315
 
179
- const response = await route.handler(request).catch(() => {
180
- return {
181
- status: 500,
182
- headers: { "content-type": "text/plain" },
183
- body: this.internalServerErrorMessage,
184
- };
185
- });
316
+ const response = await route
317
+ .handler(request)
318
+ .catch(this.handleInternalError);
186
319
 
187
320
  // empty body - just send status & headers
188
321
  if (!response.body) {