alepha 0.14.4 → 0.15.1
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 +44 -102
- package/dist/api/audits/index.d.ts +331 -443
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +2 -2
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +0 -113
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +2 -3
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +151 -262
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/notifications/index.browser.js +4 -4
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +164 -276
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +4 -4
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +265 -377
- 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 +195 -301
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +203 -184
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js +2 -2
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/cli/index.d.ts +5900 -165
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1481 -639
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +8 -4
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +29 -25
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +563 -54
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +175 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +564 -54
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +563 -54
- package/dist/core/index.native.js.map +1 -1
- 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 +89 -42
- 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 +7969 -2
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +22 -22
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts +134 -1
- 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.map +1 -1
- package/dist/lock/redis/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +1 -2
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +1 -5
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +19 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +28 -4
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/chunk-DH6iiROE.js +38 -0
- package/dist/orm/index.browser.js +9 -9
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +2821 -0
- package/dist/orm/index.bun.js.map +1 -0
- package/dist/orm/index.d.ts +318 -169
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +2086 -1776
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +4 -4
- 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 +13 -31
- 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.map +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +83 -1
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +393 -1
- package/dist/scheduler/index.js.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 +598 -112
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1808 -97
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1200 -175
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1268 -37
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +6 -3
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +1 -1
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts.map +1 -1
- 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.d.ts +115 -13
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +321 -139
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +0 -1
- 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.map +1 -1
- package/dist/server/links/index.browser.js +9 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +1 -2
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +14 -7
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +514 -1
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4462 -4
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/proxy/index.d.ts +0 -1
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/swagger/index.d.ts +1 -2
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +1 -2
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +3 -1
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +10 -10
- package/dist/sms/index.js.map +1 -1
- package/dist/thread/index.d.ts +0 -1
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/thread/index.js +2 -2
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/vite/index.d.ts +6315 -149
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +140 -469
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +9 -9
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +28 -28
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +9 -9
- package/dist/websocket/index.js.map +1 -1
- package/package.json +13 -18
- 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 +27 -18
- package/src/api/users/services/UserService.ts +7 -7
- package/src/batch/providers/BatchProvider.ts +1 -2
- package/src/cli/apps/AlephaCli.ts +2 -2
- package/src/cli/apps/AlephaPackageBuilderCli.ts +47 -20
- package/src/cli/assets/apiHelloControllerTs.ts +19 -0
- package/src/cli/assets/apiIndexTs.ts +16 -0
- package/src/cli/assets/biomeJson.ts +2 -1
- package/src/cli/assets/claudeMd.ts +308 -0
- package/src/cli/assets/dummySpecTs.ts +2 -1
- package/src/cli/assets/editorconfig.ts +2 -1
- package/src/cli/assets/mainBrowserTs.ts +4 -3
- package/src/cli/assets/mainCss.ts +24 -0
- package/src/cli/assets/mainServerTs.ts +24 -0
- package/src/cli/assets/tsconfigJson.ts +2 -1
- package/src/cli/assets/webAppRouterTs.ts +16 -0
- package/src/cli/assets/webHelloComponentTsx.ts +20 -0
- package/src/cli/assets/webIndexTs.ts +16 -0
- package/src/cli/atoms/appEntryOptions.ts +13 -0
- package/src/cli/atoms/buildOptions.ts +1 -1
- package/src/cli/atoms/changelogOptions.ts +1 -1
- package/src/cli/commands/build.ts +97 -61
- package/src/cli/commands/db.ts +21 -18
- package/src/cli/commands/deploy.ts +17 -5
- package/src/cli/commands/dev.ts +26 -47
- package/src/cli/commands/gen/env.ts +1 -1
- package/src/cli/commands/init.ts +79 -25
- package/src/cli/commands/lint.ts +9 -3
- package/src/cli/commands/test.ts +8 -2
- package/src/cli/commands/typecheck.ts +5 -1
- package/src/cli/commands/verify.ts +4 -2
- package/src/cli/defineConfig.ts +9 -0
- package/src/cli/index.ts +2 -1
- package/src/cli/providers/AppEntryProvider.ts +131 -0
- package/src/cli/providers/ViteBuildProvider.ts +82 -0
- package/src/cli/providers/ViteDevServerProvider.ts +350 -0
- package/src/cli/providers/ViteTemplateProvider.ts +27 -0
- package/src/cli/services/AlephaCliUtils.ts +72 -602
- package/src/cli/services/PackageManagerUtils.ts +308 -0
- package/src/cli/services/ProjectScaffolder.ts +329 -0
- package/src/command/helpers/Runner.ts +15 -3
- package/src/core/Alepha.ts +2 -8
- 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/primitives/$module.ts +12 -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 +878 -0
- package/src/core/providers/KeylessJsonSchemaCodec.ts +789 -0
- package/src/core/providers/SchemaValidator.spec.ts +236 -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/providers/PrettyFormatterProvider.ts +0 -9
- package/src/mcp/errors/McpError.ts +30 -0
- package/src/mcp/index.ts +3 -0
- package/src/mcp/transports/SseMcpTransport.ts +16 -6
- 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 +19 -39
- package/src/orm/providers/DrizzleKitProvider.ts +3 -5
- 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 +19 -0
- 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.ts +1 -1
- package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
- package/src/server/core/index.ts +1 -1
- package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
- package/src/server/core/providers/NodeHttpServerProvider.ts +92 -24
- 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 +144 -24
- package/src/server/core/providers/ServerRouterProvider.ts +259 -115
- package/src/server/core/providers/ServerTimingProvider.ts +2 -2
- package/src/server/links/atoms/apiLinksAtom.ts +7 -0
- package/src/server/links/index.browser.ts +2 -0
- package/src/server/links/index.ts +3 -1
- package/src/server/links/providers/LinkProvider.ts +1 -1
- package/src/server/swagger/index.ts +1 -1
- package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
- package/src/sms/providers/LocalSmsProvider.ts +8 -7
- package/src/vite/index.ts +3 -2
- package/src/vite/tasks/buildClient.ts +0 -1
- package/src/vite/tasks/buildServer.ts +80 -22
- package/src/vite/tasks/copyAssets.ts +5 -4
- package/src/vite/tasks/generateCloudflare.ts +7 -0
- package/src/vite/tasks/generateSitemap.ts +64 -23
- package/src/vite/tasks/index.ts +0 -2
- package/src/vite/tasks/prerenderPages.ts +49 -24
- 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/indexHtml.ts +0 -15
- package/src/cli/assets/mainTs.ts +0 -13
- package/src/cli/commands/format.ts +0 -17
- package/src/server/security/index.browser.ts +0 -10
- package/src/server/security/index.ts +0 -94
- package/src/vite/helpers/boot.ts +0 -106
- package/src/vite/plugins/viteAlephaDev.ts +0 -177
- package/src/vite/tasks/devServer.ts +0 -69
- package/src/vite/tasks/runAlepha.ts +0 -270
- /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
- /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AlephaError } from "alepha";
|
|
2
|
-
import type {
|
|
2
|
+
import type { IssuerPrimitive } from "alepha/security";
|
|
3
3
|
import {
|
|
4
4
|
$auth,
|
|
5
5
|
type CredentialsFn,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
* Uses username and password to authenticate users.
|
|
14
14
|
*/
|
|
15
15
|
export const $authCredentials = (
|
|
16
|
-
realm:
|
|
16
|
+
realm: IssuerPrimitive & WithLoginFn,
|
|
17
17
|
options: Partial<CredentialsOptions> = {},
|
|
18
18
|
) => {
|
|
19
19
|
const name = "credentials";
|
|
@@ -29,7 +29,7 @@ export const $authCredentials = (
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
return $auth({
|
|
32
|
-
realm,
|
|
32
|
+
issuer: realm,
|
|
33
33
|
name,
|
|
34
34
|
credentials: {
|
|
35
35
|
account,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $context, AlephaError, t } from "alepha";
|
|
2
|
-
import type {
|
|
2
|
+
import type { IssuerPrimitive } from "alepha/security";
|
|
3
3
|
import type { OAuth2Profile } from "../providers/ServerAuthProvider.ts";
|
|
4
4
|
import {
|
|
5
5
|
$auth,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
* - `GITHUB_CLIENT_SECRET`: The client secret obtained from the GitHub Developer Settings.
|
|
20
20
|
*/
|
|
21
21
|
export const $authGithub = (
|
|
22
|
-
realm:
|
|
22
|
+
realm: IssuerPrimitive & WithLinkFn,
|
|
23
23
|
options: Partial<OidcOptions> = {},
|
|
24
24
|
) => {
|
|
25
25
|
const { alepha } = $context();
|
|
@@ -45,7 +45,7 @@ export const $authGithub = (
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
return $auth({
|
|
48
|
-
realm,
|
|
48
|
+
issuer: realm,
|
|
49
49
|
name,
|
|
50
50
|
oauth: {
|
|
51
51
|
clientId: env.GITHUB_CLIENT_ID!,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $context, AlephaError, t } from "alepha";
|
|
2
|
-
import type {
|
|
2
|
+
import type { IssuerPrimitive } from "alepha/security";
|
|
3
3
|
import {
|
|
4
4
|
$auth,
|
|
5
5
|
type LinkAccountFn,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
* - `GOOGLE_CLIENT_SECRET`: The client secret obtained from the Google Developer Console.
|
|
19
19
|
*/
|
|
20
20
|
export const $authGoogle = (
|
|
21
|
-
realm:
|
|
21
|
+
realm: IssuerPrimitive & WithLinkFn,
|
|
22
22
|
options: Partial<OidcOptions> = {},
|
|
23
23
|
) => {
|
|
24
24
|
const { alepha } = $context();
|
|
@@ -44,7 +44,7 @@ export const $authGoogle = (
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
return $auth({
|
|
47
|
-
realm,
|
|
47
|
+
issuer: realm,
|
|
48
48
|
name,
|
|
49
49
|
oidc: {
|
|
50
50
|
issuer: "https://accounts.google.com",
|
|
@@ -71,8 +71,8 @@ export class ServerAuthProvider {
|
|
|
71
71
|
|
|
72
72
|
for (const identity of this.identities) {
|
|
73
73
|
if (filters.realmName) {
|
|
74
|
-
const
|
|
75
|
-
if (!
|
|
74
|
+
const issuer = identity.issuer;
|
|
75
|
+
if (!issuer || issuer.name !== filters.realmName) {
|
|
76
76
|
continue;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -145,7 +145,7 @@ export class ServerAuthProvider {
|
|
|
145
145
|
// [feature] support for auth providers with fallback
|
|
146
146
|
if (!request.headers.authorization) {
|
|
147
147
|
for (const provider of this.identities) {
|
|
148
|
-
if (
|
|
148
|
+
if ("fallback" in provider.options && !!provider.options.fallback) {
|
|
149
149
|
const token = await provider.options.fallback();
|
|
150
150
|
if (token) {
|
|
151
151
|
request.headers.authorization = `Bearer ${token}`;
|
|
@@ -340,8 +340,8 @@ export class ServerAuthProvider {
|
|
|
340
340
|
realm: query.realm,
|
|
341
341
|
});
|
|
342
342
|
|
|
343
|
-
const
|
|
344
|
-
if (!
|
|
343
|
+
const issuer = provider.issuer;
|
|
344
|
+
if (!issuer) {
|
|
345
345
|
throw new SecurityError(
|
|
346
346
|
`Auth provider '${query.provider}' does not support password grant`,
|
|
347
347
|
);
|
|
@@ -373,7 +373,7 @@ export class ServerAuthProvider {
|
|
|
373
373
|
|
|
374
374
|
const tokens = {
|
|
375
375
|
provider: query.provider,
|
|
376
|
-
...(await
|
|
376
|
+
...(await issuer.createToken(user)),
|
|
377
377
|
};
|
|
378
378
|
|
|
379
379
|
// for web applications, we store tokens in cookies
|
|
@@ -519,10 +519,10 @@ export class ServerAuthProvider {
|
|
|
519
519
|
|
|
520
520
|
this.authorizationCode.del({ cookies });
|
|
521
521
|
|
|
522
|
-
const
|
|
522
|
+
const issuer = provider.issuer;
|
|
523
523
|
|
|
524
524
|
// external, full OIDC System (e.g. Keycloak, Auth0)
|
|
525
|
-
if (!
|
|
525
|
+
if (!issuer) {
|
|
526
526
|
this.setTokens(externalTokens, cookies);
|
|
527
527
|
reply.redirect(redirectUri);
|
|
528
528
|
return;
|
|
@@ -531,7 +531,7 @@ export class ServerAuthProvider {
|
|
|
531
531
|
// internal, we need to create our own tokens
|
|
532
532
|
|
|
533
533
|
const user = await provider.user(externalTokens);
|
|
534
|
-
const tokens = await
|
|
534
|
+
const tokens = await issuer.createToken(user);
|
|
535
535
|
|
|
536
536
|
this.setTokens(
|
|
537
537
|
{
|
|
@@ -570,9 +570,9 @@ export class ServerAuthProvider {
|
|
|
570
570
|
this.tokens.del({ cookies });
|
|
571
571
|
|
|
572
572
|
// for internal providers, we can delete the session - if available
|
|
573
|
-
if (
|
|
573
|
+
if (provider.issuer && tokens.refresh_token) {
|
|
574
574
|
const onDeleteSession =
|
|
575
|
-
provider.
|
|
575
|
+
provider.issuer.options.settings?.onDeleteSession;
|
|
576
576
|
if (onDeleteSession) {
|
|
577
577
|
try {
|
|
578
578
|
await onDeleteSession(tokens.refresh_token);
|
|
@@ -635,8 +635,8 @@ export class ServerAuthProvider {
|
|
|
635
635
|
return false;
|
|
636
636
|
}
|
|
637
637
|
|
|
638
|
-
// If realm filter is specified, match against provider's
|
|
639
|
-
if (realmName && identity.
|
|
638
|
+
// If realm filter is specified, match against provider's issuer
|
|
639
|
+
if (realmName && identity.issuer?.name !== realmName) {
|
|
640
640
|
return false;
|
|
641
641
|
}
|
|
642
642
|
|
|
@@ -204,7 +204,7 @@ export class ServerCacheProvider {
|
|
|
204
204
|
|
|
205
205
|
protected readonly onSend = $hook({
|
|
206
206
|
on: "server:onSend",
|
|
207
|
-
handler:
|
|
207
|
+
handler: ({ route, request }) => {
|
|
208
208
|
// before sending the response, check if the ETag matches
|
|
209
209
|
// and if so, return a 304 Not Modified response
|
|
210
210
|
// -> this is only relevant for etag-only routes, not cached routes <-
|
|
@@ -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,
|
package/src/server/core/index.ts
CHANGED
|
@@ -141,7 +141,7 @@ export const AlephaServer = $module({
|
|
|
141
141
|
ServerRouterProvider,
|
|
142
142
|
],
|
|
143
143
|
register: (alepha: Alepha) => {
|
|
144
|
-
if (!alepha.isServerless()
|
|
144
|
+
if (!alepha.isServerless()) {
|
|
145
145
|
if (alepha.isBun()) {
|
|
146
146
|
alepha.with({
|
|
147
147
|
optional: true,
|
|
@@ -107,7 +107,7 @@ export class BunHttpServerProvider extends ServerProvider {
|
|
|
107
107
|
},
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
this.log.info(`Server listening on ${this.hostname}
|
|
110
|
+
this.log.info(`Server listening on ${this.hostname}/`);
|
|
111
111
|
} catch (err) {
|
|
112
112
|
this.log.error("Failed to start Bun server", err);
|
|
113
113
|
throw err;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
3
|
+
import { NodeHttpServerProvider } from "./NodeHttpServerProvider.ts";
|
|
4
|
+
|
|
5
|
+
describe("NodeHttpServerProvider", () => {
|
|
6
|
+
describe("graceful shutdown", () => {
|
|
7
|
+
let alepha: Alepha;
|
|
8
|
+
let server: NodeHttpServerProvider;
|
|
9
|
+
|
|
10
|
+
afterEach(async () => {
|
|
11
|
+
await alepha?.stop().catch(() => {});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("dev mode: destroys connections immediately on close", async () => {
|
|
15
|
+
alepha = Alepha.create({ env: { NODE_ENV: "development" } });
|
|
16
|
+
alepha.with(NodeHttpServerProvider);
|
|
17
|
+
|
|
18
|
+
await alepha.start();
|
|
19
|
+
server = alepha.inject(NodeHttpServerProvider);
|
|
20
|
+
|
|
21
|
+
// Make a request to establish connection
|
|
22
|
+
await fetch(`${server.hostname}/`);
|
|
23
|
+
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
await alepha.stop();
|
|
26
|
+
const elapsed = Date.now() - startTime;
|
|
27
|
+
|
|
28
|
+
// Should close instantly (under 100ms)
|
|
29
|
+
expect(elapsed).toBeLessThan(100);
|
|
30
|
+
expect(server.getConnectionsCount()).toBe(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("production mode: waits for connections then closes", async () => {
|
|
34
|
+
alepha = Alepha.create({ env: { NODE_ENV: "production" } });
|
|
35
|
+
alepha.with(NodeHttpServerProvider);
|
|
36
|
+
|
|
37
|
+
await alepha.start();
|
|
38
|
+
server = alepha.inject(NodeHttpServerProvider);
|
|
39
|
+
server.options.shutdownTimeout = 500;
|
|
40
|
+
|
|
41
|
+
// Make a request to establish keep-alive connection
|
|
42
|
+
await fetch(`${server.hostname}/`);
|
|
43
|
+
|
|
44
|
+
const startTime = Date.now();
|
|
45
|
+
await alepha.stop();
|
|
46
|
+
const elapsed = Date.now() - startTime;
|
|
47
|
+
|
|
48
|
+
// In production, should not be instant (waits for graceful close or timeout)
|
|
49
|
+
// But should complete within timeout
|
|
50
|
+
expect(elapsed).toBeLessThan(server.options.shutdownTimeout + 100);
|
|
51
|
+
expect(server.getConnectionsCount()).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("production mode: forces close after timeout", async () => {
|
|
55
|
+
alepha = Alepha.create({ env: { NODE_ENV: "production" } });
|
|
56
|
+
alepha.with(NodeHttpServerProvider);
|
|
57
|
+
|
|
58
|
+
await alepha.start();
|
|
59
|
+
server = alepha.inject(NodeHttpServerProvider);
|
|
60
|
+
server.options.shutdownTimeout = 50;
|
|
61
|
+
|
|
62
|
+
// Make a request to establish connection
|
|
63
|
+
await fetch(`${server.hostname}/`);
|
|
64
|
+
|
|
65
|
+
const startTime = Date.now();
|
|
66
|
+
await alepha.stop();
|
|
67
|
+
const elapsed = Date.now() - startTime;
|
|
68
|
+
|
|
69
|
+
// Should close around timeout
|
|
70
|
+
expect(elapsed).toBeLessThan(200);
|
|
71
|
+
expect(server.getConnectionsCount()).toBe(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("connections are tracked and cleared", async () => {
|
|
75
|
+
alepha = Alepha.create({ env: { NODE_ENV: "development" } });
|
|
76
|
+
alepha.with(NodeHttpServerProvider);
|
|
77
|
+
|
|
78
|
+
await alepha.start();
|
|
79
|
+
server = alepha.inject(NodeHttpServerProvider);
|
|
80
|
+
|
|
81
|
+
// Make multiple requests
|
|
82
|
+
await fetch(`${server.hostname}/`);
|
|
83
|
+
await fetch(`${server.hostname}/`);
|
|
84
|
+
|
|
85
|
+
await alepha.stop();
|
|
86
|
+
|
|
87
|
+
// All connections cleared after stop
|
|
88
|
+
expect(server.getConnectionsCount()).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("rejects new requests during shutdown", async () => {
|
|
92
|
+
alepha = Alepha.create({ env: { NODE_ENV: "production" } });
|
|
93
|
+
alepha.with(NodeHttpServerProvider);
|
|
94
|
+
|
|
95
|
+
await alepha.start();
|
|
96
|
+
server = alepha.inject(NodeHttpServerProvider);
|
|
97
|
+
server.options.shutdownTimeout = 500;
|
|
98
|
+
|
|
99
|
+
// Establish a connection to keep server busy
|
|
100
|
+
await fetch(`${server.hostname}/`);
|
|
101
|
+
|
|
102
|
+
// Start shutdown (don't await yet)
|
|
103
|
+
const stopPromise = alepha.stop();
|
|
104
|
+
|
|
105
|
+
// Give server.close() time to be called
|
|
106
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
107
|
+
|
|
108
|
+
// New request should fail (server no longer accepting connections)
|
|
109
|
+
let error: Error | null = null;
|
|
110
|
+
try {
|
|
111
|
+
await fetch(`${server.hostname}/`, {
|
|
112
|
+
signal: AbortSignal.timeout(100),
|
|
113
|
+
});
|
|
114
|
+
} catch (e) {
|
|
115
|
+
error = e as Error;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Should get a connection error (ECONNREFUSED or similar)
|
|
119
|
+
expect(error).not.toBeNull();
|
|
120
|
+
|
|
121
|
+
// Wait for shutdown to complete
|
|
122
|
+
await stopPromise;
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type Server,
|
|
5
5
|
type ServerResponse,
|
|
6
6
|
} from "node:http";
|
|
7
|
+
import type { Socket } from "node:net";
|
|
7
8
|
import { $env, $hook, $inject, Alepha, type Static, t } from "alepha";
|
|
8
9
|
import { DateTimeProvider } from "alepha/datetime";
|
|
9
10
|
import { $logger } from "alepha/logger";
|
|
@@ -34,6 +35,24 @@ export class NodeHttpServerProvider extends ServerProvider {
|
|
|
34
35
|
protected readonly env = $env(envSchema);
|
|
35
36
|
protected readonly router = $inject(ServerRouterProvider);
|
|
36
37
|
|
|
38
|
+
/** Track active connections for fast shutdown */
|
|
39
|
+
protected readonly connections = new Set<Socket>();
|
|
40
|
+
|
|
41
|
+
/** Get number of active connections */
|
|
42
|
+
public getConnectionsCount(): number {
|
|
43
|
+
return this.connections.size;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Server options */
|
|
47
|
+
public readonly options = {
|
|
48
|
+
/**
|
|
49
|
+
* Graceful shutdown timeout in ms.
|
|
50
|
+
* After this, remaining connections are forcefully closed.
|
|
51
|
+
* @default 30000
|
|
52
|
+
*/
|
|
53
|
+
shutdownTimeout: 10000,
|
|
54
|
+
};
|
|
55
|
+
|
|
37
56
|
public get hostname(): string {
|
|
38
57
|
if (this.server.listening) {
|
|
39
58
|
const address = this.server.address();
|
|
@@ -44,13 +63,29 @@ export class NodeHttpServerProvider extends ServerProvider {
|
|
|
44
63
|
return `http://${this.env.SERVER_HOST}:${this.env.SERVER_PORT}`;
|
|
45
64
|
}
|
|
46
65
|
|
|
66
|
+
// Pre-bound error handler to avoid function allocation per request
|
|
67
|
+
protected readonly handleRequestError = (res: ServerResponse, err: Error) => {
|
|
68
|
+
this.log.error("Error handling request", err);
|
|
69
|
+
res.statusCode = 500;
|
|
70
|
+
res.end("Internal Server Error");
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// P3: Pre-allocated event object to avoid { req, res } allocation per request
|
|
74
|
+
// Safe because handleNodeRequest completes before the next request reuses this object
|
|
75
|
+
protected readonly nodeRequestEvent = {
|
|
76
|
+
req: null as unknown as IncomingMessage,
|
|
77
|
+
res: null as unknown as ServerResponse,
|
|
78
|
+
};
|
|
79
|
+
|
|
47
80
|
public readonly server = this.createHttpServer((req, res) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
// Reuse pre-allocated event object instead of creating { req, res } per request
|
|
82
|
+
const ev = this.nodeRequestEvent;
|
|
83
|
+
ev.req = req;
|
|
84
|
+
ev.res = res;
|
|
85
|
+
// Note: handleNodeRequest returns a promise that resolves after response is sent
|
|
86
|
+
this.handleNodeRequest(ev).catch((err) =>
|
|
87
|
+
this.handleRequestError(res, err),
|
|
88
|
+
);
|
|
54
89
|
});
|
|
55
90
|
|
|
56
91
|
public readonly start = $hook({
|
|
@@ -64,25 +99,27 @@ export class NodeHttpServerProvider extends ServerProvider {
|
|
|
64
99
|
protected createHttpServer(
|
|
65
100
|
func: (req: IncomingMessage, res: ServerResponse) => void,
|
|
66
101
|
): Server {
|
|
67
|
-
|
|
102
|
+
const server = createServer(
|
|
68
103
|
{
|
|
69
104
|
// nov 25 - keep connections alive for better performance, cuz we http/1.1 by default
|
|
70
105
|
keepAlive: this.alepha.isProduction(),
|
|
71
106
|
},
|
|
72
107
|
func,
|
|
73
108
|
);
|
|
109
|
+
|
|
110
|
+
// Track connections for fast shutdown
|
|
111
|
+
server.on("connection", (socket) => {
|
|
112
|
+
this.connections.add(socket);
|
|
113
|
+
socket.on("close", () => this.connections.delete(socket));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return server;
|
|
74
117
|
}
|
|
75
118
|
|
|
76
119
|
protected readonly stop = $hook({
|
|
77
120
|
on: "stop",
|
|
78
121
|
handler: async () => {
|
|
79
|
-
|
|
80
|
-
await this.close();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// do not await in development & test
|
|
85
|
-
this.close().catch(() => {});
|
|
122
|
+
await this.close();
|
|
86
123
|
},
|
|
87
124
|
});
|
|
88
125
|
|
|
@@ -96,7 +133,7 @@ export class NodeHttpServerProvider extends ServerProvider {
|
|
|
96
133
|
|
|
97
134
|
await new Promise<void>((resolve, reject) => {
|
|
98
135
|
this.server?.listen(port, this.env.SERVER_HOST, () => {
|
|
99
|
-
this.log.info(`Server listening on ${this.hostname}
|
|
136
|
+
this.log.info(`Server listening on ${this.hostname}/`);
|
|
100
137
|
resolve();
|
|
101
138
|
});
|
|
102
139
|
|
|
@@ -107,18 +144,49 @@ export class NodeHttpServerProvider extends ServerProvider {
|
|
|
107
144
|
}
|
|
108
145
|
|
|
109
146
|
protected async close() {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
147
|
+
// Dev/Test: instant shutdown (destroy connections immediately)
|
|
148
|
+
// Production: graceful shutdown (wait for requests to complete, then close)
|
|
149
|
+
if (!this.alepha.isProduction()) {
|
|
150
|
+
this.destroyAllConnections();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Stop accepting new connections
|
|
154
|
+
const closePromise = new Promise<void>((resolve, reject) => {
|
|
155
|
+
this.server?.close((err) => (err ? reject(err) : resolve()));
|
|
118
156
|
});
|
|
119
157
|
|
|
120
|
-
|
|
158
|
+
if (this.alepha.isProduction() && this.connections.size > 0) {
|
|
159
|
+
// In production, wait for connections with timeout
|
|
160
|
+
const timeout = this.options.shutdownTimeout;
|
|
161
|
+
|
|
162
|
+
// Set up timeout to force-close connections
|
|
163
|
+
const timeoutId = setTimeout(() => {
|
|
164
|
+
if (this.connections.size > 0) {
|
|
165
|
+
this.log.warn(
|
|
166
|
+
`Shutdown timeout (${timeout}ms) reached, forcing ${this.connections.size} connections to close`,
|
|
167
|
+
);
|
|
168
|
+
// Destroy sockets - this triggers 'close' events which eventually resolves closePromise
|
|
169
|
+
for (const socket of this.connections) {
|
|
170
|
+
socket.destroy();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}, timeout);
|
|
174
|
+
|
|
175
|
+
// Wait for server to fully close (all connections closed)
|
|
176
|
+
await closePromise;
|
|
177
|
+
clearTimeout(timeoutId);
|
|
178
|
+
this.connections.clear();
|
|
179
|
+
} else {
|
|
180
|
+
await closePromise;
|
|
181
|
+
}
|
|
121
182
|
|
|
122
183
|
this.log.info("Server closed");
|
|
123
184
|
}
|
|
185
|
+
|
|
186
|
+
protected destroyAllConnections() {
|
|
187
|
+
for (const socket of this.connections) {
|
|
188
|
+
socket.destroy();
|
|
189
|
+
}
|
|
190
|
+
this.connections.clear();
|
|
191
|
+
}
|
|
124
192
|
}
|
|
@@ -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 || request.metadata.vite) {
|
|
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 || request.metadata.vite) {
|
|
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
|
}
|