@toapi/server 1.4.0 → 1.5.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/dist/create-request-handler.d.ts.map +1 -1
- package/dist/create-request-handler.js +4 -0
- package/dist/hash-tags.d.ts +8 -0
- package/dist/hash-tags.d.ts.map +1 -0
- package/dist/hash-tags.js +34 -0
- package/dist/revalidation-stream.d.ts +3 -1
- package/dist/revalidation-stream.d.ts.map +1 -1
- package/dist/revalidation-stream.js +11 -8
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-request-handler.d.ts","sourceRoot":"","sources":["../src/create-request-handler.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"create-request-handler.d.ts","sourceRoot":"","sources":["../src/create-request-handler.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAM9C,UAAU,OAAO;IACf,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAWD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,EAC7D,OAAO,GAAE,OAAY,SAmBF,OAAO,uBA0L3B;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD;AAuGD,wBAAsB,cAAc,CAAC,IAAI,EACvC,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,EACrC,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,mDAOnC"}
|
|
@@ -4,6 +4,7 @@ import { HttpError } from "@toapi/common";
|
|
|
4
4
|
import { CookieStore } from "@toapi/common";
|
|
5
5
|
import { generateOpenAPISchema } from "./openapi.js";
|
|
6
6
|
import { streamRevalidatedTags } from "./revalidation-stream.js";
|
|
7
|
+
import { encodeTags } from "./hash-tags.js";
|
|
7
8
|
const DEFAULT_TTL = 60 * 60 * 24 * 14;
|
|
8
9
|
const authUsed = Symbol("TApi.authUsed");
|
|
9
10
|
const headersSchema = z
|
|
@@ -102,6 +103,7 @@ export function createRequestHandler(api, options = {}) {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
}
|
|
106
|
+
await encodeTags(api.revalidationStreamConfig?.hashSecret, res);
|
|
105
107
|
return res;
|
|
106
108
|
}
|
|
107
109
|
catch (error) {
|
|
@@ -130,6 +132,7 @@ export function createRequestHandler(api, options = {}) {
|
|
|
130
132
|
errorLog(error);
|
|
131
133
|
}
|
|
132
134
|
}
|
|
135
|
+
await encodeTags(api.revalidationStreamConfig?.hashSecret, res);
|
|
133
136
|
return res;
|
|
134
137
|
}
|
|
135
138
|
catch (error) {
|
|
@@ -162,6 +165,7 @@ export function createRequestHandler(api, options = {}) {
|
|
|
162
165
|
errorLog(error);
|
|
163
166
|
}
|
|
164
167
|
}
|
|
168
|
+
await encodeTags(api.revalidationStreamConfig?.hashSecret, res);
|
|
165
169
|
return res;
|
|
166
170
|
}
|
|
167
171
|
catch (error) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { TResponse, type MaybePromise } from "@toapi/common";
|
|
2
|
+
export declare function hashTag(te: TextEncoder, secret: string, tag: string): Promise<string>;
|
|
3
|
+
type Encode = (tags: string[]) => MaybePromise<Uint8Array>;
|
|
4
|
+
export declare function hashEncoder(secret: string): Encode;
|
|
5
|
+
export declare function textEncoder(): Encode;
|
|
6
|
+
export declare function encodeTags(secret: string | undefined, res: TResponse<unknown>): Promise<void>;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=hash-tags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash-tags.d.ts","sourceRoot":"","sources":["../src/hash-tags.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAE1E,wBAAsB,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,mBAKzE;AAED,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,YAAY,CAAC,UAAU,CAAC,CAAC;AAE3D,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAUlD;AAED,wBAAgB,WAAW,IAAI,MAAM,CAOpC;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,GAAG,EAAE,SAAS,CAAC,OAAO,CAAC,iBAYxB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TAGS_HEADER, TResponse } from "@toapi/common";
|
|
2
|
+
export async function hashTag(te, secret, tag) {
|
|
3
|
+
const digest = await crypto.subtle.digest("SHA-256", te.encode(secret + tag));
|
|
4
|
+
const dv = new DataView(digest);
|
|
5
|
+
const num = dv.getBigUint64(0);
|
|
6
|
+
return num.toString(36);
|
|
7
|
+
}
|
|
8
|
+
export function hashEncoder(secret) {
|
|
9
|
+
const te = new TextEncoder();
|
|
10
|
+
return async (tags) => {
|
|
11
|
+
if (!tags.length)
|
|
12
|
+
return te.encode("\n");
|
|
13
|
+
const hashedTags = await Promise.all(tags.map((tag) => hashTag(te, secret, tag)));
|
|
14
|
+
const tagsString = hashedTags.join(" ");
|
|
15
|
+
return te.encode(tagsString + "\n");
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function textEncoder() {
|
|
19
|
+
const te = new TextEncoder();
|
|
20
|
+
return (tags) => {
|
|
21
|
+
if (!tags.length)
|
|
22
|
+
return te.encode("\n");
|
|
23
|
+
const tagsString = tags.join(" ");
|
|
24
|
+
return te.encode(tagsString + "\n");
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export async function encodeTags(secret, res) {
|
|
28
|
+
if (!secret)
|
|
29
|
+
return;
|
|
30
|
+
const tags = res.cache?.tags ?? res.headers.get(TAGS_HEADER)?.split(" ") ?? [];
|
|
31
|
+
const te = new TextEncoder();
|
|
32
|
+
const hashedTags = await Promise.all(tags.map((tag) => hashTag(te, secret, tag)));
|
|
33
|
+
res.headers.set(TAGS_HEADER, hashedTags.join(" "));
|
|
34
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { type MaybePromise } from "@toapi/common";
|
|
1
2
|
import type { Cache } from "./cache.js";
|
|
2
3
|
export interface RevalidationStreamConfig {
|
|
3
4
|
throttleTimeout?: number;
|
|
4
5
|
keepaliveInterval?: number;
|
|
5
|
-
filter?: (req: Request) => (tag: string) => boolean
|
|
6
|
+
filter?: (req: Request) => MaybePromise<(tag: string) => boolean>;
|
|
7
|
+
hashSecret?: string;
|
|
6
8
|
}
|
|
7
9
|
interface Options {
|
|
8
10
|
cache: Cache;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revalidation-stream.d.ts","sourceRoot":"","sources":["../src/revalidation-stream.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"revalidation-stream.d.ts","sourceRoot":"","sources":["../src/revalidation-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMxC,MAAM,WAAW,wBAAwB;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,YAAY,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,OAAO;IACf,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC,GAAG,EAAE,OAAO,CAAC;CACd;AAED,wBAAgB,qBAAqB,CAAC,EAAE,KAAK,EAAE,MAAW,EAAE,GAAG,EAAE,EAAE,OAAO,YAsEzE"}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
import { SESSION_COOKIE_NAME, TAGS_CONTENT_TYPE } from "@toapi/common";
|
|
1
|
+
import { SESSION_COOKIE_NAME, TAGS_CONTENT_TYPE, } from "@toapi/common";
|
|
2
|
+
import { hashEncoder, textEncoder } from "./hash-tags.js";
|
|
2
3
|
const DEFAULT_KEEPALIVE_INTERVAL = 10 * 1000;
|
|
3
4
|
const DEFAULT_THROTTLE_TIMEOUT = 500;
|
|
4
5
|
export function streamRevalidatedTags({ cache, config = {}, req }) {
|
|
5
6
|
const { throttleTimeout = DEFAULT_THROTTLE_TIMEOUT, keepaliveInterval = DEFAULT_KEEPALIVE_INTERVAL, filter = () => () => true, } = config;
|
|
6
|
-
const filterTag = filter(req);
|
|
7
7
|
const id = crypto.randomUUID();
|
|
8
8
|
let interval = null;
|
|
9
9
|
let timeout = null;
|
|
10
10
|
let unsubscribe = () => { };
|
|
11
11
|
const stream = new ReadableStream({
|
|
12
12
|
async start(controller) {
|
|
13
|
+
const filterTag = await filter(req);
|
|
13
14
|
let queue = new Set();
|
|
14
|
-
const
|
|
15
|
+
const encode = config.hashSecret
|
|
16
|
+
? hashEncoder(config.hashSecret)
|
|
17
|
+
: textEncoder();
|
|
15
18
|
// subscribe to tag invalidations
|
|
16
|
-
unsubscribe = cache.subscribe((tags, meta) => {
|
|
19
|
+
unsubscribe = cache.subscribe(async (tags, meta) => {
|
|
17
20
|
// ignore our own invalidations
|
|
18
21
|
if (meta &&
|
|
19
22
|
typeof meta === "object" &&
|
|
@@ -27,10 +30,10 @@ export function streamRevalidatedTags({ cache, config = {}, req }) {
|
|
|
27
30
|
}
|
|
28
31
|
// send tags to client
|
|
29
32
|
if (queue.size > 0 && !timeout) {
|
|
30
|
-
controller.enqueue(
|
|
33
|
+
controller.enqueue(await encode(Array.from(queue)));
|
|
31
34
|
queue = new Set();
|
|
32
|
-
timeout = setTimeout(() => {
|
|
33
|
-
controller.enqueue(
|
|
35
|
+
timeout = setTimeout(async () => {
|
|
36
|
+
controller.enqueue(await encode(Array.from(queue)));
|
|
34
37
|
queue = new Set();
|
|
35
38
|
timeout = null;
|
|
36
39
|
}, throttleTimeout);
|
|
@@ -39,7 +42,7 @@ export function streamRevalidatedTags({ cache, config = {}, req }) {
|
|
|
39
42
|
// keepalive. The first one is sent right away so the response headers
|
|
40
43
|
// are flushed immediately instead of only once the interval first
|
|
41
44
|
// fires — consumers skip empty lines, so this is a no-op for them.
|
|
42
|
-
const keepalive = () => controller.enqueue(
|
|
45
|
+
const keepalive = () => controller.enqueue(new Uint8Array([10]));
|
|
43
46
|
keepalive();
|
|
44
47
|
interval = setInterval(keepalive, keepaliveInterval);
|
|
45
48
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toapi/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Michel Smola",
|
|
6
6
|
"email": "michel.smola@farbenmeer.de"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@toapi/common": "^1.
|
|
23
|
+
"@toapi/common": "^1.5.0",
|
|
24
24
|
"zod-openapi": "^6.0.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|