graphile-realtime-subscriptions 0.5.0 → 0.6.0

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.
@@ -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 withPgClient callback that acquires a PgClient
13
- * for each operation. This keeps connection management external.
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 withPgClient;
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 withPgClient callback that acquires a PgClient
14
- * for each operation. This keeps connection management external.
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
- withPgClient;
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.withPgClient = options.withPgClient;
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 entries = await this.withPgClient(async (client) => {
91
- const sql = `SELECT * FROM ${this.quoteIdent(this.schema)}.drain_changes($1, $2)`;
92
- const result = await client.query(sql, [this.nodeId, this.batchLimit]);
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
- await this.withPgClient(async (client) => {
112
- const sql = `SELECT ${this.quoteIdent(this.schema)}.touch_listener($1)`;
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
- await this.withPgClient(async (client) => {
123
- const sql = `SELECT ${this.quoteIdent(this.schema)}.cleanup_ephemeral($1)`;
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) {
@@ -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 withPgClient callback that acquires a PgClient
13
- * for each operation. This keeps connection management external.
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 withPgClient;
26
+ private readonly pool;
27
27
  private readonly onChanges;
28
28
  private readonly onError;
29
29
  private pollTimer;
@@ -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 withPgClient callback that acquires a PgClient
13
- * for each operation. This keeps connection management external.
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
- withPgClient;
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.withPgClient = options.withPgClient;
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 entries = await this.withPgClient(async (client) => {
84
- const sql = `SELECT * FROM ${this.quoteIdent(this.schema)}.drain_changes($1, $2)`;
85
- const result = await client.query(sql, [this.nodeId, this.batchLimit]);
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
- await this.withPgClient(async (client) => {
105
- const sql = `SELECT ${this.quoteIdent(this.schema)}.touch_listener($1)`;
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
- await this.withPgClient(async (client) => {
116
- const sql = `SELECT ${this.quoteIdent(this.schema)}.cleanup_ephemeral($1)`;
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, PgClient, WithPgClient, RealtimeManagerOptions, } from './types';
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, WithPgClient, PgClient, RealtimeManagerOptions } from './types';
83
+ export type { CursorTrackerOptions, ChangeLogEntry, Queryable, RealtimeManagerOptions } from './types';
84
84
  export { parseNotifyPayload, EventThrottle, DEFAULT_OVERFLOW_THRESHOLD };
@@ -61,7 +61,7 @@ export class RealtimeManager {
61
61
  subscriber;
62
62
  started = false;
63
63
  constructor(options) {
64
- const { pgSubscriber, withPgClient, ...cursorOpts } = options;
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
- withPgClient,
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 PostgreSQL client interface used by CursorTracker.
17
- * Compatible with node-postgres (pg) Client or PoolClient.
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 PgClient {
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
- * Function to acquire a PgClient for executing queries.
81
- * The cursor tracker calls this for every poll and heartbeat cycle.
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
- withPgClient: WithPgClient;
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
- * Function to acquire a PgClient for executing cursor tracking queries.
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
- withPgClient: WithPgClient;
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, PgClient, WithPgClient, RealtimeManagerOptions, } from './types';
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.5.0",
3
+ "version": "0.6.0",
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.10.0",
43
+ "@pgpmjs/logger": "^2.11.0",
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": "c665e2443ec98da61e9f372cf663cb9973afef4b"
59
+ "gitHead": "50dc1d69049c207b46c237eba1e1b1cb7a5f928f"
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, WithPgClient, PgClient, RealtimeManagerOptions } from './types';
83
+ export type { CursorTrackerOptions, ChangeLogEntry, Queryable, RealtimeManagerOptions } from './types';
84
84
  export { parseNotifyPayload, EventThrottle, DEFAULT_OVERFLOW_THRESHOLD };
@@ -67,7 +67,7 @@ class RealtimeManager {
67
67
  subscriber;
68
68
  started = false;
69
69
  constructor(options) {
70
- const { pgSubscriber, withPgClient, ...cursorOpts } = options;
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
- withPgClient,
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 PostgreSQL client interface used by CursorTracker.
17
- * Compatible with node-postgres (pg) Client or PoolClient.
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 PgClient {
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
- * Function to acquire a PgClient for executing queries.
81
- * The cursor tracker calls this for every poll and heartbeat cycle.
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
- withPgClient: WithPgClient;
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
- * Function to acquire a PgClient for executing cursor tracking queries.
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
- withPgClient: WithPgClient;
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.