@atscript/moost-db 0.1.132 → 0.1.134

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { i as resolveDbSpace, n as clearDbSpaces, r as provideDbSpace, t as DEFAULT_DB_SPACE } from "./db-space-registry-CWpYwZ4R.cjs";
2
2
  import { TAtscriptAnnotatedType, TAtscriptDataType, TSerializeOptions, TSerializedAnnotatedType, TValidatorOptions, Validator } from "@atscript/typescript/utils";
3
- import { 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, TIdentification, TMetaResponse, TQueryPathOp, TQueryPathOp as TQueryPathOp$1, TQueryPathRefs, TQueryPathSource, Uniquery, UniqueryControls, collectQueryPaths } from "@atscript/db";
3
+ import { 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";
4
4
  import { HttpError } from "@moostjs/event-http";
5
5
  import { Mate, Moost, TConsoleBase, TMateParamMeta, TMoostMetadata } from "moost";
6
6
  import { parseUrl } from "@uniqu/url";
@@ -22,7 +22,9 @@ import { parseUrl } from "@uniqu/url";
22
22
  *
23
23
  * Subclass responsibilities:
24
24
  * - Pass the bound interface + logical name + (optional) kind tag through super().
25
- * - Implement {@link hasField} so insights validation can reject unknown keys.
25
+ * - Implement {@link hasField} — the field-visibility hook every validated
26
+ * path consults; a path it rejects gets the same `Unknown field` 400 as a
27
+ * nonexistent one, so overriding it hides fields per request.
26
28
  * - Register the `/query`, `/pages`, `/one(/:id)` routes with the concrete
27
29
  * handlers that match the data source's contract (DB readables route into
28
30
  * aggregate/vector/search; value-help controllers just filter/sort/paginate).
@@ -45,7 +47,11 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
45
47
  /** Cached serialized form schemas keyed by `FormType.name` — populated lazily by {@link metaForm}. */
46
48
  private _formSchemas;
47
49
  constructor(boundType: T, controllerName: string, app: Moost, kindTag?: string);
48
- /** Subclass contract: return `true` if `path` addresses a valid field on the bound source. */
50
+ /**
51
+ * Subclass contract: return `true` if `path` addresses a field that exists
52
+ * AND is visible to the current request — see the DB controller's override
53
+ * for the full list of positions that consult it.
54
+ */
49
55
  protected abstract hasField(path: string): boolean;
50
56
  /** Sets @db.http.path on the type metadata from the controller's computed prefix. */
51
57
  private _resolveHttpPath;
@@ -179,6 +185,13 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
179
185
  protected applyMetaOverlay(meta: TMetaResponse): TMetaResponse | Promise<TMetaResponse>;
180
186
  }
181
187
  //#endregion
188
+ //#region src/actions/id-validation.d.ts
189
+ /** Duck-typed shape; matches `AtscriptDbReadable`'s public surface. */
190
+ interface IdValidationSource {
191
+ readonly identifications: readonly TIdentification[];
192
+ readonly fieldDescriptors: readonly TDbFieldMeta[];
193
+ }
194
+ //#endregion
182
195
  //#region src/meta/field-capabilities.d.ts
183
196
  /**
184
197
  * Per-path HTTP capability of a DB readable — the single source that both the
@@ -195,9 +208,9 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
195
208
  * `filterable` is the value-comparison verdict, `filterOps` the narrower
196
209
  * predicates that still pass where it is `false`.
197
210
  *
198
- * A calendar-bucket source (`bucketable`) needs the physical capability, a
199
- * `number.timestamp` type, dimension status on a strict (dimension / measure
200
- * declaring) table, and an adapter with calendar-bucket units.
211
+ * A calendar-bucket source (`bucketable`) is the core's `bucketSourceVerdict`
212
+ * (the same function the core path guard runs) under the HTTP-only
213
+ * `@db.writeOnly` veto.
201
214
  */
202
215
  interface TFieldCapability {
203
216
  /** A value-comparison filter on this path passes the gate (adapter ∧ ¬writeOnly ∧ ¬encrypted ∧ policy). */
@@ -215,8 +228,8 @@ interface TFieldCapability {
215
228
  /** Present when `sortable` is `false` — the reason clause appended to the HTTP 400 message. */
216
229
  sortReason?: string;
217
230
  /**
218
- * A calendar bucket over this path passes the gate (physical ∧ timestamp ∧
219
- * (¬strict ∨ dimension) ∧ adapter has calendar buckets). Since 0.1.132.
231
+ * A calendar bucket over this path passes the gate (¬writeOnly ∧ the core's
232
+ * `bucketSourceVerdict`). Since 0.1.132.
220
233
  */
221
234
  bucketable: boolean;
222
235
  /** Present when `bucketable` is `false` — the reason clause appended to the HTTP 400 message. */
@@ -272,14 +285,8 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
272
285
  private readonly _entries;
273
286
  /** Nested-object parents (never listed, always selectable) → their listed leaves. */
274
287
  private readonly _objectParents;
275
- /**
276
- * Declared dimensions when the table is strict (declares dimensions or
277
- * measures), else `undefined` — the core rule: a grouping source, a
278
- * bucketed field included, must then be a dimension.
279
- */
280
- private readonly _dimensions;
281
- /** Paths of every JSON-value descriptor (`isJsonValueField`) — see `jsonValueAncestor`. */
282
- private readonly _jsonValueParents;
288
+ /** What `bucketSourceVerdict` reads of the table (JSON-value parents, dimensions, measures). */
289
+ private readonly _bucketTable;
283
290
  /** Listed leaves — the {@link TQueryPathSource} view for `classifyQueryPath`. */
284
291
  get leaves(): ReadonlyMap<string, unknown>;
285
292
  /** Nested-object parents — the {@link TQueryPathSource} view for `classifyQueryPath`. */
@@ -292,16 +299,23 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
292
299
  isPhysicallyFilterable(path: string): boolean;
293
300
  /**
294
301
  * Gate check for one path in one position. Returns `undefined` when the
295
- * path is accepted. Order: navigation paths first (a nav path "exists" on
296
- * the target table but is never a column here), then a listed leaf's
297
- * capability (no existence lookup needed — every listed leaf is a real
298
- * field), then `exists` (the readable's `isValidFieldPath`) and, for paths
299
- * that exist but are not leaves, the storage classification.
302
+ * path is accepted.
303
+ *
304
+ * `exists` runs FIRST, for every path (since 0.1.133): it is the
305
+ * controller's `hasField`, the visibility hook subclasses narrow per
306
+ * request (e.g. a projection-scoped viewer). A path it rejects answers
307
+ * `Unknown field "x"` exactly like a nonexistent one — never a capability
308
+ * or navigation hint, which would reveal the field and let a filter or
309
+ * sort on it act as a value oracle. Before 0.1.133 listed leaves and
310
+ * navigation paths skipped it.
300
311
  *
301
- * Existence deliberately runs BEFORE the JSON / encrypted classification:
302
- * an untyped descendant of a JSON column (`address.nope`) is reported as
303
- * `Unknown field`, not as "inside JSON-stored column" — clients pin that
304
- * wording, so do not "align" it with the core backstop's text.
312
+ * Then: navigation paths (a nav path exists on the target table but is
313
+ * never a column here), a listed leaf's capability, and for other paths
314
+ * the storage classification. Existence also runs BEFORE the JSON /
315
+ * encrypted classification: an untyped descendant of a JSON column
316
+ * (`address.nope`) is reported as `Unknown field`, not as "inside
317
+ * JSON-stored column" — clients pin that wording, so do not "align" it
318
+ * with the core backstop's text.
305
319
  *
306
320
  * `predicate` is a filter entry's class (`collectQueryPaths` records it per
307
321
  * occurrence); it only matters for `op === "filter"` on a listed leaf.
@@ -343,8 +357,17 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
343
357
  private _capabilities?;
344
358
  /** `/meta` is a projection of {@link capabilities}: a rebuilt index rebuilds the cached envelope. */
345
359
  protected metaCacheKey(): unknown;
346
- /** Bound once: the field-existence check the gate hands to `capabilities.check`. */
360
+ /** Bound once: the visibility check ({@link hasField}) the gate hands to `capabilities.check`. */
347
361
  private readonly _exists;
362
+ /**
363
+ * Id-resolution options (since 0.1.134): `{ isFieldVisible: hasField }`
364
+ * when a subclass overrides {@link hasField}, else `undefined` (the default
365
+ * accepts every real path, so resolution stays unfiltered). A unique index
366
+ * over a hidden field is never an identification.
367
+ */
368
+ protected readonly _idOpts: TIdResolveOptions | undefined;
369
+ /** Narrowed id sources, one stable object per distinct visible-identification set. */
370
+ private readonly _idSources;
348
371
  private readonly _preferredIdSet;
349
372
  private readonly _overlayIsNoOp;
350
373
  /** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
@@ -360,9 +383,34 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
360
383
  */
361
384
  private readonly _invertibleFields;
362
385
  constructor(app: Moost, readable?: AtscriptDbReadable<T>);
386
+ /**
387
+ * The identifications this request may address rows through (since
388
+ * 0.1.134): the readable's own, minus unique indexes over fields
389
+ * {@link hasField} hides. Used by `/one?…`, `DELETE /?…` and action `ids`.
390
+ * Stable per distinct outcome, so per-source caches keyed on it hit.
391
+ */
392
+ get idSource(): IdValidationSource;
363
393
  private _collectInvertibleFields;
364
394
  private _collectQuantityRefs;
365
395
  private _collectAnnotated;
396
+ /**
397
+ * THE field-visibility hook: every gated path consults it before any
398
+ * capability check (since 0.1.133) — filter keys (inside `$and` / `$or` /
399
+ * `$not`, existence predicates included), `$sort`, `$select`,
400
+ * `$groupBy`, `$having` keys, aggregate and calendar-bucket `$field`s,
401
+ * `$with` relation names and sub-query paths, and the `$search` fallback
402
+ * fields. A path it rejects is answered exactly like a nonexistent one
403
+ * (`Unknown field "x"` / `Unknown relation "x"`), so override it to hide
404
+ * fields per request (read scopes). Since 0.1.134 it also governs row
405
+ * identification — a unique index over a hidden field is not an
406
+ * identification for `/one/:id`, `/one?…`, `DELETE`, a PK-less `PATCH` or
407
+ * an action id (primary key and `preferredId` always are) — and the
408
+ * nested-object 400 hint lists visible leaves only. The default accepts every real path
409
+ * (`isValidFieldPath`). `/meta` does NOT consult it — prune hidden fields
410
+ * there with `applyMetaOverlay`. Native text search and vector search
411
+ * (`$vector` names an index) run inside the engine over its indexes, out of
412
+ * this hook's reach — keep hidden fields out of those indexes.
413
+ */
366
414
  protected hasField(path: string): boolean;
367
415
  /**
368
416
  * Structural capability gate (since 0.1.128): walks the PARSED query —
@@ -432,6 +480,14 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
432
480
  private widenPreferredIdProjection;
433
481
  private _widenArrayProjection;
434
482
  private _widenMapProjection;
483
+ /**
484
+ * The logical paths an exclusion keeps. A path goes when it, an ancestor
485
+ * or a descendant is excluded: excluding an object parent excludes its
486
+ * whole subtree, and a kept parent would carry an excluded child back
487
+ * (its other leaves stay listed on their own). Before 0.1.134 only the
488
+ * exact paths were dropped, so `$select=-a` still returned `a`'s leaves.
489
+ */
490
+ private _invertExclusion;
435
491
  /**
436
492
  * Auto-includes the sibling-ref field whenever its `@db.amount.currency.ref`
437
493
  * / `@db.unit.ref` quantity is selected — UI must never get a value without
@@ -502,7 +558,12 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
502
558
  * family (count vs no-count).
503
559
  */
504
560
  private _runReadWithActions;
505
- /** Pick the first identification (PK or unique index) whose fields are all present in the query. */
561
+ /**
562
+ * Pick the first identification (PK or unique index) whose fields are all
563
+ * present in the query. A unique index over a field {@link hasField} hides
564
+ * is not a candidate (since 0.1.134) — `?hidden=x` answers exactly like
565
+ * `?nope=x`, so it cannot probe whether a row with that value exists.
566
+ */
506
567
  protected extractIdShape(query: Record<string, string>): Record<string, unknown> | HttpError;
507
568
  /**
508
569
  * **GET /query** — returns an array of records or a count.
@@ -643,14 +704,17 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
643
704
  * several table operations.
644
705
  */
645
706
  protected withTransaction<R>(fn: () => Promise<R>): Promise<R>;
707
+ /** The table write call's trailing options — see {@link _hookArgs}. */
708
+ private readonly _writeArgs;
709
+ /** `deleteOne`'s trailing options — see {@link _hookArgs}. */
710
+ private readonly _removeArgs;
646
711
  /**
647
- * The table write call's trailing options: `[{ guard }]` only when
648
- * `guardWrite` is overridden, else nothing (the table is called exactly as
649
- * an unmodified controller always called it).
712
+ * A table call's trailing options, built once: `guard` only when the guard
713
+ * hook is overridden, `isFieldVisible` only when `hasField` is (an id or a
714
+ * PK-less payload never resolves through a hidden unique key) — else
715
+ * nothing, so an unmodified controller calls the table exactly as before.
650
716
  */
651
- private _writeArgs;
652
- /** `deleteOne`'s trailing options: `[{ guard }]` only when `guardRemove` is overridden. */
653
- private _removeArgs;
717
+ private _hookArgs;
654
718
  /** Resolves a hook result: `undefined` aborts with `abortMessage`, an `Error` is thrown, anything else passes. */
655
719
  private _checkHook;
656
720
  /** Runs `onWrite` and re-applies the shape gate to its output (a non-object is a 500 "Not saved"). */
@@ -1507,13 +1571,6 @@ declare function getControllerFormType(ctor: Function, name: string): TAtscriptA
1507
1571
  /** Discover actions on a controller, memoized per ctor. `info`-only callers map `e => e.info`. */
1508
1572
  declare function discoverActions(controllerCtor: Function, app: Moost, logger: TConsoleBase): TDbActionEnvelope[];
1509
1573
  //#endregion
1510
- //#region src/actions/id-validation.d.ts
1511
- /** Duck-typed shape; matches `AtscriptDbReadable`'s public surface. */
1512
- interface IdValidationSource {
1513
- readonly identifications: readonly TIdentification[];
1514
- readonly fieldDescriptors: readonly TDbFieldMeta[];
1515
- }
1516
- //#endregion
1517
1574
  //#region src/actions/id-cache.d.ts
1518
1575
  declare const useDbActionId: import("@wooksjs/event-core").WookComposable<{
1519
1576
  load: () => Promise<Record<string, unknown>>;
package/dist/index.d.mts CHANGED
@@ -3,7 +3,7 @@ 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, 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, TIdentification, TMetaResponse, TQueryPathOp, TQueryPathOp as TQueryPathOp$1, TQueryPathRefs, TQueryPathSource, Uniquery, UniqueryControls, collectQueryPaths } from "@atscript/db";
6
+ import { 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";
7
7
  //#region src/as-readable.controller.d.ts
8
8
  /**
9
9
  * Abstract base class for read-only HTTP controllers over an Atscript interface.
@@ -22,7 +22,9 @@ import { AtscriptDbReadable, AtscriptDbTable, BucketUnit, FilterExpr, FlatOf, TC
22
22
  *
23
23
  * Subclass responsibilities:
24
24
  * - Pass the bound interface + logical name + (optional) kind tag through super().
25
- * - Implement {@link hasField} so insights validation can reject unknown keys.
25
+ * - Implement {@link hasField} — the field-visibility hook every validated
26
+ * path consults; a path it rejects gets the same `Unknown field` 400 as a
27
+ * nonexistent one, so overriding it hides fields per request.
26
28
  * - Register the `/query`, `/pages`, `/one(/:id)` routes with the concrete
27
29
  * handlers that match the data source's contract (DB readables route into
28
30
  * aggregate/vector/search; value-help controllers just filter/sort/paginate).
@@ -45,7 +47,11 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
45
47
  /** Cached serialized form schemas keyed by `FormType.name` — populated lazily by {@link metaForm}. */
46
48
  private _formSchemas;
47
49
  constructor(boundType: T, controllerName: string, app: Moost, kindTag?: string);
48
- /** Subclass contract: return `true` if `path` addresses a valid field on the bound source. */
50
+ /**
51
+ * Subclass contract: return `true` if `path` addresses a field that exists
52
+ * AND is visible to the current request — see the DB controller's override
53
+ * for the full list of positions that consult it.
54
+ */
49
55
  protected abstract hasField(path: string): boolean;
50
56
  /** Sets @db.http.path on the type metadata from the controller's computed prefix. */
51
57
  private _resolveHttpPath;
@@ -179,6 +185,13 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
179
185
  protected applyMetaOverlay(meta: TMetaResponse): TMetaResponse | Promise<TMetaResponse>;
180
186
  }
181
187
  //#endregion
188
+ //#region src/actions/id-validation.d.ts
189
+ /** Duck-typed shape; matches `AtscriptDbReadable`'s public surface. */
190
+ interface IdValidationSource {
191
+ readonly identifications: readonly TIdentification[];
192
+ readonly fieldDescriptors: readonly TDbFieldMeta[];
193
+ }
194
+ //#endregion
182
195
  //#region src/meta/field-capabilities.d.ts
183
196
  /**
184
197
  * Per-path HTTP capability of a DB readable — the single source that both the
@@ -195,9 +208,9 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
195
208
  * `filterable` is the value-comparison verdict, `filterOps` the narrower
196
209
  * predicates that still pass where it is `false`.
197
210
  *
198
- * A calendar-bucket source (`bucketable`) needs the physical capability, a
199
- * `number.timestamp` type, dimension status on a strict (dimension / measure
200
- * declaring) table, and an adapter with calendar-bucket units.
211
+ * A calendar-bucket source (`bucketable`) is the core's `bucketSourceVerdict`
212
+ * (the same function the core path guard runs) under the HTTP-only
213
+ * `@db.writeOnly` veto.
201
214
  */
202
215
  interface TFieldCapability {
203
216
  /** A value-comparison filter on this path passes the gate (adapter ∧ ¬writeOnly ∧ ¬encrypted ∧ policy). */
@@ -215,8 +228,8 @@ interface TFieldCapability {
215
228
  /** Present when `sortable` is `false` — the reason clause appended to the HTTP 400 message. */
216
229
  sortReason?: string;
217
230
  /**
218
- * A calendar bucket over this path passes the gate (physical ∧ timestamp ∧
219
- * (¬strict ∨ dimension) ∧ adapter has calendar buckets). Since 0.1.132.
231
+ * A calendar bucket over this path passes the gate (¬writeOnly ∧ the core's
232
+ * `bucketSourceVerdict`). Since 0.1.132.
220
233
  */
221
234
  bucketable: boolean;
222
235
  /** Present when `bucketable` is `false` — the reason clause appended to the HTTP 400 message. */
@@ -272,14 +285,8 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
272
285
  private readonly _entries;
273
286
  /** Nested-object parents (never listed, always selectable) → their listed leaves. */
274
287
  private readonly _objectParents;
275
- /**
276
- * Declared dimensions when the table is strict (declares dimensions or
277
- * measures), else `undefined` — the core rule: a grouping source, a
278
- * bucketed field included, must then be a dimension.
279
- */
280
- private readonly _dimensions;
281
- /** Paths of every JSON-value descriptor (`isJsonValueField`) — see `jsonValueAncestor`. */
282
- private readonly _jsonValueParents;
288
+ /** What `bucketSourceVerdict` reads of the table (JSON-value parents, dimensions, measures). */
289
+ private readonly _bucketTable;
283
290
  /** Listed leaves — the {@link TQueryPathSource} view for `classifyQueryPath`. */
284
291
  get leaves(): ReadonlyMap<string, unknown>;
285
292
  /** Nested-object parents — the {@link TQueryPathSource} view for `classifyQueryPath`. */
@@ -292,16 +299,23 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
292
299
  isPhysicallyFilterable(path: string): boolean;
293
300
  /**
294
301
  * Gate check for one path in one position. Returns `undefined` when the
295
- * path is accepted. Order: navigation paths first (a nav path "exists" on
296
- * the target table but is never a column here), then a listed leaf's
297
- * capability (no existence lookup needed — every listed leaf is a real
298
- * field), then `exists` (the readable's `isValidFieldPath`) and, for paths
299
- * that exist but are not leaves, the storage classification.
302
+ * path is accepted.
303
+ *
304
+ * `exists` runs FIRST, for every path (since 0.1.133): it is the
305
+ * controller's `hasField`, the visibility hook subclasses narrow per
306
+ * request (e.g. a projection-scoped viewer). A path it rejects answers
307
+ * `Unknown field "x"` exactly like a nonexistent one — never a capability
308
+ * or navigation hint, which would reveal the field and let a filter or
309
+ * sort on it act as a value oracle. Before 0.1.133 listed leaves and
310
+ * navigation paths skipped it.
300
311
  *
301
- * Existence deliberately runs BEFORE the JSON / encrypted classification:
302
- * an untyped descendant of a JSON column (`address.nope`) is reported as
303
- * `Unknown field`, not as "inside JSON-stored column" — clients pin that
304
- * wording, so do not "align" it with the core backstop's text.
312
+ * Then: navigation paths (a nav path exists on the target table but is
313
+ * never a column here), a listed leaf's capability, and for other paths
314
+ * the storage classification. Existence also runs BEFORE the JSON /
315
+ * encrypted classification: an untyped descendant of a JSON column
316
+ * (`address.nope`) is reported as `Unknown field`, not as "inside
317
+ * JSON-stored column" — clients pin that wording, so do not "align" it
318
+ * with the core backstop's text.
305
319
  *
306
320
  * `predicate` is a filter entry's class (`collectQueryPaths` records it per
307
321
  * occurrence); it only matters for `op === "filter"` on a listed leaf.
@@ -343,8 +357,17 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
343
357
  private _capabilities?;
344
358
  /** `/meta` is a projection of {@link capabilities}: a rebuilt index rebuilds the cached envelope. */
345
359
  protected metaCacheKey(): unknown;
346
- /** Bound once: the field-existence check the gate hands to `capabilities.check`. */
360
+ /** Bound once: the visibility check ({@link hasField}) the gate hands to `capabilities.check`. */
347
361
  private readonly _exists;
362
+ /**
363
+ * Id-resolution options (since 0.1.134): `{ isFieldVisible: hasField }`
364
+ * when a subclass overrides {@link hasField}, else `undefined` (the default
365
+ * accepts every real path, so resolution stays unfiltered). A unique index
366
+ * over a hidden field is never an identification.
367
+ */
368
+ protected readonly _idOpts: TIdResolveOptions | undefined;
369
+ /** Narrowed id sources, one stable object per distinct visible-identification set. */
370
+ private readonly _idSources;
348
371
  private readonly _preferredIdSet;
349
372
  private readonly _overlayIsNoOp;
350
373
  /** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
@@ -360,9 +383,34 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
360
383
  */
361
384
  private readonly _invertibleFields;
362
385
  constructor(app: Moost, readable?: AtscriptDbReadable<T>);
386
+ /**
387
+ * The identifications this request may address rows through (since
388
+ * 0.1.134): the readable's own, minus unique indexes over fields
389
+ * {@link hasField} hides. Used by `/one?…`, `DELETE /?…` and action `ids`.
390
+ * Stable per distinct outcome, so per-source caches keyed on it hit.
391
+ */
392
+ get idSource(): IdValidationSource;
363
393
  private _collectInvertibleFields;
364
394
  private _collectQuantityRefs;
365
395
  private _collectAnnotated;
396
+ /**
397
+ * THE field-visibility hook: every gated path consults it before any
398
+ * capability check (since 0.1.133) — filter keys (inside `$and` / `$or` /
399
+ * `$not`, existence predicates included), `$sort`, `$select`,
400
+ * `$groupBy`, `$having` keys, aggregate and calendar-bucket `$field`s,
401
+ * `$with` relation names and sub-query paths, and the `$search` fallback
402
+ * fields. A path it rejects is answered exactly like a nonexistent one
403
+ * (`Unknown field "x"` / `Unknown relation "x"`), so override it to hide
404
+ * fields per request (read scopes). Since 0.1.134 it also governs row
405
+ * identification — a unique index over a hidden field is not an
406
+ * identification for `/one/:id`, `/one?…`, `DELETE`, a PK-less `PATCH` or
407
+ * an action id (primary key and `preferredId` always are) — and the
408
+ * nested-object 400 hint lists visible leaves only. The default accepts every real path
409
+ * (`isValidFieldPath`). `/meta` does NOT consult it — prune hidden fields
410
+ * there with `applyMetaOverlay`. Native text search and vector search
411
+ * (`$vector` names an index) run inside the engine over its indexes, out of
412
+ * this hook's reach — keep hidden fields out of those indexes.
413
+ */
366
414
  protected hasField(path: string): boolean;
367
415
  /**
368
416
  * Structural capability gate (since 0.1.128): walks the PARSED query —
@@ -432,6 +480,14 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
432
480
  private widenPreferredIdProjection;
433
481
  private _widenArrayProjection;
434
482
  private _widenMapProjection;
483
+ /**
484
+ * The logical paths an exclusion keeps. A path goes when it, an ancestor
485
+ * or a descendant is excluded: excluding an object parent excludes its
486
+ * whole subtree, and a kept parent would carry an excluded child back
487
+ * (its other leaves stay listed on their own). Before 0.1.134 only the
488
+ * exact paths were dropped, so `$select=-a` still returned `a`'s leaves.
489
+ */
490
+ private _invertExclusion;
435
491
  /**
436
492
  * Auto-includes the sibling-ref field whenever its `@db.amount.currency.ref`
437
493
  * / `@db.unit.ref` quantity is selected — UI must never get a value without
@@ -502,7 +558,12 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
502
558
  * family (count vs no-count).
503
559
  */
504
560
  private _runReadWithActions;
505
- /** Pick the first identification (PK or unique index) whose fields are all present in the query. */
561
+ /**
562
+ * Pick the first identification (PK or unique index) whose fields are all
563
+ * present in the query. A unique index over a field {@link hasField} hides
564
+ * is not a candidate (since 0.1.134) — `?hidden=x` answers exactly like
565
+ * `?nope=x`, so it cannot probe whether a row with that value exists.
566
+ */
506
567
  protected extractIdShape(query: Record<string, string>): Record<string, unknown> | HttpError;
507
568
  /**
508
569
  * **GET /query** — returns an array of records or a count.
@@ -643,14 +704,17 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
643
704
  * several table operations.
644
705
  */
645
706
  protected withTransaction<R>(fn: () => Promise<R>): Promise<R>;
707
+ /** The table write call's trailing options — see {@link _hookArgs}. */
708
+ private readonly _writeArgs;
709
+ /** `deleteOne`'s trailing options — see {@link _hookArgs}. */
710
+ private readonly _removeArgs;
646
711
  /**
647
- * The table write call's trailing options: `[{ guard }]` only when
648
- * `guardWrite` is overridden, else nothing (the table is called exactly as
649
- * an unmodified controller always called it).
712
+ * A table call's trailing options, built once: `guard` only when the guard
713
+ * hook is overridden, `isFieldVisible` only when `hasField` is (an id or a
714
+ * PK-less payload never resolves through a hidden unique key) — else
715
+ * nothing, so an unmodified controller calls the table exactly as before.
650
716
  */
651
- private _writeArgs;
652
- /** `deleteOne`'s trailing options: `[{ guard }]` only when `guardRemove` is overridden. */
653
- private _removeArgs;
717
+ private _hookArgs;
654
718
  /** Resolves a hook result: `undefined` aborts with `abortMessage`, an `Error` is thrown, anything else passes. */
655
719
  private _checkHook;
656
720
  /** Runs `onWrite` and re-applies the shape gate to its output (a non-object is a 500 "Not saved"). */
@@ -1507,13 +1571,6 @@ declare function getControllerFormType(ctor: Function, name: string): TAtscriptA
1507
1571
  /** Discover actions on a controller, memoized per ctor. `info`-only callers map `e => e.info`. */
1508
1572
  declare function discoverActions(controllerCtor: Function, app: Moost, logger: TConsoleBase): TDbActionEnvelope[];
1509
1573
  //#endregion
1510
- //#region src/actions/id-validation.d.ts
1511
- /** Duck-typed shape; matches `AtscriptDbReadable`'s public surface. */
1512
- interface IdValidationSource {
1513
- readonly identifications: readonly TIdentification[];
1514
- readonly fieldDescriptors: readonly TDbFieldMeta[];
1515
- }
1516
- //#endregion
1517
1574
  //#region src/actions/id-cache.d.ts
1518
1575
  declare const useDbActionId: import("@wooksjs/event-core").WookComposable<{
1519
1576
  load: () => Promise<Record<string, unknown>>;