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
@@ -10,6 +10,7 @@
10
10
  import pg from '#pg';
11
11
  import { postgresDialect } from './dialect.js';
12
12
  import { ValidationError } from './errors.js';
13
+ import { consoleLogger, logTo } from './logger.js';
13
14
  import { isDateType, pgTypeToTs, singularize, snakeToCamel, } from './schema.js';
14
15
  /**
15
16
  * Map a `pg_constraint.confdeltype` / `confupdtype` character to a
@@ -328,6 +329,58 @@ export function defaultExcludedTablesPresent(names, options = {}) {
328
329
  */
329
330
  // biome-ignore lint/suspicious/noControlCharactersInRegex: detecting control characters is this pattern's purpose
330
331
  const UNSAFE_CATALOG_CHARS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
332
+ /**
333
+ * Characters that must not reach a terminal or a log verbatim: everything in
334
+ * {@link UNSAFE_CATALOG_CHARS} (an ESC starts an ANSI sequence, a carriage
335
+ * return overwrites the line), plus the bidirectional controls that reorder how
336
+ * a name is displayed without changing what it is.
337
+ */
338
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: neutralizing control characters is this pattern's purpose
339
+ const TERMINAL_UNSAFE_CHARS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029\u200e\u200f\u202a-\u202e\u2066-\u2069]/g;
340
+ /**
341
+ * A catalog name made safe to PRINT. Every diagnostic that quotes a table,
342
+ * column, enum, relation or constraint name goes through this, because Postgres
343
+ * lets anyone with CREATE privilege pick any characters for those, and an
344
+ * unescaped name in an error message is a way to write escape sequences to the
345
+ * developer's terminal. Control and bidi characters become visible escapes
346
+ * (`\n`, `\u001b`); everything else, including non-ASCII letters, is untouched.
347
+ */
348
+ export function printableCatalogName(name) {
349
+ return name.replace(TERMINAL_UNSAFE_CHARS, (ch) => {
350
+ if (ch === '\n')
351
+ return '\\n';
352
+ if (ch === '\r')
353
+ return '\\r';
354
+ if (ch === '\t')
355
+ return '\\t';
356
+ return `\\u${ch.charCodeAt(0).toString(16).padStart(4, '0')}`;
357
+ });
358
+ }
359
+ /**
360
+ * An empty record for something keyed by catalog names. Prototype-less on
361
+ * purpose: a plain `{}` turns a table named `__proto__` into a prototype
362
+ * assignment (the table silently disappears), and makes `enums['constructor']`
363
+ * truthy before any enum was ever added.
364
+ */
365
+ export function nullRecord() {
366
+ return Object.create(null);
367
+ }
368
+ /**
369
+ * The TypeScript type for a catalog type name. A user-defined type can be named
370
+ * after an `Object.prototype` member (`toString`, `constructor`, `valueOf`),
371
+ * which a plain-object type table answers with a function instead of nothing, so
372
+ * that case is pinned to `unknown` here, before any type table is consulted,
373
+ * rather than emitted as function source. `lookup` is the dialect's own mapping
374
+ * for every other name.
375
+ */
376
+ function tsTypeForCatalogType(typeName, nullable, lookup = pgTypeToTs) {
377
+ const isArray = typeName.startsWith('_');
378
+ if ((isArray ? typeName.slice(1) : typeName) in Object.prototype) {
379
+ const unknownType = isArray ? 'unknown[]' : 'unknown';
380
+ return nullable ? `${unknownType} | null` : unknownType;
381
+ }
382
+ return lookup(typeName, nullable);
383
+ }
331
384
  /**
332
385
  * Refuse one catalog identifier carrying a character from
333
386
  * {@link UNSAFE_CATALOG_CHARS}. `subject` names the object in the error.
@@ -337,7 +390,7 @@ export function assertSafeCatalogIdentifier(name, subject) {
337
390
  if (match === null)
338
391
  return;
339
392
  const code = match[0].charCodeAt(0).toString(16).padStart(4, '0');
340
- throw new ValidationError(`Refusing to introspect ${subject}: its name contains the control character U+${code.toUpperCase()} ` +
393
+ throw new ValidationError(`Refusing to introspect ${printableCatalogName(subject)}: its name contains the control character U+${code.toUpperCase()} ` +
341
394
  `at position ${match.index}. Such a name cannot be safely emitted into generated code, SQL comments, or ` +
342
395
  `tooling output. Rename the database object, or exclude it from introspection.`);
343
396
  }
@@ -397,14 +450,15 @@ export function assertSafeCatalogSchema(schema) {
397
450
  /**
398
451
  * Introspect a database into {@link SchemaMetadata}, routing through the active
399
452
  * dialect's {@link Dialect.introspector} so each engine can override the catalog
400
- * SQL. PostgreSQL is driven by {@link introspectPostgresCatalog}.
453
+ * SQL. A dialect with none, PostgreSQL included, is read by
454
+ * {@link introspectPostgresCatalog}.
401
455
  */
402
456
  export async function introspect(options) {
403
457
  const dialect = options.dialect ?? postgresDialect;
404
458
  const introspector = dialect.introspector;
405
459
  const schema = introspector
406
460
  ? await introspector.introspect(options)
407
- : // Dialects without an introspector fall back to the Postgres catalog reader.
461
+ : // PostgreSQL, and any dialect without an introspector, reads the Postgres catalog.
408
462
  await introspectPostgresCatalog(options);
409
463
  // Applied here rather than inside each introspector so every engine gets it.
410
464
  const renamed = options.relationNames ? applyRelationRenames(schema, options.relationNames) : schema;
@@ -430,18 +484,18 @@ export async function introspect(options) {
430
484
  * means the call sites it was supposed to fix break at runtime instead.
431
485
  */
432
486
  export function applyRelationRenames(schema, renames) {
433
- const tables = { ...schema.tables };
487
+ const tables = Object.assign(nullRecord(), schema.tables);
434
488
  for (const [table, mapping] of Object.entries(renames)) {
435
- const meta = tables[table];
489
+ const meta = Object.hasOwn(tables, table) ? tables[table] : undefined;
436
490
  if (!meta) {
437
- throw new ValidationError(`relationNames: unknown table "${table}". Known tables: ${Object.keys(tables).join(', ')}.`);
491
+ throw new ValidationError(`relationNames: unknown table "${printableCatalogName(table)}". Known tables: ${Object.keys(tables).map(printableCatalogName).join(', ')}.`);
438
492
  }
439
- const relations = { ...meta.relations };
493
+ const relations = Object.assign(nullRecord(), meta.relations);
440
494
  const columnFields = new Set(Object.values(meta.columnMap));
441
495
  for (const [from, to] of Object.entries(mapping)) {
442
496
  if (!Object.hasOwn(relations, from)) {
443
- throw new ValidationError(`relationNames: table "${table}" has no relation "${from}". ` +
444
- `Derived relations: ${Object.keys(relations).join(', ') || '(none)'}.`);
497
+ throw new ValidationError(`relationNames: table "${printableCatalogName(table)}" has no relation "${printableCatalogName(from)}". ` +
498
+ `Derived relations: ${Object.keys(relations).map(printableCatalogName).join(', ') || '(none)'}.`);
445
499
  }
446
500
  if (from === to)
447
501
  continue;
@@ -494,21 +548,21 @@ export function assertDistinctColumnFields(tableName, columns) {
494
548
  for (const [field, names] of columnsByField) {
495
549
  if (names.length < 2)
496
550
  continue;
497
- const quoted = names.map((n) => `"${n}"`);
551
+ const quoted = names.map((n) => `"${printableCatalogName(n)}"`);
498
552
  const list = `${quoted.slice(0, -1).join(', ')} and ${quoted[quoted.length - 1]}`;
499
- collisions.push(`columns ${list} ${names.length === 2 ? 'both' : 'all'} resolve to the field "${field}"`);
553
+ collisions.push(`columns ${list} ${names.length === 2 ? 'both' : 'all'} resolve to the field "${printableCatalogName(field)}"`);
500
554
  }
501
555
  if (collisions.length === 0)
502
556
  return;
503
- throw new ValidationError(`Field collision on table "${tableName}": ${collisions.join('; ')}. A client addresses one column per field, ` +
557
+ throw new ValidationError(`Field collision on table "${printableCatalogName(tableName)}": ${collisions.join('; ')}. A client addresses one column per field, ` +
504
558
  'so reads would fold the colliding columns into one property and a write to that field would reach only ' +
505
559
  'one of them. Rename one of the columns, or set `keepColumnNames: true` in turbine.config.ts ' +
506
560
  '(`turbine generate --keep-column-names`) so every field is its raw column name.');
507
561
  }
508
562
  /**
509
563
  * PostgreSQL catalog introspector: reads information_schema + pg_catalog and
510
- * produces {@link SchemaMetadata}. This is the implementation wrapped by
511
- * `postgresDialect.introspector`; call {@link introspect} for dialect routing.
564
+ * produces {@link SchemaMetadata}. {@link introspect} calls it for PostgreSQL;
565
+ * call {@link introspect} for dialect routing.
512
566
  */
513
567
  export async function introspectPostgresCatalog(options) {
514
568
  const schema = options.schema ?? 'public';
@@ -581,8 +635,7 @@ export async function introspectPostgresCatalog(options) {
581
635
  field: options.keepColumnNames ? row.column_name : snakeToCamel(row.column_name),
582
636
  dialectType,
583
637
  pgType: dialectType,
584
- tsType: dialect.typeToTypeScript?.(isArray ? dialectType : baseType, isNullable) ??
585
- pgTypeToTs(isArray ? dialectType : baseType, isNullable),
638
+ tsType: tsTypeForCatalogType(isArray ? dialectType : baseType, isNullable, dialect.typeToTypeScript?.bind(dialect) ?? pgTypeToTs),
586
639
  nullable: isNullable,
587
640
  hasDefault: row.column_default !== null,
588
641
  // Server-generated = a sequence default (serial/BIGSERIAL → nextval(…))
@@ -678,9 +731,9 @@ export async function introspectPostgresCatalog(options) {
678
731
  });
679
732
  }
680
733
  // ----- Collect enums -----
681
- const enums = {};
734
+ const enums = nullRecord();
682
735
  for (const row of enumResult.rows) {
683
- if (!enums[row.typname])
736
+ if (!Object.hasOwn(enums, row.typname))
684
737
  enums[row.typname] = [];
685
738
  enums[row.typname].push(row.enumlabel);
686
739
  }
@@ -726,9 +779,9 @@ export async function introspectPostgresCatalog(options) {
726
779
  }
727
780
  const foreignKeys = Array.from(fkGroups.values());
728
781
  if (crossSchemaRefs.length > 0) {
729
- console.warn(`[turbine] Skipped ${crossSchemaRefs.length} foreign key(s) referencing a table outside schema "${schema}": ` +
730
- `${crossSchemaRefs.join(', ')}. Generated clients address tables by bare name within one schema, so no ` +
731
- `relation is emitted for these. Introspect the other schema separately, or add the target table to this one.`);
782
+ logTo(options.logger ?? consoleLogger, 'warn', `Skipped ${crossSchemaRefs.length} foreign key(s) referencing a table outside schema "${printableCatalogName(schema)}": ` +
783
+ `${crossSchemaRefs.map(printableCatalogName).join(', ')}. Generated clients address tables by bare name within one schema, so no ` +
784
+ `relation is emitted for these. Introspect the other schema separately, or add the target table to this one.`, { code: 'introspect-cross-schema-fk', schema, references: crossSchemaRefs.length });
732
785
  }
733
786
  // Referential actions (ON DELETE / ON UPDATE) per constraint. Keyed
734
787
  // "<table>::<constraint>" because the NAME alone is not unique (the same
@@ -761,16 +814,17 @@ export async function introspectPostgresCatalog(options) {
761
814
  enums,
762
815
  fkActions,
763
816
  legacyToManyUniques: options.legacyToManyUniques,
817
+ ...(options.logger !== undefined ? { logger: options.logger } : {}),
764
818
  });
765
819
  // ----- Assemble TableMetadata for each table -----
766
- const tables = {};
820
+ const tables = nullRecord();
767
821
  for (const tableName of tableNames) {
768
822
  const columns = columnsByTable.get(tableName) ?? [];
769
- const columnMap = {};
770
- const reverseColumnMap = {};
823
+ const columnMap = nullRecord();
824
+ const reverseColumnMap = nullRecord();
771
825
  const dateColumns = new Set();
772
- const dialectTypes = {};
773
- const pgTypes = {};
826
+ const dialectTypes = nullRecord();
827
+ const pgTypes = nullRecord();
774
828
  const allColumns = [];
775
829
  for (const col of columns) {
776
830
  columnMap[col.field] = col.name;
@@ -794,7 +848,7 @@ export async function introspectPostgresCatalog(options) {
794
848
  allColumns,
795
849
  primaryKey: pkByTable.get(tableName) ?? [],
796
850
  uniqueColumns: uniqueByTable.get(tableName) ?? [],
797
- relations: relationsByTable.get(tableName) ?? {},
851
+ relations: relationsByTable.get(tableName) ?? nullRecord(),
798
852
  indexes: indexesByTable.get(tableName) ?? [],
799
853
  checks: checksByTable.get(tableName) ?? [],
800
854
  ...(viewNameSet.has(tableName) ? { isView: true } : {}),
@@ -1181,13 +1235,13 @@ export function detectUniqueForeignKeySets(pkByTable, uniqueByTable, indexesByTa
1181
1235
  * colliding name would otherwise shadow a column field and generate types
1182
1236
  * that fail `tsc --strict` (TS2430/TS2322).
1183
1237
  */
1184
- function resolveRelationNameCollision(candidate, taken, table, source) {
1238
+ function resolveRelationNameCollision(candidate, taken, table, source, logger) {
1185
1239
  if (!taken.has(candidate))
1186
1240
  return candidate;
1187
1241
  let name = `${candidate}Rel`;
1188
1242
  for (let i = 2; taken.has(name); i++)
1189
1243
  name = `${candidate}Rel${i}`;
1190
- console.warn(`[turbine] Relation name "${candidate}" on table "${table}" (from ${source}) collides with an existing column or relation, using "${name}" instead.`);
1244
+ logTo(logger, 'warn', printableCatalogName(`Relation name "${candidate}" on table "${table}" (from ${source}) collides with an existing column or relation, using "${name}" instead.`), { code: 'introspect-relation-renamed', table, relation: name, wanted: candidate });
1191
1245
  return name;
1192
1246
  }
1193
1247
  /**
@@ -1229,7 +1283,7 @@ function resolveRelationNameCollision(candidate, taken, table, source) {
1229
1283
  * engine introspectors and `defineSchema` path do) to keep every reverse
1230
1284
  * relation `hasMany`.
1231
1285
  */
1232
- export function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActions, unknownTypedFieldsByTable, uniqueSetsByTable) {
1286
+ export function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActions, unknownTypedFieldsByTable, uniqueSetsByTable, logger = consoleLogger) {
1233
1287
  // Count FKs per (source, target) pair for disambiguation.
1234
1288
  const fkCounts = new Map();
1235
1289
  for (const fk of foreignKeys) {
@@ -1265,18 +1319,19 @@ export function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable,
1265
1319
  const taken = takenFor(table);
1266
1320
  if (!taken.has(legacy))
1267
1321
  return legacy;
1268
- // Historical json/jsonb shadow: previously worked at runtime AND compiled
1269
- // (tsType `unknown` absorbs the relation payload). Keep the name, warn -
1270
- // typeSafeRelations() keeps the generated type layer sound.
1322
+ // A relation named like a json/jsonb column keeps that name: it works at
1323
+ // runtime and compiles (the column's `unknown` type absorbs the relation
1324
+ // payload). Keep it and warn; typeSafeRelations() keeps the generated types
1325
+ // sound.
1271
1326
  if (!assignedFor(table).has(legacy) && unknownTypedFieldsByTable?.get(table)?.has(legacy)) {
1272
- console.warn(`[turbine] Relation "${legacy}" on table "${table}" (from ${source}) shadows the json/jsonb column ` +
1327
+ logTo(logger, 'warn', printableCatalogName(`Relation "${legacy}" on table "${table}" (from ${source}) shadows the json/jsonb column ` +
1273
1328
  `"${legacy}", keeping the historical name for runtime compatibility; the relation is omitted from ` +
1274
- `the generated types. Rename the column to expose it.`);
1329
+ `the generated types. Rename the column to expose it.`), { code: 'introspect-relation-shadows-json', table, relation: legacy });
1275
1330
  return legacy;
1276
1331
  }
1277
1332
  if (modern !== null && modern !== legacy && !taken.has(modern))
1278
1333
  return modern;
1279
- return resolveRelationNameCollision(modern ?? legacy, taken, table, source);
1334
+ return resolveRelationNameCollision(modern ?? legacy, taken, table, source, logger);
1280
1335
  };
1281
1336
  for (const fk of foreignKeys) {
1282
1337
  const pairKey = `${fk.sourceTable}→${fk.targetTable}`;
@@ -1325,7 +1380,7 @@ export function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable,
1325
1380
  if (actions?.onUpdate && actions.onUpdate !== 'no action')
1326
1381
  actionFields.onUpdate = actions.onUpdate;
1327
1382
  if (!relationsByTable.has(fk.sourceTable))
1328
- relationsByTable.set(fk.sourceTable, {});
1383
+ relationsByTable.set(fk.sourceTable, nullRecord());
1329
1384
  relationsByTable.get(fk.sourceTable)[belongsToName] = {
1330
1385
  type: 'belongsTo',
1331
1386
  name: belongsToName,
@@ -1366,7 +1421,7 @@ export function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable,
1366
1421
  takenFor(fk.targetTable).add(reverseName);
1367
1422
  assignedFor(fk.targetTable).add(reverseName);
1368
1423
  if (!relationsByTable.has(fk.targetTable))
1369
- relationsByTable.set(fk.targetTable, {});
1424
+ relationsByTable.set(fk.targetTable, nullRecord());
1370
1425
  relationsByTable.get(fk.targetTable)[reverseName] = {
1371
1426
  type: reverseType,
1372
1427
  name: reverseName,
@@ -1405,7 +1460,7 @@ export function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable,
1405
1460
  * - a shadowed concrete-typed column → deterministic `Rel` suffix + warn
1406
1461
  * instead of silently dropping the relation.
1407
1462
  */
1408
- export function addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, columnNamesByTable, relationsByTable, columnFieldsByTable, unknownTypedFieldsByTable, uniqueIndexColsByTable) {
1463
+ export function addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, columnNamesByTable, relationsByTable, columnFieldsByTable, unknownTypedFieldsByTable, uniqueIndexColsByTable, logger = consoleLogger) {
1409
1464
  for (const tableName of tableNames) {
1410
1465
  // FKs whose source is this table, both must be single-column.
1411
1466
  const tableFks = foreignKeys.filter((fk) => fk.sourceTable === tableName);
@@ -1451,22 +1506,22 @@ export function addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, c
1451
1506
  const targetTbl = other.targetTable; // B
1452
1507
  let relName = snakeToCamel(targetTbl); // plural table name → e.g. "tags"
1453
1508
  if (!relationsByTable.has(sourceTbl))
1454
- relationsByTable.set(sourceTbl, {});
1509
+ relationsByTable.set(sourceTbl, nullRecord());
1455
1510
  const existing = relationsByTable.get(sourceTbl);
1456
1511
  // Additive-only: never clobber an existing relation name.
1457
- if (existing[relName])
1512
+ if (Object.hasOwn(existing, relName))
1458
1513
  return;
1459
1514
  const columnFields = columnFieldsByTable?.get(sourceTbl);
1460
1515
  if (columnFields?.has(relName)) {
1461
1516
  if (unknownTypedFieldsByTable?.get(sourceTbl)?.has(relName)) {
1462
- // Historical json/jsonb shadow, worked at runtime, compiled fine.
1463
- console.warn(`[turbine] Relation "${relName}" on table "${sourceTbl}" (junction ${tableName}) shadows the ` +
1517
+ // Same json/jsonb shadow as above: keep the name and warn.
1518
+ logTo(logger, 'warn', printableCatalogName(`Relation "${relName}" on table "${sourceTbl}" (junction ${tableName}) shadows the ` +
1464
1519
  `json/jsonb column "${relName}", keeping the historical name for runtime compatibility; ` +
1465
- `the relation is omitted from the generated types.`);
1520
+ `the relation is omitted from the generated types.`), { code: 'introspect-relation-shadows-json', table: sourceTbl, relation: relName });
1466
1521
  }
1467
1522
  else {
1468
1523
  const taken = new Set([...columnFields, ...Object.keys(existing)]);
1469
- relName = resolveRelationNameCollision(relName, taken, sourceTbl, `junction ${tableName}`);
1524
+ relName = resolveRelationNameCollision(relName, taken, sourceTbl, `junction ${tableName}`, logger);
1470
1525
  }
1471
1526
  }
1472
1527
  existing[relName] = {
@@ -1530,7 +1585,8 @@ export function deriveCatalogRelations(inputs) {
1530
1585
  const uniqueSetsByTable = inputs.legacyToManyUniques
1531
1586
  ? undefined
1532
1587
  : detectUniqueForeignKeySets(pkByTable, uniqueByTable, indexesByTable);
1533
- const relationsByTable = buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, inputs.fkActions, unknownTypedFieldsByTable, uniqueSetsByTable);
1588
+ const logger = inputs.logger ?? consoleLogger;
1589
+ const relationsByTable = buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, inputs.fkActions, unknownTypedFieldsByTable, uniqueSetsByTable, logger);
1534
1590
  // Prisma's implicit m2m junctions have no primary key (just a two-column
1535
1591
  // UNIQUE index over the FK columns), so pass the introspected two-column
1536
1592
  // unique indexes as the fallback junction-key source.
@@ -1559,7 +1615,7 @@ export function deriveCatalogRelations(inputs) {
1559
1615
  if (twoColUniques.length > 0)
1560
1616
  uniqueIndexColsByTable.set(tbl, twoColUniques);
1561
1617
  }
1562
- addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, new Map(Array.from(columnsByTable, ([tbl, cols]) => [tbl, cols.map((c) => c.name)])), relationsByTable, columnFieldsByTable, unknownTypedFieldsByTable, uniqueIndexColsByTable);
1618
+ addAutoManyToManyRelations(tableNames, foreignKeys, pkByTable, new Map(Array.from(columnsByTable, ([tbl, cols]) => [tbl, cols.map((c) => c.name)])), relationsByTable, columnFieldsByTable, unknownTypedFieldsByTable, uniqueIndexColsByTable, logger);
1563
1619
  return relationsByTable;
1564
1620
  }
1565
1621
  /**
@@ -1571,7 +1627,7 @@ export function deriveCatalogRelations(inputs) {
1571
1627
  * relation names for the same logical schema (the engines previously carried
1572
1628
  * stale copies of a retired naming scheme).
1573
1629
  */
1574
- export function deriveEngineRelations(tableNames, foreignKeys, pkByTable, columnsByTable) {
1630
+ export function deriveEngineRelations(tableNames, foreignKeys, pkByTable, columnsByTable, logger = consoleLogger) {
1575
1631
  const tableSet = new Set(tableNames);
1576
1632
  const fks = foreignKeys.filter((fk) => tableSet.has(fk.sourceTable) && tableSet.has(fk.targetTable));
1577
1633
  const columnFieldsByTable = new Map();
@@ -1580,7 +1636,7 @@ export function deriveEngineRelations(tableNames, foreignKeys, pkByTable, column
1580
1636
  columnFieldsByTable.set(tbl, new Set(cols.map((c) => c.field)));
1581
1637
  unknownTypedFieldsByTable.set(tbl, new Set(cols.filter((c) => isUnknownTsType(c.tsType)).map((c) => c.field)));
1582
1638
  }
1583
- const relationsByTable = buildRelationsFromForeignKeys(fks, columnFieldsByTable, undefined, unknownTypedFieldsByTable);
1584
- addAutoManyToManyRelations(tableNames, fks, pkByTable, new Map(Array.from(columnsByTable, ([tbl, cols]) => [tbl, cols.map((c) => c.name)])), relationsByTable, columnFieldsByTable, unknownTypedFieldsByTable);
1639
+ const relationsByTable = buildRelationsFromForeignKeys(fks, columnFieldsByTable, undefined, unknownTypedFieldsByTable, undefined, logger);
1640
+ addAutoManyToManyRelations(tableNames, fks, pkByTable, new Map(Array.from(columnsByTable, ([tbl, cols]) => [tbl, cols.map((c) => c.name)])), relationsByTable, columnFieldsByTable, unknownTypedFieldsByTable, undefined, logger);
1585
1641
  return relationsByTable;
1586
1642
  }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * turbine-orm, every `code` a Turbine log line can carry.
3
+ *
4
+ * A log line's TEXT is not a stable contract; its `code` is. This list is the
5
+ * whole set. Library code imports it as a TYPE only (see `TurbineLogCode` in
6
+ * logger.ts), so an emitter cannot use a code that is not here and the list
7
+ * itself never reaches a bundle. The tests import the runtime array: one test
8
+ * per code, and a source scan that fails on a code nothing emits.
9
+ */
10
+ export declare const LOG_CODES: readonly ["unknown-config-key", "plan-cache-mode-ignored", "prepared-statements-on-pooler", "connection-option-ignored", "pool-error", "query-listener-error", "pool-listener-error", "replica-close-error", "external-pool", "pool-created", "replicas-configured", "pipeline", "raw-sql", "transaction-committed", "transaction-rolled-back", "listen", "notify", "connection-verified", "disconnect-skipped", "pool-disconnected", "typed-sql", "listen-reconnecting", "listen-lost", "parser-overwrite", "untyped-columns", "unordered-page", "auto-strategy", "deep-with", "flatten-blocked", "redundant-sort-term", "unknown-query-option", "unlimited-find-many", "temporal-infinity", "cache-mismatch", "stream-idle-timeout", "sql-comment-dropped", "relation-payload-json", "flatten-fallback", "unindexed-relation", "prisma-compat-unknown-option", "prisma-compat-native-spelling", "prisma-map-stale", "prisma-compat-junction-collision", "powdb-link-collision", "powdb-link-drift", "powdb-introspect-no-primary-key", "introspect-cross-schema-fk", "introspect-relation-renamed", "introspect-relation-shadows-json", "generate-relation-shadows-column"];
11
+ /** A stable, machine-readable log line id. */
12
+ export type TurbineLogCode = (typeof LOG_CODES)[number];
@@ -0,0 +1,74 @@
1
+ /**
2
+ * turbine-orm, every `code` a Turbine log line can carry.
3
+ *
4
+ * A log line's TEXT is not a stable contract; its `code` is. This list is the
5
+ * whole set. Library code imports it as a TYPE only (see `TurbineLogCode` in
6
+ * logger.ts), so an emitter cannot use a code that is not here and the list
7
+ * itself never reaches a bundle. The tests import the runtime array: one test
8
+ * per code, and a source scan that fails on a code nothing emits.
9
+ */
10
+ export const LOG_CODES = [
11
+ // client.ts, construction (always on)
12
+ 'unknown-config-key',
13
+ 'plan-cache-mode-ignored',
14
+ 'prepared-statements-on-pooler',
15
+ 'connection-option-ignored',
16
+ // client.ts, runtime errors
17
+ 'pool-error',
18
+ 'query-listener-error',
19
+ 'pool-listener-error',
20
+ 'replica-close-error',
21
+ // client.ts, `logging: true` lifecycle lines
22
+ 'external-pool',
23
+ 'pool-created',
24
+ 'replicas-configured',
25
+ 'pipeline',
26
+ 'raw-sql',
27
+ 'transaction-committed',
28
+ 'transaction-rolled-back',
29
+ 'listen',
30
+ 'notify',
31
+ 'connection-verified',
32
+ 'disconnect-skipped',
33
+ 'pool-disconnected',
34
+ // typed-sql.ts
35
+ 'typed-sql',
36
+ // realtime.ts
37
+ 'listen-reconnecting',
38
+ 'listen-lost',
39
+ // query/utils.ts, a pg type parser replaced (client construction, always on)
40
+ 'parser-overwrite',
41
+ // query/builder.ts, query/utils.ts, query/option-surface.ts: per-query advice
42
+ 'untyped-columns',
43
+ 'unordered-page',
44
+ 'auto-strategy',
45
+ 'deep-with',
46
+ 'flatten-blocked',
47
+ 'redundant-sort-term',
48
+ 'unknown-query-option',
49
+ // query/builder.ts, always on
50
+ 'unlimited-find-many',
51
+ 'temporal-infinity',
52
+ 'cache-mismatch',
53
+ 'stream-idle-timeout',
54
+ // query/sql-comment.ts, always on (a sqlComment.dynamic value left out)
55
+ 'sql-comment-dropped',
56
+ // query/relations.ts, per-query advice
57
+ 'relation-payload-json',
58
+ 'flatten-fallback',
59
+ 'unindexed-relation',
60
+ // prisma-compat.ts
61
+ 'prisma-compat-unknown-option',
62
+ 'prisma-compat-native-spelling',
63
+ 'prisma-map-stale',
64
+ 'prisma-compat-junction-collision',
65
+ // powdb.ts, powdb-introspect.ts
66
+ 'powdb-link-collision',
67
+ 'powdb-link-drift',
68
+ 'powdb-introspect-no-primary-key',
69
+ // introspect.ts, generate.ts
70
+ 'introspect-cross-schema-fk',
71
+ 'introspect-relation-renamed',
72
+ 'introspect-relation-shadows-json',
73
+ 'generate-relation-shadows-column',
74
+ ];
@@ -0,0 +1,87 @@
1
+ /**
2
+ * turbine-orm, the pluggable logger.
3
+ *
4
+ * Every line Turbine writes goes through a {@link TurbineLogger}: the client's
5
+ * `logger` option, or {@link consoleLogger} when none is set. Each line is a
6
+ * human message plus a {@link TurbineLogEntry} whose `code` is stable and
7
+ * machine-readable; the message text is not a stable contract.
8
+ *
9
+ * No log line carries a bound parameter value or a row value, whatever
10
+ * `logQueryParams` says: that option governs `$on('query')` events only. An
11
+ * entry about a where clause names its keys, never its values.
12
+ *
13
+ * This module and `seed.ts` (a CLI-driven runner) are the only library modules
14
+ * that call `console` directly; a source scan fails the build on another.
15
+ */
16
+ import type { ResolvedDiagnostics } from './diagnostics.js';
17
+ import type { TurbineLogCode } from './log-codes.js';
18
+ export type { TurbineLogCode } from './log-codes.js';
19
+ /** The structured half of a log line. */
20
+ export interface TurbineLogEntry {
21
+ /** Stable, machine-readable id of the line, e.g. `'pool-error'`, `'unknown-config-key'`. */
22
+ code: string;
23
+ /** The table the line is about, when there is one. */
24
+ table?: string;
25
+ /** The operation the line is about (`'findMany'`, `'$transaction'`), when there is one. */
26
+ operation?: string;
27
+ /** The error the line reports, when it reports one. */
28
+ error?: unknown;
29
+ [field: string]: unknown;
30
+ }
31
+ /**
32
+ * Where Turbine's log lines go. Every method is optional, and a missing method
33
+ * drops that level, so `{}` silences Turbine entirely. The argument order,
34
+ * `(message, entry)`, matches `console` and winston; pino and bunyan take
35
+ * `(object, message)` and need a two-line adapter.
36
+ *
37
+ * A method that throws is ignored: logging never fails the operation that
38
+ * logged.
39
+ */
40
+ export interface TurbineLogger {
41
+ debug?(message: string, entry: TurbineLogEntry): void;
42
+ info?(message: string, entry: TurbineLogEntry): void;
43
+ warn?(message: string, entry: TurbineLogEntry): void;
44
+ error?(message: string, entry: TurbineLogEntry): void;
45
+ }
46
+ /** One of the four {@link TurbineLogger} levels. */
47
+ export type TurbineLogLevel = 'debug' | 'info' | 'warn' | 'error';
48
+ /**
49
+ * The logger a client uses when `logger` is not set. `console` is looked up
50
+ * on every call, not captured, so anything that replaces `console.warn` (a
51
+ * test, an APM agent) sees Turbine's lines.
52
+ */
53
+ export declare const consoleLogger: TurbineLogger;
54
+ /**
55
+ * Validate the `logger` option: unset means {@link consoleLogger}; otherwise an
56
+ * object whose `debug` / `info` / `warn` / `error` members, where present, are
57
+ * functions. Other members are ignored, so a pino, winston or console object is
58
+ * accepted as it is. Throws `ValidationError` (E003) otherwise.
59
+ */
60
+ export declare function resolveLogger(logger: unknown): TurbineLogger;
61
+ /** An entry as Turbine's own emitters build it: the code is one of {@link TurbineLogCode}. */
62
+ export interface InternalLogEntry extends TurbineLogEntry {
63
+ code: TurbineLogCode;
64
+ }
65
+ /**
66
+ * Send one line to `logger` at `level`. A logger without that level drops it;
67
+ * a logger that throws is ignored. Called as a method, so a class-based
68
+ * logger keeps its `this`.
69
+ *
70
+ * Callers build `message` only once they know the line is wanted (behind the
71
+ * `logging` flag, the advice gate, or the warn-once registry), so a silenced
72
+ * line costs nothing.
73
+ */
74
+ export declare function logTo(logger: TurbineLogger, level: TurbineLogLevel, message: string, entry: InternalLogEntry): void;
75
+ /**
76
+ * What a client hands to code that wraps it (prisma-compat) or runs on its
77
+ * behalf: its logger and its resolved diagnostics.
78
+ */
79
+ export interface ClientLogContext {
80
+ readonly logger: TurbineLogger;
81
+ readonly diagnostics: ResolvedDiagnostics;
82
+ }
83
+ /**
84
+ * The key a `TurbineClient` exposes its {@link ClientLogContext} under.
85
+ * `Symbol.for`, so an ESM and a CJS copy of the package agree on it.
86
+ */
87
+ export declare const CLIENT_LOG_CONTEXT: unique symbol;
package/dist/logger.js ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * turbine-orm, the pluggable logger.
3
+ *
4
+ * Every line Turbine writes goes through a {@link TurbineLogger}: the client's
5
+ * `logger` option, or {@link consoleLogger} when none is set. Each line is a
6
+ * human message plus a {@link TurbineLogEntry} whose `code` is stable and
7
+ * machine-readable; the message text is not a stable contract.
8
+ *
9
+ * No log line carries a bound parameter value or a row value, whatever
10
+ * `logQueryParams` says: that option governs `$on('query')` events only. An
11
+ * entry about a where clause names its keys, never its values.
12
+ *
13
+ * This module and `seed.ts` (a CLI-driven runner) are the only library modules
14
+ * that call `console` directly; a source scan fails the build on another.
15
+ */
16
+ import { ValidationError } from './errors.js';
17
+ const LEVELS = ['debug', 'info', 'warn', 'error'];
18
+ /**
19
+ * What the default logger prints for one line: `[turbine] <message>`, plus the
20
+ * entry's error as a second argument when the message ends with a colon. Such
21
+ * a message names a failure and the error completes it (`Query listener error:`
22
+ * followed by the error and its stack); every other message is complete on its
23
+ * own and already carries what it needs from the error.
24
+ */
25
+ function consoleArgs(message, entry) {
26
+ // turbine-prefix-ok: this line only ever reaches the console.* calls in consoleLogger, never an Error.
27
+ const line = `[turbine] ${message}`;
28
+ return entry.error !== undefined && message.endsWith(':') ? [line, entry.error] : [line];
29
+ }
30
+ /**
31
+ * The logger a client uses when `logger` is not set. `console` is looked up
32
+ * on every call, not captured, so anything that replaces `console.warn` (a
33
+ * test, an APM agent) sees Turbine's lines.
34
+ */
35
+ export const consoleLogger = Object.freeze({
36
+ debug(message, entry) {
37
+ console.log(...consoleArgs(message, entry));
38
+ },
39
+ info(message, entry) {
40
+ console.info(...consoleArgs(message, entry));
41
+ },
42
+ warn(message, entry) {
43
+ console.warn(...consoleArgs(message, entry));
44
+ },
45
+ error(message, entry) {
46
+ console.error(...consoleArgs(message, entry));
47
+ },
48
+ });
49
+ /**
50
+ * Validate the `logger` option: unset means {@link consoleLogger}; otherwise an
51
+ * object whose `debug` / `info` / `warn` / `error` members, where present, are
52
+ * functions. Other members are ignored, so a pino, winston or console object is
53
+ * accepted as it is. Throws `ValidationError` (E003) otherwise.
54
+ */
55
+ export function resolveLogger(logger) {
56
+ if (logger === undefined)
57
+ return consoleLogger;
58
+ if ((typeof logger !== 'object' && typeof logger !== 'function') || logger === null || Array.isArray(logger)) {
59
+ throw new ValidationError(`Invalid logger option: expected an object with optional debug, info, warn and error methods, got ${logger === null ? 'null' : Array.isArray(logger) ? 'an array' : typeof logger}. Pass {} to silence Turbine, or leave the option unset for console.`);
60
+ }
61
+ for (const level of LEVELS) {
62
+ const member = logger[level];
63
+ if (member !== undefined && typeof member !== 'function') {
64
+ throw new ValidationError(`Invalid logger option: logger.${level} must be a function (message, entry) or absent, got ${typeof member}.`);
65
+ }
66
+ }
67
+ return logger;
68
+ }
69
+ /**
70
+ * Send one line to `logger` at `level`. A logger without that level drops it;
71
+ * a logger that throws is ignored. Called as a method, so a class-based
72
+ * logger keeps its `this`.
73
+ *
74
+ * Callers build `message` only once they know the line is wanted (behind the
75
+ * `logging` flag, the advice gate, or the warn-once registry), so a silenced
76
+ * line costs nothing.
77
+ */
78
+ export function logTo(logger, level, message, entry) {
79
+ const method = logger[level];
80
+ if (typeof method !== 'function')
81
+ return;
82
+ try {
83
+ method.call(logger, message, entry);
84
+ }
85
+ catch {
86
+ // A logging failure must never become the caller's failure.
87
+ }
88
+ }
89
+ /**
90
+ * The key a `TurbineClient` exposes its {@link ClientLogContext} under.
91
+ * `Symbol.for`, so an ESM and a CJS copy of the package agree on it.
92
+ */
93
+ export const CLIENT_LOG_CONTEXT = Symbol.for('turbine-orm.clientLogContext');