@jsenv/server 12.7.4 → 13.0.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/README.md +68 -50
- package/package.json +2 -2
- package/src/content_negotiation/{applyContentNegotiation.js → pick_accepted_content.js} +1 -3
- package/src/content_negotiation/{negotiateContentEncoding.js → pick_content_encoding.js} +3 -3
- package/src/content_negotiation/{negotiateContentLanguage.js → pick_content_language.js} +3 -3
- package/src/content_negotiation/{negotiateContentType.js → pick_content_type.js} +3 -3
- package/src/fetch_filesystem.js +2 -2
- package/src/internal/dns_resolution.js +17 -0
- package/src/internal/getServerOrigins.js +43 -14
- package/src/internal/listen.js +15 -13
- package/src/main.js +11 -13
- package/src/serve_directory.js +2 -2
- package/src/{startServer.js → server.js} +150 -122
- package/src/service_controller.js +173 -0
- package/src/{cors/plugin_cors.js → services/cors/jsenv_service_cors.js} +29 -45
- package/src/services/error_handler/jsenv_service_error_handler.js +98 -0
- package/src/{content_negotiation/checkContentNegotiation.js → services/response_acceptance_check/jsenv_service_response_acceptance_check.js} +16 -7
- package/src/services/ressource_aliases/jsenv_service_ressource_aliases.js +40 -0
- package/src/sse/{createSSERoom.js → sse_room.js} +18 -16
- package/src/content_negotiation/plugin_content_negotiation_check.js +0 -15
- package/src/error_to_response_default.js +0 -94
- package/src/plugin_request_waiting_check.js +0 -32
- package/src/ressource_aliases/plugin_ressource_aliases.js +0 -44
- package/src/server_timing/plugin_server_timing.js +0 -28
- package/src/service_composition/service_composition.js +0 -55
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
} from "@jsenv/abort"
|
|
9
9
|
import { memoize } from "@jsenv/utils/src/memoize/memoize.js"
|
|
10
10
|
|
|
11
|
+
import { createServiceController } from "./service_controller.js"
|
|
12
|
+
import { timingToServerTimingResponseHeaders } from "./server_timing/timing_header.js"
|
|
11
13
|
import { createPolyglotServer } from "./internal/server-polyglot.js"
|
|
12
14
|
import { trackServerPendingConnections } from "./internal/trackServerPendingConnections.js"
|
|
13
15
|
import { trackServerPendingRequests } from "./internal/trackServerPendingRequests.js"
|
|
@@ -26,7 +28,6 @@ import { listen, stopListening } from "./internal/listen.js"
|
|
|
26
28
|
import { composeTwoResponses } from "./internal/response_composition.js"
|
|
27
29
|
import { listenRequest } from "./internal/listenRequest.js"
|
|
28
30
|
import { listenServerConnectionError } from "./internal/listenServerConnectionError.js"
|
|
29
|
-
import { applyDefaultErrorToResponse } from "./error_to_response_default.js"
|
|
30
31
|
import {
|
|
31
32
|
STOP_REASON_INTERNAL_ERROR,
|
|
32
33
|
STOP_REASON_PROCESS_SIGHUP,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
STOP_REASON_PROCESS_EXIT,
|
|
37
38
|
STOP_REASON_NOT_SPECIFIED,
|
|
38
39
|
} from "./stopReasons.js"
|
|
40
|
+
import { composeTwoHeaders } from "./internal/headers_composition.js"
|
|
39
41
|
|
|
40
42
|
export const startServer = async ({
|
|
41
43
|
signal = new AbortController().signal,
|
|
@@ -48,8 +50,8 @@ export const startServer = async ({
|
|
|
48
50
|
http1Allowed = true,
|
|
49
51
|
redirectHttpToHttps,
|
|
50
52
|
allowHttpRequestOnHttps = false,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
acceptAnyIp = false,
|
|
54
|
+
host = acceptAnyIp ? undefined : "localhost",
|
|
53
55
|
port = 0, // assign a random available port
|
|
54
56
|
portHint,
|
|
55
57
|
privateKey,
|
|
@@ -64,16 +66,21 @@ export const startServer = async ({
|
|
|
64
66
|
// auto close when requestToResponse throw an error
|
|
65
67
|
stopOnInternalError = false,
|
|
66
68
|
keepProcessAlive = true,
|
|
67
|
-
|
|
68
|
-
plugins = {},
|
|
69
|
-
|
|
70
|
-
sendErrorDetails = false,
|
|
71
|
-
errorToResponse = () => null,
|
|
72
|
-
|
|
73
|
-
onListenStart = () => {},
|
|
74
|
-
onListenEnd = () => {},
|
|
75
|
-
onStop = () => {},
|
|
69
|
+
services = [],
|
|
76
70
|
nagle = true,
|
|
71
|
+
serverTiming = false,
|
|
72
|
+
requestWaitingMs = 0,
|
|
73
|
+
requestWaitingCallback = ({ request, warn, requestWaitingMs }) => {
|
|
74
|
+
warn(
|
|
75
|
+
createDetailedMessage(
|
|
76
|
+
`still no response found for request after ${requestWaitingMs} ms`,
|
|
77
|
+
{
|
|
78
|
+
"request url": `${request.origin}${request.ressource}`,
|
|
79
|
+
"request headers": JSON.stringify(request.headers, null, " "),
|
|
80
|
+
},
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
},
|
|
77
84
|
} = {}) => {
|
|
78
85
|
if (protocol !== "http" && protocol !== "https") {
|
|
79
86
|
throw new Error(`protocol must be http or https, got ${protocol}`)
|
|
@@ -114,6 +121,8 @@ export const startServer = async ({
|
|
|
114
121
|
allowHttpRequestOnHttps = false
|
|
115
122
|
}
|
|
116
123
|
|
|
124
|
+
const serviceController = createServiceController(services)
|
|
125
|
+
|
|
117
126
|
const processTeardownEvents = {
|
|
118
127
|
SIGHUP: stopOnExit,
|
|
119
128
|
SIGTERM: stopOnExit,
|
|
@@ -151,15 +160,14 @@ export const startServer = async ({
|
|
|
151
160
|
nodeServer.unref()
|
|
152
161
|
}
|
|
153
162
|
|
|
154
|
-
onListenStart()
|
|
155
163
|
port = await listen({
|
|
156
164
|
signal: startServerOperation.signal,
|
|
157
165
|
server: nodeServer,
|
|
158
166
|
port,
|
|
159
167
|
portHint,
|
|
160
|
-
|
|
168
|
+
host,
|
|
161
169
|
})
|
|
162
|
-
|
|
170
|
+
serviceController.callHooks("serverListening", { port })
|
|
163
171
|
startServerOperation.addAbortCallback(async () => {
|
|
164
172
|
await stopListening(nodeServer)
|
|
165
173
|
})
|
|
@@ -177,7 +185,6 @@ export const startServer = async ({
|
|
|
177
185
|
stopCallbackList.add(({ reason }) => {
|
|
178
186
|
logger.info(`${serverName} stopping server (reason: ${reason})`)
|
|
179
187
|
})
|
|
180
|
-
stopCallbackList.add(onStop)
|
|
181
188
|
stopCallbackList.add(async () => {
|
|
182
189
|
await stopListening(nodeServer)
|
|
183
190
|
})
|
|
@@ -188,6 +195,7 @@ export const startServer = async ({
|
|
|
188
195
|
const stop = memoize(async (reason = STOP_REASON_NOT_SPECIFIED) => {
|
|
189
196
|
status = "stopping"
|
|
190
197
|
await Promise.all(stopCallbackList.notify({ reason }))
|
|
198
|
+
serviceController.callHooks("serverStopped", { reason })
|
|
191
199
|
status = "stopped"
|
|
192
200
|
stoppedResolve(reason)
|
|
193
201
|
})
|
|
@@ -208,8 +216,8 @@ export const startServer = async ({
|
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
status = "opened"
|
|
211
|
-
const serverOrigins = getServerOrigins({ protocol,
|
|
212
|
-
const serverOrigin = serverOrigins.
|
|
219
|
+
const serverOrigins = await getServerOrigins({ protocol, host, port })
|
|
220
|
+
const serverOrigin = serverOrigins.local
|
|
213
221
|
|
|
214
222
|
const removeConnectionErrorListener = listenServerConnectionError(
|
|
215
223
|
nodeServer,
|
|
@@ -467,13 +475,16 @@ export const startServer = async ({
|
|
|
467
475
|
}
|
|
468
476
|
|
|
469
477
|
const handleRequest = async (request, { requestNode }) => {
|
|
478
|
+
let requestReceivedMeasure
|
|
479
|
+
if (serverTiming) {
|
|
480
|
+
requestReceivedMeasure = performance.now()
|
|
481
|
+
}
|
|
470
482
|
addRequestLog(requestNode, {
|
|
471
483
|
type: "info",
|
|
472
484
|
value: request.parent
|
|
473
485
|
? `Push ${request.ressource}`
|
|
474
486
|
: `${request.method} ${request.origin}${request.ressource}`,
|
|
475
487
|
})
|
|
476
|
-
|
|
477
488
|
const warn = (value) => {
|
|
478
489
|
addRequestLog(requestNode, {
|
|
479
490
|
type: "warn",
|
|
@@ -481,36 +492,39 @@ export const startServer = async ({
|
|
|
481
492
|
})
|
|
482
493
|
}
|
|
483
494
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
request = applyRedirectionToRequest(request, requestRedirection)
|
|
495
|
+
let requestWaitingTimeout
|
|
496
|
+
if (requestWaitingMs) {
|
|
497
|
+
requestWaitingTimeout = setTimeout(
|
|
498
|
+
() => requestWaitingCallback({ request, warn, requestWaitingMs }),
|
|
499
|
+
requestWaitingMs,
|
|
500
|
+
).unref()
|
|
491
501
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
pluginName,
|
|
504
|
-
...returnValue,
|
|
502
|
+
|
|
503
|
+
serviceController.callHooks(
|
|
504
|
+
"redirectRequest",
|
|
505
|
+
request,
|
|
506
|
+
{ warn },
|
|
507
|
+
(newRequestProperties) => {
|
|
508
|
+
if (newRequestProperties) {
|
|
509
|
+
request = applyRedirectionToRequest(request, {
|
|
510
|
+
original: request.original || request,
|
|
511
|
+
previous: request,
|
|
512
|
+
...newRequestProperties,
|
|
505
513
|
})
|
|
506
514
|
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
515
|
+
},
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
let handleRequestReturnValue
|
|
519
|
+
let errorWhileHandlingRequest = null
|
|
520
|
+
let handleRequestTimings = serverTiming ? {} : null
|
|
521
|
+
try {
|
|
522
|
+
handleRequestReturnValue = await serviceController.callAsyncHooksUntil(
|
|
523
|
+
"handleRequest",
|
|
524
|
+
request,
|
|
525
|
+
{
|
|
526
|
+
timing: handleRequestTimings,
|
|
527
|
+
warn,
|
|
514
528
|
pushResponse: async ({ path, method }) => {
|
|
515
529
|
if (typeof path !== "string" || path[0] !== "/") {
|
|
516
530
|
addRequestLog(requestNode, {
|
|
@@ -535,22 +549,24 @@ export const startServer = async ({
|
|
|
535
549
|
return
|
|
536
550
|
}
|
|
537
551
|
|
|
538
|
-
let
|
|
552
|
+
let preventedByService = null
|
|
539
553
|
const prevent = () => {
|
|
540
|
-
|
|
554
|
+
preventedByService = serviceController.getCurrentService()
|
|
541
555
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
556
|
+
serviceController.callHooksUntil(
|
|
557
|
+
"onResponsePush",
|
|
558
|
+
{ path, method },
|
|
559
|
+
{
|
|
560
|
+
request,
|
|
561
|
+
warn,
|
|
562
|
+
prevent,
|
|
563
|
+
},
|
|
564
|
+
() => preventedByService,
|
|
565
|
+
)
|
|
566
|
+
if (preventedByService) {
|
|
551
567
|
addRequestLog(requestNode, {
|
|
552
568
|
type: "debug",
|
|
553
|
-
value: `response push prevented by "${
|
|
569
|
+
value: `response push prevented by "${preventedByService.name}" service`,
|
|
554
570
|
})
|
|
555
571
|
return
|
|
556
572
|
}
|
|
@@ -565,16 +581,21 @@ export const startServer = async ({
|
|
|
565
581
|
},
|
|
566
582
|
)
|
|
567
583
|
},
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
return ABORTED_RESPONSE_PROPERTIES
|
|
584
|
+
},
|
|
585
|
+
)
|
|
586
|
+
} catch (error) {
|
|
587
|
+
errorWhileHandlingRequest = error
|
|
573
588
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if (
|
|
589
|
+
|
|
590
|
+
let responseProperties
|
|
591
|
+
if (errorWhileHandlingRequest) {
|
|
592
|
+
if (
|
|
593
|
+
errorWhileHandlingRequest.name === "AbortError" &&
|
|
594
|
+
request.signal.aborted
|
|
595
|
+
) {
|
|
596
|
+
responseProperties = ABORTED_RESPONSE_PROPERTIES
|
|
597
|
+
} else {
|
|
598
|
+
// internal error, create 500 response
|
|
578
599
|
if (
|
|
579
600
|
// stopOnInternalError stops server only if requestToResponse generated
|
|
580
601
|
// a non controlled error (internal error).
|
|
@@ -585,25 +606,28 @@ export const startServer = async ({
|
|
|
585
606
|
// il faudrais pouvoir stop que les autres response ?
|
|
586
607
|
stop(STOP_REASON_INTERNAL_ERROR)
|
|
587
608
|
}
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
609
|
+
const handleErrorReturnValue =
|
|
610
|
+
await serviceController.callAsyncHooksUntil(
|
|
611
|
+
"handleError",
|
|
612
|
+
errorWhileHandlingRequest,
|
|
613
|
+
{
|
|
614
|
+
request,
|
|
615
|
+
warn,
|
|
616
|
+
},
|
|
617
|
+
)
|
|
618
|
+
if (!handleErrorReturnValue) {
|
|
619
|
+
throw errorWhileHandlingRequest
|
|
620
|
+
}
|
|
597
621
|
addRequestLog(requestNode, {
|
|
598
622
|
type: "error",
|
|
599
623
|
value: createDetailedMessage(
|
|
600
624
|
`internal error while handling request`,
|
|
601
625
|
{
|
|
602
|
-
"error stack":
|
|
626
|
+
"error stack": errorWhileHandlingRequest.stack,
|
|
603
627
|
},
|
|
604
628
|
),
|
|
605
629
|
})
|
|
606
|
-
|
|
630
|
+
responseProperties = composeTwoResponses(
|
|
607
631
|
{
|
|
608
632
|
status: 500,
|
|
609
633
|
statusText: "Internal Server Error",
|
|
@@ -613,10 +637,10 @@ export const startServer = async ({
|
|
|
613
637
|
"content-type": "text/plain",
|
|
614
638
|
},
|
|
615
639
|
},
|
|
616
|
-
|
|
640
|
+
handleErrorReturnValue,
|
|
617
641
|
)
|
|
618
642
|
}
|
|
619
|
-
|
|
643
|
+
} else {
|
|
620
644
|
const {
|
|
621
645
|
status = 501,
|
|
622
646
|
statusText,
|
|
@@ -624,8 +648,8 @@ export const startServer = async ({
|
|
|
624
648
|
headers = {},
|
|
625
649
|
body,
|
|
626
650
|
...rest
|
|
627
|
-
} =
|
|
628
|
-
|
|
651
|
+
} = handleRequestReturnValue || {}
|
|
652
|
+
responseProperties = {
|
|
629
653
|
status,
|
|
630
654
|
statusText,
|
|
631
655
|
statusMessage,
|
|
@@ -633,23 +657,25 @@ export const startServer = async ({
|
|
|
633
657
|
body,
|
|
634
658
|
...rest,
|
|
635
659
|
}
|
|
636
|
-
return responseProperties
|
|
637
660
|
}
|
|
638
661
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
responseProperties,
|
|
648
|
-
returnValue,
|
|
649
|
-
)
|
|
650
|
-
}
|
|
662
|
+
if (serverTiming) {
|
|
663
|
+
const responseReadyMeasure = performance.now()
|
|
664
|
+
const timeToStartResponding =
|
|
665
|
+
responseReadyMeasure - requestReceivedMeasure
|
|
666
|
+
const serverTiming = {
|
|
667
|
+
...handleRequestTimings,
|
|
668
|
+
...responseProperties.timing,
|
|
669
|
+
"time to start responding": timeToStartResponding,
|
|
651
670
|
}
|
|
652
|
-
|
|
671
|
+
responseProperties.headers = composeTwoHeaders(
|
|
672
|
+
responseProperties.headers,
|
|
673
|
+
timingToServerTimingResponseHeaders(serverTiming),
|
|
674
|
+
)
|
|
675
|
+
}
|
|
676
|
+
if (requestWaitingMs) {
|
|
677
|
+
clearTimeout(requestWaitingTimeout)
|
|
678
|
+
}
|
|
653
679
|
if (
|
|
654
680
|
request.method !== "HEAD" &&
|
|
655
681
|
responseProperties.headers["content-length"] > 0 &&
|
|
@@ -660,6 +686,26 @@ export const startServer = async ({
|
|
|
660
686
|
value: `content-length header is ${responseProperties.headers["content-length"]} but body is empty`,
|
|
661
687
|
})
|
|
662
688
|
}
|
|
689
|
+
serviceController.callHooks(
|
|
690
|
+
"injectResponseHeaders",
|
|
691
|
+
responseProperties,
|
|
692
|
+
{
|
|
693
|
+
request,
|
|
694
|
+
warn,
|
|
695
|
+
},
|
|
696
|
+
(returnValue) => {
|
|
697
|
+
if (returnValue) {
|
|
698
|
+
responseProperties.headers = composeTwoHeaders(
|
|
699
|
+
responseProperties.headers,
|
|
700
|
+
returnValue,
|
|
701
|
+
)
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
)
|
|
705
|
+
serviceController.callHooks("responseReady", responseProperties, {
|
|
706
|
+
request,
|
|
707
|
+
warn,
|
|
708
|
+
})
|
|
663
709
|
return responseProperties
|
|
664
710
|
}
|
|
665
711
|
|
|
@@ -759,9 +805,13 @@ export const startServer = async ({
|
|
|
759
805
|
stopCallbackList.add(removeRequestListener)
|
|
760
806
|
|
|
761
807
|
if (startLog) {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
808
|
+
if (serverOrigins.network) {
|
|
809
|
+
logger.info(
|
|
810
|
+
`${serverName} started at ${serverOrigins.local} (${serverOrigins.network})`,
|
|
811
|
+
)
|
|
812
|
+
} else {
|
|
813
|
+
logger.info(`${serverName} started at ${serverOrigins.local}`)
|
|
814
|
+
}
|
|
765
815
|
}
|
|
766
816
|
|
|
767
817
|
const addEffect = (callback) => {
|
|
@@ -773,6 +823,7 @@ export const startServer = async ({
|
|
|
773
823
|
|
|
774
824
|
return {
|
|
775
825
|
getStatus: () => status,
|
|
826
|
+
port,
|
|
776
827
|
origin: serverOrigin,
|
|
777
828
|
origins: serverOrigins,
|
|
778
829
|
nodeServer,
|
|
@@ -811,29 +862,6 @@ const createNodeServer = async ({
|
|
|
811
862
|
})
|
|
812
863
|
}
|
|
813
864
|
|
|
814
|
-
const generateResponseDescription = async ({
|
|
815
|
-
requestToResponse,
|
|
816
|
-
request,
|
|
817
|
-
pushResponse,
|
|
818
|
-
}) => {
|
|
819
|
-
try {
|
|
820
|
-
const returnValue = await requestToResponse(request, {
|
|
821
|
-
pushResponse,
|
|
822
|
-
})
|
|
823
|
-
return {
|
|
824
|
-
returnValue,
|
|
825
|
-
}
|
|
826
|
-
} catch (error) {
|
|
827
|
-
if (error.name === "AbortError" && request.signal.aborted) {
|
|
828
|
-
return {
|
|
829
|
-
aborted: true,
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
return {
|
|
833
|
-
error,
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
865
|
const testCanPushStream = (http2Stream) => {
|
|
838
866
|
if (!http2Stream.pushAllowed) {
|
|
839
867
|
return {
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { timeStart } from "./server_timing/timing_measure.js"
|
|
2
|
+
|
|
3
|
+
const HOOK_NAMES = [
|
|
4
|
+
"serverListening",
|
|
5
|
+
"redirectRequest",
|
|
6
|
+
"handleRequest",
|
|
7
|
+
"handleError",
|
|
8
|
+
"onResponsePush",
|
|
9
|
+
"injectResponseHeaders",
|
|
10
|
+
"responseReady",
|
|
11
|
+
"serverStopped",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
export const createServiceController = (services) => {
|
|
15
|
+
const flatServices = flattenAndFilterServices(services)
|
|
16
|
+
const hookGroups = {}
|
|
17
|
+
|
|
18
|
+
const addService = (service) => {
|
|
19
|
+
Object.keys(service).forEach((key) => {
|
|
20
|
+
if (key === "name") return
|
|
21
|
+
const isHook = HOOK_NAMES.includes(key)
|
|
22
|
+
if (!isHook) {
|
|
23
|
+
console.warn(
|
|
24
|
+
`Unexpected "${key}" property on "${service.name}" service`,
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
const hookName = key
|
|
28
|
+
const hookValue = service[hookName]
|
|
29
|
+
if (hookValue) {
|
|
30
|
+
const group = hookGroups[hookName] || (hookGroups[hookName] = [])
|
|
31
|
+
group.push({
|
|
32
|
+
service,
|
|
33
|
+
name: hookName,
|
|
34
|
+
value: hookValue,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
flatServices.forEach((service) => {
|
|
40
|
+
addService(service)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
let currentService = null
|
|
44
|
+
let currentHookName = null
|
|
45
|
+
const callHook = (hook, info, context) => {
|
|
46
|
+
const hookFn = hook.value
|
|
47
|
+
if (!hookFn) {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
currentService = hook.service
|
|
51
|
+
currentHookName = hook.name
|
|
52
|
+
let timeEnd
|
|
53
|
+
if (context && context.timing) {
|
|
54
|
+
timeEnd = timeStart(
|
|
55
|
+
`${currentService.name.replace("jsenv:", "")}.${currentHookName}`,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
let valueReturned = hookFn(info, context)
|
|
59
|
+
if (context && context.timing) {
|
|
60
|
+
Object.assign(context.timing, timeEnd())
|
|
61
|
+
}
|
|
62
|
+
currentService = null
|
|
63
|
+
currentHookName = null
|
|
64
|
+
return valueReturned
|
|
65
|
+
}
|
|
66
|
+
const callAsyncHook = async (hook, info, context) => {
|
|
67
|
+
const hookFn = hook.value
|
|
68
|
+
if (!hookFn) {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
currentService = hook.service
|
|
72
|
+
currentHookName = hook.name
|
|
73
|
+
let timeEnd
|
|
74
|
+
if (context && context.timing) {
|
|
75
|
+
timeEnd = timeStart(
|
|
76
|
+
`${currentService.name.replace("jsenv:", "")}.${currentHookName}`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
let valueReturned = await hookFn(info, context)
|
|
80
|
+
if (context && context.timing) {
|
|
81
|
+
Object.assign(context.timing, timeEnd())
|
|
82
|
+
}
|
|
83
|
+
currentService = null
|
|
84
|
+
currentHookName = null
|
|
85
|
+
return valueReturned
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const callHooks = (hookName, info, context, callback = () => {}) => {
|
|
89
|
+
const hooks = hookGroups[hookName]
|
|
90
|
+
if (hooks) {
|
|
91
|
+
for (const hook of hooks) {
|
|
92
|
+
const returnValue = callHook(hook, info, context)
|
|
93
|
+
if (returnValue) {
|
|
94
|
+
callback(returnValue)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const callHooksUntil = (
|
|
100
|
+
hookName,
|
|
101
|
+
info,
|
|
102
|
+
context,
|
|
103
|
+
until = (returnValue) => returnValue,
|
|
104
|
+
) => {
|
|
105
|
+
const hooks = hookGroups[hookName]
|
|
106
|
+
if (hooks) {
|
|
107
|
+
for (const hook of hooks) {
|
|
108
|
+
const returnValue = callHook(hook, info, context)
|
|
109
|
+
const untilReturnValue = until(returnValue)
|
|
110
|
+
if (untilReturnValue) {
|
|
111
|
+
return untilReturnValue
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
const callAsyncHooksUntil = (hookName, info, context) => {
|
|
118
|
+
const hooks = hookGroups[hookName]
|
|
119
|
+
if (!hooks) {
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
if (hooks.length === 0) {
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
const visit = (index) => {
|
|
127
|
+
if (index >= hooks.length) {
|
|
128
|
+
return resolve()
|
|
129
|
+
}
|
|
130
|
+
const hook = hooks[index]
|
|
131
|
+
const returnValue = callAsyncHook(hook, info, context)
|
|
132
|
+
return Promise.resolve(returnValue).then((output) => {
|
|
133
|
+
if (output) {
|
|
134
|
+
return resolve(output)
|
|
135
|
+
}
|
|
136
|
+
return visit(index + 1)
|
|
137
|
+
}, reject)
|
|
138
|
+
}
|
|
139
|
+
visit(0)
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
services: flatServices,
|
|
145
|
+
|
|
146
|
+
callHooks,
|
|
147
|
+
callHooksUntil,
|
|
148
|
+
callAsyncHooksUntil,
|
|
149
|
+
|
|
150
|
+
getCurrentService: () => currentService,
|
|
151
|
+
getCurrentHookName: () => currentHookName,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const flattenAndFilterServices = (services) => {
|
|
156
|
+
const flatServices = []
|
|
157
|
+
const visitServiceEntry = (serviceEntry) => {
|
|
158
|
+
if (Array.isArray(serviceEntry)) {
|
|
159
|
+
serviceEntry.forEach((value) => visitServiceEntry(value))
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
if (typeof serviceEntry === "object" && serviceEntry !== null) {
|
|
163
|
+
if (!serviceEntry.name) {
|
|
164
|
+
serviceEntry.name = "anonymous"
|
|
165
|
+
}
|
|
166
|
+
flatServices.push(serviceEntry)
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`services must be objects, got ${serviceEntry}`)
|
|
170
|
+
}
|
|
171
|
+
services.forEach((serviceEntry) => visitServiceEntry(serviceEntry))
|
|
172
|
+
return flatServices
|
|
173
|
+
}
|