@atscript/db-mongo 0.1.148 → 0.1.150
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.cjs +1 -1
- package/dist/agg.mjs +1 -1
- package/dist/index.cjs +79 -20
- package/dist/index.d.cts +12 -4
- package/dist/index.d.mts +12 -4
- package/dist/index.mjs +80 -21
- package/dist/{mongo-accumulator-BKMdRf3s.cjs → mongo-accumulator-BQBMx4GC.cjs} +15 -0
- package/dist/{mongo-accumulator-BvJNCVoc.mjs → mongo-accumulator-DYhg8usT.mjs} +16 -1
- package/package.json +7 -7
package/dist/agg.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_mongo_accumulator = require("./mongo-accumulator-
|
|
2
|
+
const require_mongo_accumulator = require("./mongo-accumulator-BQBMx4GC.cjs");
|
|
3
3
|
let _atscript_db = require("@atscript/db");
|
|
4
4
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
5
5
|
let _uniqu_core = require("@uniqu/core");
|
package/dist/agg.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as orNull, i as buildMongoFilter, l as exprToMongo, n as distinctCountExpr, s as mongoFilterStages, t as buildAccumulator, u as notNullExpr } from "./mongo-accumulator-
|
|
1
|
+
import { d as orNull, i as buildMongoFilter, l as exprToMongo, n as distinctCountExpr, s as mongoFilterStages, t as buildAccumulator, u as notNullExpr } from "./mongo-accumulator-DYhg8usT.mjs";
|
|
2
2
|
import { evaluateExpr } from "@atscript/db";
|
|
3
3
|
import { resolveAlias } from "@atscript/db/agg";
|
|
4
4
|
import { BUCKET_MAX_INSTANT, BUCKET_MIN_INSTANT } from "@uniqu/core";
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_mongo_accumulator = require("./mongo-accumulator-
|
|
2
|
+
const require_mongo_accumulator = require("./mongo-accumulator-BQBMx4GC.cjs");
|
|
3
3
|
let _atscript_db = require("@atscript/db");
|
|
4
4
|
let mongodb = require("mongodb");
|
|
5
5
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
@@ -908,10 +908,24 @@ function buildSearchStage(host, text, indexName, controls) {
|
|
|
908
908
|
const index = host.getMongoSearchIndex(indexName);
|
|
909
909
|
if (!index) return;
|
|
910
910
|
if (index.type === "vector") throw new Error("Vector indexes cannot be used with text search. Use vectorSearch() instead.");
|
|
911
|
-
if (index.type === "text")
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
911
|
+
if (index.type === "text") {
|
|
912
|
+
const n = (0, _atscript_db.searchTermInteger)(text);
|
|
913
|
+
const keys = n === void 0 ? [] : host.getNumericSearchKeys(index);
|
|
914
|
+
const hasText = Object.keys(index.fields).length > 0;
|
|
915
|
+
const equalities = keys.map((key) => ({ [key]: {
|
|
916
|
+
$eq: n,
|
|
917
|
+
$type: "number"
|
|
918
|
+
} }));
|
|
919
|
+
if (!hasText) return {
|
|
920
|
+
stage: { $match: equalities.length === 0 ? { _id: { $in: [] } } : equalities.length === 1 ? equalities[0] : { $or: equalities } },
|
|
921
|
+
classicText: false
|
|
922
|
+
};
|
|
923
|
+
const textMatch = { $text: { $search: text } };
|
|
924
|
+
return {
|
|
925
|
+
stage: { $match: equalities.length > 0 ? { $or: [textMatch, ...equalities] } : textMatch },
|
|
926
|
+
classicText: true
|
|
927
|
+
};
|
|
928
|
+
}
|
|
915
929
|
const searchIndex = index;
|
|
916
930
|
const fuzzy = resolveSearchFuzzy(searchIndex, controls);
|
|
917
931
|
const strategy = searchIndex.strategy ?? "compound";
|
|
@@ -949,6 +963,11 @@ function buildSearchStage(host, text, indexName, controls) {
|
|
|
949
963
|
for (const path of paths.autocompleteOutside) clauses.push(autocompleteOp(path));
|
|
950
964
|
for (const group of paths.arrayGroups) for (const leaf of group.autocompleteLeaves) clauses.push(embeddedAutocompleteClause(group.chain, leaf));
|
|
951
965
|
}
|
|
966
|
+
const n = (0, _atscript_db.searchTermInteger)(text);
|
|
967
|
+
if (n !== void 0) for (const path of host.getNumericSearchKeys(index)) clauses.push({ equals: {
|
|
968
|
+
path,
|
|
969
|
+
value: n
|
|
970
|
+
} });
|
|
952
971
|
let body;
|
|
953
972
|
if (clauses.length <= 1) body = clauses[0] ?? textClause();
|
|
954
973
|
else body = { compound: {
|
|
@@ -1517,7 +1536,9 @@ async function syncIndexesImpl(host) {
|
|
|
1517
1536
|
let mongoType;
|
|
1518
1537
|
if (index.type === "fulltext") {
|
|
1519
1538
|
mongoType = "text";
|
|
1520
|
-
|
|
1539
|
+
const textFields = index.fields.filter((f) => !f.integer);
|
|
1540
|
+
if (textFields.length === 0) continue;
|
|
1541
|
+
for (const f of textFields) {
|
|
1521
1542
|
fields[f.name] = "text";
|
|
1522
1543
|
weights[f.name] = f.weight ?? 1;
|
|
1523
1544
|
}
|
|
@@ -2113,13 +2134,13 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2113
2134
|
createValidator: (opts) => this._table.createValidator(opts)
|
|
2114
2135
|
};
|
|
2115
2136
|
}
|
|
2116
|
-
async nativePatch(filter, patch, ops, expectedVersion) {
|
|
2137
|
+
async nativePatch(filter, patch, ops, expectedVersion, opts) {
|
|
2117
2138
|
const mongoFilter = await this._buildCasFilter(filter, expectedVersion, "nativePatch");
|
|
2118
2139
|
if (!mongoFilter) return {
|
|
2119
2140
|
matchedCount: 0,
|
|
2120
2141
|
modifiedCount: 0
|
|
2121
2142
|
};
|
|
2122
|
-
const versionColumn = this.
|
|
2143
|
+
const versionColumn = this._versionColumnFor(opts, expectedVersion);
|
|
2123
2144
|
const effectiveOps = versionColumn !== void 0 ? {
|
|
2124
2145
|
...ops,
|
|
2125
2146
|
inc: {
|
|
@@ -2227,6 +2248,18 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2227
2248
|
for (const field of this._pendingUniqueFields) this._addMongoIndexField("unique", "__pk", meta.documentPath(field));
|
|
2228
2249
|
for (const pending of this._pendingSearchFields) this._addFieldToSearchIndex("search_text", pending.indexName, pending.field, pending.mappings);
|
|
2229
2250
|
this._pendingSearchFields = [];
|
|
2251
|
+
const textBearing = [...this._table.indexes.values()].filter((i) => i.type === "fulltext" && i.fields.some((f) => !f.integer));
|
|
2252
|
+
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.`);
|
|
2253
|
+
for (const fulltext of this._table.indexes.values()) {
|
|
2254
|
+
if (fulltext.type !== "fulltext" || !fulltext.fields.some((f) => f.integer)) continue;
|
|
2255
|
+
const logical = this._indexLogicalPaths(fulltext);
|
|
2256
|
+
for (const index of this._mongoIndexes.values()) {
|
|
2257
|
+
if (index.type !== "search_text") continue;
|
|
2258
|
+
fulltext.fields.forEach((f, i) => {
|
|
2259
|
+
if (f.integer) this._addFieldToSearchIndex("search_text", index.name, logical[i], [{ type: "number" }]);
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2230
2263
|
for (const [key, value] of this._vectorFilters.entries()) {
|
|
2231
2264
|
const index = this._mongoIndexes.get(key);
|
|
2232
2265
|
if (index && index.type === "vector") index.definition.fields?.push({
|
|
@@ -2250,13 +2283,19 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2250
2283
|
this._table.flatMap;
|
|
2251
2284
|
this._searchIndexesMap = /* @__PURE__ */ new Map();
|
|
2252
2285
|
let defaultIndex;
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2286
|
+
const fulltexts = [...this._table.indexes.values()].filter((i) => i.type === "fulltext");
|
|
2287
|
+
const defaultFulltext = (0, _atscript_db.defaultFulltextIndex)(fulltexts);
|
|
2288
|
+
for (const index of fulltexts) {
|
|
2289
|
+
const converted = {
|
|
2290
|
+
key: index.key,
|
|
2291
|
+
name: index.name,
|
|
2292
|
+
type: "text",
|
|
2293
|
+
fields: Object.fromEntries(index.fields.filter((f) => !f.integer).map((f) => [f.name, "text"])),
|
|
2294
|
+
weights: Object.fromEntries(index.fields.filter((f) => f.weight && !f.integer).map((f) => [f.name, f.weight]))
|
|
2295
|
+
};
|
|
2296
|
+
if (index === defaultFulltext) defaultIndex = converted;
|
|
2297
|
+
else if (!this._searchIndexesMap.has(index.name)) this._searchIndexesMap.set(index.name, converted);
|
|
2298
|
+
}
|
|
2260
2299
|
for (const index of this._mongoIndexes.values()) switch (index.type) {
|
|
2261
2300
|
case "text":
|
|
2262
2301
|
if (!defaultIndex) defaultIndex = index;
|
|
@@ -2277,6 +2316,27 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2277
2316
|
}
|
|
2278
2317
|
return this._searchIndexesMap;
|
|
2279
2318
|
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Stored paths of the integer members matched by exact number next to a
|
|
2321
|
+
* text search on `index` (since 0.1.150). A classic text index contributes
|
|
2322
|
+
* the integer members of its own `@db.index.fulltext` (per index, as on the
|
|
2323
|
+
* SQL adapters); an Atlas index the integer members it maps (every fulltext
|
|
2324
|
+
* integer member is mapped, so they are in its reported `paths`).
|
|
2325
|
+
*/
|
|
2326
|
+
getNumericSearchKeys(index) {
|
|
2327
|
+
this._table.flatMap;
|
|
2328
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2329
|
+
if (index.type === "text") {
|
|
2330
|
+
const source = this._table.indexes.get(index.key);
|
|
2331
|
+
if (source?.type === "fulltext") {
|
|
2332
|
+
for (const f of source.fields) if (f.integer) keys.add(f.name);
|
|
2333
|
+
}
|
|
2334
|
+
} else for (const fulltext of this._table.indexes.values()) {
|
|
2335
|
+
if (fulltext.type !== "fulltext") continue;
|
|
2336
|
+
for (const f of fulltext.fields) if (f.integer) keys.add(f.name);
|
|
2337
|
+
}
|
|
2338
|
+
return [...keys];
|
|
2339
|
+
}
|
|
2280
2340
|
/** Returns a specific MongoDB search index by name. */
|
|
2281
2341
|
getMongoSearchIndex(name = DEFAULT_INDEX_NAME) {
|
|
2282
2342
|
return this.getMongoSearchIndexes().get(name);
|
|
@@ -2620,13 +2680,13 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2620
2680
|
...this._getSessionOpts()
|
|
2621
2681
|
});
|
|
2622
2682
|
}
|
|
2623
|
-
async updateOne(filter, data, ops, expectedVersion) {
|
|
2683
|
+
async updateOne(filter, data, ops, expectedVersion, opts) {
|
|
2624
2684
|
const mongoFilter = await this._buildCasFilter(filter, expectedVersion, "updateOne");
|
|
2625
2685
|
if (!mongoFilter) return {
|
|
2626
2686
|
matchedCount: 0,
|
|
2627
2687
|
modifiedCount: 0
|
|
2628
2688
|
};
|
|
2629
|
-
const updateDoc = buildMongoUpdateDoc(data, ops, this.
|
|
2689
|
+
const updateDoc = buildMongoUpdateDoc(data, ops, this._versionColumnFor(opts, expectedVersion));
|
|
2630
2690
|
this._log("updateOne", mongoFilter, updateDoc);
|
|
2631
2691
|
return this._wrapUpdate(() => this.collection.updateOne(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
2632
2692
|
}
|
|
@@ -2653,9 +2713,8 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2653
2713
|
this._log("deleteOne", mongoFilter);
|
|
2654
2714
|
return { deletedCount: (await this.collection.deleteOne(mongoFilter, this._getSessionOpts())).deletedCount };
|
|
2655
2715
|
}
|
|
2656
|
-
async updateMany(filter, data, ops) {
|
|
2657
|
-
const
|
|
2658
|
-
const updateDoc = buildMongoUpdateDoc(data, ops, versionColumn);
|
|
2716
|
+
async updateMany(filter, data, ops, opts) {
|
|
2717
|
+
const updateDoc = buildMongoUpdateDoc(data, ops, this._versionColumnFor(opts));
|
|
2659
2718
|
return this._updateMatching(filter, (mongoFilter) => {
|
|
2660
2719
|
this._log("updateMany", mongoFilter, updateDoc);
|
|
2661
2720
|
return this._wrapUpdate(() => this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts()));
|
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, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TPrimaryKeyChange, TSearchIndexInfo, TSyncColumnResult, TTableResolver, TViewCapability, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
|
|
2
|
+
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbCollation, TDbDeleteResult, TDbFieldMeta, TDbForeignKey, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbRelation, TDbUpdateOptions, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TPrimaryKeyChange, TSearchIndexInfo, TSyncColumnResult, TTableResolver, 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";
|
|
@@ -402,13 +402,21 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
402
402
|
loadRelations(rows: Array<Record<string, unknown>>, withRelations: WithRelation[], relations: ReadonlyMap<string, TDbRelation>, foreignKeys: ReadonlyMap<string, TDbForeignKey>, tableResolver?: TTableResolver): Promise<void>;
|
|
403
403
|
/** Returns the context object used by CollectionPatcher. */
|
|
404
404
|
getPatcherContext(): TCollectionPatcherContext;
|
|
405
|
-
nativePatch(filter: FilterExpr, patch: unknown, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
405
|
+
nativePatch(filter: FilterExpr, patch: unknown, ops?: TFieldOps, expectedVersion?: number, opts?: TDbUpdateOptions): Promise<TDbUpdateResult>;
|
|
406
406
|
onBeforeFlatten(_type: unknown): void;
|
|
407
407
|
onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
408
408
|
getMetadataOverrides(meta: TableMetadata): TMetadataOverrides;
|
|
409
409
|
onAfterFlatten(): void;
|
|
410
410
|
/** Returns MongoDB-specific search index map (internal). */
|
|
411
411
|
getMongoSearchIndexes(): Map<string, TMongoIndex>;
|
|
412
|
+
/**
|
|
413
|
+
* Stored paths of the integer members matched by exact number next to a
|
|
414
|
+
* text search on `index` (since 0.1.150). A classic text index contributes
|
|
415
|
+
* the integer members of its own `@db.index.fulltext` (per index, as on the
|
|
416
|
+
* SQL adapters); an Atlas index the integer members it maps (every fulltext
|
|
417
|
+
* integer member is mapped, so they are in its reported `paths`).
|
|
418
|
+
*/
|
|
419
|
+
getNumericSearchKeys(index: TMongoIndex): readonly string[];
|
|
412
420
|
/** Returns a specific MongoDB search index by name. */
|
|
413
421
|
getMongoSearchIndex(name?: string): TMongoIndex | undefined;
|
|
414
422
|
/** Returns the default similarity threshold for a vector index (from @db.search.vector.threshold). */
|
|
@@ -515,10 +523,10 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
515
523
|
findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
|
516
524
|
findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
517
525
|
count(query: DbQuery): Promise<number>;
|
|
518
|
-
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
526
|
+
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number, opts?: TDbUpdateOptions): Promise<TDbUpdateResult>;
|
|
519
527
|
replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
520
528
|
deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
521
|
-
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
529
|
+
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, opts?: TDbUpdateOptions): Promise<TDbUpdateResult>;
|
|
522
530
|
replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
523
531
|
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
524
532
|
clearCollectionCache(): void;
|
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, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TPrimaryKeyChange, TSearchIndexInfo, TSyncColumnResult, TTableResolver, TViewCapability, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
|
|
2
|
+
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbCollation, TDbDeleteResult, TDbFieldMeta, TDbForeignKey, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbRelation, TDbUpdateOptions, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TPrimaryKeyChange, TSearchIndexInfo, TSyncColumnResult, TTableResolver, 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";
|
|
@@ -402,13 +402,21 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
402
402
|
loadRelations(rows: Array<Record<string, unknown>>, withRelations: WithRelation[], relations: ReadonlyMap<string, TDbRelation>, foreignKeys: ReadonlyMap<string, TDbForeignKey>, tableResolver?: TTableResolver): Promise<void>;
|
|
403
403
|
/** Returns the context object used by CollectionPatcher. */
|
|
404
404
|
getPatcherContext(): TCollectionPatcherContext;
|
|
405
|
-
nativePatch(filter: FilterExpr, patch: unknown, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
405
|
+
nativePatch(filter: FilterExpr, patch: unknown, ops?: TFieldOps, expectedVersion?: number, opts?: TDbUpdateOptions): Promise<TDbUpdateResult>;
|
|
406
406
|
onBeforeFlatten(_type: unknown): void;
|
|
407
407
|
onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
408
408
|
getMetadataOverrides(meta: TableMetadata): TMetadataOverrides;
|
|
409
409
|
onAfterFlatten(): void;
|
|
410
410
|
/** Returns MongoDB-specific search index map (internal). */
|
|
411
411
|
getMongoSearchIndexes(): Map<string, TMongoIndex>;
|
|
412
|
+
/**
|
|
413
|
+
* Stored paths of the integer members matched by exact number next to a
|
|
414
|
+
* text search on `index` (since 0.1.150). A classic text index contributes
|
|
415
|
+
* the integer members of its own `@db.index.fulltext` (per index, as on the
|
|
416
|
+
* SQL adapters); an Atlas index the integer members it maps (every fulltext
|
|
417
|
+
* integer member is mapped, so they are in its reported `paths`).
|
|
418
|
+
*/
|
|
419
|
+
getNumericSearchKeys(index: TMongoIndex): readonly string[];
|
|
412
420
|
/** Returns a specific MongoDB search index by name. */
|
|
413
421
|
getMongoSearchIndex(name?: string): TMongoIndex | undefined;
|
|
414
422
|
/** Returns the default similarity threshold for a vector index (from @db.search.vector.threshold). */
|
|
@@ -515,10 +523,10 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
515
523
|
findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
|
516
524
|
findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
517
525
|
count(query: DbQuery): Promise<number>;
|
|
518
|
-
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
526
|
+
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number, opts?: TDbUpdateOptions): Promise<TDbUpdateResult>;
|
|
519
527
|
replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
520
528
|
deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
521
|
-
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
529
|
+
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, opts?: TDbUpdateOptions): Promise<TDbUpdateResult>;
|
|
522
530
|
replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
523
531
|
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
524
532
|
clearCollectionCache(): void;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as buildMongoQuery, c as planStages, d as orNull, f as queryNodeToExpr, i as buildMongoFilter, l as exprToMongo, n as distinctCountExpr, o as collationOfAdapter, p as correlate, r as REL_FILTER_TEMP_PREFIX, s as mongoFilterStages, t as buildAccumulator } from "./mongo-accumulator-
|
|
2
|
-
import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, ALL_VIEW_CAPABILITIES, BaseDbAdapter, DbError, DbSpace, andFilters, bucketTimeZoneUnavailable, computeInsights, containsRelationFilter, createFailureCollector, geoIndexNotFoundMessage, getDbFieldOp, getKeyProps, getPath, isAtscriptDbView, isFieldRef, relationStaticFilter, resolveDesignType, searchIndexNotFoundMessage, tableNameOf, translateQueryTree, uniqueKeyTuple, vectorIndexNotFoundMessage } from "@atscript/db";
|
|
1
|
+
import { a as buildMongoQuery, c as planStages, d as orNull, f as queryNodeToExpr, i as buildMongoFilter, l as exprToMongo, n as distinctCountExpr, o as collationOfAdapter, p as correlate, r as REL_FILTER_TEMP_PREFIX, s as mongoFilterStages, t as buildAccumulator } from "./mongo-accumulator-DYhg8usT.mjs";
|
|
2
|
+
import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, ALL_VIEW_CAPABILITIES, BaseDbAdapter, DbError, DbSpace, andFilters, bucketTimeZoneUnavailable, computeInsights, containsRelationFilter, createFailureCollector, defaultFulltextIndex, geoIndexNotFoundMessage, getDbFieldOp, getKeyProps, getPath, isAtscriptDbView, isFieldRef, relationStaticFilter, resolveDesignType, searchIndexNotFoundMessage, searchTermInteger, tableNameOf, translateQueryTree, uniqueKeyTuple, vectorIndexNotFoundMessage } from "@atscript/db";
|
|
3
3
|
import { MongoBulkWriteError, MongoClient, MongoServerError, ObjectId } from "mongodb";
|
|
4
4
|
import { resolveAggregateSearch } from "@atscript/db/agg";
|
|
5
5
|
//#region src/lib/path-utils.ts
|
|
@@ -907,10 +907,24 @@ function buildSearchStage(host, text, indexName, controls) {
|
|
|
907
907
|
const index = host.getMongoSearchIndex(indexName);
|
|
908
908
|
if (!index) return;
|
|
909
909
|
if (index.type === "vector") throw new Error("Vector indexes cannot be used with text search. Use vectorSearch() instead.");
|
|
910
|
-
if (index.type === "text")
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
910
|
+
if (index.type === "text") {
|
|
911
|
+
const n = searchTermInteger(text);
|
|
912
|
+
const keys = n === void 0 ? [] : host.getNumericSearchKeys(index);
|
|
913
|
+
const hasText = Object.keys(index.fields).length > 0;
|
|
914
|
+
const equalities = keys.map((key) => ({ [key]: {
|
|
915
|
+
$eq: n,
|
|
916
|
+
$type: "number"
|
|
917
|
+
} }));
|
|
918
|
+
if (!hasText) return {
|
|
919
|
+
stage: { $match: equalities.length === 0 ? { _id: { $in: [] } } : equalities.length === 1 ? equalities[0] : { $or: equalities } },
|
|
920
|
+
classicText: false
|
|
921
|
+
};
|
|
922
|
+
const textMatch = { $text: { $search: text } };
|
|
923
|
+
return {
|
|
924
|
+
stage: { $match: equalities.length > 0 ? { $or: [textMatch, ...equalities] } : textMatch },
|
|
925
|
+
classicText: true
|
|
926
|
+
};
|
|
927
|
+
}
|
|
914
928
|
const searchIndex = index;
|
|
915
929
|
const fuzzy = resolveSearchFuzzy(searchIndex, controls);
|
|
916
930
|
const strategy = searchIndex.strategy ?? "compound";
|
|
@@ -948,6 +962,11 @@ function buildSearchStage(host, text, indexName, controls) {
|
|
|
948
962
|
for (const path of paths.autocompleteOutside) clauses.push(autocompleteOp(path));
|
|
949
963
|
for (const group of paths.arrayGroups) for (const leaf of group.autocompleteLeaves) clauses.push(embeddedAutocompleteClause(group.chain, leaf));
|
|
950
964
|
}
|
|
965
|
+
const n = searchTermInteger(text);
|
|
966
|
+
if (n !== void 0) for (const path of host.getNumericSearchKeys(index)) clauses.push({ equals: {
|
|
967
|
+
path,
|
|
968
|
+
value: n
|
|
969
|
+
} });
|
|
951
970
|
let body;
|
|
952
971
|
if (clauses.length <= 1) body = clauses[0] ?? textClause();
|
|
953
972
|
else body = { compound: {
|
|
@@ -1516,7 +1535,9 @@ async function syncIndexesImpl(host) {
|
|
|
1516
1535
|
let mongoType;
|
|
1517
1536
|
if (index.type === "fulltext") {
|
|
1518
1537
|
mongoType = "text";
|
|
1519
|
-
|
|
1538
|
+
const textFields = index.fields.filter((f) => !f.integer);
|
|
1539
|
+
if (textFields.length === 0) continue;
|
|
1540
|
+
for (const f of textFields) {
|
|
1520
1541
|
fields[f.name] = "text";
|
|
1521
1542
|
weights[f.name] = f.weight ?? 1;
|
|
1522
1543
|
}
|
|
@@ -2112,13 +2133,13 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2112
2133
|
createValidator: (opts) => this._table.createValidator(opts)
|
|
2113
2134
|
};
|
|
2114
2135
|
}
|
|
2115
|
-
async nativePatch(filter, patch, ops, expectedVersion) {
|
|
2136
|
+
async nativePatch(filter, patch, ops, expectedVersion, opts) {
|
|
2116
2137
|
const mongoFilter = await this._buildCasFilter(filter, expectedVersion, "nativePatch");
|
|
2117
2138
|
if (!mongoFilter) return {
|
|
2118
2139
|
matchedCount: 0,
|
|
2119
2140
|
modifiedCount: 0
|
|
2120
2141
|
};
|
|
2121
|
-
const versionColumn = this.
|
|
2142
|
+
const versionColumn = this._versionColumnFor(opts, expectedVersion);
|
|
2122
2143
|
const effectiveOps = versionColumn !== void 0 ? {
|
|
2123
2144
|
...ops,
|
|
2124
2145
|
inc: {
|
|
@@ -2226,6 +2247,18 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2226
2247
|
for (const field of this._pendingUniqueFields) this._addMongoIndexField("unique", "__pk", meta.documentPath(field));
|
|
2227
2248
|
for (const pending of this._pendingSearchFields) this._addFieldToSearchIndex("search_text", pending.indexName, pending.field, pending.mappings);
|
|
2228
2249
|
this._pendingSearchFields = [];
|
|
2250
|
+
const textBearing = [...this._table.indexes.values()].filter((i) => i.type === "fulltext" && i.fields.some((f) => !f.integer));
|
|
2251
|
+
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.`);
|
|
2252
|
+
for (const fulltext of this._table.indexes.values()) {
|
|
2253
|
+
if (fulltext.type !== "fulltext" || !fulltext.fields.some((f) => f.integer)) continue;
|
|
2254
|
+
const logical = this._indexLogicalPaths(fulltext);
|
|
2255
|
+
for (const index of this._mongoIndexes.values()) {
|
|
2256
|
+
if (index.type !== "search_text") continue;
|
|
2257
|
+
fulltext.fields.forEach((f, i) => {
|
|
2258
|
+
if (f.integer) this._addFieldToSearchIndex("search_text", index.name, logical[i], [{ type: "number" }]);
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2229
2262
|
for (const [key, value] of this._vectorFilters.entries()) {
|
|
2230
2263
|
const index = this._mongoIndexes.get(key);
|
|
2231
2264
|
if (index && index.type === "vector") index.definition.fields?.push({
|
|
@@ -2249,13 +2282,19 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2249
2282
|
this._table.flatMap;
|
|
2250
2283
|
this._searchIndexesMap = /* @__PURE__ */ new Map();
|
|
2251
2284
|
let defaultIndex;
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2285
|
+
const fulltexts = [...this._table.indexes.values()].filter((i) => i.type === "fulltext");
|
|
2286
|
+
const defaultFulltext = defaultFulltextIndex(fulltexts);
|
|
2287
|
+
for (const index of fulltexts) {
|
|
2288
|
+
const converted = {
|
|
2289
|
+
key: index.key,
|
|
2290
|
+
name: index.name,
|
|
2291
|
+
type: "text",
|
|
2292
|
+
fields: Object.fromEntries(index.fields.filter((f) => !f.integer).map((f) => [f.name, "text"])),
|
|
2293
|
+
weights: Object.fromEntries(index.fields.filter((f) => f.weight && !f.integer).map((f) => [f.name, f.weight]))
|
|
2294
|
+
};
|
|
2295
|
+
if (index === defaultFulltext) defaultIndex = converted;
|
|
2296
|
+
else if (!this._searchIndexesMap.has(index.name)) this._searchIndexesMap.set(index.name, converted);
|
|
2297
|
+
}
|
|
2259
2298
|
for (const index of this._mongoIndexes.values()) switch (index.type) {
|
|
2260
2299
|
case "text":
|
|
2261
2300
|
if (!defaultIndex) defaultIndex = index;
|
|
@@ -2276,6 +2315,27 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2276
2315
|
}
|
|
2277
2316
|
return this._searchIndexesMap;
|
|
2278
2317
|
}
|
|
2318
|
+
/**
|
|
2319
|
+
* Stored paths of the integer members matched by exact number next to a
|
|
2320
|
+
* text search on `index` (since 0.1.150). A classic text index contributes
|
|
2321
|
+
* the integer members of its own `@db.index.fulltext` (per index, as on the
|
|
2322
|
+
* SQL adapters); an Atlas index the integer members it maps (every fulltext
|
|
2323
|
+
* integer member is mapped, so they are in its reported `paths`).
|
|
2324
|
+
*/
|
|
2325
|
+
getNumericSearchKeys(index) {
|
|
2326
|
+
this._table.flatMap;
|
|
2327
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2328
|
+
if (index.type === "text") {
|
|
2329
|
+
const source = this._table.indexes.get(index.key);
|
|
2330
|
+
if (source?.type === "fulltext") {
|
|
2331
|
+
for (const f of source.fields) if (f.integer) keys.add(f.name);
|
|
2332
|
+
}
|
|
2333
|
+
} else for (const fulltext of this._table.indexes.values()) {
|
|
2334
|
+
if (fulltext.type !== "fulltext") continue;
|
|
2335
|
+
for (const f of fulltext.fields) if (f.integer) keys.add(f.name);
|
|
2336
|
+
}
|
|
2337
|
+
return [...keys];
|
|
2338
|
+
}
|
|
2279
2339
|
/** Returns a specific MongoDB search index by name. */
|
|
2280
2340
|
getMongoSearchIndex(name = DEFAULT_INDEX_NAME) {
|
|
2281
2341
|
return this.getMongoSearchIndexes().get(name);
|
|
@@ -2619,13 +2679,13 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2619
2679
|
...this._getSessionOpts()
|
|
2620
2680
|
});
|
|
2621
2681
|
}
|
|
2622
|
-
async updateOne(filter, data, ops, expectedVersion) {
|
|
2682
|
+
async updateOne(filter, data, ops, expectedVersion, opts) {
|
|
2623
2683
|
const mongoFilter = await this._buildCasFilter(filter, expectedVersion, "updateOne");
|
|
2624
2684
|
if (!mongoFilter) return {
|
|
2625
2685
|
matchedCount: 0,
|
|
2626
2686
|
modifiedCount: 0
|
|
2627
2687
|
};
|
|
2628
|
-
const updateDoc = buildMongoUpdateDoc(data, ops, this.
|
|
2688
|
+
const updateDoc = buildMongoUpdateDoc(data, ops, this._versionColumnFor(opts, expectedVersion));
|
|
2629
2689
|
this._log("updateOne", mongoFilter, updateDoc);
|
|
2630
2690
|
return this._wrapUpdate(() => this.collection.updateOne(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
2631
2691
|
}
|
|
@@ -2652,9 +2712,8 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2652
2712
|
this._log("deleteOne", mongoFilter);
|
|
2653
2713
|
return { deletedCount: (await this.collection.deleteOne(mongoFilter, this._getSessionOpts())).deletedCount };
|
|
2654
2714
|
}
|
|
2655
|
-
async updateMany(filter, data, ops) {
|
|
2656
|
-
const
|
|
2657
|
-
const updateDoc = buildMongoUpdateDoc(data, ops, versionColumn);
|
|
2715
|
+
async updateMany(filter, data, ops, opts) {
|
|
2716
|
+
const updateDoc = buildMongoUpdateDoc(data, ops, this._versionColumnFor(opts));
|
|
2658
2717
|
return this._updateMatching(filter, (mongoFilter) => {
|
|
2659
2718
|
this._log("updateMany", mongoFilter, updateDoc);
|
|
2660
2719
|
return this._wrapUpdate(() => this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
@@ -195,6 +195,20 @@ const mongoVisitor = {
|
|
|
195
195
|
$options: flags
|
|
196
196
|
} } : { [field]: { $regex: pattern } };
|
|
197
197
|
}
|
|
198
|
+
if (op === _atscript_db.INTEGER_REGEX_OP) {
|
|
199
|
+
const { pattern, flags } = parseRegexString(value);
|
|
200
|
+
const options = flags.replace(/[^imsx]/g, "");
|
|
201
|
+
return { $expr: { $regexMatch: {
|
|
202
|
+
input: { $toString: { $convert: {
|
|
203
|
+
input: `$${field}`,
|
|
204
|
+
to: "long",
|
|
205
|
+
onError: null,
|
|
206
|
+
onNull: null
|
|
207
|
+
} } },
|
|
208
|
+
regex: pattern,
|
|
209
|
+
...options ? { options } : {}
|
|
210
|
+
} } };
|
|
211
|
+
}
|
|
198
212
|
if (op === "$geoWithin") {
|
|
199
213
|
const { center, radius } = value;
|
|
200
214
|
return { [field]: { $geoWithin: { $centerSphere: [center, radius / EARTH_RADIUS_M] } } };
|
|
@@ -262,6 +276,7 @@ function exactNocase(value) {
|
|
|
262
276
|
/** Operators whose result never depends on a string collation. */
|
|
263
277
|
const COLLATION_FREE_OPS = new Set([
|
|
264
278
|
"$regex",
|
|
279
|
+
_atscript_db.INTEGER_REGEX_OP,
|
|
265
280
|
"$exists",
|
|
266
281
|
"$geoWithin"
|
|
267
282
|
]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DbError, andFilters, containsRelationFilter, isFieldRef, isResolvedRelationFilter, walkFilter } from "@atscript/db";
|
|
1
|
+
import { DbError, INTEGER_REGEX_OP, andFilters, containsRelationFilter, isFieldRef, isResolvedRelationFilter, walkFilter } from "@atscript/db";
|
|
2
2
|
import { BSONRegExp } from "mongodb";
|
|
3
3
|
import { assertAggregateFn } from "@atscript/db/agg";
|
|
4
4
|
//#region src/lib/lookup-join.ts
|
|
@@ -195,6 +195,20 @@ const mongoVisitor = {
|
|
|
195
195
|
$options: flags
|
|
196
196
|
} } : { [field]: { $regex: pattern } };
|
|
197
197
|
}
|
|
198
|
+
if (op === INTEGER_REGEX_OP) {
|
|
199
|
+
const { pattern, flags } = parseRegexString(value);
|
|
200
|
+
const options = flags.replace(/[^imsx]/g, "");
|
|
201
|
+
return { $expr: { $regexMatch: {
|
|
202
|
+
input: { $toString: { $convert: {
|
|
203
|
+
input: `$${field}`,
|
|
204
|
+
to: "long",
|
|
205
|
+
onError: null,
|
|
206
|
+
onNull: null
|
|
207
|
+
} } },
|
|
208
|
+
regex: pattern,
|
|
209
|
+
...options ? { options } : {}
|
|
210
|
+
} } };
|
|
211
|
+
}
|
|
198
212
|
if (op === "$geoWithin") {
|
|
199
213
|
const { center, radius } = value;
|
|
200
214
|
return { [field]: { $geoWithin: { $centerSphere: [center, radius / EARTH_RADIUS_M] } } };
|
|
@@ -262,6 +276,7 @@ function exactNocase(value) {
|
|
|
262
276
|
/** Operators whose result never depends on a string collation. */
|
|
263
277
|
const COLLATION_FREE_OPS = new Set([
|
|
264
278
|
"$regex",
|
|
279
|
+
INTEGER_REGEX_OP,
|
|
265
280
|
"$exists",
|
|
266
281
|
"$geoWithin"
|
|
267
282
|
]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-mongo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.150",
|
|
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.101",
|
|
50
|
+
"@atscript/typescript": "^0.1.101",
|
|
51
51
|
"@uniqu/core": "^0.1.13",
|
|
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.101"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@atscript/core": "^0.1.
|
|
58
|
-
"@atscript/typescript": "^0.1.
|
|
57
|
+
"@atscript/core": "^0.1.101",
|
|
58
|
+
"@atscript/typescript": "^0.1.101",
|
|
59
59
|
"@uniqu/core": "^0.1.13",
|
|
60
60
|
"mongodb": "^6.17.0",
|
|
61
|
-
"@atscript/db": "^0.1.
|
|
61
|
+
"@atscript/db": "^0.1.150"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"postinstall": "asc -f dts",
|