@objectstack/driver-memory 2.0.7 → 3.0.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/driver-memory@2.0.7 build /home/runner/work/spec/spec/packages/plugins/driver-memory
2
+ > @objectstack/driver-memory@3.0.0 build /home/runner/work/spec/spec/packages/plugins/driver-memory
3
3
  > tsup --config ../../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- ESM dist/index.mjs 24.12 KB
14
- ESM dist/index.mjs.map 51.46 KB
15
- ESM ⚡️ Build success in 86ms
16
- CJS dist/index.js 25.22 KB
17
- CJS dist/index.js.map 51.50 KB
18
- CJS ⚡️ Build success in 89ms
13
+ ESM dist/index.mjs 36.64 KB
14
+ ESM dist/index.mjs.map 77.34 KB
15
+ ESM ⚡️ Build success in 93ms
16
+ CJS dist/index.js 37.79 KB
17
+ CJS dist/index.js.map 77.38 KB
18
+ CJS ⚡️ Build success in 98ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 10233ms
21
- DTS dist/index.d.mts 6.37 KB
22
- DTS dist/index.d.ts 6.37 KB
20
+ DTS ⚡️ Build success in 10279ms
21
+ DTS dist/index.d.mts 8.39 KB
22
+ DTS dist/index.d.ts 8.39 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @objectstack/driver-memory
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - Release v3.0.0 — unified version bump for all ObjectStack packages.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @objectstack/spec@3.0.0
13
+ - @objectstack/core@3.0.0
14
+
3
15
  ## 2.0.7
4
16
 
5
17
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
- import { QueryInput, DriverOptions } from '@objectstack/spec/data';
1
+ import { QueryInput, DriverOptions, Cube, AnalyticsQuery } from '@objectstack/spec/data';
2
2
  import { DriverInterface, Logger } from '@objectstack/core';
3
+ import { IAnalyticsService, AnalyticsResult, CubeMeta } from '@objectstack/spec/contracts';
3
4
 
4
5
  /**
5
6
  * Configuration options for the InMemory driver.
@@ -165,10 +166,75 @@ declare class InMemoryDriver implements DriverInterface {
165
166
  private generateId;
166
167
  }
167
168
 
169
+ /**
170
+ * Configuration for MemoryAnalyticsService
171
+ */
172
+ interface MemoryAnalyticsConfig {
173
+ /** The data driver instance to use for queries */
174
+ driver: InMemoryDriver;
175
+ /** Cube definitions for the semantic layer */
176
+ cubes: Cube[];
177
+ /** Optional logger */
178
+ logger?: Logger;
179
+ }
180
+ /**
181
+ * Memory-Based Analytics Service
182
+ *
183
+ * Implements IAnalyticsService using InMemoryDriver's aggregation capabilities.
184
+ * Provides a semantic layer (Cubes, Metrics, Dimensions) on top of in-memory data.
185
+ *
186
+ * Features:
187
+ * - Cube-based semantic modeling
188
+ * - Measure calculations (count, sum, avg, min, max, count_distinct)
189
+ * - Dimension grouping
190
+ * - Filter support
191
+ * - Time dimension handling
192
+ * - SQL generation (for debugging/transparency)
193
+ *
194
+ * This implementation is suitable for:
195
+ * - Development and testing
196
+ * - Local-first analytics
197
+ * - Small to medium datasets
198
+ * - Prototyping BI applications
199
+ */
200
+ declare class MemoryAnalyticsService implements IAnalyticsService {
201
+ private driver;
202
+ private cubes;
203
+ private logger;
204
+ constructor(config: MemoryAnalyticsConfig);
205
+ /**
206
+ * Execute an analytical query using the memory driver's aggregation pipeline
207
+ */
208
+ query(query: AnalyticsQuery): Promise<AnalyticsResult>;
209
+ /**
210
+ * Get available cube metadata for discovery
211
+ */
212
+ getMeta(cubeName?: string): Promise<CubeMeta[]>;
213
+ /**
214
+ * Generate SQL representation for debugging/transparency
215
+ */
216
+ generateSql(query: AnalyticsQuery): Promise<{
217
+ sql: string;
218
+ params: unknown[];
219
+ }>;
220
+ private resolveFieldPath;
221
+ private resolveMeasure;
222
+ private resolveDimension;
223
+ private getShortName;
224
+ private buildAggregator;
225
+ private measureTypeToFieldType;
226
+ private convertOperatorToMongo;
227
+ private operatorToSql;
228
+ private measureToSql;
229
+ private extractTableName;
230
+ private parseDateRangeString;
231
+ private generateSqlFromPipeline;
232
+ }
233
+
168
234
  declare const _default: {
169
235
  id: string;
170
236
  version: string;
171
237
  onEnable: (context: any) => Promise<void>;
172
238
  };
173
239
 
174
- export { InMemoryDriver, type InMemoryDriverConfig, _default as default };
240
+ export { InMemoryDriver, type InMemoryDriverConfig, type MemoryAnalyticsConfig, MemoryAnalyticsService, _default as default };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { QueryInput, DriverOptions } from '@objectstack/spec/data';
1
+ import { QueryInput, DriverOptions, Cube, AnalyticsQuery } from '@objectstack/spec/data';
2
2
  import { DriverInterface, Logger } from '@objectstack/core';
3
+ import { IAnalyticsService, AnalyticsResult, CubeMeta } from '@objectstack/spec/contracts';
3
4
 
4
5
  /**
5
6
  * Configuration options for the InMemory driver.
@@ -165,10 +166,75 @@ declare class InMemoryDriver implements DriverInterface {
165
166
  private generateId;
166
167
  }
167
168
 
169
+ /**
170
+ * Configuration for MemoryAnalyticsService
171
+ */
172
+ interface MemoryAnalyticsConfig {
173
+ /** The data driver instance to use for queries */
174
+ driver: InMemoryDriver;
175
+ /** Cube definitions for the semantic layer */
176
+ cubes: Cube[];
177
+ /** Optional logger */
178
+ logger?: Logger;
179
+ }
180
+ /**
181
+ * Memory-Based Analytics Service
182
+ *
183
+ * Implements IAnalyticsService using InMemoryDriver's aggregation capabilities.
184
+ * Provides a semantic layer (Cubes, Metrics, Dimensions) on top of in-memory data.
185
+ *
186
+ * Features:
187
+ * - Cube-based semantic modeling
188
+ * - Measure calculations (count, sum, avg, min, max, count_distinct)
189
+ * - Dimension grouping
190
+ * - Filter support
191
+ * - Time dimension handling
192
+ * - SQL generation (for debugging/transparency)
193
+ *
194
+ * This implementation is suitable for:
195
+ * - Development and testing
196
+ * - Local-first analytics
197
+ * - Small to medium datasets
198
+ * - Prototyping BI applications
199
+ */
200
+ declare class MemoryAnalyticsService implements IAnalyticsService {
201
+ private driver;
202
+ private cubes;
203
+ private logger;
204
+ constructor(config: MemoryAnalyticsConfig);
205
+ /**
206
+ * Execute an analytical query using the memory driver's aggregation pipeline
207
+ */
208
+ query(query: AnalyticsQuery): Promise<AnalyticsResult>;
209
+ /**
210
+ * Get available cube metadata for discovery
211
+ */
212
+ getMeta(cubeName?: string): Promise<CubeMeta[]>;
213
+ /**
214
+ * Generate SQL representation for debugging/transparency
215
+ */
216
+ generateSql(query: AnalyticsQuery): Promise<{
217
+ sql: string;
218
+ params: unknown[];
219
+ }>;
220
+ private resolveFieldPath;
221
+ private resolveMeasure;
222
+ private resolveDimension;
223
+ private getShortName;
224
+ private buildAggregator;
225
+ private measureTypeToFieldType;
226
+ private convertOperatorToMongo;
227
+ private operatorToSql;
228
+ private measureToSql;
229
+ private extractTableName;
230
+ private parseDateRangeString;
231
+ private generateSqlFromPipeline;
232
+ }
233
+
168
234
  declare const _default: {
169
235
  id: string;
170
236
  version: string;
171
237
  onEnable: (context: any) => Promise<void>;
172
238
  };
173
239
 
174
- export { InMemoryDriver, type InMemoryDriverConfig, _default as default };
240
+ export { InMemoryDriver, type InMemoryDriverConfig, type MemoryAnalyticsConfig, MemoryAnalyticsService, _default as default };
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  InMemoryDriver: () => InMemoryDriver,
24
+ MemoryAnalyticsService: () => MemoryAnalyticsService,
24
25
  default: () => index_default
25
26
  });
26
27
  module.exports = __toCommonJS(index_exports);
@@ -703,6 +704,399 @@ var InMemoryDriver = class {
703
704
  }
704
705
  };
705
706
 
707
+ // src/memory-analytics.ts
708
+ var import_core2 = require("@objectstack/core");
709
+ var MemoryAnalyticsService = class {
710
+ constructor(config) {
711
+ this.driver = config.driver;
712
+ this.cubes = new Map(config.cubes.map((c) => [c.name, c]));
713
+ this.logger = config.logger || (0, import_core2.createLogger)({ level: "info", format: "pretty" });
714
+ this.logger.debug("MemoryAnalyticsService initialized", { cubeCount: this.cubes.size });
715
+ }
716
+ /**
717
+ * Execute an analytical query using the memory driver's aggregation pipeline
718
+ */
719
+ async query(query) {
720
+ this.logger.debug("Executing analytics query", { cube: query.cube, measures: query.measures });
721
+ if (!query.cube) {
722
+ throw new Error("Cube name is required");
723
+ }
724
+ const cube = this.cubes.get(query.cube);
725
+ if (!cube) {
726
+ throw new Error(`Cube not found: ${query.cube}`);
727
+ }
728
+ const pipeline = [];
729
+ if (query.filters && query.filters.length > 0) {
730
+ const matchStage = {};
731
+ for (const filter of query.filters) {
732
+ const mongoOp = this.convertOperatorToMongo(filter.operator);
733
+ const fieldPath = this.resolveFieldPath(cube, filter.member);
734
+ if (filter.values && filter.values.length > 0) {
735
+ if (mongoOp === "$in") {
736
+ matchStage[fieldPath] = { $in: filter.values };
737
+ } else if (mongoOp === "$nin") {
738
+ matchStage[fieldPath] = { $nin: filter.values };
739
+ } else {
740
+ matchStage[fieldPath] = { [mongoOp]: filter.values[0] };
741
+ }
742
+ } else if (mongoOp === "$exists") {
743
+ matchStage[fieldPath] = { $exists: filter.operator === "set" };
744
+ }
745
+ }
746
+ if (Object.keys(matchStage).length > 0) {
747
+ pipeline.push({ $match: matchStage });
748
+ }
749
+ }
750
+ if (query.timeDimensions && query.timeDimensions.length > 0) {
751
+ for (const timeDim of query.timeDimensions) {
752
+ const fieldPath = this.resolveFieldPath(cube, timeDim.dimension);
753
+ if (timeDim.dateRange) {
754
+ const range = Array.isArray(timeDim.dateRange) ? timeDim.dateRange : this.parseDateRangeString(timeDim.dateRange);
755
+ if (range.length === 2) {
756
+ pipeline.push({
757
+ $match: {
758
+ [fieldPath]: {
759
+ $gte: new Date(range[0]),
760
+ $lte: new Date(range[1])
761
+ }
762
+ }
763
+ });
764
+ }
765
+ }
766
+ }
767
+ }
768
+ const groupStage = { _id: {} };
769
+ if (query.dimensions && query.dimensions.length > 0) {
770
+ for (const dim of query.dimensions) {
771
+ const fieldPath = this.resolveFieldPath(cube, dim);
772
+ const dimName = this.getShortName(dim);
773
+ groupStage._id[dimName] = `$${fieldPath}`;
774
+ }
775
+ } else {
776
+ groupStage._id = null;
777
+ }
778
+ if (query.measures && query.measures.length > 0) {
779
+ for (const measure of query.measures) {
780
+ const measureDef = this.resolveMeasure(cube, measure);
781
+ const measureName = this.getShortName(measure);
782
+ if (measureDef) {
783
+ const aggregator = this.buildAggregator(measureDef);
784
+ groupStage[measureName] = aggregator;
785
+ }
786
+ }
787
+ }
788
+ pipeline.push({ $group: groupStage });
789
+ const projectStage = { _id: 0 };
790
+ if (query.dimensions && query.dimensions.length > 0) {
791
+ for (const dim of query.dimensions) {
792
+ const dimName = this.getShortName(dim);
793
+ projectStage[dimName] = `$_id.${dimName}`;
794
+ }
795
+ }
796
+ if (query.measures && query.measures.length > 0) {
797
+ for (const measure of query.measures) {
798
+ const measureName = this.getShortName(measure);
799
+ projectStage[measureName] = `$${measureName}`;
800
+ }
801
+ }
802
+ pipeline.push({ $project: projectStage });
803
+ if (query.order && Object.keys(query.order).length > 0) {
804
+ const sortStage = {};
805
+ for (const [field, direction] of Object.entries(query.order)) {
806
+ const shortName = this.getShortName(field);
807
+ sortStage[shortName] = direction === "asc" ? 1 : -1;
808
+ }
809
+ pipeline.push({ $sort: sortStage });
810
+ }
811
+ if (query.offset) {
812
+ pipeline.push({ $skip: query.offset });
813
+ }
814
+ if (query.limit) {
815
+ pipeline.push({ $limit: query.limit });
816
+ }
817
+ const tableName = this.extractTableName(cube.sql);
818
+ const rawRows = await this.driver.aggregate(tableName, pipeline);
819
+ const rows = rawRows.map((row) => {
820
+ const renamedRow = {};
821
+ if (query.dimensions) {
822
+ for (const dim of query.dimensions) {
823
+ const shortName = this.getShortName(dim);
824
+ if (shortName in row) {
825
+ renamedRow[dim] = row[shortName];
826
+ }
827
+ }
828
+ }
829
+ if (query.measures) {
830
+ for (const measure of query.measures) {
831
+ const shortName = this.getShortName(measure);
832
+ if (shortName in row) {
833
+ renamedRow[measure] = row[shortName];
834
+ }
835
+ }
836
+ }
837
+ return renamedRow;
838
+ });
839
+ const fields = [];
840
+ if (query.dimensions) {
841
+ for (const dim of query.dimensions) {
842
+ const dimension = this.resolveDimension(cube, dim);
843
+ fields.push({
844
+ name: dim,
845
+ type: dimension?.type || "string"
846
+ });
847
+ }
848
+ }
849
+ if (query.measures) {
850
+ for (const measure of query.measures) {
851
+ const measureDef = this.resolveMeasure(cube, measure);
852
+ fields.push({
853
+ name: measure,
854
+ type: this.measureTypeToFieldType(measureDef?.type || "count")
855
+ });
856
+ }
857
+ }
858
+ this.logger.debug("Analytics query completed", { rowCount: rows.length });
859
+ return {
860
+ rows,
861
+ fields,
862
+ sql: this.generateSqlFromPipeline(tableName, pipeline)
863
+ // For debugging
864
+ };
865
+ }
866
+ /**
867
+ * Get available cube metadata for discovery
868
+ */
869
+ async getMeta(cubeName) {
870
+ const cubes = cubeName ? [this.cubes.get(cubeName)].filter(Boolean) : Array.from(this.cubes.values());
871
+ return cubes.map((cube) => ({
872
+ name: cube.name,
873
+ title: cube.title,
874
+ measures: Object.entries(cube.measures).map(([key, measure]) => ({
875
+ name: `${cube.name}.${key}`,
876
+ type: measure.type,
877
+ title: measure.label
878
+ })),
879
+ dimensions: Object.entries(cube.dimensions).map(([key, dimension]) => ({
880
+ name: `${cube.name}.${key}`,
881
+ type: dimension.type,
882
+ title: dimension.label
883
+ }))
884
+ }));
885
+ }
886
+ /**
887
+ * Generate SQL representation for debugging/transparency
888
+ */
889
+ async generateSql(query) {
890
+ if (!query.cube) {
891
+ throw new Error("Cube name is required");
892
+ }
893
+ const cube = this.cubes.get(query.cube);
894
+ if (!cube) {
895
+ throw new Error(`Cube not found: ${query.cube}`);
896
+ }
897
+ const tableName = this.extractTableName(cube.sql);
898
+ const selectClauses = [];
899
+ const groupByClauses = [];
900
+ if (query.dimensions && query.dimensions.length > 0) {
901
+ for (const dim of query.dimensions) {
902
+ const fieldPath = this.resolveFieldPath(cube, dim);
903
+ selectClauses.push(`${fieldPath} AS "${dim}"`);
904
+ groupByClauses.push(fieldPath);
905
+ }
906
+ }
907
+ if (query.measures && query.measures.length > 0) {
908
+ for (const measure of query.measures) {
909
+ const measureDef = this.resolveMeasure(cube, measure);
910
+ if (measureDef) {
911
+ const aggSql = this.measureToSql(measureDef);
912
+ selectClauses.push(`${aggSql} AS "${measure}"`);
913
+ }
914
+ }
915
+ }
916
+ const whereClauses = [];
917
+ if (query.filters && query.filters.length > 0) {
918
+ for (const filter of query.filters) {
919
+ const fieldPath = this.resolveFieldPath(cube, filter.member);
920
+ const sqlOp = this.operatorToSql(filter.operator);
921
+ if (filter.values && filter.values.length > 0) {
922
+ whereClauses.push(`${fieldPath} ${sqlOp} '${filter.values[0]}'`);
923
+ }
924
+ }
925
+ }
926
+ let sql = `SELECT ${selectClauses.join(", ")} FROM ${tableName}`;
927
+ if (whereClauses.length > 0) {
928
+ sql += ` WHERE ${whereClauses.join(" AND ")}`;
929
+ }
930
+ if (groupByClauses.length > 0) {
931
+ sql += ` GROUP BY ${groupByClauses.join(", ")}`;
932
+ }
933
+ if (query.order) {
934
+ const orderClauses = Object.entries(query.order).map(
935
+ ([field, dir]) => `"${field}" ${dir.toUpperCase()}`
936
+ );
937
+ sql += ` ORDER BY ${orderClauses.join(", ")}`;
938
+ }
939
+ if (query.limit) {
940
+ sql += ` LIMIT ${query.limit}`;
941
+ }
942
+ if (query.offset) {
943
+ sql += ` OFFSET ${query.offset}`;
944
+ }
945
+ return { sql, params: [] };
946
+ }
947
+ // ===================================
948
+ // Helper Methods
949
+ // ===================================
950
+ resolveFieldPath(cube, member) {
951
+ const parts = member.split(".");
952
+ const fieldName = parts.length > 1 ? parts[1] : parts[0];
953
+ const dimension = cube.dimensions[fieldName];
954
+ if (dimension) {
955
+ return dimension.sql.replace(/^\$/, "");
956
+ }
957
+ const measure = cube.measures[fieldName];
958
+ if (measure) {
959
+ return measure.sql.replace(/^\$/, "");
960
+ }
961
+ return fieldName;
962
+ }
963
+ resolveMeasure(cube, measureName) {
964
+ const parts = measureName.split(".");
965
+ const fieldName = parts.length > 1 ? parts[1] : parts[0];
966
+ return cube.measures[fieldName];
967
+ }
968
+ resolveDimension(cube, dimensionName) {
969
+ const parts = dimensionName.split(".");
970
+ const fieldName = parts.length > 1 ? parts[1] : parts[0];
971
+ return cube.dimensions[fieldName];
972
+ }
973
+ getShortName(fullName) {
974
+ const parts = fullName.split(".");
975
+ return parts.length > 1 ? parts[1] : parts[0];
976
+ }
977
+ buildAggregator(measure) {
978
+ const fieldPath = measure.sql.replace(/^\$/, "");
979
+ switch (measure.type) {
980
+ case "count":
981
+ return { $sum: 1 };
982
+ case "sum":
983
+ return { $sum: `$${fieldPath}` };
984
+ case "avg":
985
+ return { $avg: `$${fieldPath}` };
986
+ case "min":
987
+ return { $min: `$${fieldPath}` };
988
+ case "max":
989
+ return { $max: `$${fieldPath}` };
990
+ case "count_distinct":
991
+ return { $addToSet: `$${fieldPath}` };
992
+ // Will need post-processing for count
993
+ default:
994
+ return { $sum: 1 };
995
+ }
996
+ }
997
+ measureTypeToFieldType(measureType) {
998
+ switch (measureType) {
999
+ case "count":
1000
+ case "sum":
1001
+ case "count_distinct":
1002
+ return "number";
1003
+ case "avg":
1004
+ case "min":
1005
+ case "max":
1006
+ return "number";
1007
+ case "string":
1008
+ return "string";
1009
+ case "boolean":
1010
+ return "boolean";
1011
+ default:
1012
+ return "number";
1013
+ }
1014
+ }
1015
+ convertOperatorToMongo(operator) {
1016
+ const opMap = {
1017
+ "equals": "$eq",
1018
+ "notEquals": "$ne",
1019
+ "contains": "$regex",
1020
+ "notContains": "$not",
1021
+ "gt": "$gt",
1022
+ "gte": "$gte",
1023
+ "lt": "$lt",
1024
+ "lte": "$lte",
1025
+ "set": "$exists",
1026
+ "notSet": "$exists",
1027
+ "inDateRange": "$gte"
1028
+ // Will need special handling
1029
+ };
1030
+ return opMap[operator] || "$eq";
1031
+ }
1032
+ operatorToSql(operator) {
1033
+ const opMap = {
1034
+ "equals": "=",
1035
+ "notEquals": "!=",
1036
+ "contains": "LIKE",
1037
+ "notContains": "NOT LIKE",
1038
+ "gt": ">",
1039
+ "gte": ">=",
1040
+ "lt": "<",
1041
+ "lte": "<="
1042
+ };
1043
+ return opMap[operator] || "=";
1044
+ }
1045
+ measureToSql(measure) {
1046
+ const fieldPath = measure.sql.replace(/^\$/, "");
1047
+ switch (measure.type) {
1048
+ case "count":
1049
+ return "COUNT(*)";
1050
+ case "sum":
1051
+ return `SUM(${fieldPath})`;
1052
+ case "avg":
1053
+ return `AVG(${fieldPath})`;
1054
+ case "min":
1055
+ return `MIN(${fieldPath})`;
1056
+ case "max":
1057
+ return `MAX(${fieldPath})`;
1058
+ case "count_distinct":
1059
+ return `COUNT(DISTINCT ${fieldPath})`;
1060
+ default:
1061
+ return "COUNT(*)";
1062
+ }
1063
+ }
1064
+ extractTableName(sql) {
1065
+ return sql.trim();
1066
+ }
1067
+ parseDateRangeString(range) {
1068
+ const now = /* @__PURE__ */ new Date();
1069
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
1070
+ if (range === "today") {
1071
+ return [today.toISOString(), new Date(today.getTime() + 864e5).toISOString()];
1072
+ } else if (range.startsWith("last ")) {
1073
+ const parts = range.split(" ");
1074
+ const num = parseInt(parts[1]);
1075
+ const unit = parts[2];
1076
+ const start = new Date(today);
1077
+ if (unit.startsWith("day")) {
1078
+ start.setDate(start.getDate() - num);
1079
+ } else if (unit.startsWith("week")) {
1080
+ start.setDate(start.getDate() - num * 7);
1081
+ } else if (unit.startsWith("month")) {
1082
+ start.setMonth(start.getMonth() - num);
1083
+ } else if (unit.startsWith("year")) {
1084
+ start.setFullYear(start.getFullYear() - num);
1085
+ }
1086
+ return [start.toISOString(), now.toISOString()];
1087
+ }
1088
+ return [range, range];
1089
+ }
1090
+ generateSqlFromPipeline(table, pipeline) {
1091
+ const stages = pipeline.map((stage, idx) => {
1092
+ const op = Object.keys(stage)[0];
1093
+ return `/* Stage ${idx + 1}: ${op} */ ${JSON.stringify(stage[op])}`;
1094
+ }).join("\n");
1095
+ return `-- MongoDB Aggregation Pipeline on table: ${table}
1096
+ ${stages}`;
1097
+ }
1098
+ };
1099
+
706
1100
  // src/index.ts
707
1101
  var index_default = {
708
1102
  id: "com.objectstack.driver.memory",
@@ -721,6 +1115,7 @@ var index_default = {
721
1115
  };
722
1116
  // Annotate the CommonJS export names for ESM import in node:
723
1117
  0 && (module.exports = {
724
- InMemoryDriver
1118
+ InMemoryDriver,
1119
+ MemoryAnalyticsService
725
1120
  });
726
1121
  //# sourceMappingURL=index.js.map