@rebasepro/server-postgres 0.17.3 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +31 -0
  3. package/dist/BranchService-ByLfM_Tt.js +360 -0
  4. package/dist/BranchService-ByLfM_Tt.js.map +1 -0
  5. package/dist/__vite-browser-external-BnuHet1e.js +46 -0
  6. package/dist/__vite-browser-external-BnuHet1e.js.map +1 -0
  7. package/dist/arg-Dni7MzLB.js +87 -0
  8. package/dist/arg-Dni7MzLB.js.map +1 -0
  9. package/dist/auth-users-columns-D2LBFrMH.js +116 -0
  10. package/dist/auth-users-columns-D2LBFrMH.js.map +1 -0
  11. package/dist/backup-argv.d.ts +19 -0
  12. package/dist/backup-cli-oM9WQVlV.js +408 -0
  13. package/dist/backup-cli-oM9WQVlV.js.map +1 -0
  14. package/dist/backup-service-Br3tqwsy.js +715 -0
  15. package/dist/backup-service-Br3tqwsy.js.map +1 -0
  16. package/dist/branch-argv.d.ts +28 -0
  17. package/dist/branch-prune.d.ts +77 -0
  18. package/dist/cli-collections-path.d.ts +31 -0
  19. package/dist/cli-errors-DxN4yzdH.js +657 -0
  20. package/dist/cli-errors-DxN4yzdH.js.map +1 -0
  21. package/dist/cli-errors.d.ts +123 -0
  22. package/dist/cli-flags.d.ts +101 -0
  23. package/dist/cli-helpers.d.ts +94 -10
  24. package/dist/cli-output-CNdMql-L.js +47 -0
  25. package/dist/cli-output-CNdMql-L.js.map +1 -0
  26. package/dist/cli.js +2020 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/collection-index-BHgT_TKd.js +276 -0
  29. package/dist/collection-index-BHgT_TKd.js.map +1 -0
  30. package/dist/{collections-schema-version-BMeu3cgv.js → collections-schema-version-T6cGvrDi.js} +1 -2
  31. package/dist/{collections-schema-version-BMeu3cgv.js.map → collections-schema-version-T6cGvrDi.js.map} +1 -1
  32. package/dist/{connection-GOKU3Hu5.js → databasePoolManager-Bj5FbeAs.js} +112 -40
  33. package/dist/databasePoolManager-Bj5FbeAs.js.map +1 -0
  34. package/dist/databasePoolManager.d.ts +27 -0
  35. package/dist/doctor-D7HWmGzs.js +974 -0
  36. package/dist/doctor-D7HWmGzs.js.map +1 -0
  37. package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-2c2NV8zX.js} +7 -27
  38. package/dist/ensure-collection-policies-2c2NV8zX.js.map +1 -0
  39. package/dist/ensure-collection-tables-D4wJzM4y.js +820 -0
  40. package/dist/ensure-collection-tables-D4wJzM4y.js.map +1 -0
  41. package/dist/ensure-tables-CIojK-Rx.js +650 -0
  42. package/dist/ensure-tables-CIojK-Rx.js.map +1 -0
  43. package/dist/generate-drizzle-schema-CwEgVyjO.js +123 -0
  44. package/dist/generate-drizzle-schema-CwEgVyjO.js.map +1 -0
  45. package/dist/generate-drizzle-schema-logic-so2Z_lyC.js +528 -0
  46. package/dist/generate-drizzle-schema-logic-so2Z_lyC.js.map +1 -0
  47. package/dist/{ensure-collection-tables-DgVixhX3.js → generate-postgres-ddl-logic-CtnpIBVv.js} +126 -984
  48. package/dist/generate-postgres-ddl-logic-CtnpIBVv.js.map +1 -0
  49. package/dist/generated-schema-staleness-DQdfJl2A.js +157 -0
  50. package/dist/generated-schema-staleness-DQdfJl2A.js.map +1 -0
  51. package/dist/index.d.ts +12 -0
  52. package/dist/index.es.js +7423 -1133
  53. package/dist/index.es.js.map +1 -1
  54. package/dist/introspect-db-logic-C6LQdTxj.js +1378 -0
  55. package/dist/introspect-db-logic-C6LQdTxj.js.map +1 -0
  56. package/dist/policy-drift-kNJ6X2Uj.js +313 -0
  57. package/dist/policy-drift-kNJ6X2Uj.js.map +1 -0
  58. package/dist/{backup-service-DCk7KhhL.js → rls-bootstrap-sql-DLDnUjtx.js} +737 -2689
  59. package/dist/rls-bootstrap-sql-DLDnUjtx.js.map +1 -0
  60. package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-DeYEjYLt.js} +65 -190
  61. package/dist/rls-enforcement-DeYEjYLt.js.map +1 -0
  62. package/dist/schema/bin/generate-drizzle-schema.d.ts +1 -0
  63. package/dist/schema/bin/generate-postgres-ddl.d.ts +1 -0
  64. package/dist/schema/column-type-drift.d.ts +79 -0
  65. package/dist/schema/doctor-cli.js +129 -0
  66. package/dist/schema/doctor-cli.js.map +1 -0
  67. package/dist/schema/doctor.d.ts +42 -0
  68. package/dist/schema/ensure-collection-tables.d.ts +90 -0
  69. package/dist/schema/generate-drizzle-schema.d.ts +1 -1
  70. package/dist/schema/generate-drizzle-schema.js +24 -0
  71. package/dist/schema/generate-drizzle-schema.js.map +1 -0
  72. package/dist/schema/generate-next-step.d.ts +30 -0
  73. package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
  74. package/dist/schema/generate-postgres-ddl.d.ts +1 -1
  75. package/dist/schema/generate-postgres-ddl.js +138 -0
  76. package/dist/schema/generate-postgres-ddl.js.map +1 -0
  77. package/dist/schema/generated-schema-staleness.d.ts +37 -0
  78. package/dist/schema/introspect-db-queries.d.ts +34 -0
  79. package/dist/schema/introspect-db.js +866 -0
  80. package/dist/schema/introspect-db.js.map +1 -0
  81. package/dist/security/rls-enforcement.d.ts +63 -2
  82. package/dist/services/BranchService.d.ts +86 -1
  83. package/dist/services/collection-helpers.d.ts +1 -1
  84. package/dist/services/realtimeService.d.ts +30 -2
  85. package/dist/services/row-pipeline.d.ts +12 -1
  86. package/dist/source-Br7L7GOI.js +410 -0
  87. package/dist/source-Br7L7GOI.js.map +1 -0
  88. package/dist/utils/pg-error-utils.d.ts +3 -1
  89. package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
  90. package/dist/vector-index-DkWxm2de.js +309 -0
  91. package/dist/vector-index-DkWxm2de.js.map +1 -0
  92. package/package.json +32 -27
  93. package/dist/backup-service-DCk7KhhL.js.map +0 -1
  94. package/dist/collection-index-BRUg10H5.js +0 -5690
  95. package/dist/collection-index-BRUg10H5.js.map +0 -1
  96. package/dist/connection-GOKU3Hu5.js.map +0 -1
  97. package/dist/ensure-collection-policies-UCqgv_8c.js.map +0 -1
  98. package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
  99. package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
  100. package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
  101. package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
  102. package/dist/src-DiB5RP2Z.js +0 -541
  103. package/dist/src-DiB5RP2Z.js.map +0 -1
  104. package/dist/websocket-BZ4H5wUz.js +0 -8198
  105. package/dist/websocket-BZ4H5wUz.js.map +0 -1
  106. package/src/PostgresAdapter.ts +0 -91
  107. package/src/PostgresBackendDriver.ts +0 -1857
  108. package/src/PostgresBootstrapper.ts +0 -1157
  109. package/src/auth/ensure-tables.ts +0 -896
  110. package/src/auth/schema-version.ts +0 -260
  111. package/src/auth/services.ts +0 -1610
  112. package/src/backup/backup-cli.ts +0 -520
  113. package/src/backup/backup-cron.ts +0 -212
  114. package/src/backup/backup-logic.ts +0 -93
  115. package/src/backup/backup-service.ts +0 -485
  116. package/src/backup/index.ts +0 -12
  117. package/src/backup/pg-tools.ts +0 -403
  118. package/src/backup/retention.ts +0 -86
  119. package/src/cli-errors.ts +0 -265
  120. package/src/cli-helpers.ts +0 -569
  121. package/src/cli-output.ts +0 -43
  122. package/src/cli.ts +0 -1339
  123. package/src/collections/PostgresCollectionRegistry.ts +0 -108
  124. package/src/collections/buildRegistry.ts +0 -68
  125. package/src/collections/validate-relations.ts +0 -402
  126. package/src/connection.ts +0 -333
  127. package/src/data-transformer.ts +0 -845
  128. package/src/databasePoolManager.ts +0 -95
  129. package/src/history/HistoryService.ts +0 -254
  130. package/src/history/ensure-history-table.ts +0 -53
  131. package/src/index.ts +0 -19
  132. package/src/interfaces.ts +0 -60
  133. package/src/module-dir.ts +0 -7
  134. package/src/schema/atlas-argv.ts +0 -94
  135. package/src/schema/auth-schema.ts +0 -298
  136. package/src/schema/auth-users-columns.ts +0 -131
  137. package/src/schema/carved-out-migration.ts +0 -404
  138. package/src/schema/classify-change.ts +0 -436
  139. package/src/schema/collection-index.ts +0 -427
  140. package/src/schema/collections-schema-version.ts +0 -103
  141. package/src/schema/destructive-sql.ts +0 -94
  142. package/src/schema/doctor-cli.ts +0 -66
  143. package/src/schema/doctor-policy-checks.ts +0 -105
  144. package/src/schema/doctor.ts +0 -837
  145. package/src/schema/drizzle-ddl.ts +0 -33
  146. package/src/schema/dynamic-tables.test.ts +0 -302
  147. package/src/schema/dynamic-tables.ts +0 -293
  148. package/src/schema/ensure-collection-policies.ts +0 -198
  149. package/src/schema/ensure-collection-tables.test.ts +0 -518
  150. package/src/schema/ensure-collection-tables.ts +0 -1267
  151. package/src/schema/generate-drizzle-schema-logic.ts +0 -957
  152. package/src/schema/generate-drizzle-schema.ts +0 -143
  153. package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
  154. package/src/schema/generate-postgres-ddl.ts +0 -180
  155. package/src/schema/generate-schema-commit.ts +0 -267
  156. package/src/schema/generated-schema-staleness.ts +0 -284
  157. package/src/schema/introspect-db-constraints.ts +0 -385
  158. package/src/schema/introspect-db-inference.ts +0 -255
  159. package/src/schema/introspect-db-logic.ts +0 -1387
  160. package/src/schema/introspect-db-naming.ts +0 -15
  161. package/src/schema/introspect-db-project.ts +0 -78
  162. package/src/schema/introspect-db-queries.ts +0 -326
  163. package/src/schema/introspect-db-structure.ts +0 -670
  164. package/src/schema/introspect-db-types.ts +0 -56
  165. package/src/schema/introspect-db.ts +0 -257
  166. package/src/schema/introspect-runtime.test.ts +0 -260
  167. package/src/schema/introspect-runtime.ts +0 -327
  168. package/src/schema/non-sql-collections.test.ts +0 -131
  169. package/src/schema/rls-bootstrap-sql.ts +0 -288
  170. package/src/schema/search-column.ts +0 -643
  171. package/src/schema/test-schema.ts +0 -11
  172. package/src/schema/vector-index.ts +0 -491
  173. package/src/security/anonymous-grants.test.ts +0 -73
  174. package/src/security/policy-drift.test.ts +0 -435
  175. package/src/security/policy-drift.ts +0 -473
  176. package/src/security/rls-enforcement.ts +0 -505
  177. package/src/services/BranchService.ts +0 -326
  178. package/src/services/FetchService.ts +0 -2115
  179. package/src/services/PersistService.ts +0 -557
  180. package/src/services/RelationService.ts +0 -978
  181. package/src/services/RelationWriteService.ts +0 -653
  182. package/src/services/cdc/CdcListener.ts +0 -103
  183. package/src/services/cdc/junction-tables.ts +0 -91
  184. package/src/services/cdc/trigger-cdc.ts +0 -173
  185. package/src/services/channel-bus/ChannelBus.ts +0 -44
  186. package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
  187. package/src/services/channel-bus/index.ts +0 -114
  188. package/src/services/channel-history.ts +0 -424
  189. package/src/services/channel-presence.ts +0 -185
  190. package/src/services/collection-helpers.ts +0 -420
  191. package/src/services/dataService.ts +0 -256
  192. package/src/services/index.ts +0 -14
  193. package/src/services/junction-writes.ts +0 -295
  194. package/src/services/nested-path.ts +0 -145
  195. package/src/services/pg-notify-listener.ts +0 -151
  196. package/src/services/realtimeService.ts +0 -2545
  197. package/src/services/row-pipeline.ts +0 -240
  198. package/src/services/write-denial.ts +0 -55
  199. package/src/types.ts +0 -4
  200. package/src/utils/connection-string.ts +0 -58
  201. package/src/utils/drizzle-conditions.ts +0 -2743
  202. package/src/utils/pg-array-null-patch.ts +0 -42
  203. package/src/utils/pg-error-utils.ts +0 -391
  204. package/src/websocket.ts +0 -855
@@ -1,299 +0,0 @@
1
- /**
2
- * Channel bus over Postgres LISTEN/NOTIFY.
3
- *
4
- * Chosen because it needs nothing that a Rebase deployment does not already
5
- * have — the same database, the same direct URL the CDC listener uses. Three
6
- * properties of `NOTIFY` shape everything below:
7
- *
8
- * - **8000 bytes per payload.** Presence and cursors fit with room to spare; a
9
- * scene snapshot does not. Rather than truncate or drop, an oversized frame
10
- * on a *retained* channel is published as a pointer — the body is already in
11
- * `rebase.channel_messages` with a sequence number, so the receiver reads it
12
- * back. That is the same trick the entity path uses (notify an address,
13
- * refetch the row), applied to a different table. On an ephemeral channel
14
- * there is nothing to point at, so the publish is refused loudly instead of
15
- * reaching some instances and not others.
16
- *
17
- * - **A notify is a query on the primary database.** Not a slow one, but it
18
- * competes with the application's real queries, and that — not throughput —
19
- * is what actually limits this transport. Measured, it carried ~10k
20
- * cross-instance messages/second and stayed flat out to eight instances; what
21
- * it should not do is spend 10k queries/second of the database's budget on
22
- * cursor movement. Hence the batching below.
23
- *
24
- * - **Delivery is best-effort.** Retained channels repair themselves through
25
- * the client's history replay, so a lost frame costs a live update rather
26
- * than correctness. That is what makes coalescing safe.
27
- */
28
-
29
- import { sql } from "drizzle-orm";
30
- import { NodePgDatabase } from "drizzle-orm/node-postgres";
31
- import { logger } from "@rebasepro/server";
32
- import { PgNotifyListener } from "../pg-notify-listener";
33
- import { ChannelBus, ChannelBusFrame, ChannelBusHandler, frameByteLength } from "./ChannelBus";
34
-
35
- /** NOTIFY channel carrying channel-bus frames. */
36
- export const CHANNEL_BUS_NOTIFY_CHANNEL = "rebase_channel_bus";
37
-
38
- /**
39
- * Postgres refuses a NOTIFY payload of 8000 bytes or more. The margin below it
40
- * is for nothing in particular — it is there so that a payload which passes this
41
- * check cannot fail at the server for being a few bytes over.
42
- */
43
- export const PG_NOTIFY_MAX_PAYLOAD_BYTES = 7500;
44
-
45
- /**
46
- * How long a batching window stays open.
47
- *
48
- * Ten milliseconds is below the threshold where a human notices a cursor lag,
49
- * and it is the difference between one query per message and one query per
50
- * window under load. Set to 0 to disable coalescing entirely.
51
- */
52
- export const DEFAULT_BATCH_WINDOW_MS = 10;
53
-
54
- /** JSON overhead per frame inside a batch: the wrapping array's comma. */
55
- const BATCH_SEPARATOR_BYTES = 1;
56
- /** JSON overhead of the batch envelope itself: `{"batch":[]}`. */
57
- const BATCH_ENVELOPE_BYTES = 12;
58
-
59
- interface PendingFrame {
60
- frame: ChannelBusFrame;
61
- bytes: number;
62
- resolve: () => void;
63
- reject: (error: unknown) => void;
64
- }
65
-
66
- export class PostgresChannelBus implements ChannelBus {
67
- readonly kind = "postgres" as const;
68
- readonly maxFrameBytes = PG_NOTIFY_MAX_PAYLOAD_BYTES;
69
-
70
- private listener?: PgNotifyListener;
71
- private readonly batchWindowMs: number;
72
-
73
- /**
74
- * Frames waiting for the current window to close.
75
- *
76
- * The window is opened by a publish that found none open, and that publish
77
- * is sent *immediately* rather than joining a batch — see {@link publish}.
78
- */
79
- private pending: PendingFrame[] = [];
80
- private pendingBytes = BATCH_ENVELOPE_BYTES;
81
- private windowTimer?: ReturnType<typeof setTimeout>;
82
- private stopped = false;
83
-
84
- constructor(
85
- private readonly db: NodePgDatabase<Record<string, unknown>>,
86
- private readonly connectionString: string,
87
- options: { batchWindowMs?: number } = {}
88
- ) {
89
- const configured = options.batchWindowMs;
90
- this.batchWindowMs = typeof configured === "number" && configured >= 0
91
- ? configured
92
- : DEFAULT_BATCH_WINDOW_MS;
93
- }
94
-
95
- async start(handler: ChannelBusHandler): Promise<void> {
96
- this.stopped = false;
97
- this.listener = new PgNotifyListener({
98
- connectionString: this.connectionString,
99
- channel: CHANNEL_BUS_NOTIFY_CHANNEL,
100
- logLabel: "[ChannelBus]",
101
- onPayload: async (payload) => {
102
- const frames = parseChannelBusPayload(payload);
103
- if (!frames.length) {
104
- logger.warn("⚠️ [ChannelBus] Dropping unparseable payload.");
105
- return;
106
- }
107
- // In order: a batch preserves the sender's publish order, and a
108
- // retained channel's consumers rely on it.
109
- for (const frame of frames) await handler(frame);
110
- }
111
- });
112
- await this.listener.start();
113
- }
114
-
115
- /**
116
- * Publish, coalescing under load.
117
- *
118
- * The window is *leading edge*: a publish arriving when no window is open is
119
- * sent straight away and opens one, so an idle channel pays no added latency
120
- * at all. Frames arriving while it is open are collected and leave together
121
- * when it closes. The effect is that cost tracks elapsed time rather than
122
- * message count — one query per window instead of one per message — which is
123
- * the same shape as the retention pruning throttle, for the same reason.
124
- *
125
- * The returned promise settles when the frame has actually left, not when it
126
- * was queued, so the contract ("reaches the other instances, or rejects")
127
- * still holds.
128
- */
129
- async publish(frame: ChannelBusFrame): Promise<void> {
130
- if (this.batchWindowMs === 0 || this.stopped) {
131
- await this.send([frame]);
132
- return;
133
- }
134
-
135
- if (!this.windowTimer) {
136
- this.openWindow();
137
- await this.send([frame]);
138
- return;
139
- }
140
-
141
- const bytes = frameByteLength(frame) + BATCH_SEPARATOR_BYTES;
142
-
143
- // A batch is one NOTIFY payload, so the 8 KB ceiling applies to the
144
- // whole batch. Send what we have rather than let the frame push it over.
145
- if (this.pending.length && this.pendingBytes + bytes > this.maxFrameBytes) {
146
- this.flush();
147
- }
148
-
149
- return new Promise<void>((resolve, reject) => {
150
- this.pending.push({ frame, bytes, resolve, reject });
151
- this.pendingBytes += bytes;
152
- });
153
- }
154
-
155
- async stop(): Promise<void> {
156
- this.stopped = true;
157
- if (this.windowTimer) {
158
- clearTimeout(this.windowTimer);
159
- this.windowTimer = undefined;
160
- }
161
- // Anything still queued belongs to clients that are already waiting on
162
- // it; dropping it on shutdown would be a silent loss where a flush costs
163
- // one more query.
164
- this.flush();
165
- await this.listener?.stop();
166
- this.listener = undefined;
167
- }
168
-
169
- private openWindow(): void {
170
- this.windowTimer = setTimeout(() => {
171
- this.windowTimer = undefined;
172
- if (this.pending.length) {
173
- // Still busy: send this window's frames and open the next one,
174
- // so a sustained stream keeps costing one query per window.
175
- this.flush();
176
- this.openWindow();
177
- }
178
- // Otherwise leave it closed, so the next publish after a quiet
179
- // moment goes out immediately.
180
- }, this.batchWindowMs);
181
-
182
- // Housekeeping must never hold the process open.
183
- (this.windowTimer as unknown as { unref?: () => void }).unref?.();
184
- }
185
-
186
- /** Send everything queued and settle the promises waiting on it. */
187
- private flush(): void {
188
- if (!this.pending.length) return;
189
-
190
- const batch = this.pending;
191
- this.pending = [];
192
- this.pendingBytes = BATCH_ENVELOPE_BYTES;
193
-
194
- this.send(batch.map(p => p.frame))
195
- .then(() => { for (const p of batch) p.resolve(); })
196
- .catch((error) => { for (const p of batch) p.reject(error); });
197
- }
198
-
199
- /**
200
- * One NOTIFY.
201
- *
202
- * A single frame goes out in the plain, unwrapped shape. That is not just
203
- * economy: during a rolling deploy an instance running the previous build
204
- * understands only that shape, and low-rate traffic — presence, the tail of
205
- * a session — is exactly what is flowing while pods restart. Batching only
206
- * appears under load, which shrinks the mixed-version window to almost
207
- * nothing.
208
- */
209
- private async send(frames: ChannelBusFrame[]): Promise<void> {
210
- if (!frames.length) return;
211
- const payload = frames.length === 1
212
- ? JSON.stringify(frames[0])
213
- : JSON.stringify({ batch: frames });
214
-
215
- await this.db.execute(sql`SELECT pg_notify(${CHANNEL_BUS_NOTIFY_CHANNEL}, ${payload})`);
216
- }
217
- }
218
-
219
- /**
220
- * Parse a bus payload into the frames it carries.
221
- *
222
- * Accepts both wire shapes — a bare frame and a `{ batch: [...] }` envelope —
223
- * so an instance on the new build understands one on the old. Returns an empty
224
- * array for anything unrecognisable: a malformed or future-versioned message
225
- * must never take the listener down.
226
- */
227
- export function parseChannelBusPayload(payload: string): ChannelBusFrame[] {
228
- let parsed: unknown;
229
- try {
230
- parsed = JSON.parse(payload);
231
- } catch {
232
- return [];
233
- }
234
- if (!parsed || typeof parsed !== "object") return [];
235
-
236
- const batch = (parsed as { batch?: unknown }).batch;
237
- if (Array.isArray(batch)) {
238
- return batch
239
- .map(entry => coerceFrame(entry))
240
- .filter((frame): frame is ChannelBusFrame => frame !== null);
241
- }
242
-
243
- const single = coerceFrame(parsed);
244
- return single ? [single] : [];
245
- }
246
-
247
- /**
248
- * Parse a single bus frame, returning null for anything that is not a frame we
249
- * understand.
250
- */
251
- export function parseChannelBusFrame(payload: string): ChannelBusFrame | null {
252
- try {
253
- return coerceFrame(JSON.parse(payload));
254
- } catch {
255
- return null;
256
- }
257
- }
258
-
259
- function coerceFrame(value: unknown): ChannelBusFrame | null {
260
- if (!value || typeof value !== "object") return null;
261
-
262
- const obj = value as Record<string, unknown>;
263
- const sid = typeof obj.sid === "string" ? obj.sid : undefined;
264
- const channel = typeof obj.channel === "string" ? obj.channel : undefined;
265
- if (!sid || !channel) return null;
266
-
267
- switch (obj.kind) {
268
- case "broadcast":
269
- if (typeof obj.event !== "string") return null;
270
- return {
271
- kind: "broadcast",
272
- sid,
273
- channel,
274
- event: obj.event,
275
- from: typeof obj.from === "string" ? obj.from : undefined,
276
- seq: typeof obj.seq === "number" ? obj.seq : undefined,
277
- payload: obj.payload
278
- };
279
- case "broadcast_ref":
280
- if (typeof obj.seq !== "number") return null;
281
- return {
282
- kind: "broadcast_ref",
283
- sid,
284
- channel,
285
- from: typeof obj.from === "string" ? obj.from : undefined,
286
- seq: obj.seq
287
- };
288
- case "presence_diff":
289
- return {
290
- kind: "presence_diff",
291
- sid,
292
- channel,
293
- joins: (obj.joins ?? {}) as Record<string, Record<string, unknown>>,
294
- leaves: (obj.leaves ?? {}) as Record<string, Record<string, unknown>>
295
- };
296
- default:
297
- return null;
298
- }
299
- }
@@ -1,114 +0,0 @@
1
- /**
2
- * Resolution of the channel bus from config, environment, or a supplied instance.
3
- *
4
- * Opt-in, like every other cross-cutting realtime switch here: with nothing
5
- * configured a deployment gets the memory bus and behaves exactly as it did
6
- * before this existed. Unlike `REALTIME_CDC=auto`, there is no "try it and see"
7
- * default — a bus changes where messages go, and quietly turning on a Postgres
8
- * NOTIFY per broadcast because a direct URL happened to be set is not a
9
- * decision to make on the user's behalf.
10
- *
11
- * Two transports ship, and neither adds a service to a deployment. A third is
12
- * not a code change here: `realtime.bus` also accepts an already-constructed
13
- * {@link ChannelBus}, so a transport published as its own package plugs in
14
- * without this file learning about it. See `@rebasepro/types` →
15
- * `types/channel_bus.ts` for the contract such a package implements.
16
- */
17
-
18
- import { NodePgDatabase } from "drizzle-orm/node-postgres";
19
- import { isChannelBusInstance, type ChannelBus, type ChannelBusConfig, type ChannelBusSetting } from "@rebasepro/types";
20
- import { logger } from "@rebasepro/server";
21
- import { MemoryChannelBus } from "./ChannelBus";
22
- import { PostgresChannelBus } from "./PostgresChannelBus";
23
-
24
- export * from "./ChannelBus";
25
- export {
26
- PostgresChannelBus,
27
- CHANNEL_BUS_NOTIFY_CHANNEL,
28
- PG_NOTIFY_MAX_PAYLOAD_BYTES,
29
- DEFAULT_BATCH_WINDOW_MS,
30
- parseChannelBusFrame,
31
- parseChannelBusPayload
32
- } from "./PostgresChannelBus";
33
-
34
- export interface ChannelBusDeps {
35
- db: NodePgDatabase<Record<string, unknown>>;
36
- /**
37
- * Direct (non-pooled) Postgres URL for the LISTEN client. `LISTEN` is
38
- * session state, so behind PgBouncer in transaction mode this must be the
39
- * database itself and not the pooler.
40
- */
41
- directUrl?: string;
42
- }
43
-
44
- /**
45
- * Merge `REALTIME_CHANNEL_BUS` into the configured bus.
46
- *
47
- * The environment wins over a *named* built-in, so the transport can be changed
48
- * per deployment without a rebuild — the same reason `REALTIME_CDC` is an env
49
- * var. It does **not** win over a supplied instance: the env var can only name
50
- * transports this package knows how to construct, so honouring it there would
51
- * mean silently discarding the object the application handed us.
52
- */
53
- export function resolveChannelBusSetting(configured?: ChannelBusSetting): ChannelBusSetting {
54
- const raw = (process.env.REALTIME_CHANNEL_BUS || "").trim().toLowerCase();
55
-
56
- if (isChannelBusInstance(configured)) {
57
- if (raw && raw !== configured.kind) {
58
- logger.warn(
59
- `⚠️ [ChannelBus] REALTIME_CHANNEL_BUS="${raw}" is ignored because realtime.bus was given a ` +
60
- `"${configured.kind}" transport instance directly. Remove one of the two to make the intent clear.`
61
- );
62
- }
63
- return configured;
64
- }
65
-
66
- if (!raw) return configured ?? { type: "memory" };
67
-
68
- if (raw !== "memory" && raw !== "postgres") {
69
- logger.warn(
70
- `⚠️ [ChannelBus] Unknown REALTIME_CHANNEL_BUS value "${raw}" — expected memory|postgres, or pass a ` +
71
- "ChannelBus instance as realtime.bus for a transport that ships separately. Falling back to the " +
72
- "configured bus."
73
- );
74
- return configured ?? { type: "memory" };
75
- }
76
-
77
- // Keep the configured options (an explicit connection string) when the env
78
- // var only restates the type it was already set to.
79
- if (configured?.type === raw) return configured;
80
- return raw === "memory" ? { type: "memory" } : { type: "postgres" };
81
- }
82
-
83
- /**
84
- * Produce the bus a setting asks for.
85
- *
86
- * An instance is handed straight back — constructing it was the application's
87
- * job, and this function has nothing to add. A named built-in that turns out to
88
- * be unusable degrades to the memory bus, with the reason logged, rather than
89
- * throwing: a misconfigured bus should cost a deployment its cross-instance
90
- * fan-out, not its ability to boot.
91
- */
92
- export function createChannelBus(setting: ChannelBusSetting, deps: ChannelBusDeps): ChannelBus {
93
- if (isChannelBusInstance(setting)) return setting;
94
-
95
- switch (setting.type) {
96
- case "postgres": {
97
- const connectionString = setting.connectionString || deps.directUrl;
98
- if (!connectionString) {
99
- logger.warn(
100
- "⚠️ [ChannelBus] realtime.bus is \"postgres\" but no direct database URL is available " +
101
- "(set DATABASE_DIRECT_URL or realtime.bus.connectionString) — channel broadcast and presence " +
102
- "stay per-instance."
103
- );
104
- return new MemoryChannelBus();
105
- }
106
- return new PostgresChannelBus(deps.db, connectionString, {
107
- batchWindowMs: setting.batchWindowMs
108
- });
109
- }
110
- case "memory":
111
- default:
112
- return new MemoryChannelBus();
113
- }
114
- }