@rotorsoft/act-pg 1.4.0 → 1.4.1
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 +7 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/postgres-store.d.ts +7 -7
- package/dist/@types/postgres-store.d.ts.map +1 -1
- package/dist/index.cjs +72 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +72 -67
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -155,14 +155,14 @@ export declare class PostgresStore implements Store {
|
|
|
155
155
|
*/
|
|
156
156
|
private readonly _channel;
|
|
157
157
|
/** Active LISTEN client (one per `notify()` subscription). */
|
|
158
|
-
private
|
|
158
|
+
private _listen_client;
|
|
159
159
|
/**
|
|
160
160
|
* Notification listener attached to the active LISTEN client. Tracked
|
|
161
161
|
* separately so the re-subscribe / dispose paths can detach it before
|
|
162
162
|
* destroying the client — without this, a pool that reused the
|
|
163
163
|
* connection would re-fire the stale handler.
|
|
164
164
|
*/
|
|
165
|
-
private
|
|
165
|
+
private _listen_handler;
|
|
166
166
|
/**
|
|
167
167
|
* Cross-process commit subscription. **Present only when
|
|
168
168
|
* `config.notify === true`** — the orchestrator's auto-wire path
|
|
@@ -192,7 +192,7 @@ export declare class PostgresStore implements Store {
|
|
|
192
192
|
* destroying belt-and-braces guards against any future change in
|
|
193
193
|
* pg-pool semantics that could re-issue a half-clean client).
|
|
194
194
|
*/
|
|
195
|
-
private
|
|
195
|
+
private _teardown_listen;
|
|
196
196
|
/**
|
|
197
197
|
* Seed the database with required tables, indexes, and schema for event storage.
|
|
198
198
|
* @returns Promise that resolves when seeding is complete
|
|
@@ -282,7 +282,7 @@ export declare class PostgresStore implements Store {
|
|
|
282
282
|
* `WHERE` — callers compose it with any other predicates they need.
|
|
283
283
|
* Returns an always-true clause (`true`) when the filter is empty.
|
|
284
284
|
*/
|
|
285
|
-
private
|
|
285
|
+
private _filter_clause;
|
|
286
286
|
reset(input: string[] | StreamFilter): Promise<number>;
|
|
287
287
|
/**
|
|
288
288
|
* Clear blocked flag (and retry / error / lease state) on streams
|
|
@@ -359,13 +359,13 @@ export declare class PostgresStore implements Store {
|
|
|
359
359
|
* optional second query (in parallel) for the tail. K rows touched
|
|
360
360
|
* per query, not N events.
|
|
361
361
|
*/
|
|
362
|
-
private
|
|
362
|
+
private _query_stats_heads_only;
|
|
363
363
|
/**
|
|
364
364
|
* Full-scan path: one CTE-based query computes the per-stream
|
|
365
365
|
* `COUNT(*)` and `jsonb_object_agg(name, n)` map alongside the head
|
|
366
366
|
* (and tail when requested). All extras share the single events scan.
|
|
367
367
|
*/
|
|
368
|
-
private
|
|
368
|
+
private _query_stats_full_scan;
|
|
369
369
|
/**
|
|
370
370
|
* Implementation of the optional `Store.notify` hook. Bound onto
|
|
371
371
|
* `this.notify` in the constructor when `config.notify === true`,
|
|
@@ -387,7 +387,7 @@ export declare class PostgresStore implements Store {
|
|
|
387
387
|
* @param handler Called for each cross-process commit notification.
|
|
388
388
|
* @returns Disposer that releases the LISTEN client.
|
|
389
389
|
*/
|
|
390
|
-
private
|
|
390
|
+
private _subscribe_notifications;
|
|
391
391
|
/**
|
|
392
392
|
* Atomically truncates streams and seeds each with a snapshot or tombstone.
|
|
393
393
|
* @param targets - Streams to truncate with optional snapshot state and meta.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/postgres-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,SAAS,EACT,KAAK,EAEL,OAAO,EACP,cAAc,EACd,KAAK,EACL,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,MAAM,EACN,OAAO,EACP,KAAK,EACL,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAOxB,OAAO,EAAE,MAAM,IAAI,CAAC;AAUpB,KAAK,MAAM,GAAG,QAAQ,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC,GACA,EAAE,CAAC,UAAU,CAAC;AAsChB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2GG;AACH,qBAAa,aAAc,YAAW,KAAK;IACzC,OAAO,CAAC,KAAK,CAAC;IACd,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAwB;IAC5C;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,8DAA8D;IAC9D,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/postgres-store.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -58,10 +58,10 @@ types.setTypeParser(
|
|
|
58
58
|
var SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
59
59
|
var PG_UNIQUE_VIOLATION = "23505";
|
|
60
60
|
var NOTIFY_CHANNEL_PREFIX = "act_commit";
|
|
61
|
-
function
|
|
61
|
+
function notify_channel(schema, table) {
|
|
62
62
|
return `${NOTIFY_CHANNEL_PREFIX}_${schema}_${table}`;
|
|
63
63
|
}
|
|
64
|
-
function
|
|
64
|
+
function assert_safe_identifier(value, label) {
|
|
65
65
|
if (!SAFE_IDENTIFIER.test(value))
|
|
66
66
|
throw new Error(`Unsafe SQL identifier for ${label}: "${value}"`);
|
|
67
67
|
}
|
|
@@ -94,14 +94,14 @@ var PostgresStore = class {
|
|
|
94
94
|
*/
|
|
95
95
|
_channel;
|
|
96
96
|
/** Active LISTEN client (one per `notify()` subscription). */
|
|
97
|
-
|
|
97
|
+
_listen_client;
|
|
98
98
|
/**
|
|
99
99
|
* Notification listener attached to the active LISTEN client. Tracked
|
|
100
100
|
* separately so the re-subscribe / dispose paths can detach it before
|
|
101
101
|
* destroying the client — without this, a pool that reused the
|
|
102
102
|
* connection would re-fire the stale handler.
|
|
103
103
|
*/
|
|
104
|
-
|
|
104
|
+
_listen_handler;
|
|
105
105
|
/**
|
|
106
106
|
* Cross-process commit subscription. **Present only when
|
|
107
107
|
* `config.notify === true`** — the orchestrator's auto-wire path
|
|
@@ -119,15 +119,15 @@ var PostgresStore = class {
|
|
|
119
119
|
*/
|
|
120
120
|
constructor(config = {}) {
|
|
121
121
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
assert_safe_identifier(this.config.schema, "schema");
|
|
123
|
+
assert_safe_identifier(this.config.table, "table");
|
|
124
124
|
const { schema: _, table: __, ...poolConfig } = this.config;
|
|
125
125
|
this._pool = new Pool(poolConfig);
|
|
126
126
|
this._fqt = `"${this.config.schema}"."${this.config.table}"`;
|
|
127
127
|
this._fqs = `"${this.config.schema}"."${this.config.table}_streams"`;
|
|
128
|
-
this._channel =
|
|
128
|
+
this._channel = notify_channel(this.config.schema, this.config.table);
|
|
129
129
|
if (this.config.notify) {
|
|
130
|
-
this.notify = this.
|
|
130
|
+
this.notify = this._subscribe_notifications.bind(this);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
@@ -136,7 +136,7 @@ var PostgresStore = class {
|
|
|
136
136
|
* @returns Promise that resolves when all connections are closed
|
|
137
137
|
*/
|
|
138
138
|
async dispose() {
|
|
139
|
-
await this.
|
|
139
|
+
await this._teardown_listen();
|
|
140
140
|
await this._pool.end();
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
@@ -146,16 +146,16 @@ var PostgresStore = class {
|
|
|
146
146
|
* destroying belt-and-braces guards against any future change in
|
|
147
147
|
* pg-pool semantics that could re-issue a half-clean client).
|
|
148
148
|
*/
|
|
149
|
-
async
|
|
150
|
-
if (!this.
|
|
151
|
-
this.
|
|
152
|
-
this.
|
|
149
|
+
async _teardown_listen() {
|
|
150
|
+
if (!this._listen_client) return;
|
|
151
|
+
this._listen_client.removeListener("notification", this._listen_handler);
|
|
152
|
+
this._listen_handler = void 0;
|
|
153
153
|
try {
|
|
154
|
-
await this.
|
|
154
|
+
await this._listen_client.query(`UNLISTEN ${this._channel}`);
|
|
155
155
|
} catch {
|
|
156
156
|
}
|
|
157
|
-
this.
|
|
158
|
-
this.
|
|
157
|
+
this._listen_client.release(true);
|
|
158
|
+
this._listen_client = void 0;
|
|
159
159
|
}
|
|
160
160
|
/**
|
|
161
161
|
* Seed the database with required tables, indexes, and schema for event storage.
|
|
@@ -430,7 +430,7 @@ var PostgresStore = class {
|
|
|
430
430
|
const client = await this._pool.connect();
|
|
431
431
|
try {
|
|
432
432
|
await client.query("BEGIN");
|
|
433
|
-
const
|
|
433
|
+
const lane_clause = lane !== void 0 ? `AND s.lane = $5` : "";
|
|
434
434
|
const params = lane !== void 0 ? [lagging, leading, by, millis, lane] : [lagging, leading, by, millis];
|
|
435
435
|
const { rows } = await client.query(
|
|
436
436
|
`
|
|
@@ -439,7 +439,7 @@ var PostgresStore = class {
|
|
|
439
439
|
SELECT stream, source, at, priority, lane
|
|
440
440
|
FROM ${this._fqs} s
|
|
441
441
|
WHERE blocked = false
|
|
442
|
-
${
|
|
442
|
+
${lane_clause}
|
|
443
443
|
AND (leased_by IS NULL OR leased_until <= NOW())
|
|
444
444
|
AND (s.at < 0 OR EXISTS (
|
|
445
445
|
SELECT 1 FROM ${this._fqt} e
|
|
@@ -664,7 +664,7 @@ var PostgresStore = class {
|
|
|
664
664
|
* `WHERE` — callers compose it with any other predicates they need.
|
|
665
665
|
* Returns an always-true clause (`true`) when the filter is empty.
|
|
666
666
|
*/
|
|
667
|
-
|
|
667
|
+
_filter_clause(filter, start) {
|
|
668
668
|
const conditions = [];
|
|
669
669
|
const values = [];
|
|
670
670
|
if (filter.stream !== void 0) {
|
|
@@ -694,19 +694,19 @@ var PostgresStore = class {
|
|
|
694
694
|
};
|
|
695
695
|
}
|
|
696
696
|
async reset(input) {
|
|
697
|
-
const
|
|
697
|
+
const set_clause = `SET at = -1, retry = 0, blocked = false, error = NULL,
|
|
698
698
|
leased_by = NULL, leased_until = NULL`;
|
|
699
699
|
if (Array.isArray(input)) {
|
|
700
700
|
if (!input.length) return 0;
|
|
701
701
|
const { rowCount: rowCount2 } = await this._pool.query(
|
|
702
|
-
`UPDATE ${this._fqs} ${
|
|
702
|
+
`UPDATE ${this._fqs} ${set_clause} WHERE stream = ANY($1)`,
|
|
703
703
|
[input]
|
|
704
704
|
);
|
|
705
705
|
return rowCount2 ?? 0;
|
|
706
706
|
}
|
|
707
|
-
const { clause, values } = this.
|
|
707
|
+
const { clause, values } = this._filter_clause(input, 1);
|
|
708
708
|
const { rowCount } = await this._pool.query(
|
|
709
|
-
`UPDATE ${this._fqs} ${
|
|
709
|
+
`UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,
|
|
710
710
|
values
|
|
711
711
|
);
|
|
712
712
|
return rowCount ?? 0;
|
|
@@ -727,23 +727,23 @@ var PostgresStore = class {
|
|
|
727
727
|
* @returns Count of streams that were actually flipped (were blocked).
|
|
728
728
|
*/
|
|
729
729
|
async unblock(input) {
|
|
730
|
-
const
|
|
730
|
+
const set_clause = `SET retry = -1, blocked = false, error = NULL,
|
|
731
731
|
leased_by = NULL, leased_until = NULL`;
|
|
732
732
|
if (Array.isArray(input)) {
|
|
733
733
|
if (!input.length) return 0;
|
|
734
734
|
const { rowCount: rowCount2 } = await this._pool.query(
|
|
735
|
-
`UPDATE ${this._fqs} ${
|
|
735
|
+
`UPDATE ${this._fqs} ${set_clause}
|
|
736
736
|
WHERE stream = ANY($1) AND blocked = true`,
|
|
737
737
|
[input]
|
|
738
738
|
);
|
|
739
739
|
return rowCount2 ?? 0;
|
|
740
740
|
}
|
|
741
|
-
const { clause, values } = this.
|
|
741
|
+
const { clause, values } = this._filter_clause(
|
|
742
742
|
{ ...input, blocked: true },
|
|
743
743
|
1
|
|
744
744
|
);
|
|
745
745
|
const { rowCount } = await this._pool.query(
|
|
746
|
-
`UPDATE ${this._fqs} ${
|
|
746
|
+
`UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,
|
|
747
747
|
values
|
|
748
748
|
);
|
|
749
749
|
return rowCount ?? 0;
|
|
@@ -763,7 +763,7 @@ var PostgresStore = class {
|
|
|
763
763
|
* @returns Count of streams whose priority changed.
|
|
764
764
|
*/
|
|
765
765
|
async prioritize(filter, priority) {
|
|
766
|
-
const { clause, values } = this.
|
|
766
|
+
const { clause, values } = this._filter_clause(filter, 2);
|
|
767
767
|
const sql = `UPDATE ${this._fqs} SET priority = $1
|
|
768
768
|
WHERE priority <> $1 AND ${clause}`;
|
|
769
769
|
const { rowCount } = await this._pool.query(sql, [priority, ...values]);
|
|
@@ -870,11 +870,11 @@ var PostgresStore = class {
|
|
|
870
870
|
*/
|
|
871
871
|
async query_stats(input, options) {
|
|
872
872
|
const exclude = options?.exclude ?? [];
|
|
873
|
-
const
|
|
874
|
-
const
|
|
875
|
-
const
|
|
873
|
+
const want_tail = options?.tail ?? false;
|
|
874
|
+
const want_count = options?.count ?? false;
|
|
875
|
+
const want_names = options?.names ?? false;
|
|
876
876
|
const before = options?.before;
|
|
877
|
-
const
|
|
877
|
+
const full_scan = want_count || want_names;
|
|
878
878
|
if (Array.isArray(input) && input.length === 0) {
|
|
879
879
|
return /* @__PURE__ */ new Map();
|
|
880
880
|
}
|
|
@@ -897,29 +897,34 @@ var PostgresStore = class {
|
|
|
897
897
|
params.push(before);
|
|
898
898
|
where.push(`e.id < $${params.length}`);
|
|
899
899
|
}
|
|
900
|
-
const
|
|
901
|
-
const
|
|
902
|
-
return
|
|
903
|
-
|
|
904
|
-
|
|
900
|
+
const from_clause = `${this._fqt} e`;
|
|
901
|
+
const where_clause = `WHERE ${where.length ? where.join(" AND ") : "TRUE"}`;
|
|
902
|
+
return full_scan ? this._query_stats_full_scan(
|
|
903
|
+
from_clause,
|
|
904
|
+
where_clause,
|
|
905
905
|
params,
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
) : this.
|
|
906
|
+
want_tail,
|
|
907
|
+
want_count,
|
|
908
|
+
want_names
|
|
909
|
+
) : this._query_stats_heads_only(
|
|
910
|
+
from_clause,
|
|
911
|
+
where_clause,
|
|
912
|
+
params,
|
|
913
|
+
want_tail
|
|
914
|
+
);
|
|
910
915
|
}
|
|
911
916
|
/**
|
|
912
917
|
* Cheap path: index-only DISTINCT ON for the head per stream, plus an
|
|
913
918
|
* optional second query (in parallel) for the tail. K rows touched
|
|
914
919
|
* per query, not N events.
|
|
915
920
|
*/
|
|
916
|
-
async
|
|
921
|
+
async _query_stats_heads_only(from_clause, where_clause, params, want_tail) {
|
|
917
922
|
const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;
|
|
918
|
-
const
|
|
919
|
-
const
|
|
923
|
+
const head_sql = `SELECT DISTINCT ON (e.stream) ${cols} FROM ${from_clause} ${where_clause} ORDER BY e.stream, e.version DESC`;
|
|
924
|
+
const tail_sql = want_tail ? `SELECT DISTINCT ON (e.stream) ${cols} FROM ${from_clause} ${where_clause} ORDER BY e.stream, e.version ASC` : null;
|
|
920
925
|
const [headRes, tailRes] = await Promise.all([
|
|
921
|
-
this._pool.query(
|
|
922
|
-
|
|
926
|
+
this._pool.query(head_sql, params),
|
|
927
|
+
tail_sql ? this._pool.query(tail_sql, params) : Promise.resolve(null)
|
|
923
928
|
]);
|
|
924
929
|
const out = /* @__PURE__ */ new Map();
|
|
925
930
|
for (const row of headRes.rows) {
|
|
@@ -937,16 +942,16 @@ var PostgresStore = class {
|
|
|
937
942
|
* `COUNT(*)` and `jsonb_object_agg(name, n)` map alongside the head
|
|
938
943
|
* (and tail when requested). All extras share the single events scan.
|
|
939
944
|
*/
|
|
940
|
-
async
|
|
941
|
-
const
|
|
942
|
-
const
|
|
943
|
-
const
|
|
945
|
+
async _query_stats_full_scan(from_clause, where_clause, params, want_tail, want_count, want_names) {
|
|
946
|
+
const tail_cte = want_tail ? `, tails AS (SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version ASC)` : "";
|
|
947
|
+
const tail_join = want_tail ? `LEFT JOIN tails t ON t.stream = h.stream` : "";
|
|
948
|
+
const tail_cols = want_tail ? `, t.id AS t_id, t.stream AS t_stream, t.version AS t_version,
|
|
944
949
|
t.name AS t_name, t.data AS t_data, t.created AS t_created, t.meta AS t_meta` : "";
|
|
945
950
|
const sql = `
|
|
946
951
|
WITH ef AS (
|
|
947
952
|
SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta
|
|
948
|
-
FROM ${
|
|
949
|
-
${
|
|
953
|
+
FROM ${from_clause}
|
|
954
|
+
${where_clause}
|
|
950
955
|
),
|
|
951
956
|
agg AS (
|
|
952
957
|
SELECT stream,
|
|
@@ -962,15 +967,15 @@ var PostgresStore = class {
|
|
|
962
967
|
heads AS (
|
|
963
968
|
SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version DESC
|
|
964
969
|
)
|
|
965
|
-
${
|
|
970
|
+
${tail_cte}
|
|
966
971
|
SELECT
|
|
967
972
|
h.id, h.stream, h.version, h.name, h.data, h.created, h.meta,
|
|
968
973
|
a.cnt AS agg_count,
|
|
969
974
|
a.names AS agg_names
|
|
970
|
-
${
|
|
975
|
+
${tail_cols}
|
|
971
976
|
FROM heads h
|
|
972
977
|
LEFT JOIN agg a ON a.stream = h.stream
|
|
973
|
-
${
|
|
978
|
+
${tail_join}
|
|
974
979
|
`;
|
|
975
980
|
const res = await this._pool.query(sql, params);
|
|
976
981
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -986,7 +991,7 @@ var PostgresStore = class {
|
|
|
986
991
|
meta: row.meta
|
|
987
992
|
}
|
|
988
993
|
};
|
|
989
|
-
if (
|
|
994
|
+
if (want_tail && row.t_id !== void 0 && row.t_id !== null) {
|
|
990
995
|
stats.tail = {
|
|
991
996
|
id: row.t_id,
|
|
992
997
|
stream: row.t_stream,
|
|
@@ -997,8 +1002,8 @@ var PostgresStore = class {
|
|
|
997
1002
|
meta: row.t_meta
|
|
998
1003
|
};
|
|
999
1004
|
}
|
|
1000
|
-
if (
|
|
1001
|
-
if (
|
|
1005
|
+
if (want_count) stats.count = row.agg_count;
|
|
1006
|
+
if (want_names) stats.names = row.agg_names;
|
|
1002
1007
|
out.set(row.stream, stats);
|
|
1003
1008
|
}
|
|
1004
1009
|
return out;
|
|
@@ -1024,10 +1029,10 @@ var PostgresStore = class {
|
|
|
1024
1029
|
* @param handler Called for each cross-process commit notification.
|
|
1025
1030
|
* @returns Disposer that releases the LISTEN client.
|
|
1026
1031
|
*/
|
|
1027
|
-
async
|
|
1028
|
-
await this.
|
|
1032
|
+
async _subscribe_notifications(handler) {
|
|
1033
|
+
await this._teardown_listen();
|
|
1029
1034
|
const client = await this._pool.connect();
|
|
1030
|
-
const
|
|
1035
|
+
const on_notification = (msg) => {
|
|
1031
1036
|
if (msg.channel !== this._channel) return;
|
|
1032
1037
|
if (!msg.payload) return;
|
|
1033
1038
|
let parsed;
|
|
@@ -1064,19 +1069,19 @@ var PostgresStore = class {
|
|
|
1064
1069
|
logger.error(err, "act_commit: handler threw, listener preserved");
|
|
1065
1070
|
}
|
|
1066
1071
|
};
|
|
1067
|
-
client.on("notification",
|
|
1072
|
+
client.on("notification", on_notification);
|
|
1068
1073
|
try {
|
|
1069
1074
|
await client.query(`LISTEN ${this._channel}`);
|
|
1070
1075
|
} catch (err) {
|
|
1071
|
-
client.removeListener("notification",
|
|
1076
|
+
client.removeListener("notification", on_notification);
|
|
1072
1077
|
client.release(true);
|
|
1073
1078
|
throw err;
|
|
1074
1079
|
}
|
|
1075
|
-
this.
|
|
1076
|
-
this.
|
|
1080
|
+
this._listen_client = client;
|
|
1081
|
+
this._listen_handler = on_notification;
|
|
1077
1082
|
return async () => {
|
|
1078
|
-
if (this.
|
|
1079
|
-
await this.
|
|
1083
|
+
if (this._listen_client !== client) return;
|
|
1084
|
+
await this._teardown_listen();
|
|
1080
1085
|
};
|
|
1081
1086
|
}
|
|
1082
1087
|
/**
|