@rebasepro/server-postgres 0.23.0 → 0.24.1-canary.g56af4a4

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 (180) hide show
  1. package/dist/{BranchService-DGPL6G_C.js → BranchService-Bsn2wfaj.js} +36 -18
  2. package/dist/BranchService-Bsn2wfaj.js.map +1 -0
  3. package/dist/PostgresBackendDriver.d.ts +166 -13
  4. package/dist/auth/ensure-tables.d.ts +12 -0
  5. package/dist/auth/services.d.ts +78 -14
  6. package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
  7. package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
  8. package/dist/backup/backup-cli.d.ts +14 -0
  9. package/dist/backup/backup-service.d.ts +2 -2
  10. package/dist/backup/pg-binary.d.ts +5 -0
  11. package/dist/{backup-cli-24v4OlSp.js → backup-cli-aUfRv16h.js} +37 -29
  12. package/dist/backup-cli-aUfRv16h.js.map +1 -0
  13. package/dist/{backup-service-HQ9GC4tN.js → backup-service-BTBB7RXn.js} +60 -28
  14. package/dist/backup-service-BTBB7RXn.js.map +1 -0
  15. package/dist/boot-ddl-DVpsFN6r.js +105 -0
  16. package/dist/boot-ddl-DVpsFN6r.js.map +1 -0
  17. package/dist/cli-errors-CaI4ntyD.js +1919 -0
  18. package/dist/cli-errors-CaI4ntyD.js.map +1 -0
  19. package/dist/cli-errors.d.ts +93 -5
  20. package/dist/{cli-flags-BglvpjHv.js → cli-flags-B-Dawocl.js} +5 -2
  21. package/dist/cli-flags-B-Dawocl.js.map +1 -0
  22. package/dist/cli-helpers.d.ts +52 -12
  23. package/dist/cli-output.d.ts +16 -28
  24. package/dist/cli-push.d.ts +87 -0
  25. package/dist/cli.js +625 -190
  26. package/dist/cli.js.map +1 -1
  27. package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-drZIsSNf.js} +42 -26
  28. package/dist/column-plan-helpers-drZIsSNf.js.map +1 -0
  29. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-BPoNILnD.js} +19 -110
  30. package/dist/connection-BPoNILnD.js.map +1 -0
  31. package/dist/connection.d.ts +9 -1
  32. package/dist/data-transformer.d.ts +0 -8
  33. package/dist/databasePoolManager-BkEy2w18.js +119 -0
  34. package/dist/databasePoolManager-BkEy2w18.js.map +1 -0
  35. package/dist/deferred-index-build-DP1_SNFo.js +1051 -0
  36. package/dist/deferred-index-build-DP1_SNFo.js.map +1 -0
  37. package/dist/{doctor-Cb2thZ8s.js → doctor-C4WiftHm.js} +279 -87
  38. package/dist/doctor-C4WiftHm.js.map +1 -0
  39. package/dist/ensure-collection-policies-CXpbeNag.js +541 -0
  40. package/dist/ensure-collection-policies-CXpbeNag.js.map +1 -0
  41. package/dist/ensure-collection-tables-BhsU6qUE.js +532 -0
  42. package/dist/ensure-collection-tables-BhsU6qUE.js.map +1 -0
  43. package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-CHsgyLqg.js} +178 -75
  44. package/dist/ensure-tables-CHsgyLqg.js.map +1 -0
  45. package/dist/enum-labels-kept-D8fI1MoX.js +56 -0
  46. package/dist/enum-labels-kept-D8fI1MoX.js.map +1 -0
  47. package/dist/generate-drizzle-schema-Bz52jH2h.js +57 -0
  48. package/dist/generate-drizzle-schema-Bz52jH2h.js.map +1 -0
  49. package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-B74WN37S.js} +28 -10
  50. package/dist/generate-drizzle-schema-logic-B74WN37S.js.map +1 -0
  51. package/dist/generate-files-CdEckX6c.js +199 -0
  52. package/dist/generate-files-CdEckX6c.js.map +1 -0
  53. package/dist/generate-postgres-ddl-logic-B64O2oZF.js +152 -0
  54. package/dist/generate-postgres-ddl-logic-B64O2oZF.js.map +1 -0
  55. package/dist/generated-sql.d.ts +34 -0
  56. package/dist/history/HistoryService.d.ts +38 -6
  57. package/dist/history/ensure-history-table.d.ts +42 -4
  58. package/dist/index.es.js +5557 -7612
  59. package/dist/index.es.js.map +1 -1
  60. package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-C-Di8dA9.js} +562 -350
  61. package/dist/introspect-db-logic-C-Di8dA9.js.map +1 -0
  62. package/dist/introspect-db-queries-D6kAqReI.js +339 -0
  63. package/dist/introspect-db-queries-D6kAqReI.js.map +1 -0
  64. package/dist/pg-error-utils-vJezlXdI.js +388 -0
  65. package/dist/pg-error-utils-vJezlXdI.js.map +1 -0
  66. package/dist/{plan-schema-C0fxM8dY.js → plan-schema-B1YF_tYq.js} +278 -533
  67. package/dist/plan-schema-B1YF_tYq.js.map +1 -0
  68. package/dist/{policy-drift-DljYdrpW.js → policy-drift-Dm8i-s1J.js} +24 -7
  69. package/dist/policy-drift-Dm8i-s1J.js.map +1 -0
  70. package/dist/push-index-prebuild-Db4AQF4i.js +45 -0
  71. package/dist/push-index-prebuild-Db4AQF4i.js.map +1 -0
  72. package/dist/push-legacy-renames-BK6ilQq2.js +34 -0
  73. package/dist/push-legacy-renames-BK6ilQq2.js.map +1 -0
  74. package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CtC3y9Ol.js} +17 -151
  75. package/dist/render-ddl-CtC3y9Ol.js.map +1 -0
  76. package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-RQUljnJ1.js} +125 -1046
  77. package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +1 -0
  78. package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-DvzqlaL1.js} +137 -17
  79. package/dist/rls-enforcement-DvzqlaL1.js.map +1 -0
  80. package/dist/schema/auth-schema.d.ts +172 -2
  81. package/dist/schema/boot-ddl.d.ts +47 -0
  82. package/dist/schema/classify-change.d.ts +29 -1
  83. package/dist/schema/collection-index.d.ts +45 -0
  84. package/dist/schema/column-plan-helpers.d.ts +38 -21
  85. package/dist/schema/deferred-index-build.d.ts +131 -0
  86. package/dist/schema/destructive-sql.d.ts +27 -1
  87. package/dist/schema/doctor-cli.js +110 -6
  88. package/dist/schema/doctor-cli.js.map +1 -1
  89. package/dist/schema/doctor-search-scale.d.ts +45 -0
  90. package/dist/schema/doctor.d.ts +53 -1
  91. package/dist/schema/ensure-collection-policies.d.ts +95 -35
  92. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  93. package/dist/schema/enum-labels-kept.d.ts +26 -0
  94. package/dist/schema/generate-drizzle-schema.js +1 -1
  95. package/dist/schema/generate-files.d.ts +44 -0
  96. package/dist/schema/generate-next-step.d.ts +0 -13
  97. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
  98. package/dist/schema/generate-postgres-ddl.js +10 -81
  99. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  100. package/dist/schema/generate-schema-commit.d.ts +12 -0
  101. package/dist/schema/introspect-db-logic.d.ts +43 -0
  102. package/dist/schema/introspect-db-queries.d.ts +12 -2
  103. package/dist/schema/introspect-db-search.d.ts +22 -0
  104. package/dist/schema/introspect-db-storage.d.ts +83 -0
  105. package/dist/schema/introspect-db-structure.d.ts +10 -0
  106. package/dist/schema/introspect-db.js +17 -6
  107. package/dist/schema/introspect-db.js.map +1 -1
  108. package/dist/schema/migration-post-process.d.ts +34 -0
  109. package/dist/schema/plan/diff-plan.d.ts +127 -1
  110. package/dist/schema/plan/plan-schema.d.ts +16 -8
  111. package/dist/schema/plan/render-ddl.d.ts +6 -0
  112. package/dist/schema/plan/types.d.ts +45 -11
  113. package/dist/schema/push-index-prebuild.d.ts +25 -0
  114. package/dist/schema/push-legacy-renames.d.ts +20 -0
  115. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  116. package/dist/search-column-BM-GV6vH.js +442 -0
  117. package/dist/search-column-BM-GV6vH.js.map +1 -0
  118. package/dist/security/policy-drift.d.ts +1 -1
  119. package/dist/security/rls-enforcement.d.ts +39 -4
  120. package/dist/services/BranchService.d.ts +22 -1
  121. package/dist/services/FetchService.d.ts +63 -12
  122. package/dist/services/PersistService.d.ts +15 -0
  123. package/dist/services/RelationService.d.ts +106 -3
  124. package/dist/services/cdc/CdcListener.d.ts +26 -10
  125. package/dist/services/cdc/identity-columns.d.ts +19 -0
  126. package/dist/services/cdc/trigger-cdc.d.ts +180 -18
  127. package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
  128. package/dist/services/channel-history.d.ts +32 -5
  129. package/dist/services/collection-helpers.d.ts +26 -1
  130. package/dist/services/field-viewer.d.ts +15 -1
  131. package/dist/services/pg-notify-listener.d.ts +95 -6
  132. package/dist/services/realtimeService.d.ts +467 -53
  133. package/dist/services/row-pipeline.d.ts +16 -9
  134. package/dist/services/socket-liveness.d.ts +85 -0
  135. package/dist/services/soft-delete.d.ts +12 -0
  136. package/dist/services/sql-script.d.ts +275 -0
  137. package/dist/services/vector-iterative-scan.d.ts +40 -0
  138. package/dist/services/write-depth.d.ts +22 -0
  139. package/dist/services/write-transaction-scope.d.ts +15 -3
  140. package/dist/socket-liveness-D3UD5_BW.js +7570 -0
  141. package/dist/socket-liveness-D3UD5_BW.js.map +1 -0
  142. package/dist/socket-opt-ins.d.ts +64 -0
  143. package/dist/source-DBDbBTL4.js +811 -0
  144. package/dist/source-DBDbBTL4.js.map +1 -0
  145. package/dist/tsx-loader.d.ts +14 -0
  146. package/dist/utils/drizzle-conditions.d.ts +63 -3
  147. package/dist/utils/error-cause.d.ts +7 -0
  148. package/dist/utils/pg-error-utils.d.ts +2 -4
  149. package/dist/utils/result-rows.d.ts +10 -0
  150. package/dist/websocket.d.ts +63 -18
  151. package/package.json +7 -7
  152. package/dist/BranchService-DGPL6G_C.js.map +0 -1
  153. package/dist/backup-cli-24v4OlSp.js.map +0 -1
  154. package/dist/backup-service-HQ9GC4tN.js.map +0 -1
  155. package/dist/cli-errors-B8qHg02P.js +0 -798
  156. package/dist/cli-errors-B8qHg02P.js.map +0 -1
  157. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  158. package/dist/cli-output-CNdMql-L.js +0 -47
  159. package/dist/cli-output-CNdMql-L.js.map +0 -1
  160. package/dist/collection-index-DdnsxiJ_.js +0 -286
  161. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  162. package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
  163. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  164. package/dist/doctor-Cb2thZ8s.js.map +0 -1
  165. package/dist/ensure-collection-policies-RHUcEp4v.js +0 -105
  166. package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
  167. package/dist/ensure-collection-tables-BEEjn5cn.js +0 -857
  168. package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
  169. package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
  170. package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
  171. package/dist/generate-drizzle-schema-yzY_BLhr.js +0 -123
  172. package/dist/generate-drizzle-schema-yzY_BLhr.js.map +0 -1
  173. package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
  174. package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
  175. package/dist/plan-schema-C0fxM8dY.js.map +0 -1
  176. package/dist/policy-drift-DljYdrpW.js.map +0 -1
  177. package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
  178. package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
  179. package/dist/source-Br7L7GOI.js +0 -410
  180. package/dist/source-Br7L7GOI.js.map +0 -1
@@ -53,6 +53,22 @@ export interface ResolvedRetention {
53
53
  limit?: number;
54
54
  ttlMs?: number;
55
55
  }
56
+ /**
57
+ * A bus frame to send in the statement that numbers a retained message — see
58
+ * {@link ChannelHistoryStore.append}.
59
+ *
60
+ * It is always a pointer, `{ kind: "broadcast_ref", sid, channel, seq }`, and
61
+ * never the message. Postgres puts no privilege on `LISTEN`, so a NOTIFY payload
62
+ * reaches every role that can connect to the database; the body stays in
63
+ * `rebase.channel_messages`, behind the table grants, and each receiving
64
+ * instance reads it back by `(channel, seq)` — the sender's id with it.
65
+ */
66
+ export interface RetainedAnnouncement {
67
+ /** The NOTIFY channel the bus listens on. */
68
+ notifyChannel: string;
69
+ /** The sending instance. */
70
+ sid: string;
71
+ }
56
72
  /**
57
73
  * Persistence and replay for retained channels.
58
74
  *
@@ -63,10 +79,21 @@ export interface ResolvedRetention {
63
79
  export declare class ChannelHistoryStore {
64
80
  private db;
65
81
  private rules;
66
- /** Resolved rule per channel name, so the match runs once per channel. */
67
- private resolved;
68
- /** Channel → timestamp of its last prune, for {@link PRUNE_THROTTLE_MS}. */
82
+ /**
83
+ * Each rule with its retention resolved, once. Matched per call rather
84
+ * than cached per channel name: a cache keyed by the names clients invent
85
+ * grew by every one of them for the life of the process, and matching a
86
+ * handful of prefixes costs less than the hashing a lookup does.
87
+ */
88
+ private resolvedRules;
89
+ /**
90
+ * Channel → timestamp of its last prune, for {@link PRUNE_THROTTLE_MS}.
91
+ * An entry older than the window throttles nothing, so they are swept once
92
+ * a window: the map holds the channels pruned recently, not every channel
93
+ * ever pruned.
94
+ */
69
95
  private lastPruned;
96
+ private nextPruneSweepAt;
70
97
  private tablesReady;
71
98
  constructor(db: NodePgDatabase<Record<string, unknown>>, rules?: ChannelRetentionRule[]);
72
99
  /** Whether any channel retains anything at all. */
@@ -91,7 +118,7 @@ export declare class ChannelHistoryStore {
91
118
  * broadcasts to one channel line up in a single order — and what keeps
92
119
  * different channels from contending with each other at all.
93
120
  */
94
- append(channel: string, event: string, payload: unknown, senderId?: string): Promise<{
121
+ append(channel: string, event: string, payload: unknown, senderId?: string, announce?: RetainedAnnouncement): Promise<{
95
122
  seq: number;
96
123
  at: string;
97
124
  }>;
@@ -124,6 +151,6 @@ export declare class ChannelHistoryStore {
124
151
  * volume, which is what makes retention affordable on a hot channel.
125
152
  */
126
153
  prune(channel: string, retention: ResolvedRetention): Promise<number>;
127
- /** Forget throttle and match caches. Called on shutdown. */
154
+ /** Forget the prune throttle. Called on shutdown. */
128
155
  clear(): void;
129
156
  }
@@ -1,5 +1,5 @@
1
1
  import { PgTable, AnyPgColumn } from "drizzle-orm/pg-core";
2
- import { SQL } from "drizzle-orm";
2
+ import { type SQL } from "drizzle-orm";
3
3
  import { CollectionConfig, ResolvedHasMany, ResolvedHasOne } from "@rebasepro/types";
4
4
  import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry.js";
5
5
  import { ApiError } from "@rebasepro/server";
@@ -133,6 +133,31 @@ export declare function getPrimaryKeys(collection: CollectionConfig, registry: P
133
133
  * wrong and which collection it is wrong about.
134
134
  */
135
135
  export declare function requirePrimaryKeys(collection: CollectionConfig, registry: PostgresCollectionRegistry): PrimaryKeyInfo[];
136
+ /**
137
+ * The table's key columns, in key order.
138
+ *
139
+ * Throws when there are none, or when the table has no column for one of them:
140
+ * a WHERE built from fewer columns than the key matches every row that shares
141
+ * the columns it did find, so there is no partial answer worth returning.
142
+ *
143
+ * @param collectionPath named in the error.
144
+ */
145
+ export declare function primaryKeyColumns(table: PgTable, primaryKeys: PrimaryKeyInfo[], collectionPath: string): AnyPgColumn[];
146
+ /**
147
+ * The column a `joinPath` step names, on a table that may key it either way.
148
+ *
149
+ * A `JoinStep` is written in database terms — `on: { from: "author_id" }` —
150
+ * because there is no collection to translate through: the step names a raw
151
+ * table. A table the boot reads back out of the catalogue is keyed by the
152
+ * *wire* name, `authorId`, while a hand-written Drizzle schema may key it by
153
+ * the column name. Looking the step's name up as a key therefore found
154
+ * nothing on every booted table, and a `via` relation loaded nothing at all.
155
+ *
156
+ * The key first, as the step may already name one; then the column whose SQL
157
+ * name it is. `undefined` when the table has neither, which the caller turns
158
+ * into an error naming the step.
159
+ */
160
+ export declare function columnNamed(table: PgTable, name: string): AnyPgColumn | undefined;
136
161
  /**
137
162
  * The column on the *source* table that a `hasOne`/`hasMany` link points at.
138
163
  *
@@ -1,4 +1,5 @@
1
- import type { FieldViewer } from "@rebasepro/common";
1
+ import { type FieldViewer } from "@rebasepro/common";
2
+ import type { CollectionConfig } from "@rebasepro/types";
2
3
  /**
3
4
  * Run `fn` with `viewer` as the caller every row strip judges against.
4
5
  *
@@ -17,3 +18,16 @@ export declare function withFieldViewer<T>(viewer: FieldViewer | undefined, fn:
17
18
  * that is what establishes the viewer.
18
19
  */
19
20
  export declare function currentFieldViewer(): FieldViewer | undefined;
21
+ /**
22
+ * The names an API caller's query may not see on `collection`: its
23
+ * `excludeFromApi` fields, in every spelling. Empty on the trusted server plane
24
+ * (no viewer), which may filter and sort on those columns — the auth adapter
25
+ * looks rows up by them — and empty with no collection in hand.
26
+ *
27
+ * A query naming one of these is answered exactly as a query naming a column
28
+ * the table does not have. The REST check passes such a name through to this
29
+ * driver rather than refusing it itself, so the two answers come from one throw
30
+ * and cannot drift: a "not readable" there would confirm a column OpenAPI and
31
+ * `list_collections` hide.
32
+ */
33
+ export declare function namesHiddenFromCaller(collection: CollectionConfig | undefined): ReadonlySet<string>;
@@ -3,11 +3,11 @@
3
3
  *
4
4
  * Every cross-instance feature in the backend needs the same thing: one
5
5
  * connection *outside* the Drizzle pool that stays open, holds a `LISTEN`, and
6
- * comes back on its own after the database or the network drops it. CDC needed
7
- * it first; the channel bus needs it too. This is that connection, with the one
8
- * behaviour that matters to callers preserved: the **first** connect is
9
- * validated and rethrown, so a caller can fall back to a different strategy,
10
- * while every later drop is repaired quietly in the background.
6
+ * comes back on its own after the database or the network drops it. CDC, the
7
+ * channel bus and the cross-instance broadcast all use this one. By default the
8
+ * **first** connect is validated and rethrown, so a caller can fall back to a
9
+ * different strategy, while every later drop is repaired quietly in the
10
+ * background; `retryInitialConnect` repairs the first one the same way.
11
11
  *
12
12
  * `LISTEN` is session state, so this connection must not go through a
13
13
  * transaction-mode pooler (PgBouncer): give it the direct database URL.
@@ -30,25 +30,114 @@ export interface PgNotifyListenerOptions {
30
30
  * gone for good; a caller that mirrors state from the channel resyncs here.
31
31
  */
32
32
  onReconnect?: () => void;
33
+ /**
34
+ * How often the connection proves it is alive with a round trip. `0`
35
+ * turns the heartbeat off. See {@link PgNotifyListener} for why it exists.
36
+ */
37
+ heartbeatIntervalMs?: number;
38
+ /** How long a heartbeat may take before the connection is declared dead. */
39
+ heartbeatTimeoutMs?: number;
40
+ /**
41
+ * Treat a failed first connect like any later drop: `start()` resolves,
42
+ * the listener reports itself down, and it keeps dialling in the
43
+ * background. For a caller with nothing to fall back to, for whom a
44
+ * database that is unreachable at boot is an outage to wait out rather
45
+ * than a reason to run without the channel. Off by default: `start()`
46
+ * rejects, so a caller with a fallback can take it.
47
+ */
48
+ retryInitialConnect?: boolean;
49
+ /**
50
+ * The `application_name` the connection reports in `pg_stat_activity`.
51
+ * Visible to every role, which is what lets one instance count the others
52
+ * (see `RealtimeService.warnIfMemoryBusOnMultiplePods`). A connection
53
+ * string that sets its own `application_name` wins.
54
+ */
55
+ applicationName?: string;
56
+ }
57
+ /**
58
+ * What a listener can say about itself — read by `/health`.
59
+ *
60
+ * `connected` is "listening, and the last heartbeat answered", not "a socket
61
+ * object exists": a half-open connection has a perfectly good socket object.
62
+ */
63
+ export interface PgNotifyListenerStatus {
64
+ channel: string;
65
+ connected: boolean;
66
+ /** Since when (epoch ms) it has been down, while it is down. */
67
+ downSince?: number;
68
+ /** When (epoch ms) it last answered a heartbeat. */
69
+ lastHeartbeatAt?: number;
33
70
  }
71
+ /**
72
+ * Below every common idle-flow timeout: Azure's load balancer drops an idle
73
+ * flow at 4 minutes, AWS's NLB at 350 s, and NAT and conntrack tables and
74
+ * pooler-side reapers are often shorter. A LISTEN connection is idle by
75
+ * nature, so it is exactly the connection those timeouts kill.
76
+ */
77
+ export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 30000;
78
+ export declare const DEFAULT_HEARTBEAT_TIMEOUT_MS = 10000;
79
+ /**
80
+ * A half-open connection — one whose packets stop arriving, with no FIN and no
81
+ * RST, which is what an idle-flow timeout on a load balancer, a NAT, a dead
82
+ * middlebox or a failed-over network looks like — fires no `error` and no
83
+ * `end`. The connection object stays, `LISTEN` stays registered on a backend
84
+ * that may no longer exist, and every notification from then on is lost: CDC,
85
+ * the cross-instance broadcast and the channel bus all went quiet with no
86
+ * error, no reconnect and a green `/health`, while writes made through this
87
+ * instance still looked live because they are delivered locally.
88
+ *
89
+ * So the connection proves itself: TCP keepalive is on, and every
90
+ * {@link DEFAULT_HEARTBEAT_INTERVAL_MS} a `SELECT 1` must come back within
91
+ * {@link DEFAULT_HEARTBEAT_TIMEOUT_MS}. One that does not is torn down and
92
+ * replaced, and `onReconnect` resyncs whatever was missed.
93
+ */
34
94
  export declare class PgNotifyListener {
35
95
  private readonly options;
36
96
  private client?;
37
97
  private running;
38
98
  private reconnectTimer?;
99
+ private heartbeatTimer?;
100
+ private heartbeatInFlight;
101
+ private downSince?;
102
+ private lastHeartbeatAt?;
39
103
  constructor(options: PgNotifyListenerOptions);
40
104
  /** Whether the listener is meant to be connected right now. */
41
105
  get active(): boolean;
106
+ /** Whether it is listening on a connection that answered its last heartbeat. */
107
+ get connected(): boolean;
108
+ status(): PgNotifyListenerStatus;
42
109
  /**
43
110
  * Connect and begin listening. Idempotent.
44
111
  *
45
112
  * Rejects if the *initial* connection or `LISTEN` fails, leaving the
46
113
  * listener stopped — callers use that to degrade deliberately instead of
47
- * running blind against a channel nothing is delivering.
114
+ * running blind against a channel nothing is delivering. With
115
+ * `retryInitialConnect` it resolves instead, reports itself down, and
116
+ * keeps dialling.
48
117
  */
49
118
  start(): Promise<void>;
50
119
  /** Stop listening and release the connection. Idempotent. */
51
120
  stop(): Promise<void>;
121
+ /**
122
+ * @param first The connect `start()` makes: never a reconnect, so it owes
123
+ * no resync, and — unless `retryInitialConnect` — a failure is
124
+ * thrown to the caller rather than retried.
125
+ */
52
126
  private connect;
127
+ /**
128
+ * Give up on the current connection: it is no longer the listener's, so
129
+ * its late events are ignored and `connected` says so at once; it is
130
+ * ended without waiting on it; and a replacement is scheduled.
131
+ */
132
+ private abandon;
133
+ private startHeartbeat;
134
+ private stopHeartbeat;
135
+ /**
136
+ * One round trip on the LISTEN connection. Not answered in time, it is
137
+ * treated as dead: the client is ended — which, with the query still
138
+ * outstanding, destroys the socket rather than waiting on a goodbye that
139
+ * cannot arrive — and replaced.
140
+ */
141
+ private heartbeat;
53
142
  private scheduleReconnect;
54
143
  }