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
@@ -0,0 +1,381 @@
1
+ "use strict";
2
+ /**
3
+ * turbine-orm: `sqlComment`, sqlcommenter-style key/values on each statement
4
+ * Turbine builds, so `pg_stat_activity`, the server log, `auto_explain` and
5
+ * database APM tools can attribute a statement to a service, a model, a feature
6
+ * or a trace.
7
+ *
8
+ * SELECT ... LIMIT $2 /*action='findMany',application='billing',model='users'*\/
9
+ *
10
+ * FORMAT (the sqlcommenter spec): keys sorted, each value URL-encoded with
11
+ * `encodeURIComponent` and then `'` escaped as `\'`, each pair `key='value'`,
12
+ * pairs joined by commas, the whole comment appended after the statement with
13
+ * one space before it.
14
+ *
15
+ * WHY A VALUE CANNOT ESCAPE THE COMMENT. `encodeURIComponent` encodes `/`, so a
16
+ * value can hold neither `*\/` (the comment's end) nor `/*` (PostgreSQL and SQL
17
+ * Server nest block comments, so an opener would swallow the rest of the
18
+ * text). It also encodes every character a driver-side placeholder rewrite
19
+ * looks for (`$`, `?`, `:`, `@`) and `;`. Keys must match
20
+ * {@link SQL_COMMENT_KEY_PATTERN}, whose first-character rule also rules out
21
+ * MySQL's executable `/*!` and optimizer-hint `/*+` comments.
22
+ *
23
+ * WHERE IT IS APPENDED: at the execute seam, AFTER the SQL-template cache. The
24
+ * cache stores commentless text, so its keys, its hit rate and the lockstep
25
+ * checks are untouched by any comment, and a per-request value can never grow
26
+ * it. `$on('query')` events keep `sql` commentless and carry the comment in
27
+ * their own field.
28
+ *
29
+ * PREPARED-STATEMENT NAMES. node-postgres refuses to reuse a statement name for
30
+ * different text on one connection, and every name a connection prepares stays
31
+ * on the server until the connection closes. So:
32
+ *
33
+ * - no comment: the template's own name, as before;
34
+ * - static, model, action and tag only (constant per call site and label):
35
+ * `c_<16 hex>`, a hash of the template name and the comment, claimed from
36
+ * the same process-wide budget as every other name
37
+ * (`PREPARED_STATEMENT_BUDGET`), so the server-side cost stays bounded
38
+ * however many labels there are;
39
+ * - any dynamic value (a trace id, unique per request): unnamed, parsed on
40
+ * every execution and never kept. A per-request value under a name would
41
+ * prepare a new statement on every pooled connection for every request.
42
+ *
43
+ * Leaf apart from errors.ts, logger.ts, query-events.ts (the `$tag` scope) and
44
+ * utils.ts (the name budget); nothing here is Node-only.
45
+ */
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.SqlCommenter = exports.MAX_SQL_COMMENT_VALUE_LENGTH = exports.SQL_COMMENT_KEY_PATTERN = void 0;
48
+ exports.resolveSqlComment = resolveSqlComment;
49
+ exports.withoutTrailingComment = withoutTrailingComment;
50
+ exports.commentDeferred = commentDeferred;
51
+ const errors_js_1 = require("../errors.js");
52
+ const logger_js_1 = require("../logger.js");
53
+ const query_events_js_1 = require("../query-events.js");
54
+ const utils_js_1 = require("./utils.js");
55
+ const warn_registry_js_1 = require("./warn-registry.js");
56
+ /** What a comment key must look like. Lower case, so it can never open `/*!` or `/*+`. */
57
+ exports.SQL_COMMENT_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
58
+ /** Longest value accepted, in characters before encoding. */
59
+ exports.MAX_SQL_COMMENT_VALUE_LENGTH = 256;
60
+ /** The keys `model: true` and `tag: true` fill in. */
61
+ const MODEL_KEYS = ['action', 'model'];
62
+ const TAG_KEY = 'tag';
63
+ const OPTION_KEYS = new Set(['static', 'model', 'tag', 'dynamic']);
64
+ /** Once-only warning namespace for dropped dynamic values. */
65
+ const DROPPED_WARN_NS = 'sqlCommentDropped';
66
+ /**
67
+ * `value` URL-encoded with `'` escaped, or `undefined` when it cannot be encoded
68
+ * (a lone surrogate). `encodeURIComponent` leaves `'` as is and never emits a
69
+ * backslash; the escape covers both anyway, so it stays complete on its own.
70
+ */
71
+ function encodeValue(value) {
72
+ try {
73
+ return encodeURIComponent(value).replace(/[\\']/g, '\\$&');
74
+ }
75
+ catch {
76
+ return undefined;
77
+ }
78
+ }
79
+ function render(pairs) {
80
+ let out = '/*';
81
+ for (let i = 0; i < pairs.length; i++) {
82
+ const [key, value] = pairs[i];
83
+ out += `${i === 0 ? '' : ','}${key}='${value}'`;
84
+ }
85
+ return `${out}*/`;
86
+ }
87
+ function byKey(a, b) {
88
+ return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
89
+ }
90
+ function isPlainObject(value) {
91
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
92
+ return false;
93
+ const proto = Object.getPrototypeOf(value);
94
+ return proto === Object.prototype || proto === null;
95
+ }
96
+ /** A short, printable rendering of a caller's key for a message. */
97
+ function describeKey(key) {
98
+ return JSON.stringify(key.length > 64 ? `${key.slice(0, 64)}...` : key);
99
+ }
100
+ /**
101
+ * Builds the comment for each statement and the name it is sent under. One per
102
+ * client, shared by every table accessor and transaction of that client.
103
+ */
104
+ class SqlCommenter {
105
+ logger;
106
+ /** Static pairs, encoded and sorted. */
107
+ staticPairs;
108
+ /** Keys a dynamic value may not take: the static ones and the ones model / tag fill in. */
109
+ reservedKeys;
110
+ withModel;
111
+ withTag;
112
+ dynamic;
113
+ /** The comment when it depends on nothing per statement (no model, no tag). */
114
+ constant;
115
+ /** Rendered model / action / tag comments. Bounded: tags may come from data. */
116
+ rendered = new utils_js_1.LRUCache(1000);
117
+ /** Claimed `c_` names by template name and comment. Holds only claimed names, so the budget bounds it. */
118
+ names = new Map();
119
+ /**
120
+ * Every statement gets a comment (a static key, or the model and action),
121
+ * so a template's own name is never sent and need not be claimed from the
122
+ * budget; the commented name is claimed instead.
123
+ */
124
+ alwaysComments;
125
+ constructor(staticPairs, withModel, withTag, dynamic, logger) {
126
+ this.logger = logger;
127
+ this.staticPairs = [...staticPairs].sort(byKey);
128
+ this.withModel = withModel;
129
+ this.withTag = withTag;
130
+ this.dynamic = dynamic;
131
+ const reserved = new Set(this.staticPairs.map(([k]) => k));
132
+ if (withModel)
133
+ for (const k of MODEL_KEYS)
134
+ reserved.add(k);
135
+ if (withTag)
136
+ reserved.add(TAG_KEY);
137
+ this.reservedKeys = reserved;
138
+ this.alwaysComments = this.staticPairs.length > 0 || withModel;
139
+ this.constant =
140
+ !withModel && !withTag && this.staticPairs.length > 0
141
+ ? { comment: render(this.staticPairs), dynamic: false }
142
+ : undefined;
143
+ }
144
+ /**
145
+ * The comment for one statement of operation `action` on `model`, or
146
+ * `undefined` when it carries none. Reads the `$tag` scope and calls
147
+ * `dynamic` in the caller's async context, so call it where the statement
148
+ * is sent.
149
+ */
150
+ commentFor(model, action) {
151
+ let pairs;
152
+ let base;
153
+ if (this.withModel || this.withTag) {
154
+ const tag = this.withTag ? (0, query_events_js_1.currentQueryTag)() : undefined;
155
+ const key = `${model}\u0000${action}\u0000${tag ?? ''}`;
156
+ let hit = this.rendered.get(key);
157
+ if (hit === undefined) {
158
+ const built = [...this.staticPairs];
159
+ if (this.withModel) {
160
+ built.push(['action', encodeValue(action) ?? ''], ['model', encodeValue(model) ?? '']);
161
+ }
162
+ // A `$tag` label is 1 to 128 characters, checked when the scope opens.
163
+ const encodedTag = tag === undefined ? undefined : encodeValue(tag);
164
+ if (encodedTag !== undefined)
165
+ built.push([TAG_KEY, encodedTag]);
166
+ built.sort(byKey);
167
+ hit = { pairs: built, comment: built.length > 0 ? render(built) : '' };
168
+ this.rendered.set(key, hit);
169
+ }
170
+ pairs = hit.pairs;
171
+ base = hit.comment === '' ? undefined : { comment: hit.comment, dynamic: false };
172
+ }
173
+ else {
174
+ pairs = this.staticPairs;
175
+ base = this.constant;
176
+ }
177
+ if (this.dynamic === undefined)
178
+ return base;
179
+ const extra = this.dynamicPairs();
180
+ if (extra === undefined)
181
+ return base;
182
+ return { comment: render([...pairs, ...extra].sort(byKey)), dynamic: true };
183
+ }
184
+ /**
185
+ * The name a statement with comment `c` is sent under, given its template's
186
+ * name. A template sent unnamed stays unnamed; a dynamic comment is unnamed;
187
+ * anything else gets a stable `c_` name from the process-wide budget, or
188
+ * goes unnamed once that is spent.
189
+ */
190
+ preparedName(templateName, c) {
191
+ if (!templateName)
192
+ return templateName;
193
+ if (c.dynamic)
194
+ return '';
195
+ const key = `${templateName}\u0000${c.comment}`;
196
+ const known = this.names.get(key);
197
+ if (known !== undefined)
198
+ return known;
199
+ const name = (0, utils_js_1.claimPreparedName)(`c_${(0, utils_js_1.fnv1a64Hex)(key)}`);
200
+ if (name !== '')
201
+ this.names.set(key, name);
202
+ return name;
203
+ }
204
+ /** The usable dynamic pairs, encoded, or `undefined` when there are none. */
205
+ dynamicPairs() {
206
+ let values;
207
+ try {
208
+ values = this.dynamic?.();
209
+ }
210
+ catch (err) {
211
+ this.dropped('threw', '', `sqlComment.dynamic threw (${err instanceof Error ? err.message : String(err)}), so ` +
212
+ 'this statement carries no dynamic values. The statement itself ran as usual.');
213
+ return undefined;
214
+ }
215
+ if (values === undefined || values === null)
216
+ return undefined;
217
+ if (typeof values !== 'object' || Array.isArray(values)) {
218
+ this.dropped('shape', '', 'sqlComment.dynamic returned something other than an object of string values, so this statement carries ' +
219
+ 'no dynamic values. Return an object such as { traceparent: "00-..." }, or undefined for none.');
220
+ return undefined;
221
+ }
222
+ let out;
223
+ for (const key of Object.keys(values)) {
224
+ const value = values[key];
225
+ if (value === undefined)
226
+ continue;
227
+ const reason = this.refusal(key, value);
228
+ if (reason !== undefined) {
229
+ this.dropped(reason[0], key, `sqlComment.dynamic key ${describeKey(key)} was left out of the comment: ${reason[1]}`);
230
+ continue;
231
+ }
232
+ const encoded = encodeValue(value);
233
+ if (encoded === undefined) {
234
+ this.dropped('encoding', key, `sqlComment.dynamic key ${describeKey(key)} was left out of the comment: its value is not well-formed ` +
235
+ 'Unicode (a lone surrogate), so it cannot be URL-encoded.');
236
+ continue;
237
+ }
238
+ if (out === undefined)
239
+ out = [];
240
+ out.push([key, encoded]);
241
+ }
242
+ return out;
243
+ }
244
+ /** Why a dynamic key or value cannot be used, as [warning key, sentence], or `undefined`. */
245
+ refusal(key, value) {
246
+ if (!exports.SQL_COMMENT_KEY_PATTERN.test(key)) {
247
+ return ['key', 'a key must be lower-case letters, digits and underscores, starting with a letter.'];
248
+ }
249
+ if (this.reservedKeys.has(key)) {
250
+ return ['reserved', 'that key is already set by sqlComment.static, model or tag, which take precedence.'];
251
+ }
252
+ if (typeof value !== 'string')
253
+ return ['type', `its value is ${typeof value}, not a string.`];
254
+ if (value.length > exports.MAX_SQL_COMMENT_VALUE_LENGTH) {
255
+ return ['length', `its value is ${value.length} characters, over the ${exports.MAX_SQL_COMMENT_VALUE_LENGTH} allowed.`];
256
+ }
257
+ return undefined;
258
+ }
259
+ /** One warning per reason and key, so a hook that misbehaves on every statement says it once. */
260
+ dropped(reason, key, message) {
261
+ if (!(0, warn_registry_js_1.shouldWarnOnce)(DROPPED_WARN_NS, `${reason}|${key.slice(0, 64)}`))
262
+ return;
263
+ (0, logger_js_1.logTo)(this.logger, 'warn', message, { code: 'sql-comment-dropped', reason });
264
+ }
265
+ }
266
+ exports.SqlCommenter = SqlCommenter;
267
+ /**
268
+ * Validate `TurbineConfig.sqlComment` and build its commenter, or `undefined`
269
+ * when there is nothing to add. Throws `ValidationError` (E003) for a malformed
270
+ * option, and `UnsupportedFeatureError` (E017) where it cannot be honoured:
271
+ *
272
+ * - a backend that builds no SQL (PowDB, which injects its own query
273
+ * interface) has nothing to comment;
274
+ * - `dynamic` on an engine that prepares every statement text
275
+ * (`preparesEveryStatement`), which is MySQL: the MySQL engine sends each
276
+ * parameterized statement through mysql2's `execute()`, which keeps one
277
+ * SERVER-side prepared
278
+ * statement per distinct text per connection (up to 16,000 each), and the
279
+ * server caps the total across every client at `max_prepared_stmt_count`
280
+ * (16,382 by default). A value unique per request makes every text
281
+ * distinct, so a busy pool reaches that cap (error 1461) and every client
282
+ * of the server then fails to prepare.
283
+ */
284
+ function resolveSqlComment(value, logger, engine) {
285
+ if (value === undefined)
286
+ return undefined;
287
+ if (!isPlainObject(value)) {
288
+ throw new errors_js_1.ValidationError('Invalid sqlComment: expected an object with any of `static`, `model`, `tag` and `dynamic`, for example ' +
289
+ "{ static: { application: 'billing' }, model: true }.");
290
+ }
291
+ for (const key of Object.keys(value)) {
292
+ if (!OPTION_KEYS.has(key)) {
293
+ throw new errors_js_1.ValidationError(`Invalid sqlComment: unknown key ${describeKey(key)}. The keys are \`static\`, \`model\`, \`tag\` and \`dynamic\`.`);
294
+ }
295
+ }
296
+ const { static: staticValues, model, tag, dynamic } = value;
297
+ for (const [name, flag] of [
298
+ ['model', model],
299
+ ['tag', tag],
300
+ ]) {
301
+ if (flag !== undefined && typeof flag !== 'boolean') {
302
+ throw new errors_js_1.ValidationError(`Invalid sqlComment.${name}: expected true or false, got ${typeof flag}.`);
303
+ }
304
+ }
305
+ if (dynamic !== undefined && typeof dynamic !== 'function') {
306
+ throw new errors_js_1.ValidationError(`Invalid sqlComment.dynamic: expected a function returning an object of string values, got ${typeof dynamic}.`);
307
+ }
308
+ const pairs = [];
309
+ if (staticValues !== undefined) {
310
+ if (!isPlainObject(staticValues)) {
311
+ throw new errors_js_1.ValidationError("Invalid sqlComment.static: expected an object of string values, for example { application: 'billing' }.");
312
+ }
313
+ for (const key of Object.keys(staticValues)) {
314
+ const v = staticValues[key];
315
+ const where = `sqlComment.static key ${describeKey(key)}`;
316
+ if (!exports.SQL_COMMENT_KEY_PATTERN.test(key)) {
317
+ throw new errors_js_1.ValidationError(`Invalid ${where}: a key must be lower-case letters, digits and underscores, starting with a letter ` +
318
+ '(the comment must never open a MySQL executable or hint comment, `/*!` or `/*+`).');
319
+ }
320
+ if ((model === true && MODEL_KEYS.includes(key)) || (tag === true && key === TAG_KEY)) {
321
+ throw new errors_js_1.ValidationError(`Invalid ${where}: \`${key}\` is filled in by sqlComment.${key === TAG_KEY ? 'tag' : 'model'}. ` +
322
+ 'Rename the static key or turn that option off.');
323
+ }
324
+ if (typeof v !== 'string') {
325
+ throw new errors_js_1.ValidationError(`Invalid ${where}: expected a string value, got ${typeof v}.`);
326
+ }
327
+ if (v.length > exports.MAX_SQL_COMMENT_VALUE_LENGTH) {
328
+ throw new errors_js_1.ValidationError(`Invalid ${where}: the value is ${v.length} characters, over the ${exports.MAX_SQL_COMMENT_VALUE_LENGTH} allowed.`);
329
+ }
330
+ const encoded = encodeValue(v);
331
+ if (encoded === undefined) {
332
+ throw new errors_js_1.ValidationError(`Invalid ${where}: the value is not well-formed Unicode (a lone surrogate), so it cannot be URL-encoded.`);
333
+ }
334
+ pairs.push([key, encoded]);
335
+ }
336
+ }
337
+ if (pairs.length === 0 && model !== true && tag !== true && dynamic === undefined)
338
+ return undefined;
339
+ if (!engine.buildsSql) {
340
+ throw new errors_js_1.UnsupportedFeatureError('sqlComment', engine.dialect, 'This engine does not run SQL, so there is no statement to carry a comment. Remove the option for this client.');
341
+ }
342
+ if (dynamic !== undefined && engine.preparesEveryStatement) {
343
+ throw new errors_js_1.UnsupportedFeatureError('sqlComment.dynamic', engine.dialect, 'The MySQL engine prepares each distinct statement text on the server (mysql2 execute(), up to 16,000 per ' +
344
+ 'connection), and the server caps prepared statements across all clients at max_prepared_stmt_count ' +
345
+ '(16,382 by default). A per-statement value makes every text distinct, so a busy pool reaches that cap ' +
346
+ '(error 1461) and prepares start failing server-wide. Use `static`, `model` and `tag`, which are bounded.');
347
+ }
348
+ return new SqlCommenter(pairs, model === true, tag === true, dynamic, logger);
349
+ }
350
+ /**
351
+ * `sql` with a comment Turbine appended removed, for code that reads statement
352
+ * text to decide how to run it (an engine shim telling a row-returning
353
+ * statement from a change count). A value can never contain `/*` (it is
354
+ * URL-encoded), so the last ` /*` of a statement ending in `*\/` is the
355
+ * comment's own opener; text Turbine did not comment is returned unchanged
356
+ * unless it, too, ends in a block comment, which is equally not code.
357
+ */
358
+ function withoutTrailingComment(sql) {
359
+ if (!sql.endsWith('*/'))
360
+ return sql;
361
+ const at = sql.lastIndexOf(' /*');
362
+ return at < 0 ? sql : sql.slice(0, at);
363
+ }
364
+ /**
365
+ * `q` as sent with `comment`: its statement, and every statement of its
366
+ * reselect plan (MySQL), carry the comment. Everything else on the query is
367
+ * kept. Used by the batch paths (`pipeline`, `$transaction([...])`), which run
368
+ * deferred queries without going through a query interface.
369
+ */
370
+ function commentDeferred(q, comment) {
371
+ if (comment === undefined)
372
+ return q;
373
+ const reselect = q.reselect;
374
+ return {
375
+ ...q,
376
+ sql: `${q.sql} ${comment}`,
377
+ ...(reselect
378
+ ? { reselect: (exec) => reselect((sql, params, name) => exec(`${sql} ${comment}`, params, name)) }
379
+ : {}),
380
+ };
381
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * turbine-orm: the abandoned-stream idle guard.
3
+ *
4
+ * A cursor stream holds a checked-out connection between pulls (and, on
5
+ * PostgreSQL, an open transaction whose snapshot pins the xmin horizon for the
6
+ * whole database). A consumer that stops pulling without calling `return()`
7
+ * leaves the stream suspended at a `yield` forever, and a suspended generator
8
+ * runs no code, so nothing would ever release either. This guard is the code
9
+ * that runs instead: a timer that notices the consumer has asked for nothing for
10
+ * the configured time and closes the stream from outside.
11
+ *
12
+ * The consuming generator owns the {@link StreamIdleState}: it sets `suspended`
13
+ * around each yield and bumps `pulls` when the consumer resumes it. That is two
14
+ * writes per yield and no timer work, which is what keeps a per-row stream as
15
+ * cheap as before (a timer refreshed per row costs about 15% per row; a
16
+ * `setTimeout` per row more than doubles it).
17
+ *
18
+ * Leaf module: imports errors.ts only, so it stays in the serverless graph
19
+ * without pulling anything node-only.
20
+ */
21
+ import { TimeoutError } from '../errors.js';
22
+ /** The default for `streamIdleTimeoutMs` (client) and `idleTimeoutMs` (per call). */
23
+ export declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300000;
24
+ /**
25
+ * How much later than the idle timeout the PostgreSQL server-side limit fires.
26
+ * The client timer always expires a stream first (it ticks at most every
27
+ * {@link MAX_TICK_MS}, half this grace), so the server limit only acts when the
28
+ * process has stopped running timers: frozen between serverless invocations,
29
+ * or an event loop blocked for longer than the grace.
30
+ */
31
+ export declare const STREAM_IDLE_BACKSTOP_GRACE_MS = 10000;
32
+ /**
33
+ * The largest idle timeout accepted. PostgreSQL's
34
+ * `idle_in_transaction_session_timeout` is a 32-bit count of milliseconds, and
35
+ * the backstop adds the grace to it.
36
+ */
37
+ export declare const MAX_STREAM_IDLE_TIMEOUT_MS: number;
38
+ /**
39
+ * Validate an idle timeout: a whole number of milliseconds from 0 (disabled)
40
+ * to {@link MAX_STREAM_IDLE_TIMEOUT_MS}, else E003. `undefined` means "not
41
+ * set here", so the caller falls back to the next level.
42
+ */
43
+ export declare function resolveStreamIdleTimeout(value: unknown, name: string): number | undefined;
44
+ /**
45
+ * What the generator that yields to the consumer tells the guard. Written
46
+ * around every yield; read by the guard's timer.
47
+ */
48
+ export interface StreamIdleState {
49
+ /** How many times the consumer has resumed the stream. */
50
+ pulls: number;
51
+ /** The stream is suspended at a yield, waiting for the consumer to ask for more. */
52
+ suspended: boolean;
53
+ /** Rows handed to the consumer so far, for the log line. */
54
+ rows: number;
55
+ /** Set when the guard closed the stream; the next pull throws it. */
56
+ expired: TimeoutError | undefined;
57
+ }
58
+ export declare function newStreamIdleState(): StreamIdleState;
59
+ /** The E002 the next pull on an expired stream throws. */
60
+ export declare function streamIdleExpiredError(action: string, table: string, timeoutMs: number, unit: string): TimeoutError;
61
+ /**
62
+ * The PostgreSQL server-side limit for a stream's own transaction:
63
+ * `idle_in_transaction_session_timeout` set to the idle timeout plus the grace,
64
+ * TRANSACTION-LOCAL (`set_config(..., true)`), so it ends with the stream's
65
+ * COMMIT and cannot reach another client through a transaction pooler.
66
+ *
67
+ * A stricter limit already in force (a role or server setting below ours) is
68
+ * kept, so a stream never loosens what the operator configured. The value is a
69
+ * validated integer, never caller text: a GUC value cannot be a bind parameter.
70
+ * Reading `pg_settings` rather than calling `set_config` directly also means an
71
+ * engine without the setting gets no row and no error.
72
+ */
73
+ export declare function streamBackstopStatement(timeoutMs: number): string;
74
+ /**
75
+ * The timer half of the guard. One per open cursor stream, `unref()`ed so it
76
+ * never keeps a process alive.
77
+ *
78
+ * It ticks every `min(timeout / 4, 5 s)`. A tick that finds the stream
79
+ * suspended with no pull since the previous tick counts as idle; enough idle
80
+ * ticks in a row to cover the timeout expire the stream, so expiry lands
81
+ * between 1x the timeout and the timeout plus one tick. Time Turbine spends
82
+ * fetching never counts, because the stream is not suspended then.
83
+ *
84
+ * With `keepalive` set (PostgreSQL, where {@link streamBackstopStatement} is
85
+ * in force), a tick also sends a no-op statement when the connection has been
86
+ * quiet for half the timeout. A consumer that pulls rows of an already-fetched
87
+ * batch slowly is alive while the server sees an idle transaction; without the
88
+ * keepalive the server limit would end a stream the consumer is still reading.
89
+ * The server limit therefore only fires when this timer cannot run.
90
+ */
91
+ export declare class StreamIdleGuard {
92
+ private readonly state;
93
+ private readonly timeoutMs;
94
+ private readonly expiredError;
95
+ private readonly onExpire;
96
+ private readonly keepalive?;
97
+ private readonly timer;
98
+ private readonly idleTicksToExpire;
99
+ private lastPulls;
100
+ private idleTicks;
101
+ private lastStatementAt;
102
+ private statementsInFlight;
103
+ private keepaliveInFlight;
104
+ /** Settles once an expired stream's cleanup has run (connection released). */
105
+ expiry: Promise<void> | undefined;
106
+ constructor(state: StreamIdleState, timeoutMs: number, expiredError: () => TimeoutError, onExpire: () => Promise<void>, keepalive?: (() => Promise<unknown>) | undefined);
107
+ /** Bracket a statement on the stream's connection, so the keepalive knows it is busy. */
108
+ statementStarted(): void;
109
+ statementSettled(): void;
110
+ stop(): void;
111
+ private tick;
112
+ }
113
+ /** The connection surface a stream drives; structurally `StreamableConnection` in dialect.ts. */
114
+ interface StreamConnection {
115
+ query(text: string, values?: unknown[]): Promise<{
116
+ rows: Record<string, unknown>[];
117
+ }>;
118
+ }
119
+ /**
120
+ * The stream's connection, as the dialect sees it, with two additions: every
121
+ * statement is reported to the guard (for the keepalive), and, when `backstop`
122
+ * is given, the dialect's own BEGIN carries {@link streamBackstopStatement} in
123
+ * the same simple-protocol round trip, so the limit is in force from the
124
+ * transaction's first statement at no extra cost. Matching the exact BEGIN text
125
+ * the dialect emits keeps the dialect itself unchanged; the live backstop test
126
+ * fails if that text ever stops matching.
127
+ */
128
+ export declare function watchStreamConnection(base: StreamConnection, guard: () => StreamIdleGuard | undefined, backstop?: {
129
+ begin: string;
130
+ statement: string;
131
+ }): StreamConnection;
132
+ export {};