@atscript/db-mongo 0.1.136 → 0.1.137
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 +66 -49
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +66 -49
- package/dist/{mongo-accumulator-C8J36t07.cjs → mongo-accumulator-BalE9LkU.cjs} +93 -84
- package/dist/{mongo-accumulator-Dh6eEkyR.mjs → mongo-accumulator-Dc0w4t0l.mjs} +94 -85
- package/package.json +2 -2
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-BalE9LkU.cjs");
|
|
3
3
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
4
4
|
let _uniqu_core = require("@uniqu/core");
|
|
5
5
|
//#region src/agg.ts
|
package/dist/agg.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as orNull, n as distinctCountExpr, r as buildMongoFilter, t as buildAccumulator } from "./mongo-accumulator-Dc0w4t0l.mjs";
|
|
2
2
|
import { resolveAlias } from "@atscript/db/agg";
|
|
3
3
|
import { BUCKET_MAX_INSTANT, BUCKET_MIN_INSTANT } from "@uniqu/core";
|
|
4
4
|
//#region src/agg.ts
|
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-BalE9LkU.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");
|
|
@@ -1297,65 +1297,75 @@ async function renameTableImpl(host, oldName) {
|
|
|
1297
1297
|
async function syncColumnsImpl(host, diff) {
|
|
1298
1298
|
const renamed = [];
|
|
1299
1299
|
const added = [];
|
|
1300
|
-
const
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
host._log("syncColumns: skipping $rename for array-element field", r.oldName, "→", r.field.physicalName, "(Mongo $rename cannot traverse arrays)");
|
|
1306
|
-
continue;
|
|
1307
|
-
}
|
|
1308
|
-
renameSpec[r.oldName] = r.field.physicalName;
|
|
1309
|
-
renamed.push(r.field.physicalName);
|
|
1300
|
+
const renameSpec = {};
|
|
1301
|
+
for (const r of diff.renamed) {
|
|
1302
|
+
if (pathCrossesArray(host, r.field.path)) {
|
|
1303
|
+
host._log("syncColumns: skipping $rename for array-element field", r.oldName, "→", r.field.physicalName, "(Mongo $rename cannot traverse arrays)");
|
|
1304
|
+
continue;
|
|
1310
1305
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
if (Object.keys(setSpec).length > 0) update.$set = setSpec;
|
|
1306
|
+
renameSpec[r.oldName] = r.field.physicalName;
|
|
1307
|
+
renamed.push(r.field.physicalName);
|
|
1308
|
+
}
|
|
1309
|
+
if (renamed.length > 0) await host.collection.updateMany({}, { $rename: renameSpec }, host._getSessionOpts());
|
|
1310
|
+
for (const field of diff.added) {
|
|
1311
|
+
const value = resolveSyncDefault(field);
|
|
1312
|
+
if (value !== void 0) await defaultBackfill(host, field.path, value);
|
|
1313
|
+
added.push(field.physicalName);
|
|
1321
1314
|
}
|
|
1322
|
-
if (Object.keys(update).length > 0) await host.collection.updateMany({}, update, host._getSessionOpts());
|
|
1323
1315
|
return {
|
|
1324
1316
|
added,
|
|
1325
1317
|
renamed
|
|
1326
1318
|
};
|
|
1327
1319
|
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Backfills an added field's `@db.default` literal where the field is
|
|
1322
|
+
* missing — a stored value is never overwritten. Optional fields without a
|
|
1323
|
+
* default get no backfill (absent = "optional" in Mongo). A path crossing an
|
|
1324
|
+
* array narrows the innermost `$[]` to an `arrayFilters` element missing the
|
|
1325
|
+
* sub-path; outer arrays keep `$[]`, so empty arrays are a no-op (a bare
|
|
1326
|
+
* dotted path into an empty array is rejected with code 28).
|
|
1327
|
+
*/
|
|
1328
|
+
async function defaultBackfill(host, logicalPath, value) {
|
|
1329
|
+
const segments = positionalSegments(host, logicalPath);
|
|
1330
|
+
const inner = segments.lastIndexOf("$[]");
|
|
1331
|
+
const opts = host._getSessionOpts();
|
|
1332
|
+
if (inner === -1) {
|
|
1333
|
+
const path = segments.join(".");
|
|
1334
|
+
await host.collection.updateMany({ [path]: { $exists: false } }, { $set: { [path]: value } }, opts);
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
segments[inner] = "$[backfill]";
|
|
1338
|
+
const rest = segments.slice(inner + 1).join(".");
|
|
1339
|
+
await host.collection.updateMany({}, { $set: { [segments.join(".")]: value } }, {
|
|
1340
|
+
...opts,
|
|
1341
|
+
arrayFilters: [{ [`backfill.${rest}`]: { $exists: false } }]
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1328
1344
|
async function dropColumnsImpl(host, columns) {
|
|
1329
1345
|
if (columns.length === 0) return;
|
|
1330
1346
|
const dropped = new Set(columns);
|
|
1331
1347
|
const minimal = columns.filter((col) => !hasAncestorIn(col, dropped));
|
|
1332
1348
|
const unsetSpec = {};
|
|
1333
|
-
for (const col of minimal) unsetSpec[
|
|
1349
|
+
for (const col of minimal) unsetSpec[positionalSegments(host, col, col).join(".")] = "";
|
|
1334
1350
|
await host.collection.updateMany({}, { $unset: unsetSpec }, host._getSessionOpts());
|
|
1335
1351
|
}
|
|
1336
1352
|
/**
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
*
|
|
1341
|
-
* `logicalPath` drives the array-boundary walk (flatMap is keyed by logical
|
|
1342
|
-
* path); the leaf of `physicalPath` is preserved so any `@db.column` rename
|
|
1343
|
-
* on the leaf still applies.
|
|
1353
|
+
* The segments of a field's physical document path with `$[]` after every
|
|
1354
|
+
* segment whose logical prefix is an array in the flatMap — Mongo's
|
|
1355
|
+
* all-positional operator walks every element. `physicalPath` defaults to
|
|
1356
|
+
* the logical path's document path (same segment count).
|
|
1344
1357
|
*/
|
|
1345
|
-
function
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
const physicalSegments = physicalPath.split(".");
|
|
1349
|
-
const physicalLeaf = physicalSegments[physicalSegments.length - 1];
|
|
1358
|
+
function positionalSegments(host, logicalPath, physicalPath = host._table.getMetadata().documentPath(logicalPath)) {
|
|
1359
|
+
const logical = logicalPath.split(".");
|
|
1360
|
+
const physical = physicalPath.split(".");
|
|
1350
1361
|
const out = [];
|
|
1351
1362
|
let prefix = "";
|
|
1352
|
-
for (let i = 0; i <
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
if (!isLeaf && isArrayPath(host._table.flatMap, prefix)) out.push("$[]");
|
|
1363
|
+
for (let i = 0; i < logical.length; i++) {
|
|
1364
|
+
out.push(physical[i]);
|
|
1365
|
+
prefix = joinPath(prefix, logical[i]);
|
|
1366
|
+
if (i < logical.length - 1 && isArrayPath(host._table.flatMap, prefix)) out.push("$[]");
|
|
1357
1367
|
}
|
|
1358
|
-
return out
|
|
1368
|
+
return out;
|
|
1359
1369
|
}
|
|
1360
1370
|
/** Returns true if any non-leaf segment of the path is typed as an array. */
|
|
1361
1371
|
function pathCrossesArray(host, logicalPath) {
|
|
@@ -1818,6 +1828,13 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1818
1828
|
aggregateFns() {
|
|
1819
1829
|
return _atscript_db.ALL_AGGREGATE_FNS;
|
|
1820
1830
|
}
|
|
1831
|
+
/**
|
|
1832
|
+
* See BaseDbAdapter.viewRenderRevision. 2 = 0.1.137 (null-guarded field filters, nested document paths).
|
|
1833
|
+
* BUMP whenever `buildViewPipeline` output changes for an unchanged view definition.
|
|
1834
|
+
*/
|
|
1835
|
+
viewRenderRevision() {
|
|
1836
|
+
return "2";
|
|
1837
|
+
}
|
|
1821
1838
|
getValidatorPlugins() {
|
|
1822
1839
|
return [validateMongoIdPlugin];
|
|
1823
1840
|
}
|
|
@@ -1926,11 +1943,6 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1926
1943
|
this._addMongoIndexField("unique", "__pk", field);
|
|
1927
1944
|
this._pendingUniqueFields.push(field);
|
|
1928
1945
|
}
|
|
1929
|
-
if (metadata.has("db.default.increment")) {
|
|
1930
|
-
const physicalName = metadata.get("db.column") ?? field;
|
|
1931
|
-
const startValue = metadata.get("db.default.increment");
|
|
1932
|
-
this._incrementFields.set(physicalName, typeof startValue === "number" ? startValue : void 0);
|
|
1933
|
-
}
|
|
1934
1946
|
for (const index of metadata.get("db.mongo.search.text") || []) this._pendingSearchFields.push({
|
|
1935
1947
|
indexName: index.indexName,
|
|
1936
1948
|
field,
|
|
@@ -2006,9 +2018,14 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
2006
2018
|
path: value
|
|
2007
2019
|
});
|
|
2008
2020
|
}
|
|
2009
|
-
for (const fd of this._table.fieldDescriptors)
|
|
2010
|
-
if (
|
|
2011
|
-
|
|
2021
|
+
for (const fd of this._table.fieldDescriptors) {
|
|
2022
|
+
if (fd.collate && fd.collate !== "binary") {
|
|
2023
|
+
this._collateFields ??= /* @__PURE__ */ new Map();
|
|
2024
|
+
this._collateFields.set(fd.path, fd.collate);
|
|
2025
|
+
this._collateFields.set(fd.physicalName, fd.collate);
|
|
2026
|
+
}
|
|
2027
|
+
const def = fd.defaultValue;
|
|
2028
|
+
if (def?.kind === "fn" && def.fn === "increment") this._incrementFields.set(fd.physicalName, def.start);
|
|
2012
2029
|
}
|
|
2013
2030
|
}
|
|
2014
2031
|
/** Returns MongoDB-specific search index map (internal). */
|
package/dist/index.d.cts
CHANGED
|
@@ -305,6 +305,11 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
305
305
|
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
306
306
|
/** Every aggregate function, `countDistinct` included. */
|
|
307
307
|
aggregateFns(): ReadonlySet<AggregateFn>;
|
|
308
|
+
/**
|
|
309
|
+
* See BaseDbAdapter.viewRenderRevision. 2 = 0.1.137 (null-guarded field filters, nested document paths).
|
|
310
|
+
* BUMP whenever `buildViewPipeline` output changes for an unchanged view definition.
|
|
311
|
+
*/
|
|
312
|
+
viewRenderRevision(): string;
|
|
308
313
|
getValidatorPlugins(): ReturnType<BaseDbAdapter["getValidatorPlugins"]>;
|
|
309
314
|
/**
|
|
310
315
|
* Mongo can filter on JSON-stored fields natively — arrays via implicit
|
package/dist/index.d.mts
CHANGED
|
@@ -305,6 +305,11 @@ declare class MongoAdapter extends BaseDbAdapter {
|
|
|
305
305
|
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
306
306
|
/** Every aggregate function, `countDistinct` included. */
|
|
307
307
|
aggregateFns(): ReadonlySet<AggregateFn>;
|
|
308
|
+
/**
|
|
309
|
+
* See BaseDbAdapter.viewRenderRevision. 2 = 0.1.137 (null-guarded field filters, nested document paths).
|
|
310
|
+
* BUMP whenever `buildViewPipeline` output changes for an unchanged view definition.
|
|
311
|
+
*/
|
|
312
|
+
viewRenderRevision(): string;
|
|
308
313
|
getValidatorPlugins(): ReturnType<BaseDbAdapter["getValidatorPlugins"]>;
|
|
309
314
|
/**
|
|
310
315
|
* Mongo can filter on JSON-stored fields natively — arrays via implicit
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as queryNodeToExpr, i as orNull, n as distinctCountExpr, r as buildMongoFilter, t as buildAccumulator } from "./mongo-accumulator-Dc0w4t0l.mjs";
|
|
2
2
|
import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace, bucketTimeZoneUnavailable, computeInsights, createFailureCollector, getDbFieldOp, getKeyProps, isAtscriptDbView, isFieldRef, resolveDesignType, tableNameOf, translateQueryTree } from "@atscript/db";
|
|
3
3
|
import { MongoClient, MongoServerError, ObjectId } from "mongodb";
|
|
4
4
|
import { resolveAggregateSearch } from "@atscript/db/agg";
|
|
@@ -1296,65 +1296,75 @@ async function renameTableImpl(host, oldName) {
|
|
|
1296
1296
|
async function syncColumnsImpl(host, diff) {
|
|
1297
1297
|
const renamed = [];
|
|
1298
1298
|
const added = [];
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
host._log("syncColumns: skipping $rename for array-element field", r.oldName, "→", r.field.physicalName, "(Mongo $rename cannot traverse arrays)");
|
|
1305
|
-
continue;
|
|
1306
|
-
}
|
|
1307
|
-
renameSpec[r.oldName] = r.field.physicalName;
|
|
1308
|
-
renamed.push(r.field.physicalName);
|
|
1299
|
+
const renameSpec = {};
|
|
1300
|
+
for (const r of diff.renamed) {
|
|
1301
|
+
if (pathCrossesArray(host, r.field.path)) {
|
|
1302
|
+
host._log("syncColumns: skipping $rename for array-element field", r.oldName, "→", r.field.physicalName, "(Mongo $rename cannot traverse arrays)");
|
|
1303
|
+
continue;
|
|
1309
1304
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
}
|
|
1319
|
-
if (Object.keys(setSpec).length > 0) update.$set = setSpec;
|
|
1305
|
+
renameSpec[r.oldName] = r.field.physicalName;
|
|
1306
|
+
renamed.push(r.field.physicalName);
|
|
1307
|
+
}
|
|
1308
|
+
if (renamed.length > 0) await host.collection.updateMany({}, { $rename: renameSpec }, host._getSessionOpts());
|
|
1309
|
+
for (const field of diff.added) {
|
|
1310
|
+
const value = resolveSyncDefault(field);
|
|
1311
|
+
if (value !== void 0) await defaultBackfill(host, field.path, value);
|
|
1312
|
+
added.push(field.physicalName);
|
|
1320
1313
|
}
|
|
1321
|
-
if (Object.keys(update).length > 0) await host.collection.updateMany({}, update, host._getSessionOpts());
|
|
1322
1314
|
return {
|
|
1323
1315
|
added,
|
|
1324
1316
|
renamed
|
|
1325
1317
|
};
|
|
1326
1318
|
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Backfills an added field's `@db.default` literal where the field is
|
|
1321
|
+
* missing — a stored value is never overwritten. Optional fields without a
|
|
1322
|
+
* default get no backfill (absent = "optional" in Mongo). A path crossing an
|
|
1323
|
+
* array narrows the innermost `$[]` to an `arrayFilters` element missing the
|
|
1324
|
+
* sub-path; outer arrays keep `$[]`, so empty arrays are a no-op (a bare
|
|
1325
|
+
* dotted path into an empty array is rejected with code 28).
|
|
1326
|
+
*/
|
|
1327
|
+
async function defaultBackfill(host, logicalPath, value) {
|
|
1328
|
+
const segments = positionalSegments(host, logicalPath);
|
|
1329
|
+
const inner = segments.lastIndexOf("$[]");
|
|
1330
|
+
const opts = host._getSessionOpts();
|
|
1331
|
+
if (inner === -1) {
|
|
1332
|
+
const path = segments.join(".");
|
|
1333
|
+
await host.collection.updateMany({ [path]: { $exists: false } }, { $set: { [path]: value } }, opts);
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
segments[inner] = "$[backfill]";
|
|
1337
|
+
const rest = segments.slice(inner + 1).join(".");
|
|
1338
|
+
await host.collection.updateMany({}, { $set: { [segments.join(".")]: value } }, {
|
|
1339
|
+
...opts,
|
|
1340
|
+
arrayFilters: [{ [`backfill.${rest}`]: { $exists: false } }]
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1327
1343
|
async function dropColumnsImpl(host, columns) {
|
|
1328
1344
|
if (columns.length === 0) return;
|
|
1329
1345
|
const dropped = new Set(columns);
|
|
1330
1346
|
const minimal = columns.filter((col) => !hasAncestorIn(col, dropped));
|
|
1331
1347
|
const unsetSpec = {};
|
|
1332
|
-
for (const col of minimal) unsetSpec[
|
|
1348
|
+
for (const col of minimal) unsetSpec[positionalSegments(host, col, col).join(".")] = "";
|
|
1333
1349
|
await host.collection.updateMany({}, { $unset: unsetSpec }, host._getSessionOpts());
|
|
1334
1350
|
}
|
|
1335
1351
|
/**
|
|
1336
|
-
*
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
* `logicalPath` drives the array-boundary walk (flatMap is keyed by logical
|
|
1341
|
-
* path); the leaf of `physicalPath` is preserved so any `@db.column` rename
|
|
1342
|
-
* on the leaf still applies.
|
|
1352
|
+
* The segments of a field's physical document path with `$[]` after every
|
|
1353
|
+
* segment whose logical prefix is an array in the flatMap — Mongo's
|
|
1354
|
+
* all-positional operator walks every element. `physicalPath` defaults to
|
|
1355
|
+
* the logical path's document path (same segment count).
|
|
1343
1356
|
*/
|
|
1344
|
-
function
|
|
1345
|
-
const
|
|
1346
|
-
|
|
1347
|
-
const physicalSegments = physicalPath.split(".");
|
|
1348
|
-
const physicalLeaf = physicalSegments[physicalSegments.length - 1];
|
|
1357
|
+
function positionalSegments(host, logicalPath, physicalPath = host._table.getMetadata().documentPath(logicalPath)) {
|
|
1358
|
+
const logical = logicalPath.split(".");
|
|
1359
|
+
const physical = physicalPath.split(".");
|
|
1349
1360
|
const out = [];
|
|
1350
1361
|
let prefix = "";
|
|
1351
|
-
for (let i = 0; i <
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
if (!isLeaf && isArrayPath(host._table.flatMap, prefix)) out.push("$[]");
|
|
1362
|
+
for (let i = 0; i < logical.length; i++) {
|
|
1363
|
+
out.push(physical[i]);
|
|
1364
|
+
prefix = joinPath(prefix, logical[i]);
|
|
1365
|
+
if (i < logical.length - 1 && isArrayPath(host._table.flatMap, prefix)) out.push("$[]");
|
|
1356
1366
|
}
|
|
1357
|
-
return out
|
|
1367
|
+
return out;
|
|
1358
1368
|
}
|
|
1359
1369
|
/** Returns true if any non-leaf segment of the path is typed as an array. */
|
|
1360
1370
|
function pathCrossesArray(host, logicalPath) {
|
|
@@ -1817,6 +1827,13 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
1817
1827
|
aggregateFns() {
|
|
1818
1828
|
return ALL_AGGREGATE_FNS;
|
|
1819
1829
|
}
|
|
1830
|
+
/**
|
|
1831
|
+
* See BaseDbAdapter.viewRenderRevision. 2 = 0.1.137 (null-guarded field filters, nested document paths).
|
|
1832
|
+
* BUMP whenever `buildViewPipeline` output changes for an unchanged view definition.
|
|
1833
|
+
*/
|
|
1834
|
+
viewRenderRevision() {
|
|
1835
|
+
return "2";
|
|
1836
|
+
}
|
|
1820
1837
|
getValidatorPlugins() {
|
|
1821
1838
|
return [validateMongoIdPlugin];
|
|
1822
1839
|
}
|
|
@@ -1925,11 +1942,6 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
1925
1942
|
this._addMongoIndexField("unique", "__pk", field);
|
|
1926
1943
|
this._pendingUniqueFields.push(field);
|
|
1927
1944
|
}
|
|
1928
|
-
if (metadata.has("db.default.increment")) {
|
|
1929
|
-
const physicalName = metadata.get("db.column") ?? field;
|
|
1930
|
-
const startValue = metadata.get("db.default.increment");
|
|
1931
|
-
this._incrementFields.set(physicalName, typeof startValue === "number" ? startValue : void 0);
|
|
1932
|
-
}
|
|
1933
1945
|
for (const index of metadata.get("db.mongo.search.text") || []) this._pendingSearchFields.push({
|
|
1934
1946
|
indexName: index.indexName,
|
|
1935
1947
|
field,
|
|
@@ -2005,9 +2017,14 @@ var MongoAdapter = class MongoAdapter extends BaseDbAdapter {
|
|
|
2005
2017
|
path: value
|
|
2006
2018
|
});
|
|
2007
2019
|
}
|
|
2008
|
-
for (const fd of this._table.fieldDescriptors)
|
|
2009
|
-
if (
|
|
2010
|
-
|
|
2020
|
+
for (const fd of this._table.fieldDescriptors) {
|
|
2021
|
+
if (fd.collate && fd.collate !== "binary") {
|
|
2022
|
+
this._collateFields ??= /* @__PURE__ */ new Map();
|
|
2023
|
+
this._collateFields.set(fd.path, fd.collate);
|
|
2024
|
+
this._collateFields.set(fd.physicalName, fd.collate);
|
|
2025
|
+
}
|
|
2026
|
+
const def = fd.defaultValue;
|
|
2027
|
+
if (def?.kind === "fn" && def.fn === "increment") this._incrementFields.set(fd.physicalName, def.start);
|
|
2011
2028
|
}
|
|
2012
2029
|
}
|
|
2013
2030
|
/** Returns MongoDB-specific search index map (internal). */
|
|
@@ -1,85 +1,5 @@
|
|
|
1
1
|
let _atscript_db = require("@atscript/db");
|
|
2
2
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
3
|
-
//#region src/lib/mongo-filter.ts
|
|
4
|
-
const EMPTY = {};
|
|
5
|
-
function parseRegexString(value) {
|
|
6
|
-
if (value instanceof RegExp) return {
|
|
7
|
-
pattern: value.source,
|
|
8
|
-
flags: value.flags
|
|
9
|
-
};
|
|
10
|
-
const str = String(value);
|
|
11
|
-
const match = str.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
12
|
-
if (match) return {
|
|
13
|
-
pattern: match[1],
|
|
14
|
-
flags: match[2]
|
|
15
|
-
};
|
|
16
|
-
return {
|
|
17
|
-
pattern: str,
|
|
18
|
-
flags: ""
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Earth radius in meters used by MongoDB's `$centerSphere` radians conversion
|
|
23
|
-
* (Mongo documents dividing by 6378.1 km).
|
|
24
|
-
*/
|
|
25
|
-
const EARTH_RADIUS_M = 6378100;
|
|
26
|
-
const mongoVisitor = {
|
|
27
|
-
comparison(field, op, value) {
|
|
28
|
-
if (op === "$eq") return { [field]: value };
|
|
29
|
-
if (op === "$exists") return value ? { [field]: { $ne: null } } : { [field]: null };
|
|
30
|
-
if (op === "$regex") {
|
|
31
|
-
const { pattern, flags } = parseRegexString(value);
|
|
32
|
-
return flags ? { [field]: {
|
|
33
|
-
$regex: pattern,
|
|
34
|
-
$options: flags
|
|
35
|
-
} } : { [field]: { $regex: pattern } };
|
|
36
|
-
}
|
|
37
|
-
if (op === "$geoWithin") {
|
|
38
|
-
const { center, radius } = value;
|
|
39
|
-
return { [field]: { $geoWithin: { $centerSphere: [center, radius / EARTH_RADIUS_M] } } };
|
|
40
|
-
}
|
|
41
|
-
return { [field]: { [op]: value } };
|
|
42
|
-
},
|
|
43
|
-
and(children) {
|
|
44
|
-
if (children.length === 0) return EMPTY;
|
|
45
|
-
if (children.length === 1) return children[0];
|
|
46
|
-
return { $and: children };
|
|
47
|
-
},
|
|
48
|
-
or(children) {
|
|
49
|
-
if (children.length === 0) return { _impossible: true };
|
|
50
|
-
if (children.length === 1) return children[0];
|
|
51
|
-
return { $or: children };
|
|
52
|
-
},
|
|
53
|
-
not(child) {
|
|
54
|
-
return { $nor: [child] };
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
/** A `{ $field: path }` comparison operand (a field-to-field comparison). */
|
|
58
|
-
function isFieldOperand(value) {
|
|
59
|
-
return value !== null && typeof value === "object" && typeof value.$field === "string";
|
|
60
|
-
}
|
|
61
|
-
/** {@link mongoVisitor} plus field-to-field comparisons (`{ $field }` operands → `$expr`). */
|
|
62
|
-
const fieldOperandVisitor = {
|
|
63
|
-
...mongoVisitor,
|
|
64
|
-
comparison(field, op, value) {
|
|
65
|
-
return isFieldOperand(value) ? { $expr: { [op]: [`$${field}`, `$${value.$field}`] } } : mongoVisitor.comparison(field, op, value);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
/**
|
|
69
|
-
* Translates a generic {@link FilterExpr} into a MongoDB-compatible
|
|
70
|
-
* {@link Filter} document.
|
|
71
|
-
*
|
|
72
|
-
* MongoDB's query language is nearly identical to the `FilterExpr` structure,
|
|
73
|
-
* so this is largely a structural pass-through via the `walkFilter` visitor.
|
|
74
|
-
* `fieldOperands` (view predicates only — `translateQueryTree` output) turns
|
|
75
|
-
* `{ $field: path }` operands into field-to-field `$expr` comparisons; a
|
|
76
|
-
* request filter never gets that reading.
|
|
77
|
-
*/
|
|
78
|
-
function buildMongoFilter(filter, { fieldOperands = false } = {}) {
|
|
79
|
-
if (!filter || Object.keys(filter).length === 0) return EMPTY;
|
|
80
|
-
return (0, _atscript_db.walkFilter)(filter, fieldOperands ? fieldOperandVisitor : mongoVisitor) ?? EMPTY;
|
|
81
|
-
}
|
|
82
|
-
//#endregion
|
|
83
3
|
//#region src/lib/mongo-view-expr.ts
|
|
84
4
|
/**
|
|
85
5
|
* `x IS NOT NULL` in SQL terms — true when `x` is neither null nor missing.
|
|
@@ -122,6 +42,15 @@ function guarded(operands, cond) {
|
|
|
122
42
|
return { $and: [...operands.map((o) => notNullExpr(o)), cond] };
|
|
123
43
|
}
|
|
124
44
|
/**
|
|
45
|
+
* Field-to-field comparison `x <op> y` (`$eq` … `$lte`): both operands must
|
|
46
|
+
* be non-null (SQL `NULL = NULL` is UNKNOWN) — view predicates and
|
|
47
|
+
* `buildMongoFilter`'s field operands.
|
|
48
|
+
* @since 0.1.137
|
|
49
|
+
*/
|
|
50
|
+
function fieldCompareExpr(op, x, y) {
|
|
51
|
+
return guarded([x, y], { [op]: [x, y] });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
125
54
|
* Translates a view predicate (join condition, conditional-aggregate filter)
|
|
126
55
|
* to an aggregation expression for `$match: { $expr }` / `$cond`, matching
|
|
127
56
|
* SQL's three-valued logic wherever a NULL operand makes SQL's comparison
|
|
@@ -156,10 +85,7 @@ function queryNodeToExpr(node, pathOf) {
|
|
|
156
85
|
}
|
|
157
86
|
const op = COMPARISONS[comp.op];
|
|
158
87
|
if (!op) throw new Error(`Operator "${comp.op}" is not supported in view predicates`);
|
|
159
|
-
if ((0, _atscript_db.isFieldRef)(comp.right))
|
|
160
|
-
const y = pathOf(comp.right);
|
|
161
|
-
return guarded([x, y], { [op]: [x, y] });
|
|
162
|
-
}
|
|
88
|
+
if ((0, _atscript_db.isFieldRef)(comp.right)) return fieldCompareExpr(op, x, pathOf(comp.right));
|
|
163
89
|
if (comp.right === null || comp.right === void 0) {
|
|
164
90
|
if (op === "$eq") return isNullExpr(x);
|
|
165
91
|
if (op === "$ne") return notNullExpr(x);
|
|
@@ -170,6 +96,89 @@ function queryNodeToExpr(node, pathOf) {
|
|
|
170
96
|
return guarded([x], { [op]: [x, value] });
|
|
171
97
|
}
|
|
172
98
|
//#endregion
|
|
99
|
+
//#region src/lib/mongo-filter.ts
|
|
100
|
+
const EMPTY = {};
|
|
101
|
+
function parseRegexString(value) {
|
|
102
|
+
if (value instanceof RegExp) return {
|
|
103
|
+
pattern: value.source,
|
|
104
|
+
flags: value.flags
|
|
105
|
+
};
|
|
106
|
+
const str = String(value);
|
|
107
|
+
const match = str.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
108
|
+
if (match) return {
|
|
109
|
+
pattern: match[1],
|
|
110
|
+
flags: match[2]
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
pattern: str,
|
|
114
|
+
flags: ""
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Earth radius in meters used by MongoDB's `$centerSphere` radians conversion
|
|
119
|
+
* (Mongo documents dividing by 6378.1 km).
|
|
120
|
+
*/
|
|
121
|
+
const EARTH_RADIUS_M = 6378100;
|
|
122
|
+
const mongoVisitor = {
|
|
123
|
+
comparison(field, op, value) {
|
|
124
|
+
if (op === "$eq") return { [field]: value };
|
|
125
|
+
if (op === "$exists") return value ? { [field]: { $ne: null } } : { [field]: null };
|
|
126
|
+
if (op === "$regex") {
|
|
127
|
+
const { pattern, flags } = parseRegexString(value);
|
|
128
|
+
return flags ? { [field]: {
|
|
129
|
+
$regex: pattern,
|
|
130
|
+
$options: flags
|
|
131
|
+
} } : { [field]: { $regex: pattern } };
|
|
132
|
+
}
|
|
133
|
+
if (op === "$geoWithin") {
|
|
134
|
+
const { center, radius } = value;
|
|
135
|
+
return { [field]: { $geoWithin: { $centerSphere: [center, radius / EARTH_RADIUS_M] } } };
|
|
136
|
+
}
|
|
137
|
+
return { [field]: { [op]: value } };
|
|
138
|
+
},
|
|
139
|
+
and(children) {
|
|
140
|
+
if (children.length === 0) return EMPTY;
|
|
141
|
+
if (children.length === 1) return children[0];
|
|
142
|
+
return { $and: children };
|
|
143
|
+
},
|
|
144
|
+
or(children) {
|
|
145
|
+
if (children.length === 0) return { _impossible: true };
|
|
146
|
+
if (children.length === 1) return children[0];
|
|
147
|
+
return { $or: children };
|
|
148
|
+
},
|
|
149
|
+
not(child) {
|
|
150
|
+
return { $nor: [child] };
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
/** A `{ $field: path }` comparison operand (a field-to-field comparison). */
|
|
154
|
+
function isFieldOperand(value) {
|
|
155
|
+
return value !== null && typeof value === "object" && typeof value.$field === "string";
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* {@link mongoVisitor} plus field-to-field comparisons (`{ $field }` operands →
|
|
159
|
+
* `$expr`), null-guarded like SQL ({@link fieldCompareExpr}).
|
|
160
|
+
*/
|
|
161
|
+
const fieldOperandVisitor = {
|
|
162
|
+
...mongoVisitor,
|
|
163
|
+
comparison(field, op, value) {
|
|
164
|
+
return isFieldOperand(value) ? { $expr: fieldCompareExpr(op, `$${field}`, `$${value.$field}`) } : mongoVisitor.comparison(field, op, value);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Translates a generic {@link FilterExpr} into a MongoDB-compatible
|
|
169
|
+
* {@link Filter} document.
|
|
170
|
+
*
|
|
171
|
+
* MongoDB's query language is nearly identical to the `FilterExpr` structure,
|
|
172
|
+
* so this is largely a structural pass-through via the `walkFilter` visitor.
|
|
173
|
+
* `fieldOperands` (view predicates only — `translateQueryTree` output) turns
|
|
174
|
+
* `{ $field: path }` operands into field-to-field `$expr` comparisons; a
|
|
175
|
+
* request filter never gets that reading.
|
|
176
|
+
*/
|
|
177
|
+
function buildMongoFilter(filter, { fieldOperands = false } = {}) {
|
|
178
|
+
if (!filter || Object.keys(filter).length === 0) return EMPTY;
|
|
179
|
+
return (0, _atscript_db.walkFilter)(filter, fieldOperands ? fieldOperandVisitor : mongoVisitor) ?? EMPTY;
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
173
182
|
//#region src/lib/mongo-accumulator.ts
|
|
174
183
|
/**
|
|
175
184
|
* The `$group` accumulator of one aggregate — shared by grouped queries
|
|
@@ -1,85 +1,5 @@
|
|
|
1
1
|
import { isFieldRef, walkFilter } from "@atscript/db";
|
|
2
2
|
import { assertAggregateFn } from "@atscript/db/agg";
|
|
3
|
-
//#region src/lib/mongo-filter.ts
|
|
4
|
-
const EMPTY = {};
|
|
5
|
-
function parseRegexString(value) {
|
|
6
|
-
if (value instanceof RegExp) return {
|
|
7
|
-
pattern: value.source,
|
|
8
|
-
flags: value.flags
|
|
9
|
-
};
|
|
10
|
-
const str = String(value);
|
|
11
|
-
const match = str.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
12
|
-
if (match) return {
|
|
13
|
-
pattern: match[1],
|
|
14
|
-
flags: match[2]
|
|
15
|
-
};
|
|
16
|
-
return {
|
|
17
|
-
pattern: str,
|
|
18
|
-
flags: ""
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Earth radius in meters used by MongoDB's `$centerSphere` radians conversion
|
|
23
|
-
* (Mongo documents dividing by 6378.1 km).
|
|
24
|
-
*/
|
|
25
|
-
const EARTH_RADIUS_M = 6378100;
|
|
26
|
-
const mongoVisitor = {
|
|
27
|
-
comparison(field, op, value) {
|
|
28
|
-
if (op === "$eq") return { [field]: value };
|
|
29
|
-
if (op === "$exists") return value ? { [field]: { $ne: null } } : { [field]: null };
|
|
30
|
-
if (op === "$regex") {
|
|
31
|
-
const { pattern, flags } = parseRegexString(value);
|
|
32
|
-
return flags ? { [field]: {
|
|
33
|
-
$regex: pattern,
|
|
34
|
-
$options: flags
|
|
35
|
-
} } : { [field]: { $regex: pattern } };
|
|
36
|
-
}
|
|
37
|
-
if (op === "$geoWithin") {
|
|
38
|
-
const { center, radius } = value;
|
|
39
|
-
return { [field]: { $geoWithin: { $centerSphere: [center, radius / EARTH_RADIUS_M] } } };
|
|
40
|
-
}
|
|
41
|
-
return { [field]: { [op]: value } };
|
|
42
|
-
},
|
|
43
|
-
and(children) {
|
|
44
|
-
if (children.length === 0) return EMPTY;
|
|
45
|
-
if (children.length === 1) return children[0];
|
|
46
|
-
return { $and: children };
|
|
47
|
-
},
|
|
48
|
-
or(children) {
|
|
49
|
-
if (children.length === 0) return { _impossible: true };
|
|
50
|
-
if (children.length === 1) return children[0];
|
|
51
|
-
return { $or: children };
|
|
52
|
-
},
|
|
53
|
-
not(child) {
|
|
54
|
-
return { $nor: [child] };
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
/** A `{ $field: path }` comparison operand (a field-to-field comparison). */
|
|
58
|
-
function isFieldOperand(value) {
|
|
59
|
-
return value !== null && typeof value === "object" && typeof value.$field === "string";
|
|
60
|
-
}
|
|
61
|
-
/** {@link mongoVisitor} plus field-to-field comparisons (`{ $field }` operands → `$expr`). */
|
|
62
|
-
const fieldOperandVisitor = {
|
|
63
|
-
...mongoVisitor,
|
|
64
|
-
comparison(field, op, value) {
|
|
65
|
-
return isFieldOperand(value) ? { $expr: { [op]: [`$${field}`, `$${value.$field}`] } } : mongoVisitor.comparison(field, op, value);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
/**
|
|
69
|
-
* Translates a generic {@link FilterExpr} into a MongoDB-compatible
|
|
70
|
-
* {@link Filter} document.
|
|
71
|
-
*
|
|
72
|
-
* MongoDB's query language is nearly identical to the `FilterExpr` structure,
|
|
73
|
-
* so this is largely a structural pass-through via the `walkFilter` visitor.
|
|
74
|
-
* `fieldOperands` (view predicates only — `translateQueryTree` output) turns
|
|
75
|
-
* `{ $field: path }` operands into field-to-field `$expr` comparisons; a
|
|
76
|
-
* request filter never gets that reading.
|
|
77
|
-
*/
|
|
78
|
-
function buildMongoFilter(filter, { fieldOperands = false } = {}) {
|
|
79
|
-
if (!filter || Object.keys(filter).length === 0) return EMPTY;
|
|
80
|
-
return walkFilter(filter, fieldOperands ? fieldOperandVisitor : mongoVisitor) ?? EMPTY;
|
|
81
|
-
}
|
|
82
|
-
//#endregion
|
|
83
3
|
//#region src/lib/mongo-view-expr.ts
|
|
84
4
|
/**
|
|
85
5
|
* `x IS NOT NULL` in SQL terms — true when `x` is neither null nor missing.
|
|
@@ -122,6 +42,15 @@ function guarded(operands, cond) {
|
|
|
122
42
|
return { $and: [...operands.map((o) => notNullExpr(o)), cond] };
|
|
123
43
|
}
|
|
124
44
|
/**
|
|
45
|
+
* Field-to-field comparison `x <op> y` (`$eq` … `$lte`): both operands must
|
|
46
|
+
* be non-null (SQL `NULL = NULL` is UNKNOWN) — view predicates and
|
|
47
|
+
* `buildMongoFilter`'s field operands.
|
|
48
|
+
* @since 0.1.137
|
|
49
|
+
*/
|
|
50
|
+
function fieldCompareExpr(op, x, y) {
|
|
51
|
+
return guarded([x, y], { [op]: [x, y] });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
125
54
|
* Translates a view predicate (join condition, conditional-aggregate filter)
|
|
126
55
|
* to an aggregation expression for `$match: { $expr }` / `$cond`, matching
|
|
127
56
|
* SQL's three-valued logic wherever a NULL operand makes SQL's comparison
|
|
@@ -156,10 +85,7 @@ function queryNodeToExpr(node, pathOf) {
|
|
|
156
85
|
}
|
|
157
86
|
const op = COMPARISONS[comp.op];
|
|
158
87
|
if (!op) throw new Error(`Operator "${comp.op}" is not supported in view predicates`);
|
|
159
|
-
if (isFieldRef(comp.right))
|
|
160
|
-
const y = pathOf(comp.right);
|
|
161
|
-
return guarded([x, y], { [op]: [x, y] });
|
|
162
|
-
}
|
|
88
|
+
if (isFieldRef(comp.right)) return fieldCompareExpr(op, x, pathOf(comp.right));
|
|
163
89
|
if (comp.right === null || comp.right === void 0) {
|
|
164
90
|
if (op === "$eq") return isNullExpr(x);
|
|
165
91
|
if (op === "$ne") return notNullExpr(x);
|
|
@@ -170,6 +96,89 @@ function queryNodeToExpr(node, pathOf) {
|
|
|
170
96
|
return guarded([x], { [op]: [x, value] });
|
|
171
97
|
}
|
|
172
98
|
//#endregion
|
|
99
|
+
//#region src/lib/mongo-filter.ts
|
|
100
|
+
const EMPTY = {};
|
|
101
|
+
function parseRegexString(value) {
|
|
102
|
+
if (value instanceof RegExp) return {
|
|
103
|
+
pattern: value.source,
|
|
104
|
+
flags: value.flags
|
|
105
|
+
};
|
|
106
|
+
const str = String(value);
|
|
107
|
+
const match = str.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
108
|
+
if (match) return {
|
|
109
|
+
pattern: match[1],
|
|
110
|
+
flags: match[2]
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
pattern: str,
|
|
114
|
+
flags: ""
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Earth radius in meters used by MongoDB's `$centerSphere` radians conversion
|
|
119
|
+
* (Mongo documents dividing by 6378.1 km).
|
|
120
|
+
*/
|
|
121
|
+
const EARTH_RADIUS_M = 6378100;
|
|
122
|
+
const mongoVisitor = {
|
|
123
|
+
comparison(field, op, value) {
|
|
124
|
+
if (op === "$eq") return { [field]: value };
|
|
125
|
+
if (op === "$exists") return value ? { [field]: { $ne: null } } : { [field]: null };
|
|
126
|
+
if (op === "$regex") {
|
|
127
|
+
const { pattern, flags } = parseRegexString(value);
|
|
128
|
+
return flags ? { [field]: {
|
|
129
|
+
$regex: pattern,
|
|
130
|
+
$options: flags
|
|
131
|
+
} } : { [field]: { $regex: pattern } };
|
|
132
|
+
}
|
|
133
|
+
if (op === "$geoWithin") {
|
|
134
|
+
const { center, radius } = value;
|
|
135
|
+
return { [field]: { $geoWithin: { $centerSphere: [center, radius / EARTH_RADIUS_M] } } };
|
|
136
|
+
}
|
|
137
|
+
return { [field]: { [op]: value } };
|
|
138
|
+
},
|
|
139
|
+
and(children) {
|
|
140
|
+
if (children.length === 0) return EMPTY;
|
|
141
|
+
if (children.length === 1) return children[0];
|
|
142
|
+
return { $and: children };
|
|
143
|
+
},
|
|
144
|
+
or(children) {
|
|
145
|
+
if (children.length === 0) return { _impossible: true };
|
|
146
|
+
if (children.length === 1) return children[0];
|
|
147
|
+
return { $or: children };
|
|
148
|
+
},
|
|
149
|
+
not(child) {
|
|
150
|
+
return { $nor: [child] };
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
/** A `{ $field: path }` comparison operand (a field-to-field comparison). */
|
|
154
|
+
function isFieldOperand(value) {
|
|
155
|
+
return value !== null && typeof value === "object" && typeof value.$field === "string";
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* {@link mongoVisitor} plus field-to-field comparisons (`{ $field }` operands →
|
|
159
|
+
* `$expr`), null-guarded like SQL ({@link fieldCompareExpr}).
|
|
160
|
+
*/
|
|
161
|
+
const fieldOperandVisitor = {
|
|
162
|
+
...mongoVisitor,
|
|
163
|
+
comparison(field, op, value) {
|
|
164
|
+
return isFieldOperand(value) ? { $expr: fieldCompareExpr(op, `$${field}`, `$${value.$field}`) } : mongoVisitor.comparison(field, op, value);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Translates a generic {@link FilterExpr} into a MongoDB-compatible
|
|
169
|
+
* {@link Filter} document.
|
|
170
|
+
*
|
|
171
|
+
* MongoDB's query language is nearly identical to the `FilterExpr` structure,
|
|
172
|
+
* so this is largely a structural pass-through via the `walkFilter` visitor.
|
|
173
|
+
* `fieldOperands` (view predicates only — `translateQueryTree` output) turns
|
|
174
|
+
* `{ $field: path }` operands into field-to-field `$expr` comparisons; a
|
|
175
|
+
* request filter never gets that reading.
|
|
176
|
+
*/
|
|
177
|
+
function buildMongoFilter(filter, { fieldOperands = false } = {}) {
|
|
178
|
+
if (!filter || Object.keys(filter).length === 0) return EMPTY;
|
|
179
|
+
return walkFilter(filter, fieldOperands ? fieldOperandVisitor : mongoVisitor) ?? EMPTY;
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
173
182
|
//#region src/lib/mongo-accumulator.ts
|
|
174
183
|
/**
|
|
175
184
|
* The `$group` accumulator of one aggregate — shared by grouped queries
|
|
@@ -224,4 +233,4 @@ function distinctCountExpr(set) {
|
|
|
224
233
|
return { $size: set };
|
|
225
234
|
}
|
|
226
235
|
//#endregion
|
|
227
|
-
export {
|
|
236
|
+
export { queryNodeToExpr as a, orNull as i, distinctCountExpr as n, buildMongoFilter as r, buildAccumulator 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.137",
|
|
4
4
|
"description": "Mongodb plugin for atscript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@atscript/typescript": "^0.1.92",
|
|
59
59
|
"@uniqu/core": "^0.1.11",
|
|
60
60
|
"mongodb": "^6.17.0",
|
|
61
|
-
"@atscript/db": "^0.1.
|
|
61
|
+
"@atscript/db": "^0.1.137"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"postinstall": "asc -f dts",
|