@toapi/server 1.3.4 → 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.
@@ -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;AAK9C,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,uBAqL3B;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"}
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
@@ -28,6 +29,8 @@ export function createRequestHandler(api, options = {}) {
28
29
  if (url.pathname === `${basePath}${INVALIDATIONS_ROUTE}`) {
29
30
  return streamRevalidatedTags({
30
31
  cache: api.cache,
32
+ config: api.revalidationStreamConfig,
33
+ req,
31
34
  });
32
35
  }
33
36
  if (api.oas && url.pathname === `${basePath}${OPENAPI_ROUTE}`) {
@@ -100,6 +103,7 @@ export function createRequestHandler(api, options = {}) {
100
103
  }
101
104
  }
102
105
  }
106
+ await encodeTags(api.revalidationStreamConfig?.hashSecret, res);
103
107
  return res;
104
108
  }
105
109
  catch (error) {
@@ -128,6 +132,7 @@ export function createRequestHandler(api, options = {}) {
128
132
  errorLog(error);
129
133
  }
130
134
  }
135
+ await encodeTags(api.revalidationStreamConfig?.hashSecret, res);
131
136
  return res;
132
137
  }
133
138
  catch (error) {
@@ -160,6 +165,7 @@ export function createRequestHandler(api, options = {}) {
160
165
  errorLog(error);
161
166
  }
162
167
  }
168
+ await encodeTags(api.revalidationStreamConfig?.hashSecret, res);
163
169
  return res;
164
170
  }
165
171
  catch (error) {
@@ -3,6 +3,7 @@ import type { Logger } from "@toapi/common";
3
3
  import type { Path as BasePath, StrictParams } from "@toapi/common";
4
4
  import type { Route } from "@toapi/common";
5
5
  import { type Cache } from "./cache.js";
6
+ import type { RevalidationStreamConfig } from "./revalidation-stream.js";
6
7
  export interface OasInfo {
7
8
  title: string;
8
9
  version: string;
@@ -11,6 +12,7 @@ interface Options {
11
12
  cache?: Cache;
12
13
  oas?: OasInfo;
13
14
  logger?: Logger;
15
+ revalidationStream?: RevalidationStreamConfig;
14
16
  }
15
17
  export declare function defineApi(options?: Options): ApiDefinition<{}>;
16
18
  export declare class ApiDefinition<Routes extends Record<BasePath, unknown>> {
@@ -18,7 +20,8 @@ export declare class ApiDefinition<Routes extends Record<BasePath, unknown>> {
18
20
  cache: Cache;
19
21
  oas?: OasInfo | undefined;
20
22
  logger?: Logger | undefined;
21
- constructor(routes: Routes, cache: Cache, oas?: OasInfo | undefined, logger?: Logger | undefined);
23
+ revalidationStreamConfig?: RevalidationStreamConfig | undefined;
24
+ constructor(routes: Routes, cache: Cache, oas?: OasInfo | undefined, logger?: Logger | undefined, revalidationStreamConfig?: RevalidationStreamConfig | undefined);
22
25
  invalidate(tags: string[]): Promise<void>;
23
26
  route<Path extends BasePath, GetResponse = never, GetQuery extends Record<string, unknown> = never, PostResponse = never, PostQuery extends Record<string, unknown> = never, PostBody = never, DeleteResponse = never, DeleteQuery extends Record<string, unknown> = never, PutResponse = never, PutQuery extends Record<string, unknown> = never, PutBody = never, PatchResponse = never, PatchQuery extends Record<string, unknown> = never, PatchBody = never>(path: Path, route: MaybePromise<Route<StrictParams<Path>, GetResponse, GetQuery, PostResponse, PostQuery, PostBody, DeleteResponse, DeleteQuery, PutResponse, PutQuery, PutBody, PatchResponse, PatchQuery, PatchBody>>): ApiDefinition<Routes & { [path in Path]: MaybePromise<Route<StrictParams<Path>, GetResponse, GetQuery, PostResponse, PostQuery, PostBody, DeleteResponse, DeleteQuery, PutResponse, PutQuery, PutBody, PatchResponse, PatchQuery, PatchBody>>; }>;
24
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"define-api.d.ts","sourceRoot":"","sources":["../src/define-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,IAAI,IAAI,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,KAAK,KAAK,EAAU,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,OAAO;IACf,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,SAAS,CAAC,OAAO,GAAE,OAAY,qBAE9C;AAED,qBAAa,aAAa,CAAC,MAAM,SAAS,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC;IAExD,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,KAAK;IACZ,GAAG,CAAC,EAAE,OAAO;IACb,MAAM,CAAC,EAAE,MAAM;IAJxB,YACS,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,EACZ,GAAG,CAAC,EAAE,OAAO,YAAA,EACb,MAAM,CAAC,EAAE,MAAM,YAAA,EACpB;IAEE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,iBAE9B;IAED,KAAK,CACH,IAAI,SAAS,QAAQ,EACrB,WAAW,GAAG,KAAK,EACnB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,EAChD,YAAY,GAAG,KAAK,EACpB,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,EACjD,QAAQ,GAAG,KAAK,EAChB,cAAc,GAAG,KAAK,EACtB,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,EACnD,WAAW,GAAG,KAAK,EACnB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,EAChD,OAAO,GAAG,KAAK,EACf,aAAa,GAAG,KAAK,EACrB,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,EAClD,SAAS,GAAG,KAAK,EAEjB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,YAAY,CACjB,KAAK,CACH,YAAY,CAAC,IAAI,CAAC,EAClB,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,OAAO,EACP,aAAa,EACb,UAAU,EACV,SAAS,CACV,CACF,GAGyB,aAAa,CACrC,MAAM,GAAG,GACN,IAAI,IAAI,IAAI,GAAG,YAAY,CAC1B,KAAK,CACH,YAAY,CAAC,IAAI,CAAC,EAClB,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,OAAO,EACP,aAAa,EACb,UAAU,EACV,SAAS,CACV,CACF,GACF,CACF,CACF;CACF"}
1
+ {"version":3,"file":"define-api.d.ts","sourceRoot":"","sources":["../src/define-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,IAAI,IAAI,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,KAAK,KAAK,EAAU,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEzE,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,OAAO;IACf,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kBAAkB,CAAC,EAAE,wBAAwB,CAAC;CAC/C;AAED,wBAAgB,SAAS,CAAC,OAAO,GAAE,OAAY,qBAQ9C;AAED,qBAAa,aAAa,CAAC,MAAM,SAAS,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC;IAExD,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,KAAK;IACZ,GAAG,CAAC,EAAE,OAAO;IACb,MAAM,CAAC,EAAE,MAAM;IACf,wBAAwB,CAAC,EAAE,wBAAwB;IAL5D,YACS,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,EACZ,GAAG,CAAC,EAAE,OAAO,YAAA,EACb,MAAM,CAAC,EAAE,MAAM,YAAA,EACf,wBAAwB,CAAC,EAAE,wBAAwB,YAAA,EACxD;IAEE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,iBAE9B;IAED,KAAK,CACH,IAAI,SAAS,QAAQ,EACrB,WAAW,GAAG,KAAK,EACnB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,EAChD,YAAY,GAAG,KAAK,EACpB,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,EACjD,QAAQ,GAAG,KAAK,EAChB,cAAc,GAAG,KAAK,EACtB,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,EACnD,WAAW,GAAG,KAAK,EACnB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,EAChD,OAAO,GAAG,KAAK,EACf,aAAa,GAAG,KAAK,EACrB,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,EAClD,SAAS,GAAG,KAAK,EAEjB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,YAAY,CACjB,KAAK,CACH,YAAY,CAAC,IAAI,CAAC,EAClB,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,OAAO,EACP,aAAa,EACb,UAAU,EACV,SAAS,CACV,CACF,GAGyB,aAAa,CACrC,MAAM,GAAG,GACN,IAAI,IAAI,IAAI,GAAG,YAAY,CAC1B,KAAK,CACH,YAAY,CAAC,IAAI,CAAC,EAClB,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,EACR,OAAO,EACP,aAAa,EACb,UAAU,EACV,SAAS,CACV,CACF,GACF,CACF,CACF;CACF"}
@@ -1,17 +1,19 @@
1
1
  import { PubSub } from "./cache.js";
2
2
  export function defineApi(options = {}) {
3
- return new ApiDefinition({}, options?.cache ?? new PubSub(), options?.oas, options?.logger);
3
+ return new ApiDefinition({}, options?.cache ?? new PubSub(), options?.oas, options?.logger, options?.revalidationStream);
4
4
  }
5
5
  export class ApiDefinition {
6
6
  routes;
7
7
  cache;
8
8
  oas;
9
9
  logger;
10
- constructor(routes, cache, oas, logger) {
10
+ revalidationStreamConfig;
11
+ constructor(routes, cache, oas, logger, revalidationStreamConfig) {
11
12
  this.routes = routes;
12
13
  this.cache = cache;
13
14
  this.oas = oas;
14
15
  this.logger = logger;
16
+ this.revalidationStreamConfig = revalidationStreamConfig;
15
17
  }
16
18
  async invalidate(tags) {
17
19
  await this.cache.delete(tags);
@@ -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,7 +1,16 @@
1
+ import { type MaybePromise } from "@toapi/common";
1
2
  import type { Cache } from "./cache.js";
3
+ export interface RevalidationStreamConfig {
4
+ throttleTimeout?: number;
5
+ keepaliveInterval?: number;
6
+ filter?: (req: Request) => MaybePromise<(tag: string) => boolean>;
7
+ hashSecret?: string;
8
+ }
2
9
  interface Options {
3
10
  cache: Cache;
11
+ config?: RevalidationStreamConfig;
12
+ req: Request;
4
13
  }
5
- export declare function streamRevalidatedTags({ cache }: Options): Response;
14
+ export declare function streamRevalidatedTags({ cache, config, req }: Options): Response;
6
15
  export {};
7
16
  //# sourceMappingURL=revalidation-stream.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"revalidation-stream.d.ts","sourceRoot":"","sources":["../src/revalidation-stream.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAIxC,UAAU,OAAO;IACf,KAAK,EAAE,KAAK,CAAC;CACd;AAED,wBAAgB,qBAAqB,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,YA0CvD"}
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,33 +1,56 @@
1
1
  import { SESSION_COOKIE_NAME, TAGS_CONTENT_TYPE, } from "@toapi/common";
2
- const KEEPALIVE_INTERVAL = 10 * 1000;
3
- export function streamRevalidatedTags({ cache }) {
2
+ import { hashEncoder, textEncoder } from "./hash-tags.js";
3
+ const DEFAULT_KEEPALIVE_INTERVAL = 10 * 1000;
4
+ const DEFAULT_THROTTLE_TIMEOUT = 500;
5
+ export function streamRevalidatedTags({ cache, config = {}, req }) {
6
+ const { throttleTimeout = DEFAULT_THROTTLE_TIMEOUT, keepaliveInterval = DEFAULT_KEEPALIVE_INTERVAL, filter = () => () => true, } = config;
4
7
  const id = crypto.randomUUID();
5
8
  let interval = null;
9
+ let timeout = null;
6
10
  let unsubscribe = () => { };
7
11
  const stream = new ReadableStream({
8
12
  async start(controller) {
9
- const textEncoder = new TextEncoder();
13
+ const filterTag = await filter(req);
14
+ let queue = new Set();
15
+ const encode = config.hashSecret
16
+ ? hashEncoder(config.hashSecret)
17
+ : textEncoder();
10
18
  // subscribe to tag invalidations
11
- unsubscribe = cache.subscribe((tags, meta) => {
19
+ unsubscribe = cache.subscribe(async (tags, meta) => {
12
20
  // ignore our own invalidations
13
21
  if (meta &&
14
22
  typeof meta === "object" &&
15
23
  "clientId" in meta &&
16
24
  meta.clientId === id)
17
25
  return;
26
+ for (const tag of tags) {
27
+ if (filterTag(tag)) {
28
+ queue.add(tag);
29
+ }
30
+ }
18
31
  // send tags to client
19
- controller.enqueue(textEncoder.encode(`${tags.join(" ")}\n`));
32
+ if (queue.size > 0 && !timeout) {
33
+ controller.enqueue(await encode(Array.from(queue)));
34
+ queue = new Set();
35
+ timeout = setTimeout(async () => {
36
+ controller.enqueue(await encode(Array.from(queue)));
37
+ queue = new Set();
38
+ timeout = null;
39
+ }, throttleTimeout);
40
+ }
20
41
  });
21
42
  // keepalive. The first one is sent right away so the response headers
22
43
  // are flushed immediately instead of only once the interval first
23
44
  // fires — consumers skip empty lines, so this is a no-op for them.
24
- const keepalive = () => controller.enqueue(textEncoder.encode("\n"));
45
+ const keepalive = () => controller.enqueue(new Uint8Array([10]));
25
46
  keepalive();
26
- interval = setInterval(keepalive, KEEPALIVE_INTERVAL);
47
+ interval = setInterval(keepalive, keepaliveInterval);
27
48
  },
28
49
  cancel() {
29
50
  if (interval)
30
51
  clearInterval(interval);
52
+ if (timeout)
53
+ clearTimeout(timeout);
31
54
  unsubscribe();
32
55
  },
33
56
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toapi/server",
3
- "version": "1.3.4",
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.3.4",
23
+ "@toapi/common": "^1.5.0",
24
24
  "zod-openapi": "^6.0.0"
25
25
  },
26
26
  "devDependencies": {