@happyvertical/smrt-core 0.40.69 → 0.41.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 (237) hide show
  1. package/AGENTS.md +29 -4
  2. package/README.md +20 -1
  3. package/agents/change-feed.md +1 -1
  4. package/agents/query-bounds.md +45 -0
  5. package/agents/schema-paths.md +786 -0
  6. package/dist/browser.d.ts +1 -0
  7. package/dist/browser.d.ts.map +1 -1
  8. package/dist/browser.js +5 -3
  9. package/dist/cascade.d.ts +120 -0
  10. package/dist/cascade.d.ts.map +1 -0
  11. package/dist/cascade.js +430 -0
  12. package/dist/cascade.js.map +1 -0
  13. package/dist/change-feed.d.ts +34 -2
  14. package/dist/change-feed.d.ts.map +1 -1
  15. package/dist/change-feed.js +52 -11
  16. package/dist/change-feed.js.map +1 -1
  17. package/dist/class.d.ts +36 -3
  18. package/dist/class.d.ts.map +1 -1
  19. package/dist/class.js +87 -9
  20. package/dist/class.js.map +1 -1
  21. package/dist/collection-cache.js +0 -0
  22. package/dist/collection-cache.js.map +1 -1
  23. package/dist/collection.d.ts +130 -2
  24. package/dist/collection.d.ts.map +1 -1
  25. package/dist/collection.js +290 -57
  26. package/dist/collection.js.map +1 -1
  27. package/dist/config.d.ts +10 -0
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js.map +1 -1
  30. package/dist/database.d.ts +8 -0
  31. package/dist/database.d.ts.map +1 -1
  32. package/dist/database.js +16 -8
  33. package/dist/database.js.map +1 -1
  34. package/dist/db-errors.d.ts +105 -0
  35. package/dist/db-errors.d.ts.map +1 -0
  36. package/dist/db-errors.js +382 -0
  37. package/dist/db-errors.js.map +1 -0
  38. package/dist/decorators/index.d.ts +80 -6
  39. package/dist/decorators/index.d.ts.map +1 -1
  40. package/dist/decorators/index.js +102 -12
  41. package/dist/decorators/index.js.map +1 -1
  42. package/dist/dispatch/bus.d.ts.map +1 -1
  43. package/dist/dispatch/bus.js +4 -3
  44. package/dist/dispatch/bus.js.map +1 -1
  45. package/dist/dispatch/collections/Dispatches.d.ts.map +1 -1
  46. package/dist/dispatch/collections/Dispatches.js +19 -4
  47. package/dist/dispatch/collections/Dispatches.js.map +1 -1
  48. package/dist/dispatch/types.d.ts +5 -0
  49. package/dist/dispatch/types.d.ts.map +1 -1
  50. package/dist/embedded-write-queue.d.ts +46 -0
  51. package/dist/embedded-write-queue.d.ts.map +1 -0
  52. package/dist/embedded-write-queue.js +66 -0
  53. package/dist/embedded-write-queue.js.map +1 -0
  54. package/dist/embeddings/storage.d.ts +7 -0
  55. package/dist/embeddings/storage.d.ts.map +1 -1
  56. package/dist/embeddings/storage.js +29 -12
  57. package/dist/embeddings/storage.js.map +1 -1
  58. package/dist/errors.d.ts +31 -3
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +34 -2
  61. package/dist/errors.js.map +1 -1
  62. package/dist/generators/changes-route.d.ts.map +1 -1
  63. package/dist/generators/changes-route.js +6 -3
  64. package/dist/generators/changes-route.js.map +1 -1
  65. package/dist/generators/mcp-runtime-template.d.ts +8 -0
  66. package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
  67. package/dist/generators/mcp-runtime-template.js +38 -4
  68. package/dist/generators/mcp-runtime-template.js.map +1 -1
  69. package/dist/generators/mcp.d.ts +16 -0
  70. package/dist/generators/mcp.d.ts.map +1 -1
  71. package/dist/generators/mcp.js +41 -3
  72. package/dist/generators/mcp.js.map +1 -1
  73. package/dist/generators/rest.d.ts +22 -0
  74. package/dist/generators/rest.d.ts.map +1 -1
  75. package/dist/generators/rest.js +34 -3
  76. package/dist/generators/rest.js.map +1 -1
  77. package/dist/hierarchical.js +1 -1
  78. package/dist/index.d.ts +7 -1
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +12 -5
  81. package/dist/interceptors.d.ts +21 -0
  82. package/dist/interceptors.d.ts.map +1 -1
  83. package/dist/interceptors.js +27 -1
  84. package/dist/interceptors.js.map +1 -1
  85. package/dist/manifest/generator.d.ts.map +1 -1
  86. package/dist/manifest/generator.js +4 -7
  87. package/dist/manifest/generator.js.map +1 -1
  88. package/dist/manifest/static-manifest.js +10 -10
  89. package/dist/manifest/static-manifest.js.map +1 -1
  90. package/dist/manifest/store.js +1 -1
  91. package/dist/manifest/store.js.map +1 -1
  92. package/dist/manifest.json +19 -19
  93. package/dist/migrations/differ.d.ts +211 -9
  94. package/dist/migrations/differ.d.ts.map +1 -1
  95. package/dist/migrations/differ.js +613 -50
  96. package/dist/migrations/differ.js.map +1 -1
  97. package/dist/migrations/generator.d.ts +31 -4
  98. package/dist/migrations/generator.d.ts.map +1 -1
  99. package/dist/migrations/generator.js +49 -5
  100. package/dist/migrations/generator.js.map +1 -1
  101. package/dist/migrations/index.d.ts +4 -2
  102. package/dist/migrations/index.d.ts.map +1 -1
  103. package/dist/migrations/index.js +6 -3
  104. package/dist/migrations/minor-units.d.ts +162 -0
  105. package/dist/migrations/minor-units.d.ts.map +1 -0
  106. package/dist/migrations/minor-units.js +381 -0
  107. package/dist/migrations/minor-units.js.map +1 -0
  108. package/dist/migrations/orchestrate.js +35 -6
  109. package/dist/migrations/orchestrate.js.map +1 -1
  110. package/dist/migrations/sqlite-rebuild.d.ts +142 -0
  111. package/dist/migrations/sqlite-rebuild.d.ts.map +1 -0
  112. package/dist/migrations/sqlite-rebuild.js +514 -0
  113. package/dist/migrations/sqlite-rebuild.js.map +1 -0
  114. package/dist/migrations/tracker.d.ts +114 -1
  115. package/dist/migrations/tracker.d.ts.map +1 -1
  116. package/dist/migrations/tracker.js +331 -16
  117. package/dist/migrations/tracker.js.map +1 -1
  118. package/dist/migrations/types.d.ts +19 -4
  119. package/dist/migrations/types.d.ts.map +1 -1
  120. package/dist/migrations.js +6 -3
  121. package/dist/object.d.ts +142 -10
  122. package/dist/object.d.ts.map +1 -1
  123. package/dist/object.js +196 -41
  124. package/dist/object.js.map +1 -1
  125. package/dist/postgres-timeouts.d.ts +240 -0
  126. package/dist/postgres-timeouts.d.ts.map +1 -0
  127. package/dist/postgres-timeouts.js +204 -0
  128. package/dist/postgres-timeouts.js.map +1 -0
  129. package/dist/query-bounds.d.ts +101 -0
  130. package/dist/query-bounds.d.ts.map +1 -0
  131. package/dist/query-bounds.js +177 -0
  132. package/dist/query-bounds.js.map +1 -0
  133. package/dist/registry/class-registration.d.ts.map +1 -1
  134. package/dist/registry/class-registration.js +3 -1
  135. package/dist/registry/class-registration.js.map +1 -1
  136. package/dist/registry/manifest-field-merge.d.ts +12 -0
  137. package/dist/registry/manifest-field-merge.d.ts.map +1 -1
  138. package/dist/registry/manifest-field-merge.js +14 -2
  139. package/dist/registry/manifest-field-merge.js.map +1 -1
  140. package/dist/registry/schema-builder.d.ts +22 -1
  141. package/dist/registry/schema-builder.d.ts.map +1 -1
  142. package/dist/registry/schema-builder.js +205 -165
  143. package/dist/registry/schema-builder.js.map +1 -1
  144. package/dist/registry/types.d.ts +35 -3
  145. package/dist/registry/types.d.ts.map +1 -1
  146. package/dist/registry.d.ts +41 -46
  147. package/dist/registry.d.ts.map +1 -1
  148. package/dist/registry.js +61 -83
  149. package/dist/registry.js.map +1 -1
  150. package/dist/scanner/manifest-generator.d.ts +45 -0
  151. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  152. package/dist/scanner/manifest-generator.js +92 -28
  153. package/dist/scanner/manifest-generator.js.map +1 -1
  154. package/dist/scanner/types.d.ts +5 -0
  155. package/dist/scanner/types.d.ts.map +1 -1
  156. package/dist/scanner/types.js.map +1 -1
  157. package/dist/schema/conflict-target.d.ts +104 -0
  158. package/dist/schema/conflict-target.d.ts.map +1 -0
  159. package/dist/schema/conflict-target.js +129 -0
  160. package/dist/schema/conflict-target.js.map +1 -0
  161. package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
  162. package/dist/schema/ddl/base-strategy.js +2 -2
  163. package/dist/schema/ddl/base-strategy.js.map +1 -1
  164. package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
  165. package/dist/schema/ddl/duckdb-strategy.js +2 -1
  166. package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
  167. package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
  168. package/dist/schema/ddl/postgres-strategy.js +12 -1
  169. package/dist/schema/ddl/postgres-strategy.js.map +1 -1
  170. package/dist/schema/generator.d.ts +307 -6
  171. package/dist/schema/generator.d.ts.map +1 -1
  172. package/dist/schema/generator.js +510 -87
  173. package/dist/schema/generator.js.map +1 -1
  174. package/dist/schema/index-utils.d.ts +120 -0
  175. package/dist/schema/index-utils.d.ts.map +1 -1
  176. package/dist/schema/index-utils.js +242 -1
  177. package/dist/schema/index-utils.js.map +1 -1
  178. package/dist/schema/index.d.ts +3 -0
  179. package/dist/schema/index.d.ts.map +1 -1
  180. package/dist/schema/index.js +4 -1
  181. package/dist/schema/live-parity.d.ts +90 -0
  182. package/dist/schema/live-parity.d.ts.map +1 -0
  183. package/dist/schema/live-parity.js +602 -0
  184. package/dist/schema/live-parity.js.map +1 -0
  185. package/dist/schema/manifest-schema.d.ts +121 -0
  186. package/dist/schema/manifest-schema.d.ts.map +1 -0
  187. package/dist/schema/manifest-schema.js +267 -0
  188. package/dist/schema/manifest-schema.js.map +1 -0
  189. package/dist/schema/schema-aggregator.d.ts +24 -10
  190. package/dist/schema/schema-aggregator.d.ts.map +1 -1
  191. package/dist/schema/schema-aggregator.js +35 -90
  192. package/dist/schema/schema-aggregator.js.map +1 -1
  193. package/dist/schema/system-table-shapes.d.ts +65 -0
  194. package/dist/schema/system-table-shapes.d.ts.map +1 -0
  195. package/dist/schema/system-table-shapes.js +187 -0
  196. package/dist/schema/system-table-shapes.js.map +1 -0
  197. package/dist/schema/types.d.ts +103 -4
  198. package/dist/schema/types.d.ts.map +1 -1
  199. package/dist/schema/utils.d.ts +2 -1
  200. package/dist/schema/utils.d.ts.map +1 -1
  201. package/dist/schema/utils.js +5 -3
  202. package/dist/schema/utils.js.map +1 -1
  203. package/dist/schema.js +4 -1
  204. package/dist/smrt-knowledge.json +20 -8
  205. package/dist/sync/apply.d.ts.map +1 -1
  206. package/dist/sync/apply.js +9 -16
  207. package/dist/sync/apply.js.map +1 -1
  208. package/dist/system/compatibility.d.ts +42 -0
  209. package/dist/system/compatibility.d.ts.map +1 -1
  210. package/dist/system/compatibility.js +182 -9
  211. package/dist/system/compatibility.js.map +1 -1
  212. package/dist/system/index.d.ts +1 -0
  213. package/dist/system/index.d.ts.map +1 -1
  214. package/dist/system/index.js +3 -2
  215. package/dist/system/retention.d.ts +237 -0
  216. package/dist/system/retention.d.ts.map +1 -0
  217. package/dist/system/retention.js +497 -0
  218. package/dist/system/retention.js.map +1 -0
  219. package/dist/system/schema.d.ts +100 -15
  220. package/dist/system/schema.d.ts.map +1 -1
  221. package/dist/system/schema.js +81 -45
  222. package/dist/system/schema.js.map +1 -1
  223. package/dist/system/types.d.ts +0 -2
  224. package/dist/system/types.d.ts.map +1 -1
  225. package/dist/testing/database.d.ts.map +1 -1
  226. package/dist/testing/database.js +1 -0
  227. package/dist/testing/database.js.map +1 -1
  228. package/dist/vite-plugin/index.d.ts.map +1 -1
  229. package/dist/vite-plugin/index.js +4 -9
  230. package/dist/vite-plugin/index.js.map +1 -1
  231. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  232. package/dist/vite-plugin/sveltekit-generator.js +71 -5
  233. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  234. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  235. package/dist/vite-plugin/web-collections.js +6 -4
  236. package/dist/vite-plugin/web-collections.js.map +1 -1
  237. package/package.json +5 -5
@@ -1,6 +1,6 @@
1
1
  import { FieldDefinition, FieldMeta, ManifestSchema, SmartObjectDefinition, SmartObjectManifest } from '../scanner/types.js';
2
2
  import { DatabaseEngine } from './ddl/types.js';
3
- import { SchemaDefinition } from './types.js';
3
+ import { DeclaredIndexDefinition, SchemaDefinition } from './types.js';
4
4
  /**
5
5
  * Structural shape of a field as read from either the build-time manifest
6
6
  * (`FieldDefinition`) or the runtime ObjectRegistry (`getAllFields()` returns
@@ -25,8 +25,32 @@ interface RegistryField {
25
25
  _meta?: FieldMeta;
26
26
  }
27
27
  type SchemaGeneratorConfig = {
28
+ /**
29
+ * The table's resolved conflict target. Registry callers pass
30
+ * `ObjectRegistry.getConflictColumns()`; the manifest path passes the
31
+ * object's `decoratorConfig` after `ManifestGenerator.normalizeConflictColumns()`
32
+ * materialized the tenant-aware default (#2360). When absent, the generator
33
+ * derives the default itself from `tenantScoped` and the strategy.
34
+ */
28
35
  conflictColumns?: string[];
36
+ /**
37
+ * The schema owner's `@smrt({ tenantScoped })` config as the manifest
38
+ * carries it — consulted only to derive the default conflict target when
39
+ * `conflictColumns` is absent.
40
+ */
41
+ tenantScoped?: boolean | {
42
+ field?: string;
43
+ };
29
44
  idType?: 'uuid' | 'text';
45
+ /**
46
+ * `@smrt({ indexes: [...] })` declarations (#2357).
47
+ *
48
+ * The manifest paths receive the whole `decoratorConfig`, so this arrives for
49
+ * free there. The runtime paths rebuild a narrow config bag by hand
50
+ * (`schema/utils.ts`, `testing/database.ts`) and must thread it explicitly —
51
+ * omitting it is what made the option unreachable at runtime.
52
+ */
53
+ indexes?: DeclaredIndexDefinition[];
30
54
  registry?: {
31
55
  getConfig?(className: string): {
32
56
  idType?: 'uuid' | 'text';
@@ -55,6 +79,263 @@ export declare class SchemaGenerator {
55
79
  * Generate column definitions
56
80
  */
57
81
  private generateColumns;
82
+ /**
83
+ * Whether an existing index already leads with `leadingColumns`, in order.
84
+ *
85
+ * Only an UNQUALIFIED index (no partial `WHERE`, no JSON-path expression)
86
+ * counts. A partial index such as `... WHERE _meta_type = 'Article'` cannot
87
+ * serve a base-class polymorphic query, which carries no subtype predicate,
88
+ * so it must not suppress the standalone index (#2359, review of #2384).
89
+ *
90
+ * A B-tree serves any prefix of its column list, so an index over
91
+ * `(tenant_id, created_at, status)` covers both `(tenant_id)` equality
92
+ * lookups and `(tenant_id, created_at)` ordering — hence the prefix test
93
+ * rather than an exact match (#2363).
94
+ */
95
+ private hasUnqualifiedLeadingColumns;
96
+ /**
97
+ * Whether an existing index already serves equality lookups on `column`.
98
+ *
99
+ * Single-column shorthand for {@link hasUnqualifiedLeadingColumns}.
100
+ */
101
+ private hasUnqualifiedLeadingIndex;
102
+ /**
103
+ * The column list of the default list-ordering index for a table.
104
+ *
105
+ * Every generated list surface — REST, MCP, and the SvelteKit list route —
106
+ * pages with `ORDER BY created_at DESC, <pk> ASC` (`DEFAULT_LIST_ORDER_BY`,
107
+ * #2367). On a tenant-scoped table that page is always preceded by a
108
+ * `tenant_id = ?` equality filter from the tenancy interceptor, so the
109
+ * serving index leads with the tenant column and orders inside it.
110
+ *
111
+ * `created_at` is generator-owned on every path — a declared `createdAt`
112
+ * field is rewritten to the same `TIMESTAMP NOT NULL DEFAULT
113
+ * current_timestamp` column — so it is never a primary key and never
114
+ * inline-UNIQUE. That is why only `indexes` needs checking for existing
115
+ * coverage: no constraint-backed index can already order this column.
116
+ *
117
+ * Returns `null` when the table has no `created_at` column (no path emits
118
+ * such a table today, but the helper stays total).
119
+ */
120
+ private getDefaultListOrderingColumns;
121
+ /**
122
+ * Ensure the table can serve its own default list page from an index.
123
+ *
124
+ * Generated REST/MCP/SvelteKit list routes all page with
125
+ * `ORDER BY created_at DESC, <pk> ASC LIMIT n` and no `created_at` index
126
+ * existed on any schema path (the dead AST path indexed `updated_at`
127
+ * instead), so every default list page was a sequential scan plus a top-N
128
+ * sort of the whole table — 21 ms against 0.1 ms with the right composite on
129
+ * the workload the assessment measured (#2363, finding A2).
130
+ *
131
+ * Emitted shape:
132
+ *
133
+ * - tenant-scoped table → `(<tenant column>, created_at)`. The tenancy
134
+ * interceptor adds `tenant_id = ?` to every list, so the tenant column
135
+ * leads and `created_at` orders within it.
136
+ * - otherwise → `(created_at)`.
137
+ *
138
+ * No `DESC` declaration: PostgreSQL scans a B-tree backwards just as
139
+ * cheaply, and `IndexDefinition` carries no per-column direction. The
140
+ * trailing primary-key tiebreak is left out deliberately — its direction is
141
+ * opposite to `created_at`'s, so no single-direction index can satisfy the
142
+ * whole key anyway; the leading columns turn a full sort into an index scan
143
+ * with an incremental sort over rows that share a timestamp.
144
+ *
145
+ * Call this AFTER {@link appendDeclaredIndexes} and BEFORE
146
+ * {@link ensureReferenceColumnIndexes}: a declared composite that already
147
+ * leads with the same columns suppresses this one, and this composite in
148
+ * turn suppresses the standalone tenant index that would otherwise be a
149
+ * redundant prefix of it.
150
+ */
151
+ private ensureDefaultListOrderingIndex;
152
+ /**
153
+ * The `(meta_type, meta_id)` owner-lookup columns for a polymorphic
154
+ * association table, or `null` when the table isn't one.
155
+ *
156
+ * `SmrtPolymorphicAssociation` contributes `metaType`/`metaId`/`role` (and
157
+ * `sortOrder`) to any concrete subclass's manifest — the scanner's
158
+ * `FRAMEWORK_ABSTRACT_BASE_NAMES` wiring merges the abstract base's fields
159
+ * directly into the subclass, because the base carries no `@smrt()`
160
+ * decorator of its own to declare an index on. Detected structurally, by
161
+ * the same three-column test `cascade.ts`'s `isPolymorphicAssociationClass`
162
+ * uses on registry field maps: requiring all three keeps an unrelated class
163
+ * that merely happens to carry a `metaType` column from being treated as an
164
+ * association table.
165
+ */
166
+ private getPolymorphicAssociationOwnerColumns;
167
+ /**
168
+ * Ensure a polymorphic association table indexes its owner lookup
169
+ * `(meta_type, meta_id)` (#2364, epic #2382 finding A3).
170
+ *
171
+ * A concrete association's `conflictColumns` lead with its own FK (e.g.
172
+ * `AssetAssociation`'s `asset_id, meta_type, meta_id, role`), so
173
+ * `meta_type`/`meta_id` sit in the *middle* of that unique index — a
174
+ * lookup like `AssetAssociationCollection.byLeft(metaType, metaId)`
175
+ * ("what points at this target") filters columns 2-3 of a 4-column index
176
+ * and cannot use it as a leading prefix. `metaId` is deliberately a bare
177
+ * string, never a typed `@foreignKey`/`@crossPackageRef` (there is no
178
+ * single target table), so {@link ensureReferenceColumnIndexes} never
179
+ * covers it either.
180
+ *
181
+ * Call this alongside {@link ensureDefaultListOrderingIndex}, before
182
+ * {@link ensureReferenceColumnIndexes}.
183
+ *
184
+ * Blast radius: this applies retroactively to every
185
+ * `SmrtPolymorphicAssociation` subclass across every package on the next
186
+ * `db:migrate`, not only the ones a given change touches — roll the wave
187
+ * out with `smrt db:migrate --postgres-safe` (#2362), same as any other
188
+ * bulk index addition.
189
+ */
190
+ private ensurePolymorphicAssociationIndex;
191
+ /**
192
+ * Append `@smrt({ indexes: [...] })` declarations to a generated index set
193
+ * (#2357).
194
+ *
195
+ * Shared by every schema path — build-time AST, runtime registry CTI/STI, and
196
+ * manifest CTI/STI — so one declaration behaves identically however the schema
197
+ * was derived. Call it *first*, ahead of
198
+ * {@link ensureDefaultListOrderingIndex} (#2363) and
199
+ * {@link ensureReferenceColumnIndexes}: a declared composite leading with a
200
+ * reference column (e.g. `tenant_id`), or with the default ordering columns,
201
+ * is exactly what those helpers' leads-with check is meant to defer to, and
202
+ * appending afterwards would leave the table with a redundant standalone
203
+ * index (#2384, #2359).
204
+ *
205
+ * Nothing is dropped quietly. A column that resolves to no column on the
206
+ * table, a malformed entry, and a name collision with a different index are
207
+ * all hard errors — a silently missing index is a production performance bug
208
+ * that surfaces only under load.
209
+ *
210
+ * @param indexes - Generated index set, appended in place
211
+ * @param declared - The object's `indexes` config, from any config source
212
+ * @param columns - Emitted columns, keyed by column name
213
+ * @param tableName - Table the indexes belong to (for error messages)
214
+ */
215
+ private appendDeclaredIndexes;
216
+ /** Narrow one raw `indexes[]` entry, rejecting anything unusable. */
217
+ private validateDeclaredIndex;
218
+ /**
219
+ * Resolve a declared index column to an emitted column name.
220
+ *
221
+ * Declarations name SMRT fields, but only the runtime/manifest paths
222
+ * snake_case them into columns — the build-time AST path keys columns by the
223
+ * field name itself. Prefer an exact column hit, then the snake_case form, and
224
+ * otherwise return the input unchanged so the caller reports it as unknown.
225
+ */
226
+ private resolveDeclaredIndexColumn;
227
+ /** Whether two index definitions describe the same database object. */
228
+ private sameIndexTarget;
229
+ /**
230
+ * Ensure every reference column — `@foreignKey`, `@crossPackageRef`, and the
231
+ * tenancy-injected `tenant_id` — has an index leading with it.
232
+ *
233
+ * These are the columns every relationship load, `include:` batch, reverse
234
+ * ownership lookup and tenant-scoped read filters on. Before #2359 only the
235
+ * registry (test) paths indexed foreign keys, the manifest (production)
236
+ * paths indexed none of them, and `tenant_id` was indexed nowhere (#2356):
237
+ * 196/231 `@foreignKey` and 91/92 `@crossPackageRef` columns shipped with no
238
+ * serving index. Every schema path now calls this helper so the four paths
239
+ * stay in step (see `schema-path-parity.test.ts`).
240
+ *
241
+ * "Leading with it" rather than "always add": a table that already has an
242
+ * unqualified index starting on the column (commonly the `conflictColumns`
243
+ * unique index, or an `indexed: true` opt-in) is already served, and a
244
+ * standalone duplicate would only cost writes. A *partial* index does not
245
+ * count — see {@link hasUnqualifiedLeadingIndex}.
246
+ *
247
+ * Reference columns are therefore always indexed; `indexed: true` on one of
248
+ * them is redundant and harmless.
249
+ *
250
+ * Call this LAST in every path, after every other index (conflict, opt-in,
251
+ * unique, the declared composites, the default list-ordering composite, and
252
+ * any future addition) has been appended, so the leads-with suppression sees
253
+ * the full set. In particular {@link ensureDefaultListOrderingIndex} runs
254
+ * first on a tenant-scoped table: its `(tenant_id, created_at)` composite
255
+ * serves the tenant equality filter too, so no standalone tenant index is
256
+ * added (#2363).
257
+ */
258
+ private ensureReferenceColumnIndexes;
259
+ /**
260
+ * Whether `conflictColumns` names exactly the primary key column(s).
261
+ *
262
+ * `ON CONFLICT (id)` binds to the primary-key constraint on every engine, so
263
+ * a separate unique index over the same column set is a second B-tree over
264
+ * the same random UUIDs with nothing to add (#2359, finding A5).
265
+ */
266
+ private conflictColumnsArePrimaryKey;
267
+ /**
268
+ * The table's tenant column as the generated columns describe it: the
269
+ * column carrying `referenceKind: 'tenantId'` (set from the field's
270
+ * `__tenancy.isTenantIdField` marker on every path).
271
+ */
272
+ private findTenantColumn;
273
+ /**
274
+ * Resolve the conflict target the unique conflict index must cover
275
+ * (#2360). `config.conflictColumns` wins when the caller resolved it —
276
+ * registry callers pass `ObjectRegistry.getConflictColumns()`, the manifest
277
+ * pipeline passes the normalized `decoratorConfig` — otherwise the strategy
278
+ * default, led by the tenant column when `config.tenantScoped` names a
279
+ * tenant field that exists as a column. Both roads apply the same rule as
280
+ * `ObjectRegistry.getConflictColumns()`, so schema and upsert agree by
281
+ * construction (`schema-path-parity.test.ts`).
282
+ */
283
+ private resolveConflictTarget;
284
+ /**
285
+ * Keep `(slug, context)` lookups served when `conflictColumns` are custom.
286
+ *
287
+ * The default conflict index is `(slug, context)`, and `loadFromSlug()`,
288
+ * `getId()` and `getSavedId()` all filter on `slug`/`context` regardless of
289
+ * the configured conflict key. A class that declares custom conflict
290
+ * columns replaces that unique index and, before #2359, left those lookups
291
+ * with no index at all — 120 tables (finding A7).
292
+ *
293
+ * Emitting a plain `(slug, context)` index is the safer of the two fixes:
294
+ * routing the lookups through the conflict key would change which row a
295
+ * slug resolves to on every such class, whereas an extra non-unique index
296
+ * is purely additive. It is skipped when an unqualified index already
297
+ * serves the lookup — one leading with `slug` (e.g. custom conflict columns
298
+ * that still start with it), or the tenant-led default key
299
+ * `(tenant_id, slug, context)` of a tenant-scoped table (#2360): every slug
300
+ * lookup on such a table carries the tenant predicate (#2365) and is served
301
+ * by that prefix, so a second index would only cost writes.
302
+ */
303
+ private ensureSlugLookupIndex;
304
+ /**
305
+ * Emit the unique conflict index of an STI table from the resolved
306
+ * conflict target (#2360): the STI default `(slug, context, _meta_type)`,
307
+ * a custom `@smrt({ conflictColumns })` declared on the STI root, or the
308
+ * default led by the tenant column when the root is tenant-scoped. Shared
309
+ * by the registry and manifest STI paths so they cannot drift.
310
+ *
311
+ * No index is emitted when the target is the primary key itself
312
+ * (`ON CONFLICT (id)` binds to the PK constraint), matching the CTI paths.
313
+ */
314
+ private emitStiConflictIndex;
315
+ /**
316
+ * Emit unique indexes for `@field({ unique: true })` columns on an STI table.
317
+ *
318
+ * STI columns are the union of every class in the hierarchy, all nullable,
319
+ * so a column-level `UNIQUE` (what CTI renders inline) is not enough on its
320
+ * own: the migration differ cannot add a column constraint to an existing
321
+ * table, but it can add an index. Two shapes (#2359, finding A4):
322
+ *
323
+ * - declared on the STI **base** (and therefore inherited by every class in
324
+ * the table): one full unique index, table-wide like CTI;
325
+ * - declared only on a **descendant**: one partial unique index per class
326
+ * that carries the flag (the declaring class and, through inherited field
327
+ * metadata, its own descendants), `WHERE _meta_type = '<qualified>'`, so
328
+ * siblings that merely share the column name are not constrained.
329
+ * Uniqueness is then enforced per concrete class, not across the subtree
330
+ * — a documented limitation of the discriminator-partial shape. Engines
331
+ * without partial indexes (DuckDB, JSON) skip this shape entirely rather
332
+ * than widen it to a table-wide UNIQUE (see DuckDBStrategy.generateIndexes
333
+ * and SchemaComparer.compareIndexes).
334
+ *
335
+ * @param declarers - column name → classes whose field metadata carries
336
+ * `unique: true` for that column, in hierarchy order (base first).
337
+ */
338
+ private emitStiUniqueIndexes;
58
339
  /**
59
340
  * Generate index definitions
60
341
  */
@@ -106,7 +387,14 @@ export declare class SchemaGenerator {
106
387
  * - _meta_type: Discriminator column to identify class type
107
388
  * - _meta_data: JSON column for flexible field storage
108
389
  * - Union of all FK columns from descendants (all nullable)
109
- * - Partial indexes for FK columns (filtered by _meta_type)
390
+ * - One plain index per reference column (FK / cross-package ref /
391
+ * tenant_id). Plain rather than partial-by-class: base-class polymorphic
392
+ * queries carry no `_meta_type` predicate, so a partial index could not
393
+ * serve them, while a plain index serves both those and the child
394
+ * collections' `_meta_type = X AND fk = ?` filters — one index per column
395
+ * instead of one per (column, class) (#2359).
396
+ * - Unique indexes for `@field({ unique: true })` columns — see
397
+ * {@link emitStiUniqueIndexes}.
110
398
  *
111
399
  * @param baseClassName - Base class name for the STI hierarchy
112
400
  * @param tableName - Shared table name (from base class)
@@ -155,6 +443,13 @@ export declare class SchemaGenerator {
155
443
  * @returns ManifestSchema for storage in manifest.json
156
444
  */
157
445
  generateCTISchemaFromManifest(className: string, tableName: string, fields: Record<string, FieldDefinition>, config?: SchemaGeneratorConfig): ManifestSchema;
446
+ /**
447
+ * Resolve a class name (simple or qualified) to the key it is stored under
448
+ * in `manifest.objects`. Falls back to the input when the manifest does not
449
+ * carry the class (e.g. an external STI base absent from the aggregated
450
+ * manifest), so callers can keep using it as an opaque label.
451
+ */
452
+ private resolveManifestClassKey;
158
453
  /**
159
454
  * Find all descendants of a class in the manifest
160
455
  *
@@ -174,13 +469,19 @@ export declare class SchemaGenerator {
174
469
  */
175
470
  private toSnakeCase;
176
471
  /**
177
- * Generate SQL CREATE TABLE statement from schema definition
472
+ * Generate the CREATE TABLE statement for a schema definition.
178
473
  *
179
- * This is the single source of truth for SQL generation, consolidating
180
- * logic that was previously duplicated across multiple code paths.
474
+ * With an `engine` this delegates to that engine's DDL strategy. Without
475
+ * one it renders the engine-neutral preview stored in `schema.ddl` and
476
+ * `manifest.json`: abstract SQL types, no indexes, no triggers. That
477
+ * preview is NOT an executable representation of the table (#2358) —
478
+ * executable paths (`db:migrate`, `MigrationGenerator`, `SchemaAggregator`,
479
+ * `createIsolatedTestDbFromManifest`) render `schema.columns` and
480
+ * `schema.indexes` through `getDDLStrategy(engine)` instead.
181
481
  *
182
482
  * @param schema - Schema definition object
183
- * @returns SQL CREATE TABLE statement with indexes
483
+ * @param engine - Optional target engine; omit for the neutral preview
484
+ * @returns SQL CREATE TABLE statement (no indexes)
184
485
  */
185
486
  generateSQL(schema: SchemaDefinition, engine?: DatabaseEngine): string;
186
487
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/schema/generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/schema/generator.ts"],"names":[],"mappings":"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"}