@atscript/moost-db 0.1.141 → 0.1.143

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
@@ -1,10 +1,54 @@
1
1
  import { i as resolveDbSpace, n as clearDbSpaces, r as provideDbSpace, t as DEFAULT_DB_SPACE } from "./db-space-registry-CWpYwZ4R.cjs";
2
2
  import { TAtscriptAnnotatedType, TAtscriptDataType, TSerializeOptions, TSerializedAnnotatedType, TValidatorOptions, Validator } from "@atscript/typescript/utils";
3
- import { AggregateFn, AtscriptDbReadable, AtscriptDbTable, BucketUnit, FilterExpr, FlatOf, TCrudOp, TCrudPermissions, TCrudPermissions as TCrudPermissions$1, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbFieldMeta, TDbRemoveGuardContext, TDbRemoveGuardContext as TDbRemoveGuardContext$1, TDbWriteAction, TDbWriteAction as TDbWriteAction$1, TDbWriteGuardContext, TDbWriteGuardContext as TDbWriteGuardContext$1, TFilterPredicate, TIdResolveOptions, TIdentification, TMetaResponse, TQueryPathOp, TQueryPathOp as TQueryPathOp$1, TQueryPathRefs, TQueryPathSource, Uniquery, UniqueryControls, collectQueryPaths } from "@atscript/db";
3
+ 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, 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";
4
4
  import { HttpError } from "@moostjs/event-http";
5
5
  import { Mate, Moost, TConsoleBase, TMateParamMeta, TMoostMetadata } from "moost";
6
6
  import { parseUrl } from "@uniqu/url";
7
7
  //#region src/as-readable.controller.d.ts
8
+ /**
9
+ * Endpoint a {@link AsReadableController.prepareRequest} call serves. `/one`
10
+ * and `/one/:id` both report `"one"`; `DELETE /:id` and `DELETE /?…` both
11
+ * report `"remove"`; value-help controllers report `query` / `pages` / `one`
12
+ * like DB readables do; every `@DbAction` handler (row, rows and table
13
+ * level) reports `"action"` with the action's name in
14
+ * {@link TDbRequestContext.action}.
15
+ *
16
+ * @since 0.1.143
17
+ */
18
+ type TDbRequestEndpoint = "query" | "pages" | "geo" | "one" | "meta" | "metaForm" | "insert" | "replace" | "update" | "remove" | "action";
19
+ /**
20
+ * Context passed to {@link AsReadableController.prepareRequest}.
21
+ *
22
+ * @since 0.1.143
23
+ */
24
+ interface TDbRequestContext {
25
+ /** The endpoint being served. */
26
+ readonly endpoint: TDbRequestEndpoint;
27
+ /**
28
+ * The parsed Uniquery controls (`$select`, `$with`, `$search`, …) on read
29
+ * endpoints (`query`, `pages`, `geo`, `one`) — the object the rest of
30
+ * the pipeline validates and reads with. `undefined` on `meta`,
31
+ * `metaForm`, writes and actions.
32
+ */
33
+ readonly controls?: Record<string, unknown>;
34
+ /** `"action"` endpoint only: the `@DbAction` name being run. */
35
+ readonly action?: string;
36
+ }
37
+ /** Control DTO a {@link AsReadableController.validateControls} call checks against. @since 0.1.143 (`"geo"`) */
38
+ type TDbControlsType = "query" | "pages" | "getOne" | "geo";
39
+ /**
40
+ * A read request as {@link AsReadableController.parseRequest} hands it back.
41
+ *
42
+ * @since 0.1.143
43
+ */
44
+ interface TDbParsedRequest {
45
+ /** The parsed URL (filter, controls, insights). */
46
+ parsed: ReturnType<typeof parseUrl>;
47
+ /** `parsed.controls` — the object `prepareRequest` saw and the pipeline validates. */
48
+ controls: Record<string, unknown>;
49
+ /** `"one"` only: the URL carried non-control (filter) parts, which `/one/:id` rejects. */
50
+ hasNonControl: boolean;
51
+ }
8
52
  /**
9
53
  * Abstract base class for read-only HTTP controllers over an Atscript interface.
10
54
  *
@@ -81,20 +125,83 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
81
125
  * `ref` (`field: ""`) and their bodies always expand fully regardless of
82
126
  * `refDepth` — the write-payload shape clients need is unaffected.
83
127
  *
84
- * Annotation whitelist: keeps `meta.*`, `expect.*`, and `db.rel.*`; strips
85
- * other `db.*` (table, column, index, default, etc.). Override in subclass
86
- * to customise.
128
+ * Annotation whitelist: keeps `meta.*`, `expect.*`, `db.rel.*`, the `db.*`
129
+ * keys the shared db validator plugin reads in db-client (`db.json`,
130
+ * `db.patch.strategy`, `db.default*`, `db.column.version`,
131
+ * `db.column.derived`) and the client-facing `db.http.path` /
132
+ * `db.writeOnly`; strips every other `db.*` (table, column, index, etc.).
133
+ * Override in subclass to customise — keep the validator keys, or client
134
+ * preflight diverges from the server.
87
135
  */
88
136
  protected getSerializeOptions(): TSerializeOptions;
89
137
  private _queryControlsValidator?;
90
138
  private _pagesControlsValidator?;
91
139
  private _getOneControlsValidator?;
140
+ private _geoControlsValidator?;
92
141
  protected get queryControlsValidator(): Validator<any, unknown>;
93
142
  protected get pagesControlsValidator(): Validator<any, unknown>;
94
143
  protected get getOneControlsValidator(): Validator<any, unknown>;
95
- protected validateControls(controls: Record<string, unknown>, type: "query" | "pages" | "getOne"): string | undefined;
144
+ /** `/geo` controls validator (since 0.1.143 — `/geo` runs {@link validateParsed} like `/query`). */
145
+ protected get geoControlsValidator(): Validator<any, unknown>;
146
+ /**
147
+ * Per-request preparation hook — THE entry point for a permission layer to
148
+ * resolve per-request policy asynchronously (load the principal's scopes,
149
+ * evaluate grants) so the synchronous hooks that follow (`hasField`,
150
+ * `validateControls`, `checkCapabilities`) can consult it. Not
151
+ * implemented by default — defining it in a subclass switches it on (it is
152
+ * awaited only then, so unmodified controllers pay nothing).
153
+ *
154
+ * Runs once per request, before anything else consults the request:
155
+ * - read endpoints (`query`, `pages`, `geo`, `one`) — right after the
156
+ * URL is parsed, BEFORE validation, `hasField`, `validateControls`,
157
+ * `transformFilter` / `transformProjection`; `ctx.controls` are the
158
+ * parsed controls (mutating them is allowed and is what the pipeline
159
+ * then validates);
160
+ * - writes (`insert`, `replace`, `update`, `remove`) — at handler start,
161
+ * before the shape gate, `onWrite` / `onRemove` and any guard;
162
+ * - `meta` / `metaForm` — first;
163
+ * - `@DbAction` handlers of every level (`action`, with `ctx.action` = the
164
+ * action name) — from the action's interceptor (after the guards),
165
+ * before its ids are validated, its rows loaded, its row overlay built
166
+ * and the handler runs. A permission layer needs no separate action
167
+ * guard.
168
+ *
169
+ * A throw aborts the request with the thrown error (throw an `HttpError`
170
+ * for a specific status). Value-help controllers get it too.
171
+ *
172
+ * ```ts
173
+ * protected async prepareRequest(ctx: TDbRequestContext) {
174
+ * const scopes = await loadScopes(useAuthorization(), ctx.endpoint)
175
+ * if (!scopes) throw new HttpError(403)
176
+ * requestScopes.set(scopes) // read back by hasField / transformFilter
177
+ * }
178
+ * ```
179
+ *
180
+ * @since 0.1.143
181
+ */
182
+ protected prepareRequest?(ctx: TDbRequestContext): void | Promise<void>;
183
+ /**
184
+ * The ONE request entry of every built-in route: with a `url` (read
185
+ * endpoints) it parses the query string — `/one` keeps only the `$`
186
+ * controls ({@link parseControlsOnlyFromUrl}), every other endpoint the
187
+ * whole query ({@link parseQueryString}) — and coerces boolean controls the
188
+ * URL grammar leaves as strings (`$actions=true`); then it awaits
189
+ * {@link prepareRequest} (when implemented) with the parsed controls.
190
+ * Without a `url` (writes, `meta`, `metaForm`) only the hook runs. Custom
191
+ * routes on a subclass should call it too.
192
+ *
193
+ * @since 0.1.143
194
+ */
195
+ protected parseRequest(endpoint: TDbRequestEndpoint): Promise<undefined>;
196
+ protected parseRequest(endpoint: TDbRequestEndpoint, url: string): Promise<TDbParsedRequest>;
197
+ /**
198
+ * Validates the parsed controls against the endpoint's DTO — the hook for
199
+ * per-control authorization (override, call `super`, add rules). `"geo"`
200
+ * since 0.1.143 (`/geo` used to skip it).
201
+ */
202
+ protected validateControls(controls: Record<string, unknown>, type: TDbControlsType): string | undefined;
96
203
  protected validateInsights(insights: Map<string, unknown>): string | undefined;
97
- protected validateParsed(parsed: Uniquery, type: "query" | "pages" | "getOne"): HttpError | undefined;
204
+ protected validateParsed(parsed: Uniquery, type: TDbControlsType): HttpError | undefined;
98
205
  /**
99
206
  * Per-request gate hook, invoked by the DB readable controller right after
100
207
  * its capability gate (`checkCapabilities`) with the parsed query. The
@@ -139,6 +246,16 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
139
246
  * response by principal.
140
247
  */
141
248
  meta(): Promise<TMetaResponse>;
249
+ /**
250
+ * The `/meta` payload for the current request — the cached envelope
251
+ * through {@link applyMetaOverlay} — WITHOUT the `/meta` route's
252
+ * {@link prepareRequest} call. Internal consumers (e.g. `$actions`
253
+ * filtering on a read) use this, so the hook runs once per request with
254
+ * the endpoint actually being served.
255
+ *
256
+ * @since 0.1.143
257
+ */
258
+ protected resolveMeta(): TMetaResponse | Promise<TMetaResponse>;
142
259
  /**
143
260
  * Identity of the inputs the cached `/meta` envelope is built from — a new
144
261
  * value rebuilds it. Default: constant (built once). The DB readable
@@ -152,9 +269,20 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
152
269
  * compiled `.as` class's `.name`, registered when an action's parameter is
153
270
  * decorated with `@InputForm(FormType)`. Schemas are serialized once and
154
271
  * cached per controller; the response uses the same annotation-allowlist
155
- * policy as {@link getSerializeOptions}.
272
+ * policy as {@link getSerializeOptions}. Since 0.1.143 the form must pass
273
+ * {@link authorizeForm} — a refused form answers exactly like an unknown one.
156
274
  */
157
275
  metaForm(name: string): Promise<TSerializedAnnotatedType>;
276
+ /**
277
+ * Per-request gate for `GET /meta/form/:name`: return `false` to refuse the
278
+ * form — the response is then the same 404 an unknown form gets, so a
279
+ * refused form's existence does not leak. `actionNames` are the discovered
280
+ * actions whose input form is `name` (a permission layer typically allows
281
+ * the form iff the caller may run at least one of them). Default: `true`.
282
+ *
283
+ * @since 0.1.143
284
+ */
285
+ protected authorizeForm(_name: string, _actionNames: readonly string[]): boolean | Promise<boolean>;
158
286
  /**
159
287
  * Builds the `/meta` payload. Override in subclasses to populate source-specific
160
288
  * fields. Subclasses that fully replace the envelope must call
@@ -346,6 +474,48 @@ interface TDbDecorateContext {
346
474
  /** The request's parsed controls (`$select`, `$with`, `$actions`, …). Read-only by convention. */
347
475
  controls: Record<string, unknown>;
348
476
  }
477
+ /**
478
+ * One text / vector / geo index of the bound readable with the LOGICAL field
479
+ * paths it reads — see {@link AsDbReadableController.indexFieldPaths}.
480
+ *
481
+ * @since 0.1.143
482
+ */
483
+ interface TDbIndexFieldPaths {
484
+ /** The name a request addresses the index by (`$index` for text and geo, `$vector` for vector). */
485
+ name: string;
486
+ type: "text" | "vector" | "geo";
487
+ /**
488
+ * Logical field paths the index reads. An index whose coverage cannot be
489
+ * derived from the model (e.g. a dynamic document-search mapping) lists
490
+ * every field — fail-closed for visibility gating.
491
+ */
492
+ fields: readonly string[];
493
+ /** `true` for the index a request of this type uses when it names none. */
494
+ isDefault: boolean;
495
+ }
496
+ /**
497
+ * The field visibility of a DB readable controller — see
498
+ * {@link AsDbReadableController.fieldVisibility}.
499
+ *
500
+ * @since 0.1.143
501
+ */
502
+ interface TDbFieldVisibility {
503
+ /** `true` when `hasField` is overridden (visibility is request-scoped); else every real path is visible. */
504
+ readonly scoped: boolean;
505
+ /**
506
+ * `hasField(path)` and — when {@link scoped} — a `@db.column.derived`
507
+ * field of the bound readable only while its source path is visible too
508
+ * (a derived copy must not outlive a hidden source).
509
+ */
510
+ readonly isVisible: (path: string) => boolean;
511
+ /**
512
+ * The paths sealed out of `readable`'s read projection for this request:
513
+ * its `@db.writeOnly` fields plus, when {@link scoped}, its derived fields
514
+ * whose source `hasField` hides. `prefix` is `readable`'s path from the
515
+ * controller: `""` for the bound readable, `"rel."` for a `$with` target.
516
+ */
517
+ readonly sealedFor: (readable: AtscriptDbReadable<any>, prefix?: string) => ReadonlySet<string>;
518
+ }
349
519
  /**
350
520
  * Read-only database controller for Moost that works with any `AtscriptDbReadable`
351
521
  * (tables or views). Provides query, pages, getOne, and meta endpoints.
@@ -381,13 +551,31 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
381
551
  private _capabilities?;
382
552
  /** `/meta` is a projection of {@link capabilities}: a rebuilt index rebuilds the cached envelope. */
383
553
  protected metaCacheKey(): unknown;
384
- /** Bound once: the visibility check ({@link hasField}) the gate hands to `capabilities.check`. */
385
- private readonly _exists;
386
554
  /**
387
- * Id-resolution options (since 0.1.134): `{ isFieldVisible: hasField }`
388
- * when a subclass overrides {@link hasField}, else `undefined` (the default
389
- * accepts every real path, so resolution stays unfiltered). A unique index
390
- * over a hidden field is never an identification.
555
+ * THE field-visibility answer (since 0.1.143) every read surface consults:
556
+ * the capability gate, the index gate and `$search` fallback, id
557
+ * resolution, the `@db.writeOnly` / derived seals of the projection and of
558
+ * every `$with` level, `$actions` widening and action `requiredFields`
559
+ * (the actions module reaches it duck-typed, like {@link idSource}).
560
+ */
561
+ protected readonly fieldVisibility: TDbFieldVisibility;
562
+ /** A subclass overrides {@link hasField}: visibility is request-scoped (derived rule, index gate, id options). */
563
+ private readonly _hasFieldOverridden;
564
+ /** `@db.column.derived` path → its source's logical path, per readable (bound + `$with` targets). */
565
+ private readonly _derivedSources;
566
+ /** The bound readable's entry of {@link _derivedSources}. */
567
+ private readonly _derivedSource;
568
+ /** `@db.writeOnly` paths of `$with` target readables, collected once per target. */
569
+ private readonly _targetWriteOnly;
570
+ private _indexFieldPathsCache?;
571
+ /** {@link _nativeSearch} per request, keyed by the request's parsed controls. */
572
+ private readonly _nativeSearchByRequest;
573
+ /**
574
+ * Id-resolution options (since 0.1.134): `{ isFieldVisible }` (the
575
+ * {@link fieldVisibility} check) when a subclass overrides {@link hasField},
576
+ * else `undefined` (the default accepts every real path, so resolution
577
+ * stays unfiltered). A unique index over a hidden field is never an
578
+ * identification.
391
579
  */
392
580
  protected readonly _idOpts: TIdResolveOptions | undefined;
393
581
  /** Narrowed id sources, one stable object per distinct visible-identification set. */
@@ -396,6 +584,8 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
396
584
  private readonly _overlayIsNoOp;
397
585
  /** `true` when a subclass implements {@link decorateRows} (the override switches the hook on). */
398
586
  private readonly _decorates;
587
+ /** `true` when a subclass overrides {@link transformOne} or {@link transformFilter} (a row overlay may exist). */
588
+ private readonly _hasRowOverlay;
399
589
  /** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
400
590
  private readonly _quantityRefByPath;
401
591
  /** `@db.column.searchable` paths — the `$search` fallback when the adapter has no native search. */
@@ -418,6 +608,8 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
418
608
  get idSource(): IdValidationSource;
419
609
  private _collectInvertibleFields;
420
610
  private _collectQuantityRefs;
611
+ /** `readable`'s `@db.column.derived` path → source path map, collected once per readable. */
612
+ private _derivedSourcesOf;
421
613
  private _collectAnnotated;
422
614
  /**
423
615
  * THE field-visibility hook: every gated path consults it before any
@@ -433,9 +625,18 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
433
625
  * an action id (primary key and `preferredId` always are) — and the
434
626
  * nested-object 400 hint lists visible leaves only. The default accepts every real path
435
627
  * (`isValidFieldPath`). `/meta` does NOT consult it — prune hidden fields
436
- * there with `applyMetaOverlay`. Native text search and vector search
437
- * (`$vector` names an index) run inside the engine over its indexes, out of
438
- * this hook's reach — keep hidden fields out of those indexes.
628
+ * there with `applyMetaOverlay` ({@link indexFieldPaths} names what each
629
+ * search / geo index reads).
630
+ *
631
+ * Since 0.1.143 an override also gates the engine's indexes: a native
632
+ * text-search index (`$index`, or the default one), a vector index
633
+ * (`$vector`) or a geo index (`/geo`, `$index`) reading a hidden path
634
+ * answers exactly like a nonexistent index (400); a hidden DEFAULT text
635
+ * index falls back to the `@db.column.searchable` substring search over
636
+ * visible fields (or ignores the term when there are none). A
637
+ * `@db.column.derived` field is visible only while its source path is,
638
+ * and one whose source is hidden is sealed out of every read projection
639
+ * for the request, like a `@db.writeOnly` field.
439
640
  */
440
641
  protected hasField(path: string): boolean;
441
642
  /**
@@ -453,7 +654,9 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
453
654
  * ran first); a bucket's source is checked like any other path (op
454
655
  * `bucket`). After the per-path checks the core `$having` rule runs
455
656
  * (`checkHavingKeys`: aliases or `$groupBy` fields only), so a readable mock
456
- * and a real table answer alike.
657
+ * and a real table answer alike. Last (since 0.1.143, when {@link hasField}
658
+ * is overridden) the index gate: a text / vector / geo index the request
659
+ * uses must read only visible paths — see {@link indexFieldPaths}.
457
660
  */
458
661
  protected checkCapabilities(parsed: {
459
662
  filter?: FilterExpr;
@@ -478,8 +681,91 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
478
681
  * resolved against the target table through `isValidFieldPath`.
479
682
  */
480
683
  protected validateInsights(insights: Map<string, unknown>): string | undefined;
481
- /** {@link checkComputedSelect} (before the controls DTO), then $with relations against the readable. */
482
- protected validateParsed(parsed: Uniquery, type: "query" | "pages" | "getOne"): HttpError | undefined;
684
+ /**
685
+ * {@link checkComputedSelect} (before the controls DTO), the controls DTO
686
+ * ({@link validateControls}), then the `$with` relation names at every
687
+ * level — BEFORE the `$with` sub-query paths ({@link validateInsights}),
688
+ * so a hidden or nonexistent nested relation answers `Unknown relation`,
689
+ * never `Unknown field "rel.sub"` (since 0.1.143) — then the insights and
690
+ * the joined-row write-only veto.
691
+ */
692
+ protected validateParsed(parsed: Uniquery, type: TDbControlsType): HttpError | undefined;
693
+ /**
694
+ * `$with` relation names at every level (nested `$with` since 0.1.143):
695
+ * each segment of an entry's (dotted) name must be a relation of its
696
+ * level's readable that {@link hasField} accepts at its full path from
697
+ * this controller (`rel`, then `rel.sub` for a nested / dotted one) —
698
+ * hidden answers exactly like nonexistent: {@link unknownRelationError}
699
+ * with the entry's name and the relations visible at the level it failed
700
+ * at. A level whose target readable cannot be resolved is not descended.
701
+ */
702
+ private _checkWithRelations;
703
+ /**
704
+ * `@db.writeOnly` paths of a `$with` target — its own fields only (its
705
+ * navigation descendants are sealed one level down, by their own target).
706
+ */
707
+ private _writeOnlyOf;
708
+ /** {@link TDbFieldVisibility.sealedFor}. */
709
+ private _sealedFor;
710
+ /** The readable a `$with` entry name (`rel` or dotted `rel.sub`) loads from, if resolvable. */
711
+ private _relTarget;
712
+ /**
713
+ * Walks a `$with` tree pre-order: `visit(rel, target, path, controls)` for
714
+ * every entry whose target readable resolves (`path` = the entry's dotted
715
+ * path from this controller, `controls` = its sub-controls). The visitor
716
+ * returns replacement sub-controls, an `HttpError` to stop the walk (it is
717
+ * returned as-is), or `undefined` to keep the entry. Returns the rebuilt
718
+ * tree — the same array when nothing changed.
719
+ */
720
+ private _walkWith;
721
+ /**
722
+ * The read controls with every level sealed — the root `$select` (`select`,
723
+ * the {@link transformProjection} result) and each `$with` entry's
724
+ * `$select` lose the paths {@link TDbFieldVisibility.sealedFor} names for
725
+ * their readable (an exclusion is forced when there is no projection), so
726
+ * sealed values never leave the database. Runs AFTER `transformProjection`
727
+ * so permission overlays compose: they see the wire `$select`, this
728
+ * guarantees the seal on whatever they return.
729
+ */
730
+ private _sealControls;
731
+ /**
732
+ * The text / vector / geo indexes of the bound readable with the LOGICAL
733
+ * field paths each reads, and which one answers when a request names none.
734
+ * Text and vector entries are the adapter's `getSearchIndexes()` (the names
735
+ * `$index` / `$vector` address, their `fields` and `isDefault`; an entry
736
+ * without `fields` lists every field — fail-closed); geo entries are the
737
+ * `@db.index.geo` indexes. The request gate checks every listed path against
738
+ * {@link hasField} (only when `hasField` is overridden); permission
739
+ * overlays use it to prune `/meta` (`searchIndexes`, `searchable`,
740
+ * `vectorSearchable`, `geoSearchable`). Computed once. Override to describe
741
+ * an index the model cannot express.
742
+ *
743
+ * @since 0.1.143
744
+ */
745
+ protected indexFieldPaths(): readonly TDbIndexFieldPaths[];
746
+ private _searchIndexFieldPaths;
747
+ /** `@db.index.geo` indexes — their fields carry physical names, mapped back to logical paths. */
748
+ private _geoIndexFieldPaths;
749
+ /** Every path `entry` reads is visible to this request. */
750
+ private _indexVisible;
751
+ /**
752
+ * Native text search serves this request: the adapter searches natively
753
+ * and — under an overridden {@link hasField} — the default index (when the
754
+ * request names none) reads only visible fields. A named index is gated by
755
+ * {@link checkCapabilities}. Answered once per request (keyed by its
756
+ * parsed controls).
757
+ */
758
+ private _nativeSearch;
759
+ private _resolveNativeSearch;
760
+ /**
761
+ * Index visibility gate (only when {@link hasField} is overridden), run by
762
+ * {@link checkCapabilities} on every read: the geo index `/geo` reads
763
+ * (`$index`, or the default one), the vector index `$vector` names (or the
764
+ * default one) and the text index `$index` names must read only visible
765
+ * paths; otherwise the request is answered exactly like one naming a
766
+ * nonexistent index (the core's wording).
767
+ */
768
+ private _checkIndexGate;
483
769
  /**
484
770
  * Compute an embedding vector from a search term.
485
771
  * Override in subclass to integrate with your embedding provider (OpenAI, etc.).
@@ -522,13 +808,16 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
522
808
  private widenQuantityRefProjection;
523
809
  private _widenQuantityArrayProjection;
524
810
  private _widenQuantityMapProjection;
525
- /** WHY: the URL parser only auto-coerces `$count`; every other boolean control reaches us as `"true"`/`"1"` and would fail DTO validation. */
526
- private _coerceActionsControl;
527
811
  /** Normalize a post-`widenPreferredIdProjection` $select into `string[] | null` (`null` = all fields). */
528
812
  private _resolveProjectionForAugmenter;
529
813
  /** WHY: filter row/rows envelopes by the per-request `applyMetaOverlay` action set; skip `meta()` when overlay is identity. */
530
814
  private _resolveAugmentEnvelopes;
531
- /** Returns a widened `$select` only when at least one `requiredFields` entry is missing; `null` means "no widening needed". */
815
+ /**
816
+ * Returns a widened `$select` only when at least one `requiredFields` entry
817
+ * is missing; `null` means "no widening needed". A field the request may
818
+ * not see (`hasField`, derived source) is never added (since 0.1.143) —
819
+ * the action predicate sees it as `undefined`.
820
+ */
532
821
  private _widenSelectForActions;
533
822
  private _prepareAugmentation;
534
823
  /**
@@ -539,20 +828,20 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
539
828
  */
540
829
  private _collectSearchFallbackFields;
541
830
  /**
542
- * Removes `@db.writeOnly` fields from any `$select` shape — and forces an
543
- * exclusion when no projection was requested — so sealed values never leave
544
- * the database on a read. Runs AFTER `transformProjection` so permission
545
- * overlays compose (they see the wire `$select`; this guarantees the seal on
546
- * whatever they return).
831
+ * `select` without the `sealed` paths (see {@link _sealControls}); an
832
+ * exclusion of them is forced when there is no projection, or when every
833
+ * requested path was sealed.
547
834
  */
548
- private _sealProjection;
835
+ private _sealSelect;
549
836
  /** First `@db.writeOnly` field referenced by `$groupBy` / aggregate `$select`, or undefined. */
550
837
  private _findWriteOnlyInAggregate;
551
838
  /**
552
839
  * Merges the `$search` fallback into the filter: a case-insensitive literal
553
840
  * substring match OR'd across the `@db.column.searchable` fields, `$and`-combined
554
- * with the existing filter. Applies only when the adapter has no native search
555
- * (native wins) and the request isn't a vector search (`$vector` consumes the term).
841
+ * with the existing filter. Applies only when native search does not serve
842
+ * the request (no native search, or — since 0.1.143 — its default index
843
+ * reads a field {@link hasField} hides) and the request isn't a vector
844
+ * search (`$vector` consumes the term).
556
845
  */
557
846
  protected applySearchFallback(filter: FilterExpr | undefined, controls: Record<string, unknown>): FilterExpr | undefined;
558
847
  /**
@@ -618,6 +907,32 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
618
907
  * strategy to its read-method family (count vs no-count).
619
908
  */
620
909
  private _runReadWithActions;
910
+ /**
911
+ * The filter addressing exactly the ONE row `id` means — the readable's
912
+ * PK-first `resolveRowFilter` (since 0.1.143) under this request's
913
+ * identifications (`_idOpts`). `scope` (the row overlay) restricts which
914
+ * rows count while the id is pinned, so a row outside it never shadows one
915
+ * inside it. Readables without it (partial mocks) fall back to
916
+ * `resolveIdFilter`.
917
+ */
918
+ protected resolveRowFilter(id: unknown, scope?: FilterExpr): Promise<FilterExpr | null>;
919
+ /**
920
+ * The ONE row `id` addresses, read with `controls`. A hidden unique key is
921
+ * not an identification (since 0.1.134): the id resolves as if that index
922
+ * did not exist. Since 0.1.143 it resolves primary key first, counting
923
+ * only rows inside the overlay — an out-of-scope row never shadows an
924
+ * in-scope one, so the answer is the same as if it did not exist — in one
925
+ * step (`findOneByRow`). Readables without it (partial mocks) pin the row
926
+ * with {@link resolveRowFilter}, then read it.
927
+ */
928
+ private _findRow;
929
+ /**
930
+ * The row overlay id-addressed endpoints (`/one`, `DELETE`) apply:
931
+ * `transformOne({})` when non-empty, and only when a subclass overrides
932
+ * {@link transformOne} / {@link transformFilter} — `undefined` otherwise,
933
+ * at no cost (since 0.1.143).
934
+ */
935
+ protected rowOverlay(): Promise<FilterExpr | undefined>;
621
936
  /**
622
937
  * Pick the first identification (PK or unique index) whose fields are all
623
938
  * present in the query. A unique index over a field {@link hasField} hides
@@ -647,7 +962,9 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
647
962
  * (meters), `$index` (geo index name), plus the standard filter / `$select` /
648
963
  * `$with` / pagination syntax. Each row carries a computed `$distance`
649
964
  * (meters). With `$page` / `$size` the response is the `/pages` envelope;
650
- * otherwise a plain row array (`$skip` / `$limit` compose).
965
+ * otherwise a plain row array (`$skip` / `$limit` compose). Since 0.1.143
966
+ * the controls pass {@link validateParsed} (type `"geo"`) like `/query`'s,
967
+ * and the geo index must read only fields {@link hasField} shows.
651
968
  */
652
969
  geo(url: string): Promise<DataType[] | {
653
970
  data: DataType[];
@@ -670,7 +987,12 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
670
987
  * gating as {@link getOne}.
671
988
  */
672
989
  getOneComposite(query: Record<string, string>, url: string): Promise<DataType | HttpError>;
673
- private _findByIdAndAugment;
990
+ /**
991
+ * The shared `/one` pipeline: validation + capability gate (since 0.1.128
992
+ * on the composite form too — an unknown `$select` path used to reach the
993
+ * driver there), the sealed projection, the row read and its augmentation.
994
+ */
995
+ private _readOne;
674
996
  /**
675
997
  * **GET /meta** — returns table/view metadata for UI.
676
998
  *
@@ -698,15 +1020,18 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
698
1020
  * ### Write pipeline (since 0.1.128)
699
1021
  *
700
1022
  * ```
701
- * shape gate (400) → onWrite / onRemove (outside any transaction)
1023
+ * prepareRequest (when implemented) → shape gate (400)
1024
+ * → onWrite / onRemove (outside any transaction)
702
1025
  * → table op — the table's own transaction: validate → guardWrite / guardRemove
703
- * (only when overridden) → re-validate → write
1026
+ * (only when overridden) → re-validate → write → nested phases
1027
+ * → checkWrite (only when overridden)
704
1028
  * → 404 / 409 disambiguation
705
1029
  * ```
706
1030
  *
707
1031
  * The guard is the table's `guard` write option (`TWriteOptions.guard` /
708
1032
  * `TDeleteOptions.guard`); overriding `guardWrite` / `guardRemove` is the
709
- * switch that passes it. Built-in failures are THROWN as `HttpError`
1033
+ * switch that passes it. `checkWrite` is the `check` write option, with
1034
+ * the same switch (since 0.1.143). Built-in failures are THROWN as `HttpError`
710
1035
  * (wire-identical to returning them; a throw also rolls back a user-level
711
1036
  * `withTransaction` wrapper).
712
1037
  */
@@ -758,6 +1083,30 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
758
1083
  * the guard.
759
1084
  */
760
1085
  protected guardRemove(_ctx: TDbRemoveGuardContext$1<DataType>): void | Promise<void>;
1086
+ /**
1087
+ * Post-write check (since 0.1.143) — a row-level "WITH CHECK". Overriding it
1088
+ * is the switch (as with {@link guardWrite}): the override is passed to the
1089
+ * table as its `check` write option and runs once per insert / replace /
1090
+ * update call (bulk forms included), inside the table's transaction, AFTER
1091
+ * the main write and every nested-relation phase. `ctx.filters` holds one
1092
+ * exact primary-key filter per written row; `ctx.count(filter)` counts
1093
+ * inside the same transaction — e.g. verify every written row still matches
1094
+ * a policy filter:
1095
+ *
1096
+ * ```ts
1097
+ * protected async checkWrite(ctx: TDbWriteCheckContext) {
1098
+ * const n = await ctx.count({ $and: [{ $or: ctx.filters }, tenantFilter()] })
1099
+ * if (n !== ctx.filters.length) throw new HttpError(403)
1100
+ * }
1101
+ * ```
1102
+ *
1103
+ * Throw to reject: the transaction rolls back and the error propagates
1104
+ * unchanged. When `ctx.transactional` is `false` (the adapter's
1105
+ * transaction is a pass-through, e.g. a standalone MongoDB) the write is
1106
+ * already durable — validate before the write instead (`guardWrite`).
1107
+ * Removes have no post-image and never call it.
1108
+ */
1109
+ protected checkWrite(_ctx: TDbWriteCheckContext$1): void | Promise<void>;
761
1110
  /**
762
1111
  * Runs `fn` inside the bound table's adapter transaction (nested calls
763
1112
  * join it). For custom actions and routes that need one transaction across
@@ -769,10 +1118,11 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
769
1118
  /** `deleteOne`'s trailing options — see {@link _hookArgs}. */
770
1119
  private readonly _removeArgs;
771
1120
  /**
772
- * A table call's trailing options, built once: `guard` only when the guard
773
- * hook is overridden, `isFieldVisible` only when `hasField` is (an id or a
774
- * PK-less payload never resolves through a hidden unique key) — else
775
- * nothing, so an unmodified controller calls the table exactly as before.
1121
+ * A table call's trailing options, built once: `guard` / `check` only when
1122
+ * the matching hook is overridden, `isFieldVisible` only when `hasField`
1123
+ * is (an id or a PK-less payload never resolves through a hidden unique
1124
+ * key) — else nothing, so an unmodified controller calls the table exactly
1125
+ * as before.
776
1126
  */
777
1127
  private _hookArgs;
778
1128
  /** Resolves a hook result: `undefined` aborts with `abortMessage`, an `Error` is thrown, anything else passes. */
@@ -798,7 +1148,13 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
798
1148
  * application; callers can detect mismatches via `modifiedCount < N`.
799
1149
  */
800
1150
  private _resolveBulkCas;
801
- /** Deletes by id (guard forwarded when overridden) and maps "nothing deleted" to 404. */
1151
+ /**
1152
+ * Deletes by id (guard forwarded when overridden) and maps "nothing
1153
+ * deleted" to 404. Since 0.1.143 the row overlay ({@link rowOverlay}) is
1154
+ * the delete's `scope`: the id is pinned among in-scope rows inside the
1155
+ * table's transaction and an out-of-scope row is not deleted — a 404,
1156
+ * exactly like a missing one.
1157
+ */
802
1158
  private _deleteOrThrow;
803
1159
  /**
804
1160
  * **POST /** — inserts one or many records.
@@ -827,6 +1183,12 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
827
1183
  * returns 404 when the row is genuinely missing, 409 with
828
1184
  * `{ error: "version_mismatch", currentVersion: N }` when it's present
829
1185
  * but the supplied version is stale (§6.3). Callers throw the result.
1186
+ *
1187
+ * The row is the one the write targeted (since 0.1.143): the table's
1188
+ * `recordFilter` — the write's own resolution, primary key first — so a
1189
+ * payload carrying the full primary key addresses that row only, never a
1190
+ * different row that merely shares a unique value with the payload.
1191
+ * Tables without it (partial mocks) resolve through {@link resolveRowFilter}.
830
1192
  */
831
1193
  protected _disambiguateMismatch(data: unknown, versionColumn: string): Promise<HttpError>;
832
1194
  /**
@@ -841,6 +1203,13 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
841
1203
  }
842
1204
  //#endregion
843
1205
  //#region src/as-value-help.controller.d.ts
1206
+ /**
1207
+ * A value-help projection: the parsed `$select` (inclusion list, or a
1208
+ * `{ path: 0 | 1 }` map for the `-field` exclusion form).
1209
+ *
1210
+ * @since 0.1.143
1211
+ */
1212
+ type ValueHelpSelect<T> = (keyof T | string)[] | Record<string, 0 | 1>;
844
1213
  /**
845
1214
  * Parsed Uniquery controls with the `$search` field carved out for value-help
846
1215
  * use (the core DTO includes it but we narrow the type here so implementations
@@ -873,11 +1242,19 @@ interface ValueHelpQuery<T> {
873
1242
  * one of their own (see {@link AsDbReadableController} for the
874
1243
  * `@db.column.filterable` / `@db.column.sortable` pattern).
875
1244
  *
876
- * **Actions are intentionally NOT supported on value-help controllers.** The
877
- * `/meta` payload still includes `actions: []` for shape uniformity, and any
878
- * `@DbAction*` / `@DbActions*` decorators applied here are silently ignored.
879
- * Value-help is for FK pickers and dictionary surfaces — adding row/table
880
- * actions there would muddy the contract.
1245
+ * **Per-request scoping** (since 0.1.143) — the same three seams as the DB
1246
+ * controllers, applied by the base routes to every value-help source:
1247
+ * {@link transformFilter} (row overlay: `/query` / `/pages` filter, `/one`
1248
+ * rows), {@link transformProjection} (returned columns) and {@link hasField}
1249
+ * (a hidden field answers like an unknown one in filter / sort / select and
1250
+ * never matches `$search`).
1251
+ *
1252
+ * **Actions are NOT supported on value-help controllers.** The `/meta`
1253
+ * payload still includes `actions: []` for shape uniformity; since 0.1.143
1254
+ * any `@DbAction` / `@DbActions*` on a value-help controller is a hard error
1255
+ * (at decoration, or at construction for actions inherited from a base) —
1256
+ * previously it was dropped from `/meta` while its `@Post` route still ran
1257
+ * without any gate. Value-help is for FK pickers and dictionary surfaces.
881
1258
  */
882
1259
  declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsReadableController<T, DataType> {
883
1260
  /** Per-prop metadata map of the bound interface; eagerly built once. */
@@ -898,9 +1275,59 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
898
1275
  data: DataType[];
899
1276
  count: number;
900
1277
  }>;
901
- /** Returns the row whose primary key matches `id`, or `null` on miss. */
1278
+ /**
1279
+ * Returns the row whose primary key matches `id`, or `null` on miss. The
1280
+ * `/one` routes then apply {@link transformFilter} (a row outside the
1281
+ * overlay is a 404) and {@link transformProjection} to it in memory.
1282
+ */
902
1283
  protected abstract getOne(id: string | number): Promise<DataType | null>;
1284
+ /**
1285
+ * THE field-visibility hook: `true` when `path` is a field of the bound
1286
+ * interface visible to this request. A path it rejects in the request's
1287
+ * filter / `$sort` / `$select` gets the same `Unknown field "x"` 400 as a
1288
+ * nonexistent one, and a hidden field never takes part in `$search`
1289
+ * (`AsJsonValueHelpController`). Override to hide fields per request — it
1290
+ * gates the request only; strip the column from responses with
1291
+ * {@link transformProjection}.
1292
+ */
903
1293
  protected hasField(path: string): boolean;
1294
+ /**
1295
+ * Row overlay — the value-help counterpart of the DB controllers'
1296
+ * `transformFilter`. Receives the request filter of `/query` / `/pages`
1297
+ * and returns the one to run (AND your scope in: `{ $and: [filter, scope] }`).
1298
+ * `/one` evaluates `transformFilter({})` against the found row in memory: a
1299
+ * row outside it answers 404, exactly like a missing one. Default: identity.
1300
+ * May be async.
1301
+ *
1302
+ * @since 0.1.143
1303
+ */
1304
+ protected transformFilter(filter: FilterExpr): FilterExpr | Promise<FilterExpr>;
1305
+ /**
1306
+ * Projection hook — receives the request `$select` (`undefined` when
1307
+ * absent; `/one` always passes `undefined`) and returns the projection to
1308
+ * apply: an inclusion list / `{ path: 1 }` map, or an exclusion
1309
+ * `{ path: 0 }` map. Default: identity. May be async.
1310
+ *
1311
+ * @since 0.1.143
1312
+ */
1313
+ protected transformProjection(select: ValueHelpSelect<DataType> | undefined): ValueHelpSelect<DataType> | undefined | Promise<ValueHelpSelect<DataType> | undefined>;
1314
+ /**
1315
+ * Normalizes a value-help `$select` (the raw `parseUrl` form or a
1316
+ * {@link transformProjection} result) to the `@atscript/db-memory`
1317
+ * `{ path: 0 | 1 }` projection map:
1318
+ * - `string[]` (e.g. from `?$select=a,b`) → inclusion map `{ a: 1, b: 1 }`,
1319
+ * - a plain `{ path: 0 | 1 }` object → passed through (0 / falsy → exclude),
1320
+ * - anything else / empty → `undefined` (no projection; whole rows returned).
1321
+ */
1322
+ protected normalizeSelect(select: unknown): Record<string, 0 | 1> | undefined;
1323
+ /** `filter` through {@link transformFilter} and `controls.$select` through {@link transformProjection}. */
1324
+ private _scopedQuery;
1325
+ /**
1326
+ * `getOne` + the row overlay (miss → 404) + the projection, applied in
1327
+ * memory — `getOne` is the subclass's own lookup (id coercion included),
1328
+ * so it is not re-expressed as a {@link query} filter.
1329
+ */
1330
+ private _scopedOne;
904
1331
  /**
905
1332
  * **GET /query** — returns an array of matched rows (up to `$limit`).
906
1333
  */
@@ -960,8 +1387,8 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
960
1387
  * ties). Direction via `-` prefix on the field name or `{ [field]: 'asc' |
961
1388
  * 'desc' | 1 | -1 }`.
962
1389
  * - Search is case-insensitive substring matching across every field listed in
963
- * {@link searchableFields}. This is value-help's own concern — the shared
964
- * engine has no `$search`.
1390
+ * {@link searchableFields} that {@link hasField} keeps visible. This is
1391
+ * value-help's own concern — the shared engine has no `$search`.
965
1392
  * - Projection (`$select`) supports inclusion (`[fields]` / `{ f: 1 }`) and
966
1393
  * exclusion (`{ f: 0 }`) with dot-path nesting; the primary key is NOT auto-
967
1394
  * added (a value-help projection returns exactly the selected fields).
@@ -995,14 +1422,6 @@ declare class AsJsonValueHelpController<T extends TAtscriptAnnotatedType = TAtsc
995
1422
  * Returns `undefined` when nothing sortable was parsed (engine skips sorting).
996
1423
  */
997
1424
  private normalizeSort;
998
- /**
999
- * Normalizes the raw `parseUrl` `$select` form to the engine's `{ path: 0 |
1000
- * 1 }` projection map:
1001
- * - `string[]` (e.g. from `?$select=a,b`) → inclusion map `{ a: 1, b: 1 }`,
1002
- * - a plain `{ path: 0 | 1 }` object → passed through (0 / falsy → exclude),
1003
- * - anything else / empty → `undefined` (no projection; whole rows returned).
1004
- */
1005
- private normalizeSelect;
1006
1425
  }
1007
1426
  //#endregion
1008
1427
  //#region src/actions/types.d.ts
@@ -1086,6 +1505,14 @@ interface BaseActionOpts extends Partial<Omit<TDbActionInfo$1, "name" | "level"
1086
1505
  * `AsDbController`) — the bound table from the controller wins.
1087
1506
  */
1088
1507
  table?: AtscriptDbTable<any>;
1508
+ /**
1509
+ * `'rows'` level (`@DbActionIDs` / `@DbActionRows`): the most identifiers
1510
+ * one request may carry. Above it the request is rejected with 400 before
1511
+ * any row is loaded. Default `1000`. Server-internal — never on the wire.
1512
+ *
1513
+ * @since 0.1.143
1514
+ */
1515
+ maxIds?: number;
1089
1516
  }
1090
1517
  /**
1091
1518
  * Options accepted by `@DbAction(name, opts?)`. Generic over `TRow` (the
@@ -1475,6 +1902,16 @@ declare function errorEnvelope(statusCode: number, message: string, errors: THtt
1475
1902
  * message (defaults to `message`).
1476
1903
  */
1477
1904
  declare function badRequest(path: string, message: string, top?: string): HttpError;
1905
+ /**
1906
+ * The 400 of a `$with` relation the request cannot reach — nonexistent, or
1907
+ * hidden by `hasField` (the two answer alike): `Unknown relation "<name>"`,
1908
+ * the envelope message listing `visible` (the relations the caller CAN
1909
+ * load at that level). The single source of this wording — a permission
1910
+ * layer that rejects a relation itself should throw this.
1911
+ *
1912
+ * @since 0.1.143
1913
+ */
1914
+ declare function unknownRelationError(name: string, visible: readonly string[]): HttpError;
1478
1915
  //#endregion
1479
1916
  //#region src/validation-interceptor.d.ts
1480
1917
  declare const validationErrorTransform: () => import("moost").TInterceptorDef;
@@ -1483,10 +1920,16 @@ declare const UseValidationErrorTransform: () => ClassDecorator & MethodDecorato
1483
1920
  //#region src/actions/db-action.decorator.d.ts
1484
1921
  /**
1485
1922
  * Mark a controller method as a database action surfaced via `/meta`. Writes
1486
- * `atscript_db_action` metadata and registers a Moost interceptor when needed
1487
- * (gate when `disabled` is set, thin bound-table injector when only
1488
- * `@DbActionRow*` is present). Stacking two `@DbAction` on the same method
1489
- * is undefined and emits a warning.
1923
+ * `atscript_db_action` metadata and, for every `'row'` / `'rows'` action,
1924
+ * registers a Moost interceptor: the gate when `disabled` is set, else the
1925
+ * bound-table injector that also verifies the ids against the controller's
1926
+ * row overlay (since 0.1.143). Either first awaits the controller's
1927
+ * `prepareRequest({ endpoint: "action", action })` when it defines one —
1928
+ * before any id is validated or row loaded; a `'table'`-level action on an
1929
+ * `AsReadableController` subclass gets an interceptor for that alone
1930
+ * (since 0.1.143). Stacking two `@DbAction` on the same method
1931
+ * is undefined and emits a warning. Throws on a value-help controller
1932
+ * (since 0.1.143 — value-help controllers do not support actions).
1490
1933
  *
1491
1934
  * Generic over `TRow` (annotate at the call site: `@DbAction<Order>(...)`)
1492
1935
  * and `R` (the literal `requiredFields` tuple, inferred via `const R`).
@@ -1584,7 +2027,7 @@ declare function DbActionRows(): ParameterDecorator;
1584
2027
  * `disabled` predicate is type-narrowed by its own `requiredFields` literal.
1585
2028
  *
1586
2029
  * Multiple `@DbActions` (and shortcut) decorators on the same class
1587
- * accumulate.
2030
+ * accumulate. Throws on a value-help controller (since 0.1.143).
1588
2031
  */
1589
2032
  declare function DbActions<TRow = unknown, const D extends Record<string, unknown> = {}>(dict: D & ValidatedDict<TRow, D>): ClassDecorator;
1590
2033
  /** Sugar for `@DbActions` with `level: 'table'` injected into each entry. */
@@ -1780,6 +2223,26 @@ declare const QUERY_CONTROLS: readonly string[];
1780
2223
  declare const PAGES_CONTROLS: readonly string[];
1781
2224
  declare const ONE_CONTROLS: readonly string[];
1782
2225
  //#endregion
2226
+ //#region src/permissions/crud-handlers.d.ts
2227
+ /**
2228
+ * The handler method(s) serving each CRUD op on `AsDbReadableController` /
2229
+ * `AsDbController` — what a permission layer authorizes a `/meta` `crud`
2230
+ * entry through (an op is allowed when ANY of its handlers is). `one` is
2231
+ * served by `/one/:id` and `/one?…`, `remove` by `DELETE /:id` and
2232
+ * `DELETE /?…`. Readables (no writes) serve only the read ops.
2233
+ *
2234
+ * @since 0.1.143
2235
+ */
2236
+ declare const DB_CRUD_HANDLERS: Readonly<Record<TCrudOp$1, readonly string[]>>;
2237
+ /**
2238
+ * The handler method(s) serving each CRUD op on the value-help controllers
2239
+ * (`AsValueHelpController` / `AsJsonValueHelpController` — read ops only,
2240
+ * no `geo`). Same contract as {@link DB_CRUD_HANDLERS}.
2241
+ *
2242
+ * @since 0.1.143
2243
+ */
2244
+ declare const VALUE_HELP_CRUD_HANDLERS: Readonly<Partial<Record<TCrudOp$1, readonly string[]>>>;
2245
+ //#endregion
1783
2246
  //#region src/meta/terminal-ref.d.ts
1784
2247
  /**
1785
2248
  * Terminal-reference resolution for `/meta` and `/meta/form/:name`
@@ -1829,4 +2292,4 @@ declare function resolveTerminalRef(def: TAtscriptAnnotatedType): TTerminalRef |
1829
2292
  */
1830
2293
  declare function applyTerminalRefs(serialized: TSerializedAnnotatedType, runtime: TAtscriptAnnotatedType, options: TSerializeOptions): TSerializedAnnotatedType;
1831
2294
  //#endregion
1832
- export { ActionDisabledError, type ActionDisabledErrorBody, AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, type AtscriptDbMate, type AtscriptDbMeta, type AtscriptDbParamsMeta, DEFAULT_DB_SPACE, DbAction, DbActionDefault, type DbActionEnvelope, DbActionID, DbActionIDs, type DbActionOpts, DbActionRow, DbActionRows, DbActions, DbRowActions, DbRowsActions, DbTableActions, FieldCapabilityIndex, type IdValidationSource, InputForm, ONE_CONTROLS, PAGES_CONTROLS, QUERY_CONTROLS, READABLE_DEF, ReadableController, TABLE_DEF, TAssertExposedOptions, type TCapabilityReadable, type TCapabilityVerdict, TControllerBindingOptions, type TCrudOp, type TCrudPermissions, type TDbActionDisabledVerdict, type TDbActionInfo, type TDbActionInputFormMeta, type TDbActionIntent, type TDbActionLevel, type TDbActionMeta, type TDbActionParamKind, type TDbActionProcessor, type TDbActionsEntry, type TDbActionsEntryUnpinned, type TDbClassActionMeta, TDbDecorateContext, TDbDecorateEndpoint, type TDbRemoveGuardContext, type TDbWriteAction, type TDbWriteGuardContext, type TFieldCapability, type THttpErrorEntry, type TQueryPathOp, type TQueryPathRefs, TReadableBinding, type TReadableBindingMeta, type TTerminalRef, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, applyTerminalRefs, assertExposed, badRequest, clearDbSpaces, collectQueryPaths, dbActionBodySlot, dbActionInputSlot, discoverActions, errorEnvelope, findReadableBinding, getAtscriptDbMate, getControllerFormType, perRow, provideDbSpace, resolveBoundReadable, resolveDbSpace, resolveProp, resolveTerminalRef, useDbActionId, useDbActionIds, useDbActionInput, useDbActionRow, useDbActionRows, validationErrorTransform };
2295
+ export { ActionDisabledError, type ActionDisabledErrorBody, AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, type AtscriptDbMate, type AtscriptDbMeta, type AtscriptDbParamsMeta, DB_CRUD_HANDLERS, DEFAULT_DB_SPACE, DbAction, DbActionDefault, type DbActionEnvelope, DbActionID, DbActionIDs, type DbActionOpts, DbActionRow, DbActionRows, DbActions, DbRowActions, DbRowsActions, DbTableActions, FieldCapabilityIndex, type IdValidationSource, InputForm, ONE_CONTROLS, PAGES_CONTROLS, QUERY_CONTROLS, READABLE_DEF, ReadableController, TABLE_DEF, TAssertExposedOptions, type TCapabilityReadable, type TCapabilityVerdict, TControllerBindingOptions, type TCrudOp, type TCrudPermissions, type TDbActionDisabledVerdict, type TDbActionInfo, type TDbActionInputFormMeta, type TDbActionIntent, type TDbActionLevel, type TDbActionMeta, type TDbActionParamKind, type TDbActionProcessor, type TDbActionsEntry, type TDbActionsEntryUnpinned, type TDbClassActionMeta, TDbControlsType, TDbDecorateContext, TDbDecorateEndpoint, TDbFieldVisibility, TDbIndexFieldPaths, TDbParsedRequest, type TDbRemoveGuardContext, TDbRequestContext, TDbRequestEndpoint, type TDbWriteAction, type TDbWriteCheckContext, type TDbWriteGuardContext, type TFieldCapability, type THttpErrorEntry, type TQueryPathOp, type TQueryPathRefs, TReadableBinding, type TReadableBindingMeta, type TTerminalRef, TableController, UseValidationErrorTransform, VALUE_HELP_CRUD_HANDLERS, ValueHelpQuery, ValueHelpSelect, ViewController, applyTerminalRefs, assertExposed, badRequest, clearDbSpaces, collectQueryPaths, dbActionBodySlot, dbActionInputSlot, discoverActions, errorEnvelope, findReadableBinding, getAtscriptDbMate, getControllerFormType, perRow, provideDbSpace, resolveBoundReadable, resolveDbSpace, resolveProp, resolveTerminalRef, unknownRelationError, useDbActionId, useDbActionIds, useDbActionInput, useDbActionRow, useDbActionRows, validationErrorTransform };