@atscript/moost-db 0.1.149 → 0.1.150

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.
package/dist/index.d.cts CHANGED
@@ -6,6 +6,12 @@ import { Mate, Moost, TConsoleBase, TMateParamMeta, TMoostMetadata } from "moost
6
6
  import { parseUrl } from "@uniqu/url";
7
7
  import { EventContext } from "@wooksjs/event-core";
8
8
 
9
+ //#region src/http-path.d.ts
10
+ /** The published path of every model one app serves (`null`: mounted, published nowhere). */
11
+ interface THttpPathScope {
12
+ readonly paths: ReadonlyMap<TAtscriptAnnotatedType, string | null>;
13
+ }
14
+ //#endregion
9
15
  //#region src/as-readable.controller.d.ts
10
16
  /**
11
17
  * Endpoint a {@link AsReadableController.prepareRequest} call serves. `/one`
@@ -89,8 +95,9 @@ interface TDbParsedRequest {
89
95
  * Abstract base class for read-only HTTP controllers over an Atscript interface.
90
96
  *
91
97
  * Shared responsibilities (implemented here):
92
- * - Stamps `@db.http.path` on the bound interface's metadata at registration
93
- * with the final public path (leading slash + Moost `globalPrefix`).
98
+ * - Publishes `@db.http.path` (the model's value-help URL) per app after
99
+ * `app.init()`, derived from each controller's own bound route (see
100
+ * `docs/http/index.md`); the constructor only records which model it serves.
94
101
  * - Lazily serializes the bound interface for the `/meta` endpoint
95
102
  * (see {@link getSerializeOptions}).
96
103
  * - Provides DTO-backed validators for the Uniquery controls DTOs and the
@@ -120,23 +127,44 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
120
127
  protected app: Moost;
121
128
  /** Cached serialized type definition (lazy, computed on first access). */
122
129
  private _serializedType?;
130
+ /** App scope every cache below was built for; a different scope drops them all ({@link enterScope}). */
131
+ private _cacheScope?;
132
+ /** Scope of the synchronous build in flight — instance-local, cleared in `finally`. */
133
+ protected _buildScope?: THttpPathScope;
123
134
  /** Cached full meta response (computed lazily on first meta() call). */
124
135
  private _metaResponse?;
125
136
  /** {@link metaCacheKey} the cached response was built for. */
126
137
  private _metaResponseKey?;
127
138
  /** Cached serialized form schemas keyed by `FormType.name` — populated lazily by {@link metaForm}. */
128
139
  private _formSchemas;
129
- constructor(boundType: T, controllerName: string, app: Moost, kindTag?: string);
140
+ /**
141
+ * @param opts.canonical Multi-mount models only: marks (`true`) or excludes
142
+ * (`false`) this controller as the model's published value-help route
143
+ * (since 0.1.150). Decorator-bound controllers use the decorator option.
144
+ */
145
+ constructor(boundType: T, controllerName: string, app: Moost, kindTag?: string, opts?: {
146
+ canonical?: boolean;
147
+ });
130
148
  /**
131
149
  * Subclass contract: return `true` if `path` addresses a field that exists
132
150
  * AND is visible to the current request — see the DB controller's override
133
151
  * for the full list of positions that consult it.
134
152
  */
135
153
  protected abstract hasField(path: string): boolean;
136
- /** Sets @db.http.path on the type metadata from the controller's computed prefix. */
137
- private _resolveHttpPath;
138
- /** Lazily serializes the bound type (after all controllers have set @db.http.path). */
154
+ /**
155
+ * The app of the current event, through DI — never the one this
156
+ * (singleton) instance was constructed in, which may be gone (a re-booted
157
+ * app, a hot reload). Outside an event it is {@link app}.
158
+ *
159
+ * @since 0.1.150
160
+ */
161
+ protected currentApp(): Promise<Moost>;
162
+ /** Lazily serializes the bound type (dropped with the other caches on a scope change). */
139
163
  protected getSerializedType(): TSerializedAnnotatedType;
164
+ /** Drops every scope-dependent cache when `scope` is not the one they were built for. */
165
+ private enterScope;
166
+ /** Runs a synchronous build with `scope`'s `db.http.path` overrides applied. */
167
+ private buildScoped;
140
168
  /**
141
169
  * Serializes a type for the meta surfaces (`/meta`, `/meta/form/:name`)
142
170
  * with {@link getSerializeOptions}, then re-points every reference chain to
@@ -145,6 +173,11 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
145
173
  * serialize exactly as before.
146
174
  */
147
175
  protected serializeForMeta(type: TAtscriptAnnotatedType): TSerializedAnnotatedType;
176
+ /**
177
+ * {@link getSerializeOptions} plus the current build's per-app `db.http.path`
178
+ * overrides (a subclass's own `annotationOverrides` entry wins for the same key).
179
+ */
180
+ private _effectiveSerializeOptions;
148
181
  /**
149
182
  * One-time initialization hook. Override to seed data, register watchers, etc.
150
183
  */
@@ -282,6 +315,14 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
282
315
  * response by principal.
283
316
  */
284
317
  meta(): Promise<TMetaResponse>;
318
+ /**
319
+ * The root of a controller's own `/meta` carries its own mount as
320
+ * `db.http.path` (a secondary mount answers with its own route; references to
321
+ * the model elsewhere carry the canonical one). Left alone for parametric
322
+ * mounts and when the serialize options strip the key. Never mutates the
323
+ * cached envelope.
324
+ */
325
+ private _withOwnHttpPath;
285
326
  /**
286
327
  * The `/meta` payload for the current request — the cached envelope
287
328
  * through {@link applyMetaOverlay} — WITHOUT the `/meta` route's
@@ -1302,6 +1343,11 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
1302
1343
  private _searchIndexFieldPaths;
1303
1344
  /** `@db.index.geo` indexes — their fields carry physical names, mapped back to logical paths. */
1304
1345
  private _geoIndexFieldPaths;
1346
+ /**
1347
+ * The index visibility gate applies: {@link hasField} is overridden, or the
1348
+ * model has `@db.writeOnly` fields (never readable through a search hit).
1349
+ */
1350
+ private get _indexGateActive();
1305
1351
  /** Every path `entry` reads is visible to this request. */
1306
1352
  private _indexVisible;
1307
1353
  /**
@@ -1668,6 +1714,14 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
1668
1714
  * nothing can apply is ignored. Resolvers (query targets, `resolveQuery`)
1669
1715
  * refuse it — a subclass override that applies the term must return a new
1670
1716
  * filter object.
1717
+ *
1718
+ * A string field matches when the term is a substring of its text; an
1719
+ * INTEGER field (`number.int` and its sizes, `@expect.int`,
1720
+ * `@db.default.increment`) when it is a substring of the number's decimal
1721
+ * text — `2946` finds `29461277` (since 0.1.150). To search numeric IDs
1722
+ * declare `@db.column.searchable` (fallback) or `@db.index.fulltext`
1723
+ * (native, exact number) on the integer field — override this hook only for
1724
+ * search rules the annotations cannot express.
1671
1725
  */
1672
1726
  protected applySearchFallback(filter: FilterExpr | undefined, controls: Record<string, unknown>): FilterExpr | undefined;
1673
1727
  /**
@@ -1690,6 +1744,21 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
1690
1744
  * the same shape as the one this whole path exists to remove.
1691
1745
  */
1692
1746
  private _aggregateControls;
1747
+ /**
1748
+ * `$count` of a native text / vector search: the adapter's own count, so it
1749
+ * agrees with `/query` and `/pages`. A text search counts every match (the
1750
+ * single returned row is discarded); a vector search counts the nearest
1751
+ * neighbours up to `$limit` (default 1000), as `/query` returns them.
1752
+ */
1753
+ private _countSearched;
1754
+ /**
1755
+ * The controls a native search / vector read is run with: the request's
1756
+ * sealed controls (so every search control — `$fuzzy`, … — reaches the
1757
+ * adapter) plus the select, the threshold and the caller's paging. ONE
1758
+ * builder for `/query`, `/pages` and the native `$count`, so they can't
1759
+ * disagree about what a search matches.
1760
+ */
1761
+ private _searchReadControls;
1693
1762
  private _resolveReadStrategy;
1694
1763
  /**
1695
1764
  * Post-read row decoration hook. Not implemented by
@@ -1738,12 +1807,6 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
1738
1807
  * when implemented.
1739
1808
  */
1740
1809
  private _augmentAndDecorate;
1741
- /**
1742
- * The app of the current event, through DI — never the one this
1743
- * (singleton) instance was constructed in, which may be gone (a re-booted
1744
- * app, a hot reload).
1745
- */
1746
- private _currentApp;
1747
1810
  /** The class's `@DbActionsFrom` delegations, validated on first use (per app). */
1748
1811
  private _delegations;
1749
1812
  /**
@@ -2212,7 +2275,8 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
2212
2275
  * Bulk auto-lift: each item carries its own `version` → `$cas`.
2213
2276
  * NOTE: per-item conflict disambiguation in the response body is deferred
2214
2277
  * (§6.4) — the aggregate `{ matchedCount, modifiedCount }` surfaces partial
2215
- * application; callers can detect mismatches via `modifiedCount < N`.
2278
+ * application; callers can detect mismatches via `matchedCount < N` (`modifiedCount` may
2279
+ * be lower for unchanged values, e.g. a version-exempt patch on MySQL / Mongo).
2216
2280
  */
2217
2281
  private _resolveBulkCas;
2218
2282
  /**
@@ -2344,7 +2408,9 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
2344
2408
  protected readonly searchableFields: readonly string[];
2345
2409
  /** The `@meta.id` field name on the bound interface, if any. */
2346
2410
  protected readonly primaryKey: string | undefined;
2347
- constructor(boundType: T, controllerName: string, app: Moost);
2411
+ constructor(boundType: T, controllerName: string, app: Moost, opts?: {
2412
+ canonical?: boolean;
2413
+ });
2348
2414
  /** Executes a value-help query against the backing source. */
2349
2415
  protected abstract query(controls: ValueHelpQuery<DataType>): Promise<{
2350
2416
  data: DataType[];
@@ -2482,7 +2548,9 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
2482
2548
  declare class AsJsonValueHelpController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsValueHelpController<T, DataType> {
2483
2549
  protected rows: DataType[];
2484
2550
  private _pkIndex?;
2485
- constructor(boundType: T, rows: DataType[], app: Moost, controllerName?: string);
2551
+ constructor(boundType: T, rows: DataType[], app: Moost, controllerName?: string, opts?: {
2552
+ canonical?: boolean;
2553
+ });
2486
2554
  protected query(controls: ValueHelpQuery<DataType>): Promise<{
2487
2555
  data: DataType[];
2488
2556
  count: number;
@@ -2621,6 +2689,16 @@ interface TReadableBindingMeta {
2621
2689
  model?: TAtscriptAnnotatedType;
2622
2690
  /** Resolves the readable — lazily for token/factory, identity for instance. */
2623
2691
  resolve: () => AtscriptDbReadable<any>;
2692
+ /** The route prefix the decorator derived (`options.prefix`, `@db.http.path`, table name, …). @since 0.1.150 */
2693
+ prefix?: string;
2694
+ /**
2695
+ * Where {@link prefix} came from: the decorator's `prefix` option (or the
2696
+ * factory form), the model's own `@db.http.path`, or its table / view name.
2697
+ * @since 0.1.150
2698
+ */
2699
+ prefixSource?: "option" | "annotation" | "name";
2700
+ /** The decorator's `canonical` option. @since 0.1.150 */
2701
+ canonical?: boolean;
2624
2702
  }
2625
2703
  /**
2626
2704
  * Class- and method-level metadata written by `@atscript/moost-db`'s
@@ -2753,6 +2831,18 @@ interface TControllerBindingOptions {
2753
2831
  * annotation. Ignored for instance/factory bindings.
2754
2832
  */
2755
2833
  space?: string;
2834
+ /**
2835
+ * Marks (`true`) or excludes (`false`) this controller as the one whose route
2836
+ * is published as the model's value-help path (`db.http.path`) when the same
2837
+ * model is served by several controllers in one app. A single `canonical: true`
2838
+ * mount wins; `canonical: false` mounts are never published; without a marker
2839
+ * the model's own `@db.http.path`-derived mount breaks ties, and any remaining
2840
+ * ambiguity leaves the path unset (with a boot warning). Parametric mounts
2841
+ * (`:param` / `*` segments) are never published.
2842
+ *
2843
+ * @since 0.1.150
2844
+ */
2845
+ canonical?: boolean;
2756
2846
  }
2757
2847
  /**
2758
2848
  * Combines the boilerplate needed to turn an {@link AsDbController}
package/dist/index.d.mts CHANGED
@@ -6,6 +6,12 @@ import { parseUrl } from "@uniqu/url";
6
6
  import { AggregateFn, AtscriptDbReadable, AtscriptDbTable, BucketUnit, FilterExpr, FlatOf, TCrudOp, TCrudOp as TCrudOp$1, TCrudPermissions, TCrudPermissions as TCrudPermissions$1, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbActionTargetSummary, TDbActionTargetSummary as TDbActionTargetSummary$1, TDbAvailableActions, TDbAvailableActions as TDbAvailableActions$1, TDbFieldMeta, TDbRemoveGuardContext, TDbRemoveGuardContext as TDbRemoveGuardContext$1, TDbWriteAction, TDbWriteAction as TDbWriteAction$1, TDbWriteCheckContext, TDbWriteCheckContext as TDbWriteCheckContext$1, TDbWriteGuardContext, TDbWriteGuardContext as TDbWriteGuardContext$1, TFilterPredicate, TIdResolveOptions, TIdentification, TMetaResponse, TQueryPathOp, TQueryPathOp as TQueryPathOp$1, TQueryPathRefs, TQueryPathSource, Uniquery, UniqueryControls, collectQueryPaths } from "@atscript/db";
7
7
  import { EventContext } from "@wooksjs/event-core";
8
8
 
9
+ //#region src/http-path.d.ts
10
+ /** The published path of every model one app serves (`null`: mounted, published nowhere). */
11
+ interface THttpPathScope {
12
+ readonly paths: ReadonlyMap<TAtscriptAnnotatedType, string | null>;
13
+ }
14
+ //#endregion
9
15
  //#region src/as-readable.controller.d.ts
10
16
  /**
11
17
  * Endpoint a {@link AsReadableController.prepareRequest} call serves. `/one`
@@ -89,8 +95,9 @@ interface TDbParsedRequest {
89
95
  * Abstract base class for read-only HTTP controllers over an Atscript interface.
90
96
  *
91
97
  * Shared responsibilities (implemented here):
92
- * - Stamps `@db.http.path` on the bound interface's metadata at registration
93
- * with the final public path (leading slash + Moost `globalPrefix`).
98
+ * - Publishes `@db.http.path` (the model's value-help URL) per app after
99
+ * `app.init()`, derived from each controller's own bound route (see
100
+ * `docs/http/index.md`); the constructor only records which model it serves.
94
101
  * - Lazily serializes the bound interface for the `/meta` endpoint
95
102
  * (see {@link getSerializeOptions}).
96
103
  * - Provides DTO-backed validators for the Uniquery controls DTOs and the
@@ -120,23 +127,44 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
120
127
  protected app: Moost;
121
128
  /** Cached serialized type definition (lazy, computed on first access). */
122
129
  private _serializedType?;
130
+ /** App scope every cache below was built for; a different scope drops them all ({@link enterScope}). */
131
+ private _cacheScope?;
132
+ /** Scope of the synchronous build in flight — instance-local, cleared in `finally`. */
133
+ protected _buildScope?: THttpPathScope;
123
134
  /** Cached full meta response (computed lazily on first meta() call). */
124
135
  private _metaResponse?;
125
136
  /** {@link metaCacheKey} the cached response was built for. */
126
137
  private _metaResponseKey?;
127
138
  /** Cached serialized form schemas keyed by `FormType.name` — populated lazily by {@link metaForm}. */
128
139
  private _formSchemas;
129
- constructor(boundType: T, controllerName: string, app: Moost, kindTag?: string);
140
+ /**
141
+ * @param opts.canonical Multi-mount models only: marks (`true`) or excludes
142
+ * (`false`) this controller as the model's published value-help route
143
+ * (since 0.1.150). Decorator-bound controllers use the decorator option.
144
+ */
145
+ constructor(boundType: T, controllerName: string, app: Moost, kindTag?: string, opts?: {
146
+ canonical?: boolean;
147
+ });
130
148
  /**
131
149
  * Subclass contract: return `true` if `path` addresses a field that exists
132
150
  * AND is visible to the current request — see the DB controller's override
133
151
  * for the full list of positions that consult it.
134
152
  */
135
153
  protected abstract hasField(path: string): boolean;
136
- /** Sets @db.http.path on the type metadata from the controller's computed prefix. */
137
- private _resolveHttpPath;
138
- /** Lazily serializes the bound type (after all controllers have set @db.http.path). */
154
+ /**
155
+ * The app of the current event, through DI — never the one this
156
+ * (singleton) instance was constructed in, which may be gone (a re-booted
157
+ * app, a hot reload). Outside an event it is {@link app}.
158
+ *
159
+ * @since 0.1.150
160
+ */
161
+ protected currentApp(): Promise<Moost>;
162
+ /** Lazily serializes the bound type (dropped with the other caches on a scope change). */
139
163
  protected getSerializedType(): TSerializedAnnotatedType;
164
+ /** Drops every scope-dependent cache when `scope` is not the one they were built for. */
165
+ private enterScope;
166
+ /** Runs a synchronous build with `scope`'s `db.http.path` overrides applied. */
167
+ private buildScoped;
140
168
  /**
141
169
  * Serializes a type for the meta surfaces (`/meta`, `/meta/form/:name`)
142
170
  * with {@link getSerializeOptions}, then re-points every reference chain to
@@ -145,6 +173,11 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
145
173
  * serialize exactly as before.
146
174
  */
147
175
  protected serializeForMeta(type: TAtscriptAnnotatedType): TSerializedAnnotatedType;
176
+ /**
177
+ * {@link getSerializeOptions} plus the current build's per-app `db.http.path`
178
+ * overrides (a subclass's own `annotationOverrides` entry wins for the same key).
179
+ */
180
+ private _effectiveSerializeOptions;
148
181
  /**
149
182
  * One-time initialization hook. Override to seed data, register watchers, etc.
150
183
  */
@@ -282,6 +315,14 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
282
315
  * response by principal.
283
316
  */
284
317
  meta(): Promise<TMetaResponse>;
318
+ /**
319
+ * The root of a controller's own `/meta` carries its own mount as
320
+ * `db.http.path` (a secondary mount answers with its own route; references to
321
+ * the model elsewhere carry the canonical one). Left alone for parametric
322
+ * mounts and when the serialize options strip the key. Never mutates the
323
+ * cached envelope.
324
+ */
325
+ private _withOwnHttpPath;
285
326
  /**
286
327
  * The `/meta` payload for the current request — the cached envelope
287
328
  * through {@link applyMetaOverlay} — WITHOUT the `/meta` route's
@@ -1302,6 +1343,11 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
1302
1343
  private _searchIndexFieldPaths;
1303
1344
  /** `@db.index.geo` indexes — their fields carry physical names, mapped back to logical paths. */
1304
1345
  private _geoIndexFieldPaths;
1346
+ /**
1347
+ * The index visibility gate applies: {@link hasField} is overridden, or the
1348
+ * model has `@db.writeOnly` fields (never readable through a search hit).
1349
+ */
1350
+ private get _indexGateActive();
1305
1351
  /** Every path `entry` reads is visible to this request. */
1306
1352
  private _indexVisible;
1307
1353
  /**
@@ -1668,6 +1714,14 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
1668
1714
  * nothing can apply is ignored. Resolvers (query targets, `resolveQuery`)
1669
1715
  * refuse it — a subclass override that applies the term must return a new
1670
1716
  * filter object.
1717
+ *
1718
+ * A string field matches when the term is a substring of its text; an
1719
+ * INTEGER field (`number.int` and its sizes, `@expect.int`,
1720
+ * `@db.default.increment`) when it is a substring of the number's decimal
1721
+ * text — `2946` finds `29461277` (since 0.1.150). To search numeric IDs
1722
+ * declare `@db.column.searchable` (fallback) or `@db.index.fulltext`
1723
+ * (native, exact number) on the integer field — override this hook only for
1724
+ * search rules the annotations cannot express.
1671
1725
  */
1672
1726
  protected applySearchFallback(filter: FilterExpr | undefined, controls: Record<string, unknown>): FilterExpr | undefined;
1673
1727
  /**
@@ -1690,6 +1744,21 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
1690
1744
  * the same shape as the one this whole path exists to remove.
1691
1745
  */
1692
1746
  private _aggregateControls;
1747
+ /**
1748
+ * `$count` of a native text / vector search: the adapter's own count, so it
1749
+ * agrees with `/query` and `/pages`. A text search counts every match (the
1750
+ * single returned row is discarded); a vector search counts the nearest
1751
+ * neighbours up to `$limit` (default 1000), as `/query` returns them.
1752
+ */
1753
+ private _countSearched;
1754
+ /**
1755
+ * The controls a native search / vector read is run with: the request's
1756
+ * sealed controls (so every search control — `$fuzzy`, … — reaches the
1757
+ * adapter) plus the select, the threshold and the caller's paging. ONE
1758
+ * builder for `/query`, `/pages` and the native `$count`, so they can't
1759
+ * disagree about what a search matches.
1760
+ */
1761
+ private _searchReadControls;
1693
1762
  private _resolveReadStrategy;
1694
1763
  /**
1695
1764
  * Post-read row decoration hook. Not implemented by
@@ -1738,12 +1807,6 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
1738
1807
  * when implemented.
1739
1808
  */
1740
1809
  private _augmentAndDecorate;
1741
- /**
1742
- * The app of the current event, through DI — never the one this
1743
- * (singleton) instance was constructed in, which may be gone (a re-booted
1744
- * app, a hot reload).
1745
- */
1746
- private _currentApp;
1747
1810
  /** The class's `@DbActionsFrom` delegations, validated on first use (per app). */
1748
1811
  private _delegations;
1749
1812
  /**
@@ -2212,7 +2275,8 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
2212
2275
  * Bulk auto-lift: each item carries its own `version` → `$cas`.
2213
2276
  * NOTE: per-item conflict disambiguation in the response body is deferred
2214
2277
  * (§6.4) — the aggregate `{ matchedCount, modifiedCount }` surfaces partial
2215
- * application; callers can detect mismatches via `modifiedCount < N`.
2278
+ * application; callers can detect mismatches via `matchedCount < N` (`modifiedCount` may
2279
+ * be lower for unchanged values, e.g. a version-exempt patch on MySQL / Mongo).
2216
2280
  */
2217
2281
  private _resolveBulkCas;
2218
2282
  /**
@@ -2344,7 +2408,9 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
2344
2408
  protected readonly searchableFields: readonly string[];
2345
2409
  /** The `@meta.id` field name on the bound interface, if any. */
2346
2410
  protected readonly primaryKey: string | undefined;
2347
- constructor(boundType: T, controllerName: string, app: Moost);
2411
+ constructor(boundType: T, controllerName: string, app: Moost, opts?: {
2412
+ canonical?: boolean;
2413
+ });
2348
2414
  /** Executes a value-help query against the backing source. */
2349
2415
  protected abstract query(controls: ValueHelpQuery<DataType>): Promise<{
2350
2416
  data: DataType[];
@@ -2482,7 +2548,9 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
2482
2548
  declare class AsJsonValueHelpController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsValueHelpController<T, DataType> {
2483
2549
  protected rows: DataType[];
2484
2550
  private _pkIndex?;
2485
- constructor(boundType: T, rows: DataType[], app: Moost, controllerName?: string);
2551
+ constructor(boundType: T, rows: DataType[], app: Moost, controllerName?: string, opts?: {
2552
+ canonical?: boolean;
2553
+ });
2486
2554
  protected query(controls: ValueHelpQuery<DataType>): Promise<{
2487
2555
  data: DataType[];
2488
2556
  count: number;
@@ -2621,6 +2689,16 @@ interface TReadableBindingMeta {
2621
2689
  model?: TAtscriptAnnotatedType;
2622
2690
  /** Resolves the readable — lazily for token/factory, identity for instance. */
2623
2691
  resolve: () => AtscriptDbReadable<any>;
2692
+ /** The route prefix the decorator derived (`options.prefix`, `@db.http.path`, table name, …). @since 0.1.150 */
2693
+ prefix?: string;
2694
+ /**
2695
+ * Where {@link prefix} came from: the decorator's `prefix` option (or the
2696
+ * factory form), the model's own `@db.http.path`, or its table / view name.
2697
+ * @since 0.1.150
2698
+ */
2699
+ prefixSource?: "option" | "annotation" | "name";
2700
+ /** The decorator's `canonical` option. @since 0.1.150 */
2701
+ canonical?: boolean;
2624
2702
  }
2625
2703
  /**
2626
2704
  * Class- and method-level metadata written by `@atscript/moost-db`'s
@@ -2753,6 +2831,18 @@ interface TControllerBindingOptions {
2753
2831
  * annotation. Ignored for instance/factory bindings.
2754
2832
  */
2755
2833
  space?: string;
2834
+ /**
2835
+ * Marks (`true`) or excludes (`false`) this controller as the one whose route
2836
+ * is published as the model's value-help path (`db.http.path`) when the same
2837
+ * model is served by several controllers in one app. A single `canonical: true`
2838
+ * mount wins; `canonical: false` mounts are never published; without a marker
2839
+ * the model's own `@db.http.path`-derived mount breaks ties, and any remaining
2840
+ * ambiguity leaves the path unset (with a boot warning). Parametric mounts
2841
+ * (`:param` / `*` segments) are never published.
2842
+ *
2843
+ * @since 0.1.150
2844
+ */
2845
+ canonical?: boolean;
2756
2846
  }
2757
2847
  /**
2758
2848
  * Combines the boilerplate needed to turn an {@link AsDbController}