@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
|
@@ -29,15 +29,6 @@ export type RelationStyle = "ref" | "inline";
|
|
|
29
29
|
* to unwrap that same level back out.
|
|
30
30
|
*/
|
|
31
31
|
export declare function isJunctionRelation(relation: ResolvedRelation): boolean;
|
|
32
|
-
/**
|
|
33
|
-
* Apply {@link toRestScalar} across a row, leaving undeclared columns alone.
|
|
34
|
-
*
|
|
35
|
-
* Exported because the include loader attaches related rows itself, and a
|
|
36
|
-
* target rendered differently from its parent is the shape bug this whole file
|
|
37
|
-
* exists to prevent — a date was a string at the top level and a
|
|
38
|
-
* `{ __type: "date" }` envelope one level down, in the same response.
|
|
39
|
-
*/
|
|
40
|
-
export declare function toRestValues(row: Record<string, unknown>, collection: CollectionConfig): Record<string, unknown>;
|
|
41
32
|
/** Render one target row in the requested style. */
|
|
42
33
|
/**
|
|
43
34
|
* Drop every column this caller may not read.
|
|
@@ -69,6 +60,22 @@ export declare function toRestValues(row: Record<string, unknown>, collection: C
|
|
|
69
60
|
* though the array itself is theirs to see.
|
|
70
61
|
*/
|
|
71
62
|
export declare function stripUnreadable(row: Record<string, unknown>, collection: CollectionConfig): Record<string, unknown>;
|
|
63
|
+
/**
|
|
64
|
+
* May the current caller be handed the rows of this relation of `collection`?
|
|
65
|
+
*
|
|
66
|
+
* A relation property's value *is* its related rows, so a property the caller
|
|
67
|
+
* may not read withholds the relation whole: an `include` neither loads it nor
|
|
68
|
+
* counts it. {@link stripUnreadable} cannot be the only guard here, because the
|
|
69
|
+
* REST rows are stripped before the include loader runs (`toRestRow`, then
|
|
70
|
+
* `loadIncludes`) — the loader wrote the rows back onto the key the strip had
|
|
71
|
+
* just deleted, and `_count` beside them.
|
|
72
|
+
*
|
|
73
|
+
* Judged by every `type: "relation"` property that names this relation: by its
|
|
74
|
+
* own key (the include key of an inline relation), or by the `relationName` it
|
|
75
|
+
* addresses in the collection's `relations` array. A relation declared only in
|
|
76
|
+
* `relations` has no property, so no field rule, and stays readable.
|
|
77
|
+
*/
|
|
78
|
+
export declare function canReadRelation(collection: CollectionConfig, key: string, relation: ResolvedRelation): boolean;
|
|
72
79
|
/**
|
|
73
80
|
* The address a relation ref points at.
|
|
74
81
|
*
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Letting go of client sockets the server can no longer reach.
|
|
3
|
+
*
|
|
4
|
+
* A phone that loses signal, a laptop that sleeps, a NAT that forgets the
|
|
5
|
+
* connection: the peer is gone, and nothing tells the server. No close frame
|
|
6
|
+
* arrives and no FIN, so the socket stays open until the operating system gives
|
|
7
|
+
* up on the TCP connection — about two hours on Linux. Meanwhile every
|
|
8
|
+
* subscription it holds is refetched on every write to its collection, and
|
|
9
|
+
* every frame for it is serialised into a send buffer nobody drains.
|
|
10
|
+
*
|
|
11
|
+
* Two checks catch it. The socket must answer a ping: one that has not answered
|
|
12
|
+
* by the next is terminated. And it must read what it is sent: one whose unsent
|
|
13
|
+
* backlog passes {@link MAX_SOCKET_BUFFERED_BYTES} is terminated at the next
|
|
14
|
+
* frame for it, and when all of the process's sockets together hold more than
|
|
15
|
+
* {@link MAX_PROCESS_BUFFERED_BYTES}, the ones with the largest backlogs go
|
|
16
|
+
* first. A terminated socket closes like any other, so the realtime service
|
|
17
|
+
* drops its subscriptions, channels and presence on its `close`.
|
|
18
|
+
*/
|
|
19
|
+
import { WebSocket, type WebSocketServer } from "ws";
|
|
20
|
+
/**
|
|
21
|
+
* How often every socket is pinged. A socket that has not answered one ping by
|
|
22
|
+
* the next is terminated, so a peer that vanished is let go of within two
|
|
23
|
+
* intervals. Every browser and `ws` client answers a ping by itself.
|
|
24
|
+
*/
|
|
25
|
+
export declare const SOCKET_PING_INTERVAL_MS = 30000;
|
|
26
|
+
/**
|
|
27
|
+
* The unsent bytes a socket may have queued before it is terminated.
|
|
28
|
+
*
|
|
29
|
+
* A healthy client drains its queue as fast as the network allows; one whose
|
|
30
|
+
* backlog keeps growing is not reading, or is so far behind that what it would
|
|
31
|
+
* read is stale. Set well above a single frame — the largest body the data API
|
|
32
|
+
* accepts is 10 MiB, and a list frame can approach it — so a burst of large
|
|
33
|
+
* frames to a client that is keeping up does not trip it. A terminated client
|
|
34
|
+
* reconnects and resubscribes, which brings it up to date with one frame per
|
|
35
|
+
* subscription instead of the backlog.
|
|
36
|
+
*/
|
|
37
|
+
export declare const MAX_SOCKET_BUFFERED_BYTES: number;
|
|
38
|
+
/**
|
|
39
|
+
* The unsent bytes all of this process's sockets may hold between them before
|
|
40
|
+
* the ones with the largest backlogs are terminated.
|
|
41
|
+
*
|
|
42
|
+
* The per-socket ceiling alone bounds nothing for the process: a hundred slow
|
|
43
|
+
* phones during a bulk-update storm could each sit just under it, 1.6 GB of
|
|
44
|
+
* frames between them, before any one was let go. A healthy socket's backlog
|
|
45
|
+
* is close to nothing — the kernel takes a frame as it is written — so a
|
|
46
|
+
* process holding this much unread is already in a storm. 256 MiB is sixteen
|
|
47
|
+
* sockets at their own ceiling, and an eighth of a 2 GiB container; under a
|
|
48
|
+
* smaller memory limit it is a quarter of that limit (see
|
|
49
|
+
* {@link processSendBudget}).
|
|
50
|
+
*/
|
|
51
|
+
export declare const MAX_PROCESS_BUFFERED_BYTES: number;
|
|
52
|
+
/**
|
|
53
|
+
* The process-wide budget: {@link MAX_PROCESS_BUFFERED_BYTES}, or a quarter of
|
|
54
|
+
* the memory limit the process runs under when that is smaller — never less
|
|
55
|
+
* than one socket's own ceiling. `constrained` is the limit in bytes, 0 for
|
|
56
|
+
* none, as `process.constrainedMemory()` reports it.
|
|
57
|
+
*/
|
|
58
|
+
export declare function processSendBudget(constrained?: number): number;
|
|
59
|
+
/**
|
|
60
|
+
* Count the sockets of `wss` in the process-wide send budget. Returns the
|
|
61
|
+
* function that stops counting them, for when the server closes.
|
|
62
|
+
*/
|
|
63
|
+
export declare function budgetSendBuffers(wss: WebSocketServer): () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Terminate sockets, largest backlog first, until what this process's sockets
|
|
66
|
+
* hold unread between them is back within `budget`. Returns the sockets it
|
|
67
|
+
* terminated. A terminated socket closes like any other, and its client
|
|
68
|
+
* reconnects and re-subscribes: one fresh frame per subscription, instead of
|
|
69
|
+
* the backlog.
|
|
70
|
+
*/
|
|
71
|
+
export declare function enforceProcessSendBudget(budget?: number): WebSocket[];
|
|
72
|
+
/**
|
|
73
|
+
* Ping every socket of `wss` each `intervalMs`, and terminate the ones that did
|
|
74
|
+
* not answer the previous ping. Returns the function that stops it.
|
|
75
|
+
*/
|
|
76
|
+
export declare function reapSilentSockets(wss: WebSocketServer, intervalMs?: number): () => void;
|
|
77
|
+
/**
|
|
78
|
+
* Terminate `ws` if its unsent backlog has passed {@link MAX_SOCKET_BUFFERED_BYTES}.
|
|
79
|
+
* Returns whether it did; the caller then sends nothing.
|
|
80
|
+
*
|
|
81
|
+
* Every frame in and out of a socket comes through here, so this is also where
|
|
82
|
+
* the process-wide budget is kept, summed at most every
|
|
83
|
+
* {@link PROCESS_BUDGET_CHECK_MS} — see {@link enforceProcessSendBudget}.
|
|
84
|
+
*/
|
|
85
|
+
export declare function terminateIfBacklogged(ws: WebSocket, clientId: string): boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SQL } from "drizzle-orm";
|
|
2
2
|
import type { AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
|
|
3
3
|
import type { CollectionConfig } from "@rebasepro/types";
|
|
4
|
+
import { ApiError } from "@rebasepro/server";
|
|
4
5
|
/**
|
|
5
6
|
* Soft delete, in one place.
|
|
6
7
|
*
|
|
@@ -69,3 +70,14 @@ export declare function withSoftDelete(conditions: SQL[], collection: Collection
|
|
|
69
70
|
* For the call sites that hold one composed condition rather than a list.
|
|
70
71
|
*/
|
|
71
72
|
export declare function andSoftDelete(where: SQL | undefined, collection: CollectionConfig | undefined, table: PgTable, withDeleted?: WithDeleted): SQL | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* The refusal of an upsert whose key belongs to a row in the trash.
|
|
75
|
+
*
|
|
76
|
+
* The key is taken — the stamped row still holds it — so the write cannot be a
|
|
77
|
+
* create, and an upsert is not the operation that brings a deleted row back:
|
|
78
|
+
* that is the restore, an update setting the field to `null`, which goes
|
|
79
|
+
* through the update gate and is what history records as one. Left to the
|
|
80
|
+
* statement, `INSERT … ON CONFLICT DO UPDATE` wrote the new values into the
|
|
81
|
+
* hidden row and the caller was told "created", with the row still in the trash.
|
|
82
|
+
*/
|
|
83
|
+
export declare function rowInTrashError(path: string, key: string, softDelete: SoftDeleteField): ApiError;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { type Connection, type Pool, type PoolClient, type Submittable } from "pg";
|
|
2
|
+
import type { SqlCursor, SqlScriptResult } from "@rebasepro/types";
|
|
3
|
+
/**
|
|
4
|
+
* Running a SQL script a person wrote — the Studio console — and saying what
|
|
5
|
+
* came back, column by column.
|
|
6
|
+
*
|
|
7
|
+
* The console used to receive bare rows and guess, from the query's text, which
|
|
8
|
+
* table a cell belonged to and which column held its key. The guess wrote
|
|
9
|
+
* `UPDATE authors SET name = … WHERE id = <the post's id>` for a cell of
|
|
10
|
+
* `SELECT p.id, a.name FROM posts p JOIN authors a …`, and `UPDATE users SET
|
|
11
|
+
* name = …` for `lower(email) AS name`. The database knows the answer: every
|
|
12
|
+
* column of a result's row description carries the table and column it was
|
|
13
|
+
* read from, or zero when it was computed. This reads it off the wire.
|
|
14
|
+
*
|
|
15
|
+
* Values come back as the text the database wrote, never parsed. Parsed, a
|
|
16
|
+
* `text[]` read as a JavaScript array and went back as JSON — a malformed array
|
|
17
|
+
* literal; a `bytea` as a serialized Buffer; a `timestamp` as a Date shifted by
|
|
18
|
+
* the server's timezone; a `numeric` as a float. Text is what the database
|
|
19
|
+
* reads back exactly.
|
|
20
|
+
*/
|
|
21
|
+
/** The parts of a `RowDescription` field this reads. */
|
|
22
|
+
interface WireField {
|
|
23
|
+
name: string;
|
|
24
|
+
/** The OID of the table the column was read from; 0 when it was computed. */
|
|
25
|
+
tableID: number;
|
|
26
|
+
/** Its attribute number in that table; 0 when it was computed. */
|
|
27
|
+
columnID: number;
|
|
28
|
+
dataTypeID: number;
|
|
29
|
+
}
|
|
30
|
+
/** One statement of the script, as it came back. */
|
|
31
|
+
export interface StatementOutcome {
|
|
32
|
+
fields: WireField[];
|
|
33
|
+
rows: Record<string, string | null>[];
|
|
34
|
+
/** How many rows the database sent for it, kept or not. */
|
|
35
|
+
received: number;
|
|
36
|
+
/** Rows past the {@link SqlResultLimits} were counted and not kept. */
|
|
37
|
+
truncated: boolean;
|
|
38
|
+
/** The command tag, e.g. `SELECT 3`, `INSERT 0 1`, `CREATE TABLE`. */
|
|
39
|
+
tag?: string;
|
|
40
|
+
}
|
|
41
|
+
/** How much of one statement's result a run keeps. */
|
|
42
|
+
export interface SqlResultLimits {
|
|
43
|
+
/** Rows kept; the rest are counted and let go. */
|
|
44
|
+
maxRows: number;
|
|
45
|
+
/** Characters of values kept, summed over the kept rows. */
|
|
46
|
+
maxTextLength: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* What the Studio console keeps of a result: its first 10,000 rows, or its
|
|
50
|
+
* first 32 Mi characters of values, whichever ends first.
|
|
51
|
+
*
|
|
52
|
+
* Every row used to be kept. `TABLE events` on a million rows held 418 MB of
|
|
53
|
+
* row objects and built a 475 MB frame, and a server with 1 GiB died of it —
|
|
54
|
+
* every other user's connection with it. Ten times the console's own
|
|
55
|
+
* `LIMIT 1000`, so a run that asked for a page always gets it whole; the text
|
|
56
|
+
* budget is for the rows a count does not bound — a document column, a
|
|
57
|
+
* `jsonb` blob — and keeps the frame, the string it is built from and the
|
|
58
|
+
* socket's copy of it around 100 MB at worst.
|
|
59
|
+
*
|
|
60
|
+
* Postgres still sends every row (a simple query cannot be stopped short of
|
|
61
|
+
* cancelling it, which would abort the statements after it); they are counted,
|
|
62
|
+
* so the result says how many there were.
|
|
63
|
+
*/
|
|
64
|
+
export declare const SQL_CONSOLE_RESULT_LIMITS: SqlResultLimits;
|
|
65
|
+
/**
|
|
66
|
+
* The script, sent as one simple query and read message by message.
|
|
67
|
+
*
|
|
68
|
+
* A `Submittable` rather than `client.query(text)`: node-postgres parses every
|
|
69
|
+
* value through its type parsers and drops each field's origin, and for a
|
|
70
|
+
* multi-statement script returns an array the driver's callers took for a
|
|
71
|
+
* result with no rows. The message handlers below are the ones node-postgres
|
|
72
|
+
* calls on the active query — the same protocol `pg-cursor` implements.
|
|
73
|
+
*/
|
|
74
|
+
export declare class ScriptRun implements Submittable {
|
|
75
|
+
private readonly text;
|
|
76
|
+
private readonly limits;
|
|
77
|
+
/**
|
|
78
|
+
* Set by node-postgres when the pool has a `query_timeout`: a wrapper that
|
|
79
|
+
* clears its read timer. Called on every outcome, so the timer never
|
|
80
|
+
* outlives the run.
|
|
81
|
+
*/
|
|
82
|
+
callback?: (error: Error | null) => void;
|
|
83
|
+
/**
|
|
84
|
+
* Every statement of the script, as it came back. Only the last holds its
|
|
85
|
+
* rows: the console shows the last, and a statement's rows are let go when
|
|
86
|
+
* the next one begins — `SELECT * FROM big; SELECT 1` held every row of
|
|
87
|
+
* `big` to the end, to show one.
|
|
88
|
+
*/
|
|
89
|
+
readonly statements: StatementOutcome[];
|
|
90
|
+
readonly done: Promise<StatementOutcome[]>;
|
|
91
|
+
/** The statement whose row description arrived and whose command has not completed. */
|
|
92
|
+
private open?;
|
|
93
|
+
/** What {@link open} has kept of its rows. */
|
|
94
|
+
private budget;
|
|
95
|
+
private settled;
|
|
96
|
+
private resolveRun;
|
|
97
|
+
private rejectRun;
|
|
98
|
+
constructor(text: string, limits?: SqlResultLimits);
|
|
99
|
+
submit(connection: Connection): void;
|
|
100
|
+
handleRowDescription(message: {
|
|
101
|
+
fields: WireField[];
|
|
102
|
+
}): void;
|
|
103
|
+
handleDataRow(message: {
|
|
104
|
+
fields: (string | null)[];
|
|
105
|
+
}): void;
|
|
106
|
+
handleCommandComplete(message: {
|
|
107
|
+
text: string;
|
|
108
|
+
}): void;
|
|
109
|
+
handleEmptyQuery(): void;
|
|
110
|
+
handlePortalSuspended(): void;
|
|
111
|
+
/**
|
|
112
|
+
* `COPY … FROM STDIN` waits for data the console has no way to send. Fail
|
|
113
|
+
* the copy rather than leave the session waiting for it.
|
|
114
|
+
*/
|
|
115
|
+
handleCopyInResponse(connection: Connection): void;
|
|
116
|
+
handleCopyData(): void;
|
|
117
|
+
handleError(error: Error): void;
|
|
118
|
+
handleReadyForQuery(): void;
|
|
119
|
+
/** Let go of the rows of the statement before the one beginning: only the last is shown. */
|
|
120
|
+
private releaseRows;
|
|
121
|
+
private settle;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* `INSERT 0 1` → `INSERT`, 1. `CREATE TABLE` → `CREATE TABLE`, no count.
|
|
125
|
+
*
|
|
126
|
+
* Only the commands whose tag ends in a count have one; the numbers are
|
|
127
|
+
* stripped from the end of every other tag, of which there are none.
|
|
128
|
+
*/
|
|
129
|
+
export declare function parseCommandTag(tag: string | undefined): {
|
|
130
|
+
command?: string;
|
|
131
|
+
rowCount?: number;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* What puts a session back after caller SQL: the session user and role, then
|
|
135
|
+
* every setting. Two statements, because the extended protocol refuses a
|
|
136
|
+
* multi-command string. Pinned settings (`search_path` from the connection's
|
|
137
|
+
* startup options) come back as they were pinned.
|
|
138
|
+
*/
|
|
139
|
+
export declare const RESET_SESSION_STATEMENTS: readonly ["SET SESSION AUTHORIZATION DEFAULT", "RESET ALL"];
|
|
140
|
+
/**
|
|
141
|
+
* A connection's transaction state, as its last ReadyForQuery reported it:
|
|
142
|
+
* `I` idle, `T` in a transaction, `E` in a failed one. `null` when the client
|
|
143
|
+
* does not say (node-postgres has since 8.16; the package requires 8.22).
|
|
144
|
+
*/
|
|
145
|
+
export declare function transactionStatus(connection: object): string | null;
|
|
146
|
+
/**
|
|
147
|
+
* SQL a person ran began a transaction and did not end it.
|
|
148
|
+
*
|
|
149
|
+
* Each run is a session of its own, so a transaction cannot outlive the run
|
|
150
|
+
* that began it: a `BEGIN` alone, then an `UPDATE` in the next run, then a
|
|
151
|
+
* `ROLLBACK` in a third, committed the UPDATE and reported three successes.
|
|
152
|
+
* Refused instead, with the transaction rolled back.
|
|
153
|
+
*/
|
|
154
|
+
export declare class SqlTransactionLeftOpenError extends Error {
|
|
155
|
+
readonly code = "SQL_TRANSACTION_LEFT_OPEN";
|
|
156
|
+
constructor();
|
|
157
|
+
}
|
|
158
|
+
export interface OwnSessionOptions {
|
|
159
|
+
/**
|
|
160
|
+
* Put the session in the role the SQL runs as. Called once, before it, at
|
|
161
|
+
* session level — so the role holds for every statement, a `COMMIT` in the
|
|
162
|
+
* middle included. `SET LOCAL ROLE` inside a wrapping transaction did not:
|
|
163
|
+
* the caller's `COMMIT` ended that transaction, and every statement after
|
|
164
|
+
* it ran as the connection owner.
|
|
165
|
+
*/
|
|
166
|
+
assumeRole?: (connection: PoolClient) => Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* Cancels the run when aborted: the statement in progress is stopped with
|
|
169
|
+
* `pg_cancel_backend`, and the run fails with the database's "canceling
|
|
170
|
+
* statement due to user request". A signal already aborted runs nothing.
|
|
171
|
+
*/
|
|
172
|
+
signal?: AbortSignal;
|
|
173
|
+
}
|
|
174
|
+
/** A run whose signal was aborted before it began. */
|
|
175
|
+
export declare class SqlRunCancelledError extends Error {
|
|
176
|
+
readonly code = "SQL_RUN_CANCELLED";
|
|
177
|
+
constructor();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Run `work` — SQL a person wrote — on a connection of its own from `pool`,
|
|
181
|
+
* and put the connection back the way it was handed out.
|
|
182
|
+
*
|
|
183
|
+
* A transaction the SQL left open is rolled back, and the run refused with
|
|
184
|
+
* {@link SqlTransactionLeftOpenError}; a run that failed with one open says
|
|
185
|
+
* that it was rolled back. The session is then reset — role, session
|
|
186
|
+
* authorization, settings — and destroyed instead when it cannot be. Last,
|
|
187
|
+
* `afterReset` reads what it needs on the clean session, as the owner.
|
|
188
|
+
*/
|
|
189
|
+
export declare function onOwnSession<T>(pool: Pool, options: OwnSessionOptions, work: (connection: PoolClient) => Promise<T>, afterReset?: (connection: PoolClient, value: T) => Promise<T>): Promise<T>;
|
|
190
|
+
/**
|
|
191
|
+
* Run `sql` on a connection of its own from `pool` and return what the last
|
|
192
|
+
* statement returned, with each column's provenance and what the database
|
|
193
|
+
* said along the way — `there is no transaction in progress` for a ROLLBACK
|
|
194
|
+
* with nothing to roll back, which used to read as success.
|
|
195
|
+
*/
|
|
196
|
+
export declare function runSqlScriptOnPool(pool: Pool, sql: string, options?: OwnSessionOptions): Promise<SqlScriptResult>;
|
|
197
|
+
/**
|
|
198
|
+
* The same shape for rows that came back parsed, from a handle that is not a
|
|
199
|
+
* pool (PGlite in process): every value as text, and no column with a source —
|
|
200
|
+
* nothing to say where a value came from, so nothing the console will edit.
|
|
201
|
+
*/
|
|
202
|
+
export declare function sqlScriptResultFromRows(rows: Record<string, unknown>[], limits?: SqlResultLimits): SqlScriptResult;
|
|
203
|
+
/**
|
|
204
|
+
* How many bytes of rows one answer of the admin `executeSql` door — the
|
|
205
|
+
* WebSocket `EXECUTE_SQL` without `mode: "script"` — may carry: 128 MiB.
|
|
206
|
+
*
|
|
207
|
+
* That door had no limit at all. A million-row `SELECT` held every row parsed
|
|
208
|
+
* (432 MB of heap on the console's door before it was capped), then built the
|
|
209
|
+
* frame from them, and a server with 1 GiB died of it — with every other
|
|
210
|
+
* user's connection. Unlike the console's script door it does not keep a
|
|
211
|
+
* prefix: a program reading rows through it would take a prefix for the whole
|
|
212
|
+
* answer, so past the budget the answer is refused, {@link SqlResultTooLargeError}.
|
|
213
|
+
*
|
|
214
|
+
* Generous on purpose. The catalogue reads the admin UI makes through this door
|
|
215
|
+
* are far below it — the SQL console's schema browser reads one
|
|
216
|
+
* `information_schema.columns` row per column, 7 MB for the 100,000 columns of
|
|
217
|
+
* 5,000 tables — and anything near it is a result no browser tab should hold.
|
|
218
|
+
*/
|
|
219
|
+
export declare const SQL_RESULT_BYTE_BUDGET: number;
|
|
220
|
+
/**
|
|
221
|
+
* A result larger than the bytes its caller allowed, refused rather than
|
|
222
|
+
* returned in part. Its message is for the person who wrote the SQL.
|
|
223
|
+
*/
|
|
224
|
+
export declare class SqlResultTooLargeError extends Error {
|
|
225
|
+
readonly limitBytes: number;
|
|
226
|
+
readonly code = "RESULT_TOO_LARGE";
|
|
227
|
+
constructor(limitBytes: number);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Refuse rows that came back whole and are past `maxBytes`.
|
|
231
|
+
*
|
|
232
|
+
* Where nothing could watch the read, it is measured once done — and the
|
|
233
|
+
* answer still refused rather than sent.
|
|
234
|
+
*/
|
|
235
|
+
export declare function refuseOversizedRows<T extends Record<string, unknown>>(rows: T[], maxBytes: number): T[];
|
|
236
|
+
/**
|
|
237
|
+
* Run `work` — one query on `connection` — and stop reading once its rows pass
|
|
238
|
+
* `maxBytes`.
|
|
239
|
+
*
|
|
240
|
+
* node-postgres keeps every row of a query until the query completes, and a
|
|
241
|
+
* simple query cannot be stopped short of its end, so the only way to stop
|
|
242
|
+
* holding rows is to stop receiving them: the socket is closed. Postgres sees
|
|
243
|
+
* its client gone and abandons the statement; the session — and anything it
|
|
244
|
+
* held — goes with it, and `onOwnSession` destroys the connection rather than
|
|
245
|
+
* return it to the pool. What the caller gets is {@link SqlResultTooLargeError},
|
|
246
|
+
* never the rows read so far.
|
|
247
|
+
*
|
|
248
|
+
* Counted from the wire, message by message, as they arrive: what is held when
|
|
249
|
+
* it stops is the budget plus one network read.
|
|
250
|
+
*/
|
|
251
|
+
export declare function readWithinBudget<T extends Record<string, unknown>>(connection: PoolClient, maxBytes: number, work: () => Promise<T[]>): Promise<T[]>;
|
|
252
|
+
/** Rows one read of a {@link SqlCursor} fetches. */
|
|
253
|
+
export declare const SQL_CURSOR_BATCH_ROWS = 1000;
|
|
254
|
+
/**
|
|
255
|
+
* Open a cursor over ONE query a person wrote — the SQL console's "Download
|
|
256
|
+
* full result as CSV".
|
|
257
|
+
*
|
|
258
|
+
* On a connection of its own, in the role asked for, inside
|
|
259
|
+
* `BEGIN TRANSACTION READ ONLY`: the database refuses a write, a
|
|
260
|
+
* data-modifying `WITH` and a function that writes. The query is declared over
|
|
261
|
+
* the extended protocol, which refuses a second statement — `SELECT 1; DROP
|
|
262
|
+
* TABLE t` cannot ride along. Rows are then fetched {@link SQL_CURSOR_BATCH_ROWS}
|
|
263
|
+
* at a time, each `FETCH` under the session's `statement_timeout`; nothing
|
|
264
|
+
* fetches ahead of the reader, so memory holds one batch. The first batch is
|
|
265
|
+
* read here, so a query that fails on its first rows fails before anything has
|
|
266
|
+
* been sent.
|
|
267
|
+
*
|
|
268
|
+
* The caller closes the cursor — always. A reader that stops reading without
|
|
269
|
+
* closing is ended by `idle_in_transaction_session_timeout` after
|
|
270
|
+
* {@link SQL_CURSOR_IDLE_TIMEOUT_MS}.
|
|
271
|
+
*/
|
|
272
|
+
export declare function openSqlCursorOnPool(pool: Pool, sql: string, options?: OwnSessionOptions & {
|
|
273
|
+
batchRows?: number;
|
|
274
|
+
}): Promise<SqlCursor>;
|
|
275
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DrizzleClient } from "../interfaces.js";
|
|
2
|
+
/**
|
|
3
|
+
* pgvector's iterative index scans, for a vector read that also filters.
|
|
4
|
+
*
|
|
5
|
+
* An HNSW index answers a nearest-neighbour query with the `hnsw.ef_search`
|
|
6
|
+
* (40 by default) candidates it found, and every other condition of the query
|
|
7
|
+
* — a `where`, a search, a soft-delete stamp, a row-level-security policy — is
|
|
8
|
+
* applied to those candidates afterwards. Where few of them pass, the page
|
|
9
|
+
* comes back short, or empty, though plenty of rows match: measured on 20,000
|
|
10
|
+
* rows with half of them matching a `status` filter far from the query vector,
|
|
11
|
+
* `LIMIT 10` served 0 rows. Offset pages over the index were empty or short the
|
|
12
|
+
* same way, while deeper pages, where the planner switched to an exact scan,
|
|
13
|
+
* served rows, so a walk skipped some and could repeat others.
|
|
14
|
+
*
|
|
15
|
+
* From pgvector 0.8.0, `hnsw.iterative_scan` makes the index keep scanning
|
|
16
|
+
* until the query has its rows (bounded by `hnsw.max_scan_tuples`, 20,000 by
|
|
17
|
+
* default). `strict_order` and not `relaxed_order`: the planner reads the
|
|
18
|
+
* index's output as already sorted by distance, so out-of-order rows would be
|
|
19
|
+
* cut at the wrong place by `LIMIT` and the same row could land on two pages.
|
|
20
|
+
* Measured on the case above: 0/10 → 10/10, and the first 200 rows by offset
|
|
21
|
+
* pages of 10 with no duplicates (`relaxed_order`: 18).
|
|
22
|
+
*
|
|
23
|
+
* Set with `set_config(…, true)` — local to the transaction the read runs in,
|
|
24
|
+
* so it never outlives it on a pooled connection. A read not in a transaction
|
|
25
|
+
* is given one.
|
|
26
|
+
*
|
|
27
|
+
* pgvector before 0.8.0 has no such setting, and with its library loaded it
|
|
28
|
+
* refuses an unknown `hnsw.` parameter, so the setting is applied only where
|
|
29
|
+
* the extension's version has it; there a filtered vector read keeps the
|
|
30
|
+
* behaviour above. IVFFlat indexes are not touched: their iterative scan only
|
|
31
|
+
* comes in relaxed order.
|
|
32
|
+
*/
|
|
33
|
+
/** Whether a pgvector version (`extversion`, e.g. `"0.8.1"`) has iterative index scans. */
|
|
34
|
+
export declare function hasIterativeIndexScans(version: string | undefined | null): boolean;
|
|
35
|
+
/** Whether `db`'s pgvector has iterative index scans. Asked once per `key`. */
|
|
36
|
+
export declare function iterativeScansAvailable(db: DrizzleClient, key: object): Promise<boolean>;
|
|
37
|
+
/** Whether `db` is a transaction, in which a `set_config(…, true)` stays. */
|
|
38
|
+
export declare function inTransaction(db: DrizzleClient): boolean;
|
|
39
|
+
/** Turn on strict-order iterative HNSW scans for the rest of `db`'s transaction. */
|
|
40
|
+
export declare function enableIterativeScan(db: DrizzleClient): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep enough for any chain that ends (a hook writing another collection whose
|
|
3
|
+
* hook writes a third is depth 3), shallow enough that a loop stops at once.
|
|
4
|
+
*/
|
|
5
|
+
export declare const MAX_WRITE_DEPTH = 16;
|
|
6
|
+
/** One write in progress, and which of its hooks is running. */
|
|
7
|
+
export interface WriteFrame {
|
|
8
|
+
readonly path: string;
|
|
9
|
+
readonly operation: "save" | "delete";
|
|
10
|
+
readonly depth: number;
|
|
11
|
+
readonly parent: WriteFrame | undefined;
|
|
12
|
+
/** The hook running right now — what a nested write was started from. */
|
|
13
|
+
stage: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
/** Run one write as a frame, refusing it when the chain is already too deep. */
|
|
16
|
+
export declare function inWriteFrame<T>(path: string, operation: "save" | "delete", fn: (frame: WriteFrame) => Promise<T>): Promise<T>;
|
|
17
|
+
/**
|
|
18
|
+
* How deep in hook-started writes the code running now is: 0 outside any
|
|
19
|
+
* write, 1 inside a write a caller started, 2 or more inside a write a hook of
|
|
20
|
+
* another write started (`context.data`).
|
|
21
|
+
*/
|
|
22
|
+
export declare function currentWriteDepth(): number;
|
|
@@ -28,6 +28,8 @@ export declare class WriteTransactionScope implements AmbientTransaction {
|
|
|
28
28
|
/** The first tracked statement that failed, whether or not its caller caught it. */
|
|
29
29
|
private failure?;
|
|
30
30
|
private readonly settledHooks;
|
|
31
|
+
/** Whether application code (a hook) has run on this transaction. See {@link noteApplicationCode}. */
|
|
32
|
+
private applicationCodeRan;
|
|
31
33
|
private settledContext?;
|
|
32
34
|
/** The transaction handle, for statements in this package that are already written against drizzle. */
|
|
33
35
|
tx?: DrizzleClient;
|
|
@@ -66,11 +68,21 @@ export declare class WriteTransactionScope implements AmbientTransaction {
|
|
|
66
68
|
*
|
|
67
69
|
* So one statement is asked of the transaction before its commit. On an
|
|
68
70
|
* aborted one it fails with `25P02`, and the write is refused and rolled
|
|
69
|
-
* back instead.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
71
|
+
* back instead. The database refusing a `context.data` create, update or
|
|
72
|
+
* delete is not caught by this: each of those statements runs in a
|
|
73
|
+
* savepoint, is undone on its own, and leaves the transaction usable —
|
|
74
|
+
* which is what makes catching one safe. (Deletes ran without one, so a
|
|
75
|
+
* caught refused delete landed here, under a message saying catching it
|
|
76
|
+
* was safe.)
|
|
72
77
|
*/
|
|
73
78
|
assertCommittable(): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Application code — a collection, global or property hook, a
|
|
81
|
+
* `beforeQuery` — is about to run on this transaction, so it may catch a
|
|
82
|
+
* failed statement and leave the transaction aborted: the commit then has
|
|
83
|
+
* to ask ({@link assertCommittable}).
|
|
84
|
+
*/
|
|
85
|
+
noteApplicationCode(): void;
|
|
74
86
|
/**
|
|
75
87
|
* What a hook deferred with {@link afterSettled} is handed as `context`:
|
|
76
88
|
* the same caller, on a driver where every call is a transaction of its
|