@rebasepro/server-postgres 0.23.0 → 0.24.1-canary.g19596ef
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 +16 -0
- package/dist/{BranchService-DGPL6G_C.js → BranchService-UgKa416x.js} +47 -19
- package/dist/BranchService-UgKa416x.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +187 -13
- package/dist/PostgresBootstrapper.d.ts +101 -2
- package/dist/auth/ensure-tables.d.ts +12 -0
- package/dist/auth/services.d.ts +78 -14
- package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
- package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
- package/dist/backup/backup-cli.d.ts +75 -2
- package/dist/backup/backup-service.d.ts +2 -2
- package/dist/backup/pg-binary.d.ts +5 -0
- package/dist/{backup-cli-24v4OlSp.js → backup-cli-yiKd1GsC.js} +168 -75
- package/dist/backup-cli-yiKd1GsC.js.map +1 -0
- package/dist/{backup-service-HQ9GC4tN.js → backup-service-AZWc2wvI.js} +104 -35
- package/dist/backup-service-AZWc2wvI.js.map +1 -0
- package/dist/boot-ddl-B9ZmBJ75.js +105 -0
- package/dist/boot-ddl-B9ZmBJ75.js.map +1 -0
- package/dist/cli-console-DGee3Zl7.js +225 -0
- package/dist/cli-console-DGee3Zl7.js.map +1 -0
- package/dist/cli-console.d.ts +39 -0
- package/dist/cli-errors-oVUuD8k0.js +2180 -0
- package/dist/cli-errors-oVUuD8k0.js.map +1 -0
- package/dist/cli-errors.d.ts +133 -9
- package/dist/{cli-flags-BglvpjHv.js → cli-flags-DnPHXjRH.js} +56 -6
- package/dist/cli-flags-DnPHXjRH.js.map +1 -0
- package/dist/cli-flags.d.ts +15 -0
- package/dist/cli-helpers.d.ts +88 -34
- package/dist/cli-output.d.ts +16 -28
- package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
- package/dist/cli-prompt-egtNKkZl.js.map +1 -0
- package/dist/cli-prompt.d.ts +19 -0
- package/dist/cli-push.d.ts +87 -0
- package/dist/cli-scratch-database.d.ts +33 -8
- package/dist/cli.js +826 -230
- package/dist/cli.js.map +1 -1
- package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-D_V4oaZh.js} +43 -27
- package/dist/column-plan-helpers-D_V4oaZh.js.map +1 -0
- package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C938nR3E.js} +25 -113
- package/dist/connection-C938nR3E.js.map +1 -0
- package/dist/connection.d.ts +13 -2
- package/dist/data-transformer.d.ts +0 -8
- package/dist/databasePoolManager-DZgnBOgj.js +119 -0
- package/dist/databasePoolManager-DZgnBOgj.js.map +1 -0
- package/dist/deferred-index-build-BqmEHGaD.js +1051 -0
- package/dist/deferred-index-build-BqmEHGaD.js.map +1 -0
- package/dist/{doctor-Cb2thZ8s.js → doctor-CUAAOFZm.js} +281 -89
- package/dist/doctor-CUAAOFZm.js.map +1 -0
- package/dist/ensure-collection-policies-Bb51V2OV.js +541 -0
- package/dist/ensure-collection-policies-Bb51V2OV.js.map +1 -0
- package/dist/ensure-collection-tables-egjGBjhv.js +532 -0
- package/dist/ensure-collection-tables-egjGBjhv.js.map +1 -0
- package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-DMPWhJ6R.js} +180 -77
- package/dist/ensure-tables-DMPWhJ6R.js.map +1 -0
- package/dist/enum-labels-kept-Msq5gMcb.js +56 -0
- package/dist/enum-labels-kept-Msq5gMcb.js.map +1 -0
- package/dist/generate-drizzle-schema-DrA-YgXv.js +57 -0
- package/dist/generate-drizzle-schema-DrA-YgXv.js.map +1 -0
- package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-BaT0H7Gq.js} +29 -11
- package/dist/generate-drizzle-schema-logic-BaT0H7Gq.js.map +1 -0
- package/dist/generate-files-OoRJLEdk.js +199 -0
- package/dist/generate-files-OoRJLEdk.js.map +1 -0
- package/dist/generate-postgres-ddl-logic-B4k9djAV.js +152 -0
- package/dist/generate-postgres-ddl-logic-B4k9djAV.js.map +1 -0
- package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-BWN_PsMi.js} +2 -2
- package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-BWN_PsMi.js.map} +1 -1
- package/dist/generated-sql.d.ts +34 -0
- package/dist/history/HistoryService.d.ts +64 -5
- package/dist/history/ensure-history-table.d.ts +63 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +139 -16738
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-BCc1Jym2.js} +654 -353
- package/dist/introspect-db-logic-BCc1Jym2.js.map +1 -0
- package/dist/introspect-db-queries-BhhJg0b8.js +364 -0
- package/dist/introspect-db-queries-BhhJg0b8.js.map +1 -0
- package/dist/open-rebase-CAjWpPjs.js +20895 -0
- package/dist/open-rebase-CAjWpPjs.js.map +1 -0
- package/dist/open-rebase.d.ts +94 -0
- package/dist/pg-error-utils-Bs_Ue4wn.js +395 -0
- package/dist/pg-error-utils-Bs_Ue4wn.js.map +1 -0
- package/dist/{plan-schema-C0fxM8dY.js → plan-schema-CsNgK0qi.js} +279 -534
- package/dist/plan-schema-CsNgK0qi.js.map +1 -0
- package/dist/{policy-drift-DljYdrpW.js → policy-drift-BKC50y2p.js} +33 -9
- package/dist/policy-drift-BKC50y2p.js.map +1 -0
- package/dist/push-index-prebuild-C_qhyoJa.js +45 -0
- package/dist/push-index-prebuild-C_qhyoJa.js.map +1 -0
- package/dist/push-legacy-renames-5g9h3HOa.js +34 -0
- package/dist/push-legacy-renames-5g9h3HOa.js.map +1 -0
- package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CrfEe__B.js} +24 -157
- package/dist/render-ddl-CrfEe__B.js.map +1 -0
- package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-Bd-hJk08.js} +125 -1101
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
- package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-D7P4-ZYX.js} +138 -18
- package/dist/rls-enforcement-D7P4-ZYX.js.map +1 -0
- package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
- package/dist/schema/atlas-exclude.d.ts +42 -0
- package/dist/schema/auth-schema.d.ts +172 -2
- package/dist/schema/boot-ddl.d.ts +47 -0
- package/dist/schema/carved-out-migration.d.ts +5 -4
- package/dist/schema/classify-change.d.ts +29 -1
- package/dist/schema/collection-index.d.ts +45 -0
- package/dist/schema/column-plan-helpers.d.ts +38 -21
- package/dist/schema/deferred-index-build.d.ts +131 -0
- package/dist/schema/destructive-sql.d.ts +50 -2
- package/dist/schema/doctor-cli.js +110 -6
- package/dist/schema/doctor-cli.js.map +1 -1
- package/dist/schema/doctor-search-scale.d.ts +45 -0
- package/dist/schema/doctor.d.ts +53 -1
- package/dist/schema/ensure-collection-policies.d.ts +95 -35
- package/dist/schema/ensure-collection-tables.d.ts +56 -3
- package/dist/schema/enum-labels-kept.d.ts +26 -0
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-files.d.ts +44 -0
- package/dist/schema/generate-next-step.d.ts +0 -13
- package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
- package/dist/schema/generate-postgres-ddl.js +10 -81
- package/dist/schema/generate-postgres-ddl.js.map +1 -1
- package/dist/schema/generate-schema-commit.d.ts +12 -0
- package/dist/schema/generated-schema-diff.d.ts +7 -0
- package/dist/schema/introspect-db-logic.d.ts +63 -0
- package/dist/schema/introspect-db-queries.d.ts +21 -2
- package/dist/schema/introspect-db-search.d.ts +22 -0
- package/dist/schema/introspect-db-storage.d.ts +83 -0
- package/dist/schema/introspect-db-structure.d.ts +10 -0
- package/dist/schema/introspect-db.js +29 -21
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-baseline.d.ts +33 -0
- package/dist/schema/migration-post-process.d.ts +34 -0
- package/dist/schema/plan/diff-plan.d.ts +127 -1
- package/dist/schema/plan/plan-schema.d.ts +16 -8
- package/dist/schema/plan/render-ddl.d.ts +8 -1
- package/dist/schema/plan/types.d.ts +45 -11
- package/dist/schema/push-index-prebuild.d.ts +25 -0
- package/dist/schema/push-legacy-renames.d.ts +20 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
- package/dist/schema/undeclarable-constraints.d.ts +59 -0
- package/dist/search-column-BM-GV6vH.js +442 -0
- package/dist/search-column-BM-GV6vH.js.map +1 -0
- package/dist/security/policy-drift.d.ts +1 -1
- package/dist/security/rls-enforcement.d.ts +39 -4
- package/dist/services/BranchService.d.ts +29 -1
- package/dist/services/FetchService.d.ts +63 -12
- package/dist/services/PersistService.d.ts +15 -0
- package/dist/services/RelationService.d.ts +106 -3
- package/dist/services/cdc/CdcListener.d.ts +26 -10
- package/dist/services/cdc/identity-columns.d.ts +19 -0
- package/dist/services/cdc/trigger-cdc.d.ts +180 -18
- package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
- package/dist/services/channel-bus/index.d.ts +1 -1
- package/dist/services/channel-history.d.ts +32 -5
- package/dist/services/collection-helpers.d.ts +26 -1
- package/dist/services/field-viewer.d.ts +15 -1
- package/dist/services/pg-notify-listener.d.ts +95 -6
- package/dist/services/realtimeService.d.ts +467 -53
- package/dist/services/row-pipeline.d.ts +16 -9
- package/dist/services/socket-liveness.d.ts +85 -0
- package/dist/services/soft-delete.d.ts +12 -0
- package/dist/services/sql-script.d.ts +275 -0
- package/dist/services/vector-iterative-scan.d.ts +40 -0
- package/dist/services/write-depth.d.ts +22 -0
- package/dist/services/write-transaction-scope.d.ts +15 -3
- package/dist/socket-opt-ins.d.ts +64 -0
- package/dist/source-DB6xJv3p.js +873 -0
- package/dist/source-DB6xJv3p.js.map +1 -0
- package/dist/tsx-loader.d.ts +14 -0
- package/dist/utils/connection-string.d.ts +31 -0
- package/dist/utils/drizzle-conditions.d.ts +63 -3
- package/dist/utils/error-cause.d.ts +7 -0
- package/dist/utils/pg-error-utils.d.ts +2 -4
- package/dist/utils/result-rows.d.ts +10 -0
- package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
- package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
- package/dist/websocket-DUiiwQd2.js +1632 -0
- package/dist/websocket-DUiiwQd2.js.map +1 -0
- package/dist/websocket.d.ts +63 -18
- package/package.json +7 -7
- package/dist/BranchService-DGPL6G_C.js.map +0 -1
- package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
- package/dist/arg-Dni7MzLB.js.map +0 -1
- package/dist/backup-cli-24v4OlSp.js.map +0 -1
- package/dist/backup-service-HQ9GC4tN.js.map +0 -1
- package/dist/cli-errors-B8qHg02P.js +0 -798
- package/dist/cli-errors-B8qHg02P.js.map +0 -1
- package/dist/cli-flags-BglvpjHv.js.map +0 -1
- package/dist/cli-output-CNdMql-L.js +0 -47
- package/dist/cli-output-CNdMql-L.js.map +0 -1
- package/dist/collection-index-DdnsxiJ_.js +0 -286
- package/dist/collection-index-DdnsxiJ_.js.map +0 -1
- package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
- package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
- package/dist/doctor-Cb2thZ8s.js.map +0 -1
- package/dist/ensure-collection-policies-RHUcEp4v.js +0 -105
- package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
- package/dist/ensure-collection-tables-BEEjn5cn.js +0 -857
- package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
- package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
- package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
- package/dist/generate-drizzle-schema-yzY_BLhr.js +0 -123
- package/dist/generate-drizzle-schema-yzY_BLhr.js.map +0 -1
- package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
- package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
- package/dist/plan-schema-C0fxM8dY.js.map +0 -1
- package/dist/policy-drift-DljYdrpW.js.map +0 -1
- package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
- package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
- package/dist/source-Br7L7GOI.js +0 -410
- package/dist/source-Br7L7GOI.js.map +0 -1
|
@@ -5,7 +5,7 @@ import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
|
5
5
|
import { RealtimeProvider, CollectionSubscriptionConfig, SingleSubscriptionConfig } from "../interfaces.js";
|
|
6
6
|
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry.js";
|
|
7
7
|
import { ChannelBus } from "./channel-bus/index.js";
|
|
8
|
-
import type { ChannelRetentionRule } from "@rebasepro/types";
|
|
8
|
+
import type { ChannelRetentionRule, RealtimeListenerHealth } from "@rebasepro/types";
|
|
9
9
|
/**
|
|
10
10
|
* Auth context stored per-subscription so real-time refetches respect RLS.
|
|
11
11
|
* Mirrors the session variables set by PostgresBackendDriver.withAuth().
|
|
@@ -95,6 +95,13 @@ type StoredCollectionRequest = {
|
|
|
95
95
|
fields?: string[];
|
|
96
96
|
/** `SELECT DISTINCT` over the projection. */
|
|
97
97
|
distinct?: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* A subscription to the count alone (`listenCount`): no rows are read,
|
|
100
|
+
* and each frame carries `rows: []` and the `meta` with the total. A
|
|
101
|
+
* server that predates it reads the rows it was asked for (the SDK asks
|
|
102
|
+
* for one) and counts them as usual, so the same frame answers it.
|
|
103
|
+
*/
|
|
104
|
+
countOnly?: boolean;
|
|
98
105
|
};
|
|
99
106
|
/**
|
|
100
107
|
* What an in-process listener asks for: the read, and who it reads as.
|
|
@@ -111,6 +118,7 @@ export type DataDriverSubscriptionRequest = {
|
|
|
111
118
|
} | {
|
|
112
119
|
type: "single";
|
|
113
120
|
id: string | number;
|
|
121
|
+
include?: IncludeSpec;
|
|
114
122
|
});
|
|
115
123
|
/**
|
|
116
124
|
* A registered subscription, plus the two counters that order its deliveries.
|
|
@@ -133,11 +141,22 @@ export type DataDriverSubscriptionRequest = {
|
|
|
133
141
|
* last delivery *started* the last one *delivered*.
|
|
134
142
|
*/
|
|
135
143
|
type Subscription = {
|
|
144
|
+
/** The id the client named it by — what every frame for it carries. */
|
|
145
|
+
subscriptionId: string;
|
|
136
146
|
clientId: string;
|
|
147
|
+
/** The {@link SubscriptionGroup} whose refetch answers it. */
|
|
148
|
+
groupKey: string;
|
|
137
149
|
type: "collection" | "single";
|
|
138
150
|
path: string;
|
|
139
151
|
id?: string | number;
|
|
140
152
|
collectionRequest?: StoredCollectionRequest;
|
|
153
|
+
/**
|
|
154
|
+
* The relations a single-row subscription loads into every frame — the
|
|
155
|
+
* `include` a `findById` takes. A list keeps its own in `collectionRequest`.
|
|
156
|
+
*/
|
|
157
|
+
include?: IncludeSpec;
|
|
158
|
+
/** A single-row subscriber that asked for the row's `ETag` on every frame. */
|
|
159
|
+
etag?: boolean;
|
|
141
160
|
authContext?: SubscriptionAuthContext;
|
|
142
161
|
/**
|
|
143
162
|
* An in-process listener's error callback, called when a refetch fails.
|
|
@@ -149,7 +168,65 @@ type Subscription = {
|
|
|
149
168
|
started: number;
|
|
150
169
|
/** The highest started-sequence that has already reached the subscriber. */
|
|
151
170
|
delivered: number;
|
|
171
|
+
/**
|
|
172
|
+
* A socket subscriber's last data frame, as {@link frameDigest} of what
|
|
173
|
+
* follows its subscription id; unset before the first and after an error
|
|
174
|
+
* frame. A refetch that reads the same rows sends it nothing — see
|
|
175
|
+
* {@link sendDataFrame}.
|
|
176
|
+
*/
|
|
177
|
+
lastFrame?: string;
|
|
152
178
|
};
|
|
179
|
+
/**
|
|
180
|
+
* How many group refetches may run at once: a quarter of the connection pool
|
|
181
|
+
* the service shares with every REST request, and never fewer than two.
|
|
182
|
+
*
|
|
183
|
+
* One write to a list watched by many different principals arms a refetch
|
|
184
|
+
* per principal, and they all came due in the same tick. Started together,
|
|
185
|
+
* 200 of them were 200 transactions queued on a 20-connection pool, and an
|
|
186
|
+
* unrelated request arriving meanwhile waited behind every one (measured: a
|
|
187
|
+
* `SELECT 1` took 521 ms). Capped, the refetches queue here instead, and
|
|
188
|
+
* three quarters of the pool stays free for the requests. A pool this cannot
|
|
189
|
+
* read (a test double, a client that is not a `pg` pool) is taken to be the
|
|
190
|
+
* default 20-connection one.
|
|
191
|
+
*/
|
|
192
|
+
export declare function refetchConcurrencyFor(db: object): number;
|
|
193
|
+
/**
|
|
194
|
+
* The default ceiling on subscriptions one socket may hold.
|
|
195
|
+
*
|
|
196
|
+
* Each collection subscription is a refetch on every write to its collection;
|
|
197
|
+
* without a ceiling one socket could open tens of thousands and turn every
|
|
198
|
+
* write anyone made into as many transactions. The SDK shares identical
|
|
199
|
+
* subscriptions on a socket, so a real page holds one per distinct list or
|
|
200
|
+
* record on screen — the admin panel's tables, forms and reference previews
|
|
201
|
+
* stay well under this. See `REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET`.
|
|
202
|
+
*/
|
|
203
|
+
export declare const DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET = 1000;
|
|
204
|
+
/**
|
|
205
|
+
* Read a subscription ceiling, refusing anything that is not a positive whole
|
|
206
|
+
* number. A typo here does not fall back to a default: the ceiling is a limit
|
|
207
|
+
* someone chose, and silently running with another one is how it stops
|
|
208
|
+
* meaning anything.
|
|
209
|
+
*/
|
|
210
|
+
export declare function parseMaxSubscriptionsPerSocket(value: unknown, source: string): number;
|
|
211
|
+
/**
|
|
212
|
+
* The smallest last total at which a group's count is reused under
|
|
213
|
+
* `REALTIME_RECOUNT_INTERVAL_MS`, unless `REALTIME_RECOUNT_MIN_ROWS` says
|
|
214
|
+
* otherwise. Below it a count is cheap, and stays exact.
|
|
215
|
+
*/
|
|
216
|
+
export declare const DEFAULT_RECOUNT_MIN_ROWS = 100000;
|
|
217
|
+
/**
|
|
218
|
+
* Read one of the realtime tuning numbers (`REALTIME_REFETCH_MAX_WAIT_MS`,
|
|
219
|
+
* `REALTIME_RECOUNT_INTERVAL_MS`, `REALTIME_RECOUNT_MIN_ROWS` and their
|
|
220
|
+
* `realtime.*` options). A value someone set and mistyped stops the boot
|
|
221
|
+
* rather than quietly becoming the default.
|
|
222
|
+
*/
|
|
223
|
+
export declare function parseRealtimeTuning(value: unknown, source: string, unit: string): number;
|
|
224
|
+
/**
|
|
225
|
+
* Read `REALTIME_MAX_SOCKETS_PER_USER` / `realtime.maxSocketsPerUser`. Like the
|
|
226
|
+
* subscription ceiling, a value someone set and mistyped stops the boot rather
|
|
227
|
+
* than quietly becoming no ceiling.
|
|
228
|
+
*/
|
|
229
|
+
export declare function parseMaxSocketsPerUser(value: unknown, source: string): number;
|
|
153
230
|
/**
|
|
154
231
|
* PostgreSQL-specific realtime service.
|
|
155
232
|
* Handles WebSocket connections and subscriptions for real-time row updates.
|
|
@@ -166,8 +243,19 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
166
243
|
*/
|
|
167
244
|
readonly supportsChannels = true;
|
|
168
245
|
private clients;
|
|
246
|
+
/** What each socket opted into when it opened — see `socket-opt-ins.ts`. */
|
|
247
|
+
private clientOptIns;
|
|
169
248
|
private channels;
|
|
170
249
|
private presence;
|
|
250
|
+
/**
|
|
251
|
+
* The reverse of {@link channels} and {@link presence}: the channels each
|
|
252
|
+
* client is a member of, and those it has a presence entry in. Kept in
|
|
253
|
+
* step wherever either map gains or loses a client, so a disconnect visits
|
|
254
|
+
* what the client was in. It used to walk every channel on the instance:
|
|
255
|
+
* with a channel per document, 5.4 ms per disconnect at 50k channels.
|
|
256
|
+
*/
|
|
257
|
+
private channelsByClient;
|
|
258
|
+
private presenceByClient;
|
|
171
259
|
/**
|
|
172
260
|
* Ordered, replayable history for channels that opt into it.
|
|
173
261
|
*
|
|
@@ -188,6 +276,8 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
188
276
|
* wait on each other.
|
|
189
277
|
*/
|
|
190
278
|
private channelSendQueues;
|
|
279
|
+
/** The receiving half of the same ordering: retained bus frames per channel, one at a time. */
|
|
280
|
+
private channelReceiveQueues;
|
|
191
281
|
/**
|
|
192
282
|
* Cross-instance transport for channel frames and presence.
|
|
193
283
|
*
|
|
@@ -206,9 +296,17 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
206
296
|
private presenceStore?;
|
|
207
297
|
/** Sweeps roster rows left behind by instances that stopped heartbeating. */
|
|
208
298
|
private presenceSweepInterval?;
|
|
299
|
+
/**
|
|
300
|
+
* The clients that have tracked presence through this instance — the only
|
|
301
|
+
* ones with roster rows to delete when their socket closes. Most sockets
|
|
302
|
+
* never track presence, and each of their closes was a `DELETE` too.
|
|
303
|
+
*/
|
|
304
|
+
private presenceTrackers;
|
|
209
305
|
/**
|
|
210
306
|
* Channels whose oversized ephemeral broadcasts have already been reported,
|
|
211
307
|
* so a hot channel logs the problem once rather than once per message.
|
|
308
|
+
* Forgotten with the channel, so it holds live channels only — not every
|
|
309
|
+
* name a client ever broadcast on.
|
|
212
310
|
*/
|
|
213
311
|
private oversizedBroadcastWarned;
|
|
214
312
|
/**
|
|
@@ -228,34 +326,135 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
228
326
|
private foreignInstanceSeen;
|
|
229
327
|
/** So the multi-pod memory-bus warning is emitted once, not once per join. */
|
|
230
328
|
private memoryBusWarned;
|
|
329
|
+
/** When the database was last asked whether another instance listens — see {@link warnIfMemoryBusOnMultiplePods}. */
|
|
330
|
+
private otherInstancesCheckedAt?;
|
|
331
|
+
/** The check in flight, so a burst of joins asks once. Awaited by tests. */
|
|
332
|
+
private otherInstancesCheck?;
|
|
333
|
+
private static readonly OTHER_INSTANCES_CHECK_INTERVAL_MS;
|
|
231
334
|
private presenceInterval?;
|
|
232
335
|
private static readonly PRESENCE_TIMEOUT_MS;
|
|
233
336
|
/** How often stale roster rows from other instances are reaped. */
|
|
234
337
|
private static readonly PRESENCE_SWEEP_INTERVAL_MS;
|
|
235
338
|
private dataService;
|
|
339
|
+
/**
|
|
340
|
+
* Every subscription, by {@link subscriptionKey} — the client's own id
|
|
341
|
+
* qualified by the client that chose it.
|
|
342
|
+
*
|
|
343
|
+
* Keyed by the bare id, this was one namespace for every socket: two raw
|
|
344
|
+
* clients that both counted from `"sub-1"` replaced each other's
|
|
345
|
+
* subscription without a word, and either one's `unsubscribe` ended the
|
|
346
|
+
* other's.
|
|
347
|
+
*/
|
|
236
348
|
private _subscriptions;
|
|
349
|
+
/** In-process listeners' callbacks, by subscription key. */
|
|
237
350
|
private subscriptionCallbacks;
|
|
351
|
+
/** Each client's subscriptions, by key — for its ceiling and its disconnect. */
|
|
352
|
+
private subscriptionsByClient;
|
|
353
|
+
/**
|
|
354
|
+
* The in-process subscriptions — started by `startDataDriverSubscription`,
|
|
355
|
+
* `subscribeToCollection` or `subscribeToOne` — by the id their caller
|
|
356
|
+
* gave them, for {@link unsubscribe}, which has nothing else to go on. It
|
|
357
|
+
* used to walk every subscription on the server, sockets' included.
|
|
358
|
+
*/
|
|
359
|
+
private inProcessKeysById;
|
|
360
|
+
/** Subscriptions one refetch answers — see {@link SubscriptionGroup}. */
|
|
361
|
+
private groups;
|
|
362
|
+
/**
|
|
363
|
+
* Groups whose refetch is due and waiting for a slot, in the order they
|
|
364
|
+
* came due. A group is in it at most once; changes that arrive while it
|
|
365
|
+
* waits join the read it is waiting for, which starts after them.
|
|
366
|
+
*/
|
|
367
|
+
private refetchQueue;
|
|
368
|
+
/** Group refetches running now — at most {@link maxConcurrentRefetches}. */
|
|
369
|
+
private refetchesRunning;
|
|
370
|
+
/** See {@link refetchConcurrencyFor}. */
|
|
371
|
+
maxConcurrentRefetches: number;
|
|
372
|
+
/**
|
|
373
|
+
* The collection groups on each path, and the single-row groups at each
|
|
374
|
+
* `path` + id: what a change looks up, instead of walking every
|
|
375
|
+
* subscription on the server once per changed row.
|
|
376
|
+
*/
|
|
377
|
+
private collectionGroupsByPath;
|
|
378
|
+
private singleGroupsByAddress;
|
|
379
|
+
/** The single-row groups on each path, for a change that names no row ({@link ANY_ROW}). */
|
|
380
|
+
private singleGroupsByPath;
|
|
381
|
+
/**
|
|
382
|
+
* When something on each path a list group reads last changed: the
|
|
383
|
+
* debounce clock those groups share, moved once per changed row instead
|
|
384
|
+
* of once per row per group. See {@link notifyPathUpdate}.
|
|
385
|
+
*/
|
|
386
|
+
private listPathChangedAt;
|
|
387
|
+
/**
|
|
388
|
+
* The list groups on each path that a change still has to schedule: new
|
|
389
|
+
* ones, and those whose last refetch has started. The rest already have a
|
|
390
|
+
* refetch for every member pending, which the next change only has to
|
|
391
|
+
* keep waiting — the clock above does that.
|
|
392
|
+
*/
|
|
393
|
+
private unscheduledListGroups;
|
|
394
|
+
/**
|
|
395
|
+
* Every path a subscription holds, with the collection it lands on and how
|
|
396
|
+
* many hold it — the candidates `aliasPaths` considers, resolved once.
|
|
397
|
+
*/
|
|
398
|
+
private subscribedPaths;
|
|
399
|
+
/**
|
|
400
|
+
* How many subscriptions one socket may hold; set from config at boot. See
|
|
401
|
+
* {@link DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET}.
|
|
402
|
+
*/
|
|
403
|
+
maxSubscriptionsPerSocket: number;
|
|
404
|
+
/**
|
|
405
|
+
* The longest a change waits for its refetch, in ms; set from config at
|
|
406
|
+
* boot (`REALTIME_REFETCH_MAX_WAIT_MS`). See {@link REFETCH_MAX_WAIT_MS},
|
|
407
|
+
* the default.
|
|
408
|
+
*/
|
|
409
|
+
refetchMaxWaitMs: number;
|
|
410
|
+
/**
|
|
411
|
+
* How long a list's total may be reused before it is counted again, in ms
|
|
412
|
+
* (`REALTIME_RECOUNT_INTERVAL_MS`). Unset, the default, counts with every
|
|
413
|
+
* frame. Only for a group whose last total was at least
|
|
414
|
+
* {@link recountMinRows}: a count is cheap on a small set, and exact there.
|
|
415
|
+
*/
|
|
416
|
+
recountIntervalMs: number | undefined;
|
|
417
|
+
/** See {@link recountIntervalMs} (`REALTIME_RECOUNT_MIN_ROWS`). */
|
|
418
|
+
recountMinRows: number;
|
|
238
419
|
private driver?;
|
|
239
420
|
/** Unique identifier for this process instance, used to skip own notifications. */
|
|
240
421
|
private readonly instanceId;
|
|
241
|
-
/**
|
|
242
|
-
private
|
|
243
|
-
/**
|
|
244
|
-
private
|
|
422
|
+
/** What this instance's LISTEN connections report in `pg_stat_activity`. */
|
|
423
|
+
private readonly listenerApplicationName;
|
|
424
|
+
/** The LISTEN connection on {@link PG_NOTIFY_CHANNEL}, outside the Drizzle pool. */
|
|
425
|
+
private crossInstanceListener?;
|
|
245
426
|
/** Whether cross-instance broadcasting is active. */
|
|
246
427
|
private broadcasting;
|
|
247
|
-
/**
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
428
|
+
/**
|
|
429
|
+
* Changes waiting to be announced on {@link PG_NOTIFY_CHANNEL}, and the
|
|
430
|
+
* send that will announce them — see {@link broadcastChange}.
|
|
431
|
+
*/
|
|
432
|
+
private pendingBroadcasts;
|
|
433
|
+
private broadcastFlush?;
|
|
251
434
|
/** Debounce window (ms) for coalescing rapid row updates into a single correctness refetch. */
|
|
252
435
|
private static readonly REFETCH_DEBOUNCE_MS;
|
|
436
|
+
/**
|
|
437
|
+
* The longest a change waits for its refetch, quiet window or not.
|
|
438
|
+
*
|
|
439
|
+
* A pure debounce waits for 300 ms without a change, and a collection
|
|
440
|
+
* written more often than that — an orders, events or chat table at a few
|
|
441
|
+
* writes a second — never went quiet: its subscribers received nothing for
|
|
442
|
+
* as long as the writes continued. Under a steady stream a group now
|
|
443
|
+
* refetches once per this interval, which is the whole cost.
|
|
444
|
+
*/
|
|
445
|
+
private static readonly REFETCH_MAX_WAIT_MS;
|
|
253
446
|
/** Dedicated LISTEN client for DB-level change events (undefined unless CDC is enabled). */
|
|
254
447
|
private cdcListener?;
|
|
255
448
|
/** Whether database-level CDC is the active cross-instance change source. */
|
|
256
449
|
private cdcActive;
|
|
257
450
|
/** Junction table → the child lists its rows belong to, built when CDC starts. */
|
|
258
451
|
private junctionLinkMap?;
|
|
452
|
+
/** Changes waiting for a turn of the event loop — see {@link handleCdcEvents}. */
|
|
453
|
+
private cdcBacklog;
|
|
454
|
+
/** Changes handled in the current turn. */
|
|
455
|
+
private cdcTurnRows;
|
|
456
|
+
/** How many changed rows one turn of the event loop routes: ~5–10 ms of work. */
|
|
457
|
+
private static readonly CDC_ROWS_PER_TURN;
|
|
259
458
|
/** Reverse lookup: `schema.table` (and bare `table`) → collection, built when CDC starts. */
|
|
260
459
|
private cdcTableMap?;
|
|
261
460
|
/**
|
|
@@ -300,8 +499,75 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
300
499
|
* redaction lives.
|
|
301
500
|
*/
|
|
302
501
|
private debugLog;
|
|
502
|
+
/**
|
|
503
|
+
* {@link rawQueryLoggingEnabled}, read at most once a second.
|
|
504
|
+
*
|
|
505
|
+
* It reads two environment variables, and `process.env` is a getenv per
|
|
506
|
+
* read: called three or four times per changed row, it was the largest
|
|
507
|
+
* cost of a change notification here — 1.4 µs of every ~3.5 µs per row,
|
|
508
|
+
* so ~1.4 s of event loop for a 1M-row statement. A switch flipped at run
|
|
509
|
+
* time is honoured within a second.
|
|
510
|
+
*/
|
|
511
|
+
private debugEnabled;
|
|
512
|
+
private debugFlag;
|
|
513
|
+
private debugCheckedAt;
|
|
303
514
|
setDataDriver(driver: DataDriver): void;
|
|
304
|
-
|
|
515
|
+
/**
|
|
516
|
+
* The live subscriptions by the id their client gave them — for tests and
|
|
517
|
+
* diagnostics. Two clients may use one id; this view shows the last.
|
|
518
|
+
*/
|
|
519
|
+
get subscriptions(): ReadonlyMap<string, Subscription>;
|
|
520
|
+
/** The internal key of `subscriptionId` as `clientId` named it. */
|
|
521
|
+
private subscriptionKey;
|
|
522
|
+
/**
|
|
523
|
+
* The group a subscription's refetch belongs to. Socket subscriptions that
|
|
524
|
+
* would run the same read as the same principal share one; an in-process
|
|
525
|
+
* listener is always alone — see {@link SubscriptionGroup}.
|
|
526
|
+
*/
|
|
527
|
+
private groupKeyFor;
|
|
528
|
+
/**
|
|
529
|
+
* Register (or replace) a subscription under `key`, in its group and in
|
|
530
|
+
* every index that finds it.
|
|
531
|
+
*/
|
|
532
|
+
private registerSubscription;
|
|
533
|
+
/** Remember an in-process subscription by its caller's id — see {@link inProcessKeysById}. */
|
|
534
|
+
private indexInProcess;
|
|
535
|
+
/** Remove the subscription under `key` from everything that holds it. */
|
|
536
|
+
private dropSubscription;
|
|
537
|
+
/** Take `subscription` out of its group and path count; a group left empty goes, timer and all. */
|
|
538
|
+
private leaveGroup;
|
|
539
|
+
/** See {@link unscheduledListGroups}. */
|
|
540
|
+
private markListGroupUnscheduled;
|
|
541
|
+
/**
|
|
542
|
+
* Ask for a refetch of `group` once changes have been quiet for the
|
|
543
|
+
* debounce window — for every member, or only for `member` — and in any
|
|
544
|
+
* case no later than {@link REFETCH_MAX_WAIT_MS} after the first of them.
|
|
545
|
+
*
|
|
546
|
+
* The window is measured from the last change, as a debounce is, but the
|
|
547
|
+
* timer is not re-armed per change: a 10k-row statement is 10k changes,
|
|
548
|
+
* and re-arming a timer per change per group was work in proportion to
|
|
549
|
+
* both. A change only moves the timestamp; the timer, when it fires early,
|
|
550
|
+
* waits out the rest.
|
|
551
|
+
*/
|
|
552
|
+
private scheduleGroupRefetch;
|
|
553
|
+
private armGroupTimer;
|
|
554
|
+
/**
|
|
555
|
+
* Start queued group refetches while fewer than
|
|
556
|
+
* {@link maxConcurrentRefetches} are running. Each one that settles starts
|
|
557
|
+
* the next, so the fan-out of one write waits its turn here rather than
|
|
558
|
+
* in the pool, ahead of every REST request.
|
|
559
|
+
*/
|
|
560
|
+
private drainRefetchQueue;
|
|
561
|
+
/**
|
|
562
|
+
* One read for the group, delivered to each member it is for.
|
|
563
|
+
*
|
|
564
|
+
* Each member still claims its own delivery slot before the read and
|
|
565
|
+
* checks it after (see {@link beginDelivery}), so a member that left, was
|
|
566
|
+
* replaced or was overtaken while the read ran is skipped exactly as it
|
|
567
|
+
* was when it had a read of its own. A socket frame is serialised once and
|
|
568
|
+
* only its subscription id differs per member.
|
|
569
|
+
*/
|
|
570
|
+
private refetchGroup;
|
|
305
571
|
/**
|
|
306
572
|
* Claim a delivery slot for a subscription, before doing the work.
|
|
307
573
|
*
|
|
@@ -355,13 +621,32 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
355
621
|
*/
|
|
356
622
|
subscribeToOne(subscriptionId: string, config: SingleSubscriptionConfig, callback?: (row: Record<string, unknown> | null) => void, onError?: (error: unknown) => void): void;
|
|
357
623
|
/**
|
|
358
|
-
* Unsubscribe
|
|
624
|
+
* Unsubscribe an in-process subscription (RealtimeProvider interface).
|
|
625
|
+
*
|
|
626
|
+
* By id alone, because the in-process callers hold nothing else — so a
|
|
627
|
+
* socket's subscription under the same id is not this one, and is left
|
|
628
|
+
* alone. A socket ends its own through the `unsubscribe` frame.
|
|
359
629
|
*/
|
|
360
630
|
unsubscribe(subscriptionId: string): void;
|
|
361
631
|
addClient(clientId: string, ws: WebSocket): void;
|
|
362
632
|
handleClientMessage(clientId: string, message: WebSocketMessage, authContext?: SubscriptionAuthContext): Promise<void>;
|
|
633
|
+
/** Record what a socket opted into when it opened. See `socket-opt-ins.ts`. */
|
|
634
|
+
setClientOptIns(clientId: string, optIns: ReadonlySet<string>): void;
|
|
363
635
|
removeClient(clientId: string): Promise<void>;
|
|
636
|
+
/** Record that `clientId` is in `channel`, in a reverse index — see {@link channelsByClient}. */
|
|
637
|
+
private indexClientChannel;
|
|
638
|
+
private unindexClientChannel;
|
|
364
639
|
private handleMessage;
|
|
640
|
+
/**
|
|
641
|
+
* Refuse a new subscription past the socket's ceiling, and say so.
|
|
642
|
+
*
|
|
643
|
+
* Every collection subscription is a refetch on every write to its
|
|
644
|
+
* collection, so a socket allowed to open them without limit could turn
|
|
645
|
+
* each write anyone makes into tens of thousands of transactions. Replacing
|
|
646
|
+
* a subscription the socket already holds — the same id again — is not a
|
|
647
|
+
* new one and always passes. Returns whether the subscription may go ahead.
|
|
648
|
+
*/
|
|
649
|
+
private admitSubscription;
|
|
365
650
|
private handleCollectionSubscription;
|
|
366
651
|
private handleEntitySubscription;
|
|
367
652
|
/**
|
|
@@ -397,6 +682,10 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
397
682
|
* again, when one is installed, and left when it refuses.
|
|
398
683
|
*/
|
|
399
684
|
rescopeClient(clientId: string, authContext: SubscriptionAuthContext): Promise<void>;
|
|
685
|
+
/**
|
|
686
|
+
* End a socket's own subscription. Only its own: the id is the client's,
|
|
687
|
+
* so another socket's subscription under the same id is a different one.
|
|
688
|
+
*/
|
|
400
689
|
private handleUnsubscribe;
|
|
401
690
|
/**
|
|
402
691
|
* Enhanced notification method that handles nested relation updates.
|
|
@@ -446,15 +735,6 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
446
735
|
* whether this subscriber may see the row at all.
|
|
447
736
|
*/
|
|
448
737
|
private notifyPathUpdate;
|
|
449
|
-
/**
|
|
450
|
-
* Debounce a collection refetch for a WebSocket subscription.
|
|
451
|
-
* Coalesces rapid row mutations into a single database query.
|
|
452
|
-
*/
|
|
453
|
-
private debouncedCollectionRefetch;
|
|
454
|
-
/**
|
|
455
|
-
* Debounce a collection refetch for a DataDriver callback subscription.
|
|
456
|
-
*/
|
|
457
|
-
private debouncedDriverRefetch;
|
|
458
738
|
/**
|
|
459
739
|
* Tell an in-process listener its refetch failed, through the slot the
|
|
460
740
|
* rows would have used.
|
|
@@ -468,13 +748,20 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
468
748
|
*/
|
|
469
749
|
private reportDriverFetchFailure;
|
|
470
750
|
/**
|
|
471
|
-
* Fetch a collection with optional RLS auth context
|
|
751
|
+
* Fetch a collection with optional RLS auth context, and with `meta: true`
|
|
752
|
+
* the `meta` a socket frame carries beside the rows.
|
|
753
|
+
*
|
|
472
754
|
* When authContext is provided, the fetch runs inside a transaction
|
|
473
|
-
* with set_config calls so PostgreSQL RLS policies are enforced.
|
|
755
|
+
* with set_config calls so PostgreSQL RLS policies are enforced. The count
|
|
756
|
+
* behind `meta` runs in that same transaction — one pool acquisition and
|
|
757
|
+
* one `applyAuthContext` per refetch, where it used to take a second
|
|
758
|
+
* transaction of its own — see {@link collectionMeta}.
|
|
474
759
|
*/
|
|
475
760
|
private fetchCollectionWithAuth;
|
|
476
761
|
/**
|
|
477
|
-
* The `meta` a `collection_update` frame carries beside its rows
|
|
762
|
+
* The `meta` a `collection_update` frame carries beside its rows, counted
|
|
763
|
+
* through `service` — the subscriber's own transaction, read only, with
|
|
764
|
+
* its auth context and `beforeQuery` narrowing already bound.
|
|
478
765
|
*
|
|
479
766
|
* Frames used to carry rows and primary keys and nothing else, so the
|
|
480
767
|
* client issued a `GET /count` **per push** to fill in a total it needed to
|
|
@@ -482,22 +769,61 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
482
769
|
* write, per subscriber, forever. The refetch already knows the query; it
|
|
483
770
|
* counts once, here, under the same RLS transaction that read the rows, so
|
|
484
771
|
* the total describes the same set they came from.
|
|
772
|
+
*
|
|
773
|
+
* A first page that came back short of its limit *is* the whole set — the
|
|
774
|
+
* same filter, the same narrowing, read as the same principal — so its
|
|
775
|
+
* length is the total and nothing is counted. Not for a later page, a
|
|
776
|
+
* cursor page, a search (whose page and count are not the same query) or
|
|
777
|
+
* a `distinct` read (whose count counts rows, not values): those count.
|
|
485
778
|
*/
|
|
486
|
-
private
|
|
487
|
-
/**
|
|
488
|
-
* Debounce an row refetch for a WebSocket subscription.
|
|
489
|
-
*/
|
|
490
|
-
private debouncedSingleRefetch;
|
|
779
|
+
private collectionMeta;
|
|
491
780
|
/**
|
|
492
|
-
*
|
|
781
|
+
* A `single_update` frame's row, and — when the subscriber asked — the
|
|
782
|
+
* `ETag` a `GET /:id` of the same row answers.
|
|
783
|
+
*
|
|
784
|
+
* The tag names the row, not the read: `GET` hashes the row as
|
|
785
|
+
* `restFetchService.fetchOneForRest` serves it with no `include`, and a
|
|
786
|
+
* write's `If-Match` is compared against that same rendering. A frame
|
|
787
|
+
* whose relations are loaded therefore takes its tag from a second read
|
|
788
|
+
* without them, read first — as `GET /:id?include=` does — so a write
|
|
789
|
+
* landing between the two leaves the tag older than the body (a 412 the
|
|
790
|
+
* client recovers from) and never newer, which would let an edit made
|
|
791
|
+
* against the older body pass over a change it never saw.
|
|
493
792
|
*/
|
|
494
|
-
private
|
|
793
|
+
private fetchEntityFrame;
|
|
495
794
|
/**
|
|
496
795
|
* Fetch a single row with optional RLS auth context.
|
|
497
796
|
*/
|
|
498
797
|
private fetchEntityWithAuth;
|
|
499
|
-
|
|
500
|
-
|
|
798
|
+
/**
|
|
799
|
+
* Everything in a `collection_update` frame after its subscription id,
|
|
800
|
+
* serialised once for every member of a group: the rows, the collection's
|
|
801
|
+
* key columns, and the `meta` beside them (see {@link CollectionUpdateMeta}
|
|
802
|
+
* for the round trip that removes). The order is the one
|
|
803
|
+
* {@link CollectionUpdateMessage} declares.
|
|
804
|
+
*/
|
|
805
|
+
private collectionFrameTail;
|
|
806
|
+
/** Everything in a `single_update` frame after its subscription id. */
|
|
807
|
+
private singleFrameTail;
|
|
808
|
+
/** A frame tail with its {@link frameDigest}, computed once for every member it goes to. */
|
|
809
|
+
private dataFrame;
|
|
810
|
+
/**
|
|
811
|
+
* What a subscriber was last sent, in 44 bytes: holding the frames
|
|
812
|
+
* themselves would keep a page of rows per subscriber in memory.
|
|
813
|
+
*/
|
|
814
|
+
private frameDigest;
|
|
815
|
+
/**
|
|
816
|
+
* Send a data frame to a socket subscriber — unless it is exactly the one
|
|
817
|
+
* the subscriber was last sent.
|
|
818
|
+
*
|
|
819
|
+
* A write anywhere in a collection refetches every list of it, and the
|
|
820
|
+
* first 50 rows of a 10k-row list come back the same for 99.5% of writes.
|
|
821
|
+
* Each of those frames was sent anyway: ~30 KB per viewer per write,
|
|
822
|
+
* parsed, merged and re-rendered by every client to change nothing. The
|
|
823
|
+
* comparison is per subscriber, because the members of one group need not
|
|
824
|
+
* have been sent the same frame last — one may have joined since.
|
|
825
|
+
*/
|
|
826
|
+
private sendDataFrame;
|
|
501
827
|
/**
|
|
502
828
|
* Send a lightweight row-level patch to a collection subscriber.
|
|
503
829
|
* The client can merge this into its cached data for instant feedback.
|
|
@@ -524,6 +850,15 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
524
850
|
*/
|
|
525
851
|
private sendError;
|
|
526
852
|
private sendMessage;
|
|
853
|
+
/**
|
|
854
|
+
* Send an already-serialised frame to a client, if it is still connected.
|
|
855
|
+
*
|
|
856
|
+
* Every frame to a socket goes through here, so this is where a client
|
|
857
|
+
* that does not read what it is sent is let go of — see
|
|
858
|
+
* {@link terminateIfBacklogged}. Terminated, it closes like any other
|
|
859
|
+
* socket, and {@link removeClient} drops what it held.
|
|
860
|
+
*/
|
|
861
|
+
private sendRaw;
|
|
527
862
|
/**
|
|
528
863
|
* Extract parent paths from a nested path like "posts/70/tags"
|
|
529
864
|
* Returns ["posts", "posts/70"] for the example above
|
|
@@ -601,6 +936,21 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
601
936
|
* fraction of the room. The evidence is already in the process, so use it.
|
|
602
937
|
*/
|
|
603
938
|
private warnIfMemoryBusOnMultiplePods;
|
|
939
|
+
/**
|
|
940
|
+
* Ask the database whether another process holds a realtime LISTEN
|
|
941
|
+
* connection on it, at most once per {@link OTHER_INSTANCES_CHECK_INTERVAL_MS}.
|
|
942
|
+
*
|
|
943
|
+
* The legacy entity channel proves a second instance with every foreign
|
|
944
|
+
* `sid`, but under change capture — the default — nothing crosses that
|
|
945
|
+
* channel, so the warning above could never fire on exactly the
|
|
946
|
+
* deployments it was written for. Every LISTEN connection names itself
|
|
947
|
+
* (`application_name`, which `pg_stat_activity` shows to any role), so
|
|
948
|
+
* the count is one cheap query. Only a process that listens is asked: a
|
|
949
|
+
* single process with no LISTEN connection has nothing to compare with.
|
|
950
|
+
*/
|
|
951
|
+
private checkForOtherInstances;
|
|
952
|
+
/** Drop a channel its last local member left, and what was kept per channel for it. */
|
|
953
|
+
private forgetChannel;
|
|
604
954
|
/** Leave a broadcast channel */
|
|
605
955
|
leaveChannel(clientId: string, channel: string): void;
|
|
606
956
|
/**
|
|
@@ -668,6 +1018,7 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
668
1018
|
* its own `sid`.
|
|
669
1019
|
*/
|
|
670
1020
|
private handleBusFrame;
|
|
1021
|
+
private deliverBusFrame;
|
|
671
1022
|
/**
|
|
672
1023
|
* Install retention rules and create the tables they need.
|
|
673
1024
|
*
|
|
@@ -757,8 +1108,16 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
757
1108
|
* does not answer the close frame is dropped after a short grace.
|
|
758
1109
|
*/
|
|
759
1110
|
destroy(): Promise<void>;
|
|
760
|
-
/**
|
|
1111
|
+
/**
|
|
1112
|
+
* Whether database-level change capture is the source and is listening.
|
|
1113
|
+
*
|
|
1114
|
+
* Configured is not enough: a CDC connection that went half-open used to
|
|
1115
|
+
* leave this `true` while nothing arrived. False while the connection is
|
|
1116
|
+
* down and being replaced.
|
|
1117
|
+
*/
|
|
761
1118
|
isCdcActive(): boolean;
|
|
1119
|
+
/** The LISTEN connections this service depends on — see `RealtimeProvider.health`. */
|
|
1120
|
+
health(): RealtimeListenerHealth[];
|
|
762
1121
|
/**
|
|
763
1122
|
* Enable database-level change capture as the realtime source.
|
|
764
1123
|
*
|
|
@@ -802,13 +1161,26 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
802
1161
|
/**
|
|
803
1162
|
* Route a captured database change into the realtime pipeline.
|
|
804
1163
|
*
|
|
805
|
-
* Delivery is RLS-safe by construction: the
|
|
806
|
-
* NOT forwarded to subscribers. Instead the change is marked invalidated, so
|
|
1164
|
+
* Delivery is RLS-safe by construction: the event carries the changed row's
|
|
1165
|
+
* key and nothing else, and even that is NOT forwarded to subscribers. Instead the change is marked invalidated, so
|
|
807
1166
|
* every matching subscription re-reads the row under its own auth context via
|
|
808
1167
|
* {@link fetchCollectionWithAuth} / {@link fetchEntityWithAuth}. A subscriber
|
|
809
1168
|
* therefore only ever receives rows its RLS policies permit — filtering is per
|
|
810
1169
|
* subscriber, never per publisher.
|
|
811
1170
|
*/
|
|
1171
|
+
/**
|
|
1172
|
+
* Route the rows one notification names — a statement's batch, or one row.
|
|
1173
|
+
*
|
|
1174
|
+
* Each is handled as a change on its own (the app-emit de-duplication and
|
|
1175
|
+
* single-row addressing are per row), at most {@link CDC_ROWS_PER_TURN} in
|
|
1176
|
+
* one turn of the event loop; the rest wait in order for the next turns. A
|
|
1177
|
+
* socket read hands the listener up to 2 MB of notifications at once, and
|
|
1178
|
+
* batched that is ~250k rows: handled in one go, the event loop stalled
|
|
1179
|
+
* for ~400 ms. Sliced, a 1M-row statement is ~200 short turns that timers,
|
|
1180
|
+
* sockets and requests run between.
|
|
1181
|
+
*/
|
|
1182
|
+
private handleCdcEvents;
|
|
1183
|
+
private drainCdcBacklog;
|
|
812
1184
|
private handleCdcEvent;
|
|
813
1185
|
/**
|
|
814
1186
|
* Deliver a change on a many-to-many junction table as a change to the child
|
|
@@ -827,7 +1199,11 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
827
1199
|
* Returns whether the table was recognised as a junction.
|
|
828
1200
|
*/
|
|
829
1201
|
private handleJunctionCdcEvent;
|
|
830
|
-
/**
|
|
1202
|
+
/**
|
|
1203
|
+
* Compute the canonical (possibly composite) id string from a captured row,
|
|
1204
|
+
* or {@link ANY_ROW} when it names none — a key too large to notify, a
|
|
1205
|
+
* table with no key.
|
|
1206
|
+
*/
|
|
831
1207
|
private extractIdFromCdcRow;
|
|
832
1208
|
/**
|
|
833
1209
|
* A captured row with its key under the key's *field* names.
|
|
@@ -840,14 +1216,32 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
840
1216
|
*/
|
|
841
1217
|
private cdcRowByKeyFields;
|
|
842
1218
|
private dedupKey;
|
|
843
|
-
/**
|
|
1219
|
+
/**
|
|
1220
|
+
* Record that this instance just delivered `key` via the app path.
|
|
1221
|
+
*
|
|
1222
|
+
* Expired entries are purged so the map cannot grow unbounded under write
|
|
1223
|
+
* load — once it holds over 1,000, and at most once per dedup window. The
|
|
1224
|
+
* purge walks the whole map, and it used to run on every write past 1,000:
|
|
1225
|
+
* inside one window nothing has expired yet, so a burst of writes walked
|
|
1226
|
+
* all of it each time (703 ms of event loop for 20,000 marks). Once a
|
|
1227
|
+
* window, the map holds at most two windows' writes.
|
|
1228
|
+
*/
|
|
844
1229
|
private markAppEmit;
|
|
1230
|
+
/** When {@link markAppEmit} may next purge expired entries. */
|
|
1231
|
+
private nextAppEmitPurgeAt;
|
|
845
1232
|
/** Consume a matching app-emit record if present and unexpired; true ⇒ suppress the CDC echo. */
|
|
846
1233
|
private consumeAppEmit;
|
|
847
1234
|
/**
|
|
848
1235
|
* Enable cross-instance realtime broadcasting via Postgres LISTEN/NOTIFY.
|
|
849
|
-
*
|
|
850
|
-
*
|
|
1236
|
+
* Listens on a dedicated connection (outside the Drizzle pool) for the
|
|
1237
|
+
* changes other instances publish, through a {@link PgNotifyListener}: it
|
|
1238
|
+
* proves itself with a heartbeat, is replaced when it drops or stops
|
|
1239
|
+
* answering, and every subscription is refetched once it is back, since
|
|
1240
|
+
* whatever was published meanwhile reached nobody here.
|
|
1241
|
+
*
|
|
1242
|
+
* A database that cannot be reached at boot does not fail this call: the
|
|
1243
|
+
* listener reports itself down (see {@link health}) and keeps dialling.
|
|
1244
|
+
* There is no other cross-instance path to fall back to.
|
|
851
1245
|
*
|
|
852
1246
|
* This is an **optional** feature — if never called, the backend operates
|
|
853
1247
|
* in single-instance mode (the default, perfectly fine for most setups).
|
|
@@ -856,27 +1250,47 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
856
1250
|
*/
|
|
857
1251
|
startListening(connectionString: string): Promise<void>;
|
|
858
1252
|
/**
|
|
859
|
-
*
|
|
1253
|
+
* Announce this process's writes on {@link PG_NOTIFY_CHANNEL} without
|
|
1254
|
+
* listening for anyone else's.
|
|
1255
|
+
*
|
|
1256
|
+
* For a process that serves no websockets (`worker`, `functions`) while
|
|
1257
|
+
* change capture is not in effect. It has no subscriber of its own, so it
|
|
1258
|
+
* holds no LISTEN connection — but without capture the announcement is
|
|
1259
|
+
* the only way the `api` hears that a cron or a function wrote a row.
|
|
1260
|
+
* Sending needs only the pool.
|
|
860
1261
|
*/
|
|
861
|
-
|
|
1262
|
+
startPublishing(): void;
|
|
862
1263
|
/**
|
|
863
|
-
*
|
|
864
|
-
* Uses the main Drizzle connection (pooled) — NOT the LISTEN client.
|
|
1264
|
+
* Stop listening and clean up the dedicated LISTEN connection.
|
|
865
1265
|
*/
|
|
866
|
-
|
|
1266
|
+
stopListening(): Promise<void>;
|
|
867
1267
|
/**
|
|
868
|
-
*
|
|
1268
|
+
* Announce a change to the other instances on {@link PG_NOTIFY_CHANNEL},
|
|
1269
|
+
* with the rest of its burst. Resolves once the notification carrying it
|
|
1270
|
+
* has been sent. Uses the main Drizzle connection (pooled), not the LISTEN
|
|
1271
|
+
* client.
|
|
869
1272
|
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
872
|
-
*
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
* Schedule a reconnection attempt with a fixed 3s delay.
|
|
1273
|
+
* Changes queued in the same turn of the event loop go out together, as
|
|
1274
|
+
* many to a notification as fit under the NOTIFY ceiling: a 1,000-row save
|
|
1275
|
+
* used to be 1,000 round trips. A burst of one keeps the single-change
|
|
1276
|
+
* shape `{ sid, p, eid, db }`, which an older instance in a rolling deploy
|
|
1277
|
+
* still reads; a batch is `{ sid, c: [[p, eid, db], …] }`. Both carry
|
|
1278
|
+
* addresses and nothing else.
|
|
877
1279
|
*/
|
|
878
|
-
private
|
|
1280
|
+
private broadcastChange;
|
|
1281
|
+
private flushBroadcasts;
|
|
1282
|
+
/** One notification another instance published on {@link PG_NOTIFY_CHANNEL}: one change, or a batch. */
|
|
1283
|
+
private handleCrossInstanceNotification;
|
|
879
1284
|
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Pack `changes` into as few payloads as fit under
|
|
1287
|
+
* {@link ENTITY_NOTIFY_MAX_BYTES}. One change keeps the single-change shape.
|
|
1288
|
+
*/
|
|
1289
|
+
export declare function packEntityChanges(sid: string, changes: Array<{
|
|
1290
|
+
p: string;
|
|
1291
|
+
eid: string;
|
|
1292
|
+
db: string | null;
|
|
1293
|
+
}>): string[];
|
|
880
1294
|
/**
|
|
881
1295
|
* Alias for RealtimeService for consistent naming with other database implementations.
|
|
882
1296
|
* This allows code to use PostgresRealtimeProvider alongside future MongoRealtimeProvider, etc.
|