@atscript/moost-db 0.1.142 → 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
  *
@@ -93,12 +137,71 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
93
137
  private _queryControlsValidator?;
94
138
  private _pagesControlsValidator?;
95
139
  private _getOneControlsValidator?;
140
+ private _geoControlsValidator?;
96
141
  protected get queryControlsValidator(): Validator<any, unknown>;
97
142
  protected get pagesControlsValidator(): Validator<any, unknown>;
98
143
  protected get getOneControlsValidator(): Validator<any, unknown>;
99
- 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;
100
203
  protected validateInsights(insights: Map<string, unknown>): string | undefined;
101
- protected validateParsed(parsed: Uniquery, type: "query" | "pages" | "getOne"): HttpError | undefined;
204
+ protected validateParsed(parsed: Uniquery, type: TDbControlsType): HttpError | undefined;
102
205
  /**
103
206
  * Per-request gate hook, invoked by the DB readable controller right after
104
207
  * its capability gate (`checkCapabilities`) with the parsed query. The
@@ -143,6 +246,16 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
143
246
  * response by principal.
144
247
  */
145
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>;
146
259
  /**
147
260
  * Identity of the inputs the cached `/meta` envelope is built from — a new
148
261
  * value rebuilds it. Default: constant (built once). The DB readable
@@ -156,9 +269,20 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
156
269
  * compiled `.as` class's `.name`, registered when an action's parameter is
157
270
  * decorated with `@InputForm(FormType)`. Schemas are serialized once and
158
271
  * cached per controller; the response uses the same annotation-allowlist
159
- * 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.
160
274
  */
161
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>;
162
286
  /**
163
287
  * Builds the `/meta` payload. Override in subclasses to populate source-specific
164
288
  * fields. Subclasses that fully replace the envelope must call
@@ -350,6 +474,48 @@ interface TDbDecorateContext {
350
474
  /** The request's parsed controls (`$select`, `$with`, `$actions`, …). Read-only by convention. */
351
475
  controls: Record<string, unknown>;
352
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
+ }
353
519
  /**
354
520
  * Read-only database controller for Moost that works with any `AtscriptDbReadable`
355
521
  * (tables or views). Provides query, pages, getOne, and meta endpoints.
@@ -385,13 +551,31 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
385
551
  private _capabilities?;
386
552
  /** `/meta` is a projection of {@link capabilities}: a rebuilt index rebuilds the cached envelope. */
387
553
  protected metaCacheKey(): unknown;
388
- /** Bound once: the visibility check ({@link hasField}) the gate hands to `capabilities.check`. */
389
- private readonly _exists;
390
554
  /**
391
- * Id-resolution options (since 0.1.134): `{ isFieldVisible: hasField }`
392
- * when a subclass overrides {@link hasField}, else `undefined` (the default
393
- * accepts every real path, so resolution stays unfiltered). A unique index
394
- * 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.
395
579
  */
396
580
  protected readonly _idOpts: TIdResolveOptions | undefined;
397
581
  /** Narrowed id sources, one stable object per distinct visible-identification set. */
@@ -400,6 +584,8 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
400
584
  private readonly _overlayIsNoOp;
401
585
  /** `true` when a subclass implements {@link decorateRows} (the override switches the hook on). */
402
586
  private readonly _decorates;
587
+ /** `true` when a subclass overrides {@link transformOne} or {@link transformFilter} (a row overlay may exist). */
588
+ private readonly _hasRowOverlay;
403
589
  /** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
404
590
  private readonly _quantityRefByPath;
405
591
  /** `@db.column.searchable` paths — the `$search` fallback when the adapter has no native search. */
@@ -422,6 +608,8 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
422
608
  get idSource(): IdValidationSource;
423
609
  private _collectInvertibleFields;
424
610
  private _collectQuantityRefs;
611
+ /** `readable`'s `@db.column.derived` path → source path map, collected once per readable. */
612
+ private _derivedSourcesOf;
425
613
  private _collectAnnotated;
426
614
  /**
427
615
  * THE field-visibility hook: every gated path consults it before any
@@ -437,9 +625,18 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
437
625
  * an action id (primary key and `preferredId` always are) — and the
438
626
  * nested-object 400 hint lists visible leaves only. The default accepts every real path
439
627
  * (`isValidFieldPath`). `/meta` does NOT consult it — prune hidden fields
440
- * there with `applyMetaOverlay`. Native text search and vector search
441
- * (`$vector` names an index) run inside the engine over its indexes, out of
442
- * 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.
443
640
  */
444
641
  protected hasField(path: string): boolean;
445
642
  /**
@@ -457,7 +654,9 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
457
654
  * ran first); a bucket's source is checked like any other path (op
458
655
  * `bucket`). After the per-path checks the core `$having` rule runs
459
656
  * (`checkHavingKeys`: aliases or `$groupBy` fields only), so a readable mock
460
- * 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}.
461
660
  */
462
661
  protected checkCapabilities(parsed: {
463
662
  filter?: FilterExpr;
@@ -482,8 +681,91 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
482
681
  * resolved against the target table through `isValidFieldPath`.
483
682
  */
484
683
  protected validateInsights(insights: Map<string, unknown>): string | undefined;
485
- /** {@link checkComputedSelect} (before the controls DTO), then $with relations against the readable. */
486
- 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;
487
769
  /**
488
770
  * Compute an embedding vector from a search term.
489
771
  * Override in subclass to integrate with your embedding provider (OpenAI, etc.).
@@ -526,13 +808,16 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
526
808
  private widenQuantityRefProjection;
527
809
  private _widenQuantityArrayProjection;
528
810
  private _widenQuantityMapProjection;
529
- /** WHY: the URL parser only auto-coerces `$count`; every other boolean control reaches us as `"true"`/`"1"` and would fail DTO validation. */
530
- private _coerceActionsControl;
531
811
  /** Normalize a post-`widenPreferredIdProjection` $select into `string[] | null` (`null` = all fields). */
532
812
  private _resolveProjectionForAugmenter;
533
813
  /** WHY: filter row/rows envelopes by the per-request `applyMetaOverlay` action set; skip `meta()` when overlay is identity. */
534
814
  private _resolveAugmentEnvelopes;
535
- /** 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
+ */
536
821
  private _widenSelectForActions;
537
822
  private _prepareAugmentation;
538
823
  /**
@@ -543,20 +828,20 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
543
828
  */
544
829
  private _collectSearchFallbackFields;
545
830
  /**
546
- * Removes `@db.writeOnly` fields from any `$select` shape — and forces an
547
- * exclusion when no projection was requested — so sealed values never leave
548
- * the database on a read. Runs AFTER `transformProjection` so permission
549
- * overlays compose (they see the wire `$select`; this guarantees the seal on
550
- * 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.
551
834
  */
552
- private _sealProjection;
835
+ private _sealSelect;
553
836
  /** First `@db.writeOnly` field referenced by `$groupBy` / aggregate `$select`, or undefined. */
554
837
  private _findWriteOnlyInAggregate;
555
838
  /**
556
839
  * Merges the `$search` fallback into the filter: a case-insensitive literal
557
840
  * substring match OR'd across the `@db.column.searchable` fields, `$and`-combined
558
- * with the existing filter. Applies only when the adapter has no native search
559
- * (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).
560
845
  */
561
846
  protected applySearchFallback(filter: FilterExpr | undefined, controls: Record<string, unknown>): FilterExpr | undefined;
562
847
  /**
@@ -622,6 +907,32 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
622
907
  * strategy to its read-method family (count vs no-count).
623
908
  */
624
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>;
625
936
  /**
626
937
  * Pick the first identification (PK or unique index) whose fields are all
627
938
  * present in the query. A unique index over a field {@link hasField} hides
@@ -651,7 +962,9 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
651
962
  * (meters), `$index` (geo index name), plus the standard filter / `$select` /
652
963
  * `$with` / pagination syntax. Each row carries a computed `$distance`
653
964
  * (meters). With `$page` / `$size` the response is the `/pages` envelope;
654
- * 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.
655
968
  */
656
969
  geo(url: string): Promise<DataType[] | {
657
970
  data: DataType[];
@@ -674,7 +987,12 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
674
987
  * gating as {@link getOne}.
675
988
  */
676
989
  getOneComposite(query: Record<string, string>, url: string): Promise<DataType | HttpError>;
677
- 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;
678
996
  /**
679
997
  * **GET /meta** — returns table/view metadata for UI.
680
998
  *
@@ -702,15 +1020,18 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
702
1020
  * ### Write pipeline (since 0.1.128)
703
1021
  *
704
1022
  * ```
705
- * shape gate (400) → onWrite / onRemove (outside any transaction)
1023
+ * prepareRequest (when implemented) → shape gate (400)
1024
+ * → onWrite / onRemove (outside any transaction)
706
1025
  * → table op — the table's own transaction: validate → guardWrite / guardRemove
707
- * (only when overridden) → re-validate → write
1026
+ * (only when overridden) → re-validate → write → nested phases
1027
+ * → checkWrite (only when overridden)
708
1028
  * → 404 / 409 disambiguation
709
1029
  * ```
710
1030
  *
711
1031
  * The guard is the table's `guard` write option (`TWriteOptions.guard` /
712
1032
  * `TDeleteOptions.guard`); overriding `guardWrite` / `guardRemove` is the
713
- * 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`
714
1035
  * (wire-identical to returning them; a throw also rolls back a user-level
715
1036
  * `withTransaction` wrapper).
716
1037
  */
@@ -762,6 +1083,30 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
762
1083
  * the guard.
763
1084
  */
764
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>;
765
1110
  /**
766
1111
  * Runs `fn` inside the bound table's adapter transaction (nested calls
767
1112
  * join it). For custom actions and routes that need one transaction across
@@ -773,10 +1118,11 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
773
1118
  /** `deleteOne`'s trailing options — see {@link _hookArgs}. */
774
1119
  private readonly _removeArgs;
775
1120
  /**
776
- * A table call's trailing options, built once: `guard` only when the guard
777
- * hook is overridden, `isFieldVisible` only when `hasField` is (an id or a
778
- * PK-less payload never resolves through a hidden unique key) — else
779
- * 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.
780
1126
  */
781
1127
  private _hookArgs;
782
1128
  /** Resolves a hook result: `undefined` aborts with `abortMessage`, an `Error` is thrown, anything else passes. */
@@ -802,7 +1148,13 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
802
1148
  * application; callers can detect mismatches via `modifiedCount < N`.
803
1149
  */
804
1150
  private _resolveBulkCas;
805
- /** 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
+ */
806
1158
  private _deleteOrThrow;
807
1159
  /**
808
1160
  * **POST /** — inserts one or many records.
@@ -831,6 +1183,12 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
831
1183
  * returns 404 when the row is genuinely missing, 409 with
832
1184
  * `{ error: "version_mismatch", currentVersion: N }` when it's present
833
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}.
834
1192
  */
835
1193
  protected _disambiguateMismatch(data: unknown, versionColumn: string): Promise<HttpError>;
836
1194
  /**
@@ -845,6 +1203,13 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
845
1203
  }
846
1204
  //#endregion
847
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>;
848
1213
  /**
849
1214
  * Parsed Uniquery controls with the `$search` field carved out for value-help
850
1215
  * use (the core DTO includes it but we narrow the type here so implementations
@@ -877,11 +1242,19 @@ interface ValueHelpQuery<T> {
877
1242
  * one of their own (see {@link AsDbReadableController} for the
878
1243
  * `@db.column.filterable` / `@db.column.sortable` pattern).
879
1244
  *
880
- * **Actions are intentionally NOT supported on value-help controllers.** The
881
- * `/meta` payload still includes `actions: []` for shape uniformity, and any
882
- * `@DbAction*` / `@DbActions*` decorators applied here are silently ignored.
883
- * Value-help is for FK pickers and dictionary surfaces — adding row/table
884
- * 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.
885
1258
  */
886
1259
  declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsReadableController<T, DataType> {
887
1260
  /** Per-prop metadata map of the bound interface; eagerly built once. */
@@ -902,9 +1275,59 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
902
1275
  data: DataType[];
903
1276
  count: number;
904
1277
  }>;
905
- /** 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
+ */
906
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
+ */
907
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;
908
1331
  /**
909
1332
  * **GET /query** — returns an array of matched rows (up to `$limit`).
910
1333
  */
@@ -964,8 +1387,8 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
964
1387
  * ties). Direction via `-` prefix on the field name or `{ [field]: 'asc' |
965
1388
  * 'desc' | 1 | -1 }`.
966
1389
  * - Search is case-insensitive substring matching across every field listed in
967
- * {@link searchableFields}. This is value-help's own concern — the shared
968
- * 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`.
969
1392
  * - Projection (`$select`) supports inclusion (`[fields]` / `{ f: 1 }`) and
970
1393
  * exclusion (`{ f: 0 }`) with dot-path nesting; the primary key is NOT auto-
971
1394
  * added (a value-help projection returns exactly the selected fields).
@@ -999,14 +1422,6 @@ declare class AsJsonValueHelpController<T extends TAtscriptAnnotatedType = TAtsc
999
1422
  * Returns `undefined` when nothing sortable was parsed (engine skips sorting).
1000
1423
  */
1001
1424
  private normalizeSort;
1002
- /**
1003
- * Normalizes the raw `parseUrl` `$select` form to the engine's `{ path: 0 |
1004
- * 1 }` projection map:
1005
- * - `string[]` (e.g. from `?$select=a,b`) → inclusion map `{ a: 1, b: 1 }`,
1006
- * - a plain `{ path: 0 | 1 }` object → passed through (0 / falsy → exclude),
1007
- * - anything else / empty → `undefined` (no projection; whole rows returned).
1008
- */
1009
- private normalizeSelect;
1010
1425
  }
1011
1426
  //#endregion
1012
1427
  //#region src/actions/types.d.ts
@@ -1090,6 +1505,14 @@ interface BaseActionOpts extends Partial<Omit<TDbActionInfo$1, "name" | "level"
1090
1505
  * `AsDbController`) — the bound table from the controller wins.
1091
1506
  */
1092
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;
1093
1516
  }
1094
1517
  /**
1095
1518
  * Options accepted by `@DbAction(name, opts?)`. Generic over `TRow` (the
@@ -1479,6 +1902,16 @@ declare function errorEnvelope(statusCode: number, message: string, errors: THtt
1479
1902
  * message (defaults to `message`).
1480
1903
  */
1481
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;
1482
1915
  //#endregion
1483
1916
  //#region src/validation-interceptor.d.ts
1484
1917
  declare const validationErrorTransform: () => import("moost").TInterceptorDef;
@@ -1487,10 +1920,16 @@ declare const UseValidationErrorTransform: () => ClassDecorator & MethodDecorato
1487
1920
  //#region src/actions/db-action.decorator.d.ts
1488
1921
  /**
1489
1922
  * Mark a controller method as a database action surfaced via `/meta`. Writes
1490
- * `atscript_db_action` metadata and registers a Moost interceptor when needed
1491
- * (gate when `disabled` is set, thin bound-table injector when only
1492
- * `@DbActionRow*` is present). Stacking two `@DbAction` on the same method
1493
- * 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).
1494
1933
  *
1495
1934
  * Generic over `TRow` (annotate at the call site: `@DbAction<Order>(...)`)
1496
1935
  * and `R` (the literal `requiredFields` tuple, inferred via `const R`).
@@ -1588,7 +2027,7 @@ declare function DbActionRows(): ParameterDecorator;
1588
2027
  * `disabled` predicate is type-narrowed by its own `requiredFields` literal.
1589
2028
  *
1590
2029
  * Multiple `@DbActions` (and shortcut) decorators on the same class
1591
- * accumulate.
2030
+ * accumulate. Throws on a value-help controller (since 0.1.143).
1592
2031
  */
1593
2032
  declare function DbActions<TRow = unknown, const D extends Record<string, unknown> = {}>(dict: D & ValidatedDict<TRow, D>): ClassDecorator;
1594
2033
  /** Sugar for `@DbActions` with `level: 'table'` injected into each entry. */
@@ -1784,6 +2223,26 @@ declare const QUERY_CONTROLS: readonly string[];
1784
2223
  declare const PAGES_CONTROLS: readonly string[];
1785
2224
  declare const ONE_CONTROLS: readonly string[];
1786
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
1787
2246
  //#region src/meta/terminal-ref.d.ts
1788
2247
  /**
1789
2248
  * Terminal-reference resolution for `/meta` and `/meta/form/:name`
@@ -1833,4 +2292,4 @@ declare function resolveTerminalRef(def: TAtscriptAnnotatedType): TTerminalRef |
1833
2292
  */
1834
2293
  declare function applyTerminalRefs(serialized: TSerializedAnnotatedType, runtime: TAtscriptAnnotatedType, options: TSerializeOptions): TSerializedAnnotatedType;
1835
2294
  //#endregion
1836
- 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 };