@atscript/moost-db 0.1.145 → 0.1.147

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.mts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { i as resolveDbSpace, n as clearDbSpaces, r as provideDbSpace, t as DEFAULT_DB_SPACE } from "./db-space-registry-CKR6G-hi.mjs";
2
2
  import { TAtscriptAnnotatedType, TAtscriptDataType, TSerializeOptions, TSerializedAnnotatedType, TValidatorOptions, Validator } from "@atscript/typescript/utils";
3
- import { HttpError } from "@moostjs/event-http";
3
+ import { HttpError, MoostHttp } from "@moostjs/event-http";
4
4
  import { Mate, Moost, TConsoleBase, TMateParamMeta, TMoostMetadata } from "moost";
5
5
  import { parseUrl } from "@uniqu/url";
6
- import { AggregateFn, AtscriptDbReadable, AtscriptDbTable, BucketUnit, FilterExpr, FlatOf, TCrudOp, TCrudOp as TCrudOp$1, TCrudPermissions, TCrudPermissions as TCrudPermissions$1, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbAvailableActions, 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";
6
+ import { AggregateFn, AtscriptDbReadable, AtscriptDbTable, BucketUnit, FilterExpr, FlatOf, TCrudOp, TCrudOp as TCrudOp$1, TCrudPermissions, TCrudPermissions as TCrudPermissions$1, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbActionTargetSummary, TDbActionTargetSummary as TDbActionTargetSummary$1, TDbAvailableActions, TDbAvailableActions as TDbAvailableActions$1, TDbFieldMeta, TDbRemoveGuardContext, TDbRemoveGuardContext as TDbRemoveGuardContext$1, TDbWriteAction, TDbWriteAction as TDbWriteAction$1, TDbWriteCheckContext, TDbWriteCheckContext as TDbWriteCheckContext$1, TDbWriteGuardContext, TDbWriteGuardContext as TDbWriteGuardContext$1, TFilterPredicate, TIdResolveOptions, TIdentification, TMetaResponse, TQueryPathOp, TQueryPathOp as TQueryPathOp$1, TQueryPathRefs, TQueryPathSource, Uniquery, UniqueryControls, collectQueryPaths } from "@atscript/db";
7
+ import { EventContext } from "@wooksjs/event-core";
8
+
7
9
  //#region src/as-readable.controller.d.ts
8
10
  /**
9
11
  * Endpoint a {@link AsReadableController.prepareRequest} call serves. `/one`
@@ -12,11 +14,14 @@ import { AggregateFn, AtscriptDbReadable, AtscriptDbTable, BucketUnit, FilterExp
12
14
  * like DB readables do; every `@DbAction` handler (row, rows and table
13
15
  * level) reports `"action"` with the action's name in
14
16
  * {@link TDbRequestContext.action}; `GET /meta/actions/:id` and
15
- * `/meta/actions?…` report `"availableActions"` (since 0.1.145).
17
+ * `/meta/actions?…` report `"availableActions"` (since 0.1.145); a view's
18
+ * `POST /delegated-actions/:name` (a query target for a `@DbActionsFrom`
19
+ * action — a read of THIS controller's rows) reports `"delegatedAction"` with
20
+ * the action's name in {@link TDbRequestContext.action} (since 0.1.147).
16
21
  *
17
22
  * @since 0.1.143
18
23
  */
19
- type TDbRequestEndpoint = "query" | "pages" | "geo" | "one" | "meta" | "metaForm" | "insert" | "replace" | "update" | "remove" | "action" | "availableActions";
24
+ type TDbRequestEndpoint = "query" | "pages" | "geo" | "one" | "meta" | "metaForm" | "insert" | "replace" | "update" | "remove" | "action" | "availableActions" | "delegatedAction";
20
25
  /**
21
26
  * Context passed to {@link AsReadableController.prepareRequest}.
22
27
  *
@@ -32,7 +37,29 @@ interface TDbRequestContext {
32
37
  * `metaForm`, writes and actions.
33
38
  */
34
39
  readonly controls?: Record<string, unknown>;
35
- /** `"action"` endpoint only: the `@DbAction` name being run. */
40
+ /**
41
+ * The parsed CLIENT filter of `query`, `pages` and `geo` (absent when the
42
+ * URL carries none, and on every other endpoint) — e.g. for a permission
43
+ * layer to resolve which relations the request's relational predicates
44
+ * (`ticket=$some(…)`) touch, alongside `controls.$with`. A deep-frozen
45
+ * COPY of the parsed filter — reading it is all a hook can do; the request
46
+ * gate judges the original. Server-side filters (`transformFilter`, …) are
47
+ * not in it.
48
+ *
49
+ * @since 0.1.147
50
+ */
51
+ readonly filter?: FilterExpr;
52
+ /**
53
+ * Read endpoints (`query`, `pages`, `geo`, `one`): whether {@link filter} holds a relational
54
+ * predicate (`ticket=$some(…)`) — `false` without a filter. Reading
55
+ * `filter` copies the whole filter on first access; check this first when
56
+ * only the predicates matter. `$with` sub-filters are not counted (they are
57
+ * in `controls.$with`).
58
+ *
59
+ * @since 0.1.147
60
+ */
61
+ readonly hasRelationFilters?: boolean;
62
+ /** `"action"` / `"delegatedAction"` endpoints only: the `@DbAction` name being run. */
36
63
  readonly action?: string;
37
64
  }
38
65
  /** Control DTO a {@link AsReadableController.validateControls} call checks against. @since 0.1.143 (`"geo"`) */
@@ -321,14 +348,330 @@ interface IdValidationSource {
321
348
  readonly fieldDescriptors: readonly TDbFieldMeta[];
322
349
  }
323
350
  //#endregion
351
+ //#region src/actions/query-target.d.ts
352
+ /**
353
+ * A query target — the `query` of an action request body: "every row
354
+ * matching this query" instead of a list of identifiers (since 0.1.147).
355
+ *
356
+ * @since 0.1.147
357
+ */
358
+ interface TDbActionQueryTarget {
359
+ /** The query string `GET /query` accepts — filter plus `$search` / `$index` ONLY. */
360
+ q: string;
361
+ /** Identifiers to leave out (any identification; at most the action's `maxIds`). */
362
+ exclude?: Record<string, unknown>[];
363
+ /** Guard: the request fails with 409 `TARGET_CHANGED` when the match count differs. */
364
+ expectCount?: number;
365
+ /** Client-side cap (it never raises the server's `maxRows`). */
366
+ maxRows?: number;
367
+ /** Resolve and count only — the reply is `{ matched }`, the handler does not run. */
368
+ dryRun?: boolean;
369
+ }
370
+ /** `queryTarget` of `@DbAction` options. `true` = the defaults. @since 0.1.147 */
371
+ type TDbQueryTargetOpts = boolean | {
372
+ maxRows?: number;
373
+ batchSize?: number;
374
+ };
375
+ /**
376
+ * Key of `AsDbReadableController`'s internal query-target resolver (a
377
+ * registered symbol, like `ACTION_OVERLAY`).
378
+ */
379
+ declare const RESOLVE_TARGET: unique symbol;
380
+ /** What {@link RESOLVE_TARGET} resolves. */
381
+ interface TTargetRequest {
382
+ action: string;
383
+ /** The raw `query` of the request body — validated by the resolver. */
384
+ query: unknown;
385
+ /** The most rows the target may match (server side). */
386
+ cap: number;
387
+ /** The most `exclude` entries. */
388
+ maxExclude: number;
389
+ /**
390
+ * The row overlay the target resolves under, besides `queryTargetScope`:
391
+ * `"action"` — the controller's `rowOverlay()` (its own action runs on the
392
+ * rows); `"read"` — `transformFilter` (a view resolving rows it delegates).
393
+ */
394
+ overlay: "action" | "read";
395
+ /** Fields of the phase-1 read (sorted by the first identification's fields). */
396
+ select: readonly string[];
397
+ /** Key sets `exclude` entries may use besides the controller's identifications. */
398
+ excludeShapes?: readonly (readonly string[])[];
399
+ }
400
+ /** A resolved query target: the phase-1 snapshot plus its re-check. */
401
+ interface TResolvedTarget {
402
+ /** Rows matched at phase 1 (after `exclude`). */
403
+ matched: number;
404
+ /** The phase-1 rows (`select` fields), ordered by identity. */
405
+ rows: Record<string, unknown>[];
406
+ dryRun: boolean;
407
+ /** The validated `exclude` entries (already applied to {@link rows}). */
408
+ exclude: Record<string, unknown>[];
409
+ /**
410
+ * The rows `ids` address that STILL match the target (filter, search,
411
+ * overlay, `queryTargetScope`, `exclude`), aligned with `ids`; `select`
412
+ * plus the id fields. The FIRST call directly follows the snapshot and is
413
+ * not re-checked (served from {@link rows}, or read by identity alone
414
+ * when `select` needs more fields); every later call re-checks.
415
+ */
416
+ load(ids: readonly Record<string, unknown>[], select: Iterable<string>): Promise<Array<Record<string, unknown> | undefined>>;
417
+ }
418
+ //#endregion
419
+ //#region src/actions/types.d.ts
420
+ /**
421
+ * One entry of a `disabled` predicate's result. Truthy = the action is
422
+ * disabled for that row; falsy (`false`, `""`) = enabled. A non-empty string
423
+ * disables the action AND carries a human-readable reason — surfaced in the
424
+ * 409 `ActionDisabledError` message/body and in the row's `$disabledReasons`.
425
+ *
426
+ * @since 0.1.141 (`string`)
427
+ */
428
+ type TDbActionDisabledVerdict = boolean | string;
429
+ /** `'rows'`-level batch policy — controls whether failing rows reject or are filtered out. */
430
+ type TOnDisabledRows = "reject" | "skip";
431
+ /**
432
+ * Dot-notation field paths of `TRow`'s flat type. Drives both the runtime
433
+ * projection widening and the type narrowing of the `disabled` predicate's
434
+ * row argument. Relations are absent from `FlatOf<T>` — listing a relation
435
+ * field is therefore a compile error.
436
+ *
437
+ * Permissive fallback when `TRow = unknown` (no explicit decorator generic):
438
+ * any string is allowed and the `disabled` predicate's row arg is `any[]`,
439
+ * preserving the prior loose typing for un-annotated call sites.
440
+ */
441
+ type FlatKey<TRow> = unknown extends TRow ? string : keyof FlatOf<TRow> & string;
442
+ /** Row-shape narrowing for the `disabled` predicate. Falls back to `any` when `TRow = unknown`. */
443
+ type DisabledRowsArg<TRow, R extends readonly FlatKey<TRow>[]> = unknown extends TRow ? any[] : Pick<FlatOf<TRow>, R[number] & keyof FlatOf<TRow>>[];
444
+ interface NoGate {
445
+ requiredFields?: never;
446
+ disabled?: never;
447
+ onDisabledRows?: never;
448
+ }
449
+ interface WithGate<TRow, R extends readonly FlatKey<TRow>[]> {
450
+ /**
451
+ * Dot-notation field paths the predicate references. SERVER-INTERNAL —
452
+ * never emitted on the `/meta` wire. Consumed verbatim to widen the DB
453
+ * projection so `disabled` always sees the fields it declared.
454
+ */
455
+ requiredFields: R;
456
+ /**
457
+ * Sync batch gate predicate — returns a parallel array aligned with the
458
+ * input. Per entry: truthy = disabled for the corresponding row; a
459
+ * non-empty string also gives the reason (see
460
+ * {@link TDbActionDisabledVerdict}). The `rows`
461
+ * argument is type-narrowed to `Pick<FlatOf<TRow>, R[number]>[]`; reading
462
+ * a field not listed in `requiredFields` is a compile error.
463
+ *
464
+ * Promise return is NOT permitted — the predicate is consumed in the
465
+ * same tick by the gate and the augmenter.
466
+ */
467
+ disabled?: (rows: DisabledRowsArg<TRow, R>) => TDbActionDisabledVerdict[];
468
+ /**
469
+ * `'rows'`-level batch policy. Default `'reject'`.
470
+ *
471
+ * - `'reject'`: evaluate every row before throwing; if any row fails, the
472
+ * error body lists ALL failing IDs; handler not invoked.
473
+ * - `'skip'`: filter cached rows + cached IDs to passing-only; zero
474
+ * survivors → reject. Handler runs against the survivors.
475
+ *
476
+ * Ignored for `'row'` and `'table'` level actions.
477
+ */
478
+ onDisabledRows?: TOnDisabledRows;
479
+ }
480
+ /**
481
+ * Loose gate shape used when `TRow = unknown` (no explicit decorator generic).
482
+ * Preserves the prior un-typed call-site flexibility; the runtime still drops
483
+ * actions where `disabled` is set without `requiredFields`.
484
+ */
485
+ interface LooseGate {
486
+ requiredFields?: string[];
487
+ disabled?: (rows: any[]) => TDbActionDisabledVerdict[];
488
+ onDisabledRows?: TOnDisabledRows;
489
+ }
490
+ type GateOpts<TRow, R extends readonly FlatKey<TRow>[]> = unknown extends TRow ? LooseGate : NoGate | WithGate<TRow, R>;
491
+ interface BaseActionOpts extends Partial<Omit<TDbActionInfo$1, "name" | "level" | "processor" | "value" | "disabled" | "formUrl" | "owner" | "idMap" | "queryTarget">> {
492
+ /**
493
+ * Bound table reference. REQUIRED on non-`AsDbReadableController` classes
494
+ * when `disabled` is set OR a `@DbActionRow*` parameter is declared.
495
+ *
496
+ * Silently ignored on `AsDbReadableController` subclasses (which include
497
+ * `AsDbController`) — the bound table from the controller wins.
498
+ */
499
+ table?: AtscriptDbTable<any>;
500
+ /**
501
+ * `'rows'` level (`@DbActionIDs` / `@DbActionRows`): the most identifiers
502
+ * one request may carry. Above it the request is rejected with 400 before
503
+ * any row is loaded. Default `1000`. Server-internal — never on the wire.
504
+ *
505
+ * @since 0.1.143
506
+ */
507
+ maxIds?: number;
508
+ /**
509
+ * `'rows'` level only: the action also accepts a query target — a body
510
+ * `{ query: { q, exclude?, expectCount?, maxRows?, dryRun? }, input? }`
511
+ * meaning "every row matching `q`" (the `GET /query` filter plus
512
+ * `$search` / `$index`) under the caller's read scope (`queryTargetScope`)
513
+ * and the action's own gate. `true` = `{ maxRows: 10_000, batchSize:
514
+ * 500 }`. `maxRows` (on the wire as `queryTarget.maxRows`) caps the
515
+ * matched rows; a `@DbActionTarget()` handler gets them in `batchSize`
516
+ * batches, a `@DbActionIDs` / `@DbActionRows` handler at once (then also
517
+ * capped by `maxIds`).
518
+ *
519
+ * @since 0.1.147
520
+ */
521
+ queryTarget?: TDbQueryTargetOpts;
522
+ }
523
+ /**
524
+ * Options accepted by `@DbAction(name, opts?)`. Generic over `TRow` (the
525
+ * controller's bound atscript type) and `R` (the literal `requiredFields`
526
+ * tuple). Both are inferred at the call site via the decorator's `<TRow>`
527
+ * argument plus `const R` generic.
528
+ */
529
+ type DbActionOpts<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = BaseActionOpts & GateOpts<TRow, R>;
530
+ interface DbActionsEntryCommonBase {
531
+ label: string;
532
+ level: TDbActionLevel$1;
533
+ icon?: string;
534
+ intent?: TDbActionIntent$1;
535
+ description?: string;
536
+ order?: number;
537
+ default?: boolean;
538
+ /** Mirrors {@link TDbActionInfo.promptText} — singular/plural via tuple. */
539
+ promptText?: string | [string, string];
540
+ /** Mirrors {@link TDbActionInfo.shortcut} — single-character UI hint. */
541
+ shortcut?: string;
542
+ /**
543
+ * Input form the UI collects before invoking the action:
544
+ *
545
+ * - a compiled `.as` interface — registered on THIS controller and served
546
+ * by its own `GET /meta/form/:name`; the wire carries `inputForm: Type.name`.
547
+ * - `{ name, url }` — a form served elsewhere: `name` goes on the wire as
548
+ * `inputForm`, `url` (server-absolute path of the serialized schema, e.g.
549
+ * `"/api/shipping/meta/form/ShipForm"`) as {@link TDbActionInfo.formUrl}.
550
+ *
551
+ * Not allowed with `processor: 'navigate'`. Class-level entries only
552
+ * describe the action — validating `input` is the target handler's job
553
+ * (e.g. its own `@InputForm(Type)` param).
554
+ *
555
+ * @since 0.1.136
556
+ */
557
+ inputForm?: TAtscriptAnnotatedType | {
558
+ name: string;
559
+ url: string;
560
+ };
561
+ }
562
+ type DbActionsEntryWithGate<TRow, R extends readonly FlatKey<TRow>[]> = DbActionsEntryCommonBase & GateOpts<TRow, R>;
563
+ /**
564
+ * Class-level dict entry. `value` semantics by processor:
565
+ *
566
+ * - `'navigate'` — REQUIRED, non-empty. URL template (`$1` substituted client-side).
567
+ * - `'backend'` — REQUIRED, non-empty. Full HTTP POST path the UI client invokes.
568
+ * - `'custom'` — `value` is forbidden in the entry; the meta builder fills it
569
+ * with the dict key.
570
+ */
571
+ type TDbActionsEntry<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = (DbActionsEntryWithGate<TRow, R> & {
572
+ processor: "navigate";
573
+ value: string;
574
+ inputForm?: never;
575
+ }) | (DbActionsEntryWithGate<TRow, R> & {
576
+ processor: "custom";
577
+ value?: never;
578
+ }) | (DbActionsEntryWithGate<TRow, R> & {
579
+ processor: "backend";
580
+ value: string;
581
+ });
582
+ /** Distributes `Omit` across the discriminated union members. */
583
+ type DistributiveOmit<T, K extends keyof T> = T extends T ? Omit<T, K> : never;
584
+ /** Same as {@link TDbActionsEntry} but without the `level` field — used by the level-pinned shortcuts. */
585
+ type TDbActionsEntryUnpinned<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = DistributiveOmit<TDbActionsEntry<TRow, R>, "level">;
586
+ type DbActionsDictBase = Record<string, unknown>;
587
+ type EntryRequiredFields<E, TRow> = E extends {
588
+ requiredFields: infer R;
589
+ } ? R extends readonly FlatKey<TRow>[] ? R : [] : [];
590
+ type ValidatedDict<TRow, D extends DbActionsDictBase> = { [K in keyof D]: TDbActionsEntry<TRow, EntryRequiredFields<D[K], TRow>> };
591
+ type ValidatedUnpinnedDict<TRow, D extends DbActionsDictBase> = { [K in keyof D]: TDbActionsEntryUnpinned<TRow, EntryRequiredFields<D[K], TRow>> };
592
+ //#endregion
593
+ //#region src/actions/discover.d.ts
594
+ /**
595
+ * Pairs the wire-shaped `info` with the original decorator opts / dict entry,
596
+ * so the augmenter can invoke the live `disabled` reference (deliberately
597
+ * absent from the wire `info`).
598
+ */
599
+ /** A discovered action: its `/meta.actions[]` entry plus the server-internal opts it was declared with. */
600
+ interface TDbActionEnvelope {
601
+ info: TDbActionInfo$1;
602
+ raw: DbActionOpts | TDbActionsEntry;
603
+ }
604
+ /** Lookup helper for `AsReadableController.metaForm()`. */
605
+ declare function getControllerFormType(ctor: Function, name: string): TAtscriptAnnotatedType | undefined;
606
+ /** Discover actions on a controller, memoized per ctor. `info`-only callers map `e => e.info`. */
607
+ declare function discoverActions(controllerCtor: Function, app: Moost, logger: TConsoleBase): TDbActionEnvelope[];
608
+ /**
609
+ * The `'row'` / `'rows'`-level subset of {@link discoverActions} (since
610
+ * 0.1.145 public) — the actions `$actions` and `GET /meta/actions/:id`
611
+ * consider, in `/meta.actions` order. Memoized per controller class.
612
+ */
613
+ declare function discoverRowLevelActions(controllerCtor: Function, app: Moost, logger: TConsoleBase): TDbActionEnvelope[];
614
+ //#endregion
615
+ //#region src/actions/delegation.d.ts
616
+ /** Internal: a source controller's batch `$actions` verdicts for delegated ids. */
617
+ declare const ACTION_VERDICTS: unique symbol;
618
+ /** Internal: a source controller's `GET /meta/actions/:id` answer for one id. */
619
+ declare const AVAILABLE_ACTIONS: unique symbol;
620
+ /** Internal: the subset of delegated action names a source controller lists for the caller. */
621
+ declare const ALLOWED_ACTIONS: unique symbol;
622
+ /** `true` when the controller class declares `@DbActionsFrom` (inherited included). */
623
+ declare function hasActionDelegations(ctor: Function): boolean;
624
+ //#endregion
625
+ //#region src/actions/scope-context.d.ts
626
+ /**
627
+ * Which surface asks {@link TDbActionScopeContext} — see
628
+ * `AsDbReadableController.actionRowScope`:
629
+ *
630
+ * - `"execute"` — the action gate, about to run the action on `ids`;
631
+ * - `"rows"` — `$actions` on a read (also a view's delegated verdicts);
632
+ * - `"available"` — `GET /meta/actions/:id` (one row).
633
+ *
634
+ * @since 0.1.147
635
+ */
636
+ type TDbActionScopePurpose = "execute" | "rows" | "available";
637
+ /**
638
+ * The candidate rows `AsDbReadableController.actionRowScope` is asked about
639
+ * (since 0.1.147). Every candidate is already inside the controller's row
640
+ * overlay; the hook's result restricts them further.
641
+ *
642
+ * @since 0.1.147
643
+ */
644
+ interface TDbActionScopeContext {
645
+ /** Which surface asks — the action gate, `$actions` on a read, or `GET /meta/actions`. */
646
+ readonly purpose: TDbActionScopePurpose;
647
+ /**
648
+ * The candidates' identities (`preferredId`-shaped), deduped and never
649
+ * empty. ONE array object per evaluation, shared by every action of it —
650
+ * memoize on it (`WeakMap`) when several actions derive the same filter.
651
+ */
652
+ readonly ids: readonly Record<string, unknown>[];
653
+ /**
654
+ * The candidates' `fields` (plus the identity fields), read straight from
655
+ * the bound readable without any overlay (the candidates already passed
656
+ * it). Memoized per evaluation and field set. Nothing of it reaches the
657
+ * response.
658
+ */
659
+ loadRows(fields: readonly string[]): Promise<readonly Record<string, unknown>[]>;
660
+ }
661
+ //#endregion
324
662
  //#region src/actions/row-scope.d.ts
325
663
  /**
326
664
  * The key of `AsDbReadableController`'s internal action-overlay method —
327
- * `rowOverlay()` AND (since 0.1.145) the action's `actionRowScope`. A
328
- * registered symbol: not an overridable seam, and still found when
329
- * moost-db loads in two module realms (moost-vite SSR).
665
+ * its `rowOverlay()` (since 0.1.147 without the action's `actionRowScope`,
666
+ * which needs the candidate rows — see {@link ACTION_SCOPE}). A registered
667
+ * symbol: not an overridable seam, and still found when moost-db loads in
668
+ * two module realms (moost-vite SSR).
330
669
  */
331
670
  declare const ACTION_OVERLAY: unique symbol;
671
+ /** The controller's internal `actionRowScope` call for candidate rows (since 0.1.147). */
672
+ declare const ACTION_SCOPE: unique symbol;
673
+ /** `true` when the controller overrides `actionRowScope` (since 0.1.147). */
674
+ declare const ACTION_SCOPED: unique symbol;
332
675
  //#endregion
333
676
  //#region src/meta/field-capabilities.d.ts
334
677
  /**
@@ -459,8 +802,14 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
459
802
  *
460
803
  * `predicate` is a filter entry's class (`collectQueryPaths` records it per
461
804
  * occurrence); it only matters for `op === "filter"` on a listed leaf.
805
+ *
806
+ * `prefix` (since 0.1.147) is this index's readable's dotted path from the
807
+ * controller when it judges a relational predicate's operand (`"ticket."`):
808
+ * `exists` still receives the LOCAL path, the verdict's `path` and message
809
+ * name the prefixed one. A filter on a navigation path names the predicate
810
+ * alternative (`ticket=$some(status=…)`).
462
811
  */
463
- check(path: string, op: TQueryPathOp$1, exists: (path: string) => boolean, predicate?: TFilterPredicate): TCapabilityVerdict | undefined;
812
+ check(local: string, op: TQueryPathOp$1, exists: (path: string) => boolean, predicate?: TFilterPredicate, prefix?: string): TCapabilityVerdict | undefined;
464
813
  }
465
814
  //#endregion
466
815
  //#region src/as-db-readable.controller.d.ts
@@ -515,13 +864,15 @@ interface TDbFieldVisibility {
515
864
  /**
516
865
  * `hasField(path)` and — when {@link scoped} — a `@db.column.derived`
517
866
  * field of the bound readable only while its source path is visible too
518
- * (a derived copy must not outlive a hidden source).
867
+ * (a derived copy must not outlive a hidden source), a computed view
868
+ * column (`@db.compute`, since 0.1.147) only while every operand and every
869
+ * intermediate computed field it reads through is.
519
870
  */
520
871
  readonly isVisible: (path: string) => boolean;
521
872
  /**
522
873
  * The paths sealed out of `readable`'s read projection for this request:
523
874
  * its `@db.writeOnly` fields plus, when {@link scoped}, its derived fields
524
- * whose source `hasField` hides. `prefix` is `readable`'s path from the
875
+ * whose source `hasField` hides and its computed fields with a hidden operand. `prefix` is `readable`'s path from the
525
876
  * controller: `""` for the bound readable, `"rel."` for a `$with` target.
526
877
  */
527
878
  readonly sealedFor: (readable: AtscriptDbReadable<any>, prefix?: string) => ReadonlySet<string>;
@@ -559,6 +910,14 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
559
910
  */
560
911
  protected get capabilities(): FieldCapabilityIndex;
561
912
  private _capabilities?;
913
+ /** The client relational-predicate gate (since 0.1.147), built on first use — see {@link _relationGate}. */
914
+ private _relGate?;
915
+ /**
916
+ * The client's `$with` tree per request, keyed by its controls object —
917
+ * recorded in {@link validateParsed} before {@link validateControls} (see
918
+ * `snapshotClientWith`).
919
+ */
920
+ private readonly _clientWith;
562
921
  /** `/meta` is a projection of {@link capabilities}: a rebuilt index rebuilds the cached envelope. */
563
922
  protected metaCacheKey(): unknown;
564
923
  /**
@@ -571,7 +930,11 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
571
930
  protected readonly fieldVisibility: TDbFieldVisibility;
572
931
  /** A subclass overrides {@link hasField}: visibility is request-scoped (derived rule, index gate, id options). */
573
932
  private readonly _hasFieldOverridden;
574
- /** `@db.column.derived` path → its source's logical path, per readable (bound + `$with` targets). */
933
+ /**
934
+ * `@db.column.derived` path → its source's logical path, `@db.compute` path
935
+ * → its operands' paths plus the computed fields it reads through, per
936
+ * readable (bound + `$with` targets).
937
+ */
575
938
  private readonly _derivedSources;
576
939
  /** The bound readable's entry of {@link _derivedSources}. */
577
940
  private readonly _derivedSource;
@@ -602,6 +965,10 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
602
965
  private readonly _hasAllowedActions;
603
966
  /** `true` when a subclass overrides {@link actionRowScope} (the gate, `$actions` and `/meta/actions` apply it). */
604
967
  private readonly _hasActionRowScope;
968
+ /** `true` when the class declares `@DbActionsFrom` (since 0.1.147). */
969
+ private readonly _hasDelegations;
970
+ /** `transformProjection` is overridden (a delegation's id paths are checked against it). */
971
+ private readonly _hasProjectionHook;
605
972
  /** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
606
973
  private readonly _quantityRefByPath;
607
974
  /** `@db.column.searchable` paths — the `$search` fallback when the adapter has no native search. */
@@ -624,9 +991,11 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
624
991
  get idSource(): IdValidationSource;
625
992
  private _collectInvertibleFields;
626
993
  private _collectQuantityRefs;
627
- /** `readable`'s `@db.column.derived` path → source path map, collected once per readable. */
994
+ /**
995
+ * `readable`'s `@db.column.derived` path → source path and `@db.compute`
996
+ * path → operand paths map, collected once per readable.
997
+ */
628
998
  private _derivedSourcesOf;
629
- private _collectAnnotated;
630
999
  /**
631
1000
  * THE field-visibility hook: every gated path consults it before any
632
1001
  * capability check (since 0.1.133) — filter keys (inside `$and` / `$or` /
@@ -652,7 +1021,9 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
652
1021
  * visible fields (or ignores the term when there are none). A
653
1022
  * `@db.column.derived` field is visible only while its source path is,
654
1023
  * and one whose source is hidden is sealed out of every read projection
655
- * for the request, like a `@db.writeOnly` field.
1024
+ * for the request, like a `@db.writeOnly` field. The same holds for a
1025
+ * computed view column (`@db.compute`) and each of its operands, including
1026
+ * the intermediate computed fields it reads through.
656
1027
  */
657
1028
  protected hasField(path: string): boolean;
658
1029
  /**
@@ -723,8 +1094,6 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
723
1094
  private _writeOnlyOf;
724
1095
  /** {@link TDbFieldVisibility.sealedFor}. */
725
1096
  private _sealedFor;
726
- /** The readable a `$with` entry name (`rel` or dotted `rel.sub`) loads from, if resolvable. */
727
- private _relTarget;
728
1097
  /**
729
1098
  * Walks a `$with` tree pre-order: `visit(rel, target, path, controls)` for
730
1099
  * every entry whose target readable resolves (`path` = the entry's dotted
@@ -782,6 +1151,15 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
782
1151
  * nonexistent index (the core's wording).
783
1152
  */
784
1153
  private _checkIndexGate;
1154
+ /**
1155
+ * `/meta`'s search surface as THIS request may use it (only when
1156
+ * {@link hasField} is overridden — the rule of the index gate): indexes
1157
+ * reading a hidden field are left out of `searchIndexes`; `searchable` /
1158
+ * `vectorSearchable` / `geoSearchable` turn off when the index a request
1159
+ * naming none would use reads one (`searchable` stays on for the
1160
+ * `@db.column.searchable` fallback when any of its fields is visible).
1161
+ */
1162
+ private _applyIndexVisibility;
785
1163
  /**
786
1164
  * Compute an embedding vector from a search term.
787
1165
  * Override in subclass to integrate with your embedding provider (OpenAI, etc.).
@@ -800,6 +1178,42 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
800
1178
  * `findById`). Override to scope `/one` differently.
801
1179
  */
802
1180
  protected transformOne(filter: FilterExpr): FilterExpr | Promise<FilterExpr>;
1181
+ /**
1182
+ * Rewrites the sub-filter of a CLIENT relational predicate before it runs
1183
+ * — the row overlay of the related table (since 0.1.147). `path` is the
1184
+ * dotted navigation chain from this controller's table: `"ticket"` for
1185
+ * `ticket=$some(…)`, `"ticket.team"` for a predicate nested in its
1186
+ * operand, `"tickets.issues"` for `$with=tickets(issues=$some(…))`.
1187
+ * Default identity.
1188
+ *
1189
+ * Applied to client predicates only (the URL filter and `$with`
1190
+ * sub-filters, on `/query` incl. `$groupBy` and `$count`, `/pages`, `/geo`
1191
+ * and `/one`, and a query target's filter), after the request gate and before {@link transformFilter};
1192
+ * a nested predicate's operand is rewritten before the operand holding it,
1193
+ * and the hook's output is not walked again. Server-side filters
1194
+ * ({@link transformFilter}, {@link transformOne}, {@link actionRowScope})
1195
+ * never pass through it. Return the operand conjoined with the related
1196
+ * rows the caller may see — `$some` then only matches, and `$none` only
1197
+ * excludes, on VISIBLE related rows, exactly as `$with` shows them:
1198
+ *
1199
+ * ```ts
1200
+ * protected transformRelationFilter(path: string, filter: FilterExpr) {
1201
+ * return path === "ticket" ? { $and: [{ teamId: { $in: currentTeams() } }, filter] } : filter
1202
+ * }
1203
+ * ```
1204
+ *
1205
+ * @since 0.1.147
1206
+ */
1207
+ protected transformRelationFilter(_path: string, filter: FilterExpr): FilterExpr | Promise<FilterExpr>;
1208
+ /** The client relational-predicate gate (since 0.1.147) — one per controller. */
1209
+ private _relationGate;
1210
+ /**
1211
+ * The client filter with every relational predicate operand rewritten by
1212
+ * {@link transformRelationFilter} — and `parsed.controls.$with` replaced by
1213
+ * its rewritten tree (since 0.1.147). Costs nothing unless the hook is
1214
+ * overridden.
1215
+ */
1216
+ private _relationOverlay;
803
1217
  /**
804
1218
  * The subset of the row-level action `names` the caller may run (since
805
1219
  * 0.1.145) — what `$actions` and `GET /meta/actions/:id` list from. The
@@ -816,28 +1230,91 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
816
1230
  /**
817
1231
  * The rows the row-level action `actionName` may run on (since 0.1.145),
818
1232
  * as an extra row filter; `undefined` or `{}` = no restriction (the
819
- * default). Enforced by the action gate — ANDed with the {@link rowOverlay}
820
- * the action's ids / rows are loaded under, so an id outside it gets the
821
- * same 404 "Row not found for action identifier" as a missing one — and
822
- * reflected in `$actions` and `GET /meta/actions`, which list the action
823
- * only on rows inside it.
1233
+ * default). Enforced by the action gate — the action's ids / rows are
1234
+ * loaded under {@link rowOverlay}, then checked against this filter, so an
1235
+ * id outside it gets the same 404 "Row not found for action identifier"
1236
+ * as a missing one — and reflected in `$actions` and
1237
+ * `GET /meta/actions`, which list the action only on rows inside it.
1238
+ *
1239
+ * Since 0.1.147 the hook receives the candidate rows (`ctx`), so a scope
1240
+ * can depend on them — e.g. derive `{ ticketKey: { $in: … } }` from a
1241
+ * related table read for exactly these rows:
1242
+ *
1243
+ * | `ctx.purpose` | asked by | `ctx.ids` |
1244
+ * | --- | --- | --- |
1245
+ * | `"execute"` | the action gate | the loaded ids / rows (≤ `maxIds`; one batch of a query target) |
1246
+ * | `"rows"` | `$actions` on a read (and a view's delegated verdicts) | the read's rows |
1247
+ * | `"available"` | `GET /meta/actions/:id` | the one row |
1248
+ *
1249
+ * - Called only with at least one candidate, at most once per action per
1250
+ * evaluation; `ctx.ids` is the same array object for every action of
1251
+ * one evaluation (memoize on it with a `WeakMap`).
1252
+ * - Candidates are already inside the row overlay — ids that do not exist
1253
+ * or fall outside it never reach the hook.
1254
+ * - The result only restricts (`ids ∧ rowOverlay ∧ scope`); a throw fails
1255
+ * the request — never a silent "allow".
1256
+ * - The filter runs straight against the bound readable: it may use
1257
+ * fields {@link hasField} hides, and nothing of it (nor of
1258
+ * `ctx.loadRows`) reaches the response. Equal filters — the same object,
1259
+ * or structurally equal ones — share one id-only query.
1260
+ * - Runs after {@link prepareRequest} and, on the action route, after the
1261
+ * request body is read (it needs the ids).
824
1262
  *
825
- * Runs after {@link prepareRequest}, once per action per request. `$actions`
826
- * checks the page's rows with one id-only query per distinct filter
827
- * OBJECT (return the same object for several actions to share one query),
828
- * straight against the bound readable: the filter may use fields
829
- * {@link hasField} hides, and nothing of it reaches the response. Not
830
- * overriding it costs nothing.
1263
+ * Not overriding it costs nothing; a one-parameter override keeps working.
1264
+ * moost-db always passes `ctx` — it is optional in the signature only so
1265
+ * `super.actionRowScope(name)` calls in existing overrides keep compiling.
1266
+ *
1267
+ * ```ts
1268
+ * protected async actionRowScope(action: string, ctx: TDbActionScopeContext) {
1269
+ * if (action !== "resolve") return undefined
1270
+ * const issues = await ctx.loadRows(["ticketKey"])
1271
+ * const tickets = await ticketTable.findMany({
1272
+ * filter: { key: { $in: issues.map((i) => i.ticketKey) }, teamId: currentTeamId() },
1273
+ * controls: { $select: ["key"] },
1274
+ * })
1275
+ * return { ticketKey: { $in: tickets.map((t) => t.key) } }
1276
+ * }
1277
+ * ```
1278
+ *
1279
+ * The scope may use relational predicates (since 0.1.147) — a server-side
1280
+ * filter, so no `@db.rel.filterable` opt-in and no
1281
+ * {@link transformRelationFilter} apply. On an Issue controller:
831
1282
  *
832
1283
  * ```ts
833
1284
  * protected actionRowScope(action: string) {
834
- * return action === "approve" ? { ownerId: currentUserId() } : undefined
1285
+ * return action === "resolve"
1286
+ * ? { ticket: { $some: { teamId: { $in: currentTeams() }, status: "open" } } }
1287
+ * : undefined
835
1288
  * }
836
1289
  * ```
837
1290
  *
838
1291
  * @since 0.1.145
839
1292
  */
840
- protected actionRowScope(_actionName: string): FilterExpr | undefined | Promise<FilterExpr | undefined>;
1293
+ protected actionRowScope(_actionName: string, _ctx?: TDbActionScopeContext): FilterExpr | undefined | Promise<FilterExpr | undefined>;
1294
+ /**
1295
+ * The read scope a query target (an action request `{ query }`, since
1296
+ * 0.1.147) resolves under, on top of the action's row overlay — so "every
1297
+ * row matching the query" never reaches rows the caller cannot list. A
1298
+ * view resolving a delegated action's query target applies it too.
1299
+ * Default: `transformFilter({})` (the read overlay of `/query`). Throw an
1300
+ * `HttpError` to refuse query targets for the caller (e.g. no read grant).
1301
+ *
1302
+ * Runs as a READ of this controller: it is called in a child of the
1303
+ * action event — of the delegating event for a view resolving a delegated
1304
+ * target — (moost `withControllerContext`, the controller's `query`
1305
+ * handler) after `prepareRequest({ endpoint: "query", controls, filter })`
1306
+ * with the target's own filter and controls — so a permission layer's
1307
+ * per-request state is the READ request's (its read grant, the policy of
1308
+ * the relations its predicates touch), never the action's, and nothing of
1309
+ * it leaks back. The target's query (`q` filter and controls, `exclude`)
1310
+ * is validated in that read context ({@link validateControls},
1311
+ * {@link checkCapabilities}, {@link hasField}), where the client
1312
+ * predicates' {@link transformRelationFilter} also runs: a query target
1313
+ * never filters on, nor counts by, a field the caller can't read.
1314
+ *
1315
+ * @since 0.1.147
1316
+ */
1317
+ protected queryTargetScope(_action: string): FilterExpr | undefined | Promise<FilterExpr | undefined>;
841
1318
  /**
842
1319
  * Transform projection before querying.
843
1320
  * May return a Promise for async lookups.
@@ -866,6 +1343,13 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
866
1343
  private _resolveProjectionForAugmenter;
867
1344
  /** Row/rows envelopes narrowed to {@link allowedActions}; no hook call when neither it nor `applyMetaOverlay` is overridden. */
868
1345
  private _resolveAugmentEnvelopes;
1346
+ /**
1347
+ * Once per class: `actionRowScope` is overridden (a permission layer always
1348
+ * does) while the controller's OWN row-level actions can't be scoped — the
1349
+ * readable has no identity, so `$actions` withholds every action with a
1350
+ * non-empty row scope. Silent for controllers without own row actions.
1351
+ */
1352
+ private _warnScopeWithoutIdentity;
869
1353
  /**
870
1354
  * Returns a widened `$select` only when at least one `requiredFields` entry
871
1355
  * is missing; `null` means "no widening needed". A field the request may
@@ -875,17 +1359,27 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
875
1359
  private _widenSelectForActions;
876
1360
  private _prepareAugmentation;
877
1361
  /**
878
- * {@link actionRowScope} of every offered action (in parallel, alongside
879
- * `overlay`), grouped by filter object; each group's filter is composed
880
- * with the row overlay exactly as the gate composes it.
1362
+ * {@link actionRowScope} of every action in `names` for one evaluation
1363
+ * (`ctx`, in parallel, alongside `overlay`), grouped by filter — object
1364
+ * identity first, then structural equality (`filterKey`), so equal filters
1365
+ * share one query; each group's filter is composed with the row overlay
1366
+ * exactly as the gate composes it.
881
1367
  */
882
1368
  private _resolveActionScopeGroups;
883
- /** Per scoped action, a per-row mask of rows outside its scope (one id-only read per group). */
884
- private _outOfScopeMasks;
885
1369
  /**
886
- * The row filter {@link actionRowScope} returns for `action` — `undefined`
887
- * when empty or when the hook is not overridden (no call). THE per-action
888
- * scope rule: the action gate, `$actions` and `/meta/actions` all read it here.
1370
+ * Per action of `names`, a per-row mask (parallel to `rows`) of rows
1371
+ * outside its {@link actionRowScope} — one id-only read per distinct
1372
+ * filter. The hook sees the rows' identities (`purpose`); a row lacking
1373
+ * its identity is in no scope. No candidate → no hook call, every row
1374
+ * masked for every action (fail closed). `undefined` when the hook is not
1375
+ * overridden or `rows` is empty.
1376
+ */
1377
+ private _scopeMasks;
1378
+ /**
1379
+ * The row filter {@link actionRowScope} returns for `action` and the
1380
+ * candidates of `ctx` — `undefined` when empty or when the hook is not
1381
+ * overridden (no call). THE per-action scope rule: the action gate,
1382
+ * `$actions` and `/meta/actions` all read it here.
889
1383
  */
890
1384
  private _actionScope;
891
1385
  /**
@@ -894,12 +1388,35 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
894
1388
  */
895
1389
  private _gateFields;
896
1390
  /**
897
- * @internal The action gate's overlay (reached by the `@DbAction`
898
- * interceptor through {@link ACTION_OVERLAY}): {@link rowOverlay} AND the
899
- * action's {@link actionRowScope} (since 0.1.145) — `undefined` when both
900
- * are empty.
1391
+ * @internal The action gate's row overlay (reached by the `@DbAction`
1392
+ * interceptor through {@link ACTION_OVERLAY}): {@link rowOverlay}. Since
1393
+ * 0.1.147 the action's {@link actionRowScope} is applied separately to the
1394
+ * loaded candidates ({@link ACTION_SCOPE}).
1395
+ */
1396
+ [ACTION_OVERLAY](): Promise<FilterExpr | undefined>;
1397
+ /** @internal {@link actionRowScope} for the gate's loaded candidates (since 0.1.147). */
1398
+ [ACTION_SCOPE](action: string, ctx: TDbActionScopeContext): Promise<FilterExpr | undefined>;
1399
+ /** @internal `true` when {@link actionRowScope} is overridden (since 0.1.147). */
1400
+ get [ACTION_SCOPED](): boolean;
1401
+ /**
1402
+ * @internal Resolves a query target (phase 1, since 0.1.147): the `query`
1403
+ * body validated (shape, `$search` / `$index` only, the read's capability
1404
+ * and index gate), then ONE read of `select` ordered by identity —
1405
+ * `filter (+ $search) ∧ overlay ∧ queryTargetScope ∧ ¬exclude`, at most
1406
+ * `cap + 1` rows. More than the cap → 400 `TARGET_TOO_LARGE`; a count
1407
+ * other than `expectCount` → 409 `TARGET_CHANGED`. `load` re-reads rows
1408
+ * of the snapshot that still match the same target (phase 2) — except for
1409
+ * the first batch, which directly follows the snapshot.
901
1410
  */
902
- [ACTION_OVERLAY](action: string | undefined): Promise<FilterExpr | undefined>;
1411
+ [RESOLVE_TARGET](req: TTargetRequest): Promise<TResolvedTarget>;
1412
+ /**
1413
+ * Runs `fn` as a READ of this controller (since 0.1.147): in a child of
1414
+ * the current event whose controller context is this controller's `query`
1415
+ * handler, after `prepareRequest({ endpoint: "query", controls, filter })` — the
1416
+ * request-scoped state a permission layer builds there (read grant, field
1417
+ * visibility) is the read's and stays in the child.
1418
+ */
1419
+ private _asRead;
903
1420
  /**
904
1421
  * `@db.column.searchable` paths, minus anything the adapter can't filter
905
1422
  * (JSON storage, encrypted), `@db.writeOnly` fields and navigation
@@ -980,8 +1497,65 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
980
1497
  * promise or microtask, when there is no hook or it is synchronous.
981
1498
  */
982
1499
  private _finishRows;
983
- /** `$actions` augmentation (when `prep`), then {@link decorateRows} when implemented. */
1500
+ /**
1501
+ * `$actions` augmentation (when `prep`) — own actions, then the delegated
1502
+ * ones (`delegated`: per delegation, per row) — then {@link decorateRows}
1503
+ * when implemented.
1504
+ */
984
1505
  private _augmentAndDecorate;
1506
+ /**
1507
+ * The app of the current event, through DI — never the one this
1508
+ * (singleton) instance was constructed in, which may be gone (a re-booted
1509
+ * app, a hot reload).
1510
+ */
1511
+ private _currentApp;
1512
+ /** The class's `@DbActionsFrom` delegations, validated on first use (per app). */
1513
+ private _delegations;
1514
+ /**
1515
+ * The delegations this request may use: every id path visible
1516
+ * ({@link hasField}) and kept by {@link transformProjection} — a
1517
+ * delegation whose ids the request can't read is dropped.
1518
+ */
1519
+ private _activeDelegations;
1520
+ /**
1521
+ * Runs `fn` on the delegation's source controller (this event's instance)
1522
+ * evaluated as itself ({@link runAsController}); a 401 / 403 from its
1523
+ * `prepareRequest` (the caller holds no grant there) yields `refused`.
1524
+ */
1525
+ private _onSource;
1526
+ /** Per row, the source's verdict for the row it maps to (`undefined`: no source id). */
1527
+ private _delegatedRowVerdicts;
1528
+ /** The `/meta.actions` entries of the delegations the caller may run (per its source). */
1529
+ private _delegatedInfos;
1530
+ /**
1531
+ * The cached `/meta` envelope through {@link applyMetaOverlay}, plus —
1532
+ * since 0.1.147 — the `@DbActionsFrom` actions the caller may run as their
1533
+ * source decides (`allowedActions` of the source, evaluated as itself) and,
1534
+ * under an overridden {@link hasField}, the search surface narrowed to the
1535
+ * indexes the request may use (the index gate's rule). Delegated entries
1536
+ * never pass this controller's own `applyMetaOverlay`.
1537
+ */
1538
+ protected resolveMeta(): TMetaResponse | Promise<TMetaResponse>;
1539
+ /**
1540
+ * The delegated part of `GET /meta/actions…` for the source ids `sourceIdOf`
1541
+ * derives from the request (`undefined`: not derivable by key renaming —
1542
+ * the delegation is left out).
1543
+ */
1544
+ private _delegatedAvailable;
1545
+ /**
1546
+ * @internal Source side of a delegation: the `$actions` verdicts of `names`
1547
+ * for `ids` (aligned; `undefined` = not found under the row overlay), as
1548
+ * this controller's own `$actions` / `GET /meta/actions` compute them —
1549
+ * its `prepareRequest("availableActions")`, `allowedActions`, row overlay,
1550
+ * `actionRowScope` (`purpose: "rows"`) and `disabled`.
1551
+ */
1552
+ [ACTION_VERDICTS](ids: Record<string, unknown>[], names: readonly string[]): Promise<Array<TDbAvailableActions$1 | undefined>>;
1553
+ /** @internal Source side of a delegation: `GET /meta/actions` for one id, `names` only. */
1554
+ [AVAILABLE_ACTIONS](id: Record<string, unknown>, names: readonly string[]): Promise<TDbAvailableActions$1>;
1555
+ /** @internal Source side of a delegation: the `names` the caller may run (`allowedActions`). */
1556
+ [ALLOWED_ACTIONS](names: readonly string[]): Promise<readonly string[]>;
1557
+ /** {@link _resolveAugmentEnvelopes} restricted to `names`. */
1558
+ private _envelopesNamed;
985
1559
  /**
986
1560
  * Shared `query` / `pages` / `geo` pipeline: prepare actions augmentation + read
987
1561
  * strategy in parallel, pre-widen $select for `requiredFields`, run
@@ -1087,18 +1661,75 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
1087
1661
  * `{ actions: [] }`. {@link prepareRequest} runs first with
1088
1662
  * `endpoint: "availableActions"`.
1089
1663
  */
1090
- availableActionsById(id: string): Promise<TDbAvailableActions>;
1664
+ availableActionsById(id: string): Promise<TDbAvailableActions$1>;
1091
1665
  /**
1092
1666
  * **GET /meta/actions?field1=val1&…** — {@link availableActionsById} by
1093
1667
  * composite key (composite primary key or compound unique index), the
1094
1668
  * `/one?…` rules.
1095
1669
  */
1096
- availableActions(query: Record<string, string>): Promise<TDbAvailableActions | HttpError>;
1670
+ availableActions(query: Record<string, string>): Promise<TDbAvailableActions$1 | HttpError>;
1671
+ /**
1672
+ * **POST /delegated-actions/:name** — a query target for a `@DbActionsFrom`
1673
+ * action whose source action declares `queryTarget` (since 0.1.147); the
1674
+ * action's `/meta` entry points here (`queryTarget.url`). Body
1675
+ * `{ query: { q, exclude?, expectCount?, maxRows?, dryRun? }, input? }`.
1676
+ *
1677
+ * The source must list the action for the caller (its `allowedActions`,
1678
+ * as `$actions` does) — else 403, dry runs included. This controller then
1679
+ * resolves the rows matching `q` under its own read scope (`transformFilter`
1680
+ * ∧ {@link queryTargetScope} ∧ ¬`exclude` — `exclude` entries use this
1681
+ * controller's identifications or the delegation's id paths, and the
1682
+ * source rows they map to are left out even when other view rows map to
1683
+ * them too), maps them to source ids (a row without one is skipped as
1684
+ * `"unmapped"`), then runs the SOURCE's action route on them in batches
1685
+ * inside this request (`MoostHttp.invoke`) — its guards, `prepareRequest`,
1686
+ * row overlay, `actionRowScope` and `disabled` re-check every batch, and
1687
+ * every body reader of the source sees the batch's `{ ids, input }`. Before
1688
+ * each batch the view rows are re-checked against the target: a source id
1689
+ * none of whose view rows still matches is skipped as `"stale"`; ids the
1690
+ * source's gate refuses are skipped with their reasons. A batch failing
1691
+ * once a source handler started (in it or an earlier batch) stops the run:
1692
+ * the answer is the partial summary with `aborted` and every id not run
1693
+ * listed in `failed`. A failure before any source handler started (a 403,
1694
+ * the source's `@InputForm` 400, …) is the request's error — nothing ran,
1695
+ * and every batch carries the same `input`. The
1696
+ * `message` (string) each batch's handler returned is passed on:
1697
+ * `messages` per batch, `message` the distinct ones joined by newlines. A
1698
+ * dry run answers `{ matched }`; otherwise the answer is the run's
1699
+ * {@link TDbActionTargetSummary}. An action of the delegation that takes no
1700
+ * query target answers 400 `TARGET_INVALID`. `prepareRequest` runs first
1701
+ * with `endpoint: "delegatedAction"`. Registered only on controllers
1702
+ * declaring `@DbActionsFrom`.
1703
+ */
1704
+ runDelegatedOnQuery(): Promise<TDbActionTargetSummary$1 | {
1705
+ matched: number;
1706
+ }>;
1707
+ /**
1708
+ * The id keys (`idMap` fields) of the source rows `exclude` leaves out of a
1709
+ * delegated target — an entry by the id paths names its source row
1710
+ * directly; one by a view identification names the source row of that view
1711
+ * row (read without overlay: excluding can only narrow the run).
1712
+ */
1713
+ private _excludedSourceKeys;
1714
+ /**
1715
+ * The source ids of `batch` that some view row still maps to under the
1716
+ * target's query (phase-2 re-check); the others are recorded in
1717
+ * `summary.skipped` as `"stale"`.
1718
+ */
1719
+ private _stillTargeted;
1097
1720
  /**
1098
1721
  * The row resolves ONCE, like `/one/:id` under {@link rowOverlay}; scoped
1099
- * actions are then checked on it exactly like `$actions` rows.
1722
+ * actions are then checked on it (`purpose: "available"`) exactly like
1723
+ * `$actions` rows.
1100
1724
  */
1101
1725
  private _availableActions;
1726
+ /**
1727
+ * Per row (`undefined` = not found → no verdict): the actions of
1728
+ * `envelopes` it lists — minus those `masks` put it outside of, and those
1729
+ * whose `disabled` rule refuses it on the fields its gate loads
1730
+ * (`fieldsOf`, parallel to `envelopes`) — with the refusal reasons.
1731
+ */
1732
+ private _verdicts;
1102
1733
  /**
1103
1734
  * **GET /meta** — returns table/view metadata for UI.
1104
1735
  *
@@ -1530,166 +2161,6 @@ declare class AsJsonValueHelpController<T extends TAtscriptAnnotatedType = TAtsc
1530
2161
  private normalizeSort;
1531
2162
  }
1532
2163
  //#endregion
1533
- //#region src/actions/types.d.ts
1534
- /**
1535
- * One entry of a `disabled` predicate's result. Truthy = the action is
1536
- * disabled for that row; falsy (`false`, `""`) = enabled. A non-empty string
1537
- * disables the action AND carries a human-readable reason — surfaced in the
1538
- * 409 `ActionDisabledError` message/body and in the row's `$disabledReasons`.
1539
- *
1540
- * @since 0.1.141 (`string`)
1541
- */
1542
- type TDbActionDisabledVerdict = boolean | string;
1543
- /** `'rows'`-level batch policy — controls whether failing rows reject or are filtered out. */
1544
- type TOnDisabledRows = "reject" | "skip";
1545
- /**
1546
- * Dot-notation field paths of `TRow`'s flat type. Drives both the runtime
1547
- * projection widening and the type narrowing of the `disabled` predicate's
1548
- * row argument. Relations are absent from `FlatOf<T>` — listing a relation
1549
- * field is therefore a compile error.
1550
- *
1551
- * Permissive fallback when `TRow = unknown` (no explicit decorator generic):
1552
- * any string is allowed and the `disabled` predicate's row arg is `any[]`,
1553
- * preserving the prior loose typing for un-annotated call sites.
1554
- */
1555
- type FlatKey<TRow> = unknown extends TRow ? string : keyof FlatOf<TRow> & string;
1556
- /** Row-shape narrowing for the `disabled` predicate. Falls back to `any` when `TRow = unknown`. */
1557
- type DisabledRowsArg<TRow, R extends readonly FlatKey<TRow>[]> = unknown extends TRow ? any[] : Pick<FlatOf<TRow>, R[number] & keyof FlatOf<TRow>>[];
1558
- interface NoGate {
1559
- requiredFields?: never;
1560
- disabled?: never;
1561
- onDisabledRows?: never;
1562
- }
1563
- interface WithGate<TRow, R extends readonly FlatKey<TRow>[]> {
1564
- /**
1565
- * Dot-notation field paths the predicate references. SERVER-INTERNAL —
1566
- * never emitted on the `/meta` wire. Consumed verbatim to widen the DB
1567
- * projection so `disabled` always sees the fields it declared.
1568
- */
1569
- requiredFields: R;
1570
- /**
1571
- * Sync batch gate predicate — returns a parallel array aligned with the
1572
- * input. Per entry: truthy = disabled for the corresponding row; a
1573
- * non-empty string also gives the reason (see
1574
- * {@link TDbActionDisabledVerdict}). The `rows`
1575
- * argument is type-narrowed to `Pick<FlatOf<TRow>, R[number]>[]`; reading
1576
- * a field not listed in `requiredFields` is a compile error.
1577
- *
1578
- * Promise return is NOT permitted — the predicate is consumed in the
1579
- * same tick by the gate and the augmenter.
1580
- */
1581
- disabled?: (rows: DisabledRowsArg<TRow, R>) => TDbActionDisabledVerdict[];
1582
- /**
1583
- * `'rows'`-level batch policy. Default `'reject'`.
1584
- *
1585
- * - `'reject'`: evaluate every row before throwing; if any row fails, the
1586
- * error body lists ALL failing IDs; handler not invoked.
1587
- * - `'skip'`: filter cached rows + cached IDs to passing-only; zero
1588
- * survivors → reject. Handler runs against the survivors.
1589
- *
1590
- * Ignored for `'row'` and `'table'` level actions.
1591
- */
1592
- onDisabledRows?: TOnDisabledRows;
1593
- }
1594
- /**
1595
- * Loose gate shape used when `TRow = unknown` (no explicit decorator generic).
1596
- * Preserves the prior un-typed call-site flexibility; the runtime still drops
1597
- * actions where `disabled` is set without `requiredFields`.
1598
- */
1599
- interface LooseGate {
1600
- requiredFields?: string[];
1601
- disabled?: (rows: any[]) => TDbActionDisabledVerdict[];
1602
- onDisabledRows?: TOnDisabledRows;
1603
- }
1604
- type GateOpts<TRow, R extends readonly FlatKey<TRow>[]> = unknown extends TRow ? LooseGate : NoGate | WithGate<TRow, R>;
1605
- interface BaseActionOpts extends Partial<Omit<TDbActionInfo$1, "name" | "level" | "processor" | "value" | "disabled" | "formUrl">> {
1606
- /**
1607
- * Bound table reference. REQUIRED on non-`AsDbReadableController` classes
1608
- * when `disabled` is set OR a `@DbActionRow*` parameter is declared.
1609
- *
1610
- * Silently ignored on `AsDbReadableController` subclasses (which include
1611
- * `AsDbController`) — the bound table from the controller wins.
1612
- */
1613
- table?: AtscriptDbTable<any>;
1614
- /**
1615
- * `'rows'` level (`@DbActionIDs` / `@DbActionRows`): the most identifiers
1616
- * one request may carry. Above it the request is rejected with 400 before
1617
- * any row is loaded. Default `1000`. Server-internal — never on the wire.
1618
- *
1619
- * @since 0.1.143
1620
- */
1621
- maxIds?: number;
1622
- }
1623
- /**
1624
- * Options accepted by `@DbAction(name, opts?)`. Generic over `TRow` (the
1625
- * controller's bound atscript type) and `R` (the literal `requiredFields`
1626
- * tuple). Both are inferred at the call site via the decorator's `<TRow>`
1627
- * argument plus `const R` generic.
1628
- */
1629
- type DbActionOpts<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = BaseActionOpts & GateOpts<TRow, R>;
1630
- interface DbActionsEntryCommonBase {
1631
- label: string;
1632
- level: TDbActionLevel$1;
1633
- icon?: string;
1634
- intent?: TDbActionIntent$1;
1635
- description?: string;
1636
- order?: number;
1637
- default?: boolean;
1638
- /** Mirrors {@link TDbActionInfo.promptText} — singular/plural via tuple. */
1639
- promptText?: string | [string, string];
1640
- /** Mirrors {@link TDbActionInfo.shortcut} — single-character UI hint. */
1641
- shortcut?: string;
1642
- /**
1643
- * Input form the UI collects before invoking the action:
1644
- *
1645
- * - a compiled `.as` interface — registered on THIS controller and served
1646
- * by its own `GET /meta/form/:name`; the wire carries `inputForm: Type.name`.
1647
- * - `{ name, url }` — a form served elsewhere: `name` goes on the wire as
1648
- * `inputForm`, `url` (server-absolute path of the serialized schema, e.g.
1649
- * `"/api/shipping/meta/form/ShipForm"`) as {@link TDbActionInfo.formUrl}.
1650
- *
1651
- * Not allowed with `processor: 'navigate'`. Class-level entries only
1652
- * describe the action — validating `input` is the target handler's job
1653
- * (e.g. its own `@InputForm(Type)` param).
1654
- *
1655
- * @since 0.1.136
1656
- */
1657
- inputForm?: TAtscriptAnnotatedType | {
1658
- name: string;
1659
- url: string;
1660
- };
1661
- }
1662
- type DbActionsEntryWithGate<TRow, R extends readonly FlatKey<TRow>[]> = DbActionsEntryCommonBase & GateOpts<TRow, R>;
1663
- /**
1664
- * Class-level dict entry. `value` semantics by processor:
1665
- *
1666
- * - `'navigate'` — REQUIRED, non-empty. URL template (`$1` substituted client-side).
1667
- * - `'backend'` — REQUIRED, non-empty. Full HTTP POST path the UI client invokes.
1668
- * - `'custom'` — `value` is forbidden in the entry; the meta builder fills it
1669
- * with the dict key.
1670
- */
1671
- type TDbActionsEntry<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = (DbActionsEntryWithGate<TRow, R> & {
1672
- processor: "navigate";
1673
- value: string;
1674
- inputForm?: never;
1675
- }) | (DbActionsEntryWithGate<TRow, R> & {
1676
- processor: "custom";
1677
- value?: never;
1678
- }) | (DbActionsEntryWithGate<TRow, R> & {
1679
- processor: "backend";
1680
- value: string;
1681
- });
1682
- /** Distributes `Omit` across the discriminated union members. */
1683
- type DistributiveOmit<T, K extends keyof T> = T extends T ? Omit<T, K> : never;
1684
- /** Same as {@link TDbActionsEntry} but without the `level` field — used by the level-pinned shortcuts. */
1685
- type TDbActionsEntryUnpinned<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = DistributiveOmit<TDbActionsEntry<TRow, R>, "level">;
1686
- type DbActionsDictBase = Record<string, unknown>;
1687
- type EntryRequiredFields<E, TRow> = E extends {
1688
- requiredFields: infer R;
1689
- } ? R extends readonly FlatKey<TRow>[] ? R : [] : [];
1690
- type ValidatedDict<TRow, D extends DbActionsDictBase> = { [K in keyof D]: TDbActionsEntry<TRow, EntryRequiredFields<D[K], TRow>> };
1691
- type ValidatedUnpinnedDict<TRow, D extends DbActionsDictBase> = { [K in keyof D]: TDbActionsEntryUnpinned<TRow, EntryRequiredFields<D[K], TRow>> };
1692
- //#endregion
1693
2164
  //#region src/actions/keys.d.ts
1694
2165
  type TDbActionRowMarker = true;
1695
2166
  /** Stamped by `@InputForm(FormType)` — the compiled `.as` class + the wire name (`FormType.name`). */
@@ -1707,6 +2178,18 @@ interface TDbClassActionMeta {
1707
2178
  name: string;
1708
2179
  entry: TDbActionsEntry;
1709
2180
  }
2181
+ /**
2182
+ * Class-level entry written by `@DbActionsFrom(source, opts)` (since
2183
+ * 0.1.147): a controller whose row-level actions this controller delegates.
2184
+ */
2185
+ interface TDbActionsFromMeta {
2186
+ /** Lazy reference to the source controller class. */
2187
+ source: () => Function;
2188
+ /** Source identification field → path in this controller's rows. */
2189
+ idMap?: Record<string, string>;
2190
+ /** Subset of the source's row / rows-level action names (default: all). */
2191
+ actions?: readonly string[];
2192
+ }
1710
2193
  /** Param marker kind — informs level inference and ID-resolution shape. */
1711
2194
  type TDbActionParamKind = "id" | "ids";
1712
2195
  declare module "moost" {
@@ -1717,12 +2200,14 @@ declare module "moost" {
1717
2200
  atscript_db_action_row?: TDbActionRowMarker;
1718
2201
  atscript_db_action_rows?: TDbActionRowMarker;
1719
2202
  atscript_db_endpoint?: TDbRequestEndpoint;
2203
+ atscript_db_actions_from?: TDbActionsFromMeta[];
1720
2204
  }
1721
2205
  interface TMoostParamsMetadata {
1722
2206
  atscript_db_action_param?: TDbActionParamKind;
1723
2207
  atscript_db_action_row?: TDbActionRowMarker;
1724
2208
  atscript_db_action_rows?: TDbActionRowMarker;
1725
2209
  atscript_db_action_input_form?: TDbActionInputFormMeta;
2210
+ atscript_db_action_target?: true;
1726
2211
  atscript_type?: TAtscriptAnnotatedType;
1727
2212
  }
1728
2213
  }
@@ -1775,6 +2260,8 @@ interface AtscriptDbMeta {
1775
2260
  * {@link getDbEndpoint}.
1776
2261
  */
1777
2262
  atscript_db_endpoint?: TDbRequestEndpoint;
2263
+ /** Class-level — written by `@DbActionsFrom(...)` (since 0.1.147). Decorators accumulate. */
2264
+ atscript_db_actions_from?: TDbActionsFromMeta[];
1778
2265
  }
1779
2266
  /**
1780
2267
  * Param-level metadata written by `@atscript/moost-db`'s param
@@ -1788,6 +2275,8 @@ interface AtscriptDbParamsMeta {
1788
2275
  atscript_db_action_row?: true;
1789
2276
  /** Param-level marker — written by `@DbActionRows()`. */
1790
2277
  atscript_db_action_rows?: true;
2278
+ /** Param-level marker — written by `@DbActionTarget()` (since 0.1.147). */
2279
+ atscript_db_action_target?: true;
1791
2280
  /**
1792
2281
  * Param-level — written by `@InputForm(FormType)`. Carries the
1793
2282
  * compiled `.as` class plus its `.name` so `discoverActions` can both
@@ -2051,7 +2540,8 @@ declare const UseValidationErrorTransform: () => ClassDecorator & MethodDecorato
2051
2540
  /**
2052
2541
  * Mark a controller method as a database action surfaced via `/meta`. Writes
2053
2542
  * `atscript_db_action` metadata and, for every `'row'` / `'rows'` action,
2054
- * registers a Moost interceptor: the gate when `disabled` is set, else the
2543
+ * registers a Moost interceptor: the batch gate for a `@DbActionTarget()`
2544
+ * handler (since 0.1.147), the gate when `disabled` is set, else the
2055
2545
  * bound-table injector that also verifies the ids against the controller's
2056
2546
  * row overlay (since 0.1.143). Either first awaits the controller's
2057
2547
  * `prepareRequest({ endpoint: "action", action })` when it defines one —
@@ -2220,28 +2710,6 @@ declare function InputForm<T extends TAtscriptAnnotatedType & {
2220
2710
  readonly name: string;
2221
2711
  }>(formType?: T, validatorOpts?: Partial<TValidatorOptions>): ParameterDecorator;
2222
2712
  //#endregion
2223
- //#region src/actions/discover.d.ts
2224
- /**
2225
- * Pairs the wire-shaped `info` with the original decorator opts / dict entry,
2226
- * so the augmenter can invoke the live `disabled` reference (deliberately
2227
- * absent from the wire `info`).
2228
- */
2229
- /** A discovered action: its `/meta.actions[]` entry plus the server-internal opts it was declared with. */
2230
- interface TDbActionEnvelope {
2231
- info: TDbActionInfo$1;
2232
- raw: DbActionOpts | TDbActionsEntry;
2233
- }
2234
- /** Lookup helper for `AsReadableController.metaForm()`. */
2235
- declare function getControllerFormType(ctor: Function, name: string): TAtscriptAnnotatedType | undefined;
2236
- /** Discover actions on a controller, memoized per ctor. `info`-only callers map `e => e.info`. */
2237
- declare function discoverActions(controllerCtor: Function, app: Moost, logger: TConsoleBase): TDbActionEnvelope[];
2238
- /**
2239
- * The `'row'` / `'rows'`-level subset of {@link discoverActions} (since
2240
- * 0.1.145 public) — the actions `$actions` and `GET /meta/actions/:id`
2241
- * consider, in `/meta.actions` order. Memoized per controller class.
2242
- */
2243
- declare function discoverRowLevelActions(controllerCtor: Function, app: Moost, logger: TConsoleBase): TDbActionEnvelope[];
2244
- //#endregion
2245
2713
  //#region src/actions/id-cache.d.ts
2246
2714
  declare const useDbActionId: import("@wooksjs/event-core").WookComposable<{
2247
2715
  load: () => Promise<Record<string, unknown>>;
@@ -2267,10 +2735,15 @@ declare const useDbActionRows: import("@wooksjs/event-core").WookComposable<{
2267
2735
  * absent (`'table'`-level).
2268
2736
  * - `input` — present only when the action declares an `@InputForm()`
2269
2737
  * parameter; carries the form payload the user filled out.
2738
+ * - `query` — a query target instead of `ids` (since 0.1.147): "every row
2739
+ * matching this query", for `'rows'` actions declaring `queryTarget` — see
2740
+ * {@link TDbActionQueryTarget}. Never together with `ids`.
2270
2741
  */
2271
2742
  interface DbActionEnvelope {
2272
2743
  ids?: unknown;
2273
2744
  input?: unknown;
2745
+ /** @since 0.1.147 — validated by the action's gate (`TDbActionQueryTarget`). */
2746
+ query?: unknown;
2274
2747
  }
2275
2748
  /**
2276
2749
  * Cached parse of the action request body. Centralises the shape check so
@@ -2339,6 +2812,165 @@ declare class ActionDisabledError extends HttpError<ActionDisabledErrorBody> {
2339
2812
  constructor(action: string, id?: Record<string, unknown>, ids?: Record<string, unknown>[], reasons?: readonly (string | null | undefined)[]);
2340
2813
  }
2341
2814
  //#endregion
2815
+ //#region src/actions/action-target-error.d.ts
2816
+ /**
2817
+ * Why a query-targeted action request was refused:
2818
+ *
2819
+ * - `TARGET_INVALID` (400) — malformed `query` (unknown key, a control other
2820
+ * than `$search` / `$index`, `ids` and `query` together, an action that
2821
+ * accepts no query target);
2822
+ * - `TARGET_TOO_LARGE` (400) — more rows match than the cap (`cap`);
2823
+ * - `TARGET_CHANGED` (409) — the match count differs from `expectCount`
2824
+ * (`matched` carries the current count).
2825
+ *
2826
+ * @since 0.1.147
2827
+ */
2828
+ type TActionTargetErrorCode = "TARGET_INVALID" | "TARGET_TOO_LARGE" | "TARGET_CHANGED";
2829
+ /**
2830
+ * Wire body of {@link ActionTargetError} — the moost `ServerError` envelope
2831
+ * plus the `name` discriminator `@atscript/db-client` maps to its typed error.
2832
+ *
2833
+ * @since 0.1.147
2834
+ */
2835
+ interface ActionTargetErrorBody {
2836
+ name: "ActionTargetError";
2837
+ message: string;
2838
+ statusCode: 400 | 409;
2839
+ code: TActionTargetErrorCode;
2840
+ action: string;
2841
+ /** `TARGET_CHANGED`: the current match count. */
2842
+ matched?: number;
2843
+ /** `TARGET_TOO_LARGE`: the most rows one request may target. */
2844
+ cap?: number;
2845
+ }
2846
+ /**
2847
+ * A refused query target (400 / 409) — see {@link TActionTargetErrorCode}.
2848
+ *
2849
+ * @since 0.1.147
2850
+ */
2851
+ declare class ActionTargetError extends HttpError<ActionTargetErrorBody> {
2852
+ name: string;
2853
+ constructor(code: TActionTargetErrorCode, action: string, message: string, extra?: {
2854
+ matched?: number;
2855
+ cap?: number;
2856
+ });
2857
+ }
2858
+ //#endregion
2859
+ //#region src/actions/target.d.ts
2860
+ /**
2861
+ * The rows a `'rows'` action runs on, as the handler surface of
2862
+ * `@DbActionTarget()` / {@link useDbActionTarget} (since 0.1.147) — the same
2863
+ * for an id target (`{ ids }`) and a query target (`{ query }`).
2864
+ *
2865
+ * @since 0.1.147
2866
+ */
2867
+ interface TDbActionTarget<Row = Record<string, unknown>> {
2868
+ /** `"ids"` — the body listed identifiers; `"query"` — a query target. */
2869
+ readonly kind: "ids" | "query";
2870
+ /** Rows the target resolved to (after `exclude`, before the per-batch gate). */
2871
+ readonly matched: number;
2872
+ /**
2873
+ * The target in batches, each already gated: the row overlay, the
2874
+ * action's `actionRowScope` for the batch, `disabled`, and — for a query
2875
+ * target — the query itself (a row that changed out of it is skipped as
2876
+ * `"stale"`). Rows carry the identity fields and the visible
2877
+ * `requiredFields`. Single pass.
2878
+ */
2879
+ batches(): AsyncIterable<{
2880
+ ids: Record<string, unknown>[];
2881
+ rows: Row[];
2882
+ }>;
2883
+ /** Reports a row the handler could not process — listed in {@link summary}. */
2884
+ fail(id: Record<string, unknown>, reason: string): void;
2885
+ /** What happened so far: matched, processed, skipped (by the gate) and failed rows. */
2886
+ summary(): TDbActionTargetSummary$1;
2887
+ }
2888
+ /**
2889
+ * The current `'rows'` action's {@link TDbActionTarget} (since 0.1.147).
2890
+ * Available in the handler of every `'rows'` action — `@DbActionTarget()`
2891
+ * handlers process it batch by batch; `@DbActionIDs()` / `@DbActionRows()`
2892
+ * handlers read it for `summary()` (the ids `onDisabledRows: 'skip'`
2893
+ * dropped, with their reasons).
2894
+ *
2895
+ * @since 0.1.147
2896
+ */
2897
+ declare function useDbActionTarget<Row = Record<string, unknown>>(ctx?: EventContext): TDbActionTarget<Row>;
2898
+ /**
2899
+ * Parameter decorator injecting the action's {@link TDbActionTarget}
2900
+ * (since 0.1.147). Makes the action `'rows'` level and switches its gate to
2901
+ * batch mode: the handler iterates `target.batches()`, each batch gated on
2902
+ * its own (skip semantics — rows failing the gate are skipped and reported
2903
+ * in `summary()`, never rejected). Accepts `{ ids }` bodies, and `{ query }`
2904
+ * bodies when the action declares `queryTarget`. Not combinable with
2905
+ * `@DbActionID*` / `@DbActionRow*`.
2906
+ *
2907
+ * ```ts
2908
+ * @Post("actions/close")
2909
+ * @DbAction<Issue>("close", { label: "Close", queryTarget: true })
2910
+ * async close(@DbActionTarget() target: TDbActionTarget<Issue>) {
2911
+ * for await (const { ids } of target.batches()) await closeIssues(ids)
2912
+ * return target.summary()
2913
+ * }
2914
+ * ```
2915
+ *
2916
+ * @since 0.1.147
2917
+ */
2918
+ declare function DbActionTarget(): ParameterDecorator;
2919
+ //#endregion
2920
+ //#region src/actions/db-actions-from.decorator.d.ts
2921
+ /** Options of {@link DbActionsFrom}. @since 0.1.147 */
2922
+ interface TDbActionsFromOpts {
2923
+ /**
2924
+ * Source identification field → the path in THIS controller's rows that
2925
+ * carries its value. Its keys must be exactly one identification of the
2926
+ * source (its primary key or a unique index). Default: derived from the
2927
+ * view definition — the view column that plainly maps each of the
2928
+ * source's `preferredId` fields. Required when this controller is not
2929
+ * bound to a view.
2930
+ */
2931
+ idMap?: Record<string, string>;
2932
+ /** The source's row / rows-level actions to delegate. Default: all of them. */
2933
+ actions?: readonly string[];
2934
+ }
2935
+ /**
2936
+ * Lists another controller's row / rows-level actions on THIS controller —
2937
+ * typically a `@ViewController` over the table whose actions the view's
2938
+ * rows stand for (since 0.1.147). The actions stay the source's: its route
2939
+ * runs them (`value`), its gate, permissions, `actionRowScope` and
2940
+ * `disabled` decide; the view only maps its rows to source ids.
2941
+ *
2942
+ * - `/meta.actions` lists them with `owner` (the source's base path) and,
2943
+ * when the ids are renamed, `idMap`; forms come from the source
2944
+ * (`formUrl`).
2945
+ * - `$actions` on the view's rows carries the source's verdict for the row
2946
+ * each view row maps to — what the source's own `GET /meta/actions/:id`
2947
+ * answers for it.
2948
+ * - `GET /meta/actions/:id` on the view answers them when the source id is
2949
+ * the view id renamed (`idMap` paths ⊆ the id used).
2950
+ * - A source action declaring `queryTarget` also takes a query target on the
2951
+ * VIEW (`queryTarget.url`): the view resolves "every view row matching the
2952
+ * query" under its own read scope, maps the rows to source ids and runs the
2953
+ * source's action route on them in batches — the source re-checks every
2954
+ * batch.
2955
+ *
2956
+ * Repeatable (several sources, listed in declaration order). The source is
2957
+ * referenced lazily (no import cycles between controllers) and must be
2958
+ * registered with the app. Only a
2959
+ * controller declaring it gets the `POST {prefix}/delegated-actions/:name`
2960
+ * route (subclasses inherit it).
2961
+ *
2962
+ * ```ts
2963
+ * @ViewController(IssueBoard)
2964
+ * @DbActionsFrom(() => IssueController) // id → id
2965
+ * export class IssueBoardController extends AsDbReadableController<typeof IssueBoard> {}
2966
+ *
2967
+ * @DbActionsFrom(() => IssueController, { idMap: { id: "issueId" }, actions: ["close"] })
2968
+ * ```
2969
+ *
2970
+ * @since 0.1.147
2971
+ */
2972
+ declare function DbActionsFrom(source: () => Function, opts?: TDbActionsFromOpts): ClassDecorator;
2973
+ //#endregion
2342
2974
  //#region src/actions/per-row.d.ts
2343
2975
  /**
2344
2976
  * Lift a per-row predicate into the batch shape required by
@@ -2429,4 +3061,37 @@ declare function resolveTerminalRef(def: TAtscriptAnnotatedType): TTerminalRef |
2429
3061
  */
2430
3062
  declare function applyTerminalRefs(serialized: TSerializedAnnotatedType, runtime: TAtscriptAnnotatedType, options: TSerializeOptions): TSerializedAnnotatedType;
2431
3063
  //#endregion
2432
- 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 TDbActionEnvelope, 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, discoverRowLevelActions, errorEnvelope, findReadableBinding, getAtscriptDbMate, getControllerFormType, getDbEndpoint, perRow, provideDbSpace, resolveBoundReadable, resolveDbSpace, resolveProp, resolveTerminalRef, unknownRelationError, useDbActionId, useDbActionIds, useDbActionInput, useDbActionRow, useDbActionRows, validationErrorTransform };
3064
+ //#region src/relation-predicates.d.ts
3065
+ /**
3066
+ * Client relational predicates (`nav=$some(…)` / `nav=$none(…)`, since
3067
+ * 0.1.147) at the HTTP layer: the request gate and the
3068
+ * `transformRelationFilter` overlay walk. Server-side filters
3069
+ * (`transformFilter`, `transformOne`, `actionRowScope`) never pass through
3070
+ * here — they are the authorization rule itself.
3071
+ *
3072
+ * Invariant: a client predicate on relation `n` reveals nothing that
3073
+ * `$with=n(<same filter>)` would not reveal under the same controller policy
3074
+ * — same visibility (`hasField` at `n` and `n.*`), the related table's own
3075
+ * capability rules (encrypted, `@db.writeOnly`, JSON storage,
3076
+ * `@db.table.filterable 'manual'`, geo, …) and the same row overlay
3077
+ * (`transformRelationFilter`). On top, the relation must opt in with
3078
+ * `@db.rel.filterable` (a predicate filters the PARENT rows).
3079
+ */
3080
+ /**
3081
+ * Nesting limit of CLIENT relational predicates per predicate chain (`$with`
3082
+ * hops don't count; server-added predicates are not counted). Below the
3083
+ * core's `REL_FILTER_MAX_DEPTH` so server overlays have headroom.
3084
+ *
3085
+ * @since 0.1.147
3086
+ */
3087
+ declare const REL_FILTER_CLIENT_MAX_DEPTH = 3;
3088
+ /**
3089
+ * Count limit of CLIENT relational predicates per request — root filter and
3090
+ * `$with` sub-filters together. Below the core's `REL_FILTER_MAX_NODES` so
3091
+ * server overlays have headroom.
3092
+ *
3093
+ * @since 0.1.147
3094
+ */
3095
+ declare const REL_FILTER_CLIENT_MAX_NODES = 8;
3096
+ //#endregion
3097
+ export { ActionDisabledError, type ActionDisabledErrorBody, ActionTargetError, type ActionTargetErrorBody, 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, DbActionTarget, DbActions, DbActionsFrom, DbRowActions, DbRowsActions, DbTableActions, FieldCapabilityIndex, type IdValidationSource, InputForm, ONE_CONTROLS, PAGES_CONTROLS, QUERY_CONTROLS, READABLE_DEF, REL_FILTER_CLIENT_MAX_DEPTH, REL_FILTER_CLIENT_MAX_NODES, ReadableController, TABLE_DEF, type TActionTargetErrorCode, TAssertExposedOptions, type TCapabilityReadable, type TCapabilityVerdict, TControllerBindingOptions, type TCrudOp, type TCrudPermissions, type TDbActionDisabledVerdict, type TDbActionEnvelope, type TDbActionInfo, type TDbActionInputFormMeta, type TDbActionIntent, type TDbActionLevel, type TDbActionMeta, type TDbActionParamKind, type TDbActionProcessor, type TDbActionQueryTarget, type TDbActionScopeContext, type TDbActionScopePurpose, type TDbActionTarget, type TDbActionTargetSummary, type TDbActionsEntry, type TDbActionsEntryUnpinned, type TDbActionsFromMeta, type TDbActionsFromOpts, type TDbAvailableActions, type TDbClassActionMeta, TDbControlsType, TDbDecorateContext, TDbDecorateEndpoint, TDbFieldVisibility, TDbIndexFieldPaths, TDbParsedRequest, type TDbQueryTargetOpts, 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, discoverRowLevelActions, errorEnvelope, findReadableBinding, getAtscriptDbMate, getControllerFormType, getDbEndpoint, hasActionDelegations, perRow, provideDbSpace, resolveBoundReadable, resolveDbSpace, resolveProp, resolveTerminalRef, unknownRelationError, useDbActionId, useDbActionIds, useDbActionInput, useDbActionRow, useDbActionRows, useDbActionTarget, validationErrorTransform };