@hydranium/protocol 1.0.0-next.117 → 1.0.0-next.123
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/lib/client/data-connection.d.ts +28 -66
- package/lib/client/data-connection.d.ts.map +1 -1
- package/lib/client/data-connection.js +41 -112
- package/lib/client/data-connection.js.map +1 -1
- package/lib/client/data-session.d.ts +168 -93
- package/lib/client/data-session.d.ts.map +1 -1
- package/lib/client/data-session.js +291 -147
- package/lib/client/data-session.js.map +1 -1
- package/lib/data/data-protocol-methods.d.ts +2 -2
- package/lib/data/data-protocol-methods.d.ts.map +1 -1
- package/lib/data/data-protocol-methods.js +4 -0
- package/lib/data/data-protocol-methods.js.map +1 -1
- package/lib/data/data-server-protocol.d.ts +66 -30
- package/lib/data/data-server-protocol.d.ts.map +1 -1
- package/lib/data/requests.d.ts +67 -11
- package/lib/data/requests.d.ts.map +1 -1
- package/lib/errors.d.ts +66 -4
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +96 -5
- package/lib/errors.js.map +1 -1
- package/lib/messages/index.d.ts +1 -0
- package/lib/messages/index.d.ts.map +1 -1
- package/lib/messages/index.js +4 -1
- package/lib/messages/index.js.map +1 -1
- package/lib/model-server.d.ts +13 -1
- package/lib/model-server.d.ts.map +1 -1
- package/lib/model-server.js.map +1 -1
- package/lib/model-service/based-on.d.ts +2 -0
- package/lib/model-service/based-on.d.ts.map +1 -1
- package/lib/model-service/based-on.js +3 -0
- package/lib/model-service/based-on.js.map +1 -1
- package/lib/model-service/index.d.ts +0 -1
- package/lib/model-service/index.d.ts.map +1 -1
- package/lib/model-service/index.js +3 -5
- package/lib/model-service/index.js.map +1 -1
- package/lib/patch-merge.d.ts +28 -27
- package/lib/patch-merge.d.ts.map +1 -1
- package/lib/patch-merge.js +66 -22
- package/lib/patch-merge.js.map +1 -1
- package/lib/transfer-document.d.ts +2 -3
- package/lib/transfer-document.d.ts.map +1 -1
- package/lib/transfer-document.js.map +1 -1
- package/package.json +1 -1
- package/src/client/data-connection.ts +69 -119
- package/src/client/data-session.ts +348 -177
- package/src/data/data-protocol-methods.ts +4 -0
- package/src/data/data-server-protocol.ts +74 -30
- package/src/data/requests.ts +71 -11
- package/src/errors.ts +101 -4
- package/src/messages/index.ts +1 -0
- package/src/model-server.ts +13 -5
- package/src/model-service/based-on.ts +3 -0
- package/src/model-service/index.ts +3 -5
- package/src/patch-merge.ts +90 -37
- package/src/transfer-document.ts +2 -3
- package/lib/model-service/args.d.ts +0 -65
- package/lib/model-service/args.d.ts.map +0 -1
- package/lib/model-service/args.js +0 -11
- package/lib/model-service/args.js.map +0 -1
- package/src/model-service/args.ts +0 -69
|
@@ -10,10 +10,10 @@ import { type DataClientProtocol, type DataServerProtocol, type DiagnosticOf, ty
|
|
|
10
10
|
import type { TransferElement } from '../transfer-element';
|
|
11
11
|
import { DataEvents } from './data-events';
|
|
12
12
|
import type { DataPort } from './data-port';
|
|
13
|
-
import { DataSession } from './data-session';
|
|
13
|
+
import { DataSession, type DataSessionFactory } from './data-session';
|
|
14
14
|
import { RpcConnection, type RpcConnectionLifecycle } from './rpc-connection';
|
|
15
15
|
/** Options for {@link DataConnection}. */
|
|
16
|
-
export interface DataConnectionOptions extends RpcConnectionLifecycle {
|
|
16
|
+
export interface DataConnectionOptions<TTransfer extends TransferElement = TransferElement, TServer extends DataServerProtocol<TTransfer, DiagnosticOf<TServer>> = DataServerProtocol<TTransfer>> extends RpcConnectionLifecycle {
|
|
17
17
|
/**
|
|
18
18
|
* Wire namespace the server is addressed under. Defaults to the
|
|
19
19
|
* framework's {@link DATA_SERVER_WIRE_PREFIX}, which is what an unmodified
|
|
@@ -22,9 +22,11 @@ export interface DataConnectionOptions extends RpcConnectionLifecycle {
|
|
|
22
22
|
* "Unhandled method" rather than failing at wire-up.
|
|
23
23
|
*/
|
|
24
24
|
readonly methodNamespace?: string;
|
|
25
|
+
/** Builds the sessions `createSession` hands out. Defaults to a plain {@link DataSession}. */
|
|
26
|
+
readonly sessionFactory?: DataSessionFactory<TTransfer, TServer>;
|
|
25
27
|
}
|
|
26
28
|
/** {@link DataConnectionOptions} for a client that does not speak {@link DataClientProtocol}. */
|
|
27
|
-
export interface DataConnectionOptionsWithMethods<TClient extends object
|
|
29
|
+
export interface DataConnectionOptionsWithMethods<TClient extends object, TTransfer extends TransferElement = TransferElement, TServer extends DataServerProtocol<TTransfer, DiagnosticOf<TServer>> = DataServerProtocol<TTransfer>> extends DataConnectionOptions<TTransfer, TServer> {
|
|
28
30
|
/**
|
|
29
31
|
* Method names of the client to bind as inbound handlers. Declare it
|
|
30
32
|
* `as const satisfies ReadonlyArray<keyof YourClient & string>` so the list
|
|
@@ -40,14 +42,14 @@ export interface DataConnectionOptionsWithMethods<TClient extends object> extend
|
|
|
40
42
|
* request/response-only client binding the default list fails at wire-up. The
|
|
41
43
|
* conditional turns that into a compile error.
|
|
42
44
|
*/
|
|
43
|
-
export type DataConnectionArgs<TTransfer extends TransferElement, TClient extends object
|
|
45
|
+
export type DataConnectionArgs<TTransfer extends TransferElement, TClient extends object, TServer extends DataServerProtocol<TTransfer, DiagnosticOf<TServer>> = DataServerProtocol<TTransfer>> = TClient extends DataClientProtocol<TTransfer> ? [options?: DataConnectionOptions<TTransfer, TServer> & Partial<DataConnectionOptionsWithMethods<TClient, TTransfer, TServer>>] : [options: DataConnectionOptionsWithMethods<TClient, TTransfer, TServer>];
|
|
44
46
|
/**
|
|
45
47
|
* A {@link RpcConnection} to the data head, carrying as many participants as
|
|
46
48
|
* the host has interested parties.
|
|
47
49
|
*
|
|
48
50
|
* Document operations live on the participants rather than here: they carry a
|
|
49
51
|
* `clientId`, which identifies a participant rather than a wire, and the server
|
|
50
|
-
* keys its
|
|
52
|
+
* keys its opens and watches per `(uri, clientId)`. Two parties sharing one
|
|
51
53
|
* identity cannot tell each other's writes from their own echoes.
|
|
52
54
|
*
|
|
53
55
|
* Generic over the transfer root so this file names no grammar. An adopter
|
|
@@ -56,74 +58,34 @@ export type DataConnectionArgs<TTransfer extends TransferElement, TClient extend
|
|
|
56
58
|
*/
|
|
57
59
|
export declare class DataConnection<TTransfer extends TransferElement, TServer extends DataServerProtocol<TTransfer, DiagnosticOf<TServer>> = DataServerProtocol<TTransfer>, TClient extends object = DataClientProtocol<TTransfer>> extends RpcConnection<TServer, TClient> {
|
|
58
60
|
protected readonly sessions: Set<DataSession<TTransfer, TServer>>;
|
|
61
|
+
protected readonly sessionFactory: DataSessionFactory<TTransfer, TServer>;
|
|
62
|
+
constructor(port: DataPort, client: TClient, ...rest: DataConnectionArgs<TTransfer, TClient, TServer>);
|
|
59
63
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* nobody whose documents the stale hold could affect.
|
|
67
|
-
*/
|
|
68
|
-
protected readonly orphanedHolds: Map<string, {
|
|
69
|
-
readonly uri: string;
|
|
70
|
-
readonly clientId: string;
|
|
71
|
-
}>;
|
|
72
|
-
/** Guards {@link releaseOrphanedHolds} against re-entering through its own proxy lookup. */
|
|
73
|
-
protected releasingOrphans: boolean;
|
|
74
|
-
constructor(port: DataPort, client: TClient, ...rest: DataConnectionArgs<TTransfer, TClient>);
|
|
75
|
-
/**
|
|
76
|
-
* Mint a participant on this connection under `clientId`.
|
|
77
|
-
*
|
|
78
|
-
* `clientId` must be distinct per participant and stable for its lifetime:
|
|
79
|
-
* it keys the server's per-`(uri, clientId)` hold and watch, and it is the
|
|
80
|
-
* echo key an inbound `onDocumentUpdated` is matched against.
|
|
64
|
+
* Start a participant on this connection, registered with the server under a
|
|
65
|
+
* fresh id, `label` plus `#` plus a random UUID, or under `clientId` when
|
|
66
|
+
* given. Synchronous: the registration is sent at once, and the session's
|
|
67
|
+
* calls wait for it. The server refuses an id live anywhere in its process,
|
|
68
|
+
* and every call of that session then rejects with a
|
|
69
|
+
* `DuplicateClientIdError` code.
|
|
81
70
|
*
|
|
82
71
|
* Throws for an id in {@link FRAMEWORK_CLIENT_IDS} — those are authors the
|
|
83
72
|
* SERVER emits rather than participants, so a session holding one would read
|
|
84
|
-
* the framework's own broadcasts as its own echoes and drop them
|
|
85
|
-
*
|
|
86
|
-
* looks like a dead connection.
|
|
87
|
-
*
|
|
88
|
-
* Throws, too, for an id a LIVE session on this connection already holds.
|
|
89
|
-
* Per-document membership is a set of client ids, so two participants
|
|
90
|
-
* sharing one collapse to a single hold and the first close releases it
|
|
91
|
-
* under the survivor, which then stops receiving updates for a document it
|
|
92
|
-
* is still showing. A constant bound once per participant KIND — one per
|
|
93
|
-
* widget class rather than per instance — satisfies the type and violates
|
|
94
|
-
* this.
|
|
95
|
-
*
|
|
96
|
-
* And throws for an id whose previous session left a hold this connection is
|
|
97
|
-
* still trying to release. That release closes `(uri, clientId)`, so an id
|
|
98
|
-
* reissued while it is outstanding can have the new participant's hold closed
|
|
99
|
-
* out from under it. The id frees itself as soon as the release lands.
|
|
100
|
-
*
|
|
101
|
-
* The checks span this connection only, so a head several connections reach
|
|
102
|
-
* can still be addressed twice under one id.
|
|
73
|
+
* the framework's own broadcasts as its own echoes and drop them — and for
|
|
74
|
+
* an id a live session on this connection already holds.
|
|
103
75
|
*/
|
|
104
|
-
createSession(clientId
|
|
76
|
+
createSession(label?: string, clientId?: string): DataSession<TTransfer, TServer>;
|
|
105
77
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*/
|
|
112
|
-
protected orphanHold(uri: string, clientId: string): void;
|
|
113
|
-
/**
|
|
114
|
-
* Try to close every hold handed over by a disposed session, keeping the ones
|
|
115
|
-
* the server still refuses.
|
|
116
|
-
*
|
|
117
|
-
* Errors are swallowed per entry: this runs off another participant's
|
|
118
|
-
* operation, which has no interest in an unrelated release and no caller to
|
|
119
|
-
* surface it to. A hold that survives every attempt is released by the
|
|
120
|
-
* server's connection-close cleanup.
|
|
78
|
+
* After the transport dropped, reconnect on the next macrotask for the
|
|
79
|
+
* sessions with documents open, so they re-watch and follow their documents
|
|
80
|
+
* again without waiting for a call of their own; a session with nothing
|
|
81
|
+
* open restores on its next call. Nothing is scheduled once this connection
|
|
82
|
+
* is disposed, which drops its generation too.
|
|
121
83
|
*/
|
|
122
|
-
protected
|
|
84
|
+
protected dropGeneration(): void;
|
|
123
85
|
/**
|
|
124
|
-
* Sessions are detached rather than disposed: the server
|
|
125
|
-
* on a connection it sees close, so
|
|
126
|
-
*
|
|
86
|
+
* Sessions are detached rather than disposed: the server ends every session
|
|
87
|
+
* on a connection it sees close, so ending each one first sends requests
|
|
88
|
+
* over a connection this call is about to dispose.
|
|
127
89
|
*/
|
|
128
90
|
dispose(): void;
|
|
129
91
|
}
|
|
@@ -145,7 +107,7 @@ export declare class DataConnection<TTransfer extends TransferElement, TServer e
|
|
|
145
107
|
export declare class DataConnectionWithEvents<TTransfer extends TransferElement, TServer extends DataServerProtocol<TTransfer, DiagnosticOf<TServer>> = DataServerProtocol<TTransfer>> extends DataConnection<TTransfer, TServer, DataEvents<TTransfer, DiagnosticOf<TServer>, ProjectOf<TServer>>> {
|
|
146
108
|
/** Server pushes, fanned out to as many local listeners as the host has. */
|
|
147
109
|
readonly events: DataEvents<TTransfer, DiagnosticOf<TServer>, ProjectOf<TServer>>;
|
|
148
|
-
constructor(port: DataPort, options?: DataConnectionOptions);
|
|
110
|
+
constructor(port: DataPort, options?: DataConnectionOptions<TTransfer, TServer>);
|
|
149
111
|
/** Disposes the fan-out it created, which no caller else holds. */
|
|
150
112
|
dispose(): void;
|
|
151
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-connection.d.ts","sourceRoot":"","sources":["../../src/client/data-connection.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAGlF,OAAO,EAGJ,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,SAAS,EAChB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"data-connection.d.ts","sourceRoot":"","sources":["../../src/client/data-connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,7 +20,7 @@ const rpc_connection_1 = require("./rpc-connection");
|
|
|
20
20
|
*
|
|
21
21
|
* Document operations live on the participants rather than here: they carry a
|
|
22
22
|
* `clientId`, which identifies a participant rather than a wire, and the server
|
|
23
|
-
* keys its
|
|
23
|
+
* keys its opens and watches per `(uri, clientId)`. Two parties sharing one
|
|
24
24
|
* identity cannot tell each other's writes from their own echoes.
|
|
25
25
|
*
|
|
26
26
|
* Generic over the transfer root so this file names no grammar. An adopter
|
|
@@ -29,18 +29,7 @@ const rpc_connection_1 = require("./rpc-connection");
|
|
|
29
29
|
*/
|
|
30
30
|
class DataConnection extends rpc_connection_1.RpcConnection {
|
|
31
31
|
sessions = new Set();
|
|
32
|
-
|
|
33
|
-
* Holds a disposed session could not release, keyed `clientId` + `uri` so a
|
|
34
|
-
* repeated handover does not queue the same close twice.
|
|
35
|
-
*
|
|
36
|
-
* Retried on this connection's own activity rather than on a timer: every
|
|
37
|
-
* session operation asks for the proxy, so a shared connection with anything
|
|
38
|
-
* else going on supplies the occasions, and one with nothing going on has
|
|
39
|
-
* nobody whose documents the stale hold could affect.
|
|
40
|
-
*/
|
|
41
|
-
orphanedHolds = new Map();
|
|
42
|
-
/** Guards {@link releaseOrphanedHolds} against re-entering through its own proxy lookup. */
|
|
43
|
-
releasingOrphans = false;
|
|
32
|
+
sessionFactory;
|
|
44
33
|
constructor(port, client, ...rest) {
|
|
45
34
|
const [options = {}] = rest;
|
|
46
35
|
super(port, client, {
|
|
@@ -51,130 +40,70 @@ class DataConnection extends rpc_connection_1.RpcConnection {
|
|
|
51
40
|
clientMethods: options.clientMethods ?? data_1.DATA_CLIENT_PROTOCOL_METHODS,
|
|
52
41
|
lifecycle: options
|
|
53
42
|
});
|
|
43
|
+
this.sessionFactory =
|
|
44
|
+
options.sessionFactory ?? ((clientId, host, label) => new data_session_1.DataSession(clientId, host, label));
|
|
54
45
|
}
|
|
55
46
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* it
|
|
60
|
-
*
|
|
47
|
+
* Start a participant on this connection, registered with the server under a
|
|
48
|
+
* fresh id, `label` plus `#` plus a random UUID, or under `clientId` when
|
|
49
|
+
* given. Synchronous: the registration is sent at once, and the session's
|
|
50
|
+
* calls wait for it. The server refuses an id live anywhere in its process,
|
|
51
|
+
* and every call of that session then rejects with a
|
|
52
|
+
* `DuplicateClientIdError` code.
|
|
61
53
|
*
|
|
62
54
|
* Throws for an id in {@link FRAMEWORK_CLIENT_IDS} — those are authors the
|
|
63
55
|
* SERVER emits rather than participants, so a session holding one would read
|
|
64
|
-
* the framework's own broadcasts as its own echoes and drop them
|
|
65
|
-
*
|
|
66
|
-
* looks like a dead connection.
|
|
67
|
-
*
|
|
68
|
-
* Throws, too, for an id a LIVE session on this connection already holds.
|
|
69
|
-
* Per-document membership is a set of client ids, so two participants
|
|
70
|
-
* sharing one collapse to a single hold and the first close releases it
|
|
71
|
-
* under the survivor, which then stops receiving updates for a document it
|
|
72
|
-
* is still showing. A constant bound once per participant KIND — one per
|
|
73
|
-
* widget class rather than per instance — satisfies the type and violates
|
|
74
|
-
* this.
|
|
75
|
-
*
|
|
76
|
-
* And throws for an id whose previous session left a hold this connection is
|
|
77
|
-
* still trying to release. That release closes `(uri, clientId)`, so an id
|
|
78
|
-
* reissued while it is outstanding can have the new participant's hold closed
|
|
79
|
-
* out from under it. The id frees itself as soon as the release lands.
|
|
80
|
-
*
|
|
81
|
-
* The checks span this connection only, so a head several connections reach
|
|
82
|
-
* can still be addressed twice under one id.
|
|
56
|
+
* the framework's own broadcasts as its own echoes and drop them — and for
|
|
57
|
+
* an id a live session on this connection already holds.
|
|
83
58
|
*/
|
|
84
|
-
createSession(clientId) {
|
|
59
|
+
createSession(label = 'session', clientId) {
|
|
85
60
|
this.assertLive();
|
|
86
|
-
|
|
87
|
-
|
|
61
|
+
const id = clientId ?? `${label}#${globalThis.crypto.randomUUID()}`;
|
|
62
|
+
if (client_ids_1.FRAMEWORK_CLIENT_IDS.includes(id)) {
|
|
63
|
+
throw new Error(`clientId '${id}' is reserved by the framework and cannot identify a participant`);
|
|
88
64
|
}
|
|
89
|
-
if ([...this.sessions].some(session => session.clientId ===
|
|
90
|
-
throw new Error(`clientId '${
|
|
65
|
+
if ([...this.sessions].some(session => session.clientId === id)) {
|
|
66
|
+
throw new Error(`clientId '${id}' already identifies a live participant on this connection`);
|
|
91
67
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// `(uri, clientId)` — which the server keys one hold per. Handing the
|
|
95
|
-
// id over now lets that close land on the NEW participant's hold
|
|
96
|
-
// instead, taking a document away from a session that opened it
|
|
97
|
-
// successfully. Nothing about that fails on its own: the document
|
|
98
|
-
// simply stops following, and the close that did it was issued for a
|
|
99
|
-
// participant already gone.
|
|
100
|
-
//
|
|
101
|
-
// Attempted first, so an id whose release has become possible is free
|
|
102
|
-
// by the next call rather than waiting for other traffic.
|
|
103
|
-
void this.releaseOrphanedHolds();
|
|
104
|
-
throw new Error(`clientId '${clientId}' has an unreleased hold on this connection and cannot identify a new participant yet`);
|
|
105
|
-
}
|
|
106
|
-
const session = new data_session_1.DataSession(clientId, {
|
|
107
|
-
connected: async () => {
|
|
108
|
-
const server = await this.connected();
|
|
109
|
-
// Unawaited: a session's own operation must not wait on, or fail
|
|
110
|
-
// for, the release of a hold another participant abandoned.
|
|
111
|
-
void this.releaseOrphanedHolds();
|
|
112
|
-
return server;
|
|
113
|
-
},
|
|
68
|
+
const session = this.sessionFactory(id, {
|
|
69
|
+
connected: () => this.connected(),
|
|
114
70
|
releaseSession: released => this.sessions.delete(released),
|
|
115
|
-
|
|
116
|
-
});
|
|
71
|
+
reportError: (error, reported) => this.reportError(error, reported)
|
|
72
|
+
}, label);
|
|
117
73
|
this.sessions.add(session);
|
|
74
|
+
// A failure reaches the session's own calls, which wait for the same
|
|
75
|
+
// registration; caught here only so it is not also reported unhandled.
|
|
76
|
+
session.connected().catch(() => undefined);
|
|
118
77
|
return session;
|
|
119
78
|
}
|
|
120
79
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*/
|
|
127
|
-
orphanHold(uri, clientId) {
|
|
128
|
-
this.orphanedHolds.set(`${clientId}\u0000${uri}`, { uri, clientId });
|
|
129
|
-
void this.releaseOrphanedHolds();
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Try to close every hold handed over by a disposed session, keeping the ones
|
|
133
|
-
* the server still refuses.
|
|
134
|
-
*
|
|
135
|
-
* Errors are swallowed per entry: this runs off another participant's
|
|
136
|
-
* operation, which has no interest in an unrelated release and no caller to
|
|
137
|
-
* surface it to. A hold that survives every attempt is released by the
|
|
138
|
-
* server's connection-close cleanup.
|
|
80
|
+
* After the transport dropped, reconnect on the next macrotask for the
|
|
81
|
+
* sessions with documents open, so they re-watch and follow their documents
|
|
82
|
+
* again without waiting for a call of their own; a session with nothing
|
|
83
|
+
* open restores on its next call. Nothing is scheduled once this connection
|
|
84
|
+
* is disposed, which drops its generation too.
|
|
139
85
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
for (const [key, hold] of [...this.orphanedHolds]) {
|
|
148
|
-
try {
|
|
149
|
-
await server.closeModelDocument({ uri: hold.uri, clientId: hold.clientId });
|
|
150
|
-
this.orphanedHolds.delete(key);
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
// Kept for the next occasion.
|
|
86
|
+
dropGeneration() {
|
|
87
|
+
const dropped = this.generation !== undefined;
|
|
88
|
+
super.dropGeneration();
|
|
89
|
+
if (dropped && !this.disposed) {
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
if (!this.disposed) {
|
|
92
|
+
this.sessions.forEach(session => session.reconnect());
|
|
154
93
|
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
// No proxy to release through; the entries wait for one.
|
|
159
|
-
}
|
|
160
|
-
finally {
|
|
161
|
-
this.releasingOrphans = false;
|
|
94
|
+
}, 0);
|
|
162
95
|
}
|
|
163
96
|
}
|
|
164
97
|
/**
|
|
165
|
-
* Sessions are detached rather than disposed: the server
|
|
166
|
-
* on a connection it sees close, so
|
|
167
|
-
*
|
|
98
|
+
* Sessions are detached rather than disposed: the server ends every session
|
|
99
|
+
* on a connection it sees close, so ending each one first sends requests
|
|
100
|
+
* over a connection this call is about to dispose.
|
|
168
101
|
*/
|
|
169
102
|
dispose() {
|
|
170
103
|
for (const session of [...this.sessions]) {
|
|
171
104
|
session.detach();
|
|
172
105
|
}
|
|
173
106
|
this.sessions.clear();
|
|
174
|
-
// Dropped rather than closed, for the same reason the sessions detach: the
|
|
175
|
-
// server releases every hold on a connection it sees close, and the close
|
|
176
|
-
// would travel over the connection being disposed.
|
|
177
|
-
this.orphanedHolds.clear();
|
|
178
107
|
super.dispose();
|
|
179
108
|
}
|
|
180
109
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-connection.js","sourceRoot":"","sources":["../../src/client/data-connection.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;AAElF,8CAAqD;AACrD,kCAOiB;AAEjB,+CAA2C;AAE3C,
|
|
1
|
+
{"version":3,"file":"data-connection.js","sourceRoot":"","sources":["../../src/client/data-connection.ts"],"names":[],"mappings":"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"}
|