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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/dist/{BranchService-DGPL6G_C.js → BranchService-Bsn2wfaj.js} +36 -18
  2. package/dist/BranchService-Bsn2wfaj.js.map +1 -0
  3. package/dist/PostgresBackendDriver.d.ts +166 -13
  4. package/dist/auth/ensure-tables.d.ts +12 -0
  5. package/dist/auth/services.d.ts +78 -14
  6. package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
  7. package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
  8. package/dist/backup/backup-cli.d.ts +14 -0
  9. package/dist/backup/backup-service.d.ts +2 -2
  10. package/dist/backup/pg-binary.d.ts +5 -0
  11. package/dist/{backup-cli-24v4OlSp.js → backup-cli-aUfRv16h.js} +37 -29
  12. package/dist/backup-cli-aUfRv16h.js.map +1 -0
  13. package/dist/{backup-service-HQ9GC4tN.js → backup-service-BTBB7RXn.js} +60 -28
  14. package/dist/backup-service-BTBB7RXn.js.map +1 -0
  15. package/dist/boot-ddl-DVpsFN6r.js +105 -0
  16. package/dist/boot-ddl-DVpsFN6r.js.map +1 -0
  17. package/dist/cli-errors-CaI4ntyD.js +1919 -0
  18. package/dist/cli-errors-CaI4ntyD.js.map +1 -0
  19. package/dist/cli-errors.d.ts +93 -5
  20. package/dist/{cli-flags-BglvpjHv.js → cli-flags-B-Dawocl.js} +5 -2
  21. package/dist/cli-flags-B-Dawocl.js.map +1 -0
  22. package/dist/cli-helpers.d.ts +52 -12
  23. package/dist/cli-output.d.ts +16 -28
  24. package/dist/cli-push.d.ts +87 -0
  25. package/dist/cli.js +625 -190
  26. package/dist/cli.js.map +1 -1
  27. package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-drZIsSNf.js} +42 -26
  28. package/dist/column-plan-helpers-drZIsSNf.js.map +1 -0
  29. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-BPoNILnD.js} +19 -110
  30. package/dist/connection-BPoNILnD.js.map +1 -0
  31. package/dist/connection.d.ts +9 -1
  32. package/dist/data-transformer.d.ts +0 -8
  33. package/dist/databasePoolManager-BkEy2w18.js +119 -0
  34. package/dist/databasePoolManager-BkEy2w18.js.map +1 -0
  35. package/dist/deferred-index-build-DP1_SNFo.js +1051 -0
  36. package/dist/deferred-index-build-DP1_SNFo.js.map +1 -0
  37. package/dist/{doctor-Cb2thZ8s.js → doctor-C4WiftHm.js} +279 -87
  38. package/dist/doctor-C4WiftHm.js.map +1 -0
  39. package/dist/ensure-collection-policies-CXpbeNag.js +541 -0
  40. package/dist/ensure-collection-policies-CXpbeNag.js.map +1 -0
  41. package/dist/ensure-collection-tables-BhsU6qUE.js +532 -0
  42. package/dist/ensure-collection-tables-BhsU6qUE.js.map +1 -0
  43. package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-CHsgyLqg.js} +178 -75
  44. package/dist/ensure-tables-CHsgyLqg.js.map +1 -0
  45. package/dist/enum-labels-kept-D8fI1MoX.js +56 -0
  46. package/dist/enum-labels-kept-D8fI1MoX.js.map +1 -0
  47. package/dist/generate-drizzle-schema-Bz52jH2h.js +57 -0
  48. package/dist/generate-drizzle-schema-Bz52jH2h.js.map +1 -0
  49. package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-B74WN37S.js} +28 -10
  50. package/dist/generate-drizzle-schema-logic-B74WN37S.js.map +1 -0
  51. package/dist/generate-files-CdEckX6c.js +199 -0
  52. package/dist/generate-files-CdEckX6c.js.map +1 -0
  53. package/dist/generate-postgres-ddl-logic-B64O2oZF.js +152 -0
  54. package/dist/generate-postgres-ddl-logic-B64O2oZF.js.map +1 -0
  55. package/dist/generated-sql.d.ts +34 -0
  56. package/dist/history/HistoryService.d.ts +38 -6
  57. package/dist/history/ensure-history-table.d.ts +42 -4
  58. package/dist/index.es.js +5557 -7612
  59. package/dist/index.es.js.map +1 -1
  60. package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-C-Di8dA9.js} +562 -350
  61. package/dist/introspect-db-logic-C-Di8dA9.js.map +1 -0
  62. package/dist/introspect-db-queries-D6kAqReI.js +339 -0
  63. package/dist/introspect-db-queries-D6kAqReI.js.map +1 -0
  64. package/dist/pg-error-utils-vJezlXdI.js +388 -0
  65. package/dist/pg-error-utils-vJezlXdI.js.map +1 -0
  66. package/dist/{plan-schema-C0fxM8dY.js → plan-schema-B1YF_tYq.js} +278 -533
  67. package/dist/plan-schema-B1YF_tYq.js.map +1 -0
  68. package/dist/{policy-drift-DljYdrpW.js → policy-drift-Dm8i-s1J.js} +24 -7
  69. package/dist/policy-drift-Dm8i-s1J.js.map +1 -0
  70. package/dist/push-index-prebuild-Db4AQF4i.js +45 -0
  71. package/dist/push-index-prebuild-Db4AQF4i.js.map +1 -0
  72. package/dist/push-legacy-renames-BK6ilQq2.js +34 -0
  73. package/dist/push-legacy-renames-BK6ilQq2.js.map +1 -0
  74. package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CtC3y9Ol.js} +17 -151
  75. package/dist/render-ddl-CtC3y9Ol.js.map +1 -0
  76. package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-RQUljnJ1.js} +125 -1046
  77. package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +1 -0
  78. package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-DvzqlaL1.js} +137 -17
  79. package/dist/rls-enforcement-DvzqlaL1.js.map +1 -0
  80. package/dist/schema/auth-schema.d.ts +172 -2
  81. package/dist/schema/boot-ddl.d.ts +47 -0
  82. package/dist/schema/classify-change.d.ts +29 -1
  83. package/dist/schema/collection-index.d.ts +45 -0
  84. package/dist/schema/column-plan-helpers.d.ts +38 -21
  85. package/dist/schema/deferred-index-build.d.ts +131 -0
  86. package/dist/schema/destructive-sql.d.ts +27 -1
  87. package/dist/schema/doctor-cli.js +110 -6
  88. package/dist/schema/doctor-cli.js.map +1 -1
  89. package/dist/schema/doctor-search-scale.d.ts +45 -0
  90. package/dist/schema/doctor.d.ts +53 -1
  91. package/dist/schema/ensure-collection-policies.d.ts +95 -35
  92. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  93. package/dist/schema/enum-labels-kept.d.ts +26 -0
  94. package/dist/schema/generate-drizzle-schema.js +1 -1
  95. package/dist/schema/generate-files.d.ts +44 -0
  96. package/dist/schema/generate-next-step.d.ts +0 -13
  97. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
  98. package/dist/schema/generate-postgres-ddl.js +10 -81
  99. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  100. package/dist/schema/generate-schema-commit.d.ts +12 -0
  101. package/dist/schema/introspect-db-logic.d.ts +43 -0
  102. package/dist/schema/introspect-db-queries.d.ts +12 -2
  103. package/dist/schema/introspect-db-search.d.ts +22 -0
  104. package/dist/schema/introspect-db-storage.d.ts +83 -0
  105. package/dist/schema/introspect-db-structure.d.ts +10 -0
  106. package/dist/schema/introspect-db.js +17 -6
  107. package/dist/schema/introspect-db.js.map +1 -1
  108. package/dist/schema/migration-post-process.d.ts +34 -0
  109. package/dist/schema/plan/diff-plan.d.ts +127 -1
  110. package/dist/schema/plan/plan-schema.d.ts +16 -8
  111. package/dist/schema/plan/render-ddl.d.ts +6 -0
  112. package/dist/schema/plan/types.d.ts +45 -11
  113. package/dist/schema/push-index-prebuild.d.ts +25 -0
  114. package/dist/schema/push-legacy-renames.d.ts +20 -0
  115. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  116. package/dist/search-column-BM-GV6vH.js +442 -0
  117. package/dist/search-column-BM-GV6vH.js.map +1 -0
  118. package/dist/security/policy-drift.d.ts +1 -1
  119. package/dist/security/rls-enforcement.d.ts +39 -4
  120. package/dist/services/BranchService.d.ts +22 -1
  121. package/dist/services/FetchService.d.ts +63 -12
  122. package/dist/services/PersistService.d.ts +15 -0
  123. package/dist/services/RelationService.d.ts +106 -3
  124. package/dist/services/cdc/CdcListener.d.ts +26 -10
  125. package/dist/services/cdc/identity-columns.d.ts +19 -0
  126. package/dist/services/cdc/trigger-cdc.d.ts +180 -18
  127. package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
  128. package/dist/services/channel-history.d.ts +32 -5
  129. package/dist/services/collection-helpers.d.ts +26 -1
  130. package/dist/services/field-viewer.d.ts +15 -1
  131. package/dist/services/pg-notify-listener.d.ts +95 -6
  132. package/dist/services/realtimeService.d.ts +467 -53
  133. package/dist/services/row-pipeline.d.ts +16 -9
  134. package/dist/services/socket-liveness.d.ts +85 -0
  135. package/dist/services/soft-delete.d.ts +12 -0
  136. package/dist/services/sql-script.d.ts +275 -0
  137. package/dist/services/vector-iterative-scan.d.ts +40 -0
  138. package/dist/services/write-depth.d.ts +22 -0
  139. package/dist/services/write-transaction-scope.d.ts +15 -3
  140. package/dist/socket-liveness-D3UD5_BW.js +7570 -0
  141. package/dist/socket-liveness-D3UD5_BW.js.map +1 -0
  142. package/dist/socket-opt-ins.d.ts +64 -0
  143. package/dist/source-DBDbBTL4.js +811 -0
  144. package/dist/source-DBDbBTL4.js.map +1 -0
  145. package/dist/tsx-loader.d.ts +14 -0
  146. package/dist/utils/drizzle-conditions.d.ts +63 -3
  147. package/dist/utils/error-cause.d.ts +7 -0
  148. package/dist/utils/pg-error-utils.d.ts +2 -4
  149. package/dist/utils/result-rows.d.ts +10 -0
  150. package/dist/websocket.d.ts +63 -18
  151. package/package.json +7 -7
  152. package/dist/BranchService-DGPL6G_C.js.map +0 -1
  153. package/dist/backup-cli-24v4OlSp.js.map +0 -1
  154. package/dist/backup-service-HQ9GC4tN.js.map +0 -1
  155. package/dist/cli-errors-B8qHg02P.js +0 -798
  156. package/dist/cli-errors-B8qHg02P.js.map +0 -1
  157. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  158. package/dist/cli-output-CNdMql-L.js +0 -47
  159. package/dist/cli-output-CNdMql-L.js.map +0 -1
  160. package/dist/collection-index-DdnsxiJ_.js +0 -286
  161. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  162. package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
  163. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  164. package/dist/doctor-Cb2thZ8s.js.map +0 -1
  165. package/dist/ensure-collection-policies-RHUcEp4v.js +0 -105
  166. package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
  167. package/dist/ensure-collection-tables-BEEjn5cn.js +0 -857
  168. package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
  169. package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
  170. package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
  171. package/dist/generate-drizzle-schema-yzY_BLhr.js +0 -123
  172. package/dist/generate-drizzle-schema-yzY_BLhr.js.map +0 -1
  173. package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
  174. package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
  175. package/dist/plan-schema-C0fxM8dY.js.map +0 -1
  176. package/dist/policy-drift-DljYdrpW.js.map +0 -1
  177. package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
  178. package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
  179. package/dist/source-Br7L7GOI.js +0 -410
  180. package/dist/source-Br7L7GOI.js.map +0 -1
@@ -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. A failed `context.data` write is not caught by this: it
70
- * runs in a savepoint, is undone on its own, and leaves the transaction
71
- * usable — which is what makes catching one safe.
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