@atscript/db-mongo 0.1.161 → 0.1.162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +97 -32
- package/dist/index.d.cts +23 -6
- package/dist/index.d.mts +23 -6
- package/dist/index.mjs +98 -34
- package/dist/plugin.cjs +20 -7
- package/dist/plugin.mjs +21 -8
- package/dist/search-messages-BqLxxrb8.cjs +16 -0
- package/dist/search-messages-mQSpJ4GD.mjs +11 -0
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_mongo_accumulator = require("./mongo-accumulator-BW-RNFg0.cjs");
|
|
3
|
+
const require_search_messages = require("./search-messages-BqLxxrb8.cjs");
|
|
3
4
|
let _atscript_db = require("@atscript/db");
|
|
4
5
|
let mongodb = require("mongodb");
|
|
5
6
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
@@ -79,6 +80,19 @@ function containsAggregationExpr(v) {
|
|
|
79
80
|
return false;
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
83
|
+
* A field's current value in an aggregation pipeline update — a missing field
|
|
84
|
+
* counts as `0`, like the `$inc` / `$mul` update operators (the pipeline
|
|
85
|
+
* `$add` / `$multiply` would yield `null`) — so a document written before an
|
|
86
|
+
* `@db.onUpdate.inc` counter existed starts counting.
|
|
87
|
+
*/
|
|
88
|
+
function fieldOpCurrent(field) {
|
|
89
|
+
return { $ifNull: [`$${field}`, 0] };
|
|
90
|
+
}
|
|
91
|
+
/** Aggregation expression adding `by` to `field` (an `$inc` in a pipeline update). */
|
|
92
|
+
function incExpr(field, by) {
|
|
93
|
+
return { $add: [fieldOpCurrent(field), by] };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
82
96
|
* CollectionPatcher is a small helper that converts a *patch payload* produced
|
|
83
97
|
* by Atscript into a shape that the official MongoDB driver understands – a
|
|
84
98
|
* triple of `(filter, update, options)` to be fed to `collection.updateOne()`.
|
|
@@ -143,9 +157,9 @@ var CollectionPatcher = class {
|
|
|
143
157
|
updateOptions: this.optionsObj
|
|
144
158
|
};
|
|
145
159
|
}
|
|
146
|
-
/**
|
|
160
|
+
/** Aggregation expression of an `$inc` / `$mul` field op (see {@link fieldOpCurrent}). */
|
|
147
161
|
_fieldOpExpr(key, op, value) {
|
|
148
|
-
return op === "inc" ?
|
|
162
|
+
return op === "inc" ? incExpr(key, value) : { $multiply: [fieldOpCurrent(key), value] };
|
|
149
163
|
}
|
|
150
164
|
/**
|
|
151
165
|
* Helper – lazily create `$set` section and assign *key* → *value*.
|
|
@@ -1840,8 +1854,12 @@ const PK_INDEX = mongoIndexKey("unique", "__pk");
|
|
|
1840
1854
|
* index of a skipped key rebuild, the indexes over a column whose type change
|
|
1841
1855
|
* is pending (its present-only filter is typed).
|
|
1842
1856
|
*/
|
|
1843
|
-
async function syncIndexesImpl(host, opts) {
|
|
1844
|
-
|
|
1857
|
+
async function syncIndexesImpl(host, opts, dryRun = false) {
|
|
1858
|
+
/** Indexes dropped because their definition drifted — their re-creation is a rebuild. */
|
|
1859
|
+
const replaced = /* @__PURE__ */ new Set();
|
|
1860
|
+
if (dryRun) {
|
|
1861
|
+
if (!await host.db.listCollections({ name: host.collection.collectionName }, { nameOnly: true }).hasNext()) return [];
|
|
1862
|
+
} else await host.ensureCollectionExists();
|
|
1845
1863
|
const allIndexes = /* @__PURE__ */ new Map();
|
|
1846
1864
|
for (const [key, index] of host._table.indexes.entries()) {
|
|
1847
1865
|
const fields = {};
|
|
@@ -1888,6 +1906,7 @@ async function syncIndexesImpl(host, opts) {
|
|
|
1888
1906
|
for (const remote of existingIndexes) if (isKept(remote.name, remote.key, remote.weights)) kept.add(remote.name);
|
|
1889
1907
|
for (const name of kept) indexesToCreate.delete(name);
|
|
1890
1908
|
const { attempt, throwIfAny } = (0, _atscript_db.createFailureCollector)("index sync");
|
|
1909
|
+
const { changes, apply } = (0, _atscript_db.createChangeRecorder)(dryRun, attempt);
|
|
1891
1910
|
let defaultCollation;
|
|
1892
1911
|
const collectionDefaultCollation = async () => {
|
|
1893
1912
|
if (defaultCollation === void 0) defaultCollation = (await host.db.listCollections({ name: host.collection.collectionName }).next())?.options?.collation ?? null;
|
|
@@ -1906,16 +1925,21 @@ async function syncIndexesImpl(host, opts) {
|
|
|
1906
1925
|
if (fieldsMatch && weightsMatch && optionsMatch) indexesToCreate.delete(remote.name);
|
|
1907
1926
|
else if (violation !== void 0) {
|
|
1908
1927
|
indexesToCreate.delete(remote.name);
|
|
1928
|
+
if (dryRun) continue;
|
|
1909
1929
|
await attempt(`replace unique index "${remote.name}"`, () => Promise.reject(/* @__PURE__ */ new Error(`existing documents violate the new index (duplicate key ${JSON.stringify(violation)}); the current index is kept`)));
|
|
1910
|
-
} else
|
|
1930
|
+
} else if (dryRun) replaced.add(remote.name);
|
|
1931
|
+
else {
|
|
1911
1932
|
host._log("dropIndex", remote.name);
|
|
1912
|
-
await attempt(`drop index "${remote.name}"`, () => host.collection.dropIndex(remote.name));
|
|
1933
|
+
if (await attempt(`drop index "${remote.name}"`, () => host.collection.dropIndex(remote.name))) replaced.add(remote.name);
|
|
1913
1934
|
}
|
|
1914
1935
|
}
|
|
1915
|
-
} else {
|
|
1936
|
+
} else await apply({
|
|
1937
|
+
name: remote.name,
|
|
1938
|
+
action: "drop"
|
|
1939
|
+
}, `drop index "${remote.name}"`, () => {
|
|
1916
1940
|
host._log("dropIndex", remote.name);
|
|
1917
|
-
|
|
1918
|
-
}
|
|
1941
|
+
return host.collection.dropIndex(remote.name);
|
|
1942
|
+
});
|
|
1919
1943
|
}
|
|
1920
1944
|
for (const [key, value] of allIndexes.entries()) {
|
|
1921
1945
|
if (!isPlainIndex(value) || !indexesToCreate.has(key)) continue;
|
|
@@ -1955,8 +1979,12 @@ async function syncIndexesImpl(host, opts) {
|
|
|
1955
1979
|
indexOptions = { name: key };
|
|
1956
1980
|
break;
|
|
1957
1981
|
}
|
|
1958
|
-
await
|
|
1982
|
+
await apply({
|
|
1983
|
+
name: key,
|
|
1984
|
+
action: replaced.has(key) ? "rebuild" : "add"
|
|
1985
|
+
}, label, () => host.collection.createIndex(value.fields, indexOptions));
|
|
1959
1986
|
}
|
|
1987
|
+
const search = (0, _atscript_db.createChangeRecorder)(dryRun);
|
|
1960
1988
|
try {
|
|
1961
1989
|
const toUpdate = /* @__PURE__ */ new Set();
|
|
1962
1990
|
const existingSearchIndexes = await host.collection.listSearchIndexes().toArray();
|
|
@@ -1979,31 +2007,41 @@ async function syncIndexesImpl(host, opts) {
|
|
|
1979
2007
|
break;
|
|
1980
2008
|
default:
|
|
1981
2009
|
}
|
|
1982
|
-
} else if (remote.status !== "DELETING") {
|
|
2010
|
+
} else if (remote.status !== "DELETING") await search.apply({
|
|
2011
|
+
name: remote.name,
|
|
2012
|
+
action: "drop"
|
|
2013
|
+
}, remote.name, () => {
|
|
1983
2014
|
host._log("dropSearchIndex", remote.name);
|
|
1984
|
-
|
|
1985
|
-
}
|
|
2015
|
+
return host.collection.dropSearchIndex(remote.name);
|
|
2016
|
+
});
|
|
1986
2017
|
}
|
|
1987
2018
|
for (const [key, value] of indexesToCreate.entries()) switch (value.type) {
|
|
1988
2019
|
case "dynamic_text":
|
|
1989
2020
|
case "search_text":
|
|
1990
|
-
case "vector":
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2021
|
+
case "vector": {
|
|
2022
|
+
const update = toUpdate.has(key);
|
|
2023
|
+
await search.apply({
|
|
2024
|
+
name: key,
|
|
2025
|
+
action: update ? "rebuild" : "add"
|
|
2026
|
+
}, key, () => {
|
|
2027
|
+
if (update) {
|
|
2028
|
+
host._log("updateSearchIndex", key, value.definition);
|
|
2029
|
+
return host.collection.updateSearchIndex(key, value.definition);
|
|
2030
|
+
}
|
|
1995
2031
|
host._log("createSearchIndex", key, value.type);
|
|
1996
|
-
|
|
2032
|
+
return host.collection.createSearchIndex({
|
|
1997
2033
|
name: key,
|
|
1998
2034
|
type: value.type === "vector" ? "vectorSearch" : "search",
|
|
1999
2035
|
definition: value.definition
|
|
2000
2036
|
});
|
|
2001
|
-
}
|
|
2037
|
+
});
|
|
2002
2038
|
break;
|
|
2039
|
+
}
|
|
2003
2040
|
default:
|
|
2004
2041
|
}
|
|
2005
2042
|
} catch {}
|
|
2006
2043
|
throwIfAny();
|
|
2044
|
+
return [...changes, ...search.changes];
|
|
2007
2045
|
}
|
|
2008
2046
|
/**
|
|
2009
2047
|
* The collation a plain / unique index is built with (since 0.1.151): the
|
|
@@ -2273,22 +2311,33 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2273
2311
|
* Uses MongoDB's Convenient Transaction API (`session.withTransaction()`).
|
|
2274
2312
|
* This handles txnNumber management and automatic retry for
|
|
2275
2313
|
* `TransientTransactionError` / `UnknownTransactionCommitResult`.
|
|
2314
|
+
*
|
|
2315
|
+
* `opts.isolation` (since 0.1.162): a MongoDB transaction reads one
|
|
2316
|
+
* snapshot, which satisfies `'read committed'` and `'repeatable read'`;
|
|
2317
|
+
* `'serializable'` throws `ISOLATION_NOT_SUPPORTED` (snapshot isolation
|
|
2318
|
+
* allows write skew), and so does any level on a topology without
|
|
2319
|
+
* transactions (standalone), where `fn` would run without one.
|
|
2276
2320
|
*/
|
|
2277
|
-
async withTransaction(fn) {
|
|
2321
|
+
async withTransaction(fn, opts) {
|
|
2322
|
+
const isolation = this._checkIsolation(opts);
|
|
2278
2323
|
if (this._getTransactionState()) return fn();
|
|
2279
|
-
|
|
2324
|
+
const passThrough = () => {
|
|
2325
|
+
if (isolation) throw this._isolationNotSupported(`Isolation '${isolation}' needs a MongoDB transaction — this topology (standalone) has none`);
|
|
2326
|
+
return fn();
|
|
2327
|
+
};
|
|
2328
|
+
if (this._txDisabled || !this._client) return passThrough();
|
|
2280
2329
|
try {
|
|
2281
2330
|
const topology = this._client.topology;
|
|
2282
2331
|
if (topology) {
|
|
2283
2332
|
const type = (topology.description ?? topology.s?.description)?.type;
|
|
2284
2333
|
if (type === "Single" || type === "Unknown") {
|
|
2285
2334
|
this._txDisabled = true;
|
|
2286
|
-
return
|
|
2335
|
+
return passThrough();
|
|
2287
2336
|
}
|
|
2288
2337
|
}
|
|
2289
2338
|
} catch {
|
|
2290
2339
|
this._txDisabled = true;
|
|
2291
|
-
return
|
|
2340
|
+
return passThrough();
|
|
2292
2341
|
}
|
|
2293
2342
|
const session = this._client.startSession();
|
|
2294
2343
|
try {
|
|
@@ -2303,6 +2352,17 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2303
2352
|
} catch {}
|
|
2304
2353
|
}
|
|
2305
2354
|
}
|
|
2355
|
+
static _ISOLATION_LEVELS = new Set(["read committed", "repeatable read"]);
|
|
2356
|
+
/**
|
|
2357
|
+
* `'read committed'` and `'repeatable read'` — a transaction's snapshot
|
|
2358
|
+
* read satisfies both. No `'serializable'` (snapshot isolation allows
|
|
2359
|
+
* write skew). Locking reads are not supported (`supportsReadLocks()` is
|
|
2360
|
+
* false → `LOCK_NOT_SUPPORTED`): use `$cas` / `withOptimisticRetry`.
|
|
2361
|
+
* @since 0.1.162
|
|
2362
|
+
*/
|
|
2363
|
+
isolationLevels() {
|
|
2364
|
+
return MongoAdapter._ISOLATION_LEVELS;
|
|
2365
|
+
}
|
|
2306
2366
|
static _noSession = Object.freeze({});
|
|
2307
2367
|
/** Returns `{ session }` opts if inside a transaction, empty object otherwise. */
|
|
2308
2368
|
_getSessionOpts() {
|
|
@@ -2670,6 +2730,8 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2670
2730
|
for (const field of this._pendingUniqueFields) this._addMongoIndexField("unique", "__pk", meta.documentPath(field));
|
|
2671
2731
|
for (const pending of this._pendingSearchFields) this._addFieldToSearchIndex("search_text", pending.indexName, pending.field, pending.mappings);
|
|
2672
2732
|
this._pendingSearchFields = [];
|
|
2733
|
+
const [encrypted] = meta.encryptedFields;
|
|
2734
|
+
if (encrypted !== void 0 && [...this._mongoIndexes.values()].some((i) => i.type === "dynamic_text")) throw new Error(require_search_messages.dynamicSearchEncryptedMessage(encrypted));
|
|
2673
2735
|
const textBearing = [...this._table.indexes.values()].filter((i) => i.type === "fulltext" && i.fields.some((f) => !f.integer));
|
|
2674
2736
|
if (textBearing.length > 1) throw new Error(`MongoDB allows one text index per collection: "${this._table.tableName}" declares ${textBearing.length} @db.index.fulltext names with text fields (${textBearing.map((i) => `"${i.name}"`).join(", ")}). Merge them into one name; integer-only fulltext indexes may be declared separately.`);
|
|
2675
2737
|
for (const fulltext of this._table.indexes.values()) {
|
|
@@ -3150,12 +3212,14 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
3150
3212
|
modifiedCount: 0
|
|
3151
3213
|
};
|
|
3152
3214
|
const versionColumn = this._table.versionColumnPhysical;
|
|
3215
|
+
const counters = this._table.onUpdateIncPhysical;
|
|
3153
3216
|
this._log("replaceOne", mongoFilter, data);
|
|
3154
|
-
if (versionColumn !== void 0) {
|
|
3155
|
-
const
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
} }
|
|
3217
|
+
if (versionColumn !== void 0 || counters.length > 0) {
|
|
3218
|
+
const replacement = {};
|
|
3219
|
+
for (const [key, value] of Object.entries(data)) replacement[key] = { $literal: value };
|
|
3220
|
+
for (const col of counters) replacement[col] = incExpr(col, 1);
|
|
3221
|
+
if (versionColumn !== void 0) replacement[versionColumn] = { $add: [`$${versionColumn}`, 1] };
|
|
3222
|
+
const pipeline = [{ $replaceWith: replacement }];
|
|
3159
3223
|
return this._wrapUpdate(() => this.collection.updateOne(mongoFilter, pipeline, this._getSessionOpts()));
|
|
3160
3224
|
}
|
|
3161
3225
|
return this._wrapUpdate(() => this.collection.replaceOne(mongoFilter, data, this._getSessionOpts()));
|
|
@@ -3174,7 +3238,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
3174
3238
|
});
|
|
3175
3239
|
}
|
|
3176
3240
|
async replaceMany(filter, data) {
|
|
3177
|
-
const updateDoc = buildMongoUpdateDoc(data,
|
|
3241
|
+
const updateDoc = buildMongoUpdateDoc(data, this._onUpdateIncOps(), this._table.versionColumnPhysical);
|
|
3178
3242
|
return this._updateMatching(filter, (mongoFilter) => {
|
|
3179
3243
|
this._log("replaceMany", mongoFilter, updateDoc);
|
|
3180
3244
|
return this._wrapUpdate(() => this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
@@ -3215,8 +3279,8 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
3215
3279
|
* `__pk` index first (`dropIndexesForColumns`).
|
|
3216
3280
|
*/
|
|
3217
3281
|
async rebuildPrimaryKey(_change) {}
|
|
3218
|
-
async
|
|
3219
|
-
return syncIndexesImpl(this, opts);
|
|
3282
|
+
async _syncIndexes(dryRun, opts) {
|
|
3283
|
+
return syncIndexesImpl(this, opts, dryRun);
|
|
3220
3284
|
}
|
|
3221
3285
|
async syncColumns(diff) {
|
|
3222
3286
|
return syncColumnsImpl(this, diff);
|
|
@@ -3605,6 +3669,7 @@ exports.buildMongoFilter = require_mongo_accumulator.buildMongoFilter;
|
|
|
3605
3669
|
exports.buildMongoQuery = require_mongo_accumulator.buildMongoQuery;
|
|
3606
3670
|
exports.collationOfAdapter = require_mongo_accumulator.collationOfAdapter;
|
|
3607
3671
|
exports.createAdapter = createAdapter;
|
|
3672
|
+
exports.incExpr = incExpr;
|
|
3608
3673
|
exports.mongoFilterStages = require_mongo_accumulator.mongoFilterStages;
|
|
3609
3674
|
exports.mongoIndexKey = mongoIndexKey;
|
|
3610
3675
|
exports.planStages = require_mongo_accumulator.planStages;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as buildMongoFilter, c as mongoFilterStages, i as TMongoFilterPlan, l as planStages, n as TMongoFieldCollation, o as buildMongoQuery, r as TMongoFilterOptions, s as collationOfAdapter, t as REL_FILTER_TEMP_PREFIX } from "./mongo-filter-CGd9ryOD.cjs";
|
|
2
|
-
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbCollation, TDbDeleteResult, TDbFieldMeta, TDbForeignKey, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbRelation, TDbUpdateOptions, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TPrimaryKeyChange, TSearchIndexInfo, TSyncColumnResult, TSyncIndexesOptions, TTableResolver, TViewCapability, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
|
|
2
|
+
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbCollation, TDbDeleteResult, TDbFieldMeta, TDbForeignKey, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbIsolationLevel, TDbObjectKind, TDbRelation, TDbUpdateOptions, TDbUpdateResult, TExistingTableOption, TFieldOps, TIndexChange, TMetadataOverrides, TPrimaryKeyChange, TSearchIndexInfo, TSyncColumnResult, TSyncIndexesOptions, TTableResolver, TTransactionOptions, TViewCapability, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
|
|
3
3
|
import { AggregateOptions, AggregationCursor, ClientSession, Collection, Db, Document, Filter, MongoClient, ObjectId, UpdateFilter, UpdateOptions } from "mongodb";
|
|
4
4
|
import { TAtscriptAnnotatedType, TMetadataMap, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
|
|
5
5
|
import { AggregateFn, BucketUnit } from "@uniqu/core";
|
|
@@ -14,6 +14,8 @@ interface TCollectionPatcherContext {
|
|
|
14
14
|
prepareId(id: any): any;
|
|
15
15
|
createValidator(opts?: Partial<TValidatorOptions>): Validator<any>;
|
|
16
16
|
}
|
|
17
|
+
/** Aggregation expression adding `by` to `field` (an `$inc` in a pipeline update). */
|
|
18
|
+
declare function incExpr(field: string, by: number): Document;
|
|
17
19
|
/**
|
|
18
20
|
* CollectionPatcher is a small helper that converts a *patch payload* produced
|
|
19
21
|
* by Atscript into a shape that the official MongoDB driver understands – a
|
|
@@ -64,7 +66,7 @@ declare class CollectionPatcher {
|
|
|
64
66
|
updateFilter: Document[];
|
|
65
67
|
updateOptions: UpdateOptions;
|
|
66
68
|
};
|
|
67
|
-
/**
|
|
69
|
+
/** Aggregation expression of an `$inc` / `$mul` field op (see {@link fieldOpCurrent}). */
|
|
68
70
|
private _fieldOpExpr;
|
|
69
71
|
/**
|
|
70
72
|
* Helper – lazily create `$set` section and assign *key* → *value*.
|
|
@@ -327,8 +329,23 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
327
329
|
* Uses MongoDB's Convenient Transaction API (`session.withTransaction()`).
|
|
328
330
|
* This handles txnNumber management and automatic retry for
|
|
329
331
|
* `TransientTransactionError` / `UnknownTransactionCommitResult`.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
+
*
|
|
333
|
+
* `opts.isolation` (since 0.1.162): a MongoDB transaction reads one
|
|
334
|
+
* snapshot, which satisfies `'read committed'` and `'repeatable read'`;
|
|
335
|
+
* `'serializable'` throws `ISOLATION_NOT_SUPPORTED` (snapshot isolation
|
|
336
|
+
* allows write skew), and so does any level on a topology without
|
|
337
|
+
* transactions (standalone), where `fn` would run without one.
|
|
338
|
+
*/
|
|
339
|
+
withTransaction<T>(fn: () => Promise<T>, opts?: TTransactionOptions): Promise<T>;
|
|
340
|
+
private static readonly _ISOLATION_LEVELS;
|
|
341
|
+
/**
|
|
342
|
+
* `'read committed'` and `'repeatable read'` — a transaction's snapshot
|
|
343
|
+
* read satisfies both. No `'serializable'` (snapshot isolation allows
|
|
344
|
+
* write skew). Locking reads are not supported (`supportsReadLocks()` is
|
|
345
|
+
* false → `LOCK_NOT_SUPPORTED`): use `$cas` / `withOptimisticRetry`.
|
|
346
|
+
* @since 0.1.162
|
|
347
|
+
*/
|
|
348
|
+
isolationLevels(): ReadonlySet<TDbIsolationLevel>;
|
|
332
349
|
private static readonly _noSession;
|
|
333
350
|
/** Returns `{ session }` opts if inside a transaction, empty object otherwise. */
|
|
334
351
|
protected _getSessionOpts(): {
|
|
@@ -596,7 +613,7 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
596
613
|
* `__pk` index first (`dropIndexesForColumns`).
|
|
597
614
|
*/
|
|
598
615
|
rebuildPrimaryKey(_change: TPrimaryKeyChange): Promise<void>;
|
|
599
|
-
|
|
616
|
+
protected _syncIndexes(dryRun: boolean, opts?: TSyncIndexesOptions): Promise<TIndexChange[]>;
|
|
600
617
|
syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
|
|
601
618
|
dropColumns(columns: string[]): Promise<void>;
|
|
602
619
|
dropIndexesForColumns(columns: string[]): Promise<void>;
|
|
@@ -697,4 +714,4 @@ declare const validateMongoIdPlugin: TValidatorPlugin;
|
|
|
697
714
|
/** A {@link DbSpace} over the database of `connection`; `options` go to every {@link MongoAdapter}. */
|
|
698
715
|
declare function createAdapter(connection: string, options?: TMongoAdapterOptions): DbSpace;
|
|
699
716
|
//#endregion
|
|
700
|
-
export { CollectionPatcher, INDEX_PREFIX, MongoAdapter, REL_FILTER_TEMP_PREFIX, TCollectionPatcherContext, TMongoAdapterOptions, TMongoFieldCollation, TMongoFilterOptions, TMongoFilterPlan, type TMongoIndex, type TMongoSearchIndexDefinition, type TPlainIndex, type TSearchIndex, buildMongoFilter, buildMongoQuery, collationOfAdapter, createAdapter, mongoFilterStages, mongoIndexKey, planStages, validateMongoIdPlugin };
|
|
717
|
+
export { CollectionPatcher, INDEX_PREFIX, MongoAdapter, REL_FILTER_TEMP_PREFIX, TCollectionPatcherContext, TMongoAdapterOptions, TMongoFieldCollation, TMongoFilterOptions, TMongoFilterPlan, type TMongoIndex, type TMongoSearchIndexDefinition, type TPlainIndex, type TSearchIndex, buildMongoFilter, buildMongoQuery, collationOfAdapter, createAdapter, incExpr, mongoFilterStages, mongoIndexKey, planStages, validateMongoIdPlugin };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as buildMongoFilter, c as mongoFilterStages, i as TMongoFilterPlan, l as planStages, n as TMongoFieldCollation, o as buildMongoQuery, r as TMongoFilterOptions, s as collationOfAdapter, t as REL_FILTER_TEMP_PREFIX } from "./mongo-filter-CGd9ryOD.mjs";
|
|
2
|
-
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbCollation, TDbDeleteResult, TDbFieldMeta, TDbForeignKey, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbRelation, TDbUpdateOptions, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TPrimaryKeyChange, TSearchIndexInfo, TSyncColumnResult, TSyncIndexesOptions, TTableResolver, TViewCapability, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
|
|
2
|
+
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbCollation, TDbDeleteResult, TDbFieldMeta, TDbForeignKey, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbIsolationLevel, TDbObjectKind, TDbRelation, TDbUpdateOptions, TDbUpdateResult, TExistingTableOption, TFieldOps, TIndexChange, TMetadataOverrides, TPrimaryKeyChange, TSearchIndexInfo, TSyncColumnResult, TSyncIndexesOptions, TTableResolver, TTransactionOptions, TViewCapability, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
|
|
3
3
|
import { AggregateOptions, AggregationCursor, ClientSession, Collection, Db, Document, Filter, MongoClient, ObjectId, UpdateFilter, UpdateOptions } from "mongodb";
|
|
4
4
|
import { AggregateFn, BucketUnit } from "@uniqu/core";
|
|
5
5
|
import { TAtscriptAnnotatedType, TMetadataMap, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
|
|
@@ -14,6 +14,8 @@ interface TCollectionPatcherContext {
|
|
|
14
14
|
prepareId(id: any): any;
|
|
15
15
|
createValidator(opts?: Partial<TValidatorOptions>): Validator<any>;
|
|
16
16
|
}
|
|
17
|
+
/** Aggregation expression adding `by` to `field` (an `$inc` in a pipeline update). */
|
|
18
|
+
declare function incExpr(field: string, by: number): Document;
|
|
17
19
|
/**
|
|
18
20
|
* CollectionPatcher is a small helper that converts a *patch payload* produced
|
|
19
21
|
* by Atscript into a shape that the official MongoDB driver understands – a
|
|
@@ -64,7 +66,7 @@ declare class CollectionPatcher {
|
|
|
64
66
|
updateFilter: Document[];
|
|
65
67
|
updateOptions: UpdateOptions;
|
|
66
68
|
};
|
|
67
|
-
/**
|
|
69
|
+
/** Aggregation expression of an `$inc` / `$mul` field op (see {@link fieldOpCurrent}). */
|
|
68
70
|
private _fieldOpExpr;
|
|
69
71
|
/**
|
|
70
72
|
* Helper – lazily create `$set` section and assign *key* → *value*.
|
|
@@ -327,8 +329,23 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
327
329
|
* Uses MongoDB's Convenient Transaction API (`session.withTransaction()`).
|
|
328
330
|
* This handles txnNumber management and automatic retry for
|
|
329
331
|
* `TransientTransactionError` / `UnknownTransactionCommitResult`.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
+
*
|
|
333
|
+
* `opts.isolation` (since 0.1.162): a MongoDB transaction reads one
|
|
334
|
+
* snapshot, which satisfies `'read committed'` and `'repeatable read'`;
|
|
335
|
+
* `'serializable'` throws `ISOLATION_NOT_SUPPORTED` (snapshot isolation
|
|
336
|
+
* allows write skew), and so does any level on a topology without
|
|
337
|
+
* transactions (standalone), where `fn` would run without one.
|
|
338
|
+
*/
|
|
339
|
+
withTransaction<T>(fn: () => Promise<T>, opts?: TTransactionOptions): Promise<T>;
|
|
340
|
+
private static readonly _ISOLATION_LEVELS;
|
|
341
|
+
/**
|
|
342
|
+
* `'read committed'` and `'repeatable read'` — a transaction's snapshot
|
|
343
|
+
* read satisfies both. No `'serializable'` (snapshot isolation allows
|
|
344
|
+
* write skew). Locking reads are not supported (`supportsReadLocks()` is
|
|
345
|
+
* false → `LOCK_NOT_SUPPORTED`): use `$cas` / `withOptimisticRetry`.
|
|
346
|
+
* @since 0.1.162
|
|
347
|
+
*/
|
|
348
|
+
isolationLevels(): ReadonlySet<TDbIsolationLevel>;
|
|
332
349
|
private static readonly _noSession;
|
|
333
350
|
/** Returns `{ session }` opts if inside a transaction, empty object otherwise. */
|
|
334
351
|
protected _getSessionOpts(): {
|
|
@@ -596,7 +613,7 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
596
613
|
* `__pk` index first (`dropIndexesForColumns`).
|
|
597
614
|
*/
|
|
598
615
|
rebuildPrimaryKey(_change: TPrimaryKeyChange): Promise<void>;
|
|
599
|
-
|
|
616
|
+
protected _syncIndexes(dryRun: boolean, opts?: TSyncIndexesOptions): Promise<TIndexChange[]>;
|
|
600
617
|
syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
|
|
601
618
|
dropColumns(columns: string[]): Promise<void>;
|
|
602
619
|
dropIndexesForColumns(columns: string[]): Promise<void>;
|
|
@@ -697,4 +714,4 @@ declare const validateMongoIdPlugin: TValidatorPlugin;
|
|
|
697
714
|
/** A {@link DbSpace} over the database of `connection`; `options` go to every {@link MongoAdapter}. */
|
|
698
715
|
declare function createAdapter(connection: string, options?: TMongoAdapterOptions): DbSpace;
|
|
699
716
|
//#endregion
|
|
700
|
-
export { CollectionPatcher, INDEX_PREFIX, MongoAdapter, REL_FILTER_TEMP_PREFIX, TCollectionPatcherContext, TMongoAdapterOptions, TMongoFieldCollation, TMongoFilterOptions, TMongoFilterPlan, type TMongoIndex, type TMongoSearchIndexDefinition, type TPlainIndex, type TSearchIndex, buildMongoFilter, buildMongoQuery, collationOfAdapter, createAdapter, mongoFilterStages, mongoIndexKey, planStages, validateMongoIdPlugin };
|
|
717
|
+
export { CollectionPatcher, INDEX_PREFIX, MongoAdapter, REL_FILTER_TEMP_PREFIX, TCollectionPatcherContext, TMongoAdapterOptions, TMongoFieldCollation, TMongoFilterOptions, TMongoFilterPlan, type TMongoIndex, type TMongoSearchIndexDefinition, type TPlainIndex, type TSearchIndex, buildMongoFilter, buildMongoQuery, collationOfAdapter, createAdapter, incExpr, mongoFilterStages, mongoIndexKey, planStages, validateMongoIdPlugin };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { a as buildMongoQuery, c as planStages, f as sortStages, g as queryNodeToExpr, h as orNull, i as buildMongoFilter, l as correlate, n as distinctCountExpr, o as collationOfAdapter, p as exprToMongo, r as REL_FILTER_TEMP_PREFIX, s as mongoFilterStages, t as buildAccumulator, u as hasNullsPlacement } from "./mongo-accumulator-DMPFOscc.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { t as dynamicSearchEncryptedMessage } from "./search-messages-mQSpJ4GD.mjs";
|
|
3
|
+
import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, ALL_VIEW_CAPABILITIES, BaseDbAdapter, DbError, DbSpace, andFilters, bucketTimeZoneUnavailable, computeInsights, containsRelationFilter, createChangeRecorder, createFailureCollector, defaultFulltextIndex, geoIndexNotFoundMessage, getDbFieldOp, getKeyProps, getPath, isAtscriptDbView, isFieldRef, isPlainObject, isViewType, queryReadColumns, relationStaticFilter, resolveDesignType, searchIndexNotFoundMessage, searchTermInteger, tableNameOf, translateQueryTree, uniqueKeyTuple, vectorIndexNotFoundMessage } from "@atscript/db";
|
|
3
4
|
import { MongoBulkWriteError, MongoClient, MongoServerError, ObjectId } from "mongodb";
|
|
4
5
|
import { resolveAggregateSearch } from "@atscript/db/agg";
|
|
5
6
|
//#region src/lib/path-utils.ts
|
|
@@ -78,6 +79,19 @@ function containsAggregationExpr(v) {
|
|
|
78
79
|
return false;
|
|
79
80
|
}
|
|
80
81
|
/**
|
|
82
|
+
* A field's current value in an aggregation pipeline update — a missing field
|
|
83
|
+
* counts as `0`, like the `$inc` / `$mul` update operators (the pipeline
|
|
84
|
+
* `$add` / `$multiply` would yield `null`) — so a document written before an
|
|
85
|
+
* `@db.onUpdate.inc` counter existed starts counting.
|
|
86
|
+
*/
|
|
87
|
+
function fieldOpCurrent(field) {
|
|
88
|
+
return { $ifNull: [`$${field}`, 0] };
|
|
89
|
+
}
|
|
90
|
+
/** Aggregation expression adding `by` to `field` (an `$inc` in a pipeline update). */
|
|
91
|
+
function incExpr(field, by) {
|
|
92
|
+
return { $add: [fieldOpCurrent(field), by] };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
81
95
|
* CollectionPatcher is a small helper that converts a *patch payload* produced
|
|
82
96
|
* by Atscript into a shape that the official MongoDB driver understands – a
|
|
83
97
|
* triple of `(filter, update, options)` to be fed to `collection.updateOne()`.
|
|
@@ -142,9 +156,9 @@ var CollectionPatcher = class {
|
|
|
142
156
|
updateOptions: this.optionsObj
|
|
143
157
|
};
|
|
144
158
|
}
|
|
145
|
-
/**
|
|
159
|
+
/** Aggregation expression of an `$inc` / `$mul` field op (see {@link fieldOpCurrent}). */
|
|
146
160
|
_fieldOpExpr(key, op, value) {
|
|
147
|
-
return op === "inc" ?
|
|
161
|
+
return op === "inc" ? incExpr(key, value) : { $multiply: [fieldOpCurrent(key), value] };
|
|
148
162
|
}
|
|
149
163
|
/**
|
|
150
164
|
* Helper – lazily create `$set` section and assign *key* → *value*.
|
|
@@ -1839,8 +1853,12 @@ const PK_INDEX = mongoIndexKey("unique", "__pk");
|
|
|
1839
1853
|
* index of a skipped key rebuild, the indexes over a column whose type change
|
|
1840
1854
|
* is pending (its present-only filter is typed).
|
|
1841
1855
|
*/
|
|
1842
|
-
async function syncIndexesImpl(host, opts) {
|
|
1843
|
-
|
|
1856
|
+
async function syncIndexesImpl(host, opts, dryRun = false) {
|
|
1857
|
+
/** Indexes dropped because their definition drifted — their re-creation is a rebuild. */
|
|
1858
|
+
const replaced = /* @__PURE__ */ new Set();
|
|
1859
|
+
if (dryRun) {
|
|
1860
|
+
if (!await host.db.listCollections({ name: host.collection.collectionName }, { nameOnly: true }).hasNext()) return [];
|
|
1861
|
+
} else await host.ensureCollectionExists();
|
|
1844
1862
|
const allIndexes = /* @__PURE__ */ new Map();
|
|
1845
1863
|
for (const [key, index] of host._table.indexes.entries()) {
|
|
1846
1864
|
const fields = {};
|
|
@@ -1887,6 +1905,7 @@ async function syncIndexesImpl(host, opts) {
|
|
|
1887
1905
|
for (const remote of existingIndexes) if (isKept(remote.name, remote.key, remote.weights)) kept.add(remote.name);
|
|
1888
1906
|
for (const name of kept) indexesToCreate.delete(name);
|
|
1889
1907
|
const { attempt, throwIfAny } = createFailureCollector("index sync");
|
|
1908
|
+
const { changes, apply } = createChangeRecorder(dryRun, attempt);
|
|
1890
1909
|
let defaultCollation;
|
|
1891
1910
|
const collectionDefaultCollation = async () => {
|
|
1892
1911
|
if (defaultCollation === void 0) defaultCollation = (await host.db.listCollections({ name: host.collection.collectionName }).next())?.options?.collation ?? null;
|
|
@@ -1905,16 +1924,21 @@ async function syncIndexesImpl(host, opts) {
|
|
|
1905
1924
|
if (fieldsMatch && weightsMatch && optionsMatch) indexesToCreate.delete(remote.name);
|
|
1906
1925
|
else if (violation !== void 0) {
|
|
1907
1926
|
indexesToCreate.delete(remote.name);
|
|
1927
|
+
if (dryRun) continue;
|
|
1908
1928
|
await attempt(`replace unique index "${remote.name}"`, () => Promise.reject(/* @__PURE__ */ new Error(`existing documents violate the new index (duplicate key ${JSON.stringify(violation)}); the current index is kept`)));
|
|
1909
|
-
} else
|
|
1929
|
+
} else if (dryRun) replaced.add(remote.name);
|
|
1930
|
+
else {
|
|
1910
1931
|
host._log("dropIndex", remote.name);
|
|
1911
|
-
await attempt(`drop index "${remote.name}"`, () => host.collection.dropIndex(remote.name));
|
|
1932
|
+
if (await attempt(`drop index "${remote.name}"`, () => host.collection.dropIndex(remote.name))) replaced.add(remote.name);
|
|
1912
1933
|
}
|
|
1913
1934
|
}
|
|
1914
|
-
} else {
|
|
1935
|
+
} else await apply({
|
|
1936
|
+
name: remote.name,
|
|
1937
|
+
action: "drop"
|
|
1938
|
+
}, `drop index "${remote.name}"`, () => {
|
|
1915
1939
|
host._log("dropIndex", remote.name);
|
|
1916
|
-
|
|
1917
|
-
}
|
|
1940
|
+
return host.collection.dropIndex(remote.name);
|
|
1941
|
+
});
|
|
1918
1942
|
}
|
|
1919
1943
|
for (const [key, value] of allIndexes.entries()) {
|
|
1920
1944
|
if (!isPlainIndex(value) || !indexesToCreate.has(key)) continue;
|
|
@@ -1954,8 +1978,12 @@ async function syncIndexesImpl(host, opts) {
|
|
|
1954
1978
|
indexOptions = { name: key };
|
|
1955
1979
|
break;
|
|
1956
1980
|
}
|
|
1957
|
-
await
|
|
1981
|
+
await apply({
|
|
1982
|
+
name: key,
|
|
1983
|
+
action: replaced.has(key) ? "rebuild" : "add"
|
|
1984
|
+
}, label, () => host.collection.createIndex(value.fields, indexOptions));
|
|
1958
1985
|
}
|
|
1986
|
+
const search = createChangeRecorder(dryRun);
|
|
1959
1987
|
try {
|
|
1960
1988
|
const toUpdate = /* @__PURE__ */ new Set();
|
|
1961
1989
|
const existingSearchIndexes = await host.collection.listSearchIndexes().toArray();
|
|
@@ -1978,31 +2006,41 @@ async function syncIndexesImpl(host, opts) {
|
|
|
1978
2006
|
break;
|
|
1979
2007
|
default:
|
|
1980
2008
|
}
|
|
1981
|
-
} else if (remote.status !== "DELETING") {
|
|
2009
|
+
} else if (remote.status !== "DELETING") await search.apply({
|
|
2010
|
+
name: remote.name,
|
|
2011
|
+
action: "drop"
|
|
2012
|
+
}, remote.name, () => {
|
|
1982
2013
|
host._log("dropSearchIndex", remote.name);
|
|
1983
|
-
|
|
1984
|
-
}
|
|
2014
|
+
return host.collection.dropSearchIndex(remote.name);
|
|
2015
|
+
});
|
|
1985
2016
|
}
|
|
1986
2017
|
for (const [key, value] of indexesToCreate.entries()) switch (value.type) {
|
|
1987
2018
|
case "dynamic_text":
|
|
1988
2019
|
case "search_text":
|
|
1989
|
-
case "vector":
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2020
|
+
case "vector": {
|
|
2021
|
+
const update = toUpdate.has(key);
|
|
2022
|
+
await search.apply({
|
|
2023
|
+
name: key,
|
|
2024
|
+
action: update ? "rebuild" : "add"
|
|
2025
|
+
}, key, () => {
|
|
2026
|
+
if (update) {
|
|
2027
|
+
host._log("updateSearchIndex", key, value.definition);
|
|
2028
|
+
return host.collection.updateSearchIndex(key, value.definition);
|
|
2029
|
+
}
|
|
1994
2030
|
host._log("createSearchIndex", key, value.type);
|
|
1995
|
-
|
|
2031
|
+
return host.collection.createSearchIndex({
|
|
1996
2032
|
name: key,
|
|
1997
2033
|
type: value.type === "vector" ? "vectorSearch" : "search",
|
|
1998
2034
|
definition: value.definition
|
|
1999
2035
|
});
|
|
2000
|
-
}
|
|
2036
|
+
});
|
|
2001
2037
|
break;
|
|
2038
|
+
}
|
|
2002
2039
|
default:
|
|
2003
2040
|
}
|
|
2004
2041
|
} catch {}
|
|
2005
2042
|
throwIfAny();
|
|
2043
|
+
return [...changes, ...search.changes];
|
|
2006
2044
|
}
|
|
2007
2045
|
/**
|
|
2008
2046
|
* The collation a plain / unique index is built with (since 0.1.151): the
|
|
@@ -2272,22 +2310,33 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2272
2310
|
* Uses MongoDB's Convenient Transaction API (`session.withTransaction()`).
|
|
2273
2311
|
* This handles txnNumber management and automatic retry for
|
|
2274
2312
|
* `TransientTransactionError` / `UnknownTransactionCommitResult`.
|
|
2313
|
+
*
|
|
2314
|
+
* `opts.isolation` (since 0.1.162): a MongoDB transaction reads one
|
|
2315
|
+
* snapshot, which satisfies `'read committed'` and `'repeatable read'`;
|
|
2316
|
+
* `'serializable'` throws `ISOLATION_NOT_SUPPORTED` (snapshot isolation
|
|
2317
|
+
* allows write skew), and so does any level on a topology without
|
|
2318
|
+
* transactions (standalone), where `fn` would run without one.
|
|
2275
2319
|
*/
|
|
2276
|
-
async withTransaction(fn) {
|
|
2320
|
+
async withTransaction(fn, opts) {
|
|
2321
|
+
const isolation = this._checkIsolation(opts);
|
|
2277
2322
|
if (this._getTransactionState()) return fn();
|
|
2278
|
-
|
|
2323
|
+
const passThrough = () => {
|
|
2324
|
+
if (isolation) throw this._isolationNotSupported(`Isolation '${isolation}' needs a MongoDB transaction — this topology (standalone) has none`);
|
|
2325
|
+
return fn();
|
|
2326
|
+
};
|
|
2327
|
+
if (this._txDisabled || !this._client) return passThrough();
|
|
2279
2328
|
try {
|
|
2280
2329
|
const topology = this._client.topology;
|
|
2281
2330
|
if (topology) {
|
|
2282
2331
|
const type = (topology.description ?? topology.s?.description)?.type;
|
|
2283
2332
|
if (type === "Single" || type === "Unknown") {
|
|
2284
2333
|
this._txDisabled = true;
|
|
2285
|
-
return
|
|
2334
|
+
return passThrough();
|
|
2286
2335
|
}
|
|
2287
2336
|
}
|
|
2288
2337
|
} catch {
|
|
2289
2338
|
this._txDisabled = true;
|
|
2290
|
-
return
|
|
2339
|
+
return passThrough();
|
|
2291
2340
|
}
|
|
2292
2341
|
const session = this._client.startSession();
|
|
2293
2342
|
try {
|
|
@@ -2302,6 +2351,17 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2302
2351
|
} catch {}
|
|
2303
2352
|
}
|
|
2304
2353
|
}
|
|
2354
|
+
static _ISOLATION_LEVELS = new Set(["read committed", "repeatable read"]);
|
|
2355
|
+
/**
|
|
2356
|
+
* `'read committed'` and `'repeatable read'` — a transaction's snapshot
|
|
2357
|
+
* read satisfies both. No `'serializable'` (snapshot isolation allows
|
|
2358
|
+
* write skew). Locking reads are not supported (`supportsReadLocks()` is
|
|
2359
|
+
* false → `LOCK_NOT_SUPPORTED`): use `$cas` / `withOptimisticRetry`.
|
|
2360
|
+
* @since 0.1.162
|
|
2361
|
+
*/
|
|
2362
|
+
isolationLevels() {
|
|
2363
|
+
return MongoAdapter._ISOLATION_LEVELS;
|
|
2364
|
+
}
|
|
2305
2365
|
static _noSession = Object.freeze({});
|
|
2306
2366
|
/** Returns `{ session }` opts if inside a transaction, empty object otherwise. */
|
|
2307
2367
|
_getSessionOpts() {
|
|
@@ -2669,6 +2729,8 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2669
2729
|
for (const field of this._pendingUniqueFields) this._addMongoIndexField("unique", "__pk", meta.documentPath(field));
|
|
2670
2730
|
for (const pending of this._pendingSearchFields) this._addFieldToSearchIndex("search_text", pending.indexName, pending.field, pending.mappings);
|
|
2671
2731
|
this._pendingSearchFields = [];
|
|
2732
|
+
const [encrypted] = meta.encryptedFields;
|
|
2733
|
+
if (encrypted !== void 0 && [...this._mongoIndexes.values()].some((i) => i.type === "dynamic_text")) throw new Error(dynamicSearchEncryptedMessage(encrypted));
|
|
2672
2734
|
const textBearing = [...this._table.indexes.values()].filter((i) => i.type === "fulltext" && i.fields.some((f) => !f.integer));
|
|
2673
2735
|
if (textBearing.length > 1) throw new Error(`MongoDB allows one text index per collection: "${this._table.tableName}" declares ${textBearing.length} @db.index.fulltext names with text fields (${textBearing.map((i) => `"${i.name}"`).join(", ")}). Merge them into one name; integer-only fulltext indexes may be declared separately.`);
|
|
2674
2736
|
for (const fulltext of this._table.indexes.values()) {
|
|
@@ -3149,12 +3211,14 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
3149
3211
|
modifiedCount: 0
|
|
3150
3212
|
};
|
|
3151
3213
|
const versionColumn = this._table.versionColumnPhysical;
|
|
3214
|
+
const counters = this._table.onUpdateIncPhysical;
|
|
3152
3215
|
this._log("replaceOne", mongoFilter, data);
|
|
3153
|
-
if (versionColumn !== void 0) {
|
|
3154
|
-
const
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
} }
|
|
3216
|
+
if (versionColumn !== void 0 || counters.length > 0) {
|
|
3217
|
+
const replacement = {};
|
|
3218
|
+
for (const [key, value] of Object.entries(data)) replacement[key] = { $literal: value };
|
|
3219
|
+
for (const col of counters) replacement[col] = incExpr(col, 1);
|
|
3220
|
+
if (versionColumn !== void 0) replacement[versionColumn] = { $add: [`$${versionColumn}`, 1] };
|
|
3221
|
+
const pipeline = [{ $replaceWith: replacement }];
|
|
3158
3222
|
return this._wrapUpdate(() => this.collection.updateOne(mongoFilter, pipeline, this._getSessionOpts()));
|
|
3159
3223
|
}
|
|
3160
3224
|
return this._wrapUpdate(() => this.collection.replaceOne(mongoFilter, data, this._getSessionOpts()));
|
|
@@ -3173,7 +3237,7 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
3173
3237
|
});
|
|
3174
3238
|
}
|
|
3175
3239
|
async replaceMany(filter, data) {
|
|
3176
|
-
const updateDoc = buildMongoUpdateDoc(data,
|
|
3240
|
+
const updateDoc = buildMongoUpdateDoc(data, this._onUpdateIncOps(), this._table.versionColumnPhysical);
|
|
3177
3241
|
return this._updateMatching(filter, (mongoFilter) => {
|
|
3178
3242
|
this._log("replaceMany", mongoFilter, updateDoc);
|
|
3179
3243
|
return this._wrapUpdate(() => this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
@@ -3214,8 +3278,8 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
3214
3278
|
* `__pk` index first (`dropIndexesForColumns`).
|
|
3215
3279
|
*/
|
|
3216
3280
|
async rebuildPrimaryKey(_change) {}
|
|
3217
|
-
async
|
|
3218
|
-
return syncIndexesImpl(this, opts);
|
|
3281
|
+
async _syncIndexes(dryRun, opts) {
|
|
3282
|
+
return syncIndexesImpl(this, opts, dryRun);
|
|
3219
3283
|
}
|
|
3220
3284
|
async syncColumns(diff) {
|
|
3221
3285
|
return syncColumnsImpl(this, diff);
|
|
@@ -3596,4 +3660,4 @@ function createAdapter(connection, options) {
|
|
|
3596
3660
|
return new DbSpace(() => new MongoAdapter(db, client, options), { onClose: () => client.close() });
|
|
3597
3661
|
}
|
|
3598
3662
|
//#endregion
|
|
3599
|
-
export { CollectionPatcher, INDEX_PREFIX, MongoAdapter, REL_FILTER_TEMP_PREFIX, buildMongoFilter, buildMongoQuery, collationOfAdapter, createAdapter, mongoFilterStages, mongoIndexKey, planStages, validateMongoIdPlugin };
|
|
3663
|
+
export { CollectionPatcher, INDEX_PREFIX, MongoAdapter, REL_FILTER_TEMP_PREFIX, buildMongoFilter, buildMongoQuery, collationOfAdapter, createAdapter, incExpr, mongoFilterStages, mongoIndexKey, planStages, validateMongoIdPlugin };
|
package/dist/plugin.cjs
CHANGED
|
@@ -2,7 +2,9 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
+
const require_search_messages = require("./search-messages-BqLxxrb8.cjs");
|
|
5
6
|
let _atscript_core = require("@atscript/core");
|
|
7
|
+
let _atscript_db_shared = require("@atscript/db/shared");
|
|
6
8
|
//#region src/plugin/annotations.ts
|
|
7
9
|
const analyzers = [
|
|
8
10
|
"lucene.standard",
|
|
@@ -50,7 +52,7 @@ const fuzzyDescription = "Maximum typo tolerance, applied **at query time** to t
|
|
|
50
52
|
*/
|
|
51
53
|
const annotations = {
|
|
52
54
|
collection: new _atscript_core.AnnotationSpec({
|
|
53
|
-
description: "Marks an interface as a **MongoDB collection**.\n\n- Use together with `@db.table \"name\"` which provides the collection name.\n- Automatically injects a **non-optional** `_id` field if not explicitly defined.\n- `_id` must be of type **`string`**, **`number`**, or **`mongo.objectId
|
|
55
|
+
description: (0, _atscript_core.annotationExample)("Marks an interface as a **MongoDB collection**.\n\n- Use together with `@db.table \"name\"` which provides the collection name.\n- Automatically injects a **non-optional** `_id` field if not explicitly defined.\n- `_id` must be of type **`string`**, **`number`**, or **`mongo.objectId`**.", "@db.table \"users\"", "@db.mongo.collection", "export interface User {", " _id: mongo.objectId", " email: string.email", "}"),
|
|
54
56
|
nodeType: ["interface"],
|
|
55
57
|
passedWhenReferred: false,
|
|
56
58
|
validate(token, args, doc) {
|
|
@@ -90,7 +92,7 @@ const annotations = {
|
|
|
90
92
|
}
|
|
91
93
|
}),
|
|
92
94
|
capped: new _atscript_core.AnnotationSpec({
|
|
93
|
-
description: "Creates a **capped collection** with a fixed maximum size.\n\n- Capped collections have fixed size and maintain insertion order.\n- Ideal for logs, event streams, and cache-like data.\n- Changing the cap size requires dropping and recreating the collection — use `@db.sync.method \"drop\"` to allow this
|
|
95
|
+
description: (0, _atscript_core.annotationExample)("Creates a **capped collection** with a fixed maximum size.\n\n- Capped collections have fixed size and maintain insertion order.\n- Ideal for logs, event streams, and cache-like data.\n- Changing the cap size requires dropping and recreating the collection — use `@db.sync.method \"drop\"` to allow this.", "@db.table \"logs\"", "@db.mongo.collection", "@db.mongo.capped 10485760, 10000", "@db.sync.method \"drop\"", "export interface LogEntry {", " message: string", " timestamp: number", "}"),
|
|
94
96
|
nodeType: ["interface"],
|
|
95
97
|
passedWhenReferred: false,
|
|
96
98
|
multiple: false,
|
|
@@ -108,10 +110,19 @@ const annotations = {
|
|
|
108
110
|
}),
|
|
109
111
|
search: {
|
|
110
112
|
dynamic: new _atscript_core.AnnotationSpec({
|
|
111
|
-
description: "Creates a **dynamic MongoDB Search Index** that applies to the entire collection.\n\n- **Indexes all text fields automatically** (no need to specify fields).\n- Supports **language analyzers** for text tokenization.\n- Enables **fuzzy search** (typo tolerance) if needed
|
|
113
|
+
description: (0, _atscript_core.annotationExample)("Creates a **dynamic MongoDB Search Index** that applies to the entire collection.\n\n- **Indexes all text fields automatically** (no need to specify fields).\n- Supports **language analyzers** for text tokenization.\n- Enables **fuzzy search** (typo tolerance) if needed.", "@db.mongo.search.dynamic \"lucene.english\", 1", "export interface MongoCollection {}"),
|
|
112
114
|
nodeType: ["interface"],
|
|
113
115
|
passedWhenReferred: false,
|
|
114
116
|
multiple: false,
|
|
117
|
+
validate(token, _args, doc) {
|
|
118
|
+
const encrypted = (0, _atscript_db_shared.findStoredField)(token.parentNode, doc, (prop, path) => prop.countAnnotations("db.encrypted") > 0 ? path : void 0);
|
|
119
|
+
if (!encrypted) return [];
|
|
120
|
+
return [{
|
|
121
|
+
message: require_search_messages.dynamicSearchEncryptedMessage(encrypted),
|
|
122
|
+
severity: 1,
|
|
123
|
+
range: token.range
|
|
124
|
+
}];
|
|
125
|
+
},
|
|
115
126
|
argument: [{
|
|
116
127
|
optional: true,
|
|
117
128
|
name: "analyzer",
|
|
@@ -126,7 +137,7 @@ const annotations = {
|
|
|
126
137
|
}]
|
|
127
138
|
}),
|
|
128
139
|
static: new _atscript_core.AnnotationSpec({
|
|
129
|
-
description: "Defines a **MongoDB Atlas Search Index** for the collection. The props can refer to this index using `@db.mongo.search.text` annotation.\n\n- **Creates a named search index** for full-text search.\n- **Specify analyzers and fuzzy search** behavior at the index level.\n- **Fields must explicitly use `@db.mongo.search.text`** to be included in this search index
|
|
140
|
+
description: (0, _atscript_core.annotationExample)("Defines a **MongoDB Atlas Search Index** for the collection. The props can refer to this index using `@db.mongo.search.text` annotation.\n\n- **Creates a named search index** for full-text search.\n- **Specify analyzers and fuzzy search** behavior at the index level.\n- **Fields must explicitly use `@db.mongo.search.text`** to be included in this search index.", "@db.mongo.search.static \"lucene.english\", 1, \"mySearchIndex\"", "export interface MongoCollection {}"),
|
|
130
141
|
nodeType: ["interface"],
|
|
131
142
|
passedWhenReferred: false,
|
|
132
143
|
multiple: true,
|
|
@@ -160,8 +171,9 @@ const annotations = {
|
|
|
160
171
|
]
|
|
161
172
|
}),
|
|
162
173
|
text: new _atscript_core.AnnotationSpec({
|
|
163
|
-
description: "Marks a field to be **included in a MongoDB Atlas Search Index** defined by `@db.mongo.search.static`.\n\n- **The field has to reference an existing search index name**.\n- If index name is not defined, a new search index with default attributes will be created
|
|
174
|
+
description: (0, _atscript_core.annotationExample)("Marks a field to be **included in a MongoDB Atlas Search Index** defined by `@db.mongo.search.static`.\n\n- **The field has to reference an existing search index name**.\n- If index name is not defined, a new search index with default attributes will be created.", "@db.mongo.search.text \"lucene.english\", \"mySearchIndex\"", "firstName: string"),
|
|
164
175
|
nodeType: ["prop"],
|
|
176
|
+
validate: (0, _atscript_db_shared.refuseCiphertext)("@db.mongo.search.text"),
|
|
165
177
|
passedWhenReferred: false,
|
|
166
178
|
multiple: true,
|
|
167
179
|
argument: [{
|
|
@@ -178,8 +190,9 @@ const annotations = {
|
|
|
178
190
|
}]
|
|
179
191
|
}),
|
|
180
192
|
autocomplete: new _atscript_core.AnnotationSpec({
|
|
181
|
-
description: "Marks a field for **prefix / typeahead (as-you-type)** matching in a MongoDB Atlas Search Index.\n\n- Indexes the field as the Atlas **`autocomplete`** type, **and** double-maps it as `string` so exact-word hits still rank.\n- Lets `search()` match partial words: with the default `edgeGram` tokenization, `\"art\"` matches `\"Artem\"` **as you type** (no whole word required).\n- Use `nGram` tokenization for true mid-word (infix/substring) matching at higher index cost.\n- Like `@db.mongo.search.text`, the field joins the index named by `indexName` (or the default index)
|
|
193
|
+
description: (0, _atscript_core.annotationExample)("Marks a field for **prefix / typeahead (as-you-type)** matching in a MongoDB Atlas Search Index.\n\n- Indexes the field as the Atlas **`autocomplete`** type, **and** double-maps it as `string` so exact-word hits still rank.\n- Lets `search()` match partial words: with the default `edgeGram` tokenization, `\"art\"` matches `\"Artem\"` **as you type** (no whole word required).\n- Use `nGram` tokenization for true mid-word (infix/substring) matching at higher index cost.\n- Like `@db.mongo.search.text`, the field joins the index named by `indexName` (or the default index).", "@db.mongo.search.autocomplete \"users\"", "username: string"),
|
|
182
194
|
nodeType: ["prop"],
|
|
195
|
+
validate: (0, _atscript_db_shared.refuseCiphertext)("@db.mongo.search.autocomplete"),
|
|
183
196
|
passedWhenReferred: false,
|
|
184
197
|
multiple: true,
|
|
185
198
|
argument: [
|
|
@@ -229,7 +242,7 @@ const annotations = {
|
|
|
229
242
|
//#region src/plugin/primitives.ts
|
|
230
243
|
const primitives = { mongo: { extensions: { objectId: {
|
|
231
244
|
type: "string",
|
|
232
|
-
documentation: "Represents a **MongoDB ObjectId**.\n\n- Stored as a **string** but can be converted to an ObjectId at runtime.\n- Useful for handling `_id` fields and queries that require ObjectId conversion.\n- Automatically converts string `_id` values into **MongoDB ObjectId** when needed
|
|
245
|
+
documentation: (0, _atscript_core.annotationExample)("Represents a **MongoDB ObjectId**.\n\n- Stored as a **string** but can be converted to an ObjectId at runtime.\n- Useful for handling `_id` fields and queries that require ObjectId conversion.\n- Automatically converts string `_id` values into **MongoDB ObjectId** when needed.", "userId: mongo.objectId"),
|
|
233
246
|
annotations: { "expect.pattern": { pattern: "^[a-fA-F0-9]{24}$" } }
|
|
234
247
|
} } } };
|
|
235
248
|
//#endregion
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as dynamicSearchEncryptedMessage } from "./search-messages-mQSpJ4GD.mjs";
|
|
2
|
+
import { AnnotationSpec, annotationExample, isInterface, isPrimitive, isRef, isStructure } from "@atscript/core";
|
|
3
|
+
import { findStoredField, refuseCiphertext } from "@atscript/db/shared";
|
|
2
4
|
//#region src/plugin/annotations.ts
|
|
3
5
|
const analyzers = [
|
|
4
6
|
"lucene.standard",
|
|
@@ -46,7 +48,7 @@ const fuzzyDescription = "Maximum typo tolerance, applied **at query time** to t
|
|
|
46
48
|
*/
|
|
47
49
|
const annotations = {
|
|
48
50
|
collection: new AnnotationSpec({
|
|
49
|
-
description: "Marks an interface as a **MongoDB collection**.\n\n- Use together with `@db.table \"name\"` which provides the collection name.\n- Automatically injects a **non-optional** `_id` field if not explicitly defined.\n- `_id` must be of type **`string`**, **`number`**, or **`mongo.objectId
|
|
51
|
+
description: annotationExample("Marks an interface as a **MongoDB collection**.\n\n- Use together with `@db.table \"name\"` which provides the collection name.\n- Automatically injects a **non-optional** `_id` field if not explicitly defined.\n- `_id` must be of type **`string`**, **`number`**, or **`mongo.objectId`**.", "@db.table \"users\"", "@db.mongo.collection", "export interface User {", " _id: mongo.objectId", " email: string.email", "}"),
|
|
50
52
|
nodeType: ["interface"],
|
|
51
53
|
passedWhenReferred: false,
|
|
52
54
|
validate(token, args, doc) {
|
|
@@ -86,7 +88,7 @@ const annotations = {
|
|
|
86
88
|
}
|
|
87
89
|
}),
|
|
88
90
|
capped: new AnnotationSpec({
|
|
89
|
-
description: "Creates a **capped collection** with a fixed maximum size.\n\n- Capped collections have fixed size and maintain insertion order.\n- Ideal for logs, event streams, and cache-like data.\n- Changing the cap size requires dropping and recreating the collection — use `@db.sync.method \"drop\"` to allow this
|
|
91
|
+
description: annotationExample("Creates a **capped collection** with a fixed maximum size.\n\n- Capped collections have fixed size and maintain insertion order.\n- Ideal for logs, event streams, and cache-like data.\n- Changing the cap size requires dropping and recreating the collection — use `@db.sync.method \"drop\"` to allow this.", "@db.table \"logs\"", "@db.mongo.collection", "@db.mongo.capped 10485760, 10000", "@db.sync.method \"drop\"", "export interface LogEntry {", " message: string", " timestamp: number", "}"),
|
|
90
92
|
nodeType: ["interface"],
|
|
91
93
|
passedWhenReferred: false,
|
|
92
94
|
multiple: false,
|
|
@@ -104,10 +106,19 @@ const annotations = {
|
|
|
104
106
|
}),
|
|
105
107
|
search: {
|
|
106
108
|
dynamic: new AnnotationSpec({
|
|
107
|
-
description: "Creates a **dynamic MongoDB Search Index** that applies to the entire collection.\n\n- **Indexes all text fields automatically** (no need to specify fields).\n- Supports **language analyzers** for text tokenization.\n- Enables **fuzzy search** (typo tolerance) if needed
|
|
109
|
+
description: annotationExample("Creates a **dynamic MongoDB Search Index** that applies to the entire collection.\n\n- **Indexes all text fields automatically** (no need to specify fields).\n- Supports **language analyzers** for text tokenization.\n- Enables **fuzzy search** (typo tolerance) if needed.", "@db.mongo.search.dynamic \"lucene.english\", 1", "export interface MongoCollection {}"),
|
|
108
110
|
nodeType: ["interface"],
|
|
109
111
|
passedWhenReferred: false,
|
|
110
112
|
multiple: false,
|
|
113
|
+
validate(token, _args, doc) {
|
|
114
|
+
const encrypted = findStoredField(token.parentNode, doc, (prop, path) => prop.countAnnotations("db.encrypted") > 0 ? path : void 0);
|
|
115
|
+
if (!encrypted) return [];
|
|
116
|
+
return [{
|
|
117
|
+
message: dynamicSearchEncryptedMessage(encrypted),
|
|
118
|
+
severity: 1,
|
|
119
|
+
range: token.range
|
|
120
|
+
}];
|
|
121
|
+
},
|
|
111
122
|
argument: [{
|
|
112
123
|
optional: true,
|
|
113
124
|
name: "analyzer",
|
|
@@ -122,7 +133,7 @@ const annotations = {
|
|
|
122
133
|
}]
|
|
123
134
|
}),
|
|
124
135
|
static: new AnnotationSpec({
|
|
125
|
-
description: "Defines a **MongoDB Atlas Search Index** for the collection. The props can refer to this index using `@db.mongo.search.text` annotation.\n\n- **Creates a named search index** for full-text search.\n- **Specify analyzers and fuzzy search** behavior at the index level.\n- **Fields must explicitly use `@db.mongo.search.text`** to be included in this search index
|
|
136
|
+
description: annotationExample("Defines a **MongoDB Atlas Search Index** for the collection. The props can refer to this index using `@db.mongo.search.text` annotation.\n\n- **Creates a named search index** for full-text search.\n- **Specify analyzers and fuzzy search** behavior at the index level.\n- **Fields must explicitly use `@db.mongo.search.text`** to be included in this search index.", "@db.mongo.search.static \"lucene.english\", 1, \"mySearchIndex\"", "export interface MongoCollection {}"),
|
|
126
137
|
nodeType: ["interface"],
|
|
127
138
|
passedWhenReferred: false,
|
|
128
139
|
multiple: true,
|
|
@@ -156,8 +167,9 @@ const annotations = {
|
|
|
156
167
|
]
|
|
157
168
|
}),
|
|
158
169
|
text: new AnnotationSpec({
|
|
159
|
-
description: "Marks a field to be **included in a MongoDB Atlas Search Index** defined by `@db.mongo.search.static`.\n\n- **The field has to reference an existing search index name**.\n- If index name is not defined, a new search index with default attributes will be created
|
|
170
|
+
description: annotationExample("Marks a field to be **included in a MongoDB Atlas Search Index** defined by `@db.mongo.search.static`.\n\n- **The field has to reference an existing search index name**.\n- If index name is not defined, a new search index with default attributes will be created.", "@db.mongo.search.text \"lucene.english\", \"mySearchIndex\"", "firstName: string"),
|
|
160
171
|
nodeType: ["prop"],
|
|
172
|
+
validate: refuseCiphertext("@db.mongo.search.text"),
|
|
161
173
|
passedWhenReferred: false,
|
|
162
174
|
multiple: true,
|
|
163
175
|
argument: [{
|
|
@@ -174,8 +186,9 @@ const annotations = {
|
|
|
174
186
|
}]
|
|
175
187
|
}),
|
|
176
188
|
autocomplete: new AnnotationSpec({
|
|
177
|
-
description: "Marks a field for **prefix / typeahead (as-you-type)** matching in a MongoDB Atlas Search Index.\n\n- Indexes the field as the Atlas **`autocomplete`** type, **and** double-maps it as `string` so exact-word hits still rank.\n- Lets `search()` match partial words: with the default `edgeGram` tokenization, `\"art\"` matches `\"Artem\"` **as you type** (no whole word required).\n- Use `nGram` tokenization for true mid-word (infix/substring) matching at higher index cost.\n- Like `@db.mongo.search.text`, the field joins the index named by `indexName` (or the default index)
|
|
189
|
+
description: annotationExample("Marks a field for **prefix / typeahead (as-you-type)** matching in a MongoDB Atlas Search Index.\n\n- Indexes the field as the Atlas **`autocomplete`** type, **and** double-maps it as `string` so exact-word hits still rank.\n- Lets `search()` match partial words: with the default `edgeGram` tokenization, `\"art\"` matches `\"Artem\"` **as you type** (no whole word required).\n- Use `nGram` tokenization for true mid-word (infix/substring) matching at higher index cost.\n- Like `@db.mongo.search.text`, the field joins the index named by `indexName` (or the default index).", "@db.mongo.search.autocomplete \"users\"", "username: string"),
|
|
178
190
|
nodeType: ["prop"],
|
|
191
|
+
validate: refuseCiphertext("@db.mongo.search.autocomplete"),
|
|
179
192
|
passedWhenReferred: false,
|
|
180
193
|
multiple: true,
|
|
181
194
|
argument: [
|
|
@@ -225,7 +238,7 @@ const annotations = {
|
|
|
225
238
|
//#region src/plugin/primitives.ts
|
|
226
239
|
const primitives = { mongo: { extensions: { objectId: {
|
|
227
240
|
type: "string",
|
|
228
|
-
documentation: "Represents a **MongoDB ObjectId**.\n\n- Stored as a **string** but can be converted to an ObjectId at runtime.\n- Useful for handling `_id` fields and queries that require ObjectId conversion.\n- Automatically converts string `_id` values into **MongoDB ObjectId** when needed
|
|
241
|
+
documentation: annotationExample("Represents a **MongoDB ObjectId**.\n\n- Stored as a **string** but can be converted to an ObjectId at runtime.\n- Useful for handling `_id` fields and queries that require ObjectId conversion.\n- Automatically converts string `_id` values into **MongoDB ObjectId** when needed.", "userId: mongo.objectId"),
|
|
229
242
|
annotations: { "expect.pattern": { pattern: "^[a-fA-F0-9]{24}$" } }
|
|
230
243
|
} } } };
|
|
231
244
|
//#endregion
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/lib/search-messages.ts
|
|
2
|
+
/**
|
|
3
|
+
* The refusal of `@db.mongo.search.dynamic` on a collection storing an
|
|
4
|
+
* `@db.encrypted` field (at `path`) — shared by the compile-time check and
|
|
5
|
+
* its runtime mirror.
|
|
6
|
+
*/
|
|
7
|
+
function dynamicSearchEncryptedMessage(path) {
|
|
8
|
+
return `@db.mongo.search.dynamic indexes every field, including the @db.encrypted field "${path}" — Atlas Search over ciphertext is impossible; use @db.mongo.search.static with @db.mongo.search.text fields`;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
Object.defineProperty(exports, "dynamicSearchEncryptedMessage", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function() {
|
|
14
|
+
return dynamicSearchEncryptedMessage;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/lib/search-messages.ts
|
|
2
|
+
/**
|
|
3
|
+
* The refusal of `@db.mongo.search.dynamic` on a collection storing an
|
|
4
|
+
* `@db.encrypted` field (at `path`) — shared by the compile-time check and
|
|
5
|
+
* its runtime mirror.
|
|
6
|
+
*/
|
|
7
|
+
function dynamicSearchEncryptedMessage(path) {
|
|
8
|
+
return `@db.mongo.search.dynamic indexes every field, including the @db.encrypted field "${path}" — Atlas Search over ciphertext is impossible; use @db.mongo.search.static with @db.mongo.search.text fields`;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { dynamicSearchEncryptedMessage as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-mongo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.162",
|
|
4
4
|
"description": "Mongodb plugin for atscript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -46,19 +46,19 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@atscript/core": "^0.1.
|
|
50
|
-
"@atscript/typescript": "^0.1.
|
|
49
|
+
"@atscript/core": "^0.1.109",
|
|
50
|
+
"@atscript/typescript": "^0.1.109",
|
|
51
51
|
"@uniqu/core": "^0.1.15",
|
|
52
52
|
"mongodb": "^6.17.0",
|
|
53
53
|
"mongodb-memory-server-core": "^10.0.0",
|
|
54
|
-
"unplugin-atscript": "^0.1.
|
|
54
|
+
"unplugin-atscript": "^0.1.109"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@atscript/core": "^0.1.
|
|
58
|
-
"@atscript/typescript": "^0.1.
|
|
57
|
+
"@atscript/core": "^0.1.109",
|
|
58
|
+
"@atscript/typescript": "^0.1.109",
|
|
59
59
|
"@uniqu/core": "^0.1.15",
|
|
60
60
|
"mongodb": "^6.17.0",
|
|
61
|
-
"@atscript/db": "^0.1.
|
|
61
|
+
"@atscript/db": "^0.1.162"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"build": "vp pack",
|