alepha 0.13.0 → 0.13.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/dist/api-jobs/index.d.ts +26 -26
- package/dist/api-users/index.d.ts +1 -1
- package/dist/cli/{dist-Sz2EXvQX.cjs → dist-Dl9Vl7Ur.js} +17 -13
- package/dist/cli/{dist-BBPjuQ56.js.map → dist-Dl9Vl7Ur.js.map} +1 -1
- package/dist/cli/index.d.ts +3 -11
- package/dist/cli/index.js +106 -74
- package/dist/cli/index.js.map +1 -1
- package/dist/email/index.js +71 -73
- package/dist/email/index.js.map +1 -1
- package/dist/orm/index.d.ts +1 -1
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/index.d.ts +4 -4
- package/dist/retry/index.d.ts +1 -1
- package/dist/retry/index.js +2 -2
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +6 -6
- package/dist/security/index.d.ts +28 -28
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server-health/index.d.ts +17 -17
- package/dist/server-metrics/index.js +170 -174
- package/dist/server-metrics/index.js.map +1 -1
- package/dist/server-security/index.d.ts +9 -9
- package/dist/vite/index.js +4 -5
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/package.json +52 -103
- package/src/cli/apps/AlephaPackageBuilderCli.ts +7 -2
- package/src/cli/assets/appRouterTs.ts +9 -0
- package/src/cli/assets/indexHtml.ts +2 -1
- package/src/cli/assets/mainBrowserTs.ts +10 -0
- package/src/cli/commands/CoreCommands.ts +6 -5
- package/src/cli/commands/DrizzleCommands.ts +65 -57
- package/src/cli/commands/VerifyCommands.ts +1 -1
- package/src/cli/services/ProjectUtils.ts +44 -38
- package/src/orm/providers/DrizzleKitProvider.ts +1 -1
- package/src/retry/descriptors/$retry.ts +5 -3
- package/src/server/providers/NodeHttpServerProvider.ts +1 -1
- package/src/vite/helpers/boot.ts +3 -3
- package/dist/api-files/index.cjs +0 -1293
- package/dist/api-files/index.cjs.map +0 -1
- package/dist/api-files/index.d.cts +0 -829
- package/dist/api-jobs/index.cjs +0 -274
- package/dist/api-jobs/index.cjs.map +0 -1
- package/dist/api-jobs/index.d.cts +0 -654
- package/dist/api-notifications/index.cjs +0 -380
- package/dist/api-notifications/index.cjs.map +0 -1
- package/dist/api-notifications/index.d.cts +0 -289
- package/dist/api-parameters/index.cjs +0 -66
- package/dist/api-parameters/index.cjs.map +0 -1
- package/dist/api-parameters/index.d.cts +0 -84
- package/dist/api-users/index.cjs +0 -6009
- package/dist/api-users/index.cjs.map +0 -1
- package/dist/api-users/index.d.cts +0 -4740
- package/dist/api-verifications/index.cjs +0 -407
- package/dist/api-verifications/index.cjs.map +0 -1
- package/dist/api-verifications/index.d.cts +0 -207
- package/dist/batch/index.cjs +0 -408
- package/dist/batch/index.cjs.map +0 -1
- package/dist/batch/index.d.cts +0 -330
- package/dist/bin/index.cjs +0 -17
- package/dist/bin/index.cjs.map +0 -1
- package/dist/bin/index.d.cts +0 -1
- package/dist/bucket/index.cjs +0 -303
- package/dist/bucket/index.cjs.map +0 -1
- package/dist/bucket/index.d.cts +0 -355
- package/dist/cache/index.cjs +0 -241
- package/dist/cache/index.cjs.map +0 -1
- package/dist/cache/index.d.cts +0 -202
- package/dist/cache-redis/index.cjs +0 -84
- package/dist/cache-redis/index.cjs.map +0 -1
- package/dist/cache-redis/index.d.cts +0 -40
- package/dist/cli/chunk-DSlc6foC.cjs +0 -43
- package/dist/cli/dist-BBPjuQ56.js +0 -2778
- package/dist/cli/dist-Sz2EXvQX.cjs.map +0 -1
- package/dist/cli/index.cjs +0 -1241
- package/dist/cli/index.cjs.map +0 -1
- package/dist/cli/index.d.cts +0 -422
- package/dist/command/index.cjs +0 -693
- package/dist/command/index.cjs.map +0 -1
- package/dist/command/index.d.cts +0 -340
- package/dist/core/index.cjs +0 -2264
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.d.cts +0 -1927
- package/dist/datetime/index.cjs +0 -318
- package/dist/datetime/index.cjs.map +0 -1
- package/dist/datetime/index.d.cts +0 -145
- package/dist/email/index.cjs +0 -10874
- package/dist/email/index.cjs.map +0 -1
- package/dist/email/index.d.cts +0 -186
- package/dist/fake/index.cjs +0 -34641
- package/dist/fake/index.cjs.map +0 -1
- package/dist/fake/index.d.cts +0 -74
- package/dist/file/index.cjs +0 -1212
- package/dist/file/index.cjs.map +0 -1
- package/dist/file/index.d.cts +0 -698
- package/dist/lock/index.cjs +0 -226
- package/dist/lock/index.cjs.map +0 -1
- package/dist/lock/index.d.cts +0 -361
- package/dist/lock-redis/index.cjs +0 -113
- package/dist/lock-redis/index.cjs.map +0 -1
- package/dist/lock-redis/index.d.cts +0 -24
- package/dist/logger/index.cjs +0 -521
- package/dist/logger/index.cjs.map +0 -1
- package/dist/logger/index.d.cts +0 -281
- package/dist/orm/index.cjs +0 -2986
- package/dist/orm/index.cjs.map +0 -1
- package/dist/orm/index.d.cts +0 -2213
- package/dist/queue/index.cjs +0 -1044
- package/dist/queue/index.cjs.map +0 -1
- package/dist/queue/index.d.cts +0 -1265
- package/dist/queue-redis/index.cjs +0 -873
- package/dist/queue-redis/index.cjs.map +0 -1
- package/dist/queue-redis/index.d.cts +0 -82
- package/dist/redis/index.cjs +0 -153
- package/dist/redis/index.cjs.map +0 -1
- package/dist/redis/index.d.cts +0 -82
- package/dist/retry/index.cjs +0 -146
- package/dist/retry/index.cjs.map +0 -1
- package/dist/retry/index.d.cts +0 -172
- package/dist/router/index.cjs +0 -111
- package/dist/router/index.cjs.map +0 -1
- package/dist/router/index.d.cts +0 -46
- package/dist/scheduler/index.cjs +0 -576
- package/dist/scheduler/index.cjs.map +0 -1
- package/dist/scheduler/index.d.cts +0 -145
- package/dist/security/index.cjs +0 -2402
- package/dist/security/index.cjs.map +0 -1
- package/dist/security/index.d.cts +0 -598
- package/dist/server/index.cjs +0 -1680
- package/dist/server/index.cjs.map +0 -1
- package/dist/server/index.d.cts +0 -810
- package/dist/server-auth/index.cjs +0 -3146
- package/dist/server-auth/index.cjs.map +0 -1
- package/dist/server-auth/index.d.cts +0 -1164
- package/dist/server-cache/index.cjs +0 -252
- package/dist/server-cache/index.cjs.map +0 -1
- package/dist/server-cache/index.d.cts +0 -164
- package/dist/server-compress/index.cjs +0 -141
- package/dist/server-compress/index.cjs.map +0 -1
- package/dist/server-compress/index.d.cts +0 -38
- package/dist/server-cookies/index.cjs +0 -234
- package/dist/server-cookies/index.cjs.map +0 -1
- package/dist/server-cookies/index.d.cts +0 -144
- package/dist/server-cors/index.cjs +0 -201
- package/dist/server-cors/index.cjs.map +0 -1
- package/dist/server-cors/index.d.cts +0 -140
- package/dist/server-health/index.cjs +0 -62
- package/dist/server-health/index.cjs.map +0 -1
- package/dist/server-health/index.d.cts +0 -58
- package/dist/server-helmet/index.cjs +0 -131
- package/dist/server-helmet/index.cjs.map +0 -1
- package/dist/server-helmet/index.d.cts +0 -97
- package/dist/server-links/index.cjs +0 -992
- package/dist/server-links/index.cjs.map +0 -1
- package/dist/server-links/index.d.cts +0 -513
- package/dist/server-metrics/index.cjs +0 -4535
- package/dist/server-metrics/index.cjs.map +0 -1
- package/dist/server-metrics/index.d.cts +0 -35
- package/dist/server-multipart/index.cjs +0 -237
- package/dist/server-multipart/index.cjs.map +0 -1
- package/dist/server-multipart/index.d.cts +0 -50
- package/dist/server-proxy/index.cjs +0 -186
- package/dist/server-proxy/index.cjs.map +0 -1
- package/dist/server-proxy/index.d.cts +0 -234
- package/dist/server-rate-limit/index.cjs +0 -241
- package/dist/server-rate-limit/index.cjs.map +0 -1
- package/dist/server-rate-limit/index.d.cts +0 -183
- package/dist/server-security/index.cjs +0 -316
- package/dist/server-security/index.cjs.map +0 -1
- package/dist/server-security/index.d.cts +0 -173
- package/dist/server-static/index.cjs +0 -170
- package/dist/server-static/index.cjs.map +0 -1
- package/dist/server-static/index.d.cts +0 -121
- package/dist/server-swagger/index.cjs +0 -1021
- package/dist/server-swagger/index.cjs.map +0 -1
- package/dist/server-swagger/index.d.cts +0 -382
- package/dist/sms/index.cjs +0 -221
- package/dist/sms/index.cjs.map +0 -1
- package/dist/sms/index.d.cts +0 -130
- package/dist/thread/index.cjs +0 -350
- package/dist/thread/index.cjs.map +0 -1
- package/dist/thread/index.d.cts +0 -260
- package/dist/topic/index.cjs +0 -282
- package/dist/topic/index.cjs.map +0 -1
- package/dist/topic/index.d.cts +0 -523
- package/dist/topic-redis/index.cjs +0 -71
- package/dist/topic-redis/index.cjs.map +0 -1
- package/dist/topic-redis/index.d.cts +0 -42
- package/dist/vite/index.cjs +0 -1077
- package/dist/vite/index.cjs.map +0 -1
- package/dist/vite/index.d.cts +0 -542
- package/dist/websocket/index.cjs +0 -1117
- package/dist/websocket/index.cjs.map +0 -1
- package/dist/websocket/index.d.cts +0 -861
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
let alepha = require("alepha");
|
|
2
|
-
let alepha_cache = require("alepha/cache");
|
|
3
|
-
let node_crypto = require("node:crypto");
|
|
4
|
-
let alepha_datetime = require("alepha/datetime");
|
|
5
|
-
let alepha_logger = require("alepha/logger");
|
|
6
|
-
let alepha_server = require("alepha/server");
|
|
7
|
-
|
|
8
|
-
//#region src/server-cache/providers/ServerCacheProvider.ts
|
|
9
|
-
alepha_server.ActionDescriptor.prototype.invalidate = async function() {
|
|
10
|
-
await this.alepha.inject(ServerCacheProvider).invalidate(this.route);
|
|
11
|
-
};
|
|
12
|
-
var ServerCacheProvider = class {
|
|
13
|
-
log = (0, alepha_logger.$logger)();
|
|
14
|
-
alepha = (0, alepha.$inject)(alepha.Alepha);
|
|
15
|
-
time = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
|
|
16
|
-
cache = (0, alepha_cache.$cache)({ provider: "memory" });
|
|
17
|
-
generateETag(content) {
|
|
18
|
-
return `"${(0, node_crypto.createHash)("md5").update(content).digest("hex")}"`;
|
|
19
|
-
}
|
|
20
|
-
async invalidate(route) {
|
|
21
|
-
if (!route.cache) return;
|
|
22
|
-
await this.cache.invalidate(this.createCacheKey(route));
|
|
23
|
-
}
|
|
24
|
-
onActionRequest = (0, alepha.$hook)({
|
|
25
|
-
on: "action:onRequest",
|
|
26
|
-
handler: async ({ action, request }) => {
|
|
27
|
-
const cache = action.route.cache;
|
|
28
|
-
if (this.shouldStore(cache)) {
|
|
29
|
-
const key = this.createCacheKey(action.route, request);
|
|
30
|
-
const cached = await this.cache.get(key);
|
|
31
|
-
if (cached) {
|
|
32
|
-
const body = cached.contentType === "application/json" ? JSON.parse(cached.body) : cached.body;
|
|
33
|
-
this.log.trace("Cache hit for action", {
|
|
34
|
-
key,
|
|
35
|
-
action: action.name
|
|
36
|
-
});
|
|
37
|
-
request.reply.body = body;
|
|
38
|
-
} else this.log.trace("Cache miss for action", {
|
|
39
|
-
key,
|
|
40
|
-
action: action.name
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
onActionResponse = (0, alepha.$hook)({
|
|
46
|
-
on: "action:onResponse",
|
|
47
|
-
handler: async ({ action, request, response }) => {
|
|
48
|
-
const cache = action.route.cache;
|
|
49
|
-
if (!this.shouldStore(cache) || !response) return;
|
|
50
|
-
if (request.reply.status && request.reply.status >= 400) return;
|
|
51
|
-
const contentType = typeof response === "string" ? "text/plain" : "application/json";
|
|
52
|
-
const body = contentType === "text/plain" ? response : JSON.stringify(response);
|
|
53
|
-
const generatedEtag = this.generateETag(body);
|
|
54
|
-
const lastModified = this.time.toISOString();
|
|
55
|
-
const key = this.createCacheKey(action.route, request);
|
|
56
|
-
this.log.trace("Storing response", {
|
|
57
|
-
key,
|
|
58
|
-
action: action.name
|
|
59
|
-
});
|
|
60
|
-
await this.cache.set(key, {
|
|
61
|
-
body,
|
|
62
|
-
lastModified,
|
|
63
|
-
contentType,
|
|
64
|
-
hash: generatedEtag
|
|
65
|
-
});
|
|
66
|
-
const cacheControl = this.buildCacheControlHeader(cache);
|
|
67
|
-
if (cacheControl) request.reply.setHeader("cache-control", cacheControl);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
onRequest = (0, alepha.$hook)({
|
|
71
|
-
on: "server:onRequest",
|
|
72
|
-
handler: async ({ route, request }) => {
|
|
73
|
-
const cache = route.cache;
|
|
74
|
-
const shouldStore = this.shouldStore(cache);
|
|
75
|
-
const shouldUseEtag = this.shouldUseEtag(cache);
|
|
76
|
-
if (!shouldStore && !shouldUseEtag) return;
|
|
77
|
-
const key = this.createCacheKey(route, request);
|
|
78
|
-
const cached = await this.cache.get(key);
|
|
79
|
-
if (cached) {
|
|
80
|
-
if (request.headers["if-none-match"] === cached.hash || request.headers["if-modified-since"] === cached.lastModified) {
|
|
81
|
-
request.reply.status = 304;
|
|
82
|
-
request.reply.setHeader("etag", cached.hash);
|
|
83
|
-
request.reply.setHeader("last-modified", cached.lastModified);
|
|
84
|
-
this.log.trace("ETag match, returning 304", {
|
|
85
|
-
route: route.path,
|
|
86
|
-
etag: cached.hash
|
|
87
|
-
});
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (shouldStore) {
|
|
91
|
-
this.log.trace("Cache hit for route", {
|
|
92
|
-
key,
|
|
93
|
-
route: route.path
|
|
94
|
-
});
|
|
95
|
-
request.reply.body = cached.body;
|
|
96
|
-
request.reply.status = cached.status ?? 200;
|
|
97
|
-
if (cached.contentType) request.reply.setHeader("Content-Type", cached.contentType);
|
|
98
|
-
request.reply.setHeader("etag", cached.hash);
|
|
99
|
-
request.reply.setHeader("last-modified", cached.lastModified);
|
|
100
|
-
}
|
|
101
|
-
} else if (shouldStore) this.log.trace("Cache miss for route", {
|
|
102
|
-
key,
|
|
103
|
-
route: route.path
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
onSend = (0, alepha.$hook)({
|
|
108
|
-
on: "server:onSend",
|
|
109
|
-
handler: async ({ route, request }) => {
|
|
110
|
-
const cache = route.cache;
|
|
111
|
-
const shouldStore = this.shouldStore(cache);
|
|
112
|
-
const shouldUseEtag = this.shouldUseEtag(cache);
|
|
113
|
-
if (request.reply.headers.etag) return;
|
|
114
|
-
if (!shouldStore && shouldUseEtag && request.reply.body != null && (typeof request.reply.body === "string" || Buffer.isBuffer(request.reply.body))) {
|
|
115
|
-
const generatedEtag = this.generateETag(request.reply.body);
|
|
116
|
-
if (request.headers["if-none-match"] === generatedEtag) {
|
|
117
|
-
request.reply.status = 304;
|
|
118
|
-
request.reply.body = void 0;
|
|
119
|
-
request.reply.setHeader("etag", generatedEtag);
|
|
120
|
-
this.log.trace("ETag match on send, returning 304", {
|
|
121
|
-
route: route.path,
|
|
122
|
-
etag: generatedEtag
|
|
123
|
-
});
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
onResponse = (0, alepha.$hook)({
|
|
130
|
-
on: "server:onResponse",
|
|
131
|
-
priority: "first",
|
|
132
|
-
handler: async ({ route, request, response }) => {
|
|
133
|
-
const cache = route.cache;
|
|
134
|
-
const cacheControl = this.buildCacheControlHeader(cache);
|
|
135
|
-
if (cacheControl) response.headers["cache-control"] = cacheControl;
|
|
136
|
-
const shouldStore = this.shouldStore(cache);
|
|
137
|
-
const shouldUseEtag = this.shouldUseEtag(cache);
|
|
138
|
-
if (!shouldStore && !shouldUseEtag) return;
|
|
139
|
-
if (typeof response.body !== "string") return;
|
|
140
|
-
if (response.status && response.status >= 400) return;
|
|
141
|
-
const key = this.createCacheKey(route, request);
|
|
142
|
-
const generatedEtag = this.generateETag(response.body);
|
|
143
|
-
const lastModified = this.time.toISOString();
|
|
144
|
-
response.headers ??= {};
|
|
145
|
-
if (shouldStore) {
|
|
146
|
-
this.log.trace("Storing response", {
|
|
147
|
-
key,
|
|
148
|
-
route: route.path,
|
|
149
|
-
cache: !!cache,
|
|
150
|
-
etag: shouldUseEtag
|
|
151
|
-
});
|
|
152
|
-
await this.cache.set(key, {
|
|
153
|
-
body: response.body,
|
|
154
|
-
status: response.status,
|
|
155
|
-
contentType: response.headers?.["content-type"],
|
|
156
|
-
lastModified,
|
|
157
|
-
hash: generatedEtag
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
if (shouldUseEtag) {
|
|
161
|
-
response.headers.etag = generatedEtag;
|
|
162
|
-
response.headers["last-modified"] = lastModified;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
buildCacheControlHeader(cache) {
|
|
167
|
-
if (!cache) return;
|
|
168
|
-
if (cache === true || typeof cache === "string" || typeof cache === "number") return;
|
|
169
|
-
const control = cache.control;
|
|
170
|
-
if (!control) return;
|
|
171
|
-
if (typeof control === "string") return control;
|
|
172
|
-
if (control === true) return "public, max-age=300";
|
|
173
|
-
const directives = [];
|
|
174
|
-
if (control.public) directives.push("public");
|
|
175
|
-
if (control.private) directives.push("private");
|
|
176
|
-
if (control.noCache) directives.push("no-cache");
|
|
177
|
-
if (control.noStore) directives.push("no-store");
|
|
178
|
-
if (control.maxAge !== void 0) {
|
|
179
|
-
const maxAgeSeconds = this.durationToSeconds(control.maxAge);
|
|
180
|
-
directives.push(`max-age=${maxAgeSeconds}`);
|
|
181
|
-
}
|
|
182
|
-
if (control.sMaxAge !== void 0) {
|
|
183
|
-
const sMaxAgeSeconds = this.durationToSeconds(control.sMaxAge);
|
|
184
|
-
directives.push(`s-maxage=${sMaxAgeSeconds}`);
|
|
185
|
-
}
|
|
186
|
-
if (control.mustRevalidate) directives.push("must-revalidate");
|
|
187
|
-
if (control.proxyRevalidate) directives.push("proxy-revalidate");
|
|
188
|
-
if (control.immutable) directives.push("immutable");
|
|
189
|
-
return directives.length > 0 ? directives.join(", ") : void 0;
|
|
190
|
-
}
|
|
191
|
-
durationToSeconds(duration) {
|
|
192
|
-
if (typeof duration === "number") return duration;
|
|
193
|
-
return this.time.duration(duration).asSeconds();
|
|
194
|
-
}
|
|
195
|
-
shouldStore(cache) {
|
|
196
|
-
if (!cache) return false;
|
|
197
|
-
if (cache === true) return true;
|
|
198
|
-
if (typeof cache === "object" && cache.store) return true;
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
shouldUseEtag(cache) {
|
|
202
|
-
if (cache === true) return true;
|
|
203
|
-
if (typeof cache === "object" && cache.etag) return true;
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
createCacheKey(route, config) {
|
|
207
|
-
const params = [];
|
|
208
|
-
for (const [key, value] of Object.entries(config?.params ?? {})) params.push(`${key}=${value}`);
|
|
209
|
-
for (const [key, value] of Object.entries(config?.query ?? {})) params.push(`${key}=${value}`);
|
|
210
|
-
return `${route.method}:${route.path.replaceAll(":", "")}:${params.join(",").replaceAll(":", "")}`;
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
//#endregion
|
|
215
|
-
//#region src/server-cache/index.ts
|
|
216
|
-
/**
|
|
217
|
-
* Plugin for Alepha Server that provides server-side caching capabilities.
|
|
218
|
-
* It uses the Alepha Cache module to cache responses from server actions ($action).
|
|
219
|
-
* It also provides a ETag-based cache invalidation mechanism.
|
|
220
|
-
*
|
|
221
|
-
* @example
|
|
222
|
-
* ```ts
|
|
223
|
-
* import { Alepha } from "alepha";
|
|
224
|
-
* import { $action } from "alepha/server";
|
|
225
|
-
* import { AlephaServerCache } from "alepha/server/cache";
|
|
226
|
-
*
|
|
227
|
-
* class ApiServer {
|
|
228
|
-
* hello = $action({
|
|
229
|
-
* cache: true,
|
|
230
|
-
* handler: () => "Hello, World!",
|
|
231
|
-
* });
|
|
232
|
-
* }
|
|
233
|
-
*
|
|
234
|
-
* const alepha = Alepha.create()
|
|
235
|
-
* .with(AlephaServerCache)
|
|
236
|
-
* .with(ApiServer);
|
|
237
|
-
*
|
|
238
|
-
* run(alepha);
|
|
239
|
-
* ```
|
|
240
|
-
*
|
|
241
|
-
* @see {@link ServerCacheProvider}
|
|
242
|
-
* @module alepha.server.cache
|
|
243
|
-
*/
|
|
244
|
-
const AlephaServerCache = (0, alepha.$module)({
|
|
245
|
-
name: "alepha.server.cache",
|
|
246
|
-
services: [alepha_cache.AlephaCache, ServerCacheProvider]
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
//#endregion
|
|
250
|
-
exports.AlephaServerCache = AlephaServerCache;
|
|
251
|
-
exports.ServerCacheProvider = ServerCacheProvider;
|
|
252
|
-
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["Alepha","DateTimeProvider","directives: string[]","params: string[]","AlephaCache"],"sources":["../../src/server-cache/providers/ServerCacheProvider.ts","../../src/server-cache/index.ts"],"sourcesContent":["import type { BinaryLike } from \"node:crypto\";\nimport { createHash } from \"node:crypto\";\nimport { $hook, $inject, Alepha } from \"alepha\";\nimport { $cache, type CacheDescriptorOptions } from \"alepha/cache\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport {\n ActionDescriptor,\n type RequestConfigSchema,\n type ServerRequest,\n type ServerRoute,\n} from \"alepha/server\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/server\" {\n interface ServerRoute {\n /**\n * Enable caching for this route.\n * - If true: enables both store and etag\n * - If object: fine-grained control over store, etag, and cache-control headers\n *\n * @default false\n */\n cache?: ServerRouteCache;\n }\n\n interface ActionDescriptor<TConfig extends RequestConfigSchema> {\n invalidate: () => Promise<void>;\n }\n}\n\nActionDescriptor.prototype.invalidate = async function (\n this: ActionDescriptor<RequestConfigSchema>,\n) {\n await this.alepha.inject(ServerCacheProvider).invalidate(this.route);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ServerCacheProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly time = $inject(DateTimeProvider);\n protected readonly cache = $cache<RouteCacheEntry>({\n provider: \"memory\",\n });\n\n public generateETag(content: BinaryLike): string {\n return `\"${createHash(\"md5\").update(content).digest(\"hex\")}\"`;\n }\n\n public async invalidate(route: ServerRoute) {\n const cache = route.cache;\n if (!cache) {\n return;\n }\n\n await this.cache.invalidate(this.createCacheKey(route));\n }\n\n protected readonly onActionRequest = $hook({\n on: \"action:onRequest\",\n handler: async ({ action, request }) => {\n const cache = action.route.cache;\n\n const shouldStore = this.shouldStore(cache);\n\n // Only check cache if storing is enabled\n if (shouldStore) {\n const key = this.createCacheKey(action.route, request);\n const cached = await this.cache.get(key);\n\n if (cached) {\n const body =\n cached.contentType === \"application/json\"\n ? JSON.parse(cached.body)\n : cached.body;\n\n this.log.trace(\"Cache hit for action\", {\n key,\n action: action.name,\n });\n\n request.reply.body = body; // just re-use, full trust\n } else {\n this.log.trace(\"Cache miss for action\", {\n key,\n action: action.name,\n });\n }\n }\n },\n });\n\n protected readonly onActionResponse = $hook({\n on: \"action:onResponse\",\n handler: async ({ action, request, response }) => {\n const cache = action.route.cache;\n\n const shouldStore = this.shouldStore(cache);\n\n if (!shouldStore || !response) {\n return;\n }\n\n // Don't cache error responses (status >= 400)\n if (request.reply.status && request.reply.status >= 400) {\n return;\n }\n\n // TODO: serialize the response body, exactly like in the server response hook\n // this is bad\n const contentType =\n typeof response === \"string\" ? \"text/plain\" : \"application/json\";\n const body =\n contentType === \"text/plain\" ? response : JSON.stringify(response);\n\n const generatedEtag = this.generateETag(body);\n const lastModified = this.time.toISOString();\n\n // Store response for cached actions\n const key = this.createCacheKey(action.route, request);\n\n this.log.trace(\"Storing response\", {\n key,\n action: action.name,\n });\n\n await this.cache.set(key, {\n body: body,\n lastModified,\n contentType: contentType,\n hash: generatedEtag,\n });\n\n // Set Cache-Control header if configured (for HTTP responses)\n const cacheControl = this.buildCacheControlHeader(cache);\n if (cacheControl) {\n request.reply.setHeader(\"cache-control\", cacheControl);\n }\n },\n });\n\n protected readonly onRequest = $hook({\n on: \"server:onRequest\",\n handler: async ({ route, request }) => {\n const cache = route.cache;\n\n const shouldStore = this.shouldStore(cache);\n const shouldUseEtag = this.shouldUseEtag(cache);\n\n // Check for cached response or ETag\n if (!shouldStore && !shouldUseEtag) {\n return;\n }\n\n const key = this.createCacheKey(route, request);\n const cached = await this.cache.get(key);\n\n if (cached) {\n // Check if client has matching ETag - return 304 for both cached and etag-only routes\n if (\n request.headers[\"if-none-match\"] === cached.hash ||\n request.headers[\"if-modified-since\"] === cached.lastModified\n ) {\n request.reply.status = 304;\n request.reply.setHeader(\"etag\", cached.hash);\n request.reply.setHeader(\"last-modified\", cached.lastModified);\n this.log.trace(\"ETag match, returning 304\", {\n route: route.path,\n etag: cached.hash,\n });\n return;\n }\n\n // Only serve cached content if storing is enabled (not for etag-only routes)\n if (shouldStore) {\n this.log.trace(\"Cache hit for route\", {\n key,\n route: route.path,\n });\n\n // if the cache is found, we can skip the request processing\n // and return the cached response\n request.reply.body = cached.body;\n request.reply.status = cached.status ?? 200;\n\n if (cached.contentType) {\n request.reply.setHeader(\"Content-Type\", cached.contentType);\n }\n\n request.reply.setHeader(\"etag\", cached.hash);\n request.reply.setHeader(\"last-modified\", cached.lastModified);\n }\n } else if (shouldStore) {\n this.log.trace(\"Cache miss for route\", {\n key,\n route: route.path,\n });\n }\n },\n });\n\n protected readonly onSend = $hook({\n on: \"server:onSend\",\n handler: async ({ route, request }) => {\n // before sending the response, check if the ETag matches\n // and if so, return a 304 Not Modified response\n // -> this is only relevant for etag-only routes, not cached routes <-\n const cache = route.cache;\n\n const shouldStore = this.shouldStore(cache);\n const shouldUseEtag = this.shouldUseEtag(cache);\n\n if (request.reply.headers.etag) {\n // ETag already set, skip\n return;\n }\n\n if (\n !shouldStore &&\n shouldUseEtag &&\n request.reply.body != null &&\n (typeof request.reply.body === \"string\" ||\n Buffer.isBuffer(request.reply.body))\n ) {\n const generatedEtag = this.generateETag(request.reply.body);\n\n if (request.headers[\"if-none-match\"] === generatedEtag) {\n request.reply.status = 304;\n request.reply.body = undefined;\n request.reply.setHeader(\"etag\", generatedEtag);\n this.log.trace(\"ETag match on send, returning 304\", {\n route: route.path,\n etag: generatedEtag,\n });\n return;\n }\n }\n },\n });\n\n protected readonly onResponse = $hook({\n on: \"server:onResponse\",\n priority: \"first\",\n handler: async ({ route, request, response }) => {\n const cache = route.cache;\n\n // Set Cache-Control header if configured\n const cacheControl = this.buildCacheControlHeader(cache);\n if (cacheControl) {\n response.headers[\"cache-control\"] = cacheControl;\n }\n\n const shouldStore = this.shouldStore(cache);\n const shouldUseEtag = this.shouldUseEtag(cache);\n\n // Skip if neither cache nor etag is enabled\n if (!shouldStore && !shouldUseEtag) {\n return;\n }\n\n // Only process string responses (text, html, json, etc.)\n // Buffer is not supported by alepha/cache for now\n if (typeof response.body !== \"string\") {\n return;\n }\n\n // Don't cache error responses (status >= 400)\n if (response.status && response.status >= 400) {\n return;\n }\n\n const key = this.createCacheKey(route, request);\n const generatedEtag = this.generateETag(response.body);\n const lastModified = this.time.toISOString();\n\n // Initialize headers if not present\n response.headers ??= {};\n\n // Store response if storing is enabled\n if (shouldStore) {\n this.log.trace(\"Storing response\", {\n key,\n route: route.path,\n cache: !!cache,\n etag: shouldUseEtag,\n });\n\n await this.cache.set(key, {\n body: response.body,\n status: response.status,\n contentType: response.headers?.[\"content-type\"],\n lastModified,\n hash: generatedEtag,\n });\n }\n\n // Set ETag headers if etag is enabled\n if (shouldUseEtag) {\n response.headers.etag = generatedEtag;\n response.headers[\"last-modified\"] = lastModified;\n }\n },\n });\n\n public buildCacheControlHeader(cache?: ServerRouteCache): string | undefined {\n if (!cache) {\n return undefined;\n }\n\n // If cache is true or a DurationLike, no Cache-Control header is set\n if (\n cache === true ||\n typeof cache === \"string\" ||\n typeof cache === \"number\"\n ) {\n return undefined;\n }\n\n const control = cache.control;\n if (!control) {\n return undefined;\n }\n\n // If control is a string, return it directly\n if (typeof control === \"string\") {\n return control;\n }\n\n // If control is true, return default Cache-Control\n if (control === true) {\n return \"public, max-age=300\";\n }\n\n // Build Cache-Control from object directives\n const directives: string[] = [];\n\n if (control.public) {\n directives.push(\"public\");\n }\n if (control.private) {\n directives.push(\"private\");\n }\n if (control.noCache) {\n directives.push(\"no-cache\");\n }\n if (control.noStore) {\n directives.push(\"no-store\");\n }\n if (control.maxAge !== undefined) {\n const maxAgeSeconds = this.durationToSeconds(control.maxAge);\n directives.push(`max-age=${maxAgeSeconds}`);\n }\n if (control.sMaxAge !== undefined) {\n const sMaxAgeSeconds = this.durationToSeconds(control.sMaxAge);\n directives.push(`s-maxage=${sMaxAgeSeconds}`);\n }\n if (control.mustRevalidate) {\n directives.push(\"must-revalidate\");\n }\n if (control.proxyRevalidate) {\n directives.push(\"proxy-revalidate\");\n }\n if (control.immutable) {\n directives.push(\"immutable\");\n }\n\n return directives.length > 0 ? directives.join(\", \") : undefined;\n }\n\n protected durationToSeconds(duration: number | DurationLike): number {\n if (typeof duration === \"number\") {\n return duration;\n }\n\n return this.time.duration(duration).asSeconds();\n }\n\n protected shouldStore(cache?: ServerRouteCache): boolean {\n if (!cache) return false;\n if (cache === true) return true;\n if (typeof cache === \"object\" && cache.store) return true;\n return false;\n }\n\n protected shouldUseEtag(cache?: ServerRouteCache): boolean {\n // cache: true enables etag\n if (cache === true) return true;\n // Check object form\n if (typeof cache === \"object\" && cache.etag) return true;\n return false;\n }\n\n protected createCacheKey(route: ServerRoute, config?: ServerRequest): string {\n const params: string[] = [];\n for (const [key, value] of Object.entries(config?.params ?? {})) {\n params.push(`${key}=${value}`);\n }\n for (const [key, value] of Object.entries(config?.query ?? {})) {\n params.push(`${key}=${value}`);\n }\n\n return `${route.method}:${route.path.replaceAll(\":\", \"\")}:${params.join(\",\").replaceAll(\":\", \"\")}`;\n }\n}\n\nexport type ServerRouteCache =\n /**\n * If true, enables caching with:\n * - store: true\n * - etag: true\n */\n | boolean\n /**\n * Object configuration for fine-grained cache control.\n *\n * If empty, no caching will be applied.\n */\n | {\n /**\n * If true, enables storing cached responses. (in-memory, Redis, @see alepha/cache for other providers)\n * If a DurationLike is provided, it will be used as the TTL for the cache.\n * If CacheDescriptorOptions is provided, it will be used to configure the cache storage.\n *\n * @default false\n */\n store?: true | DurationLike | CacheDescriptorOptions;\n /**\n * If true, enables ETag support for the cached responses.\n */\n etag?: true;\n /**\n * - If true, sets Cache-Control to \"public, max-age=300\" (5 minutes).\n * - If string, sets Cache-Control to the provided value directly.\n * - If object, configures Cache-Control directives.\n */\n control?: /**\n * If true, sets Cache-Control to \"public, max-age=300\" (5 minutes).\n */\n | true\n /**\n * If string, sets Cache-Control to the provided value directly.\n */\n | string\n /**\n * If object, configures Cache-Control directives.\n */\n | {\n /**\n * Indicates that the response may be cached by any cache.\n */\n public?: boolean;\n /**\n * Indicates that the response is intended for a single user and must not be stored by a shared cache.\n */\n private?: boolean;\n /**\n * Forces caches to submit the request to the origin server for validation before releasing a cached copy.\n */\n noCache?: boolean;\n /**\n * Instructs caches not to store the response.\n */\n noStore?: boolean;\n /**\n * Maximum amount of time a resource is considered fresh.\n * Can be specified as a number (seconds) or as a DurationLike object.\n *\n * @example 300 // 5 minutes in seconds\n * @example { minutes: 5 } // 5 minutes\n * @example { hours: 1 } // 1 hour\n */\n maxAge?: number | DurationLike;\n /**\n * Overrides max-age for shared caches (e.g., CDNs).\n * Can be specified as a number (seconds) or as a DurationLike object.\n */\n sMaxAge?: number | DurationLike;\n /**\n * Indicates that once a resource becomes stale, caches must not use it without successful validation.\n */\n mustRevalidate?: boolean;\n /**\n * Similar to must-revalidate, but only for shared caches.\n */\n proxyRevalidate?: boolean;\n /**\n * Indicates that the response can be stored but must be revalidated before each use.\n */\n immutable?: boolean;\n };\n };\n\ninterface RouteCacheEntry {\n contentType?: string;\n body: any;\n status?: number;\n lastModified: string;\n hash: string;\n}\n","import { $module } from \"alepha\";\nimport { AlephaCache } from \"alepha/cache\";\nimport { ServerCacheProvider } from \"./providers/ServerCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/ServerCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Server that provides server-side caching capabilities.\n * It uses the Alepha Cache module to cache responses from server actions ($action).\n * It also provides a ETag-based cache invalidation mechanism.\n *\n * @example\n * ```ts\n * import { Alepha } from \"alepha\";\n * import { $action } from \"alepha/server\";\n * import { AlephaServerCache } from \"alepha/server/cache\";\n *\n * class ApiServer {\n * hello = $action({\n * cache: true,\n * handler: () => \"Hello, World!\",\n * });\n * }\n *\n * const alepha = Alepha.create()\n * .with(AlephaServerCache)\n * .with(ApiServer);\n *\n * run(alepha);\n * ```\n *\n * @see {@link ServerCacheProvider}\n * @module alepha.server.cache\n */\nexport const AlephaServerCache = $module({\n name: \"alepha.server.cache\",\n services: [AlephaCache, ServerCacheProvider],\n});\n"],"mappings":"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"}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import * as alepha1 from "alepha";
|
|
2
|
-
import { Alepha } from "alepha";
|
|
3
|
-
import * as alepha_logger0 from "alepha/logger";
|
|
4
|
-
import * as alepha_cache0 from "alepha/cache";
|
|
5
|
-
import { CacheDescriptorOptions } from "alepha/cache";
|
|
6
|
-
import { BinaryLike } from "node:crypto";
|
|
7
|
-
import { DateTimeProvider, DurationLike } from "alepha/datetime";
|
|
8
|
-
import { RequestConfigSchema, ServerRequest, ServerRoute } from "alepha/server";
|
|
9
|
-
|
|
10
|
-
//#region src/server-cache/providers/ServerCacheProvider.d.ts
|
|
11
|
-
declare module "alepha/server" {
|
|
12
|
-
interface ServerRoute {
|
|
13
|
-
/**
|
|
14
|
-
* Enable caching for this route.
|
|
15
|
-
* - If true: enables both store and etag
|
|
16
|
-
* - If object: fine-grained control over store, etag, and cache-control headers
|
|
17
|
-
*
|
|
18
|
-
* @default false
|
|
19
|
-
*/
|
|
20
|
-
cache?: ServerRouteCache;
|
|
21
|
-
}
|
|
22
|
-
interface ActionDescriptor<TConfig extends RequestConfigSchema> {
|
|
23
|
-
invalidate: () => Promise<void>;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
declare class ServerCacheProvider {
|
|
27
|
-
protected readonly log: alepha_logger0.Logger;
|
|
28
|
-
protected readonly alepha: Alepha;
|
|
29
|
-
protected readonly time: DateTimeProvider;
|
|
30
|
-
protected readonly cache: alepha_cache0.CacheDescriptorFn<RouteCacheEntry, any[]>;
|
|
31
|
-
generateETag(content: BinaryLike): string;
|
|
32
|
-
invalidate(route: ServerRoute): Promise<void>;
|
|
33
|
-
protected readonly onActionRequest: alepha1.HookDescriptor<"action:onRequest">;
|
|
34
|
-
protected readonly onActionResponse: alepha1.HookDescriptor<"action:onResponse">;
|
|
35
|
-
protected readonly onRequest: alepha1.HookDescriptor<"server:onRequest">;
|
|
36
|
-
protected readonly onSend: alepha1.HookDescriptor<"server:onSend">;
|
|
37
|
-
protected readonly onResponse: alepha1.HookDescriptor<"server:onResponse">;
|
|
38
|
-
buildCacheControlHeader(cache?: ServerRouteCache): string | undefined;
|
|
39
|
-
protected durationToSeconds(duration: number | DurationLike): number;
|
|
40
|
-
protected shouldStore(cache?: ServerRouteCache): boolean;
|
|
41
|
-
protected shouldUseEtag(cache?: ServerRouteCache): boolean;
|
|
42
|
-
protected createCacheKey(route: ServerRoute, config?: ServerRequest): string;
|
|
43
|
-
}
|
|
44
|
-
type ServerRouteCache =
|
|
45
|
-
/**
|
|
46
|
-
* If true, enables caching with:
|
|
47
|
-
* - store: true
|
|
48
|
-
* - etag: true
|
|
49
|
-
*/
|
|
50
|
-
boolean
|
|
51
|
-
/**
|
|
52
|
-
* Object configuration for fine-grained cache control.
|
|
53
|
-
*
|
|
54
|
-
* If empty, no caching will be applied.
|
|
55
|
-
*/ | {
|
|
56
|
-
/**
|
|
57
|
-
* If true, enables storing cached responses. (in-memory, Redis, @see alepha/cache for other providers)
|
|
58
|
-
* If a DurationLike is provided, it will be used as the TTL for the cache.
|
|
59
|
-
* If CacheDescriptorOptions is provided, it will be used to configure the cache storage.
|
|
60
|
-
*
|
|
61
|
-
* @default false
|
|
62
|
-
*/
|
|
63
|
-
store?: true | DurationLike | CacheDescriptorOptions;
|
|
64
|
-
/**
|
|
65
|
-
* If true, enables ETag support for the cached responses.
|
|
66
|
-
*/
|
|
67
|
-
etag?: true;
|
|
68
|
-
/**
|
|
69
|
-
* - If true, sets Cache-Control to "public, max-age=300" (5 minutes).
|
|
70
|
-
* - If string, sets Cache-Control to the provided value directly.
|
|
71
|
-
* - If object, configures Cache-Control directives.
|
|
72
|
-
*/
|
|
73
|
-
control?: true
|
|
74
|
-
/**
|
|
75
|
-
* If string, sets Cache-Control to the provided value directly.
|
|
76
|
-
*/ | string
|
|
77
|
-
/**
|
|
78
|
-
* If object, configures Cache-Control directives.
|
|
79
|
-
*/ | {
|
|
80
|
-
/**
|
|
81
|
-
* Indicates that the response may be cached by any cache.
|
|
82
|
-
*/
|
|
83
|
-
public?: boolean;
|
|
84
|
-
/**
|
|
85
|
-
* Indicates that the response is intended for a single user and must not be stored by a shared cache.
|
|
86
|
-
*/
|
|
87
|
-
private?: boolean;
|
|
88
|
-
/**
|
|
89
|
-
* Forces caches to submit the request to the origin server for validation before releasing a cached copy.
|
|
90
|
-
*/
|
|
91
|
-
noCache?: boolean;
|
|
92
|
-
/**
|
|
93
|
-
* Instructs caches not to store the response.
|
|
94
|
-
*/
|
|
95
|
-
noStore?: boolean;
|
|
96
|
-
/**
|
|
97
|
-
* Maximum amount of time a resource is considered fresh.
|
|
98
|
-
* Can be specified as a number (seconds) or as a DurationLike object.
|
|
99
|
-
*
|
|
100
|
-
* @example 300 // 5 minutes in seconds
|
|
101
|
-
* @example { minutes: 5 } // 5 minutes
|
|
102
|
-
* @example { hours: 1 } // 1 hour
|
|
103
|
-
*/
|
|
104
|
-
maxAge?: number | DurationLike;
|
|
105
|
-
/**
|
|
106
|
-
* Overrides max-age for shared caches (e.g., CDNs).
|
|
107
|
-
* Can be specified as a number (seconds) or as a DurationLike object.
|
|
108
|
-
*/
|
|
109
|
-
sMaxAge?: number | DurationLike;
|
|
110
|
-
/**
|
|
111
|
-
* Indicates that once a resource becomes stale, caches must not use it without successful validation.
|
|
112
|
-
*/
|
|
113
|
-
mustRevalidate?: boolean;
|
|
114
|
-
/**
|
|
115
|
-
* Similar to must-revalidate, but only for shared caches.
|
|
116
|
-
*/
|
|
117
|
-
proxyRevalidate?: boolean;
|
|
118
|
-
/**
|
|
119
|
-
* Indicates that the response can be stored but must be revalidated before each use.
|
|
120
|
-
*/
|
|
121
|
-
immutable?: boolean;
|
|
122
|
-
};
|
|
123
|
-
};
|
|
124
|
-
interface RouteCacheEntry {
|
|
125
|
-
contentType?: string;
|
|
126
|
-
body: any;
|
|
127
|
-
status?: number;
|
|
128
|
-
lastModified: string;
|
|
129
|
-
hash: string;
|
|
130
|
-
}
|
|
131
|
-
//#endregion
|
|
132
|
-
//#region src/server-cache/index.d.ts
|
|
133
|
-
/**
|
|
134
|
-
* Plugin for Alepha Server that provides server-side caching capabilities.
|
|
135
|
-
* It uses the Alepha Cache module to cache responses from server actions ($action).
|
|
136
|
-
* It also provides a ETag-based cache invalidation mechanism.
|
|
137
|
-
*
|
|
138
|
-
* @example
|
|
139
|
-
* ```ts
|
|
140
|
-
* import { Alepha } from "alepha";
|
|
141
|
-
* import { $action } from "alepha/server";
|
|
142
|
-
* import { AlephaServerCache } from "alepha/server/cache";
|
|
143
|
-
*
|
|
144
|
-
* class ApiServer {
|
|
145
|
-
* hello = $action({
|
|
146
|
-
* cache: true,
|
|
147
|
-
* handler: () => "Hello, World!",
|
|
148
|
-
* });
|
|
149
|
-
* }
|
|
150
|
-
*
|
|
151
|
-
* const alepha = Alepha.create()
|
|
152
|
-
* .with(AlephaServerCache)
|
|
153
|
-
* .with(ApiServer);
|
|
154
|
-
*
|
|
155
|
-
* run(alepha);
|
|
156
|
-
* ```
|
|
157
|
-
*
|
|
158
|
-
* @see {@link ServerCacheProvider}
|
|
159
|
-
* @module alepha.server.cache
|
|
160
|
-
*/
|
|
161
|
-
declare const AlephaServerCache: alepha1.Service<alepha1.Module>;
|
|
162
|
-
//#endregion
|
|
163
|
-
export { AlephaServerCache, ServerCacheProvider, ServerRouteCache };
|
|
164
|
-
//# sourceMappingURL=index.d.cts.map
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
//#region rolldown:runtime
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
-
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
-
__defProp(to, key, {
|
|
14
|
-
get: ((k) => from[k]).bind(null, key),
|
|
15
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return to;
|
|
21
|
-
};
|
|
22
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
-
value: mod,
|
|
24
|
-
enumerable: true
|
|
25
|
-
}) : target, mod));
|
|
26
|
-
|
|
27
|
-
//#endregion
|
|
28
|
-
let alepha = require("alepha");
|
|
29
|
-
let alepha_server = require("alepha/server");
|
|
30
|
-
let node_stream = require("node:stream");
|
|
31
|
-
let node_stream_web = require("node:stream/web");
|
|
32
|
-
let node_util = require("node:util");
|
|
33
|
-
let node_zlib = require("node:zlib");
|
|
34
|
-
node_zlib = __toESM(node_zlib);
|
|
35
|
-
|
|
36
|
-
//#region src/server-compress/providers/ServerCompressProvider.ts
|
|
37
|
-
const gzip = (0, node_util.promisify)(node_zlib.gzip);
|
|
38
|
-
const createGzip = node_zlib.createGzip;
|
|
39
|
-
const brotli = (0, node_util.promisify)(node_zlib.brotliCompress);
|
|
40
|
-
const createBrotliCompress = node_zlib.createBrotliCompress;
|
|
41
|
-
const zstd = node_zlib.zstdCompress ? (0, node_util.promisify)(node_zlib.zstdCompress) : void 0;
|
|
42
|
-
const createZstdCompress = zstd ? node_zlib.createZstdCompress : void 0;
|
|
43
|
-
var ServerCompressProvider = class ServerCompressProvider {
|
|
44
|
-
static compressors = {
|
|
45
|
-
gzip: {
|
|
46
|
-
compress: gzip,
|
|
47
|
-
stream: createGzip
|
|
48
|
-
},
|
|
49
|
-
br: {
|
|
50
|
-
compress: brotli,
|
|
51
|
-
stream: createBrotliCompress
|
|
52
|
-
},
|
|
53
|
-
zstd: zstd && createZstdCompress ? {
|
|
54
|
-
compress: zstd,
|
|
55
|
-
stream: createZstdCompress
|
|
56
|
-
} : void 0
|
|
57
|
-
};
|
|
58
|
-
alepha = (0, alepha.$inject)(alepha.Alepha);
|
|
59
|
-
get options() {
|
|
60
|
-
return {
|
|
61
|
-
allowedContentTypes: [
|
|
62
|
-
"application/json",
|
|
63
|
-
"text/html",
|
|
64
|
-
"application/javascript",
|
|
65
|
-
"text/plain",
|
|
66
|
-
"text/css"
|
|
67
|
-
],
|
|
68
|
-
...this.alepha.state.get("alepha.server.compress.options")
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
onResponse = (0, alepha.$hook)({
|
|
72
|
-
on: "server:onResponse",
|
|
73
|
-
handler: async ({ request, response }) => {
|
|
74
|
-
if (response.headers["content-encoding"]) return;
|
|
75
|
-
const acceptEncoding = request.headers["accept-encoding"];
|
|
76
|
-
if (!acceptEncoding) return;
|
|
77
|
-
if (!this.isAllowedContentType(response.headers["content-type"])) return;
|
|
78
|
-
for (const encoding of [
|
|
79
|
-
"zstd",
|
|
80
|
-
"br",
|
|
81
|
-
"gzip"
|
|
82
|
-
]) if (acceptEncoding.includes(encoding) && ServerCompressProvider.compressors[encoding]) {
|
|
83
|
-
await this.compress(encoding, response);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
isAllowedContentType(contentType) {
|
|
89
|
-
if (!contentType) return false;
|
|
90
|
-
const lowerContentType = contentType.toLowerCase();
|
|
91
|
-
return !!this.options.allowedContentTypes.find((it) => lowerContentType.includes(it));
|
|
92
|
-
}
|
|
93
|
-
async compress(encoding, response) {
|
|
94
|
-
const body = response.body;
|
|
95
|
-
const compressor = ServerCompressProvider.compressors[encoding];
|
|
96
|
-
if (!compressor) return;
|
|
97
|
-
const params = this.getParams(encoding);
|
|
98
|
-
if (typeof body === "string" || Buffer.isBuffer(body) || body instanceof ArrayBuffer) {
|
|
99
|
-
const compressed = await compressor.compress(body, { params });
|
|
100
|
-
this.setHeaders(response, encoding);
|
|
101
|
-
response.headers["content-length"] = compressed.length.toString();
|
|
102
|
-
response.body = compressed;
|
|
103
|
-
}
|
|
104
|
-
if (typeof body === "object" && body instanceof node_stream.Readable) {
|
|
105
|
-
this.setHeaders(response, encoding);
|
|
106
|
-
response.body = body.pipe(compressor.stream({ params }));
|
|
107
|
-
}
|
|
108
|
-
if (typeof body === "object" && body instanceof node_stream_web.ReadableStream) {
|
|
109
|
-
this.setHeaders(response, encoding);
|
|
110
|
-
response.body = node_stream.Readable.fromWeb(body).pipe(compressor.stream({ params }));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
getParams(encoding) {
|
|
114
|
-
if (encoding === "zstd") return { [node_zlib.constants.ZSTD_c_compressionLevel]: 3 };
|
|
115
|
-
if (encoding === "br") return {};
|
|
116
|
-
if (encoding === "gzip") return {};
|
|
117
|
-
return {};
|
|
118
|
-
}
|
|
119
|
-
setHeaders(response, encoding) {
|
|
120
|
-
response.headers.vary = "content-encoding";
|
|
121
|
-
response.headers["content-encoding"] = encoding;
|
|
122
|
-
response.headers["cache-control"] = "no-cache";
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
//#endregion
|
|
127
|
-
//#region src/server-compress/index.ts
|
|
128
|
-
/**
|
|
129
|
-
* Plugin for Alepha Server that provides server-side compression capabilities.
|
|
130
|
-
*
|
|
131
|
-
* Compresses responses using gzip, brotli, or zstd based on the `Accept-Encoding` header.
|
|
132
|
-
*/
|
|
133
|
-
const AlephaServerCompress = (0, alepha.$module)({
|
|
134
|
-
name: "alepha.server.compress",
|
|
135
|
-
services: [alepha_server.AlephaServer, ServerCompressProvider]
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
//#endregion
|
|
139
|
-
exports.AlephaServerCompress = AlephaServerCompress;
|
|
140
|
-
exports.ServerCompressProvider = ServerCompressProvider;
|
|
141
|
-
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["zlib","Alepha","Readable","ReadableStream","AlephaServer"],"sources":["../../src/server-compress/providers/ServerCompressProvider.ts","../../src/server-compress/index.ts"],"sourcesContent":["import { Readable, type Transform } from \"node:stream\";\nimport { ReadableStream } from \"node:stream/web\";\nimport { promisify } from \"node:util\";\nimport * as zlib from \"node:zlib\";\nimport { $hook, $inject, Alepha, type HookDescriptor } from \"alepha\";\nimport type { ServerResponse } from \"alepha/server\";\n\nconst gzip = promisify(zlib.gzip);\nconst createGzip = zlib.createGzip;\nconst brotli = promisify(zlib.brotliCompress);\nconst createBrotliCompress = zlib.createBrotliCompress;\nconst zstd = zlib.zstdCompress ? promisify(zlib.zstdCompress) : undefined;\nconst createZstdCompress = zstd ? zlib.createZstdCompress : undefined;\n\ndeclare module \"alepha\" {\n interface State {\n \"alepha.server.compress.options\"?: ServerCompressProviderOptions;\n }\n}\n\nexport class ServerCompressProvider {\n static compressors: Record<\n string,\n | {\n compress: (...args: any[]) => Promise<Buffer>;\n stream: (options?: any) => Transform;\n }\n | undefined\n > = {\n gzip: {\n compress: gzip,\n stream: createGzip,\n },\n br: {\n compress: brotli,\n stream: createBrotliCompress,\n },\n zstd:\n zstd && createZstdCompress\n ? {\n compress: zstd,\n stream: createZstdCompress,\n }\n : undefined,\n };\n\n protected readonly alepha = $inject(Alepha);\n\n protected get options(): ServerCompressProviderOptions {\n return {\n allowedContentTypes: [\n \"application/json\",\n \"text/html\",\n \"application/javascript\",\n \"text/plain\",\n \"text/css\",\n ],\n ...this.alepha.state.get(\"alepha.server.compress.options\"),\n };\n }\n\n public readonly onResponse: HookDescriptor<\"server:onResponse\"> = $hook({\n on: \"server:onResponse\",\n handler: async ({ request, response }) => {\n // skip if already compressed\n if (response.headers[\"content-encoding\"]) {\n return;\n }\n\n const acceptEncoding = request.headers[\"accept-encoding\"]; // skip if no accept-encoding header\n if (!acceptEncoding) {\n return;\n }\n\n // skip if not json or html (for now)\n if (!this.isAllowedContentType(response.headers[\"content-type\"])) {\n return;\n }\n\n for (const encoding of [\"zstd\", \"br\", \"gzip\"] as const) {\n if (\n acceptEncoding.includes(encoding) &&\n ServerCompressProvider.compressors[encoding]\n ) {\n await this.compress(encoding, response);\n return;\n }\n }\n },\n });\n\n protected isAllowedContentType(contentType: string | undefined): boolean {\n if (!contentType) {\n return false;\n }\n\n const lowerContentType = contentType.toLowerCase();\n\n return !!this.options.allowedContentTypes.find((it) =>\n lowerContentType.includes(it),\n );\n }\n\n protected async compress(\n encoding: keyof typeof ServerCompressProvider.compressors,\n response: ServerResponse,\n ): Promise<void> {\n const body = response.body; // can be string or Buffer or ArrayBuffer or Readable\n\n const compressor = ServerCompressProvider.compressors[encoding];\n if (!compressor) {\n return;\n }\n\n const params = this.getParams(encoding);\n\n if (\n typeof body === \"string\" ||\n Buffer.isBuffer(body) ||\n body instanceof ArrayBuffer\n ) {\n const compressed = await compressor.compress(body, {\n params,\n });\n this.setHeaders(response, encoding);\n response.headers[\"content-length\"] = compressed.length.toString();\n response.body = compressed;\n }\n\n if (typeof body === \"object\" && body instanceof Readable) {\n this.setHeaders(response, encoding);\n response.body = body.pipe(compressor.stream({ params }));\n }\n\n if (typeof body === \"object\" && body instanceof ReadableStream) {\n this.setHeaders(response, encoding);\n response.body = Readable.fromWeb(body).pipe(\n compressor.stream({ params }),\n );\n }\n }\n\n protected getParams(\n encoding: keyof typeof ServerCompressProvider.compressors,\n ): Record<number, any> {\n if (encoding === \"zstd\") {\n return {\n [zlib.constants.ZSTD_c_compressionLevel]: 3, // default compression level for zstd\n };\n }\n if (encoding === \"br\") {\n return {};\n }\n if (encoding === \"gzip\") {\n return {};\n }\n return {};\n }\n\n protected setHeaders(\n response: ServerResponse,\n encoding: keyof typeof ServerCompressProvider.compressors,\n ): void {\n response.headers.vary = \"content-encoding\";\n response.headers[\"content-encoding\"] = encoding;\n response.headers[\"cache-control\"] = \"no-cache\";\n }\n}\n\nexport interface ServerCompressProviderOptions {\n allowedContentTypes: string[];\n}\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { ServerCompressProvider } from \"./providers/ServerCompressProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/ServerCompressProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Server that provides server-side compression capabilities.\n *\n * Compresses responses using gzip, brotli, or zstd based on the `Accept-Encoding` header.\n */\nexport const AlephaServerCompress = $module({\n name: \"alepha.server.compress\",\n services: [AlephaServer, ServerCompressProvider],\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,MAAM,gCAAiBA,UAAK,KAAK;AACjC,MAAM,aAAaA,UAAK;AACxB,MAAM,kCAAmBA,UAAK,eAAe;AAC7C,MAAM,uBAAuBA,UAAK;AAClC,MAAM,OAAOA,UAAK,wCAAyBA,UAAK,aAAa,GAAG;AAChE,MAAM,qBAAqB,OAAOA,UAAK,qBAAqB;AAQ5D,IAAa,yBAAb,MAAa,uBAAuB;CAClC,OAAO,cAOH;EACF,MAAM;GACJ,UAAU;GACV,QAAQ;GACT;EACD,IAAI;GACF,UAAU;GACV,QAAQ;GACT;EACD,MACE,QAAQ,qBACJ;GACE,UAAU;GACV,QAAQ;GACT,GACD;EACP;CAED,AAAmB,6BAAiBC,cAAO;CAE3C,IAAc,UAAyC;AACrD,SAAO;GACL,qBAAqB;IACnB;IACA;IACA;IACA;IACA;IACD;GACD,GAAG,KAAK,OAAO,MAAM,IAAI,iCAAiC;GAC3D;;CAGH,AAAgB,+BAAwD;EACtE,IAAI;EACJ,SAAS,OAAO,EAAE,SAAS,eAAe;AAExC,OAAI,SAAS,QAAQ,oBACnB;GAGF,MAAM,iBAAiB,QAAQ,QAAQ;AACvC,OAAI,CAAC,eACH;AAIF,OAAI,CAAC,KAAK,qBAAqB,SAAS,QAAQ,gBAAgB,CAC9D;AAGF,QAAK,MAAM,YAAY;IAAC;IAAQ;IAAM;IAAO,CAC3C,KACE,eAAe,SAAS,SAAS,IACjC,uBAAuB,YAAY,WACnC;AACA,UAAM,KAAK,SAAS,UAAU,SAAS;AACvC;;;EAIP,CAAC;CAEF,AAAU,qBAAqB,aAA0C;AACvE,MAAI,CAAC,YACH,QAAO;EAGT,MAAM,mBAAmB,YAAY,aAAa;AAElD,SAAO,CAAC,CAAC,KAAK,QAAQ,oBAAoB,MAAM,OAC9C,iBAAiB,SAAS,GAAG,CAC9B;;CAGH,MAAgB,SACd,UACA,UACe;EACf,MAAM,OAAO,SAAS;EAEtB,MAAM,aAAa,uBAAuB,YAAY;AACtD,MAAI,CAAC,WACH;EAGF,MAAM,SAAS,KAAK,UAAU,SAAS;AAEvC,MACE,OAAO,SAAS,YAChB,OAAO,SAAS,KAAK,IACrB,gBAAgB,aAChB;GACA,MAAM,aAAa,MAAM,WAAW,SAAS,MAAM,EACjD,QACD,CAAC;AACF,QAAK,WAAW,UAAU,SAAS;AACnC,YAAS,QAAQ,oBAAoB,WAAW,OAAO,UAAU;AACjE,YAAS,OAAO;;AAGlB,MAAI,OAAO,SAAS,YAAY,gBAAgBC,sBAAU;AACxD,QAAK,WAAW,UAAU,SAAS;AACnC,YAAS,OAAO,KAAK,KAAK,WAAW,OAAO,EAAE,QAAQ,CAAC,CAAC;;AAG1D,MAAI,OAAO,SAAS,YAAY,gBAAgBC,gCAAgB;AAC9D,QAAK,WAAW,UAAU,SAAS;AACnC,YAAS,OAAOD,qBAAS,QAAQ,KAAK,CAAC,KACrC,WAAW,OAAO,EAAE,QAAQ,CAAC,CAC9B;;;CAIL,AAAU,UACR,UACqB;AACrB,MAAI,aAAa,OACf,QAAO,GACJF,UAAK,UAAU,0BAA0B,GAC3C;AAEH,MAAI,aAAa,KACf,QAAO,EAAE;AAEX,MAAI,aAAa,OACf,QAAO,EAAE;AAEX,SAAO,EAAE;;CAGX,AAAU,WACR,UACA,UACM;AACN,WAAS,QAAQ,OAAO;AACxB,WAAS,QAAQ,sBAAsB;AACvC,WAAS,QAAQ,mBAAmB;;;;;;;;;;;ACtJxC,MAAa,2CAA+B;CAC1C,MAAM;CACN,UAAU,CAACI,4BAAc,uBAAuB;CACjD,CAAC"}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import * as alepha0 from "alepha";
|
|
2
|
-
import { Alepha, HookDescriptor } from "alepha";
|
|
3
|
-
import { Transform } from "node:stream";
|
|
4
|
-
import { ServerResponse } from "alepha/server";
|
|
5
|
-
|
|
6
|
-
//#region src/server-compress/providers/ServerCompressProvider.d.ts
|
|
7
|
-
declare module "alepha" {
|
|
8
|
-
interface State {
|
|
9
|
-
"alepha.server.compress.options"?: ServerCompressProviderOptions;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
declare class ServerCompressProvider {
|
|
13
|
-
static compressors: Record<string, {
|
|
14
|
-
compress: (...args: any[]) => Promise<Buffer>;
|
|
15
|
-
stream: (options?: any) => Transform;
|
|
16
|
-
} | undefined>;
|
|
17
|
-
protected readonly alepha: Alepha;
|
|
18
|
-
protected get options(): ServerCompressProviderOptions;
|
|
19
|
-
readonly onResponse: HookDescriptor<"server:onResponse">;
|
|
20
|
-
protected isAllowedContentType(contentType: string | undefined): boolean;
|
|
21
|
-
protected compress(encoding: keyof typeof ServerCompressProvider.compressors, response: ServerResponse): Promise<void>;
|
|
22
|
-
protected getParams(encoding: keyof typeof ServerCompressProvider.compressors): Record<number, any>;
|
|
23
|
-
protected setHeaders(response: ServerResponse, encoding: keyof typeof ServerCompressProvider.compressors): void;
|
|
24
|
-
}
|
|
25
|
-
interface ServerCompressProviderOptions {
|
|
26
|
-
allowedContentTypes: string[];
|
|
27
|
-
}
|
|
28
|
-
//#endregion
|
|
29
|
-
//#region src/server-compress/index.d.ts
|
|
30
|
-
/**
|
|
31
|
-
* Plugin for Alepha Server that provides server-side compression capabilities.
|
|
32
|
-
*
|
|
33
|
-
* Compresses responses using gzip, brotli, or zstd based on the `Accept-Encoding` header.
|
|
34
|
-
*/
|
|
35
|
-
declare const AlephaServerCompress: alepha0.Service<alepha0.Module>;
|
|
36
|
-
//#endregion
|
|
37
|
-
export { AlephaServerCompress, ServerCompressProvider, ServerCompressProviderOptions };
|
|
38
|
-
//# sourceMappingURL=index.d.cts.map
|