alepha 0.15.3 → 0.15.5
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 +26 -11
- package/dist/api/audits/index.d.ts +335 -335
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +11 -3
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +3 -3
- package/dist/api/files/index.js +4 -3
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +198 -155
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +103 -5
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +198 -198
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js +3 -3
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +1 -0
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +3 -3
- package/dist/api/notifications/index.js +4 -3
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +263 -263
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +41 -30
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +383 -77
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +284 -72
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +131 -131
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +3 -3
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/batch/index.d.ts +3 -3
- package/dist/batch/index.js +3 -3
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +3 -3
- package/dist/bucket/index.js +6 -6
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/core/index.d.ts +3 -3
- package/dist/cache/core/index.js +3 -3
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cli/index.d.ts +5612 -20
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +122 -91
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +11 -4
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +8 -6
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +4 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts +3 -3
- package/dist/datetime/index.js +3 -3
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +16 -16
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +10562 -10
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +3 -3
- package/dist/fake/index.js +3 -3
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +9 -4
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +12 -4
- package/dist/lock/core/index.js.map +1 -1
- package/dist/logger/index.d.ts +3 -3
- package/dist/logger/index.js +6 -3
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +3 -3
- package/dist/mcp/index.js +3 -3
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.d.ts +12 -12
- package/dist/orm/index.js +4 -4
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +3 -3
- package/dist/queue/core/index.js +3 -3
- package/dist/queue/core/index.js.map +1 -1
- package/dist/react/auth/index.browser.js +2 -1
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.d.ts +3 -3
- package/dist/react/auth/index.js +5 -4
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.d.ts +6 -6
- package/dist/react/core/index.js +3 -3
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +3 -3
- package/dist/react/form/index.js +3 -3
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.d.ts +3 -3
- package/dist/react/head/index.js +3 -3
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +3 -3
- package/dist/react/i18n/index.js +3 -3
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.css +337 -0
- package/dist/react/intro/index.css.map +1 -0
- package/dist/react/intro/index.d.ts +10 -0
- package/dist/react/intro/index.d.ts.map +1 -0
- package/dist/react/intro/index.js +222 -0
- package/dist/react/intro/index.js.map +1 -0
- package/dist/react/router/index.browser.js +2 -2
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +11 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +21 -11
- package/dist/react/router/index.js.map +1 -1
- package/dist/redis/index.d.ts +22 -22
- package/dist/redis/index.js +3 -3
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +3 -3
- package/dist/retry/index.js +3 -3
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +16 -4
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +45 -7
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +3 -3
- package/dist/security/index.js +5 -5
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +3 -3
- package/dist/server/auth/index.js +3 -3
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +3 -3
- package/dist/server/cache/index.js +3 -3
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +3 -3
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +4 -3
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/cookies/index.d.ts +3 -3
- package/dist/server/cookies/index.js +3 -3
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +14 -25
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +13 -29
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +3 -3
- package/dist/server/cors/index.js +3 -3
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/health/index.d.ts +20 -20
- package/dist/server/health/index.js +3 -3
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/helmet/index.d.ts +3 -3
- package/dist/server/helmet/index.js +3 -3
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.d.ts +42 -42
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +4 -4
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +3 -3
- package/dist/server/metrics/index.js +3 -3
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts +3 -3
- package/dist/server/multipart/index.js +3 -3
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts +3 -3
- package/dist/server/proxy/index.js +3 -3
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +3 -3
- package/dist/server/rate-limit/index.js +3 -3
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.d.ts +3 -3
- package/dist/server/static/index.js +6 -6
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +3 -3
- package/dist/server/swagger/index.js +6 -6
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +3 -3
- package/dist/sms/index.js +6 -6
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.d.ts +3 -3
- package/dist/system/index.js +3 -3
- package/dist/system/index.js.map +1 -1
- package/dist/thread/index.d.ts +3 -3
- package/dist/thread/index.js +3 -3
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts +3 -3
- package/dist/topic/core/index.js +3 -3
- package/dist/topic/core/index.js.map +1 -1
- package/dist/vite/index.d.ts +6286 -4
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +28 -2
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +37 -37
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +3 -3
- package/dist/websocket/index.js.map +1 -1
- package/package.json +12 -4
- package/src/api/audits/controllers/AdminAuditController.ts +8 -0
- package/src/api/audits/index.ts +3 -3
- package/src/api/files/controllers/AdminFileStatsController.ts +1 -0
- package/src/api/files/index.ts +3 -3
- package/src/api/jobs/controllers/AdminJobController.ts +18 -2
- package/src/api/jobs/index.ts +4 -3
- package/src/api/jobs/services/JobAudits.spec.ts +89 -0
- package/src/api/jobs/services/JobAudits.ts +101 -0
- package/src/api/keys/index.ts +3 -3
- package/src/api/notifications/controllers/AdminNotificationController.ts +1 -0
- package/src/api/notifications/index.ts +3 -3
- package/src/api/parameters/controllers/AdminConfigController.ts +10 -0
- package/src/api/parameters/index.ts +5 -3
- package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1 -1
- package/src/api/users/__tests__/ApiKeys.spec.ts +1 -1
- package/src/api/users/__tests__/EmailVerification.spec.ts +16 -1
- package/src/api/users/__tests__/PasswordReset.spec.ts +11 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -0
- package/src/api/users/controllers/AdminIdentityController.ts +3 -0
- package/src/api/users/controllers/AdminSessionController.ts +3 -0
- package/src/api/users/controllers/AdminUserController.ts +5 -0
- package/src/api/users/index.ts +8 -9
- package/src/api/users/primitives/$realm.ts +117 -19
- package/src/api/users/providers/RealmProvider.ts +15 -7
- package/src/api/users/services/CredentialService.spec.ts +11 -0
- package/src/api/users/services/CredentialService.ts +47 -24
- package/src/api/users/services/IdentityService.ts +12 -4
- package/src/api/users/services/RegistrationService.spec.ts +11 -0
- package/src/api/users/services/RegistrationService.ts +33 -12
- package/src/api/users/services/SessionService.ts +83 -12
- package/src/api/users/services/UserAudits.ts +47 -0
- package/src/api/users/services/UserFiles.ts +19 -0
- package/src/api/users/services/UserJobs.spec.ts +107 -0
- package/src/api/users/services/UserJobs.ts +62 -0
- package/src/api/users/services/UserParameters.ts +23 -0
- package/src/api/users/services/UserService.ts +34 -17
- package/src/api/verifications/index.ts +3 -3
- package/src/batch/index.ts +3 -3
- package/src/bucket/index.ts +3 -3
- package/src/cache/core/index.ts +3 -3
- package/src/cli/commands/build.ts +1 -0
- package/src/cli/commands/db.ts +9 -0
- package/src/cli/commands/init.spec.ts +2 -17
- package/src/cli/commands/init.ts +37 -1
- package/src/cli/providers/ViteDevServerProvider.ts +36 -2
- package/src/cli/services/AlephaCliUtils.ts +17 -0
- package/src/cli/services/PackageManagerUtils.ts +15 -1
- package/src/cli/services/ProjectScaffolder.ts +8 -13
- package/src/cli/templates/agentMd.ts +2 -25
- package/src/cli/templates/apiAppSecurityTs.ts +37 -2
- package/src/cli/templates/mainCss.ts +2 -32
- package/src/cli/templates/webAppRouterTs.ts +5 -5
- package/src/cli/templates/webHomeComponentTsx.ts +10 -0
- package/src/command/helpers/Runner.ts +14 -1
- package/src/command/index.ts +3 -3
- package/src/core/helpers/primitive.ts +0 -5
- package/src/core/index.ts +3 -3
- package/src/datetime/index.ts +3 -3
- package/src/email/index.ts +3 -3
- package/src/email/index.workerd.ts +36 -0
- package/src/email/providers/LocalEmailProvider.ts +2 -2
- package/src/email/providers/WorkermailerEmailProvider.ts +221 -0
- package/src/fake/index.ts +3 -3
- package/src/lock/core/index.ts +3 -3
- package/src/lock/core/primitives/$lock.ts +13 -1
- package/src/logger/index.ts +3 -3
- package/src/logger/providers/PrettyFormatterProvider.ts +7 -0
- package/src/mcp/index.ts +3 -3
- package/src/orm/index.ts +3 -3
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
- package/src/queue/core/index.ts +3 -3
- package/src/react/auth/index.ts +3 -3
- package/src/react/auth/services/ReactAuth.ts +3 -1
- package/src/react/core/index.ts +3 -3
- package/src/react/form/index.ts +3 -3
- package/src/react/head/index.ts +3 -3
- package/src/react/i18n/index.ts +3 -3
- package/src/react/intro/components/GettingStarted.css +334 -0
- package/src/react/intro/components/GettingStarted.tsx +276 -0
- package/src/react/intro/index.ts +1 -0
- package/src/react/router/atoms/ssrManifestAtom.ts +7 -0
- package/src/react/router/index.browser.ts +2 -0
- package/src/react/router/index.ts +2 -0
- package/src/react/router/providers/ReactServerProvider.ts +14 -4
- package/src/react/router/providers/SSRManifestProvider.ts +7 -0
- package/src/redis/index.ts +3 -3
- package/src/retry/index.ts +3 -3
- package/src/router/index.ts +3 -3
- package/src/scheduler/index.ts +3 -3
- package/src/scheduler/index.workerd.ts +43 -0
- package/src/scheduler/providers/CronProvider.ts +53 -6
- package/src/scheduler/providers/WorkerdCronProvider.ts +102 -0
- package/src/security/index.ts +3 -3
- package/src/security/providers/JwtProvider.ts +2 -2
- package/src/server/auth/index.ts +3 -3
- package/src/server/cache/index.ts +3 -3
- package/src/server/compress/index.ts +3 -3
- package/src/server/compress/providers/ServerCompressProvider.ts +6 -0
- package/src/server/cookies/index.ts +3 -3
- package/src/server/core/index.ts +3 -3
- package/src/server/core/primitives/$action.spec.ts +3 -2
- package/src/server/core/primitives/$action.ts +6 -2
- package/src/server/core/providers/NodeHttpServerProvider.ts +2 -15
- package/src/server/core/providers/ServerProvider.ts +4 -2
- package/src/server/core/providers/ServerRouterProvider.ts +5 -27
- package/src/server/cors/index.ts +3 -3
- package/src/server/health/index.ts +3 -3
- package/src/server/helmet/index.ts +3 -3
- package/src/server/links/index.ts +3 -3
- package/src/server/links/providers/ServerLinksProvider.spec.ts +332 -0
- package/src/server/links/providers/ServerLinksProvider.ts +1 -1
- package/src/server/metrics/index.ts +3 -3
- package/src/server/multipart/index.ts +3 -3
- package/src/server/proxy/index.ts +3 -3
- package/src/server/rate-limit/index.ts +3 -3
- package/src/server/static/index.ts +3 -3
- package/src/server/swagger/index.ts +3 -3
- package/src/sms/index.ts +3 -3
- package/src/system/index.ts +3 -3
- package/src/thread/index.ts +3 -3
- package/src/topic/core/index.ts +3 -3
- package/src/vite/tasks/generateCloudflare.ts +38 -2
- package/src/websocket/index.ts +3 -3
- package/src/cli/templates/webHelloComponentTsx.ts +0 -30
- /package/src/api/users/{notifications → services}/UserNotifications.ts +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { randomInt } from "node:crypto";
|
|
2
2
|
import { $inject, Alepha } from "alepha";
|
|
3
|
-
import { AuditService } from "alepha/api/audits";
|
|
4
3
|
import type { FileController } from "alepha/api/files";
|
|
5
4
|
import { DateTimeProvider } from "alepha/datetime";
|
|
6
5
|
import { $logger } from "alepha/logger";
|
|
@@ -15,6 +14,7 @@ import { $client } from "alepha/server/links";
|
|
|
15
14
|
import { FileSystemProvider } from "alepha/system";
|
|
16
15
|
import type { UserEntity } from "../entities/users.ts";
|
|
17
16
|
import { RealmProvider } from "../providers/RealmProvider.ts";
|
|
17
|
+
import { UserAudits } from "./UserAudits.ts";
|
|
18
18
|
|
|
19
19
|
export class SessionService {
|
|
20
20
|
protected readonly alepha = $inject(Alepha);
|
|
@@ -24,7 +24,14 @@ export class SessionService {
|
|
|
24
24
|
protected readonly log = $logger();
|
|
25
25
|
protected readonly realmProvider = $inject(RealmProvider);
|
|
26
26
|
protected readonly fileController = $client<FileController>();
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
protected userAudits(realmName?: string) {
|
|
29
|
+
const realm = this.realmProvider.getRealm(realmName);
|
|
30
|
+
if (realm.features.audits) {
|
|
31
|
+
return this.alepha.inject(UserAudits);
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
28
35
|
|
|
29
36
|
public users(userRealmName?: string) {
|
|
30
37
|
return this.realmProvider.userRepository(userRealmName);
|
|
@@ -38,6 +45,55 @@ export class SessionService {
|
|
|
38
45
|
return this.realmProvider.identityRepository(userRealmName);
|
|
39
46
|
}
|
|
40
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Check if user should be auto-promoted to admin based on adminEmails/adminUsernames settings.
|
|
50
|
+
* If user matches and doesn't have admin role, promote them.
|
|
51
|
+
*/
|
|
52
|
+
protected async ensureAdminRole(
|
|
53
|
+
user: {
|
|
54
|
+
id: string;
|
|
55
|
+
email?: string | null;
|
|
56
|
+
username?: string | null;
|
|
57
|
+
roles: string[];
|
|
58
|
+
},
|
|
59
|
+
userRealmName?: string,
|
|
60
|
+
): Promise<boolean> {
|
|
61
|
+
if (user.roles.includes("admin")) return false;
|
|
62
|
+
|
|
63
|
+
const { settings, name } = this.realmProvider.getRealm(userRealmName);
|
|
64
|
+
const adminEmails = settings.adminEmails ?? [];
|
|
65
|
+
const adminUsernames = settings.adminUsernames ?? [];
|
|
66
|
+
|
|
67
|
+
const isAdminByEmail = user.email && adminEmails.includes(user.email);
|
|
68
|
+
const isAdminByUsername =
|
|
69
|
+
user.username && adminUsernames.includes(user.username);
|
|
70
|
+
|
|
71
|
+
if (!isAdminByEmail && !isAdminByUsername) return false;
|
|
72
|
+
|
|
73
|
+
// Promote to admin
|
|
74
|
+
user.roles = [...user.roles.filter((r) => r !== "admin"), "admin"];
|
|
75
|
+
await this.users(userRealmName).updateById(user.id, { roles: user.roles });
|
|
76
|
+
|
|
77
|
+
const reason = isAdminByEmail ? "adminEmails" : "adminUsernames";
|
|
78
|
+
this.log.info(`User auto-promoted to admin via ${reason} setting`, {
|
|
79
|
+
userId: user.id,
|
|
80
|
+
email: user.email,
|
|
81
|
+
username: user.username,
|
|
82
|
+
realm: name,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await this.userAudits(userRealmName)?.recordUser("role_change", {
|
|
86
|
+
userId: user.id,
|
|
87
|
+
userEmail: user.email ?? undefined,
|
|
88
|
+
userRealm: name,
|
|
89
|
+
resourceId: user.id,
|
|
90
|
+
description: `User auto-promoted to admin via ${reason} setting`,
|
|
91
|
+
metadata: { addedRole: "admin", reason },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
41
97
|
/**
|
|
42
98
|
* Random delay to prevent timing attacks (50-200ms)
|
|
43
99
|
* Uses cryptographically secure random number generation
|
|
@@ -80,7 +136,7 @@ export class SessionService {
|
|
|
80
136
|
realm: name,
|
|
81
137
|
});
|
|
82
138
|
|
|
83
|
-
await this.
|
|
139
|
+
await this.userAudits(userRealmName)?.recordAuth("login_failed", {
|
|
84
140
|
userRealm: name,
|
|
85
141
|
description: "Username does not match required format",
|
|
86
142
|
metadata: { provider, username },
|
|
@@ -101,7 +157,7 @@ export class SessionService {
|
|
|
101
157
|
realm: name,
|
|
102
158
|
});
|
|
103
159
|
|
|
104
|
-
await this.
|
|
160
|
+
await this.userAudits(userRealmName)?.recordAuth("login_failed", {
|
|
105
161
|
userRealm: name,
|
|
106
162
|
description: "Invalid login identifier format",
|
|
107
163
|
metadata: { provider, username },
|
|
@@ -118,7 +174,7 @@ export class SessionService {
|
|
|
118
174
|
realm: name,
|
|
119
175
|
});
|
|
120
176
|
|
|
121
|
-
await this.
|
|
177
|
+
await this.userAudits(userRealmName)?.recordAuth("login_failed", {
|
|
122
178
|
userRealm: name,
|
|
123
179
|
description: "User not found",
|
|
124
180
|
metadata: { provider, username },
|
|
@@ -157,7 +213,7 @@ export class SessionService {
|
|
|
157
213
|
realm: name,
|
|
158
214
|
});
|
|
159
215
|
|
|
160
|
-
await this.
|
|
216
|
+
await this.userAudits(userRealmName)?.recordAuth("login_failed", {
|
|
161
217
|
userRealm: name,
|
|
162
218
|
resourceId: user.id,
|
|
163
219
|
description: "Invalid password",
|
|
@@ -167,7 +223,7 @@ export class SessionService {
|
|
|
167
223
|
throw new InvalidCredentialsError();
|
|
168
224
|
}
|
|
169
225
|
|
|
170
|
-
await this.
|
|
226
|
+
await this.userAudits(userRealmName)?.recordAuth("login", {
|
|
171
227
|
userId: user.id,
|
|
172
228
|
userEmail: user.email ?? undefined,
|
|
173
229
|
userRealm: name,
|
|
@@ -176,6 +232,9 @@ export class SessionService {
|
|
|
176
232
|
metadata: { provider, username },
|
|
177
233
|
});
|
|
178
234
|
|
|
235
|
+
// Auto-promote to admin if configured
|
|
236
|
+
await this.ensureAdminRole(user, userRealmName);
|
|
237
|
+
|
|
179
238
|
return user;
|
|
180
239
|
} catch (error) {
|
|
181
240
|
if (error instanceof InvalidCredentialsError) {
|
|
@@ -251,6 +310,9 @@ export class SessionService {
|
|
|
251
310
|
},
|
|
252
311
|
});
|
|
253
312
|
|
|
313
|
+
// Auto-promote to admin if configured (handles "I promote you admin" case)
|
|
314
|
+
await this.ensureAdminRole(user, userRealmName);
|
|
315
|
+
|
|
254
316
|
this.log.debug("Session refreshed", {
|
|
255
317
|
sessionId: session.id,
|
|
256
318
|
userId: session.userId,
|
|
@@ -281,7 +343,7 @@ export class SessionService {
|
|
|
281
343
|
if (session) {
|
|
282
344
|
const { name } = this.realmProvider.getRealm(userRealmName);
|
|
283
345
|
|
|
284
|
-
await this.
|
|
346
|
+
await this.userAudits(userRealmName)?.recordAuth("logout", {
|
|
285
347
|
userId: session.userId,
|
|
286
348
|
userRealm: name,
|
|
287
349
|
sessionId: session.id,
|
|
@@ -324,7 +386,7 @@ export class SessionService {
|
|
|
324
386
|
|
|
325
387
|
const user = await users.findById(identity.userId);
|
|
326
388
|
|
|
327
|
-
await this.
|
|
389
|
+
await this.userAudits(userRealmName)?.recordAuth("login", {
|
|
328
390
|
userId: user.id,
|
|
329
391
|
userEmail: user.email ?? undefined,
|
|
330
392
|
userRealm: realm.name,
|
|
@@ -333,6 +395,9 @@ export class SessionService {
|
|
|
333
395
|
metadata: { provider, providerUserId: profile.sub },
|
|
334
396
|
});
|
|
335
397
|
|
|
398
|
+
// Auto-promote to admin if configured
|
|
399
|
+
await this.ensureAdminRole(user, userRealmName);
|
|
400
|
+
|
|
336
401
|
return user;
|
|
337
402
|
}
|
|
338
403
|
|
|
@@ -368,7 +433,7 @@ export class SessionService {
|
|
|
368
433
|
userId: existing.id,
|
|
369
434
|
});
|
|
370
435
|
|
|
371
|
-
await this.
|
|
436
|
+
await this.userAudits(userRealmName)?.recordAuth("login", {
|
|
372
437
|
userId: existing.id,
|
|
373
438
|
userEmail: existing.email ?? undefined,
|
|
374
439
|
userRealm: realm.name,
|
|
@@ -377,6 +442,9 @@ export class SessionService {
|
|
|
377
442
|
metadata: { provider, providerUserId: profile.sub, linked: true },
|
|
378
443
|
});
|
|
379
444
|
|
|
445
|
+
// Auto-promote to admin if configured
|
|
446
|
+
await this.ensureAdminRole(existing, userRealmName);
|
|
447
|
+
|
|
380
448
|
return existing;
|
|
381
449
|
}
|
|
382
450
|
|
|
@@ -432,7 +500,7 @@ export class SessionService {
|
|
|
432
500
|
});
|
|
433
501
|
|
|
434
502
|
// Audit: user created via OAuth
|
|
435
|
-
await this.
|
|
503
|
+
await this.userAudits(userRealmName)?.recordUser("create", {
|
|
436
504
|
userId: user.id,
|
|
437
505
|
userEmail: user.email ?? undefined,
|
|
438
506
|
userRealm: realm.name,
|
|
@@ -447,7 +515,7 @@ export class SessionService {
|
|
|
447
515
|
});
|
|
448
516
|
|
|
449
517
|
// Audit: login event
|
|
450
|
-
await this.
|
|
518
|
+
await this.userAudits(userRealmName)?.recordAuth("login", {
|
|
451
519
|
userId: user.id,
|
|
452
520
|
userEmail: user.email ?? undefined,
|
|
453
521
|
userRealm: realm.name,
|
|
@@ -456,6 +524,9 @@ export class SessionService {
|
|
|
456
524
|
metadata: { provider, providerUserId: profile.sub, firstLogin: true },
|
|
457
525
|
});
|
|
458
526
|
|
|
527
|
+
// Auto-promote to admin if configured
|
|
528
|
+
await this.ensureAdminRole(user, userRealmName);
|
|
529
|
+
|
|
459
530
|
return user;
|
|
460
531
|
}
|
|
461
532
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { $inject } from "alepha";
|
|
2
|
+
import { AuditService, type CreateAudit } from "alepha/api/audits";
|
|
3
|
+
|
|
4
|
+
type AuditContext = Omit<CreateAudit, "type" | "action">;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* User-specific audit wrapper service.
|
|
8
|
+
*
|
|
9
|
+
* This service wraps the core AuditService to provide user-related audit logging.
|
|
10
|
+
* It is lazy-loaded when the `audits` feature is enabled in the realm.
|
|
11
|
+
*/
|
|
12
|
+
export class UserAudits {
|
|
13
|
+
protected readonly auditService = $inject(AuditService);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Record a user-related audit event.
|
|
17
|
+
*/
|
|
18
|
+
public recordUser(
|
|
19
|
+
action:
|
|
20
|
+
| "create"
|
|
21
|
+
| "update"
|
|
22
|
+
| "delete"
|
|
23
|
+
| "role_change"
|
|
24
|
+
| "enable"
|
|
25
|
+
| "disable",
|
|
26
|
+
context: AuditContext,
|
|
27
|
+
) {
|
|
28
|
+
return this.auditService.recordUser(action, context);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Record an authentication-related audit event.
|
|
33
|
+
*/
|
|
34
|
+
public recordAuth(
|
|
35
|
+
action: "login" | "logout" | "login_failed" | "token_refresh",
|
|
36
|
+
context: AuditContext,
|
|
37
|
+
) {
|
|
38
|
+
return this.auditService.recordAuth(action, context);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Record a generic audit event.
|
|
43
|
+
*/
|
|
44
|
+
public record(category: string, action: string, context: AuditContext) {
|
|
45
|
+
return this.auditService.record(category, action, context);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { $bucket } from "alepha/bucket";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* User-specific file storage wrapper service.
|
|
5
|
+
*
|
|
6
|
+
* This service provides file storage for user-related files such as:
|
|
7
|
+
* - User avatars/profile pictures
|
|
8
|
+
*
|
|
9
|
+
* It is lazy-loaded when the `files` feature is enabled in the realm.
|
|
10
|
+
*/
|
|
11
|
+
export class UserFiles {
|
|
12
|
+
/**
|
|
13
|
+
* Bucket for user avatar storage.
|
|
14
|
+
*/
|
|
15
|
+
public readonly avatars = $bucket({
|
|
16
|
+
maxSize: 5 * 1024 * 1024, // 5 MB
|
|
17
|
+
mimeTypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import { AlephaApiJobs } from "alepha/api/jobs";
|
|
3
|
+
import { $repository, AlephaOrm } from "alepha/orm";
|
|
4
|
+
import { describe, test } from "vitest";
|
|
5
|
+
import { sessions } from "../entities/sessions.ts";
|
|
6
|
+
import { users } from "../entities/users.ts";
|
|
7
|
+
import { UserJobs } from "./UserJobs.ts";
|
|
8
|
+
|
|
9
|
+
describe("UserJobs", () => {
|
|
10
|
+
describe("purgeExpiredSessions", () => {
|
|
11
|
+
test("should delete expired sessions", async ({ expect }) => {
|
|
12
|
+
const alepha = Alepha.create().with(AlephaOrm).with(AlephaApiJobs);
|
|
13
|
+
|
|
14
|
+
class TestRepositories {
|
|
15
|
+
userRepository = $repository(users);
|
|
16
|
+
sessionRepository = $repository(sessions);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const userJobs = alepha.inject(UserJobs);
|
|
20
|
+
const repos = alepha.inject(TestRepositories);
|
|
21
|
+
await alepha.start();
|
|
22
|
+
|
|
23
|
+
// Create a test user
|
|
24
|
+
const user = await repos.userRepository.create({
|
|
25
|
+
email: "test@example.com",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Create expired sessions (expiresAt in the past)
|
|
29
|
+
const pastDate = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); // 1 day ago
|
|
30
|
+
await repos.sessionRepository.create({
|
|
31
|
+
userId: user.id,
|
|
32
|
+
refreshToken: crypto.randomUUID(),
|
|
33
|
+
expiresAt: pastDate,
|
|
34
|
+
});
|
|
35
|
+
await repos.sessionRepository.create({
|
|
36
|
+
userId: user.id,
|
|
37
|
+
refreshToken: crypto.randomUUID(),
|
|
38
|
+
expiresAt: pastDate,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Create a valid session (expiresAt in the future)
|
|
42
|
+
const futureDate = new Date(
|
|
43
|
+
Date.now() + 24 * 60 * 60 * 1000,
|
|
44
|
+
).toISOString(); // 1 day from now
|
|
45
|
+
await repos.sessionRepository.create({
|
|
46
|
+
userId: user.id,
|
|
47
|
+
refreshToken: crypto.randomUUID(),
|
|
48
|
+
expiresAt: futureDate,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Verify we have 3 sessions
|
|
52
|
+
const sessionsBefore = await repos.sessionRepository.findMany();
|
|
53
|
+
expect(sessionsBefore).toHaveLength(3);
|
|
54
|
+
|
|
55
|
+
// Trigger the job
|
|
56
|
+
await userJobs.purgeExpiredSessions.trigger();
|
|
57
|
+
|
|
58
|
+
// Verify only the valid session remains
|
|
59
|
+
const sessionsAfter = await repos.sessionRepository.findMany();
|
|
60
|
+
expect(sessionsAfter).toHaveLength(1);
|
|
61
|
+
expect(new Date(sessionsAfter[0].expiresAt).getTime()).toBeGreaterThan(
|
|
62
|
+
Date.now(),
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("should handle case when no expired sessions exist", async ({
|
|
67
|
+
expect,
|
|
68
|
+
}) => {
|
|
69
|
+
const alepha = Alepha.create().with(AlephaOrm).with(AlephaApiJobs);
|
|
70
|
+
|
|
71
|
+
class TestRepositories {
|
|
72
|
+
userRepository = $repository(users);
|
|
73
|
+
sessionRepository = $repository(sessions);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const userJobs = alepha.inject(UserJobs);
|
|
77
|
+
const repos = alepha.inject(TestRepositories);
|
|
78
|
+
await alepha.start();
|
|
79
|
+
|
|
80
|
+
// Create a test user
|
|
81
|
+
const user = await repos.userRepository.create({
|
|
82
|
+
email: "test2@example.com",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Create only valid sessions
|
|
86
|
+
const futureDate = new Date(
|
|
87
|
+
Date.now() + 24 * 60 * 60 * 1000,
|
|
88
|
+
).toISOString();
|
|
89
|
+
await repos.sessionRepository.create({
|
|
90
|
+
userId: user.id,
|
|
91
|
+
refreshToken: crypto.randomUUID(),
|
|
92
|
+
expiresAt: futureDate,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Verify we have 1 session
|
|
96
|
+
const sessionsBefore = await repos.sessionRepository.findMany();
|
|
97
|
+
expect(sessionsBefore).toHaveLength(1);
|
|
98
|
+
|
|
99
|
+
// Trigger the job - should not throw
|
|
100
|
+
await userJobs.purgeExpiredSessions.trigger();
|
|
101
|
+
|
|
102
|
+
// Session should still exist
|
|
103
|
+
const sessionsAfter = await repos.sessionRepository.findMany();
|
|
104
|
+
expect(sessionsAfter).toHaveLength(1);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { $inject } from "alepha";
|
|
2
|
+
import { $job } from "alepha/api/jobs";
|
|
3
|
+
import { DateTimeProvider } from "alepha/datetime";
|
|
4
|
+
import { $logger } from "alepha/logger";
|
|
5
|
+
import { $repository } from "alepha/orm";
|
|
6
|
+
import { sessions } from "../entities/sessions.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* User-specific jobs wrapper service.
|
|
10
|
+
*
|
|
11
|
+
* This service handles user-related scheduled jobs such as:
|
|
12
|
+
* - Session purge (cleaning up expired sessions)
|
|
13
|
+
* - Verification code cleanup
|
|
14
|
+
* - Inactive user notifications
|
|
15
|
+
*
|
|
16
|
+
* It is lazy-loaded when the `jobs` feature is enabled in the realm.
|
|
17
|
+
*/
|
|
18
|
+
export class UserJobs {
|
|
19
|
+
protected readonly log = $logger();
|
|
20
|
+
protected readonly dateTimeProvider = $inject(DateTimeProvider);
|
|
21
|
+
protected readonly sessionRepository = $repository(sessions);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Purge expired sessions from the database.
|
|
25
|
+
*
|
|
26
|
+
* This job runs daily at 3:00 AM and removes all sessions
|
|
27
|
+
* where the `expiresAt` timestamp has passed.
|
|
28
|
+
*/
|
|
29
|
+
public readonly purgeExpiredSessions = $job({
|
|
30
|
+
name: "users.purgeExpiredSessions",
|
|
31
|
+
description: "Remove expired user sessions from the database",
|
|
32
|
+
cron: "0 3 * * *", // Daily at 3:00 AM
|
|
33
|
+
handler: async () => {
|
|
34
|
+
const now = this.dateTimeProvider.nowISOString();
|
|
35
|
+
|
|
36
|
+
this.log.info("Starting expired sessions purge", { cutoffTime: now });
|
|
37
|
+
|
|
38
|
+
const expiredSessions = await this.sessionRepository.findMany({
|
|
39
|
+
where: {
|
|
40
|
+
expiresAt: { lt: now },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (expiredSessions.length === 0) {
|
|
45
|
+
this.log.info("No expired sessions found");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.log.info("Found expired sessions", {
|
|
50
|
+
count: expiredSessions.length,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const deletedIds = await this.sessionRepository.deleteMany({
|
|
54
|
+
expiresAt: { lt: now },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.log.info("Expired sessions purged successfully", {
|
|
58
|
+
deletedCount: deletedIds.length,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { $config } from "alepha/api/parameters";
|
|
2
|
+
import { realmAuthSettingsAtom } from "../atoms/realmAuthSettingsAtom.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* User-specific configuration service.
|
|
6
|
+
*
|
|
7
|
+
* This service wraps the core ConfigStore to provide realm settings management.
|
|
8
|
+
* It is lazy-loaded when the `parameters` feature is enabled in the realm.
|
|
9
|
+
*/
|
|
10
|
+
export class UserParameters {
|
|
11
|
+
/**
|
|
12
|
+
* Realm authentication settings configuration.
|
|
13
|
+
*
|
|
14
|
+
* Controls user registration, login methods, verification requirements,
|
|
15
|
+
* and password policies for the realm.
|
|
16
|
+
*/
|
|
17
|
+
public readonly realmSettings = $config({
|
|
18
|
+
name: "alepha.api.users.realmSettings",
|
|
19
|
+
description: "Realm authentication and registration settings",
|
|
20
|
+
schema: realmAuthSettingsAtom.schema,
|
|
21
|
+
default: realmAuthSettingsAtom.options.default,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -1,23 +1,38 @@
|
|
|
1
|
-
import { $inject } from "alepha";
|
|
2
|
-
import { AuditService } from "alepha/api/audits";
|
|
1
|
+
import { $inject, Alepha } from "alepha";
|
|
3
2
|
import type { VerificationController } from "alepha/api/verifications";
|
|
4
3
|
import { $logger } from "alepha/logger";
|
|
5
4
|
import { type Page, parseQueryString } from "alepha/orm";
|
|
6
5
|
import { BadRequestError } from "alepha/server";
|
|
7
6
|
import { $client } from "alepha/server/links";
|
|
8
7
|
import type { UserEntity } from "../entities/users.ts";
|
|
9
|
-
import { UserNotifications } from "../notifications/UserNotifications.ts";
|
|
10
8
|
import { RealmProvider } from "../providers/RealmProvider.ts";
|
|
11
9
|
import type { CreateUser } from "../schemas/createUserSchema.ts";
|
|
12
10
|
import type { UpdateUser } from "../schemas/updateUserSchema.ts";
|
|
13
11
|
import type { UserQuery } from "../schemas/userQuerySchema.ts";
|
|
12
|
+
import { UserAudits } from "./UserAudits.ts";
|
|
13
|
+
import { UserNotifications } from "./UserNotifications.ts";
|
|
14
14
|
|
|
15
15
|
export class UserService {
|
|
16
|
+
protected readonly alepha = $inject(Alepha);
|
|
16
17
|
protected readonly log = $logger();
|
|
17
18
|
protected readonly verificationController = $client<VerificationController>();
|
|
18
|
-
protected readonly userNotifications = $inject(UserNotifications);
|
|
19
19
|
protected readonly realmProvider = $inject(RealmProvider);
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
protected userAudits(realmName?: string) {
|
|
22
|
+
const realm = this.realmProvider.getRealm(realmName);
|
|
23
|
+
if (realm.features.audits) {
|
|
24
|
+
return this.alepha.inject(UserAudits);
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected userNotifications(realmName?: string) {
|
|
30
|
+
const realm = this.realmProvider.getRealm(realmName);
|
|
31
|
+
if (realm.features.notifications) {
|
|
32
|
+
return this.alepha.inject(UserNotifications);
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
21
36
|
|
|
22
37
|
public users(userRealmName?: string) {
|
|
23
38
|
return this.realmProvider.userRepository(userRealmName);
|
|
@@ -79,21 +94,23 @@ export class UserService {
|
|
|
79
94
|
? `${verifyUrl}${url.search}`
|
|
80
95
|
: url.pathname + url.search;
|
|
81
96
|
|
|
82
|
-
await this.userNotifications
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
await this.userNotifications(userRealmName)?.emailVerificationLink.push(
|
|
98
|
+
{
|
|
99
|
+
contact: email,
|
|
100
|
+
variables: {
|
|
101
|
+
email,
|
|
102
|
+
verifyUrl: fullVerifyUrl,
|
|
103
|
+
expiresInMinutes: Math.floor(verification.codeExpiration / 60),
|
|
104
|
+
},
|
|
88
105
|
},
|
|
89
|
-
|
|
106
|
+
);
|
|
90
107
|
|
|
91
108
|
this.log.debug("Email verification link sent", {
|
|
92
109
|
email,
|
|
93
110
|
userId: user.id,
|
|
94
111
|
});
|
|
95
112
|
} else {
|
|
96
|
-
await this.userNotifications
|
|
113
|
+
await this.userNotifications(userRealmName)?.emailVerification.push({
|
|
97
114
|
contact: email,
|
|
98
115
|
variables: {
|
|
99
116
|
email,
|
|
@@ -158,7 +175,7 @@ export class UserService {
|
|
|
158
175
|
|
|
159
176
|
const realm = this.realmProvider.getRealm(userRealmName);
|
|
160
177
|
|
|
161
|
-
await this.
|
|
178
|
+
await this.userAudits(userRealmName)?.recordUser("update", {
|
|
162
179
|
userId: user.id,
|
|
163
180
|
userEmail: email,
|
|
164
181
|
userRealm: realm.name,
|
|
@@ -314,7 +331,7 @@ export class UserService {
|
|
|
314
331
|
email: user.email,
|
|
315
332
|
});
|
|
316
333
|
|
|
317
|
-
await this.
|
|
334
|
+
await this.userAudits(userRealmName)?.recordUser("create", {
|
|
318
335
|
userRealm: realm.name,
|
|
319
336
|
resourceId: user.id,
|
|
320
337
|
description: "User created",
|
|
@@ -357,7 +374,7 @@ export class UserService {
|
|
|
357
374
|
data.roles !== undefined &&
|
|
358
375
|
JSON.stringify(before.roles) !== JSON.stringify(data.roles);
|
|
359
376
|
|
|
360
|
-
await this.
|
|
377
|
+
await this.userAudits(userRealmName)?.recordUser(
|
|
361
378
|
isRoleChange ? "role_change" : "update",
|
|
362
379
|
{
|
|
363
380
|
userRealm: realm.name,
|
|
@@ -384,7 +401,7 @@ export class UserService {
|
|
|
384
401
|
|
|
385
402
|
const realm = this.realmProvider.getRealm(userRealmName);
|
|
386
403
|
|
|
387
|
-
await this.
|
|
404
|
+
await this.userAudits(userRealmName)?.recordUser("delete", {
|
|
388
405
|
userRealm: realm.name,
|
|
389
406
|
resourceId: id,
|
|
390
407
|
severity: "warning",
|
|
@@ -18,9 +18,9 @@ export * from "./services/VerificationService.ts";
|
|
|
18
18
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* |
|
|
22
|
-
*
|
|
23
|
-
* |
|
|
21
|
+
* | Stability | Since | Runtime |
|
|
22
|
+
* |-----------|-------|---------|
|
|
23
|
+
* | 3 - stable | 0.13.0 | node, bun, workerd|
|
|
24
24
|
*
|
|
25
25
|
* Email and phone verification workflows.
|
|
26
26
|
*
|
package/src/batch/index.ts
CHANGED
|
@@ -10,9 +10,9 @@ export * from "./providers/BatchProvider.ts";
|
|
|
10
10
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* |
|
|
14
|
-
*
|
|
15
|
-
* |
|
|
13
|
+
* | Stability | Since | Runtime |
|
|
14
|
+
* |-----------|-------|---------|
|
|
15
|
+
* | 3 - stable | 0.8.0 | node, bun|
|
|
16
16
|
*
|
|
17
17
|
* Batch accumulation and processing.
|
|
18
18
|
*
|
package/src/bucket/index.ts
CHANGED
|
@@ -43,9 +43,9 @@ declare module "alepha" {
|
|
|
43
43
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* |
|
|
47
|
-
*
|
|
48
|
-
* |
|
|
46
|
+
* | Stability | Since | Runtime |
|
|
47
|
+
* |-----------|-------|---------|
|
|
48
|
+
* | 3 - stable | 0.9.0 | node, bun, workerd|
|
|
49
49
|
*
|
|
50
50
|
* Unified file storage abstraction across multiple backends.
|
|
51
51
|
*
|
package/src/cache/core/index.ts
CHANGED
|
@@ -12,9 +12,9 @@ export * from "./providers/MemoryCacheProvider.ts";
|
|
|
12
12
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* |
|
|
16
|
-
*
|
|
17
|
-
* |
|
|
15
|
+
* | Stability | Since | Runtime |
|
|
16
|
+
* |-----------|-------|---------|
|
|
17
|
+
* | 3 - stable | 0.9.0 | node, bun, workerd|
|
|
18
18
|
*
|
|
19
19
|
* Type-safe caching with TTL support.
|
|
20
20
|
*
|