@atscript/db 0.1.133 → 0.1.135

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.
@@ -1,6 +1,6 @@
1
1
  import { f as TFieldOps } from "./ops-AqhV7s9o.mjs";
2
2
  import { FlatOf, FlatOf as FlatOf$1, NavPropsOf, NavPropsOf as NavPropsOf$1, OwnPropsOf, OwnPropsOf as OwnPropsOf$1, PrimaryKeyOf, PrimaryKeyOf as PrimaryKeyOf$1, TAtscriptAnnotatedType, TAtscriptDataType, TAtscriptTypeObject, TMetadataMap, TSerializedAnnotatedType, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
3
- import { AggregateControls, AggregateExpr, AggregateExpr as AggregateExpr$1, AggregateFn, AggregateQuery, AggregateQuery as AggregateQuery$1, AggregateResult, BucketUnit, FieldOpsFor, FilterExpr, FilterExpr as FilterExpr$1, ResolvedBucket, TypedWithRelation, Uniquery, Uniquery as Uniquery$1, UniqueryControls, UniqueryControls as UniqueryControls$1, UniqueryInsights, WithRelation, WithRelation as WithRelation$1 } from "@uniqu/core";
3
+ import { AggregateControls, AggregateExpr, AggregateExpr as AggregateExpr$1, AggregateFn as AggregateFn$1, AggregateQuery, AggregateQuery as AggregateQuery$1, AggregateResult, BucketUnit, FieldOpsFor, FilterExpr, FilterExpr as FilterExpr$1, ResolvedBucket, TypedWithRelation, Uniquery, Uniquery as Uniquery$1, UniqueryControls, UniqueryControls as UniqueryControls$1, UniqueryInsights, WithRelation, WithRelation as WithRelation$1 } from "@uniqu/core";
4
4
 
5
5
  //#region src/query/uniqu-select.d.ts
6
6
  /**
@@ -361,6 +361,14 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
361
361
  get preferredId(): readonly string[];
362
362
  /** Legitimate row-identifier shapes (primary key + every unique index). */
363
363
  get identifications(): readonly TIdentification[];
364
+ /**
365
+ * The {@link identifications} an id may resolve through when fields are
366
+ * hidden per request (since 0.1.134): a unique index with a field that
367
+ * fails `isFieldVisible` is dropped, as if it did not exist. Primary-key,
368
+ * `preferredId` and `@meta.id` fields always count as visible. Without a
369
+ * predicate, every identification.
370
+ */
371
+ identificationsVisibleTo(isFieldVisible?: TIdResolveOptions["isFieldVisible"]): readonly TIdentification[];
364
372
  /**
365
373
  * Physical column name of the single `@meta.id` field, or `null` when the
366
374
  * schema has zero or multiple `@meta.id` fields. Used by adapters to return
@@ -414,6 +422,19 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
414
422
  * Pre-computed field metadata for adapter use.
415
423
  */
416
424
  get fieldDescriptors(): readonly TDbFieldMeta[];
425
+ /**
426
+ * The target table of the navigation relation `navField` (since 0.1.134),
427
+ * resolved through the table resolver this readable was built with (the
428
+ * `DbSpace`'s). `undefined` when `navField` is not a relation of this table
429
+ * or no resolver is available (a table constructed without a `DbSpace`).
430
+ * Top-level relation names only — no dotted paths.
431
+ *
432
+ * ```typescript
433
+ * const users = posts.relatedTable('author')
434
+ * users?.primaryKeys // ['id']
435
+ * ```
436
+ */
437
+ relatedTable(navField: string): ReturnType<TTableResolver>;
417
438
  /**
418
439
  * Resolves whether `path` references a real field — directly via `flatMap`
419
440
  * or transitively through a nav relation by recursing into the target
@@ -567,17 +588,20 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
567
588
  * same identification resolution as {@link findById}. Public so callers can
568
589
  * AND-combine the id-filter with a row-level read overlay before issuing
569
590
  * `findOne` (avoiding the existence leak that `findById` would cause).
591
+ * `opts.isFieldVisible` (since 0.1.134) drops unique indexes over hidden
592
+ * fields — see {@link identificationsVisibleTo}.
570
593
  */
571
- resolveIdFilter(id: unknown): FilterExpr | null;
594
+ resolveIdFilter(id: unknown, opts?: TIdResolveOptions): FilterExpr | null;
572
595
  /**
573
596
  * Resolve an id value into a filter expression.
574
597
  *
575
598
  * When `preferredId` differs from the PK, scalar ids resolve only against
576
599
  * the preferred field (deterministic addressing). Otherwise scalars try PK
577
600
  * + every single-field unique index; objects try PK + compound unique
578
- * indexes.
601
+ * indexes. With `opts.isFieldVisible`, only the identifications
602
+ * {@link identificationsVisibleTo} keeps are tried.
579
603
  */
580
- protected _resolveIdFilter(id: unknown): FilterExpr | null;
604
+ protected _resolveIdFilter(id: unknown, opts?: TIdResolveOptions): FilterExpr | null;
581
605
  /** Build a single-key filter from `idObj` over `fields`, or null if any field is missing/incompatible. */
582
606
  private _tryCompoundFilter;
583
607
  /**
@@ -1391,6 +1415,7 @@ declare class TableMetadata {
1391
1415
  physicalNames: ReadonlySet<string>;
1392
1416
  private _built;
1393
1417
  private _identifications?;
1418
+ private _alwaysAddressable?;
1394
1419
  private _collateMap;
1395
1420
  private _columnFromMap;
1396
1421
  constructor(nestedObjects: boolean);
@@ -1487,6 +1512,12 @@ declare class TableMetadata {
1487
1512
  private _buildIdentifications;
1488
1513
  /** Legitimate row-identifier shapes — primary key first, then each unique index. */
1489
1514
  getIdentifications(): readonly TIdentification[];
1515
+ /**
1516
+ * Fields that always count as visible for row identification (since
1517
+ * 0.1.134): primary key, `preferredId` and `@meta.id` fields — see
1518
+ * `AtscriptDbReadable.identificationsVisibleTo`.
1519
+ */
1520
+ getAlwaysAddressable(): ReadonlySet<string>;
1490
1521
  private _resolvePreferredId;
1491
1522
  }
1492
1523
  //#endregion
@@ -2114,8 +2145,13 @@ interface TTouchManyOptions {
2114
2145
  */
2115
2146
  require?: "all" | "any";
2116
2147
  }
2117
- /** Options of `insertOne/Many`, `replaceOne` / `bulkReplace`, `updateOne` / `bulkUpdate`. */
2118
- interface TWriteOptions<Row = Record<string, unknown>> {
2148
+ /**
2149
+ * Options of `insertOne/Many`, `replaceOne` / `bulkReplace`, `updateOne` / `bulkUpdate`.
2150
+ * `isFieldVisible` (since 0.1.134, see {@link TIdResolveOptions}) applies to the
2151
+ * top-level rows only — a payload without its primary key identifies through
2152
+ * a unique index; nested-relation writes ignore it.
2153
+ */
2154
+ interface TWriteOptions<Row = Record<string, unknown>> extends TIdResolveOptions {
2119
2155
  /** Nested-relation write recursion limit (default 3). */
2120
2156
  maxDepth?: number;
2121
2157
  /**
@@ -2128,8 +2164,8 @@ interface TWriteOptions<Row = Record<string, unknown>> {
2128
2164
  */
2129
2165
  guard?: TDbWriteGuard<Row>;
2130
2166
  }
2131
- /** Options of `deleteOne`. */
2132
- interface TDeleteOptions<Row = Record<string, unknown>> {
2167
+ /** Options of `deleteOne`. `isFieldVisible` since 0.1.134 — see {@link TIdResolveOptions}. */
2168
+ interface TDeleteOptions<Row = Record<string, unknown>> extends TIdResolveOptions {
2133
2169
  /**
2134
2170
  * Validated-stage guard (since 0.1.128): invoked inside the table's
2135
2171
  * transaction after the id resolved to a filter and before cascade /
@@ -2137,6 +2173,17 @@ interface TDeleteOptions<Row = Record<string, unknown>> {
2137
2173
  */
2138
2174
  guard?: TDbRemoveGuard<Row>;
2139
2175
  }
2176
+ /** Options of `resolveIdFilter` / `identificationsVisibleTo` (since 0.1.134). */
2177
+ interface TIdResolveOptions {
2178
+ /**
2179
+ * Field-visibility predicate (e.g. a per-request read scope). A unique-index
2180
+ * identification with a field that fails it is ignored — the id resolves
2181
+ * exactly as if that index did not exist, so a hidden unique key can never
2182
+ * answer "a row with this value exists". Primary-key, `preferredId` and
2183
+ * `@meta.id` fields always count as visible.
2184
+ */
2185
+ isFieldVisible?: (path: string) => boolean;
2186
+ }
2140
2187
  /**
2141
2188
  * Adds `null` to every optional property of `O`. Optional columns store SQL
2142
2189
  * NULL / Mongo null, and the runtime validator accepts `null` for optional
@@ -2176,7 +2223,8 @@ interface TBucketFieldSource {
2176
2223
  * appear in $groupBy", string `$groupBy` entries) with the table's names as
2177
2224
  * the collision set: a bucket alias may not equal a logical path, a physical
2178
2225
  * column or a navigation field, so a label is never reverse-mapped as a
2179
- * column.
2226
+ * column. Aggregate entries are checked against `SUPPORTED_AGGREGATE_FNS`
2227
+ * (and `'*'` is `count`'s only).
2180
2228
  *
2181
2229
  * Which layer validates what:
2182
2230
  * - **Shapes** (this normalizer) run FIRST at every entry point — the core's
@@ -2227,4 +2275,4 @@ declare function isJsonValueField(fd: TDbFieldMeta): boolean;
2227
2275
  */
2228
2276
  declare function jsonValueAncestor(path: string, jsonValueParents: ReadonlySet<string>): string | undefined;
2229
2277
  //#endregion
2230
- export { TDbWriteGuardContext as $, TDbActionIntent as A, isGeoPointType as At, TDbIndexField as B, NoopLogger as Bt, PrimaryKeyOf$1 as C, TypedWithRelation as Ct, TCrudOp as D, TableMetadata as Dt, TColumnDiff as E, WithRelation$1 as Et, TDbDefaultValue as F, resolveDesignType as Ft, TDbReferentialAction as G, TDbInsertManyResult as H, UniquSelect as Ht, TDbDeleteResult as I, DbEncryption as It, TDbRemoveGuardContext as J, TDbRelation as K, TDbFieldMeta as L, TDbEncryptionOptions as Lt, TDbActionProcessor as M, BaseDbAdapter as Mt, TDbCollation as N, AtscriptDbReadable as Nt, TCrudPermissions as O, findAncestorInSet as Ot, TDbDefaultFn as P, DbResponse as Pt, TDbWriteGuard as Q, TDbForeignKey as R, DocumentFieldMapper as Rt, OwnPropsOf$1 as S, TWriteTableResolver as St, TCascadeTarget as T, UniqueryControls$1 as Tt, TDbInsertResult as U, TDbIndexType as V, TGenericLogger as Vt, TDbObjectKind as W, TDbUpdateResult as X, TDbStorageType as Y, TDbWriteAction as Z, FieldOpsFor as _, TTableOptionDiff as _t, jsonValueAncestor as a, TFieldMeta as at, NavPropsOf$1 as b, TValueFormatterPair as bt, AggregateExpr$1 as c, TIdDescriptor as ct, AggregateResult as d, TMetadataOverrides as dt, TDeleteOptions as et, AtscriptDbWritable as f, TPrimaryKeyChange as ft, DbRow as g, TSyncColumnResult as gt, DbQuery as h, TSearchIndexInfo as ht, isJsonValueField as i, TExistingTableOption as it, TDbActionLevel as j, ALL_BUCKET_UNITS as jt, TDbActionInfo as k, isGeoIndexableType as kt, AggregateFn as l, TIdentification as lt, DbPatch as m, TRelationInfo as mt, TResolvedBucket as n, TExistingColumn as nt, resolveCalendarBuckets as o, TFkLookupResolver as ot, DbControls as p, TReferencingForeignKey as pt, TDbRemoveGuard as q, isBucketableField as r, TExistingForeignKey as rt, AggregateControls as s, TFkLookupTarget as st, TBucketFieldSource as t, TEnsureTableOptions as tt, AggregateQuery$1 as u, TMetaResponse as ut, FilterExpr$1 as v, TTableResolver as vt, TCascadeResolver as w, Uniquery$1 as wt, NullableOptional as x, TWriteOptions as xt, FlatOf$1 as y, TTouchManyOptions as yt, TDbIndex as z, FieldMappingStrategy as zt };
2278
+ export { TDbWriteGuardContext as $, TDbActionIntent as A, isGeoIndexableType as At, TDbIndexField as B, FieldMappingStrategy as Bt, PrimaryKeyOf$1 as C, TWriteTableResolver as Ct, TCrudOp as D, WithRelation$1 as Dt, TColumnDiff as E, UniqueryControls$1 as Et, TDbDefaultValue as F, DbResponse as Ft, TDbReferentialAction as G, TDbInsertManyResult as H, TGenericLogger as Ht, TDbDeleteResult as I, resolveDesignType as It, TDbRemoveGuardContext as J, TDbRelation as K, TDbFieldMeta as L, DbEncryption as Lt, TDbActionProcessor as M, ALL_BUCKET_UNITS as Mt, TDbCollation as N, BaseDbAdapter as Nt, TCrudPermissions as O, TableMetadata as Ot, TDbDefaultFn as P, AtscriptDbReadable as Pt, TDbWriteGuard as Q, TDbForeignKey as R, TDbEncryptionOptions as Rt, OwnPropsOf$1 as S, TWriteOptions as St, TCascadeTarget as T, Uniquery$1 as Tt, TDbInsertResult as U, UniquSelect as Ut, TDbIndexType as V, NoopLogger as Vt, TDbObjectKind as W, TDbUpdateResult as X, TDbStorageType as Y, TDbWriteAction as Z, FieldOpsFor as _, TSyncColumnResult as _t, jsonValueAncestor as a, TFieldMeta as at, NavPropsOf$1 as b, TTouchManyOptions as bt, AggregateExpr$1 as c, TIdDescriptor as ct, AggregateResult as d, TMetaResponse as dt, TDeleteOptions as et, AtscriptDbWritable as f, TMetadataOverrides as ft, DbRow as g, TSearchIndexInfo as gt, DbQuery as h, TRelationInfo as ht, isJsonValueField as i, TExistingTableOption as it, TDbActionLevel as j, isGeoPointType as jt, TDbActionInfo as k, findAncestorInSet as kt, AggregateFn$1 as l, TIdResolveOptions as lt, DbPatch as m, TReferencingForeignKey as mt, TResolvedBucket as n, TExistingColumn as nt, resolveCalendarBuckets as o, TFkLookupResolver as ot, DbControls as p, TPrimaryKeyChange as pt, TDbRemoveGuard as q, isBucketableField as r, TExistingForeignKey as rt, AggregateControls as s, TFkLookupTarget as st, TBucketFieldSource as t, TEnsureTableOptions as tt, AggregateQuery$1 as u, TIdentification as ut, FilterExpr$1 as v, TTableOptionDiff as vt, TCascadeResolver as w, TypedWithRelation as wt, NullableOptional as x, TValueFormatterPair as xt, FlatOf$1 as y, TTableResolver as yt, TDbIndex as z, DocumentFieldMapper as zt };
@@ -1,4 +1,4 @@
1
- import { Dt as TableMetadata, H as TDbInsertManyResult, I as TDbDeleteResult, It as DbEncryption, Lt as TDbEncryptionOptions, Mt as BaseDbAdapter, Nt as AtscriptDbReadable, St as TWriteTableResolver, U as TDbInsertResult, Vt as TGenericLogger, X as TDbUpdateResult, et as TDeleteOptions, g as DbRow, m as DbPatch, ot as TFkLookupResolver, pt as TReferencingForeignKey, vt as TTableResolver, w as TCascadeResolver, x as NullableOptional, xt as TWriteOptions, yt as TTouchManyOptions } from "./buckets-BENuu_tK.mjs";
1
+ import { Ct as TWriteTableResolver, H as TDbInsertManyResult, Ht as TGenericLogger, I as TDbDeleteResult, Lt as DbEncryption, Nt as BaseDbAdapter, Ot as TableMetadata, Pt as AtscriptDbReadable, Rt as TDbEncryptionOptions, St as TWriteOptions, U as TDbInsertResult, X as TDbUpdateResult, bt as TTouchManyOptions, et as TDeleteOptions, g as DbRow, lt as TIdResolveOptions, m as DbPatch, mt as TReferencingForeignKey, ot as TFkLookupResolver, w as TCascadeResolver, x as NullableOptional, yt as TTableResolver } from "./buckets-GruoVxH5.mjs";
2
2
  import { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryFieldRef as AtscriptQueryFieldRef$1, AtscriptQueryNode, AtscriptQueryNode as AtscriptQueryNode$1, AtscriptRef, FlatOf, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAtscriptAnnotatedType, TAtscriptDataType, Validator } from "@atscript/typescript/utils";
3
3
  import { FilterExpr } from "@uniqu/core";
4
4
 
@@ -202,9 +202,11 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
202
202
  * 2. Single-field unique index — first `@db.index.unique` field found.
203
203
  * 3. Compound unique index — first compound unique index whose fields are all present.
204
204
  *
205
- * Throws when no identifying fields can be found.
205
+ * Throws when no identifying fields can be found. With `isFieldVisible`
206
+ * (since 0.1.134), a unique index over a hidden field is skipped, as if it
207
+ * did not exist — see {@link identificationsVisibleTo}.
206
208
  */
207
- protected _extractRecordFilter(payload: Record<string, unknown>): FilterExpr;
209
+ protected _extractRecordFilter(payload: Record<string, unknown>, opts?: TIdResolveOptions): FilterExpr;
208
210
  private _prepareFilterValue;
209
211
  /**
210
212
  * Lazy — builds a `normalized-path → from-depth` map from `this._meta.flatMap`
@@ -1,4 +1,4 @@
1
- import { Dt as TableMetadata, H as TDbInsertManyResult, I as TDbDeleteResult, It as DbEncryption, Lt as TDbEncryptionOptions, Mt as BaseDbAdapter, Nt as AtscriptDbReadable, St as TWriteTableResolver, U as TDbInsertResult, Vt as TGenericLogger, X as TDbUpdateResult, et as TDeleteOptions, g as DbRow, m as DbPatch, ot as TFkLookupResolver, pt as TReferencingForeignKey, vt as TTableResolver, w as TCascadeResolver, x as NullableOptional, xt as TWriteOptions, yt as TTouchManyOptions } from "./buckets-CTWy7Y1k.cjs";
1
+ import { Ct as TWriteTableResolver, H as TDbInsertManyResult, Ht as TGenericLogger, I as TDbDeleteResult, Lt as DbEncryption, Nt as BaseDbAdapter, Ot as TableMetadata, Pt as AtscriptDbReadable, Rt as TDbEncryptionOptions, St as TWriteOptions, U as TDbInsertResult, X as TDbUpdateResult, bt as TTouchManyOptions, et as TDeleteOptions, g as DbRow, lt as TIdResolveOptions, m as DbPatch, mt as TReferencingForeignKey, ot as TFkLookupResolver, w as TCascadeResolver, x as NullableOptional, yt as TTableResolver } from "./buckets-D6PBXKRJ.cjs";
2
2
  import { FilterExpr } from "@uniqu/core";
3
3
  import { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryFieldRef as AtscriptQueryFieldRef$1, AtscriptQueryNode, AtscriptQueryNode as AtscriptQueryNode$1, AtscriptRef, FlatOf, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAtscriptAnnotatedType, TAtscriptDataType, Validator } from "@atscript/typescript/utils";
4
4
 
@@ -202,9 +202,11 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
202
202
  * 2. Single-field unique index — first `@db.index.unique` field found.
203
203
  * 3. Compound unique index — first compound unique index whose fields are all present.
204
204
  *
205
- * Throws when no identifying fields can be found.
205
+ * Throws when no identifying fields can be found. With `isFieldVisible`
206
+ * (since 0.1.134), a unique index over a hidden field is skipped, as if it
207
+ * did not exist — see {@link identificationsVisibleTo}.
206
208
  */
207
- protected _extractRecordFilter(payload: Record<string, unknown>): FilterExpr;
209
+ protected _extractRecordFilter(payload: Record<string, unknown>, opts?: TIdResolveOptions): FilterExpr;
208
210
  private _prepareFilterValue;
209
211
  /**
210
212
  * Lazy — builds a `normalized-path → from-depth` map from `this._meta.flatMap`
@@ -1,5 +1,5 @@
1
1
  const require_db_error = require("./db-error-DTkkeu5b.cjs");
2
- require("./agg.cjs");
2
+ const require_agg = require("./agg-EcIbAFnJ.cjs");
3
3
  const require_validator = require("./validator-BtZbcLN2.cjs");
4
4
  const require_nested_writer = require("./nested-writer-wk1EFUNY.cjs");
5
5
  const require_ops = require("./ops.cjs");
@@ -23,7 +23,8 @@ const NoopLogger = {
23
23
  * appear in $groupBy", string `$groupBy` entries) with the table's names as
24
24
  * the collision set: a bucket alias may not equal a logical path, a physical
25
25
  * column or a navigation field, so a label is never reverse-mapped as a
26
- * column.
26
+ * column. Aggregate entries are checked against `SUPPORTED_AGGREGATE_FNS`
27
+ * (and `'*'` is `count`'s only).
27
28
  *
28
29
  * Which layer validates what:
29
30
  * - **Shapes** (this normalizer) run FIRST at every entry point — the core's
@@ -48,6 +49,7 @@ const NoopLogger = {
48
49
  function resolveCalendarBuckets(controls, fields, aggregate) {
49
50
  const res = (0, _uniqu_core.resolveBuckets)(controls, {
50
51
  aggregate,
52
+ fns: require_agg.SUPPORTED_AGGREGATE_FNS,
51
53
  isField: (name) => fields.flatMap.has(name) || fields.navFields.has(name) || fields.physicalNames.has(name)
52
54
  });
53
55
  if (!res.ok) throw new require_db_error.DbError("INVALID_QUERY", res.issues);
@@ -212,6 +214,7 @@ var TableMetadata = class {
212
214
  physicalNames = /* @__PURE__ */ new Set();
213
215
  _built = false;
214
216
  _identifications;
217
+ _alwaysAddressable;
215
218
  _collateMap = /* @__PURE__ */ new Map();
216
219
  _columnFromMap = /* @__PURE__ */ new Map();
217
220
  constructor(nestedObjects) {
@@ -281,6 +284,11 @@ var TableMetadata = class {
281
284
  if (!this.nestedObjects) this._buildLeafIndexes();
282
285
  this._buildIdentifications();
283
286
  this._resolvePreferredId(type);
287
+ this._alwaysAddressable = new Set([
288
+ ...this.primaryKeys,
289
+ ...this.preferredId,
290
+ ...this.originalMetaIdFields
291
+ ]);
284
292
  this._finalizeIndexes();
285
293
  this._collateMap.clear();
286
294
  this._columnFromMap.clear();
@@ -739,6 +747,14 @@ var TableMetadata = class {
739
747
  getIdentifications() {
740
748
  return this._identifications ?? [];
741
749
  }
750
+ /**
751
+ * Fields that always count as visible for row identification (since
752
+ * 0.1.134): primary key, `preferredId` and `@meta.id` fields — see
753
+ * `AtscriptDbReadable.identificationsVisibleTo`.
754
+ */
755
+ getAlwaysAddressable() {
756
+ return this._alwaysAddressable ?? /* @__PURE__ */ new Set();
757
+ }
742
758
  _resolvePreferredId(type) {
743
759
  const preferred = type.metadata.get("db.table.preferredId.uniqueIndex");
744
760
  if (preferred === void 0) {
@@ -2116,6 +2132,19 @@ var AtscriptDbReadable = class {
2116
2132
  return this._meta.getIdentifications();
2117
2133
  }
2118
2134
  /**
2135
+ * The {@link identifications} an id may resolve through when fields are
2136
+ * hidden per request (since 0.1.134): a unique index with a field that
2137
+ * fails `isFieldVisible` is dropped, as if it did not exist. Primary-key,
2138
+ * `preferredId` and `@meta.id` fields always count as visible. Without a
2139
+ * predicate, every identification.
2140
+ */
2141
+ identificationsVisibleTo(isFieldVisible) {
2142
+ const all = this.identifications;
2143
+ if (!isFieldVisible) return all;
2144
+ const always = this._meta.getAlwaysAddressable();
2145
+ return all.filter((ident) => ident.fields.every((f) => always.has(f) || isFieldVisible(f)));
2146
+ }
2147
+ /**
2119
2148
  * Physical column name of the single `@meta.id` field, or `null` when the
2120
2149
  * schema has zero or multiple `@meta.id` fields. Used by adapters to return
2121
2150
  * the user's logical ID instead of the DB-generated one on insert.
@@ -2235,6 +2264,24 @@ var AtscriptDbReadable = class {
2235
2264
  return this._meta.fieldDescriptors;
2236
2265
  }
2237
2266
  /**
2267
+ * The target table of the navigation relation `navField` (since 0.1.134),
2268
+ * resolved through the table resolver this readable was built with (the
2269
+ * `DbSpace`'s). `undefined` when `navField` is not a relation of this table
2270
+ * or no resolver is available (a table constructed without a `DbSpace`).
2271
+ * Top-level relation names only — no dotted paths.
2272
+ *
2273
+ * ```typescript
2274
+ * const users = posts.relatedTable('author')
2275
+ * users?.primaryKeys // ['id']
2276
+ * ```
2277
+ */
2278
+ relatedTable(navField) {
2279
+ this._ensureBuilt();
2280
+ const targetType = this._meta.relations.get(navField)?.targetType();
2281
+ if (!targetType || !this._tableResolver) return;
2282
+ return this._tableResolver(targetType);
2283
+ }
2284
+ /**
2238
2285
  * Resolves whether `path` references a real field — directly via `flatMap`
2239
2286
  * or transitively through a nav relation by recursing into the target
2240
2287
  * table. Defense-in-depth for query-path validation: `flattenAnnotatedType`
@@ -2250,12 +2297,7 @@ var AtscriptDbReadable = class {
2250
2297
  if (dotIdx === -1) return false;
2251
2298
  const head = path.slice(0, dotIdx);
2252
2299
  const tail = path.slice(dotIdx + 1);
2253
- if (!this.navFields.has(head)) return false;
2254
- const relation = this._meta.relations.get(head);
2255
- if (!relation) return false;
2256
- const targetType = relation.targetType();
2257
- if (!targetType || !this._tableResolver) return false;
2258
- const targetTable = this._tableResolver(targetType);
2300
+ const targetTable = this.relatedTable(head);
2259
2301
  if (!targetTable || typeof targetTable.isValidFieldPath !== "function") return false;
2260
2302
  const visited = _visited ?? /* @__PURE__ */ new Set();
2261
2303
  const cycleKey = `${this.tableName}:${head}`;
@@ -2621,9 +2663,11 @@ var AtscriptDbReadable = class {
2621
2663
  * same identification resolution as {@link findById}. Public so callers can
2622
2664
  * AND-combine the id-filter with a row-level read overlay before issuing
2623
2665
  * `findOne` (avoiding the existence leak that `findById` would cause).
2666
+ * `opts.isFieldVisible` (since 0.1.134) drops unique indexes over hidden
2667
+ * fields — see {@link identificationsVisibleTo}.
2624
2668
  */
2625
- resolveIdFilter(id) {
2626
- return this._resolveIdFilter(id);
2669
+ resolveIdFilter(id, opts) {
2670
+ return this._resolveIdFilter(id, opts);
2627
2671
  }
2628
2672
  /**
2629
2673
  * Resolve an id value into a filter expression.
@@ -2631,9 +2675,10 @@ var AtscriptDbReadable = class {
2631
2675
  * When `preferredId` differs from the PK, scalar ids resolve only against
2632
2676
  * the preferred field (deterministic addressing). Otherwise scalars try PK
2633
2677
  * + every single-field unique index; objects try PK + compound unique
2634
- * indexes.
2678
+ * indexes. With `opts.isFieldVisible`, only the identifications
2679
+ * {@link identificationsVisibleTo} keeps are tried.
2635
2680
  */
2636
- _resolveIdFilter(id) {
2681
+ _resolveIdFilter(id, opts) {
2637
2682
  const pkFields = this.primaryKeys;
2638
2683
  const preferredFields = this.preferredId;
2639
2684
  const isExplicitPreferred = preferredFields.length !== pkFields.length || preferredFields.some((f, i) => f !== pkFields[i]);
@@ -2645,12 +2690,13 @@ var AtscriptDbReadable = class {
2645
2690
  };
2646
2691
  const orFilters = [];
2647
2692
  const idObj = isScalar ? null : id;
2648
- for (const ident of this.identifications) {
2693
+ const identifications = this.identificationsVisibleTo(opts?.isFieldVisible);
2694
+ for (const ident of identifications) {
2649
2695
  if (ident.fields.length !== 1) continue;
2650
2696
  const filter = tryScalarOrField(ident.fields[0]);
2651
2697
  if (filter) orFilters.push(filter);
2652
2698
  }
2653
- if (idObj) for (const ident of this.identifications) {
2699
+ if (idObj) for (const ident of identifications) {
2654
2700
  if (ident.fields.length < 2) continue;
2655
2701
  if (ident.source !== "primaryKey" && orFilters.length > 0) break;
2656
2702
  const filter = this._tryCompoundFilter(ident.fields, idObj);
@@ -4052,7 +4098,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
4052
4098
  const data = items[i];
4053
4099
  for (const navField of this._meta.navFields) delete data[navField];
4054
4100
  if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
4055
- const filter = this._extractRecordFilter(data);
4101
+ const filter = this._extractRecordFilter(data, opts);
4056
4102
  const prepared = this._fieldMapper.prepareForWrite(data, this._meta, this.adapter);
4057
4103
  const result = await this.adapter.replaceOne(this._fieldMapper.translateFilter(filter, this._meta), prepared, expectedVersions[i]);
4058
4104
  matchedCount += result.matchedCount;
@@ -4127,7 +4173,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
4127
4173
  const expectedVersion = expectedVersions[i];
4128
4174
  const data = { ...payload };
4129
4175
  for (const navField of this._meta.navFields) delete data[navField];
4130
- const filter = this._extractRecordFilter(data);
4176
+ const filter = this._extractRecordFilter(data, opts);
4131
4177
  for (const key of Object.keys(filter)) delete data[key];
4132
4178
  if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
4133
4179
  const translatedFilter = this._fieldMapper.translateFilter(filter, this._meta);
@@ -4261,7 +4307,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
4261
4307
  */
4262
4308
  async deleteOne(id, opts) {
4263
4309
  this._ensureBuilt();
4264
- const filter = this._resolveIdFilter(id);
4310
+ const filter = this._resolveIdFilter(id, opts);
4265
4311
  if (!filter) return { deletedCount: 0 };
4266
4312
  const guard = opts?.guard;
4267
4313
  const needsCascade = this._integrity.needsCascade(this._cascadeResolver);
@@ -4435,9 +4481,11 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
4435
4481
  * 2. Single-field unique index — first `@db.index.unique` field found.
4436
4482
  * 3. Compound unique index — first compound unique index whose fields are all present.
4437
4483
  *
4438
- * Throws when no identifying fields can be found.
4484
+ * Throws when no identifying fields can be found. With `isFieldVisible`
4485
+ * (since 0.1.134), a unique index over a hidden field is skipped, as if it
4486
+ * did not exist — see {@link identificationsVisibleTo}.
4439
4487
  */
4440
- _extractRecordFilter(payload) {
4488
+ _extractRecordFilter(payload, opts) {
4441
4489
  const pkFields = this.primaryKeys;
4442
4490
  if (pkFields.length > 0) {
4443
4491
  let allPresent = true;
@@ -4451,17 +4499,15 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
4451
4499
  return filter;
4452
4500
  }
4453
4501
  }
4454
- for (const prop of this.uniqueProps) if (payload[prop] !== void 0) return { [prop]: this._prepareFilterValue(prop, payload[prop]) };
4455
- for (const index of this._meta.indexes.values()) {
4456
- if (index.type !== "unique" || index.fields.length < 2) continue;
4457
- let allPresent = true;
4458
- for (const indexField of index.fields) if (payload[indexField.name] === void 0) {
4459
- allPresent = false;
4460
- break;
4461
- }
4462
- if (allPresent) {
4502
+ const identifications = this.identificationsVisibleTo(opts?.isFieldVisible);
4503
+ const singleFields = /* @__PURE__ */ new Set();
4504
+ for (const ident of identifications) if (ident.source !== "primaryKey" && ident.fields.length === 1) singleFields.add(ident.fields[0]);
4505
+ for (const prop of this.uniqueProps) if (singleFields.has(prop) && payload[prop] !== void 0) return { [prop]: this._prepareFilterValue(prop, payload[prop]) };
4506
+ for (const ident of identifications) {
4507
+ if (ident.source === "primaryKey" || ident.fields.length < 2) continue;
4508
+ if (ident.fields.every((field) => payload[field] !== void 0)) {
4463
4509
  const filter = {};
4464
- for (const indexField of index.fields) filter[indexField.name] = this._prepareFilterValue(indexField.name, payload[indexField.name]);
4510
+ for (const field of ident.fields) filter[field] = this._prepareFilterValue(field, payload[field]);
4465
4511
  return filter;
4466
4512
  }
4467
4513
  }
@@ -1,5 +1,5 @@
1
1
  import { n as CasMismatchError, r as DbError } from "./db-error-D5uilS_A.mjs";
2
- import { resolveAlias } from "./agg.mjs";
2
+ import { a as SUPPORTED_AGGREGATE_FNS, i as resolveAlias } from "./agg-CvXDGnKi.mjs";
3
3
  import { a as forceNavNonOptional, c as getKeyProps, i as dbPlugin, l as isEmptyObject, n as buildPatchPartial, t as buildDbValidator, u as isPlainObject } from "./validator-Ch7UIQl9.mjs";
4
4
  import { a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, g as findRemoteFK, h as findFKForRelation, i as batchPatchNestedFrom, l as batchReplaceNestedVia, m as remapDeleteFkViolation, n as batchInsertNestedTo, o as batchPatchNestedVia, p as enrichFkViolation, r as batchInsertNestedVia, s as batchReplaceNestedFrom, t as batchInsertNestedFrom, u as checkDepthOverflow } from "./nested-writer-CqL24ojl.mjs";
5
5
  import { separateCas, separateFieldOps } from "./ops.mjs";
@@ -23,7 +23,8 @@ const NoopLogger = {
23
23
  * appear in $groupBy", string `$groupBy` entries) with the table's names as
24
24
  * the collision set: a bucket alias may not equal a logical path, a physical
25
25
  * column or a navigation field, so a label is never reverse-mapped as a
26
- * column.
26
+ * column. Aggregate entries are checked against `SUPPORTED_AGGREGATE_FNS`
27
+ * (and `'*'` is `count`'s only).
27
28
  *
28
29
  * Which layer validates what:
29
30
  * - **Shapes** (this normalizer) run FIRST at every entry point — the core's
@@ -48,6 +49,7 @@ const NoopLogger = {
48
49
  function resolveCalendarBuckets(controls, fields, aggregate) {
49
50
  const res = resolveBuckets(controls, {
50
51
  aggregate,
52
+ fns: SUPPORTED_AGGREGATE_FNS,
51
53
  isField: (name) => fields.flatMap.has(name) || fields.navFields.has(name) || fields.physicalNames.has(name)
52
54
  });
53
55
  if (!res.ok) throw new DbError("INVALID_QUERY", res.issues);
@@ -212,6 +214,7 @@ var TableMetadata = class {
212
214
  physicalNames = /* @__PURE__ */ new Set();
213
215
  _built = false;
214
216
  _identifications;
217
+ _alwaysAddressable;
215
218
  _collateMap = /* @__PURE__ */ new Map();
216
219
  _columnFromMap = /* @__PURE__ */ new Map();
217
220
  constructor(nestedObjects) {
@@ -281,6 +284,11 @@ var TableMetadata = class {
281
284
  if (!this.nestedObjects) this._buildLeafIndexes();
282
285
  this._buildIdentifications();
283
286
  this._resolvePreferredId(type);
287
+ this._alwaysAddressable = new Set([
288
+ ...this.primaryKeys,
289
+ ...this.preferredId,
290
+ ...this.originalMetaIdFields
291
+ ]);
284
292
  this._finalizeIndexes();
285
293
  this._collateMap.clear();
286
294
  this._columnFromMap.clear();
@@ -739,6 +747,14 @@ var TableMetadata = class {
739
747
  getIdentifications() {
740
748
  return this._identifications ?? [];
741
749
  }
750
+ /**
751
+ * Fields that always count as visible for row identification (since
752
+ * 0.1.134): primary key, `preferredId` and `@meta.id` fields — see
753
+ * `AtscriptDbReadable.identificationsVisibleTo`.
754
+ */
755
+ getAlwaysAddressable() {
756
+ return this._alwaysAddressable ?? /* @__PURE__ */ new Set();
757
+ }
742
758
  _resolvePreferredId(type) {
743
759
  const preferred = type.metadata.get("db.table.preferredId.uniqueIndex");
744
760
  if (preferred === void 0) {
@@ -2116,6 +2132,19 @@ var AtscriptDbReadable = class {
2116
2132
  return this._meta.getIdentifications();
2117
2133
  }
2118
2134
  /**
2135
+ * The {@link identifications} an id may resolve through when fields are
2136
+ * hidden per request (since 0.1.134): a unique index with a field that
2137
+ * fails `isFieldVisible` is dropped, as if it did not exist. Primary-key,
2138
+ * `preferredId` and `@meta.id` fields always count as visible. Without a
2139
+ * predicate, every identification.
2140
+ */
2141
+ identificationsVisibleTo(isFieldVisible) {
2142
+ const all = this.identifications;
2143
+ if (!isFieldVisible) return all;
2144
+ const always = this._meta.getAlwaysAddressable();
2145
+ return all.filter((ident) => ident.fields.every((f) => always.has(f) || isFieldVisible(f)));
2146
+ }
2147
+ /**
2119
2148
  * Physical column name of the single `@meta.id` field, or `null` when the
2120
2149
  * schema has zero or multiple `@meta.id` fields. Used by adapters to return
2121
2150
  * the user's logical ID instead of the DB-generated one on insert.
@@ -2235,6 +2264,24 @@ var AtscriptDbReadable = class {
2235
2264
  return this._meta.fieldDescriptors;
2236
2265
  }
2237
2266
  /**
2267
+ * The target table of the navigation relation `navField` (since 0.1.134),
2268
+ * resolved through the table resolver this readable was built with (the
2269
+ * `DbSpace`'s). `undefined` when `navField` is not a relation of this table
2270
+ * or no resolver is available (a table constructed without a `DbSpace`).
2271
+ * Top-level relation names only — no dotted paths.
2272
+ *
2273
+ * ```typescript
2274
+ * const users = posts.relatedTable('author')
2275
+ * users?.primaryKeys // ['id']
2276
+ * ```
2277
+ */
2278
+ relatedTable(navField) {
2279
+ this._ensureBuilt();
2280
+ const targetType = this._meta.relations.get(navField)?.targetType();
2281
+ if (!targetType || !this._tableResolver) return;
2282
+ return this._tableResolver(targetType);
2283
+ }
2284
+ /**
2238
2285
  * Resolves whether `path` references a real field — directly via `flatMap`
2239
2286
  * or transitively through a nav relation by recursing into the target
2240
2287
  * table. Defense-in-depth for query-path validation: `flattenAnnotatedType`
@@ -2250,12 +2297,7 @@ var AtscriptDbReadable = class {
2250
2297
  if (dotIdx === -1) return false;
2251
2298
  const head = path.slice(0, dotIdx);
2252
2299
  const tail = path.slice(dotIdx + 1);
2253
- if (!this.navFields.has(head)) return false;
2254
- const relation = this._meta.relations.get(head);
2255
- if (!relation) return false;
2256
- const targetType = relation.targetType();
2257
- if (!targetType || !this._tableResolver) return false;
2258
- const targetTable = this._tableResolver(targetType);
2300
+ const targetTable = this.relatedTable(head);
2259
2301
  if (!targetTable || typeof targetTable.isValidFieldPath !== "function") return false;
2260
2302
  const visited = _visited ?? /* @__PURE__ */ new Set();
2261
2303
  const cycleKey = `${this.tableName}:${head}`;
@@ -2621,9 +2663,11 @@ var AtscriptDbReadable = class {
2621
2663
  * same identification resolution as {@link findById}. Public so callers can
2622
2664
  * AND-combine the id-filter with a row-level read overlay before issuing
2623
2665
  * `findOne` (avoiding the existence leak that `findById` would cause).
2666
+ * `opts.isFieldVisible` (since 0.1.134) drops unique indexes over hidden
2667
+ * fields — see {@link identificationsVisibleTo}.
2624
2668
  */
2625
- resolveIdFilter(id) {
2626
- return this._resolveIdFilter(id);
2669
+ resolveIdFilter(id, opts) {
2670
+ return this._resolveIdFilter(id, opts);
2627
2671
  }
2628
2672
  /**
2629
2673
  * Resolve an id value into a filter expression.
@@ -2631,9 +2675,10 @@ var AtscriptDbReadable = class {
2631
2675
  * When `preferredId` differs from the PK, scalar ids resolve only against
2632
2676
  * the preferred field (deterministic addressing). Otherwise scalars try PK
2633
2677
  * + every single-field unique index; objects try PK + compound unique
2634
- * indexes.
2678
+ * indexes. With `opts.isFieldVisible`, only the identifications
2679
+ * {@link identificationsVisibleTo} keeps are tried.
2635
2680
  */
2636
- _resolveIdFilter(id) {
2681
+ _resolveIdFilter(id, opts) {
2637
2682
  const pkFields = this.primaryKeys;
2638
2683
  const preferredFields = this.preferredId;
2639
2684
  const isExplicitPreferred = preferredFields.length !== pkFields.length || preferredFields.some((f, i) => f !== pkFields[i]);
@@ -2645,12 +2690,13 @@ var AtscriptDbReadable = class {
2645
2690
  };
2646
2691
  const orFilters = [];
2647
2692
  const idObj = isScalar ? null : id;
2648
- for (const ident of this.identifications) {
2693
+ const identifications = this.identificationsVisibleTo(opts?.isFieldVisible);
2694
+ for (const ident of identifications) {
2649
2695
  if (ident.fields.length !== 1) continue;
2650
2696
  const filter = tryScalarOrField(ident.fields[0]);
2651
2697
  if (filter) orFilters.push(filter);
2652
2698
  }
2653
- if (idObj) for (const ident of this.identifications) {
2699
+ if (idObj) for (const ident of identifications) {
2654
2700
  if (ident.fields.length < 2) continue;
2655
2701
  if (ident.source !== "primaryKey" && orFilters.length > 0) break;
2656
2702
  const filter = this._tryCompoundFilter(ident.fields, idObj);
@@ -4052,7 +4098,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
4052
4098
  const data = items[i];
4053
4099
  for (const navField of this._meta.navFields) delete data[navField];
4054
4100
  if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
4055
- const filter = this._extractRecordFilter(data);
4101
+ const filter = this._extractRecordFilter(data, opts);
4056
4102
  const prepared = this._fieldMapper.prepareForWrite(data, this._meta, this.adapter);
4057
4103
  const result = await this.adapter.replaceOne(this._fieldMapper.translateFilter(filter, this._meta), prepared, expectedVersions[i]);
4058
4104
  matchedCount += result.matchedCount;
@@ -4127,7 +4173,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
4127
4173
  const expectedVersion = expectedVersions[i];
4128
4174
  const data = { ...payload };
4129
4175
  for (const navField of this._meta.navFields) delete data[navField];
4130
- const filter = this._extractRecordFilter(data);
4176
+ const filter = this._extractRecordFilter(data, opts);
4131
4177
  for (const key of Object.keys(filter)) delete data[key];
4132
4178
  if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
4133
4179
  const translatedFilter = this._fieldMapper.translateFilter(filter, this._meta);
@@ -4261,7 +4307,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
4261
4307
  */
4262
4308
  async deleteOne(id, opts) {
4263
4309
  this._ensureBuilt();
4264
- const filter = this._resolveIdFilter(id);
4310
+ const filter = this._resolveIdFilter(id, opts);
4265
4311
  if (!filter) return { deletedCount: 0 };
4266
4312
  const guard = opts?.guard;
4267
4313
  const needsCascade = this._integrity.needsCascade(this._cascadeResolver);
@@ -4435,9 +4481,11 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
4435
4481
  * 2. Single-field unique index — first `@db.index.unique` field found.
4436
4482
  * 3. Compound unique index — first compound unique index whose fields are all present.
4437
4483
  *
4438
- * Throws when no identifying fields can be found.
4484
+ * Throws when no identifying fields can be found. With `isFieldVisible`
4485
+ * (since 0.1.134), a unique index over a hidden field is skipped, as if it
4486
+ * did not exist — see {@link identificationsVisibleTo}.
4439
4487
  */
4440
- _extractRecordFilter(payload) {
4488
+ _extractRecordFilter(payload, opts) {
4441
4489
  const pkFields = this.primaryKeys;
4442
4490
  if (pkFields.length > 0) {
4443
4491
  let allPresent = true;
@@ -4451,17 +4499,15 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
4451
4499
  return filter;
4452
4500
  }
4453
4501
  }
4454
- for (const prop of this.uniqueProps) if (payload[prop] !== void 0) return { [prop]: this._prepareFilterValue(prop, payload[prop]) };
4455
- for (const index of this._meta.indexes.values()) {
4456
- if (index.type !== "unique" || index.fields.length < 2) continue;
4457
- let allPresent = true;
4458
- for (const indexField of index.fields) if (payload[indexField.name] === void 0) {
4459
- allPresent = false;
4460
- break;
4461
- }
4462
- if (allPresent) {
4502
+ const identifications = this.identificationsVisibleTo(opts?.isFieldVisible);
4503
+ const singleFields = /* @__PURE__ */ new Set();
4504
+ for (const ident of identifications) if (ident.source !== "primaryKey" && ident.fields.length === 1) singleFields.add(ident.fields[0]);
4505
+ for (const prop of this.uniqueProps) if (singleFields.has(prop) && payload[prop] !== void 0) return { [prop]: this._prepareFilterValue(prop, payload[prop]) };
4506
+ for (const ident of identifications) {
4507
+ if (ident.source === "primaryKey" || ident.fields.length < 2) continue;
4508
+ if (ident.fields.every((field) => payload[field] !== void 0)) {
4463
4509
  const filter = {};
4464
- for (const indexField of index.fields) filter[indexField.name] = this._prepareFilterValue(indexField.name, payload[indexField.name]);
4510
+ for (const field of ident.fields) filter[field] = this._prepareFilterValue(field, payload[field]);
4465
4511
  return filter;
4466
4512
  }
4467
4513
  }