@atscript/moost-db 0.1.146 → 0.1.148
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/{db-space-registry-C_wft7kl.mjs → db-space-registry-Cdb0FGCn.mjs} +28 -2
- package/dist/{db-space-registry-CKR6G-hi.d.mts → db-space-registry-Cvd8s9ql.d.mts} +17 -1
- package/dist/{db-space-registry-DqKr5Zdk.cjs → db-space-registry-D5gnC_-6.cjs} +32 -0
- package/dist/{db-space-registry-CWpYwZ4R.d.cts → db-space-registry-fXB8LnGe.d.cts} +17 -1
- package/dist/index.cjs +4594 -1408
- package/dist/index.d.cts +1238 -248
- package/dist/index.d.mts +1239 -249
- package/dist/index.mjs +4586 -1411
- package/dist/testing.cjs +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +21 -19
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as resolveDbSpace, i as provideDbSpace, n as clearDbSpaces, r as closeDbSpaces, t as DEFAULT_DB_SPACE } from "./db-space-registry-Cvd8s9ql.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,8 +37,38 @@ interface TDbRequestContext {
|
|
|
32
37
|
* `metaForm`, writes and actions.
|
|
33
38
|
*/
|
|
34
39
|
readonly controls?: Record<string, unknown>;
|
|
35
|
-
/**
|
|
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;
|
|
64
|
+
/**
|
|
65
|
+
* `"insert"` endpoint only: the `?$onConflict=` mode the request carries
|
|
66
|
+
* (`"ignore"`); absent for a plain insert. A controller can refuse it by
|
|
67
|
+
* throwing from `prepareRequest`.
|
|
68
|
+
*
|
|
69
|
+
* @since 0.1.148
|
|
70
|
+
*/
|
|
71
|
+
readonly onConflict?: "ignore";
|
|
37
72
|
}
|
|
38
73
|
/** Control DTO a {@link AsReadableController.validateControls} call checks against. @since 0.1.143 (`"geo"`) */
|
|
39
74
|
type TDbControlsType = "query" | "pages" | "getOne" | "geo";
|
|
@@ -183,8 +218,8 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
|
|
|
183
218
|
protected prepareRequest?(ctx: TDbRequestContext): void | Promise<void>;
|
|
184
219
|
/**
|
|
185
220
|
* The ONE request entry of every built-in route: with a `url` (read
|
|
186
|
-
* endpoints) it parses the query string — `/one`
|
|
187
|
-
* controls ({@link parseControlsOnlyFromUrl}), every other endpoint the
|
|
221
|
+
* endpoints, and `POST /`) it parses the query string — `/one` and `POST /`
|
|
222
|
+
* keep only the `$` controls ({@link parseControlsOnlyFromUrl}), every other endpoint the
|
|
188
223
|
* whole query ({@link parseQueryString}) — and coerces boolean controls the
|
|
189
224
|
* URL grammar leaves as strings (`$actions=true`); then it awaits
|
|
190
225
|
* {@link prepareRequest} (when implemented) with the parsed controls.
|
|
@@ -321,14 +356,404 @@ interface IdValidationSource {
|
|
|
321
356
|
readonly fieldDescriptors: readonly TDbFieldMeta[];
|
|
322
357
|
}
|
|
323
358
|
//#endregion
|
|
359
|
+
//#region src/actions/query-target.d.ts
|
|
360
|
+
/**
|
|
361
|
+
* A query target — the `query` of an action request body: "every row
|
|
362
|
+
* matching this query" instead of a list of identifiers (since 0.1.147).
|
|
363
|
+
*
|
|
364
|
+
* @since 0.1.147
|
|
365
|
+
*/
|
|
366
|
+
interface TDbActionQueryTarget {
|
|
367
|
+
/** The query string `GET /query` accepts — filter plus `$search` / `$index` ONLY. */
|
|
368
|
+
q: string;
|
|
369
|
+
/** Identifiers to leave out (any identification; at most the action's `maxIds`). */
|
|
370
|
+
exclude?: Record<string, unknown>[];
|
|
371
|
+
/** Guard: the request fails with 409 `TARGET_CHANGED` when the match count differs. */
|
|
372
|
+
expectCount?: number;
|
|
373
|
+
/** Client-side cap (it never raises the server's `maxRows`). */
|
|
374
|
+
maxRows?: number;
|
|
375
|
+
/** Resolve and count only — the reply is `{ matched }`, the handler does not run. */
|
|
376
|
+
dryRun?: boolean;
|
|
377
|
+
}
|
|
378
|
+
/** `queryTarget` of `@DbAction` options. `true` = the defaults. @since 0.1.147 */
|
|
379
|
+
type TDbQueryTargetOpts = boolean | {
|
|
380
|
+
maxRows?: number;
|
|
381
|
+
batchSize?: number;
|
|
382
|
+
};
|
|
383
|
+
/**
|
|
384
|
+
* Key of `AsDbReadableController`'s internal query-target resolver (a
|
|
385
|
+
* registered symbol, like `ACTION_OVERLAY`).
|
|
386
|
+
*/
|
|
387
|
+
declare const RESOLVE_TARGET: unique symbol;
|
|
388
|
+
/** What {@link RESOLVE_TARGET} resolves. */
|
|
389
|
+
interface TTargetRequest {
|
|
390
|
+
action: string;
|
|
391
|
+
/** The raw `query` of the request body — validated by the resolver. */
|
|
392
|
+
query: unknown;
|
|
393
|
+
/** The most rows the target may match (server side). */
|
|
394
|
+
cap: number;
|
|
395
|
+
/** The most `exclude` entries. */
|
|
396
|
+
maxExclude: number;
|
|
397
|
+
/**
|
|
398
|
+
* The row overlay the target resolves under, besides `queryTargetScope`:
|
|
399
|
+
* `"action"` — the controller's `rowOverlay()` (its own action runs on the
|
|
400
|
+
* rows); `"read"` — `transformFilter` (a view resolving rows it delegates).
|
|
401
|
+
*/
|
|
402
|
+
overlay: "action" | "read";
|
|
403
|
+
/** Fields of the phase-1 read (sorted by the first identification's fields). */
|
|
404
|
+
select: readonly string[];
|
|
405
|
+
/** Key sets `exclude` entries may use besides the controller's identifications. */
|
|
406
|
+
excludeShapes?: readonly (readonly string[])[];
|
|
407
|
+
}
|
|
408
|
+
/** A resolved query target: the phase-1 snapshot plus its re-check. */
|
|
409
|
+
interface TResolvedTarget {
|
|
410
|
+
/** Rows matched at phase 1 (after `exclude`). */
|
|
411
|
+
matched: number;
|
|
412
|
+
/** The phase-1 rows (`select` fields), ordered by identity. */
|
|
413
|
+
rows: Record<string, unknown>[];
|
|
414
|
+
dryRun: boolean;
|
|
415
|
+
/** The validated `exclude` entries (already applied to {@link rows}). */
|
|
416
|
+
exclude: Record<string, unknown>[];
|
|
417
|
+
/**
|
|
418
|
+
* The rows `ids` address that STILL match the target (filter, search,
|
|
419
|
+
* overlay, `queryTargetScope`, `exclude`), aligned with `ids`; `select`
|
|
420
|
+
* plus the id fields. The FIRST call directly follows the snapshot and is
|
|
421
|
+
* not re-checked (served from {@link rows}, or read by identity alone
|
|
422
|
+
* when `select` needs more fields); every later call re-checks.
|
|
423
|
+
*/
|
|
424
|
+
load(ids: readonly Record<string, unknown>[], select: Iterable<string>): Promise<Array<Record<string, unknown> | undefined>>;
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region src/actions/types.d.ts
|
|
428
|
+
/**
|
|
429
|
+
* One entry of a `disabled` predicate's result. Truthy = the action is
|
|
430
|
+
* disabled for that row; falsy (`false`, `""`) = enabled. A non-empty string
|
|
431
|
+
* disables the action AND carries a human-readable reason — surfaced in the
|
|
432
|
+
* 409 `ActionDisabledError` message/body and in the row's `$disabledReasons`.
|
|
433
|
+
*
|
|
434
|
+
* @since 0.1.141 (`string`)
|
|
435
|
+
*/
|
|
436
|
+
type TDbActionDisabledVerdict = boolean | string;
|
|
437
|
+
/** `'rows'`-level batch policy — controls whether failing rows reject or are filtered out. */
|
|
438
|
+
type TOnDisabledRows = "reject" | "skip";
|
|
439
|
+
/**
|
|
440
|
+
* Dot-notation field paths of `TRow`'s flat type. Drives both the runtime
|
|
441
|
+
* projection widening and the type narrowing of the `disabled` predicate's
|
|
442
|
+
* row argument. Relations are absent from `FlatOf<T>` — listing a relation
|
|
443
|
+
* field is therefore a compile error.
|
|
444
|
+
*
|
|
445
|
+
* Permissive fallback when `TRow = unknown` (no explicit decorator generic):
|
|
446
|
+
* any string is allowed and the `disabled` predicate's row arg is `any[]`,
|
|
447
|
+
* preserving the prior loose typing for un-annotated call sites.
|
|
448
|
+
*/
|
|
449
|
+
type FlatKey<TRow> = unknown extends TRow ? string : keyof FlatOf<TRow> & string;
|
|
450
|
+
/** Row-shape narrowing for the `disabled` predicate. Falls back to `any` when `TRow = unknown`. */
|
|
451
|
+
type DisabledRowsArg<TRow, R extends readonly FlatKey<TRow>[]> = unknown extends TRow ? any[] : Pick<FlatOf<TRow>, R[number] & keyof FlatOf<TRow>>[];
|
|
452
|
+
interface NoGate {
|
|
453
|
+
requiredFields?: never;
|
|
454
|
+
disabled?: never;
|
|
455
|
+
onDisabledRows?: never;
|
|
456
|
+
}
|
|
457
|
+
interface WithGate<TRow, R extends readonly FlatKey<TRow>[]> {
|
|
458
|
+
/**
|
|
459
|
+
* Dot-notation field paths the predicate references. SERVER-INTERNAL —
|
|
460
|
+
* never emitted on the `/meta` wire. Consumed verbatim to widen the DB
|
|
461
|
+
* projection so `disabled` always sees the fields it declared.
|
|
462
|
+
*/
|
|
463
|
+
requiredFields: R;
|
|
464
|
+
/**
|
|
465
|
+
* Sync batch gate predicate — returns a parallel array aligned with the
|
|
466
|
+
* input. Per entry: truthy = disabled for the corresponding row; a
|
|
467
|
+
* non-empty string also gives the reason (see
|
|
468
|
+
* {@link TDbActionDisabledVerdict}). The `rows`
|
|
469
|
+
* argument is type-narrowed to `Pick<FlatOf<TRow>, R[number]>[]`; reading
|
|
470
|
+
* a field not listed in `requiredFields` is a compile error.
|
|
471
|
+
*
|
|
472
|
+
* Promise return is NOT permitted — the predicate is consumed in the
|
|
473
|
+
* same tick by the gate and the augmenter.
|
|
474
|
+
*/
|
|
475
|
+
disabled?: (rows: DisabledRowsArg<TRow, R>) => TDbActionDisabledVerdict[];
|
|
476
|
+
/**
|
|
477
|
+
* `'rows'`-level batch policy. Default `'reject'`.
|
|
478
|
+
*
|
|
479
|
+
* - `'reject'`: evaluate every row before throwing; if any row fails, the
|
|
480
|
+
* error body lists ALL failing IDs; handler not invoked.
|
|
481
|
+
* - `'skip'`: filter cached rows + cached IDs to passing-only; zero
|
|
482
|
+
* survivors → reject. Handler runs against the survivors.
|
|
483
|
+
*
|
|
484
|
+
* Ignored for `'row'` and `'table'` level actions.
|
|
485
|
+
*/
|
|
486
|
+
onDisabledRows?: TOnDisabledRows;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Loose gate shape used when `TRow = unknown` (no explicit decorator generic).
|
|
490
|
+
* Preserves the prior un-typed call-site flexibility; the runtime still drops
|
|
491
|
+
* actions where `disabled` is set without `requiredFields`.
|
|
492
|
+
*/
|
|
493
|
+
interface LooseGate {
|
|
494
|
+
requiredFields?: string[];
|
|
495
|
+
disabled?: (rows: any[]) => TDbActionDisabledVerdict[];
|
|
496
|
+
onDisabledRows?: TOnDisabledRows;
|
|
497
|
+
}
|
|
498
|
+
type GateOpts<TRow, R extends readonly FlatKey<TRow>[]> = unknown extends TRow ? LooseGate : NoGate | WithGate<TRow, R>;
|
|
499
|
+
interface BaseActionOpts extends Partial<Omit<TDbActionInfo$1, "name" | "level" | "processor" | "value" | "disabled" | "formUrl" | "owner" | "idMap" | "queryTarget">> {
|
|
500
|
+
/**
|
|
501
|
+
* Bound table reference. REQUIRED on non-`AsDbReadableController` classes
|
|
502
|
+
* when `disabled` is set OR a `@DbActionRow*` parameter is declared.
|
|
503
|
+
*
|
|
504
|
+
* Silently ignored on `AsDbReadableController` subclasses (which include
|
|
505
|
+
* `AsDbController`) — the bound table from the controller wins.
|
|
506
|
+
*/
|
|
507
|
+
table?: AtscriptDbTable<any>;
|
|
508
|
+
/**
|
|
509
|
+
* `'rows'` level (`@DbActionIDs` / `@DbActionRows`): the most identifiers
|
|
510
|
+
* one request may carry. Above it the request is rejected with 400 before
|
|
511
|
+
* any row is loaded. Default `1000`. Server-internal — never on the wire.
|
|
512
|
+
*
|
|
513
|
+
* @since 0.1.143
|
|
514
|
+
*/
|
|
515
|
+
maxIds?: number;
|
|
516
|
+
/**
|
|
517
|
+
* `'rows'` level only: the action also accepts a query target — a body
|
|
518
|
+
* `{ query: { q, exclude?, expectCount?, maxRows?, dryRun? }, input? }`
|
|
519
|
+
* meaning "every row matching `q`" (the `GET /query` filter plus
|
|
520
|
+
* `$search` / `$index`) under the caller's read scope (`queryTargetScope`)
|
|
521
|
+
* and the action's own gate. `true` = `{ maxRows: 10_000, batchSize:
|
|
522
|
+
* 500 }`. `maxRows` (on the wire as `queryTarget.maxRows`) caps the
|
|
523
|
+
* matched rows; a `@DbActionTarget()` handler gets them in `batchSize`
|
|
524
|
+
* batches, a `@DbActionIDs` / `@DbActionRows` handler at once (then also
|
|
525
|
+
* capped by `maxIds`).
|
|
526
|
+
*
|
|
527
|
+
* @since 0.1.147
|
|
528
|
+
*/
|
|
529
|
+
queryTarget?: TDbQueryTargetOpts;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Options accepted by `@DbAction(name, opts?)`. Generic over `TRow` (the
|
|
533
|
+
* controller's bound atscript type) and `R` (the literal `requiredFields`
|
|
534
|
+
* tuple). Both are inferred at the call site via the decorator's `<TRow>`
|
|
535
|
+
* argument plus `const R` generic.
|
|
536
|
+
*/
|
|
537
|
+
type DbActionOpts<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = BaseActionOpts & GateOpts<TRow, R>;
|
|
538
|
+
interface DbActionsEntryCommonBase {
|
|
539
|
+
label: string;
|
|
540
|
+
level: TDbActionLevel$1;
|
|
541
|
+
icon?: string;
|
|
542
|
+
intent?: TDbActionIntent$1;
|
|
543
|
+
description?: string;
|
|
544
|
+
order?: number;
|
|
545
|
+
default?: boolean;
|
|
546
|
+
/** Mirrors {@link TDbActionInfo.promptText} — singular/plural via tuple. */
|
|
547
|
+
promptText?: string | [string, string];
|
|
548
|
+
/** Mirrors {@link TDbActionInfo.shortcut} — single-character UI hint. */
|
|
549
|
+
shortcut?: string;
|
|
550
|
+
/**
|
|
551
|
+
* Input form the UI collects before invoking the action:
|
|
552
|
+
*
|
|
553
|
+
* - a compiled `.as` interface — registered on THIS controller and served
|
|
554
|
+
* by its own `GET /meta/form/:name`; the wire carries `inputForm: Type.name`.
|
|
555
|
+
* - `{ name, url }` — a form served elsewhere: `name` goes on the wire as
|
|
556
|
+
* `inputForm`, `url` (server-absolute path of the serialized schema, e.g.
|
|
557
|
+
* `"/api/shipping/meta/form/ShipForm"`) as {@link TDbActionInfo.formUrl}.
|
|
558
|
+
*
|
|
559
|
+
* Not allowed with `processor: 'navigate'`. Class-level entries only
|
|
560
|
+
* describe the action — validating `input` is the target handler's job
|
|
561
|
+
* (e.g. its own `@InputForm(Type)` param).
|
|
562
|
+
*
|
|
563
|
+
* @since 0.1.136
|
|
564
|
+
*/
|
|
565
|
+
inputForm?: TAtscriptAnnotatedType | {
|
|
566
|
+
name: string;
|
|
567
|
+
url: string;
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
type DbActionsEntryWithGate<TRow, R extends readonly FlatKey<TRow>[]> = DbActionsEntryCommonBase & GateOpts<TRow, R>;
|
|
571
|
+
/**
|
|
572
|
+
* Class-level dict entry. `value` semantics by processor:
|
|
573
|
+
*
|
|
574
|
+
* - `'navigate'` — REQUIRED, non-empty. URL template (`$1` substituted client-side).
|
|
575
|
+
* - `'backend'` — REQUIRED, non-empty. Full HTTP POST path the UI client invokes.
|
|
576
|
+
* - `'custom'` — `value` is forbidden in the entry; the meta builder fills it
|
|
577
|
+
* with the dict key.
|
|
578
|
+
*/
|
|
579
|
+
type TDbActionsEntry<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = (DbActionsEntryWithGate<TRow, R> & {
|
|
580
|
+
processor: "navigate";
|
|
581
|
+
value: string;
|
|
582
|
+
inputForm?: never;
|
|
583
|
+
}) | (DbActionsEntryWithGate<TRow, R> & {
|
|
584
|
+
processor: "custom";
|
|
585
|
+
value?: never;
|
|
586
|
+
}) | (DbActionsEntryWithGate<TRow, R> & {
|
|
587
|
+
processor: "backend";
|
|
588
|
+
value: string;
|
|
589
|
+
});
|
|
590
|
+
/** Distributes `Omit` across the discriminated union members. */
|
|
591
|
+
type DistributiveOmit<T, K extends keyof T> = T extends T ? Omit<T, K> : never;
|
|
592
|
+
/** Same as {@link TDbActionsEntry} but without the `level` field — used by the level-pinned shortcuts. */
|
|
593
|
+
type TDbActionsEntryUnpinned<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = DistributiveOmit<TDbActionsEntry<TRow, R>, "level">;
|
|
594
|
+
type DbActionsDictBase = Record<string, unknown>;
|
|
595
|
+
type EntryRequiredFields<E, TRow> = E extends {
|
|
596
|
+
requiredFields: infer R;
|
|
597
|
+
} ? R extends readonly FlatKey<TRow>[] ? R : [] : [];
|
|
598
|
+
type ValidatedDict<TRow, D extends DbActionsDictBase> = { [K in keyof D]: TDbActionsEntry<TRow, EntryRequiredFields<D[K], TRow>> };
|
|
599
|
+
type ValidatedUnpinnedDict<TRow, D extends DbActionsDictBase> = { [K in keyof D]: TDbActionsEntryUnpinned<TRow, EntryRequiredFields<D[K], TRow>> };
|
|
600
|
+
/**
|
|
601
|
+
* An id as an id-addressed endpoint received it: a path scalar (always a
|
|
602
|
+
* string from the URL) or an identification object (`?field=value` forms,
|
|
603
|
+
* action bodies).
|
|
604
|
+
*
|
|
605
|
+
* @since 0.1.148
|
|
606
|
+
*/
|
|
607
|
+
type TDbRowIdInput = string | number | boolean | Record<string, unknown>;
|
|
608
|
+
/**
|
|
609
|
+
* Which endpoint asks `resolveRowIds`:
|
|
610
|
+
*
|
|
611
|
+
* - `"action"` — an action route: the body's validated ids;
|
|
612
|
+
* - `"available"` — `GET /meta/actions/:id` | `?…` (and a view asking its source);
|
|
613
|
+
* - `"one"` — `GET /one/:id` | `?…`;
|
|
614
|
+
* - `"remove"` — `DELETE /:id` | `?…`.
|
|
615
|
+
*
|
|
616
|
+
* @since 0.1.148
|
|
617
|
+
*/
|
|
618
|
+
type TDbRowIdPurpose = "action" | "available" | "one" | "remove";
|
|
619
|
+
/**
|
|
620
|
+
* Context of `AsDbReadableController.resolveRowIds`.
|
|
621
|
+
*
|
|
622
|
+
* @since 0.1.148
|
|
623
|
+
*/
|
|
624
|
+
interface TDbRowIdsContext {
|
|
625
|
+
readonly purpose: TDbRowIdPurpose;
|
|
626
|
+
/** `"action"` only: the action's name. */
|
|
627
|
+
readonly action?: string;
|
|
628
|
+
/** `"action"` only: the action's level. */
|
|
629
|
+
readonly level?: "row" | "rows";
|
|
630
|
+
/**
|
|
631
|
+
* The row overlay the endpoint will apply to the resolved id
|
|
632
|
+
* (`rowOverlay()`; for actions the gate's overlay), `undefined` when none.
|
|
633
|
+
* Server data — never sent to the client. Resolve INSIDE it when an alias
|
|
634
|
+
* could name several rows, so a row the caller can't reach never shadows
|
|
635
|
+
* one they can.
|
|
636
|
+
*/
|
|
637
|
+
readonly overlay?: FilterExpr;
|
|
638
|
+
}
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region src/actions/discover.d.ts
|
|
641
|
+
/**
|
|
642
|
+
* Pairs the wire-shaped `info` with the original decorator opts / dict entry,
|
|
643
|
+
* so the augmenter can invoke the live `disabled` reference (deliberately
|
|
644
|
+
* absent from the wire `info`).
|
|
645
|
+
*/
|
|
646
|
+
/** A discovered action: its `/meta.actions[]` entry plus the server-internal opts it was declared with. */
|
|
647
|
+
interface TDbActionEnvelope {
|
|
648
|
+
info: TDbActionInfo$1;
|
|
649
|
+
raw: DbActionOpts | TDbActionsEntry;
|
|
650
|
+
}
|
|
651
|
+
/** Lookup helper for `AsReadableController.metaForm()`. */
|
|
652
|
+
declare function getControllerFormType(ctor: Function, name: string): TAtscriptAnnotatedType | undefined;
|
|
653
|
+
/** Discover actions on a controller, memoized per ctor. `info`-only callers map `e => e.info`. */
|
|
654
|
+
declare function discoverActions(controllerCtor: Function, app: Moost, logger: TConsoleBase): TDbActionEnvelope[];
|
|
655
|
+
/**
|
|
656
|
+
* The `'row'` / `'rows'`-level subset of {@link discoverActions} (since
|
|
657
|
+
* 0.1.145 public) — the actions `$actions` and `GET /meta/actions/:id`
|
|
658
|
+
* consider, in `/meta.actions` order. Memoized per controller class.
|
|
659
|
+
*/
|
|
660
|
+
declare function discoverRowLevelActions(controllerCtor: Function, app: Moost, logger: TConsoleBase): TDbActionEnvelope[];
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region src/actions/delegation.d.ts
|
|
663
|
+
/** Internal: a source controller's batch `$actions` verdicts for delegated ids. */
|
|
664
|
+
declare const ACTION_VERDICTS: unique symbol;
|
|
665
|
+
/** Internal: a source controller's `GET /meta/actions/:id` answer for one id. */
|
|
666
|
+
declare const AVAILABLE_ACTIONS: unique symbol;
|
|
667
|
+
/** Internal: the subset of delegated action names a source controller lists for the caller. */
|
|
668
|
+
declare const ALLOWED_ACTIONS: unique symbol;
|
|
669
|
+
/** `true` when the controller class declares `@DbActionsFrom` (inherited included). */
|
|
670
|
+
declare function hasActionDelegations(ctor: Function): boolean;
|
|
671
|
+
//#endregion
|
|
672
|
+
//#region src/actions/rows-by-id.d.ts
|
|
673
|
+
/** One id the client sent, with the {@link identityKey} of the id it resolved to. */
|
|
674
|
+
interface TRequestedId {
|
|
675
|
+
id: Record<string, unknown>;
|
|
676
|
+
key: string;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* The result of a `resolveRowIds` call over an action's ids: the resolved
|
|
680
|
+
* ids with duplicate identities collapsed to the first (what handlers and
|
|
681
|
+
* the row load see) and — only when an id changed or collapsed — EVERY
|
|
682
|
+
* request id in request order (`requests`), the single model all refusals,
|
|
683
|
+
* reasons, summaries and counts are judged and reported in.
|
|
684
|
+
*/
|
|
685
|
+
interface TAppliedIds {
|
|
686
|
+
ids: Record<string, unknown>[];
|
|
687
|
+
requests?: readonly TRequestedId[];
|
|
688
|
+
}
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region src/actions/scope-context.d.ts
|
|
691
|
+
/**
|
|
692
|
+
* Which surface asks {@link TDbActionScopeContext} — see
|
|
693
|
+
* `AsDbReadableController.actionRowScope`:
|
|
694
|
+
*
|
|
695
|
+
* - `"execute"` — the action gate, about to run the action on `ids`;
|
|
696
|
+
* - `"rows"` — `$actions` on a read (also a view's delegated verdicts);
|
|
697
|
+
* - `"available"` — `GET /meta/actions/:id` (one row).
|
|
698
|
+
*
|
|
699
|
+
* @since 0.1.147
|
|
700
|
+
*/
|
|
701
|
+
type TDbActionScopePurpose = "execute" | "rows" | "available";
|
|
702
|
+
/**
|
|
703
|
+
* The candidate rows `AsDbReadableController.actionRowScope` is asked about
|
|
704
|
+
* (since 0.1.147). Every candidate is inside the controller's row overlay;
|
|
705
|
+
* the hook's result restricts them further.
|
|
706
|
+
*
|
|
707
|
+
* Exception (since 0.1.148): at `purpose: "execute"` with NO row overlay, the
|
|
708
|
+
* hook is asked BEFORE the rows are loaded when the request's ids are in
|
|
709
|
+
* `preferredId` shape — `ids` are then the request's (resolved) ids, deduped,
|
|
710
|
+
* and may name rows that do not exist (`loadRows` omits them). A restriction
|
|
711
|
+
* it returns joins the single row load (`id ∧ scope`); an answer of
|
|
712
|
+
* `undefined` / `null` / `{}` makes the gate load nothing.
|
|
713
|
+
*
|
|
714
|
+
* @since 0.1.147
|
|
715
|
+
*/
|
|
716
|
+
interface TDbActionScopeContext {
|
|
717
|
+
/**
|
|
718
|
+
* Which surface asks: `"execute"` — the action gate (≤ `maxIds` ids; one
|
|
719
|
+
* batch of a query target); `"rows"` — `$actions` on a read (also a view's
|
|
720
|
+
* delegated verdicts), the read's rows; `"available"` — `GET /meta/actions/:id`,
|
|
721
|
+
* the one row.
|
|
722
|
+
*/
|
|
723
|
+
readonly purpose: TDbActionScopePurpose;
|
|
724
|
+
/**
|
|
725
|
+
* The candidates' identities (`preferredId`-shaped), deduped and never
|
|
726
|
+
* empty — at `"execute"` without a row overlay, the request's ids (they may
|
|
727
|
+
* not exist; see above). ONE array object per evaluation, shared by every action of it —
|
|
728
|
+
* memoize on it (`WeakMap`) when several actions derive the same filter.
|
|
729
|
+
*/
|
|
730
|
+
readonly ids: readonly Record<string, unknown>[];
|
|
731
|
+
/**
|
|
732
|
+
* The candidates' `fields` (plus the identity fields), read straight from
|
|
733
|
+
* the bound readable without any overlay (the candidates already passed
|
|
734
|
+
* it). Memoized per evaluation and field set. Nothing of it reaches the
|
|
735
|
+
* response.
|
|
736
|
+
*/
|
|
737
|
+
loadRows(fields: readonly string[]): Promise<readonly Record<string, unknown>[]>;
|
|
738
|
+
}
|
|
739
|
+
//#endregion
|
|
324
740
|
//#region src/actions/row-scope.d.ts
|
|
325
741
|
/**
|
|
326
742
|
* The key of `AsDbReadableController`'s internal action-overlay method —
|
|
327
|
-
* `rowOverlay()`
|
|
328
|
-
*
|
|
329
|
-
*
|
|
743
|
+
* its `rowOverlay()` (since 0.1.147 without the action's `actionRowScope`,
|
|
744
|
+
* which needs the candidate rows — see {@link ACTION_SCOPE}). A registered
|
|
745
|
+
* symbol: not an overridable seam, and still found when moost-db loads in
|
|
746
|
+
* two module realms (moost-vite SSR).
|
|
330
747
|
*/
|
|
331
748
|
declare const ACTION_OVERLAY: unique symbol;
|
|
749
|
+
/** The controller's internal `actionRowScope` call for candidate rows (since 0.1.147). */
|
|
750
|
+
declare const ACTION_SCOPE: unique symbol;
|
|
751
|
+
/** `true` when the controller overrides `actionRowScope` (since 0.1.147). */
|
|
752
|
+
declare const ACTION_SCOPED: unique symbol;
|
|
753
|
+
/** The controller's internal `resolveRowIds` call for an action's ids (since 0.1.148). */
|
|
754
|
+
declare const ROW_RESOLVE_IDS: unique symbol;
|
|
755
|
+
/** `true` when the controller overrides `resolveRowIds` (since 0.1.148). */
|
|
756
|
+
declare const ROW_RESOLVES: unique symbol;
|
|
332
757
|
//#endregion
|
|
333
758
|
//#region src/meta/field-capabilities.d.ts
|
|
334
759
|
/**
|
|
@@ -370,16 +795,37 @@ interface TFieldCapability {
|
|
|
370
795
|
* `bucketSourceVerdict`). Since 0.1.132.
|
|
371
796
|
*/
|
|
372
797
|
bucketable: boolean;
|
|
798
|
+
/**
|
|
799
|
+
* `$groupBy` on this path passes the gate: physically filterable (adapter ∧
|
|
800
|
+
* ¬writeOnly ∧ ¬encrypted) and, on a table declaring dimensions / measures,
|
|
801
|
+
* a dimension. Since 0.1.148.
|
|
802
|
+
*/
|
|
803
|
+
groupable: boolean;
|
|
804
|
+
/** Present when `groupable` is `false` — the reason clause appended to the HTTP 400 message. */
|
|
805
|
+
groupReason?: string;
|
|
373
806
|
/** Present when `bucketable` is `false` — the reason clause appended to the HTTP 400 message. */
|
|
374
807
|
bucketReason?: string;
|
|
808
|
+
/**
|
|
809
|
+
* The path may be an operand of query-time arithmetic: a numeric field
|
|
810
|
+
* ({@link numericOperandProblem}) that aggregates (`$groupBy` / aggregate
|
|
811
|
+
* capability, ¬writeOnly, visible). Only on an adapter with
|
|
812
|
+
* `supportsAggregateExpressions()`. Since 0.1.148.
|
|
813
|
+
*/
|
|
814
|
+
numeric: boolean;
|
|
375
815
|
}
|
|
376
816
|
/** One rejected path: `path` is the offending logical path, `message` the full sentence. */
|
|
377
817
|
interface TCapabilityVerdict {
|
|
378
818
|
path: string;
|
|
379
819
|
message: string;
|
|
380
820
|
}
|
|
821
|
+
/**
|
|
822
|
+
* The positions {@link FieldCapabilityIndex.check} judges: the core's path
|
|
823
|
+
* positions plus the `$select` of an aggregate query (`groupedSelect`), where
|
|
824
|
+
* a plain field must be a `$groupBy` key — a display-only decoration never is.
|
|
825
|
+
*/
|
|
826
|
+
type TGateOp = TQueryPathOp$1 | "groupedSelect";
|
|
381
827
|
/** The readable members the index reads. */
|
|
382
|
-
type TCapabilityReadable = Pick<AtscriptDbReadable, "type" | "fieldDescriptors" | "flatMap" | "navFields" | "relations" | "ignoredFields" | "canFilterField" | "canSortField" | "isGeoSearchable" | "calendarBucketUnits" | "aggregateFns" | "dimensions" | "measures">;
|
|
828
|
+
type TCapabilityReadable = Pick<AtscriptDbReadable, "type" | "fieldDescriptors" | "flatMap" | "navFields" | "relations" | "ignoredFields" | "canFilterField" | "canSortField" | "isGeoSearchable" | "calendarBucketUnits" | "aggregateFns" | "supportsAggregateExpressions" | "dimensions" | "measures">;
|
|
383
829
|
/**
|
|
384
830
|
* Capability index of one readable.
|
|
385
831
|
*
|
|
@@ -413,6 +859,8 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
|
|
|
413
859
|
readonly bucketUnits: readonly BucketUnit[];
|
|
414
860
|
/** Aggregate functions the adapter renders, in canonical `ALL_AGGREGATE_FNS` order (`/meta.aggregateFns`). */
|
|
415
861
|
readonly aggregateFns: readonly AggregateFn[];
|
|
862
|
+
/** Whether the adapter renders aggregate arithmetic (`/meta.aggregateExpressions`). */
|
|
863
|
+
readonly aggregateExpressions: boolean;
|
|
416
864
|
/** The adapter-level capabilities this index was built against — see {@link adapterSignature}. */
|
|
417
865
|
readonly signature: string;
|
|
418
866
|
/**
|
|
@@ -421,8 +869,19 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
|
|
|
421
869
|
* {@link signature} differs from this is stale. Any new adapter-level input
|
|
422
870
|
* the index reads must be added here.
|
|
423
871
|
*/
|
|
424
|
-
static adapterSignature(source: Pick<TCapabilityReadable, "isGeoSearchable" | "calendarBucketUnits" | "aggregateFns">): string;
|
|
872
|
+
static adapterSignature(source: Pick<TCapabilityReadable, "isGeoSearchable" | "calendarBucketUnits" | "aggregateFns" | "supportsAggregateExpressions">): string;
|
|
873
|
+
/**
|
|
874
|
+
* The navigation paths of a readable: its `navFields`, else its relation
|
|
875
|
+
* names (partial readables list only the latter).
|
|
876
|
+
*/
|
|
877
|
+
static navPathsOf(source: Pick<TCapabilityReadable, "navFields" | "relations">): Set<string>;
|
|
425
878
|
private readonly _entries;
|
|
879
|
+
/**
|
|
880
|
+
* Declared display-only decorations (`@DbDecorations`, since 0.1.148) —
|
|
881
|
+
* virtual entries: key → the readable paths it `requires`. Selectable only;
|
|
882
|
+
* visible while every required path is.
|
|
883
|
+
*/
|
|
884
|
+
private readonly _decorations;
|
|
426
885
|
/** Nested-object parents (never listed, always selectable) → their listed leaves. */
|
|
427
886
|
private readonly _objectParents;
|
|
428
887
|
/** What `bucketSourceVerdict` reads of the table (JSON-value parents, dimensions, measures). */
|
|
@@ -431,10 +890,16 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
|
|
|
431
890
|
get leaves(): ReadonlyMap<string, unknown>;
|
|
432
891
|
/** Nested-object parents — the {@link TQueryPathSource} view for `classifyQueryPath`. */
|
|
433
892
|
get objectParents(): ReadonlyMap<string, unknown>;
|
|
434
|
-
constructor(source: TCapabilityReadable, writeOnly: ReadonlySet<string>);
|
|
893
|
+
constructor(source: TCapabilityReadable, writeOnly: ReadonlySet<string>, decorations?: ReadonlyMap<string, readonly string[]>);
|
|
435
894
|
private _buildEntry;
|
|
436
895
|
/** Listed leaves in descriptor order — the `/meta.fields` projection source. */
|
|
437
896
|
entries(): IterableIterator<[path: string, cap: TFieldCapability, fd: TDbFieldMeta]>;
|
|
897
|
+
/** The declared decoration keys, in declaration order. */
|
|
898
|
+
get decorationKeys(): IterableIterator<string>;
|
|
899
|
+
/** The capability of the declared decoration `key` (selectable only), `undefined` when `key` is none. */
|
|
900
|
+
decorationCap(key: string): Readonly<TFieldCapability> | undefined;
|
|
901
|
+
/** The decoration `key` is visible: every path it `requires` passes `exists` (the hidden-field hook). */
|
|
902
|
+
decorationVisible(key: string, exists: (path: string) => boolean): boolean;
|
|
438
903
|
/** Physical filter capability (adapter ∧ ¬writeOnly ∧ ¬encrypted) — ignores the manual-mode policy. */
|
|
439
904
|
isPhysicallyFilterable(path: string): boolean;
|
|
440
905
|
/**
|
|
@@ -457,10 +922,21 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
|
|
|
457
922
|
* JSON-stored column" — clients pin that wording, so do not "align" it
|
|
458
923
|
* with the core backstop's text.
|
|
459
924
|
*
|
|
925
|
+
* A declared decoration (`@DbDecorations`) is a virtual entry: `select` while
|
|
926
|
+
* every path it requires passes `exists`, any other position a display-only
|
|
927
|
+
* refusal (`groupedSelect` is a `$select` of an aggregate query), and hidden
|
|
928
|
+
* sources answer `Unknown field` like a nonexistent path.
|
|
929
|
+
*
|
|
460
930
|
* `predicate` is a filter entry's class (`collectQueryPaths` records it per
|
|
461
931
|
* occurrence); it only matters for `op === "filter"` on a listed leaf.
|
|
932
|
+
*
|
|
933
|
+
* `prefix` (since 0.1.147) is this index's readable's dotted path from the
|
|
934
|
+
* controller when it judges a relational predicate's operand (`"ticket."`):
|
|
935
|
+
* `exists` still receives the LOCAL path, the verdict's `path` and message
|
|
936
|
+
* name the prefixed one. A filter on a navigation path names the predicate
|
|
937
|
+
* alternative (`ticket=$some(status=…)`).
|
|
462
938
|
*/
|
|
463
|
-
check(
|
|
939
|
+
check(local: string, gateOp: TGateOp, exists: (path: string) => boolean, predicate?: TFilterPredicate, prefix?: string): TCapabilityVerdict | undefined;
|
|
464
940
|
}
|
|
465
941
|
//#endregion
|
|
466
942
|
//#region src/as-db-readable.controller.d.ts
|
|
@@ -483,6 +959,15 @@ interface TDbDecorateContext {
|
|
|
483
959
|
projection: UniqueryControls["$select"] | undefined;
|
|
484
960
|
/** The request's parsed controls (`$select`, `$with`, `$actions`, …). Read-only by convention. */
|
|
485
961
|
controls: Record<string, unknown>;
|
|
962
|
+
/**
|
|
963
|
+
* The declared decoration keys ([`@DbDecorations`](./decorations/db-decorations.decorator.ts))
|
|
964
|
+
* this response must carry — the ones the client asked for (all of them
|
|
965
|
+
* without a `$select`) whose sources are visible and kept by
|
|
966
|
+
* `transformProjection`. Compute only these; a declared key not listed is
|
|
967
|
+
* removed from the rows after the hook. Empty when none is declared.
|
|
968
|
+
* Undeclared (`$`-prefixed) keys are not affected. Since 0.1.148.
|
|
969
|
+
*/
|
|
970
|
+
decorations: ReadonlySet<string>;
|
|
486
971
|
}
|
|
487
972
|
/**
|
|
488
973
|
* One text / vector / geo index of the bound readable with the LOGICAL field
|
|
@@ -515,13 +1000,15 @@ interface TDbFieldVisibility {
|
|
|
515
1000
|
/**
|
|
516
1001
|
* `hasField(path)` and — when {@link scoped} — a `@db.column.derived`
|
|
517
1002
|
* field of the bound readable only while its source path is visible too
|
|
518
|
-
* (a derived copy must not outlive a hidden source)
|
|
1003
|
+
* (a derived copy must not outlive a hidden source), a computed view
|
|
1004
|
+
* column (`@db.compute`, since 0.1.147) only while every operand and every
|
|
1005
|
+
* intermediate computed field it reads through is.
|
|
519
1006
|
*/
|
|
520
1007
|
readonly isVisible: (path: string) => boolean;
|
|
521
1008
|
/**
|
|
522
1009
|
* The paths sealed out of `readable`'s read projection for this request:
|
|
523
1010
|
* its `@db.writeOnly` fields plus, when {@link scoped}, its derived fields
|
|
524
|
-
* whose source `hasField` hides. `prefix` is `readable`'s path from the
|
|
1011
|
+
* whose source `hasField` hides and its computed fields with a hidden operand. `prefix` is `readable`'s path from the
|
|
525
1012
|
* controller: `""` for the bound readable, `"rel."` for a `$with` target.
|
|
526
1013
|
*/
|
|
527
1014
|
readonly sealedFor: (readable: AtscriptDbReadable<any>, prefix?: string) => ReadonlySet<string>;
|
|
@@ -559,6 +1046,14 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
559
1046
|
*/
|
|
560
1047
|
protected get capabilities(): FieldCapabilityIndex;
|
|
561
1048
|
private _capabilities?;
|
|
1049
|
+
/** The client relational-predicate gate (since 0.1.147), built on first use — see {@link _relationGate}. */
|
|
1050
|
+
private _relGate?;
|
|
1051
|
+
/**
|
|
1052
|
+
* The client's `$with` tree per request, keyed by its controls object —
|
|
1053
|
+
* recorded in {@link validateParsed} before {@link validateControls} (see
|
|
1054
|
+
* `snapshotClientWith`).
|
|
1055
|
+
*/
|
|
1056
|
+
private readonly _clientWith;
|
|
562
1057
|
/** `/meta` is a projection of {@link capabilities}: a rebuilt index rebuilds the cached envelope. */
|
|
563
1058
|
protected metaCacheKey(): unknown;
|
|
564
1059
|
/**
|
|
@@ -571,12 +1066,18 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
571
1066
|
protected readonly fieldVisibility: TDbFieldVisibility;
|
|
572
1067
|
/** A subclass overrides {@link hasField}: visibility is request-scoped (derived rule, index gate, id options). */
|
|
573
1068
|
private readonly _hasFieldOverridden;
|
|
574
|
-
/**
|
|
1069
|
+
/**
|
|
1070
|
+
* `@db.column.derived` path → its source's logical path, `@db.compute` path
|
|
1071
|
+
* → its operands' paths plus the computed fields it reads through, per
|
|
1072
|
+
* readable (bound + `$with` targets).
|
|
1073
|
+
*/
|
|
575
1074
|
private readonly _derivedSources;
|
|
576
1075
|
/** The bound readable's entry of {@link _derivedSources}. */
|
|
577
1076
|
private readonly _derivedSource;
|
|
578
1077
|
/** `@db.writeOnly` paths of `$with` target readables, collected once per target. */
|
|
579
1078
|
private readonly _targetWriteOnly;
|
|
1079
|
+
/** Own leaf paths per readable (bound + `$with` targets), see {@link _leavesOf}. */
|
|
1080
|
+
private readonly _targetLeaves;
|
|
580
1081
|
private _indexFieldPathsCache?;
|
|
581
1082
|
/** {@link _nativeSearch} per request, keyed by the request's parsed controls. */
|
|
582
1083
|
private readonly _nativeSearchByRequest;
|
|
@@ -600,8 +1101,18 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
600
1101
|
private readonly _gateFieldsMemo;
|
|
601
1102
|
/** `true` when a subclass overrides {@link allowedActions}. */
|
|
602
1103
|
private readonly _hasAllowedActions;
|
|
1104
|
+
/** The class's validated `@DbDecorations` (since 0.1.148), `undefined` when none is declared. */
|
|
1105
|
+
private readonly _decorations;
|
|
1106
|
+
/** The decoration plumbing of {@link _decorations} — see `DecorationPlanner`. */
|
|
1107
|
+
private readonly _planner;
|
|
603
1108
|
/** `true` when a subclass overrides {@link actionRowScope} (the gate, `$actions` and `/meta/actions` apply it). */
|
|
604
1109
|
private readonly _hasActionRowScope;
|
|
1110
|
+
/** @internal `true` when a subclass overrides {@link resolveRowIds} (every id-addressed endpoint calls it; since 0.1.148). */
|
|
1111
|
+
readonly [ROW_RESOLVES]: boolean;
|
|
1112
|
+
/** `true` when the class declares `@DbActionsFrom` (since 0.1.147). */
|
|
1113
|
+
private readonly _hasDelegations;
|
|
1114
|
+
/** `transformProjection` is overridden (a delegation's id paths are checked against it). */
|
|
1115
|
+
private readonly _hasProjectionHook;
|
|
605
1116
|
/** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
|
|
606
1117
|
private readonly _quantityRefByPath;
|
|
607
1118
|
/** `@db.column.searchable` paths — the `$search` fallback when the adapter has no native search. */
|
|
@@ -615,6 +1126,12 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
615
1126
|
*/
|
|
616
1127
|
private readonly _invertibleFields;
|
|
617
1128
|
constructor(app: Moost, readable?: AtscriptDbReadable<T>);
|
|
1129
|
+
/**
|
|
1130
|
+
* The class's `@DbDecorations`, validated against the bound readable once
|
|
1131
|
+
* per class and readable (a `[moost-db]` error when invalid); warns once
|
|
1132
|
+
* when `decorateRows` is not implemented.
|
|
1133
|
+
*/
|
|
1134
|
+
private _resolveDecorations;
|
|
618
1135
|
/**
|
|
619
1136
|
* The identifications this request may address rows through (since
|
|
620
1137
|
* 0.1.134): the readable's own, minus unique indexes over fields
|
|
@@ -624,9 +1141,11 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
624
1141
|
get idSource(): IdValidationSource;
|
|
625
1142
|
private _collectInvertibleFields;
|
|
626
1143
|
private _collectQuantityRefs;
|
|
627
|
-
/**
|
|
1144
|
+
/**
|
|
1145
|
+
* `readable`'s `@db.column.derived` path → source path and `@db.compute`
|
|
1146
|
+
* path → operand paths map, collected once per readable.
|
|
1147
|
+
*/
|
|
628
1148
|
private _derivedSourcesOf;
|
|
629
|
-
private _collectAnnotated;
|
|
630
1149
|
/**
|
|
631
1150
|
* THE field-visibility hook: every gated path consults it before any
|
|
632
1151
|
* capability check (since 0.1.133) — filter keys (inside `$and` / `$or` /
|
|
@@ -652,7 +1171,9 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
652
1171
|
* visible fields (or ignores the term when there are none). A
|
|
653
1172
|
* `@db.column.derived` field is visible only while its source path is,
|
|
654
1173
|
* and one whose source is hidden is sealed out of every read projection
|
|
655
|
-
* for the request, like a `@db.writeOnly` field.
|
|
1174
|
+
* for the request, like a `@db.writeOnly` field. The same holds for a
|
|
1175
|
+
* computed view column (`@db.compute`) and each of its operands, including
|
|
1176
|
+
* the intermediate computed fields it reads through.
|
|
656
1177
|
*/
|
|
657
1178
|
protected hasField(path: string): boolean;
|
|
658
1179
|
/**
|
|
@@ -723,8 +1244,6 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
723
1244
|
private _writeOnlyOf;
|
|
724
1245
|
/** {@link TDbFieldVisibility.sealedFor}. */
|
|
725
1246
|
private _sealedFor;
|
|
726
|
-
/** The readable a `$with` entry name (`rel` or dotted `rel.sub`) loads from, if resolvable. */
|
|
727
|
-
private _relTarget;
|
|
728
1247
|
/**
|
|
729
1248
|
* Walks a `$with` tree pre-order: `visit(rel, target, path, controls)` for
|
|
730
1249
|
* every entry whose target readable resolves (`path` = the entry's dotted
|
|
@@ -782,6 +1301,15 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
782
1301
|
* nonexistent index (the core's wording).
|
|
783
1302
|
*/
|
|
784
1303
|
private _checkIndexGate;
|
|
1304
|
+
/**
|
|
1305
|
+
* `/meta`'s search surface as THIS request may use it (only when
|
|
1306
|
+
* {@link hasField} is overridden — the rule of the index gate): indexes
|
|
1307
|
+
* reading a hidden field are left out of `searchIndexes`; `searchable` /
|
|
1308
|
+
* `vectorSearchable` / `geoSearchable` turn off when the index a request
|
|
1309
|
+
* naming none would use reads one (`searchable` stays on for the
|
|
1310
|
+
* `@db.column.searchable` fallback when any of its fields is visible).
|
|
1311
|
+
*/
|
|
1312
|
+
private _applyIndexVisibility;
|
|
785
1313
|
/**
|
|
786
1314
|
* Compute an embedding vector from a search term.
|
|
787
1315
|
* Override in subclass to integrate with your embedding provider (OpenAI, etc.).
|
|
@@ -800,6 +1328,42 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
800
1328
|
* `findById`). Override to scope `/one` differently.
|
|
801
1329
|
*/
|
|
802
1330
|
protected transformOne(filter: FilterExpr): FilterExpr | Promise<FilterExpr>;
|
|
1331
|
+
/**
|
|
1332
|
+
* Rewrites the sub-filter of a CLIENT relational predicate before it runs
|
|
1333
|
+
* — the row overlay of the related table (since 0.1.147). `path` is the
|
|
1334
|
+
* dotted navigation chain from this controller's table: `"ticket"` for
|
|
1335
|
+
* `ticket=$some(…)`, `"ticket.team"` for a predicate nested in its
|
|
1336
|
+
* operand, `"tickets.issues"` for `$with=tickets(issues=$some(…))`.
|
|
1337
|
+
* Default identity.
|
|
1338
|
+
*
|
|
1339
|
+
* Applied to client predicates only (the URL filter and `$with`
|
|
1340
|
+
* sub-filters, on `/query` incl. `$groupBy` and `$count`, `/pages`, `/geo`
|
|
1341
|
+
* and `/one`, and a query target's filter), after the request gate and before {@link transformFilter};
|
|
1342
|
+
* a nested predicate's operand is rewritten before the operand holding it,
|
|
1343
|
+
* and the hook's output is not walked again. Server-side filters
|
|
1344
|
+
* ({@link transformFilter}, {@link transformOne}, {@link actionRowScope})
|
|
1345
|
+
* never pass through it. Return the operand conjoined with the related
|
|
1346
|
+
* rows the caller may see — `$some` then only matches, and `$none` only
|
|
1347
|
+
* excludes, on VISIBLE related rows, exactly as `$with` shows them:
|
|
1348
|
+
*
|
|
1349
|
+
* ```ts
|
|
1350
|
+
* protected transformRelationFilter(path: string, filter: FilterExpr) {
|
|
1351
|
+
* return path === "ticket" ? { $and: [{ teamId: { $in: currentTeams() } }, filter] } : filter
|
|
1352
|
+
* }
|
|
1353
|
+
* ```
|
|
1354
|
+
*
|
|
1355
|
+
* @since 0.1.147
|
|
1356
|
+
*/
|
|
1357
|
+
protected transformRelationFilter(_path: string, filter: FilterExpr): FilterExpr | Promise<FilterExpr>;
|
|
1358
|
+
/** The client relational-predicate gate (since 0.1.147) — one per controller. */
|
|
1359
|
+
private _relationGate;
|
|
1360
|
+
/**
|
|
1361
|
+
* The client filter with every relational predicate operand rewritten by
|
|
1362
|
+
* {@link transformRelationFilter} — and `parsed.controls.$with` replaced by
|
|
1363
|
+
* its rewritten tree (since 0.1.147). Costs nothing unless the hook is
|
|
1364
|
+
* overridden.
|
|
1365
|
+
*/
|
|
1366
|
+
private _relationOverlay;
|
|
803
1367
|
/**
|
|
804
1368
|
* The subset of the row-level action `names` the caller may run (since
|
|
805
1369
|
* 0.1.145) — what `$actions` and `GET /meta/actions/:id` list from. The
|
|
@@ -816,33 +1380,98 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
816
1380
|
/**
|
|
817
1381
|
* The rows the row-level action `actionName` may run on (since 0.1.145),
|
|
818
1382
|
* as an extra row filter; `undefined` or `{}` = no restriction (the
|
|
819
|
-
* default). Enforced by the action gate —
|
|
820
|
-
*
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
1383
|
+
* default). Enforced by the action gate — the action's ids / rows are
|
|
1384
|
+
* loaded under {@link rowOverlay}, then checked against this filter (or —
|
|
1385
|
+
* without an overlay — the filter joins the load), so an id outside it gets
|
|
1386
|
+
* the same 404 "Row not found for action identifier"
|
|
1387
|
+
* as a missing one — and reflected in `$actions` and
|
|
1388
|
+
* `GET /meta/actions`, which list the action only on rows inside it.
|
|
1389
|
+
*
|
|
1390
|
+
* The hook receives the candidate rows (`ctx`), so a scope can depend on
|
|
1391
|
+
* them — what `ctx.purpose`, `ctx.ids` and `ctx.loadRows` hold, and when the
|
|
1392
|
+
* hook is asked before any load, is documented on {@link TDbActionScopeContext}.
|
|
1393
|
+
* Called only with at least one candidate, at most once per action per
|
|
1394
|
+
* evaluation. An answer restricting nothing (`undefined`, `null`, `{}`)
|
|
1395
|
+
* makes the gate load no row at all; a restriction is folded into the one
|
|
1396
|
+
* row load. The result only restricts (`ids ∧ rowOverlay ∧ scope`); a throw
|
|
1397
|
+
* fails the request — never a silent "allow". The filter runs straight
|
|
1398
|
+
* against the bound readable: it may use fields {@link hasField} hides, and
|
|
1399
|
+
* nothing of it reaches the response. Runs after {@link prepareRequest} and,
|
|
1400
|
+
* on the action route, after the request body is read and
|
|
1401
|
+
* {@link resolveRowIds}.
|
|
1402
|
+
*
|
|
1403
|
+
* Not overriding it costs nothing; a one-parameter override keeps working.
|
|
1404
|
+
* moost-db always passes `ctx` — it is optional in the signature only so
|
|
1405
|
+
* `super.actionRowScope(name)` calls in existing overrides keep compiling.
|
|
1406
|
+
*
|
|
1407
|
+
* ```ts
|
|
1408
|
+
* protected async actionRowScope(action: string, ctx: TDbActionScopeContext) {
|
|
1409
|
+
* if (action !== "resolve") return undefined
|
|
1410
|
+
* const issues = await ctx.loadRows(["ticketKey"])
|
|
1411
|
+
* const tickets = await ticketTable.findMany({
|
|
1412
|
+
* filter: { key: { $in: issues.map((i) => i.ticketKey) }, teamId: currentTeamId() },
|
|
1413
|
+
* controls: { $select: ["key"] },
|
|
1414
|
+
* })
|
|
1415
|
+
* return { ticketKey: { $in: tickets.map((t) => t.key) } }
|
|
1416
|
+
* }
|
|
1417
|
+
* ```
|
|
1418
|
+
*
|
|
1419
|
+
* The scope may use relational predicates (since 0.1.147) — a server-side
|
|
1420
|
+
* filter, so no `@db.rel.filterable` opt-in and no
|
|
1421
|
+
* {@link transformRelationFilter} apply. On an Issue controller:
|
|
831
1422
|
*
|
|
832
1423
|
* ```ts
|
|
833
1424
|
* protected actionRowScope(action: string) {
|
|
834
|
-
* return action === "
|
|
1425
|
+
* return action === "resolve"
|
|
1426
|
+
* ? { ticket: { $some: { teamId: { $in: currentTeams() }, status: "open" } } }
|
|
1427
|
+
* : undefined
|
|
835
1428
|
* }
|
|
836
1429
|
* ```
|
|
837
1430
|
*
|
|
838
1431
|
* @since 0.1.145
|
|
839
1432
|
*/
|
|
840
|
-
protected actionRowScope(_actionName: string): FilterExpr | undefined | Promise<FilterExpr | undefined>;
|
|
1433
|
+
protected actionRowScope(_actionName: string, _ctx?: TDbActionScopeContext): FilterExpr | undefined | Promise<FilterExpr | undefined>;
|
|
1434
|
+
/**
|
|
1435
|
+
* The read scope a query target (an action request `{ query }`, since
|
|
1436
|
+
* 0.1.147) resolves under, on top of the action's row overlay — so "every
|
|
1437
|
+
* row matching the query" never reaches rows the caller cannot list. A
|
|
1438
|
+
* view resolving a delegated action's query target applies it too.
|
|
1439
|
+
* Default: `transformFilter({})` (the read overlay of `/query`). Throw an
|
|
1440
|
+
* `HttpError` to refuse query targets for the caller (e.g. no read grant).
|
|
1441
|
+
*
|
|
1442
|
+
* Runs as a READ of this controller: it is called in a child of the
|
|
1443
|
+
* action event — of the delegating event for a view resolving a delegated
|
|
1444
|
+
* target — (moost `withControllerContext`, the controller's `query`
|
|
1445
|
+
* handler) after `prepareRequest({ endpoint: "query", controls, filter })`
|
|
1446
|
+
* with the target's own filter and controls — so a permission layer's
|
|
1447
|
+
* per-request state is the READ request's (its read grant, the policy of
|
|
1448
|
+
* the relations its predicates touch), never the action's, and nothing of
|
|
1449
|
+
* it leaks back. The target's query (`q` filter and controls, `exclude`)
|
|
1450
|
+
* is validated in that read context ({@link validateControls},
|
|
1451
|
+
* {@link checkCapabilities}, {@link hasField}), where the client
|
|
1452
|
+
* predicates' {@link transformRelationFilter} also runs: a query target
|
|
1453
|
+
* never filters on, nor counts by, a field the caller can't read.
|
|
1454
|
+
*
|
|
1455
|
+
* @since 0.1.147
|
|
1456
|
+
*/
|
|
1457
|
+
protected queryTargetScope(_action: string): FilterExpr | undefined | Promise<FilterExpr | undefined>;
|
|
841
1458
|
/**
|
|
842
1459
|
* Transform projection before querying.
|
|
843
1460
|
* May return a Promise for async lookups.
|
|
844
1461
|
*/
|
|
845
1462
|
protected transformProjection(projection?: UniqueryControls["$select"]): UniqueryControls["$select"] | undefined | Promise<UniqueryControls["$select"] | undefined>;
|
|
1463
|
+
/**
|
|
1464
|
+
* The shared projection step of `/query`, `/pages`, `/geo` and `/one`:
|
|
1465
|
+
* splits the declared decoration keys out of the wire `$select`
|
|
1466
|
+
* ({@link DecorationPlanner.plan}), runs {@link transformProjection} on the
|
|
1467
|
+
* rest (decoration keys never reach it — a permission layer needs no
|
|
1468
|
+
* change; their `requires` paths are added so the hook's inputs are read),
|
|
1469
|
+
* then seals every projection level. `finish` completes it once the
|
|
1470
|
+
* endpoint is past its own checks: the preferred-id widening (an
|
|
1471
|
+
* `HttpError` for a mixed `$select`) and the decoration step — what every
|
|
1472
|
+
* read endpoint then passes to {@link _runReadWithActions}.
|
|
1473
|
+
*/
|
|
1474
|
+
private _projectRead;
|
|
846
1475
|
private widenPreferredIdProjection;
|
|
847
1476
|
private _widenArrayProjection;
|
|
848
1477
|
private _widenMapProjection;
|
|
@@ -866,6 +1495,13 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
866
1495
|
private _resolveProjectionForAugmenter;
|
|
867
1496
|
/** Row/rows envelopes narrowed to {@link allowedActions}; no hook call when neither it nor `applyMetaOverlay` is overridden. */
|
|
868
1497
|
private _resolveAugmentEnvelopes;
|
|
1498
|
+
/**
|
|
1499
|
+
* Once per class: `actionRowScope` is overridden (a permission layer always
|
|
1500
|
+
* does) while the controller's OWN row-level actions can't be scoped — the
|
|
1501
|
+
* readable has no identity, so `$actions` withholds every action with a
|
|
1502
|
+
* non-empty row scope. Silent for controllers without own row actions.
|
|
1503
|
+
*/
|
|
1504
|
+
private _warnScopeWithoutIdentity;
|
|
869
1505
|
/**
|
|
870
1506
|
* Returns a widened `$select` only when at least one `requiredFields` entry
|
|
871
1507
|
* is missing; `null` means "no widening needed". A field the request may
|
|
@@ -875,17 +1511,27 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
875
1511
|
private _widenSelectForActions;
|
|
876
1512
|
private _prepareAugmentation;
|
|
877
1513
|
/**
|
|
878
|
-
* {@link actionRowScope} of every
|
|
879
|
-
* `overlay`), grouped by filter object
|
|
880
|
-
*
|
|
1514
|
+
* {@link actionRowScope} of every action in `names` for one evaluation
|
|
1515
|
+
* (`ctx`, in parallel, alongside `overlay`), grouped by filter — object
|
|
1516
|
+
* identity first, then structural equality (`filterKey`), so equal filters
|
|
1517
|
+
* share one query; each group's filter is composed with the row overlay
|
|
1518
|
+
* exactly as the gate composes it.
|
|
881
1519
|
*/
|
|
882
1520
|
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
1521
|
/**
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
1522
|
+
* Per action of `names`, a per-row mask (parallel to `rows`) of rows
|
|
1523
|
+
* outside its {@link actionRowScope} — one id-only read per distinct
|
|
1524
|
+
* filter. The hook sees the rows' identities (`purpose`); a row lacking
|
|
1525
|
+
* its identity is in no scope. No candidate → no hook call, every row
|
|
1526
|
+
* masked for every action (fail closed). `undefined` when the hook is not
|
|
1527
|
+
* overridden or `rows` is empty.
|
|
1528
|
+
*/
|
|
1529
|
+
private _scopeMasks;
|
|
1530
|
+
/**
|
|
1531
|
+
* The row filter {@link actionRowScope} returns for `action` and the
|
|
1532
|
+
* candidates of `ctx` — `undefined` when empty or when the hook is not
|
|
1533
|
+
* overridden (no call). THE per-action scope rule: the action gate,
|
|
1534
|
+
* `$actions` and `/meta/actions` all read it here.
|
|
889
1535
|
*/
|
|
890
1536
|
private _actionScope;
|
|
891
1537
|
/**
|
|
@@ -894,12 +1540,41 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
894
1540
|
*/
|
|
895
1541
|
private _gateFields;
|
|
896
1542
|
/**
|
|
897
|
-
* @internal The action gate's overlay (reached by the `@DbAction`
|
|
898
|
-
* interceptor through {@link ACTION_OVERLAY}): {@link rowOverlay}
|
|
899
|
-
* action's {@link actionRowScope}
|
|
900
|
-
*
|
|
1543
|
+
* @internal The action gate's row overlay (reached by the `@DbAction`
|
|
1544
|
+
* interceptor through {@link ACTION_OVERLAY}): {@link rowOverlay}. Since
|
|
1545
|
+
* 0.1.147 the action's {@link actionRowScope} is applied separately to the
|
|
1546
|
+
* loaded candidates ({@link ACTION_SCOPE}).
|
|
901
1547
|
*/
|
|
902
|
-
[ACTION_OVERLAY](
|
|
1548
|
+
[ACTION_OVERLAY](): Promise<FilterExpr | undefined>;
|
|
1549
|
+
/**
|
|
1550
|
+
* @internal {@link resolveRowIds} for an action's validated ids (since
|
|
1551
|
+
* 0.1.148): the output validated, duplicate identities collapsed, and the
|
|
1552
|
+
* ids as the client sent them kept for the error bodies and summaries.
|
|
1553
|
+
*/
|
|
1554
|
+
[ROW_RESOLVE_IDS](ids: readonly Record<string, unknown>[], ctx: TDbRowIdsContext): Promise<TAppliedIds>;
|
|
1555
|
+
/** @internal {@link actionRowScope} for the gate's loaded candidates (since 0.1.147). */
|
|
1556
|
+
[ACTION_SCOPE](action: string, ctx: TDbActionScopeContext): Promise<FilterExpr | undefined>;
|
|
1557
|
+
/** @internal `true` when {@link actionRowScope} is overridden (since 0.1.147). */
|
|
1558
|
+
get [ACTION_SCOPED](): boolean;
|
|
1559
|
+
/**
|
|
1560
|
+
* @internal Resolves a query target (phase 1, since 0.1.147): the `query`
|
|
1561
|
+
* body validated (shape, `$search` / `$index` only, the read's capability
|
|
1562
|
+
* and index gate), then ONE read of `select` ordered by identity —
|
|
1563
|
+
* `filter (+ $search) ∧ overlay ∧ queryTargetScope ∧ ¬exclude`, at most
|
|
1564
|
+
* `cap + 1` rows. More than the cap → 400 `TARGET_TOO_LARGE`; a count
|
|
1565
|
+
* other than `expectCount` → 409 `TARGET_CHANGED`. `load` re-reads rows
|
|
1566
|
+
* of the snapshot that still match the same target (phase 2) — except for
|
|
1567
|
+
* the first batch, which directly follows the snapshot.
|
|
1568
|
+
*/
|
|
1569
|
+
[RESOLVE_TARGET](req: TTargetRequest): Promise<TResolvedTarget>;
|
|
1570
|
+
/**
|
|
1571
|
+
* Runs `fn` as a READ of this controller (since 0.1.147): in a child of
|
|
1572
|
+
* the current event whose controller context is this controller's `query`
|
|
1573
|
+
* handler, after `prepareRequest({ endpoint: "query", controls, filter })` — the
|
|
1574
|
+
* request-scoped state a permission layer builds there (read grant, field
|
|
1575
|
+
* visibility) is the read's and stays in the child.
|
|
1576
|
+
*/
|
|
1577
|
+
private _asRead;
|
|
903
1578
|
/**
|
|
904
1579
|
* `@db.column.searchable` paths, minus anything the adapter can't filter
|
|
905
1580
|
* (JSON storage, encrypted), `@db.writeOnly` fields and navigation
|
|
@@ -910,11 +1585,13 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
910
1585
|
/**
|
|
911
1586
|
* `select` without the `sealed` paths (see {@link _sealControls}); an
|
|
912
1587
|
* exclusion of them is forced when there is no projection, or when every
|
|
913
|
-
* requested path was sealed.
|
|
1588
|
+
* requested path was sealed. An inclusion naming a PARENT of a sealed path
|
|
1589
|
+
* (`secret` over a write-only `secret.hash`) is replaced by the parent's
|
|
1590
|
+
* unsealed leaves, so a sealed descendant never rides along with it.
|
|
914
1591
|
*/
|
|
915
1592
|
private _sealSelect;
|
|
916
|
-
/**
|
|
917
|
-
private
|
|
1593
|
+
/** Own leaf field paths (no navigation, no ignored field) of `readable`, once per readable. */
|
|
1594
|
+
private _leavesOf;
|
|
918
1595
|
/**
|
|
919
1596
|
* Merges the `$search` fallback into the filter: a case-insensitive literal
|
|
920
1597
|
* substring match OR'd across the `@db.column.searchable` fields, `$and`-combined
|
|
@@ -956,11 +1633,17 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
956
1633
|
* (reach them through the parent row), a `/one` 404, or value-help
|
|
957
1634
|
* controllers.
|
|
958
1635
|
*
|
|
959
|
-
*
|
|
960
|
-
* `$actions` and `$distance`, so they can never collide with a field
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
*
|
|
1636
|
+
* Two kinds of keys. **Undeclared** keys: name them with a `$` prefix, like
|
|
1637
|
+
* `$actions` and `$distance`, so they can never collide with a field; they
|
|
1638
|
+
* are always kept and a client cannot select them. **Declared** keys
|
|
1639
|
+
* ({@link DbDecorations}, since 0.1.148): list them with `@DbDecorations`,
|
|
1640
|
+
* and a client may name them in `$select`; compute only `ctx.decorations`,
|
|
1641
|
+
* read your inputs from the columns `requires` names, and any declared key
|
|
1642
|
+
* not in `ctx.decorations` — and every column added only for the hook — is
|
|
1643
|
+
* removed from the rows afterwards. Do not overwrite `$actions` or
|
|
1644
|
+
* `$disabledReasons`. Without `@DbDecorations`, columns the hook needs but
|
|
1645
|
+
* the client did not select must be added in {@link transformProjection} —
|
|
1646
|
+
* they are then part of the response.
|
|
964
1647
|
*
|
|
965
1648
|
* ```ts
|
|
966
1649
|
* protected async decorateRows(rows: Record<string, unknown>[], ctx: TDbDecorateContext) {
|
|
@@ -979,24 +1662,154 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
979
1662
|
* subclass implements it. Returns the hook's result — `undefined`, with no
|
|
980
1663
|
* promise or microtask, when there is no hook or it is synchronous.
|
|
981
1664
|
*/
|
|
982
|
-
private _finishRows;
|
|
983
|
-
/**
|
|
984
|
-
|
|
1665
|
+
private _finishRows;
|
|
1666
|
+
/**
|
|
1667
|
+
* `$actions` augmentation (when `prep`) — own actions, then the delegated
|
|
1668
|
+
* ones (`delegated`: per delegation, per row) — then {@link decorateRows}
|
|
1669
|
+
* when implemented.
|
|
1670
|
+
*/
|
|
1671
|
+
private _augmentAndDecorate;
|
|
1672
|
+
/**
|
|
1673
|
+
* The app of the current event, through DI — never the one this
|
|
1674
|
+
* (singleton) instance was constructed in, which may be gone (a re-booted
|
|
1675
|
+
* app, a hot reload).
|
|
1676
|
+
*/
|
|
1677
|
+
private _currentApp;
|
|
1678
|
+
/** The class's `@DbActionsFrom` delegations, validated on first use (per app). */
|
|
1679
|
+
private _delegations;
|
|
1680
|
+
/**
|
|
1681
|
+
* The delegations this request may use: every id path visible
|
|
1682
|
+
* ({@link hasField}) and kept by {@link transformProjection} — a
|
|
1683
|
+
* delegation whose ids the request can't read is dropped.
|
|
1684
|
+
*/
|
|
1685
|
+
private _activeDelegations;
|
|
1686
|
+
/**
|
|
1687
|
+
* Runs `fn` on the delegation's source controller (this event's instance)
|
|
1688
|
+
* evaluated as itself ({@link runAsController}); a 401 / 403 from its
|
|
1689
|
+
* `prepareRequest` (the caller holds no grant there) yields `refused`.
|
|
1690
|
+
*/
|
|
1691
|
+
private _onSource;
|
|
1692
|
+
/** Per row, the source's verdict for the row it maps to (`undefined`: no source id). */
|
|
1693
|
+
private _delegatedRowVerdicts;
|
|
1694
|
+
/** The `/meta.actions` entries of the delegations the caller may run (per its source). */
|
|
1695
|
+
private _delegatedInfos;
|
|
1696
|
+
/**
|
|
1697
|
+
* The cached `/meta` envelope through {@link applyMetaOverlay}, plus —
|
|
1698
|
+
* since 0.1.147 — the `@DbActionsFrom` actions the caller may run as their
|
|
1699
|
+
* source decides (`allowedActions` of the source, evaluated as itself) and,
|
|
1700
|
+
* under an overridden {@link hasField}, the search surface narrowed to the
|
|
1701
|
+
* indexes the request may use (the index gate's rule). Delegated entries
|
|
1702
|
+
* never pass this controller's own `applyMetaOverlay`.
|
|
1703
|
+
*/
|
|
1704
|
+
protected resolveMeta(): TMetaResponse | Promise<TMetaResponse>;
|
|
1705
|
+
/**
|
|
1706
|
+
* The delegated part of `GET /meta/actions…` for the source ids `sourceIdOf`
|
|
1707
|
+
* derives from the request (`undefined`: not derivable by key renaming —
|
|
1708
|
+
* the delegation is left out).
|
|
1709
|
+
*/
|
|
1710
|
+
private _delegatedAvailable;
|
|
1711
|
+
/**
|
|
1712
|
+
* @internal Source side of a delegation: the `$actions` verdicts of `names`
|
|
1713
|
+
* for `ids` (aligned; `undefined` = not found under the row overlay), as
|
|
1714
|
+
* this controller's own `$actions` / `GET /meta/actions` compute them —
|
|
1715
|
+
* its `prepareRequest("availableActions")`, `allowedActions`, row overlay,
|
|
1716
|
+
* `actionRowScope` (`purpose: "rows"`) and `disabled`.
|
|
1717
|
+
*/
|
|
1718
|
+
[ACTION_VERDICTS](ids: Record<string, unknown>[], names: readonly string[]): Promise<Array<TDbAvailableActions$1 | undefined>>;
|
|
1719
|
+
/** @internal Source side of a delegation: `GET /meta/actions` for one id, `names` only. */
|
|
1720
|
+
[AVAILABLE_ACTIONS](id: Record<string, unknown>, names: readonly string[]): Promise<TDbAvailableActions$1>;
|
|
1721
|
+
/** @internal Source side of a delegation: the `names` the caller may run (`allowedActions`). */
|
|
1722
|
+
[ALLOWED_ACTIONS](names: readonly string[]): Promise<readonly string[]>;
|
|
1723
|
+
/** {@link _resolveAugmentEnvelopes} restricted to `names`. */
|
|
1724
|
+
private _envelopesNamed;
|
|
1725
|
+
/**
|
|
1726
|
+
* Shared `query` / `pages` / `geo` pipeline: prepare actions augmentation + read
|
|
1727
|
+
* strategy in parallel, pre-widen $select for `requiredFields`, run
|
|
1728
|
+
* `exec`, and augment `result.data` with `$actions` when the request set
|
|
1729
|
+
* `$actions=true`, then run {@link decorateRows}. Caller dispatches the
|
|
1730
|
+
* strategy to its read-method family (count vs no-count).
|
|
1731
|
+
*/
|
|
1732
|
+
private _runReadWithActions;
|
|
1733
|
+
/**
|
|
1734
|
+
* Maps the ids an id-addressed endpoint received to the rows' current ids
|
|
1735
|
+
* (since 0.1.148) — the seam for stale or alias ids, e.g. a natural key that
|
|
1736
|
+
* was renamed. Called once per request, after {@link prepareRequest} and
|
|
1737
|
+
* after the request's own validation, before anything reads the row, by:
|
|
1738
|
+
*
|
|
1739
|
+
* | `ctx.purpose` | endpoint | `ids` |
|
|
1740
|
+
* | --- | --- | --- |
|
|
1741
|
+
* | `"one"` | `GET /one/:id`, `GET /one?…` | one id: the path string, or the `?`-form identification object |
|
|
1742
|
+
* | `"available"` | `GET /meta/actions/:id`, `?…` (and a view asking its source) | one id, as above |
|
|
1743
|
+
* | `"remove"` | `DELETE /:id`, `DELETE /?…` (`AsDbController`) | one id, as above |
|
|
1744
|
+
* | `"action"` | an action route | the body's validated ids (one for a `'row'` action) |
|
|
1745
|
+
*
|
|
1746
|
+
* Contract:
|
|
1747
|
+
*
|
|
1748
|
+
* - Return one id per input id, index-aligned (anything else is a 500). An
|
|
1749
|
+
* id that already names a row must come back UNCHANGED — the current
|
|
1750
|
+
* holder of a key wins over an alias; so must an id you cannot resolve
|
|
1751
|
+
* (never throw for an unknown alias: a custom error is an oracle — the
|
|
1752
|
+
* endpoint then answers its normal miss).
|
|
1753
|
+
* - The output is validated (a server bug is a 500, never a client 400): a
|
|
1754
|
+
* scalar is resolved like a path scalar (primary key first, then the
|
|
1755
|
+
* visible unique keys, inside the row overlay); an object must be one of
|
|
1756
|
+
* the visible identifications ({@link idSource}); an `"action"` id must
|
|
1757
|
+
* be such an object.
|
|
1758
|
+
* - The resolved id is never trusted for access: the endpoint still reads
|
|
1759
|
+
* or deletes it under {@link rowOverlay} and the visible identifications.
|
|
1760
|
+
* `ctx.overlay` is that overlay — resolve INSIDE it when an alias could
|
|
1761
|
+
* name several rows, so a row the caller can't reach never shadows one
|
|
1762
|
+
* they can. Don't log or return the canonical id in errors.
|
|
1763
|
+
* - Handlers (`@DbActionID()`, `useDbActionId()`), {@link rowOverlay} reads,
|
|
1764
|
+
* {@link actionRowScope} and `onRemove` / `guardRemove` receive the
|
|
1765
|
+
* resolved ids; error bodies and `summary()` echo the ids the client sent.
|
|
1766
|
+
* - Write bodies (`POST` / `PUT` / `PATCH`) are not resolved — use
|
|
1767
|
+
* `onWrite`. Not called by value-help controllers, `$actions` on a read
|
|
1768
|
+
* or a query target. Not overriding it costs nothing.
|
|
1769
|
+
*
|
|
1770
|
+
* It is NOT overridden by {@link resolveRowFilter}, which does not take part
|
|
1771
|
+
* in `/one` for real tables and views.
|
|
1772
|
+
*
|
|
1773
|
+
* ```ts
|
|
1774
|
+
* protected async resolveRowIds(ids: readonly TDbRowIdInput[], ctx: TDbRowIdsContext) {
|
|
1775
|
+
* return Promise.all(ids.map(async (id) => {
|
|
1776
|
+
* const key = typeof id === "object" ? id.code : id
|
|
1777
|
+
* if (typeof key !== "string") return id
|
|
1778
|
+
* // the current holder of the key wins; consult the alias table on a miss
|
|
1779
|
+
* // resolve INSIDE the overlay: a row the caller cannot reach never wins
|
|
1780
|
+
* const inScope = (code: string) =>
|
|
1781
|
+
* this.readable.count({ filter: ctx.overlay ? { $and: [{ code }, ctx.overlay] } : { code } })
|
|
1782
|
+
* if (await inScope(key)) return id
|
|
1783
|
+
* const alias = await aliases.findOne({ filter: { oldCode: key } })
|
|
1784
|
+
* if (!alias || !(await inScope(alias.newCode))) return id
|
|
1785
|
+
* return typeof id === "object" ? { code: alias.newCode } : alias.newCode
|
|
1786
|
+
* }))
|
|
1787
|
+
* }
|
|
1788
|
+
* ```
|
|
1789
|
+
*
|
|
1790
|
+
* @since 0.1.148
|
|
1791
|
+
*/
|
|
1792
|
+
protected resolveRowIds(ids: readonly TDbRowIdInput[], _ctx: TDbRowIdsContext): readonly TDbRowIdInput[] | Promise<readonly TDbRowIdInput[]>;
|
|
1793
|
+
/** {@link resolveRowIds} with its output validated (a server bug is a 500). */
|
|
1794
|
+
private _runResolveRowIds;
|
|
985
1795
|
/**
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
990
|
-
* strategy to its read-method family (count vs no-count).
|
|
1796
|
+
* @internal The one id of an id-addressed endpoint through
|
|
1797
|
+
* {@link resolveRowIds} (identity, at no cost, when it is not overridden)
|
|
1798
|
+
* and the row overlay it was resolved inside — computed once, for the
|
|
1799
|
+
* endpoint's read or delete under that overlay.
|
|
991
1800
|
*/
|
|
992
|
-
|
|
1801
|
+
protected _resolveWithOverlay(id: TDbRowIdInput, purpose: Exclude<TDbRowIdPurpose, "action">): Promise<{
|
|
1802
|
+
id: TDbRowIdInput;
|
|
1803
|
+
overlay: FilterExpr | undefined;
|
|
1804
|
+
}>;
|
|
993
1805
|
/**
|
|
994
1806
|
* The filter addressing exactly the ONE row `id` means — the readable's
|
|
995
1807
|
* PK-first `resolveRowFilter` (since 0.1.143) under this request's
|
|
996
1808
|
* identifications (`_idOpts`). `scope` (the row overlay) restricts which
|
|
997
1809
|
* rows count while the id is pinned, so a row outside it never shadows one
|
|
998
1810
|
* inside it. Readables without it (partial mocks) fall back to
|
|
999
|
-
* `resolveIdFilter`.
|
|
1811
|
+
* `resolveIdFilter`. Not an alias seam: `/one` reads through `findOneByRow`
|
|
1812
|
+
* on every real table or view — map stale ids in {@link resolveRowIds}.
|
|
1000
1813
|
*/
|
|
1001
1814
|
protected resolveRowFilter(id: unknown, scope?: FilterExpr): Promise<FilterExpr | null>;
|
|
1002
1815
|
/**
|
|
@@ -1087,18 +1900,95 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
1087
1900
|
* `{ actions: [] }`. {@link prepareRequest} runs first with
|
|
1088
1901
|
* `endpoint: "availableActions"`.
|
|
1089
1902
|
*/
|
|
1090
|
-
availableActionsById(id: string): Promise<TDbAvailableActions>;
|
|
1903
|
+
availableActionsById(id: string): Promise<TDbAvailableActions$1>;
|
|
1091
1904
|
/**
|
|
1092
1905
|
* **GET /meta/actions?field1=val1&…** — {@link availableActionsById} by
|
|
1093
1906
|
* composite key (composite primary key or compound unique index), the
|
|
1094
1907
|
* `/one?…` rules.
|
|
1095
1908
|
*/
|
|
1096
|
-
availableActions(query: Record<string, string>): Promise<TDbAvailableActions | HttpError>;
|
|
1909
|
+
availableActions(query: Record<string, string>): Promise<TDbAvailableActions$1 | HttpError>;
|
|
1910
|
+
/** Every field of every identification (primary key and unique indexes) the view addresses a row by. */
|
|
1911
|
+
private _identificationFields;
|
|
1912
|
+
/**
|
|
1913
|
+
* A delegation's source id: each source id field from the row's mapped path
|
|
1914
|
+
* of the RESOLVED `id`. A path of ANY of the view's identifications
|
|
1915
|
+
* (`consumed` — not just the one the request matched: `?id=1&code=T-OLD` names
|
|
1916
|
+
* `code` too) is NEVER taken from the raw `?` query: that value may be an
|
|
1917
|
+
* alias `resolveRowIds` rewrote, and the source would see (and answer for)
|
|
1918
|
+
* it. Paths outside the identification fall back to `fallback`'s (the raw
|
|
1919
|
+
* query's) value; `undefined` when a path has none.
|
|
1920
|
+
*/
|
|
1921
|
+
private _sourceIdOf;
|
|
1922
|
+
/**
|
|
1923
|
+
* {@link _availableActions} for a request id (`names`: only those actions):
|
|
1924
|
+
* through {@link resolveRowIds} (`"available"`) first, the row overlay
|
|
1925
|
+
* computed once for both — and the resolved id returned as an object (a
|
|
1926
|
+
* scalar is the single-field `preferredId` value) for the delegated part to
|
|
1927
|
+
* derive its source id from.
|
|
1928
|
+
*/
|
|
1929
|
+
private _availableResolved;
|
|
1930
|
+
/**
|
|
1931
|
+
* **POST /delegated-actions/:name** — a query target for a `@DbActionsFrom`
|
|
1932
|
+
* action whose source action declares `queryTarget` (since 0.1.147); the
|
|
1933
|
+
* action's `/meta` entry points here (`queryTarget.url`). Body
|
|
1934
|
+
* `{ query: { q, exclude?, expectCount?, maxRows?, dryRun? }, input? }`.
|
|
1935
|
+
*
|
|
1936
|
+
* The source must list the action for the caller (its `allowedActions`,
|
|
1937
|
+
* as `$actions` does) — else 403, dry runs included. This controller then
|
|
1938
|
+
* resolves the rows matching `q` under its own read scope (`transformFilter`
|
|
1939
|
+
* ∧ {@link queryTargetScope} ∧ ¬`exclude` — `exclude` entries use this
|
|
1940
|
+
* controller's identifications or the delegation's id paths, and the
|
|
1941
|
+
* source rows they map to are left out even when other view rows map to
|
|
1942
|
+
* them too), maps them to source ids (a row without one is skipped as
|
|
1943
|
+
* `"unmapped"`), then runs the SOURCE's action route on them in batches
|
|
1944
|
+
* inside this request (`MoostHttp.invoke`) — its guards, `prepareRequest`,
|
|
1945
|
+
* row overlay, `actionRowScope` and `disabled` re-check every batch, and
|
|
1946
|
+
* every body reader of the source sees the batch's `{ ids, input }`. Before
|
|
1947
|
+
* each batch the view rows are re-checked against the target: a source id
|
|
1948
|
+
* none of whose view rows still matches is skipped as `"stale"`; ids the
|
|
1949
|
+
* source's gate refuses are skipped with their reasons. A batch failing
|
|
1950
|
+
* once a source handler started (in it or an earlier batch) stops the run:
|
|
1951
|
+
* the answer is the partial summary with `aborted` and every id not run
|
|
1952
|
+
* listed in `failed`. A failure before any source handler started (a 403,
|
|
1953
|
+
* the source's `@InputForm` 400, …) is the request's error — nothing ran,
|
|
1954
|
+
* and every batch carries the same `input`. The
|
|
1955
|
+
* `message` (string) each batch's handler returned is passed on:
|
|
1956
|
+
* `messages` per batch, `message` the distinct ones joined by newlines. A
|
|
1957
|
+
* dry run answers `{ matched }`; otherwise the answer is the run's
|
|
1958
|
+
* {@link TDbActionTargetSummary}. An action of the delegation that takes no
|
|
1959
|
+
* query target answers 400 `TARGET_INVALID`. `prepareRequest` runs first
|
|
1960
|
+
* with `endpoint: "delegatedAction"`. Registered only on controllers
|
|
1961
|
+
* declaring `@DbActionsFrom`.
|
|
1962
|
+
*/
|
|
1963
|
+
runDelegatedOnQuery(): Promise<TDbActionTargetSummary$1 | {
|
|
1964
|
+
matched: number;
|
|
1965
|
+
}>;
|
|
1966
|
+
/**
|
|
1967
|
+
* The id keys (`idMap` fields) of the source rows `exclude` leaves out of a
|
|
1968
|
+
* delegated target — an entry by the id paths names its source row
|
|
1969
|
+
* directly; one by a view identification names the source row of that view
|
|
1970
|
+
* row (read without overlay: excluding can only narrow the run).
|
|
1971
|
+
*/
|
|
1972
|
+
private _excludedSourceKeys;
|
|
1973
|
+
/**
|
|
1974
|
+
* The source ids of `batch` that some view row still maps to under the
|
|
1975
|
+
* target's query (phase-2 re-check); the others are recorded in
|
|
1976
|
+
* `summary.skipped` as `"stale"`.
|
|
1977
|
+
*/
|
|
1978
|
+
private _stillTargeted;
|
|
1097
1979
|
/**
|
|
1098
1980
|
* The row resolves ONCE, like `/one/:id` under {@link rowOverlay}; scoped
|
|
1099
|
-
* actions are then checked on it exactly like
|
|
1981
|
+
* actions are then checked on it (`purpose: "available"`) exactly like
|
|
1982
|
+
* `$actions` rows.
|
|
1100
1983
|
*/
|
|
1101
1984
|
private _availableActions;
|
|
1985
|
+
/**
|
|
1986
|
+
* Per row (`undefined` = not found → no verdict): the actions of
|
|
1987
|
+
* `envelopes` it lists — minus those `masks` put it outside of, and those
|
|
1988
|
+
* whose `disabled` rule refuses it on the fields its gate loads
|
|
1989
|
+
* (`fieldsOf`, parallel to `envelopes`) — with the refusal reasons.
|
|
1990
|
+
*/
|
|
1991
|
+
private _verdicts;
|
|
1102
1992
|
/**
|
|
1103
1993
|
* **GET /meta** — returns table/view metadata for UI.
|
|
1104
1994
|
*
|
|
@@ -1156,7 +2046,9 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
|
|
|
1156
2046
|
*/
|
|
1157
2047
|
protected onWrite(action: TDbWriteAction$1, data: unknown): unknown;
|
|
1158
2048
|
/**
|
|
1159
|
-
* Intercepts delete operations.
|
|
2049
|
+
* Intercepts delete operations. Receives the id {@link resolveRowIds}
|
|
2050
|
+
* resolved (the one the request carried when it is not overridden; since
|
|
2051
|
+
* 0.1.148). Return `undefined` to abort (500 "Not
|
|
1160
2052
|
* deleted"); return an `Error` instance to respond with that error.
|
|
1161
2053
|
* Runs outside any transaction. May be async (e.g. to resolve composite
|
|
1162
2054
|
* ids from external state).
|
|
@@ -1264,8 +2156,16 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
|
|
|
1264
2156
|
private _deleteOrThrow;
|
|
1265
2157
|
/**
|
|
1266
2158
|
* **POST /** — inserts one or many records.
|
|
2159
|
+
*
|
|
2160
|
+
* `?$onConflict=ignore` (since 0.1.148) skips rows colliding on the primary
|
|
2161
|
+
* key or a unique index instead of answering 409. The response then is
|
|
2162
|
+
* `{ insertedId?, conflict }` for an object body and
|
|
2163
|
+
* `{ insertedCount, insertedIds, inserted, conflicts }` for an array body.
|
|
2164
|
+
* Any other `$` control on POST answers 400.
|
|
1267
2165
|
*/
|
|
1268
|
-
insert(payload: unknown): Promise<unknown>;
|
|
2166
|
+
insert(payload: unknown, url?: string): Promise<unknown>;
|
|
2167
|
+
/** The only POST control: `$onConflict` (`error` | `ignore`). Anything else `$…` → 400. */
|
|
2168
|
+
private _readOnConflict;
|
|
1269
2169
|
/**
|
|
1270
2170
|
* **PUT /** — fully replaces one or many records matched by primary key.
|
|
1271
2171
|
*
|
|
@@ -1530,165 +2430,58 @@ declare class AsJsonValueHelpController<T extends TAtscriptAnnotatedType = TAtsc
|
|
|
1530
2430
|
private normalizeSort;
|
|
1531
2431
|
}
|
|
1532
2432
|
//#endregion
|
|
1533
|
-
//#region src/
|
|
1534
|
-
/**
|
|
1535
|
-
|
|
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>;
|
|
2433
|
+
//#region src/decorations/db-decorations.decorator.d.ts
|
|
2434
|
+
/** Options of {@link DbDecorations}. @since 0.1.148 */
|
|
2435
|
+
interface TDbDecorationsOpts<D = Record<string, unknown>> {
|
|
1614
2436
|
/**
|
|
1615
|
-
*
|
|
1616
|
-
*
|
|
1617
|
-
*
|
|
1618
|
-
*
|
|
1619
|
-
*
|
|
2437
|
+
* Per decoration key, the bound readable's field paths `decorateRows` reads
|
|
2438
|
+
* to compute it. Those paths are selected (and kept visible) automatically
|
|
2439
|
+
* and stripped from the response unless the client selected them. A
|
|
2440
|
+
* decoration that reveals a field's data MUST list it: the decoration is
|
|
2441
|
+
* then served — and listed in `/meta` — only while that field is visible.
|
|
1620
2442
|
*/
|
|
1621
|
-
|
|
2443
|
+
requires?: { [K in keyof D & string]?: readonly string[] };
|
|
1622
2444
|
}
|
|
1623
|
-
/**
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
};
|
|
2445
|
+
/** Class metadata written by {@link DbDecorations}. */
|
|
2446
|
+
interface TDbDecorationsMeta {
|
|
2447
|
+
/** The declared interface (a plain object type, no `@db.table` / `@db.view`). */
|
|
2448
|
+
type: TAtscriptAnnotatedType;
|
|
2449
|
+
/** Decoration key → the readable's field paths it reads. */
|
|
2450
|
+
requires: Readonly<Record<string, readonly string[]>>;
|
|
1661
2451
|
}
|
|
1662
|
-
type DbActionsEntryWithGate<TRow, R extends readonly FlatKey<TRow>[]> = DbActionsEntryCommonBase & GateOpts<TRow, R>;
|
|
1663
2452
|
/**
|
|
1664
|
-
*
|
|
2453
|
+
* Declares display-only (decoration) fields of a table or view controller —
|
|
2454
|
+
* values `decorateRows` computes and attaches to rows (since 0.1.148).
|
|
1665
2455
|
*
|
|
1666
|
-
*
|
|
1667
|
-
* -
|
|
1668
|
-
*
|
|
1669
|
-
*
|
|
2456
|
+
* `type` is a plain atscript interface (no `@db.table` / `@db.view`) whose
|
|
2457
|
+
* top-level props are the decorations: their `@meta.label`, `@expect.*` and
|
|
2458
|
+
* `@ui.*` annotations travel in `/meta.decorations`, each key is listed in
|
|
2459
|
+
* `/meta.fields` with `decoration: true`, and a client may name it in
|
|
2460
|
+
* `$select`. A decoration is never filterable, sortable or groupable, and it
|
|
2461
|
+
* is not part of `/meta.type` (forms and write validation never see it).
|
|
2462
|
+
*
|
|
2463
|
+
* ```ts
|
|
2464
|
+
* @TableController(TicketTable)
|
|
2465
|
+
* @DbDecorations(TicketDecorations, { requires: { ownerName: ["ownerId"] } })
|
|
2466
|
+
* export class TicketsController extends AsDbController<typeof TicketTable> {
|
|
2467
|
+
* protected async decorateRows(rows: Record<string, unknown>[], ctx: TDbDecorateContext) {
|
|
2468
|
+
* if (ctx.decorations.has("ownerName")) {
|
|
2469
|
+
* // read rows[i].ownerId, set rows[i].ownerName
|
|
2470
|
+
* }
|
|
2471
|
+
* }
|
|
2472
|
+
* }
|
|
2473
|
+
* ```
|
|
2474
|
+
*
|
|
2475
|
+
* Validated once per class at first use (a `[moost-db]` error): the type is an
|
|
2476
|
+
* object interface; keys are top-level identifiers that collide with no field
|
|
2477
|
+
* or relation of the readable; every `requires` path is an own, readable
|
|
2478
|
+
* (not `@db.writeOnly`) field or a parent object of own fields (on SQL a nested
|
|
2479
|
+
* object is flattened to leaf columns; the hook still gets the whole object). Inherited under `@Inherit()`. Not supported on
|
|
2480
|
+
* value-help controllers.
|
|
2481
|
+
*
|
|
2482
|
+
* @since 0.1.148
|
|
1670
2483
|
*/
|
|
1671
|
-
|
|
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>> };
|
|
2484
|
+
declare function DbDecorations<D extends TAtscriptAnnotatedType>(type: D, opts?: TDbDecorationsOpts<TAtscriptDataType<D>>): ClassDecorator;
|
|
1692
2485
|
//#endregion
|
|
1693
2486
|
//#region src/actions/keys.d.ts
|
|
1694
2487
|
type TDbActionRowMarker = true;
|
|
@@ -1707,6 +2500,18 @@ interface TDbClassActionMeta {
|
|
|
1707
2500
|
name: string;
|
|
1708
2501
|
entry: TDbActionsEntry;
|
|
1709
2502
|
}
|
|
2503
|
+
/**
|
|
2504
|
+
* Class-level entry written by `@DbActionsFrom(source, opts)` (since
|
|
2505
|
+
* 0.1.147): a controller whose row-level actions this controller delegates.
|
|
2506
|
+
*/
|
|
2507
|
+
interface TDbActionsFromMeta {
|
|
2508
|
+
/** Lazy reference to the source controller class. */
|
|
2509
|
+
source: () => Function;
|
|
2510
|
+
/** Source identification field → path in this controller's rows. */
|
|
2511
|
+
idMap?: Record<string, string>;
|
|
2512
|
+
/** Subset of the source's row / rows-level action names (default: all). */
|
|
2513
|
+
actions?: readonly string[];
|
|
2514
|
+
}
|
|
1710
2515
|
/** Param marker kind — informs level inference and ID-resolution shape. */
|
|
1711
2516
|
type TDbActionParamKind = "id" | "ids";
|
|
1712
2517
|
declare module "moost" {
|
|
@@ -1717,12 +2522,15 @@ declare module "moost" {
|
|
|
1717
2522
|
atscript_db_action_row?: TDbActionRowMarker;
|
|
1718
2523
|
atscript_db_action_rows?: TDbActionRowMarker;
|
|
1719
2524
|
atscript_db_endpoint?: TDbRequestEndpoint;
|
|
2525
|
+
atscript_db_actions_from?: TDbActionsFromMeta[];
|
|
2526
|
+
atscript_db_decorations?: TDbDecorationsMeta;
|
|
1720
2527
|
}
|
|
1721
2528
|
interface TMoostParamsMetadata {
|
|
1722
2529
|
atscript_db_action_param?: TDbActionParamKind;
|
|
1723
2530
|
atscript_db_action_row?: TDbActionRowMarker;
|
|
1724
2531
|
atscript_db_action_rows?: TDbActionRowMarker;
|
|
1725
2532
|
atscript_db_action_input_form?: TDbActionInputFormMeta;
|
|
2533
|
+
atscript_db_action_target?: true;
|
|
1726
2534
|
atscript_type?: TAtscriptAnnotatedType;
|
|
1727
2535
|
}
|
|
1728
2536
|
}
|
|
@@ -1775,6 +2583,10 @@ interface AtscriptDbMeta {
|
|
|
1775
2583
|
* {@link getDbEndpoint}.
|
|
1776
2584
|
*/
|
|
1777
2585
|
atscript_db_endpoint?: TDbRequestEndpoint;
|
|
2586
|
+
/** Class-level — written by `@DbActionsFrom(...)` (since 0.1.147). Decorators accumulate. */
|
|
2587
|
+
atscript_db_actions_from?: TDbActionsFromMeta[];
|
|
2588
|
+
/** Class-level — written by `@DbDecorations(...)` (since 0.1.148). */
|
|
2589
|
+
atscript_db_decorations?: TDbDecorationsMeta;
|
|
1778
2590
|
}
|
|
1779
2591
|
/**
|
|
1780
2592
|
* Param-level metadata written by `@atscript/moost-db`'s param
|
|
@@ -1788,6 +2600,8 @@ interface AtscriptDbParamsMeta {
|
|
|
1788
2600
|
atscript_db_action_row?: true;
|
|
1789
2601
|
/** Param-level marker — written by `@DbActionRows()`. */
|
|
1790
2602
|
atscript_db_action_rows?: true;
|
|
2603
|
+
/** Param-level marker — written by `@DbActionTarget()` (since 0.1.147). */
|
|
2604
|
+
atscript_db_action_target?: true;
|
|
1791
2605
|
/**
|
|
1792
2606
|
* Param-level — written by `@InputForm(FormType)`. Carries the
|
|
1793
2607
|
* compiled `.as` class plus its `.name` so `discoverActions` can both
|
|
@@ -2051,7 +2865,8 @@ declare const UseValidationErrorTransform: () => ClassDecorator & MethodDecorato
|
|
|
2051
2865
|
/**
|
|
2052
2866
|
* Mark a controller method as a database action surfaced via `/meta`. Writes
|
|
2053
2867
|
* `atscript_db_action` metadata and, for every `'row'` / `'rows'` action,
|
|
2054
|
-
* registers a Moost interceptor: the gate
|
|
2868
|
+
* registers a Moost interceptor: the batch gate for a `@DbActionTarget()`
|
|
2869
|
+
* handler (since 0.1.147), the gate when `disabled` is set, else the
|
|
2055
2870
|
* bound-table injector that also verifies the ids against the controller's
|
|
2056
2871
|
* row overlay (since 0.1.143). Either first awaits the controller's
|
|
2057
2872
|
* `prepareRequest({ endpoint: "action", action })` when it defines one —
|
|
@@ -2220,28 +3035,6 @@ declare function InputForm<T extends TAtscriptAnnotatedType & {
|
|
|
2220
3035
|
readonly name: string;
|
|
2221
3036
|
}>(formType?: T, validatorOpts?: Partial<TValidatorOptions>): ParameterDecorator;
|
|
2222
3037
|
//#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
3038
|
//#region src/actions/id-cache.d.ts
|
|
2246
3039
|
declare const useDbActionId: import("@wooksjs/event-core").WookComposable<{
|
|
2247
3040
|
load: () => Promise<Record<string, unknown>>;
|
|
@@ -2267,10 +3060,15 @@ declare const useDbActionRows: import("@wooksjs/event-core").WookComposable<{
|
|
|
2267
3060
|
* absent (`'table'`-level).
|
|
2268
3061
|
* - `input` — present only when the action declares an `@InputForm()`
|
|
2269
3062
|
* parameter; carries the form payload the user filled out.
|
|
3063
|
+
* - `query` — a query target instead of `ids` (since 0.1.147): "every row
|
|
3064
|
+
* matching this query", for `'rows'` actions declaring `queryTarget` — see
|
|
3065
|
+
* {@link TDbActionQueryTarget}. Never together with `ids`.
|
|
2270
3066
|
*/
|
|
2271
3067
|
interface DbActionEnvelope {
|
|
2272
3068
|
ids?: unknown;
|
|
2273
3069
|
input?: unknown;
|
|
3070
|
+
/** @since 0.1.147 — validated by the action's gate (`TDbActionQueryTarget`). */
|
|
3071
|
+
query?: unknown;
|
|
2274
3072
|
}
|
|
2275
3073
|
/**
|
|
2276
3074
|
* Cached parse of the action request body. Centralises the shape check so
|
|
@@ -2339,6 +3137,165 @@ declare class ActionDisabledError extends HttpError<ActionDisabledErrorBody> {
|
|
|
2339
3137
|
constructor(action: string, id?: Record<string, unknown>, ids?: Record<string, unknown>[], reasons?: readonly (string | null | undefined)[]);
|
|
2340
3138
|
}
|
|
2341
3139
|
//#endregion
|
|
3140
|
+
//#region src/actions/action-target-error.d.ts
|
|
3141
|
+
/**
|
|
3142
|
+
* Why a query-targeted action request was refused:
|
|
3143
|
+
*
|
|
3144
|
+
* - `TARGET_INVALID` (400) — malformed `query` (unknown key, a control other
|
|
3145
|
+
* than `$search` / `$index`, `ids` and `query` together, an action that
|
|
3146
|
+
* accepts no query target);
|
|
3147
|
+
* - `TARGET_TOO_LARGE` (400) — more rows match than the cap (`cap`);
|
|
3148
|
+
* - `TARGET_CHANGED` (409) — the match count differs from `expectCount`
|
|
3149
|
+
* (`matched` carries the current count).
|
|
3150
|
+
*
|
|
3151
|
+
* @since 0.1.147
|
|
3152
|
+
*/
|
|
3153
|
+
type TActionTargetErrorCode = "TARGET_INVALID" | "TARGET_TOO_LARGE" | "TARGET_CHANGED";
|
|
3154
|
+
/**
|
|
3155
|
+
* Wire body of {@link ActionTargetError} — the moost `ServerError` envelope
|
|
3156
|
+
* plus the `name` discriminator `@atscript/db-client` maps to its typed error.
|
|
3157
|
+
*
|
|
3158
|
+
* @since 0.1.147
|
|
3159
|
+
*/
|
|
3160
|
+
interface ActionTargetErrorBody {
|
|
3161
|
+
name: "ActionTargetError";
|
|
3162
|
+
message: string;
|
|
3163
|
+
statusCode: 400 | 409;
|
|
3164
|
+
code: TActionTargetErrorCode;
|
|
3165
|
+
action: string;
|
|
3166
|
+
/** `TARGET_CHANGED`: the current match count. */
|
|
3167
|
+
matched?: number;
|
|
3168
|
+
/** `TARGET_TOO_LARGE`: the most rows one request may target. */
|
|
3169
|
+
cap?: number;
|
|
3170
|
+
}
|
|
3171
|
+
/**
|
|
3172
|
+
* A refused query target (400 / 409) — see {@link TActionTargetErrorCode}.
|
|
3173
|
+
*
|
|
3174
|
+
* @since 0.1.147
|
|
3175
|
+
*/
|
|
3176
|
+
declare class ActionTargetError extends HttpError<ActionTargetErrorBody> {
|
|
3177
|
+
name: string;
|
|
3178
|
+
constructor(code: TActionTargetErrorCode, action: string, message: string, extra?: {
|
|
3179
|
+
matched?: number;
|
|
3180
|
+
cap?: number;
|
|
3181
|
+
});
|
|
3182
|
+
}
|
|
3183
|
+
//#endregion
|
|
3184
|
+
//#region src/actions/target.d.ts
|
|
3185
|
+
/**
|
|
3186
|
+
* The rows a `'rows'` action runs on, as the handler surface of
|
|
3187
|
+
* `@DbActionTarget()` / {@link useDbActionTarget} (since 0.1.147) — the same
|
|
3188
|
+
* for an id target (`{ ids }`) and a query target (`{ query }`).
|
|
3189
|
+
*
|
|
3190
|
+
* @since 0.1.147
|
|
3191
|
+
*/
|
|
3192
|
+
interface TDbActionTarget<Row = Record<string, unknown>> {
|
|
3193
|
+
/** `"ids"` — the body listed identifiers; `"query"` — a query target. */
|
|
3194
|
+
readonly kind: "ids" | "query";
|
|
3195
|
+
/** Rows the target resolved to (after `exclude`, before the per-batch gate). */
|
|
3196
|
+
readonly matched: number;
|
|
3197
|
+
/**
|
|
3198
|
+
* The target in batches, each already gated: the row overlay, the
|
|
3199
|
+
* action's `actionRowScope` for the batch, `disabled`, and — for a query
|
|
3200
|
+
* target — the query itself (a row that changed out of it is skipped as
|
|
3201
|
+
* `"stale"`). Rows carry the identity fields and the visible
|
|
3202
|
+
* `requiredFields`. Single pass.
|
|
3203
|
+
*/
|
|
3204
|
+
batches(): AsyncIterable<{
|
|
3205
|
+
ids: Record<string, unknown>[];
|
|
3206
|
+
rows: Row[];
|
|
3207
|
+
}>;
|
|
3208
|
+
/** Reports a row the handler could not process — listed in {@link summary}. */
|
|
3209
|
+
fail(id: Record<string, unknown>, reason: string): void;
|
|
3210
|
+
/** What happened so far: matched, processed, skipped (by the gate) and failed rows. */
|
|
3211
|
+
summary(): TDbActionTargetSummary$1;
|
|
3212
|
+
}
|
|
3213
|
+
/**
|
|
3214
|
+
* The current `'rows'` action's {@link TDbActionTarget} (since 0.1.147).
|
|
3215
|
+
* Available in the handler of every `'rows'` action — `@DbActionTarget()`
|
|
3216
|
+
* handlers process it batch by batch; `@DbActionIDs()` / `@DbActionRows()`
|
|
3217
|
+
* handlers read it for `summary()` (the ids `onDisabledRows: 'skip'`
|
|
3218
|
+
* dropped, with their reasons).
|
|
3219
|
+
*
|
|
3220
|
+
* @since 0.1.147
|
|
3221
|
+
*/
|
|
3222
|
+
declare function useDbActionTarget<Row = Record<string, unknown>>(ctx?: EventContext): TDbActionTarget<Row>;
|
|
3223
|
+
/**
|
|
3224
|
+
* Parameter decorator injecting the action's {@link TDbActionTarget}
|
|
3225
|
+
* (since 0.1.147). Makes the action `'rows'` level and switches its gate to
|
|
3226
|
+
* batch mode: the handler iterates `target.batches()`, each batch gated on
|
|
3227
|
+
* its own (skip semantics — rows failing the gate are skipped and reported
|
|
3228
|
+
* in `summary()`, never rejected). Accepts `{ ids }` bodies, and `{ query }`
|
|
3229
|
+
* bodies when the action declares `queryTarget`. Not combinable with
|
|
3230
|
+
* `@DbActionID*` / `@DbActionRow*`.
|
|
3231
|
+
*
|
|
3232
|
+
* ```ts
|
|
3233
|
+
* @Post("actions/close")
|
|
3234
|
+
* @DbAction<Issue>("close", { label: "Close", queryTarget: true })
|
|
3235
|
+
* async close(@DbActionTarget() target: TDbActionTarget<Issue>) {
|
|
3236
|
+
* for await (const { ids } of target.batches()) await closeIssues(ids)
|
|
3237
|
+
* return target.summary()
|
|
3238
|
+
* }
|
|
3239
|
+
* ```
|
|
3240
|
+
*
|
|
3241
|
+
* @since 0.1.147
|
|
3242
|
+
*/
|
|
3243
|
+
declare function DbActionTarget(): ParameterDecorator;
|
|
3244
|
+
//#endregion
|
|
3245
|
+
//#region src/actions/db-actions-from.decorator.d.ts
|
|
3246
|
+
/** Options of {@link DbActionsFrom}. @since 0.1.147 */
|
|
3247
|
+
interface TDbActionsFromOpts {
|
|
3248
|
+
/**
|
|
3249
|
+
* Source identification field → the path in THIS controller's rows that
|
|
3250
|
+
* carries its value. Its keys must be exactly one identification of the
|
|
3251
|
+
* source (its primary key or a unique index). Default: derived from the
|
|
3252
|
+
* view definition — the view column that plainly maps each of the
|
|
3253
|
+
* source's `preferredId` fields. Required when this controller is not
|
|
3254
|
+
* bound to a view.
|
|
3255
|
+
*/
|
|
3256
|
+
idMap?: Record<string, string>;
|
|
3257
|
+
/** The source's row / rows-level actions to delegate. Default: all of them. */
|
|
3258
|
+
actions?: readonly string[];
|
|
3259
|
+
}
|
|
3260
|
+
/**
|
|
3261
|
+
* Lists another controller's row / rows-level actions on THIS controller —
|
|
3262
|
+
* typically a `@ViewController` over the table whose actions the view's
|
|
3263
|
+
* rows stand for (since 0.1.147). The actions stay the source's: its route
|
|
3264
|
+
* runs them (`value`), its gate, permissions, `actionRowScope` and
|
|
3265
|
+
* `disabled` decide; the view only maps its rows to source ids.
|
|
3266
|
+
*
|
|
3267
|
+
* - `/meta.actions` lists them with `owner` (the source's base path) and,
|
|
3268
|
+
* when the ids are renamed, `idMap`; forms come from the source
|
|
3269
|
+
* (`formUrl`).
|
|
3270
|
+
* - `$actions` on the view's rows carries the source's verdict for the row
|
|
3271
|
+
* each view row maps to — what the source's own `GET /meta/actions/:id`
|
|
3272
|
+
* answers for it.
|
|
3273
|
+
* - `GET /meta/actions/:id` on the view answers them when the source id is
|
|
3274
|
+
* the view id renamed (`idMap` paths ⊆ the id used).
|
|
3275
|
+
* - A source action declaring `queryTarget` also takes a query target on the
|
|
3276
|
+
* VIEW (`queryTarget.url`): the view resolves "every view row matching the
|
|
3277
|
+
* query" under its own read scope, maps the rows to source ids and runs the
|
|
3278
|
+
* source's action route on them in batches — the source re-checks every
|
|
3279
|
+
* batch.
|
|
3280
|
+
*
|
|
3281
|
+
* Repeatable (several sources, listed in declaration order). The source is
|
|
3282
|
+
* referenced lazily (no import cycles between controllers) and must be
|
|
3283
|
+
* registered with the app. Only a
|
|
3284
|
+
* controller declaring it gets the `POST {prefix}/delegated-actions/:name`
|
|
3285
|
+
* route (subclasses inherit it).
|
|
3286
|
+
*
|
|
3287
|
+
* ```ts
|
|
3288
|
+
* @ViewController(IssueBoard)
|
|
3289
|
+
* @DbActionsFrom(() => IssueController) // id → id
|
|
3290
|
+
* export class IssueBoardController extends AsDbReadableController<typeof IssueBoard> {}
|
|
3291
|
+
*
|
|
3292
|
+
* @DbActionsFrom(() => IssueController, { idMap: { id: "issueId" }, actions: ["close"] })
|
|
3293
|
+
* ```
|
|
3294
|
+
*
|
|
3295
|
+
* @since 0.1.147
|
|
3296
|
+
*/
|
|
3297
|
+
declare function DbActionsFrom(source: () => Function, opts?: TDbActionsFromOpts): ClassDecorator;
|
|
3298
|
+
//#endregion
|
|
2342
3299
|
//#region src/actions/per-row.d.ts
|
|
2343
3300
|
/**
|
|
2344
3301
|
* Lift a per-row predicate into the batch shape required by
|
|
@@ -2429,4 +3386,37 @@ declare function resolveTerminalRef(def: TAtscriptAnnotatedType): TTerminalRef |
|
|
|
2429
3386
|
*/
|
|
2430
3387
|
declare function applyTerminalRefs(serialized: TSerializedAnnotatedType, runtime: TAtscriptAnnotatedType, options: TSerializeOptions): TSerializedAnnotatedType;
|
|
2431
3388
|
//#endregion
|
|
2432
|
-
|
|
3389
|
+
//#region src/relation-predicates.d.ts
|
|
3390
|
+
/**
|
|
3391
|
+
* Client relational predicates (`nav=$some(…)` / `nav=$none(…)`, since
|
|
3392
|
+
* 0.1.147) at the HTTP layer: the request gate and the
|
|
3393
|
+
* `transformRelationFilter` overlay walk. Server-side filters
|
|
3394
|
+
* (`transformFilter`, `transformOne`, `actionRowScope`) never pass through
|
|
3395
|
+
* here — they are the authorization rule itself.
|
|
3396
|
+
*
|
|
3397
|
+
* Invariant: a client predicate on relation `n` reveals nothing that
|
|
3398
|
+
* `$with=n(<same filter>)` would not reveal under the same controller policy
|
|
3399
|
+
* — same visibility (`hasField` at `n` and `n.*`), the related table's own
|
|
3400
|
+
* capability rules (encrypted, `@db.writeOnly`, JSON storage,
|
|
3401
|
+
* `@db.table.filterable 'manual'`, geo, …) and the same row overlay
|
|
3402
|
+
* (`transformRelationFilter`). On top, the relation must opt in with
|
|
3403
|
+
* `@db.rel.filterable` (a predicate filters the PARENT rows).
|
|
3404
|
+
*/
|
|
3405
|
+
/**
|
|
3406
|
+
* Nesting limit of CLIENT relational predicates per predicate chain (`$with`
|
|
3407
|
+
* hops don't count; server-added predicates are not counted). Below the
|
|
3408
|
+
* core's `REL_FILTER_MAX_DEPTH` so server overlays have headroom.
|
|
3409
|
+
*
|
|
3410
|
+
* @since 0.1.147
|
|
3411
|
+
*/
|
|
3412
|
+
declare const REL_FILTER_CLIENT_MAX_DEPTH = 3;
|
|
3413
|
+
/**
|
|
3414
|
+
* Count limit of CLIENT relational predicates per request — root filter and
|
|
3415
|
+
* `$with` sub-filters together. Below the core's `REL_FILTER_MAX_NODES` so
|
|
3416
|
+
* server overlays have headroom.
|
|
3417
|
+
*
|
|
3418
|
+
* @since 0.1.147
|
|
3419
|
+
*/
|
|
3420
|
+
declare const REL_FILTER_CLIENT_MAX_NODES = 8;
|
|
3421
|
+
//#endregion
|
|
3422
|
+
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, DbDecorations, 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, type TDbDecorationsOpts, TDbFieldVisibility, TDbIndexFieldPaths, TDbParsedRequest, type TDbQueryTargetOpts, type TDbRemoveGuardContext, TDbRequestContext, TDbRequestEndpoint, type TDbRowIdInput, type TDbRowIdPurpose, type TDbRowIdsContext, type TDbWriteAction, type TDbWriteCheckContext, type TDbWriteGuardContext, type TFieldCapability, type TGateOp, type THttpErrorEntry, type TQueryPathOp, type TQueryPathRefs, TReadableBinding, type TReadableBindingMeta, type TTerminalRef, TableController, UseValidationErrorTransform, VALUE_HELP_CRUD_HANDLERS, ValueHelpQuery, ValueHelpSelect, ViewController, applyTerminalRefs, assertExposed, badRequest, clearDbSpaces, closeDbSpaces, 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 };
|