@superblocksteam/shared 0.9607.3 → 0.9608.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/database-lifecycle/appDatabaseCompatibility.d.ts +49 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.d.ts.map +1 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.js +32 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.js.map +1 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.test.d.ts +2 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.test.d.ts.map +1 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.test.js +47 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.test.js.map +1 -0
- package/dist/database-lifecycle/destructiveMigrations.d.ts +12 -0
- package/dist/database-lifecycle/destructiveMigrations.d.ts.map +1 -1
- package/dist/database-lifecycle/destructiveMigrations.js +114 -0
- package/dist/database-lifecycle/destructiveMigrations.js.map +1 -1
- package/dist/database-lifecycle/destructiveMigrations.test.js +79 -0
- package/dist/database-lifecycle/destructiveMigrations.test.js.map +1 -1
- package/dist/database-lifecycle/index.d.ts +46 -2
- package/dist/database-lifecycle/index.d.ts.map +1 -1
- package/dist/database-lifecycle/index.js +127 -1
- package/dist/database-lifecycle/index.js.map +1 -1
- package/dist/database-lifecycle/providerDiagnostics.test.d.ts +2 -0
- package/dist/database-lifecycle/providerDiagnostics.test.d.ts.map +1 -0
- package/dist/database-lifecycle/providerDiagnostics.test.js +119 -0
- package/dist/database-lifecycle/providerDiagnostics.test.js.map +1 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.d.ts +65 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.d.ts.map +1 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.js +87 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.js.map +1 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.test.d.ts +2 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.test.d.ts.map +1 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.test.js +285 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.test.js.map +1 -0
- package/dist/errors/http.d.ts +15 -4
- package/dist/errors/http.d.ts.map +1 -1
- package/dist/errors/http.js +25 -7
- package/dist/errors/http.js.map +1 -1
- package/dist/errors/http.test.d.ts +2 -0
- package/dist/errors/http.test.d.ts.map +1 -0
- package/dist/errors/http.test.js +28 -0
- package/dist/errors/http.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/templates/restApiIntegration.d.ts.map +1 -1
- package/dist/plugins/templates/restApiIntegration.js +2 -0
- package/dist/plugins/templates/restApiIntegration.js.map +1 -1
- package/dist/plugins/templates/shared/auth.d.ts +3 -1
- package/dist/plugins/templates/shared/auth.d.ts.map +1 -1
- package/dist/plugins/templates/shared/auth.js +129 -17
- package/dist/plugins/templates/shared/auth.js.map +1 -1
- package/dist/plugins/templates/shared/auth.test.d.ts +2 -0
- package/dist/plugins/templates/shared/auth.test.d.ts.map +1 -0
- package/dist/plugins/templates/shared/auth.test.js +116 -0
- package/dist/plugins/templates/shared/auth.test.js.map +1 -0
- package/dist/plugins/templates/shared/restapiintegration.d.ts +2 -0
- package/dist/plugins/templates/shared/restapiintegration.d.ts.map +1 -1
- package/dist/plugins/templates/shared/restapiintegration.js +3 -1
- package/dist/plugins/templates/shared/restapiintegration.js.map +1 -1
- package/dist/socket/dist-entry.test.d.ts +2 -0
- package/dist/socket/dist-entry.test.d.ts.map +1 -0
- package/dist/socket/dist-entry.test.js +34 -0
- package/dist/socket/dist-entry.test.js.map +1 -0
- package/dist/socket/heartbeat.d.ts +258 -0
- package/dist/socket/heartbeat.d.ts.map +1 -0
- package/dist/socket/heartbeat.js +307 -0
- package/dist/socket/heartbeat.js.map +1 -0
- package/dist/socket/heartbeat.test.d.ts +2 -0
- package/dist/socket/heartbeat.test.d.ts.map +1 -0
- package/dist/socket/heartbeat.test.js +431 -0
- package/dist/socket/heartbeat.test.js.map +1 -0
- package/dist/socket/index.d.ts +1 -0
- package/dist/socket/index.d.ts.map +1 -1
- package/dist/socket/index.js +1 -0
- package/dist/socket/index.js.map +1 -1
- package/dist/socket/util.d.ts.map +1 -1
- package/dist/socket/util.js +15 -1
- package/dist/socket/util.js.map +1 -1
- package/dist/socket/util.test.js +12 -2
- package/dist/socket/util.test.js.map +1 -1
- package/dist/types/ai/index.d.ts +3 -0
- package/dist/types/ai/index.d.ts.map +1 -1
- package/dist/types/ai/index.js +3 -0
- package/dist/types/ai/index.js.map +1 -1
- package/dist/types/application/index.d.ts +12 -0
- package/dist/types/application/index.d.ts.map +1 -1
- package/dist/types/application/index.js.map +1 -1
- package/dist/types/codeSecurity/index.d.ts +55 -1
- package/dist/types/codeSecurity/index.d.ts.map +1 -1
- package/dist/types/codeSecurity/index.js +21 -1
- package/dist/types/codeSecurity/index.js.map +1 -1
- package/dist/types/codeSecurity/index.test.d.ts +2 -0
- package/dist/types/codeSecurity/index.test.d.ts.map +1 -0
- package/dist/types/codeSecurity/index.test.js +19 -0
- package/dist/types/codeSecurity/index.test.js.map +1 -0
- package/dist/types/datasource/auth.d.ts +12 -1
- package/dist/types/datasource/auth.d.ts.map +1 -1
- package/dist/types/datasource/auth.js +20 -2
- package/dist/types/datasource/auth.js.map +1 -1
- package/dist/types/datasource/auth.test.js +3 -1
- package/dist/types/datasource/auth.test.js.map +1 -1
- package/dist/types/group/index.d.ts +1 -3
- package/dist/types/group/index.d.ts.map +1 -1
- package/dist/types/group/index.js +1 -3
- package/dist/types/group/index.js.map +1 -1
- package/dist/types/integration/index.d.ts +11 -1
- package/dist/types/integration/index.d.ts.map +1 -1
- package/dist/types/integration/index.js +11 -1
- package/dist/types/integration/index.js.map +1 -1
- package/dist/types/plugin/form.d.ts +32 -0
- package/dist/types/plugin/form.d.ts.map +1 -1
- package/dist/types/plugin/form.js +73 -1
- package/dist/types/plugin/form.js.map +1 -1
- package/dist/types/plugin/form.test.js +128 -0
- package/dist/types/plugin/form.test.js.map +1 -1
- package/dist/types/profile/index.d.ts +16 -1
- package/dist/types/profile/index.d.ts.map +1 -1
- package/dist/types/profile/index.js +7 -1
- package/dist/types/profile/index.js.map +1 -1
- package/dist/types/rbac/index.d.ts +2 -0
- package/dist/types/rbac/index.d.ts.map +1 -1
- package/dist/types/rbac/index.js +2 -0
- package/dist/types/rbac/index.js.map +1 -1
- package/dist/types/response/GetRecentApplicationsResponseBody.d.ts +4 -0
- package/dist/types/response/GetRecentApplicationsResponseBody.d.ts.map +1 -0
- package/dist/types/response/GetRecentApplicationsResponseBody.js +3 -0
- package/dist/types/response/GetRecentApplicationsResponseBody.js.map +1 -0
- package/dist/types/response/index.d.ts +1 -0
- package/dist/types/response/index.d.ts.map +1 -1
- package/dist/types/response/index.js +1 -0
- package/dist/types/response/index.js.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.d.ts +49 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.js +28 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.js.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.d.ts +2 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.js +45 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.js.map +1 -0
- package/dist-esm/database-lifecycle/destructiveMigrations.d.ts +12 -0
- package/dist-esm/database-lifecycle/destructiveMigrations.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/destructiveMigrations.js +113 -0
- package/dist-esm/database-lifecycle/destructiveMigrations.js.map +1 -1
- package/dist-esm/database-lifecycle/destructiveMigrations.test.js +80 -1
- package/dist-esm/database-lifecycle/destructiveMigrations.test.js.map +1 -1
- package/dist-esm/database-lifecycle/index.d.ts +46 -2
- package/dist-esm/database-lifecycle/index.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/index.js +123 -1
- package/dist-esm/database-lifecycle/index.js.map +1 -1
- package/dist-esm/database-lifecycle/providerDiagnostics.test.d.ts +2 -0
- package/dist-esm/database-lifecycle/providerDiagnostics.test.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/providerDiagnostics.test.js +117 -0
- package/dist-esm/database-lifecycle/providerDiagnostics.test.js.map +1 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.d.ts +65 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.d.ts.map +1 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.js +79 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.js.map +1 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.d.ts +2 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.d.ts.map +1 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.js +283 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.js.map +1 -0
- package/dist-esm/errors/http.d.ts +15 -4
- package/dist-esm/errors/http.d.ts.map +1 -1
- package/dist-esm/errors/http.js +22 -6
- package/dist-esm/errors/http.js.map +1 -1
- package/dist-esm/errors/http.test.d.ts +2 -0
- package/dist-esm/errors/http.test.d.ts.map +1 -0
- package/dist-esm/errors/http.test.js +26 -0
- package/dist-esm/errors/http.test.js.map +1 -0
- package/dist-esm/index.d.ts +2 -0
- package/dist-esm/index.d.ts.map +1 -1
- package/dist-esm/index.js +2 -0
- package/dist-esm/index.js.map +1 -1
- package/dist-esm/plugins/templates/restApiIntegration.d.ts.map +1 -1
- package/dist-esm/plugins/templates/restApiIntegration.js +2 -0
- package/dist-esm/plugins/templates/restApiIntegration.js.map +1 -1
- package/dist-esm/plugins/templates/shared/auth.d.ts +3 -1
- package/dist-esm/plugins/templates/shared/auth.d.ts.map +1 -1
- package/dist-esm/plugins/templates/shared/auth.js +129 -17
- package/dist-esm/plugins/templates/shared/auth.js.map +1 -1
- package/dist-esm/plugins/templates/shared/auth.test.d.ts +2 -0
- package/dist-esm/plugins/templates/shared/auth.test.d.ts.map +1 -0
- package/dist-esm/plugins/templates/shared/auth.test.js +114 -0
- package/dist-esm/plugins/templates/shared/auth.test.js.map +1 -0
- package/dist-esm/plugins/templates/shared/restapiintegration.d.ts +2 -0
- package/dist-esm/plugins/templates/shared/restapiintegration.d.ts.map +1 -1
- package/dist-esm/plugins/templates/shared/restapiintegration.js +3 -1
- package/dist-esm/plugins/templates/shared/restapiintegration.js.map +1 -1
- package/dist-esm/socket/dist-entry.test.d.ts +2 -0
- package/dist-esm/socket/dist-entry.test.d.ts.map +1 -0
- package/dist-esm/socket/dist-entry.test.js +29 -0
- package/dist-esm/socket/dist-entry.test.js.map +1 -0
- package/dist-esm/socket/heartbeat.d.ts +258 -0
- package/dist-esm/socket/heartbeat.d.ts.map +1 -0
- package/dist-esm/socket/heartbeat.js +303 -0
- package/dist-esm/socket/heartbeat.js.map +1 -0
- package/dist-esm/socket/heartbeat.test.d.ts +2 -0
- package/dist-esm/socket/heartbeat.test.d.ts.map +1 -0
- package/dist-esm/socket/heartbeat.test.js +429 -0
- package/dist-esm/socket/heartbeat.test.js.map +1 -0
- package/dist-esm/socket/index.d.ts +1 -0
- package/dist-esm/socket/index.d.ts.map +1 -1
- package/dist-esm/socket/index.js +1 -0
- package/dist-esm/socket/index.js.map +1 -1
- package/dist-esm/socket/util.d.ts.map +1 -1
- package/dist-esm/socket/util.js +15 -1
- package/dist-esm/socket/util.js.map +1 -1
- package/dist-esm/socket/util.test.js +12 -2
- package/dist-esm/socket/util.test.js.map +1 -1
- package/dist-esm/types/ai/index.d.ts +3 -0
- package/dist-esm/types/ai/index.d.ts.map +1 -1
- package/dist-esm/types/ai/index.js +3 -0
- package/dist-esm/types/ai/index.js.map +1 -1
- package/dist-esm/types/application/index.d.ts +12 -0
- package/dist-esm/types/application/index.d.ts.map +1 -1
- package/dist-esm/types/application/index.js.map +1 -1
- package/dist-esm/types/codeSecurity/index.d.ts +55 -1
- package/dist-esm/types/codeSecurity/index.d.ts.map +1 -1
- package/dist-esm/types/codeSecurity/index.js +17 -1
- package/dist-esm/types/codeSecurity/index.js.map +1 -1
- package/dist-esm/types/codeSecurity/index.test.d.ts +2 -0
- package/dist-esm/types/codeSecurity/index.test.d.ts.map +1 -0
- package/dist-esm/types/codeSecurity/index.test.js +17 -0
- package/dist-esm/types/codeSecurity/index.test.js.map +1 -0
- package/dist-esm/types/datasource/auth.d.ts +12 -1
- package/dist-esm/types/datasource/auth.d.ts.map +1 -1
- package/dist-esm/types/datasource/auth.js +19 -2
- package/dist-esm/types/datasource/auth.js.map +1 -1
- package/dist-esm/types/datasource/auth.test.js +3 -1
- package/dist-esm/types/datasource/auth.test.js.map +1 -1
- package/dist-esm/types/group/index.d.ts +1 -3
- package/dist-esm/types/group/index.d.ts.map +1 -1
- package/dist-esm/types/group/index.js +1 -3
- package/dist-esm/types/group/index.js.map +1 -1
- package/dist-esm/types/integration/index.d.ts +11 -1
- package/dist-esm/types/integration/index.d.ts.map +1 -1
- package/dist-esm/types/integration/index.js +11 -1
- package/dist-esm/types/integration/index.js.map +1 -1
- package/dist-esm/types/plugin/form.d.ts +32 -0
- package/dist-esm/types/plugin/form.d.ts.map +1 -1
- package/dist-esm/types/plugin/form.js +68 -0
- package/dist-esm/types/plugin/form.js.map +1 -1
- package/dist-esm/types/plugin/form.test.js +129 -1
- package/dist-esm/types/plugin/form.test.js.map +1 -1
- package/dist-esm/types/profile/index.d.ts +16 -1
- package/dist-esm/types/profile/index.d.ts.map +1 -1
- package/dist-esm/types/profile/index.js +7 -1
- package/dist-esm/types/profile/index.js.map +1 -1
- package/dist-esm/types/rbac/index.d.ts +2 -0
- package/dist-esm/types/rbac/index.d.ts.map +1 -1
- package/dist-esm/types/rbac/index.js +2 -0
- package/dist-esm/types/rbac/index.js.map +1 -1
- package/dist-esm/types/response/GetRecentApplicationsResponseBody.d.ts +4 -0
- package/dist-esm/types/response/GetRecentApplicationsResponseBody.d.ts.map +1 -0
- package/dist-esm/types/response/GetRecentApplicationsResponseBody.js +2 -0
- package/dist-esm/types/response/GetRecentApplicationsResponseBody.js.map +1 -0
- package/dist-esm/types/response/index.d.ts +1 -0
- package/dist-esm/types/response/index.d.ts.map +1 -1
- package/dist-esm/types/response/index.js +1 -0
- package/dist-esm/types/response/index.js.map +1 -1
- package/package.json +1 -1
- package/src/database-lifecycle/appDatabaseCompatibility.test.ts +55 -0
- package/src/database-lifecycle/appDatabaseCompatibility.ts +62 -0
- package/src/database-lifecycle/destructiveMigrations.test.ts +108 -1
- package/src/database-lifecycle/destructiveMigrations.ts +129 -0
- package/src/database-lifecycle/index.ts +173 -0
- package/src/database-lifecycle/providerDiagnostics.test.ts +134 -0
- package/src/environment-compatibility/appEnvironmentCompatibility.test.ts +326 -0
- package/src/environment-compatibility/appEnvironmentCompatibility.ts +173 -0
- package/src/errors/http.test.ts +32 -0
- package/src/errors/http.ts +24 -6
- package/src/index.ts +2 -0
- package/src/plugins/templates/restApiIntegration.ts +2 -0
- package/src/plugins/templates/shared/auth.test.ts +162 -0
- package/src/plugins/templates/shared/auth.ts +135 -18
- package/src/plugins/templates/shared/restapiintegration.ts +3 -1
- package/src/socket/dist-entry.test.ts +29 -0
- package/src/socket/heartbeat.test.ts +538 -0
- package/src/socket/heartbeat.ts +469 -0
- package/src/socket/index.ts +1 -0
- package/src/socket/util.test.ts +14 -2
- package/src/socket/util.ts +15 -1
- package/src/types/ai/index.ts +3 -0
- package/src/types/application/index.ts +14 -0
- package/src/types/codeSecurity/index.test.ts +21 -0
- package/src/types/codeSecurity/index.ts +83 -1
- package/src/types/datasource/auth.test.ts +3 -1
- package/src/types/datasource/auth.ts +24 -3
- package/src/types/group/index.ts +1 -3
- package/src/types/integration/index.ts +14 -0
- package/src/types/plugin/form.test.ts +174 -1
- package/src/types/plugin/form.ts +100 -2
- package/src/types/profile/index.ts +19 -1
- package/src/types/rbac/index.ts +2 -0
- package/src/types/response/GetRecentApplicationsResponseBody.ts +4 -0
- package/src/types/response/index.ts +1 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A paced liveness beat over a request channel.
|
|
3
|
+
*
|
|
4
|
+
* Every long-lived socket in this repo eventually needs the same thing: proof
|
|
5
|
+
* that the peer is still answering, cheaply, for as long as the connection is
|
|
6
|
+
* held. The naive version - an interval that fires a request and counts the
|
|
7
|
+
* failures - is wrong in three ways that only show up under load, and each one
|
|
8
|
+
* was found the hard way:
|
|
9
|
+
*
|
|
10
|
+
* 1. A request inherits the channel's own budget, which is the whole
|
|
11
|
+
* caller-facing operation (minutes). A beat that borrows it cannot report a
|
|
12
|
+
* miss inside any window a caller waits in, so it holds its own deadline.
|
|
13
|
+
* 2. Without an in-flight guard, a peer that stopped answering collects a new
|
|
14
|
+
* pending request every interval, none of which can ever be reconciled.
|
|
15
|
+
* The answer paces the beats, not the interval.
|
|
16
|
+
* 3. A beat's response shares the socket with bulk pushes, so a large transfer
|
|
17
|
+
* head-of-line blocks the answer past any sane deadline while the
|
|
18
|
+
* connection is perfectly healthy. Acting on that kills a busy socket and,
|
|
19
|
+
* where the caller reconnects, re-sends the same payload and does it again.
|
|
20
|
+
*
|
|
21
|
+
* The third is the subtle one, and it takes two guards, because neither is
|
|
22
|
+
* sufficient alone. Inbound traffic during the beat's own wait proves the
|
|
23
|
+
* connection is alive - but a bulk RPC response is a single websocket message,
|
|
24
|
+
* and no receive event fires until its last byte lands, so that cannot be the
|
|
25
|
+
* only guard. A single missed beat therefore also gets grace: a transfer merely
|
|
26
|
+
* has to finish inside one extra interval for the next answer to get through.
|
|
27
|
+
*
|
|
28
|
+
* Both guards answer to one bound. Inbound data proves the receive path, not
|
|
29
|
+
* that the request channel answers, so no amount of either excuse may let a
|
|
30
|
+
* wedged channel hide forever: once
|
|
31
|
+
* {@link SocketHeartbeatOptions.maxConsecutiveUnanswered} beats in a row have
|
|
32
|
+
* gone unanswered, the next silence is believed whatever the reason, and the
|
|
33
|
+
* grace is bypassed with it.
|
|
34
|
+
*
|
|
35
|
+
* Extracted from the browser editor's dev-server heartbeat, which learned all
|
|
36
|
+
* of the above the hard way and is the first thing to beat with it.
|
|
37
|
+
*/
|
|
38
|
+
/** Why a beat settled the way it did. */
|
|
39
|
+
export type BeatOutcome =
|
|
40
|
+
/** Answered. The peer is there and the channel works. */
|
|
41
|
+
'success'
|
|
42
|
+
/** A definite answer that was an error. It arrived, so the channel works. */
|
|
43
|
+
| 'refused'
|
|
44
|
+
/** Unanswered, but the socket was provably delivering data. Excused. */
|
|
45
|
+
| 'lapse'
|
|
46
|
+
/** Unanswered and believed, but held back so a slow transfer can catch up. */
|
|
47
|
+
| 'grace'
|
|
48
|
+
/** Unanswered and believed. The caller should act on this one. */
|
|
49
|
+
| 'miss';
|
|
50
|
+
/**
|
|
51
|
+
* Running account of the beat, readable at any time and reported when a socket
|
|
52
|
+
* dies.
|
|
53
|
+
*
|
|
54
|
+
* A socket can die before any beat settles, so `sent` counts beats issued
|
|
55
|
+
* rather than outcomes observed: unanswered beats are the strongest evidence
|
|
56
|
+
* there is that the peer was gone, and an account built from settled outcomes
|
|
57
|
+
* alone would report the most telling deaths as having nothing to say.
|
|
58
|
+
*/
|
|
59
|
+
export type HeartbeatAccount = {
|
|
60
|
+
/**
|
|
61
|
+
* Consecutive believed silences. Only an unanswered beat that nothing excused
|
|
62
|
+
* counts here, so this is what decides when the beat is lost - and why a
|
|
63
|
+
* refusal, which is an answer, can never close a connection on its own.
|
|
64
|
+
*/
|
|
65
|
+
readonly failures: number;
|
|
66
|
+
/**
|
|
67
|
+
* Unanswered beats excused because the socket was provably delivering data at
|
|
68
|
+
* the time. Purely diagnostic: this is the difference between a dead
|
|
69
|
+
* connection and a live one whose request channel went quiet, which no close
|
|
70
|
+
* code can tell apart.
|
|
71
|
+
*
|
|
72
|
+
* Cleared by an answered beat or a new connection, not by a refusal - see
|
|
73
|
+
* {@link HeartbeatAccount.unanswered}.
|
|
74
|
+
*/
|
|
75
|
+
readonly lapses: number;
|
|
76
|
+
readonly lastError?: string;
|
|
77
|
+
readonly lastReceivedAt?: number;
|
|
78
|
+
readonly lastSuccessAt?: number;
|
|
79
|
+
/**
|
|
80
|
+
* When the beat still waiting for its answer went out, or absent if none is.
|
|
81
|
+
*
|
|
82
|
+
* `sent` says a beat was issued and {@link HeartbeatAccount.lastSuccessAt}
|
|
83
|
+
* says one was answered; neither says whether the last one came back. Without
|
|
84
|
+
* this, a socket that answered a beat and then went silent is indistinguishable
|
|
85
|
+
* from one answering right up to the close.
|
|
86
|
+
*
|
|
87
|
+
* Deliberately survives {@link SocketHeartbeat.stop}, which is the first thing
|
|
88
|
+
* a caller does with a dead connection: the account is read after the socket
|
|
89
|
+
* is gone, no settle is coming for a retired beat, and clearing the stamp on
|
|
90
|
+
* the way out would erase the only record that a beat was outstanding.
|
|
91
|
+
*/
|
|
92
|
+
readonly pendingSince?: number;
|
|
93
|
+
/**
|
|
94
|
+
* Consecutive beats answered with an error. Kept apart from
|
|
95
|
+
* {@link HeartbeatAccount.failures} because a refusal proves the channel
|
|
96
|
+
* works: it belongs in the report, never in the decision to give up on a
|
|
97
|
+
* connection.
|
|
98
|
+
*/
|
|
99
|
+
readonly refusals: number;
|
|
100
|
+
/** Every beat issued, including one still waiting. */
|
|
101
|
+
readonly sent: number;
|
|
102
|
+
/**
|
|
103
|
+
* Beats that got no answer, excused or not. This is what the deferral bound is
|
|
104
|
+
* measured against, so it is also what says how long a silence had been
|
|
105
|
+
* tolerated when the beat was finally believed.
|
|
106
|
+
*
|
|
107
|
+
* Saturates at {@link SocketHeartbeatOptions.maxConsecutiveUnanswered}, so
|
|
108
|
+
* the run it reports can never exceed the bound it is measured against -
|
|
109
|
+
* beats keep going out after the alarm is raised, and an unbounded count
|
|
110
|
+
* would describe a silence longer than the one that was acted on.
|
|
111
|
+
*
|
|
112
|
+
* Cleared only by a definite success, proof of life, or a new connection. A
|
|
113
|
+
* refusal leaves it standing: it answers one beat, and were it to clear the
|
|
114
|
+
* run, a channel alternating between timing out and refusing could defer the
|
|
115
|
+
* bound forever while inbound traffic excused every silence.
|
|
116
|
+
*/
|
|
117
|
+
readonly unanswered: number;
|
|
118
|
+
};
|
|
119
|
+
export interface SocketHeartbeatOptions {
|
|
120
|
+
/** Issues one beat. The cheapest authorized round trip the channel offers. */
|
|
121
|
+
beat: () => Promise<unknown>;
|
|
122
|
+
/** Gap between beats. A beat still in flight when one is due skips it. */
|
|
123
|
+
intervalMs: number;
|
|
124
|
+
/** How long one beat waits for its answer before it counts as unanswered. */
|
|
125
|
+
timeoutMs: number;
|
|
126
|
+
/**
|
|
127
|
+
* Consecutive genuine misses before the beat is declared lost. Defaults to
|
|
128
|
+
* {@link DEFAULT_MAX_CONSECUTIVE_MISSES}.
|
|
129
|
+
*/
|
|
130
|
+
maxConsecutiveMisses?: number;
|
|
131
|
+
/**
|
|
132
|
+
* Consecutive unanswered beats to tolerate before neither proof of life nor
|
|
133
|
+
* grace may excuse another. Defaults to
|
|
134
|
+
* {@link DEFAULT_MAX_CONSECUTIVE_UNANSWERED}.
|
|
135
|
+
*/
|
|
136
|
+
maxConsecutiveUnanswered?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Renders a rejection for the account. Channels that reject with plain
|
|
139
|
+
* objects rather than `Error`s need this, or the cause reads
|
|
140
|
+
* `[object Object]`.
|
|
141
|
+
*/
|
|
142
|
+
describeError?: (error: unknown) => string;
|
|
143
|
+
/**
|
|
144
|
+
* Whether a rejection means the beat went unanswered, as opposed to being
|
|
145
|
+
* refused. Only an unanswered beat can lapse or be believed: a refusal is a
|
|
146
|
+
* definite answer about the peer, and treating it as silence would hide the
|
|
147
|
+
* very thing it reports. Defaults to recognizing this heartbeat's own
|
|
148
|
+
* deadline.
|
|
149
|
+
*/
|
|
150
|
+
isUnanswered?: (error: unknown) => boolean;
|
|
151
|
+
/**
|
|
152
|
+
* The beat is lost: enough consecutive beats went unanswered that the peer
|
|
153
|
+
* cannot be assumed to be there.
|
|
154
|
+
*
|
|
155
|
+
* Edge-triggered. It fires on the beat that crosses the threshold and stays
|
|
156
|
+
* quiet until proof of life re-arms it, because the counters it reports are
|
|
157
|
+
* an account read at socket death - resetting them to re-arm the alarm would
|
|
158
|
+
* erase the evidence the death report is built from.
|
|
159
|
+
*
|
|
160
|
+
* Throwing from here says the loss could not be acted on - the socket lookup
|
|
161
|
+
* failed, the close was rejected - and re-arms the alarm, because the
|
|
162
|
+
* connection this was meant to tear down is still there. A caller that
|
|
163
|
+
* swallows its own failure instead gets one alarm for a teardown that never
|
|
164
|
+
* happened.
|
|
165
|
+
*
|
|
166
|
+
* A refusal never reaches this. The peer answered, so whatever is wrong is
|
|
167
|
+
* not the connection, and tearing one down over an application-level refusal
|
|
168
|
+
* would replace a working channel with an outage.
|
|
169
|
+
*/
|
|
170
|
+
onLost: (account: HeartbeatAccount) => void;
|
|
171
|
+
/** Every settled beat, for telemetry. Receives the account at settle time. */
|
|
172
|
+
onOutcome?: (outcome: BeatOutcome, durationMs: number, account: HeartbeatAccount) => void;
|
|
173
|
+
/**
|
|
174
|
+
* Whether the channel can carry a beat at all right now - an authorized
|
|
175
|
+
* credential, an open socket. A gate that is closed skips the beat entirely
|
|
176
|
+
* rather than counting it, because a channel with nothing to protect yet has
|
|
177
|
+
* neither missed nor answered anything.
|
|
178
|
+
*/
|
|
179
|
+
shouldBeat?: () => boolean;
|
|
180
|
+
}
|
|
181
|
+
export declare const DEFAULT_MAX_CONSECUTIVE_MISSES = 2;
|
|
182
|
+
export declare const DEFAULT_MAX_CONSECUTIVE_UNANSWERED = 8;
|
|
183
|
+
export declare class SocketHeartbeat {
|
|
184
|
+
private readonly options;
|
|
185
|
+
private inFlight;
|
|
186
|
+
private intervalTimer;
|
|
187
|
+
/**
|
|
188
|
+
* Identifies the current chain of scheduled beats.
|
|
189
|
+
*
|
|
190
|
+
* Separate from {@link epoch} on purpose: a reset means a new connection, not
|
|
191
|
+
* a new cadence, so retiring an in-flight beat must not also unhook the
|
|
192
|
+
* interval that would have issued the next one.
|
|
193
|
+
*/
|
|
194
|
+
private intervalToken;
|
|
195
|
+
/** Whether the alarm has already been raised for the current run of misses. */
|
|
196
|
+
private lost;
|
|
197
|
+
/**
|
|
198
|
+
* Bumped by every start, stop and reset.
|
|
199
|
+
*
|
|
200
|
+
* A deadline armed for a previous connection fires after a reconnect - an
|
|
201
|
+
* intentional close does not reject the request it was waiting on - and
|
|
202
|
+
* letting it tick the new run's counters would condemn a healthy socket on
|
|
203
|
+
* the strength of a dead one's silence.
|
|
204
|
+
*/
|
|
205
|
+
private epoch;
|
|
206
|
+
private state;
|
|
207
|
+
constructor(options: SocketHeartbeatOptions);
|
|
208
|
+
/** A snapshot, so a caller holding it cannot be surprised by a later beat. */
|
|
209
|
+
get account(): HeartbeatAccount;
|
|
210
|
+
/**
|
|
211
|
+
* Begins beating on a fresh account, replacing any cadence already running.
|
|
212
|
+
*
|
|
213
|
+
* Returns the way to stop *this* cadence. The disposer is inert once a later
|
|
214
|
+
* start has replaced it, which matters whenever one heartbeat is shared
|
|
215
|
+
* across successive connections: a superseded connection's teardown arrives
|
|
216
|
+
* late, and an unguarded stop would silently take the live connection's beat
|
|
217
|
+
* down with it.
|
|
218
|
+
*/
|
|
219
|
+
start(): () => void;
|
|
220
|
+
stop(): void;
|
|
221
|
+
/**
|
|
222
|
+
* Starts the account over for a new connection, without touching the
|
|
223
|
+
* interval.
|
|
224
|
+
*
|
|
225
|
+
* Separate from {@link start} because a caller may learn it has a new
|
|
226
|
+
* connection before it is ready to beat on one, and the old socket's armed
|
|
227
|
+
* deadline has to stop counting the moment that happens rather than when the
|
|
228
|
+
* first new beat goes out.
|
|
229
|
+
*/
|
|
230
|
+
reset(): void;
|
|
231
|
+
/**
|
|
232
|
+
* Beats once, now, outside the interval.
|
|
233
|
+
*
|
|
234
|
+
* For callers with a reason to check immediately - a user who just came back
|
|
235
|
+
* to the keyboard - rather than waiting out a cadence sized for idle
|
|
236
|
+
* connections. Safe alongside a running interval: the in-flight guard means
|
|
237
|
+
* one of the two simply skips.
|
|
238
|
+
*/
|
|
239
|
+
beatNow(): Promise<void>;
|
|
240
|
+
/**
|
|
241
|
+
* Stamps inbound traffic. Call this for every message the socket delivers:
|
|
242
|
+
* it is what lets a beat starved behind a bulk transfer be told apart from
|
|
243
|
+
* one starved by a peer that is gone.
|
|
244
|
+
*/
|
|
245
|
+
recordReceive(): void;
|
|
246
|
+
/** Retires whatever is in flight, so its settle cannot touch the next run. */
|
|
247
|
+
private invalidate;
|
|
248
|
+
private schedule;
|
|
249
|
+
private fire;
|
|
250
|
+
private settle;
|
|
251
|
+
private recordAnswer;
|
|
252
|
+
private recordRejection;
|
|
253
|
+
/** Traffic that arrived while this beat was waiting, not merely at some point. */
|
|
254
|
+
private receivedSince;
|
|
255
|
+
private get maxConsecutiveMisses();
|
|
256
|
+
private get maxConsecutiveUnanswered();
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=heartbeat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.d.ts","sourceRoot":"","sources":["../../src/socket/heartbeat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A paced liveness beat over a request channel.
|
|
4
|
+
*
|
|
5
|
+
* Every long-lived socket in this repo eventually needs the same thing: proof
|
|
6
|
+
* that the peer is still answering, cheaply, for as long as the connection is
|
|
7
|
+
* held. The naive version - an interval that fires a request and counts the
|
|
8
|
+
* failures - is wrong in three ways that only show up under load, and each one
|
|
9
|
+
* was found the hard way:
|
|
10
|
+
*
|
|
11
|
+
* 1. A request inherits the channel's own budget, which is the whole
|
|
12
|
+
* caller-facing operation (minutes). A beat that borrows it cannot report a
|
|
13
|
+
* miss inside any window a caller waits in, so it holds its own deadline.
|
|
14
|
+
* 2. Without an in-flight guard, a peer that stopped answering collects a new
|
|
15
|
+
* pending request every interval, none of which can ever be reconciled.
|
|
16
|
+
* The answer paces the beats, not the interval.
|
|
17
|
+
* 3. A beat's response shares the socket with bulk pushes, so a large transfer
|
|
18
|
+
* head-of-line blocks the answer past any sane deadline while the
|
|
19
|
+
* connection is perfectly healthy. Acting on that kills a busy socket and,
|
|
20
|
+
* where the caller reconnects, re-sends the same payload and does it again.
|
|
21
|
+
*
|
|
22
|
+
* The third is the subtle one, and it takes two guards, because neither is
|
|
23
|
+
* sufficient alone. Inbound traffic during the beat's own wait proves the
|
|
24
|
+
* connection is alive - but a bulk RPC response is a single websocket message,
|
|
25
|
+
* and no receive event fires until its last byte lands, so that cannot be the
|
|
26
|
+
* only guard. A single missed beat therefore also gets grace: a transfer merely
|
|
27
|
+
* has to finish inside one extra interval for the next answer to get through.
|
|
28
|
+
*
|
|
29
|
+
* Both guards answer to one bound. Inbound data proves the receive path, not
|
|
30
|
+
* that the request channel answers, so no amount of either excuse may let a
|
|
31
|
+
* wedged channel hide forever: once
|
|
32
|
+
* {@link SocketHeartbeatOptions.maxConsecutiveUnanswered} beats in a row have
|
|
33
|
+
* gone unanswered, the next silence is believed whatever the reason, and the
|
|
34
|
+
* grace is bypassed with it.
|
|
35
|
+
*
|
|
36
|
+
* Extracted from the browser editor's dev-server heartbeat, which learned all
|
|
37
|
+
* of the above the hard way and is the first thing to beat with it.
|
|
38
|
+
*/
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.SocketHeartbeat = exports.DEFAULT_MAX_CONSECUTIVE_UNANSWERED = exports.DEFAULT_MAX_CONSECUTIVE_MISSES = void 0;
|
|
41
|
+
exports.DEFAULT_MAX_CONSECUTIVE_MISSES = 2;
|
|
42
|
+
exports.DEFAULT_MAX_CONSECUTIVE_UNANSWERED = 8;
|
|
43
|
+
/** Keeps a Node timer from holding the process open; a no-op in a browser. */
|
|
44
|
+
function unref(timer) {
|
|
45
|
+
timer.unref?.();
|
|
46
|
+
}
|
|
47
|
+
/** This heartbeat's own deadline, so a rejection it raised is recognizable. */
|
|
48
|
+
class BeatTimeoutError extends Error {
|
|
49
|
+
constructor(timeoutMs) {
|
|
50
|
+
super(`beat timed out after ${timeoutMs}ms`);
|
|
51
|
+
this.name = 'BeatTimeoutError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function describeUnknownError(error) {
|
|
55
|
+
if (error instanceof Error) {
|
|
56
|
+
return error.message;
|
|
57
|
+
}
|
|
58
|
+
if (typeof error === 'object' && error !== null) {
|
|
59
|
+
const { message } = error;
|
|
60
|
+
if (typeof message === 'string' && message.length > 0) {
|
|
61
|
+
return message;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
return JSON.stringify(error);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return String(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return String(error);
|
|
71
|
+
}
|
|
72
|
+
const emptyAccount = () => ({ failures: 0, lapses: 0, refusals: 0, sent: 0, unanswered: 0 });
|
|
73
|
+
class SocketHeartbeat {
|
|
74
|
+
options;
|
|
75
|
+
inFlight = false;
|
|
76
|
+
intervalTimer;
|
|
77
|
+
/**
|
|
78
|
+
* Identifies the current chain of scheduled beats.
|
|
79
|
+
*
|
|
80
|
+
* Separate from {@link epoch} on purpose: a reset means a new connection, not
|
|
81
|
+
* a new cadence, so retiring an in-flight beat must not also unhook the
|
|
82
|
+
* interval that would have issued the next one.
|
|
83
|
+
*/
|
|
84
|
+
intervalToken = 0;
|
|
85
|
+
/** Whether the alarm has already been raised for the current run of misses. */
|
|
86
|
+
lost = false;
|
|
87
|
+
/**
|
|
88
|
+
* Bumped by every start, stop and reset.
|
|
89
|
+
*
|
|
90
|
+
* A deadline armed for a previous connection fires after a reconnect - an
|
|
91
|
+
* intentional close does not reject the request it was waiting on - and
|
|
92
|
+
* letting it tick the new run's counters would condemn a healthy socket on
|
|
93
|
+
* the strength of a dead one's silence.
|
|
94
|
+
*/
|
|
95
|
+
epoch = 0;
|
|
96
|
+
state = emptyAccount();
|
|
97
|
+
constructor(options) {
|
|
98
|
+
this.options = options;
|
|
99
|
+
}
|
|
100
|
+
/** A snapshot, so a caller holding it cannot be surprised by a later beat. */
|
|
101
|
+
get account() {
|
|
102
|
+
return { ...this.state };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Begins beating on a fresh account, replacing any cadence already running.
|
|
106
|
+
*
|
|
107
|
+
* Returns the way to stop *this* cadence. The disposer is inert once a later
|
|
108
|
+
* start has replaced it, which matters whenever one heartbeat is shared
|
|
109
|
+
* across successive connections: a superseded connection's teardown arrives
|
|
110
|
+
* late, and an unguarded stop would silently take the live connection's beat
|
|
111
|
+
* down with it.
|
|
112
|
+
*/
|
|
113
|
+
start() {
|
|
114
|
+
this.stop();
|
|
115
|
+
this.reset();
|
|
116
|
+
this.schedule();
|
|
117
|
+
const token = this.intervalToken;
|
|
118
|
+
return () => {
|
|
119
|
+
if (token === this.intervalToken) {
|
|
120
|
+
this.stop();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
stop() {
|
|
125
|
+
this.invalidate();
|
|
126
|
+
this.intervalToken += 1;
|
|
127
|
+
if (this.intervalTimer) {
|
|
128
|
+
clearTimeout(this.intervalTimer);
|
|
129
|
+
this.intervalTimer = undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Starts the account over for a new connection, without touching the
|
|
134
|
+
* interval.
|
|
135
|
+
*
|
|
136
|
+
* Separate from {@link start} because a caller may learn it has a new
|
|
137
|
+
* connection before it is ready to beat on one, and the old socket's armed
|
|
138
|
+
* deadline has to stop counting the moment that happens rather than when the
|
|
139
|
+
* first new beat goes out.
|
|
140
|
+
*/
|
|
141
|
+
reset() {
|
|
142
|
+
this.invalidate();
|
|
143
|
+
this.state = emptyAccount();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Beats once, now, outside the interval.
|
|
147
|
+
*
|
|
148
|
+
* For callers with a reason to check immediately - a user who just came back
|
|
149
|
+
* to the keyboard - rather than waiting out a cadence sized for idle
|
|
150
|
+
* connections. Safe alongside a running interval: the in-flight guard means
|
|
151
|
+
* one of the two simply skips.
|
|
152
|
+
*/
|
|
153
|
+
async beatNow() {
|
|
154
|
+
await this.fire(this.epoch);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Stamps inbound traffic. Call this for every message the socket delivers:
|
|
158
|
+
* it is what lets a beat starved behind a bulk transfer be told apart from
|
|
159
|
+
* one starved by a peer that is gone.
|
|
160
|
+
*/
|
|
161
|
+
recordReceive() {
|
|
162
|
+
this.state.lastReceivedAt = Date.now();
|
|
163
|
+
}
|
|
164
|
+
/** Retires whatever is in flight, so its settle cannot touch the next run. */
|
|
165
|
+
invalidate() {
|
|
166
|
+
this.epoch += 1;
|
|
167
|
+
this.inFlight = false;
|
|
168
|
+
this.lost = false;
|
|
169
|
+
}
|
|
170
|
+
schedule() {
|
|
171
|
+
const token = this.intervalToken;
|
|
172
|
+
this.intervalTimer = setTimeout(() => {
|
|
173
|
+
if (token !== this.intervalToken) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// Rescheduled before the beat is issued rather than after it settles, so
|
|
177
|
+
// the interval stays the gap between beats instead of drifting by however
|
|
178
|
+
// long each answer took.
|
|
179
|
+
this.schedule();
|
|
180
|
+
void this.fire(this.epoch);
|
|
181
|
+
}, this.options.intervalMs);
|
|
182
|
+
unref(this.intervalTimer);
|
|
183
|
+
}
|
|
184
|
+
async fire(armedEpoch) {
|
|
185
|
+
if (this.inFlight || this.options.shouldBeat?.() === false) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.inFlight = true;
|
|
189
|
+
this.state.sent += 1;
|
|
190
|
+
const sentAt = Date.now();
|
|
191
|
+
this.state.pendingSince = sentAt;
|
|
192
|
+
let deadline;
|
|
193
|
+
try {
|
|
194
|
+
await Promise.race([
|
|
195
|
+
this.options.beat(),
|
|
196
|
+
new Promise((_, reject) => {
|
|
197
|
+
deadline = setTimeout(() => reject(new BeatTimeoutError(this.options.timeoutMs)), this.options.timeoutMs);
|
|
198
|
+
unref(deadline);
|
|
199
|
+
})
|
|
200
|
+
]);
|
|
201
|
+
this.settle(armedEpoch, sentAt, undefined);
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
this.settle(armedEpoch, sentAt, error ?? new Error('beat failed'));
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
if (deadline !== undefined) {
|
|
208
|
+
clearTimeout(deadline);
|
|
209
|
+
}
|
|
210
|
+
// Only this epoch's beat owns the guard. Releasing it after a reset would
|
|
211
|
+
// let a stale settle admit a second concurrent beat.
|
|
212
|
+
if (armedEpoch === this.epoch) {
|
|
213
|
+
this.inFlight = false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
settle(armedEpoch, sentAt, error) {
|
|
218
|
+
if (armedEpoch !== this.epoch) {
|
|
219
|
+
// A beat belonging to a connection that is already gone. It says nothing
|
|
220
|
+
// about the current one, so it is neither counted nor reported.
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const durationMs = Date.now() - sentAt;
|
|
224
|
+
// Nothing is outstanding any more: this beat is the one that was, and a
|
|
225
|
+
// stale-epoch beat returned above without touching the current run's stamp.
|
|
226
|
+
delete this.state.pendingSince;
|
|
227
|
+
const outcome = error === undefined ? this.recordAnswer() : this.recordRejection(error, sentAt);
|
|
228
|
+
const account = this.account;
|
|
229
|
+
this.options.onOutcome?.(outcome, durationMs, account);
|
|
230
|
+
if (outcome === 'miss' && !this.lost) {
|
|
231
|
+
this.lost = true;
|
|
232
|
+
try {
|
|
233
|
+
this.options.onLost(account);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// The caller could not act on the loss, so the connection it meant to
|
|
237
|
+
// tear down is still there and still not answering. Re-arm, or the one
|
|
238
|
+
// alarm this run gets has been spent on a teardown that never happened.
|
|
239
|
+
// Swallowed rather than rethrown: `settle` already runs inside the
|
|
240
|
+
// beat's own catch, and throwing from here escapes `fire` as an
|
|
241
|
+
// unhandled rejection.
|
|
242
|
+
this.lost = false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
recordAnswer() {
|
|
247
|
+
this.state.failures = 0;
|
|
248
|
+
this.state.lapses = 0;
|
|
249
|
+
this.state.refusals = 0;
|
|
250
|
+
this.state.unanswered = 0;
|
|
251
|
+
this.state.lastSuccessAt = Date.now();
|
|
252
|
+
this.lost = false;
|
|
253
|
+
delete this.state.lastError;
|
|
254
|
+
return 'success';
|
|
255
|
+
}
|
|
256
|
+
recordRejection(error, sentAt) {
|
|
257
|
+
const describe = this.options.describeError ?? describeUnknownError;
|
|
258
|
+
if (!(this.options.isUnanswered ?? ((cause) => cause instanceof BeatTimeoutError))(error)) {
|
|
259
|
+
// A definite answer, and recorded as one: a peer that answers is
|
|
260
|
+
// reachable, so a refusal must never close a connection over what is an
|
|
261
|
+
// application-level problem.
|
|
262
|
+
//
|
|
263
|
+
// It answers this beat and no others, so every count of what went
|
|
264
|
+
// missing is left exactly as it stands. Clearing `failures` would spend
|
|
265
|
+
// a refusal on the grace meant for a starved pong; clearing `unanswered`
|
|
266
|
+
// would hand a channel that alternates between timing out and refusing
|
|
267
|
+
// an unlimited deferral, because where inbound traffic excuses every
|
|
268
|
+
// silence that counter is the only thing left that can end one.
|
|
269
|
+
this.state.refusals += 1;
|
|
270
|
+
this.state.lastError = describe(error);
|
|
271
|
+
return 'refused';
|
|
272
|
+
}
|
|
273
|
+
// Saturating rather than growing: this is what the caller reports as the
|
|
274
|
+
// length of the silence that killed the connection, and beats keep going
|
|
275
|
+
// out after the alarm is raised - the caller still has a socket to tear
|
|
276
|
+
// down - so an unbounded count would report a run longer than the bound it
|
|
277
|
+
// is measured against.
|
|
278
|
+
this.state.unanswered = Math.min(this.state.unanswered + 1, this.maxConsecutiveUnanswered);
|
|
279
|
+
// The cause of every unanswered beat, excused or not. A lapse that leaves
|
|
280
|
+
// this stale would have the log for an excused timeout name whatever failed
|
|
281
|
+
// before it.
|
|
282
|
+
this.state.lastError = describe(error);
|
|
283
|
+
const excusable = this.state.unanswered < this.maxConsecutiveUnanswered;
|
|
284
|
+
if (excusable && this.receivedSince(sentAt)) {
|
|
285
|
+
this.state.lapses += 1;
|
|
286
|
+
// Traffic during the beat's own wait proves the connection is alive, which
|
|
287
|
+
// clears an earlier suspicion as surely as an answer would.
|
|
288
|
+
this.state.failures = 0;
|
|
289
|
+
this.lost = false;
|
|
290
|
+
return 'lapse';
|
|
291
|
+
}
|
|
292
|
+
this.state.failures += 1;
|
|
293
|
+
return excusable && this.state.failures < this.maxConsecutiveMisses ? 'grace' : 'miss';
|
|
294
|
+
}
|
|
295
|
+
/** Traffic that arrived while this beat was waiting, not merely at some point. */
|
|
296
|
+
receivedSince(sentAt) {
|
|
297
|
+
return this.state.lastReceivedAt !== undefined && this.state.lastReceivedAt >= sentAt;
|
|
298
|
+
}
|
|
299
|
+
get maxConsecutiveMisses() {
|
|
300
|
+
return this.options.maxConsecutiveMisses ?? exports.DEFAULT_MAX_CONSECUTIVE_MISSES;
|
|
301
|
+
}
|
|
302
|
+
get maxConsecutiveUnanswered() {
|
|
303
|
+
return this.options.maxConsecutiveUnanswered ?? exports.DEFAULT_MAX_CONSECUTIVE_UNANSWERED;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
exports.SocketHeartbeat = SocketHeartbeat;
|
|
307
|
+
//# sourceMappingURL=heartbeat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../../src/socket/heartbeat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.test.d.ts","sourceRoot":"","sources":["../../src/socket/heartbeat.test.ts"],"names":[],"mappings":""}
|