@objectstack/driver-memory 4.0.5 → 4.1.0
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.d.mts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +164 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +164 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -842,9 +842,14 @@ var _InMemoryDriver = class _InMemoryDriver {
|
|
|
842
842
|
performAggregation(records, query) {
|
|
843
843
|
const { groupBy, aggregations } = query;
|
|
844
844
|
const groups = /* @__PURE__ */ new Map();
|
|
845
|
+
const normalizeGroupBy = (node) => {
|
|
846
|
+
if (typeof node === "string") return { field: node, alias: node };
|
|
847
|
+
return { field: node.field, alias: node.alias ?? node.field };
|
|
848
|
+
};
|
|
845
849
|
if (groupBy && groupBy.length > 0) {
|
|
846
850
|
for (const record of records) {
|
|
847
|
-
const keyParts = groupBy.map((
|
|
851
|
+
const keyParts = groupBy.map((node) => {
|
|
852
|
+
const { field } = normalizeGroupBy(node);
|
|
848
853
|
const val = getValueByPath(record, field);
|
|
849
854
|
return val === void 0 || val === null ? "null" : String(val);
|
|
850
855
|
});
|
|
@@ -863,8 +868,9 @@ var _InMemoryDriver = class _InMemoryDriver {
|
|
|
863
868
|
if (groupBy && groupBy.length > 0) {
|
|
864
869
|
if (groupRecords.length > 0) {
|
|
865
870
|
const firstRecord = groupRecords[0];
|
|
866
|
-
for (const
|
|
867
|
-
|
|
871
|
+
for (const node of groupBy) {
|
|
872
|
+
const { field, alias } = normalizeGroupBy(node);
|
|
873
|
+
this.setValueByPath(row, alias, getValueByPath(firstRecord, field));
|
|
868
874
|
}
|
|
869
875
|
}
|
|
870
876
|
}
|
|
@@ -1145,18 +1151,20 @@ var MemoryAnalyticsService = class {
|
|
|
1145
1151
|
throw new Error(`Cube not found: ${query.cube}`);
|
|
1146
1152
|
}
|
|
1147
1153
|
const pipeline = [];
|
|
1148
|
-
|
|
1154
|
+
const normalizedFilters = this.normalizeFilters(query);
|
|
1155
|
+
if (normalizedFilters.length > 0) {
|
|
1149
1156
|
const matchStage = {};
|
|
1150
|
-
for (const filter of
|
|
1157
|
+
for (const filter of normalizedFilters) {
|
|
1151
1158
|
const mongoOp = this.convertOperatorToMongo(filter.operator);
|
|
1152
1159
|
const fieldPath = this.resolveFieldPath(cube, filter.member);
|
|
1153
1160
|
if (filter.values && filter.values.length > 0) {
|
|
1161
|
+
const coerced = filter.values.map((v) => this.coerceFilterValue(v));
|
|
1154
1162
|
if (mongoOp === "$in") {
|
|
1155
|
-
matchStage[fieldPath] = { $in:
|
|
1163
|
+
matchStage[fieldPath] = { $in: coerced };
|
|
1156
1164
|
} else if (mongoOp === "$nin") {
|
|
1157
|
-
matchStage[fieldPath] = { $nin:
|
|
1165
|
+
matchStage[fieldPath] = { $nin: coerced };
|
|
1158
1166
|
} else {
|
|
1159
|
-
matchStage[fieldPath] = { [mongoOp]:
|
|
1167
|
+
matchStage[fieldPath] = { [mongoOp]: coerced[0] };
|
|
1160
1168
|
}
|
|
1161
1169
|
} else if (mongoOp === "$exists") {
|
|
1162
1170
|
matchStage[fieldPath] = { $exists: filter.operator === "set" };
|
|
@@ -1333,12 +1341,14 @@ var MemoryAnalyticsService = class {
|
|
|
1333
1341
|
}
|
|
1334
1342
|
}
|
|
1335
1343
|
const whereClauses = [];
|
|
1336
|
-
|
|
1337
|
-
|
|
1344
|
+
const normalizedFilters = this.normalizeFilters(query);
|
|
1345
|
+
if (normalizedFilters.length > 0) {
|
|
1346
|
+
for (const filter of normalizedFilters) {
|
|
1338
1347
|
const fieldPath = this.resolveFieldPath(cube, filter.member);
|
|
1339
1348
|
const sqlOp = this.operatorToSql(filter.operator);
|
|
1340
1349
|
if (filter.values && filter.values.length > 0) {
|
|
1341
|
-
|
|
1350
|
+
const literal = this.toSqlLiteral(filter.values[0]);
|
|
1351
|
+
whereClauses.push(`${fieldPath} ${sqlOp} ${literal}`);
|
|
1342
1352
|
}
|
|
1343
1353
|
}
|
|
1344
1354
|
}
|
|
@@ -1366,6 +1376,147 @@ var MemoryAnalyticsService = class {
|
|
|
1366
1376
|
// ===================================
|
|
1367
1377
|
// Helper Methods
|
|
1368
1378
|
// ===================================
|
|
1379
|
+
/**
|
|
1380
|
+
* Normalize filters into a cube-style array regardless of input shape.
|
|
1381
|
+
*
|
|
1382
|
+
* Accepts:
|
|
1383
|
+
* - undefined / null → []
|
|
1384
|
+
* - cube-style array `[{member, operator, values}]` → returned as-is
|
|
1385
|
+
* - MongoDB FilterCondition object (per spec/data/filter.zod.ts):
|
|
1386
|
+
* * implicit equality: `{is_active: true}`
|
|
1387
|
+
* * operator wrapper: `{stage: {$nin: [...]}}`
|
|
1388
|
+
* * mixed: `{stage: 'won', amount: {$gte: 100}}`
|
|
1389
|
+
* → flattened into one cube-style entry per (field, operator) pair
|
|
1390
|
+
*
|
|
1391
|
+
* Logical combinators (`$and`, `$or`, `$not`) are not yet expanded into
|
|
1392
|
+
* the cube pipeline; for current dashboard widget metadata the implicit
|
|
1393
|
+
* top-level AND of fields is sufficient. `$and` clauses are flattened
|
|
1394
|
+
* into the same AND list.
|
|
1395
|
+
*/
|
|
1396
|
+
normalizeFilters(query) {
|
|
1397
|
+
if (!query || typeof query !== "object") return [];
|
|
1398
|
+
const out = [];
|
|
1399
|
+
const where = query.where;
|
|
1400
|
+
if (where && typeof where === "object" && !Array.isArray(where)) {
|
|
1401
|
+
this.flattenFilterCondition(where, out);
|
|
1402
|
+
}
|
|
1403
|
+
return out;
|
|
1404
|
+
}
|
|
1405
|
+
flattenFilterCondition(cond, out) {
|
|
1406
|
+
for (const [key, raw] of Object.entries(cond)) {
|
|
1407
|
+
if (raw == null) continue;
|
|
1408
|
+
if (key === "$and" && Array.isArray(raw)) {
|
|
1409
|
+
for (const sub of raw) {
|
|
1410
|
+
if (sub && typeof sub === "object") {
|
|
1411
|
+
this.flattenFilterCondition(sub, out);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
if (key === "$or" || key === "$not") continue;
|
|
1417
|
+
if (typeof raw === "object" && !Array.isArray(raw) && !(raw instanceof Date)) {
|
|
1418
|
+
const wrapper = raw;
|
|
1419
|
+
const opEntries = Object.keys(wrapper).filter((k) => k.startsWith("$"));
|
|
1420
|
+
if (opEntries.length > 0) {
|
|
1421
|
+
for (const opKey of opEntries) {
|
|
1422
|
+
const cubeOp = this.mongoOperatorToCubeOperator(opKey);
|
|
1423
|
+
if (!cubeOp) continue;
|
|
1424
|
+
const v = wrapper[opKey];
|
|
1425
|
+
const values2 = Array.isArray(v) ? v.map((x) => this.stringifyForCube(x)) : [this.stringifyForCube(v)];
|
|
1426
|
+
out.push({ member: key, operator: cubeOp, values: values2 });
|
|
1427
|
+
}
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
for (const [nestedKey, nestedVal] of Object.entries(wrapper)) {
|
|
1431
|
+
this.flattenFilterCondition({ [`${key}.${nestedKey}`]: nestedVal }, out);
|
|
1432
|
+
}
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
const values = Array.isArray(raw) ? raw.map((x) => this.stringifyForCube(x)) : [this.stringifyForCube(raw)];
|
|
1436
|
+
out.push({
|
|
1437
|
+
member: key,
|
|
1438
|
+
operator: Array.isArray(raw) ? "in" : "equals",
|
|
1439
|
+
values
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Map MongoDB-style `$op` keys (from FilterCondition) to the cube-style
|
|
1445
|
+
* operator names accepted by `convertOperatorToMongo` / `operatorToSql`.
|
|
1446
|
+
*/
|
|
1447
|
+
mongoOperatorToCubeOperator(op) {
|
|
1448
|
+
switch (op) {
|
|
1449
|
+
case "$eq":
|
|
1450
|
+
return "equals";
|
|
1451
|
+
case "$ne":
|
|
1452
|
+
return "notEquals";
|
|
1453
|
+
case "$gt":
|
|
1454
|
+
return "gt";
|
|
1455
|
+
case "$gte":
|
|
1456
|
+
return "gte";
|
|
1457
|
+
case "$lt":
|
|
1458
|
+
return "lt";
|
|
1459
|
+
case "$lte":
|
|
1460
|
+
return "lte";
|
|
1461
|
+
case "$in":
|
|
1462
|
+
return "in";
|
|
1463
|
+
case "$nin":
|
|
1464
|
+
return "notIn";
|
|
1465
|
+
case "$contains":
|
|
1466
|
+
return "contains";
|
|
1467
|
+
case "$notContains":
|
|
1468
|
+
return "notContains";
|
|
1469
|
+
case "$exists":
|
|
1470
|
+
return "set";
|
|
1471
|
+
default:
|
|
1472
|
+
return null;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Stringify a filter value for cube-style storage. Booleans become
|
|
1477
|
+
* `'1'/'0'` so that downstream consumers expecting SQLite-style
|
|
1478
|
+
* numeric booleans match correctly. The in-memory pipeline uses
|
|
1479
|
+
* {@link coerceFilterValue} to recover real JS types from these
|
|
1480
|
+
* strings.
|
|
1481
|
+
*/
|
|
1482
|
+
stringifyForCube(v) {
|
|
1483
|
+
if (v == null) return "";
|
|
1484
|
+
if (typeof v === "boolean") return v ? "1" : "0";
|
|
1485
|
+
if (v instanceof Date) return v.toISOString();
|
|
1486
|
+
if (typeof v === "object") return JSON.stringify(v);
|
|
1487
|
+
return String(v);
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Recover a runtime value from its cube-stringified form for in-memory
|
|
1491
|
+
* comparison. Booleans, integers, floats and ISO-date-like strings are
|
|
1492
|
+
* coerced; everything else stays as a string.
|
|
1493
|
+
*/
|
|
1494
|
+
coerceFilterValue(s) {
|
|
1495
|
+
if (s === "true") return true;
|
|
1496
|
+
if (s === "false") return false;
|
|
1497
|
+
if (s === "null") return null;
|
|
1498
|
+
if (/^-?\d+$/.test(s)) {
|
|
1499
|
+
const n = Number(s);
|
|
1500
|
+
if (Number.isFinite(n)) return n;
|
|
1501
|
+
}
|
|
1502
|
+
if (/^-?\d+\.\d+$/.test(s)) {
|
|
1503
|
+
const n = Number(s);
|
|
1504
|
+
if (Number.isFinite(n)) return n;
|
|
1505
|
+
}
|
|
1506
|
+
return s;
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Type-aware SQL literal formatter. Booleans and numbers are emitted
|
|
1510
|
+
* unquoted; everything else is single-quoted with embedded quotes
|
|
1511
|
+
* escaped.
|
|
1512
|
+
*/
|
|
1513
|
+
toSqlLiteral(s) {
|
|
1514
|
+
if (s === "true") return "1";
|
|
1515
|
+
if (s === "false") return "0";
|
|
1516
|
+
if (s === "null") return "NULL";
|
|
1517
|
+
if (/^-?\d+(\.\d+)?$/.test(s)) return s;
|
|
1518
|
+
return `'${s.replace(/'/g, "''")}'`;
|
|
1519
|
+
}
|
|
1369
1520
|
resolveFieldPath(cube, member) {
|
|
1370
1521
|
const parts = member.split(".");
|
|
1371
1522
|
const fieldName = parts.length > 1 ? parts[1] : parts[0];
|
|
@@ -1454,6 +1605,8 @@ var MemoryAnalyticsService = class {
|
|
|
1454
1605
|
"gte": "$gte",
|
|
1455
1606
|
"lt": "$lt",
|
|
1456
1607
|
"lte": "$lte",
|
|
1608
|
+
"in": "$in",
|
|
1609
|
+
"notIn": "$nin",
|
|
1457
1610
|
"set": "$exists",
|
|
1458
1611
|
"notSet": "$exists",
|
|
1459
1612
|
"inDateRange": "$gte"
|