@atscript/db 0.1.141 → 0.1.143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agg.d.cts +1 -1
- package/dist/agg.d.mts +1 -1
- package/dist/{buckets-Bv4pah66.d.cts → buckets-Ba5lP_o9.d.cts} +358 -17
- package/dist/{buckets-CjL7F-hp.d.mts → buckets-BlJ4cdlr.d.mts} +358 -17
- package/dist/{column-diff-DiBbXyLA.d.cts → column-diff-BlGxPobU.d.cts} +1 -1
- package/dist/{column-diff-CfPNcP6e.cjs → column-diff-D_Kyuh0S.cjs} +906 -209
- package/dist/{column-diff-n-k5KY0u.d.mts → column-diff-PqA_edXA.d.mts} +1 -1
- package/dist/{column-diff-CmFNXV8C.mjs → column-diff-e2oHc71_.mjs} +850 -177
- package/dist/index.cjs +16 -10
- package/dist/index.d.cts +30 -5
- package/dist/index.d.mts +30 -5
- package/dist/index.mjs +6 -5
- package/dist/nested-writer-CnOOAehr.mjs +880 -0
- package/dist/nested-writer-xfQwxplL.cjs +1029 -0
- package/dist/{object-DSN0h9lB.d.cts → object-CtPYTIvy.d.cts} +3 -1
- package/dist/{object-DSN0h9lB.d.mts → object-CtPYTIvy.d.mts} +3 -1
- package/dist/object-Djg28csK.cjs +101 -0
- package/dist/object-TkiJQ-Dp.mjs +66 -0
- package/dist/rel.cjs +5 -2
- package/dist/rel.d.cts +76 -10
- package/dist/rel.d.mts +76 -10
- package/dist/rel.mjs +3 -3
- package/dist/{relation-helpers-B59to_dG.d.mts → relation-helpers-JTEyZzVd.d.cts} +1 -1
- package/dist/{relation-helpers-DQ_nRsV9.d.cts → relation-helpers-MTwzaSGA.d.mts} +1 -1
- package/dist/{relation-loader-CgJ8bK6X.cjs → relation-loader-CBPY6kM7.cjs} +1 -1
- package/dist/{relation-loader-CuhEBzFU.mjs → relation-loader-D9XuXaMv.mjs} +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/dist/{validator-DASnXf1j.cjs → validator-CVS-onRg.cjs} +0 -101
- package/dist/{validator-D8bPsXPN.mjs → validator-Clu2q_7z.mjs} +1 -66
- package/dist/validator.cjs +4 -3
- package/dist/validator.d.cts +1 -1
- package/dist/validator.d.mts +1 -1
- package/dist/validator.mjs +2 -1
- package/package.json +1 -1
- package/dist/nested-writer-BO3vhbkP.mjs +0 -661
- package/dist/nested-writer-DYsRxZ5f.cjs +0 -768
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { n as CasMismatchError, r as DbError } from "./db-error-D5uilS_A.mjs";
|
|
2
2
|
import { i as SUPPORTED_AGGREGATE_FNS, n as BASE_AGGREGATE_FNS } from "./aggregate-fns-CfsveE1w.mjs";
|
|
3
|
-
import { a as
|
|
3
|
+
import { C as findRemoteFK, S as findFKForRelation, T as tableNameOf, _ as remapDeleteFkViolation, a as batchPatchNestedFrom, b as sameKey, c as batchReplaceNestedFrom, d as checkDepthOverflow, f as planNestedFromVia, g as enrichFkViolation, h as validateBatch, i as batchInsertNestedVia, l as batchReplaceNestedTo, m as preValidateNestedFrom, n as batchInsertNestedFrom, p as planPatchNestedTo, r as batchInsertNestedTo, s as batchPatchNestedVia, t as applyPatchNestedTo, u as batchReplaceNestedVia, v as pkTupleKey, x as findFKEntryForRelation, y as rowMatchesKey } from "./nested-writer-CnOOAehr.mjs";
|
|
4
4
|
import { i as isJsonLeafType, n as DERIVED_INCOMPATIBLE } from "./derived-rules-0sKn4f5C.mjs";
|
|
5
|
-
import { a as
|
|
5
|
+
import { a as isPlainObject, i as isEmptyObject, n as findAncestorInSet, o as selfOrAncestor, r as getPath, t as deletePath } from "./object-TkiJQ-Dp.mjs";
|
|
6
6
|
import { resolveAlias } from "./agg.mjs";
|
|
7
7
|
import { separateCas, separateFieldOps } from "./ops.mjs";
|
|
8
|
+
import { a as forceNavNonOptional, c as getKeyProps, i as dbPlugin, n as buildPatchPartial, t as buildDbValidator } from "./validator-Clu2q_7z.mjs";
|
|
8
9
|
import { flattenAnnotatedType, isAnnotatedType } from "@atscript/typescript/utils";
|
|
9
10
|
import { BUCKET_UNITS, isAggregateExpr, isBucketExpr, isPrimitive, resolveBuckets } from "@uniqu/core";
|
|
10
11
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -197,6 +198,33 @@ function sourceIndex(type) {
|
|
|
197
198
|
return idx;
|
|
198
199
|
}
|
|
199
200
|
/**
|
|
201
|
+
* The read seals a view column inherits from the source path it reads
|
|
202
|
+
* (since 0.1.143): `writeOnly` when the path or any ancestor object is
|
|
203
|
+
* `@db.writeOnly` (a leaf of a sealed object is sealed too); `encrypted` when
|
|
204
|
+
* the path itself is `@db.encrypted` (it reads the ciphertext column).
|
|
205
|
+
* Reads the source's live field metadata, so a source VIEW whose own fields
|
|
206
|
+
* inherited a seal (see `inheritViewFieldSeals`) passes it on.
|
|
207
|
+
* @since 0.1.143
|
|
208
|
+
*/
|
|
209
|
+
function sourceFieldSeals(sourceType, logicalPath) {
|
|
210
|
+
const { flatMap } = sourceIndex(sourceType);
|
|
211
|
+
const props = sourceType.type.kind === "object" ? sourceType.type.props : void 0;
|
|
212
|
+
const seals = {
|
|
213
|
+
writeOnly: false,
|
|
214
|
+
encrypted: false
|
|
215
|
+
};
|
|
216
|
+
let prefix = "";
|
|
217
|
+
for (const segment of logicalPath.split(".")) {
|
|
218
|
+
const node = prefix ? flatMap.get(`${prefix}.${segment}`) : props?.get(segment);
|
|
219
|
+
prefix = prefix ? `${prefix}.${segment}` : segment;
|
|
220
|
+
const metadata = node?.metadata;
|
|
221
|
+
if (!metadata) break;
|
|
222
|
+
if (metadata.has("db.writeOnly")) seals.writeOnly = true;
|
|
223
|
+
if (prefix === logicalPath && metadata.has("db.encrypted")) seals.encrypted = true;
|
|
224
|
+
}
|
|
225
|
+
return seals;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
200
228
|
* Resolves a LOGICAL path of a source table (a view field's chain ref, an
|
|
201
229
|
* aggregate's field, a predicate operand) to where it is physically stored.
|
|
202
230
|
* Internal — `AtscriptDbView.resolveRefSource` is the public entry.
|
|
@@ -2362,6 +2390,37 @@ function guardBucketUnits(adapter, buckets) {
|
|
|
2362
2390
|
}]);
|
|
2363
2391
|
}
|
|
2364
2392
|
//#endregion
|
|
2393
|
+
//#region src/shared/index-messages.ts
|
|
2394
|
+
/**
|
|
2395
|
+
* Wording of the "index not found" errors — one source for the core, every
|
|
2396
|
+
* adapter and the HTTP layer, so a request naming a hidden index reads
|
|
2397
|
+
* exactly like one naming a nonexistent index.
|
|
2398
|
+
*/
|
|
2399
|
+
/**
|
|
2400
|
+
* `Search index "<name>" not found`, or — without a name — the "no default
|
|
2401
|
+
* text index" message.
|
|
2402
|
+
* @since 0.1.143
|
|
2403
|
+
*/
|
|
2404
|
+
function searchIndexNotFoundMessage(indexName) {
|
|
2405
|
+
return indexName ? `Search index "${indexName}" not found` : "No search index available";
|
|
2406
|
+
}
|
|
2407
|
+
/**
|
|
2408
|
+
* `Vector index "<name>" not found`, or — without a name — the "no vector
|
|
2409
|
+
* index" message.
|
|
2410
|
+
* @since 0.1.143
|
|
2411
|
+
*/
|
|
2412
|
+
function vectorIndexNotFoundMessage(indexName) {
|
|
2413
|
+
return indexName ? `Vector index "${indexName}" not found` : "No vector index available";
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* `Geo index "<name>" not found on table "<table>"`, or — without a name —
|
|
2417
|
+
* the "table declares no geo index" message.
|
|
2418
|
+
* @since 0.1.143
|
|
2419
|
+
*/
|
|
2420
|
+
function geoIndexNotFoundMessage(tableName, indexName) {
|
|
2421
|
+
return indexName === void 0 ? `Table "${tableName}" declares no @db.index.geo — geoSearch requires a geo index` : `Geo index "${indexName}" not found on table "${tableName}"`;
|
|
2422
|
+
}
|
|
2423
|
+
//#endregion
|
|
2365
2424
|
//#region src/table/db-readable.ts
|
|
2366
2425
|
/**
|
|
2367
2426
|
* Resolves the design type from an annotated type.
|
|
@@ -2722,6 +2781,34 @@ var AtscriptDbReadable = class {
|
|
|
2722
2781
|
this._ensureBuilt();
|
|
2723
2782
|
return this._meta.relations;
|
|
2724
2783
|
}
|
|
2784
|
+
/**
|
|
2785
|
+
* The `@db.rel.FK` entry a `@db.rel.to` relation is backed by — paired
|
|
2786
|
+
* exactly like relation loading and nested writes pair them: by the
|
|
2787
|
+
* relation's alias when it has one, else by the target table. `undefined`
|
|
2788
|
+
* for an unknown name, a `@db.rel.from` / `@db.rel.via` relation (their key
|
|
2789
|
+
* lives on the other table) or a TO relation without a matching FK.
|
|
2790
|
+
*
|
|
2791
|
+
* @since 0.1.143
|
|
2792
|
+
*/
|
|
2793
|
+
foreignKeyOf(relationName) {
|
|
2794
|
+
const relation = this.relations.get(relationName);
|
|
2795
|
+
if (relation?.direction !== "to") return void 0;
|
|
2796
|
+
return findFKEntryForRelation(relation, this._meta.foreignKeys);
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Logical paths stored as ONE JSON column (`storage: "json"` — `@db.json`
|
|
2800
|
+
* fields and nested objects / arrays a relational adapter serializes): the
|
|
2801
|
+
* engine cannot address a sub-path of such a column in a projection,
|
|
2802
|
+
* filter or sort, so a permission layer treats it atomically (visible whole
|
|
2803
|
+
* or not at all). Navigation fields excluded; empty on document adapters
|
|
2804
|
+
* (they store nested values natively).
|
|
2805
|
+
*
|
|
2806
|
+
* @since 0.1.143
|
|
2807
|
+
*/
|
|
2808
|
+
get jsonParents() {
|
|
2809
|
+
this._ensureBuilt();
|
|
2810
|
+
return this._meta.jsonParents;
|
|
2811
|
+
}
|
|
2725
2812
|
/** The underlying database adapter instance. */
|
|
2726
2813
|
get dbAdapter() {
|
|
2727
2814
|
return this.adapter;
|
|
@@ -2760,6 +2847,100 @@ var AtscriptDbReadable = class {
|
|
|
2760
2847
|
return this._fieldMapper.reconstructRows(rows, this._meta, controls);
|
|
2761
2848
|
}
|
|
2762
2849
|
/**
|
|
2850
|
+
* Translates a read query for the adapter. A `$select` that leaves out a
|
|
2851
|
+
* key a `$with` relation joins on — a TO relation's foreign key, the key a
|
|
2852
|
+
* FROM / VIA relation is looked up by — is widened with it for the read
|
|
2853
|
+
* (since 0.1.143), and {@link _finishRead} strips it again once the
|
|
2854
|
+
* relations are loaded: the joined object never reads `null` just because
|
|
2855
|
+
* its key was not selected.
|
|
2856
|
+
*/
|
|
2857
|
+
_translateRead(query) {
|
|
2858
|
+
let readQuery = query ?? {};
|
|
2859
|
+
const controls = readQuery.controls;
|
|
2860
|
+
const withRelations = controls?.$with;
|
|
2861
|
+
let widened = [];
|
|
2862
|
+
if (withRelations?.length && controls?.$select) {
|
|
2863
|
+
const widen = this._widenSelectForWith(controls.$select, withRelations);
|
|
2864
|
+
if (widen) {
|
|
2865
|
+
readQuery = {
|
|
2866
|
+
...readQuery,
|
|
2867
|
+
controls: {
|
|
2868
|
+
...controls,
|
|
2869
|
+
$select: widen.select
|
|
2870
|
+
}
|
|
2871
|
+
};
|
|
2872
|
+
widened = widen.added;
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
return {
|
|
2876
|
+
translated: this._fieldMapper.translateQuery(readQuery, this._meta),
|
|
2877
|
+
controls: readQuery.controls,
|
|
2878
|
+
withRelations,
|
|
2879
|
+
widened
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
/** Reconstructs + decrypts a read's rows, loads its `$with` relations and strips widened keys. */
|
|
2883
|
+
async _finishRead(results, read) {
|
|
2884
|
+
const rows = this._fromRead(results, read.controls);
|
|
2885
|
+
await this._decryptRows(rows);
|
|
2886
|
+
if (read.withRelations?.length) {
|
|
2887
|
+
await this.loadRelations(rows, read.withRelations);
|
|
2888
|
+
for (const key of read.widened) for (const row of rows) deletePath(row, key);
|
|
2889
|
+
}
|
|
2890
|
+
return rows;
|
|
2891
|
+
}
|
|
2892
|
+
/** `$select` plus the join keys of `withRelations` it leaves out — `undefined` when none is missing. */
|
|
2893
|
+
_widenSelectForWith(select, withRelations) {
|
|
2894
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2895
|
+
for (const rel of withRelations) for (const key of this._joinKeysOf(rel.name)) keys.add(key);
|
|
2896
|
+
if (keys.size === 0) return void 0;
|
|
2897
|
+
if (isExclusionProjection(select)) {
|
|
2898
|
+
const added = [...keys].filter((key) => key in select);
|
|
2899
|
+
if (added.length === 0) return void 0;
|
|
2900
|
+
const next = { ...select };
|
|
2901
|
+
for (const key of added) delete next[key];
|
|
2902
|
+
return {
|
|
2903
|
+
select: next,
|
|
2904
|
+
added
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
const named = new Set(Array.isArray(select) ? select.filter((key) => typeof key === "string") : Object.keys(select).filter((key) => select[key]));
|
|
2908
|
+
if (named.size === 0) return void 0;
|
|
2909
|
+
const added = [...keys].filter((key) => selfOrAncestor(key, named) === void 0);
|
|
2910
|
+
if (added.length === 0) return void 0;
|
|
2911
|
+
return {
|
|
2912
|
+
select: Array.isArray(select) ? [...select, ...added] : {
|
|
2913
|
+
...select,
|
|
2914
|
+
...Object.fromEntries(added.map((k) => [k, 1]))
|
|
2915
|
+
},
|
|
2916
|
+
added
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
_joinKeysCache;
|
|
2920
|
+
/**
|
|
2921
|
+
* The keys of THIS table a `$with` relation joins on: a TO relation's
|
|
2922
|
+
* foreign-key fields; the fields a FROM relation's (or a VIA junction's)
|
|
2923
|
+
* foreign key references — typically the primary key. An adapter that
|
|
2924
|
+
* loads relations natively re-reads the rows by primary key, so it is
|
|
2925
|
+
* always included there. Empty for an unknown or nested (`a.b`) name.
|
|
2926
|
+
*/
|
|
2927
|
+
_joinKeysOf(relName) {
|
|
2928
|
+
const cache = this._joinKeysCache ??= /* @__PURE__ */ new Map();
|
|
2929
|
+
let keys = cache.get(relName);
|
|
2930
|
+
if (keys === void 0) {
|
|
2931
|
+
keys = [];
|
|
2932
|
+
const relation = this._meta.relations.get(relName);
|
|
2933
|
+
if (relation?.direction === "to") keys = this._findFKForRelation(relation)?.localFields ?? [];
|
|
2934
|
+
else if (relation && this._tableResolver) {
|
|
2935
|
+
const remote = relation.direction === "from" ? this._tableResolver(relation.targetType()) : relation.viaType && this._tableResolver(relation.viaType());
|
|
2936
|
+
keys = (remote && this._findRemoteFK(remote, this.tableName, relation.direction === "from" ? relation.alias : void 0))?.targetFields ?? [];
|
|
2937
|
+
}
|
|
2938
|
+
if (relation && this.adapter.supportsNativeRelations()) keys = [...new Set([...keys, ...this.primaryKeys])];
|
|
2939
|
+
cache.set(relName, keys);
|
|
2940
|
+
}
|
|
2941
|
+
return keys;
|
|
2942
|
+
}
|
|
2943
|
+
/**
|
|
2763
2944
|
* Pre-computed field metadata for adapter use.
|
|
2764
2945
|
*/
|
|
2765
2946
|
get fieldDescriptors() {
|
|
@@ -2842,13 +3023,10 @@ var AtscriptDbReadable = class {
|
|
|
2842
3023
|
async findOne(query) {
|
|
2843
3024
|
this._ensureBuilt();
|
|
2844
3025
|
this._guardQuery(query);
|
|
2845
|
-
const
|
|
2846
|
-
const
|
|
2847
|
-
const result = await this.adapter.findOne(translatedQuery);
|
|
3026
|
+
const read = this._translateRead(query);
|
|
3027
|
+
const result = await this.adapter.findOne(read.translated);
|
|
2848
3028
|
if (!result) return null;
|
|
2849
|
-
const [row] = this.
|
|
2850
|
-
await this._decryptRows([row]);
|
|
2851
|
-
if (withRelations?.length) await this.loadRelations([row], withRelations);
|
|
3029
|
+
const [row] = await this._finishRead([result], read);
|
|
2852
3030
|
return row;
|
|
2853
3031
|
}
|
|
2854
3032
|
/**
|
|
@@ -2859,13 +3037,8 @@ var AtscriptDbReadable = class {
|
|
|
2859
3037
|
async findMany(query) {
|
|
2860
3038
|
this._ensureBuilt();
|
|
2861
3039
|
this._guardQuery(query);
|
|
2862
|
-
const
|
|
2863
|
-
|
|
2864
|
-
const results = await this.adapter.findMany(translatedQuery);
|
|
2865
|
-
const rows = this._fromRead(results, query.controls);
|
|
2866
|
-
await this._decryptRows(rows);
|
|
2867
|
-
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
2868
|
-
return rows;
|
|
3040
|
+
const read = this._translateRead(query);
|
|
3041
|
+
return await this._finishRead(await this.adapter.findMany(read.translated), read);
|
|
2869
3042
|
}
|
|
2870
3043
|
/**
|
|
2871
3044
|
* Counts records matching the query.
|
|
@@ -2885,14 +3058,10 @@ var AtscriptDbReadable = class {
|
|
|
2885
3058
|
async findManyWithCount(query) {
|
|
2886
3059
|
this._ensureBuilt();
|
|
2887
3060
|
this._guardQuery(query);
|
|
2888
|
-
const
|
|
2889
|
-
const
|
|
2890
|
-
const result = await this.adapter.findManyWithCount(translated);
|
|
2891
|
-
const rows = this._fromRead(result.data, query.controls);
|
|
2892
|
-
await this._decryptRows(rows);
|
|
2893
|
-
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
3061
|
+
const read = this._translateRead(query);
|
|
3062
|
+
const result = await this.adapter.findManyWithCount(read.translated);
|
|
2894
3063
|
return {
|
|
2895
|
-
data:
|
|
3064
|
+
data: await this._finishRead(result.data, read),
|
|
2896
3065
|
count: result.count
|
|
2897
3066
|
};
|
|
2898
3067
|
}
|
|
@@ -3003,13 +3172,9 @@ var AtscriptDbReadable = class {
|
|
|
3003
3172
|
this._ensureBuilt();
|
|
3004
3173
|
this._ensureSearchable();
|
|
3005
3174
|
this._guardQuery(query);
|
|
3006
|
-
const
|
|
3007
|
-
const
|
|
3008
|
-
|
|
3009
|
-
const rows = this._fromRead(results, query.controls);
|
|
3010
|
-
await this._decryptRows(rows);
|
|
3011
|
-
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
3012
|
-
return rows;
|
|
3175
|
+
const read = this._translateRead(query);
|
|
3176
|
+
const results = await this.adapter.search(text, read.translated, indexName);
|
|
3177
|
+
return await this._finishRead(results, read);
|
|
3013
3178
|
}
|
|
3014
3179
|
/**
|
|
3015
3180
|
* Full-text search with count for paginated search results.
|
|
@@ -3018,14 +3183,10 @@ var AtscriptDbReadable = class {
|
|
|
3018
3183
|
this._ensureBuilt();
|
|
3019
3184
|
this._ensureSearchable();
|
|
3020
3185
|
this._guardQuery(query);
|
|
3021
|
-
const
|
|
3022
|
-
const
|
|
3023
|
-
const result = await this.adapter.searchWithCount(text, translated, indexName);
|
|
3024
|
-
const rows = this._fromRead(result.data, query.controls);
|
|
3025
|
-
await this._decryptRows(rows);
|
|
3026
|
-
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
3186
|
+
const read = this._translateRead(query);
|
|
3187
|
+
const result = await this.adapter.searchWithCount(text, read.translated, indexName);
|
|
3027
3188
|
return {
|
|
3028
|
-
data:
|
|
3189
|
+
data: await this._finishRead(result.data, read),
|
|
3029
3190
|
count: result.count
|
|
3030
3191
|
};
|
|
3031
3192
|
}
|
|
@@ -3044,13 +3205,9 @@ var AtscriptDbReadable = class {
|
|
|
3044
3205
|
const { vector, query, indexName } = this._resolveVectorSearchArgs(vectorOrIndex, maybeVectorOrQuery, maybeQuery);
|
|
3045
3206
|
this._ensureBuilt();
|
|
3046
3207
|
this._guardQuery(query);
|
|
3047
|
-
const
|
|
3048
|
-
const
|
|
3049
|
-
|
|
3050
|
-
const rows = this._fromRead(results, query?.controls);
|
|
3051
|
-
await this._decryptRows(rows);
|
|
3052
|
-
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
3053
|
-
return rows;
|
|
3208
|
+
const read = this._translateRead(query);
|
|
3209
|
+
const results = await this.adapter.vectorSearch(vector, read.translated, indexName);
|
|
3210
|
+
return await this._finishRead(results, read);
|
|
3054
3211
|
}
|
|
3055
3212
|
/**
|
|
3056
3213
|
* Vector similarity search with count for paginated results.
|
|
@@ -3063,14 +3220,10 @@ var AtscriptDbReadable = class {
|
|
|
3063
3220
|
const { vector, query, indexName } = this._resolveVectorSearchArgs(vectorOrIndex, maybeVectorOrQuery, maybeQuery);
|
|
3064
3221
|
this._ensureBuilt();
|
|
3065
3222
|
this._guardQuery(query);
|
|
3066
|
-
const
|
|
3067
|
-
const
|
|
3068
|
-
const result = await this.adapter.vectorSearchWithCount(vector, translated, indexName);
|
|
3069
|
-
const rows = this._fromRead(result.data, query?.controls);
|
|
3070
|
-
await this._decryptRows(rows);
|
|
3071
|
-
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
3223
|
+
const read = this._translateRead(query);
|
|
3224
|
+
const result = await this.adapter.vectorSearchWithCount(vector, read.translated, indexName);
|
|
3072
3225
|
return {
|
|
3073
|
-
data:
|
|
3226
|
+
data: await this._finishRead(result.data, read),
|
|
3074
3227
|
count: result.count
|
|
3075
3228
|
};
|
|
3076
3229
|
}
|
|
@@ -3103,12 +3256,9 @@ var AtscriptDbReadable = class {
|
|
|
3103
3256
|
*/
|
|
3104
3257
|
async geoSearch(pointOrIndex, maybePointOrQuery, maybeQuery) {
|
|
3105
3258
|
const { point, query, indexName } = this._resolveGeoSearchArgs(pointOrIndex, maybePointOrQuery, maybeQuery);
|
|
3106
|
-
const
|
|
3107
|
-
const results = await this.adapter.geoSearch(point, translated, indexName);
|
|
3108
|
-
|
|
3109
|
-
await this._decryptRows(rows);
|
|
3110
|
-
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
3111
|
-
return rows;
|
|
3259
|
+
const read = this._prepareGeoSearch(point, query, indexName);
|
|
3260
|
+
const results = await this.adapter.geoSearch(point, read.translated, indexName);
|
|
3261
|
+
return await this._finishRead(results, read);
|
|
3112
3262
|
}
|
|
3113
3263
|
/**
|
|
3114
3264
|
* Distance-ranked geospatial search with count for paginated results.
|
|
@@ -3119,13 +3269,10 @@ var AtscriptDbReadable = class {
|
|
|
3119
3269
|
*/
|
|
3120
3270
|
async geoSearchWithCount(pointOrIndex, maybePointOrQuery, maybeQuery) {
|
|
3121
3271
|
const { point, query, indexName } = this._resolveGeoSearchArgs(pointOrIndex, maybePointOrQuery, maybeQuery);
|
|
3122
|
-
const
|
|
3123
|
-
const result = await this.adapter.geoSearchWithCount(point, translated, indexName);
|
|
3124
|
-
const rows = this._fromRead(result.data, query?.controls);
|
|
3125
|
-
await this._decryptRows(rows);
|
|
3126
|
-
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
3272
|
+
const read = this._prepareGeoSearch(point, query, indexName);
|
|
3273
|
+
const result = await this.adapter.geoSearchWithCount(point, read.translated, indexName);
|
|
3127
3274
|
return {
|
|
3128
|
-
data:
|
|
3275
|
+
data: await this._finishRead(result.data, read),
|
|
3129
3276
|
count: result.count
|
|
3130
3277
|
};
|
|
3131
3278
|
}
|
|
@@ -3145,18 +3292,18 @@ var AtscriptDbReadable = class {
|
|
|
3145
3292
|
/** Shared geoSearch validation + query translation. */
|
|
3146
3293
|
_prepareGeoSearch(point, query, indexName) {
|
|
3147
3294
|
this._ensureBuilt();
|
|
3148
|
-
if (!this.adapter.isGeoSearchable()) throw new DbError("GEO_NOT_SUPPORTED", [{
|
|
3149
|
-
path: "",
|
|
3150
|
-
message: `Geo search is not supported by the adapter behind table "${this.tableName}"`
|
|
3151
|
-
}]);
|
|
3152
3295
|
const geoIndexes = [...this._meta.indexes.values()].filter((index) => index.type === "geo");
|
|
3153
3296
|
if (geoIndexes.length === 0) throw new DbError("GEO_INDEX_MISSING", [{
|
|
3154
3297
|
path: "",
|
|
3155
|
-
message:
|
|
3298
|
+
message: geoIndexNotFoundMessage(this.tableName)
|
|
3156
3299
|
}]);
|
|
3157
3300
|
if (indexName !== void 0 && !geoIndexes.some((index) => index.name === indexName)) throw new DbError("GEO_INDEX_MISSING", [{
|
|
3158
3301
|
path: indexName,
|
|
3159
|
-
message:
|
|
3302
|
+
message: geoIndexNotFoundMessage(this.tableName, indexName)
|
|
3303
|
+
}]);
|
|
3304
|
+
if (!this.adapter.isGeoSearchable()) throw new DbError("GEO_NOT_SUPPORTED", [{
|
|
3305
|
+
path: "",
|
|
3306
|
+
message: `Geo search is not supported by the adapter behind table "${this.tableName}"`
|
|
3160
3307
|
}]);
|
|
3161
3308
|
assertGeoPoint(point, "$center");
|
|
3162
3309
|
const controls = query?.controls ?? {};
|
|
@@ -3172,17 +3319,17 @@ var AtscriptDbReadable = class {
|
|
|
3172
3319
|
}]);
|
|
3173
3320
|
}
|
|
3174
3321
|
this._guardQuery(query);
|
|
3175
|
-
|
|
3176
|
-
return {
|
|
3177
|
-
translated: this._fieldMapper.translateQuery(query || {}, this._meta),
|
|
3178
|
-
withRelations
|
|
3179
|
-
};
|
|
3322
|
+
return this._translateRead(query);
|
|
3180
3323
|
}
|
|
3181
3324
|
/**
|
|
3182
3325
|
* Finds a single record by any type-compatible identifier — primary key
|
|
3183
3326
|
* or single-field unique index.
|
|
3184
3327
|
* The return type excludes nav props unless `$with` is provided in controls.
|
|
3185
3328
|
*
|
|
3329
|
+
* The id addresses exactly ONE row, primary key first (since 0.1.143) —
|
|
3330
|
+
* see {@link resolveRowFilter}: when a scalar id equals one row's primary
|
|
3331
|
+
* key and another row's unique key, the primary-key row is returned.
|
|
3332
|
+
*
|
|
3186
3333
|
* ```typescript
|
|
3187
3334
|
* // Without relations — nav props stripped from result
|
|
3188
3335
|
* const user = await table.findById('123')
|
|
@@ -3192,61 +3339,194 @@ var AtscriptDbReadable = class {
|
|
|
3192
3339
|
* ```
|
|
3193
3340
|
*/
|
|
3194
3341
|
async findById(id, query) {
|
|
3342
|
+
return this.findOneByRow(id, { controls: query?.controls });
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* Reads the ONE row an id addresses — resolved exactly like
|
|
3346
|
+
* {@link resolveRowFilter} (primary key first, `opts.scope` /
|
|
3347
|
+
* `opts.isFieldVisible` as there) — with `opts.controls` applied, in one
|
|
3348
|
+
* step: the identifications are probed in order with the caller's
|
|
3349
|
+
* controls and the first row found wins, so no pin-then-reread. The row
|
|
3350
|
+
* must also match `opts.scope` (an out-of-scope row answers `null` like a
|
|
3351
|
+
* missing one).
|
|
3352
|
+
*
|
|
3353
|
+
* @since 0.1.143
|
|
3354
|
+
*/
|
|
3355
|
+
async findOneByRow(id, opts) {
|
|
3195
3356
|
this._ensureBuilt();
|
|
3196
|
-
const
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3357
|
+
const controls = opts?.controls ?? {};
|
|
3358
|
+
for (const candidate of this._idCandidates(id, opts)) {
|
|
3359
|
+
const row = await this.findOne({
|
|
3360
|
+
filter: this._andScope(candidate, opts?.scope),
|
|
3361
|
+
controls
|
|
3362
|
+
});
|
|
3363
|
+
if (row) return row;
|
|
3364
|
+
}
|
|
3365
|
+
return null;
|
|
3202
3366
|
}
|
|
3203
3367
|
/**
|
|
3204
3368
|
* Resolve an id value (scalar or object) into a {@link FilterExpr} using the
|
|
3205
|
-
* same
|
|
3369
|
+
* same identifications as {@link findById}. Public so callers can
|
|
3206
3370
|
* AND-combine the id-filter with a row-level read overlay before issuing
|
|
3207
3371
|
* `findOne` (avoiding the existence leak that `findById` would cause).
|
|
3208
3372
|
* `opts.isFieldVisible` (since 0.1.134) drops unique indexes over hidden
|
|
3209
3373
|
* fields — see {@link identificationsVisibleTo}.
|
|
3374
|
+
*
|
|
3375
|
+
* The result is a plain `$or` over every identification the id is
|
|
3376
|
+
* type-compatible with (a scalar can equal one row's primary key AND
|
|
3377
|
+
* another row's unique key), so it may match more than one row. Code that
|
|
3378
|
+
* must address exactly one row — writes, pre-images, `/one` reads — uses
|
|
3379
|
+
* {@link resolveRowFilter} instead.
|
|
3210
3380
|
*/
|
|
3211
3381
|
resolveIdFilter(id, opts) {
|
|
3212
3382
|
return this._resolveIdFilter(id, opts);
|
|
3213
3383
|
}
|
|
3214
3384
|
/**
|
|
3215
|
-
* Resolve an id value into a filter
|
|
3385
|
+
* Resolve an id value (scalar or object) into a filter that matches exactly
|
|
3386
|
+
* ONE row, deterministically and primary key first (since 0.1.143):
|
|
3387
|
+
*
|
|
3388
|
+
* - an id that yields a single identification (e.g. a numeric PK with no
|
|
3389
|
+
* type-compatible unique key) resolves to it without a read;
|
|
3390
|
+
* - an object id carrying the complete primary key resolves by the primary
|
|
3391
|
+
* key alone — exactly like a write payload is identified;
|
|
3392
|
+
* - otherwise the identifications are tried in order (primary key first,
|
|
3393
|
+
* then each unique index): the first one that matches a row wins and the
|
|
3394
|
+
* result is that row's exact primary-key filter;
|
|
3395
|
+
* - when none matches, the first identification is returned (it matches
|
|
3396
|
+
* nothing, so callers answer "not found" as usual).
|
|
3397
|
+
*
|
|
3398
|
+
* `null` when the id resolves to no identification at all. Every write
|
|
3399
|
+
* (`deleteOne`, the guards' `current()`) and `findById` go through this
|
|
3400
|
+
* resolution; call it inside the write's transaction when the answer must
|
|
3401
|
+
* stay pinned. `opts.isFieldVisible` as in {@link resolveIdFilter}.
|
|
3402
|
+
*
|
|
3403
|
+
* `opts.scope` (a row-level overlay) restricts which rows count as matches
|
|
3404
|
+
* while the identifications are tried — a row outside it never shadows one
|
|
3405
|
+
* inside it, so the answer is the same as if that row did not exist. AND
|
|
3406
|
+
* the scope onto the result to exclude an out-of-scope row the id names
|
|
3407
|
+
* unambiguously. See {@link TRowResolveOptions}.
|
|
3408
|
+
*/
|
|
3409
|
+
async resolveRowFilter(id, opts) {
|
|
3410
|
+
this._ensureBuilt();
|
|
3411
|
+
return this._pinIdCandidates(this._idCandidates(id, opts), opts?.scope);
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* Resolve an id value into a filter expression (the `$or` of
|
|
3415
|
+
* {@link _idCandidates}; a single candidate is returned as-is).
|
|
3416
|
+
*/
|
|
3417
|
+
_resolveIdFilter(id, opts) {
|
|
3418
|
+
const candidates = this._idCandidates(id, opts, false);
|
|
3419
|
+
if (candidates.length === 0) return null;
|
|
3420
|
+
if (candidates.length === 1) return candidates[0];
|
|
3421
|
+
return { $or: candidates };
|
|
3422
|
+
}
|
|
3423
|
+
/**
|
|
3424
|
+
* The ordered identification filters an id value can resolve through —
|
|
3425
|
+
* primary key first, then each unique index.
|
|
3216
3426
|
*
|
|
3217
3427
|
* When `preferredId` differs from the PK, scalar ids resolve only against
|
|
3218
3428
|
* the preferred field (deterministic addressing). Otherwise scalars try PK
|
|
3219
3429
|
* + every single-field unique index; objects try PK + compound unique
|
|
3220
3430
|
* indexes. With `opts.isFieldVisible`, only the identifications
|
|
3221
|
-
* {@link identificationsVisibleTo} keeps are tried.
|
|
3431
|
+
* {@link identificationsVisibleTo} keeps are tried. With `pkWins` (the
|
|
3432
|
+
* default), an object id carrying the complete primary key yields the
|
|
3433
|
+
* primary-key filter alone.
|
|
3222
3434
|
*/
|
|
3223
|
-
|
|
3435
|
+
_idCandidates(id, opts, pkWins = true) {
|
|
3224
3436
|
const pkFields = this.primaryKeys;
|
|
3225
3437
|
const preferredFields = this.preferredId;
|
|
3226
3438
|
const isExplicitPreferred = preferredFields.length !== pkFields.length || preferredFields.some((f, i) => f !== pkFields[i]);
|
|
3227
3439
|
const isScalar = id === null || typeof id !== "object";
|
|
3228
|
-
if (isScalar && isExplicitPreferred && preferredFields.length === 1)
|
|
3440
|
+
if (isScalar && isExplicitPreferred && preferredFields.length === 1) {
|
|
3441
|
+
const filter = this._tryFieldFilter(preferredFields[0], id);
|
|
3442
|
+
return filter ? [filter] : [];
|
|
3443
|
+
}
|
|
3444
|
+
const idObj = isScalar ? null : id;
|
|
3445
|
+
if (idObj && pkWins && pkFields.length > 0) {
|
|
3446
|
+
const pkFilter = this._tryCompoundFilter(pkFields, idObj);
|
|
3447
|
+
if (pkFilter) return [pkFilter];
|
|
3448
|
+
}
|
|
3229
3449
|
const tryScalarOrField = (field) => {
|
|
3230
|
-
const value = isScalar ? id :
|
|
3450
|
+
const value = isScalar ? id : idObj[field];
|
|
3231
3451
|
return value === void 0 ? null : this._tryFieldFilter(field, value);
|
|
3232
3452
|
};
|
|
3233
|
-
const
|
|
3234
|
-
const idObj = isScalar ? null : id;
|
|
3453
|
+
const candidates = [];
|
|
3235
3454
|
const identifications = this.identificationsVisibleTo(opts?.isFieldVisible);
|
|
3236
3455
|
for (const ident of identifications) {
|
|
3237
3456
|
if (ident.fields.length !== 1) continue;
|
|
3238
3457
|
const filter = tryScalarOrField(ident.fields[0]);
|
|
3239
|
-
if (filter)
|
|
3458
|
+
if (filter) candidates.push(filter);
|
|
3240
3459
|
}
|
|
3241
3460
|
if (idObj) for (const ident of identifications) {
|
|
3242
3461
|
if (ident.fields.length < 2) continue;
|
|
3243
|
-
if (ident.source !== "primaryKey" &&
|
|
3462
|
+
if (ident.source !== "primaryKey" && candidates.length > 0) break;
|
|
3244
3463
|
const filter = this._tryCompoundFilter(ident.fields, idObj);
|
|
3245
|
-
if (filter)
|
|
3464
|
+
if (filter) candidates.push(filter);
|
|
3465
|
+
}
|
|
3466
|
+
return candidates;
|
|
3467
|
+
}
|
|
3468
|
+
/**
|
|
3469
|
+
* Picks the one row a list of identification candidates addresses — see
|
|
3470
|
+
* {@link resolveRowFilter}. A lone candidate needs no read. With a
|
|
3471
|
+
* non-empty `scope`, only rows matching it count as matches.
|
|
3472
|
+
*/
|
|
3473
|
+
async _pinIdCandidates(candidates, scope) {
|
|
3474
|
+
if (candidates.length === 0) return null;
|
|
3475
|
+
if (candidates.length === 1) return candidates[0];
|
|
3476
|
+
const pkFields = this.primaryKeys;
|
|
3477
|
+
const select = new Set(pkFields);
|
|
3478
|
+
for (const candidate of candidates) for (const field in candidate) select.add(field);
|
|
3479
|
+
const rows = await this.findMany({
|
|
3480
|
+
filter: this._andScope({ $or: candidates }, scope),
|
|
3481
|
+
controls: { $select: [...select] }
|
|
3482
|
+
});
|
|
3483
|
+
if (rows.length === 0) return candidates[0];
|
|
3484
|
+
for (const candidate of candidates) {
|
|
3485
|
+
const row = rows.find((r) => rowMatchesKey(r, candidate));
|
|
3486
|
+
if (row) return this._pkFilterFrom(row) ?? candidate;
|
|
3487
|
+
}
|
|
3488
|
+
return this._probeIdCandidates(candidates, scope);
|
|
3489
|
+
}
|
|
3490
|
+
/** Sequential fallback of {@link _pinIdCandidates}: first candidate matching a row wins. */
|
|
3491
|
+
async _probeIdCandidates(candidates, scope) {
|
|
3492
|
+
for (const candidate of candidates) {
|
|
3493
|
+
const pinned = await this._readPkFilter(candidate, scope);
|
|
3494
|
+
if (pinned) return pinned;
|
|
3246
3495
|
}
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3496
|
+
return candidates[0];
|
|
3497
|
+
}
|
|
3498
|
+
/**
|
|
3499
|
+
* The exact primary-key filter of `row` (values prepared like ids) — `null`
|
|
3500
|
+
* when the table has no primary key or `row` lacks a key field.
|
|
3501
|
+
*/
|
|
3502
|
+
_pkFilterFrom(row) {
|
|
3503
|
+
const pkFields = this.primaryKeys;
|
|
3504
|
+
if (pkFields.length === 0) return null;
|
|
3505
|
+
const filter = {};
|
|
3506
|
+
for (const field of pkFields) {
|
|
3507
|
+
const value = row[field];
|
|
3508
|
+
if (value === void 0) return null;
|
|
3509
|
+
const fieldType = this.flatMap.get(field);
|
|
3510
|
+
filter[field] = fieldType ? this.adapter.prepareId(value, fieldType) : value;
|
|
3511
|
+
}
|
|
3512
|
+
return filter;
|
|
3513
|
+
}
|
|
3514
|
+
/**
|
|
3515
|
+
* Reads the row `filter` (AND `scope`) matches and returns its exact
|
|
3516
|
+
* primary-key filter (`filter` itself on a table without one); `undefined`
|
|
3517
|
+
* when no row matches.
|
|
3518
|
+
*/
|
|
3519
|
+
async _readPkFilter(filter, scope) {
|
|
3520
|
+
const pkFields = this.primaryKeys;
|
|
3521
|
+
const row = await this.findOne({
|
|
3522
|
+
filter: this._andScope(filter, scope),
|
|
3523
|
+
controls: pkFields.length > 0 ? { $select: [...pkFields] } : {}
|
|
3524
|
+
});
|
|
3525
|
+
return row ? this._pkFilterFrom(row) ?? filter : void 0;
|
|
3526
|
+
}
|
|
3527
|
+
/** `filter` AND a row scope — `filter` itself when the scope is absent or empty. */
|
|
3528
|
+
_andScope(filter, scope) {
|
|
3529
|
+
return scope === void 0 || isEmptyObject(scope) ? filter : { $and: [filter, scope] };
|
|
3250
3530
|
}
|
|
3251
3531
|
/** Build a single-key filter from `idObj` over `fields`, or null if any field is missing/incompatible. */
|
|
3252
3532
|
_tryCompoundFilter(fields, idObj) {
|
|
@@ -3281,7 +3561,7 @@ var AtscriptDbReadable = class {
|
|
|
3281
3561
|
* Public entry point for relation loading. Used by adapters for nested $with delegation.
|
|
3282
3562
|
*/
|
|
3283
3563
|
async loadRelations(rows, withRelations) {
|
|
3284
|
-
const { loadRelationsImpl } = await import("./relation-loader-
|
|
3564
|
+
const { loadRelationsImpl } = await import("./relation-loader-D9XuXaMv.mjs").then((n) => n.n);
|
|
3285
3565
|
return loadRelationsImpl(rows, withRelations, this);
|
|
3286
3566
|
}
|
|
3287
3567
|
/**
|
|
@@ -3485,6 +3765,16 @@ var BaseDbAdapter = class {
|
|
|
3485
3765
|
return findTxContext(this._transactionOwner())?.state;
|
|
3486
3766
|
}
|
|
3487
3767
|
/**
|
|
3768
|
+
* `true` when the current async context runs inside a REAL transaction of
|
|
3769
|
+
* this adapter (its owner) — one a throw rolls back (since 0.1.143).
|
|
3770
|
+
* `false` outside any transaction and inside a pass-through
|
|
3771
|
+
* `withTransaction` (adapters without transaction primitives such as the
|
|
3772
|
+
* in-memory one, a standalone MongoDB topology).
|
|
3773
|
+
*/
|
|
3774
|
+
isInTransaction() {
|
|
3775
|
+
return this._getTransactionState() !== void 0;
|
|
3776
|
+
}
|
|
3777
|
+
/**
|
|
3488
3778
|
* Runs `fn` inside the transaction ALS context with the given state.
|
|
3489
3779
|
* Adapters that override `withTransaction` (e.g., to use MongoDB's
|
|
3490
3780
|
* `session.withTransaction()` Convenient API) use this to set up the
|
|
@@ -3763,6 +4053,25 @@ var BaseDbAdapter = class {
|
|
|
3763
4053
|
getSearchIndexes() {
|
|
3764
4054
|
return [];
|
|
3765
4055
|
}
|
|
4056
|
+
_physicalToLogical;
|
|
4057
|
+
/**
|
|
4058
|
+
* The LOGICAL field paths an index reads (its `fields` carry physical
|
|
4059
|
+
* names) — what adapters report as {@link TSearchIndexInfo.fields}. A
|
|
4060
|
+
* derived column never shadows the regular field sharing its physical name.
|
|
4061
|
+
* @since 0.1.143
|
|
4062
|
+
*/
|
|
4063
|
+
_indexLogicalPaths(index) {
|
|
4064
|
+
let logical = this._physicalToLogical;
|
|
4065
|
+
if (!logical) {
|
|
4066
|
+
logical = /* @__PURE__ */ new Map();
|
|
4067
|
+
for (const fd of this._table.fieldDescriptors) {
|
|
4068
|
+
if (fd.ignored || fd.derived && logical.has(fd.physicalName)) continue;
|
|
4069
|
+
logical.set(fd.physicalName, fd.path);
|
|
4070
|
+
}
|
|
4071
|
+
this._physicalToLogical = logical;
|
|
4072
|
+
}
|
|
4073
|
+
return index.fields.map((field) => logical.get(field.name) ?? field.name);
|
|
4074
|
+
}
|
|
3766
4075
|
/**
|
|
3767
4076
|
* Whether this adapter can run TEXT search — `search()`, `searchWithCount()`
|
|
3768
4077
|
* and the grouped `$search` path all gate on it. Vector capability is a
|
|
@@ -3844,7 +4153,7 @@ var BaseDbAdapter = class {
|
|
|
3844
4153
|
const column = (indexName ? geoIndexes.find((candidate) => candidate.name === indexName) : geoIndexes[0])?.fields[0]?.name;
|
|
3845
4154
|
if (!column) throw new DbError("GEO_INDEX_MISSING", [{
|
|
3846
4155
|
path: indexName ?? "",
|
|
3847
|
-
message:
|
|
4156
|
+
message: geoIndexNotFoundMessage(this._table.tableName, indexName)
|
|
3848
4157
|
}]);
|
|
3849
4158
|
return column;
|
|
3850
4159
|
}
|
|
@@ -4466,30 +4775,99 @@ function _shallowPrunedClone(source) {
|
|
|
4466
4775
|
}
|
|
4467
4776
|
return out;
|
|
4468
4777
|
}
|
|
4778
|
+
/** Same keys, same key values (across driver representations). */
|
|
4779
|
+
function sameFilter(a, b) {
|
|
4780
|
+
const aKeys = Object.keys(a);
|
|
4781
|
+
return aKeys.length === Object.keys(b).length && aKeys.every((key) => sameKey(a[key], b[key]));
|
|
4782
|
+
}
|
|
4469
4783
|
/**
|
|
4470
|
-
* {@link TDbWriteGuardContext} handed to a write guard:
|
|
4471
|
-
*
|
|
4784
|
+
* {@link TDbWriteGuardContext} handed to a write guard: sparse per-index
|
|
4785
|
+
* caches of record filters and pre-image reads, allocated on first use.
|
|
4786
|
+
* The write reuses a read for its own target pin ({@link readFor}).
|
|
4472
4787
|
*/
|
|
4473
4788
|
var WriteGuardContext = class {
|
|
4474
4789
|
action;
|
|
4475
4790
|
rows;
|
|
4476
4791
|
expectedVersions;
|
|
4477
4792
|
_table;
|
|
4478
|
-
|
|
4479
|
-
|
|
4793
|
+
_opts;
|
|
4794
|
+
_filters;
|
|
4795
|
+
_reads;
|
|
4796
|
+
constructor(action, rows, expectedVersions, _table, _opts) {
|
|
4480
4797
|
this.action = action;
|
|
4481
4798
|
this.rows = rows;
|
|
4482
4799
|
this.expectedVersions = expectedVersions;
|
|
4483
4800
|
this._table = _table;
|
|
4801
|
+
this._opts = _opts;
|
|
4802
|
+
}
|
|
4803
|
+
filterFor(i) {
|
|
4804
|
+
const filters = this._filters ??= [];
|
|
4805
|
+
let filter = filters[i];
|
|
4806
|
+
if (filter === void 0) {
|
|
4807
|
+
filter = this._table._recordFilterOrNull(this.rows[i], this._opts);
|
|
4808
|
+
filters[i] = filter;
|
|
4809
|
+
}
|
|
4810
|
+
return filter;
|
|
4484
4811
|
}
|
|
4485
4812
|
current(i) {
|
|
4486
|
-
const
|
|
4487
|
-
let
|
|
4488
|
-
if (!
|
|
4489
|
-
|
|
4490
|
-
|
|
4813
|
+
const reads = this._reads ??= [];
|
|
4814
|
+
let read = reads[i];
|
|
4815
|
+
if (!read) {
|
|
4816
|
+
const filter = this.filterFor(i);
|
|
4817
|
+
read = filter ? this._table.findOne({
|
|
4818
|
+
filter,
|
|
4819
|
+
controls: {}
|
|
4820
|
+
}) : Promise.resolve(null);
|
|
4821
|
+
reads[i] = read;
|
|
4822
|
+
}
|
|
4823
|
+
return read;
|
|
4824
|
+
}
|
|
4825
|
+
currentAll() {
|
|
4826
|
+
const reads = this._reads ??= [];
|
|
4827
|
+
const pending = [];
|
|
4828
|
+
for (let i = 0; i < this.rows.length; i++) {
|
|
4829
|
+
if (reads[i]) continue;
|
|
4830
|
+
if (this.filterFor(i)) pending.push(i);
|
|
4831
|
+
else reads[i] = Promise.resolve(null);
|
|
4832
|
+
}
|
|
4833
|
+
if (pending.length > 0) {
|
|
4834
|
+
const found = this._readMany(pending.map((i) => this.filterFor(i)));
|
|
4835
|
+
pending.forEach((i, k) => {
|
|
4836
|
+
reads[i] = found.then((rows) => rows[k]);
|
|
4837
|
+
});
|
|
4491
4838
|
}
|
|
4492
|
-
return
|
|
4839
|
+
return Promise.all(this.rows.map((_, i) => reads[i]));
|
|
4840
|
+
}
|
|
4841
|
+
/**
|
|
4842
|
+
* One `findMany` for the `filters` (full rows, like `current(i)`), matched
|
|
4843
|
+
* back in memory; a filter the store matched differently (e.g. a
|
|
4844
|
+
* case-insensitive collation) is re-read on its own.
|
|
4845
|
+
*/
|
|
4846
|
+
async _readMany(filters) {
|
|
4847
|
+
const table = this._table;
|
|
4848
|
+
const rows = await table.findMany({
|
|
4849
|
+
filter: filters.length === 1 ? filters[0] : { $or: filters },
|
|
4850
|
+
controls: {}
|
|
4851
|
+
});
|
|
4852
|
+
const used = /* @__PURE__ */ new Set();
|
|
4853
|
+
const out = filters.map((filter) => {
|
|
4854
|
+
const row = rows.find((r) => rowMatchesKey(r, filter));
|
|
4855
|
+
if (row) used.add(row);
|
|
4856
|
+
return row ?? null;
|
|
4857
|
+
});
|
|
4858
|
+
if (used.size < rows.length) {
|
|
4859
|
+
for (let k = 0; k < filters.length; k++) if (out[k] === null) out[k] = await table.findOne({
|
|
4860
|
+
filter: filters[k],
|
|
4861
|
+
controls: {}
|
|
4862
|
+
});
|
|
4863
|
+
}
|
|
4864
|
+
return out;
|
|
4865
|
+
}
|
|
4866
|
+
/** The pre-image `current(i)` read by exactly `filter`, if the guard asked for it. */
|
|
4867
|
+
readFor(i, filter) {
|
|
4868
|
+
const read = this._reads?.[i];
|
|
4869
|
+
const readBy = this._filters?.[i];
|
|
4870
|
+
return read && readBy && sameFilter(readBy, filter) ? read : void 0;
|
|
4493
4871
|
}
|
|
4494
4872
|
};
|
|
4495
4873
|
/** {@link TDbRemoveGuardContext} handed to a delete guard (one memoised pre-image read). */
|
|
@@ -4511,6 +4889,23 @@ var RemoveGuardContext = class {
|
|
|
4511
4889
|
return this._pending;
|
|
4512
4890
|
}
|
|
4513
4891
|
};
|
|
4892
|
+
/** Number of `true` entries. */
|
|
4893
|
+
function countTrue(flags) {
|
|
4894
|
+
let n = 0;
|
|
4895
|
+
for (const flag of flags) if (flag) n++;
|
|
4896
|
+
return n;
|
|
4897
|
+
}
|
|
4898
|
+
/**
|
|
4899
|
+
* A write item matched nothing although its row was relied upon — a replace
|
|
4900
|
+
* whose nested TO rows were already written, or a nested child re-parented
|
|
4901
|
+
* since the plan. The row changed meanwhile; thrown to roll back.
|
|
4902
|
+
*/
|
|
4903
|
+
function concurrentChange() {
|
|
4904
|
+
return new DbError("CONFLICT", [{
|
|
4905
|
+
path: "",
|
|
4906
|
+
message: "The record changed during the write — nothing was written, retry"
|
|
4907
|
+
}]);
|
|
4908
|
+
}
|
|
4514
4909
|
/** Upper bound of keys per `touchMany` UPDATE statement (parameter-count safety). */
|
|
4515
4910
|
const TOUCH_MANY_CHUNK = 500;
|
|
4516
4911
|
/** `touchMany` input rejection — always `INVALID_QUERY`, path names the key. */
|
|
@@ -4581,11 +4976,13 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4581
4976
|
* Recursive up to `maxDepth` (default 3).
|
|
4582
4977
|
*
|
|
4583
4978
|
* `opts.guard` (since 0.1.128) runs once inside the transaction, after
|
|
4584
|
-
* defaults + validation, with the prepared rows
|
|
4979
|
+
* defaults + validation, with the prepared rows; `opts.check` (since
|
|
4980
|
+
* 0.1.143) runs once after every phase with the inserted rows' primary-key
|
|
4981
|
+
* filters — see {@link TWriteOptions}.
|
|
4585
4982
|
*/
|
|
4586
4983
|
async insertMany(payloads, opts) {
|
|
4587
4984
|
this._ensureBuilt();
|
|
4588
|
-
const { _depth, _action, maxDepth: userMax, guard } = opts ?? {};
|
|
4985
|
+
const { _depth, _action, maxDepth: userMax, guard, check } = opts ?? {};
|
|
4589
4986
|
const maxDepth = userMax ?? 3;
|
|
4590
4987
|
const depth = _depth ?? 0;
|
|
4591
4988
|
const canNest = depth < maxDepth && this._writeTableResolver && this._meta.navFields.size > 0;
|
|
@@ -4602,7 +4999,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4602
4999
|
this._applyDepthCtx(ctx, depth);
|
|
4603
5000
|
validateBatch(validator, items, ctx);
|
|
4604
5001
|
if (guard) {
|
|
4605
|
-
await guard(new WriteGuardContext(_action ?? "insertMany", items, Array.from({ length: items.length }), this));
|
|
5002
|
+
await guard(new WriteGuardContext(_action ?? "insertMany", items, Array.from({ length: items.length }), this, opts));
|
|
4606
5003
|
validateBatch(validator, items, ctx);
|
|
4607
5004
|
}
|
|
4608
5005
|
await this._encryptItems(items, "write");
|
|
@@ -4618,6 +5015,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4618
5015
|
const result = await this.adapter.insertMany(prepared);
|
|
4619
5016
|
if (canNest) await batchInsertNestedFrom(host, originals, result.insertedIds, maxDepth, depth);
|
|
4620
5017
|
if (canNest) await batchInsertNestedVia(host, originals, result.insertedIds, maxDepth, depth);
|
|
5018
|
+
if (check) await this._runWriteCheck(check, _action ?? "insertMany", this._insertedPkFilters(items, prepared, result.insertedIds));
|
|
4621
5019
|
return result;
|
|
4622
5020
|
}));
|
|
4623
5021
|
}
|
|
@@ -4640,11 +5038,13 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4640
5038
|
* re-create junction rows. Fully recursive up to `maxDepth` (default 3).
|
|
4641
5039
|
*
|
|
4642
5040
|
* `opts.guard` (since 0.1.128) runs once inside the transaction, after
|
|
4643
|
-
* `$cas` extraction, defaults + validation
|
|
5041
|
+
* `$cas` extraction, defaults + validation; `opts.check` (since 0.1.143)
|
|
5042
|
+
* after every phase — see {@link TWriteOptions}. The nested phases run only
|
|
5043
|
+
* for the rows the main replace matched.
|
|
4644
5044
|
*/
|
|
4645
5045
|
async bulkReplace(payloads, opts) {
|
|
4646
5046
|
this._ensureBuilt();
|
|
4647
|
-
const { _depth, _action, maxDepth: userMax, guard } = opts ?? {};
|
|
5047
|
+
const { _depth, _action, _ownedBy, maxDepth: userMax, guard, check } = opts ?? {};
|
|
4648
5048
|
const maxDepth = userMax ?? 3;
|
|
4649
5049
|
const depth = _depth ?? 0;
|
|
4650
5050
|
const canNest = depth < maxDepth && this._writeTableResolver && this._meta.navFields.size > 0;
|
|
@@ -4666,31 +5066,40 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4666
5066
|
};
|
|
4667
5067
|
this._applyDepthCtx(ctx, depth);
|
|
4668
5068
|
validateBatch(validator, items, ctx);
|
|
5069
|
+
let guardCtx;
|
|
4669
5070
|
if (guard) {
|
|
4670
|
-
|
|
5071
|
+
guardCtx = new WriteGuardContext(_action ?? "replaceMany", items, expectedVersions, this, opts);
|
|
5072
|
+
await guard(guardCtx);
|
|
4671
5073
|
validateBatch(validator, items, ctx);
|
|
4672
5074
|
}
|
|
4673
5075
|
await this._encryptItems(items, "write");
|
|
4674
5076
|
const host = this;
|
|
4675
|
-
|
|
5077
|
+
const rowFilters = this._rowFilters(items, opts);
|
|
5078
|
+
const nestedPlan = canNest ? await planNestedFromVia(host, originals, "replace") : void 0;
|
|
5079
|
+
const nestedTo = canNest ? items.map((item) => this._carriesNestedTo(item)) : [];
|
|
5080
|
+
const targets = await this._pinTargets(rowFilters, (i) => nestedTo[i] || check !== void 0 && !this._isPkFilter(rowFilters[i]), guardCtx);
|
|
5081
|
+
const toApplied = this._gateNestedTo(items, nestedTo, targets, expectedVersions);
|
|
5082
|
+
if (toApplied.some(Boolean)) await batchReplaceNestedTo(host, items, maxDepth, depth);
|
|
4676
5083
|
await this._integrity.validateForeignKeys(items, this._meta, this._fkLookupResolver, this._writeTableResolver);
|
|
4677
5084
|
if (canNest) await preValidateNestedFrom(host, originals);
|
|
4678
|
-
let matchedCount = 0;
|
|
4679
5085
|
let modifiedCount = 0;
|
|
5086
|
+
const matched = [];
|
|
4680
5087
|
for (let i = 0; i < items.length; i++) {
|
|
4681
5088
|
const data = items[i];
|
|
4682
5089
|
for (const navField of this._meta.navFields) delete data[navField];
|
|
4683
5090
|
if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
|
|
4684
|
-
const filter = this._extractRecordFilter(data, opts);
|
|
4685
5091
|
const prepared = this._fieldMapper.prepareForWrite(data, this._meta, this.adapter);
|
|
4686
|
-
const result = await this.adapter.replaceOne(this._fieldMapper.translateFilter(
|
|
4687
|
-
matchedCount += result.matchedCount;
|
|
5092
|
+
const result = await this.adapter.replaceOne(this._fieldMapper.translateFilter(rowFilters[i], this._meta), prepared, expectedVersions[i]);
|
|
4688
5093
|
modifiedCount += result.modifiedCount;
|
|
5094
|
+
matched[i] = result.matchedCount > 0;
|
|
5095
|
+
if (!matched[i] && (toApplied[i] || _ownedBy?.strict?.[i])) throw concurrentChange();
|
|
4689
5096
|
}
|
|
4690
|
-
|
|
4691
|
-
if (
|
|
5097
|
+
const matchedOriginals = canNest ? originals.filter((_, i) => matched[i]) : [];
|
|
5098
|
+
if (matchedOriginals.length > 0) await batchReplaceNestedFrom(host, matchedOriginals, maxDepth, depth, nestedPlan);
|
|
5099
|
+
if (matchedOriginals.length > 0) await batchReplaceNestedVia(host, matchedOriginals, maxDepth, depth);
|
|
5100
|
+
if (check) await this._runWriteCheck(check, _action ?? "replaceMany", this._writtenPkFilters(rowFilters, targets, matched));
|
|
4692
5101
|
return {
|
|
4693
|
-
matchedCount,
|
|
5102
|
+
matchedCount: countTrue(matched),
|
|
4694
5103
|
modifiedCount
|
|
4695
5104
|
};
|
|
4696
5105
|
}));
|
|
@@ -4714,11 +5123,14 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4714
5123
|
*
|
|
4715
5124
|
* `opts.guard` (since 0.1.128) runs once inside the transaction, after
|
|
4716
5125
|
* `$cas` extraction and validation, with the patches (identifying fields
|
|
4717
|
-
* present, `$cas` removed)
|
|
5126
|
+
* present, `$cas` removed); `opts.check` (since 0.1.143) after every
|
|
5127
|
+
* phase — see {@link TWriteOptions}. The nested phases run only for the
|
|
5128
|
+
* rows the main patch matched; a nested TO object patches the row the
|
|
5129
|
+
* STORED foreign key references.
|
|
4718
5130
|
*/
|
|
4719
5131
|
async bulkUpdate(payloads, opts) {
|
|
4720
5132
|
this._ensureBuilt();
|
|
4721
|
-
const { _depth, _action, maxDepth: userMax, guard } = opts ?? {};
|
|
5133
|
+
const { _depth, _action, _ownedBy, maxDepth: userMax, guard, check } = opts ?? {};
|
|
4722
5134
|
const maxDepth = userMax ?? 3;
|
|
4723
5135
|
const depth = _depth ?? 0;
|
|
4724
5136
|
const canNest = depth < maxDepth && this._writeTableResolver && this._meta.navFields.size > 0;
|
|
@@ -4740,33 +5152,39 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4740
5152
|
};
|
|
4741
5153
|
this._applyDepthCtx(ctx, depth);
|
|
4742
5154
|
validateBatch(validator, cloned, ctx);
|
|
5155
|
+
let guardCtx;
|
|
4743
5156
|
if (guard) {
|
|
4744
|
-
|
|
5157
|
+
guardCtx = new WriteGuardContext(_action ?? "updateMany", cloned, expectedVersions, this, opts);
|
|
5158
|
+
await guard(guardCtx);
|
|
4745
5159
|
validateBatch(validator, cloned, ctx);
|
|
4746
5160
|
}
|
|
4747
5161
|
const originals = canNest ? cloned.map((p) => ({ ...p })) : [];
|
|
4748
5162
|
await this._encryptItems(cloned, "patch");
|
|
4749
5163
|
const host = this;
|
|
4750
|
-
|
|
5164
|
+
const rowFilters = this._rowFilters(cloned, opts);
|
|
5165
|
+
const nestedPlan = canNest ? await planNestedFromVia(host, originals, "patch") : void 0;
|
|
5166
|
+
const nestedTo = canNest ? cloned.map((payload) => this._carriesNestedTo(payload)) : [];
|
|
5167
|
+
const targets = await this._pinTargets(rowFilters, (i) => nestedTo[i] || check !== void 0 && !this._isPkFilter(rowFilters[i]), guardCtx);
|
|
5168
|
+
const toPlans = nestedTo.includes(true) ? await planPatchNestedTo(host, cloned, targets) : [];
|
|
4751
5169
|
await this._integrity.validateForeignKeys(cloned, this._meta, this._fkLookupResolver, this._writeTableResolver, true);
|
|
4752
|
-
let matchedCount = 0;
|
|
4753
5170
|
let modifiedCount = 0;
|
|
5171
|
+
const matched = [];
|
|
4754
5172
|
for (let i = 0; i < cloned.length; i++) {
|
|
4755
5173
|
const payload = cloned[i];
|
|
4756
5174
|
const expectedVersion = expectedVersions[i];
|
|
4757
5175
|
const data = { ...payload };
|
|
4758
5176
|
for (const navField of this._meta.navFields) delete data[navField];
|
|
4759
|
-
const filter =
|
|
5177
|
+
const filter = rowFilters[i];
|
|
4760
5178
|
for (const key of Object.keys(filter)) delete data[key];
|
|
4761
5179
|
this._meta.stripDerived(data);
|
|
4762
5180
|
if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
|
|
4763
5181
|
const translatedFilter = this._fieldMapper.translateFilter(filter, this._meta);
|
|
4764
5182
|
if (isEmptyObject(data) && expectedVersion === void 0) {
|
|
4765
|
-
|
|
5183
|
+
matched[i] = await this.adapter.count({
|
|
4766
5184
|
filter: translatedFilter,
|
|
4767
5185
|
controls: {}
|
|
4768
|
-
});
|
|
4769
|
-
|
|
5186
|
+
}) > 0;
|
|
5187
|
+
if (!matched[i] && _ownedBy?.strict?.[i]) throw concurrentChange();
|
|
4770
5188
|
continue;
|
|
4771
5189
|
}
|
|
4772
5190
|
let result;
|
|
@@ -4788,13 +5206,17 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4788
5206
|
result = await this.adapter.updateOne(translatedFilter, resolved, translatedOps, expectedVersion);
|
|
4789
5207
|
} else result = await this.adapter.updateOne(translatedFilter, translatedUpdate, translatedOps, expectedVersion);
|
|
4790
5208
|
}
|
|
4791
|
-
matchedCount += result.matchedCount;
|
|
4792
5209
|
modifiedCount += result.modifiedCount;
|
|
5210
|
+
matched[i] = result.matchedCount > 0;
|
|
5211
|
+
if (!matched[i] && _ownedBy?.strict?.[i]) throw concurrentChange();
|
|
4793
5212
|
}
|
|
4794
|
-
if (
|
|
4795
|
-
|
|
5213
|
+
if (toPlans.length > 0) await applyPatchNestedTo(toPlans, maxDepth, depth, matched);
|
|
5214
|
+
const matchedOriginals = canNest ? originals.filter((_, i) => matched[i]) : [];
|
|
5215
|
+
if (matchedOriginals.length > 0) await batchPatchNestedFrom(host, matchedOriginals, maxDepth, depth, nestedPlan);
|
|
5216
|
+
if (matchedOriginals.length > 0) await batchPatchNestedVia(host, matchedOriginals, maxDepth, depth, nestedPlan);
|
|
5217
|
+
if (check) await this._runWriteCheck(check, _action ?? "updateMany", this._writtenPkFilters(rowFilters, targets, matched));
|
|
4796
5218
|
return {
|
|
4797
|
-
matchedCount,
|
|
5219
|
+
matchedCount: countTrue(matched),
|
|
4798
5220
|
modifiedCount
|
|
4799
5221
|
};
|
|
4800
5222
|
}));
|
|
@@ -4879,29 +5301,40 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4879
5301
|
}
|
|
4880
5302
|
/**
|
|
4881
5303
|
* Deletes a single record by any type-compatible identifier — primary key
|
|
4882
|
-
* or single-field unique index. Uses the same resolution logic as `findById
|
|
5304
|
+
* or single-field unique index. Uses the same resolution logic as `findById`:
|
|
5305
|
+
* the id addresses exactly ONE row, primary key first (since 0.1.143 — see
|
|
5306
|
+
* {@link resolveRowFilter}); an id that could name several rows is pinned
|
|
5307
|
+
* inside the transaction, so the guard, the cascade and the delete all see
|
|
5308
|
+
* the same row.
|
|
4883
5309
|
*
|
|
4884
5310
|
* When the adapter does not support native foreign keys (e.g. MongoDB),
|
|
4885
5311
|
* cascade and setNull actions are applied before the delete.
|
|
4886
5312
|
*
|
|
4887
5313
|
* `opts.guard` (since 0.1.128) runs inside the transaction once the id has
|
|
4888
5314
|
* resolved to a filter, before cascade / delete — see {@link TDeleteOptions}.
|
|
5315
|
+
* `opts.scope` (since 0.1.143) is a row scope: an ambiguous id is pinned
|
|
5316
|
+
* among in-scope rows only (see {@link TRowResolveOptions}) and the delete
|
|
5317
|
+
* — guard `current()` and cascade included — targets the row only while it
|
|
5318
|
+
* matches the scope, so an out-of-scope row answers `{ deletedCount: 0 }`
|
|
5319
|
+
* exactly like a missing one.
|
|
4889
5320
|
* An id that resolves to no filter answers `{ deletedCount: 0 }` without
|
|
4890
5321
|
* calling the guard.
|
|
4891
5322
|
*/
|
|
4892
5323
|
async deleteOne(id, opts) {
|
|
4893
5324
|
this._ensureBuilt();
|
|
4894
|
-
const
|
|
4895
|
-
if (
|
|
5325
|
+
const candidates = this._idCandidates(id, opts);
|
|
5326
|
+
if (candidates.length === 0) return { deletedCount: 0 };
|
|
4896
5327
|
const guard = opts?.guard;
|
|
4897
5328
|
const needsCascade = this._integrity.needsCascade(this._cascadeResolver);
|
|
4898
|
-
const translated = this._fieldMapper.translateFilter(filter, this._meta);
|
|
4899
5329
|
const run = async () => {
|
|
5330
|
+
const pinned = await this._pinIdCandidates(candidates, opts?.scope);
|
|
5331
|
+
const filter = this._andScope(pinned, opts?.scope);
|
|
5332
|
+
const translated = this._fieldMapper.translateFilter(filter, this._meta);
|
|
4900
5333
|
if (guard) await guard(new RemoveGuardContext(id, filter, this));
|
|
4901
5334
|
if (needsCascade) await this._integrity.cascadeBeforeDelete(filter, this.tableName, this._meta, this._cascadeResolver, (f) => this._fieldMapper.translateFilter(f, this._meta), this.adapter);
|
|
4902
5335
|
return this.adapter.deleteOne(translated);
|
|
4903
5336
|
};
|
|
4904
|
-
return remapDeleteFkViolation(this.tableName, () => guard || needsCascade ? this.adapter.withTransaction(run) : run());
|
|
5337
|
+
return remapDeleteFkViolation(this.tableName, () => guard || needsCascade || candidates.length > 1 ? this.adapter.withTransaction(run) : run());
|
|
4905
5338
|
}
|
|
4906
5339
|
async updateMany(filter, data) {
|
|
4907
5340
|
this._ensureBuilt();
|
|
@@ -4994,23 +5427,188 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4994
5427
|
}
|
|
4995
5428
|
}
|
|
4996
5429
|
/**
|
|
4997
|
-
*
|
|
4998
|
-
* no identifying key (e.g. an auto-increment insert) or
|
|
4999
|
-
* resolved — never throws for a missing key.
|
|
5430
|
+
* The record filter a guard's `current(i)` reads its pre-image by: `null`
|
|
5431
|
+
* when the row has no identifying key (e.g. an auto-increment insert) or
|
|
5432
|
+
* the key cannot be resolved — never throws for a missing key. Identified
|
|
5433
|
+
* exactly like the write itself (primary key first, then a unique index —
|
|
5434
|
+
* since 0.1.143), so it is always the row the write targets.
|
|
5000
5435
|
* @internal
|
|
5001
5436
|
*/
|
|
5002
|
-
|
|
5003
|
-
if (!row) return null;
|
|
5004
|
-
let filter;
|
|
5437
|
+
_recordFilterOrNull(row, opts) {
|
|
5438
|
+
if (!row || typeof row !== "object") return null;
|
|
5005
5439
|
try {
|
|
5006
|
-
|
|
5440
|
+
return this._extractRecordFilter(row, opts);
|
|
5007
5441
|
} catch {
|
|
5008
5442
|
return null;
|
|
5009
5443
|
}
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5444
|
+
}
|
|
5445
|
+
/**
|
|
5446
|
+
* Each item's record filter (see {@link _extractRecordFilter}). A nested
|
|
5447
|
+
* FROM re-entry (`_ownedBy`) also pins the child's foreign key to its
|
|
5448
|
+
* parent, so the write never touches a child of another parent.
|
|
5449
|
+
*/
|
|
5450
|
+
_rowFilters(items, opts) {
|
|
5451
|
+
const owner = opts?._ownedBy?.field;
|
|
5452
|
+
return items.map((item) => {
|
|
5453
|
+
const filter = this._extractRecordFilter(item, opts);
|
|
5454
|
+
return owner === void 0 || item[owner] === void 0 ? filter : {
|
|
5455
|
+
...filter,
|
|
5456
|
+
[owner]: this._prepareFilterValue(owner, item[owner])
|
|
5457
|
+
};
|
|
5458
|
+
});
|
|
5459
|
+
}
|
|
5460
|
+
/** Whether `filter` names every primary-key field (so it IS the row's exact key). */
|
|
5461
|
+
_isPkFilter(filter) {
|
|
5462
|
+
const pkFields = this.primaryKeys;
|
|
5463
|
+
return pkFields.length > 0 && pkFields.every((f) => filter[f] !== void 0);
|
|
5464
|
+
}
|
|
5465
|
+
/**
|
|
5466
|
+
* The TO relations with their single-field local foreign key (lazily
|
|
5467
|
+
* listed) — what a write pins per item for its nested TO phase.
|
|
5468
|
+
*/
|
|
5469
|
+
_toRelationsCache;
|
|
5470
|
+
_toRelations() {
|
|
5471
|
+
if (!this._toRelationsCache) {
|
|
5472
|
+
const out = [];
|
|
5473
|
+
for (const [navField, relation] of this._meta.relations) {
|
|
5474
|
+
if (relation.direction !== "to") continue;
|
|
5475
|
+
const fk = this._findFKForRelation(relation);
|
|
5476
|
+
out.push({
|
|
5477
|
+
navField,
|
|
5478
|
+
fkField: fk?.localFields.length === 1 ? fk.localFields[0] : void 0
|
|
5479
|
+
});
|
|
5480
|
+
}
|
|
5481
|
+
this._toRelationsCache = out;
|
|
5482
|
+
}
|
|
5483
|
+
return this._toRelationsCache;
|
|
5484
|
+
}
|
|
5485
|
+
/** Whether `item` carries a nested TO object (Phase 1 work). */
|
|
5486
|
+
_carriesNestedTo(item) {
|
|
5487
|
+
return this._toRelations().some(({ navField }) => {
|
|
5488
|
+
const nested = item[navField];
|
|
5489
|
+
return !!nested && typeof nested === "object" && !Array.isArray(nested);
|
|
5490
|
+
});
|
|
5491
|
+
}
|
|
5492
|
+
/**
|
|
5493
|
+
* Reads — once per write call, inside its transaction — the stored row
|
|
5494
|
+
* each item `need`s, by the item's record filter: its primary key, version
|
|
5495
|
+
* and single-field TO foreign keys, in ONE read for the batch. A pre-image
|
|
5496
|
+
* the guard's `current(i)` already read by the same filter is reused.
|
|
5497
|
+
* `null` = no such row; `undefined` = not needed.
|
|
5498
|
+
*/
|
|
5499
|
+
async _pinTargets(rowFilters, need, guardCtx) {
|
|
5500
|
+
const targets = [];
|
|
5501
|
+
const pending = [];
|
|
5502
|
+
for (let i = 0; i < rowFilters.length; i++) {
|
|
5503
|
+
if (!need(i)) continue;
|
|
5504
|
+
const read = guardCtx?.readFor(i, rowFilters[i]);
|
|
5505
|
+
if (read) targets[i] = await read;
|
|
5506
|
+
else pending.push(i);
|
|
5507
|
+
}
|
|
5508
|
+
if (pending.length === 0) return targets;
|
|
5509
|
+
const select = new Set(this.primaryKeys);
|
|
5510
|
+
const versionField = this._meta.versionField;
|
|
5511
|
+
if (versionField !== void 0) select.add(versionField);
|
|
5512
|
+
for (const { fkField } of this._toRelations()) if (fkField !== void 0) select.add(fkField);
|
|
5513
|
+
for (const i of pending) for (const field in rowFilters[i]) select.add(field);
|
|
5514
|
+
const controls = { $select: [...select] };
|
|
5515
|
+
const filters = pending.map((i) => rowFilters[i]);
|
|
5516
|
+
const rows = await this.findMany({
|
|
5517
|
+
filter: filters.length === 1 ? filters[0] : { $or: filters },
|
|
5518
|
+
controls
|
|
5519
|
+
});
|
|
5520
|
+
const used = /* @__PURE__ */ new Set();
|
|
5521
|
+
for (const i of pending) {
|
|
5522
|
+
const row = rows.find((r) => rowMatchesKey(r, rowFilters[i]));
|
|
5523
|
+
targets[i] = row ?? null;
|
|
5524
|
+
if (row) used.add(row);
|
|
5525
|
+
}
|
|
5526
|
+
if (used.size < rows.length) {
|
|
5527
|
+
for (const i of pending) if (targets[i] === null) targets[i] = await this.findOne({
|
|
5528
|
+
filter: rowFilters[i],
|
|
5529
|
+
controls
|
|
5530
|
+
});
|
|
5531
|
+
}
|
|
5532
|
+
return targets;
|
|
5533
|
+
}
|
|
5534
|
+
/**
|
|
5535
|
+
* The exact primary-key filter of every row the write matched: the record
|
|
5536
|
+
* filter itself when it is the primary key, else the pinned row's key.
|
|
5537
|
+
*/
|
|
5538
|
+
_writtenPkFilters(rowFilters, targets, matched) {
|
|
5539
|
+
const out = [];
|
|
5540
|
+
for (let i = 0; i < rowFilters.length; i++) {
|
|
5541
|
+
if (!matched[i]) continue;
|
|
5542
|
+
const target = targets[i];
|
|
5543
|
+
const filter = this._isPkFilter(rowFilters[i]) ? rowFilters[i] : target ? this._pkFilterFrom(target) : null;
|
|
5544
|
+
if (filter) out.push(filter);
|
|
5545
|
+
}
|
|
5546
|
+
return out;
|
|
5547
|
+
}
|
|
5548
|
+
/** Invokes a {@link TWriteOptions.check} with de-duplicated PK filters (inside the transaction). */
|
|
5549
|
+
async _runWriteCheck(check, action, filters) {
|
|
5550
|
+
const pkFields = this.primaryKeys;
|
|
5551
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5552
|
+
const unique = [];
|
|
5553
|
+
for (const filter of filters) {
|
|
5554
|
+
const key = pkTupleKey(filter, pkFields);
|
|
5555
|
+
if (seen.has(key)) continue;
|
|
5556
|
+
seen.add(key);
|
|
5557
|
+
unique.push(filter);
|
|
5558
|
+
}
|
|
5559
|
+
await check({
|
|
5560
|
+
action,
|
|
5561
|
+
filters: unique,
|
|
5562
|
+
transactional: this.adapter.isInTransaction(),
|
|
5563
|
+
count: (filter) => this.count({
|
|
5564
|
+
filter,
|
|
5565
|
+
controls: {}
|
|
5566
|
+
})
|
|
5567
|
+
});
|
|
5568
|
+
}
|
|
5569
|
+
/**
|
|
5570
|
+
* The exact primary-key filter of each inserted row: the logical key from
|
|
5571
|
+
* the row (SDK defaults applied), else the stored key the adapter wrote into
|
|
5572
|
+
* the prepared row (e.g. a driver-assigned `_id`), else — single-field keys
|
|
5573
|
+
* only — the adapter's `insertedIds` (auto-increment).
|
|
5574
|
+
*/
|
|
5575
|
+
_insertedPkFilters(items, prepared, insertedIds) {
|
|
5576
|
+
const pkFields = this.primaryKeys;
|
|
5577
|
+
if (pkFields.length === 0 && items.length > 0) throw new DbError("INVALID_QUERY", [{
|
|
5578
|
+
path: "",
|
|
5579
|
+
message: "Write check requires a primary key on the table"
|
|
5580
|
+
}]);
|
|
5581
|
+
return items.map((item, i) => {
|
|
5582
|
+
const key = {};
|
|
5583
|
+
for (const field of pkFields) {
|
|
5584
|
+
let value = item[field];
|
|
5585
|
+
if (value === void 0) value = prepared[i]?.[this._meta.columnMap.get(field) ?? field];
|
|
5586
|
+
if (value === void 0 && pkFields.length === 1) value = insertedIds[i];
|
|
5587
|
+
if (value === void 0 || value === null) throw new DbError("INVALID_QUERY", [{
|
|
5588
|
+
path: field,
|
|
5589
|
+
message: `Write check: cannot resolve the primary key of inserted row [${i}]`
|
|
5590
|
+
}]);
|
|
5591
|
+
key[field] = value;
|
|
5592
|
+
}
|
|
5593
|
+
return this._pkFilterFrom(key);
|
|
5594
|
+
});
|
|
5595
|
+
}
|
|
5596
|
+
/**
|
|
5597
|
+
* Drops the nested TO objects of every replace item whose main replace
|
|
5598
|
+
* cannot match (pinned row missing, or stale `$cas`) so Phase 1 never
|
|
5599
|
+
* writes a related row for a replace that does nothing. Returns, per item,
|
|
5600
|
+
* whether its TO objects stay (a later 0-match for such an item is a
|
|
5601
|
+
* concurrent change and rolls back).
|
|
5602
|
+
*/
|
|
5603
|
+
_gateNestedTo(items, nestedTo, targets, expectedVersions) {
|
|
5604
|
+
const versionField = this._meta.versionField;
|
|
5605
|
+
return items.map((item, i) => {
|
|
5606
|
+
if (!nestedTo[i]) return false;
|
|
5607
|
+
const row = targets[i];
|
|
5608
|
+
const expected = expectedVersions[i];
|
|
5609
|
+
const kept = !!row && (expected === void 0 || versionField === void 0 || sameKey(row[versionField], expected));
|
|
5610
|
+
if (!kept) for (const { navField } of this._toRelations()) delete item[navField];
|
|
5611
|
+
return kept;
|
|
5014
5612
|
});
|
|
5015
5613
|
}
|
|
5016
5614
|
/**
|
|
@@ -5059,6 +5657,19 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
5059
5657
|
}
|
|
5060
5658
|
}
|
|
5061
5659
|
/**
|
|
5660
|
+
* The filter an `updateOne` / `replaceOne` of `payload` targets its row by
|
|
5661
|
+
* — the write's own resolution (primary key first, then a unique index;
|
|
5662
|
+
* see {@link _extractRecordFilter}), so a caller explaining a write's
|
|
5663
|
+
* outcome (e.g. a CAS mismatch) reads exactly the row the write addressed.
|
|
5664
|
+
* Throws `NOT_FOUND` when the payload carries no identifying fields.
|
|
5665
|
+
*
|
|
5666
|
+
* @since 0.1.143
|
|
5667
|
+
*/
|
|
5668
|
+
recordFilter(payload, opts) {
|
|
5669
|
+
this._ensureBuilt();
|
|
5670
|
+
return this._extractRecordFilter(payload, opts);
|
|
5671
|
+
}
|
|
5672
|
+
/**
|
|
5062
5673
|
* Extracts a record-identifying filter from a payload.
|
|
5063
5674
|
*
|
|
5064
5675
|
* Resolution order:
|
|
@@ -5072,18 +5683,8 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
5072
5683
|
*/
|
|
5073
5684
|
_extractRecordFilter(payload, opts) {
|
|
5074
5685
|
const pkFields = this.primaryKeys;
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
for (const field of pkFields) if (payload[field] === void 0) {
|
|
5078
|
-
allPresent = false;
|
|
5079
|
-
break;
|
|
5080
|
-
}
|
|
5081
|
-
if (allPresent) {
|
|
5082
|
-
const filter = {};
|
|
5083
|
-
for (const field of pkFields) filter[field] = this._prepareFilterValue(field, payload[field]);
|
|
5084
|
-
return filter;
|
|
5085
|
-
}
|
|
5086
|
-
}
|
|
5686
|
+
const pkFilter = this._pkFilterFrom(payload);
|
|
5687
|
+
if (pkFilter) return pkFilter;
|
|
5087
5688
|
const identifications = this.identificationsVisibleTo(opts?.isFieldVisible);
|
|
5088
5689
|
const singleFields = /* @__PURE__ */ new Set();
|
|
5089
5690
|
for (const ident of identifications) if (ident.source !== "primaryKey" && ident.fields.length === 1) singleFields.add(ident.fields[0]);
|
|
@@ -5221,6 +5822,81 @@ function readViewAgg(metadata) {
|
|
|
5221
5822
|
};
|
|
5222
5823
|
}
|
|
5223
5824
|
}
|
|
5825
|
+
/** The type getter of a `@db.view.for` / join / chain ref (a bare getter or `{ type }`). */
|
|
5826
|
+
function refType(ref) {
|
|
5827
|
+
return typeof ref === "function" ? ref : ref.type;
|
|
5828
|
+
}
|
|
5829
|
+
/**
|
|
5830
|
+
* Where a view field reads from: its chain ref, else — on the entry table —
|
|
5831
|
+
* the aggregate's field, else the same-named field. `sourceType` is
|
|
5832
|
+
* `undefined` for an external view's untyped field (no entry table).
|
|
5833
|
+
*/
|
|
5834
|
+
function viewFieldSource(fieldName, fieldType, entryType) {
|
|
5835
|
+
const agg = readViewAgg(fieldType.metadata);
|
|
5836
|
+
const chainRef = fieldType.ref?.field ? fieldType.ref : void 0;
|
|
5837
|
+
if (chainRef) return {
|
|
5838
|
+
agg,
|
|
5839
|
+
chained: true,
|
|
5840
|
+
sourceType: chainRef.type(),
|
|
5841
|
+
sourcePath: chainRef.field
|
|
5842
|
+
};
|
|
5843
|
+
const aggField = agg?.aggField;
|
|
5844
|
+
return {
|
|
5845
|
+
agg,
|
|
5846
|
+
chained: false,
|
|
5847
|
+
sourceType: entryType?.(),
|
|
5848
|
+
sourcePath: aggField && aggField !== "*" ? aggField : fieldName
|
|
5849
|
+
};
|
|
5850
|
+
}
|
|
5851
|
+
/** View types whose fields already carry their inherited seals. */
|
|
5852
|
+
const sealedViews = /* @__PURE__ */ new WeakSet();
|
|
5853
|
+
/**
|
|
5854
|
+
* Whether `type` declares a view (managed `@db.view.for` or external
|
|
5855
|
+
* `@db.view`) — how `DbSpace.get` tells views from tables.
|
|
5856
|
+
* @since 0.1.143
|
|
5857
|
+
*/
|
|
5858
|
+
function isViewType(type) {
|
|
5859
|
+
return type.metadata.has("db.view") || type.metadata.has("db.view.for");
|
|
5860
|
+
}
|
|
5861
|
+
/**
|
|
5862
|
+
* Carries the read seals of each view column's source field onto the view
|
|
5863
|
+
* type's own field metadata (since 0.1.143), so a view never exposes a value
|
|
5864
|
+
* differently from the table it reads:
|
|
5865
|
+
*
|
|
5866
|
+
* - `@db.writeOnly` — a column reading a write-only field (or a leaf of a
|
|
5867
|
+
* write-only object), or aggregating one, is write-only on the view too, so
|
|
5868
|
+
* HTTP layers seal it exactly as on the table.
|
|
5869
|
+
* - `@db.encrypted` — a column reading an encrypted field reads its
|
|
5870
|
+
* ciphertext, so it is encrypted on the view too: rows come back decrypted
|
|
5871
|
+
* and filters / sorts on it are rejected, as on the table. An aggregate over
|
|
5872
|
+
* an encrypted field is rejected (the table refuses it as well).
|
|
5873
|
+
*
|
|
5874
|
+
* The source is resolved exactly like {@link AtscriptDbView.getViewColumnMappings}
|
|
5875
|
+
* does ({@link viewFieldSource}); a source that is itself a view is sealed
|
|
5876
|
+
* first. Runs once per view type, when a view over it is first built.
|
|
5877
|
+
*/
|
|
5878
|
+
function inheritViewFieldSeals(viewType) {
|
|
5879
|
+
if (sealedViews.has(viewType) || viewType.type.kind !== "object") return;
|
|
5880
|
+
sealedViews.add(viewType);
|
|
5881
|
+
try {
|
|
5882
|
+
const forRef = viewType.metadata.get("db.view.for");
|
|
5883
|
+
const entryType = forRef && refType(forRef);
|
|
5884
|
+
for (const [fieldName, fieldType] of viewType.type.props.entries()) {
|
|
5885
|
+
const { agg, sourceType, sourcePath } = viewFieldSource(fieldName, fieldType, entryType);
|
|
5886
|
+
if (agg?.aggField === "*" || !sourceType) continue;
|
|
5887
|
+
const source = viewSourceOf(sourceType).type;
|
|
5888
|
+
if (isViewType(source)) inheritViewFieldSeals(source);
|
|
5889
|
+
const seals = sourceFieldSeals(source, sourcePath);
|
|
5890
|
+
if (seals.writeOnly) fieldType.metadata.set("db.writeOnly", true);
|
|
5891
|
+
if (!seals.encrypted) continue;
|
|
5892
|
+
if (agg) throw new Error(`View "${tableNameOf(viewType)}" field "${fieldName}": @db.agg.${agg.aggFn} over the @db.encrypted field "${sourcePath}" — ciphertext cannot be aggregated`);
|
|
5893
|
+
fieldType.metadata.set("db.encrypted", true);
|
|
5894
|
+
}
|
|
5895
|
+
} catch (error) {
|
|
5896
|
+
sealedViews.delete(viewType);
|
|
5897
|
+
throw error;
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5224
5900
|
/**
|
|
5225
5901
|
* Database view abstraction driven by Atscript `@db.view.*` annotations.
|
|
5226
5902
|
*
|
|
@@ -5241,6 +5917,15 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
5241
5917
|
return true;
|
|
5242
5918
|
}
|
|
5243
5919
|
/**
|
|
5920
|
+
* Builds the view's metadata — first stamping its fields with their
|
|
5921
|
+
* sources' read seals (`inheritViewFieldSeals`, once per view type), so
|
|
5922
|
+
* every metadata consumer sees the sealed type.
|
|
5923
|
+
*/
|
|
5924
|
+
_ensureBuilt() {
|
|
5925
|
+
if (!this._meta.isBuilt) inheritViewFieldSeals(this._type);
|
|
5926
|
+
super._ensureBuilt();
|
|
5927
|
+
}
|
|
5928
|
+
/**
|
|
5244
5929
|
* Whether this is an external view — declared with `@db.view` only,
|
|
5245
5930
|
* without `@db.view.for`. External views reference pre-existing DB views
|
|
5246
5931
|
* and are not managed (created/dropped) by schema sync.
|
|
@@ -5260,16 +5945,14 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
5260
5945
|
if (this._viewPlan) return this._viewPlan;
|
|
5261
5946
|
if (this.isExternal) throw new Error(`Cannot compute view plan for external view "${this.tableName}". External views (declared without @db.view.for) reference pre-existing DB views.`);
|
|
5262
5947
|
const metadata = this._type.metadata;
|
|
5263
|
-
const
|
|
5264
|
-
const entryType = typeof forRef === "function" ? forRef : forRef.type;
|
|
5948
|
+
const entryType = refType(metadata.get("db.view.for"));
|
|
5265
5949
|
const entry = viewSourceOf(entryType());
|
|
5266
5950
|
if (entry.alias) throw new Error(`View "${this.tableName}": @db.view.for "${entry.name}" is a @db.alias — a join alias cannot be the entry table`);
|
|
5267
5951
|
const entryTable = entry.table;
|
|
5268
5952
|
const rawJoins = metadata.get("db.view.joins");
|
|
5269
5953
|
const joins = [];
|
|
5270
5954
|
if (rawJoins) for (const join of rawJoins) {
|
|
5271
|
-
const
|
|
5272
|
-
const targetType = typeof targetRef === "function" ? targetRef : targetRef.type;
|
|
5955
|
+
const targetType = refType(join.target);
|
|
5273
5956
|
const target = viewSourceOf(targetType());
|
|
5274
5957
|
joins.push({
|
|
5275
5958
|
targetType,
|
|
@@ -5368,23 +6051,13 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
5368
6051
|
const leftJoined = new Set(plan.joins.filter((j) => j.kind === "left").map((j) => j.scope));
|
|
5369
6052
|
for (const [fieldName, fieldType] of this._type.type.props.entries()) {
|
|
5370
6053
|
if (ignored.has(fieldName)) continue;
|
|
5371
|
-
const agg =
|
|
6054
|
+
const { agg, chained, sourceType, sourcePath } = viewFieldSource(fieldName, fieldType, plan.entryType);
|
|
5372
6055
|
const aggField = agg?.aggField;
|
|
5373
6056
|
if (aggField === "*" && agg?.aggFn !== "count") fail(fieldName, `aggregate "${agg?.aggFn}" needs a field — only count accepts *`);
|
|
5374
|
-
let sourceType;
|
|
5375
|
-
let sourcePath;
|
|
5376
|
-
const chainRef = fieldType.ref?.field ? fieldType.ref : void 0;
|
|
5377
|
-
if (chainRef) {
|
|
5378
|
-
sourceType = chainRef.type();
|
|
5379
|
-
sourcePath = chainRef.field;
|
|
5380
|
-
} else {
|
|
5381
|
-
sourceType = plan.entryType();
|
|
5382
|
-
sourcePath = aggField && aggField !== "*" ? aggField : fieldName;
|
|
5383
|
-
}
|
|
5384
6057
|
const src = viewSourceOf(sourceType);
|
|
5385
6058
|
const sourceTable = src.name;
|
|
5386
6059
|
const joinNullable = leftJoined.has(sourceTable);
|
|
5387
|
-
if (aggField === "*" && !
|
|
6060
|
+
if (aggField === "*" && !chained) {
|
|
5388
6061
|
mappings.push({
|
|
5389
6062
|
viewColumn: viewName(fieldName) ?? fieldName,
|
|
5390
6063
|
viewPath: fieldName,
|
|
@@ -5845,4 +6518,4 @@ function computeColumnDiff(desired, existing, typeMapper, opts) {
|
|
|
5845
6518
|
return diff;
|
|
5846
6519
|
}
|
|
5847
6520
|
//#endregion
|
|
5848
|
-
export {
|
|
6521
|
+
export { isGeoIndexableType as $, vectorIndexNotFoundMessage as A, guardAggregate as B, createFailureCollector as C, resolveDesignType as D, AtscriptDbReadable as E, bucketSourceVerdict as F, narrowerFilterOps as G, guardPath as H, canFilterLeaf as I, RelationalFieldMapper as J, sortFieldNames as K, checkHavingKeys as L, ENCRYPTED_REASON as M, acceptedOperatorsHint as N, geoIndexNotFoundMessage as O, assertGeoPoint as P, TableMetadata as Q, classifyQueryPath as R, BaseDbAdapter as S, NativeIntegrity as T, guardPaths as U, guardFilter as V, guardQuery as W, FieldMappingStrategy as X, DocumentFieldMapper as Y, UniquSelect as Z, AtscriptDbTable as _, hasForeignKeyChanges as a, normalizeComputedSelect as at, ApplicationIntegrity as b, computeTableHash as c, snapshotToExistingColumns as d, isGeoPointType as et, snapshotToExistingTableOptions as f, isViewType as g, isAtscriptDbView as h, fkKey as i, jsonValueAncestor as it, ADAPTER_FILTER_REASON as j, searchIndexNotFoundMessage as k, computeTableSnapshot as l, AtscriptDbView as m, isColumnTypeChanged as n, isBucketableField as nt, canonicalizeQueryNode as o, resolveCalendarBuckets as ot, viewSnapshotSources as p, unsupportedOperatorMessage as q, computeForeignKeyDiff as r, isJsonValueField as rt, computeSchemaHash as s, NoopLogger as st, computeColumnDiff as t, aliasTargetOf as tt, computeViewSnapshot as u, assertNoVersionWrites as v, IntegrityStrategy as w, ALL_BUCKET_UNITS as x, decomposePatch as y, collectQueryPaths as z };
|