@simtlix/simfinity-js 2.0.2 → 2.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/README.md CHANGED
@@ -7,6 +7,7 @@ A powerful Node.js framework that automatically generates GraphQL schemas from y
7
7
  - **Automatic Schema Generation**: Define your object model, and Simfinity.js generates all queries and mutations
8
8
  - **MongoDB Integration**: Seamless translation between GraphQL and MongoDB
9
9
  - **Powerful Querying**: Any query that can be executed in MongoDB can be executed in GraphQL
10
+ - **Aggregation Queries**: Built-in support for GROUP BY queries with aggregation operations (SUM, COUNT, AVG, MIN, MAX)
10
11
  - **Auto-Generated Resolvers**: Automatically generates resolve methods for relationship fields
11
12
  - **Automatic Index Creation**: Automatically creates MongoDB indexes for all ObjectId fields, including nested embedded objects and relationship fields
12
13
  - **Business Logic**: Implement business logic and domain validations declaratively
@@ -1358,6 +1359,238 @@ console.log(UserType.getFields()); // Access GraphQL fields
1358
1359
  const BookInput = simfinity.getInputType(BookType);
1359
1360
  ```
1360
1361
 
1362
+ ## 📊 Aggregation Queries
1363
+
1364
+ Simfinity.js now supports powerful GraphQL aggregation queries with GROUP BY functionality, allowing you to perform aggregate operations (SUM, COUNT, AVG, MIN, MAX) on your data.
1365
+
1366
+ ### Overview
1367
+
1368
+ For each entity type registered with `connect()`, an additional aggregation endpoint is automatically generated with the format `{entityname}_aggregate`.
1369
+
1370
+ ### Features
1371
+
1372
+ - **Group By**: Group results by any field (direct or related entity field path)
1373
+ - **Aggregation Operations**: SUM, COUNT, AVG, MIN, MAX
1374
+ - **Filtering**: Use the same filter parameters as regular queries
1375
+ - **Sorting**: Sort by groupId or any calculated fact (metrics), with support for multiple sort fields
1376
+ - **Pagination**: Use the same pagination parameters as regular queries
1377
+ - **Related Entity Fields**: Group by or aggregate on fields from related entities using dot notation
1378
+
1379
+ ### GraphQL Types
1380
+
1381
+ #### QLAggregationOperation (Enum)
1382
+ - `SUM`: Sum of numeric values
1383
+ - `COUNT`: Count of records
1384
+ - `AVG`: Average of numeric values
1385
+ - `MIN`: Minimum value
1386
+ - `MAX`: Maximum value
1387
+
1388
+ #### QLTypeAggregationFact (Input)
1389
+ ```graphql
1390
+ input QLTypeAggregationFact {
1391
+ operation: QLAggregationOperation!
1392
+ factName: String!
1393
+ path: String!
1394
+ }
1395
+ ```
1396
+
1397
+ #### QLTypeAggregationExpression (Input)
1398
+ ```graphql
1399
+ input QLTypeAggregationExpression {
1400
+ groupId: String!
1401
+ facts: [QLTypeAggregationFact!]!
1402
+ }
1403
+ ```
1404
+
1405
+ #### QLTypeAggregationResult (Output)
1406
+ ```graphql
1407
+ type QLTypeAggregationResult {
1408
+ groupId: JSON
1409
+ facts: JSON
1410
+ }
1411
+ ```
1412
+
1413
+ ### Quick Examples
1414
+
1415
+ #### Simple Group By
1416
+ ```graphql
1417
+ query {
1418
+ series_aggregate(
1419
+ aggregation: {
1420
+ groupId: "category"
1421
+ facts: [
1422
+ { operation: COUNT, factName: "total", path: "id" }
1423
+ ]
1424
+ }
1425
+ ) {
1426
+ groupId
1427
+ facts
1428
+ }
1429
+ }
1430
+ ```
1431
+
1432
+ #### Group By Related Entity
1433
+ ```graphql
1434
+ query {
1435
+ series_aggregate(
1436
+ aggregation: {
1437
+ groupId: "country.name"
1438
+ facts: [
1439
+ { operation: COUNT, factName: "count", path: "id" }
1440
+ { operation: AVG, factName: "avgRating", path: "rating" }
1441
+ ]
1442
+ }
1443
+ ) {
1444
+ groupId
1445
+ facts
1446
+ }
1447
+ }
1448
+ ```
1449
+
1450
+ #### Multiple Aggregation Facts
1451
+ ```graphql
1452
+ query {
1453
+ series_aggregate(
1454
+ aggregation: {
1455
+ groupId: "category"
1456
+ facts: [
1457
+ { operation: COUNT, factName: "total", path: "id" }
1458
+ { operation: SUM, factName: "totalEpisodes", path: "episodeCount" }
1459
+ { operation: AVG, factName: "avgRating", path: "rating" }
1460
+ { operation: MIN, factName: "minRating", path: "rating" }
1461
+ { operation: MAX, factName: "maxRating", path: "rating" }
1462
+ ]
1463
+ }
1464
+ ) {
1465
+ groupId
1466
+ facts
1467
+ }
1468
+ }
1469
+ ```
1470
+
1471
+ #### With Filtering
1472
+ ```graphql
1473
+ query {
1474
+ series_aggregate(
1475
+ rating: { operator: GTE, value: 8.0 }
1476
+ aggregation: {
1477
+ groupId: "category"
1478
+ facts: [
1479
+ { operation: COUNT, factName: "highRated", path: "id" }
1480
+ ]
1481
+ }
1482
+ ) {
1483
+ groupId
1484
+ facts
1485
+ }
1486
+ }
1487
+ ```
1488
+
1489
+ #### Sorting by Multiple Fields
1490
+ ```graphql
1491
+ query {
1492
+ series_aggregate(
1493
+ sort: {
1494
+ terms: [
1495
+ { field: "total", order: "DESC" }, # Sort by count first
1496
+ { field: "groupId", order: "ASC" } # Then by name
1497
+ ]
1498
+ }
1499
+ aggregation: {
1500
+ groupId: "category"
1501
+ facts: [
1502
+ { operation: COUNT, factName: "total", path: "id" }
1503
+ { operation: AVG, factName: "avgRating", path: "rating" }
1504
+ ]
1505
+ }
1506
+ ) {
1507
+ groupId
1508
+ facts
1509
+ }
1510
+ }
1511
+ ```
1512
+
1513
+ #### With Pagination (Top 5)
1514
+ ```graphql
1515
+ query {
1516
+ series_aggregate(
1517
+ sort: {
1518
+ terms: [{ field: "total", order: "DESC" }]
1519
+ }
1520
+ pagination: {
1521
+ page: 1
1522
+ size: 5
1523
+ }
1524
+ aggregation: {
1525
+ groupId: "category"
1526
+ facts: [
1527
+ { operation: COUNT, factName: "total", path: "id" }
1528
+ ]
1529
+ }
1530
+ ) {
1531
+ groupId
1532
+ facts
1533
+ }
1534
+ }
1535
+ ```
1536
+
1537
+ ### Field Path Resolution
1538
+
1539
+ The `groupId` and `path` parameters support:
1540
+
1541
+ 1. **Direct Fields**: Simple field names from the entity
1542
+ - Example: `"category"`, `"rating"`, `"id"`
1543
+
1544
+ 2. **Related Entity Fields**: Dot notation for fields in related entities
1545
+ - Example: `"country.name"`, `"studio.foundedYear"`
1546
+
1547
+ 3. **Nested Related Entities**: Multiple levels of relationships
1548
+ - Example: `"country.region.name"`
1549
+
1550
+ ### Sorting Options
1551
+
1552
+ - Sort by **groupId** or **any fact name**
1553
+ - **Multiple sort fields supported** - results are sorted by the first field, then by the second field for ties, etc.
1554
+ - Set the `field` parameter to:
1555
+ - `"groupId"` to sort by the grouping field
1556
+ - Any fact name (e.g., `"avgRating"`, `"total"`) to sort by that calculated metric
1557
+ - The `order` parameter (ASC/DESC) determines the sort direction for each field
1558
+ - If a field doesn't match groupId or any fact name, it defaults to groupId
1559
+ - If no sort is specified, defaults to sorting by groupId ascending
1560
+
1561
+ ### Pagination Notes
1562
+
1563
+ - The `page` and `size` parameters work as expected
1564
+ - The `count` parameter is **ignored** for aggregation queries
1565
+ - Pagination is applied **after** grouping and sorting
1566
+
1567
+ ### MongoDB Translation
1568
+
1569
+ Aggregation queries are translated to efficient MongoDB aggregation pipelines:
1570
+
1571
+ 1. **$lookup**: Joins with related entity collections
1572
+ 2. **$unwind**: Flattens joined arrays
1573
+ 3. **$match**: Applies filters (before grouping)
1574
+ 4. **$group**: Groups by the specified field with aggregation operations
1575
+ 5. **$project**: Formats final output with groupId and facts fields
1576
+ 6. **$sort**: Sorts results by groupId or facts (with multiple fields support)
1577
+ 7. **$limit** / **$skip**: Applied for pagination (after sorting)
1578
+
1579
+ ### Result Structure
1580
+
1581
+ Results are returned in a consistent format:
1582
+ ```json
1583
+ {
1584
+ "groupId": <value>,
1585
+ "facts": {
1586
+ "factName1": <calculated_value>,
1587
+ "factName2": <calculated_value>
1588
+ }
1589
+ }
1590
+ ```
1591
+
1592
+ For complete documentation with more examples, see [AGGREGATION_EXAMPLE.md](./AGGREGATION_EXAMPLE.md) and [AGGREGATION_CHANGES_SUMMARY.md](./AGGREGATION_CHANGES_SUMMARY.md).
1593
+
1361
1594
  ## 📚 Complete Example
1362
1595
 
1363
1596
  Here's a complete bookstore example with relationships, validations, and state machines:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simtlix/simfinity-js",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -13,6 +13,41 @@ import QLSort from './const/QLSort.js';
13
13
 
14
14
  mongoose.set('strictQuery', false);
15
15
 
16
+ // Custom JSON scalar type for aggregation results
17
+ const GraphQLJSON = new GraphQLScalarType({
18
+ name: 'JSON',
19
+ description: 'The `JSON` scalar type represents JSON values as specified by ECMA-404',
20
+ serialize(value) {
21
+ return value;
22
+ },
23
+ parseValue(value) {
24
+ return value;
25
+ },
26
+ parseLiteral(ast) {
27
+ switch (ast.kind) {
28
+ case Kind.STRING:
29
+ case Kind.BOOLEAN:
30
+ return ast.value;
31
+ case Kind.INT:
32
+ case Kind.FLOAT:
33
+ return parseFloat(ast.value);
34
+ case Kind.OBJECT: {
35
+ const value = Object.create(null);
36
+ ast.fields.forEach((field) => {
37
+ value[field.name.value] = GraphQLJSON.parseLiteral(field.value);
38
+ });
39
+ return value;
40
+ }
41
+ case Kind.LIST:
42
+ return ast.values.map((n) => GraphQLJSON.parseLiteral(n));
43
+ case Kind.NULL:
44
+ return null;
45
+ default:
46
+ return undefined;
47
+ }
48
+ },
49
+ });
50
+
16
51
  // Adding 'extensions' field into instronspection query
17
52
  const RelationType = new GraphQLObjectType({
18
53
  name: 'RelationType',
@@ -147,6 +182,42 @@ const QLSortExpression = new GraphQLInputObjectType({
147
182
  }),
148
183
  });
149
184
 
185
+ const QLAggregationOperation = new GraphQLEnumType({
186
+ name: 'QLAggregationOperation',
187
+ values: {
188
+ SUM: { value: 'SUM' },
189
+ COUNT: { value: 'COUNT' },
190
+ AVG: { value: 'AVG' },
191
+ MIN: { value: 'MIN' },
192
+ MAX: { value: 'MAX' },
193
+ },
194
+ });
195
+
196
+ const QLTypeAggregationFact = new GraphQLInputObjectType({
197
+ name: 'QLTypeAggregationFact',
198
+ fields: () => ({
199
+ operation: { type: new GraphQLNonNull(QLAggregationOperation) },
200
+ factName: { type: new GraphQLNonNull(GraphQLString) },
201
+ path: { type: new GraphQLNonNull(GraphQLString) },
202
+ }),
203
+ });
204
+
205
+ const QLTypeAggregationExpression = new GraphQLInputObjectType({
206
+ name: 'QLTypeAggregationExpression',
207
+ fields: () => ({
208
+ groupId: { type: new GraphQLNonNull(GraphQLString) },
209
+ facts: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(QLTypeAggregationFact))) },
210
+ }),
211
+ });
212
+
213
+ const QLTypeAggregationResult = new GraphQLObjectType({
214
+ name: 'QLTypeAggregationResult',
215
+ fields: () => ({
216
+ groupId: { type: GraphQLJSON },
217
+ facts: { type: GraphQLJSON },
218
+ }),
219
+ });
220
+
150
221
  const isNonNullOfType = (fieldEntryType, graphQLType) => {
151
222
  let isOfType = false;
152
223
  if (fieldEntryType instanceof GraphQLNonNull) {
@@ -1482,6 +1553,254 @@ const buildQuery = async (input, gqltype, isCount) => {
1482
1553
  return aggregateClauses;
1483
1554
  };
1484
1555
 
1556
+ const buildFieldPath = (gqltype, fieldPath) => {
1557
+ // This function resolves a field path (e.g., "category" or "country.name")
1558
+ // and returns the MongoDB field path and any necessary lookups
1559
+ const pathParts = fieldPath.split('.');
1560
+ const aggregateClauses = [];
1561
+ let currentPath = '';
1562
+ let currentGQLType = gqltype;
1563
+
1564
+ for (let i = 0; i < pathParts.length; i++) {
1565
+ const part = pathParts[i];
1566
+ const field = currentGQLType.getFields()[part];
1567
+
1568
+ if (!field) {
1569
+ throw new Error(`Field ${part} not found in type ${currentGQLType.name}`);
1570
+ }
1571
+
1572
+ let fieldType = field.type;
1573
+ if (fieldType instanceof GraphQLNonNull || fieldType instanceof GraphQLList) {
1574
+ fieldType = fieldType.ofType;
1575
+ }
1576
+
1577
+ // If it's an object type with non-embedded relation, we need a lookup
1578
+ if ((fieldType instanceof GraphQLObjectType) &&
1579
+ field.extensions && field.extensions.relation &&
1580
+ !field.extensions.relation.embedded) {
1581
+
1582
+ const relatedModel = typesDict.types[fieldType.name].model;
1583
+ const collectionName = relatedModel.collection.collectionName;
1584
+ const connectionField = field.extensions.relation.connectionField || part;
1585
+
1586
+ const lookupAlias = currentPath ? `${currentPath}_${part}` : part;
1587
+ const localField = currentPath ? `${currentPath}.${connectionField}` : connectionField;
1588
+
1589
+ aggregateClauses.push({
1590
+ $lookup: {
1591
+ from: collectionName,
1592
+ foreignField: '_id',
1593
+ localField,
1594
+ as: lookupAlias,
1595
+ },
1596
+ });
1597
+
1598
+ aggregateClauses.push({
1599
+ $unwind: { path: `$${lookupAlias}`, preserveNullAndEmptyArrays: true },
1600
+ });
1601
+
1602
+ currentPath = lookupAlias;
1603
+ currentGQLType = fieldType;
1604
+ } else if (fieldType instanceof GraphQLObjectType &&
1605
+ field.extensions && field.extensions.relation &&
1606
+ field.extensions.relation.embedded) {
1607
+ // Embedded object - just append to path
1608
+ currentPath = currentPath ? `${currentPath}.${part}` : part;
1609
+ currentGQLType = fieldType;
1610
+ } else {
1611
+ // Scalar field - final part of path
1612
+ if (part === 'id') {
1613
+ currentPath = currentPath ? `${currentPath}._id` : '_id';
1614
+ } else {
1615
+ currentPath = currentPath ? `${currentPath}.${part}` : part;
1616
+ }
1617
+ }
1618
+ }
1619
+
1620
+ return { mongoPath: currentPath, lookups: aggregateClauses };
1621
+ };
1622
+
1623
+ const buildAggregationQuery = async (input, gqltype, aggregationExpression) => {
1624
+ const aggregateClauses = [];
1625
+ const matchesClauses = { $match: {} };
1626
+ let addMatch = false;
1627
+ const aggregationsIncluded = {};
1628
+ const sortTerms = []; // Store multiple sort terms
1629
+ let limitClause = null;
1630
+ let skipClause = null;
1631
+
1632
+ // Build filter and lookup clauses (similar to buildQuery)
1633
+ for (const [key, filterField] of Object.entries(input)) {
1634
+ if (Object.prototype.hasOwnProperty.call(input, key) && key !== 'pagination' && key !== 'sort' && key !== 'aggregation') {
1635
+ const qlField = gqltype.getFields()[key];
1636
+
1637
+ const result = await buildQueryTerms(filterField, qlField, key);
1638
+
1639
+ if (result) {
1640
+ for (const [prop, aggregate] of Object.entries(result.aggregateClauses)) {
1641
+ aggregateClauses.push(aggregate.lookup);
1642
+ aggregateClauses.push(aggregate.unwind);
1643
+ aggregationsIncluded[prop] = true;
1644
+ }
1645
+
1646
+ for (const [matchClauseKey, matchClause] of Object.entries(result.matchesClauses)) {
1647
+ if (Object.prototype.hasOwnProperty.call(result.matchesClauses, matchClauseKey)) {
1648
+ for (const [matchKey, match] of Object.entries(matchClause)) {
1649
+ if (Object.prototype.hasOwnProperty.call(matchClause, matchKey)) {
1650
+ matchesClauses.$match[matchKey] = match;
1651
+ addMatch = true;
1652
+ }
1653
+ }
1654
+ }
1655
+ }
1656
+ }
1657
+ } else if (key === 'sort' && filterField && filterField.terms && filterField.terms.length > 0) {
1658
+ // Extract all sort terms
1659
+ filterField.terms.forEach(sortTerm => {
1660
+ sortTerms.push({
1661
+ field: sortTerm.field || 'groupId',
1662
+ direction: sortTerm.order === 'ASC' ? 1 : -1,
1663
+ });
1664
+ });
1665
+ } else if (key === 'pagination' && filterField) {
1666
+ // Handle pagination (ignore count parameter)
1667
+ if (filterField.page && filterField.size) {
1668
+ const skip = filterField.size * (filterField.page - 1);
1669
+ limitClause = { $limit: filterField.size + skip };
1670
+ skipClause = { $skip: skip };
1671
+ }
1672
+ }
1673
+ }
1674
+
1675
+ if (addMatch) {
1676
+ aggregateClauses.push(matchesClauses);
1677
+ }
1678
+
1679
+ // Now build the aggregation with $group
1680
+ const { groupId, facts } = aggregationExpression;
1681
+
1682
+ // Resolve the groupId field path
1683
+ const groupIdPath = buildFieldPath(gqltype, groupId);
1684
+
1685
+ // Add any lookups needed for the groupId field
1686
+ groupIdPath.lookups.forEach(lookup => {
1687
+ const lookupKey = Object.keys(lookup)[0];
1688
+ const lookupAlias = lookup[lookupKey].as;
1689
+ if (!aggregationsIncluded[lookupAlias]) {
1690
+ aggregateClauses.push(lookup);
1691
+ // Check if next item is an unwind for this lookup
1692
+ const unwindItem = groupIdPath.lookups[groupIdPath.lookups.indexOf(lookup) + 1];
1693
+ if (unwindItem && unwindItem.$unwind) {
1694
+ aggregateClauses.push(unwindItem);
1695
+ }
1696
+ aggregationsIncluded[lookupAlias] = true;
1697
+ }
1698
+ });
1699
+
1700
+ // Build the $group stage
1701
+ const groupStage = {
1702
+ $group: {
1703
+ _id: `$${groupIdPath.mongoPath}`,
1704
+ },
1705
+ };
1706
+
1707
+ // Add aggregation operations for each fact
1708
+ facts.forEach(fact => {
1709
+ const { operation, factName, path } = fact;
1710
+ const factPath = buildFieldPath(gqltype, path);
1711
+
1712
+ // Add any lookups needed for the fact field
1713
+ factPath.lookups.forEach(lookup => {
1714
+ const lookupKey = Object.keys(lookup)[0];
1715
+ const lookupAlias = lookup[lookupKey].as;
1716
+ if (!aggregationsIncluded[lookupAlias]) {
1717
+ aggregateClauses.push(lookup);
1718
+ // Check if next item is an unwind for this lookup
1719
+ const unwindItem = factPath.lookups[factPath.lookups.indexOf(lookup) + 1];
1720
+ if (unwindItem && unwindItem.$unwind) {
1721
+ aggregateClauses.push(unwindItem);
1722
+ }
1723
+ aggregationsIncluded[lookupAlias] = true;
1724
+ }
1725
+ });
1726
+
1727
+ // Map GraphQL operations to MongoDB aggregation operators
1728
+ let mongoOperation;
1729
+ switch (operation) {
1730
+ case 'SUM':
1731
+ mongoOperation = { $sum: `$${factPath.mongoPath}` };
1732
+ break;
1733
+ case 'COUNT':
1734
+ mongoOperation = { $sum: 1 };
1735
+ break;
1736
+ case 'AVG':
1737
+ mongoOperation = { $avg: `$${factPath.mongoPath}` };
1738
+ break;
1739
+ case 'MIN':
1740
+ mongoOperation = { $min: `$${factPath.mongoPath}` };
1741
+ break;
1742
+ case 'MAX':
1743
+ mongoOperation = { $max: `$${factPath.mongoPath}` };
1744
+ break;
1745
+ default:
1746
+ throw new Error(`Unknown aggregation operation: ${operation}`);
1747
+ }
1748
+
1749
+ groupStage.$group[factName] = mongoOperation;
1750
+ });
1751
+
1752
+ aggregateClauses.push(groupStage);
1753
+
1754
+ // Add a final projection stage to format the output
1755
+ aggregateClauses.push({
1756
+ $project: {
1757
+ _id: 0,
1758
+ groupId: '$_id',
1759
+ facts: Object.fromEntries(facts.map(fact => [fact.factName, `$${fact.factName}`])),
1760
+ },
1761
+ });
1762
+
1763
+ // Build sort object from multiple sort terms
1764
+ if (sortTerms.length > 0) {
1765
+ const sortObject = {};
1766
+ const factNames = facts.map(fact => fact.factName);
1767
+
1768
+ sortTerms.forEach(sortTerm => {
1769
+ let sortFieldPath = 'groupId';
1770
+
1771
+ if (sortTerm.field !== 'groupId') {
1772
+ // Check if the field is one of the fact names
1773
+ if (factNames.includes(sortTerm.field)) {
1774
+ sortFieldPath = `facts.${sortTerm.field}`;
1775
+ }
1776
+ // If not found, default to groupId (already set)
1777
+ }
1778
+
1779
+ sortObject[sortFieldPath] = sortTerm.direction;
1780
+ });
1781
+
1782
+ // Add sort stage with all sort fields
1783
+ aggregateClauses.push({
1784
+ $sort: sortObject,
1785
+ });
1786
+ } else {
1787
+ // Default sort by groupId ascending if no sort terms provided
1788
+ aggregateClauses.push({
1789
+ $sort: { groupId: 1 },
1790
+ });
1791
+ }
1792
+
1793
+ // Add pagination if provided
1794
+ if (limitClause) {
1795
+ aggregateClauses.push(limitClause);
1796
+ }
1797
+ if (skipClause) {
1798
+ aggregateClauses.push(skipClause);
1799
+ }
1800
+
1801
+ return aggregateClauses;
1802
+ };
1803
+
1485
1804
  const buildRootQuery = (name, includedTypes) => {
1486
1805
  const rootQueryArgs = {};
1487
1806
  rootQueryArgs.name = name;
@@ -1545,6 +1864,29 @@ const buildRootQuery = (name, includedTypes) => {
1545
1864
  return result;
1546
1865
  },
1547
1866
  };
1867
+
1868
+ // Add aggregate endpoint
1869
+ const aggregateArgsObject = { ...argsObject };
1870
+ aggregateArgsObject.aggregation = {
1871
+ type: new GraphQLNonNull(QLTypeAggregationExpression),
1872
+ };
1873
+
1874
+ rootQueryArgs.fields[`${type.listEntitiesEndpointName}_aggregate`] = {
1875
+ type: new GraphQLList(QLTypeAggregationResult),
1876
+ args: aggregateArgsObject,
1877
+ async resolve(parent, args, context) {
1878
+ const params = {
1879
+ type,
1880
+ args,
1881
+ operation: 'aggregate',
1882
+ context,
1883
+ };
1884
+ excecuteMiddleware(params);
1885
+ const aggregateClauses = await buildAggregationQuery(args, type.gqltype, args.aggregation);
1886
+ const result = await type.model.aggregate(aggregateClauses);
1887
+ return result;
1888
+ },
1889
+ };
1548
1890
  }
1549
1891
  }
1550
1892
  }
@@ -1,10 +0,0 @@
1
- ---
2
- name: Bug report
3
- about: Create a report to help us improve
4
- title: "[BUG]"
5
- labels: bug
6
- assignees: ''
7
-
8
- ---
9
-
10
- **Describe the bug*
@@ -1,10 +0,0 @@
1
- ---
2
- name: Feature request
3
- about: Suggest an idea for this project
4
- title: "[FEATURE]"
5
- labels: feature
6
- assignees: ''
7
-
8
- ---
9
-
10
- ** Describe the feature **
@@ -1,19 +0,0 @@
1
- name: Build on master
2
-
3
- on:
4
- push:
5
- branches: master
6
-
7
- jobs:
8
- build:
9
- runs-on: ubuntu-24.04
10
- steps:
11
- - uses: actions/checkout@v2.3.4
12
- - uses: actions/setup-node@v1.4.4
13
- with:
14
- node-version: 14
15
- - run: npm ci
16
- - run: npx json -f package.json peerDependencies | npx json -ka | xargs -i{} bash -c 'echo $0@$(npx json -f package.json peerDependencies.$0)' {} | xargs -i{} npm install --save-optional {}
17
- - run: npm run lint
18
- - run: npm test
19
-
@@ -1,45 +0,0 @@
1
- name: Publish packages
2
-
3
- on:
4
- create:
5
- tags:
6
- - 'v*'
7
-
8
- jobs:
9
- build:
10
- runs-on: ubuntu-24.04
11
- steps:
12
- - uses: actions/checkout@v2.3.4
13
- - uses: actions/setup-node@v1.4.4
14
- with:
15
- node-version: 14
16
- - run: npm ci
17
- - run: npx json -f package.json peerDependencies | npx json -ka | xargs -i{} bash -c 'echo $0@$(npx json -f package.json peerDependencies.$0)' {} | xargs -i{} npm install --save-optional {}
18
- - run: npm test
19
-
20
- publish-npm:
21
- needs: build
22
- runs-on: ubuntu-24.04
23
- steps:
24
- - uses: actions/checkout@v2.3.4
25
- - uses: actions/setup-node@v1.4.4
26
- with:
27
- node-version: 14
28
- registry-url: https://registry.npmjs.org/
29
- - run: npm ci
30
- - run: npm publish --access public
31
- env:
32
- NODE_AUTH_TOKEN: ${{secrets.NPMJS_TOKEN}}
33
- publish-gpr:
34
- needs: build
35
- runs-on: ubuntu-24.04
36
- steps:
37
- - uses: actions/checkout@v2.3.4
38
- - uses: actions/setup-node@v1.4.4
39
- with:
40
- node-version: 14
41
- registry-url: https://npm.pkg.github.com/
42
- - run: npm ci
43
- - run: npm publish
44
- env:
45
- NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}