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
@@ -25,10 +25,16 @@
25
25
  */
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
27
  exports.camelToSnake = exports.column = exports.ColumnBuilder = void 0;
28
+ exports.tsTypeTextProblem = tsTypeTextProblem;
28
29
  exports.isDocFieldIndexDef = isDocFieldIndexDef;
29
30
  exports.defineSchema = defineSchema;
30
31
  exports.table = table;
31
32
  exports.applyManyToManyRelations = applyManyToManyRelations;
33
+ // Type-only import, erased at runtime, so it cannot introduce a circular
34
+ // runtime dependency (the local `camelToSnakeLocal` copy avoids that for the
35
+ // value-level helper).
36
+ const errors_js_1 = require("./errors.js");
37
+ const utils_js_1 = require("./query/utils.js");
32
38
  /** Maps shorthand names to actual Postgres type strings */
33
39
  const TYPE_MAP = {
34
40
  // `serial` maps to SERIAL (int4). Its values fit in a JS `number` and pg
@@ -63,16 +69,42 @@ const TYPE_MAP = {
63
69
  enum: 'ENUM',
64
70
  vector: 'VECTOR',
65
71
  };
72
+ /** What a value is, for a message: `an array`, `null`, `a string`. */
73
+ function describeKind(value) {
74
+ if (value === null)
75
+ return 'null';
76
+ if (Array.isArray(value))
77
+ return 'an array';
78
+ return typeof value === 'object' ? 'an object' : `a ${typeof value}`;
79
+ }
80
+ /**
81
+ * A declared DEFAULT as the SQL text the DDL generators take. A string is raw
82
+ * SQL, kept as written. A finite number or a boolean is the literal it is,
83
+ * where it used to reach the generator as a non-string and crash it with a
84
+ * TypeError. `undefined` and `null` mean no default.
85
+ */
86
+ function declaredDefault(value, where) {
87
+ if (value === undefined || value === null)
88
+ return null;
89
+ if (typeof value === 'string')
90
+ return value;
91
+ if (typeof value === 'boolean')
92
+ return value ? 'true' : 'false';
93
+ if ((typeof value === 'number' && Number.isFinite(value)) || typeof value === 'bigint')
94
+ return String(value);
95
+ throw new errors_js_1.ValidationError(`Invalid default for ${where}: received ${describeKind(value)}${typeof value === 'number' ? ` (${value})` : ''}. ` +
96
+ `Write it as a string of raw SQL such as 'now()' or "'active'", or as a finite number or a boolean.`);
97
+ }
66
98
  /** Convert a user-facing ColumnDef to the internal ColumnConfig */
67
- function resolveColumn(def) {
68
- if (!(def.type in TYPE_MAP)) {
69
- throw new Error(`Invalid column type "${def.type}". Valid types: ${Object.keys(TYPE_MAP).join(', ')}`);
99
+ function resolveColumn(def, where = 'a column') {
100
+ if (!Object.hasOwn(TYPE_MAP, def.type)) {
101
+ throw new errors_js_1.ValidationError(`Invalid column type "${def.type}". Valid types: ${Object.keys(TYPE_MAP).join(', ')}`);
70
102
  }
71
103
  if (def.type === 'enum' && !def.enumName) {
72
- throw new Error(`Column of type "enum" requires an "enumName" (the CREATE TYPE name).`);
104
+ throw new errors_js_1.ValidationError(`Column of type "enum" requires an "enumName" (the CREATE TYPE name).`);
73
105
  }
74
106
  if (def.type === 'vector' && (def.dimensions == null || def.dimensions <= 0)) {
75
- throw new Error(`Column of type "vector" requires a positive "dimensions" count.`);
107
+ throw new errors_js_1.ValidationError(`Column of type "vector" requires a positive "dimensions" count.`);
76
108
  }
77
109
  // `references` is either the "table.column" string or a { target, onDelete, onUpdate } object.
78
110
  let referencesTarget = null;
@@ -81,7 +113,11 @@ function resolveColumn(def) {
81
113
  if (typeof def.references === 'string') {
82
114
  referencesTarget = def.references;
83
115
  }
84
- else if (def.references) {
116
+ else if (def.references !== undefined && def.references !== null) {
117
+ if (typeof def.references !== 'object' || typeof def.references.target !== 'string') {
118
+ throw new errors_js_1.ValidationError(`defineSchema: ${where} has a "references" that is neither a "table.column" string nor ` +
119
+ `{ target: "table.column", onDelete?, onUpdate? }.`);
120
+ }
85
121
  referencesTarget = def.references.target;
86
122
  onDelete = def.references.onDelete ?? null;
87
123
  onUpdate = def.references.onUpdate ?? null;
@@ -92,7 +128,7 @@ function resolveColumn(def) {
92
128
  isNotNull: def.notNull ?? false,
93
129
  isNullable: def.nullable ?? false,
94
130
  isUnique: def.unique ?? false,
95
- defaultValue: def.default ?? null,
131
+ defaultValue: declaredDefault(def.default, where),
96
132
  referencesTarget,
97
133
  maxLength: def.maxLength ?? null,
98
134
  onDelete,
@@ -103,15 +139,306 @@ function resolveColumn(def) {
103
139
  check: def.check ?? null,
104
140
  pii: def.pii ?? false,
105
141
  updatedAt: def.updatedAt ?? false,
142
+ tsType: checkedTsType(def.tsType),
106
143
  };
107
144
  }
145
+ /** Characters a declared type may not contain (the generator refuses the same set). */
146
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: refusing control characters is this pattern's purpose
147
+ const UNSAFE_TS_TYPE_TEXT = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
148
+ /** Characters a type expression uses outside a string literal and a bracket. */
149
+ const TS_TYPE_CHAR = /^[\p{L}\p{N}_$.:?|&!+\-= \t]$/u;
150
+ const TS_TYPE_CLOSER = { '(': ')', '[': ']', '{': '}', '<': '>' };
151
+ /**
152
+ * Why `text` cannot be written into a generated file as a TypeScript type, or
153
+ * null when it can. The text lands verbatim as a member type or an alias body,
154
+ * so what has to hold is that it stays ONE type: brackets balance, nothing at
155
+ * the top level ends the member (`;` or `,`), and it holds no comment, template
156
+ * literal or character a type does not use. A `//` would swallow the rest of the
157
+ * line and a stray `}` would close the interface and start a statement.
158
+ *
159
+ * It does not check that the type is well formed or that its names exist; the
160
+ * compiler of the generated file does that.
161
+ */
162
+ function tsTypeTextProblem(text) {
163
+ if (text.trim() === '')
164
+ return 'it is empty';
165
+ if (UNSAFE_TS_TYPE_TEXT.test(text))
166
+ return 'it contains a control or line-separator character';
167
+ const chars = Array.from(text);
168
+ const open = [];
169
+ for (let i = 0; i < chars.length; i++) {
170
+ const c = chars[i];
171
+ if (c === "'" || c === '"') {
172
+ let end = i + 1;
173
+ while (end < chars.length && chars[end] !== c)
174
+ end += chars[end] === '\\' ? 2 : 1;
175
+ if (end >= chars.length)
176
+ return `a ${c} string literal is not closed`;
177
+ if (chars.slice(i, end).join('').includes('*/'))
178
+ return 'a string literal contains "*/"';
179
+ i = end;
180
+ }
181
+ else if (c === '=' && chars[i + 1] === '>') {
182
+ i++;
183
+ }
184
+ else if (TS_TYPE_CLOSER[c] !== undefined) {
185
+ open.push(TS_TYPE_CLOSER[c]);
186
+ }
187
+ else if (c === ')' || c === ']' || c === '}' || c === '>') {
188
+ if (open.pop() !== c)
189
+ return `"${c}" closes a bracket that was never opened`;
190
+ }
191
+ else if (c === ';' || c === ',') {
192
+ if (open.length === 0)
193
+ return `"${c}" outside any brackets would end the type`;
194
+ }
195
+ else if (c === '`') {
196
+ return 'a template literal type is not supported here, name it with a type alias and use import()';
197
+ }
198
+ else if (!TS_TYPE_CHAR.test(c)) {
199
+ return `"${c}" cannot appear in a type${c === '/' ? ' (no comments)' : ''}`;
200
+ }
201
+ }
202
+ return open.length > 0 ? 'a bracket is not closed' : null;
203
+ }
204
+ /**
205
+ * A `tsType` is emitted verbatim into generated source, so it must be one type
206
+ * on one line (see {@link tsTypeTextProblem}).
207
+ */
208
+ function checkedTsType(tsType) {
209
+ if (tsType === undefined)
210
+ return null;
211
+ const problem = typeof tsType === 'string' ? tsTypeTextProblem(tsType) : 'it is not a string';
212
+ if (problem !== null) {
213
+ throw new errors_js_1.ValidationError(`Invalid column "tsType" ${JSON.stringify(tsType)}: ${problem}. ` +
214
+ `Give one TypeScript type on one line, for example '{ theme: string }' or "import('../src/types.js').Settings".`);
215
+ }
216
+ return tsType;
217
+ }
108
218
  /** Type guard: is this index declaration a doc-field expression index? */
109
219
  function isDocFieldIndexDef(idx) {
110
220
  return 'docField' in idx && typeof idx.docField === 'string';
111
221
  }
112
- /** Check if a value is a TableDef (from legacy table() builder) */
222
+ // ---------------------------------------------------------------------------
223
+ // Known keys: one table per declaration shape
224
+ // ---------------------------------------------------------------------------
225
+ //
226
+ // A schema is static code, and a key `defineSchema` does not know is not
227
+ // harmless: it is an option the caller meant and did not get (`defaultValue`
228
+ // for `default` emits a column with no DEFAULT, `uniq` for `unique` a
229
+ // non-unique index). So an unknown key is refused, at compile time in a
230
+ // literal and at runtime for JSON or plain JS.
231
+ //
232
+ // Each table is a `Record<keyof Shape, true>`: adding an option to the shape
233
+ // without listing it here fails the build, and listing a key the shape does
234
+ // not have fails as an excess property. The compile-time check types below
235
+ // and the runtime refusal both read these, so they cannot disagree.
236
+ const COLUMN_KEYS = {
237
+ type: true,
238
+ primaryKey: true,
239
+ notNull: true,
240
+ nullable: true,
241
+ unique: true,
242
+ default: true,
243
+ references: true,
244
+ maxLength: true,
245
+ enumName: true,
246
+ dimensions: true,
247
+ array: true,
248
+ check: true,
249
+ pii: true,
250
+ updatedAt: true,
251
+ tsType: true,
252
+ };
253
+ const REFERENCE_KEYS = { target: true, onDelete: true, onUpdate: true };
254
+ const CHECK_KEYS = { name: true, expression: true };
255
+ const MANY_TO_MANY_KEYS = {
256
+ name: true,
257
+ target: true,
258
+ through: true,
259
+ sourceKey: true,
260
+ targetKey: true,
261
+ references: true,
262
+ };
263
+ const INDEX_KEYS = {
264
+ columns: true,
265
+ docField: true,
266
+ path: true,
267
+ unique: true,
268
+ name: true,
269
+ };
270
+ const TABLE_OPTION_KEYS = {
271
+ primaryKey: true,
272
+ manyToMany: true,
273
+ checks: true,
274
+ indexes: true,
275
+ };
276
+ const SCHEMA_OPTION_KEYS = { enums: true };
277
+ /**
278
+ * Refuse every key of `value` that `known` does not list (E003), naming where
279
+ * it sits and the key it most likely meant.
280
+ */
281
+ function assertKnownKeys(value, known, where) {
282
+ for (const key of Object.keys(value)) {
283
+ if (Object.hasOwn(known, key))
284
+ continue;
285
+ // suggestKey covers typos and the metadata spellings (`defaultValue`,
286
+ // `isUnique`) alike: the second pass matches a key that adds words.
287
+ const suggestion = (0, utils_js_1.suggestKey)(key, Object.keys(known));
288
+ throw new errors_js_1.ValidationError(`defineSchema: unknown key "${key}" in ${where}.` +
289
+ (suggestion ? ` Did you mean "${suggestion}"?` : '') +
290
+ ` Valid keys: ${Object.keys(known).join(', ')}.`);
291
+ }
292
+ }
293
+ /** Each element of an option array, checked as `what` (non-objects are left to the consumer's own checks). */
294
+ function assertKnownElementKeys(list, known, where) {
295
+ list.forEach((item, i) => {
296
+ if (typeof item === 'object' && item !== null)
297
+ assertKnownKeys(item, known, `${where}[${i}]`);
298
+ });
299
+ }
300
+ /**
301
+ * Refuse `__proto__` as a table or column name. `JSON.parse` can produce it as
302
+ * an own key, but assigning to it on a record sets the record's prototype
303
+ * instead of adding a key, so the column would vanish without a word.
304
+ */
305
+ function assertOrdinaryName(name, what) {
306
+ if (name === '__proto__') {
307
+ throw new errors_js_1.ValidationError(`defineSchema: ${what} is named "__proto__", which JavaScript treats as the prototype setter rather than a ` +
308
+ `property name, so it could not be stored or queried. Rename it.`);
309
+ }
310
+ }
311
+ /**
312
+ * An index is either a column index (`columns`) or a document-field index
313
+ * (`docField` and `path`), never both. A declaration carrying both was taken
314
+ * for the document-field kind, which only PowDB creates, so the SQL engines
315
+ * skipped it without saying so.
316
+ */
317
+ function assertIndexKinds(list, accessor) {
318
+ list.forEach((item, i) => {
319
+ if (typeof item === 'object' && item !== null && 'columns' in item && 'docField' in item) {
320
+ throw new errors_js_1.ValidationError(`defineSchema: index ${i} of table "${accessor}" declares both "columns" and "docField". ` +
321
+ `Use "columns" for a column index, or "docField" with "path" for an index into a json document. ` +
322
+ `Declare two indexes if you need both.`);
323
+ }
324
+ });
325
+ }
326
+ /**
327
+ * Is this value a TableDef (the legacy `table()` builder's output)?
328
+ *
329
+ * A TableDef carries a STRING `name` and a `columns` record of column configs.
330
+ * In the object format a key called `name` or `columns` is a column, and a
331
+ * column definition is an object, so the string is what tells the two apart: a
332
+ * table with columns named `name` and `columns` used to be read as a TableDef
333
+ * and emitted as a different table. A table that has a string `name` but is not
334
+ * a TableDef falls through to the object format, which refuses the string.
335
+ */
113
336
  function isTableDef(v) {
114
- return typeof v === 'object' && v !== null && 'columns' in v && 'name' in v;
337
+ if (typeof v !== 'object' || v === null || Array.isArray(v))
338
+ return false;
339
+ const t = v;
340
+ if (typeof t.name !== 'string')
341
+ return false;
342
+ const columns = t.columns;
343
+ if (typeof columns !== 'object' || columns === null || Array.isArray(columns))
344
+ return false;
345
+ return Object.values(columns).every((c) => typeof c === 'object' && c !== null);
346
+ }
347
+ /** A name list for a message: `a, b, c`, or `(none)`. */
348
+ function nameList(names) {
349
+ const list = [...names];
350
+ return list.length > 0 ? list.join(', ') : '(none)';
351
+ }
352
+ /** The DDL column name of a field: what `CREATE TABLE` and every index write for it. */
353
+ const ddlColumnName = camelToSnakeLocal;
354
+ /**
355
+ * Refuse a schema that names something it does not contain. Every one of these
356
+ * used to be accepted and then went wrong somewhere else, or nowhere:
357
+ *
358
+ * - `userId` and `user_id` in one table are the SAME column to the database, so
359
+ * the DDL declared it twice.
360
+ * - A `references` that is not `table.column` was dropped (no foreign key, no
361
+ * error), and one naming a column its target table does not have became a
362
+ * `REFERENCES` clause the database rejects at push time. A target table that
363
+ * is not in this schema is left alone, since it may be owned elsewhere.
364
+ * - An index on a column the table does not have failed the same way.
365
+ *
366
+ * It runs once, after every table is built, because a reference may name a
367
+ * table declared later.
368
+ */
369
+ function assertSchemaIntegrity(tables) {
370
+ const byDdlName = new Map();
371
+ const byName = new Map();
372
+ for (const [accessor, table] of Object.entries(tables)) {
373
+ const owner = byDdlName.get(table.name);
374
+ if (owner !== undefined) {
375
+ throw new errors_js_1.ValidationError(`defineSchema: tables "${owner}" and "${accessor}" are both the table "${table.name}" in the database ` +
376
+ `(a camelCase name is written in snake_case). Keep one, or rename one.`);
377
+ }
378
+ byDdlName.set(table.name, accessor);
379
+ byName.set(accessor, table);
380
+ byName.set(table.name, table);
381
+ }
382
+ for (const [accessor, table] of Object.entries(tables)) {
383
+ const columnOf = new Map();
384
+ for (const field of Object.keys(table.columns)) {
385
+ const ddl = ddlColumnName(field);
386
+ const other = columnOf.get(ddl);
387
+ if (other !== undefined) {
388
+ throw new errors_js_1.ValidationError(`defineSchema: table "${accessor}" has columns "${other}" and "${field}", which are the same column ` +
389
+ `"${ddl}" in the database. Keep one.`);
390
+ }
391
+ columnOf.set(ddl, field);
392
+ }
393
+ const hasColumn = (name) => Object.hasOwn(table.columns, name) || columnOf.has(name);
394
+ const columnNames = () => Object.keys(table.columns);
395
+ for (const [i, idx] of (table.indexes ?? []).entries()) {
396
+ if (!isDocFieldIndexDef(idx) && !Array.isArray(idx.columns)) {
397
+ throw new errors_js_1.ValidationError(`defineSchema: index ${i} of table "${accessor}" declares neither "columns" (an array of column names) ` +
398
+ `nor "docField" with "path".`);
399
+ }
400
+ const named = isDocFieldIndexDef(idx) ? [idx.docField] : idx.columns;
401
+ for (const name of named) {
402
+ if (typeof name === 'string' && hasColumn(name))
403
+ continue;
404
+ const suggestion = typeof name === 'string' ? (0, utils_js_1.suggestKey)(name, columnNames()) : null;
405
+ throw new errors_js_1.ValidationError(`defineSchema: index ${i} of table "${accessor}" names ${typeof name === 'string' ? `column "${name}"` : `a ${describeKind(name)} instead of a column name`}, ` +
406
+ `which the table does not have.${suggestion ? ` Did you mean "${suggestion}"?` : ''} ` +
407
+ `Columns: ${nameList(columnNames())}.`);
408
+ }
409
+ }
410
+ for (const [field, config] of Object.entries(table.columns)) {
411
+ if (config.referencesTarget === null || config.referencesTarget === undefined)
412
+ continue;
413
+ const where = `column "${accessor}.${field}"`;
414
+ const target = config.referencesTarget;
415
+ const parts = typeof target === 'string' ? target.split('.') : [];
416
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
417
+ throw new errors_js_1.ValidationError(`defineSchema: ${where} has references ${JSON.stringify(target)}, which is not in "table.column" form, so no foreign ` +
418
+ `key would be created. Write it as "users.id".` +
419
+ (parts.length > 2 ? ' A schema-qualified name ("auth.users.id") is not supported.' : ''));
420
+ }
421
+ const [refTable, refColumn] = parts;
422
+ // A table outside this schema is allowed, since something else may own
423
+ // it. Its columns cannot be checked here.
424
+ const targetTable = byName.get(refTable);
425
+ if (!targetTable)
426
+ continue;
427
+ const targetDdl = new Set(Object.keys(targetTable.columns).map(ddlColumnName));
428
+ const isField = Object.hasOwn(targetTable.columns, refColumn);
429
+ if (!isField && !targetDdl.has(refColumn)) {
430
+ const suggestion = (0, utils_js_1.suggestKey)(refColumn, Object.keys(targetTable.columns));
431
+ throw new errors_js_1.ValidationError(`defineSchema: ${where} references "${target}", but table "${refTable}" has no column "${refColumn}".` +
432
+ (suggestion ? ` Did you mean "${suggestion}"?` : '') +
433
+ ` Columns: ${nameList(Object.keys(targetTable.columns))}.`);
434
+ }
435
+ // The DDL names the column as written, so a field name (`displayName`)
436
+ // becomes the column it is (`display_name`) here, where the table is known.
437
+ if (isField && ddlColumnName(refColumn) !== refColumn) {
438
+ config.referencesTarget = `${refTable}.${ddlColumnName(refColumn)}`;
439
+ }
440
+ }
441
+ }
115
442
  }
116
443
  /**
117
444
  * Define the full database schema using plain objects.
@@ -134,7 +461,10 @@ function isTableDef(v) {
134
461
  */
135
462
  function defineSchema(input, options) {
136
463
  const tables = {};
464
+ if (options !== undefined)
465
+ assertKnownKeys(options, SCHEMA_OPTION_KEYS, 'the defineSchema options');
137
466
  for (const [accessor, value] of Object.entries(input)) {
467
+ assertOrdinaryName(accessor, `table "${accessor}"`);
138
468
  // The user-facing key is the camelCase JS accessor; the DDL-facing
139
469
  // table name is its snake_case form (e.g. `postTags` → `post_tags`).
140
470
  const dbName = camelToSnakeLocal(accessor);
@@ -157,8 +487,10 @@ function defineSchema(input, options) {
157
487
  if (fieldName === 'indexes') {
158
488
  if (def !== undefined) {
159
489
  if (!Array.isArray(def)) {
160
- throw new Error(`Table "${accessor}": "indexes" must be an array of index declarations`);
490
+ throw new errors_js_1.ValidationError(`Table "${accessor}": "indexes" must be an array of index declarations`);
161
491
  }
492
+ assertKnownElementKeys(def, INDEX_KEYS, `table "${accessor}" indexes`);
493
+ assertIndexKinds(def, accessor);
162
494
  indexes = def;
163
495
  }
164
496
  continue;
@@ -166,8 +498,9 @@ function defineSchema(input, options) {
166
498
  if (fieldName === 'manyToMany') {
167
499
  if (def !== undefined) {
168
500
  if (!Array.isArray(def)) {
169
- throw new Error(`Table "${accessor}": "manyToMany" must be an array of relation declarations`);
501
+ throw new errors_js_1.ValidationError(`Table "${accessor}": "manyToMany" must be an array of relation declarations`);
170
502
  }
503
+ assertKnownElementKeys(def, MANY_TO_MANY_KEYS, `table "${accessor}" manyToMany`);
171
504
  m2m = def;
172
505
  }
173
506
  continue;
@@ -175,8 +508,9 @@ function defineSchema(input, options) {
175
508
  if (fieldName === 'checks') {
176
509
  if (def !== undefined) {
177
510
  if (!Array.isArray(def)) {
178
- throw new Error(`Table "${accessor}": "checks" must be an array of { name?, expression } objects`);
511
+ throw new errors_js_1.ValidationError(`Table "${accessor}": "checks" must be an array of { name?, expression } objects`);
179
512
  }
513
+ assertKnownElementKeys(def, CHECK_KEYS, `table "${accessor}" checks`);
180
514
  checks = def;
181
515
  }
182
516
  continue;
@@ -185,14 +519,35 @@ function defineSchema(input, options) {
185
519
  // Top-level composite primary key declaration
186
520
  if (def !== undefined) {
187
521
  if (!Array.isArray(def)) {
188
- throw new Error(`Table "${accessor}": top-level "primaryKey" must be an array of column names (string[])`);
522
+ throw new errors_js_1.ValidationError(`Table "${accessor}": top-level "primaryKey" must be an array of column names (string[])`);
189
523
  }
190
524
  pk = def;
191
525
  }
192
526
  continue;
193
527
  }
194
- // Anything else is a ColumnDef
195
- columns[fieldName] = resolveColumn(def);
528
+ // Anything else is a ColumnDef. An array here is a table option under a
529
+ // name defineSchema does not know (`primarykey`, `index`): refuse it by
530
+ // that name rather than as a column with no type.
531
+ if (Array.isArray(def)) {
532
+ const suggestion = (0, utils_js_1.suggestKey)(fieldName, Object.keys(TABLE_OPTION_KEYS));
533
+ throw new errors_js_1.ValidationError(`defineSchema: table "${accessor}" has an array under "${fieldName}", which is not a table option ` +
534
+ `(${Object.keys(TABLE_OPTION_KEYS).join(', ')}) and cannot be a column.` +
535
+ (suggestion ? ` Did you mean "${suggestion}"?` : ''));
536
+ }
537
+ assertOrdinaryName(fieldName, `column "${accessor}.${fieldName}"`);
538
+ // An `undefined` column is absent, like an `undefined` option anywhere else.
539
+ if (def === undefined)
540
+ continue;
541
+ if (typeof def !== 'object' || def === null) {
542
+ throw new errors_js_1.ValidationError(`defineSchema: column "${accessor}.${fieldName}" must be an object such as { type: 'text' }, ` +
543
+ `received ${describeKind(def)}.`);
544
+ }
545
+ assertKnownKeys(def, COLUMN_KEYS, `column "${accessor}.${fieldName}"`);
546
+ const ref = def.references;
547
+ if (typeof ref === 'object' && ref !== null) {
548
+ assertKnownKeys(ref, REFERENCE_KEYS, `the references of column "${accessor}.${fieldName}"`);
549
+ }
550
+ columns[fieldName] = resolveColumn(def, `column "${accessor}.${fieldName}"`);
196
551
  }
197
552
  // Validate composite PK references real columns and clear column-level PKs
198
553
  // for those columns so we don't double-emit `PRIMARY KEY` clauses.
@@ -201,7 +556,7 @@ function defineSchema(input, options) {
201
556
  if (pk && pk.length > 0) {
202
557
  for (const colName of pk) {
203
558
  if (!(colName in columns)) {
204
- throw new Error(`Table "${accessor}": composite primaryKey references unknown column "${colName}". ` +
559
+ throw new errors_js_1.ValidationError(`Table "${accessor}": composite primaryKey references unknown column "${colName}". ` +
205
560
  `Known columns: ${Object.keys(columns).join(', ') || '(none)'}`);
206
561
  }
207
562
  // A composite PK at the table level supersedes any column-level
@@ -226,6 +581,7 @@ function defineSchema(input, options) {
226
581
  };
227
582
  }
228
583
  }
584
+ assertSchemaIntegrity(tables);
229
585
  return { tables, ...(options?.enums ? { enums: options.enums } : {}) };
230
586
  }
231
587
  /**
@@ -258,6 +614,7 @@ class ColumnBuilder {
258
614
  check: null,
259
615
  pii: false,
260
616
  updatedAt: false,
617
+ tsType: null,
261
618
  };
262
619
  }
263
620
  serial() {
@@ -351,10 +708,12 @@ class ColumnBuilder {
351
708
  return this;
352
709
  }
353
710
  default(val) {
354
- this._config.defaultValue = val;
711
+ this._config.defaultValue = declaredDefault(val, 'ColumnBuilder.default()');
355
712
  return this;
356
713
  }
357
714
  references(target, opts) {
715
+ if (opts !== undefined)
716
+ assertKnownKeys(opts, { onDelete: true, onUpdate: true }, `references("${target}") options`);
358
717
  this._config.referencesTarget = target;
359
718
  this._config.onDelete = opts?.onDelete ?? null;
360
719
  this._config.onUpdate = opts?.onUpdate ?? null;
@@ -373,6 +732,11 @@ class ColumnBuilder {
373
732
  this._config.updatedAt = true;
374
733
  return this;
375
734
  }
735
+ /** The TypeScript type `turbine generate` emits for this column. See {@link ColumnDef.tsType}. */
736
+ tsType(type) {
737
+ this._config.tsType = checkedTsType(type);
738
+ return this;
739
+ }
376
740
  array() {
377
741
  this._config.isArray = true;
378
742
  return this;
@@ -414,7 +778,7 @@ exports.column = new Proxy({}, {
414
778
  };
415
779
  }
416
780
  return () => {
417
- throw new Error(`Unknown column type: ${prop}`);
781
+ throw new errors_js_1.ValidationError(`Unknown column type: ${prop}`);
418
782
  };
419
783
  },
420
784
  });
@@ -45,7 +45,27 @@
45
45
  * ```
46
46
  */
47
47
  import { type SchemaMetadata } from './schema.js';
48
- import { type SchemaDef } from './schema-builder.js';
48
+ import { type ColumnConfig, type SchemaDef } from './schema-builder.js';
49
+ /** The code-first-only flags a column can declare. */
50
+ export interface CodeFirstColumnTags {
51
+ pii?: true;
52
+ updatedAt?: true;
53
+ maxLength?: number;
54
+ /**
55
+ * The declared TypeScript type, WITHOUT nullability. On metadata built by
56
+ * {@link schemaDefToMetadata} it is the column's `declaredTsType`, never its
57
+ * `tsType`; `turbine generate` passes it to the generator as a `columnTypes`
58
+ * entry.
59
+ */
60
+ tsType?: string;
61
+ }
62
+ /**
63
+ * The tags a column declares, as the fields they take on {@link ColumnMetadata}.
64
+ * The one place the tag set is listed, so `schemaDefToMetadata` and the CLI's
65
+ * overlay of these tags onto an introspected schema (`cli/code-first-tags.ts`)
66
+ * cannot disagree about what counts as one.
67
+ */
68
+ export declare function declaredTagsOf(config: ColumnConfig): CodeFirstColumnTags;
49
69
  /**
50
70
  * Convert a code-first {@link SchemaDef} into runtime {@link SchemaMetadata}.
51
71
  *
@@ -46,6 +46,7 @@
46
46
  * ```
47
47
  */
48
48
  Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.declaredTagsOf = declaredTagsOf;
49
50
  exports.schemaDefToMetadata = schemaDefToMetadata;
50
51
  const errors_js_1 = require("./errors.js");
51
52
  const introspect_js_1 = require("./introspect.js");
@@ -160,6 +161,20 @@ function mapIndexes(tableDef, declared) {
160
161
  }
161
162
  return out;
162
163
  }
164
+ /**
165
+ * The tags a column declares, as the fields they take on {@link ColumnMetadata}.
166
+ * The one place the tag set is listed, so `schemaDefToMetadata` and the CLI's
167
+ * overlay of these tags onto an introspected schema (`cli/code-first-tags.ts`)
168
+ * cannot disagree about what counts as one.
169
+ */
170
+ function declaredTagsOf(config) {
171
+ return {
172
+ ...(config.pii ? { pii: true } : {}),
173
+ ...(config.updatedAt ? { updatedAt: true } : {}),
174
+ ...(config.maxLength != null ? { maxLength: config.maxLength } : {}),
175
+ ...(config.tsType != null ? { tsType: config.tsType } : {}),
176
+ };
177
+ }
163
178
  // ---------------------------------------------------------------------------
164
179
  // The converter
165
180
  // ---------------------------------------------------------------------------
@@ -328,23 +343,30 @@ function schemaDefToMetadata(def) {
328
343
  const serial = isSerialType(config.type);
329
344
  // PK members and serials are NOT NULL in Postgres regardless of flags.
330
345
  const nullable = !(config.isPrimaryKey || config.isNotNull || serial);
346
+ const { tsType: declaredTsType, ...declaredTags } = declaredTagsOf(config);
331
347
  const col = {
332
348
  name,
333
349
  field,
334
350
  dialectType: wireType,
335
351
  pgType: wireType,
336
- tsType: (0, schema_js_1.pgTypeToTs)(wireType, nullable),
337
352
  nullable,
338
353
  hasDefault: config.defaultValue != null || serial,
354
+ // The expression itself, as written in the schema (`now()`, `'draft'`).
355
+ // Engines with no column defaults (PowDB) apply it client-side on
356
+ // create, which they cannot do from `hasDefault` alone.
357
+ ...(config.defaultValue != null ? { defaultValue: config.defaultValue } : {}),
339
358
  isArray: config.isArray,
340
359
  arrayType: (0, schema_js_1.pgArrayType)(base),
341
360
  pgArrayType: (0, schema_js_1.pgArrayType)(base),
342
361
  ...(serial ? { isGenerated: true } : {}),
343
- // PII is a code-first declaration; carry it through so query-layer
344
- // default-projection exclusion and Studio redaction can honor it.
345
- // Only set when true, keeping output byte-stable for untagged schemas.
346
- ...(config.pii ? { pii: true } : {}),
347
- ...(config.maxLength != null ? { maxLength: config.maxLength } : {}),
362
+ // PII, updatedAt and maxLength are code-first declarations; carry them
363
+ // through so query-layer projection, the updatedAt stamp and Studio
364
+ // redaction can honor them. Only set when declared, keeping output
365
+ // byte-stable for untagged schemas.
366
+ ...declaredTags,
367
+ // Always the mapped type. A declared `tsType` rides along separately.
368
+ tsType: (0, schema_js_1.pgTypeToTs)(wireType, nullable),
369
+ ...(declaredTsType !== undefined ? { declaredTsType } : {}),
348
370
  };
349
371
  columns.push(col);
350
372
  columnMap[field] = name;
@@ -373,7 +395,7 @@ function schemaDefToMetadata(def) {
373
395
  allColumns,
374
396
  primaryKey: pk,
375
397
  uniqueColumns,
376
- relations: relationsByTable.get(tableDef.name) ?? {},
398
+ relations: { ...relationsByTable.get(tableDef.name) },
377
399
  // Declared `indexes` (plain column + doc-field expression) carry through;
378
400
  // an undeclared table gets `[]`, which keeps schemaHasIndexInfo() false so
379
401
  // the index advisor stays silent. Doc-field (docPath) indexes are ignored