@zukhruf/single-flight 0.3.15 → 0.3.16

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.
Files changed (144) hide show
  1. package/README.md +110 -91
  2. package/dist/client/flight-client.d.ts +44 -0
  3. package/dist/client/flight-client.d.ts.map +1 -0
  4. package/dist/client/flight-client.js +222 -0
  5. package/dist/client/flight-client.js.map +1 -0
  6. package/dist/codec.d.ts +12 -0
  7. package/dist/codec.d.ts.map +1 -0
  8. package/dist/codec.js +2 -0
  9. package/dist/codec.js.map +1 -0
  10. package/dist/connection/connection-supervisor.d.ts +39 -0
  11. package/dist/connection/connection-supervisor.d.ts.map +1 -0
  12. package/dist/connection/connection-supervisor.js +195 -0
  13. package/dist/connection/connection-supervisor.js.map +1 -0
  14. package/dist/connection/connection.d.ts +21 -0
  15. package/dist/connection/connection.d.ts.map +1 -0
  16. package/dist/connection/connection.js +2 -0
  17. package/dist/connection/connection.js.map +1 -0
  18. package/dist/connection/connector.d.ts +15 -0
  19. package/dist/connection/connector.d.ts.map +1 -0
  20. package/dist/connection/connector.js +2 -0
  21. package/dist/connection/connector.js.map +1 -0
  22. package/dist/connection/electing-connector.d.ts +23 -0
  23. package/dist/connection/electing-connector.d.ts.map +1 -0
  24. package/dist/connection/electing-connector.js +148 -0
  25. package/dist/connection/electing-connector.js.map +1 -0
  26. package/dist/connection/handshake.d.ts +31 -0
  27. package/dist/connection/handshake.d.ts.map +1 -0
  28. package/dist/connection/handshake.js +102 -0
  29. package/dist/connection/handshake.js.map +1 -0
  30. package/dist/connection/local-directory-connector.d.ts +14 -0
  31. package/dist/connection/local-directory-connector.d.ts.map +1 -0
  32. package/dist/connection/local-directory-connector.js +23 -0
  33. package/dist/connection/local-directory-connector.js.map +1 -0
  34. package/dist/connection/protocol-version-error.d.ts +12 -0
  35. package/dist/connection/protocol-version-error.d.ts.map +1 -0
  36. package/dist/connection/protocol-version-error.js +19 -0
  37. package/dist/connection/protocol-version-error.js.map +1 -0
  38. package/dist/connection/socket-connection.d.ts +18 -0
  39. package/dist/connection/socket-connection.d.ts.map +1 -0
  40. package/dist/connection/socket-connection.js +52 -0
  41. package/dist/connection/socket-connection.js.map +1 -0
  42. package/dist/connection/socket-path.d.ts +7 -0
  43. package/dist/connection/socket-path.d.ts.map +1 -0
  44. package/dist/connection/socket-path.js +24 -0
  45. package/dist/connection/socket-path.js.map +1 -0
  46. package/dist/coordinator/flight-coordinator.d.ts +33 -0
  47. package/dist/coordinator/flight-coordinator.d.ts.map +1 -0
  48. package/dist/coordinator/flight-coordinator.js +178 -0
  49. package/dist/coordinator/flight-coordinator.js.map +1 -0
  50. package/dist/coordinator/flight-server.d.ts +24 -0
  51. package/dist/coordinator/flight-server.d.ts.map +1 -0
  52. package/dist/coordinator/flight-server.js +74 -0
  53. package/dist/coordinator/flight-server.js.map +1 -0
  54. package/dist/coordinator/flight.d.ts +20 -0
  55. package/dist/coordinator/flight.d.ts.map +1 -0
  56. package/dist/coordinator/flight.js +39 -0
  57. package/dist/coordinator/flight.js.map +1 -0
  58. package/dist/coordinator/party.d.ts +38 -0
  59. package/dist/coordinator/party.d.ts.map +1 -0
  60. package/dist/coordinator/party.js +43 -0
  61. package/dist/coordinator/party.js.map +1 -0
  62. package/dist/coordinator/session.d.ts +16 -0
  63. package/dist/coordinator/session.d.ts.map +1 -0
  64. package/dist/coordinator/session.js +103 -0
  65. package/dist/coordinator/session.js.map +1 -0
  66. package/dist/election/leader-election.d.ts +24 -0
  67. package/dist/election/leader-election.d.ts.map +1 -0
  68. package/dist/election/leader-election.js +80 -0
  69. package/dist/election/leader-election.js.map +1 -0
  70. package/dist/election/leadership.d.ts +14 -0
  71. package/dist/election/leadership.d.ts.map +1 -0
  72. package/dist/election/leadership.js +23 -0
  73. package/dist/election/leadership.js.map +1 -0
  74. package/dist/errors.d.ts +9 -12
  75. package/dist/errors.d.ts.map +1 -1
  76. package/dist/errors.js +13 -11
  77. package/dist/errors.js.map +1 -1
  78. package/dist/index.d.ts +6 -5
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +3 -3
  81. package/dist/index.js.map +1 -1
  82. package/dist/local-directory/local-directory.d.ts +7 -0
  83. package/dist/local-directory/local-directory.d.ts.map +1 -0
  84. package/dist/local-directory/local-directory.js +75 -0
  85. package/dist/local-directory/local-directory.js.map +1 -0
  86. package/dist/local-directory/network-directory-error.d.ts +8 -0
  87. package/dist/local-directory/network-directory-error.d.ts.map +1 -0
  88. package/dist/local-directory/network-directory-error.js +13 -0
  89. package/dist/local-directory/network-directory-error.js.map +1 -0
  90. package/dist/protocol/flight-protocol.d.ts +94 -0
  91. package/dist/protocol/flight-protocol.d.ts.map +1 -0
  92. package/dist/protocol/flight-protocol.js +60 -0
  93. package/dist/protocol/flight-protocol.js.map +1 -0
  94. package/dist/shared/fs/durable-write.d.ts +7 -0
  95. package/dist/shared/fs/durable-write.d.ts.map +1 -0
  96. package/dist/shared/fs/durable-write.js +47 -0
  97. package/dist/shared/fs/durable-write.js.map +1 -0
  98. package/dist/shared/fs/errno.d.ts +2 -0
  99. package/dist/shared/fs/errno.d.ts.map +1 -0
  100. package/dist/shared/fs/errno.js +4 -0
  101. package/dist/shared/fs/errno.js.map +1 -0
  102. package/dist/{patiently.d.ts → shared/fs/patiently.d.ts} +1 -1
  103. package/dist/shared/fs/patiently.d.ts.map +1 -0
  104. package/dist/{patiently.js → shared/fs/patiently.js} +7 -8
  105. package/dist/shared/fs/patiently.js.map +1 -0
  106. package/dist/shared/fs/replace-file.d.ts +8 -0
  107. package/dist/shared/fs/replace-file.d.ts.map +1 -0
  108. package/dist/shared/fs/replace-file.js +12 -0
  109. package/dist/shared/fs/replace-file.js.map +1 -0
  110. package/dist/shared/is-record.d.ts +3 -0
  111. package/dist/shared/is-record.d.ts.map +1 -0
  112. package/dist/shared/is-record.js +3 -0
  113. package/dist/shared/is-record.js.map +1 -0
  114. package/dist/shared/sqlite/is-busy.d.ts +3 -0
  115. package/dist/shared/sqlite/is-busy.d.ts.map +1 -0
  116. package/dist/shared/sqlite/is-busy.js +10 -0
  117. package/dist/shared/sqlite/is-busy.js.map +1 -0
  118. package/dist/single-flight.d.ts +41 -9
  119. package/dist/single-flight.d.ts.map +1 -1
  120. package/dist/single-flight.js +91 -31
  121. package/dist/single-flight.js.map +1 -1
  122. package/package.json +2 -2
  123. package/dist/file-flight-records.d.ts +0 -22
  124. package/dist/file-flight-records.d.ts.map +0 -1
  125. package/dist/file-flight-records.js +0 -124
  126. package/dist/file-flight-records.js.map +0 -1
  127. package/dist/flight-records.d.ts +0 -67
  128. package/dist/flight-records.d.ts.map +0 -1
  129. package/dist/flight-records.js +0 -38
  130. package/dist/flight-records.js.map +0 -1
  131. package/dist/flight.d.ts +0 -12
  132. package/dist/flight.d.ts.map +0 -1
  133. package/dist/flight.js +0 -38
  134. package/dist/flight.js.map +0 -1
  135. package/dist/follow.d.ts +0 -31
  136. package/dist/follow.d.ts.map +0 -1
  137. package/dist/follow.js +0 -60
  138. package/dist/follow.js.map +0 -1
  139. package/dist/patiently.d.ts.map +0 -1
  140. package/dist/patiently.js.map +0 -1
  141. package/dist/shared-flight.d.ts +0 -25
  142. package/dist/shared-flight.d.ts.map +0 -1
  143. package/dist/shared-flight.js +0 -89
  144. package/dist/shared-flight.js.map +0 -1
@@ -0,0 +1,39 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { Connector } from './connector.ts';
3
+ export type SupervisorStatus = 'idle' | 'connecting' | 'connected' | 'unavailable' | 'closed';
4
+ export interface SupervisorEvents<Incoming> {
5
+ /** A connection is open: the first one, or the replacement for a lost one. */
6
+ connected: [];
7
+ message: [message: Incoming];
8
+ /** The open connection is gone and a replacement is on its way. Nothing sent on it will be answered. */
9
+ disconnected: [];
10
+ /** The connector can never connect again. */
11
+ unavailable: [];
12
+ /** One attempt to connect failed; the next `open` tries again. */
13
+ failed: [error: unknown];
14
+ }
15
+ /**
16
+ * Keeps one connection open at a time. It connects on `open`, replaces a
17
+ * connection that is lost, and stops for good when its connector has no peer
18
+ * left. It never reads the messages it carries: what a replacement must be
19
+ * told again is for its listener to decide.
20
+ */
21
+ export declare class ConnectionSupervisor<Outgoing, Incoming> extends EventEmitter<SupervisorEvents<Incoming>> {
22
+ #private;
23
+ constructor(connector: Connector<Outgoing, Incoming>);
24
+ get status(): SupervisorStatus;
25
+ /** Starts connecting, unless a connection is open, on its way, or never coming. */
26
+ open(): void;
27
+ /** Whether `message` was handed to an open connection. A failed delivery is reported as a loss. */
28
+ send(message: Outgoing): boolean;
29
+ /**
30
+ * Keeps the process alive through the open connection. A replacement starts
31
+ * with its adapter's default, so a listener that waits applies this again on
32
+ * `connected`.
33
+ */
34
+ ref(): void;
35
+ unref(): void;
36
+ /** Closes for good, after any connect still on its way has stopped. */
37
+ close(): Promise<void>;
38
+ }
39
+ //# sourceMappingURL=connection-supervisor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-supervisor.d.ts","sourceRoot":"","sources":["../../src/connection/connection-supervisor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,MAAM,gBAAgB,GAC1B,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,aAAa,GAAG,QAAQ,CAAC;AAEjE,MAAM,WAAW,gBAAgB,CAAC,QAAQ;IACxC,8EAA8E;IAC9E,SAAS,EAAE,EAAE,CAAC;IACd,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC7B,wGAAwG;IACxG,YAAY,EAAE,EAAE,CAAC;IACjB,6CAA6C;IAC7C,WAAW,EAAE,EAAE,CAAC;IAChB,kEAAkE;IAClE,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;CAC1B;AAED;;;;;GAKG;AACH,qBAAa,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAE,SAAQ,YAAY,CACxE,gBAAgB,CAAC,QAAQ,CAAC,CAC3B;;gBAGa,SAAS,EAAE,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAKpD,IAAI,MAAM,IAAI,gBAAgB,CAE7B;IAED,mFAAmF;IACnF,IAAI;IAIJ,mGAAmG;IACnG,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IAIhC;;;;OAIG;IACH,GAAG;IAIH,KAAK;IAIL,uEAAuE;IACvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAGvB"}
@@ -0,0 +1,195 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { Latch } from '@zukhruf/async';
3
+ /**
4
+ * Keeps one connection open at a time. It connects on `open`, replaces a
5
+ * connection that is lost, and stops for good when its connector has no peer
6
+ * left. It never reads the messages it carries: what a replacement must be
7
+ * told again is for its listener to decide.
8
+ */
9
+ export class ConnectionSupervisor extends EventEmitter {
10
+ #supervision;
11
+ constructor(connector) {
12
+ super();
13
+ this.#supervision = new Supervision(connector, this);
14
+ }
15
+ get status() {
16
+ return this.#supervision.state.status;
17
+ }
18
+ /** Starts connecting, unless a connection is open, on its way, or never coming. */
19
+ open() {
20
+ this.#supervision.state.open();
21
+ }
22
+ /** Whether `message` was handed to an open connection. A failed delivery is reported as a loss. */
23
+ send(message) {
24
+ return this.#supervision.state.send(message);
25
+ }
26
+ /**
27
+ * Keeps the process alive through the open connection. A replacement starts
28
+ * with its adapter's default, so a listener that waits applies this again on
29
+ * `connected`.
30
+ */
31
+ ref() {
32
+ this.#supervision.state.reference(true);
33
+ }
34
+ unref() {
35
+ this.#supervision.state.reference(false);
36
+ }
37
+ /** Closes for good, after any connect still on its way has stopped. */
38
+ close() {
39
+ return this.#supervision.state.close();
40
+ }
41
+ }
42
+ /** What the states share: the current state, and how to move to the next. */
43
+ class Supervision {
44
+ connector;
45
+ events;
46
+ state;
47
+ constructor(connector, events) {
48
+ this.connector = connector;
49
+ this.events = events;
50
+ this.state = new Idle(this);
51
+ }
52
+ /** A state that stopped being current must not act on what it started. */
53
+ isCurrent(state) {
54
+ return this.state === state;
55
+ }
56
+ become(state) {
57
+ this.state = state;
58
+ state.enter();
59
+ }
60
+ }
61
+ class Idle {
62
+ status = 'idle';
63
+ #supervision;
64
+ constructor(supervision) {
65
+ this.#supervision = supervision;
66
+ }
67
+ enter() { }
68
+ open() {
69
+ this.#supervision.become(new Connecting(this.#supervision));
70
+ }
71
+ send() {
72
+ return false;
73
+ }
74
+ reference() { }
75
+ async close() {
76
+ this.#supervision.become(new Closed());
77
+ }
78
+ }
79
+ class Connecting {
80
+ status = 'connecting';
81
+ #supervision;
82
+ #abort = new AbortController();
83
+ /** Opens when the connect ends, so `close` can wait for it. */
84
+ #ended = new Latch();
85
+ constructor(supervision) {
86
+ this.#supervision = supervision;
87
+ }
88
+ enter() {
89
+ void this.#connect().finally(() => this.#ended.open());
90
+ }
91
+ async #connect() {
92
+ const supervision = this.#supervision;
93
+ let connection;
94
+ try {
95
+ connection = await Promise.try(() => supervision.connector.connect(this.#abort.signal));
96
+ }
97
+ catch (error) {
98
+ if (!supervision.isCurrent(this))
99
+ return;
100
+ supervision.become(new Idle(supervision));
101
+ supervision.events.emit('failed', error);
102
+ return;
103
+ }
104
+ if (!supervision.isCurrent(this)) {
105
+ connection?.close();
106
+ return;
107
+ }
108
+ supervision.become(connection
109
+ ? new Connected(supervision, connection)
110
+ : new Unavailable(supervision));
111
+ }
112
+ open() { }
113
+ send() {
114
+ return false;
115
+ }
116
+ reference() { }
117
+ async close() {
118
+ this.#supervision.become(new Closed());
119
+ this.#abort.abort();
120
+ await this.#ended.wait();
121
+ }
122
+ }
123
+ class Connected {
124
+ status = 'connected';
125
+ #supervision;
126
+ #connection;
127
+ constructor(supervision, connection) {
128
+ this.#supervision = supervision;
129
+ this.#connection = connection;
130
+ }
131
+ enter() {
132
+ const supervision = this.#supervision;
133
+ this.#connection.on('message', (message) => {
134
+ if (supervision.isCurrent(this))
135
+ supervision.events.emit('message', message);
136
+ });
137
+ this.#connection.once('close', () => this.#lose());
138
+ supervision.events.emit('connected');
139
+ }
140
+ open() { }
141
+ /** The connection may report its loss while it sends, so the state is checked after the call. */
142
+ send(message) {
143
+ this.#connection.send(message).catch(() => this.#lose());
144
+ return this.#supervision.isCurrent(this);
145
+ }
146
+ reference(referenced) {
147
+ if (referenced)
148
+ this.#connection.ref();
149
+ else
150
+ this.#connection.unref();
151
+ }
152
+ async close() {
153
+ this.#supervision.become(new Closed());
154
+ this.#connection.close();
155
+ }
156
+ /** A close event and a failed send can both report one loss; only the first counts. */
157
+ #lose() {
158
+ const supervision = this.#supervision;
159
+ if (!supervision.isCurrent(this))
160
+ return;
161
+ this.#connection.close();
162
+ // Leave this state before the listener hears of the loss, so its sends wait for the replacement.
163
+ supervision.become(new Connecting(supervision));
164
+ supervision.events.emit('disconnected');
165
+ }
166
+ }
167
+ class Unavailable {
168
+ status = 'unavailable';
169
+ #supervision;
170
+ constructor(supervision) {
171
+ this.#supervision = supervision;
172
+ }
173
+ enter() {
174
+ this.#supervision.events.emit('unavailable');
175
+ }
176
+ open() { }
177
+ send() {
178
+ return false;
179
+ }
180
+ reference() { }
181
+ async close() {
182
+ this.#supervision.become(new Closed());
183
+ }
184
+ }
185
+ class Closed {
186
+ status = 'closed';
187
+ enter() { }
188
+ open() { }
189
+ send() {
190
+ return false;
191
+ }
192
+ reference() { }
193
+ async close() { }
194
+ }
195
+ //# sourceMappingURL=connection-supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-supervisor.js","sourceRoot":"","sources":["../../src/connection/connection-supervisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { EventEmitter } from 'node:events';
2
+ export interface ConnectionEvents<Incoming> {
3
+ message: [message: Incoming];
4
+ /** Emitted once, when the peer is gone. */
5
+ close: [];
6
+ }
7
+ /**
8
+ * A message channel to one peer. Adapters wrap IPC channels and sockets.
9
+ * Subscribe in the task that receives the connection: a message that arrives
10
+ * while nobody listens is not kept.
11
+ */
12
+ export interface Connection<Outgoing, Incoming> extends EventEmitter<ConnectionEvents<Incoming>> {
13
+ /** Rejects when the message cannot be delivered because the peer is gone. */
14
+ send(message: Outgoing): Promise<void>;
15
+ /** Keeps the process alive while its owner waits for a message. */
16
+ ref(): void;
17
+ /** Lets the process exit; messages and closure are still reported while it runs. */
18
+ unref(): void;
19
+ close(): void;
20
+ }
21
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/connection/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,gBAAgB,CAAC,QAAQ;IACxC,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC7B,2CAA2C;IAC3C,KAAK,EAAE,EAAE,CAAC;CACX;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAE,SAAQ,YAAY,CAClE,gBAAgB,CAAC,QAAQ,CAAC,CAC3B;IACC,6EAA6E;IAC7E,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,mEAAmE;IACnE,GAAG,IAAI,IAAI,CAAC;IACZ,oFAAoF;IACpF,KAAK,IAAI,IAAI,CAAC;IACd,KAAK,IAAI,IAAI,CAAC;CACf"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/connection/connection.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ import type { FlightRequest, FlightResponse } from '../protocol/flight-protocol.ts';
2
+ import type { Connection } from './connection.ts';
3
+ export type FlightConnection = Connection<FlightRequest, FlightResponse>;
4
+ /** Decides how a client reaches its coordinator, and whether it can again after losing it. */
5
+ export interface Connector<Outgoing, Incoming> {
6
+ /**
7
+ * Resolves to `undefined` when the coordinator is gone for good and nothing
8
+ * can replace it.
9
+ * Rejects when this attempt failed and a later one may succeed. Once
10
+ * `signal` aborts, rejects with its reason and leaves nothing started.
11
+ */
12
+ connect(signal: AbortSignal): Promise<Connection<Outgoing, Incoming> | undefined>;
13
+ }
14
+ export type FlightConnector = Connector<FlightRequest, FlightResponse>;
15
+ //# sourceMappingURL=connector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../src/connection/connector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACf,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;AAEzE,8FAA8F;AAC9F,MAAM,WAAW,SAAS,CAAC,QAAQ,EAAE,QAAQ;IAC3C;;;;;OAKG;IACH,OAAO,CACL,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,SAAS,CAAC,CAAC;CACxD;AAED,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=connector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector.js","sourceRoot":"","sources":["../../src/connection/connector.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import type { LeaderElection } from '../election/leader-election.ts';
2
+ import type { Leadership } from '../election/leadership.ts';
3
+ import type { FlightConnection, FlightConnector } from './connector.ts';
4
+ export interface ElectingConnectorOptions {
5
+ socketPath: string;
6
+ /** The connector only campaigns, so it needs only that part of an election. */
7
+ election: Pick<LeaderElection, 'campaign'>;
8
+ pollInterval: number;
9
+ /** Starts serving for a term this process just won. */
10
+ serve(leadership: Leadership): Promise<void>;
11
+ }
12
+ /**
13
+ * Reaches the coordinator's socket, and when nobody serves it, campaigns to
14
+ * become the coordinator. Never gives up: a lost coordinator is always
15
+ * replaced by a candidate. An aborted connect never starts serving: a term
16
+ * won after the abort is resigned.
17
+ */
18
+ export declare class ElectingConnector implements FlightConnector {
19
+ #private;
20
+ constructor(options: ElectingConnectorOptions);
21
+ connect(signal: AbortSignal): Promise<FlightConnection>;
22
+ }
23
+ //# sourceMappingURL=electing-connector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"electing-connector.d.ts","sourceRoot":"","sources":["../../src/connection/electing-connector.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAM5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAWxE,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,KAAK,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAED;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,eAAe;;gBAG3C,OAAO,EAAE,wBAAwB;IAIvC,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC;CA2F9D"}
@@ -0,0 +1,148 @@
1
+ import { connect } from 'node:net';
2
+ import { setTimeout as delay } from 'node:timers/promises';
3
+ import { untilAborted } from '@zukhruf/async';
4
+ import { isFlightResponse, } from "../protocol/flight-protocol.js";
5
+ import { PROTOCOL_VERSION, greet } from "./handshake.js";
6
+ import { ProtocolVersionError } from "./protocol-version-error.js";
7
+ import { SocketConnection } from "./socket-connection.js";
8
+ /**
9
+ * How long a coordinator may keep its term while it hangs up on every hello.
10
+ * One that stops ends its term within moments.
11
+ */
12
+ const HANDSHAKE_PATIENCE = 1000;
13
+ /**
14
+ * Reaches the coordinator's socket, and when nobody serves it, campaigns to
15
+ * become the coordinator. Never gives up: a lost coordinator is always
16
+ * replaced by a candidate. An aborted connect never starts serving: a term
17
+ * won after the abort is resigned.
18
+ */
19
+ export class ElectingConnector {
20
+ #options;
21
+ constructor(options) {
22
+ this.#options = options;
23
+ }
24
+ async connect(signal) {
25
+ const { socketPath, pollInterval } = this.#options;
26
+ for (;;) {
27
+ const coordinator = await reachUnlessAborted(socketPath, signal);
28
+ if (coordinator) {
29
+ const connection = (await this.#follow(coordinator, signal)) ??
30
+ (await this.#outlastHangUp(signal));
31
+ if (connection)
32
+ return connection;
33
+ }
34
+ const leadership = await this.#campaign(pollInterval, signal);
35
+ if (leadership) {
36
+ const own = await this.#coordinate(leadership, signal);
37
+ if (own)
38
+ return own;
39
+ }
40
+ else {
41
+ await delay(pollInterval, undefined, { signal });
42
+ }
43
+ }
44
+ }
45
+ /**
46
+ * After a coordinator hung up on the hello: a coordinator that stops frees
47
+ * its term within moments, so this process coordinates or reaches the
48
+ * successor soon enough to reassert its flights in the successor's grace
49
+ * window. A coordinator that keeps its term and hangs up on every hello
50
+ * fails the connect after HANDSHAKE_PATIENCE. Resolves `undefined` once
51
+ * nothing listens.
52
+ */
53
+ async #outlastHangUp(signal) {
54
+ const { socketPath, pollInterval } = this.#options;
55
+ const deadline = performance.now() + HANDSHAKE_PATIENCE;
56
+ for (;;) {
57
+ const leadership = await this.#campaign(0, signal);
58
+ if (leadership)
59
+ return this.#coordinate(leadership, signal);
60
+ const again = await reachUnlessAborted(socketPath, signal);
61
+ if (!again)
62
+ return undefined;
63
+ const connection = await this.#follow(again, signal);
64
+ if (connection)
65
+ return connection;
66
+ if (performance.now() >= deadline) {
67
+ throw new ProtocolVersionError(PROTOCOL_VERSION, undefined);
68
+ }
69
+ await delay(pollInterval, undefined, { signal });
70
+ }
71
+ }
72
+ /** Campaigns until `timeout`; a term won after `signal` aborted is resigned. */
73
+ async #campaign(timeout, signal) {
74
+ const leadership = await this.#options.election.campaign({ timeout });
75
+ if (signal.aborted)
76
+ await leadership?.resign();
77
+ signal.throwIfAborted();
78
+ return leadership;
79
+ }
80
+ /** Serves the term this process just won, and reaches its own server like any other process. */
81
+ async #coordinate(leadership, signal) {
82
+ try {
83
+ await this.#options.serve(leadership);
84
+ }
85
+ catch (error) {
86
+ // A term that nothing serves would stop every other candidate from coordinating.
87
+ await leadership.resign();
88
+ throw error;
89
+ }
90
+ const own = await reachUnlessAborted(this.#options.socketPath, signal);
91
+ if (!own)
92
+ return undefined;
93
+ const greeting = await greetUnlessAborted(own, signal);
94
+ if (greeting.kind === 'welcome')
95
+ return flightConnection(own);
96
+ own.destroy();
97
+ return undefined;
98
+ }
99
+ /** Uses the coordinator on `socket` if it speaks this protocol; `undefined` when it hung up on the hello. */
100
+ async #follow(socket, signal) {
101
+ const greeting = await greetUnlessAborted(socket, signal);
102
+ if (greeting.kind === 'welcome')
103
+ return flightConnection(socket);
104
+ socket.destroy();
105
+ if (greeting.kind === 'refused') {
106
+ throw new ProtocolVersionError(PROTOCOL_VERSION, greeting.version);
107
+ }
108
+ return undefined;
109
+ }
110
+ }
111
+ function flightConnection(socket) {
112
+ return new SocketConnection(socket, isFlightResponse);
113
+ }
114
+ /** Like `reach`, but a socket reached after `signal` aborted is destroyed, not returned. */
115
+ async function reachUnlessAborted(socketPath, signal) {
116
+ signal.throwIfAborted();
117
+ const socket = await reach(socketPath);
118
+ if (signal.aborted)
119
+ socket?.destroy();
120
+ signal.throwIfAborted();
121
+ return socket;
122
+ }
123
+ /** Resolves `undefined` when nothing listens yet (no file, or a dead coordinator's file). */
124
+ function reach(socketPath) {
125
+ return new Promise((resolve) => {
126
+ const socket = connect(socketPath);
127
+ const fail = () => {
128
+ socket.destroy();
129
+ resolve(undefined);
130
+ };
131
+ socket.once('error', fail);
132
+ socket.once('connect', () => {
133
+ socket.off('error', fail);
134
+ resolve(socket);
135
+ });
136
+ });
137
+ }
138
+ /** Greets the coordinator on `socket`; a client that closes meanwhile gives the socket up. */
139
+ async function greetUnlessAborted(socket, signal) {
140
+ try {
141
+ return await untilAborted(greet(socket), signal);
142
+ }
143
+ catch (error) {
144
+ socket.destroy();
145
+ throw error;
146
+ }
147
+ }
148
+ //# sourceMappingURL=electing-connector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"electing-connector.js","sourceRoot":"","sources":["../../src/connection/electing-connector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { Socket } from 'node:net';
2
+ /**
3
+ * The version of the messages a single flight's processes exchange. It changes
4
+ * only when they change, so processes that run different package versions
5
+ * with the same protocol still share a directory. The `hello` and the
6
+ * `refused` answer keep their shape in every version: they are how two
7
+ * versions find out that they differ.
8
+ */
9
+ export declare const PROTOCOL_VERSION = 1;
10
+ /** What the coordinator answered to this process's `hello`. */
11
+ export type Greeting = {
12
+ kind: 'welcome';
13
+ } | {
14
+ kind: 'refused';
15
+ version: number;
16
+ }
17
+ /** The coordinator closed the connection without an answer: it stops. */
18
+ | {
19
+ kind: 'closed';
20
+ };
21
+ /** Says which protocol this process speaks, and reads the coordinator's answer. */
22
+ export declare function greet(socket: Socket): Promise<Greeting>;
23
+ /**
24
+ * Reads a new process's `hello`. A process that speaks this protocol is
25
+ * welcomed. A `hello` of another protocol or version is refused with this
26
+ * coordinator's version, and the connection ends once that answer is
27
+ * written. A process that opens with anything else is no single flight, and
28
+ * its connection ends at once.
29
+ */
30
+ export declare function welcome(socket: Socket): Promise<boolean>;
31
+ //# sourceMappingURL=handshake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/connection/handshake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAOvC;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,+DAA+D;AAC/D,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AACtC,yEAAyE;GACvE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEvB,mFAAmF;AACnF,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAc7D;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB9D"}
@@ -0,0 +1,102 @@
1
+ import { isRecord } from "../shared/is-record.js";
2
+ /** The protocol a single flight's processes speak, named in every hello, so no process of another protocol is ever served. */
3
+ const PROTOCOL = 'single-flight';
4
+ /**
5
+ * The version of the messages a single flight's processes exchange. It changes
6
+ * only when they change, so processes that run different package versions
7
+ * with the same protocol still share a directory. The `hello` and the
8
+ * `refused` answer keep their shape in every version: they are how two
9
+ * versions find out that they differ.
10
+ */
11
+ export const PROTOCOL_VERSION = 1;
12
+ /** Says which protocol this process speaks, and reads the coordinator's answer. */
13
+ export async function greet(socket) {
14
+ socket.write(`${JSON.stringify({ op: 'hello', protocol: PROTOCOL, version: PROTOCOL_VERSION })}\n`);
15
+ const answer = parse(await readLine(socket));
16
+ if (isRecord(answer) && answer.op === 'welcome')
17
+ return { kind: 'welcome' };
18
+ if (isRecord(answer) &&
19
+ answer.op === 'refused' &&
20
+ typeof answer.version === 'number') {
21
+ return { kind: 'refused', version: answer.version };
22
+ }
23
+ return { kind: 'closed' };
24
+ }
25
+ /**
26
+ * Reads a new process's `hello`. A process that speaks this protocol is
27
+ * welcomed. A `hello` of another protocol or version is refused with this
28
+ * coordinator's version, and the connection ends once that answer is
29
+ * written. A process that opens with anything else is no single flight, and
30
+ * its connection ends at once.
31
+ */
32
+ export async function welcome(socket) {
33
+ const hello = parse(await readLine(socket));
34
+ if (!isRecord(hello) || hello.op !== 'hello') {
35
+ socket.destroy();
36
+ return false;
37
+ }
38
+ if (hello.protocol === PROTOCOL && hello.version === PROTOCOL_VERSION) {
39
+ socket.write(`${JSON.stringify({ op: 'welcome' })}\n`);
40
+ return true;
41
+ }
42
+ // Destroyed once the answer is written: a paused socket would never see a silent process hang up.
43
+ socket.end(`${JSON.stringify({ op: 'refused', protocol: PROTOCOL, version: PROTOCOL_VERSION })}\n`, () => socket.destroy());
44
+ return false;
45
+ }
46
+ function parse(line) {
47
+ if (line === undefined)
48
+ return undefined;
49
+ try {
50
+ return JSON.parse(line);
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ /** A hello and each answer to it are one short line, so a longer first line is none of them. */
57
+ const FIRST_LINE_LIMIT = 1024;
58
+ /**
59
+ * Reads exactly one line and puts back whatever arrived after it, so the
60
+ * reader that takes over the socket next sees every later message. Resolves
61
+ * `undefined` when the socket closes first, or when the line grows past
62
+ * FIRST_LINE_LIMIT bytes: a process that never ends its line cannot fill the
63
+ * memory of this one.
64
+ */
65
+ function readLine(socket) {
66
+ const { promise, resolve } = Promise.withResolvers();
67
+ const chunks = [];
68
+ let lineBytes = 0;
69
+ const stop = () => {
70
+ socket.off('data', onData);
71
+ socket.off('close', onClose);
72
+ socket.pause();
73
+ };
74
+ const onData = (chunk) => {
75
+ const end = chunk.indexOf(0x0a);
76
+ lineBytes += end === -1 ? chunk.length : end;
77
+ if (lineBytes > FIRST_LINE_LIMIT) {
78
+ stop();
79
+ resolve(undefined);
80
+ return;
81
+ }
82
+ if (end === -1) {
83
+ chunks.push(chunk);
84
+ return;
85
+ }
86
+ stop();
87
+ const rest = chunk.subarray(end + 1);
88
+ if (rest.length > 0)
89
+ socket.unshift(rest);
90
+ resolve(Buffer.concat([...chunks, chunk.subarray(0, end)]).toString('utf8'));
91
+ };
92
+ const onClose = () => {
93
+ socket.off('data', onData);
94
+ resolve(undefined);
95
+ };
96
+ // Every error is followed by `close`, which reports the lost connection as no answer.
97
+ socket.on('error', () => { });
98
+ socket.on('data', onData);
99
+ socket.once('close', onClose);
100
+ return promise;
101
+ }
102
+ //# sourceMappingURL=handshake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.js","sourceRoot":"","sources":["../../src/connection/handshake.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { FlightConnection, FlightConnector } from './connector.ts';
2
+ /**
3
+ * Refuses a flight directory on a network file system before the first
4
+ * connection exists. Every connect asks, but a directory that passed once is
5
+ * remembered, so a failover never fails here and never costs a flight that
6
+ * this process leads. Runs keep their call order, because the client queues
7
+ * them before it connects.
8
+ */
9
+ export declare class LocalDirectoryConnector implements FlightConnector {
10
+ #private;
11
+ constructor(directory: string, connector: FlightConnector);
12
+ connect(signal: AbortSignal): Promise<FlightConnection | undefined>;
13
+ }
14
+ //# sourceMappingURL=local-directory-connector.d.ts.map