@simtlix/simfinity-js 2.4.4 → 2.5.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/.claude/worktrees/agitated-kepler/.claude/settings.local.json +23 -0
- package/.claude/worktrees/agitated-kepler/AGGREGATION_CHANGES_SUMMARY.md +235 -0
- package/.claude/worktrees/agitated-kepler/AGGREGATION_EXAMPLE.md +567 -0
- package/.claude/worktrees/agitated-kepler/LICENSE +201 -0
- package/.claude/worktrees/agitated-kepler/README.md +3941 -0
- package/.claude/worktrees/agitated-kepler/eslint.config.mjs +71 -0
- package/.claude/worktrees/agitated-kepler/package-lock.json +4740 -0
- package/.claude/worktrees/agitated-kepler/package.json +41 -0
- package/.claude/worktrees/agitated-kepler/src/auth/errors.js +44 -0
- package/.claude/worktrees/agitated-kepler/src/auth/expressions.js +273 -0
- package/.claude/worktrees/agitated-kepler/src/auth/index.js +391 -0
- package/.claude/worktrees/agitated-kepler/src/auth/rules.js +274 -0
- package/.claude/worktrees/agitated-kepler/src/const/QLOperator.js +39 -0
- package/.claude/worktrees/agitated-kepler/src/const/QLSort.js +28 -0
- package/.claude/worktrees/agitated-kepler/src/const/QLValue.js +39 -0
- package/.claude/worktrees/agitated-kepler/src/errors/internal-server.error.js +11 -0
- package/.claude/worktrees/agitated-kepler/src/errors/simfinity.error.js +15 -0
- package/.claude/worktrees/agitated-kepler/src/index.js +2412 -0
- package/.claude/worktrees/agitated-kepler/src/plugins.js +53 -0
- package/.claude/worktrees/agitated-kepler/src/scalars.js +188 -0
- package/.claude/worktrees/agitated-kepler/src/validators.js +250 -0
- package/.claude/worktrees/agitated-kepler/yarn.lock +1154 -0
- package/.cursor/rules/simfinity-core-functions.mdc +3 -1
- package/README.md +202 -0
- package/git-report.js +224 -0
- package/package.json +1 -1
- package/src/index.js +237 -23
package/src/index.js
CHANGED
|
@@ -167,6 +167,25 @@ const QLTypeFilterExpression = new GraphQLInputObjectType({
|
|
|
167
167
|
}),
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
+
const QLFilterCondition = new GraphQLInputObjectType({
|
|
171
|
+
name: 'QLFilterCondition',
|
|
172
|
+
fields: () => ({
|
|
173
|
+
field: { type: new GraphQLNonNull(GraphQLString) },
|
|
174
|
+
operator: { type: QLOperator },
|
|
175
|
+
value: { type: QLValue },
|
|
176
|
+
path: { type: GraphQLString },
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const QLFilterGroup = new GraphQLInputObjectType({
|
|
181
|
+
name: 'QLFilterGroup',
|
|
182
|
+
fields: () => ({
|
|
183
|
+
AND: { type: new GraphQLList(QLFilterGroup) },
|
|
184
|
+
OR: { type: new GraphQLList(QLFilterGroup) },
|
|
185
|
+
conditions: { type: new GraphQLList(QLFilterCondition) },
|
|
186
|
+
}),
|
|
187
|
+
});
|
|
188
|
+
|
|
170
189
|
const QLPagination = new GraphQLInputObjectType({
|
|
171
190
|
name: 'QLPagination',
|
|
172
191
|
fields: () => ({
|
|
@@ -416,8 +435,8 @@ const buildInputType = (gqltype) => {
|
|
|
416
435
|
if (fieldEntry.type.ofType === gqltype) {
|
|
417
436
|
selfReferenceCollections[fieldEntryName] = fieldEntry;
|
|
418
437
|
} else {
|
|
419
|
-
const listInputTypeForAdd = graphQLListInputType(typesDict, fieldEntry, fieldEntryName, 'A', fieldEntry.extensions?.relation?.connectionField);
|
|
420
|
-
const listInputTypeForUpdate = graphQLListInputType(typesDictForUpdate, fieldEntry, fieldEntryName, 'U', fieldEntry.extensions?.relation?.connectionField);
|
|
438
|
+
const listInputTypeForAdd = graphQLListInputType(typesDict, fieldEntry, fieldEntryName, gqltype.name + 'A', fieldEntry.extensions?.relation?.connectionField);
|
|
439
|
+
const listInputTypeForUpdate = graphQLListInputType(typesDictForUpdate, fieldEntry, fieldEntryName, gqltype.name +'U', fieldEntry.extensions?.relation?.connectionField);
|
|
421
440
|
if (listInputTypeForAdd && listInputTypeForUpdate) {
|
|
422
441
|
fieldArg.type = listInputTypeForAdd;
|
|
423
442
|
fieldArgForUpdate.type = listInputTypeForUpdate;
|
|
@@ -1492,10 +1511,122 @@ const buildQueryTerms = async (filterField, qlField, fieldName) => {
|
|
|
1492
1511
|
return { aggregateClauses, matchesClauses };
|
|
1493
1512
|
};
|
|
1494
1513
|
|
|
1514
|
+
const MAX_FILTER_GROUP_DEPTH = 5;
|
|
1515
|
+
|
|
1516
|
+
const buildFilterGroupMatch = async (filterGroup, gqltype, aggregateClauses, aggregationsIncluded, depth = 0) => {
|
|
1517
|
+
if (depth > MAX_FILTER_GROUP_DEPTH) {
|
|
1518
|
+
throw new SimfinityError('Filter nesting too deep', 'FILTER_DEPTH_EXCEEDED', 400);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
const parts = [];
|
|
1522
|
+
const fields = gqltype.getFields();
|
|
1523
|
+
|
|
1524
|
+
// Process leaf conditions
|
|
1525
|
+
if (filterGroup.conditions && filterGroup.conditions.length > 0) {
|
|
1526
|
+
for (const condition of filterGroup.conditions) {
|
|
1527
|
+
const qlField = fields[condition.field];
|
|
1528
|
+
if (!qlField) {
|
|
1529
|
+
throw new SimfinityError(
|
|
1530
|
+
`Unknown filter field: ${condition.field}`,
|
|
1531
|
+
'INVALID_FILTER_FIELD',
|
|
1532
|
+
400,
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
let filterInput;
|
|
1537
|
+
let fieldType = qlField.type;
|
|
1538
|
+
if (fieldType instanceof GraphQLList || fieldType instanceof GraphQLNonNull) {
|
|
1539
|
+
fieldType = fieldType.ofType;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
if (fieldType instanceof GraphQLObjectType
|
|
1543
|
+
|| isNonNullOfType(fieldType, GraphQLObjectType)) {
|
|
1544
|
+
// Object/relation field — wrap as QLTypeFilterExpression shape
|
|
1545
|
+
if (!condition.path) {
|
|
1546
|
+
throw new SimfinityError(
|
|
1547
|
+
`Filter on object field "${condition.field}" requires a path`,
|
|
1548
|
+
'MISSING_FILTER_PATH',
|
|
1549
|
+
400,
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
filterInput = {
|
|
1553
|
+
terms: [{
|
|
1554
|
+
path: condition.path,
|
|
1555
|
+
operator: condition.operator,
|
|
1556
|
+
value: condition.value,
|
|
1557
|
+
}],
|
|
1558
|
+
};
|
|
1559
|
+
} else {
|
|
1560
|
+
// Scalar/enum field
|
|
1561
|
+
filterInput = {
|
|
1562
|
+
operator: condition.operator,
|
|
1563
|
+
value: condition.value,
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
const result = await buildQueryTerms(filterInput, qlField, condition.field);
|
|
1568
|
+
|
|
1569
|
+
if (result) {
|
|
1570
|
+
// Collect lookups (deduplicated)
|
|
1571
|
+
for (const [prop, aggregate] of Object.entries(result.aggregateClauses)) {
|
|
1572
|
+
if (!aggregationsIncluded[prop]) {
|
|
1573
|
+
aggregateClauses.push(aggregate.lookup);
|
|
1574
|
+
aggregateClauses.push(aggregate.unwind);
|
|
1575
|
+
aggregationsIncluded[prop] = true;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// Collect match conditions
|
|
1580
|
+
for (const matchClause of Object.values(result.matchesClauses)) {
|
|
1581
|
+
for (const [matchKey, match] of Object.entries(matchClause)) {
|
|
1582
|
+
parts.push({ [matchKey]: match });
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// Process AND sub-groups
|
|
1590
|
+
if (filterGroup.AND && filterGroup.AND.length > 0) {
|
|
1591
|
+
for (const subGroup of filterGroup.AND) {
|
|
1592
|
+
const subMatch = await buildFilterGroupMatch(
|
|
1593
|
+
subGroup, gqltype, aggregateClauses, aggregationsIncluded, depth + 1,
|
|
1594
|
+
);
|
|
1595
|
+
if (subMatch) {
|
|
1596
|
+
parts.push(subMatch);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// Process OR sub-groups
|
|
1602
|
+
if (filterGroup.OR && filterGroup.OR.length > 0) {
|
|
1603
|
+
const orParts = [];
|
|
1604
|
+
for (const subGroup of filterGroup.OR) {
|
|
1605
|
+
const subMatch = await buildFilterGroupMatch(
|
|
1606
|
+
subGroup, gqltype, aggregateClauses, aggregationsIncluded, depth + 1,
|
|
1607
|
+
);
|
|
1608
|
+
if (subMatch) {
|
|
1609
|
+
orParts.push(subMatch);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
if (orParts.length === 1) {
|
|
1613
|
+
parts.push(orParts[0]);
|
|
1614
|
+
} else if (orParts.length > 1) {
|
|
1615
|
+
parts.push({ $or: orParts });
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
if (parts.length === 0) return null;
|
|
1620
|
+
if (parts.length === 1) return parts[0];
|
|
1621
|
+
return { $and: parts };
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
const RESERVED_QUERY_KEYS = new Set(['pagination', 'sort', 'AND', 'OR', 'aggregation']);
|
|
1625
|
+
|
|
1495
1626
|
const buildQuery = async (input, gqltype, isCount) => {
|
|
1496
1627
|
const aggregateClauses = [];
|
|
1497
|
-
const
|
|
1498
|
-
let
|
|
1628
|
+
const flatMatchConditions = {};
|
|
1629
|
+
let hasFlat = false;
|
|
1499
1630
|
let limitClause = { $limit: 100 };
|
|
1500
1631
|
let skipClause = { $skip: 0 };
|
|
1501
1632
|
let sortClause = {};
|
|
@@ -1503,7 +1634,7 @@ const buildQuery = async (input, gqltype, isCount) => {
|
|
|
1503
1634
|
const aggregationsIncluded = {};
|
|
1504
1635
|
|
|
1505
1636
|
for (const [key, filterField] of Object.entries(input)) {
|
|
1506
|
-
if (Object.prototype.hasOwnProperty.call(input, key) && key
|
|
1637
|
+
if (Object.prototype.hasOwnProperty.call(input, key) && !RESERVED_QUERY_KEYS.has(key)) {
|
|
1507
1638
|
const qlField = gqltype.getFields()[key];
|
|
1508
1639
|
|
|
1509
1640
|
const result = await buildQueryTerms(filterField, qlField, key);
|
|
@@ -1519,8 +1650,8 @@ const buildQuery = async (input, gqltype, isCount) => {
|
|
|
1519
1650
|
if (Object.prototype.hasOwnProperty.call(result.matchesClauses, matchClauseKey)) {
|
|
1520
1651
|
for (const [matchKey, match] of Object.entries(matchClause)) {
|
|
1521
1652
|
if (Object.prototype.hasOwnProperty.call(matchClause, matchKey)) {
|
|
1522
|
-
|
|
1523
|
-
|
|
1653
|
+
flatMatchConditions[matchKey] = match;
|
|
1654
|
+
hasFlat = true;
|
|
1524
1655
|
}
|
|
1525
1656
|
}
|
|
1526
1657
|
}
|
|
@@ -1539,9 +1670,9 @@ const buildQuery = async (input, gqltype, isCount) => {
|
|
|
1539
1670
|
|
|
1540
1671
|
if (sort.field.indexOf('.') >= 0) {
|
|
1541
1672
|
const sortParts = sort.field.split('.');
|
|
1542
|
-
|
|
1673
|
+
|
|
1543
1674
|
fixedSortField = sortParts[0];
|
|
1544
|
-
|
|
1675
|
+
|
|
1545
1676
|
for (let i = 1; i < sortParts.length - 1; i++) {
|
|
1546
1677
|
fixedSortField += `_${sortParts[i]}`;
|
|
1547
1678
|
}
|
|
@@ -1564,8 +1695,45 @@ const buildQuery = async (input, gqltype, isCount) => {
|
|
|
1564
1695
|
}
|
|
1565
1696
|
}
|
|
1566
1697
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1698
|
+
// Combine flat conditions with AND/OR groups
|
|
1699
|
+
const topLevelAndParts = [];
|
|
1700
|
+
|
|
1701
|
+
if (hasFlat) {
|
|
1702
|
+
topLevelAndParts.push(flatMatchConditions);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
if (input.AND && input.AND.length > 0) {
|
|
1706
|
+
for (const group of input.AND) {
|
|
1707
|
+
const groupMatch = await buildFilterGroupMatch(
|
|
1708
|
+
group, gqltype, aggregateClauses, aggregationsIncluded,
|
|
1709
|
+
);
|
|
1710
|
+
if (groupMatch) {
|
|
1711
|
+
topLevelAndParts.push(groupMatch);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
if (input.OR && input.OR.length > 0) {
|
|
1717
|
+
const orParts = [];
|
|
1718
|
+
for (const group of input.OR) {
|
|
1719
|
+
const groupMatch = await buildFilterGroupMatch(
|
|
1720
|
+
group, gqltype, aggregateClauses, aggregationsIncluded,
|
|
1721
|
+
);
|
|
1722
|
+
if (groupMatch) {
|
|
1723
|
+
orParts.push(groupMatch);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
if (orParts.length === 1) {
|
|
1727
|
+
topLevelAndParts.push(orParts[0]);
|
|
1728
|
+
} else if (orParts.length > 1) {
|
|
1729
|
+
topLevelAndParts.push({ $or: orParts });
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
if (topLevelAndParts.length === 1) {
|
|
1734
|
+
aggregateClauses.push({ $match: topLevelAndParts[0] });
|
|
1735
|
+
} else if (topLevelAndParts.length > 1) {
|
|
1736
|
+
aggregateClauses.push({ $match: { $and: topLevelAndParts } });
|
|
1569
1737
|
}
|
|
1570
1738
|
|
|
1571
1739
|
if (addSort && !isCount) {
|
|
@@ -1653,33 +1821,33 @@ const buildFieldPath = (gqltype, fieldPath) => {
|
|
|
1653
1821
|
|
|
1654
1822
|
const buildAggregationQuery = async (input, gqltype, aggregationExpression) => {
|
|
1655
1823
|
const aggregateClauses = [];
|
|
1656
|
-
const
|
|
1657
|
-
let
|
|
1824
|
+
const flatMatchConditions = {};
|
|
1825
|
+
let hasFlat = false;
|
|
1658
1826
|
const aggregationsIncluded = {};
|
|
1659
1827
|
const sortTerms = []; // Store multiple sort terms
|
|
1660
1828
|
let limitClause = null;
|
|
1661
1829
|
let skipClause = null;
|
|
1662
|
-
|
|
1830
|
+
|
|
1663
1831
|
// Build filter and lookup clauses (similar to buildQuery)
|
|
1664
1832
|
for (const [key, filterField] of Object.entries(input)) {
|
|
1665
|
-
if (Object.prototype.hasOwnProperty.call(input, key) && key
|
|
1833
|
+
if (Object.prototype.hasOwnProperty.call(input, key) && !RESERVED_QUERY_KEYS.has(key)) {
|
|
1666
1834
|
const qlField = gqltype.getFields()[key];
|
|
1667
|
-
|
|
1835
|
+
|
|
1668
1836
|
const result = await buildQueryTerms(filterField, qlField, key);
|
|
1669
|
-
|
|
1837
|
+
|
|
1670
1838
|
if (result) {
|
|
1671
1839
|
for (const [prop, aggregate] of Object.entries(result.aggregateClauses)) {
|
|
1672
1840
|
aggregateClauses.push(aggregate.lookup);
|
|
1673
1841
|
aggregateClauses.push(aggregate.unwind);
|
|
1674
1842
|
aggregationsIncluded[prop] = true;
|
|
1675
1843
|
}
|
|
1676
|
-
|
|
1844
|
+
|
|
1677
1845
|
for (const [matchClauseKey, matchClause] of Object.entries(result.matchesClauses)) {
|
|
1678
1846
|
if (Object.prototype.hasOwnProperty.call(result.matchesClauses, matchClauseKey)) {
|
|
1679
1847
|
for (const [matchKey, match] of Object.entries(matchClause)) {
|
|
1680
1848
|
if (Object.prototype.hasOwnProperty.call(matchClause, matchKey)) {
|
|
1681
|
-
|
|
1682
|
-
|
|
1849
|
+
flatMatchConditions[matchKey] = match;
|
|
1850
|
+
hasFlat = true;
|
|
1683
1851
|
}
|
|
1684
1852
|
}
|
|
1685
1853
|
}
|
|
@@ -1702,9 +1870,46 @@ const buildAggregationQuery = async (input, gqltype, aggregationExpression) => {
|
|
|
1702
1870
|
}
|
|
1703
1871
|
}
|
|
1704
1872
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1873
|
+
|
|
1874
|
+
// Combine flat conditions with AND/OR groups
|
|
1875
|
+
const topLevelAndParts = [];
|
|
1876
|
+
|
|
1877
|
+
if (hasFlat) {
|
|
1878
|
+
topLevelAndParts.push(flatMatchConditions);
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
if (input.AND && input.AND.length > 0) {
|
|
1882
|
+
for (const group of input.AND) {
|
|
1883
|
+
const groupMatch = await buildFilterGroupMatch(
|
|
1884
|
+
group, gqltype, aggregateClauses, aggregationsIncluded,
|
|
1885
|
+
);
|
|
1886
|
+
if (groupMatch) {
|
|
1887
|
+
topLevelAndParts.push(groupMatch);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
if (input.OR && input.OR.length > 0) {
|
|
1893
|
+
const orParts = [];
|
|
1894
|
+
for (const group of input.OR) {
|
|
1895
|
+
const groupMatch = await buildFilterGroupMatch(
|
|
1896
|
+
group, gqltype, aggregateClauses, aggregationsIncluded,
|
|
1897
|
+
);
|
|
1898
|
+
if (groupMatch) {
|
|
1899
|
+
orParts.push(groupMatch);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
if (orParts.length === 1) {
|
|
1903
|
+
topLevelAndParts.push(orParts[0]);
|
|
1904
|
+
} else if (orParts.length > 1) {
|
|
1905
|
+
topLevelAndParts.push({ $or: orParts });
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
if (topLevelAndParts.length === 1) {
|
|
1910
|
+
aggregateClauses.push({ $match: topLevelAndParts[0] });
|
|
1911
|
+
} else if (topLevelAndParts.length > 1) {
|
|
1912
|
+
aggregateClauses.push({ $match: { $and: topLevelAndParts } });
|
|
1708
1913
|
}
|
|
1709
1914
|
|
|
1710
1915
|
// Now build the aggregation with $group
|
|
@@ -2150,6 +2355,8 @@ export { default as scalars } from './scalars.js';
|
|
|
2150
2355
|
export { default as plugins } from './plugins.js';
|
|
2151
2356
|
export { default as auth } from './auth/index.js';
|
|
2152
2357
|
|
|
2358
|
+
export { buildQuery, buildFilterGroupMatch };
|
|
2359
|
+
|
|
2153
2360
|
const createArgsForQuery = (argTypes) => {
|
|
2154
2361
|
const argsObject = {};
|
|
2155
2362
|
|
|
@@ -2182,6 +2389,13 @@ const createArgsForQuery = (argTypes) => {
|
|
|
2182
2389
|
|
|
2183
2390
|
argsObject.sort = {};
|
|
2184
2391
|
argsObject.sort.type = QLSortExpression;
|
|
2392
|
+
|
|
2393
|
+
argsObject.AND = {};
|
|
2394
|
+
argsObject.AND.type = new GraphQLList(QLFilterGroup);
|
|
2395
|
+
|
|
2396
|
+
argsObject.OR = {};
|
|
2397
|
+
argsObject.OR.type = new GraphQLList(QLFilterGroup);
|
|
2398
|
+
|
|
2185
2399
|
return argsObject;
|
|
2186
2400
|
};
|
|
2187
2401
|
|