@vesk/adapter 0.2.71 → 0.2.72
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/prod-server.d.ts +2 -4
- package/dist/prod-server.d.ts.map +1 -1
- package/dist/response-pipeline.d.ts.map +1 -1
- package/dist/response-pipeline.js +23 -0
- package/dist/server-only-plugin.d.ts.map +1 -1
- package/dist/server-only-plugin.js +19 -0
- package/dist/server-only.d.ts +26 -0
- package/dist/server-only.d.ts.map +1 -1
- package/dist/server-only.js +26 -0
- package/dist/web-request.d.ts +22 -0
- package/dist/web-request.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/prod-server.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { type Server } from 'node:http';
|
|
2
2
|
import type { CompressionConfig, ProdServerOptions } from '@vesk/adapter/src/types';
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
healthCheck?: boolean;
|
|
6
|
-
}): Promise<Server>;
|
|
3
|
+
export type { CompressionConfig, ProdServerOptions };
|
|
4
|
+
export declare function startProdServer(outDir: string, options?: ProdServerOptions): Promise<Server>;
|
|
7
5
|
//# sourceMappingURL=prod-server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAejG,OAAO,KAAK,EAAkB,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAejG,OAAO,KAAK,EAAkB,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAIpG,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,CAAC;AAyHrD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwoBlG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-pipeline.d.ts","sourceRoot":"","sources":["../src/response-pipeline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"response-pipeline.d.ts","sourceRoot":"","sources":["../src/response-pipeline.ts"],"names":[],"mappings":"AAgEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAKjE,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,MAAM,CAAC;AAE5C;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,eAAe,GAAG,IAAI,CAgC/F;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAwB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAWjG;AAED,MAAM,WAAW,uBAAuB;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AA8BD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACtC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,uBAA4B,GACnC,IAAI,CA2ON"}
|
|
@@ -29,6 +29,29 @@
|
|
|
29
29
|
* Deliberately not here: range requests, `Last-Modified`/`If-Modified-Since`
|
|
30
30
|
* (the ETag covers the same revalidation need and cannot disagree with itself),
|
|
31
31
|
* and compression of already-encoded bodies.
|
|
32
|
+
*
|
|
33
|
+
* ## Shape
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* installResponsePipeline(res, req, {
|
|
37
|
+
* compress?: boolean; // default true
|
|
38
|
+
* compressThreshold?: number;// default 1024
|
|
39
|
+
* etag?: boolean; // default true
|
|
40
|
+
* brotliQuality?: number; // 0-11, default 5
|
|
41
|
+
* gzipLevel?: number; // 1-9, default 6
|
|
42
|
+
* })
|
|
43
|
+
*
|
|
44
|
+
* // pure helpers, exported for testing and for any other transport
|
|
45
|
+
* negotiateEncoding(acceptEncoding): 'br' | 'gzip' | null
|
|
46
|
+
* isCompressibleType(contentType): boolean
|
|
47
|
+
* etagFor(body: Buffer): '"..."'
|
|
48
|
+
* ifNoneMatchSatisfied(ifNoneMatch, etag): boolean
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* Install it ONCE per request, before any header is written. It is idempotent
|
|
52
|
+
* per `res` in the sense that matters — a second install would defer again —
|
|
53
|
+
* but a handler that bypasses it loses compression, `Vary`, ETag and 304 for
|
|
54
|
+
* everything it serves, silently.
|
|
32
55
|
*/
|
|
33
56
|
import { createHash } from 'node:crypto';
|
|
34
57
|
import { brotliCompress, constants as zlibConstants, createBrotliCompress, createGzip, gzip, } from 'node:zlib';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-only-plugin.d.ts","sourceRoot":"","sources":["../src/server-only-plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server-only-plugin.d.ts","sourceRoot":"","sources":["../src/server-only-plugin.ts"],"names":[],"mappings":"AA6CA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IACtC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAClE;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACtF;AAED,kGAAkG;AAClG,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,CAAC,KAAK,EAAE;QACb,SAAS,EAAE,CACT,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,EAC5C,EAAE,EAAE,CAAC,IAAI,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,KACtD,IAAI,CAAC;QACV,MAAM,EAAE,CACN,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,EAC3C,EAAE,EAAE,CAAC,IAAI,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,KAAK;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,KACjE,IAAI,CAAC;KACX,KAAK,IAAI,CAAC;CACZ;AAMD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CA+C1E;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAmB3D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAoDxE;AAED,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,iBAAiB,CAoDzF"}
|
|
@@ -19,6 +19,25 @@
|
|
|
19
19
|
* TCP drivers are NOT stubbed. A browser bundle cannot contain one under any
|
|
20
20
|
* circumstances, so a stub would ship a page that looks fine and fails at the
|
|
21
21
|
* first query; they are reported and left for esbuild to reject.
|
|
22
|
+
*
|
|
23
|
+
* ## Shape
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* serverOnlyPlugin({
|
|
27
|
+
* onStub?: (hit: { specifier, binding, importer }) => void, // stubbed
|
|
28
|
+
* onDriver?:(hit: { specifier, importer }) => void, // refused
|
|
29
|
+
* onEager?: (hit: { specifier, bindings: string[], importer }) => void, // module-scope: build error
|
|
30
|
+
* })
|
|
31
|
+
*
|
|
32
|
+
* // helpers, exported for testing
|
|
33
|
+
* importedNamesFor(code, specifier): string[] // what the importer asks for
|
|
34
|
+
* referencedAtTopLevel(code, name): boolean // module-scope use?
|
|
35
|
+
* stubFactorySource(specifier): string // the shared throwing proxy
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* `onEager` returning null (rather than a stub) is deliberate: the browser would
|
|
39
|
+
* evaluate that statement while loading the chunk, so there is no call site to
|
|
40
|
+
* throw at.
|
|
22
41
|
*/
|
|
23
42
|
import { readFileSync } from 'node:fs';
|
|
24
43
|
import { parse } from '@vesk/compiler/src/parser';
|
package/dist/server-only.d.ts
CHANGED
|
@@ -25,6 +25,32 @@
|
|
|
25
25
|
* opt-out: the alternative is discovering it in a deployment log.
|
|
26
26
|
*
|
|
27
27
|
* AST-only throughout. No text scanning — see the module rules.
|
|
28
|
+
*
|
|
29
|
+
* ## Shape
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* // classification
|
|
33
|
+
* classifySpecifier(spec): 'server-only-package' | 'tcp-driver' | null
|
|
34
|
+
* isServerOnlySpecifier(spec): boolean
|
|
35
|
+
* isTcpDriverSpecifier(spec): boolean
|
|
36
|
+
*
|
|
37
|
+
* // enforcement
|
|
38
|
+
* serverOnlyStubs(notes: Array<{ specifier, binding }>): string // from a recorded list
|
|
39
|
+
* serverOnlyImportStubs(code, classify?): string // scanned from source
|
|
40
|
+
* scanServerOnlyImports(files: Array<{ path, code }>): ServerOnlyViolation[]
|
|
41
|
+
* tcpDriverViolations(appDir): ServerOnlyViolation[] // whole app tree
|
|
42
|
+
* edgeDriverBuildError(violations): Error // the socketless message
|
|
43
|
+
*
|
|
44
|
+
* // target
|
|
45
|
+
* isSocketlessTarget(platform, target): boolean
|
|
46
|
+
* collectAppSources(dir, out?): string[]
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Adding to the table: extend `SERVER_ONLY_EXACT` / `TCP_DRIVERS`, and — if the
|
|
50
|
+
* package is legitimately usable on the client through a subpath — leave that
|
|
51
|
+
* subpath unmatched, as `slintorm/edge` is. See
|
|
52
|
+
* `packages/adapter/AGENTS.md` for the three outcomes the plugin must
|
|
53
|
+
* distinguish.
|
|
28
54
|
*/
|
|
29
55
|
export type ServerOnlyKind = 'server-only-package' | 'tcp-driver';
|
|
30
56
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-only.d.ts","sourceRoot":"","sources":["../src/server-only.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"server-only.d.ts","sourceRoot":"","sources":["../src/server-only.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAsCH,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,YAAY,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAMrE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAkB5F;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,cAAc,GAAG,IAAwB,GAAG,MAAM,CAoBjI;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAWD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAC3C,mBAAmB,EAAE,CA8BvB;AAYD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,mBAAmB,EAAE,GAAG,KAAK,CAU7E;AAYD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAGlH;AAMD,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAwB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAUzE"}
|
package/dist/server-only.js
CHANGED
|
@@ -25,6 +25,32 @@
|
|
|
25
25
|
* opt-out: the alternative is discovering it in a deployment log.
|
|
26
26
|
*
|
|
27
27
|
* AST-only throughout. No text scanning — see the module rules.
|
|
28
|
+
*
|
|
29
|
+
* ## Shape
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* // classification
|
|
33
|
+
* classifySpecifier(spec): 'server-only-package' | 'tcp-driver' | null
|
|
34
|
+
* isServerOnlySpecifier(spec): boolean
|
|
35
|
+
* isTcpDriverSpecifier(spec): boolean
|
|
36
|
+
*
|
|
37
|
+
* // enforcement
|
|
38
|
+
* serverOnlyStubs(notes: Array<{ specifier, binding }>): string // from a recorded list
|
|
39
|
+
* serverOnlyImportStubs(code, classify?): string // scanned from source
|
|
40
|
+
* scanServerOnlyImports(files: Array<{ path, code }>): ServerOnlyViolation[]
|
|
41
|
+
* tcpDriverViolations(appDir): ServerOnlyViolation[] // whole app tree
|
|
42
|
+
* edgeDriverBuildError(violations): Error // the socketless message
|
|
43
|
+
*
|
|
44
|
+
* // target
|
|
45
|
+
* isSocketlessTarget(platform, target): boolean
|
|
46
|
+
* collectAppSources(dir, out?): string[]
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Adding to the table: extend `SERVER_ONLY_EXACT` / `TCP_DRIVERS`, and — if the
|
|
50
|
+
* package is legitimately usable on the client through a subpath — leave that
|
|
51
|
+
* subpath unmatched, as `slintorm/edge` is. See
|
|
52
|
+
* `packages/adapter/AGENTS.md` for the three outcomes the plugin must
|
|
53
|
+
* distinguish.
|
|
28
54
|
*/
|
|
29
55
|
import { parse } from '@vesk/compiler/src/parser';
|
|
30
56
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
package/dist/web-request.d.ts
CHANGED
|
@@ -19,6 +19,28 @@
|
|
|
19
19
|
* `text()`/`json()`/`formData()` serve from that cache if the stream has already
|
|
20
20
|
* been taken. A handler that reads `request.body` and then calls
|
|
21
21
|
* `request.text()` gets the body once, not a hang.
|
|
22
|
+
*
|
|
23
|
+
* ## Shape
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* makeWebRequest(nodeReq, url, maxBodyBytes?): ExtendedRequest
|
|
27
|
+
*
|
|
28
|
+
* interface ExtendedRequest extends Request {
|
|
29
|
+
* body: ReadableStream<Uint8Array> | null; // lazy; null for GET/HEAD
|
|
30
|
+
* text(): Promise<string>;
|
|
31
|
+
* json(): Promise<unknown>;
|
|
32
|
+
* formData(): Promise<FormData>;
|
|
33
|
+
* clone(): ExtendedRequest; // same object — one socket, one read
|
|
34
|
+
* cookies: Record<string, string>;
|
|
35
|
+
* query: Record<string, string>;
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* bodyTooLarge(maxBytes): Error & { status: 413 } // re-exported by prod-server
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* One instance per request. Do not attach string-keyed properties to it — that is
|
|
42
|
+
* a public name the framework has to reserve forever, with no type for it. Use a
|
|
43
|
+
* private `Symbol` (see `AUTO_PARSED` in `@vesk/plugin-multipart`).
|
|
22
44
|
*/
|
|
23
45
|
import type { IncomingMessage } from 'node:http';
|
|
24
46
|
export interface ExtendedRequest extends Request {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-request.d.ts","sourceRoot":"","sources":["../src/web-request.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"web-request.d.ts","sourceRoot":"","sources":["../src/web-request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAIjD,MAAM,WAAW,eAAgB,SAAQ,OAAO;IAC9C,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACzB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9B,KAAK,IAAI,eAAe,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,eAAe,EACxB,GAAG,EAAE,MAAM,EACX,YAAY,GAAE,MAA+B,GAC5C,eAAe,CAwIjB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vesk/adapter",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.72",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vesk adapter — builds deployable .vesk/ output for Deno and Node serverless",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"sharp": "^0.35.3"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@vesk/compiler": "^0.2.
|
|
27
|
-
"@vesk/runtime": "^0.2.
|
|
28
|
-
"@vesk/types": "^0.2.
|
|
26
|
+
"@vesk/compiler": "^0.2.72",
|
|
27
|
+
"@vesk/runtime": "^0.2.72",
|
|
28
|
+
"@vesk/types": "^0.2.72"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/ws": "^8.18.1",
|