@@ -18,6 +18,23 @@ API Request → QueryRunner → [MongoDB|ClickHouse|Future Adapters]
1818
1919## Query Definition Structure
2020
21+ ``` typescript
22+ interface QueryDefinition <TParams = unknown , TResult = unknown > {
23+ name: string ; // Unique identifier for logging/debugging
24+ adapters: {
25+ mongodb? : AdapterConfig <TParams , TResult >;
26+ clickhouse? : AdapterConfig <TParams , TResult >;
27+ };
28+ }
29+
30+ interface AdapterConfig <TParams = unknown , TResult = unknown > {
31+ handler: QueryHandler <TParams , TResult >;
32+ transform? : TransformFunction <TResult >;
33+ available? : boolean ; // Optional, defaults to true
34+ }
35+ ```
36+
37+ ** Example implementation:**
2138``` javascript
2239const queryDef = {
2340 name: ' QUERY_NAME' , // Unique identifier for logging/debugging
@@ -33,9 +50,8 @@ const queryDef = {
3350
3451 return {
3552 _queryMeta: {
36- type: ' mongodb' ,
37- query: pipeline,
38- collection: ' events' // Optional: additional metadata
53+ adapter: ' mongodb' , // Required: adapter name
54+ query: pipeline // Required: actual executed query
3955 },
4056 data: result
4157 };
@@ -54,15 +70,16 @@ const queryDef = {
5470
5571 return {
5672 _queryMeta: {
57- type : ' clickhouse' ,
58- query: sql
73+ adapter : ' clickhouse' , // Required: adapter name
74+ query: sql // Required: actual executed query
5975 },
6076 data: result .data
6177 };
6278 },
63- transform: async (result , transformOptions ) => {
79+ transform: async (data , transformOptions ) => {
6480 // Transform ClickHouse results to match expected format
65- return result .data .map (row => ({
81+ // Note: transform receives only the data portion, not full result
82+ return data .map (row => ({
6683 total: parseInt (row .total ) || 0
6784 }));
6885 },
@@ -74,18 +91,34 @@ const queryDef = {
7491
7592### Handler Requirements
7693
94+ ** TypeScript Signature:**
95+ ``` typescript
96+ type QueryHandler <TParams = unknown , TResult = unknown > = (
97+ params : TParams ,
98+ options ? : ExecutionOptions
99+ ) => Promise <QueryResult <TResult >>;
100+
101+ interface QueryResult <TData = unknown > {
102+ _queryMeta: QueryMeta ;
103+ data: TData ;
104+ }
105+
106+ interface QueryMeta {
107+ adapter: AdapterName ; // 'mongodb' | 'clickhouse'
108+ query: unknown ; // The actual query/pipeline executed
109+ }
110+ ```
111+
77112** Input Parameters:**
78113- ` params ` : Query-specific parameters (app_id, date ranges, filters, etc.)
79- - ` options ` * (optional)* : Execution options (timeout, connection settings , etc.)
114+ - ` options ` * (optional)* : ExecutionOptions (adapter, comparison mode , etc.)
80115
81116** Required Output Structure:**
82117``` javascript
83118{
84119 _queryMeta: {
85- type: ' mongodb' | ' clickhouse' , // Adapter type
86- query: actualQuery, // Executed query (pipeline/SQL) for debugging
87- collection?: string, // Optional: MongoDB collection name
88- // Add any other optional metadata fields as needed
120+ adapter: ' mongodb' | ' clickhouse' , // Required: adapter name
121+ query: actualQuery // Required: executed query for debugging
89122 },
90123 data: queryResults // Raw query results
91124}
@@ -96,9 +129,17 @@ const queryDef = {
96129Each adapter can optionally define a transform function to normalize or process results:
97130
98131** Transform Function Signature:**
132+ ``` typescript
133+ type TransformFunction <TInput = unknown , TOutput = unknown > = (
134+ data : TInput ,
135+ transformOptions ? : Record <string , unknown >
136+ ) => Promise <TOutput >;
137+ ```
138+
139+ ** Implementation:**
99140``` javascript
100- transform: async (result , transformOptions ) => {
101- // result: Full handler result with _queryMeta and data
141+ transform: async (data , transformOptions ) => {
142+ // data: Only the data portion from handler result (not full QueryResult)
102143 // transformOptions: Configuration object passed from executeQuery
103144 return transformedData;
104145}
@@ -109,12 +150,14 @@ transform: async (result, transformOptions) => {
109150- Transform functions should be lightweight and focused
110151- Handle data type differences (e.g., string to number conversion)
111152- MongoDB adapters often don't need transforms if data is already normalized
153+ - Transform receives only the ` data ` portion, not the full ` QueryResult ` object
112154
113155#### Handler Best Practices
114- - Always include ` _queryMeta ` with actual executed query for debugging
156+ - Always include ` _queryMeta ` with ` adapter ` name and actual executed ` query ` for debugging
115157- Return consistent data structure across adapters when possible
116158- Handle errors gracefully and let them bubble up
117- - Use ` options ` parameter for adapter-specific settings
159+ - Use ` options ` parameter for execution-related settings (not adapter-specific configs)
160+ - Follow TypeScript interfaces for type safety
118161
119162## Comparison Mode
120163
@@ -140,25 +183,53 @@ const result = await queryRunner.executeQuery(queryDef, params, { comparison: tr
140183const QueryRunner = require (' ./api/parts/data/QueryRunner.js' );
141184common .queryRunner = new QueryRunner ();
142185
143- // Normal execution
186+ // Normal execution - returns only transformed data, not full QueryResult
144187const result = await common .queryRunner .executeQuery (queryDef, params);
145188
146189// Force specific adapter
147190const result = await common .queryRunner .executeQuery (queryDef, params, { adapter: ' clickhouse' });
148191
149- // Comparison mode
192+ // Comparison mode - logs detailed comparison data to files
150193const result = await common .queryRunner .executeQuery (queryDef, params, { comparison: true });
151194
152195// With transform options
153196const transformOptions = { dateFormat: ' ISO' , includeMetadata: true };
154197const result = await common .queryRunner .executeQuery (queryDef, params, {}, transformOptions);
155198```
156199
200+ ### TypeScript Usage
201+
202+ ``` typescript
203+ import QueryRunner , { QueryDefinition , ExecutionOptions } from ' ./types/QueryRunner' ;
204+
205+ // Type-safe query definition
206+ const queryDef: QueryDefinition <{app_id: string }, {total: number }[]> = {
207+ name: ' GET_EVENT_TOTALS' ,
208+ adapters: {
209+ mongodb: {
210+ handler : async (params , options ) => {
211+ // Implementation with type safety
212+ return {
213+ _queryMeta: { adapter: ' mongodb' , query: pipeline },
214+ data: results
215+ };
216+ }
217+ }
218+ }
219+ };
220+
221+ // Type-safe execution
222+ const result: {total: number }[] = await queryRunner .executeQuery (queryDef , { app_id: ' test' });
223+ ```
224+
157225## Best Practices
158226
159- 1 . Always implement ` _queryMeta ` for debugging
160- 2 . Use adapter-specific transforms only when data formats differ between adapters
227+ 1 . Always implement ` _queryMeta ` with correct ` adapter ` name and ` query ` fields
228+ 2 . Use adapter-specific transforms only when data formats differ between adapters
1612293 . Keep transform functions lightweight and focused on format normalization
1622304 . Test with comparison mode during development to validate adapter consistency
1632315 . Monitor debug timing logs for performance issues
164- 6 . Prefer normalizing data in handlers over complex transforms when possible
232+ 6 . Prefer normalizing data in handlers over complex transforms when possible
233+ 7 . Use TypeScript definitions for type safety and better IDE support
234+ 8 . Remember that ` executeQuery ` returns only the transformed data, not the full ` QueryResult `
235+ 9 . Transform functions receive only the ` data ` portion, not the complete handler result
0 commit comments