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