@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.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +68 -2
- package/dist/index.d.ts +68 -2
- package/dist/index.js +396 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +394 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +3 -0
- package/src/memory-analytics.test.ts +346 -0
- package/src/memory-analytics.ts +518 -0
- package/src/memory-driver.ts +3 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/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
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.mjs [22m[
|
|
14
|
-
[32mESM[39m [1mdist/index.mjs.map [22m[
|
|
15
|
-
[32mESM[39m ⚡️ Build success in
|
|
16
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
17
|
-
[32mCJS[39m [1mdist/index.js.map [22m[
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m36.64 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m77.34 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 93ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m37.79 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m77.38 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 98ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 10279ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m8.39 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m8.39 KB[39m
|
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
|