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
@@ -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,
@@ -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,6 +41,113 @@ 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 {
30
152
  if (this.alepha.isViteDev()) {
31
153
  return `http://localhost:${this.alepha.env.SERVER_PORT}`;
@@ -54,15 +176,16 @@ export class ServerProvider {
54
176
  /**
55
177
  * Handle Node.js HTTP request event.
56
178
  *
57
- * Technically, we just convert Node.js request to Web Standard Request.
179
+ * Optimized to avoid expensive URL parsing when possible.
58
180
  */
59
181
  public async handleNodeRequest(
60
182
  nodeRequestEvent: NodeRequestEvent,
61
183
  ): Promise<void> {
62
184
  const { req, res } = nodeRequestEvent;
63
- 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}`);
64
187
 
65
- if (this.isViteNotFound(req.url, route, params)) {
188
+ if (this.isViteNotFound(rawUrl, route, params)) {
66
189
  return;
67
190
  }
68
191
 
@@ -75,11 +198,16 @@ export class ServerProvider {
75
198
  }
76
199
 
77
200
  const headers = (req.headers ?? {}) as Record<string, string>;
78
- const proto = headers["x-forwarded-proto"] === "https" ? "https" : "http";
79
- const url = new URL(`${proto}://${headers.host}${req.url}`);
80
- const query = Object.fromEntries(url.searchParams.entries());
81
201
  const method = (req.method?.toUpperCase() ?? "GET") as RouteMethod;
82
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
+
83
211
  const request: ServerRequestData = {
84
212
  method,
85
213
  url,
@@ -89,13 +217,9 @@ export class ServerProvider {
89
217
  raw: { node: nodeRequestEvent },
90
218
  };
91
219
 
92
- const response = await route.handler(request).catch(() => {
93
- return {
94
- status: 500,
95
- headers: { "content-type": "text/plain" },
96
- body: this.internalServerErrorMessage,
97
- };
98
- });
220
+ const response = await route
221
+ .handler(request)
222
+ .catch(this.handleInternalError);
99
223
 
100
224
  // empty body - just send status & headers
101
225
  if (!response.body) {
@@ -175,7 +299,10 @@ export class ServerProvider {
175
299
  headers[key] = value;
176
300
  });
177
301
 
178
- 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
+ : {};
179
306
  const method = (req.method.toUpperCase() ?? "GET") as RouteMethod;
180
307
  const request: ServerRequestData = {
181
308
  method,
@@ -186,13 +313,9 @@ export class ServerProvider {
186
313
  raw: { web: ev },
187
314
  };
188
315
 
189
- const response = await route.handler(request).catch(() => {
190
- return {
191
- status: 500,
192
- headers: { "content-type": "text/plain" },
193
- body: this.internalServerErrorMessage,
194
- };
195
- });
316
+ const response = await route
317
+ .handler(request)
318
+ .catch(this.handleInternalError);
196
319
 
197
320
  // empty body - just send status & headers
198
321
  if (!response.body) {