@atscript/moost-db 0.1.126 → 0.1.128
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 +719 -261
- package/dist/index.d.cts +326 -45
- package/dist/index.d.mts +326 -45
- package/dist/index.mjs +711 -265
- package/package.json +19 -19
package/dist/index.d.mts
CHANGED
|
@@ -3,26 +3,8 @@ 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 { AtscriptDbReadable, AtscriptDbTable, 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, TIdentification, TMetaResponse, Uniquery, UniqueryControls } from "@atscript/db";
|
|
6
|
+
import { AtscriptDbReadable, AtscriptDbTable, 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, 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
|
-
* Optional gate configuration for a single request. Each present entry enables
|
|
10
|
-
* the corresponding check; omitted entries skip that gate entirely.
|
|
11
|
-
*/
|
|
12
|
-
interface ReadableGates {
|
|
13
|
-
filter?: {
|
|
14
|
-
predicate: (field: string) => boolean;
|
|
15
|
-
annotation: string;
|
|
16
|
-
};
|
|
17
|
-
sort?: {
|
|
18
|
-
predicate: (field: string) => boolean;
|
|
19
|
-
annotation: string;
|
|
20
|
-
};
|
|
21
|
-
search?: {
|
|
22
|
-
allowed: boolean;
|
|
23
|
-
rejectionMessage: string;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
8
|
/**
|
|
27
9
|
* Abstract base class for read-only HTTP controllers over an Atscript interface.
|
|
28
10
|
*
|
|
@@ -67,6 +49,14 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
|
|
|
67
49
|
private _resolveHttpPath;
|
|
68
50
|
/** Lazily serializes the bound type (after all controllers have set @db.http.path). */
|
|
69
51
|
protected getSerializedType(): TSerializedAnnotatedType;
|
|
52
|
+
/**
|
|
53
|
+
* Serializes a type for the meta surfaces (`/meta`, `/meta/form/:name`)
|
|
54
|
+
* with {@link getSerializeOptions}, then re-points every reference chain to
|
|
55
|
+
* its terminal field and inherits the `db.rel.FK` value-help marker through
|
|
56
|
+
* the chain (since 0.1.128; see `meta/terminal-ref.ts`). Direct references
|
|
57
|
+
* serialize exactly as before.
|
|
58
|
+
*/
|
|
59
|
+
protected serializeForMeta(type: TAtscriptAnnotatedType): TSerializedAnnotatedType;
|
|
70
60
|
/**
|
|
71
61
|
* One-time initialization hook. Override to seed data, register watchers, etc.
|
|
72
62
|
*/
|
|
@@ -98,12 +88,28 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
|
|
|
98
88
|
protected validateInsights(insights: Map<string, unknown>): string | undefined;
|
|
99
89
|
protected validateParsed(parsed: Uniquery, type: "query" | "pages" | "getOne"): HttpError | undefined;
|
|
100
90
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
91
|
+
* Per-request gate hook, invoked by the DB readable controller right after
|
|
92
|
+
* its capability gate (`checkCapabilities`) with the parsed query. The
|
|
93
|
+
* default accepts everything. Return an `HttpError` to reject.
|
|
94
|
+
*
|
|
95
|
+
* @deprecated since 0.1.128 — the filter / sort gate is the
|
|
96
|
+
* `FieldCapabilityIndex` behind `checkCapabilities` (override that, or read
|
|
97
|
+
* `this.capabilities` on `AsDbReadableController`); this hook only remains
|
|
98
|
+
* so subclasses that overrode it keep being called.
|
|
104
99
|
*/
|
|
105
|
-
protected checkGates(
|
|
100
|
+
protected checkGates(_parsed: {
|
|
101
|
+
filter?: FilterExpr;
|
|
102
|
+
controls?: object;
|
|
103
|
+
}): HttpError | undefined;
|
|
106
104
|
protected parseQueryString(url: string): import("@uniqu/url").UrlQuery;
|
|
105
|
+
/**
|
|
106
|
+
* The ONE place a query string meets the `@uniqu/url` grammar. A lexer /
|
|
107
|
+
* parser error (e.g. an unquoted `-` in a value: `?name=json-w1`) is the
|
|
108
|
+
* client's fault, so since 0.1.128 it is a 400 with the validation envelope
|
|
109
|
+
* `{ message, statusCode: 400, errors: [{ path: "", message }] }` instead of
|
|
110
|
+
* an unhandled 500. Quote such values: `?name='json-w1'`.
|
|
111
|
+
*/
|
|
112
|
+
protected parseUrlOr400(queryString: string): ReturnType<typeof parseUrl>;
|
|
107
113
|
/**
|
|
108
114
|
* Parse a URL keeping only `$*` control keywords; report whether any
|
|
109
115
|
* non-control parts were present. Used by `/one` routes where the
|
|
@@ -163,6 +169,90 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
|
|
|
163
169
|
protected applyMetaOverlay(meta: TMetaResponse): TMetaResponse | Promise<TMetaResponse>;
|
|
164
170
|
}
|
|
165
171
|
//#endregion
|
|
172
|
+
//#region src/meta/field-capabilities.d.ts
|
|
173
|
+
/**
|
|
174
|
+
* Per-path HTTP capability of a DB readable — the single source that both the
|
|
175
|
+
* `/meta.fields` projection and the request gate are computed from, so the
|
|
176
|
+
* two can never disagree (since 0.1.128).
|
|
177
|
+
*
|
|
178
|
+
* Physical capability (adapter `canFilterField` / `canSortField`, storage,
|
|
179
|
+
* `@db.encrypted`) is combined with HTTP policy (`@db.writeOnly`,
|
|
180
|
+
* `@db.table.filterable / sortable 'manual'` + `@db.column.*`). Policy applies
|
|
181
|
+
* to filters and `$sort` only; `$groupBy`, `$having` keys and aggregate
|
|
182
|
+
* `$field`s use the physical capability alone.
|
|
183
|
+
*/
|
|
184
|
+
interface TFieldCapability {
|
|
185
|
+
/** A filter on this path passes the gate (adapter ∧ ¬writeOnly ∧ ¬encrypted ∧ policy). */
|
|
186
|
+
filterable: boolean;
|
|
187
|
+
/** A `$sort` on this path passes the gate (adapter ∧ ¬writeOnly ∧ ¬encrypted ∧ policy). */
|
|
188
|
+
sortable: boolean;
|
|
189
|
+
/** The path may appear in `$select`. `@db.writeOnly` fields are selectable — the seal strips them after the gate. */
|
|
190
|
+
selectable: boolean;
|
|
191
|
+
/** Advisory: index-backed (explicit `@db.index*`, primary key, unique). Never affects acceptance. */
|
|
192
|
+
indexed: boolean;
|
|
193
|
+
/** Present when `filterable` is `false` — the reason clause appended to the HTTP 400 message. */
|
|
194
|
+
filterReason?: string;
|
|
195
|
+
/** Present when `sortable` is `false` — the reason clause appended to the HTTP 400 message. */
|
|
196
|
+
sortReason?: string;
|
|
197
|
+
}
|
|
198
|
+
/** One rejected path: `path` is the offending logical path, `message` the full sentence. */
|
|
199
|
+
interface TCapabilityVerdict {
|
|
200
|
+
path: string;
|
|
201
|
+
message: string;
|
|
202
|
+
}
|
|
203
|
+
/** The readable members the index reads. */
|
|
204
|
+
type TCapabilityReadable = Pick<AtscriptDbReadable, "type" | "fieldDescriptors" | "flatMap" | "navFields" | "relations" | "ignoredFields" | "canFilterField" | "canSortField">;
|
|
205
|
+
/**
|
|
206
|
+
* Capability index of one readable, built once per controller.
|
|
207
|
+
*
|
|
208
|
+
* - {@link entries} feeds `/meta.fields` (listed leaves in descriptor order);
|
|
209
|
+
* - {@link check} is the request gate: same inputs, same answer.
|
|
210
|
+
*
|
|
211
|
+
* Paths outside the index are classified by the core's `classifyQueryPath`
|
|
212
|
+
* (the same rules the core backstop applies) — navigation path, nested-object
|
|
213
|
+
* parent, JSON descendant (relational adapters), encrypted descendant,
|
|
214
|
+
* unknown — so the 400 names the storage reason and the alternative.
|
|
215
|
+
*/
|
|
216
|
+
declare class FieldCapabilityIndex implements TQueryPathSource {
|
|
217
|
+
readonly filterableManual: boolean;
|
|
218
|
+
readonly sortableManual: boolean;
|
|
219
|
+
/** Navigation relations (`@db.rel.to/from/via`), incl. nested ones. */
|
|
220
|
+
readonly navFields: ReadonlySet<string>;
|
|
221
|
+
/** `@db.writeOnly` paths. */
|
|
222
|
+
readonly writeOnly: ReadonlySet<string>;
|
|
223
|
+
/** Descriptors stored as one JSON column (relational adapters). */
|
|
224
|
+
readonly jsonParents: ReadonlySet<string>;
|
|
225
|
+
/** Descriptors carrying `@db.encrypted` (the ciphertext column on relational adapters). */
|
|
226
|
+
readonly encryptedFields: ReadonlySet<string>;
|
|
227
|
+
private readonly _entries;
|
|
228
|
+
/** Nested-object parents (never listed, always selectable) → their listed leaves. */
|
|
229
|
+
private readonly _objectParents;
|
|
230
|
+
/** Listed leaves — the {@link TQueryPathSource} view for `classifyQueryPath`. */
|
|
231
|
+
get leaves(): ReadonlyMap<string, unknown>;
|
|
232
|
+
/** Nested-object parents — the {@link TQueryPathSource} view for `classifyQueryPath`. */
|
|
233
|
+
get objectParents(): ReadonlyMap<string, unknown>;
|
|
234
|
+
constructor(source: TCapabilityReadable, writeOnly: ReadonlySet<string>);
|
|
235
|
+
private _buildEntry;
|
|
236
|
+
/** Listed leaves in descriptor order — the `/meta.fields` projection source. */
|
|
237
|
+
entries(): IterableIterator<[path: string, cap: TFieldCapability, fd: TDbFieldMeta]>;
|
|
238
|
+
/** Physical filter capability (adapter ∧ ¬writeOnly ∧ ¬encrypted) — ignores the manual-mode policy. */
|
|
239
|
+
isPhysicallyFilterable(path: string): boolean;
|
|
240
|
+
/**
|
|
241
|
+
* Gate check for one path in one position. Returns `undefined` when the
|
|
242
|
+
* path is accepted. Order: navigation paths first (a nav path "exists" on
|
|
243
|
+
* the target table but is never a column here), then a listed leaf's
|
|
244
|
+
* capability (no existence lookup needed — every listed leaf is a real
|
|
245
|
+
* field), then `exists` (the readable's `isValidFieldPath`) and, for paths
|
|
246
|
+
* that exist but are not leaves, the storage classification.
|
|
247
|
+
*
|
|
248
|
+
* Existence deliberately runs BEFORE the JSON / encrypted classification:
|
|
249
|
+
* an untyped descendant of a JSON column (`address.nope`) is reported as
|
|
250
|
+
* `Unknown field`, not as "inside JSON-stored column" — clients pin that
|
|
251
|
+
* wording, so do not "align" it with the core backstop's text.
|
|
252
|
+
*/
|
|
253
|
+
check(path: string, op: TQueryPathOp$1, exists: (path: string) => boolean): TCapabilityVerdict | undefined;
|
|
254
|
+
}
|
|
255
|
+
//#endregion
|
|
166
256
|
//#region src/as-db-readable.controller.d.ts
|
|
167
257
|
/**
|
|
168
258
|
* Read-only database controller for Moost that works with any `AtscriptDbReadable`
|
|
@@ -182,30 +272,58 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
182
272
|
* (or any non-table readable).
|
|
183
273
|
*/
|
|
184
274
|
protected get table(): AtscriptDbTable<T>;
|
|
185
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Per-path capability index (since 0.1.128): the ONE input both `/meta.fields`
|
|
277
|
+
* and the request gate ({@link checkCapabilities}) are computed from, so
|
|
278
|
+
* metadata and runtime can never diverge.
|
|
279
|
+
*/
|
|
280
|
+
protected readonly capabilities: FieldCapabilityIndex;
|
|
281
|
+
/** Bound once: the field-existence check the gate hands to `capabilities.check`. */
|
|
282
|
+
private readonly _exists;
|
|
186
283
|
private readonly _preferredIdSet;
|
|
187
284
|
private readonly _overlayIsNoOp;
|
|
188
285
|
/** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
|
|
189
286
|
private readonly _quantityRefByPath;
|
|
190
|
-
/** Paths the adapter vetoes for filtering (e.g. JSON storage on SQL). Symmetric with `/meta` `filterable: false`. */
|
|
191
|
-
private readonly _adapterNonFilterable;
|
|
192
287
|
/** `@db.column.searchable` paths — the `$search` fallback when the adapter has no native search. */
|
|
193
288
|
private readonly _searchFallbackFields;
|
|
194
289
|
/** `@db.writeOnly` paths — settable in writes, sealed out of every read surface. */
|
|
195
290
|
private readonly _writeOnlySet;
|
|
291
|
+
/**
|
|
292
|
+
* Logical paths an exclusion `$select` inverts into: every listed leaf and
|
|
293
|
+
* (on nested-object adapters) object parents — never navigation descendants,
|
|
294
|
+
* which the core path guard rejects.
|
|
295
|
+
*/
|
|
296
|
+
private readonly _invertibleFields;
|
|
196
297
|
constructor(app: Moost, readable?: AtscriptDbReadable<T>);
|
|
197
|
-
private
|
|
298
|
+
private _collectInvertibleFields;
|
|
198
299
|
private _collectQuantityRefs;
|
|
199
|
-
private _buildGates;
|
|
200
300
|
private _collectAnnotated;
|
|
201
301
|
protected hasField(path: string): boolean;
|
|
202
302
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
303
|
+
* Structural capability gate (since 0.1.128): walks the PARSED query —
|
|
304
|
+
* filter tree, `$sort`, `$select`, `$groupBy`, `$having`, aggregate
|
|
305
|
+
* `$field`s — and checks every root path against {@link capabilities}, the
|
|
306
|
+
* same index `/meta.fields` is projected from. Runs on the wire request,
|
|
307
|
+
* before `transformFilter` / `transformProjection` and before the write-only
|
|
308
|
+
* seal (a `@db.writeOnly` field is selectable; the seal strips it silently).
|
|
309
|
+
*
|
|
310
|
+
* Rejections use the structured envelope `{ message, statusCode: 400,
|
|
311
|
+
* errors: [{ path, message }] }` — `path` is the offending logical path.
|
|
312
|
+
* After the per-path checks the core `$having` rule runs (`checkHavingKeys`:
|
|
313
|
+
* aliases or `$groupBy` fields only), so a readable mock and a real table
|
|
314
|
+
* answer alike.
|
|
315
|
+
*/
|
|
316
|
+
protected checkCapabilities(parsed: {
|
|
317
|
+
filter?: FilterExpr;
|
|
318
|
+
controls?: object;
|
|
319
|
+
}): HttpError | undefined;
|
|
320
|
+
/**
|
|
321
|
+
* Root-path existence moved into {@link checkCapabilities}; the insights map
|
|
322
|
+
* only serves `$with` sub-controls here — the URL parser flattens
|
|
323
|
+
* `$with=assignee($select=name)` into the insight `assignee.name`, which is
|
|
324
|
+
* resolved against the target table through `isValidFieldPath`.
|
|
207
325
|
*/
|
|
208
|
-
protected
|
|
326
|
+
protected validateInsights(insights: Map<string, unknown>): string | undefined;
|
|
209
327
|
/** Validates $with relations against the readable. */
|
|
210
328
|
protected validateParsed(parsed: Uniquery, type: "query" | "pages" | "getOne"): HttpError | undefined;
|
|
211
329
|
/**
|
|
@@ -251,7 +369,12 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
251
369
|
/** Returns a widened `$select` only when at least one `requiredFields` entry is missing; `null` means "no widening needed". */
|
|
252
370
|
private _widenSelectForActions;
|
|
253
371
|
private _prepareAugmentation;
|
|
254
|
-
/**
|
|
372
|
+
/**
|
|
373
|
+
* `@db.column.searchable` paths, minus anything the adapter can't filter
|
|
374
|
+
* (JSON storage, encrypted), `@db.writeOnly` fields and navigation
|
|
375
|
+
* descendants — physical capability only (manual-mode policy does not
|
|
376
|
+
* apply to `$search`).
|
|
377
|
+
*/
|
|
255
378
|
private _collectSearchFallbackFields;
|
|
256
379
|
/**
|
|
257
380
|
* Removes `@db.writeOnly` fields from any `$select` shape — and forces an
|
|
@@ -350,20 +473,109 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
350
473
|
* @TableController(usersTable)
|
|
351
474
|
* export class UsersController extends AsDbController<typeof UserModel> {}
|
|
352
475
|
* ```
|
|
476
|
+
*
|
|
477
|
+
* ### Write pipeline (since 0.1.128)
|
|
478
|
+
*
|
|
479
|
+
* ```
|
|
480
|
+
* shape gate (400) → onWrite / onRemove (outside any transaction)
|
|
481
|
+
* → table op — the table's own transaction: validate → guardWrite / guardRemove
|
|
482
|
+
* (only when overridden) → re-validate → write
|
|
483
|
+
* → 404 / 409 disambiguation
|
|
484
|
+
* ```
|
|
485
|
+
*
|
|
486
|
+
* The guard is the table's `guard` write option (`TWriteOptions.guard` /
|
|
487
|
+
* `TDeleteOptions.guard`); overriding `guardWrite` / `guardRemove` is the
|
|
488
|
+
* switch that passes it. Built-in failures are THROWN as `HttpError`
|
|
489
|
+
* (wire-identical to returning them; a throw also rolls back a user-level
|
|
490
|
+
* `withTransaction` wrapper).
|
|
353
491
|
*/
|
|
354
492
|
declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsDbReadableController<T, DataType> {
|
|
355
493
|
constructor(app: Moost, table?: AtscriptDbTable<T>);
|
|
356
494
|
protected buildCrud(): TCrudPermissions$1;
|
|
357
495
|
/**
|
|
358
|
-
* Intercepts write operations
|
|
359
|
-
*
|
|
496
|
+
* Intercepts write operations with the UNTRUSTED body (after the shape gate:
|
|
497
|
+
* an object, or an array of objects). Runs outside any transaction. Return
|
|
498
|
+
* the data in the shape received — an object, or an array of objects for
|
|
499
|
+
* the `*Many` actions; anything else (including `undefined`) aborts with
|
|
500
|
+
* 500 "Not saved". Return an `Error` instance to respond with that error
|
|
501
|
+
* (since 0.1.128 — previously passed on as data); throw to respond with
|
|
502
|
+
* the thrown error's status. May be async (e.g. to enrich payloads from
|
|
503
|
+
* session / permissions).
|
|
360
504
|
*/
|
|
361
|
-
protected onWrite(action:
|
|
505
|
+
protected onWrite(action: TDbWriteAction$1, data: unknown): unknown;
|
|
362
506
|
/**
|
|
363
|
-
* Intercepts delete operations. Return `undefined` to abort
|
|
364
|
-
*
|
|
507
|
+
* Intercepts delete operations. Return `undefined` to abort (500 "Not
|
|
508
|
+
* deleted"); return an `Error` instance to respond with that error.
|
|
509
|
+
* Runs outside any transaction. May be async (e.g. to resolve composite
|
|
510
|
+
* ids from external state).
|
|
365
511
|
*/
|
|
366
512
|
protected onRemove(id: unknown): unknown;
|
|
513
|
+
/**
|
|
514
|
+
* Validated-stage write guard (since 0.1.128). Overriding it is the switch:
|
|
515
|
+
* the override is passed to the table as its `guard` write option and runs
|
|
516
|
+
* once inside the table's own transaction, after defaults + validation and
|
|
517
|
+
* before encryption / nested-relation phases. `ctx.rows` are the validated
|
|
518
|
+
* rows (defaults applied on insert/replace; `$cas` removed on update) and
|
|
519
|
+
* may be enriched in place — the table validates them again afterwards.
|
|
520
|
+
* Reject by throwing (an `HttpError` is recommended): the transaction rolls
|
|
521
|
+
* back and the error propagates unchanged. Unmodified controllers pass no
|
|
522
|
+
* guard and pay nothing.
|
|
523
|
+
*
|
|
524
|
+
* Do not swallow `DbError`s (on PostgreSQL the transaction is aborted after
|
|
525
|
+
* a failed statement), and do not await external I/O on SQLite (the guard
|
|
526
|
+
* holds the connection). On MongoDB replica sets the transaction callback —
|
|
527
|
+
* and therefore this guard — may run more than once on transient errors.
|
|
528
|
+
*/
|
|
529
|
+
protected guardWrite(_ctx: TDbWriteGuardContext$1<DataType>): void | Promise<void>;
|
|
530
|
+
/**
|
|
531
|
+
* Validated-stage remove guard (since 0.1.128). Same switch semantics as
|
|
532
|
+
* {@link guardWrite}: the override becomes `deleteOne`'s `guard` option and
|
|
533
|
+
* runs inside the table's transaction once `onRemove`'s id resolved to a
|
|
534
|
+
* filter. A missing row still reaches the guard — `ctx.current()` resolves
|
|
535
|
+
* to `null` and the 404 comes after the guard; only an id that cannot be
|
|
536
|
+
* resolved to a filter at all (malformed for the key type) is a 404 before
|
|
537
|
+
* the guard.
|
|
538
|
+
*/
|
|
539
|
+
protected guardRemove(_ctx: TDbRemoveGuardContext$1<DataType>): void | Promise<void>;
|
|
540
|
+
/**
|
|
541
|
+
* Runs `fn` inside the bound table's adapter transaction (nested calls
|
|
542
|
+
* join it). For custom actions and routes that need one transaction across
|
|
543
|
+
* several table operations.
|
|
544
|
+
*/
|
|
545
|
+
protected withTransaction<R>(fn: () => Promise<R>): Promise<R>;
|
|
546
|
+
/**
|
|
547
|
+
* The table write call's trailing options: `[{ guard }]` only when
|
|
548
|
+
* `guardWrite` is overridden, else nothing (the table is called exactly as
|
|
549
|
+
* an unmodified controller always called it).
|
|
550
|
+
*/
|
|
551
|
+
private _writeArgs;
|
|
552
|
+
/** `deleteOne`'s trailing options: `[{ guard }]` only when `guardRemove` is overridden. */
|
|
553
|
+
private _removeArgs;
|
|
554
|
+
/** Resolves a hook result: `undefined` aborts with `abortMessage`, an `Error` is thrown, anything else passes. */
|
|
555
|
+
private _checkHook;
|
|
556
|
+
/** Runs `onWrite` and re-applies the shape gate to its output (a non-object is a 500 "Not saved"). */
|
|
557
|
+
private _writeBody;
|
|
558
|
+
/**
|
|
559
|
+
* Normalises the OCC shape of one write item in place through the shared
|
|
560
|
+
* `reconcileCas` (since 0.1.128): a top-level `version` field in a write
|
|
561
|
+
* body is a `$cas` directive, not a SET — it is lifted to
|
|
562
|
+
* `$cas: { [versionColumn]: version }`; a raw SDK-shaped `$cas` is accepted
|
|
563
|
+
* as sent; both present with different values → 400 at `$cas`; a malformed
|
|
564
|
+
* `$cas` reports `separateCas`'s own message. Returns `true` iff the item is
|
|
565
|
+
* CAS-bearing — callers use this to gate the 404/409 disambiguation
|
|
566
|
+
* `findOne` on `matchedCount === 0`. On a non-versioned table nothing is
|
|
567
|
+
* lifted and a raw `$cas` reaches the table, which rejects it.
|
|
568
|
+
*/
|
|
569
|
+
private _resolveCas;
|
|
570
|
+
/**
|
|
571
|
+
* Bulk auto-lift: each item carries its own `version` → `$cas`.
|
|
572
|
+
* NOTE: per-item conflict disambiguation in the response body is deferred
|
|
573
|
+
* (§6.4) — the aggregate `{ matchedCount, modifiedCount }` surfaces partial
|
|
574
|
+
* application; callers can detect mismatches via `modifiedCount < N`.
|
|
575
|
+
*/
|
|
576
|
+
private _resolveBulkCas;
|
|
577
|
+
/** Deletes by id (guard forwarded when overridden) and maps "nothing deleted" to 404. */
|
|
578
|
+
private _deleteOrThrow;
|
|
367
579
|
/**
|
|
368
580
|
* **POST /** — inserts one or many records.
|
|
369
581
|
*/
|
|
@@ -372,22 +584,25 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
|
|
|
372
584
|
* **PUT /** — fully replaces one or many records matched by primary key.
|
|
373
585
|
*
|
|
374
586
|
* When the table opts into OCC (`@db.column.version`), a top-level `version`
|
|
375
|
-
* field in the body is auto-lifted to `$cas` (§6.2 of VERSION_PROPOSAL.md)
|
|
376
|
-
* On `matchedCount === 0` for a
|
|
377
|
-
* 404 (row gone) vs 409 (version
|
|
587
|
+
* field in the body is auto-lifted to `$cas` (§6.2 of VERSION_PROPOSAL.md);
|
|
588
|
+
* a raw `$cas` is accepted as sent. On `matchedCount === 0` for a
|
|
589
|
+
* CAS-bearing write, this disambiguates 404 (row gone) vs 409 (version
|
|
590
|
+
* mismatch) via a single `findOne` after the table call.
|
|
378
591
|
*/
|
|
379
592
|
replace(payload: unknown): Promise<unknown>;
|
|
380
593
|
/**
|
|
381
594
|
* **PATCH /** — partially updates one or many records matched by primary key.
|
|
382
595
|
*
|
|
383
|
-
* Same OCC semantics as {@link replace} (§6.2 / §6.3).
|
|
596
|
+
* Same OCC semantics as {@link replace} (§6.2 / §6.3). A PK-only body
|
|
597
|
+
* carrying `version` (or `$cas`) is a real write — the "versioned touch":
|
|
598
|
+
* the CAS statement executes and bumps the version on a hit.
|
|
384
599
|
*/
|
|
385
600
|
update(payload: unknown): Promise<unknown>;
|
|
386
601
|
/**
|
|
387
602
|
* Disambiguates a `matchedCount === 0` result on a CAS-protected write:
|
|
388
603
|
* returns 404 when the row is genuinely missing, 409 with
|
|
389
604
|
* `{ error: "version_mismatch", currentVersion: N }` when it's present
|
|
390
|
-
* but the supplied version is stale (§6.3).
|
|
605
|
+
* but the supplied version is stale (§6.3). Callers throw the result.
|
|
391
606
|
*/
|
|
392
607
|
protected _disambiguateMismatch(data: unknown, versionColumn: string): Promise<HttpError>;
|
|
393
608
|
/**
|
|
@@ -986,6 +1201,26 @@ interface TAssertExposedOptions {
|
|
|
986
1201
|
*/
|
|
987
1202
|
declare function assertExposed(app: Moost, models: readonly TAtscriptAnnotatedType[], options?: TAssertExposedOptions): TAtscriptAnnotatedType[];
|
|
988
1203
|
//#endregion
|
|
1204
|
+
//#region src/http-errors.d.ts
|
|
1205
|
+
/** One entry of the structured error envelope's `errors` array. */
|
|
1206
|
+
interface THttpErrorEntry {
|
|
1207
|
+
path: string;
|
|
1208
|
+
message: string;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* The structured error envelope every `moost-db` rejection uses (since
|
|
1212
|
+
* 0.1.128 all of them do): `{ message, statusCode, errors: [{ path, message }] }`
|
|
1213
|
+
* — the same shape the validation interceptor renders for `ValidatorError` /
|
|
1214
|
+
* `DbError`, so clients parse one format.
|
|
1215
|
+
*/
|
|
1216
|
+
declare function errorEnvelope(statusCode: number, message: string, errors: THttpErrorEntry[]): HttpError;
|
|
1217
|
+
/**
|
|
1218
|
+
* A 400 with a single `errors` entry: `path` names the offending logical
|
|
1219
|
+
* path / body position, `message` the reason, `top` the envelope's top-level
|
|
1220
|
+
* message (defaults to `message`).
|
|
1221
|
+
*/
|
|
1222
|
+
declare function badRequest(path: string, message: string, top?: string): HttpError;
|
|
1223
|
+
//#endregion
|
|
989
1224
|
//#region src/validation-interceptor.d.ts
|
|
990
1225
|
declare const validationErrorTransform: () => import("moost").TInterceptorDef;
|
|
991
1226
|
declare const UseValidationErrorTransform: () => ClassDecorator & MethodDecorator;
|
|
@@ -1283,4 +1518,50 @@ declare const QUERY_CONTROLS: readonly string[];
|
|
|
1283
1518
|
declare const PAGES_CONTROLS: readonly string[];
|
|
1284
1519
|
declare const ONE_CONTROLS: readonly string[];
|
|
1285
1520
|
//#endregion
|
|
1286
|
-
|
|
1521
|
+
//#region src/meta/terminal-ref.d.ts
|
|
1522
|
+
/**
|
|
1523
|
+
* Terminal-reference resolution for `/meta` and `/meta/form/:name`
|
|
1524
|
+
* (since 0.1.128).
|
|
1525
|
+
*
|
|
1526
|
+
* A prop declared through a reference chain — a view field `code: Issue.code`
|
|
1527
|
+
* where `Issue.code: Dict.code` carries `@db.rel.FK` — serializes with
|
|
1528
|
+
* `refDepth: 0.5` as a shallow ref to its DIRECT hop (`Issue.code`), and the
|
|
1529
|
+
* `@db.rel.FK` marker does not travel through references. Value-help pickers
|
|
1530
|
+
* therefore target the wrong table. This post-pass re-points every serialized
|
|
1531
|
+
* `ref` to the chain's terminal field (the column's value domain) and
|
|
1532
|
+
* inherits the FK marker when any hop of the chain is an FK. The runtime type
|
|
1533
|
+
* is never mutated; direct references serialize byte-identically.
|
|
1534
|
+
*/
|
|
1535
|
+
/** Result of walking a reference chain to its end. */
|
|
1536
|
+
interface TTerminalRef {
|
|
1537
|
+
type: TAtscriptAnnotatedType;
|
|
1538
|
+
field: string;
|
|
1539
|
+
/** `true` when the prop itself or any hop of its chain carries `@db.rel.FK`. */
|
|
1540
|
+
fk: boolean;
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Resolves a (possibly dotted) prop path against an object type by walking
|
|
1544
|
+
* `type.props` one segment at a time. Bails with `undefined` as soon as a hop
|
|
1545
|
+
* is not an object type or the segment is missing.
|
|
1546
|
+
*/
|
|
1547
|
+
declare function resolveProp(type: TAtscriptAnnotatedType, field: string): TAtscriptAnnotatedType | undefined;
|
|
1548
|
+
/**
|
|
1549
|
+
* Follows `def.ref` hop by hop until a prop without a `ref` (a primary key or
|
|
1550
|
+
* a plain column) is reached. Cycle-safe (visited on `<typeId>.<field>`) and
|
|
1551
|
+
* bounded by chain length.
|
|
1552
|
+
*/
|
|
1553
|
+
declare function resolveTerminalRef(def: TAtscriptAnnotatedType): TTerminalRef | undefined;
|
|
1554
|
+
/**
|
|
1555
|
+
* Post-pass over a serialized type in lock-step with its runtime type: every
|
|
1556
|
+
* object prop (recursing into nested objects and array elements — never into
|
|
1557
|
+
* `ref` bodies or navigation subtrees) whose runtime prop has a `ref` gets its
|
|
1558
|
+
* serialized `ref` re-pointed to the terminal field (shallow `{ id, metadata }`
|
|
1559
|
+
* target, serialized with the same annotation whitelist) and, when the chain
|
|
1560
|
+
* passes an FK, `metadata["db.rel.FK"] = true` (never the hop's alias).
|
|
1561
|
+
*
|
|
1562
|
+
* Only shallow refs (`refDepth` with a `.5` step) are rewritten; a full-body
|
|
1563
|
+
* ref target is left alone. Mutates and returns `serialized`.
|
|
1564
|
+
*/
|
|
1565
|
+
declare function applyTerminalRefs(serialized: TSerializedAnnotatedType, runtime: TAtscriptAnnotatedType, options: TSerializeOptions): TSerializedAnnotatedType;
|
|
1566
|
+
//#endregion
|
|
1567
|
+
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 TDbActionInfo, type TDbActionInputFormMeta, type TDbActionIntent, type TDbActionLevel, type TDbActionMeta, type TDbActionParamKind, type TDbActionProcessor, type TDbActionsEntry, type TDbActionsEntryUnpinned, type TDbClassActionMeta, 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 };
|