@atscript/db-mongo 0.1.140 → 0.1.141
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 +40 -45
- package/dist/index.d.cts +5 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.mjs +40 -45
- package/dist/plugin.cjs +4 -0
- package/dist/plugin.mjs +4 -0
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -1033,7 +1033,10 @@ function joinedAs(table) {
|
|
|
1033
1033
|
* pipeline paths (SQL null semantics — `queryNodeToExpr`).
|
|
1034
1034
|
*
|
|
1035
1035
|
* All field paths are PHYSICAL document paths (`@db.column` renames), joined
|
|
1036
|
-
* tables live under `__joined_<
|
|
1036
|
+
* tables live under `__joined_<scope>` — the physical collection, or the
|
|
1037
|
+
* `@db.alias` type name for an aliased join (`__joined_Manager`), whose
|
|
1038
|
+
* `$lookup.from` stays the physical collection (since 0.1.141).
|
|
1039
|
+
* The entry collection and a `$lookup.from` may be views. A column whose source may be missing
|
|
1037
1040
|
* (`nullable` mapping) reads as null; every other column is a plain path, so
|
|
1038
1041
|
* a `$match` / `$sort` on the view can still push down to an index.
|
|
1039
1042
|
* @since 0.1.136
|
|
@@ -1052,10 +1055,10 @@ function buildViewPipeline(view) {
|
|
|
1052
1055
|
for (const join of plan.joins) {
|
|
1053
1056
|
pipeline.push(buildLookup(view, join, prefixes));
|
|
1054
1057
|
pipeline.push({ $unwind: {
|
|
1055
|
-
path: `$${joinedAs(join.
|
|
1058
|
+
path: `$${joinedAs(join.scope)}`,
|
|
1056
1059
|
preserveNullAndEmptyArrays: join.kind === "left"
|
|
1057
1060
|
} });
|
|
1058
|
-
prefixes.set(join.
|
|
1061
|
+
prefixes.set(join.scope, `${joinedAs(join.scope)}.`);
|
|
1059
1062
|
}
|
|
1060
1063
|
if (plan.filter) pipeline.push({ $match: viewMatch(plan.filter, pathOf) });
|
|
1061
1064
|
/** A column's source field as an aggregation operand. */
|
|
@@ -1099,7 +1102,8 @@ function buildViewPipeline(view) {
|
|
|
1099
1102
|
* `$match: { $expr }`, SQL null semantics).
|
|
1100
1103
|
*/
|
|
1101
1104
|
function buildLookup(view, join, outer) {
|
|
1102
|
-
const
|
|
1105
|
+
const scope = join.scope;
|
|
1106
|
+
const as = joinedAs(scope);
|
|
1103
1107
|
const simple = simpleJoinFields(view, join, outer);
|
|
1104
1108
|
if (simple) return { $lookup: {
|
|
1105
1109
|
from: join.targetTable,
|
|
@@ -1110,9 +1114,9 @@ function buildLookup(view, join, outer) {
|
|
|
1110
1114
|
const varByPath = /* @__PURE__ */ new Map();
|
|
1111
1115
|
const pathOf = (ref) => {
|
|
1112
1116
|
const { table, source } = view.resolveRefSource(ref);
|
|
1113
|
-
if (table ===
|
|
1117
|
+
if (table === scope) return `$${source.column}`;
|
|
1114
1118
|
const prefix = outer.get(table);
|
|
1115
|
-
if (prefix === void 0) throw new Error(`View "${view.tableName}": the join on "${
|
|
1119
|
+
if (prefix === void 0) throw new Error(`View "${view.tableName}": the join on "${scope}" references "${table}", which is not joined before it`);
|
|
1116
1120
|
const outerPath = prefix + source.column;
|
|
1117
1121
|
let varName = varByPath.get(outerPath);
|
|
1118
1122
|
if (!varName) {
|
|
@@ -1141,14 +1145,15 @@ function simpleJoinFields(view, join, outer) {
|
|
|
1141
1145
|
if ("$or" in node || "$not" in node || "$and" in node) return void 0;
|
|
1142
1146
|
const comp = node;
|
|
1143
1147
|
if (comp.op !== "$eq" || !(0, _atscript_db.isFieldRef)(comp.right)) return void 0;
|
|
1148
|
+
const scope = join.scope;
|
|
1144
1149
|
const left = view.resolveRefSource(comp.left);
|
|
1145
1150
|
const right = view.resolveRefSource(comp.right);
|
|
1146
1151
|
let target;
|
|
1147
1152
|
let local;
|
|
1148
|
-
if (left.table ===
|
|
1153
|
+
if (left.table === scope && right.table !== scope) {
|
|
1149
1154
|
target = left;
|
|
1150
1155
|
local = right;
|
|
1151
|
-
} else if (right.table ===
|
|
1156
|
+
} else if (right.table === scope && left.table !== scope) {
|
|
1152
1157
|
target = right;
|
|
1153
1158
|
local = left;
|
|
1154
1159
|
} else return;
|
|
@@ -1895,7 +1900,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1895
1900
|
}
|
|
1896
1901
|
async nativePatch(filter, patch, ops, expectedVersion) {
|
|
1897
1902
|
const mongoFilter = this._buildCasFilter(filter, expectedVersion, "nativePatch");
|
|
1898
|
-
const versionColumn = this._table.
|
|
1903
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
1899
1904
|
const effectiveOps = versionColumn !== void 0 ? {
|
|
1900
1905
|
...ops,
|
|
1901
1906
|
inc: {
|
|
@@ -1905,14 +1910,10 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1905
1910
|
} : ops;
|
|
1906
1911
|
const { updateFilter, updateOptions } = new CollectionPatcher(this.getPatcherContext(), patch, effectiveOps).preparePatch();
|
|
1907
1912
|
this._log("updateOne (patch)", mongoFilter, updateFilter);
|
|
1908
|
-
|
|
1913
|
+
return this._wrapUpdate(() => this.collection.updateOne(mongoFilter, updateFilter, {
|
|
1909
1914
|
...updateOptions,
|
|
1910
1915
|
...this._getSessionOpts()
|
|
1911
|
-
});
|
|
1912
|
-
return {
|
|
1913
|
-
matchedCount: result.matchedCount,
|
|
1914
|
-
modifiedCount: result.modifiedCount
|
|
1915
|
-
};
|
|
1916
|
+
}));
|
|
1916
1917
|
}
|
|
1917
1918
|
onBeforeFlatten(_type) {
|
|
1918
1919
|
const typeMeta = _type.metadata;
|
|
@@ -2144,7 +2145,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2144
2145
|
* rather than silently dropping the CAS predicate.
|
|
2145
2146
|
*/
|
|
2146
2147
|
_buildCasFilter(filter, expectedVersion, op) {
|
|
2147
|
-
const versionColumn = this._table.
|
|
2148
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
2148
2149
|
if (expectedVersion !== void 0 && versionColumn === void 0) throw new Error(`${op}: expectedVersion requires versionColumn`);
|
|
2149
2150
|
const mongoFilter = require_mongo_accumulator.buildMongoFilter(filter);
|
|
2150
2151
|
if (expectedVersion !== void 0 && versionColumn !== void 0) mongoFilter[versionColumn] = expectedVersion;
|
|
@@ -2152,7 +2153,9 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2152
2153
|
}
|
|
2153
2154
|
/**
|
|
2154
2155
|
* Wraps an async operation to catch MongoDB duplicate key errors
|
|
2155
|
-
* (code 11000) and rethrow as structured `DbError
|
|
2156
|
+
* (code 11000) and rethrow as structured `DbError` — every write path
|
|
2157
|
+
* (insert, replace, update, patch, their `*Many` variants) goes through it,
|
|
2158
|
+
* so a unique-index violation is a `CONFLICT` whichever statement hit it.
|
|
2156
2159
|
*/
|
|
2157
2160
|
async _wrapDuplicateKeyError(fn) {
|
|
2158
2161
|
try {
|
|
@@ -2165,8 +2168,16 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2165
2168
|
throw error;
|
|
2166
2169
|
}
|
|
2167
2170
|
}
|
|
2171
|
+
/** An update-shaped write under {@link _wrapDuplicateKeyError}, reduced to `TDbUpdateResult`. */
|
|
2172
|
+
async _wrapUpdate(fn) {
|
|
2173
|
+
const { matchedCount, modifiedCount } = await this._wrapDuplicateKeyError(fn);
|
|
2174
|
+
return {
|
|
2175
|
+
matchedCount,
|
|
2176
|
+
modifiedCount
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2168
2179
|
async insertOne(data) {
|
|
2169
|
-
const versionColumn = this._table.
|
|
2180
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
2170
2181
|
if (versionColumn !== void 0 && !(versionColumn in data)) data[versionColumn] = 0;
|
|
2171
2182
|
if (this._incrementFields.size > 0) {
|
|
2172
2183
|
const fields = this._fieldsNeedingIncrement(data);
|
|
@@ -2180,7 +2191,7 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2180
2191
|
return { insertedId: objectIdFromStorage(this._resolveInsertedId(data, result.insertedId)) };
|
|
2181
2192
|
}
|
|
2182
2193
|
async insertMany(data) {
|
|
2183
|
-
const versionColumn = this._table.
|
|
2194
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
2184
2195
|
if (versionColumn !== void 0) {
|
|
2185
2196
|
for (const item of data) if (!(versionColumn in item)) item[versionColumn] = 0;
|
|
2186
2197
|
}
|
|
@@ -2226,30 +2237,22 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2226
2237
|
}
|
|
2227
2238
|
async updateOne(filter, data, ops, expectedVersion) {
|
|
2228
2239
|
const mongoFilter = this._buildCasFilter(filter, expectedVersion, "updateOne");
|
|
2229
|
-
const updateDoc = buildMongoUpdateDoc(data, ops, this._table.
|
|
2240
|
+
const updateDoc = buildMongoUpdateDoc(data, ops, this._table.versionColumnPhysical);
|
|
2230
2241
|
this._log("updateOne", mongoFilter, updateDoc);
|
|
2231
|
-
|
|
2232
|
-
return {
|
|
2233
|
-
matchedCount: result.matchedCount,
|
|
2234
|
-
modifiedCount: result.modifiedCount
|
|
2235
|
-
};
|
|
2242
|
+
return this._wrapUpdate(() => this.collection.updateOne(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
2236
2243
|
}
|
|
2237
2244
|
async replaceOne(filter, data, expectedVersion) {
|
|
2238
2245
|
const mongoFilter = this._buildCasFilter(filter, expectedVersion, "replaceOne");
|
|
2239
|
-
const versionColumn = this._table.
|
|
2246
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
2240
2247
|
this._log("replaceOne", mongoFilter, data);
|
|
2241
|
-
let result;
|
|
2242
2248
|
if (versionColumn !== void 0) {
|
|
2243
2249
|
const pipeline = [{ $replaceWith: {
|
|
2244
2250
|
...data,
|
|
2245
2251
|
[versionColumn]: { $add: [`$${versionColumn}`, 1] }
|
|
2246
2252
|
} }];
|
|
2247
|
-
|
|
2248
|
-
}
|
|
2249
|
-
return
|
|
2250
|
-
matchedCount: result.matchedCount,
|
|
2251
|
-
modifiedCount: result.modifiedCount
|
|
2252
|
-
};
|
|
2253
|
+
return this._wrapUpdate(() => this.collection.updateOne(mongoFilter, pipeline, this._getSessionOpts()));
|
|
2254
|
+
}
|
|
2255
|
+
return this._wrapUpdate(() => this.collection.replaceOne(mongoFilter, data, this._getSessionOpts()));
|
|
2253
2256
|
}
|
|
2254
2257
|
async deleteOne(filter) {
|
|
2255
2258
|
const mongoFilter = require_mongo_accumulator.buildMongoFilter(filter);
|
|
@@ -2257,25 +2260,17 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2257
2260
|
return { deletedCount: (await this.collection.deleteOne(mongoFilter, this._getSessionOpts())).deletedCount };
|
|
2258
2261
|
}
|
|
2259
2262
|
async updateMany(filter, data, ops) {
|
|
2260
|
-
const versionColumn = this._table.
|
|
2263
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
2261
2264
|
const mongoFilter = require_mongo_accumulator.buildMongoFilter(filter);
|
|
2262
2265
|
const updateDoc = buildMongoUpdateDoc(data, ops, versionColumn);
|
|
2263
2266
|
this._log("updateMany", mongoFilter, updateDoc);
|
|
2264
|
-
|
|
2265
|
-
return {
|
|
2266
|
-
matchedCount: result.matchedCount,
|
|
2267
|
-
modifiedCount: result.modifiedCount
|
|
2268
|
-
};
|
|
2267
|
+
return this._wrapUpdate(() => this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
2269
2268
|
}
|
|
2270
2269
|
async replaceMany(filter, data) {
|
|
2271
2270
|
const mongoFilter = require_mongo_accumulator.buildMongoFilter(filter);
|
|
2272
|
-
const updateDoc = buildMongoUpdateDoc(data, void 0, this._table.
|
|
2271
|
+
const updateDoc = buildMongoUpdateDoc(data, void 0, this._table.versionColumnPhysical);
|
|
2273
2272
|
this._log("replaceMany", mongoFilter, updateDoc);
|
|
2274
|
-
|
|
2275
|
-
return {
|
|
2276
|
-
matchedCount: result.matchedCount,
|
|
2277
|
-
modifiedCount: result.modifiedCount
|
|
2278
|
-
};
|
|
2273
|
+
return this._wrapUpdate(() => this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
2279
2274
|
}
|
|
2280
2275
|
async deleteMany(filter) {
|
|
2281
2276
|
const mongoFilter = require_mongo_accumulator.buildMongoFilter(filter);
|
package/dist/index.d.cts
CHANGED
|
@@ -390,9 +390,13 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
390
390
|
private _buildCasFilter;
|
|
391
391
|
/**
|
|
392
392
|
* Wraps an async operation to catch MongoDB duplicate key errors
|
|
393
|
-
* (code 11000) and rethrow as structured `DbError
|
|
393
|
+
* (code 11000) and rethrow as structured `DbError` — every write path
|
|
394
|
+
* (insert, replace, update, patch, their `*Many` variants) goes through it,
|
|
395
|
+
* so a unique-index violation is a `CONFLICT` whichever statement hit it.
|
|
394
396
|
*/
|
|
395
397
|
private _wrapDuplicateKeyError;
|
|
398
|
+
/** An update-shaped write under {@link _wrapDuplicateKeyError}, reduced to `TDbUpdateResult`. */
|
|
399
|
+
private _wrapUpdate;
|
|
396
400
|
insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
|
|
397
401
|
insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
|
|
398
402
|
findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
package/dist/index.d.mts
CHANGED
|
@@ -390,9 +390,13 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
390
390
|
private _buildCasFilter;
|
|
391
391
|
/**
|
|
392
392
|
* Wraps an async operation to catch MongoDB duplicate key errors
|
|
393
|
-
* (code 11000) and rethrow as structured `DbError
|
|
393
|
+
* (code 11000) and rethrow as structured `DbError` — every write path
|
|
394
|
+
* (insert, replace, update, patch, their `*Many` variants) goes through it,
|
|
395
|
+
* so a unique-index violation is a `CONFLICT` whichever statement hit it.
|
|
394
396
|
*/
|
|
395
397
|
private _wrapDuplicateKeyError;
|
|
398
|
+
/** An update-shaped write under {@link _wrapDuplicateKeyError}, reduced to `TDbUpdateResult`. */
|
|
399
|
+
private _wrapUpdate;
|
|
396
400
|
insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
|
|
397
401
|
insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
|
|
398
402
|
findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
package/dist/index.mjs
CHANGED
|
@@ -1032,7 +1032,10 @@ function joinedAs(table) {
|
|
|
1032
1032
|
* pipeline paths (SQL null semantics — `queryNodeToExpr`).
|
|
1033
1033
|
*
|
|
1034
1034
|
* All field paths are PHYSICAL document paths (`@db.column` renames), joined
|
|
1035
|
-
* tables live under `__joined_<
|
|
1035
|
+
* tables live under `__joined_<scope>` — the physical collection, or the
|
|
1036
|
+
* `@db.alias` type name for an aliased join (`__joined_Manager`), whose
|
|
1037
|
+
* `$lookup.from` stays the physical collection (since 0.1.141).
|
|
1038
|
+
* The entry collection and a `$lookup.from` may be views. A column whose source may be missing
|
|
1036
1039
|
* (`nullable` mapping) reads as null; every other column is a plain path, so
|
|
1037
1040
|
* a `$match` / `$sort` on the view can still push down to an index.
|
|
1038
1041
|
* @since 0.1.136
|
|
@@ -1051,10 +1054,10 @@ function buildViewPipeline(view) {
|
|
|
1051
1054
|
for (const join of plan.joins) {
|
|
1052
1055
|
pipeline.push(buildLookup(view, join, prefixes));
|
|
1053
1056
|
pipeline.push({ $unwind: {
|
|
1054
|
-
path: `$${joinedAs(join.
|
|
1057
|
+
path: `$${joinedAs(join.scope)}`,
|
|
1055
1058
|
preserveNullAndEmptyArrays: join.kind === "left"
|
|
1056
1059
|
} });
|
|
1057
|
-
prefixes.set(join.
|
|
1060
|
+
prefixes.set(join.scope, `${joinedAs(join.scope)}.`);
|
|
1058
1061
|
}
|
|
1059
1062
|
if (plan.filter) pipeline.push({ $match: viewMatch(plan.filter, pathOf) });
|
|
1060
1063
|
/** A column's source field as an aggregation operand. */
|
|
@@ -1098,7 +1101,8 @@ function buildViewPipeline(view) {
|
|
|
1098
1101
|
* `$match: { $expr }`, SQL null semantics).
|
|
1099
1102
|
*/
|
|
1100
1103
|
function buildLookup(view, join, outer) {
|
|
1101
|
-
const
|
|
1104
|
+
const scope = join.scope;
|
|
1105
|
+
const as = joinedAs(scope);
|
|
1102
1106
|
const simple = simpleJoinFields(view, join, outer);
|
|
1103
1107
|
if (simple) return { $lookup: {
|
|
1104
1108
|
from: join.targetTable,
|
|
@@ -1109,9 +1113,9 @@ function buildLookup(view, join, outer) {
|
|
|
1109
1113
|
const varByPath = /* @__PURE__ */ new Map();
|
|
1110
1114
|
const pathOf = (ref) => {
|
|
1111
1115
|
const { table, source } = view.resolveRefSource(ref);
|
|
1112
|
-
if (table ===
|
|
1116
|
+
if (table === scope) return `$${source.column}`;
|
|
1113
1117
|
const prefix = outer.get(table);
|
|
1114
|
-
if (prefix === void 0) throw new Error(`View "${view.tableName}": the join on "${
|
|
1118
|
+
if (prefix === void 0) throw new Error(`View "${view.tableName}": the join on "${scope}" references "${table}", which is not joined before it`);
|
|
1115
1119
|
const outerPath = prefix + source.column;
|
|
1116
1120
|
let varName = varByPath.get(outerPath);
|
|
1117
1121
|
if (!varName) {
|
|
@@ -1140,14 +1144,15 @@ function simpleJoinFields(view, join, outer) {
|
|
|
1140
1144
|
if ("$or" in node || "$not" in node || "$and" in node) return void 0;
|
|
1141
1145
|
const comp = node;
|
|
1142
1146
|
if (comp.op !== "$eq" || !isFieldRef(comp.right)) return void 0;
|
|
1147
|
+
const scope = join.scope;
|
|
1143
1148
|
const left = view.resolveRefSource(comp.left);
|
|
1144
1149
|
const right = view.resolveRefSource(comp.right);
|
|
1145
1150
|
let target;
|
|
1146
1151
|
let local;
|
|
1147
|
-
if (left.table ===
|
|
1152
|
+
if (left.table === scope && right.table !== scope) {
|
|
1148
1153
|
target = left;
|
|
1149
1154
|
local = right;
|
|
1150
|
-
} else if (right.table ===
|
|
1155
|
+
} else if (right.table === scope && left.table !== scope) {
|
|
1151
1156
|
target = right;
|
|
1152
1157
|
local = left;
|
|
1153
1158
|
} else return;
|
|
@@ -1894,7 +1899,7 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
1894
1899
|
}
|
|
1895
1900
|
async nativePatch(filter, patch, ops, expectedVersion) {
|
|
1896
1901
|
const mongoFilter = this._buildCasFilter(filter, expectedVersion, "nativePatch");
|
|
1897
|
-
const versionColumn = this._table.
|
|
1902
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
1898
1903
|
const effectiveOps = versionColumn !== void 0 ? {
|
|
1899
1904
|
...ops,
|
|
1900
1905
|
inc: {
|
|
@@ -1904,14 +1909,10 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
1904
1909
|
} : ops;
|
|
1905
1910
|
const { updateFilter, updateOptions } = new CollectionPatcher(this.getPatcherContext(), patch, effectiveOps).preparePatch();
|
|
1906
1911
|
this._log("updateOne (patch)", mongoFilter, updateFilter);
|
|
1907
|
-
|
|
1912
|
+
return this._wrapUpdate(() => this.collection.updateOne(mongoFilter, updateFilter, {
|
|
1908
1913
|
...updateOptions,
|
|
1909
1914
|
...this._getSessionOpts()
|
|
1910
|
-
});
|
|
1911
|
-
return {
|
|
1912
|
-
matchedCount: result.matchedCount,
|
|
1913
|
-
modifiedCount: result.modifiedCount
|
|
1914
|
-
};
|
|
1915
|
+
}));
|
|
1915
1916
|
}
|
|
1916
1917
|
onBeforeFlatten(_type) {
|
|
1917
1918
|
const typeMeta = _type.metadata;
|
|
@@ -2143,7 +2144,7 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2143
2144
|
* rather than silently dropping the CAS predicate.
|
|
2144
2145
|
*/
|
|
2145
2146
|
_buildCasFilter(filter, expectedVersion, op) {
|
|
2146
|
-
const versionColumn = this._table.
|
|
2147
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
2147
2148
|
if (expectedVersion !== void 0 && versionColumn === void 0) throw new Error(`${op}: expectedVersion requires versionColumn`);
|
|
2148
2149
|
const mongoFilter = buildMongoFilter(filter);
|
|
2149
2150
|
if (expectedVersion !== void 0 && versionColumn !== void 0) mongoFilter[versionColumn] = expectedVersion;
|
|
@@ -2151,7 +2152,9 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2151
2152
|
}
|
|
2152
2153
|
/**
|
|
2153
2154
|
* Wraps an async operation to catch MongoDB duplicate key errors
|
|
2154
|
-
* (code 11000) and rethrow as structured `DbError
|
|
2155
|
+
* (code 11000) and rethrow as structured `DbError` — every write path
|
|
2156
|
+
* (insert, replace, update, patch, their `*Many` variants) goes through it,
|
|
2157
|
+
* so a unique-index violation is a `CONFLICT` whichever statement hit it.
|
|
2155
2158
|
*/
|
|
2156
2159
|
async _wrapDuplicateKeyError(fn) {
|
|
2157
2160
|
try {
|
|
@@ -2164,8 +2167,16 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2164
2167
|
throw error;
|
|
2165
2168
|
}
|
|
2166
2169
|
}
|
|
2170
|
+
/** An update-shaped write under {@link _wrapDuplicateKeyError}, reduced to `TDbUpdateResult`. */
|
|
2171
|
+
async _wrapUpdate(fn) {
|
|
2172
|
+
const { matchedCount, modifiedCount } = await this._wrapDuplicateKeyError(fn);
|
|
2173
|
+
return {
|
|
2174
|
+
matchedCount,
|
|
2175
|
+
modifiedCount
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2167
2178
|
async insertOne(data) {
|
|
2168
|
-
const versionColumn = this._table.
|
|
2179
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
2169
2180
|
if (versionColumn !== void 0 && !(versionColumn in data)) data[versionColumn] = 0;
|
|
2170
2181
|
if (this._incrementFields.size > 0) {
|
|
2171
2182
|
const fields = this._fieldsNeedingIncrement(data);
|
|
@@ -2179,7 +2190,7 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2179
2190
|
return { insertedId: objectIdFromStorage(this._resolveInsertedId(data, result.insertedId)) };
|
|
2180
2191
|
}
|
|
2181
2192
|
async insertMany(data) {
|
|
2182
|
-
const versionColumn = this._table.
|
|
2193
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
2183
2194
|
if (versionColumn !== void 0) {
|
|
2184
2195
|
for (const item of data) if (!(versionColumn in item)) item[versionColumn] = 0;
|
|
2185
2196
|
}
|
|
@@ -2225,30 +2236,22 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2225
2236
|
}
|
|
2226
2237
|
async updateOne(filter, data, ops, expectedVersion) {
|
|
2227
2238
|
const mongoFilter = this._buildCasFilter(filter, expectedVersion, "updateOne");
|
|
2228
|
-
const updateDoc = buildMongoUpdateDoc(data, ops, this._table.
|
|
2239
|
+
const updateDoc = buildMongoUpdateDoc(data, ops, this._table.versionColumnPhysical);
|
|
2229
2240
|
this._log("updateOne", mongoFilter, updateDoc);
|
|
2230
|
-
|
|
2231
|
-
return {
|
|
2232
|
-
matchedCount: result.matchedCount,
|
|
2233
|
-
modifiedCount: result.modifiedCount
|
|
2234
|
-
};
|
|
2241
|
+
return this._wrapUpdate(() => this.collection.updateOne(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
2235
2242
|
}
|
|
2236
2243
|
async replaceOne(filter, data, expectedVersion) {
|
|
2237
2244
|
const mongoFilter = this._buildCasFilter(filter, expectedVersion, "replaceOne");
|
|
2238
|
-
const versionColumn = this._table.
|
|
2245
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
2239
2246
|
this._log("replaceOne", mongoFilter, data);
|
|
2240
|
-
let result;
|
|
2241
2247
|
if (versionColumn !== void 0) {
|
|
2242
2248
|
const pipeline = [{ $replaceWith: {
|
|
2243
2249
|
...data,
|
|
2244
2250
|
[versionColumn]: { $add: [`$${versionColumn}`, 1] }
|
|
2245
2251
|
} }];
|
|
2246
|
-
|
|
2247
|
-
}
|
|
2248
|
-
return
|
|
2249
|
-
matchedCount: result.matchedCount,
|
|
2250
|
-
modifiedCount: result.modifiedCount
|
|
2251
|
-
};
|
|
2252
|
+
return this._wrapUpdate(() => this.collection.updateOne(mongoFilter, pipeline, this._getSessionOpts()));
|
|
2253
|
+
}
|
|
2254
|
+
return this._wrapUpdate(() => this.collection.replaceOne(mongoFilter, data, this._getSessionOpts()));
|
|
2252
2255
|
}
|
|
2253
2256
|
async deleteOne(filter) {
|
|
2254
2257
|
const mongoFilter = buildMongoFilter(filter);
|
|
@@ -2256,25 +2259,17 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2256
2259
|
return { deletedCount: (await this.collection.deleteOne(mongoFilter, this._getSessionOpts())).deletedCount };
|
|
2257
2260
|
}
|
|
2258
2261
|
async updateMany(filter, data, ops) {
|
|
2259
|
-
const versionColumn = this._table.
|
|
2262
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
2260
2263
|
const mongoFilter = buildMongoFilter(filter);
|
|
2261
2264
|
const updateDoc = buildMongoUpdateDoc(data, ops, versionColumn);
|
|
2262
2265
|
this._log("updateMany", mongoFilter, updateDoc);
|
|
2263
|
-
|
|
2264
|
-
return {
|
|
2265
|
-
matchedCount: result.matchedCount,
|
|
2266
|
-
modifiedCount: result.modifiedCount
|
|
2267
|
-
};
|
|
2266
|
+
return this._wrapUpdate(() => this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
2268
2267
|
}
|
|
2269
2268
|
async replaceMany(filter, data) {
|
|
2270
2269
|
const mongoFilter = buildMongoFilter(filter);
|
|
2271
|
-
const updateDoc = buildMongoUpdateDoc(data, void 0, this._table.
|
|
2270
|
+
const updateDoc = buildMongoUpdateDoc(data, void 0, this._table.versionColumnPhysical);
|
|
2272
2271
|
this._log("replaceMany", mongoFilter, updateDoc);
|
|
2273
|
-
|
|
2274
|
-
return {
|
|
2275
|
-
matchedCount: result.matchedCount,
|
|
2276
|
-
modifiedCount: result.modifiedCount
|
|
2277
|
-
};
|
|
2272
|
+
return this._wrapUpdate(() => this.collection.updateMany(mongoFilter, updateDoc, this._getSessionOpts()));
|
|
2278
2273
|
}
|
|
2279
2274
|
async deleteMany(filter) {
|
|
2280
2275
|
const mongoFilter = buildMongoFilter(filter);
|
package/dist/plugin.cjs
CHANGED
|
@@ -52,6 +52,7 @@ const annotations = {
|
|
|
52
52
|
collection: new _atscript_core.AnnotationSpec({
|
|
53
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`**.\n\n**Example:**\n```atscript\n@db.table \"users\"\n@db.mongo.collection\nexport interface User {\n _id: mongo.objectId\n email: string.email\n}\n```\n",
|
|
54
54
|
nodeType: ["interface"],
|
|
55
|
+
passedWhenReferred: false,
|
|
55
56
|
validate(token, args, doc) {
|
|
56
57
|
const parent = token.parentNode;
|
|
57
58
|
const struc = parent?.getDefinition();
|
|
@@ -91,6 +92,7 @@ const annotations = {
|
|
|
91
92
|
capped: new _atscript_core.AnnotationSpec({
|
|
92
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.\n\n**Example:**\n```atscript\n@db.table \"logs\"\n@db.mongo.collection\n@db.mongo.capped 10485760, 10000\n@db.sync.method \"drop\"\nexport interface LogEntry {\n message: string\n timestamp: number\n}\n```\n",
|
|
93
94
|
nodeType: ["interface"],
|
|
95
|
+
passedWhenReferred: false,
|
|
94
96
|
multiple: false,
|
|
95
97
|
argument: [{
|
|
96
98
|
optional: false,
|
|
@@ -108,6 +110,7 @@ const annotations = {
|
|
|
108
110
|
dynamic: new _atscript_core.AnnotationSpec({
|
|
109
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.\n\n**Example:**\n```atscript\n@db.mongo.search.dynamic \"lucene.english\", 1\nexport interface MongoCollection {}\n```\n",
|
|
110
112
|
nodeType: ["interface"],
|
|
113
|
+
passedWhenReferred: false,
|
|
111
114
|
multiple: false,
|
|
112
115
|
argument: [{
|
|
113
116
|
optional: true,
|
|
@@ -125,6 +128,7 @@ const annotations = {
|
|
|
125
128
|
static: new _atscript_core.AnnotationSpec({
|
|
126
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.\n\n**Example:**\n```atscript\n@db.mongo.search.static \"lucene.english\", 1, \"mySearchIndex\"\nexport interface MongoCollection {}\n```\n",
|
|
127
130
|
nodeType: ["interface"],
|
|
131
|
+
passedWhenReferred: false,
|
|
128
132
|
multiple: true,
|
|
129
133
|
argument: [
|
|
130
134
|
{
|
package/dist/plugin.mjs
CHANGED
|
@@ -48,6 +48,7 @@ const annotations = {
|
|
|
48
48
|
collection: new AnnotationSpec({
|
|
49
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`**.\n\n**Example:**\n```atscript\n@db.table \"users\"\n@db.mongo.collection\nexport interface User {\n _id: mongo.objectId\n email: string.email\n}\n```\n",
|
|
50
50
|
nodeType: ["interface"],
|
|
51
|
+
passedWhenReferred: false,
|
|
51
52
|
validate(token, args, doc) {
|
|
52
53
|
const parent = token.parentNode;
|
|
53
54
|
const struc = parent?.getDefinition();
|
|
@@ -87,6 +88,7 @@ const annotations = {
|
|
|
87
88
|
capped: new AnnotationSpec({
|
|
88
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.\n\n**Example:**\n```atscript\n@db.table \"logs\"\n@db.mongo.collection\n@db.mongo.capped 10485760, 10000\n@db.sync.method \"drop\"\nexport interface LogEntry {\n message: string\n timestamp: number\n}\n```\n",
|
|
89
90
|
nodeType: ["interface"],
|
|
91
|
+
passedWhenReferred: false,
|
|
90
92
|
multiple: false,
|
|
91
93
|
argument: [{
|
|
92
94
|
optional: false,
|
|
@@ -104,6 +106,7 @@ const annotations = {
|
|
|
104
106
|
dynamic: new AnnotationSpec({
|
|
105
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.\n\n**Example:**\n```atscript\n@db.mongo.search.dynamic \"lucene.english\", 1\nexport interface MongoCollection {}\n```\n",
|
|
106
108
|
nodeType: ["interface"],
|
|
109
|
+
passedWhenReferred: false,
|
|
107
110
|
multiple: false,
|
|
108
111
|
argument: [{
|
|
109
112
|
optional: true,
|
|
@@ -121,6 +124,7 @@ const annotations = {
|
|
|
121
124
|
static: new AnnotationSpec({
|
|
122
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.\n\n**Example:**\n```atscript\n@db.mongo.search.static \"lucene.english\", 1, \"mySearchIndex\"\nexport interface MongoCollection {}\n```\n",
|
|
123
126
|
nodeType: ["interface"],
|
|
127
|
+
passedWhenReferred: false,
|
|
124
128
|
multiple: true,
|
|
125
129
|
argument: [
|
|
126
130
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-mongo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.141",
|
|
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.95",
|
|
50
|
+
"@atscript/typescript": "^0.1.95",
|
|
51
51
|
"@uniqu/core": "^0.1.11",
|
|
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.95"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@atscript/core": "^0.1.
|
|
58
|
-
"@atscript/typescript": "^0.1.
|
|
57
|
+
"@atscript/core": "^0.1.95",
|
|
58
|
+
"@atscript/typescript": "^0.1.95",
|
|
59
59
|
"@uniqu/core": "^0.1.11",
|
|
60
60
|
"mongodb": "^6.17.0",
|
|
61
|
-
"@atscript/db": "^0.1.
|
|
61
|
+
"@atscript/db": "^0.1.141"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"postinstall": "asc -f dts",
|