@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.
- package/README.md +113 -93
- package/dist/client/flight-client.d.ts +44 -0
- package/dist/client/flight-client.d.ts.map +1 -0
- package/dist/client/flight-client.js +227 -0
- package/dist/client/flight-client.js.map +1 -0
- package/dist/codec.d.ts +12 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +2 -0
- package/dist/codec.js.map +1 -0
- package/dist/connection/connection-supervisor.d.ts +39 -0
- package/dist/connection/connection-supervisor.d.ts.map +1 -0
- package/dist/connection/connection-supervisor.js +195 -0
- package/dist/connection/connection-supervisor.js.map +1 -0
- package/dist/connection/connection.d.ts +21 -0
- package/dist/connection/connection.d.ts.map +1 -0
- package/dist/connection/connection.js +2 -0
- package/dist/connection/connection.js.map +1 -0
- package/dist/connection/connector.d.ts +15 -0
- package/dist/connection/connector.d.ts.map +1 -0
- package/dist/connection/connector.js +2 -0
- package/dist/connection/connector.js.map +1 -0
- package/dist/connection/electing-connector.d.ts +23 -0
- package/dist/connection/electing-connector.d.ts.map +1 -0
- package/dist/connection/electing-connector.js +148 -0
- package/dist/connection/electing-connector.js.map +1 -0
- package/dist/connection/handshake.d.ts +31 -0
- package/dist/connection/handshake.d.ts.map +1 -0
- package/dist/connection/handshake.js +102 -0
- package/dist/connection/handshake.js.map +1 -0
- package/dist/connection/local-directory-connector.d.ts +14 -0
- package/dist/connection/local-directory-connector.d.ts.map +1 -0
- package/dist/connection/local-directory-connector.js +23 -0
- package/dist/connection/local-directory-connector.js.map +1 -0
- package/dist/connection/protocol-version-error.d.ts +12 -0
- package/dist/connection/protocol-version-error.d.ts.map +1 -0
- package/dist/connection/protocol-version-error.js +19 -0
- package/dist/connection/protocol-version-error.js.map +1 -0
- package/dist/connection/socket-connection.d.ts +18 -0
- package/dist/connection/socket-connection.d.ts.map +1 -0
- package/dist/connection/socket-connection.js +52 -0
- package/dist/connection/socket-connection.js.map +1 -0
- package/dist/connection/socket-path.d.ts +7 -0
- package/dist/connection/socket-path.d.ts.map +1 -0
- package/dist/connection/socket-path.js +24 -0
- package/dist/connection/socket-path.js.map +1 -0
- package/dist/coordinator/flight-coordinator.d.ts +33 -0
- package/dist/coordinator/flight-coordinator.d.ts.map +1 -0
- package/dist/coordinator/flight-coordinator.js +178 -0
- package/dist/coordinator/flight-coordinator.js.map +1 -0
- package/dist/coordinator/flight-server.d.ts +24 -0
- package/dist/coordinator/flight-server.d.ts.map +1 -0
- package/dist/coordinator/flight-server.js +74 -0
- package/dist/coordinator/flight-server.js.map +1 -0
- package/dist/coordinator/flight.d.ts +20 -0
- package/dist/coordinator/flight.d.ts.map +1 -0
- package/dist/coordinator/flight.js +39 -0
- package/dist/coordinator/flight.js.map +1 -0
- package/dist/coordinator/party.d.ts +38 -0
- package/dist/coordinator/party.d.ts.map +1 -0
- package/dist/coordinator/party.js +43 -0
- package/dist/coordinator/party.js.map +1 -0
- package/dist/coordinator/session.d.ts +16 -0
- package/dist/coordinator/session.d.ts.map +1 -0
- package/dist/coordinator/session.js +103 -0
- package/dist/coordinator/session.js.map +1 -0
- package/dist/election/leader-election.d.ts +24 -0
- package/dist/election/leader-election.d.ts.map +1 -0
- package/dist/election/leader-election.js +78 -0
- package/dist/election/leader-election.js.map +1 -0
- package/dist/election/leadership.d.ts +14 -0
- package/dist/election/leadership.d.ts.map +1 -0
- package/dist/election/leadership.js +23 -0
- package/dist/election/leadership.js.map +1 -0
- package/dist/errors.d.ts +9 -12
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +13 -11
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/protocol/flight-protocol.d.ts +94 -0
- package/dist/protocol/flight-protocol.d.ts.map +1 -0
- package/dist/protocol/flight-protocol.js +61 -0
- package/dist/protocol/flight-protocol.js.map +1 -0
- package/dist/shared/is-record.d.ts +3 -0
- package/dist/shared/is-record.d.ts.map +1 -0
- package/dist/shared/is-record.js +3 -0
- package/dist/shared/is-record.js.map +1 -0
- package/dist/shared/sqlite/is-busy.d.ts +3 -0
- package/dist/shared/sqlite/is-busy.d.ts.map +1 -0
- package/dist/shared/sqlite/is-busy.js +10 -0
- package/dist/shared/sqlite/is-busy.js.map +1 -0
- package/dist/single-flight.d.ts +43 -9
- package/dist/single-flight.d.ts.map +1 -1
- package/dist/single-flight.js +98 -31
- package/dist/single-flight.js.map +1 -1
- package/package.json +6 -6
- package/dist/file-flight-records.d.ts +0 -22
- package/dist/file-flight-records.d.ts.map +0 -1
- package/dist/file-flight-records.js +0 -124
- package/dist/file-flight-records.js.map +0 -1
- package/dist/flight-records.d.ts +0 -67
- package/dist/flight-records.d.ts.map +0 -1
- package/dist/flight-records.js +0 -38
- package/dist/flight-records.js.map +0 -1
- package/dist/flight.d.ts +0 -12
- package/dist/flight.d.ts.map +0 -1
- package/dist/flight.js +0 -38
- package/dist/flight.js.map +0 -1
- package/dist/follow.d.ts +0 -31
- package/dist/follow.d.ts.map +0 -1
- package/dist/follow.js +0 -60
- package/dist/follow.js.map +0 -1
- package/dist/patiently.d.ts +0 -3
- package/dist/patiently.d.ts.map +0 -1
- package/dist/patiently.js +0 -31
- package/dist/patiently.js.map +0 -1
- package/dist/shared-flight.d.ts +0 -25
- package/dist/shared-flight.d.ts.map +0 -1
- package/dist/shared-flight.js +0 -89
- package/dist/shared-flight.js.map +0 -1
package/dist/flight-records.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/** The parts of a thrown value that a record keeps. A value that is not an error keeps only its text. */
|
|
2
|
-
export function recordedError(error) {
|
|
3
|
-
if (!(error instanceof Error))
|
|
4
|
-
return { name: 'Error', message: String(error) };
|
|
5
|
-
const { name, message } = error;
|
|
6
|
-
const code = 'code' in error ? error.code : undefined;
|
|
7
|
-
return typeof code === 'string' || typeof code === 'number'
|
|
8
|
-
? { name, message, code }
|
|
9
|
-
: { name, message };
|
|
10
|
-
}
|
|
11
|
-
export const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
12
|
-
export function isRecordedError(value) {
|
|
13
|
-
if (!isRecord(value))
|
|
14
|
-
return false;
|
|
15
|
-
const { name, message, code } = value;
|
|
16
|
-
return (typeof name === 'string' &&
|
|
17
|
-
typeof message === 'string' &&
|
|
18
|
-
(code === undefined || typeof code === 'string' || typeof code === 'number'));
|
|
19
|
-
}
|
|
20
|
-
export function isOutcome(value) {
|
|
21
|
-
if (!isRecord(value))
|
|
22
|
-
return false;
|
|
23
|
-
switch (value['status']) {
|
|
24
|
-
case 'running':
|
|
25
|
-
case 'missing':
|
|
26
|
-
return true;
|
|
27
|
-
case 'succeeded':
|
|
28
|
-
return 'value' in value;
|
|
29
|
-
case 'failed':
|
|
30
|
-
return isRecordedError(value['error']);
|
|
31
|
-
case 'interrupted':
|
|
32
|
-
return (value['successor'] === undefined ||
|
|
33
|
-
typeof value['successor'] === 'string');
|
|
34
|
-
default:
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
//# sourceMappingURL=flight-records.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"flight-records.js","sourceRoot":"","sources":["../src/flight-records.ts"],"names":[],"mappings":"AAsDA,yGAAyG;AACzG,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACnD,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;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,KAAc,EAC8B,EAAE,CAC9C,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AAE9C,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACtC,OAAO,CACL,OAAO,IAAI,KAAK,QAAQ;QACxB,OAAO,OAAO,KAAK,QAAQ;QAC3B,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAC7E,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,QAAQ,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxB,KAAK,SAAS,CAAC;QACf,KAAK,SAAS;YACZ,OAAO,IAAI,CAAC;QACd,KAAK,WAAW;YACd,OAAO,OAAO,IAAI,KAAK,CAAC;QAC1B,KAAK,QAAQ;YACX,OAAO,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACzC,KAAK,aAAa;YAChB,OAAO,CACL,KAAK,CAAC,WAAW,CAAC,KAAK,SAAS;gBAChC,OAAO,KAAK,CAAC,WAAW,CAAC,KAAK,QAAQ,CACvC,CAAC;QACJ;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC"}
|
package/dist/flight.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/** One run of the work for a key in this process. Every caller that joins it gets how it ended. */
|
|
2
|
-
export declare class Flight<T> {
|
|
3
|
-
#private;
|
|
4
|
-
constructor(work: (abandoned: AbortSignal) => Promise<T>);
|
|
5
|
-
/** The outcome of the work, once it ended. Never rejects. */
|
|
6
|
-
get ended(): Promise<PromiseSettledResult<Awaited<T>>>;
|
|
7
|
-
/** Aborts when every caller cancelled its wait before the flight ended. */
|
|
8
|
-
get abandoned(): AbortSignal;
|
|
9
|
-
/** Waits for the flight for one caller, until it ends or `signal` aborts. */
|
|
10
|
-
wait(signal: AbortSignal | undefined): Promise<T>;
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=flight.d.ts.map
|
package/dist/flight.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"flight.d.ts","sourceRoot":"","sources":["../src/flight.ts"],"names":[],"mappings":"AAEA,mGAAmG;AACnG,qBAAa,MAAM,CAAC,CAAC;;gBAOP,IAAI,EAAE,CAAC,SAAS,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC;IAMxD,6DAA6D;IAC7D,IAAI,KAAK,IAAI,OAAO,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAErD;IAED,2EAA2E;IAC3E,IAAI,SAAS,IAAI,WAAW,CAE3B;IAED,6EAA6E;IACvE,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC;CAcxD"}
|
package/dist/flight.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { Latch, untilAborted } from '@zukhruf/async';
|
|
2
|
-
/** One run of the work for a key in this process. Every caller that joins it gets how it ended. */
|
|
3
|
-
export class Flight {
|
|
4
|
-
/** Opens with the outcome of the work. An error is data here, so the latch never rejects; each caller throws it. */
|
|
5
|
-
#ended = new Latch();
|
|
6
|
-
#abandoned = new AbortController();
|
|
7
|
-
/** The callers that wait for the flight now. */
|
|
8
|
-
#waiting = 0;
|
|
9
|
-
constructor(work) {
|
|
10
|
-
void Promise.allSettled([Promise.try(work, this.#abandoned.signal)]).then(([outcome]) => this.#ended.open(outcome));
|
|
11
|
-
}
|
|
12
|
-
/** The outcome of the work, once it ended. Never rejects. */
|
|
13
|
-
get ended() {
|
|
14
|
-
return this.#ended.wait();
|
|
15
|
-
}
|
|
16
|
-
/** Aborts when every caller cancelled its wait before the flight ended. */
|
|
17
|
-
get abandoned() {
|
|
18
|
-
return this.#abandoned.signal;
|
|
19
|
-
}
|
|
20
|
-
/** Waits for the flight for one caller, until it ends or `signal` aborts. */
|
|
21
|
-
async wait(signal) {
|
|
22
|
-
this.#waiting++;
|
|
23
|
-
const leave = () => {
|
|
24
|
-
this.#waiting--;
|
|
25
|
-
if (this.#waiting === 0)
|
|
26
|
-
this.#abandoned.abort();
|
|
27
|
-
};
|
|
28
|
-
signal?.addEventListener('abort', leave, { once: true });
|
|
29
|
-
void this.#ended
|
|
30
|
-
.wait()
|
|
31
|
-
.then(() => signal?.removeEventListener('abort', leave));
|
|
32
|
-
const outcome = await untilAborted(this.#ended.wait(), signal);
|
|
33
|
-
if (outcome.status === 'rejected')
|
|
34
|
-
throw outcome.reason;
|
|
35
|
-
return outcome.value;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
//# sourceMappingURL=flight.js.map
|
package/dist/flight.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"flight.js","sourceRoot":"","sources":["../src/flight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAErD,mGAAmG;AACnG,MAAM,OAAO,MAAM;IACjB,oHAAoH;IAC3G,MAAM,GAAG,IAAI,KAAK,EAAoC,CAAC;IACvD,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IAC5C,gDAAgD;IAChD,QAAQ,GAAG,CAAC,CAAC;IAEb,YAAY,IAA4C;QACtD,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CACvE,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CACzC,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC5B,CAAC;IAED,2EAA2E;IAC3E,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;IAChC,CAAC;IAED,6EAA6E;IAC7E,KAAK,CAAC,IAAI,CAAC,MAA+B;QACxC,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChB,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC;gBAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACnD,CAAC,CAAC;QACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,KAAK,IAAI,CAAC,MAAM;aACb,IAAI,EAAE;aACN,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;QAC/D,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU;YAAE,MAAM,OAAO,CAAC,MAAM,CAAC;QACxD,OAAO,OAAO,CAAC,KAAK,CAAC;IACvB,CAAC;CACF"}
|
package/dist/follow.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { Mutex } from '@zukhruf/mutex';
|
|
2
|
-
import type { FlightRecords, LatestFlight } from './flight-records.ts';
|
|
3
|
-
/** What a joiner watches while it waits for a flight in another process, and how often it looks. */
|
|
4
|
-
export interface Watch {
|
|
5
|
-
readonly records: FlightRecords;
|
|
6
|
-
readonly mutex: Mutex;
|
|
7
|
-
readonly key: string;
|
|
8
|
-
readonly pollInterval: number;
|
|
9
|
-
/** Aborts when no caller in this process waits any more: the watch then stops. */
|
|
10
|
-
readonly abandoned: AbortSignal;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* The flight that holds the key now, found by its record. `undefined` when
|
|
14
|
-
* the key became free and no new flight began, so the caller can try to lead.
|
|
15
|
-
*/
|
|
16
|
-
export declare function findFlight({ records, mutex, key, pollInterval, abandoned }: Watch, before: LatestFlight | undefined): Promise<string | undefined>;
|
|
17
|
-
/** How following ended: with the value of the flight, or with no flight to follow, so the caller tries to lead. */
|
|
18
|
-
export type Followed = {
|
|
19
|
-
readonly followed: true;
|
|
20
|
-
readonly value: unknown;
|
|
21
|
-
} | {
|
|
22
|
-
readonly followed: false;
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Waits for flight `id` to end and gives its value. It reads the record, not
|
|
26
|
-
* the lock: back-to-back holders can keep the key held at every look, so
|
|
27
|
-
* waiting for a free key could wait forever. The lock only tells whether a
|
|
28
|
-
* running flight still has a holder.
|
|
29
|
-
*/
|
|
30
|
-
export declare function follow({ records, mutex, key, pollInterval, abandoned }: Watch, first: string): Promise<Followed>;
|
|
31
|
-
//# sourceMappingURL=follow.d.ts.map
|
package/dist/follow.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"follow.d.ts","sourceRoot":"","sources":["../src/follow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAO5C,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEvE,oGAAoG;AACpG,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;CACjC;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,KAAK,EACvD,MAAM,EAAE,YAAY,GAAG,SAAS,GAC/B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B;AAED,mHAAmH;AACnH,MAAM,MAAM,QAAQ,GAChB;IAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAA;CAAE,CAAC;AAEjC;;;;;GAKG;AACH,wBAAsB,MAAM,CAC1B,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,KAAK,EACvD,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,QAAQ,CAAC,CAkCnB"}
|
package/dist/follow.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
-
import { FlightFailedError, FlightInterruptedError, FlightOutcomeLostError, } from "./errors.js";
|
|
3
|
-
/**
|
|
4
|
-
* The flight that holds the key now, found by its record. `undefined` when
|
|
5
|
-
* the key became free and no new flight began, so the caller can try to lead.
|
|
6
|
-
*/
|
|
7
|
-
export async function findFlight({ records, mutex, key, pollInterval, abandoned }, before) {
|
|
8
|
-
for (;;) {
|
|
9
|
-
await delay(pollInterval, undefined, { signal: abandoned });
|
|
10
|
-
const now = await records.latest(key);
|
|
11
|
-
if (now && (now.id !== before?.id || before.status === 'running'))
|
|
12
|
-
return now.id;
|
|
13
|
-
if (!(await mutex.isHeld(key)))
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Waits for flight `id` to end and gives its value. It reads the record, not
|
|
19
|
-
* the lock: back-to-back holders can keep the key held at every look, so
|
|
20
|
-
* waiting for a free key could wait forever. The lock only tells whether a
|
|
21
|
-
* running flight still has a holder.
|
|
22
|
-
*/
|
|
23
|
-
export async function follow({ records, mutex, key, pollInterval, abandoned }, first) {
|
|
24
|
-
let id = first;
|
|
25
|
-
let seenHeld = false;
|
|
26
|
-
for (;;) {
|
|
27
|
-
const outcome = await records.outcome(key, id);
|
|
28
|
-
switch (outcome.status) {
|
|
29
|
-
case 'succeeded':
|
|
30
|
-
return { followed: true, value: outcome.value };
|
|
31
|
-
case 'failed':
|
|
32
|
-
throw new FlightFailedError(key, outcome.error);
|
|
33
|
-
case 'interrupted':
|
|
34
|
-
if (outcome.successor === undefined)
|
|
35
|
-
throw new FlightInterruptedError(key);
|
|
36
|
-
// The next holder closed a flight whose holder stopped, and began its own.
|
|
37
|
-
id = outcome.successor;
|
|
38
|
-
seenHeld = false;
|
|
39
|
-
continue;
|
|
40
|
-
case 'running':
|
|
41
|
-
if (await mutex.isHeld(key)) {
|
|
42
|
-
seenHeld = true;
|
|
43
|
-
await delay(pollInterval, undefined, { signal: abandoned });
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
// The holder can finish between the two looks: read the record again.
|
|
47
|
-
if ((await records.outcome(key, id)).status !== 'running')
|
|
48
|
-
continue;
|
|
49
|
-
// A record that was running before this joiner came was left by a
|
|
50
|
-
// holder that stopped long ago: there is no flight to join.
|
|
51
|
-
if (!seenHeld)
|
|
52
|
-
return { followed: false };
|
|
53
|
-
// This joiner saw the holder run the flight, and the holder stopped.
|
|
54
|
-
throw new FlightInterruptedError(key);
|
|
55
|
-
case 'missing':
|
|
56
|
-
throw new FlightOutcomeLostError(key);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
//# sourceMappingURL=follow.js.map
|
package/dist/follow.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"follow.js","sourceRoot":"","sources":["../src/follow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAI3D,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAarB;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAS,EACvD,MAAgC;IAEhC,SAAS,CAAC;QACR,MAAM,KAAK,CAAC,YAAY,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC5D,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,MAAM,EAAE,EAAE,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC;YAC/D,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAAE,OAAO,SAAS,CAAC;IACnD,CAAC;AACH,CAAC;AAOD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAS,EACvD,KAAa;IAEb,IAAI,EAAE,GAAG,KAAK,CAAC;IACf,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,SAAS,CAAC;QACR,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC/C,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;YACvB,KAAK,WAAW;gBACd,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;YAClD,KAAK,QAAQ;gBACX,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YAClD,KAAK,aAAa;gBAChB,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;oBACjC,MAAM,IAAI,sBAAsB,CAAC,GAAG,CAAC,CAAC;gBACxC,2EAA2E;gBAC3E,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;gBACvB,QAAQ,GAAG,KAAK,CAAC;gBACjB,SAAS;YACX,KAAK,SAAS;gBACZ,IAAI,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC5B,QAAQ,GAAG,IAAI,CAAC;oBAChB,MAAM,KAAK,CAAC,YAAY,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;oBAC5D,SAAS;gBACX,CAAC;gBACD,sEAAsE;gBACtE,IAAI,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS;oBAAE,SAAS;gBACpE,kEAAkE;gBAClE,4DAA4D;gBAC5D,IAAI,CAAC,QAAQ;oBAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;gBAC1C,qEAAqE;gBACrE,MAAM,IAAI,sBAAsB,CAAC,GAAG,CAAC,CAAC;YACxC,KAAK,SAAS;gBACZ,MAAM,IAAI,sBAAsB,CAAC,GAAG,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/patiently.d.ts
DELETED
package/dist/patiently.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"patiently.d.ts","sourceRoot":"","sources":["../src/patiently.ts"],"names":[],"mappings":"AAoBA,uFAAuF;AACvF,wBAAsB,SAAS,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAc1E"}
|
package/dist/patiently.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
-
/** How long to retry a file operation that Windows refuses for a moment. */
|
|
3
|
-
const WINDOWS_PATIENCE = 1000;
|
|
4
|
-
/**
|
|
5
|
-
* Windows refuses a file for a moment while another process has it open, for
|
|
6
|
-
* example a joiner that reads the record while the holder replaces it. The
|
|
7
|
-
* error code is the same as for a real permission denial, so a refusal is
|
|
8
|
-
* retried only for a limited time.
|
|
9
|
-
*/
|
|
10
|
-
function isRefusedForNow(error) {
|
|
11
|
-
return (process.platform === 'win32' &&
|
|
12
|
-
error instanceof Error &&
|
|
13
|
-
'code' in error &&
|
|
14
|
-
['EPERM', 'EACCES', 'EBUSY'].includes(String(error.code)));
|
|
15
|
-
}
|
|
16
|
-
/** Runs `operation`, and runs it again while Windows refuses the file for a moment. */
|
|
17
|
-
export async function patiently(operation) {
|
|
18
|
-
const started = performance.now();
|
|
19
|
-
for (let attempt = 0;; attempt++) {
|
|
20
|
-
try {
|
|
21
|
-
return await operation();
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
if (!isRefusedForNow(error) ||
|
|
25
|
-
performance.now() - started > WINDOWS_PATIENCE)
|
|
26
|
-
throw error;
|
|
27
|
-
await delay(Math.min(5 * 2 ** attempt, 100));
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=patiently.js.map
|
package/dist/patiently.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"patiently.js","sourceRoot":"","sources":["../src/patiently.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE3D,4EAA4E;AAC5E,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B;;;;;GAKG;AACH,SAAS,eAAe,CAAC,KAAc;IACrC,OAAO,CACL,OAAO,CAAC,QAAQ,KAAK,OAAO;QAC5B,KAAK,YAAY,KAAK;QACtB,MAAM,IAAI,KAAK;QACf,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAC1D,CAAC;AACJ,CAAC;AAED,uFAAuF;AACvF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAI,SAA2B;IAC5D,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAClC,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,OAAO,MAAM,SAAS,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IACE,CAAC,eAAe,CAAC,KAAK,CAAC;gBACvB,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,gBAAgB;gBAE9C,MAAM,KAAK,CAAC;YACd,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/shared-flight.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { type Lease, type Mutex } from '@zukhruf/mutex';
|
|
2
|
-
import { type FlightRecords } from './flight-records.ts';
|
|
3
|
-
import { type FlightValue, type RunOptions } from './single-flight.ts';
|
|
4
|
-
export interface SharedFlightOptions<T> {
|
|
5
|
-
/** Decides who flies: only the holder of the key runs the work. */
|
|
6
|
-
mutex: Mutex;
|
|
7
|
-
/** Where the outcomes live, so callers in other processes can join. Never the lock directory of the mutex. */
|
|
8
|
-
records: FlightRecords;
|
|
9
|
-
/** Turns a value read back from a record into `T`. Every caller, the leader too, gets `parse` of the value as JSON. */
|
|
10
|
-
parse: (value: unknown) => T;
|
|
11
|
-
/** Milliseconds between two looks at a record while a caller joins. Defaults to 100. */
|
|
12
|
-
pollInterval?: number;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Callers of one key share the flight in progress, in this process and in
|
|
16
|
-
* other processes. Callers in this process share one flight first. The holder
|
|
17
|
-
* of the key leads the flight, and the records let callers in other
|
|
18
|
-
* processes join it.
|
|
19
|
-
*/
|
|
20
|
-
export declare class SharedFlight<T> {
|
|
21
|
-
#private;
|
|
22
|
-
constructor({ mutex, records, parse, pollInterval, }: SharedFlightOptions<T>);
|
|
23
|
-
run(key: string, work: (lease: Lease) => Promise<T>, options?: RunOptions): Promise<FlightValue<T>>;
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=shared-flight.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"shared-flight.d.ts","sourceRoot":"","sources":["../src/shared-flight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAS,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE/D,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,qBAAqB,CAAC;AAExE,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,UAAU,EAEhB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,mEAAmE;IACnE,KAAK,EAAE,KAAK,CAAC;IACb,8GAA8G;IAC9G,OAAO,EAAE,aAAa,CAAC;IACvB,uHAAuH;IACvH,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,CAAC;IAC7B,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAcD;;;;;GAKG;AACH,qBAAa,YAAY,CAAC,CAAC;;gBAOb,EACV,KAAK,EACL,OAAO,EACP,KAAK,EACL,YAAkB,GACnB,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAYnB,GAAG,CACP,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,EAClC,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;CA0E3B"}
|
package/dist/shared-flight.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { Modes } from '@zukhruf/mutex';
|
|
2
|
-
import { recordedError } from "./flight-records.js";
|
|
3
|
-
import { findFlight, follow } from "./follow.js";
|
|
4
|
-
import { SingleFlight, } from "./single-flight.js";
|
|
5
|
-
const skipIfBusy = Modes.skipIfBusy();
|
|
6
|
-
/** Values travel as JSON, so every caller gets the value in the shape that a joiner in another process reads. */
|
|
7
|
-
const encode = (value) => JSON.parse(JSON.stringify(value) ?? 'null');
|
|
8
|
-
/**
|
|
9
|
-
* Callers of one key share the flight in progress, in this process and in
|
|
10
|
-
* other processes. Callers in this process share one flight first. The holder
|
|
11
|
-
* of the key leads the flight, and the records let callers in other
|
|
12
|
-
* processes join it.
|
|
13
|
-
*/
|
|
14
|
-
export class SharedFlight {
|
|
15
|
-
#local = new SingleFlight();
|
|
16
|
-
#mutex;
|
|
17
|
-
#records;
|
|
18
|
-
#parse;
|
|
19
|
-
#pollInterval;
|
|
20
|
-
constructor({ mutex, records, parse, pollInterval = 100, }) {
|
|
21
|
-
if (!(pollInterval >= 0) || !Number.isFinite(pollInterval)) {
|
|
22
|
-
throw new RangeError(`pollInterval must be a finite number of milliseconds >= 0, got ${pollInterval}.`);
|
|
23
|
-
}
|
|
24
|
-
this.#mutex = mutex;
|
|
25
|
-
this.#records = records;
|
|
26
|
-
this.#parse = parse;
|
|
27
|
-
this.#pollInterval = pollInterval;
|
|
28
|
-
}
|
|
29
|
-
async run(key, work, options = {}) {
|
|
30
|
-
const { value, joined } = await this.#local.run(key, (abandoned) => this.#leadOrFollow(key, work, options.onJoin, abandoned), options);
|
|
31
|
-
return { value: value.value, joined: joined || value.followed };
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Runs for the first caller in this process. The callers that joined it
|
|
35
|
-
* here heard `onJoin` already; this caller hears it once it joins a flight
|
|
36
|
-
* of another process. Once no caller here waits (`abandoned`), it stops
|
|
37
|
-
* looking and never takes the key; a flight that it leads runs to its end,
|
|
38
|
-
* because callers in other processes can wait for its outcome.
|
|
39
|
-
*/
|
|
40
|
-
async #leadOrFollow(key, work, onJoin, abandoned) {
|
|
41
|
-
const watch = {
|
|
42
|
-
records: this.#records,
|
|
43
|
-
mutex: this.#mutex,
|
|
44
|
-
key,
|
|
45
|
-
pollInterval: this.#pollInterval,
|
|
46
|
-
abandoned,
|
|
47
|
-
};
|
|
48
|
-
let told = false;
|
|
49
|
-
for (;;) {
|
|
50
|
-
const before = await this.#records.latest(key);
|
|
51
|
-
const led = await this.#mutex.acquire(key, (lease) => this.#lead(key, work, lease), { mode: skipIfBusy, signal: abandoned });
|
|
52
|
-
if (led.acquired)
|
|
53
|
-
return { value: led.value, followed: false };
|
|
54
|
-
const id = await findFlight(watch, before);
|
|
55
|
-
if (id === undefined)
|
|
56
|
-
continue;
|
|
57
|
-
if (!told)
|
|
58
|
-
onJoin?.();
|
|
59
|
-
told = true;
|
|
60
|
-
const followed = await follow(watch, id);
|
|
61
|
-
if (followed.followed)
|
|
62
|
-
return { value: this.#parse(followed.value), followed: true };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
async #lead(key, work, lease) {
|
|
66
|
-
const id = await this.#records.begin(key);
|
|
67
|
-
let value;
|
|
68
|
-
try {
|
|
69
|
-
// A value that JSON cannot carry fails the flight: no joiner could read it.
|
|
70
|
-
value = encode(await work(lease));
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
await this.#records.finish(key, id, {
|
|
74
|
-
status: 'failed',
|
|
75
|
-
error: recordedError(error),
|
|
76
|
-
});
|
|
77
|
-
throw error;
|
|
78
|
-
}
|
|
79
|
-
// Another holder may have run the work too: the value is not the outcome of one flight.
|
|
80
|
-
if (lease.signal.aborted) {
|
|
81
|
-
await this.#records.finish(key, id, { status: 'interrupted' });
|
|
82
|
-
throw lease.signal.reason;
|
|
83
|
-
}
|
|
84
|
-
// A throw here rejects the leader, although the work did its side effects.
|
|
85
|
-
await this.#records.finish(key, id, { status: 'succeeded', value });
|
|
86
|
-
return this.#parse(value);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
//# sourceMappingURL=shared-flight.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"shared-flight.js","sourceRoot":"","sources":["../src/shared-flight.ts"],"names":[],"mappings":"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"}
|