turbine-orm 0.81.0 → 0.90.0

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 (295) hide show
  1. package/README.md +26 -25
  2. package/dist/checkout.d.ts +27 -2
  3. package/dist/checkout.js +75 -4
  4. package/dist/cjs/checkout.d.ts +27 -2
  5. package/dist/cjs/checkout.js +76 -3
  6. package/dist/cjs/cli/code-first-tags.d.ts +62 -0
  7. package/dist/cjs/cli/code-first-tags.js +124 -0
  8. package/dist/cjs/cli/compile-query.js +9 -0
  9. package/dist/cjs/cli/config.d.ts +35 -6
  10. package/dist/cjs/cli/config.js +17 -1
  11. package/dist/cjs/cli/destructive.d.ts +7 -4
  12. package/dist/cjs/cli/destructive.js +224 -20
  13. package/dist/cjs/cli/error-catalog.js +36 -0
  14. package/dist/cjs/cli/error-text.d.ts +15 -0
  15. package/dist/cjs/cli/error-text.js +40 -0
  16. package/dist/cjs/cli/index.d.ts +62 -4
  17. package/dist/cjs/cli/index.js +568 -108
  18. package/dist/cjs/cli/local-server-guard.d.ts +53 -0
  19. package/dist/cjs/cli/local-server-guard.js +119 -0
  20. package/dist/cjs/cli/local-server-output.d.ts +41 -0
  21. package/dist/cjs/cli/local-server-output.js +103 -0
  22. package/dist/cjs/cli/mcp.d.ts +8 -0
  23. package/dist/cjs/cli/mcp.js +121 -32
  24. package/dist/cjs/cli/migrate.d.ts +113 -5
  25. package/dist/cjs/cli/migrate.js +270 -21
  26. package/dist/cjs/cli/observe.d.ts +6 -1
  27. package/dist/cjs/cli/observe.js +85 -55
  28. package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
  29. package/dist/cjs/cli/pii-predicate-guard.js +98 -37
  30. package/dist/cjs/cli/prisma-resolve.js +17 -13
  31. package/dist/cjs/cli/prisma-schema.js +12 -4
  32. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  33. package/dist/cjs/cli/studio.d.ts +14 -2
  34. package/dist/cjs/cli/studio.js +244 -69
  35. package/dist/cjs/cli/ui.d.ts +1 -1
  36. package/dist/cjs/client.d.ts +558 -85
  37. package/dist/cjs/client.js +1270 -395
  38. package/dist/cjs/connection-guard.d.ts +13 -0
  39. package/dist/cjs/connection-guard.js +28 -0
  40. package/dist/cjs/connection-url.d.ts +51 -16
  41. package/dist/cjs/connection-url.js +67 -34
  42. package/dist/cjs/diagnostics.d.ts +81 -0
  43. package/dist/cjs/diagnostics.js +147 -0
  44. package/dist/cjs/dialect.d.ts +449 -20
  45. package/dist/cjs/dialect.js +117 -76
  46. package/dist/cjs/engine-config.d.ts +7 -6
  47. package/dist/cjs/errors.d.ts +126 -6
  48. package/dist/cjs/errors.js +256 -13
  49. package/dist/cjs/generate.d.ts +57 -7
  50. package/dist/cjs/generate.js +635 -279
  51. package/dist/cjs/global-filters-config.d.ts +59 -0
  52. package/dist/cjs/global-filters-config.js +367 -0
  53. package/dist/cjs/index.d.ts +9 -4
  54. package/dist/cjs/index.js +10 -2
  55. package/dist/cjs/introspect.d.ts +31 -6
  56. package/dist/cjs/introspect.js +108 -50
  57. package/dist/cjs/log-codes.d.ts +12 -0
  58. package/dist/cjs/log-codes.js +77 -0
  59. package/dist/cjs/logger.d.ts +87 -0
  60. package/dist/cjs/logger.js +98 -0
  61. package/dist/cjs/mssql.d.ts +8 -5
  62. package/dist/cjs/mssql.js +352 -65
  63. package/dist/cjs/mysql.d.ts +3 -5
  64. package/dist/cjs/mysql.js +420 -70
  65. package/dist/cjs/nested-write.d.ts +3 -0
  66. package/dist/cjs/nested-write.js +179 -114
  67. package/dist/cjs/pipeline-submittable.d.ts +12 -0
  68. package/dist/cjs/pipeline-submittable.js +118 -44
  69. package/dist/cjs/pipeline.js +21 -26
  70. package/dist/cjs/powdb-introspect.d.ts +5 -2
  71. package/dist/cjs/powdb-introspect.js +20 -15
  72. package/dist/cjs/powdb-shared.d.ts +36 -5
  73. package/dist/cjs/powdb-shared.js +67 -8
  74. package/dist/cjs/powdb.d.ts +4 -0
  75. package/dist/cjs/powdb.js +183 -51
  76. package/dist/cjs/powql.d.ts +394 -51
  77. package/dist/cjs/powql.js +1829 -284
  78. package/dist/cjs/prisma-compat.d.ts +23 -15
  79. package/dist/cjs/prisma-compat.js +507 -372
  80. package/dist/cjs/query/aggregates.d.ts +17 -1
  81. package/dist/cjs/query/aggregates.js +261 -29
  82. package/dist/cjs/query/batched-loader.d.ts +25 -6
  83. package/dist/cjs/query/batched-loader.js +103 -20
  84. package/dist/cjs/query/builder.d.ts +325 -184
  85. package/dist/cjs/query/builder.js +1452 -490
  86. package/dist/cjs/query/column-ref.d.ts +61 -0
  87. package/dist/cjs/query/column-ref.js +73 -0
  88. package/dist/cjs/query/compound-unique.d.ts +21 -0
  89. package/dist/cjs/query/compound-unique.js +98 -18
  90. package/dist/cjs/query/deferred.d.ts +53 -16
  91. package/dist/cjs/query/expr.d.ts +217 -0
  92. package/dist/cjs/query/expr.js +859 -0
  93. package/dist/cjs/query/filters.d.ts +155 -53
  94. package/dist/cjs/query/filters.js +613 -76
  95. package/dist/cjs/query/index.d.ts +2 -0
  96. package/dist/cjs/query/index.js +7 -1
  97. package/dist/cjs/query/json-value.d.ts +43 -0
  98. package/dist/cjs/query/json-value.js +59 -0
  99. package/dist/cjs/query/normalize-args.d.ts +87 -0
  100. package/dist/cjs/query/normalize-args.js +527 -0
  101. package/dist/cjs/query/option-surface.d.ts +16 -4
  102. package/dist/cjs/query/option-surface.js +57 -14
  103. package/dist/cjs/query/relation-names.d.ts +14 -0
  104. package/dist/cjs/query/relation-names.js +77 -18
  105. package/dist/cjs/query/relations.d.ts +30 -3
  106. package/dist/cjs/query/relations.js +219 -63
  107. package/dist/cjs/query/row-lock.d.ts +49 -0
  108. package/dist/cjs/query/row-lock.js +138 -0
  109. package/dist/cjs/query/sql-comment.d.ts +161 -0
  110. package/dist/cjs/query/sql-comment.js +381 -0
  111. package/dist/cjs/query/stream-idle.d.ts +132 -0
  112. package/dist/cjs/query/stream-idle.js +212 -0
  113. package/dist/cjs/query/table-client.d.ts +101 -0
  114. package/dist/cjs/query/table-client.js +74 -0
  115. package/dist/cjs/query/types.d.ts +677 -298
  116. package/dist/cjs/query/utils.d.ts +105 -6
  117. package/dist/cjs/query/utils.js +426 -19
  118. package/dist/cjs/query/warn-registry.d.ts +18 -0
  119. package/dist/cjs/query/warn-registry.js +18 -0
  120. package/dist/cjs/query/where-compile.d.ts +138 -14
  121. package/dist/cjs/query/where-compile.js +513 -29
  122. package/dist/cjs/query/where.d.ts +159 -59
  123. package/dist/cjs/query/where.js +716 -246
  124. package/dist/cjs/query/writes.d.ts +112 -50
  125. package/dist/cjs/query/writes.js +1498 -200
  126. package/dist/cjs/query-events.d.ts +4 -2
  127. package/dist/cjs/query-events.js +3 -1
  128. package/dist/cjs/realtime.d.ts +7 -3
  129. package/dist/cjs/realtime.js +13 -2
  130. package/dist/cjs/schema-builder.d.ts +55 -4
  131. package/dist/cjs/schema-builder.js +382 -18
  132. package/dist/cjs/schema-metadata.d.ts +21 -1
  133. package/dist/cjs/schema-metadata.js +29 -7
  134. package/dist/cjs/schema-sql.d.ts +75 -5
  135. package/dist/cjs/schema-sql.js +497 -45
  136. package/dist/cjs/schema.d.ts +26 -5
  137. package/dist/cjs/schema.js +110 -13
  138. package/dist/cjs/sql-placeholders.d.ts +56 -0
  139. package/dist/cjs/sql-placeholders.js +204 -0
  140. package/dist/cjs/sqlite.d.ts +75 -20
  141. package/dist/cjs/sqlite.js +593 -95
  142. package/dist/cjs/supplied-pool-types.d.ts +48 -0
  143. package/dist/cjs/supplied-pool-types.js +256 -0
  144. package/dist/cjs/transaction-control.d.ts +51 -0
  145. package/dist/cjs/transaction-control.js +316 -0
  146. package/dist/cjs/transaction-state.d.ts +342 -0
  147. package/dist/cjs/transaction-state.js +707 -0
  148. package/dist/cjs/typed-sql.d.ts +37 -3
  149. package/dist/cjs/typed-sql.js +102 -8
  150. package/dist/cli/code-first-tags.d.ts +62 -0
  151. package/dist/cli/code-first-tags.js +120 -0
  152. package/dist/cli/compile-query.js +9 -0
  153. package/dist/cli/config.d.ts +35 -6
  154. package/dist/cli/config.js +16 -1
  155. package/dist/cli/destructive.d.ts +7 -4
  156. package/dist/cli/destructive.js +224 -20
  157. package/dist/cli/error-catalog.js +36 -0
  158. package/dist/cli/error-text.d.ts +15 -0
  159. package/dist/cli/error-text.js +37 -0
  160. package/dist/cli/index.d.ts +62 -4
  161. package/dist/cli/index.js +569 -112
  162. package/dist/cli/local-server-guard.d.ts +53 -0
  163. package/dist/cli/local-server-guard.js +113 -0
  164. package/dist/cli/local-server-output.d.ts +41 -0
  165. package/dist/cli/local-server-output.js +98 -0
  166. package/dist/cli/mcp.d.ts +8 -0
  167. package/dist/cli/mcp.js +121 -34
  168. package/dist/cli/migrate.d.ts +113 -5
  169. package/dist/cli/migrate.js +261 -21
  170. package/dist/cli/observe.d.ts +6 -1
  171. package/dist/cli/observe.js +84 -54
  172. package/dist/cli/pii-predicate-guard.d.ts +43 -23
  173. package/dist/cli/pii-predicate-guard.js +100 -35
  174. package/dist/cli/prisma-resolve.js +15 -11
  175. package/dist/cli/prisma-schema.js +12 -4
  176. package/dist/cli/studio-ui.generated.js +1 -1
  177. package/dist/cli/studio.d.ts +14 -2
  178. package/dist/cli/studio.js +246 -71
  179. package/dist/cli/ui.d.ts +1 -1
  180. package/dist/client.d.ts +558 -85
  181. package/dist/client.js +1275 -400
  182. package/dist/connection-guard.d.ts +13 -0
  183. package/dist/connection-guard.js +27 -0
  184. package/dist/connection-url.d.ts +51 -16
  185. package/dist/connection-url.js +63 -34
  186. package/dist/diagnostics.d.ts +81 -0
  187. package/dist/diagnostics.js +144 -0
  188. package/dist/dialect.d.ts +449 -20
  189. package/dist/dialect.js +115 -43
  190. package/dist/engine-config.d.ts +7 -6
  191. package/dist/errors.d.ts +126 -6
  192. package/dist/errors.js +252 -12
  193. package/dist/generate.d.ts +57 -7
  194. package/dist/generate.js +635 -281
  195. package/dist/global-filters-config.d.ts +59 -0
  196. package/dist/global-filters-config.js +361 -0
  197. package/dist/index.d.ts +9 -4
  198. package/dist/index.js +4 -2
  199. package/dist/introspect.d.ts +31 -6
  200. package/dist/introspect.js +106 -50
  201. package/dist/log-codes.d.ts +12 -0
  202. package/dist/log-codes.js +74 -0
  203. package/dist/logger.d.ts +87 -0
  204. package/dist/logger.js +93 -0
  205. package/dist/mssql.d.ts +8 -5
  206. package/dist/mssql.js +354 -67
  207. package/dist/mysql.d.ts +3 -5
  208. package/dist/mysql.js +423 -73
  209. package/dist/nested-write.d.ts +3 -0
  210. package/dist/nested-write.js +177 -112
  211. package/dist/pipeline-submittable.d.ts +12 -0
  212. package/dist/pipeline-submittable.js +117 -44
  213. package/dist/pipeline.js +21 -26
  214. package/dist/powdb-introspect.d.ts +5 -2
  215. package/dist/powdb-introspect.js +21 -16
  216. package/dist/powdb-shared.d.ts +36 -5
  217. package/dist/powdb-shared.js +65 -8
  218. package/dist/powdb.d.ts +4 -0
  219. package/dist/powdb.js +183 -51
  220. package/dist/powql.d.ts +394 -51
  221. package/dist/powql.js +1836 -291
  222. package/dist/prisma-compat.d.ts +23 -15
  223. package/dist/prisma-compat.js +508 -373
  224. package/dist/query/aggregates.d.ts +17 -1
  225. package/dist/query/aggregates.js +263 -32
  226. package/dist/query/batched-loader.d.ts +25 -6
  227. package/dist/query/batched-loader.js +101 -21
  228. package/dist/query/builder.d.ts +325 -184
  229. package/dist/query/builder.js +1458 -495
  230. package/dist/query/column-ref.d.ts +61 -0
  231. package/dist/query/column-ref.js +66 -0
  232. package/dist/query/compound-unique.d.ts +21 -0
  233. package/dist/query/compound-unique.js +97 -19
  234. package/dist/query/deferred.d.ts +53 -16
  235. package/dist/query/expr.d.ts +217 -0
  236. package/dist/query/expr.js +848 -0
  237. package/dist/query/filters.d.ts +155 -53
  238. package/dist/query/filters.js +597 -72
  239. package/dist/query/index.d.ts +2 -0
  240. package/dist/query/index.js +4 -0
  241. package/dist/query/json-value.d.ts +43 -0
  242. package/dist/query/json-value.js +54 -0
  243. package/dist/query/normalize-args.d.ts +87 -0
  244. package/dist/query/normalize-args.js +517 -0
  245. package/dist/query/option-surface.d.ts +16 -4
  246. package/dist/query/option-surface.js +56 -13
  247. package/dist/query/relation-names.d.ts +14 -0
  248. package/dist/query/relation-names.js +77 -19
  249. package/dist/query/relations.d.ts +30 -3
  250. package/dist/query/relations.js +220 -67
  251. package/dist/query/row-lock.d.ts +49 -0
  252. package/dist/query/row-lock.js +131 -0
  253. package/dist/query/sql-comment.d.ts +161 -0
  254. package/dist/query/sql-comment.js +374 -0
  255. package/dist/query/stream-idle.d.ts +132 -0
  256. package/dist/query/stream-idle.js +203 -0
  257. package/dist/query/table-client.d.ts +101 -0
  258. package/dist/query/table-client.js +70 -0
  259. package/dist/query/types.d.ts +677 -298
  260. package/dist/query/utils.d.ts +105 -6
  261. package/dist/query/utils.js +414 -18
  262. package/dist/query/warn-registry.d.ts +18 -0
  263. package/dist/query/warn-registry.js +18 -0
  264. package/dist/query/where-compile.d.ts +138 -14
  265. package/dist/query/where-compile.js +508 -31
  266. package/dist/query/where.d.ts +159 -59
  267. package/dist/query/where.js +711 -248
  268. package/dist/query/writes.d.ts +112 -50
  269. package/dist/query/writes.js +1496 -202
  270. package/dist/query-events.d.ts +4 -2
  271. package/dist/query-events.js +3 -1
  272. package/dist/realtime.d.ts +7 -3
  273. package/dist/realtime.js +13 -2
  274. package/dist/schema-builder.d.ts +55 -4
  275. package/dist/schema-builder.js +381 -18
  276. package/dist/schema-metadata.d.ts +21 -1
  277. package/dist/schema-metadata.js +28 -7
  278. package/dist/schema-sql.d.ts +75 -5
  279. package/dist/schema-sql.js +494 -44
  280. package/dist/schema.d.ts +26 -5
  281. package/dist/schema.js +109 -13
  282. package/dist/sql-placeholders.d.ts +56 -0
  283. package/dist/sql-placeholders.js +200 -0
  284. package/dist/sqlite.d.ts +75 -20
  285. package/dist/sqlite.js +597 -99
  286. package/dist/supplied-pool-types.d.ts +48 -0
  287. package/dist/supplied-pool-types.js +247 -0
  288. package/dist/transaction-control.d.ts +51 -0
  289. package/dist/transaction-control.js +311 -0
  290. package/dist/transaction-state.d.ts +342 -0
  291. package/dist/transaction-state.js +696 -0
  292. package/dist/typed-sql.d.ts +37 -3
  293. package/dist/typed-sql.js +100 -8
  294. package/package.json +7 -4
  295. package/skills/turbine-orm/SKILL.md +56 -6
@@ -58,6 +58,19 @@ export interface CheckoutGuard extends ConnectionGuard {
58
58
  * event (an engine shim) is left alone.
59
59
  */
60
60
  export declare function absorbCheckedOutErrors(pool: unknown): void;
61
+ export interface CheckoutWatch {
62
+ /** Connections currently checked out of the pool, in checkout order. */
63
+ readonly checkedOut: ReadonlySet<object>;
64
+ }
65
+ /**
66
+ * Track which connections of a pool Turbine owns are checked out, from
67
+ * pg-pool's public `acquire` / `release` / `remove` events, and call `onLost`
68
+ * once per connection that fails WHILE checked out (an idle connection's
69
+ * failure is pg-pool's own `'error'` event). Used for the client's
70
+ * `connectionLost` event and for force-closing what `disconnect({ timeoutMs })`
71
+ * is still waiting on. A pool without those events is left alone.
72
+ */
73
+ export declare function watchCheckouts(pool: unknown, onLost?: (err: Error) => void): CheckoutWatch;
61
74
  /**
62
75
  * Guard a connection for as long as the caller holds it. Use {@link guardCheckout}
63
76
  * for a pooled checkout; this form is for a client whose whole lifetime the
@@ -32,6 +32,7 @@
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
33
  exports.LONG_IDLE_SETTLE_MS = void 0;
34
34
  exports.absorbCheckedOutErrors = absorbCheckedOutErrors;
35
+ exports.watchCheckouts = watchCheckouts;
35
36
  exports.guardConnection = guardConnection;
36
37
  exports.guardCheckout = guardCheckout;
37
38
  exports.settleEventLoop = settleEventLoop;
@@ -54,6 +55,33 @@ function absorbCheckedOutErrors(pool) {
54
55
  client.on?.('error', () => { });
55
56
  });
56
57
  }
58
+ /**
59
+ * Track which connections of a pool Turbine owns are checked out, from
60
+ * pg-pool's public `acquire` / `release` / `remove` events, and call `onLost`
61
+ * once per connection that fails WHILE checked out (an idle connection's
62
+ * failure is pg-pool's own `'error'` event). Used for the client's
63
+ * `connectionLost` event and for force-closing what `disconnect({ timeoutMs })`
64
+ * is still waiting on. A pool without those events is left alone.
65
+ */
66
+ function watchCheckouts(pool, onLost) {
67
+ const checkedOut = new Set();
68
+ const emitter = pool;
69
+ if (typeof emitter.on !== 'function')
70
+ return { checkedOut };
71
+ const reported = new WeakSet();
72
+ emitter.on('acquire', (client) => checkedOut.add(client));
73
+ emitter.on('release', (_err, client) => checkedOut.delete(client));
74
+ emitter.on('remove', (client) => checkedOut.delete(client));
75
+ emitter.on('connect', (client) => {
76
+ client.on?.('error', (err) => {
77
+ if (!checkedOut.has(client) || reported.has(client))
78
+ return;
79
+ reported.add(client);
80
+ onLost?.(err);
81
+ });
82
+ });
83
+ return { checkedOut };
84
+ }
57
85
  /**
58
86
  * Guard a connection for as long as the caller holds it. Use {@link guardCheckout}
59
87
  * for a pooled checkout; this form is for a client whose whole lifetime the
@@ -107,12 +107,33 @@ export interface PoolerDetection {
107
107
  * blocks a legitimate database, so precision wins and the caller is expected to
108
108
  * offer an override.
109
109
  */
110
- export declare function detectPooler(connectionString: string): PoolerDetection;
110
+ export declare function detectPooler(connectionString: string, environment?: PgEnvironmentTarget): PoolerDetection;
111
+ /**
112
+ * The `PGHOST` / `PGPORT` values a connection target falls back to. node-postgres
113
+ * reads a field from the connection string or the config first and the
114
+ * environment only when that leaves it unset, so a string that names no port (or
115
+ * no host) is still connecting to wherever these point.
116
+ */
117
+ export interface PgEnvironmentTarget {
118
+ readonly host?: string | undefined;
119
+ readonly port?: string | undefined;
120
+ }
121
+ /** `target` with the host and port it leaves unset taken from `environment`, the way node-postgres does. */
122
+ export declare function fillTargetFromEnvironment(target: ConnectionTarget, environment: PgEnvironmentTarget): ConnectionTarget;
123
+ /** {@link detectPooler} for a target that is already parsed. */
124
+ export declare function detectPoolerTarget(target: ConnectionTarget): PoolerDetection;
111
125
  export interface PoolerRefusalOptions {
112
126
  /** The command being refused, e.g. `turbine doctor`. */
113
127
  command: string;
114
128
  /** The flag that overrides the refusal, e.g. `--allow-pooler`. */
115
129
  allowFlag: string;
130
+ /**
131
+ * Why THIS command cannot use a pooler, in place of the default paragraph
132
+ * (written for `doctor`: session state, session-scoped views, the read bound).
133
+ * Lines are printed as given, between the endpoint and the advice to use the
134
+ * direct endpoint.
135
+ */
136
+ explanation?: readonly string[];
116
137
  }
117
138
  /**
118
139
  * The lines a command prints when it refuses a pooler endpoint.
@@ -158,6 +179,30 @@ export interface ConnectionOptionsConfig {
158
179
  * a value that can carry a space can carry a second `-c`.
159
180
  */
160
181
  export declare function withStatementTimeoutOption(config: ConnectionOptionsConfig, statementTimeoutMs: number): ConnectionOptionsConfig;
182
+ /**
183
+ * The `-c statement_timeout=<ms>` startup setting, or `null` when the value is
184
+ * not a non-negative safe integer (see the injection note on
185
+ * {@link withStatementTimeoutOption}). No unit suffix: the GUC's default unit
186
+ * is milliseconds.
187
+ */
188
+ export declare function statementTimeoutSetting(statementTimeoutMs: number): string | null;
189
+ /**
190
+ * `config` with one `-c name=value` startup setting appended to its libpq
191
+ * `options`, in whichever place pg will read them from: an `options` query
192
+ * parameter already on the connection string wins over the `options` field,
193
+ * and the field falls back to `PGOPTIONS` only while it is unset, so both are
194
+ * read first and appended to rather than replaced.
195
+ *
196
+ * `setting` is emitted verbatim. The caller must have built it from a closed
197
+ * set or a narrowed number: the `options` string is split on whitespace, so a
198
+ * caller-controlled value could carry a second `-c`.
199
+ */
200
+ export declare function withStartupSetting<T extends {
201
+ connectionString?: string;
202
+ options?: string;
203
+ }>(config: T, setting: string): T & {
204
+ options?: string;
205
+ };
161
206
  /**
162
207
  * `connectionString` with `setting` appended to an existing `options` query
163
208
  * parameter, or `null` when it carries none (in which case the caller uses the
@@ -170,22 +215,12 @@ export declare function withStatementTimeoutOption(config: ConnectionOptionsConf
170
215
  * password is not parseable by pg either, so there is no shape this handles
171
216
  * differently from the driver.
172
217
  *
173
- * Shared with `client.ts`, which needs the identical merge for
174
- * `plan_cache_mode`. It was duplicated there until 2026-08-16, and the reason
175
- * is worth keeping because it was wrong in an instructive way: importing it
176
- * from `client.ts` adds an import edge from a module ~100 test processes load,
177
- * and this file's merged coverage read 40% while measuring 100% in isolation,
178
- * which moved the aggregate gate 75.48% -> 75.03% against a 75% floor. The
179
- * conclusion drawn was that the refactor cost the gate's headroom.
180
- *
181
- * The 40% was a c8 merge defect, not an import cost: c8's V8-level merge is not
182
- * monotonic, and once the report merges at the istanbul level instead this file
183
- * reads 100% with the edge in place (see `//merge-bug` in .c8rc.json). So the
184
- * duplication was paying for an artifact, and the two copies are now one.
218
+ * Used by {@link withStartupSetting} (the client's pool configuration) and
219
+ * {@link withSearchPathOption}.
185
220
  *
186
- * Keep this module import-free regardless. That part was never about coverage:
187
- * `query/` and `cli/` both reach it, and an edge back to `client.ts` would be
188
- * the cycle `scripts/check-import-cycles.mjs` exists to refuse.
221
+ * Keep this module import-free: `query/` and `cli/` both reach it, and an
222
+ * edge back to `client.ts` would be the cycle `scripts/check-import-cycles.mjs`
223
+ * exists to refuse.
189
224
  */
190
225
  export declare function mergeConnectionStringOptions(connectionString: string, setting: string): string | null;
191
226
  /**
@@ -37,8 +37,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.SEARCH_PATH_SCHEMA_PATTERN = exports.POOLER_PORTS = exports.POOLER_HOST_TOKENS = void 0;
38
38
  exports.parseConnectionTarget = parseConnectionTarget;
39
39
  exports.detectPooler = detectPooler;
40
+ exports.fillTargetFromEnvironment = fillTargetFromEnvironment;
41
+ exports.detectPoolerTarget = detectPoolerTarget;
40
42
  exports.poolerRefusalMessage = poolerRefusalMessage;
41
43
  exports.withStatementTimeoutOption = withStatementTimeoutOption;
44
+ exports.statementTimeoutSetting = statementTimeoutSetting;
45
+ exports.withStartupSetting = withStartupSetting;
42
46
  exports.mergeConnectionStringOptions = mergeConnectionStringOptions;
43
47
  exports.isPlainSchemaIdentifier = isPlainSchemaIdentifier;
44
48
  exports.parseSearchPathValue = parseSearchPathValue;
@@ -117,8 +121,22 @@ function noPooler(target) {
117
121
  * blocks a legitimate database, so precision wins and the caller is expected to
118
122
  * offer an override.
119
123
  */
120
- function detectPooler(connectionString) {
121
- const target = parseConnectionTarget(connectionString);
124
+ function detectPooler(connectionString, environment) {
125
+ const parsed = parseConnectionTarget(connectionString);
126
+ return detectPoolerTarget(environment === undefined ? parsed : fillTargetFromEnvironment(parsed, environment));
127
+ }
128
+ /** `target` with the host and port it leaves unset taken from `environment`, the way node-postgres does. */
129
+ function fillTargetFromEnvironment(target, environment) {
130
+ const host = target.host ?? (environment.host ? environment.host.trim().toLowerCase() || null : null);
131
+ let port = target.port;
132
+ if (port === null && environment.port) {
133
+ const parsed = Number.parseInt(environment.port, 10);
134
+ port = Number.isInteger(parsed) ? parsed : null;
135
+ }
136
+ return { host, port };
137
+ }
138
+ /** {@link detectPooler} for a target that is already parsed. */
139
+ function detectPoolerTarget(target) {
122
140
  if (target.host !== null) {
123
141
  for (const label of target.host.split('.')) {
124
142
  for (const token of label.split(/[-_]/)) {
@@ -171,6 +189,21 @@ function deriveDirectHost(host, token) {
171
189
  });
172
190
  return changed ? rewritten.join('.') : null;
173
191
  }
192
+ /** Why `turbine doctor` refuses a pooler; the paragraph {@link poolerRefusalMessage} prints by default. */
193
+ const DEFAULT_POOLER_EXPLANATION = [
194
+ 'A transaction pooler (PgBouncer, a Neon "-pooler" endpoint, Supabase\'s pooler) does not',
195
+ 'give a client its own server backend. It multiplexes many clients onto a few shared',
196
+ 'backends and reuses one as soon as a transaction ends, so:',
197
+ '',
198
+ ' 1. Session state is not private. Anything this command sets on the session can be left',
199
+ ' behind for another client, and whatever an earlier client left behind can be in',
200
+ ' force for this one. That is how a "read-only guardrail" becomes an outage.',
201
+ ' 2. Session-scoped views describe the wrong session. pg_prepared_statements, which this',
202
+ ' report tells you to read to confirm a cached plan, belongs to whichever backend',
203
+ ' answered, not to your application.',
204
+ ' 3. The read bounds may not apply. This command asks for its statement_timeout as a',
205
+ ' connection parameter, and a pooler is free to drop or reject one.',
206
+ ];
174
207
  /**
175
208
  * The lines a command prints when it refuses a pooler endpoint.
176
209
  *
@@ -188,18 +221,7 @@ function poolerRefusalMessage(detection, options) {
188
221
  ` Endpoint: ${where}${detection.port === null ? '' : `:${detection.port}`}`,
189
222
  ` Detected: ${what}`,
190
223
  '',
191
- 'A transaction pooler (PgBouncer, a Neon "-pooler" endpoint, Supabase\'s pooler) does not',
192
- 'give a client its own server backend. It multiplexes many clients onto a few shared',
193
- 'backends and reuses one as soon as a transaction ends, so:',
194
- '',
195
- ' 1. Session state is not private. Anything this command sets on the session can be left',
196
- ' behind for another client, and whatever an earlier client left behind can be in',
197
- ' force for this one. That is how a "read-only guardrail" becomes an outage.',
198
- ' 2. Session-scoped views describe the wrong session. pg_prepared_statements, which this',
199
- ' report tells you to read to confirm a cached plan, belongs to whichever backend',
200
- ' answered, not to your application.',
201
- ' 3. The read bounds may not apply. This command asks for its statement_timeout as a',
202
- ' connection parameter, and a pooler is free to drop or reject one.',
224
+ ...(options.explanation ?? DEFAULT_POOLER_EXPLANATION),
203
225
  '',
204
226
  'Use the DIRECT (non-pooled) endpoint for the same database instead.',
205
227
  ];
@@ -244,12 +266,33 @@ function poolerRefusalMessage(detection, options) {
244
266
  * a value that can carry a space can carry a second `-c`.
245
267
  */
246
268
  function withStatementTimeoutOption(config, statementTimeoutMs) {
269
+ const setting = statementTimeoutSetting(statementTimeoutMs);
270
+ return setting === null ? config : withStartupSetting(config, setting);
271
+ }
272
+ /**
273
+ * The `-c statement_timeout=<ms>` startup setting, or `null` when the value is
274
+ * not a non-negative safe integer (see the injection note on
275
+ * {@link withStatementTimeoutOption}). No unit suffix: the GUC's default unit
276
+ * is milliseconds.
277
+ */
278
+ function statementTimeoutSetting(statementTimeoutMs) {
247
279
  if (!Number.isSafeInteger(statementTimeoutMs) || statementTimeoutMs < 0)
248
- return config;
249
- // No unit suffix: statement_timeout's default unit IS milliseconds, which is
250
- // what `SET statement_timeout = <n>` meant before this replaced it.
251
- const setting = `-c statement_timeout=${statementTimeoutMs}`;
252
- const merged = mergeConnectionStringOptions(config.connectionString, setting);
280
+ return null;
281
+ return `-c statement_timeout=${statementTimeoutMs}`;
282
+ }
283
+ /**
284
+ * `config` with one `-c name=value` startup setting appended to its libpq
285
+ * `options`, in whichever place pg will read them from: an `options` query
286
+ * parameter already on the connection string wins over the `options` field,
287
+ * and the field falls back to `PGOPTIONS` only while it is unset, so both are
288
+ * read first and appended to rather than replaced.
289
+ *
290
+ * `setting` is emitted verbatim. The caller must have built it from a closed
291
+ * set or a narrowed number: the `options` string is split on whitespace, so a
292
+ * caller-controlled value could carry a second `-c`.
293
+ */
294
+ function withStartupSetting(config, setting) {
295
+ const merged = config.connectionString ? mergeConnectionStringOptions(config.connectionString, setting) : null;
253
296
  if (merged !== null)
254
297
  return { ...config, connectionString: merged };
255
298
  const existing = config.options || (typeof process !== 'undefined' ? process.env?.PGOPTIONS : undefined);
@@ -267,22 +310,12 @@ function withStatementTimeoutOption(config, statementTimeoutMs) {
267
310
  * password is not parseable by pg either, so there is no shape this handles
268
311
  * differently from the driver.
269
312
  *
270
- * Shared with `client.ts`, which needs the identical merge for
271
- * `plan_cache_mode`. It was duplicated there until 2026-08-16, and the reason
272
- * is worth keeping because it was wrong in an instructive way: importing it
273
- * from `client.ts` adds an import edge from a module ~100 test processes load,
274
- * and this file's merged coverage read 40% while measuring 100% in isolation,
275
- * which moved the aggregate gate 75.48% -> 75.03% against a 75% floor. The
276
- * conclusion drawn was that the refactor cost the gate's headroom.
277
- *
278
- * The 40% was a c8 merge defect, not an import cost: c8's V8-level merge is not
279
- * monotonic, and once the report merges at the istanbul level instead this file
280
- * reads 100% with the edge in place (see `//merge-bug` in .c8rc.json). So the
281
- * duplication was paying for an artifact, and the two copies are now one.
313
+ * Used by {@link withStartupSetting} (the client's pool configuration) and
314
+ * {@link withSearchPathOption}.
282
315
  *
283
- * Keep this module import-free regardless. That part was never about coverage:
284
- * `query/` and `cli/` both reach it, and an edge back to `client.ts` would be
285
- * the cycle `scripts/check-import-cycles.mjs` exists to refuse.
316
+ * Keep this module import-free: `query/` and `cli/` both reach it, and an
317
+ * edge back to `client.ts` would be the cycle `scripts/check-import-cycles.mjs`
318
+ * exists to refuse.
286
319
  */
287
320
  function mergeConnectionStringOptions(connectionString, setting) {
288
321
  const q = connectionString.indexOf('?');
@@ -0,0 +1,81 @@
1
+ /**
2
+ * turbine-orm, the `diagnostics` switch.
3
+ *
4
+ * One client option decides what Turbine checks and says about itself at run
5
+ * time: per-query advice (unknown options, unordered pages, deep `with`,
6
+ * strategy notes, missing FK indexes, redundant sort terms) and the SQL-cache
7
+ * self-check. It is resolved ONCE, when a client is constructed, and travels to
8
+ * every query interface the client builds, so no hot path reads `process.env`.
9
+ *
10
+ * This module is the only place in the library (outside `cli/`) that reads
11
+ * `NODE_ENV` or the two `TURBINE_*` cache-check variables; a source scan
12
+ * (`fleet-diagnostics-env-scan.test.ts`) fails the build on a second reader.
13
+ *
14
+ * Zero imports besides errors.ts, so client.ts, query/ and the engines can all
15
+ * depend on it without a new cycle.
16
+ */
17
+ /** `'development'`: advice on, cache self-check on every hit. `'production'`: both off. */
18
+ export type DiagnosticsMode = 'development' | 'production';
19
+ /** The SQL-cache self-check: rebuild on every cache hit, a sampled fraction of hits, or never. */
20
+ export type CacheCheckMode = 'every-hit' | 'off' | {
21
+ readonly sample: number;
22
+ };
23
+ /** Per-part overrides for {@link DiagnosticsConfig}. Unset parts follow the mode. */
24
+ export interface DiagnosticsOptions {
25
+ /**
26
+ * Per-query advice: unknown query options, unordered pages, deep `with`
27
+ * trees, relation-strategy notes, missing FK indexes, redundant sort terms.
28
+ * Default: on in development, off in production.
29
+ */
30
+ advice?: boolean | undefined;
31
+ /**
32
+ * SQL-cache self-check. `'every-hit'` rebuilds the SQL on each cache hit
33
+ * and compares; `{ sample }` compares that fraction of hits, in `(0, 1]`;
34
+ * `'off'` never compares. Default: `'every-hit'` in development, `'off'` in
35
+ * production. A mismatch means Turbine has a bug, and throws.
36
+ */
37
+ cacheCheck?: CacheCheckMode | undefined;
38
+ }
39
+ /**
40
+ * The `diagnostics` client option: a mode, or per-part overrides on top of
41
+ * one. When no mode is given it comes from `NODE_ENV`, read once when the
42
+ * client is constructed: `'production'` when `NODE_ENV` is `'production'` (or
43
+ * when there is no `process` at all, as on an edge runtime), else
44
+ * `'development'`.
45
+ */
46
+ export type DiagnosticsConfig = DiagnosticsMode | (DiagnosticsOptions & {
47
+ mode?: DiagnosticsMode | undefined;
48
+ });
49
+ /** What a client actually runs with. Frozen; built only by {@link resolveDiagnostics}. */
50
+ export interface ResolvedDiagnostics {
51
+ readonly mode: DiagnosticsMode;
52
+ /** Whether per-query advice is emitted. */
53
+ readonly advice: boolean;
54
+ readonly cacheCheck: CacheCheckMode;
55
+ }
56
+ /**
57
+ * The environment variables this module reads. `null` in their place means
58
+ * there is no `process` to read (an edge runtime), which resolves to
59
+ * production.
60
+ */
61
+ export interface DiagnosticsEnv {
62
+ NODE_ENV?: string | undefined;
63
+ TURBINE_DISABLE_CACHE_CHECK?: string | undefined;
64
+ TURBINE_CACHE_CHECK_SAMPLE?: string | undefined;
65
+ }
66
+ /**
67
+ * Resolve the `diagnostics` option, once, at client construction.
68
+ *
69
+ * Precedence for each part: the explicit option, then (for `cacheCheck` only)
70
+ * the legacy `TURBINE_DISABLE_CACHE_CHECK` / `TURBINE_CACHE_CHECK_SAMPLE`
71
+ * variables, then the mode's default. The mode itself is the option's, else
72
+ * `NODE_ENV`'s.
73
+ *
74
+ * Throws `ValidationError` (E003) for an unknown mode, an unknown key, a
75
+ * non-boolean `advice`, or a `sample` outside `(0, 1]`. A value this function
76
+ * returned is handed back by reference.
77
+ *
78
+ * @param env - The environment to read; defaults to `process.env`. Tests pass
79
+ * one explicitly; `null` stands for a runtime with no `process`.
80
+ */
81
+ export declare function resolveDiagnostics(config?: unknown, env?: DiagnosticsEnv | null): ResolvedDiagnostics;
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ /**
3
+ * turbine-orm, the `diagnostics` switch.
4
+ *
5
+ * One client option decides what Turbine checks and says about itself at run
6
+ * time: per-query advice (unknown options, unordered pages, deep `with`,
7
+ * strategy notes, missing FK indexes, redundant sort terms) and the SQL-cache
8
+ * self-check. It is resolved ONCE, when a client is constructed, and travels to
9
+ * every query interface the client builds, so no hot path reads `process.env`.
10
+ *
11
+ * This module is the only place in the library (outside `cli/`) that reads
12
+ * `NODE_ENV` or the two `TURBINE_*` cache-check variables; a source scan
13
+ * (`fleet-diagnostics-env-scan.test.ts`) fails the build on a second reader.
14
+ *
15
+ * Zero imports besides errors.ts, so client.ts, query/ and the engines can all
16
+ * depend on it without a new cycle.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.resolveDiagnostics = resolveDiagnostics;
20
+ const errors_js_1 = require("./errors.js");
21
+ const MODES = ['development', 'production'];
22
+ const OPTION_KEYS = new Set(['mode', 'advice', 'cacheCheck']);
23
+ /**
24
+ * Marks objects {@link resolveDiagnostics} built, so resolving a resolved value
25
+ * hands it back by reference. A `TransactionClient` builds a query interface
26
+ * per table per transaction from the client's options; re-resolving there would
27
+ * put the environment reads back on a per-transaction path.
28
+ */
29
+ const resolvedSet = new WeakSet();
30
+ /**
31
+ * Read the variables once. Every read is guarded: no `process` (edge runtimes)
32
+ * and no `process.env` (some sandboxes) both mean "no environment".
33
+ */
34
+ function readEnv() {
35
+ if (typeof process === 'undefined' || process.env === undefined)
36
+ return null;
37
+ const env = process.env;
38
+ return {
39
+ NODE_ENV: env.NODE_ENV,
40
+ TURBINE_DISABLE_CACHE_CHECK: env.TURBINE_DISABLE_CACHE_CHECK,
41
+ TURBINE_CACHE_CHECK_SAMPLE: env.TURBINE_CACHE_CHECK_SAMPLE,
42
+ };
43
+ }
44
+ function invalid(detail) {
45
+ return new errors_js_1.ValidationError(`Invalid diagnostics option: ${detail}. Expected 'development', 'production', or ` +
46
+ "{ mode?, advice?: boolean, cacheCheck?: 'every-hit' | 'off' | { sample: number in (0, 1] } }.");
47
+ }
48
+ function checkSample(value) {
49
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0 || value > 1) {
50
+ throw invalid(`cacheCheck.sample must be a number in (0, 1], got ${describe(value)}`);
51
+ }
52
+ return value;
53
+ }
54
+ function describe(value) {
55
+ if (typeof value === 'string')
56
+ return JSON.stringify(value);
57
+ if (typeof value === 'number' || typeof value === 'boolean' || value === null || value === undefined) {
58
+ return String(value);
59
+ }
60
+ return Array.isArray(value) ? 'an array' : `a ${typeof value}`;
61
+ }
62
+ function resolveCacheCheck(value) {
63
+ if (value === 'every-hit' || value === 'off')
64
+ return value;
65
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
66
+ const keys = Object.keys(value);
67
+ if (keys.length !== 1 || keys[0] !== 'sample') {
68
+ throw invalid(`cacheCheck object takes exactly one key, sample; got { ${keys.join(', ')} }`);
69
+ }
70
+ return Object.freeze({ sample: checkSample(value.sample) });
71
+ }
72
+ throw invalid(`cacheCheck ${describe(value)}`);
73
+ }
74
+ /**
75
+ * The legacy cache-check variables, under an explicit `cacheCheck`.
76
+ * `TURBINE_DISABLE_CACHE_CHECK=1` turns the check off in any mode. In
77
+ * production mode, `TURBINE_CACHE_CHECK_SAMPLE` sets a sample rate: a rate of
78
+ * 1 or more checks every hit, and 0, a negative or an unparseable value leaves
79
+ * the check off, as these variables always behaved. Development mode already
80
+ * checks every hit, so the sample variable is not read there.
81
+ */
82
+ function legacyCacheCheck(mode, env) {
83
+ if (env === null)
84
+ return undefined;
85
+ if (env.TURBINE_DISABLE_CACHE_CHECK === '1')
86
+ return 'off';
87
+ if (mode !== 'production' || env.TURBINE_CACHE_CHECK_SAMPLE === undefined)
88
+ return undefined;
89
+ const rate = Number.parseFloat(env.TURBINE_CACHE_CHECK_SAMPLE);
90
+ if (!Number.isFinite(rate) || rate <= 0)
91
+ return 'off';
92
+ return Object.freeze({ sample: Math.min(rate, 1) });
93
+ }
94
+ /**
95
+ * Resolve the `diagnostics` option, once, at client construction.
96
+ *
97
+ * Precedence for each part: the explicit option, then (for `cacheCheck` only)
98
+ * the legacy `TURBINE_DISABLE_CACHE_CHECK` / `TURBINE_CACHE_CHECK_SAMPLE`
99
+ * variables, then the mode's default. The mode itself is the option's, else
100
+ * `NODE_ENV`'s.
101
+ *
102
+ * Throws `ValidationError` (E003) for an unknown mode, an unknown key, a
103
+ * non-boolean `advice`, or a `sample` outside `(0, 1]`. A value this function
104
+ * returned is handed back by reference.
105
+ *
106
+ * @param env - The environment to read; defaults to `process.env`. Tests pass
107
+ * one explicitly; `null` stands for a runtime with no `process`.
108
+ */
109
+ function resolveDiagnostics(config, env = readEnv()) {
110
+ if (typeof config === 'object' && config !== null && resolvedSet.has(config)) {
111
+ return config;
112
+ }
113
+ let options = {};
114
+ if (typeof config === 'string') {
115
+ if (!MODES.includes(config))
116
+ throw invalid(`unknown mode ${describe(config)}`);
117
+ options = { mode: config };
118
+ }
119
+ else if (typeof config === 'object' && config !== null && !Array.isArray(config)) {
120
+ for (const key of Object.keys(config)) {
121
+ if (!OPTION_KEYS.has(key))
122
+ throw invalid(`unknown key "${key}"`);
123
+ }
124
+ options = config;
125
+ if (options.mode !== undefined && !MODES.includes(options.mode)) {
126
+ throw invalid(`unknown mode ${describe(options.mode)}`);
127
+ }
128
+ if (options.advice !== undefined && typeof options.advice !== 'boolean') {
129
+ throw invalid(`advice must be a boolean, got ${describe(options.advice)}`);
130
+ }
131
+ }
132
+ else if (config !== undefined && config !== null) {
133
+ throw invalid(describe(config));
134
+ }
135
+ const mode = options.mode ??
136
+ (env === null || env.NODE_ENV === 'production' ? 'production' : 'development');
137
+ const cacheCheck = options.cacheCheck !== undefined
138
+ ? resolveCacheCheck(options.cacheCheck)
139
+ : (legacyCacheCheck(mode, env) ?? (mode === 'production' ? 'off' : 'every-hit'));
140
+ const resolved = Object.freeze({
141
+ mode,
142
+ advice: options.advice ?? mode === 'development',
143
+ cacheCheck,
144
+ });
145
+ resolvedSet.add(resolved);
146
+ return resolved;
147
+ }