@zukhruf/single-flight 0.3.15 → 0.3.17

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 (122) hide show
  1. package/README.md +113 -93
  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 +227 -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 +78 -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/protocol/flight-protocol.d.ts +94 -0
  83. package/dist/protocol/flight-protocol.d.ts.map +1 -0
  84. package/dist/protocol/flight-protocol.js +61 -0
  85. package/dist/protocol/flight-protocol.js.map +1 -0
  86. package/dist/shared/is-record.d.ts +3 -0
  87. package/dist/shared/is-record.d.ts.map +1 -0
  88. package/dist/shared/is-record.js +3 -0
  89. package/dist/shared/is-record.js.map +1 -0
  90. package/dist/shared/sqlite/is-busy.d.ts +3 -0
  91. package/dist/shared/sqlite/is-busy.d.ts.map +1 -0
  92. package/dist/shared/sqlite/is-busy.js +10 -0
  93. package/dist/shared/sqlite/is-busy.js.map +1 -0
  94. package/dist/single-flight.d.ts +43 -9
  95. package/dist/single-flight.d.ts.map +1 -1
  96. package/dist/single-flight.js +98 -31
  97. package/dist/single-flight.js.map +1 -1
  98. package/package.json +6 -6
  99. package/dist/file-flight-records.d.ts +0 -22
  100. package/dist/file-flight-records.d.ts.map +0 -1
  101. package/dist/file-flight-records.js +0 -124
  102. package/dist/file-flight-records.js.map +0 -1
  103. package/dist/flight-records.d.ts +0 -67
  104. package/dist/flight-records.d.ts.map +0 -1
  105. package/dist/flight-records.js +0 -38
  106. package/dist/flight-records.js.map +0 -1
  107. package/dist/flight.d.ts +0 -12
  108. package/dist/flight.d.ts.map +0 -1
  109. package/dist/flight.js +0 -38
  110. package/dist/flight.js.map +0 -1
  111. package/dist/follow.d.ts +0 -31
  112. package/dist/follow.d.ts.map +0 -1
  113. package/dist/follow.js +0 -60
  114. package/dist/follow.js.map +0 -1
  115. package/dist/patiently.d.ts +0 -3
  116. package/dist/patiently.d.ts.map +0 -1
  117. package/dist/patiently.js +0 -31
  118. package/dist/patiently.js.map +0 -1
  119. package/dist/shared-flight.d.ts +0 -25
  120. package/dist/shared-flight.d.ts.map +0 -1
  121. package/dist/shared-flight.js +0 -89
  122. package/dist/shared-flight.js.map +0 -1
@@ -0,0 +1,38 @@
1
+ import type { FlightResponse } from '../protocol/flight-protocol.ts';
2
+ import type { Flight } from './flight.ts';
3
+ /** Where a party's answers go: the session of the party's connection. */
4
+ export interface Inbox {
5
+ reply(response: FlightResponse): void;
6
+ /** The party is answered for good, so the connection may use its id no more. */
7
+ forget(id: string): void;
8
+ }
9
+ /** What a party is to the coordinator now. */
10
+ export type Role =
11
+ /** A run that waits for the grace window, a token, or the reassertion of its flight. */
12
+ {
13
+ kind: 'waiting';
14
+ } | {
15
+ kind: 'leading';
16
+ flight: Flight;
17
+ } | {
18
+ kind: 'joining';
19
+ flight: Flight;
20
+ }
21
+ /** Answered for good, withdrawn, or its connection is gone. */
22
+ | {
23
+ kind: 'gone';
24
+ };
25
+ /** One request of one connection: a run that comes to lead a flight or to join one, until it is answered for good. */
26
+ export declare class Party {
27
+ #private;
28
+ readonly id: string;
29
+ constructor(id: string, inbox: Inbox);
30
+ get role(): Role;
31
+ lead(flight: Flight): void;
32
+ /** A leader from before a failover claimed its flight again; it already knows that it leads. */
33
+ resume(flight: Flight): void;
34
+ join(flight: Flight): void;
35
+ /** Answers for good with `response`, or ends without an answer when it was withdrawn or its connection is gone. */
36
+ end(response?: FlightResponse): void;
37
+ }
38
+ //# sourceMappingURL=party.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"party.d.ts","sourceRoot":"","sources":["../../src/coordinator/party.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,yEAAyE;AACzE,MAAM,WAAW,KAAK;IACpB,KAAK,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,gFAAgF;IAChF,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,8CAA8C;AAC9C,MAAM,MAAM,IAAI;AACd,wFAAwF;AACtF;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACrC,+DAA+D;GAC7D;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,sHAAsH;AACtH,qBAAa,KAAK;;IAChB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAIR,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;IAKpC,IAAI,IAAI,IAAI,IAAI,CAEf;IAED,IAAI,CAAC,MAAM,EAAE,MAAM;IASnB,gGAAgG;IAChG,MAAM,CAAC,MAAM,EAAE,MAAM;IAIrB,IAAI,CAAC,MAAM,EAAE,MAAM;IASnB,mHAAmH;IACnH,GAAG,CAAC,QAAQ,CAAC,EAAE,cAAc;CAM9B"}
@@ -0,0 +1,43 @@
1
+ /** One request of one connection: a run that comes to lead a flight or to join one, until it is answered for good. */
2
+ export class Party {
3
+ id;
4
+ #inbox;
5
+ #role = { kind: 'waiting' };
6
+ constructor(id, inbox) {
7
+ this.id = id;
8
+ this.#inbox = inbox;
9
+ }
10
+ get role() {
11
+ return this.#role;
12
+ }
13
+ lead(flight) {
14
+ this.#role = { kind: 'leading', flight };
15
+ this.#inbox.reply({
16
+ op: 'lead',
17
+ id: this.id,
18
+ token: flight.token.toString(),
19
+ });
20
+ }
21
+ /** A leader from before a failover claimed its flight again; it already knows that it leads. */
22
+ resume(flight) {
23
+ this.#role = { kind: 'leading', flight };
24
+ }
25
+ join(flight) {
26
+ this.#role = { kind: 'joining', flight };
27
+ this.#inbox.reply({
28
+ op: 'joined',
29
+ id: this.id,
30
+ flight: flight.token.toString(),
31
+ });
32
+ }
33
+ /** Answers for good with `response`, or ends without an answer when it was withdrawn or its connection is gone. */
34
+ end(response) {
35
+ if (this.#role.kind === 'gone')
36
+ return;
37
+ this.#role = { kind: 'gone' };
38
+ this.#inbox.forget(this.id);
39
+ if (response)
40
+ this.#inbox.reply(response);
41
+ }
42
+ }
43
+ //# sourceMappingURL=party.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"party.js","sourceRoot":"","sources":["../../src/coordinator/party.ts"],"names":[],"mappings":"AAmBA,sHAAsH;AACtH,MAAM,OAAO,KAAK;IACP,EAAE,CAAS;IACX,MAAM,CAAQ;IACvB,KAAK,GAAS,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAElC,YAAY,EAAU,EAAE,KAAY;QAClC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,IAAI,CAAC,MAAc;QACjB,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAChB,EAAE,EAAE,MAAM;YACV,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE;SAC/B,CAAC,CAAC;IACL,CAAC;IAED,gGAAgG;IAChG,MAAM,CAAC,MAAc;QACnB,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAC3C,CAAC;IAED,IAAI,CAAC,MAAc;QACjB,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAChB,EAAE,EAAE,QAAQ;YACZ,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE;SAChC,CAAC,CAAC;IACL,CAAC;IAED,mHAAmH;IACnH,GAAG,CAAC,QAAyB;QAC3B,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO;QACvC,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,QAAQ;YAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ import type { Connection } from '../connection/connection.ts';
2
+ import { type FlightResponse, type RequestEnvelope } from '../protocol/flight-protocol.ts';
3
+ import type { FlightCoordinator } from './flight-coordinator.ts';
4
+ import { type Inbox } from './party.ts';
5
+ /**
6
+ * One client connection. It turns each request into a party of the
7
+ * coordinator, and when the connection is gone, it withdraws them all: the
8
+ * flights it leads are interrupted, and the flights it joined lose a joiner.
9
+ */
10
+ export declare class Session implements Inbox {
11
+ #private;
12
+ constructor(coordinator: FlightCoordinator, connection: Connection<FlightResponse, RequestEnvelope>);
13
+ reply(response: FlightResponse): void;
14
+ forget(id: string): void;
15
+ }
16
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/coordinator/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EAErB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,KAAK,KAAK,EAAS,MAAM,YAAY,CAAC;AAE/C;;;;GAIG;AACH,qBAAa,OAAQ,YAAW,KAAK;;gBAMjC,WAAW,EAAE,iBAAiB,EAC9B,UAAU,EAAE,UAAU,CAAC,cAAc,EAAE,eAAe,CAAC;IAQzD,KAAK,CAAC,QAAQ,EAAE,cAAc;IAI9B,MAAM,CAAC,EAAE,EAAE,MAAM;CAgElB"}
@@ -0,0 +1,103 @@
1
+ import { FencingToken } from '@zukhruf/fencing';
2
+ import { isFlightRequest, } from "../protocol/flight-protocol.js";
3
+ import { Party } from "./party.js";
4
+ /**
5
+ * One client connection. It turns each request into a party of the
6
+ * coordinator, and when the connection is gone, it withdraws them all: the
7
+ * flights it leads are interrupted, and the flights it joined lose a joiner.
8
+ */
9
+ export class Session {
10
+ #coordinator;
11
+ #parties = new Map();
12
+ #phase;
13
+ constructor(coordinator, connection) {
14
+ this.#coordinator = coordinator;
15
+ this.#phase = new Serving(connection);
16
+ connection.on('message', (request) => this.#handle(request));
17
+ connection.once('close', () => this.#end());
18
+ }
19
+ reply(response) {
20
+ this.#phase.reply(response);
21
+ }
22
+ forget(id) {
23
+ this.#parties.delete(id);
24
+ }
25
+ #handle(request) {
26
+ // A newer process may ask what this version does not know. The answer
27
+ // keeps its connection, and with it every flight it leads.
28
+ if (!isFlightRequest(request)) {
29
+ return this.reply({ op: 'unsupported', id: request.id });
30
+ }
31
+ switch (request.op) {
32
+ case 'run': {
33
+ const party = this.#open(request.id);
34
+ if (!party)
35
+ return;
36
+ void (request.flight === undefined
37
+ ? this.#coordinator.run(party, request.key)
38
+ : this.#coordinator.rejoin(party, request.key, request.flight)).catch(() => this.#phase.close());
39
+ return;
40
+ }
41
+ case 'land': {
42
+ const role = this.#parties.get(request.id)?.role;
43
+ if (role?.kind !== 'leading') {
44
+ return this.reply({ op: 'rejected', id: request.id });
45
+ }
46
+ return this.#coordinator.land(role.flight, request.outcome);
47
+ }
48
+ case 'cancel':
49
+ return this.#withdraw(request.id);
50
+ case 'reassert': {
51
+ const party = this.#open(request.id);
52
+ if (!party)
53
+ return;
54
+ return this.#coordinator.reassert(party, request.key, FencingToken.parse(request.token));
55
+ }
56
+ }
57
+ }
58
+ /** A client never sends one id twice on one connection; a second request with it is ignored. */
59
+ #open(id) {
60
+ if (this.#parties.has(id))
61
+ return undefined;
62
+ const party = new Party(id, this);
63
+ this.#parties.set(id, party);
64
+ return party;
65
+ }
66
+ #withdraw(id) {
67
+ const party = this.#parties.get(id);
68
+ if (!party)
69
+ return;
70
+ const role = party.role;
71
+ party.end();
72
+ if (role.kind === 'joining')
73
+ role.flight.remove(party);
74
+ if (role.kind === 'leading')
75
+ this.#coordinator.interrupt(role.flight);
76
+ }
77
+ /** Ends Serving first, so nothing is answered on a connection that is gone. */
78
+ #end() {
79
+ this.#phase = new Ended();
80
+ for (const id of [...this.#parties.keys()])
81
+ this.#withdraw(id);
82
+ }
83
+ }
84
+ /** The client is connected: the session answers it. */
85
+ class Serving {
86
+ #connection;
87
+ constructor(connection) {
88
+ this.#connection = connection;
89
+ }
90
+ reply(response) {
91
+ // A failed send means the client is gone; `close` then withdraws its parties.
92
+ this.#connection.send(response).catch(() => { });
93
+ }
94
+ close() {
95
+ this.#connection.close();
96
+ }
97
+ }
98
+ /** The client is gone: nothing is answered. */
99
+ class Ended {
100
+ reply() { }
101
+ close() { }
102
+ }
103
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/coordinator/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { Leadership } from './leadership.ts';
2
+ export interface LeaderElectionOptions {
3
+ /** Milliseconds between attempts while another process coordinates. */
4
+ pollInterval?: number;
5
+ }
6
+ export interface CampaignOptions {
7
+ /** Milliseconds to keep trying before conceding to the current coordinator. */
8
+ timeout?: number;
9
+ }
10
+ /**
11
+ * Elects the coordinator among the processes sharing `directory`. The claim is
12
+ * an exclusive SQLite transaction on `flight.lock`, held for the whole term, so
13
+ * the kernel ends the term if the coordinator dies. Each term gets a higher
14
+ * epoch than every term before it, recorded in `flight.epoch`. Never delete
15
+ * `flight.lock` while campaigners run: a new file would let a second
16
+ * coordinator win.
17
+ */
18
+ export declare class LeaderElection {
19
+ #private;
20
+ constructor(directory: string, { pollInterval }?: LeaderElectionOptions);
21
+ /** Resolves with the new term, or `undefined` if another process still coordinates after `timeout`. */
22
+ campaign({ timeout }?: CampaignOptions): Promise<Leadership | undefined>;
23
+ }
24
+ //# sourceMappingURL=leader-election.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leader-election.d.ts","sourceRoot":"","sources":["../../src/election/leader-election.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,WAAW,qBAAqB;IACpC,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,qBAAa,cAAc;;gBAKvB,SAAS,EAAE,MAAM,EACjB,EAAE,YAAiB,EAAE,GAAE,qBAA0B;IAMnD,uGAAuG;IACjG,QAAQ,CAAC,EAAE,OAAW,EAAE,GAAE,eAAoB,GAAG,OAAO,CAC5D,UAAU,GAAG,SAAS,CACvB;CAyCF"}
@@ -0,0 +1,78 @@
1
+ import { mkdir, readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { DatabaseSync } from 'node:sqlite';
4
+ import { setTimeout as delay } from 'node:timers/promises';
5
+ import { assertLocalDirectory, durableWrite, isErrno } from '@zukhruf/fs';
6
+ import { isBusy } from "../shared/sqlite/is-busy.js";
7
+ import { Leadership } from "./leadership.js";
8
+ /**
9
+ * Elects the coordinator among the processes sharing `directory`. The claim is
10
+ * an exclusive SQLite transaction on `flight.lock`, held for the whole term, so
11
+ * the kernel ends the term if the coordinator dies. Each term gets a higher
12
+ * epoch than every term before it, recorded in `flight.epoch`. Never delete
13
+ * `flight.lock` while campaigners run: a new file would let a second
14
+ * coordinator win.
15
+ */
16
+ export class LeaderElection {
17
+ #directory;
18
+ #pollInterval;
19
+ constructor(directory, { pollInterval = 10 } = {}) {
20
+ this.#directory = directory;
21
+ this.#pollInterval = pollInterval;
22
+ }
23
+ /** Resolves with the new term, or `undefined` if another process still coordinates after `timeout`. */
24
+ async campaign({ timeout = 0 } = {}) {
25
+ await assertLocalDirectory(this.#directory);
26
+ await mkdir(this.#directory, { recursive: true });
27
+ // A busy timeout above zero would block this process's event loop while it
28
+ // waits, so the claim is retried here instead.
29
+ const claim = new DatabaseSync(join(this.#directory, 'flight.lock'), {
30
+ timeout: 0,
31
+ });
32
+ const deadline = performance.now() + timeout;
33
+ try {
34
+ for (;;) {
35
+ if (this.#tryClaim(claim))
36
+ return new Leadership(await this.#nextEpoch(), claim);
37
+ if (performance.now() >= deadline)
38
+ break;
39
+ await delay(this.#pollInterval);
40
+ }
41
+ }
42
+ catch (error) {
43
+ claim.close();
44
+ throw error;
45
+ }
46
+ claim.close();
47
+ return undefined;
48
+ }
49
+ #tryClaim(claim) {
50
+ try {
51
+ claim.exec('BEGIN EXCLUSIVE');
52
+ return true;
53
+ }
54
+ catch (error) {
55
+ if (isBusy(error))
56
+ return false;
57
+ throw error;
58
+ }
59
+ }
60
+ /** Safe without further locking: only the claim holder gets here. */
61
+ async #nextEpoch() {
62
+ const path = join(this.#directory, 'flight.epoch');
63
+ const epoch = (await readEpoch(path)) + 1n;
64
+ await durableWrite(path, epoch.toString());
65
+ return epoch;
66
+ }
67
+ }
68
+ async function readEpoch(path) {
69
+ try {
70
+ return BigInt(await readFile(path, 'utf8'));
71
+ }
72
+ catch (error) {
73
+ if (isErrno(error, 'ENOENT'))
74
+ return 0n;
75
+ throw error;
76
+ }
77
+ }
78
+ //# sourceMappingURL=leader-election.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leader-election.js","sourceRoot":"","sources":["../../src/election/leader-election.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE1E,OAAO,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAY7C;;;;;;;GAOG;AACH,MAAM,OAAO,cAAc;IAChB,UAAU,CAAS;IACnB,aAAa,CAAS;IAE/B,YACE,SAAiB,EACjB,EAAE,YAAY,GAAG,EAAE,KAA4B,EAAE;QAEjD,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC;IACpC,CAAC;IAED,uGAAuG;IACvG,KAAK,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,CAAC,KAAsB,EAAE;QAGlD,MAAM,oBAAoB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,2EAA2E;QAC3E,+CAA+C;QAC/C,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,EAAE;YACnE,OAAO,EAAE,CAAC;SACX,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;QAC7C,IAAI,CAAC;YACH,SAAS,CAAC;gBACR,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;oBACvB,OAAO,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,EAAE,KAAK,CAAC,CAAC;gBACxD,IAAI,WAAW,CAAC,GAAG,EAAE,IAAI,QAAQ;oBAAE,MAAM;gBACzC,MAAM,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,MAAM,KAAK,CAAC;QACd,CAAC;QACD,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,SAAS,CAAC,KAAmB;QAC3B,IAAI,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,MAAM,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;YAChC,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,CAAC,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;QAC3C,MAAM,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC3C,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAED,KAAK,UAAU,SAAS,CAAC,IAAY;IACnC,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAC;QACxC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ /**
3
+ * One term as the elected coordinator. It lasts until `resign` or until the
4
+ * process dies, when the kernel releases the claim and another campaigner can win.
5
+ */
6
+ export declare class Leadership implements AsyncDisposable {
7
+ #private;
8
+ /** Grows with every term, so a newer coordinator's tokens always outrank an older one's. */
9
+ readonly epoch: bigint;
10
+ constructor(epoch: bigint, claim: DatabaseSync);
11
+ resign(): Promise<void>;
12
+ [Symbol.asyncDispose](): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=leadership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leadership.d.ts","sourceRoot":"","sources":["../../src/election/leadership.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;GAGG;AACH,qBAAa,UAAW,YAAW,eAAe;;IAChD,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAGX,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY;IAKxC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAM7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAGvC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * One term as the elected coordinator. It lasts until `resign` or until the
3
+ * process dies, when the kernel releases the claim and another campaigner can win.
4
+ */
5
+ export class Leadership {
6
+ /** Grows with every term, so a newer coordinator's tokens always outrank an older one's. */
7
+ epoch;
8
+ #claim;
9
+ constructor(epoch, claim) {
10
+ this.epoch = epoch;
11
+ this.#claim = claim;
12
+ }
13
+ async resign() {
14
+ if (!this.#claim.isOpen)
15
+ return;
16
+ this.#claim.exec('ROLLBACK');
17
+ this.#claim.close();
18
+ }
19
+ [Symbol.asyncDispose]() {
20
+ return this.resign();
21
+ }
22
+ }
23
+ //# sourceMappingURL=leadership.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leadership.js","sourceRoot":"","sources":["../../src/election/leadership.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,UAAU;IACrB,4FAA4F;IACnF,KAAK,CAAS;IACd,MAAM,CAAe;IAE9B,YAAY,KAAa,EAAE,KAAmB;QAC5C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,OAAO;QAChC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,CAAC,MAAM,CAAC,YAAY,CAAC;QACnB,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;CACF"}
package/dist/errors.d.ts CHANGED
@@ -1,22 +1,19 @@
1
- import type { RecordedError } from './flight-records.ts';
2
- /** The flight that this caller joined in another process failed. `failure` is the error that its leader recorded. */
1
+ import type { Failure } from './protocol/flight-protocol.ts';
2
+ /** The work of the flight that this caller joined failed. `failure` describes the error that its leader got. */
3
3
  export declare class FlightFailedError extends Error {
4
4
  readonly key: string;
5
- readonly failure: RecordedError;
6
- constructor(key: string, failure: RecordedError);
5
+ readonly failure: Failure;
6
+ constructor(key: string, failure: Failure);
7
7
  }
8
8
  /**
9
- * The holder of the flight that this caller joined stopped before the flight
10
- * had an outcome: its process stopped, its lease was lost, or its records
11
- * refused the outcome.
9
+ * The flight that this caller joined ended without an outcome: its leader's
10
+ * process stopped, or its leader lost the flight. The work is never run again
11
+ * for this caller; a new call starts a new flight.
12
12
  */
13
13
  export declare class FlightInterruptedError extends Error {
14
14
  readonly key: string;
15
15
  constructor(key: string);
16
16
  }
17
- /** The record of the flight that this caller joined was gone before the caller read its outcome. */
18
- export declare class FlightOutcomeLostError extends Error {
19
- readonly key: string;
20
- constructor(key: string);
21
- }
17
+ /** The parts of a thrown value that cross to the joiners. A value that is not an error keeps only its text. */
18
+ export declare function failureOf(error: unknown): Failure;
22
19
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,qHAAqH;AACrH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;gBAEpB,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa;CAQhD;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,GAAG,EAAE,MAAM;CAOxB;AAED,oGAAoG;AACpG,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,GAAG,EAAE,MAAM;CAOxB"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAE7D,gHAAgH;AAChH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAEd,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;CAQ1C;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,GAAG,EAAE,MAAM;CAOxB;AAED,+GAA+G;AAC/G,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CASjD"}
package/dist/errors.js CHANGED
@@ -1,4 +1,4 @@
1
- /** The flight that this caller joined in another process failed. `failure` is the error that its leader recorded. */
1
+ /** The work of the flight that this caller joined failed. `failure` describes the error that its leader got. */
2
2
  export class FlightFailedError extends Error {
3
3
  key;
4
4
  failure;
@@ -10,9 +10,9 @@ export class FlightFailedError extends Error {
10
10
  }
11
11
  }
12
12
  /**
13
- * The holder of the flight that this caller joined stopped before the flight
14
- * had an outcome: its process stopped, its lease was lost, or its records
15
- * refused the outcome.
13
+ * The flight that this caller joined ended without an outcome: its leader's
14
+ * process stopped, or its leader lost the flight. The work is never run again
15
+ * for this caller; a new call starts a new flight.
16
16
  */
17
17
  export class FlightInterruptedError extends Error {
18
18
  key;
@@ -22,13 +22,15 @@ export class FlightInterruptedError extends Error {
22
22
  this.key = key;
23
23
  }
24
24
  }
25
- /** The record of the flight that this caller joined was gone before the caller read its outcome. */
26
- export class FlightOutcomeLostError extends Error {
27
- key;
28
- constructor(key) {
29
- super(`The record of the flight of ${JSON.stringify(key)} that this caller joined was gone before the caller read its outcome.`);
30
- this.name = 'FlightOutcomeLostError';
31
- this.key = key;
25
+ /** The parts of a thrown value that cross to the joiners. A value that is not an error keeps only its text. */
26
+ export function failureOf(error) {
27
+ if (!(error instanceof Error)) {
28
+ return { name: 'Error', message: String(error) };
32
29
  }
30
+ const { name, message } = error;
31
+ const code = 'code' in error ? error.code : undefined;
32
+ return typeof code === 'string' || typeof code === 'number'
33
+ ? { name, message, code }
34
+ : { name, message };
33
35
  }
34
36
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,qHAAqH;AACrH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,GAAG,CAAS;IACZ,OAAO,CAAgB;IAEhC,YAAY,GAAW,EAAE,OAAsB;QAC7C,KAAK,CACH,iBAAiB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CACnF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACtC,GAAG,CAAS;IAErB,YAAY,GAAW;QACrB,KAAK,CACH,iBAAiB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,4DAA4D,CACjG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAED,oGAAoG;AACpG,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACtC,GAAG,CAAS;IAErB,YAAY,GAAW;QACrB,KAAK,CACH,+BAA+B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,uEAAuE,CAC1H,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,gHAAgH;AAChH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,GAAG,CAAS;IACZ,OAAO,CAAU;IAE1B,YAAY,GAAW,EAAE,OAAgB;QACvC,KAAK,CACH,iBAAiB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CACnF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACtC,GAAG,CAAS;IAErB,YAAY,GAAW;QACrB,KAAK,CACH,iBAAiB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,4DAA4D,CACjG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAED,+GAA+G;AAC/G,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACnD,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,QAAQ;QACzD,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;QACzB,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AACxB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- export { type FlightValue, type RunOptions, SingleFlight, } from './single-flight.ts';
2
- export type { Finished, FlightRecords, LatestFlight, Outcome, RecordedError, Status, } from './flight-records.ts';
3
- export { FileFlightRecords, type FileFlightRecordsOptions, } from './file-flight-records.ts';
4
- export { SharedFlight, type SharedFlightOptions } from './shared-flight.ts';
5
- export { FlightFailedError, FlightInterruptedError, FlightOutcomeLostError, } from './errors.ts';
1
+ export { type FlightValue, type RunOptions, SingleFlight, type SingleFlightOptions, } from './single-flight.ts';
2
+ export type { Codec } from './codec.ts';
3
+ export type { Failure } from './protocol/flight-protocol.ts';
4
+ export { FlightFailedError, FlightInterruptedError } from './errors.ts';
5
+ export { ProtocolVersionError } from './connection/protocol-version-error.ts';
6
+ export { NetworkDirectoryError } from '@zukhruf/fs';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,OAAO,EACP,aAAa,EACb,MAAM,GACP,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,iBAAiB,EACjB,KAAK,wBAAwB,GAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,YAAY,EACZ,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,YAAY,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { SingleFlight, } from "./single-flight.js";
2
- export { FileFlightRecords, } from "./file-flight-records.js";
3
- export { SharedFlight } from "./shared-flight.js";
4
- export { FlightFailedError, FlightInterruptedError, FlightOutcomeLostError, } from "./errors.js";
2
+ export { FlightFailedError, FlightInterruptedError } from "./errors.js";
3
+ export { ProtocolVersionError } from "./connection/protocol-version-error.js";
4
+ export { NetworkDirectoryError } from '@zukhruf/fs';
5
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,GACb,MAAM,oBAAoB,CAAC;AAS5B,OAAO,EACL,iBAAiB,GAElB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAA4B,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,GAEb,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,94 @@
1
+ /** A leader's error as text, because an Error object cannot cross a process boundary. */
2
+ export interface Failure {
3
+ readonly name: string;
4
+ readonly message: string;
5
+ /** The error's `code`, when it has one, as Node.js errors do. */
6
+ readonly code?: string | number;
7
+ }
8
+ /** How a flight landed: the value as the codec encoded it, or the leader's failure. */
9
+ export type Outcome = {
10
+ value: string;
11
+ } | {
12
+ failure: Failure;
13
+ };
14
+ /** Tokens and flights travel as decimal strings because JSON has no bigint. */
15
+ export type FlightRequest =
16
+ /**
17
+ * Leads the flight of `key` when none is in progress, or joins the flight in
18
+ * progress. After a lost connection, a run that the coordinator had told
19
+ * which flight it joined is sent again with that flight's token: such a
20
+ * rejoin never leads, so a joiner whose leader died never runs the work
21
+ * again. A run that got no answer has joined nothing, and goes again as a
22
+ * plain run.
23
+ */
24
+ {
25
+ op: 'run';
26
+ id: string;
27
+ key: string;
28
+ flight?: string;
29
+ }
30
+ /** The leader of `id` ends its flight with `outcome`. Kept by the client until the coordinator acknowledges it. */
31
+ | {
32
+ op: 'land';
33
+ id: string;
34
+ outcome: Outcome;
35
+ }
36
+ /** Withdraws a run that is not answered yet, or a joiner from its flight. */
37
+ | {
38
+ op: 'cancel';
39
+ id: string;
40
+ }
41
+ /** A leader from before a failover claims its flight again, during the new coordinator's grace window. */
42
+ | {
43
+ op: 'reassert';
44
+ id: string;
45
+ key: string;
46
+ token: string;
47
+ };
48
+ export type FlightResponse =
49
+ /** The run leads a new flight; `token` is its lease's fencing token. */
50
+ {
51
+ op: 'lead';
52
+ id: string;
53
+ token: string;
54
+ }
55
+ /** The run joined the flight in progress, whose leader holds the token `flight`. */
56
+ | {
57
+ op: 'joined';
58
+ id: string;
59
+ flight: string;
60
+ } | {
61
+ op: 'landed';
62
+ id: string;
63
+ outcome: Outcome;
64
+ }
65
+ /** The flight that the run joined ended without an outcome: its leader stopped or lost its lease. */
66
+ | {
67
+ op: 'interrupted';
68
+ id: string;
69
+ }
70
+ /** The coordinator handed a landing to the joiners. */
71
+ | {
72
+ op: 'ack';
73
+ id: string;
74
+ }
75
+ /** The flight of `id` is no longer its leader's: a reassertion or a landing came too late. */
76
+ | {
77
+ op: 'rejected';
78
+ id: string;
79
+ }
80
+ /** The coordinator does not know the request: it runs an older version. */
81
+ | {
82
+ op: 'unsupported';
83
+ id: string;
84
+ };
85
+ /** A request whose `op` this process may not know. A coordinator answers one it does not know with `unsupported`. */
86
+ export interface RequestEnvelope {
87
+ op: string;
88
+ id: string;
89
+ }
90
+ export declare function isRequestEnvelope(message: unknown): message is RequestEnvelope;
91
+ /** Messages arrive from another process, so their shape is checked before use. */
92
+ export declare function isFlightRequest(message: unknown): message is FlightRequest;
93
+ export declare function isFlightResponse(message: unknown): message is FlightResponse;
94
+ //# sourceMappingURL=flight-protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flight-protocol.d.ts","sourceRoot":"","sources":["../../src/protocol/flight-protocol.ts"],"names":[],"mappings":"AAIA,yFAAyF;AACzF,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACjC;AAED,uFAAuF;AACvF,MAAM,MAAM,OAAO,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/D,+EAA+E;AAC/E,MAAM,MAAM,aAAa;AACvB;;;;;;;GAOG;AACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE;AACzD,mHAAmH;GACjH;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE;AAC9C,6EAA6E;GAC3E;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AAC9B,0GAA0G;GACxG;IAAE,EAAE,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D,MAAM,MAAM,cAAc;AACxB,wEAAwE;AACtE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC3C,oFAAoF;GAClF;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE;AAChD,qGAAqG;GACnG;IAAE,EAAE,EAAE,aAAa,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AACnC,uDAAuD;GACrD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AAC3B,8FAA8F;GAC5F;IAAE,EAAE,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE;AAChC,2EAA2E;GACzE;IAAE,EAAE,EAAE,aAAa,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC,qHAAqH;AACrH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAKD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,GACf,OAAO,IAAI,eAAe,CAM5B;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,aAAa,CAiB1E;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,cAAc,CAiB5E"}