srvx-nightly 0.11.7 → 0.11.8
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/_chunks/_url.mjs +14 -0
- package/dist/_chunks/loader.d.mts +87 -0
- package/dist/adapters/aws-lambda.mjs +1 -0
- package/dist/adapters/bun.mjs +1 -0
- package/dist/adapters/bunny.mjs +1 -0
- package/dist/adapters/cloudflare.mjs +1 -0
- package/dist/adapters/deno.mjs +1 -0
- package/dist/adapters/generic.mjs +1 -0
- package/dist/adapters/node.mjs +47 -1
- package/dist/adapters/service-worker.mjs +1 -0
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/loader.d.mts +1 -86
- package/dist/tracing.mjs +21 -0
- package/package.json +10 -10
package/dist/_chunks/_url.mjs
CHANGED
|
@@ -25,6 +25,20 @@ function lazyInherit(target, source, sourceKey) {
|
|
|
25
25
|
if (modified) Object.defineProperty(target, key, desc);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* URL wrapper with fast paths to access to the following props:
|
|
30
|
+
*
|
|
31
|
+
* - `url.pathname`
|
|
32
|
+
* - `url.search`
|
|
33
|
+
* - `url.searchParams`
|
|
34
|
+
* - `url.protocol`
|
|
35
|
+
*
|
|
36
|
+
* **NOTES:**
|
|
37
|
+
*
|
|
38
|
+
* - It is assumed that the input URL is **already encoded** and formatted from an HTTP request and contains no hash.
|
|
39
|
+
* - Triggering the setters or getters on other props will deoptimize to full URL parsing.
|
|
40
|
+
* - Changes to `searchParams` will be discarded as we don't track them.
|
|
41
|
+
*/
|
|
28
42
|
const FastURL = /* @__PURE__ */ (() => {
|
|
29
43
|
const NativeURL = globalThis.URL;
|
|
30
44
|
const FastURL = class URL {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Server, ServerHandler } from "srvx";
|
|
2
|
+
|
|
3
|
+
//#region src/loader.d.ts
|
|
4
|
+
declare const defaultExts: string[];
|
|
5
|
+
declare const defaultEntries: string[];
|
|
6
|
+
/**
|
|
7
|
+
* Options for loading a server entry module.
|
|
8
|
+
*/
|
|
9
|
+
type LoadOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* Path or URL to the server entry file.
|
|
12
|
+
*
|
|
13
|
+
* If not provided, common entry points will be searched automatically.
|
|
14
|
+
*/
|
|
15
|
+
entry?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Base directory for resolving relative paths.
|
|
18
|
+
*
|
|
19
|
+
* @default "."
|
|
20
|
+
*/
|
|
21
|
+
dir?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Set to `false` to disable interception of `http.Server.listen` to detect legacy handlers.
|
|
24
|
+
*
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
interceptHttpListen?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Set to `false` to disable Node.js handler (req, res) compatibility.
|
|
30
|
+
*/
|
|
31
|
+
nodeCompat?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Node.js server instance to return when intercepting `http.Server.listen`.
|
|
34
|
+
*/
|
|
35
|
+
nodeServer?: NodeServer;
|
|
36
|
+
/**
|
|
37
|
+
* srvx server instance to return when intercepting `http.Server.listen`.
|
|
38
|
+
*/
|
|
39
|
+
srvxServer?: Server;
|
|
40
|
+
/**
|
|
41
|
+
* Hook called after the module is loaded to allow for custom processing.
|
|
42
|
+
*
|
|
43
|
+
* You can return a modified version of the module if needed.
|
|
44
|
+
*/
|
|
45
|
+
onLoad?: (module: unknown) => any;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Result of loading a server entry module.
|
|
49
|
+
*/
|
|
50
|
+
type LoadedServerEntry = {
|
|
51
|
+
/**
|
|
52
|
+
* The web fetch handler extracted from the loaded module.
|
|
53
|
+
*
|
|
54
|
+
* This is resolved from `module.fetch`, `module.default.fetch`,
|
|
55
|
+
* or upgraded from a legacy Node.js handler.
|
|
56
|
+
*/
|
|
57
|
+
fetch?: ServerHandler;
|
|
58
|
+
/**
|
|
59
|
+
* The raw loaded module.
|
|
60
|
+
*/
|
|
61
|
+
module?: any;
|
|
62
|
+
/**
|
|
63
|
+
* Whether the handler was upgraded from a legacy Node.js HTTP handler.
|
|
64
|
+
*
|
|
65
|
+
* When `true`, the original module exported a Node.js-style `(req, res)` handler
|
|
66
|
+
* that has been wrapped for web fetch compatibility.
|
|
67
|
+
*/
|
|
68
|
+
nodeCompat?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The resolved `file://` URL of the loaded entry module.
|
|
71
|
+
*/
|
|
72
|
+
url?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Whether the specified entry file was not found.
|
|
75
|
+
*
|
|
76
|
+
* When `true`, no valid entry point could be located.
|
|
77
|
+
*/
|
|
78
|
+
notFound?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* The original srvx server instance if the module used the loader API to export a server directly.
|
|
81
|
+
*/
|
|
82
|
+
srvxServer?: Server;
|
|
83
|
+
};
|
|
84
|
+
declare function loadServerEntry(opts: LoadOptions): Promise<LoadedServerEntry>;
|
|
85
|
+
type NodeServer = NonNullable<Server["node"]>["server"];
|
|
86
|
+
//#endregion
|
|
87
|
+
export { loadServerEntry as a, defaultExts as i, LoadedServerEntry as n, defaultEntries as r, LoadOptions as t };
|
package/dist/adapters/bun.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../_chunks/_utils.mjs";
|
|
1
2
|
import { t as FastURL } from "../_chunks/_url.mjs";
|
|
2
3
|
import { a as resolveTLSOptions, i as resolvePortAndHost, n as fmtURL, r as printListening, t as createWaitUntil } from "../_chunks/_utils2.mjs";
|
|
3
4
|
import { n as gracefulShutdownPlugin, r as wrapFetch } from "../_chunks/_plugins.mjs";
|
package/dist/adapters/bunny.mjs
CHANGED
package/dist/adapters/deno.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../_chunks/_utils.mjs";
|
|
1
2
|
import { t as FastURL } from "../_chunks/_url.mjs";
|
|
2
3
|
import { a as resolveTLSOptions, i as resolvePortAndHost, n as fmtURL, r as printListening, t as createWaitUntil } from "../_chunks/_utils2.mjs";
|
|
3
4
|
import { n as gracefulShutdownPlugin, r as wrapFetch } from "../_chunks/_plugins.mjs";
|
package/dist/adapters/node.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../_chunks/_utils.mjs";
|
|
1
2
|
import { n as lazyInherit, t as FastURL } from "../_chunks/_url.mjs";
|
|
2
3
|
import { a as resolveTLSOptions, i as resolvePortAndHost, n as fmtURL, r as printListening, t as createWaitUntil } from "../_chunks/_utils2.mjs";
|
|
3
4
|
import { n as gracefulShutdownPlugin, r as wrapFetch, t as errorPlugin } from "../_chunks/_plugins.mjs";
|
|
@@ -62,6 +63,11 @@ function streamBody(stream, nodeRes) {
|
|
|
62
63
|
nodeRes.off("error", streamCancel);
|
|
63
64
|
});
|
|
64
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Validates an HTTP Host header value (domain, IPv4, or bracketed IPv6) with optional port.
|
|
68
|
+
* Intended for preliminary filtering invalid values like "localhost:3000/foobar?"
|
|
69
|
+
*/
|
|
70
|
+
const HOST_RE = /^(\[(?:[A-Fa-f0-9:.]+)\]|(?:[A-Za-z0-9_-]+\.)*[A-Za-z0-9_-]+|(?:\d{1,3}\.){3}\d{1,3})(:\d{1,5})?$/;
|
|
65
71
|
var NodeRequestURL = class extends FastURL {
|
|
66
72
|
#req;
|
|
67
73
|
constructor({ req }) {
|
|
@@ -70,7 +76,11 @@ var NodeRequestURL = class extends FastURL {
|
|
|
70
76
|
const qIndex = path.indexOf("?");
|
|
71
77
|
const pathname = qIndex === -1 ? path : path?.slice(0, qIndex) || "/";
|
|
72
78
|
const search = qIndex === -1 ? "" : path?.slice(qIndex) || "";
|
|
73
|
-
|
|
79
|
+
let host = req.headers.host || req.headers[":authority"];
|
|
80
|
+
if (host) {
|
|
81
|
+
if (!HOST_RE.test(host)) throw new TypeError(`Invalid host header: ${host}`);
|
|
82
|
+
} else if (req.socket) host = `${req.socket.localFamily === "IPv6" ? "[" + req.socket.localAddress + "]" : req.socket.localAddress}:${req.socket?.localPort || "80"}`;
|
|
83
|
+
else host = "localhost";
|
|
74
84
|
const protocol = req.socket?.encrypted || req.headers["x-forwarded-proto"] === "https" || req.headers[":scheme"] === "https" ? "https:" : "http:";
|
|
75
85
|
super({
|
|
76
86
|
protocol,
|
|
@@ -248,6 +258,13 @@ const NodeRequest = /* @__PURE__ */ (() => {
|
|
|
248
258
|
Object.setPrototypeOf(Request.prototype, NativeRequest.prototype);
|
|
249
259
|
return Request;
|
|
250
260
|
})();
|
|
261
|
+
/**
|
|
262
|
+
* Undici uses an incompatible Request constructor depending on private property accessors.
|
|
263
|
+
*
|
|
264
|
+
* This utility, patches global Request to support `new Request(req)` in Node.js.
|
|
265
|
+
*
|
|
266
|
+
* Alternatively you can use `new Request(req._request || req)` instead of patching global Request.
|
|
267
|
+
*/
|
|
251
268
|
function patchGlobalRequest() {
|
|
252
269
|
const NativeRequest = globalThis[Symbol.for("srvx.nativeRequest")] ??= globalThis.Request;
|
|
253
270
|
const PatchedRequest = class Request extends NativeRequest {
|
|
@@ -282,6 +299,11 @@ function readBody(req) {
|
|
|
282
299
|
req.on("data", onData).once("end", onEnd).once("error", onError);
|
|
283
300
|
});
|
|
284
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Fast Response for Node.js runtime
|
|
304
|
+
*
|
|
305
|
+
* It is faster because in most cases it doesn't create a full Response instance.
|
|
306
|
+
*/
|
|
285
307
|
const NodeResponse = /* @__PURE__ */ (() => {
|
|
286
308
|
const NativeResponse = globalThis.Response;
|
|
287
309
|
const STATUS_CODES = globalThis.process?.getBuiltinModule?.("node:http")?.STATUS_CODES || {};
|
|
@@ -390,6 +412,11 @@ const NodeResponse = /* @__PURE__ */ (() => {
|
|
|
390
412
|
Object.setPrototypeOf(NodeResponse.prototype, NativeResponse.prototype);
|
|
391
413
|
return NodeResponse;
|
|
392
414
|
})();
|
|
415
|
+
/**
|
|
416
|
+
* Events:
|
|
417
|
+
* - Readable (req from client): readable => data => end (push(null)) => error => close
|
|
418
|
+
* - Writable (res to client): pipe => unpipe => drain => finish (end called) => error => close
|
|
419
|
+
*/
|
|
393
420
|
var WebRequestSocket = class extends Duplex {
|
|
394
421
|
_httpMessage;
|
|
395
422
|
autoSelectFamilyAttemptedAddresses = [];
|
|
@@ -604,6 +631,17 @@ var WebServerResponse = class extends ServerResponse {
|
|
|
604
631
|
});
|
|
605
632
|
}
|
|
606
633
|
};
|
|
634
|
+
/**
|
|
635
|
+
* Calls a Node.js HTTP Request handler with a Fetch API Request object and returns a Response object.
|
|
636
|
+
*
|
|
637
|
+
* If the web Request contains an existing Node.js req/res pair (indicating it originated from a Node.js server from srvx/node), it will be called directly.
|
|
638
|
+
*
|
|
639
|
+
* Otherwise, new Node.js IncomingMessage and ServerResponse objects are created and linked to a custom Duplex stream that bridges the Fetch API streams with Node.js streams.
|
|
640
|
+
*
|
|
641
|
+
* The handler is invoked with these objects, and the response is constructed from the ServerResponse once it is finished.
|
|
642
|
+
*
|
|
643
|
+
* @experimental Behavior might be unstable.
|
|
644
|
+
*/
|
|
607
645
|
async function fetchNodeHandler(handler, req) {
|
|
608
646
|
const nodeRuntime = req.runtime?.node;
|
|
609
647
|
if (nodeRuntime && nodeRuntime.req && nodeRuntime.res) return await callNodeHandler(handler, req);
|
|
@@ -624,6 +662,9 @@ async function fetchNodeHandler(handler, req) {
|
|
|
624
662
|
});
|
|
625
663
|
}
|
|
626
664
|
}
|
|
665
|
+
/**
|
|
666
|
+
* Converts a Fetch API handler to a Node.js HTTP handler.
|
|
667
|
+
*/
|
|
627
668
|
function toNodeHandler(handler) {
|
|
628
669
|
if (handler.__nodeHandler) return handler.__nodeHandler;
|
|
629
670
|
function convertedNodeHandler(nodeReq, nodeRes) {
|
|
@@ -637,6 +678,11 @@ function toNodeHandler(handler) {
|
|
|
637
678
|
assignFnName(convertedNodeHandler, handler, " (converted to Node handler)");
|
|
638
679
|
return convertedNodeHandler;
|
|
639
680
|
}
|
|
681
|
+
/**
|
|
682
|
+
* Converts a Node.js HTTP handler into a Fetch API handler.
|
|
683
|
+
*
|
|
684
|
+
* @experimental Behavior might be unstable and won't work in Bun and Deno currently (tracker: https://github.com/h3js/srvx/issues/132)
|
|
685
|
+
*/
|
|
640
686
|
function toFetchHandler(handler) {
|
|
641
687
|
if (handler.__fetchHandler) return handler.__fetchHandler;
|
|
642
688
|
function convertedNodeHandler(req) {
|
package/dist/cli.d.mts
CHANGED
package/dist/cli.mjs
CHANGED
package/dist/loader.d.mts
CHANGED
|
@@ -1,87 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//#region src/loader.d.ts
|
|
4
|
-
declare const defaultExts: string[];
|
|
5
|
-
declare const defaultEntries: string[];
|
|
6
|
-
/**
|
|
7
|
-
* Options for loading a server entry module.
|
|
8
|
-
*/
|
|
9
|
-
type LoadOptions = {
|
|
10
|
-
/**
|
|
11
|
-
* Path or URL to the server entry file.
|
|
12
|
-
*
|
|
13
|
-
* If not provided, common entry points will be searched automatically.
|
|
14
|
-
*/
|
|
15
|
-
entry?: string;
|
|
16
|
-
/**
|
|
17
|
-
* Base directory for resolving relative paths.
|
|
18
|
-
*
|
|
19
|
-
* @default "."
|
|
20
|
-
*/
|
|
21
|
-
dir?: string;
|
|
22
|
-
/**
|
|
23
|
-
* Set to `false` to disable interception of `http.Server.listen` to detect legacy handlers.
|
|
24
|
-
*
|
|
25
|
-
* @default true
|
|
26
|
-
*/
|
|
27
|
-
interceptHttpListen?: boolean;
|
|
28
|
-
/**
|
|
29
|
-
* Set to `false` to disable Node.js handler (req, res) compatibility.
|
|
30
|
-
*/
|
|
31
|
-
nodeCompat?: boolean;
|
|
32
|
-
/**
|
|
33
|
-
* Node.js server instance to return when intercepting `http.Server.listen`.
|
|
34
|
-
*/
|
|
35
|
-
nodeServer?: NodeServer;
|
|
36
|
-
/**
|
|
37
|
-
* srvx server instance to return when intercepting `http.Server.listen`.
|
|
38
|
-
*/
|
|
39
|
-
srvxServer?: Server;
|
|
40
|
-
/**
|
|
41
|
-
* Hook called after the module is loaded to allow for custom processing.
|
|
42
|
-
*
|
|
43
|
-
* You can return a modified version of the module if needed.
|
|
44
|
-
*/
|
|
45
|
-
onLoad?: (module: unknown) => any;
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* Result of loading a server entry module.
|
|
49
|
-
*/
|
|
50
|
-
type LoadedServerEntry = {
|
|
51
|
-
/**
|
|
52
|
-
* The web fetch handler extracted from the loaded module.
|
|
53
|
-
*
|
|
54
|
-
* This is resolved from `module.fetch`, `module.default.fetch`,
|
|
55
|
-
* or upgraded from a legacy Node.js handler.
|
|
56
|
-
*/
|
|
57
|
-
fetch?: ServerHandler;
|
|
58
|
-
/**
|
|
59
|
-
* The raw loaded module.
|
|
60
|
-
*/
|
|
61
|
-
module?: any;
|
|
62
|
-
/**
|
|
63
|
-
* Whether the handler was upgraded from a legacy Node.js HTTP handler.
|
|
64
|
-
*
|
|
65
|
-
* When `true`, the original module exported a Node.js-style `(req, res)` handler
|
|
66
|
-
* that has been wrapped for web fetch compatibility.
|
|
67
|
-
*/
|
|
68
|
-
nodeCompat?: boolean;
|
|
69
|
-
/**
|
|
70
|
-
* The resolved `file://` URL of the loaded entry module.
|
|
71
|
-
*/
|
|
72
|
-
url?: string;
|
|
73
|
-
/**
|
|
74
|
-
* Whether the specified entry file was not found.
|
|
75
|
-
*
|
|
76
|
-
* When `true`, no valid entry point could be located.
|
|
77
|
-
*/
|
|
78
|
-
notFound?: boolean;
|
|
79
|
-
/**
|
|
80
|
-
* The original srvx server instance if the module used the loader API to export a server directly.
|
|
81
|
-
*/
|
|
82
|
-
srvxServer?: Server;
|
|
83
|
-
};
|
|
84
|
-
declare function loadServerEntry(opts: LoadOptions): Promise<LoadedServerEntry>;
|
|
85
|
-
type NodeServer = NonNullable<Server["node"]>["server"];
|
|
86
|
-
//#endregion
|
|
1
|
+
import { a as loadServerEntry, i as defaultExts, n as LoadedServerEntry, r as defaultEntries, t as LoadOptions } from "./_chunks/loader.mjs";
|
|
87
2
|
export { LoadOptions, LoadedServerEntry, defaultEntries, defaultExts, loadServerEntry };
|
package/dist/tracing.mjs
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @experimental Channel names, event types and config options may change in future releases.
|
|
4
|
+
*
|
|
5
|
+
* Tracing plugin that adds diagnostics channel tracing to middleware and fetch handlers.
|
|
6
|
+
*
|
|
7
|
+
* This plugin wraps all middleware and the fetch handler with tracing instrumentation,
|
|
8
|
+
* allowing you to subscribe to `srvx.request` and `srvx.middleware` tracing channels.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { serve } from "srvx";
|
|
13
|
+
* import { tracingPlugin } from "srvx/tracing";
|
|
14
|
+
*
|
|
15
|
+
* const server = serve({
|
|
16
|
+
* fetch: (req) => new Response("OK"),
|
|
17
|
+
* middleware: [myMiddleware],
|
|
18
|
+
* plugins: [tracingPlugin()],
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
1
22
|
function tracingPlugin(opts = {}) {
|
|
2
23
|
return (server) => {
|
|
3
24
|
const { tracingChannel } = globalThis.process?.getBuiltinModule?.("node:diagnostics_channel") || {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "srvx-nightly",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.8",
|
|
4
4
|
"description": "Universal Server.",
|
|
5
5
|
"homepage": "https://srvx.h3.dev",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"vitest": "vitest"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@cloudflare/workers-types": "^4.
|
|
62
|
+
"@cloudflare/workers-types": "^4.20260304.0",
|
|
63
63
|
"@hono/node-server": "^1.19.9",
|
|
64
64
|
"@mitata/counters": "^0.0.8",
|
|
65
65
|
"@mjackson/node-fetch-server": "^0.7.0",
|
|
@@ -67,10 +67,10 @@
|
|
|
67
67
|
"@types/bun": "^1.3.9",
|
|
68
68
|
"@types/deno": "^2.5.0",
|
|
69
69
|
"@types/express": "^5.0.6",
|
|
70
|
-
"@types/node": "^25.
|
|
70
|
+
"@types/node": "^25.3.0",
|
|
71
71
|
"@types/node-forge": "^1.3.14",
|
|
72
|
-
"@types/serviceworker": "^0.0.
|
|
73
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
72
|
+
"@types/serviceworker": "^0.0.192",
|
|
73
|
+
"@typescript/native-preview": "^7.0.0-dev.20260225.1",
|
|
74
74
|
"@vitest/coverage-v8": "^4.0.18",
|
|
75
75
|
"@whatwg-node/server": "^0.10.18",
|
|
76
76
|
"automd": "^0.4.3",
|
|
@@ -83,10 +83,10 @@
|
|
|
83
83
|
"mdbox": "^0.1.1",
|
|
84
84
|
"mitata": "^1.0.34",
|
|
85
85
|
"node-forge": "^1.3.3",
|
|
86
|
-
"obuild": "^0.4.
|
|
87
|
-
"oxfmt": "^0.
|
|
88
|
-
"oxlint": "^1.
|
|
89
|
-
"srvx-release": "npm:srvx@^0.11.
|
|
86
|
+
"obuild": "^0.4.31",
|
|
87
|
+
"oxfmt": "^0.35.0",
|
|
88
|
+
"oxlint": "^1.50.0",
|
|
89
|
+
"srvx-release": "npm:srvx@^0.11.7",
|
|
90
90
|
"tslib": "^2.8.1",
|
|
91
91
|
"typescript": "^5.9.3",
|
|
92
92
|
"undici": "^7.22.0",
|
|
@@ -98,5 +98,5 @@
|
|
|
98
98
|
"engines": {
|
|
99
99
|
"node": ">=20.16.0"
|
|
100
100
|
},
|
|
101
|
-
"packageManager": "pnpm@10.
|
|
101
|
+
"packageManager": "pnpm@10.30.2"
|
|
102
102
|
}
|