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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { $inject, createPrimitive, KIND, Primitive } from "alepha";
|
|
2
2
|
import { SecurityProvider } from "../providers/SecurityProvider.ts";
|
|
3
|
+
import type { IssuerPrimitive } from "./$issuer.ts";
|
|
3
4
|
import type { PermissionPrimitive } from "./$permission.ts";
|
|
4
|
-
import type { RealmPrimitive } from "./$realm.ts";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Create a new role.
|
|
@@ -23,7 +23,7 @@ export interface RolePrimitiveOptions {
|
|
|
23
23
|
*/
|
|
24
24
|
description?: string;
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
issuer?: string | IssuerPrimitive;
|
|
27
27
|
|
|
28
28
|
permissions?: Array<
|
|
29
29
|
| string
|
|
@@ -60,10 +60,10 @@ export class RolePrimitive extends Primitive<RolePrimitiveOptions> {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Get the
|
|
63
|
+
* Get the issuer of the role.
|
|
64
64
|
*/
|
|
65
|
-
public get
|
|
66
|
-
return this.options.
|
|
65
|
+
public get issuer(): string | IssuerPrimitive | undefined {
|
|
66
|
+
return this.options.issuer;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
public can(permission: string | PermissionPrimitive): boolean {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Alepha } from "alepha";
|
|
2
2
|
import { DateTimeProvider } from "alepha/datetime";
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
|
-
import { $
|
|
4
|
+
import { $issuer, $serviceAccount } from "../index.ts";
|
|
5
5
|
|
|
6
6
|
class App {
|
|
7
7
|
oauth2 = $serviceAccount({
|
|
@@ -16,12 +16,12 @@ class App {
|
|
|
16
16
|
expiresIn: 300, // 5 minutes
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
secret: "your-
|
|
19
|
+
issuer = $issuer({
|
|
20
|
+
secret: "your-issuer-secret",
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
jwt = $serviceAccount({
|
|
24
|
-
|
|
24
|
+
issuer: this.issuer,
|
|
25
25
|
user: {
|
|
26
26
|
id: "service-account",
|
|
27
27
|
},
|
|
@@ -42,7 +42,7 @@ describe("$serviceAccount", () => {
|
|
|
42
42
|
);
|
|
43
43
|
expect(payload).toEqual({
|
|
44
44
|
sub: expect.any(String),
|
|
45
|
-
aud: "
|
|
45
|
+
aud: "issuer",
|
|
46
46
|
iat: expect.any(Number),
|
|
47
47
|
exp: expect.any(Number),
|
|
48
48
|
sid: expect.any(String),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { $context } from "alepha";
|
|
2
2
|
import { DateTimeProvider } from "alepha/datetime";
|
|
3
3
|
import type { UserAccount } from "../schemas/userAccountInfoSchema.ts";
|
|
4
|
-
import type { AccessTokenResponse,
|
|
4
|
+
import type { AccessTokenResponse, IssuerPrimitive } from "./$issuer.ts";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Allow to get an access token for a service account.
|
|
@@ -138,7 +138,7 @@ export const $serviceAccount = (
|
|
|
138
138
|
return tokenFromCache;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
const token = await options.
|
|
141
|
+
const token = await options.issuer.createToken(options.user);
|
|
142
142
|
|
|
143
143
|
cacheToken({
|
|
144
144
|
...token,
|
|
@@ -157,7 +157,7 @@ export type ServiceAccountPrimitiveOptions = {
|
|
|
157
157
|
oauth2: Oauth2ServiceAccountPrimitiveOptions;
|
|
158
158
|
}
|
|
159
159
|
| {
|
|
160
|
-
|
|
160
|
+
issuer: IssuerPrimitive;
|
|
161
161
|
user: UserAccount;
|
|
162
162
|
}
|
|
163
163
|
);
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { $hook, $inject, Alepha } from "alepha";
|
|
3
3
|
import { $logger } from "alepha/logger";
|
|
4
|
-
import {
|
|
5
|
-
JwtProvider,
|
|
6
|
-
type Permission,
|
|
7
|
-
SecurityProvider,
|
|
8
|
-
type UserAccountToken,
|
|
9
|
-
userAccountInfoSchema,
|
|
10
|
-
} from "alepha/security";
|
|
11
4
|
import {
|
|
12
5
|
$action,
|
|
13
6
|
ForbiddenError,
|
|
14
7
|
type ServerRequest,
|
|
15
8
|
UnauthorizedError,
|
|
16
9
|
} from "alepha/server";
|
|
10
|
+
import type { UserAccountToken } from "../interfaces/UserAccountToken.ts";
|
|
11
|
+
import type { Permission } from "../schemas/permissionSchema.ts";
|
|
12
|
+
import { userAccountInfoSchema } from "../schemas/userAccountInfoSchema.ts";
|
|
13
|
+
import { JwtProvider } from "./JwtProvider.ts";
|
|
14
|
+
import { SecurityProvider } from "./SecurityProvider.ts";
|
|
17
15
|
import {
|
|
18
16
|
type BasicAuthOptions,
|
|
19
17
|
isBasicAuth,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { DateTimeProvider } from "alepha/datetime";
|
|
10
10
|
import {
|
|
11
11
|
type AccessTokenResponse,
|
|
12
|
-
type
|
|
12
|
+
type IssuerPrimitive,
|
|
13
13
|
SecurityError,
|
|
14
14
|
SecurityProvider,
|
|
15
15
|
type UserAccount,
|
|
@@ -105,10 +105,10 @@ export type AuthExternal = {
|
|
|
105
105
|
* When using your own authentication system, e.g. using a database to store user accounts.
|
|
106
106
|
* This is usually used with a custom login form.
|
|
107
107
|
*
|
|
108
|
-
* This relies on the `
|
|
108
|
+
* This relies on the `issuer`, which is used to create/verify the access token.
|
|
109
109
|
*/
|
|
110
110
|
export type AuthInternal = {
|
|
111
|
-
|
|
111
|
+
issuer: IssuerPrimitive;
|
|
112
112
|
} & (
|
|
113
113
|
| {
|
|
114
114
|
/**
|
|
@@ -261,9 +261,9 @@ export class AuthPrimitive extends Primitive<AuthPrimitiveOptions> {
|
|
|
261
261
|
return this.options.name ?? this.config.propertyKey;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
public get
|
|
265
|
-
if ("
|
|
266
|
-
return this.options.
|
|
264
|
+
public get issuer(): IssuerPrimitive | undefined {
|
|
265
|
+
if ("issuer" in this.options) {
|
|
266
|
+
return this.options.issuer;
|
|
267
267
|
}
|
|
268
268
|
return undefined;
|
|
269
269
|
}
|
|
@@ -308,13 +308,13 @@ export class AuthPrimitive extends Primitive<AuthPrimitiveOptions> {
|
|
|
308
308
|
refreshToken: string,
|
|
309
309
|
accessToken?: string,
|
|
310
310
|
): Promise<AccessTokenResponse> {
|
|
311
|
-
if ("
|
|
312
|
-
return this.options.
|
|
311
|
+
if ("issuer" in this.options) {
|
|
312
|
+
return this.options.issuer
|
|
313
313
|
.refreshToken(refreshToken, accessToken)
|
|
314
314
|
.then((it) => it.tokens)
|
|
315
315
|
.catch((error) => {
|
|
316
316
|
throw new SecurityError(
|
|
317
|
-
"Failed to refresh access token using the refresh token (
|
|
317
|
+
"Failed to refresh access token using the refresh token (issuer)",
|
|
318
318
|
{
|
|
319
319
|
cause: error,
|
|
320
320
|
},
|
|
@@ -337,7 +337,7 @@ export class AuthPrimitive extends Primitive<AuthPrimitiveOptions> {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
throw new AlephaError(
|
|
340
|
-
"No
|
|
340
|
+
"No issuer or OAuth2 configuration available for refreshing the access token",
|
|
341
341
|
);
|
|
342
342
|
}
|
|
343
343
|
|
|
@@ -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
|
|
|
@@ -865,6 +865,189 @@ describe("ServerCacheProvider", () => {
|
|
|
865
865
|
});
|
|
866
866
|
});
|
|
867
867
|
|
|
868
|
+
describe("Stream caching support", () => {
|
|
869
|
+
test("should cache ReadableStream responses via tee", async ({
|
|
870
|
+
expect,
|
|
871
|
+
}) => {
|
|
872
|
+
// Create a simple ReadableStream that emits chunks
|
|
873
|
+
const chunks = ["Hello", " ", "World"];
|
|
874
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
875
|
+
start(controller) {
|
|
876
|
+
const encoder = new TextEncoder();
|
|
877
|
+
for (const chunk of chunks) {
|
|
878
|
+
controller.enqueue(encoder.encode(chunk));
|
|
879
|
+
}
|
|
880
|
+
controller.close();
|
|
881
|
+
},
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
// Access the protected method via type assertion
|
|
885
|
+
const provider = cacheProvider as any;
|
|
886
|
+
const key = "test-stream-key";
|
|
887
|
+
|
|
888
|
+
// Collect the stream for cache
|
|
889
|
+
const hash = await provider.collectStreamForCache(
|
|
890
|
+
stream,
|
|
891
|
+
key,
|
|
892
|
+
200,
|
|
893
|
+
"text/html",
|
|
894
|
+
true,
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
expect(hash).toBeDefined();
|
|
898
|
+
expect(hash).toMatch(/^"[a-f0-9]+"$/); // ETag format
|
|
899
|
+
|
|
900
|
+
// Verify the cache contains the collected content
|
|
901
|
+
const cached = await provider.cache.get(key);
|
|
902
|
+
expect(cached).toBeDefined();
|
|
903
|
+
expect(cached.body).toBe("Hello World");
|
|
904
|
+
expect(cached.status).toBe(200);
|
|
905
|
+
expect(cached.contentType).toBe("text/html");
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
test("should tee stream so client and cache both receive data", async ({
|
|
909
|
+
expect,
|
|
910
|
+
}) => {
|
|
911
|
+
const encoder = new TextEncoder();
|
|
912
|
+
const chunks = ["<html>", "<body>", "Content", "</body>", "</html>"];
|
|
913
|
+
|
|
914
|
+
const originalStream = new ReadableStream<Uint8Array>({
|
|
915
|
+
start(controller) {
|
|
916
|
+
for (const chunk of chunks) {
|
|
917
|
+
controller.enqueue(encoder.encode(chunk));
|
|
918
|
+
}
|
|
919
|
+
controller.close();
|
|
920
|
+
},
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
// Tee the stream like ServerCacheProvider does
|
|
924
|
+
const [clientStream, cacheStream] = originalStream.tee();
|
|
925
|
+
|
|
926
|
+
// Read from client stream (simulates client receiving data)
|
|
927
|
+
const clientReader = clientStream.getReader();
|
|
928
|
+
const clientChunks: string[] = [];
|
|
929
|
+
const decoder = new TextDecoder();
|
|
930
|
+
|
|
931
|
+
while (true) {
|
|
932
|
+
const { done, value } = await clientReader.read();
|
|
933
|
+
if (done) break;
|
|
934
|
+
clientChunks.push(decoder.decode(value, { stream: true }));
|
|
935
|
+
}
|
|
936
|
+
clientChunks.push(decoder.decode()); // flush
|
|
937
|
+
|
|
938
|
+
// Read from cache stream (simulates cache collection)
|
|
939
|
+
const cacheReader = cacheStream.getReader();
|
|
940
|
+
const cacheChunks: string[] = [];
|
|
941
|
+
const cacheDecoder = new TextDecoder();
|
|
942
|
+
|
|
943
|
+
while (true) {
|
|
944
|
+
const { done, value } = await cacheReader.read();
|
|
945
|
+
if (done) break;
|
|
946
|
+
cacheChunks.push(cacheDecoder.decode(value, { stream: true }));
|
|
947
|
+
}
|
|
948
|
+
cacheChunks.push(cacheDecoder.decode()); // flush
|
|
949
|
+
|
|
950
|
+
// Both should receive the same data
|
|
951
|
+
const clientData = clientChunks.join("");
|
|
952
|
+
const cacheData = cacheChunks.join("");
|
|
953
|
+
|
|
954
|
+
expect(clientData).toBe("<html><body>Content</body></html>");
|
|
955
|
+
expect(cacheData).toBe("<html><body>Content</body></html>");
|
|
956
|
+
expect(clientData).toBe(cacheData);
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
test("should handle empty stream gracefully", async ({ expect }) => {
|
|
960
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
961
|
+
start(controller) {
|
|
962
|
+
controller.close();
|
|
963
|
+
},
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
const provider = cacheProvider as any;
|
|
967
|
+
const key = "empty-stream-key";
|
|
968
|
+
|
|
969
|
+
const hash = await provider.collectStreamForCache(
|
|
970
|
+
stream,
|
|
971
|
+
key,
|
|
972
|
+
200,
|
|
973
|
+
"text/html",
|
|
974
|
+
true,
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
expect(hash).toBeDefined();
|
|
978
|
+
|
|
979
|
+
const cached = await provider.cache.get(key);
|
|
980
|
+
expect(cached).toBeDefined();
|
|
981
|
+
expect(cached.body).toBe("");
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
test("should handle large stream with multiple chunks", async ({
|
|
985
|
+
expect,
|
|
986
|
+
}) => {
|
|
987
|
+
const encoder = new TextEncoder();
|
|
988
|
+
const chunkCount = 100;
|
|
989
|
+
const chunkContent = "x".repeat(1000);
|
|
990
|
+
|
|
991
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
992
|
+
start(controller) {
|
|
993
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
994
|
+
controller.enqueue(encoder.encode(chunkContent));
|
|
995
|
+
}
|
|
996
|
+
controller.close();
|
|
997
|
+
},
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
const provider = cacheProvider as any;
|
|
1001
|
+
const key = "large-stream-key";
|
|
1002
|
+
|
|
1003
|
+
const hash = await provider.collectStreamForCache(
|
|
1004
|
+
stream,
|
|
1005
|
+
key,
|
|
1006
|
+
200,
|
|
1007
|
+
"text/html",
|
|
1008
|
+
false,
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
// hash should be undefined when generateEtag is false
|
|
1012
|
+
expect(hash).toBeUndefined();
|
|
1013
|
+
|
|
1014
|
+
const cached = await provider.cache.get(key);
|
|
1015
|
+
expect(cached).toBeDefined();
|
|
1016
|
+
expect(cached.body.length).toBe(chunkCount * chunkContent.length);
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
test("should generate correct ETag for streamed content", async ({
|
|
1020
|
+
expect,
|
|
1021
|
+
}) => {
|
|
1022
|
+
const content = "Test content for ETag";
|
|
1023
|
+
const encoder = new TextEncoder();
|
|
1024
|
+
|
|
1025
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
1026
|
+
start(controller) {
|
|
1027
|
+
controller.enqueue(encoder.encode(content));
|
|
1028
|
+
controller.close();
|
|
1029
|
+
},
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
const provider = cacheProvider as any;
|
|
1033
|
+
|
|
1034
|
+
// Generate ETag from stream
|
|
1035
|
+
const streamHash = await provider.collectStreamForCache(
|
|
1036
|
+
stream,
|
|
1037
|
+
"etag-test-key",
|
|
1038
|
+
200,
|
|
1039
|
+
"text/html",
|
|
1040
|
+
true,
|
|
1041
|
+
);
|
|
1042
|
+
|
|
1043
|
+
// Generate ETag from string directly
|
|
1044
|
+
const stringHash = cacheProvider.generateETag(content);
|
|
1045
|
+
|
|
1046
|
+
// Both should produce the same ETag
|
|
1047
|
+
expect(streamHash).toBe(stringHash);
|
|
1048
|
+
});
|
|
1049
|
+
});
|
|
1050
|
+
|
|
868
1051
|
describe("Error response caching", () => {
|
|
869
1052
|
test("should NOT cache 500 error responses", async ({ expect }) => {
|
|
870
1053
|
const response1 = await app.errorAction.fetch();
|
|
@@ -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 <-
|
|
@@ -261,24 +261,55 @@ export class ServerCacheProvider {
|
|
|
261
261
|
return;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
// Only process string responses (text, html, json, etc.)
|
|
265
|
-
// Buffer is not supported by alepha/cache for now
|
|
266
|
-
if (typeof response.body !== "string") {
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
264
|
// Don't cache error responses (status >= 400)
|
|
271
265
|
if (response.status && response.status >= 400) {
|
|
272
266
|
return;
|
|
273
267
|
}
|
|
274
268
|
|
|
269
|
+
// Initialize headers if not present
|
|
270
|
+
response.headers ??= {};
|
|
271
|
+
|
|
275
272
|
const key = this.createCacheKey(route, request);
|
|
273
|
+
|
|
274
|
+
// Handle ReadableStream responses (e.g., SSR streaming)
|
|
275
|
+
if (response.body instanceof ReadableStream && shouldStore) {
|
|
276
|
+
// Tee the stream: one for client, one for cache collection
|
|
277
|
+
const [clientStream, cacheStream] = (
|
|
278
|
+
response.body as ReadableStream<Uint8Array>
|
|
279
|
+
).tee();
|
|
280
|
+
|
|
281
|
+
// Replace response body with client stream (continues streaming to client)
|
|
282
|
+
response.body = clientStream as typeof response.body;
|
|
283
|
+
|
|
284
|
+
// Collect cache stream in background (non-blocking)
|
|
285
|
+
this.collectStreamForCache(
|
|
286
|
+
cacheStream,
|
|
287
|
+
key,
|
|
288
|
+
response.status,
|
|
289
|
+
response.headers?.["content-type"],
|
|
290
|
+
shouldUseEtag,
|
|
291
|
+
)
|
|
292
|
+
.then((hash) => {
|
|
293
|
+
if (shouldUseEtag && hash) {
|
|
294
|
+
// Note: headers already sent for streaming, etag only useful for future requests
|
|
295
|
+
this.log.trace("Stream cached with hash", { key, hash });
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
.catch((err) => {
|
|
299
|
+
this.log.warn("Failed to cache stream", { key, error: err });
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Only process string responses (text, html, json, etc.)
|
|
306
|
+
if (typeof response.body !== "string") {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
276
310
|
const generatedEtag = this.generateETag(response.body);
|
|
277
311
|
const lastModified = this.time.toISOString();
|
|
278
312
|
|
|
279
|
-
// Initialize headers if not present
|
|
280
|
-
response.headers ??= {};
|
|
281
|
-
|
|
282
313
|
// Store response if storing is enabled
|
|
283
314
|
if (shouldStore) {
|
|
284
315
|
this.log.trace("Storing response", {
|
|
@@ -404,6 +435,60 @@ export class ServerCacheProvider {
|
|
|
404
435
|
|
|
405
436
|
return `${route.method}:${route.path.replaceAll(":", "")}:${params.join(",").replaceAll(":", "")}`;
|
|
406
437
|
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Collect a ReadableStream into a string and store it in the cache.
|
|
441
|
+
* This runs in the background while the original stream is sent to the client.
|
|
442
|
+
*
|
|
443
|
+
* @param stream - The stream to collect
|
|
444
|
+
* @param key - Cache key
|
|
445
|
+
* @param status - HTTP status code
|
|
446
|
+
* @param contentType - Content-Type header
|
|
447
|
+
* @param generateEtag - Whether to generate and return an ETag
|
|
448
|
+
* @returns The generated ETag hash, or undefined
|
|
449
|
+
*/
|
|
450
|
+
protected async collectStreamForCache(
|
|
451
|
+
stream: ReadableStream<Uint8Array>,
|
|
452
|
+
key: string,
|
|
453
|
+
status: number | undefined,
|
|
454
|
+
contentType: string | undefined,
|
|
455
|
+
generateEtag: boolean,
|
|
456
|
+
): Promise<string | undefined> {
|
|
457
|
+
const chunks: Uint8Array[] = [];
|
|
458
|
+
const reader = stream.getReader();
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
while (true) {
|
|
462
|
+
const { done, value } = await reader.read();
|
|
463
|
+
if (done) break;
|
|
464
|
+
chunks.push(value);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Combine chunks into a single string
|
|
468
|
+
const decoder = new TextDecoder();
|
|
469
|
+
const body =
|
|
470
|
+
chunks
|
|
471
|
+
.map((chunk) => decoder.decode(chunk, { stream: true }))
|
|
472
|
+
.join("") + decoder.decode(); // Flush remaining
|
|
473
|
+
|
|
474
|
+
const hash = this.generateETag(body);
|
|
475
|
+
const lastModified = this.time.toISOString();
|
|
476
|
+
|
|
477
|
+
this.log.trace("Storing streamed response", { key });
|
|
478
|
+
|
|
479
|
+
await this.cache.set(key, {
|
|
480
|
+
body,
|
|
481
|
+
status,
|
|
482
|
+
contentType,
|
|
483
|
+
lastModified,
|
|
484
|
+
hash,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
return generateEtag ? hash : undefined;
|
|
488
|
+
} finally {
|
|
489
|
+
reader.releaseLock();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
407
492
|
}
|
|
408
493
|
|
|
409
494
|
export type ServerRouteCache =
|