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