@atscript/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/agg.d.cts +1 -1
- package/dist/agg.d.mts +1 -1
- package/dist/{buckets-BvoYYQ2Q.d.cts → buckets-Bo2IcPZ_.d.cts} +74 -21
- package/dist/{buckets-D2XPrwSC.d.mts → buckets-Bys2QXyk.d.mts} +74 -21
- package/dist/{db-space-BQQEgB3d.d.mts → db-space-DtGx5HIC.d.cts} +6 -4
- package/dist/{db-space-Cslq5Dux.d.cts → db-space-grfuZcPR.d.mts} +6 -4
- package/dist/{db-view-DVGWWhA3.mjs → db-view-B64IpZ2j.mjs} +163 -60
- package/dist/{db-view-CuFAhzwF.cjs → db-view-pgm2dHIo.cjs} +180 -59
- package/dist/index.cjs +4 -1
- package/dist/index.d.cts +65 -7
- package/dist/index.d.mts +65 -7
- package/dist/index.mjs +2 -2
- package/dist/rel.d.cts +1 -1
- package/dist/rel.d.mts +1 -1
- package/dist/sync.cjs +1 -1
- package/dist/sync.d.cts +2 -2
- package/dist/sync.d.mts +2 -2
- package/dist/sync.mjs +1 -1
- package/package.json +3 -3
|
@@ -32,10 +32,12 @@ const NoopLogger = {
|
|
|
32
32
|
* moost-db's HTTP gate — so both layers answer with the same wording.
|
|
33
33
|
* Everything downstream (`collectQueryPaths`, the field mappers,
|
|
34
34
|
* `UniquSelect`, adapters) assumes normalized input and does not re-check.
|
|
35
|
-
* - **
|
|
36
|
-
* filterability
|
|
37
|
-
*
|
|
38
|
-
*
|
|
35
|
+
* - **The source field** (encryption, JSON ancestor, timestamp type,
|
|
36
|
+
* physical filterability, strict-mode dimension, an adapter with calendar
|
|
37
|
+
* buckets) is `bucketSourceVerdict` — ONE function, called by the path
|
|
38
|
+
* guard (`guardPath` op `bucket`) and by moost-db's capability index, so
|
|
39
|
+
* `/meta.fields[P].bucketable` and the core cannot disagree.
|
|
40
|
+
* - **The unit** (`calendarBucketUnits()` lacks it) is `guardAggregate`'s
|
|
39
41
|
* (`BUCKET_NOT_SUPPORTED`); SQL builders only re-assert the inlined
|
|
40
42
|
* literals (defense in depth).
|
|
41
43
|
*
|
|
@@ -52,11 +54,14 @@ function resolveCalendarBuckets(controls, fields, aggregate) {
|
|
|
52
54
|
return res.buckets;
|
|
53
55
|
}
|
|
54
56
|
/**
|
|
55
|
-
* Whether a field
|
|
56
|
-
* `integer` leaf carrying the `timestamp` tag
|
|
57
|
-
* `.created`, `.updated`) that is not
|
|
58
|
-
* declaration — no annotation opts a field
|
|
59
|
-
*
|
|
57
|
+
* Whether a field's TYPE allows it to be the source of a calendar bucket: a
|
|
58
|
+
* `number` / `integer` leaf carrying the `timestamp` tag
|
|
59
|
+
* (`number.timestamp`, `.created`, `.updated`) that is not
|
|
60
|
+
* `@db.encrypted`. The type is the declaration — no annotation opts a field
|
|
61
|
+
* in.
|
|
62
|
+
*
|
|
63
|
+
* @deprecated since 0.1.133 — the type rule alone; use `bucketSourceVerdict`,
|
|
64
|
+
* the full bucket-source rule the core and moost-db apply.
|
|
60
65
|
*/
|
|
61
66
|
function isBucketableField(fd) {
|
|
62
67
|
if (fd.encrypted) return false;
|
|
@@ -207,6 +212,7 @@ var TableMetadata = class {
|
|
|
207
212
|
physicalNames = /* @__PURE__ */ new Set();
|
|
208
213
|
_built = false;
|
|
209
214
|
_identifications;
|
|
215
|
+
_alwaysAddressable;
|
|
210
216
|
_collateMap = /* @__PURE__ */ new Map();
|
|
211
217
|
_columnFromMap = /* @__PURE__ */ new Map();
|
|
212
218
|
constructor(nestedObjects) {
|
|
@@ -276,6 +282,11 @@ var TableMetadata = class {
|
|
|
276
282
|
if (!this.nestedObjects) this._buildLeafIndexes();
|
|
277
283
|
this._buildIdentifications();
|
|
278
284
|
this._resolvePreferredId(type);
|
|
285
|
+
this._alwaysAddressable = new Set([
|
|
286
|
+
...this.primaryKeys,
|
|
287
|
+
...this.preferredId,
|
|
288
|
+
...this.originalMetaIdFields
|
|
289
|
+
]);
|
|
279
290
|
this._finalizeIndexes();
|
|
280
291
|
this._collateMap.clear();
|
|
281
292
|
this._columnFromMap.clear();
|
|
@@ -734,6 +745,14 @@ var TableMetadata = class {
|
|
|
734
745
|
getIdentifications() {
|
|
735
746
|
return this._identifications ?? [];
|
|
736
747
|
}
|
|
748
|
+
/**
|
|
749
|
+
* Fields that always count as visible for row identification (since
|
|
750
|
+
* 0.1.134): primary key, `preferredId` and `@meta.id` fields — see
|
|
751
|
+
* `AtscriptDbReadable.identificationsVisibleTo`.
|
|
752
|
+
*/
|
|
753
|
+
getAlwaysAddressable() {
|
|
754
|
+
return this._alwaysAddressable ?? /* @__PURE__ */ new Set();
|
|
755
|
+
}
|
|
737
756
|
_resolvePreferredId(type) {
|
|
738
757
|
const preferred = type.metadata.get("db.table.preferredId.uniqueIndex");
|
|
739
758
|
if (preferred === void 0) {
|
|
@@ -1362,8 +1381,10 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
|
|
|
1362
1381
|
* - malformed `$select` computed entries / `$groupBy` entries, calendar
|
|
1363
1382
|
* buckets outside grouped queries or with a bad unit / zone / alias →
|
|
1364
1383
|
* `INVALID_QUERY` (the shared normalizer, `resolveCalendarBuckets`)
|
|
1365
|
-
* - a calendar bucket over a
|
|
1366
|
-
*
|
|
1384
|
+
* - a calendar bucket over a field that is not a bucket source
|
|
1385
|
+
* (`bucketSourceVerdict`) → `INVALID_QUERY`, or `BUCKET_NOT_SUPPORTED`
|
|
1386
|
+
* when the adapter has no calendar buckets at all; a unit the adapter's
|
|
1387
|
+
* `calendarBucketUnits()` lacks → `BUCKET_NOT_SUPPORTED`
|
|
1367
1388
|
* - every filter / `$sort` / `$select` / `$groupBy` / `$having` / aggregate
|
|
1368
1389
|
* path must resolve to physical storage on THIS adapter and pass the
|
|
1369
1390
|
* capability its position (for a filter entry: its predicate class, see
|
|
@@ -1450,8 +1471,8 @@ const OP_VERB = {
|
|
|
1450
1471
|
aggregate: "aggregate over",
|
|
1451
1472
|
bucket: "bucket"
|
|
1452
1473
|
};
|
|
1453
|
-
function pathError(path, message) {
|
|
1454
|
-
return new DbError(
|
|
1474
|
+
function pathError(path, message, code = "INVALID_QUERY") {
|
|
1475
|
+
return new DbError(code, [{
|
|
1455
1476
|
path,
|
|
1456
1477
|
message
|
|
1457
1478
|
}]);
|
|
@@ -1488,6 +1509,16 @@ function sortFieldNames(sort) {
|
|
|
1488
1509
|
if (typeof sort === "object") return Object.keys(sort);
|
|
1489
1510
|
return [];
|
|
1490
1511
|
}
|
|
1512
|
+
/**
|
|
1513
|
+
* The reason clause for a leaf the adapter cannot filter (value comparison).
|
|
1514
|
+
* @internal Shared wording for moost-db's capability index — not consumer API.
|
|
1515
|
+
*/
|
|
1516
|
+
const ADAPTER_FILTER_REASON = "adapter cannot filter on this storage type";
|
|
1517
|
+
/**
|
|
1518
|
+
* The reason clause for an `@db.encrypted` leaf in a comparing / ordering position.
|
|
1519
|
+
* @internal Shared wording for moost-db's capability index — not consumer API.
|
|
1520
|
+
*/
|
|
1521
|
+
const ENCRYPTED_REASON = "field is @db.encrypted (ciphertext cannot be compared or ordered)";
|
|
1491
1522
|
/** The operator each non-`compare` predicate class stands for, in `filterOps` order. */
|
|
1492
1523
|
const FILTER_PREDICATE_OPS = {
|
|
1493
1524
|
exists: "$exists",
|
|
@@ -1536,6 +1567,52 @@ function narrowerFilterOps(fd, adapter) {
|
|
|
1536
1567
|
function acceptedOperatorsHint(ops) {
|
|
1537
1568
|
return ops.length > 0 ? ` (accepted operators: ${ops.join(", ")})` : "";
|
|
1538
1569
|
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Strict mode: a table declaring dimensions or measures groups only by
|
|
1572
|
+
* dimensions (a calendar bucket's source included) and aggregates only
|
|
1573
|
+
* measures.
|
|
1574
|
+
*/
|
|
1575
|
+
function isStrictTable(table) {
|
|
1576
|
+
return table.dimensions.length > 0 || table.measures.length > 0;
|
|
1577
|
+
}
|
|
1578
|
+
function rejectSource(code, reason) {
|
|
1579
|
+
return {
|
|
1580
|
+
ok: false,
|
|
1581
|
+
code,
|
|
1582
|
+
reason
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Whether the stored leaf `fd` may be the source of a calendar bucket
|
|
1587
|
+
* (since 0.1.133) — every schema and adapter rule, once, for the core path
|
|
1588
|
+
* guard ({@link guardPath} op `bucket`) and moost-db's capability index
|
|
1589
|
+
* (`/meta.fields[P].bucketable` and the HTTP gate) alike. First failing rule
|
|
1590
|
+
* wins, in this order:
|
|
1591
|
+
*
|
|
1592
|
+
* 1. `encrypted` — `@db.encrypted` (ciphertext has no calendar);
|
|
1593
|
+
* 2. `jsonDescendant` — inside a JSON value (`jsonValueAncestor`):
|
|
1594
|
+
* relational adapters cannot address it and nested-object adapters must
|
|
1595
|
+
* not diverge from them;
|
|
1596
|
+
* 3. `notTimestamp` — not a `number.timestamp` leaf;
|
|
1597
|
+
* 4. `notFilterable` — the adapter cannot filter (so cannot group) the storage;
|
|
1598
|
+
* 5. `notDimension` — a strict table ({@link isStrictTable}) and the field
|
|
1599
|
+
* is not a dimension;
|
|
1600
|
+
* 6. `noBuckets` — the adapter reports no `calendarBucketUnits()`.
|
|
1601
|
+
*
|
|
1602
|
+
* Whether the adapter supports the requested UNIT is a per-query rule, not
|
|
1603
|
+
* a field's (the core's `BUCKET_NOT_SUPPORTED`). Caller-specific policy
|
|
1604
|
+
* layers on top: moost-db rejects `@db.writeOnly` sources before asking.
|
|
1605
|
+
*/
|
|
1606
|
+
function bucketSourceVerdict(fd, table, adapter) {
|
|
1607
|
+
if (fd.encrypted) return rejectSource("encrypted", ENCRYPTED_REASON);
|
|
1608
|
+
const jsonAncestor = jsonValueAncestor(fd.path, table.jsonValueParents);
|
|
1609
|
+
if (jsonAncestor !== void 0) return rejectSource("jsonDescendant", `inside JSON-stored column "${jsonAncestor}"`);
|
|
1610
|
+
if (!isBucketableField(fd)) return rejectSource("notTimestamp", "not a timestamp field (declare it number.timestamp)");
|
|
1611
|
+
if (!adapter.canFilterField(fd)) return rejectSource("notFilterable", ADAPTER_FILTER_REASON);
|
|
1612
|
+
if (isStrictTable(table) && !table.dimensions.includes(fd.path)) return rejectSource("notDimension", "not a dimension");
|
|
1613
|
+
if (adapter.calendarBucketUnits().size === 0) return rejectSource("noBuckets", "adapter has no calendar buckets");
|
|
1614
|
+
return { ok: true };
|
|
1615
|
+
}
|
|
1539
1616
|
function collectFilterKeys(filter, push, skip, refs) {
|
|
1540
1617
|
if (!filter || typeof filter !== "object" || Array.isArray(filter)) return;
|
|
1541
1618
|
for (const [key, value] of Object.entries(filter)) {
|
|
@@ -1662,8 +1739,8 @@ function pathSourceOf(meta) {
|
|
|
1662
1739
|
* - a leaf → physical capability (`canSortField` for `$sort`; for a filter
|
|
1663
1740
|
* entry {@link canFilterLeaf} of its `predicate` class; `canFilterField`
|
|
1664
1741
|
* for `$groupBy` / `$having` / aggregate `$field`s; `$select` always passes);
|
|
1665
|
-
* a calendar-bucket source must
|
|
1666
|
-
*
|
|
1742
|
+
* a calendar-bucket source must pass `bucketSourceVerdict` (the rules
|
|
1743
|
+
* moost-db's capability index applies too);
|
|
1667
1744
|
* - a nested-object parent → only `$select`, and only when it expands to
|
|
1668
1745
|
* leaf columns (`selectExpansion`);
|
|
1669
1746
|
* - everything else is rejected.
|
|
@@ -1683,17 +1760,15 @@ function guardPath(meta, adapter, path, op, predicate = "compare") {
|
|
|
1683
1760
|
if (op === "select") return;
|
|
1684
1761
|
const fd = meta.descriptorByPath.get(path);
|
|
1685
1762
|
if (op === "bucket") {
|
|
1686
|
-
const
|
|
1687
|
-
if (
|
|
1688
|
-
if (!isBucketableField(fd)) throw pathError(path, notTimestampMessage(path));
|
|
1689
|
-
if (!adapter.canFilterField(fd)) throw pathError(path, `Cannot bucket "${path}" — adapter cannot filter on this storage type`);
|
|
1763
|
+
const verdict = bucketSourceVerdict(fd, meta, adapter);
|
|
1764
|
+
if (!verdict.ok) throw pathError(path, `Cannot bucket "${path}" — ${verdict.reason}`, verdict.code === "noBuckets" ? "BUCKET_NOT_SUPPORTED" : "INVALID_QUERY");
|
|
1690
1765
|
return;
|
|
1691
1766
|
}
|
|
1692
1767
|
if (op === "sort") {
|
|
1693
1768
|
if (!adapter.canSortField(fd)) throw pathError(path, `Cannot sort by "${path}" — adapter cannot sort on this storage type`);
|
|
1694
1769
|
return;
|
|
1695
1770
|
}
|
|
1696
|
-
if (!canFilterLeaf(fd, predicate, adapter)) throw pathError(path, `Cannot ${verb} "${path}" —
|
|
1771
|
+
if (!canFilterLeaf(fd, predicate, adapter)) throw pathError(path, `Cannot ${verb} "${path}" — ${ADAPTER_FILTER_REASON}${op === "filter" ? acceptedOperatorsHint(narrowerFilterOps(fd, adapter)) : ""}`);
|
|
1697
1772
|
return;
|
|
1698
1773
|
}
|
|
1699
1774
|
case "objectParent":
|
|
@@ -1793,13 +1868,10 @@ function guardAggregate(meta, adapter, query, resolved) {
|
|
|
1793
1868
|
const having = refs ? checkHavingKeys(refs) : void 0;
|
|
1794
1869
|
if (having) throw new DbError("INVALID_QUERY", [having]);
|
|
1795
1870
|
}
|
|
1796
|
-
/** The core wording for a bucket over a field that is not `number.timestamp`. */
|
|
1797
|
-
function notTimestampMessage(path) {
|
|
1798
|
-
return `Cannot bucket "${path}" — not a timestamp field (declare it number.timestamp)`;
|
|
1799
|
-
}
|
|
1800
1871
|
/**
|
|
1801
1872
|
* Rejects a calendar bucket whose unit this adapter cannot group by
|
|
1802
|
-
* (`calendarBucketUnits()`;
|
|
1873
|
+
* (`calendarBucketUnits()`; an adapter with none was already answered per
|
|
1874
|
+
* source by `bucketSourceVerdict`) with `BUCKET_NOT_SUPPORTED`,
|
|
1803
1875
|
* before anything is translated — third-party adapters get a clean 400,
|
|
1804
1876
|
* never an engine error or a silent fallback.
|
|
1805
1877
|
*/
|
|
@@ -2058,6 +2130,19 @@ var AtscriptDbReadable = class {
|
|
|
2058
2130
|
return this._meta.getIdentifications();
|
|
2059
2131
|
}
|
|
2060
2132
|
/**
|
|
2133
|
+
* The {@link identifications} an id may resolve through when fields are
|
|
2134
|
+
* hidden per request (since 0.1.134): a unique index with a field that
|
|
2135
|
+
* fails `isFieldVisible` is dropped, as if it did not exist. Primary-key,
|
|
2136
|
+
* `preferredId` and `@meta.id` fields always count as visible. Without a
|
|
2137
|
+
* predicate, every identification.
|
|
2138
|
+
*/
|
|
2139
|
+
identificationsVisibleTo(isFieldVisible) {
|
|
2140
|
+
const all = this.identifications;
|
|
2141
|
+
if (!isFieldVisible) return all;
|
|
2142
|
+
const always = this._meta.getAlwaysAddressable();
|
|
2143
|
+
return all.filter((ident) => ident.fields.every((f) => always.has(f) || isFieldVisible(f)));
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2061
2146
|
* Physical column name of the single `@meta.id` field, or `null` when the
|
|
2062
2147
|
* schema has zero or multiple `@meta.id` fields. Used by adapters to return
|
|
2063
2148
|
* the user's logical ID instead of the DB-generated one on insert.
|
|
@@ -2177,6 +2262,24 @@ var AtscriptDbReadable = class {
|
|
|
2177
2262
|
return this._meta.fieldDescriptors;
|
|
2178
2263
|
}
|
|
2179
2264
|
/**
|
|
2265
|
+
* The target table of the navigation relation `navField` (since 0.1.134),
|
|
2266
|
+
* resolved through the table resolver this readable was built with (the
|
|
2267
|
+
* `DbSpace`'s). `undefined` when `navField` is not a relation of this table
|
|
2268
|
+
* or no resolver is available (a table constructed without a `DbSpace`).
|
|
2269
|
+
* Top-level relation names only — no dotted paths.
|
|
2270
|
+
*
|
|
2271
|
+
* ```typescript
|
|
2272
|
+
* const users = posts.relatedTable('author')
|
|
2273
|
+
* users?.primaryKeys // ['id']
|
|
2274
|
+
* ```
|
|
2275
|
+
*/
|
|
2276
|
+
relatedTable(navField) {
|
|
2277
|
+
this._ensureBuilt();
|
|
2278
|
+
const targetType = this._meta.relations.get(navField)?.targetType();
|
|
2279
|
+
if (!targetType || !this._tableResolver) return;
|
|
2280
|
+
return this._tableResolver(targetType);
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2180
2283
|
* Resolves whether `path` references a real field — directly via `flatMap`
|
|
2181
2284
|
* or transitively through a nav relation by recursing into the target
|
|
2182
2285
|
* table. Defense-in-depth for query-path validation: `flattenAnnotatedType`
|
|
@@ -2192,12 +2295,7 @@ var AtscriptDbReadable = class {
|
|
|
2192
2295
|
if (dotIdx === -1) return false;
|
|
2193
2296
|
const head = path.slice(0, dotIdx);
|
|
2194
2297
|
const tail = path.slice(dotIdx + 1);
|
|
2195
|
-
|
|
2196
|
-
const relation = this._meta.relations.get(head);
|
|
2197
|
-
if (!relation) return false;
|
|
2198
|
-
const targetType = relation.targetType();
|
|
2199
|
-
if (!targetType || !this._tableResolver) return false;
|
|
2200
|
-
const targetTable = this._tableResolver(targetType);
|
|
2298
|
+
const targetTable = this.relatedTable(head);
|
|
2201
2299
|
if (!targetTable || typeof targetTable.isValidFieldPath !== "function") return false;
|
|
2202
2300
|
const visited = _visited ?? /* @__PURE__ */ new Set();
|
|
2203
2301
|
const cycleKey = `${this.tableName}:${head}`;
|
|
@@ -2280,10 +2378,11 @@ var AtscriptDbReadable = class {
|
|
|
2280
2378
|
* `resolveCalendarBuckets`: shapes, unit, zone, alias, grouping)
|
|
2281
2379
|
* - Plain fields in $select are a subset of $groupBy
|
|
2282
2380
|
* - When dimensions/measures are defined (strict mode): $groupBy fields
|
|
2283
|
-
* must be dimensions
|
|
2284
|
-
*
|
|
2285
|
-
*
|
|
2286
|
-
* adapter's calendar-bucket units
|
|
2381
|
+
* must be dimensions, aggregate $field values must be measures (or '*')
|
|
2382
|
+
* - the path guard (a bucket source must pass `bucketSourceVerdict` —
|
|
2383
|
+
* timestamp type, no JSON ancestor, a dimension in strict mode, an
|
|
2384
|
+
* adapter with calendar buckets) and the adapter's calendar-bucket units
|
|
2385
|
+
* (`BUCKET_NOT_SUPPORTED`)
|
|
2287
2386
|
*
|
|
2288
2387
|
* Translates field names, delegates to adapter.aggregate(),
|
|
2289
2388
|
* then reverse-maps and applies fromStorage formatters on results.
|
|
@@ -2300,11 +2399,11 @@ var AtscriptDbReadable = class {
|
|
|
2300
2399
|
}]);
|
|
2301
2400
|
}
|
|
2302
2401
|
const { dimensions, measures } = this._meta;
|
|
2303
|
-
if (
|
|
2402
|
+
if (isStrictTable(this._meta)) {
|
|
2304
2403
|
const dimSet = new Set(dimensions);
|
|
2305
2404
|
const measSet = new Set(measures);
|
|
2306
|
-
const
|
|
2307
|
-
for (const field of $groupBy
|
|
2405
|
+
const bucketAliases = new Set(buckets.map((b) => b.alias));
|
|
2406
|
+
for (const field of $groupBy) if (!dimSet.has(field) && !bucketAliases.has(field)) throw new DbError("INVALID_QUERY", [{
|
|
2308
2407
|
path: "$groupBy",
|
|
2309
2408
|
message: `Field "${field}" is not a dimension`
|
|
2310
2409
|
}]);
|
|
@@ -2562,9 +2661,11 @@ var AtscriptDbReadable = class {
|
|
|
2562
2661
|
* same identification resolution as {@link findById}. Public so callers can
|
|
2563
2662
|
* AND-combine the id-filter with a row-level read overlay before issuing
|
|
2564
2663
|
* `findOne` (avoiding the existence leak that `findById` would cause).
|
|
2664
|
+
* `opts.isFieldVisible` (since 0.1.134) drops unique indexes over hidden
|
|
2665
|
+
* fields — see {@link identificationsVisibleTo}.
|
|
2565
2666
|
*/
|
|
2566
|
-
resolveIdFilter(id) {
|
|
2567
|
-
return this._resolveIdFilter(id);
|
|
2667
|
+
resolveIdFilter(id, opts) {
|
|
2668
|
+
return this._resolveIdFilter(id, opts);
|
|
2568
2669
|
}
|
|
2569
2670
|
/**
|
|
2570
2671
|
* Resolve an id value into a filter expression.
|
|
@@ -2572,9 +2673,10 @@ var AtscriptDbReadable = class {
|
|
|
2572
2673
|
* When `preferredId` differs from the PK, scalar ids resolve only against
|
|
2573
2674
|
* the preferred field (deterministic addressing). Otherwise scalars try PK
|
|
2574
2675
|
* + every single-field unique index; objects try PK + compound unique
|
|
2575
|
-
* indexes.
|
|
2676
|
+
* indexes. With `opts.isFieldVisible`, only the identifications
|
|
2677
|
+
* {@link identificationsVisibleTo} keeps are tried.
|
|
2576
2678
|
*/
|
|
2577
|
-
_resolveIdFilter(id) {
|
|
2679
|
+
_resolveIdFilter(id, opts) {
|
|
2578
2680
|
const pkFields = this.primaryKeys;
|
|
2579
2681
|
const preferredFields = this.preferredId;
|
|
2580
2682
|
const isExplicitPreferred = preferredFields.length !== pkFields.length || preferredFields.some((f, i) => f !== pkFields[i]);
|
|
@@ -2586,12 +2688,13 @@ var AtscriptDbReadable = class {
|
|
|
2586
2688
|
};
|
|
2587
2689
|
const orFilters = [];
|
|
2588
2690
|
const idObj = isScalar ? null : id;
|
|
2589
|
-
|
|
2691
|
+
const identifications = this.identificationsVisibleTo(opts?.isFieldVisible);
|
|
2692
|
+
for (const ident of identifications) {
|
|
2590
2693
|
if (ident.fields.length !== 1) continue;
|
|
2591
2694
|
const filter = tryScalarOrField(ident.fields[0]);
|
|
2592
2695
|
if (filter) orFilters.push(filter);
|
|
2593
2696
|
}
|
|
2594
|
-
if (idObj) for (const ident of
|
|
2697
|
+
if (idObj) for (const ident of identifications) {
|
|
2595
2698
|
if (ident.fields.length < 2) continue;
|
|
2596
2699
|
if (ident.source !== "primaryKey" && orFilters.length > 0) break;
|
|
2597
2700
|
const filter = this._tryCompoundFilter(ident.fields, idObj);
|
|
@@ -3993,7 +4096,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
3993
4096
|
const data = items[i];
|
|
3994
4097
|
for (const navField of this._meta.navFields) delete data[navField];
|
|
3995
4098
|
if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
|
|
3996
|
-
const filter = this._extractRecordFilter(data);
|
|
4099
|
+
const filter = this._extractRecordFilter(data, opts);
|
|
3997
4100
|
const prepared = this._fieldMapper.prepareForWrite(data, this._meta, this.adapter);
|
|
3998
4101
|
const result = await this.adapter.replaceOne(this._fieldMapper.translateFilter(filter, this._meta), prepared, expectedVersions[i]);
|
|
3999
4102
|
matchedCount += result.matchedCount;
|
|
@@ -4068,7 +4171,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4068
4171
|
const expectedVersion = expectedVersions[i];
|
|
4069
4172
|
const data = { ...payload };
|
|
4070
4173
|
for (const navField of this._meta.navFields) delete data[navField];
|
|
4071
|
-
const filter = this._extractRecordFilter(data);
|
|
4174
|
+
const filter = this._extractRecordFilter(data, opts);
|
|
4072
4175
|
for (const key of Object.keys(filter)) delete data[key];
|
|
4073
4176
|
if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
|
|
4074
4177
|
const translatedFilter = this._fieldMapper.translateFilter(filter, this._meta);
|
|
@@ -4202,7 +4305,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4202
4305
|
*/
|
|
4203
4306
|
async deleteOne(id, opts) {
|
|
4204
4307
|
this._ensureBuilt();
|
|
4205
|
-
const filter = this._resolveIdFilter(id);
|
|
4308
|
+
const filter = this._resolveIdFilter(id, opts);
|
|
4206
4309
|
if (!filter) return { deletedCount: 0 };
|
|
4207
4310
|
const guard = opts?.guard;
|
|
4208
4311
|
const needsCascade = this._integrity.needsCascade(this._cascadeResolver);
|
|
@@ -4376,9 +4479,11 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4376
4479
|
* 2. Single-field unique index — first `@db.index.unique` field found.
|
|
4377
4480
|
* 3. Compound unique index — first compound unique index whose fields are all present.
|
|
4378
4481
|
*
|
|
4379
|
-
* Throws when no identifying fields can be found.
|
|
4482
|
+
* Throws when no identifying fields can be found. With `isFieldVisible`
|
|
4483
|
+
* (since 0.1.134), a unique index over a hidden field is skipped, as if it
|
|
4484
|
+
* did not exist — see {@link identificationsVisibleTo}.
|
|
4380
4485
|
*/
|
|
4381
|
-
_extractRecordFilter(payload) {
|
|
4486
|
+
_extractRecordFilter(payload, opts) {
|
|
4382
4487
|
const pkFields = this.primaryKeys;
|
|
4383
4488
|
if (pkFields.length > 0) {
|
|
4384
4489
|
let allPresent = true;
|
|
@@ -4392,17 +4497,15 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4392
4497
|
return filter;
|
|
4393
4498
|
}
|
|
4394
4499
|
}
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
}
|
|
4403
|
-
if (allPresent) {
|
|
4500
|
+
const identifications = this.identificationsVisibleTo(opts?.isFieldVisible);
|
|
4501
|
+
const singleFields = /* @__PURE__ */ new Set();
|
|
4502
|
+
for (const ident of identifications) if (ident.source !== "primaryKey" && ident.fields.length === 1) singleFields.add(ident.fields[0]);
|
|
4503
|
+
for (const prop of this.uniqueProps) if (singleFields.has(prop) && payload[prop] !== void 0) return { [prop]: this._prepareFilterValue(prop, payload[prop]) };
|
|
4504
|
+
for (const ident of identifications) {
|
|
4505
|
+
if (ident.source === "primaryKey" || ident.fields.length < 2) continue;
|
|
4506
|
+
if (ident.fields.every((field) => payload[field] !== void 0)) {
|
|
4404
4507
|
const filter = {};
|
|
4405
|
-
for (const
|
|
4508
|
+
for (const field of ident.fields) filter[field] = this._prepareFilterValue(field, payload[field]);
|
|
4406
4509
|
return filter;
|
|
4407
4510
|
}
|
|
4408
4511
|
}
|
|
@@ -4652,4 +4755,4 @@ function isAtscriptDbView(readable) {
|
|
|
4652
4755
|
return readable.isView;
|
|
4653
4756
|
}
|
|
4654
4757
|
//#endregion
|
|
4655
|
-
export {
|
|
4758
|
+
export { unsupportedOperatorMessage as A, isJsonValueField as B, guardAggregate as C, guardQuery as D, guardPaths as E, TableMetadata as F, resolveCalendarBuckets as H, findAncestorInSet as I, isGeoIndexableType as L, DocumentFieldMapper as M, FieldMappingStrategy as N, narrowerFilterOps as O, UniquSelect as P, isGeoPointType as R, collectQueryPaths as S, guardPath as T, NoopLogger as U, jsonValueAncestor as V, assertGeoPoint as _, decomposePatch as a, checkHavingKeys as b, BaseDbAdapter as c, NativeIntegrity as d, AtscriptDbReadable as f, acceptedOperatorsHint as g, ENCRYPTED_REASON as h, assertNoVersionWrites as i, RelationalFieldMapper as j, sortFieldNames as k, createFailureCollector as l, ADAPTER_FILTER_REASON as m, isAtscriptDbView as n, ApplicationIntegrity as o, resolveDesignType as p, AtscriptDbTable as r, ALL_BUCKET_UNITS as s, AtscriptDbView as t, IntegrityStrategy as u, bucketSourceVerdict as v, guardFilter as w, classifyQueryPath as x, canFilterLeaf as y, isBucketableField as z };
|