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.
- package/README.md +2 -5
- package/dist/api/audits/index.d.ts +620 -811
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/files/index.d.ts +185 -377
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +0 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +245 -435
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/notifications/index.d.ts +238 -429
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +236 -427
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/users/index.browser.js +1 -2
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +1010 -1196
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +178 -151
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +17 -17
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/batch/index.d.ts +122 -122
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +1 -2
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +163 -163
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/cache/core/index.d.ts +46 -46
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cli/index.d.ts +384 -285
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1113 -623
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +299 -300
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +13 -9
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +445 -103
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +733 -625
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +446 -103
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +445 -103
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts +44 -44
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +4 -4
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +97 -50
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +129 -33
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +7981 -14
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/file/index.d.ts +523 -390
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +253 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +208 -208
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/redis/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +25 -26
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +12 -2
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +197 -197
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/chunk-DtkW-qnP.js +38 -0
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +2814 -0
- package/dist/orm/index.bun.js.map +1 -0
- package/dist/orm/index.d.ts +1228 -1216
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +2041 -1967
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +248 -248
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/redis/index.bun.js +285 -0
- package/dist/redis/index.bun.js.map +1 -0
- package/dist/redis/index.d.ts +118 -136
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +18 -38
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +69 -69
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/router/index.d.ts +6 -6
- package/dist/router/index.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +25 -25
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/security/index.browser.js +5 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +417 -254
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +386 -86
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +110 -110
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +20 -20
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +62 -47
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +56 -3
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +6 -0
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +36 -1
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/cookies/index.d.ts +6 -6
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +3 -3
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js +2 -2
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +242 -150
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +294 -125
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +11 -12
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/health/index.d.ts +0 -1
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/helmet/index.d.ts +2 -2
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +123 -124
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +1 -2
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/multipart/index.d.ts +6 -6
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/proxy/index.d.ts +102 -103
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.d.ts +16 -16
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/static/index.d.ts +44 -44
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js +4 -0
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +48 -49
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +3 -5
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +13 -11
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +7 -7
- package/dist/sms/index.js.map +1 -1
- package/dist/thread/index.d.ts +71 -72
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/topic/core/index.d.ts +318 -318
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/redis/index.d.ts +6 -6
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/vite/index.d.ts +5805 -249
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +599 -513
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +6 -6
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +247 -247
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +6 -6
- package/dist/websocket/index.js.map +1 -1
- package/package.json +9 -14
- package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
- package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
- package/src/api/users/entities/users.ts +1 -1
- package/src/api/users/index.ts +8 -8
- package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
- package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
- package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
- package/src/api/users/services/CredentialService.ts +7 -7
- package/src/api/users/services/IdentityService.ts +4 -4
- package/src/api/users/services/RegistrationService.spec.ts +25 -27
- package/src/api/users/services/RegistrationService.ts +38 -27
- package/src/api/users/services/SessionCrudService.ts +3 -3
- package/src/api/users/services/SessionService.spec.ts +3 -3
- package/src/api/users/services/SessionService.ts +28 -9
- package/src/api/users/services/UserService.ts +7 -7
- package/src/batch/providers/BatchProvider.ts +1 -2
- package/src/cli/apps/AlephaCli.ts +0 -2
- package/src/cli/apps/AlephaPackageBuilderCli.ts +38 -19
- package/src/cli/assets/apiHelloControllerTs.ts +18 -0
- package/src/cli/assets/apiIndexTs.ts +16 -0
- package/src/cli/assets/claudeMd.ts +303 -0
- package/src/cli/assets/mainBrowserTs.ts +2 -2
- package/src/cli/assets/mainServerTs.ts +24 -0
- package/src/cli/assets/webAppRouterTs.ts +15 -0
- package/src/cli/assets/webHelloComponentTsx.ts +16 -0
- package/src/cli/assets/webIndexTs.ts +16 -0
- package/src/cli/atoms/buildOptions.ts +88 -0
- package/src/cli/commands/build.ts +70 -87
- package/src/cli/commands/db.ts +21 -22
- package/src/cli/commands/deploy.ts +17 -5
- package/src/cli/commands/dev.ts +22 -14
- package/src/cli/commands/format.ts +8 -2
- package/src/cli/commands/gen/env.ts +53 -0
- package/src/cli/commands/gen/openapi.ts +1 -1
- package/src/cli/commands/gen/resource.ts +15 -0
- package/src/cli/commands/gen.ts +7 -1
- package/src/cli/commands/init.ts +74 -30
- package/src/cli/commands/lint.ts +8 -2
- package/src/cli/commands/test.ts +8 -3
- package/src/cli/commands/typecheck.ts +5 -1
- package/src/cli/commands/verify.ts +5 -3
- package/src/cli/defineConfig.ts +49 -7
- package/src/cli/index.ts +0 -1
- package/src/cli/services/AlephaCliUtils.ts +39 -589
- package/src/cli/services/PackageManagerUtils.ts +301 -0
- package/src/cli/services/ProjectScaffolder.ts +306 -0
- package/src/command/helpers/Runner.spec.ts +2 -2
- package/src/command/helpers/Runner.ts +16 -4
- package/src/command/primitives/$command.ts +0 -6
- package/src/command/providers/CliProvider.ts +1 -3
- package/src/core/Alepha.ts +42 -0
- package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +2 -0
- package/src/core/primitives/$hook.ts +6 -2
- package/src/core/primitives/$module.spec.ts +4 -0
- package/src/core/providers/AlsProvider.ts +1 -1
- package/src/core/providers/CodecManager.spec.ts +12 -6
- package/src/core/providers/CodecManager.ts +26 -6
- package/src/core/providers/EventManager.ts +169 -13
- package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +621 -0
- package/src/core/providers/KeylessJsonSchemaCodec.ts +407 -0
- package/src/core/providers/StateManager.spec.ts +27 -16
- package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
- package/src/email/providers/LocalEmailProvider.ts +52 -15
- package/src/email/providers/NodemailerEmailProvider.ts +167 -56
- package/src/file/errors/FileError.ts +7 -0
- package/src/file/index.ts +9 -1
- package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
- package/src/logger/index.ts +15 -3
- package/src/mcp/transports/StdioMcpTransport.ts +1 -1
- package/src/orm/index.browser.ts +1 -19
- package/src/orm/index.bun.ts +77 -0
- package/src/orm/index.shared-server.ts +22 -0
- package/src/orm/index.shared.ts +15 -0
- package/src/orm/index.ts +13 -39
- package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
- package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
- package/src/orm/services/Repository.ts +8 -0
- package/src/queue/core/providers/WorkerProvider.spec.ts +48 -32
- package/src/redis/index.bun.ts +35 -0
- package/src/redis/providers/BunRedisProvider.ts +12 -43
- package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
- package/src/redis/providers/NodeRedisProvider.ts +16 -34
- package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
- package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
- package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
- package/src/security/index.browser.ts +5 -0
- package/src/security/index.ts +90 -7
- package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
- package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
- package/src/security/primitives/$role.ts +5 -5
- package/src/security/primitives/$serviceAccount.spec.ts +5 -5
- package/src/security/primitives/$serviceAccount.ts +3 -3
- package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
- package/src/server/auth/primitives/$auth.ts +10 -10
- package/src/server/auth/primitives/$authCredentials.ts +3 -3
- package/src/server/auth/primitives/$authGithub.ts +3 -3
- package/src/server/auth/primitives/$authGoogle.ts +3 -3
- package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
- package/src/server/cache/providers/ServerCacheProvider.spec.ts +183 -0
- package/src/server/cache/providers/ServerCacheProvider.ts +95 -10
- package/src/server/compress/providers/ServerCompressProvider.ts +61 -2
- package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
- package/src/server/core/helpers/ServerReply.ts +2 -2
- package/src/server/core/providers/NodeHttpServerProvider.ts +25 -6
- package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
- package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
- package/src/server/core/providers/ServerProvider.ts +155 -22
- package/src/server/core/providers/ServerRouterProvider.ts +259 -115
- package/src/server/core/providers/ServerTimingProvider.ts +2 -2
- package/src/server/links/index.ts +1 -1
- package/src/server/links/providers/LinkProvider.ts +1 -1
- package/src/server/static/providers/ServerStaticProvider.ts +10 -0
- package/src/server/swagger/index.ts +1 -1
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +5 -8
- package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
- package/src/sms/providers/LocalSmsProvider.ts +8 -7
- package/src/vite/helpers/boot.ts +28 -17
- package/src/vite/helpers/importViteReact.ts +13 -0
- package/src/vite/index.ts +1 -21
- package/src/vite/plugins/viteAlephaDev.ts +16 -1
- package/src/vite/plugins/viteAlephaSsrPreload.ts +222 -0
- package/src/vite/tasks/buildClient.ts +11 -0
- package/src/vite/tasks/buildServer.ts +59 -4
- package/src/vite/tasks/devServer.ts +71 -0
- package/src/vite/tasks/generateCloudflare.ts +7 -0
- package/src/vite/tasks/index.ts +2 -1
- package/dist/server/security/index.browser.js +0 -13
- package/dist/server/security/index.browser.js.map +0 -1
- package/dist/server/security/index.d.ts +0 -173
- package/dist/server/security/index.d.ts.map +0 -1
- package/dist/server/security/index.js +0 -311
- package/dist/server/security/index.js.map +0 -1
- package/src/cli/assets/appRouterTs.ts +0 -9
- package/src/cli/assets/mainTs.ts +0 -13
- package/src/cli/assets/viteConfigTs.ts +0 -14
- package/src/cli/commands/run.ts +0 -24
- package/src/server/security/index.browser.ts +0 -10
- package/src/server/security/index.ts +0 -94
- package/src/vite/plugins/viteAlepha.ts +0 -37
- package/src/vite/plugins/viteAlephaBuild.ts +0 -281
- /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
- /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
|
-
|
|
140
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
15
|
+
* Redirect to a given URL with optional status code (default 301).
|
|
16
16
|
*/
|
|
17
|
-
public redirect(url: string, status: number =
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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:
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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 (
|
|
46
|
-
|
|
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
|
-
*
|
|
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
|
|
185
|
+
const rawUrl = req.url!;
|
|
186
|
+
const { route, params } = this.router.match(`/${req.method}${rawUrl}`);
|
|
61
187
|
|
|
62
|
-
if (this.isViteNotFound(
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
|
180
|
-
|
|
181
|
-
|
|
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) {
|