@devrev/meerkat-core 0.0.96 → 0.0.97
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/README.md +70 -6
- package/ast-builder/ast-builder.js +1 -1
- package/ast-builder/ast-builder.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,75 @@
|
|
|
1
|
-
# meerkat-core
|
|
1
|
+
# @devrev/meerkat-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@devrev/meerkat-core` is the foundational library for the Meerkat ecosystem, a TypeScript SDK that seamlessly translates Cube-like queries into DuckDB Abstract Syntax Trees (AST). It provides the core logic for query transformation, designed to be environment-agnostic, running in both Node.js and browser environments.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This package focuses exclusively on generating a DuckDB-compatible AST from a JSON-based query object. It does not handle query execution, which is the responsibility of environment-specific packages like `@devrev/meerkat-node` and `@devrev/meerkat-browser`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Key Features
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- **Cube-to-AST Transformation**: Converts Cube-style JSON queries into DuckDB-compatible SQL ASTs.
|
|
10
|
+
- **Environment Agnostic**: Runs in both Node.js and browser environments.
|
|
11
|
+
- **Type-Safe**: Provides strong TypeScript definitions for queries, schemas, and filters.
|
|
12
|
+
- **Advanced Filtering and Joins**: Supports complex filters, logical operators, and multi-table joins.
|
|
13
|
+
- **Extensible by Design**: Leverages DuckDB's native JSON serialization, avoiding the limitations of traditional query builders.
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @devrev/meerkat-core
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Core Concepts
|
|
22
|
+
|
|
23
|
+
`meerkat-core` revolves around two main objects:
|
|
24
|
+
|
|
25
|
+
1. **`Query`**: A JSON object that defines your analytics request. It specifies measures, dimensions, filters, and ordering.
|
|
26
|
+
2. **`TableSchema`**: Defines the structure of your data tables, including columns, measures, dimensions, and joins.
|
|
27
|
+
|
|
28
|
+
The library uses these objects to generate a DuckDB AST. This AST can then be passed to an execution engine.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
Here's how to transform a Cube-style query into a DuckDB AST:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { cubeToDuckdbAST, Query, TableSchema } from '@devrev/meerkat-core';
|
|
36
|
+
|
|
37
|
+
// 1. Define the schema for your table
|
|
38
|
+
const schema: TableSchema = {
|
|
39
|
+
name: 'users',
|
|
40
|
+
sql: 'SELECT * FROM users',
|
|
41
|
+
columns: [
|
|
42
|
+
{ name: 'id', type: 'INTEGER' },
|
|
43
|
+
{ name: 'name', type: 'VARCHAR' },
|
|
44
|
+
{ name: 'city', type: 'VARCHAR' },
|
|
45
|
+
{ name: 'signed_up_at', type: 'TIMESTAMP' },
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// 2. Define your query
|
|
50
|
+
const query: Query = {
|
|
51
|
+
measures: ['users.count'],
|
|
52
|
+
dimensions: ['users.city'],
|
|
53
|
+
filters: [
|
|
54
|
+
{
|
|
55
|
+
member: 'users.city',
|
|
56
|
+
operator: 'equals',
|
|
57
|
+
values: ['New York'],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
limit: 100,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// 3. Generate the DuckDB AST
|
|
64
|
+
const ast = cubeToDuckdbAST(query, schema);
|
|
65
|
+
|
|
66
|
+
// The `ast` can now be deserialized into a SQL string for execution.
|
|
67
|
+
console.log(JSON.stringify(ast, null, 2));
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Ecosystem
|
|
71
|
+
|
|
72
|
+
`meerkat-core` is the foundation for:
|
|
73
|
+
|
|
74
|
+
- **`@devrev/meerkat-node`**: For server-side analytics in Node.js with `@duckdb/node-api`.
|
|
75
|
+
- **`@devrev/meerkat-browser`**: For client-side analytics in the browser with `@duckdb/duckdb-wasm`.
|
|
@@ -75,7 +75,7 @@ const cubeToDuckdbAST = (query, tableSchema, options)=>{
|
|
|
75
75
|
];
|
|
76
76
|
}
|
|
77
77
|
node.modifiers = [];
|
|
78
|
-
if (query.order) {
|
|
78
|
+
if (query.order && Object.keys(query.order).length > 0) {
|
|
79
79
|
node.modifiers.push((0, _cubeorderbytransformer.cubeOrderByToAST)(query.order));
|
|
80
80
|
}
|
|
81
81
|
if (query.limit || query.offset) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../meerkat-core/src/ast-builder/ast-builder.ts"],"sourcesContent":["import { cubeFilterToDuckdbAST } from '../cube-filter-transformer/factory';\nimport { cubeDimensionToGroupByAST } from '../cube-group-by-transformer/cube-group-by-transformer';\nimport { cubeLimitOffsetToAST } from '../cube-limit-offset-transformer/cube-limit-offset-transformer';\nimport { cubeOrderByToAST } from '../cube-order-by-transformer/cube-order-by-transformer';\nimport {\n QueryFiltersWithInfo,\n QueryFiltersWithInfoSingular,\n} from '../cube-to-duckdb/cube-filter-to-duckdb';\nimport { traverseAndFilter } from '../filter-params/filter-params-ast';\nimport { memberKeyToSafeKey } from '../member-formatters/member-key-to-safe-key';\nimport {\n FilterType,\n MeerkatQueryFilter,\n Query,\n} from '../types/cube-types/query';\nimport { TableSchema } from '../types/cube-types/table';\nimport { SelectStatement } from '../types/duckdb-serialization-types';\nimport { SelectNode } from '../types/duckdb-serialization-types/serialization/QueryNode';\nimport { getBaseAST } from '../utils/base-ast';\nimport { cubeFiltersEnrichment } from '../utils/cube-filter-enrichment';\nimport { modifyLeafMeerkatFilter } from '../utils/modify-meerkat-filter';\n\nconst formatFilters = (\n queryFiltersWithInfo: QueryFiltersWithInfo,\n filterType?: FilterType\n) => {\n /*\n * If the type of filter is set to base filter where\n */\n return filterType === 'BASE_FILTER'\n ? queryFiltersWithInfo\n : (modifyLeafMeerkatFilter(queryFiltersWithInfo, (item) => {\n return {\n ...item,\n member: memberKeyToSafeKey(item.member),\n };\n }) as QueryFiltersWithInfo);\n};\n\nconst getFormattedFilters = ({\n queryFiltersWithInfo,\n filterType,\n mapperFn,\n baseAST,\n}: {\n queryFiltersWithInfo: QueryFiltersWithInfo;\n filterType?: FilterType;\n baseAST: SelectStatement;\n mapperFn: (val: QueryFiltersWithInfoSingular) => MeerkatQueryFilter | null;\n}) => {\n const filters = queryFiltersWithInfo\n .map((item) => mapperFn(item))\n .filter(Boolean) as QueryFiltersWithInfoSingular[];\n const formattedFilters = formatFilters(filters, filterType);\n return cubeFilterToDuckdbAST(formattedFilters, baseAST);\n};\n\nexport const cubeToDuckdbAST = (\n query: Query,\n tableSchema: TableSchema,\n options?: { filterType: FilterType }\n) => {\n /**\n * Obviously, if no table schema was found, return null.\n */\n if (!tableSchema) {\n return null;\n }\n\n const baseAST = getBaseAST();\n const node = baseAST.node as SelectNode;\n if (query.filters && query.filters.length > 0) {\n /**\n * Make a copy of the query filters and enrich them with the table schema.\n */\n const queryFiltersWithInfo = cubeFiltersEnrichment(\n JSON.parse(JSON.stringify(query.filters)),\n tableSchema\n );\n\n if (!queryFiltersWithInfo) {\n return null;\n }\n\n const whereClause = getFormattedFilters({\n baseAST,\n mapperFn: (item) =>\n traverseAndFilter(\n item,\n (value) => !query.measures.includes(value.member)\n ),\n queryFiltersWithInfo,\n filterType: options?.filterType,\n });\n\n const havingClause = getFormattedFilters({\n baseAST,\n mapperFn: (item) =>\n traverseAndFilter(item, (value) =>\n query.measures.includes(value.member)\n ),\n queryFiltersWithInfo,\n filterType: options?.filterType,\n });\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n //@ts-ignore\n node.where_clause = whereClause;\n node.having = havingClause;\n }\n if (\n query.measures.length &&\n query.dimensions &&\n query.dimensions?.length > 0\n ) {\n node.group_expressions = cubeDimensionToGroupByAST(query.dimensions);\n const groupSets = [];\n /**\n * We only support one group set for now.\n */\n for (let i = 0; i < node.group_expressions.length; i++) {\n groupSets.push(i);\n }\n node.group_sets = [groupSets];\n }\n node.modifiers = [];\n if (query.order) {\n node.modifiers.push(cubeOrderByToAST(query.order));\n }\n if (query.limit || query.offset) {\n // Type assertion is needed here because the AST is not typed correctly.\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n //@ts-ignore\n node.modifiers.push(cubeLimitOffsetToAST(query.limit, query.offset));\n }\n\n return baseAST;\n};\n"],"names":["cubeToDuckdbAST","formatFilters","queryFiltersWithInfo","filterType","modifyLeafMeerkatFilter","item","member","memberKeyToSafeKey","getFormattedFilters","mapperFn","baseAST","filters","map","filter","Boolean","formattedFilters","cubeFilterToDuckdbAST","query","tableSchema","options","getBaseAST","node","length","cubeFiltersEnrichment","JSON","parse","stringify","whereClause","traverseAndFilter","value","measures","includes","havingClause","where_clause","having","dimensions","group_expressions","cubeDimensionToGroupByAST","groupSets","i","push","group_sets","modifiers","order","cubeOrderByToAST","limit","offset","cubeLimitOffsetToAST"],"mappings":";+BAyDaA;;;eAAAA;;;;yBAzDyB;wCACI;4CACL;wCACJ;iCAKC;oCACC;yBASR;sCACW;qCACE;AAExC,MAAMC,gBAAgB,CACpBC,sBACAC;IAEA;;GAEC,GACD,OAAOA,eAAe,gBAClBD,uBACCE,IAAAA,4CAAuB,EAACF,sBAAsB,CAACG;QAC9C,OAAO,eACFA;YACHC,QAAQC,IAAAA,sCAAkB,EAACF,KAAKC,MAAM;;IAE1C;AACN;AAEA,MAAME,sBAAsB,CAAC,EAC3BN,oBAAoB,EACpBC,UAAU,EACVM,QAAQ,EACRC,OAAO,EAMR;IACC,MAAMC,UAAUT,qBACbU,GAAG,CAAC,CAACP,OAASI,SAASJ,OACvBQ,MAAM,CAACC;IACV,MAAMC,mBAAmBd,cAAcU,SAASR;IAChD,OAAOa,IAAAA,8BAAqB,EAACD,kBAAkBL;AACjD;AAEO,MAAMV,kBAAkB,CAC7BiB,OACAC,aACAC;QAqDEF;IAnDF;;GAEC,GACD,IAAI,CAACC,aAAa;QAChB,OAAO;IACT;IAEA,MAAMR,UAAUU,IAAAA,mBAAU;IAC1B,MAAMC,OAAOX,QAAQW,IAAI;IACzB,IAAIJ,MAAMN,OAAO,IAAIM,MAAMN,OAAO,CAACW,MAAM,GAAG,GAAG;QAC7C;;KAEC,GACD,MAAMpB,uBAAuBqB,IAAAA,2CAAqB,EAChDC,KAAKC,KAAK,CAACD,KAAKE,SAAS,CAACT,MAAMN,OAAO,IACvCO;QAGF,IAAI,CAAChB,sBAAsB;YACzB,OAAO;QACT;QAEA,MAAMyB,cAAcnB,oBAAoB;YACtCE;YACAD,UAAU,CAACJ,OACTuB,IAAAA,kCAAiB,EACfvB,MACA,CAACwB,QAAU,CAACZ,MAAMa,QAAQ,CAACC,QAAQ,CAACF,MAAMvB,MAAM;YAEpDJ;YACAC,UAAU,EAAEgB,2BAAAA,QAAShB,UAAU;QACjC;QAEA,MAAM6B,eAAexB,oBAAoB;YACvCE;YACAD,UAAU,CAACJ,OACTuB,IAAAA,kCAAiB,EAACvB,MAAM,CAACwB,QACvBZ,MAAMa,QAAQ,CAACC,QAAQ,CAACF,MAAMvB,MAAM;YAExCJ;YACAC,UAAU,EAAEgB,2BAAAA,QAAShB,UAAU;QACjC;QAEA,6DAA6D;QAC7D,YAAY;QACZkB,KAAKY,YAAY,GAAGN;QACpBN,KAAKa,MAAM,GAAGF;IAChB;IACA,IACEf,MAAMa,QAAQ,CAACR,MAAM,IACrBL,MAAMkB,UAAU,IAChBlB,EAAAA,oBAAAA,MAAMkB,UAAU,qBAAhBlB,kBAAkBK,MAAM,IAAG,GAC3B;QACAD,KAAKe,iBAAiB,GAAGC,IAAAA,iDAAyB,EAACpB,MAAMkB,UAAU;QACnE,MAAMG,YAAY,EAAE;QACpB;;KAEC,GACD,IAAK,IAAIC,IAAI,GAAGA,IAAIlB,KAAKe,iBAAiB,CAACd,MAAM,EAAEiB,IAAK;YACtDD,UAAUE,IAAI,CAACD;QACjB;QACAlB,KAAKoB,UAAU,GAAG;YAACH;SAAU;IAC/B;IACAjB,KAAKqB,SAAS,GAAG,EAAE;IACnB,IAAIzB,MAAM0B,KAAK,
|
|
1
|
+
{"version":3,"sources":["../../../meerkat-core/src/ast-builder/ast-builder.ts"],"sourcesContent":["import { cubeFilterToDuckdbAST } from '../cube-filter-transformer/factory';\nimport { cubeDimensionToGroupByAST } from '../cube-group-by-transformer/cube-group-by-transformer';\nimport { cubeLimitOffsetToAST } from '../cube-limit-offset-transformer/cube-limit-offset-transformer';\nimport { cubeOrderByToAST } from '../cube-order-by-transformer/cube-order-by-transformer';\nimport {\n QueryFiltersWithInfo,\n QueryFiltersWithInfoSingular,\n} from '../cube-to-duckdb/cube-filter-to-duckdb';\nimport { traverseAndFilter } from '../filter-params/filter-params-ast';\nimport { memberKeyToSafeKey } from '../member-formatters/member-key-to-safe-key';\nimport {\n FilterType,\n MeerkatQueryFilter,\n Query,\n} from '../types/cube-types/query';\nimport { TableSchema } from '../types/cube-types/table';\nimport { SelectStatement } from '../types/duckdb-serialization-types';\nimport { SelectNode } from '../types/duckdb-serialization-types/serialization/QueryNode';\nimport { getBaseAST } from '../utils/base-ast';\nimport { cubeFiltersEnrichment } from '../utils/cube-filter-enrichment';\nimport { modifyLeafMeerkatFilter } from '../utils/modify-meerkat-filter';\n\nconst formatFilters = (\n queryFiltersWithInfo: QueryFiltersWithInfo,\n filterType?: FilterType\n) => {\n /*\n * If the type of filter is set to base filter where\n */\n return filterType === 'BASE_FILTER'\n ? queryFiltersWithInfo\n : (modifyLeafMeerkatFilter(queryFiltersWithInfo, (item) => {\n return {\n ...item,\n member: memberKeyToSafeKey(item.member),\n };\n }) as QueryFiltersWithInfo);\n};\n\nconst getFormattedFilters = ({\n queryFiltersWithInfo,\n filterType,\n mapperFn,\n baseAST,\n}: {\n queryFiltersWithInfo: QueryFiltersWithInfo;\n filterType?: FilterType;\n baseAST: SelectStatement;\n mapperFn: (val: QueryFiltersWithInfoSingular) => MeerkatQueryFilter | null;\n}) => {\n const filters = queryFiltersWithInfo\n .map((item) => mapperFn(item))\n .filter(Boolean) as QueryFiltersWithInfoSingular[];\n const formattedFilters = formatFilters(filters, filterType);\n return cubeFilterToDuckdbAST(formattedFilters, baseAST);\n};\n\nexport const cubeToDuckdbAST = (\n query: Query,\n tableSchema: TableSchema,\n options?: { filterType: FilterType }\n) => {\n /**\n * Obviously, if no table schema was found, return null.\n */\n if (!tableSchema) {\n return null;\n }\n\n const baseAST = getBaseAST();\n const node = baseAST.node as SelectNode;\n if (query.filters && query.filters.length > 0) {\n /**\n * Make a copy of the query filters and enrich them with the table schema.\n */\n const queryFiltersWithInfo = cubeFiltersEnrichment(\n JSON.parse(JSON.stringify(query.filters)),\n tableSchema\n );\n\n if (!queryFiltersWithInfo) {\n return null;\n }\n\n const whereClause = getFormattedFilters({\n baseAST,\n mapperFn: (item) =>\n traverseAndFilter(\n item,\n (value) => !query.measures.includes(value.member)\n ),\n queryFiltersWithInfo,\n filterType: options?.filterType,\n });\n\n const havingClause = getFormattedFilters({\n baseAST,\n mapperFn: (item) =>\n traverseAndFilter(item, (value) =>\n query.measures.includes(value.member)\n ),\n queryFiltersWithInfo,\n filterType: options?.filterType,\n });\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n //@ts-ignore\n node.where_clause = whereClause;\n node.having = havingClause;\n }\n if (\n query.measures.length &&\n query.dimensions &&\n query.dimensions?.length > 0\n ) {\n node.group_expressions = cubeDimensionToGroupByAST(query.dimensions);\n const groupSets = [];\n /**\n * We only support one group set for now.\n */\n for (let i = 0; i < node.group_expressions.length; i++) {\n groupSets.push(i);\n }\n node.group_sets = [groupSets];\n }\n node.modifiers = [];\n if (query.order && Object.keys(query.order).length > 0) {\n node.modifiers.push(cubeOrderByToAST(query.order));\n }\n if (query.limit || query.offset) {\n // Type assertion is needed here because the AST is not typed correctly.\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n //@ts-ignore\n node.modifiers.push(cubeLimitOffsetToAST(query.limit, query.offset));\n }\n\n return baseAST;\n};\n"],"names":["cubeToDuckdbAST","formatFilters","queryFiltersWithInfo","filterType","modifyLeafMeerkatFilter","item","member","memberKeyToSafeKey","getFormattedFilters","mapperFn","baseAST","filters","map","filter","Boolean","formattedFilters","cubeFilterToDuckdbAST","query","tableSchema","options","getBaseAST","node","length","cubeFiltersEnrichment","JSON","parse","stringify","whereClause","traverseAndFilter","value","measures","includes","havingClause","where_clause","having","dimensions","group_expressions","cubeDimensionToGroupByAST","groupSets","i","push","group_sets","modifiers","order","Object","keys","cubeOrderByToAST","limit","offset","cubeLimitOffsetToAST"],"mappings":"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"}
|