graphile-realtime-subscriptions 0.4.0 → 0.5.2
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/cursor-tracker.d.ts +3 -3
- package/cursor-tracker.js +11 -17
- package/esm/cursor-tracker.d.ts +3 -3
- package/esm/cursor-tracker.js +11 -17
- package/esm/index.d.ts +1 -1
- package/esm/plugin.d.ts +1 -1
- package/esm/realtime-manager.js +2 -2
- package/esm/types.d.ts +15 -13
- package/index.d.ts +1 -1
- package/package.json +3 -3
- package/plugin.d.ts +1 -1
- package/realtime-manager.js +2 -2
- package/types.d.ts +15 -13
package/cursor-tracker.d.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* 4. stop() → calls cleanup_ephemeral() to remove ephemeral subscriptions
|
|
10
10
|
* and delete the listener_node row
|
|
11
11
|
*
|
|
12
|
-
* The caller provides a
|
|
13
|
-
*
|
|
12
|
+
* The caller provides a Queryable (typically a pg.Pool from pg-cache)
|
|
13
|
+
* and this class calls pool.query() directly for each operation.
|
|
14
14
|
*/
|
|
15
15
|
import type { CursorTrackerOptions, ChangeLogEntry } from './types';
|
|
16
16
|
declare const DEFAULT_POLL_INTERVAL_MS = 5000;
|
|
@@ -23,7 +23,7 @@ export declare class CursorTracker {
|
|
|
23
23
|
private readonly pollIntervalMs;
|
|
24
24
|
private readonly heartbeatIntervalMs;
|
|
25
25
|
private readonly batchLimit;
|
|
26
|
-
private readonly
|
|
26
|
+
private readonly pool;
|
|
27
27
|
private readonly onChanges;
|
|
28
28
|
private readonly onError;
|
|
29
29
|
private pollTimer;
|
package/cursor-tracker.js
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* 4. stop() → calls cleanup_ephemeral() to remove ephemeral subscriptions
|
|
11
11
|
* and delete the listener_node row
|
|
12
12
|
*
|
|
13
|
-
* The caller provides a
|
|
14
|
-
*
|
|
13
|
+
* The caller provides a Queryable (typically a pg.Pool from pg-cache)
|
|
14
|
+
* and this class calls pool.query() directly for each operation.
|
|
15
15
|
*/
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.DEFAULT_SCHEMA = exports.DEFAULT_BATCH_LIMIT = exports.DEFAULT_HEARTBEAT_INTERVAL_MS = exports.DEFAULT_POLL_INTERVAL_MS = exports.CursorTracker = void 0;
|
|
@@ -33,7 +33,7 @@ class CursorTracker {
|
|
|
33
33
|
pollIntervalMs;
|
|
34
34
|
heartbeatIntervalMs;
|
|
35
35
|
batchLimit;
|
|
36
|
-
|
|
36
|
+
pool;
|
|
37
37
|
onChanges;
|
|
38
38
|
onError;
|
|
39
39
|
pollTimer = null;
|
|
@@ -46,7 +46,7 @@ class CursorTracker {
|
|
|
46
46
|
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
47
47
|
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
48
48
|
this.batchLimit = options.batchLimit ?? DEFAULT_BATCH_LIMIT;
|
|
49
|
-
this.
|
|
49
|
+
this.pool = options.pool;
|
|
50
50
|
this.onChanges = options.onChanges ?? (() => { });
|
|
51
51
|
this.onError = options.onError ?? ((err) => {
|
|
52
52
|
log.error(`CursorTracker error: ${err.message}`);
|
|
@@ -87,11 +87,9 @@ class CursorTracker {
|
|
|
87
87
|
return [];
|
|
88
88
|
this.draining = true;
|
|
89
89
|
try {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return result.rows.map((row) => row.drain_changes);
|
|
94
|
-
});
|
|
90
|
+
const sql = `SELECT * FROM ${this.quoteIdent(this.schema)}.drain_changes($1, $2)`;
|
|
91
|
+
const result = await this.pool.query(sql, [this.nodeId, this.batchLimit]);
|
|
92
|
+
const entries = result.rows.map((row) => row.drain_changes);
|
|
95
93
|
if (entries.length > 0) {
|
|
96
94
|
log.info(`Drained ${entries.length} change(s) for node=${this.nodeId}`);
|
|
97
95
|
this.onChanges(entries);
|
|
@@ -108,10 +106,8 @@ class CursorTracker {
|
|
|
108
106
|
}
|
|
109
107
|
async touchListener() {
|
|
110
108
|
try {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
await client.query(sql, [this.nodeId]);
|
|
114
|
-
});
|
|
109
|
+
const sql = `SELECT ${this.quoteIdent(this.schema)}.touch_listener($1)`;
|
|
110
|
+
await this.pool.query(sql, [this.nodeId]);
|
|
115
111
|
}
|
|
116
112
|
catch (err) {
|
|
117
113
|
this.onError(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -119,10 +115,8 @@ class CursorTracker {
|
|
|
119
115
|
}
|
|
120
116
|
async cleanupEphemeral() {
|
|
121
117
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
await client.query(sql, [this.nodeId]);
|
|
125
|
-
});
|
|
118
|
+
const sql = `SELECT ${this.quoteIdent(this.schema)}.cleanup_ephemeral($1)`;
|
|
119
|
+
await this.pool.query(sql, [this.nodeId]);
|
|
126
120
|
log.info(`Cleaned up ephemeral subscriptions for node=${this.nodeId}`);
|
|
127
121
|
}
|
|
128
122
|
catch (err) {
|
package/esm/cursor-tracker.d.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* 4. stop() → calls cleanup_ephemeral() to remove ephemeral subscriptions
|
|
10
10
|
* and delete the listener_node row
|
|
11
11
|
*
|
|
12
|
-
* The caller provides a
|
|
13
|
-
*
|
|
12
|
+
* The caller provides a Queryable (typically a pg.Pool from pg-cache)
|
|
13
|
+
* and this class calls pool.query() directly for each operation.
|
|
14
14
|
*/
|
|
15
15
|
import type { CursorTrackerOptions, ChangeLogEntry } from './types';
|
|
16
16
|
declare const DEFAULT_POLL_INTERVAL_MS = 5000;
|
|
@@ -23,7 +23,7 @@ export declare class CursorTracker {
|
|
|
23
23
|
private readonly pollIntervalMs;
|
|
24
24
|
private readonly heartbeatIntervalMs;
|
|
25
25
|
private readonly batchLimit;
|
|
26
|
-
private readonly
|
|
26
|
+
private readonly pool;
|
|
27
27
|
private readonly onChanges;
|
|
28
28
|
private readonly onError;
|
|
29
29
|
private pollTimer;
|
package/esm/cursor-tracker.js
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* 4. stop() → calls cleanup_ephemeral() to remove ephemeral subscriptions
|
|
10
10
|
* and delete the listener_node row
|
|
11
11
|
*
|
|
12
|
-
* The caller provides a
|
|
13
|
-
*
|
|
12
|
+
* The caller provides a Queryable (typically a pg.Pool from pg-cache)
|
|
13
|
+
* and this class calls pool.query() directly for each operation.
|
|
14
14
|
*/
|
|
15
15
|
import { randomUUID } from 'crypto';
|
|
16
16
|
import { Logger } from '@pgpmjs/logger';
|
|
@@ -26,7 +26,7 @@ export class CursorTracker {
|
|
|
26
26
|
pollIntervalMs;
|
|
27
27
|
heartbeatIntervalMs;
|
|
28
28
|
batchLimit;
|
|
29
|
-
|
|
29
|
+
pool;
|
|
30
30
|
onChanges;
|
|
31
31
|
onError;
|
|
32
32
|
pollTimer = null;
|
|
@@ -39,7 +39,7 @@ export class CursorTracker {
|
|
|
39
39
|
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
40
40
|
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
41
41
|
this.batchLimit = options.batchLimit ?? DEFAULT_BATCH_LIMIT;
|
|
42
|
-
this.
|
|
42
|
+
this.pool = options.pool;
|
|
43
43
|
this.onChanges = options.onChanges ?? (() => { });
|
|
44
44
|
this.onError = options.onError ?? ((err) => {
|
|
45
45
|
log.error(`CursorTracker error: ${err.message}`);
|
|
@@ -80,11 +80,9 @@ export class CursorTracker {
|
|
|
80
80
|
return [];
|
|
81
81
|
this.draining = true;
|
|
82
82
|
try {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return result.rows.map((row) => row.drain_changes);
|
|
87
|
-
});
|
|
83
|
+
const sql = `SELECT * FROM ${this.quoteIdent(this.schema)}.drain_changes($1, $2)`;
|
|
84
|
+
const result = await this.pool.query(sql, [this.nodeId, this.batchLimit]);
|
|
85
|
+
const entries = result.rows.map((row) => row.drain_changes);
|
|
88
86
|
if (entries.length > 0) {
|
|
89
87
|
log.info(`Drained ${entries.length} change(s) for node=${this.nodeId}`);
|
|
90
88
|
this.onChanges(entries);
|
|
@@ -101,10 +99,8 @@ export class CursorTracker {
|
|
|
101
99
|
}
|
|
102
100
|
async touchListener() {
|
|
103
101
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
await client.query(sql, [this.nodeId]);
|
|
107
|
-
});
|
|
102
|
+
const sql = `SELECT ${this.quoteIdent(this.schema)}.touch_listener($1)`;
|
|
103
|
+
await this.pool.query(sql, [this.nodeId]);
|
|
108
104
|
}
|
|
109
105
|
catch (err) {
|
|
110
106
|
this.onError(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -112,10 +108,8 @@ export class CursorTracker {
|
|
|
112
108
|
}
|
|
113
109
|
async cleanupEphemeral() {
|
|
114
110
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
await client.query(sql, [this.nodeId]);
|
|
118
|
-
});
|
|
111
|
+
const sql = `SELECT ${this.quoteIdent(this.schema)}.cleanup_ephemeral($1)`;
|
|
112
|
+
await this.pool.query(sql, [this.nodeId]);
|
|
119
113
|
log.info(`Cleaned up ephemeral subscriptions for node=${this.nodeId}`);
|
|
120
114
|
}
|
|
121
115
|
catch (err) {
|
package/esm/index.d.ts
CHANGED
|
@@ -21,4 +21,4 @@ export { RealtimeSubscriptionsPreset } from './preset';
|
|
|
21
21
|
export { CursorTracker } from './cursor-tracker';
|
|
22
22
|
export { RealtimeManager } from './realtime-manager';
|
|
23
23
|
export type { RealtimeSubscriptionsPluginOptions } from './types';
|
|
24
|
-
export type { CursorTrackerOptions, ChangeLogEntry,
|
|
24
|
+
export type { CursorTrackerOptions, ChangeLogEntry, Queryable, RealtimeManagerOptions, } from './types';
|
package/esm/plugin.d.ts
CHANGED
|
@@ -80,5 +80,5 @@ export declare function createRealtimeSubscriptionsPlugin(options?: RealtimeSubs
|
|
|
80
80
|
export { createRealtimeSubscriptionsPlugin as RealtimeSubscriptionsPlugin };
|
|
81
81
|
export { CursorTracker } from './cursor-tracker';
|
|
82
82
|
export { RealtimeManager } from './realtime-manager';
|
|
83
|
-
export type { CursorTrackerOptions, ChangeLogEntry,
|
|
83
|
+
export type { CursorTrackerOptions, ChangeLogEntry, Queryable, RealtimeManagerOptions } from './types';
|
|
84
84
|
export { parseNotifyPayload, EventThrottle, DEFAULT_OVERFLOW_THRESHOLD };
|
package/esm/realtime-manager.js
CHANGED
|
@@ -61,7 +61,7 @@ export class RealtimeManager {
|
|
|
61
61
|
subscriber;
|
|
62
62
|
started = false;
|
|
63
63
|
constructor(options) {
|
|
64
|
-
const { pgSubscriber,
|
|
64
|
+
const { pgSubscriber, pool, ...cursorOpts } = options;
|
|
65
65
|
this.subscriber = pgSubscriber;
|
|
66
66
|
this.cursorTracker = new CursorTracker({
|
|
67
67
|
nodeId: cursorOpts.nodeId,
|
|
@@ -69,7 +69,7 @@ export class RealtimeManager {
|
|
|
69
69
|
pollIntervalMs: cursorOpts.pollIntervalMs,
|
|
70
70
|
heartbeatIntervalMs: cursorOpts.heartbeatIntervalMs,
|
|
71
71
|
batchLimit: cursorOpts.batchLimit,
|
|
72
|
-
|
|
72
|
+
pool,
|
|
73
73
|
onChanges: (entries) => this.dispatchEntries(entries),
|
|
74
74
|
onError: cursorOpts.onError ?? ((err) => {
|
|
75
75
|
log.error(`RealtimeManager error: ${err.message}`);
|
package/esm/types.d.ts
CHANGED
|
@@ -13,19 +13,18 @@ export interface RealtimeSubscriptionsPluginOptions {
|
|
|
13
13
|
overflowThreshold?: number;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* A minimal
|
|
17
|
-
*
|
|
16
|
+
* A minimal query-capable interface — satisfied by pg.Pool, pg.Client,
|
|
17
|
+
* pg.PoolClient, or any object with a compatible `query` method.
|
|
18
|
+
*
|
|
19
|
+
* CursorTracker and RealtimeManager use only one-shot queries, so
|
|
20
|
+
* `pool.query()` (which internally borrows a client, runs the query,
|
|
21
|
+
* and releases the client) is all that's needed — no callback wrapper.
|
|
18
22
|
*/
|
|
19
|
-
export interface
|
|
23
|
+
export interface Queryable {
|
|
20
24
|
query<R = Record<string, unknown>>(text: string, values?: unknown[]): Promise<{
|
|
21
25
|
rows: R[];
|
|
22
26
|
}>;
|
|
23
27
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Callback that provides a PgClient for executing queries.
|
|
26
|
-
* The client is released after the callback returns.
|
|
27
|
-
*/
|
|
28
|
-
export type WithPgClient = <T>(callback: (client: PgClient) => Promise<T>) => Promise<T>;
|
|
29
28
|
/**
|
|
30
29
|
* A single entry from drain_changes(), representing a change_log row
|
|
31
30
|
* matched against subscriber tables.
|
|
@@ -77,10 +76,12 @@ export interface CursorTrackerOptions {
|
|
|
77
76
|
*/
|
|
78
77
|
batchLimit?: number;
|
|
79
78
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
79
|
+
* A query-capable object (typically a pg.Pool from pg-cache) used to
|
|
80
|
+
* run drain_changes(), touch_listener(), and cleanup_ephemeral() queries.
|
|
81
|
+
* pool.query() internally borrows a connection and releases it after
|
|
82
|
+
* each call — no manual connection management needed.
|
|
82
83
|
*/
|
|
83
|
-
|
|
84
|
+
pool: Queryable;
|
|
84
85
|
/**
|
|
85
86
|
* Called when drain_changes() returns new change_log entries.
|
|
86
87
|
* The entries include subscriber_ids for fan-out routing.
|
|
@@ -104,9 +105,10 @@ export interface RealtimeManagerOptions {
|
|
|
104
105
|
*/
|
|
105
106
|
pgSubscriber: unknown;
|
|
106
107
|
/**
|
|
107
|
-
*
|
|
108
|
+
* A query-capable object (typically a pg.Pool from pg-cache) used by
|
|
109
|
+
* the underlying CursorTracker for drain_changes() polling.
|
|
108
110
|
*/
|
|
109
|
-
|
|
111
|
+
pool: Queryable;
|
|
110
112
|
/**
|
|
111
113
|
* Unique identifier for this listener node.
|
|
112
114
|
* Should be stable across restarts if you want cursor continuity.
|
package/index.d.ts
CHANGED
|
@@ -21,4 +21,4 @@ export { RealtimeSubscriptionsPreset } from './preset';
|
|
|
21
21
|
export { CursorTracker } from './cursor-tracker';
|
|
22
22
|
export { RealtimeManager } from './realtime-manager';
|
|
23
23
|
export type { RealtimeSubscriptionsPluginOptions } from './types';
|
|
24
|
-
export type { CursorTrackerOptions, ChangeLogEntry,
|
|
24
|
+
export type { CursorTrackerOptions, ChangeLogEntry, Queryable, RealtimeManagerOptions, } from './types';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-realtime-subscriptions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Realtime subscription plugin for PostGraphile v5 — per-table GraphQL subscriptions via LISTEN/NOTIFY",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"homepage": "https://github.com/constructive-io/constructive",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"url": "https://github.com/constructive-io/constructive/issues"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@pgpmjs/logger": "^2.
|
|
43
|
+
"@pgpmjs/logger": "^2.10.1",
|
|
44
44
|
"@pgsql/quotes": "^17.1.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"@types/node": "^22.19.11",
|
|
57
57
|
"makage": "^0.3.0"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "d43f1d7e38cc39a71b4f8d2cafb39f46df12e31a"
|
|
60
60
|
}
|
package/plugin.d.ts
CHANGED
|
@@ -80,5 +80,5 @@ export declare function createRealtimeSubscriptionsPlugin(options?: RealtimeSubs
|
|
|
80
80
|
export { createRealtimeSubscriptionsPlugin as RealtimeSubscriptionsPlugin };
|
|
81
81
|
export { CursorTracker } from './cursor-tracker';
|
|
82
82
|
export { RealtimeManager } from './realtime-manager';
|
|
83
|
-
export type { CursorTrackerOptions, ChangeLogEntry,
|
|
83
|
+
export type { CursorTrackerOptions, ChangeLogEntry, Queryable, RealtimeManagerOptions } from './types';
|
|
84
84
|
export { parseNotifyPayload, EventThrottle, DEFAULT_OVERFLOW_THRESHOLD };
|
package/realtime-manager.js
CHANGED
|
@@ -67,7 +67,7 @@ class RealtimeManager {
|
|
|
67
67
|
subscriber;
|
|
68
68
|
started = false;
|
|
69
69
|
constructor(options) {
|
|
70
|
-
const { pgSubscriber,
|
|
70
|
+
const { pgSubscriber, pool, ...cursorOpts } = options;
|
|
71
71
|
this.subscriber = pgSubscriber;
|
|
72
72
|
this.cursorTracker = new cursor_tracker_1.CursorTracker({
|
|
73
73
|
nodeId: cursorOpts.nodeId,
|
|
@@ -75,7 +75,7 @@ class RealtimeManager {
|
|
|
75
75
|
pollIntervalMs: cursorOpts.pollIntervalMs,
|
|
76
76
|
heartbeatIntervalMs: cursorOpts.heartbeatIntervalMs,
|
|
77
77
|
batchLimit: cursorOpts.batchLimit,
|
|
78
|
-
|
|
78
|
+
pool,
|
|
79
79
|
onChanges: (entries) => this.dispatchEntries(entries),
|
|
80
80
|
onError: cursorOpts.onError ?? ((err) => {
|
|
81
81
|
log.error(`RealtimeManager error: ${err.message}`);
|
package/types.d.ts
CHANGED
|
@@ -13,19 +13,18 @@ export interface RealtimeSubscriptionsPluginOptions {
|
|
|
13
13
|
overflowThreshold?: number;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* A minimal
|
|
17
|
-
*
|
|
16
|
+
* A minimal query-capable interface — satisfied by pg.Pool, pg.Client,
|
|
17
|
+
* pg.PoolClient, or any object with a compatible `query` method.
|
|
18
|
+
*
|
|
19
|
+
* CursorTracker and RealtimeManager use only one-shot queries, so
|
|
20
|
+
* `pool.query()` (which internally borrows a client, runs the query,
|
|
21
|
+
* and releases the client) is all that's needed — no callback wrapper.
|
|
18
22
|
*/
|
|
19
|
-
export interface
|
|
23
|
+
export interface Queryable {
|
|
20
24
|
query<R = Record<string, unknown>>(text: string, values?: unknown[]): Promise<{
|
|
21
25
|
rows: R[];
|
|
22
26
|
}>;
|
|
23
27
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Callback that provides a PgClient for executing queries.
|
|
26
|
-
* The client is released after the callback returns.
|
|
27
|
-
*/
|
|
28
|
-
export type WithPgClient = <T>(callback: (client: PgClient) => Promise<T>) => Promise<T>;
|
|
29
28
|
/**
|
|
30
29
|
* A single entry from drain_changes(), representing a change_log row
|
|
31
30
|
* matched against subscriber tables.
|
|
@@ -77,10 +76,12 @@ export interface CursorTrackerOptions {
|
|
|
77
76
|
*/
|
|
78
77
|
batchLimit?: number;
|
|
79
78
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
79
|
+
* A query-capable object (typically a pg.Pool from pg-cache) used to
|
|
80
|
+
* run drain_changes(), touch_listener(), and cleanup_ephemeral() queries.
|
|
81
|
+
* pool.query() internally borrows a connection and releases it after
|
|
82
|
+
* each call — no manual connection management needed.
|
|
82
83
|
*/
|
|
83
|
-
|
|
84
|
+
pool: Queryable;
|
|
84
85
|
/**
|
|
85
86
|
* Called when drain_changes() returns new change_log entries.
|
|
86
87
|
* The entries include subscriber_ids for fan-out routing.
|
|
@@ -104,9 +105,10 @@ export interface RealtimeManagerOptions {
|
|
|
104
105
|
*/
|
|
105
106
|
pgSubscriber: unknown;
|
|
106
107
|
/**
|
|
107
|
-
*
|
|
108
|
+
* A query-capable object (typically a pg.Pool from pg-cache) used by
|
|
109
|
+
* the underlying CursorTracker for drain_changes() polling.
|
|
108
110
|
*/
|
|
109
|
-
|
|
111
|
+
pool: Queryable;
|
|
110
112
|
/**
|
|
111
113
|
* Unique identifier for this listener node.
|
|
112
114
|
* Should be stable across restarts if you want cursor continuity.
|