@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.
- package/dist/{BranchService-DGPL6G_C.js → BranchService-Bsn2wfaj.js} +36 -18
- package/dist/BranchService-Bsn2wfaj.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +166 -13
- 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 +14 -0
- 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-aUfRv16h.js} +37 -29
- package/dist/backup-cli-aUfRv16h.js.map +1 -0
- package/dist/{backup-service-HQ9GC4tN.js → backup-service-BTBB7RXn.js} +60 -28
- package/dist/backup-service-BTBB7RXn.js.map +1 -0
- package/dist/boot-ddl-DVpsFN6r.js +105 -0
- package/dist/boot-ddl-DVpsFN6r.js.map +1 -0
- package/dist/cli-errors-CaI4ntyD.js +1919 -0
- package/dist/cli-errors-CaI4ntyD.js.map +1 -0
- package/dist/cli-errors.d.ts +93 -5
- package/dist/{cli-flags-BglvpjHv.js → cli-flags-B-Dawocl.js} +5 -2
- package/dist/cli-flags-B-Dawocl.js.map +1 -0
- package/dist/cli-helpers.d.ts +52 -12
- package/dist/cli-output.d.ts +16 -28
- package/dist/cli-push.d.ts +87 -0
- package/dist/cli.js +625 -190
- package/dist/cli.js.map +1 -1
- package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-drZIsSNf.js} +42 -26
- package/dist/column-plan-helpers-drZIsSNf.js.map +1 -0
- package/dist/{databasePoolManager-Bj5FbeAs.js → connection-BPoNILnD.js} +19 -110
- package/dist/connection-BPoNILnD.js.map +1 -0
- package/dist/connection.d.ts +9 -1
- package/dist/data-transformer.d.ts +0 -8
- package/dist/databasePoolManager-BkEy2w18.js +119 -0
- package/dist/databasePoolManager-BkEy2w18.js.map +1 -0
- package/dist/deferred-index-build-DP1_SNFo.js +1051 -0
- package/dist/deferred-index-build-DP1_SNFo.js.map +1 -0
- package/dist/{doctor-Cb2thZ8s.js → doctor-C4WiftHm.js} +279 -87
- package/dist/doctor-C4WiftHm.js.map +1 -0
- package/dist/ensure-collection-policies-CXpbeNag.js +541 -0
- package/dist/ensure-collection-policies-CXpbeNag.js.map +1 -0
- package/dist/ensure-collection-tables-BhsU6qUE.js +532 -0
- package/dist/ensure-collection-tables-BhsU6qUE.js.map +1 -0
- package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-CHsgyLqg.js} +178 -75
- package/dist/ensure-tables-CHsgyLqg.js.map +1 -0
- package/dist/enum-labels-kept-D8fI1MoX.js +56 -0
- package/dist/enum-labels-kept-D8fI1MoX.js.map +1 -0
- package/dist/generate-drizzle-schema-Bz52jH2h.js +57 -0
- package/dist/generate-drizzle-schema-Bz52jH2h.js.map +1 -0
- package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-B74WN37S.js} +28 -10
- package/dist/generate-drizzle-schema-logic-B74WN37S.js.map +1 -0
- package/dist/generate-files-CdEckX6c.js +199 -0
- package/dist/generate-files-CdEckX6c.js.map +1 -0
- package/dist/generate-postgres-ddl-logic-B64O2oZF.js +152 -0
- package/dist/generate-postgres-ddl-logic-B64O2oZF.js.map +1 -0
- package/dist/generated-sql.d.ts +34 -0
- package/dist/history/HistoryService.d.ts +38 -6
- package/dist/history/ensure-history-table.d.ts +42 -4
- package/dist/index.es.js +5557 -7612
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-C-Di8dA9.js} +562 -350
- package/dist/introspect-db-logic-C-Di8dA9.js.map +1 -0
- package/dist/introspect-db-queries-D6kAqReI.js +339 -0
- package/dist/introspect-db-queries-D6kAqReI.js.map +1 -0
- package/dist/pg-error-utils-vJezlXdI.js +388 -0
- package/dist/pg-error-utils-vJezlXdI.js.map +1 -0
- package/dist/{plan-schema-C0fxM8dY.js → plan-schema-B1YF_tYq.js} +278 -533
- package/dist/plan-schema-B1YF_tYq.js.map +1 -0
- package/dist/{policy-drift-DljYdrpW.js → policy-drift-Dm8i-s1J.js} +24 -7
- package/dist/policy-drift-Dm8i-s1J.js.map +1 -0
- package/dist/push-index-prebuild-Db4AQF4i.js +45 -0
- package/dist/push-index-prebuild-Db4AQF4i.js.map +1 -0
- package/dist/push-legacy-renames-BK6ilQq2.js +34 -0
- package/dist/push-legacy-renames-BK6ilQq2.js.map +1 -0
- package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CtC3y9Ol.js} +17 -151
- package/dist/render-ddl-CtC3y9Ol.js.map +1 -0
- package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-RQUljnJ1.js} +125 -1046
- package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +1 -0
- package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-DvzqlaL1.js} +137 -17
- package/dist/rls-enforcement-DvzqlaL1.js.map +1 -0
- package/dist/schema/auth-schema.d.ts +172 -2
- package/dist/schema/boot-ddl.d.ts +47 -0
- 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 +27 -1
- 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/introspect-db-logic.d.ts +43 -0
- package/dist/schema/introspect-db-queries.d.ts +12 -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 +17 -6
- package/dist/schema/introspect-db.js.map +1 -1
- 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 +6 -0
- 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/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 +22 -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-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-liveness-D3UD5_BW.js +7570 -0
- package/dist/socket-liveness-D3UD5_BW.js.map +1 -0
- package/dist/socket-opt-ins.d.ts +64 -0
- package/dist/source-DBDbBTL4.js +811 -0
- package/dist/source-DBDbBTL4.js.map +1 -0
- package/dist/tsx-loader.d.ts +14 -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/websocket.d.ts +63 -18
- package/package.json +7 -7
- package/dist/BranchService-DGPL6G_C.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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SQL } from "drizzle-orm";
|
|
2
|
-
import { PgTable } from "drizzle-orm/pg-core";
|
|
2
|
+
import { AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
|
|
3
3
|
import { FilterValues, OrderByTuple, LogicalCondition } from "@rebasepro/types";
|
|
4
4
|
import type { IncludeSpec, VectorSearchParams } from "@rebasepro/types";
|
|
5
5
|
import { RelationService } from "./RelationService.js";
|
|
@@ -218,6 +218,18 @@ export declare class FetchService {
|
|
|
218
218
|
* no related row, so a typo in an `include` looked like empty data.
|
|
219
219
|
*/
|
|
220
220
|
private resolveIncludeLevel;
|
|
221
|
+
/**
|
|
222
|
+
* Resolve every level of an include tree against the collections, without
|
|
223
|
+
* loading anything — the 400 for an unknown relation, decided before the
|
|
224
|
+
* read rather than by it.
|
|
225
|
+
*
|
|
226
|
+
* {@link loadIncludes} resolves a level only once it has rows to load it
|
|
227
|
+
* onto. On its own that made the refusal depend on the data: a typo in
|
|
228
|
+
* `include` against an empty table, under a filter that matched nothing,
|
|
229
|
+
* or below a relation that loaded no rows answered 200, and the same
|
|
230
|
+
* request became a 400 the first time a row matched.
|
|
231
|
+
*/
|
|
232
|
+
private assertIncludeResolvable;
|
|
221
233
|
/**
|
|
222
234
|
* The extra `WHERE` an include's own `where`/`logical` puts on the target.
|
|
223
235
|
*
|
|
@@ -227,14 +239,25 @@ export declare class FetchService {
|
|
|
227
239
|
*/
|
|
228
240
|
private includeNarrowing;
|
|
229
241
|
/**
|
|
230
|
-
*
|
|
242
|
+
* Refuse a `count` the loader cannot serve honestly, before anything runs.
|
|
231
243
|
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
|
|
244
|
+
* - On a to-one relation: there is nothing to count past a row or none,
|
|
245
|
+
* and answering 0 or 1 under `_count` would invent a shape for it.
|
|
246
|
+
* - On a collection with a property named `_count`: the total would be
|
|
247
|
+
* written over that column's value. A silent overwrite answers 200 with
|
|
248
|
+
* the column gone, so it is a 400 naming why.
|
|
249
|
+
*/
|
|
250
|
+
private assertCountsAllowed;
|
|
251
|
+
/**
|
|
252
|
+
* Cap and project the rows of ONE relation, per parent.
|
|
253
|
+
*
|
|
254
|
+
* The order and the limit are Postgres's: the batch loader ranks each
|
|
255
|
+
* parent's related rows by the include's `orderBy` and reads back only the
|
|
256
|
+
* ones within its `limit` (see
|
|
257
|
+
* {@link RelationService.batchFetchRelatedEntitiesMany}), so `rows`
|
|
258
|
+
* arrives sorted and already cut. The slice here is what holds the limit
|
|
259
|
+
* where a `via` chain reaches one target along two paths — those rows share
|
|
260
|
+
* a rank — and it costs nothing where nothing is left to cut.
|
|
238
261
|
*/
|
|
239
262
|
private shapeRelatedRows;
|
|
240
263
|
/**
|
|
@@ -313,9 +336,10 @@ export declare class FetchService {
|
|
|
313
336
|
* Extract cursor pagination conditions from startAfter options.
|
|
314
337
|
*
|
|
315
338
|
* "Every row that sorts after this one", written out as a comparison over
|
|
316
|
-
* the same keys the `ORDER BY` uses and ending on the same
|
|
317
|
-
* one key that is the familiar
|
|
318
|
-
* several it nests, each key's
|
|
339
|
+
* the same keys the `ORDER BY` uses and ending on the same primary key,
|
|
340
|
+
* descending. With one sort key that is the familiar
|
|
341
|
+
* `k > v OR (k = v AND id < cursorId)`; with several it nests, each key's
|
|
342
|
+
* tie handing the decision to the next.
|
|
319
343
|
*/
|
|
320
344
|
private buildCursorConditions;
|
|
321
345
|
/** The table's primary key columns, in key order. */
|
|
@@ -359,7 +383,7 @@ export declare class FetchService {
|
|
|
359
383
|
*/
|
|
360
384
|
private preciseCursorValue;
|
|
361
385
|
/**
|
|
362
|
-
* "Sorts strictly after the cursor row", over `keys` and then the
|
|
386
|
+
* "Sorts strictly after the cursor row", over `keys` and then the primary key.
|
|
363
387
|
*
|
|
364
388
|
* Built by recursion rather than as a row-value comparison — `(a, b) > (x, y)`
|
|
365
389
|
* would be shorter, but it is only correct when every key runs the same
|
|
@@ -645,6 +669,33 @@ export declare class FetchService {
|
|
|
645
669
|
/** See `FetchCollectionProps.withDeleted`. */
|
|
646
670
|
withDeleted?: WithDeleted;
|
|
647
671
|
}): Promise<Record<string, unknown> | null>;
|
|
672
|
+
/**
|
|
673
|
+
* {@link fetchOneForRest}'s read of a row an INSERT just wrote, taken from
|
|
674
|
+
* the INSERT's own `RETURNING` instead of a second statement — or
|
|
675
|
+
* `undefined` when the two could answer differently.
|
|
676
|
+
*
|
|
677
|
+
* A write answers with `fetchOneForRest(slug, id, undefined, …, { withDeleted: true })`
|
|
678
|
+
* (see `PersistService.save`): the row's visible columns, rendered by
|
|
679
|
+
* `toRestRow`. For a row the statement itself wrote, with nothing written
|
|
680
|
+
* after it, `RETURNING` those same columns is that row — the same values
|
|
681
|
+
* through the same column decoders, in the same order, rendered the same
|
|
682
|
+
* way. Two things narrow the read and not the statement, and either one
|
|
683
|
+
* keeps the read:
|
|
684
|
+
*
|
|
685
|
+
* - a `beforeQuery` adds conditions to the read (and runs application
|
|
686
|
+
* code): a row outside them is no answer today, and would be one from
|
|
687
|
+
* `RETURNING`;
|
|
688
|
+
* - per-field read access, which projects the read for the viewer.
|
|
689
|
+
*
|
|
690
|
+
* Row-level security is the same either way. `INSERT … RETURNING` holds
|
|
691
|
+
* the new row to the table's SELECT policies and refuses one they hide
|
|
692
|
+
* (42501) — which the INSERT's `RETURNING <key>` already did, so returning
|
|
693
|
+
* more columns changes nothing about who may write or read what.
|
|
694
|
+
*/
|
|
695
|
+
insertReturning(collectionPath: string): {
|
|
696
|
+
columns: Record<string, AnyPgColumn>;
|
|
697
|
+
render: (returned: Record<string, unknown>) => Record<string, unknown>;
|
|
698
|
+
} | undefined;
|
|
648
699
|
/**
|
|
649
700
|
* Fetch raw rows without any relation processing (for REST fast path)
|
|
650
701
|
*/
|
|
@@ -75,6 +75,21 @@ export declare class PersistService {
|
|
|
75
75
|
* for why a zero-row write cannot be reported as success.
|
|
76
76
|
*/
|
|
77
77
|
private explainZeroRowWrite;
|
|
78
|
+
/**
|
|
79
|
+
* Lock one row against every other write to it until this transaction
|
|
80
|
+
* ends: `FOR NO KEY UPDATE`, the lock an UPDATE of it takes anyway.
|
|
81
|
+
*
|
|
82
|
+
* For a conditional write (`If-Match`): taken before the row is read and
|
|
83
|
+
* compared, so a write that lands meanwhile is either already committed and
|
|
84
|
+
* read, or waits for this one. Without it two writes carrying the same tag
|
|
85
|
+
* both read the old row, both passed, and the second overwrote the first.
|
|
86
|
+
*
|
|
87
|
+
* Under row-level security the lock reads through the caller's UPDATE
|
|
88
|
+
* policies as well as their SELECT ones, so a row they may read but not
|
|
89
|
+
* update is simply not locked — and their write is refused by the database
|
|
90
|
+
* all the same. A nested address locks the row it ends in.
|
|
91
|
+
*/
|
|
92
|
+
lockRow(collectionPath: string, id: string | number): Promise<void>;
|
|
78
93
|
/**
|
|
79
94
|
* Delete an row by ID
|
|
80
95
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SQL } from "drizzle-orm";
|
|
2
|
-
import { PgTable } from "drizzle-orm/pg-core";
|
|
2
|
+
import { AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
|
|
3
3
|
import { DrizzleClient } from "../interfaces.js";
|
|
4
4
|
import { CollectionConfig, FilterValues, OrderByTuple, ResolvedRelation, ResolvedHasMany, ResolvedHasOne } from "@rebasepro/types";
|
|
5
5
|
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry.js";
|
|
@@ -16,6 +16,70 @@ import { type ReadCallContextProvider } from "./read-scope.js";
|
|
|
16
16
|
* the same claim about the same drizzle behaviour in a second spelling.
|
|
17
17
|
*/
|
|
18
18
|
export declare function applyDynamicJoin<T>(query: T, joinTable: PgTable, condition: SQL): T;
|
|
19
|
+
/**
|
|
20
|
+
* The ON condition of one `joinPath` step: every `from` column equal to its
|
|
21
|
+
* `to` column, or `undefined` naming the first column either table lacks.
|
|
22
|
+
*
|
|
23
|
+
* `on.from`/`on.to` are a column or a tuple, and a tuple is how a step joins on
|
|
24
|
+
* a composite key. Comparing the first pair alone joined each row to every row
|
|
25
|
+
* sharing that column — every locale of a translation, not the one addressed.
|
|
26
|
+
*/
|
|
27
|
+
export declare function joinStepCondition(currentTable: PgTable, joinTable: PgTable, step: {
|
|
28
|
+
on: {
|
|
29
|
+
from: string | string[];
|
|
30
|
+
to: string | string[];
|
|
31
|
+
};
|
|
32
|
+
}): {
|
|
33
|
+
condition: SQL;
|
|
34
|
+
} | {
|
|
35
|
+
missing: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* What an include asks of ONE to-many relation's rows, per parent: how many,
|
|
39
|
+
* and in what order. See {@link RelationService.batchFetchRelatedEntitiesMany}.
|
|
40
|
+
*/
|
|
41
|
+
export interface RelatedRowWindow {
|
|
42
|
+
/** Rows to keep per parent. */
|
|
43
|
+
limit?: number;
|
|
44
|
+
/** Their order, by the target's own columns. */
|
|
45
|
+
orderBy?: OrderByTuple[];
|
|
46
|
+
/**
|
|
47
|
+
* Filled, when given and a `limit` is set, with each parent's number of
|
|
48
|
+
* matching related rows *before* the limit — what `include: { x: { limit,
|
|
49
|
+
* count: true } }` serves under `_count`. Counted in the ranking subquery,
|
|
50
|
+
* so it is the same statement, the same conditions and the same RLS
|
|
51
|
+
* context as the rows. A parent with no related row is absent (zero).
|
|
52
|
+
* Without a limit nothing is cut, and the caller counts what it got.
|
|
53
|
+
*/
|
|
54
|
+
totals?: Map<string, number>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* How the batch loaders render a target row.
|
|
58
|
+
*
|
|
59
|
+
* - `"admin"` — the view model the admin panel reads: declared types parsed
|
|
60
|
+
* (`parseDataFromServer`), the target's own to-one relations as
|
|
61
|
+
* `{ __type: "relation" }` refs, undeclared columns dropped.
|
|
62
|
+
* - `"rest"` — the row the target's own route serves (`toRestRow`): what an
|
|
63
|
+
* `include` embeds. The REST contract is that a developer never sees a ref,
|
|
64
|
+
* and an included row is rendered exactly as the same row is at the top
|
|
65
|
+
* level.
|
|
66
|
+
*/
|
|
67
|
+
export type RelatedRowStyle = "admin" | "rest";
|
|
68
|
+
/**
|
|
69
|
+
* The order one parent's related rows come in: the include's own keys, then
|
|
70
|
+
* the target's key ascending.
|
|
71
|
+
*
|
|
72
|
+
* The key ascending is the order an unordered batch read handed rows back in
|
|
73
|
+
* on an append-only table, and so the rows a `limit` with no `orderBy` kept —
|
|
74
|
+
* stated now rather than left to the plan. A key that names no column of the
|
|
75
|
+
* target sorts nothing, as it sorted nothing when the sort ran over the
|
|
76
|
+
* returned rows in JavaScript; the caller's field access on it was already
|
|
77
|
+
* judged at the route.
|
|
78
|
+
*
|
|
79
|
+
* NULLs go where a top-level `orderBy` puts them: the stated placement, else
|
|
80
|
+
* Postgres's own (last ascending, first descending).
|
|
81
|
+
*/
|
|
82
|
+
export declare function relatedRowOrder(targetTable: PgTable, targetKeys: AnyPgColumn[], orderBy: OrderByTuple[] | undefined): SQL[];
|
|
19
83
|
/**
|
|
20
84
|
* Service for handling all relation-related operations.
|
|
21
85
|
* Handles fetching, updating, and managing row relations.
|
|
@@ -101,6 +165,21 @@ export declare class RelationService {
|
|
|
101
165
|
* nobody was comparing.
|
|
102
166
|
*/
|
|
103
167
|
private toRelatedRow;
|
|
168
|
+
/**
|
|
169
|
+
* A target row as its own REST route serves it, for the `"rest"` style.
|
|
170
|
+
*
|
|
171
|
+
* The loaders read the target with `SELECT *`, so the columns a REST read
|
|
172
|
+
* never selects — a generated search vector — are dropped first; then
|
|
173
|
+
* `toRestRow` renders the rest, which applies the target's field access.
|
|
174
|
+
* Built once per batch: the hidden set is a property of the table.
|
|
175
|
+
*
|
|
176
|
+
* It replaces a second pass. An include used to render each target row as
|
|
177
|
+
* the admin's view model (`parseDataFromServer`, which also minted a
|
|
178
|
+
* `{ __type: "relation" }` ref for the target's own to-one relations) and
|
|
179
|
+
* then re-render that for REST: 3.4 µs a row against 1.4 µs for the row
|
|
180
|
+
* itself, and an envelope in a response whose contract has none.
|
|
181
|
+
*/
|
|
182
|
+
private restRendering;
|
|
104
183
|
/**
|
|
105
184
|
* The junction row's own columns, as the caller may see them.
|
|
106
185
|
*
|
|
@@ -224,13 +303,37 @@ export declare class RelationService {
|
|
|
224
303
|
* the rows that come back, because filtering afterwards reads every related
|
|
225
304
|
* row of every parent in order to discard most of them.
|
|
226
305
|
*/
|
|
227
|
-
batchFetchRelatedEntities(parentCollectionPath: string, parentIds: (string | number)[], _relationKey: string, relation: ResolvedRelation, narrow?: SQL
|
|
306
|
+
batchFetchRelatedEntities(parentCollectionPath: string, parentIds: (string | number)[], _relationKey: string, relation: ResolvedRelation, narrow?: SQL, options?: {
|
|
307
|
+
/** How each target row is rendered; see {@link RelatedRowStyle}. */
|
|
308
|
+
style?: RelatedRowStyle;
|
|
309
|
+
/**
|
|
310
|
+
* The parent rows themselves, in `parentIds` order, when the caller
|
|
311
|
+
* holds them. A `belongsTo` reads each one's foreign key off its
|
|
312
|
+
* row rather than selecting it again.
|
|
313
|
+
*/
|
|
314
|
+
parentRows?: Record<string, unknown>[];
|
|
315
|
+
}): Promise<Map<string, RelatedRow<Record<string, unknown>>>>;
|
|
228
316
|
/**
|
|
229
317
|
* Batch fetch many-cardinality related rows for multiple parent rows.
|
|
230
318
|
* Returns a Map<parentId, RelatedRow[]> instead of Map<parentId, RelatedRow>.
|
|
231
319
|
* Uses a single SQL query with IN clause to avoid N+1.
|
|
320
|
+
*
|
|
321
|
+
* `window` is an include's own `limit` and `orderBy`, and both are applied
|
|
322
|
+
* by Postgres. A limit is *per parent*, so it is a rank within each
|
|
323
|
+
* parent's rows rather than a `LIMIT`: a subquery ranks the keys of every
|
|
324
|
+
* matching row — under the same relation condition, `narrow`, soft delete
|
|
325
|
+
* and `beforeQuery` the unlimited read applies — and the read joins back to
|
|
326
|
+
* the ones ranked within the limit. It used to read every related row of
|
|
327
|
+
* every parent and cut the list in JavaScript: 200 rows served out of
|
|
328
|
+
* 200,000 read, parsed and rendered for `{ messages: { limit: 2 } }` on a
|
|
329
|
+
* page of 100 threads. Rows come back in the window's order, so the
|
|
330
|
+
* per-parent lists are already sorted.
|
|
232
331
|
*/
|
|
233
332
|
batchFetchRelatedEntitiesMany(parentCollectionPath: string, parentIds: (string | number)[], _relationKey: string, relation: ResolvedRelation,
|
|
234
333
|
/** An extra condition on the target — see {@link batchFetchRelatedEntities}. */
|
|
235
|
-
narrow?: SQL
|
|
334
|
+
narrow?: SQL, window?: RelatedRowWindow,
|
|
335
|
+
/** `style` — how each target row is rendered; see {@link RelatedRowStyle}. */
|
|
336
|
+
options?: {
|
|
337
|
+
style?: RelatedRowStyle;
|
|
338
|
+
}): Promise<Map<string, RelatedRow<Record<string, unknown>>[]>>;
|
|
236
339
|
}
|
|
@@ -1,25 +1,38 @@
|
|
|
1
|
+
import { type PgNotifyListenerStatus } from "../pg-notify-listener.js";
|
|
1
2
|
/**
|
|
2
3
|
* A single database change captured by the CDC triggers and delivered over the
|
|
3
|
-
* `rebase_cdc` NOTIFY channel.
|
|
4
|
+
* `rebase_cdc` NOTIFY channel — one row of a notification, which may name many.
|
|
4
5
|
*/
|
|
5
6
|
export interface CdcChangeEvent {
|
|
6
7
|
schema: string;
|
|
7
8
|
table: string;
|
|
8
|
-
|
|
9
|
+
/** `TRUNCATE` changed every row, and `row` is empty. */
|
|
10
|
+
op: "INSERT" | "UPDATE" | "DELETE" | "TRUNCATE";
|
|
9
11
|
/**
|
|
10
|
-
* The changed
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* The changed row's identity, by column: its key, and for a junction table
|
|
13
|
+
* the two ids naming the child list (from NEW for insert/update, OLD for
|
|
14
|
+
* delete). Never the row's other values — any login can LISTEN; see
|
|
15
|
+
* `buildCdcFunctionSql`.
|
|
13
16
|
*/
|
|
14
17
|
row: Record<string, unknown>;
|
|
15
|
-
/** True when the
|
|
18
|
+
/** True when even the key was too large to notify, so `row` is empty. */
|
|
16
19
|
truncated?: boolean;
|
|
17
20
|
}
|
|
18
21
|
/**
|
|
19
|
-
* Parse a `rebase_cdc` NOTIFY payload
|
|
20
|
-
* a single bad message can never
|
|
22
|
+
* Parse a `rebase_cdc` NOTIFY payload into the changes it names, one per row.
|
|
23
|
+
* Returns `null` for anything malformed so a single bad message can never
|
|
24
|
+
* crash the listener.
|
|
25
|
+
*
|
|
26
|
+
* Reads every shape a capture trigger sends, whichever version installed it —
|
|
27
|
+
* during a rolling deploy the database's triggers and this instance need not
|
|
28
|
+
* be the same version:
|
|
29
|
+
* - `{ schema, table, op, row }` — one row: the row trigger, and a statement
|
|
30
|
+
* that changed one row;
|
|
31
|
+
* - `{ schema, table, op, cols, keys }` — a statement's rows in a batch. With
|
|
32
|
+
* one column a key is the value; with several, an array in `cols` order;
|
|
33
|
+
* - `{ schema, table, op: "TRUNCATE" }`.
|
|
21
34
|
*/
|
|
22
|
-
export declare function parseCdcPayload(payload: string): CdcChangeEvent | null;
|
|
35
|
+
export declare function parseCdcPayload(payload: string): CdcChangeEvent[] | null;
|
|
23
36
|
/**
|
|
24
37
|
* Dedicated Postgres LISTEN client for database-level CDC.
|
|
25
38
|
*
|
|
@@ -36,7 +49,7 @@ export declare class CdcListener {
|
|
|
36
49
|
* drop. Every change committed in the gap was notified to nobody, so
|
|
37
50
|
* this is where subscribers are told to look again.
|
|
38
51
|
*/
|
|
39
|
-
constructor(connectionString: string,
|
|
52
|
+
constructor(connectionString: string, onEvents: (events: CdcChangeEvent[]) => void | Promise<void>, onReconnect?: () => void, applicationName?: string);
|
|
40
53
|
/**
|
|
41
54
|
* Connect and begin listening. Idempotent.
|
|
42
55
|
*
|
|
@@ -49,4 +62,7 @@ export declare class CdcListener {
|
|
|
49
62
|
start(): Promise<void>;
|
|
50
63
|
/** Stop listening and release the connection. */
|
|
51
64
|
stop(): Promise<void>;
|
|
65
|
+
/** Listening on a connection that answered its last heartbeat — see {@link PgNotifyListener}. */
|
|
66
|
+
get connected(): boolean;
|
|
67
|
+
status(): PgNotifyListenerStatus;
|
|
52
68
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CollectionConfig } from "@rebasepro/types";
|
|
2
|
+
import type { PostgresCollectionRegistry } from "../../collections/PostgresCollectionRegistry.js";
|
|
3
|
+
/** One key field of a collection, and the column it is stored in. */
|
|
4
|
+
export interface KeyColumn {
|
|
5
|
+
fieldName: string;
|
|
6
|
+
columnName: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The columns a collection's rows are addressed by.
|
|
10
|
+
*
|
|
11
|
+
* A change notification names its row by column — that is all a trigger sees —
|
|
12
|
+
* and an address is built from the key's *field* names. Both ends read the
|
|
13
|
+
* mapping from here: the provisioner, to attach the trigger with exactly these
|
|
14
|
+
* columns (and nothing else leaves the database), and the consumer, to turn the
|
|
15
|
+
* captured columns back into an address. A key declared as `userId` over
|
|
16
|
+
* `user_id` used to be looked up by field name on the captured row, never
|
|
17
|
+
* found, and every external write reached no single-row subscriber.
|
|
18
|
+
*/
|
|
19
|
+
export declare function collectionKeyColumns(collection: CollectionConfig, registry: PostgresCollectionRegistry): KeyColumn[];
|
|
@@ -6,9 +6,13 @@ import type { RawSqlRunner } from "../../security/rls-enforcement.js";
|
|
|
6
6
|
* — like Supabase Realtime — sees *every* commit regardless of how it was made.
|
|
7
7
|
* When logical replication is unavailable (managed Postgres without
|
|
8
8
|
* `wal_level=logical`, no replication privilege, no `REPLICA IDENTITY`), this
|
|
9
|
-
* trigger-based fallback provides the same guarantee
|
|
9
|
+
* trigger-based fallback provides the same guarantee:
|
|
10
10
|
*
|
|
11
|
-
* AFTER INSERT/UPDATE/DELETE trigger → pg_notify('rebase_cdc', payload)
|
|
11
|
+
* AFTER INSERT/UPDATE/DELETE/TRUNCATE statement trigger → pg_notify('rebase_cdc', payload)
|
|
12
|
+
*
|
|
13
|
+
* once per statement, naming the changed rows by key in batches of about a
|
|
14
|
+
* thousand — a 1M-row UPDATE is ~1,000 notifications, where a row trigger sent
|
|
15
|
+
* a million into Postgres's shared notification queue, to every instance.
|
|
12
16
|
*
|
|
13
17
|
* A single dedicated LISTEN client per backend instance consumes the channel
|
|
14
18
|
* (see {@link CdcListener}) and feeds the change into the existing
|
|
@@ -21,29 +25,127 @@ import type { RawSqlRunner } from "../../security/rls-enforcement.js";
|
|
|
21
25
|
*/
|
|
22
26
|
/** Postgres NOTIFY channel carrying database-level change events. */
|
|
23
27
|
export declare const CDC_CHANNEL = "rebase_cdc";
|
|
24
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Schema-qualified name of the row-level trigger function: one notification
|
|
30
|
+
* per changed row. What every version before statement-level capture attached
|
|
31
|
+
* everywhere, and still what a partitioned or foreign table gets — see
|
|
32
|
+
* {@link levelForRelkind}.
|
|
33
|
+
*/
|
|
25
34
|
export declare const CDC_TRIGGER_FUNCTION = "rebase.rebase_cdc_notify";
|
|
26
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Schema-qualified name of the statement-level trigger function: one
|
|
37
|
+
* notification per ~1,000 changed rows. See {@link buildCdcFunctionSql}.
|
|
38
|
+
*
|
|
39
|
+
* A function of its own rather than a new body for the row function, because
|
|
40
|
+
* an instance of an older version replaces the row function's body every time
|
|
41
|
+
* it boots: a statement trigger calling it would then run a row trigger's body,
|
|
42
|
+
* for every write, until the next boot of this version.
|
|
43
|
+
*/
|
|
44
|
+
export declare const CDC_STATEMENT_FUNCTION = "rebase.rebase_cdc_notify_statement";
|
|
45
|
+
/** Name of the per-table row-level trigger (unqualified — triggers are namespaced by table). */
|
|
27
46
|
export declare const CDC_TRIGGER_NAME = "rebase_cdc_trigger";
|
|
47
|
+
/** How a table is instrumented: one notification per statement's ~1,000 rows, or one per row. */
|
|
48
|
+
export type CdcTriggerLevel = "statement" | "row";
|
|
49
|
+
/** One trigger this module creates, as `pg_trigger` describes it. */
|
|
50
|
+
interface CdcTriggerSpec {
|
|
51
|
+
name: string;
|
|
52
|
+
/** The function it calls. */
|
|
53
|
+
fn: "row" | "statement";
|
|
54
|
+
/** `CREATE TRIGGER` between the name and `ON`. */
|
|
55
|
+
timing: string;
|
|
56
|
+
/** `CREATE TRIGGER` between the table and `EXECUTE`. */
|
|
57
|
+
forEach: string;
|
|
58
|
+
/** `tgtype`: ROW (1), BEFORE (2), INSERT (4), DELETE (8), UPDATE (16), TRUNCATE (32), INSTEAD (64). */
|
|
59
|
+
type: number;
|
|
60
|
+
/** `tgnewtable` / `tgoldtable`: the transition tables it captures. */
|
|
61
|
+
newTable: string | null;
|
|
62
|
+
oldTable: string | null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The statement-level triggers a plain table gets. One per event, because
|
|
66
|
+
* Postgres refuses transition tables on a trigger with more than one event.
|
|
67
|
+
* The TRUNCATE one is new with them: a row trigger never fires on `TRUNCATE`,
|
|
68
|
+
* so a truncated table used to be announced to nobody.
|
|
69
|
+
*/
|
|
70
|
+
export declare const CDC_STATEMENT_TRIGGERS: readonly CdcTriggerSpec[];
|
|
71
|
+
/**
|
|
72
|
+
* The level a table is instrumented at, by `pg_class.relkind`.
|
|
73
|
+
*
|
|
74
|
+
* A plain table gets statement triggers. A partitioned table keeps the row
|
|
75
|
+
* trigger: Postgres clones a row trigger onto every partition, so a write made
|
|
76
|
+
* straight to a partition is still captured, while a statement trigger on the
|
|
77
|
+
* parent fires only for statements against the parent. A foreign table
|
|
78
|
+
* cannot have transition tables at all.
|
|
79
|
+
*/
|
|
80
|
+
export declare const levelForRelkind: (relkind: string | null | undefined) => CdcTriggerLevel;
|
|
28
81
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* The
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
82
|
+
* How long after a boot first converts a table from the row trigger, the
|
|
83
|
+
* statement triggers still announce every change one row per notification.
|
|
84
|
+
*
|
|
85
|
+
* The triggers are the database's, and an instance of the previous version
|
|
86
|
+
* still running beside this one — a rolling deploy, a rollback — reads the
|
|
87
|
+
* same channel with a parser that knows one row per notification: given a
|
|
88
|
+
* batch it sees no key, so its lists refetch but its single-row and
|
|
89
|
+
* many-to-many subscribers hear nothing. While any such instance may be up,
|
|
90
|
+
* the statement triggers speak its protocol; after this window they batch.
|
|
91
|
+
* Every version that batches reads both. A boot of the older version re-attaches its row trigger (it
|
|
92
|
+
* creates a missing one), and the next boot of this one converts it back and
|
|
93
|
+
* re-opens the window, so a rollback is covered the same way.
|
|
37
94
|
*/
|
|
38
|
-
export declare
|
|
95
|
+
export declare const CDC_ROW_COMPAT_MS: number;
|
|
39
96
|
/**
|
|
40
|
-
* SQL that (re)
|
|
41
|
-
*
|
|
97
|
+
* SQL that (re)creates both CDC trigger functions. Safe to run repeatedly:
|
|
98
|
+
* `CREATE OR REPLACE` updates in place without dropping dependent triggers —
|
|
99
|
+
* which is also how a database instrumented by an older version gets these
|
|
100
|
+
* bodies on its next boot, for every table at once, re-attached or not.
|
|
101
|
+
*
|
|
102
|
+
* Both functions emit `{ schema, table, op, ... }`, and the rows are named by
|
|
103
|
+
* **identity, never the tuple**. Postgres puts no privilege on `LISTEN`: any
|
|
104
|
+
* role that can connect can listen on this channel, whatever it may `SELECT`.
|
|
105
|
+
* When `row` was `to_jsonb(NEW)`, a login with no grant on anything received
|
|
106
|
+
* every changed row of every instrumented table — `password_hash` and
|
|
107
|
+
* verification tokens from the auth table included — past RLS and column
|
|
108
|
+
* grants. The consumer never read anything but the key: every subscriber
|
|
109
|
+
* re-reads the row under its own scope.
|
|
110
|
+
*
|
|
111
|
+
* The identity is, in order:
|
|
112
|
+
* - the columns the trigger was attached with (`TG_ARGV`) — the key the
|
|
113
|
+
* consumer addresses the row by, and for a junction table the two ids that
|
|
114
|
+
* name the child list it changed; see {@link buildCdcTriggerSql};
|
|
115
|
+
* - otherwise the table's primary key, read from the catalogue — a trigger
|
|
116
|
+
* attached with no columns, by an older boot or on a table no collection
|
|
117
|
+
* maps any more;
|
|
118
|
+
* - otherwise `id`, if the table has one, and else nothing: a change the
|
|
119
|
+
* consumer can only treat as collection-wide.
|
|
120
|
+
*
|
|
121
|
+
* `compatUntil` is the end of the window in which the statement function
|
|
122
|
+
* still sends one row per notification — see {@link CDC_ROW_COMPAT_MS}. A
|
|
123
|
+
* timestamp, or `-infinity` for none.
|
|
42
124
|
*/
|
|
43
|
-
export declare function
|
|
125
|
+
export declare function buildCdcFunctionSql(options?: {
|
|
126
|
+
compatUntil?: string;
|
|
127
|
+
}): string;
|
|
128
|
+
/**
|
|
129
|
+
* SQL that (re)attaches the CDC triggers to a single table, at `level`.
|
|
130
|
+
* Every trigger this module owns is dropped first, whichever level it was
|
|
131
|
+
* created at, so a table moves between levels — a row trigger left by an
|
|
132
|
+
* older version becomes the statement triggers — in one transaction, with no
|
|
133
|
+
* moment at which a write is announced by neither.
|
|
134
|
+
*
|
|
135
|
+
* `identityColumns` are what the payload names the changed rows by — see
|
|
136
|
+
* {@link buildCdcFunctionSql}. Passed as trigger arguments, so one shared
|
|
137
|
+
* function serves every table. Empty means "the primary key".
|
|
138
|
+
*/
|
|
139
|
+
export declare function buildCdcTriggerSql(schema: string, table: string, identityColumns?: readonly string[], level?: CdcTriggerLevel): string;
|
|
44
140
|
export interface CdcTableRef {
|
|
45
141
|
schema: string;
|
|
46
142
|
table: string;
|
|
143
|
+
/**
|
|
144
|
+
* The columns a change to this table is announced by — the collection's
|
|
145
|
+
* key columns, or a junction's two id columns. Absent means the primary
|
|
146
|
+
* key. Nothing else ever leaves the trigger; see {@link buildCdcFunctionSql}.
|
|
147
|
+
*/
|
|
148
|
+
identityColumns?: string[];
|
|
47
149
|
}
|
|
48
150
|
export interface ProvisionResult {
|
|
49
151
|
/** Tables the trigger was successfully attached to. */
|
|
@@ -54,11 +156,71 @@ export interface ProvisionResult {
|
|
|
54
156
|
}>;
|
|
55
157
|
}
|
|
56
158
|
/**
|
|
57
|
-
*
|
|
159
|
+
* Replace the already-installed trigger functions with the current bodies, and
|
|
160
|
+
* install nothing where there is none.
|
|
161
|
+
*
|
|
162
|
+
* For a process that owns the schema but is not provisioning capture on this
|
|
163
|
+
* boot — `REALTIME_CDC=off`, or no direct URL to listen on. The triggers an
|
|
164
|
+
* earlier boot attached keep firing whether anything consumes them or not, so
|
|
165
|
+
* a database instrumented by a version whose function put whole rows on the
|
|
166
|
+
* channel kept doing so, for every login that cares to `LISTEN`, until capture
|
|
167
|
+
* was switched back on. The statement function keeps its compatibility
|
|
168
|
+
* window. Returns whether a function was there to replace.
|
|
169
|
+
*/
|
|
170
|
+
export declare function refreshInstalledCdcFunction(run: RawSqlRunner): Promise<boolean>;
|
|
171
|
+
/**
|
|
172
|
+
* Take change capture off the database: detach every capture trigger and drop
|
|
173
|
+
* both functions. Returns how many triggers were attached.
|
|
174
|
+
*
|
|
175
|
+
* For `REALTIME_CDC=off`. Capture switched off used to leave the triggers in
|
|
176
|
+
* place, so every write to every instrumented table still ran one and sent a
|
|
177
|
+
* notification on a channel nothing listened to. Nothing depends on them once
|
|
178
|
+
* capture is off: a process without capture announces its own writes on the
|
|
179
|
+
* cross-instance channel, and the instances that serve realtime listen there.
|
|
180
|
+
*
|
|
181
|
+
* `DROP FUNCTION … CASCADE` takes the triggers with it in one statement. It
|
|
182
|
+
* waits for each table's lock like any trigger DDL, so it runs with the boot's
|
|
183
|
+
* lock budget and is retried on a lock timeout; a database still contended
|
|
184
|
+
* after that keeps its triggers and the caller reports it.
|
|
185
|
+
*/
|
|
186
|
+
export declare function removeTriggerCdc(run: RawSqlRunner): Promise<number>;
|
|
187
|
+
/**
|
|
188
|
+
* What a boot that does not provision capture does with capture an earlier
|
|
189
|
+
* boot installed.
|
|
190
|
+
*
|
|
191
|
+
* - `capture: "off"` — `REALTIME_CDC=off` — removes it ({@link removeTriggerCdc}).
|
|
192
|
+
* - `capture: "not-provisioned"` — capture is wanted but this boot could not
|
|
193
|
+
* install it (no direct URL, `auto` fell back) — leaves the triggers and
|
|
194
|
+
* brings the functions up to date ({@link refreshInstalledCdcFunction}),
|
|
195
|
+
* since they keep firing.
|
|
196
|
+
*/
|
|
197
|
+
export declare function settleInstalledCapture(run: RawSqlRunner, options: {
|
|
198
|
+
capture: "off" | "not-provisioned";
|
|
199
|
+
}): Promise<{
|
|
200
|
+
removed: number;
|
|
201
|
+
} | {
|
|
202
|
+
refreshed: boolean;
|
|
203
|
+
}>;
|
|
204
|
+
/**
|
|
205
|
+
* Idempotently install the CDC trigger functions and per-table triggers.
|
|
206
|
+
*
|
|
207
|
+
* A plain table gets the statement-level triggers, a partitioned or foreign
|
|
208
|
+
* one the row trigger ({@link levelForRelkind}). A table whose triggers are
|
|
209
|
+
* already exactly the ones this would create — same function, same events,
|
|
210
|
+
* enabled, same identity arguments — is left alone; one with a trigger
|
|
211
|
+
* missing, disabled, attached with different arguments or at the other level
|
|
212
|
+
* is (re-)attached, all of it in one transaction.
|
|
213
|
+
*
|
|
214
|
+
* Converting a table from a row trigger an older version attached opens the
|
|
215
|
+
* statement function's compatibility window ({@link CDC_ROW_COMPAT_MS}): the
|
|
216
|
+
* instances that attached it may still be listening.
|
|
58
217
|
*
|
|
59
218
|
* Runs as the owner (server) connection at bootstrap. A table that does not yet
|
|
60
219
|
* exist in the database (schema drift) is skipped with a warning rather than
|
|
61
220
|
* aborting the whole install, so one un-migrated collection cannot disable CDC
|
|
62
221
|
* for the rest.
|
|
63
222
|
*/
|
|
64
|
-
export declare function provisionTriggerCdc(run: RawSqlRunner, tables: CdcTableRef[]
|
|
223
|
+
export declare function provisionTriggerCdc(run: RawSqlRunner, tables: CdcTableRef[], options?: {
|
|
224
|
+
now?: number;
|
|
225
|
+
}): Promise<ProvisionResult>;
|
|
226
|
+
export {};
|
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
* properties of `NOTIFY` shape everything below:
|
|
7
7
|
*
|
|
8
8
|
* - **8000 bytes per payload.** Presence and cursors fit with room to spare; a
|
|
9
|
-
* scene snapshot does not.
|
|
10
|
-
*
|
|
11
|
-
* `rebase.channel_messages` with a sequence
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
9
|
+
* scene snapshot does not. A message on a *retained* channel never travels
|
|
10
|
+
* here at all, whatever its size: the statement that numbers it notifies a
|
|
11
|
+
* pointer — the body is already in `rebase.channel_messages` with a sequence
|
|
12
|
+
* number, so the receiver reads it back (`ChannelHistoryStore.append`). That
|
|
13
|
+
* is the same trick the entity path uses (notify an address, refetch the
|
|
14
|
+
* row), applied to a different table, and for the same reason: any database
|
|
15
|
+
* login can `LISTEN`. On an ephemeral channel there is nothing to point at,
|
|
16
|
+
* so the frame carries the message, and an oversized one is refused loudly
|
|
17
|
+
* instead of reaching some instances and not others.
|
|
16
18
|
*
|
|
17
19
|
* - **A notify is a query on the primary database.** Not a slow one, but it
|
|
18
20
|
* competes with the application's real queries, and that — not throughput —
|
|
@@ -26,6 +28,7 @@
|
|
|
26
28
|
* than correctness. That is what makes coalescing safe.
|
|
27
29
|
*/
|
|
28
30
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
31
|
+
import { type PgNotifyListenerStatus } from "../pg-notify-listener.js";
|
|
29
32
|
import { ChannelBus, ChannelBusFrame, ChannelBusHandler } from "./ChannelBus.js";
|
|
30
33
|
/** NOTIFY channel carrying channel-bus frames. */
|
|
31
34
|
export declare const CHANNEL_BUS_NOTIFY_CHANNEL = "rebase_channel_bus";
|
|
@@ -79,6 +82,8 @@ export declare class PostgresChannelBus implements ChannelBus {
|
|
|
79
82
|
* still holds.
|
|
80
83
|
*/
|
|
81
84
|
publish(frame: ChannelBusFrame): Promise<void>;
|
|
85
|
+
/** The LISTEN connection's state, for `/health` — see {@link PgNotifyListener}. */
|
|
86
|
+
status(): PgNotifyListenerStatus | undefined;
|
|
82
87
|
stop(): Promise<void>;
|
|
83
88
|
private openWindow;
|
|
84
89
|
/** Send everything queued and settle the promises waiting on it. */
|