@zukhruf/mutex 0.3.39 → 0.3.41

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 (89) hide show
  1. package/README.md +26 -14
  2. package/dist/index.d.ts +0 -15
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +0 -15
  5. package/dist/index.js.map +1 -1
  6. package/dist/lock-stores/docker/docker-engine-store.d.ts +37 -0
  7. package/dist/lock-stores/docker/docker-engine-store.d.ts.map +1 -0
  8. package/dist/lock-stores/docker/docker-engine-store.js +312 -0
  9. package/dist/lock-stores/docker/docker-engine-store.js.map +1 -0
  10. package/dist/lock-stores/docker/index.d.ts +2 -0
  11. package/dist/lock-stores/docker/index.d.ts.map +1 -0
  12. package/dist/lock-stores/docker/index.js +2 -0
  13. package/dist/lock-stores/docker/index.js.map +1 -0
  14. package/dist/lock-stores/file-system/index.d.ts +5 -0
  15. package/dist/lock-stores/file-system/index.d.ts.map +1 -0
  16. package/dist/lock-stores/file-system/index.js +5 -0
  17. package/dist/lock-stores/file-system/index.js.map +1 -0
  18. package/dist/lock-stores/ipc/index.d.ts +5 -0
  19. package/dist/lock-stores/ipc/index.d.ts.map +1 -0
  20. package/dist/lock-stores/ipc/index.js +5 -0
  21. package/dist/lock-stores/ipc/index.js.map +1 -0
  22. package/dist/lock-stores/mariadb/index.d.ts +2 -0
  23. package/dist/lock-stores/mariadb/index.d.ts.map +1 -0
  24. package/dist/lock-stores/mariadb/index.js +2 -0
  25. package/dist/lock-stores/mariadb/index.js.map +1 -0
  26. package/dist/lock-stores/mariadb/mariadb-store.d.ts +33 -0
  27. package/dist/lock-stores/mariadb/mariadb-store.d.ts.map +1 -0
  28. package/dist/lock-stores/mariadb/mariadb-store.js +36 -0
  29. package/dist/lock-stores/mariadb/mariadb-store.js.map +1 -0
  30. package/dist/lock-stores/memory/index.d.ts +2 -0
  31. package/dist/lock-stores/memory/index.d.ts.map +1 -0
  32. package/dist/lock-stores/memory/index.js +2 -0
  33. package/dist/lock-stores/memory/index.js.map +1 -0
  34. package/dist/lock-stores/mysql/get-lock-dialect.d.ts +46 -0
  35. package/dist/lock-stores/mysql/get-lock-dialect.d.ts.map +1 -0
  36. package/dist/lock-stores/mysql/get-lock-dialect.js +80 -0
  37. package/dist/lock-stores/mysql/get-lock-dialect.js.map +1 -0
  38. package/dist/lock-stores/mysql/index.d.ts +2 -0
  39. package/dist/lock-stores/mysql/index.d.ts.map +1 -0
  40. package/dist/lock-stores/mysql/index.js +2 -0
  41. package/dist/lock-stores/mysql/index.js.map +1 -0
  42. package/dist/lock-stores/mysql/mysql-store.d.ts +37 -0
  43. package/dist/lock-stores/mysql/mysql-store.d.ts.map +1 -0
  44. package/dist/lock-stores/mysql/mysql-store.js +35 -0
  45. package/dist/lock-stores/mysql/mysql-store.js.map +1 -0
  46. package/dist/lock-stores/postgres/index.d.ts +2 -0
  47. package/dist/lock-stores/postgres/index.d.ts.map +1 -0
  48. package/dist/lock-stores/postgres/index.js +2 -0
  49. package/dist/lock-stores/postgres/index.js.map +1 -0
  50. package/dist/lock-stores/postgres/postgres-store.d.ts +41 -0
  51. package/dist/lock-stores/postgres/postgres-store.d.ts.map +1 -0
  52. package/dist/lock-stores/postgres/postgres-store.js +83 -0
  53. package/dist/lock-stores/postgres/postgres-store.js.map +1 -0
  54. package/dist/lock-stores/session/session-lock-store.d.ts +72 -0
  55. package/dist/lock-stores/session/session-lock-store.d.ts.map +1 -0
  56. package/dist/lock-stores/session/session-lock-store.js +221 -0
  57. package/dist/lock-stores/session/session-lock-store.js.map +1 -0
  58. package/dist/lock-stores/socket/index.d.ts +7 -0
  59. package/dist/lock-stores/socket/index.d.ts.map +1 -0
  60. package/dist/lock-stores/socket/index.js +7 -0
  61. package/dist/lock-stores/socket/index.js.map +1 -0
  62. package/dist/lock-stores/sqlite/index.d.ts +4 -0
  63. package/dist/lock-stores/sqlite/index.d.ts.map +1 -0
  64. package/dist/lock-stores/sqlite/index.js +3 -0
  65. package/dist/lock-stores/sqlite/index.js.map +1 -0
  66. package/dist/lock-stores/sqlite/sqlite-store.d.ts.map +1 -1
  67. package/dist/lock-stores/sqlite/sqlite-store.js +6 -25
  68. package/dist/lock-stores/sqlite/sqlite-store.js.map +1 -1
  69. package/dist/lock-stores/sqlserver/index.d.ts +2 -0
  70. package/dist/lock-stores/sqlserver/index.d.ts.map +1 -0
  71. package/dist/lock-stores/sqlserver/index.js +2 -0
  72. package/dist/lock-stores/sqlserver/index.js.map +1 -0
  73. package/dist/lock-stores/sqlserver/sqlserver-store.d.ts +40 -0
  74. package/dist/lock-stores/sqlserver/sqlserver-store.d.ts.map +1 -0
  75. package/dist/lock-stores/sqlserver/sqlserver-store.js +104 -0
  76. package/dist/lock-stores/sqlserver/sqlserver-store.js.map +1 -0
  77. package/dist/lock-stores/thread/index.d.ts +5 -0
  78. package/dist/lock-stores/thread/index.d.ts.map +1 -0
  79. package/dist/lock-stores/thread/index.js +5 -0
  80. package/dist/lock-stores/thread/index.js.map +1 -0
  81. package/dist/shared/in-process-turns.d.ts +17 -0
  82. package/dist/shared/in-process-turns.d.ts.map +1 -0
  83. package/dist/shared/in-process-turns.js +39 -0
  84. package/dist/shared/in-process-turns.js.map +1 -0
  85. package/dist/shared/key-digest.d.ts +8 -0
  86. package/dist/shared/key-digest.d.ts.map +1 -0
  87. package/dist/shared/key-digest.js +11 -0
  88. package/dist/shared/key-digest.js.map +1 -0
  89. package/package.json +30 -8
@@ -0,0 +1,36 @@
1
+ import { getLockDialect, lockName, quoteName, } from "../mysql/get-lock-dialect.js";
2
+ import { SessionLockStore } from "../session/session-lock-store.js";
3
+ /**
4
+ * MariaDB's longest `GET_LOCK` timeout that the store uses: one year, in
5
+ * seconds. A negative timeout gives NULL, and a timeout that ends after
6
+ * 2^64 nanoseconds since 1970 gives 0 at once.
7
+ */
8
+ const oneYear = 31_536_000;
9
+ /**
10
+ * Holds a MariaDB user lock (`GET_LOCK`) for each key, on a session of its
11
+ * own. The server releases the lock when the session ends, so a holder that
12
+ * stops never leaves a key locked. Every process on every machine that
13
+ * reaches one MariaDB database shares the locks.
14
+ */
15
+ export class MariadbStore extends SessionLockStore {
16
+ constructor({ connect, sequence, heartbeatInterval }) {
17
+ super({ connect, heartbeatInterval }, getLockDialect(mariadbServer(quoteName(sequence))));
18
+ }
19
+ }
20
+ function mariadbServer(sequence) {
21
+ return {
22
+ server: 'MariaDB',
23
+ // The session's max_statement_time makes a waiting GET_LOCK give NULL.
24
+ waitStatement: `SET STATEMENT max_statement_time = 0 FOR SELECT GET_LOCK(${lockName}, ${oneYear}) AS locked`,
25
+ run: (session, sql, values) => session.query(sql, values),
26
+ giveBack: (session) => void session.release(),
27
+ mint: async (run) => {
28
+ const row = await run(`SELECT CAST(NEXTVAL(${sequence}) AS CHAR) AS token`, []);
29
+ if (typeof row?.['token'] !== 'string') {
30
+ throw new Error(`NEXTVAL of sequence ${sequence} gave no number.`);
31
+ }
32
+ return BigInt(row['token']);
33
+ },
34
+ };
35
+ }
36
+ //# sourceMappingURL=mariadb-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mariadb-store.js","sourceRoot":"","sources":["../../../src/lock-stores/mariadb/mariadb-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,cAAc,EACd,QAAQ,EACR,SAAS,GACV,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAyBpE;;;;GAIG;AACH,MAAM,OAAO,GAAG,UAAU,CAAC;AAE3B;;;;;GAKG;AACH,MAAM,OAAO,YAAa,SAAQ,gBAAgC;IAChE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAuB;QACvE,KAAK,CACH,EAAE,OAAO,EAAE,iBAAiB,EAAE,EAC9B,cAAc,CAAC,aAAa,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CACnD,CAAC;IACJ,CAAC;CACF;AAED,SAAS,aAAa,CAAC,QAAgB;IACrC,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,uEAAuE;QACvE,aAAa,EAAE,4DAA4D,QAAQ,KAAK,OAAO,aAAa;QAC5G,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC;QACzD,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,OAAO,EAAE;QAC7C,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,GAAG,GAAG,MAAM,GAAG,CACnB,uBAAuB,QAAQ,qBAAqB,EACpD,EAAE,CACH,CAAC;YACF,IAAI,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,kBAAkB,CAAC,CAAC;YACrE,CAAC;YACD,OAAO,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { MemoryStore, type MemoryStoreOptions } from './memory-store.ts';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/memory/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { MemoryStore } from "./memory-store.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lock-stores/memory/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA2B,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { SessionLockDialect } from '../session/session-lock-store.ts';
2
+ /** The events and the close of one connection to a server of the MySQL family: the parts that both drivers share. */
3
+ export interface GetLockSession {
4
+ on(event: 'error', listener: (error: Error) => void): unknown;
5
+ on(event: 'end', listener: () => void): unknown;
6
+ off(event: 'error' | 'end', listener: (error: Error) => void): unknown;
7
+ /** Closes the connection; the server then releases each user lock of the session. */
8
+ destroy(): void;
9
+ }
10
+ export type Row = Record<string, unknown>;
11
+ /** Runs one statement on the session and gives its first row, if it gives rows. */
12
+ export type RunStatement = (sql: string, values: unknown[]) => Promise<Row | undefined>;
13
+ /** What differs between MySQL and MariaDB, and between their drivers. */
14
+ export interface GetLockServer<Session extends GetLockSession> {
15
+ /** The name of the server in error messages. */
16
+ readonly server: string;
17
+ /**
18
+ * `SELECT GET_LOCK(${lockName}, <timeout>) AS locked`, with the longest timeout of
19
+ * the server, and free of the time limit of a statement of the session.
20
+ */
21
+ readonly waitStatement: string;
22
+ /** Runs a statement with the driver, and gives the result: the rows of a SELECT. */
23
+ run(session: Session, sql: string, values: unknown[]): Promise<unknown>;
24
+ /** Gives a session that holds nothing back to its pool. */
25
+ giveBack(session: Session): void;
26
+ /** Gives the next fencing token from the object that the caller created. */
27
+ mint(run: RunStatement): Promise<bigint>;
28
+ }
29
+ /**
30
+ * The name of the user lock, made on the server from the database of the
31
+ * session and the digest of the key (the one `?`). A user lock belongs to
32
+ * the whole server, but the token source belongs to one database, so the
33
+ * lock belongs to one database too. SHA-256 gives 64 lowercase hex
34
+ * characters: the limit of MySQL, and the same under a comparison with or
35
+ * without case. With no database, the name is NULL, and the statement fails.
36
+ */
37
+ export declare const lockName = "SHA2(CONCAT(DATABASE(), ':', ?), 256)";
38
+ /**
39
+ * The dialect of a user lock (`GET_LOCK`) for each key. The name of each lock
40
+ * includes the database of the session, so the locks belong to one database.
41
+ * The client sends the key digest as 64 lowercase hex characters.
42
+ */
43
+ export declare function getLockDialect<Session extends GetLockSession>(server: GetLockServer<Session>): SessionLockDialect<Session>;
44
+ /** Quotes one identifier for MySQL and MariaDB. */
45
+ export declare function quoteName(name: string): string;
46
+ //# sourceMappingURL=get-lock-dialect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-lock-dialect.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/mysql/get-lock-dialect.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAE3E,qHAAqH;AACrH,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;IAC9D,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;IAChD,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;IACvE,qFAAqF;IACrF,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C,mFAAmF;AACnF,MAAM,MAAM,YAAY,GAAG,CACzB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,EAAE,KACd,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC;AAE9B,yEAAyE;AACzE,MAAM,WAAW,aAAa,CAAC,OAAO,SAAS,cAAc;IAC3D,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,oFAAoF;IACpF,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxE,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,4EAA4E;IAC5E,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,0CAA0C,CAAC;AAehE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,SAAS,cAAc,EAC3D,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,GAC7B,kBAAkB,CAAC,OAAO,CAAC,CA+D7B;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C"}
@@ -0,0 +1,80 @@
1
+ import { untilAborted } from '@zukhruf/async';
2
+ import { keyDigest } from "../../shared/key-digest.js";
3
+ /**
4
+ * The name of the user lock, made on the server from the database of the
5
+ * session and the digest of the key (the one `?`). A user lock belongs to
6
+ * the whole server, but the token source belongs to one database, so the
7
+ * lock belongs to one database too. SHA-256 gives 64 lowercase hex
8
+ * characters: the limit of MySQL, and the same under a comparison with or
9
+ * without case. With no database, the name is NULL, and the statement fails.
10
+ */
11
+ export const lockName = "SHA2(CONCAT(DATABASE(), ':', ?), 256)";
12
+ /** `GET_LOCK` gives 1 for a grant and 0 for a timeout; NULL is an error, for example a killed statement. */
13
+ function granted(row, server) {
14
+ const locked = row?.['locked'];
15
+ if (locked === null || locked === undefined) {
16
+ throw new Error(`GET_LOCK of ${server} gave NULL.`);
17
+ }
18
+ return Number(locked) === 1;
19
+ }
20
+ function isRow(value) {
21
+ return typeof value === 'object' && value !== null;
22
+ }
23
+ /**
24
+ * The dialect of a user lock (`GET_LOCK`) for each key. The name of each lock
25
+ * includes the database of the session, so the locks belong to one database.
26
+ * The client sends the key digest as 64 lowercase hex characters.
27
+ */
28
+ export function getLockDialect(server) {
29
+ const runOn = (session) => async (sql, values) => {
30
+ const result = await server.run(session, sql, values);
31
+ if (!Array.isArray(result))
32
+ return undefined;
33
+ const first = result[0];
34
+ return isRow(first) ? first : undefined;
35
+ };
36
+ return {
37
+ server: server.server,
38
+ lockName: (key) => keyDigest(key).toString('hex'),
39
+ // A timeout of `GET_LOCK` (MariaDB's longest is one year) waits again.
40
+ wait: async (session, { name }, signal) => {
41
+ for (;;) {
42
+ const row = await untilAborted(runOn(session)(server.waitStatement, [name]), signal);
43
+ if (granted(row, server.server))
44
+ return;
45
+ }
46
+ },
47
+ try: async (session, { name }) => granted(await runOn(session)(`SELECT GET_LOCK(${lockName}, 0) AS locked`, [
48
+ name,
49
+ ]), server.server),
50
+ mint: (session) => server.mint(runOn(session)),
51
+ // `IS_USED_LOCK` never acquires the lock. It gives NULL for a free lock.
52
+ holds: async (session, { name }) => {
53
+ const row = await runOn(session)(`SELECT IS_USED_LOCK(${lockName}) = CONNECTION_ID() AS held`, [name]);
54
+ return Number(row?.['held']) === 1;
55
+ },
56
+ release: async (session, { name }) => {
57
+ const row = await runOn(session)(`SELECT RELEASE_LOCK(${lockName}) AS released`, [name]);
58
+ return Number(row?.['released']) === 1;
59
+ },
60
+ /** Reads `IS_USED_LOCK`, which never acquires the lock. A session with no database fails. */
61
+ isHeld: async (session, { name }) => {
62
+ const row = await runOn(session)(`SELECT IS_USED_LOCK(${lockName}) AS holder, DATABASE() AS db`, [name]);
63
+ if (row?.['db'] === null) {
64
+ throw new Error(`The ${server.server} session has no database.`);
65
+ }
66
+ return row?.['holder'] !== null;
67
+ },
68
+ detach: (session, { error, end }) => {
69
+ session.off('error', error);
70
+ session.off('end', end);
71
+ },
72
+ giveBack: (session) => server.giveBack(session),
73
+ destroy: (session) => session.destroy(),
74
+ };
75
+ }
76
+ /** Quotes one identifier for MySQL and MariaDB. */
77
+ export function quoteName(name) {
78
+ return `\`${name.replaceAll('`', '``')}\``;
79
+ }
80
+ //# sourceMappingURL=get-lock-dialect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-lock-dialect.js","sourceRoot":"","sources":["../../../src/lock-stores/mysql/get-lock-dialect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { MysqlStore, type MysqlSession, type MysqlStoreOptions, } from './mysql-store.ts';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/mysql/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { MysqlStore, } from "./mysql-store.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lock-stores/mysql/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,GAGX,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { SessionLockStore } from '../session/session-lock-store.ts';
2
+ import { type GetLockSession } from './get-lock-dialect.ts';
3
+ /**
4
+ * One connection to the MySQL server: only the methods that the store calls.
5
+ * A `PoolConnection` of `mysql2/promise` fits it.
6
+ */
7
+ export interface MysqlSession extends GetLockSession {
8
+ query(sql: string, values: unknown[]): Promise<[unknown, unknown]>;
9
+ /** Gives the session back to its pool. */
10
+ release(): void;
11
+ }
12
+ export interface MysqlStoreOptions {
13
+ /** Gives a new session. The store owns each session that it gets. */
14
+ connect(): Promise<MysqlSession>;
15
+ /**
16
+ * The name of an existing table in the database of the session, with one
17
+ * row and one `BIGINT UNSIGNED` column `id`, for the fencing tokens. The
18
+ * store runs no DDL.
19
+ */
20
+ table: string;
21
+ /**
22
+ * Milliseconds between two heartbeats of a holder, and the time limit of
23
+ * each heartbeat: the longest time that a holder can work after its session
24
+ * ended, before its signal aborts.
25
+ */
26
+ heartbeatInterval: number;
27
+ }
28
+ /**
29
+ * Holds a MySQL user lock (`GET_LOCK`) for each key, on a session of its own.
30
+ * The server releases the lock when the session ends, so a holder that stops
31
+ * never leaves a key locked. Every process on every machine that reaches one
32
+ * MySQL database shares the locks.
33
+ */
34
+ export declare class MysqlStore extends SessionLockStore<MysqlSession> {
35
+ constructor({ connect, table, heartbeatInterval }: MysqlStoreOptions);
36
+ }
37
+ //# sourceMappingURL=mysql-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/mysql/mysql-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAEL,KAAK,cAAc,EAIpB,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IACnE,0CAA0C;IAC1C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IACjC;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,qBAAa,UAAW,SAAQ,gBAAgB,CAAC,YAAY,CAAC;gBAChD,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,iBAAiB;CAMrE"}
@@ -0,0 +1,35 @@
1
+ import { SessionLockStore } from "../session/session-lock-store.js";
2
+ import { getLockDialect, lockName, quoteName, } from "./get-lock-dialect.js";
3
+ /**
4
+ * Holds a MySQL user lock (`GET_LOCK`) for each key, on a session of its own.
5
+ * The server releases the lock when the session ends, so a holder that stops
6
+ * never leaves a key locked. Every process on every machine that reaches one
7
+ * MySQL database shares the locks.
8
+ */
9
+ export class MysqlStore extends SessionLockStore {
10
+ constructor({ connect, table, heartbeatInterval }) {
11
+ super({ connect, heartbeatInterval }, getLockDialect(mysqlServer(quoteName(table))));
12
+ }
13
+ }
14
+ function mysqlServer(table) {
15
+ return {
16
+ server: 'MySQL',
17
+ // A negative timeout waits with no limit. The hint lifts the session's
18
+ // max_execution_time, which interrupts a waiting GET_LOCK.
19
+ waitStatement: `SELECT /*+ SET_VAR(max_execution_time = 0) */ GET_LOCK(${lockName}, -1) AS locked`,
20
+ run: async (session, sql, values) => (await session.query(sql, values))[0],
21
+ giveBack: (session) => session.release(),
22
+ // The sequence of the MySQL manual (LAST_INSERT_ID(expr)). An UPDATE that
23
+ // changes no row leaves LAST_INSERT_ID() at the session's last token.
24
+ mint: async (run) => {
25
+ await run(`UPDATE ${table} SET id = LAST_INSERT_ID(id + 1)`, []);
26
+ const row = await run('SELECT CAST(LAST_INSERT_ID() AS CHAR) AS token, ROW_COUNT() AS updated', []);
27
+ const updated = Number(row?.['updated']);
28
+ if (updated !== 1 || typeof row?.['token'] !== 'string') {
29
+ throw new Error(`The token table ${table} must have exactly one row; the update changed ${updated}.`);
30
+ }
31
+ return BigInt(row['token']);
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=mysql-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql-store.js","sourceRoot":"","sources":["../../../src/lock-stores/mysql/mysql-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAGL,cAAc,EACd,QAAQ,EACR,SAAS,GACV,MAAM,uBAAuB,CAAC;AA6B/B;;;;;GAKG;AACH,MAAM,OAAO,UAAW,SAAQ,gBAA8B;IAC5D,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAqB;QAClE,KAAK,CACH,EAAE,OAAO,EAAE,iBAAiB,EAAE,EAC9B,cAAc,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAC9C,CAAC;IACJ,CAAC;CACF;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO;QACL,MAAM,EAAE,OAAO;QACf,uEAAuE;QACvE,2DAA2D;QAC3D,aAAa,EAAE,0DAA0D,QAAQ,iBAAiB;QAClG,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1E,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE;QACxC,0EAA0E;QAC1E,sEAAsE;QACtE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,GAAG,CAAC,UAAU,KAAK,kCAAkC,EAAE,EAAE,CAAC,CAAC;YACjE,MAAM,GAAG,GAAG,MAAM,GAAG,CACnB,wEAAwE,EACxE,EAAE,CACH,CAAC;YACF,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;YACzC,IAAI,OAAO,KAAK,CAAC,IAAI,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACxD,MAAM,IAAI,KAAK,CACb,mBAAmB,KAAK,kDAAkD,OAAO,GAAG,CACrF,CAAC;YACJ,CAAC;YACD,OAAO,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { PostgresStore, type PostgresSession, type PostgresStoreOptions, } from './postgres-store.ts';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/postgres/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { PostgresStore, } from "./postgres-store.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lock-stores/postgres/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,GAGd,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { SessionLockStore } from '../session/session-lock-store.ts';
2
+ /**
3
+ * One connection to the Postgres server: only the methods that the store
4
+ * calls. A `PoolClient` of `pg` (node-postgres) fits it.
5
+ */
6
+ export interface PostgresSession {
7
+ query(text: string, values: unknown[]): Promise<{
8
+ rows: Record<string, unknown>[];
9
+ }>;
10
+ on(event: 'error', listener: (error: Error) => void): unknown;
11
+ on(event: 'end', listener: () => void): unknown;
12
+ off(event: 'error' | 'end', listener: (error: Error) => void): unknown;
13
+ /** `true` destroys the session; `false` gives it back to its pool. */
14
+ release(destroy: boolean): void;
15
+ }
16
+ export interface PostgresStoreOptions {
17
+ /** Gives a new session. The store owns each session that it gets. */
18
+ connect(): Promise<PostgresSession>;
19
+ /** The name of an existing sequence for the fencing tokens. The store runs no DDL. */
20
+ sequence: string;
21
+ /**
22
+ * Milliseconds between two heartbeats of a holder, and the time limit of
23
+ * each heartbeat: the longest time that a holder can work after its session
24
+ * ended, before its signal aborts.
25
+ */
26
+ heartbeatInterval: number;
27
+ }
28
+ /**
29
+ * Holds a session-level advisory lock of Postgres for each key, on a session
30
+ * of its own. The server releases the lock when the session ends, so a
31
+ * holder that stops never leaves a key locked. Every process on every machine
32
+ * that reaches one database shares the locks.
33
+ *
34
+ * Callers in this process first line up in a queue in memory, so only the
35
+ * first one gets a session: one session per key per process, however many
36
+ * callers wait, and first-come order within the process.
37
+ */
38
+ export declare class PostgresStore extends SessionLockStore<PostgresSession> {
39
+ constructor(options: PostgresStoreOptions);
40
+ }
41
+ //# sourceMappingURL=postgres-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/postgres/postgres-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,gBAAgB,EACjB,MAAM,kCAAkC,CAAC;AAE1C;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,CACH,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,EAAE,GAChB,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;KAAE,CAAC,CAAC;IAChD,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;IAC9D,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;IAChD,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;IACvE,sEAAsE;IACtE,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACpC,sFAAsF;IACtF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAeD;;;;;;;;;GASG;AACH,qBAAa,aAAc,SAAQ,gBAAgB,CAAC,eAAe,CAAC;gBACtD,OAAO,EAAE,oBAAoB;CAG1C"}
@@ -0,0 +1,83 @@
1
+ import { untilAborted } from '@zukhruf/async';
2
+ import { keyDigest } from "../../shared/key-digest.js";
3
+ import { SessionLockStore, } from "../session/session-lock-store.js";
4
+ /** The granted advisory locks of a lock number in the database of the session. */
5
+ const lockRows = `FROM pg_catalog.pg_locks
6
+ WHERE locktype = 'advisory'
7
+ AND database = (SELECT oid FROM pg_catalog.pg_database WHERE datname = pg_catalog.current_database())
8
+ AND objsubid = 1
9
+ AND ((classid::bigint << 32) | objid::bigint) = $1::bigint
10
+ AND granted`;
11
+ const heldQuery = `SELECT EXISTS (SELECT 1 ${lockRows}) AS held`;
12
+ /** Whether the session of the heartbeat holds the lock. */
13
+ const holdsQuery = `SELECT EXISTS (SELECT 1 ${lockRows} AND pid = pg_catalog.pg_backend_pid()) AS held`;
14
+ /**
15
+ * Holds a session-level advisory lock of Postgres for each key, on a session
16
+ * of its own. The server releases the lock when the session ends, so a
17
+ * holder that stops never leaves a key locked. Every process on every machine
18
+ * that reaches one database shares the locks.
19
+ *
20
+ * Callers in this process first line up in a queue in memory, so only the
21
+ * first one gets a session: one session per key per process, however many
22
+ * callers wait, and first-come order within the process.
23
+ */
24
+ export class PostgresStore extends SessionLockStore {
25
+ constructor(options) {
26
+ super(options, postgresDialect(options));
27
+ }
28
+ }
29
+ /**
30
+ * The number of the advisory lock of `key`: the first 8 bytes of the key
31
+ * digest, as a signed 64-bit integer. Sent as text, because a JavaScript
32
+ * number cannot hold 64 bits.
33
+ */
34
+ function lockNumber(key) {
35
+ return keyDigest(key).readBigInt64BE(0).toString();
36
+ }
37
+ const rowsOf = async (session, text, values) => (await session.query(text, values)).rows;
38
+ function postgresDialect(options) {
39
+ return {
40
+ server: 'Postgres',
41
+ lockName: lockNumber,
42
+ wait: async (session, { name }, signal) => {
43
+ // A timeout of the pool must not stop the wait.
44
+ await session.query('SET statement_timeout = 0; SET lock_timeout = 0', []);
45
+ await untilAborted(session.query('SELECT pg_advisory_lock($1::bigint)', [name]), signal);
46
+ },
47
+ try: async (session, { name }) => {
48
+ const [row] = await rowsOf(session, 'SELECT pg_try_advisory_lock($1::bigint) AS locked', [name]);
49
+ return row?.locked === true;
50
+ },
51
+ mint: async (session) => {
52
+ const [row] = await rowsOf(session, 'SELECT nextval($1::regclass)::text AS token', [options.sequence]);
53
+ if (typeof row?.token !== 'string') {
54
+ throw new Error(`nextval of sequence ${JSON.stringify(options.sequence)} gave no number.`);
55
+ }
56
+ return BigInt(row.token);
57
+ },
58
+ holds: async (session, { name }) => {
59
+ const [row] = await rowsOf(session, holdsQuery, [name]);
60
+ return row?.held === true;
61
+ },
62
+ release: async (session, { name }) => {
63
+ const [row] = await rowsOf(session, 'SELECT pg_advisory_unlock($1::bigint) AS released', [name]);
64
+ if (row?.released !== true)
65
+ return false;
66
+ await session.query('RESET statement_timeout; RESET lock_timeout', []);
67
+ return true;
68
+ },
69
+ /** Reads `pg_locks`, and never runs an advisory lock function. */
70
+ isHeld: async (session, { name }) => {
71
+ const [row] = await rowsOf(session, heldQuery, [name]);
72
+ return row?.held === true;
73
+ },
74
+ detach: (session, { error, end }) => {
75
+ session.off('error', error);
76
+ session.off('end', end);
77
+ },
78
+ // The session holds nothing: it goes back to the pool with its own settings.
79
+ giveBack: (session) => session.release(false),
80
+ destroy: (session) => session.release(true),
81
+ };
82
+ }
83
+ //# sourceMappingURL=postgres-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-store.js","sourceRoot":"","sources":["../../../src/lock-stores/postgres/postgres-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAEL,gBAAgB,GACjB,MAAM,kCAAkC,CAAC;AA+B1C,kFAAkF;AAClF,MAAM,QAAQ,GAAG;;;;;gBAKD,CAAC;AAEjB,MAAM,SAAS,GAAG,2BAA2B,QAAQ,WAAW,CAAC;AAEjE,2DAA2D;AAC3D,MAAM,UAAU,GAAG,2BAA2B,QAAQ,iDAAiD,CAAC;AAExG;;;;;;;;;GASG;AACH,MAAM,OAAO,aAAc,SAAQ,gBAAiC;IAClE,YAAY,OAA6B;QACvC,KAAK,CAAC,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3C,CAAC;CACF;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AACrD,CAAC;AAED,MAAM,MAAM,GAAG,KAAK,EAClB,OAAwB,EACxB,IAAY,EACZ,MAAiB,EACjB,EAAE,CAAC,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAE9C,SAAS,eAAe,CACtB,OAA6B;IAE7B,OAAO;QACL,MAAM,EAAE,UAAU;QAClB,QAAQ,EAAE,UAAU;QACpB,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE;YACxC,gDAAgD;YAChD,MAAM,OAAO,CAAC,KAAK,CACjB,iDAAiD,EACjD,EAAE,CACH,CAAC;YACF,MAAM,YAAY,CAChB,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,CAAC,IAAI,CAAC,CAAC,EAC5D,MAAM,CACP,CAAC;QACJ,CAAC;QACD,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC/B,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,MAAM,CACxB,OAAO,EACP,mDAAmD,EACnD,CAAC,IAAI,CAAC,CACP,CAAC;YACF,OAAO,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAC9B,CAAC;QACD,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACtB,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,MAAM,CACxB,OAAO,EACP,6CAA6C,EAC7C,CAAC,OAAO,CAAC,QAAQ,CAAC,CACnB,CAAC;YACF,IAAI,OAAO,GAAG,EAAE,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACnC,MAAM,IAAI,KAAK,CACb,uBAAuB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAC1E,CAAC;YACJ,CAAC;YACD,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QACD,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACjC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;YACxD,OAAO,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC;QAC5B,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YACnC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,MAAM,CACxB,OAAO,EACP,mDAAmD,EACnD,CAAC,IAAI,CAAC,CACP,CAAC;YACF,IAAI,GAAG,EAAE,QAAQ,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YACzC,MAAM,OAAO,CAAC,KAAK,CAAC,6CAA6C,EAAE,EAAE,CAAC,CAAC;YACvE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,kEAAkE;QAClE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAClC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;YACvD,OAAO,GAAG,EAAE,IAAI,KAAK,IAAI,CAAC;QAC5B,CAAC;QACD,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE;YAClC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC1B,CAAC;QACD,6EAA6E;QAC7E,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC;QAC7C,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;KAC5C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,72 @@
1
+ import type { LockHandle } from '../../mutex/lease.ts';
2
+ import type { AcquireOptions, LockStore } from '../../mutex/lock-store.ts';
3
+ /** The events of a session that tell the store that the session ended. */
4
+ export interface SessionEvents {
5
+ on(event: 'error', listener: (error: Error) => void): unknown;
6
+ on(event: 'end', listener: () => void): unknown;
7
+ }
8
+ /** The listeners that the store puts on each session that it owns. */
9
+ export interface SessionListeners {
10
+ error: (error: Error) => void;
11
+ end: () => void;
12
+ }
13
+ /** The lock of one key on the server. */
14
+ export interface SessionLock {
15
+ key: string;
16
+ /** The name of the lock on the server, made from the key digest. */
17
+ name: string;
18
+ }
19
+ /** What differs between the servers of the SQL lock stores, and between their drivers. */
20
+ export interface SessionLockDialect<Session extends SessionEvents> {
21
+ /** The name of the server in error messages. */
22
+ readonly server: string;
23
+ /** The name of the lock of `key` on the server. */
24
+ lockName(key: string): string;
25
+ /** Waits in the server's queue until the session holds the lock. A cancel of `signal` rejects at once. */
26
+ wait(session: Session, lock: SessionLock, signal: AbortSignal | undefined): Promise<void>;
27
+ /** Takes the lock only when no other session holds it. */
28
+ try(session: Session, lock: SessionLock): Promise<boolean>;
29
+ /** Gives the next fencing token. */
30
+ mint(session: Session): Promise<bigint>;
31
+ /** Whether this session holds the lock now. It never acquires the lock. */
32
+ holds(session: Session, lock: SessionLock): Promise<boolean>;
33
+ /** Releases the lock; `false` when the session did not hold it. */
34
+ release(session: Session, lock: SessionLock): Promise<boolean>;
35
+ /** Whether any session holds the lock now. It never acquires the lock. */
36
+ isHeld(session: Session, lock: SessionLock): Promise<boolean>;
37
+ /** Takes the store's listeners off a session that goes back to its pool. */
38
+ detach(session: Session, listeners: SessionListeners): void;
39
+ /** Gives back a session that holds nothing: to its pool, or by a close. */
40
+ giveBack(session: Session): void;
41
+ /** Ends the connection: the server then releases each lock of the session. */
42
+ destroy(session: Session): void;
43
+ }
44
+ export interface SessionLockStoreOptions<Session extends SessionEvents> {
45
+ /** Gives a new session. The store owns each session that it gets. */
46
+ connect(): Promise<Session>;
47
+ /**
48
+ * Milliseconds between two heartbeats of a holder, and the time limit of
49
+ * each heartbeat: the longest time that a holder can work after its session
50
+ * ended, before its signal aborts.
51
+ */
52
+ heartbeatInterval: number;
53
+ }
54
+ /**
55
+ * Holds a session-level lock of a SQL server for each key, on a session of
56
+ * its own. The server releases the lock when the session ends, so a holder
57
+ * that stops never leaves a key locked. Every process on every machine that
58
+ * reaches one database shares the locks.
59
+ *
60
+ * Callers in this process first line up in a queue in memory, so only the
61
+ * first one gets a session: one session per key per process, however many
62
+ * callers wait, and first-come order within the process.
63
+ */
64
+ export declare class SessionLockStore<Session extends SessionEvents> implements LockStore {
65
+ #private;
66
+ constructor(options: SessionLockStoreOptions<Session>, dialect: SessionLockDialect<Session>);
67
+ acquire(key: string, options?: AcquireOptions): Promise<LockHandle>;
68
+ tryAcquire(key: string): Promise<LockHandle | undefined>;
69
+ /** Asks the server on a short session, and never acquires the lock. */
70
+ isHeld(key: string): Promise<boolean>;
71
+ }
72
+ //# sourceMappingURL=session-lock-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-lock-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/session/session-lock-store.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAG3E,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;IAC9D,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;CACjD;AAED,sEAAsE;AACtE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0FAA0F;AAC1F,MAAM,WAAW,kBAAkB,CAAC,OAAO,SAAS,aAAa;IAC/D,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,0GAA0G;IAC1G,IAAI,CACF,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,0DAA0D;IAC1D,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,oCAAoC;IACpC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,2EAA2E;IAC3E,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,mEAAmE;IACnE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,0EAA0E;IAC1E,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,4EAA4E;IAC5E,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC5D,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,8EAA8E;IAC9E,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB,CAAC,OAAO,SAAS,aAAa;IACpE,qEAAqE;IACrE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAgB,CAC3B,OAAO,SAAS,aAAa,CAC7B,YAAW,SAAS;;gBAMlB,OAAO,EAAE,uBAAuB,CAAC,OAAO,CAAC,EACzC,OAAO,EAAE,kBAAkB,CAAC,OAAO,CAAC;IAMtC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IASnE,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAQxD,uEAAuE;IACjE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAwC5C"}