@simtlix/simfinity-js 1.9.1 → 2.0.1
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 +116 -0
- package/package.json +1 -1
- package/simtlix-simfinity-js-1.9.1.tgz +0 -0
- package/src/index.js +80 -42
package/README.md
CHANGED
|
@@ -204,6 +204,122 @@ query {
|
|
|
204
204
|
- `NIN` - Not in array
|
|
205
205
|
- `BTW` - Between two values
|
|
206
206
|
|
|
207
|
+
### Collection Field Filtering
|
|
208
|
+
|
|
209
|
+
Simfinity.js now supports filtering collection fields (one-to-many relationships) using the same powerful query format. This allows you to filter related objects directly within your GraphQL queries.
|
|
210
|
+
|
|
211
|
+
#### Basic Collection Filtering
|
|
212
|
+
|
|
213
|
+
Filter collection fields using the same operators and format as main queries:
|
|
214
|
+
|
|
215
|
+
```graphql
|
|
216
|
+
query {
|
|
217
|
+
series {
|
|
218
|
+
seasons(number: { operator: EQ, value: 1 }) {
|
|
219
|
+
number
|
|
220
|
+
id
|
|
221
|
+
year
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### Advanced Collection Filtering
|
|
228
|
+
|
|
229
|
+
You can use complex filtering with nested object properties:
|
|
230
|
+
|
|
231
|
+
```graphql
|
|
232
|
+
query {
|
|
233
|
+
series {
|
|
234
|
+
seasons(
|
|
235
|
+
year: { operator: GTE, value: 2020 }
|
|
236
|
+
episodes: {
|
|
237
|
+
terms: [
|
|
238
|
+
{
|
|
239
|
+
path: "name",
|
|
240
|
+
operator: LIKE,
|
|
241
|
+
value: "Pilot"
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
) {
|
|
246
|
+
number
|
|
247
|
+
year
|
|
248
|
+
episodes {
|
|
249
|
+
name
|
|
250
|
+
date
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
#### Collection Filtering with Multiple Conditions
|
|
258
|
+
|
|
259
|
+
Combine multiple filter conditions for collection fields:
|
|
260
|
+
|
|
261
|
+
```graphql
|
|
262
|
+
query {
|
|
263
|
+
series {
|
|
264
|
+
seasons(
|
|
265
|
+
number: { operator: GT, value: 1 }
|
|
266
|
+
year: { operator: BTW, value: [2015, 2023] }
|
|
267
|
+
) {
|
|
268
|
+
number
|
|
269
|
+
year
|
|
270
|
+
state
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
#### Nested Collection Filtering
|
|
277
|
+
|
|
278
|
+
Filter deeply nested collections using dot notation:
|
|
279
|
+
|
|
280
|
+
```graphql
|
|
281
|
+
query {
|
|
282
|
+
series {
|
|
283
|
+
seasons(
|
|
284
|
+
episodes: {
|
|
285
|
+
terms: [
|
|
286
|
+
{
|
|
287
|
+
path: "name",
|
|
288
|
+
operator: LIKE,
|
|
289
|
+
value: "Final"
|
|
290
|
+
}
|
|
291
|
+
]
|
|
292
|
+
}
|
|
293
|
+
) {
|
|
294
|
+
number
|
|
295
|
+
episodes {
|
|
296
|
+
name
|
|
297
|
+
date
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### Collection Filtering with Array Operations
|
|
305
|
+
|
|
306
|
+
Use array operations for collection fields:
|
|
307
|
+
|
|
308
|
+
```graphql
|
|
309
|
+
query {
|
|
310
|
+
series {
|
|
311
|
+
seasons(
|
|
312
|
+
categories: { operator: IN, value: ["Drama", "Crime"] }
|
|
313
|
+
) {
|
|
314
|
+
number
|
|
315
|
+
categories
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Note**: Collection field filtering uses the exact same format as main query filtering, ensuring consistency across your GraphQL API. All available operators (`EQ`, `NE`, `GT`, `LT`, `GTE`, `LTE`, `LIKE`, `IN`, `NIN`, `BTW`) work with collection fields.
|
|
322
|
+
|
|
207
323
|
## 🔧 Middlewares
|
|
208
324
|
|
|
209
325
|
Middlewares provide a powerful way to intercept and process all GraphQL operations before they execute. Use them for cross-cutting concerns like authentication, logging, validation, and performance monitoring.
|
package/package.json
CHANGED
|
Binary file
|
package/src/index.js
CHANGED
|
@@ -1499,7 +1499,7 @@ const buildRootQuery = (name, includedTypes) => {
|
|
|
1499
1499
|
rootQueryArgs.fields[type.simpleEntityEndpointName] = {
|
|
1500
1500
|
type: type.gqltype,
|
|
1501
1501
|
args: { id: { type: GraphQLID } },
|
|
1502
|
-
resolve(parent, args, context) {
|
|
1502
|
+
async resolve(parent, args, context) {
|
|
1503
1503
|
/* Here we define how to get data from database source
|
|
1504
1504
|
this will return the type with id passed in argument
|
|
1505
1505
|
by the user */
|
|
@@ -1510,43 +1510,13 @@ const buildRootQuery = (name, includedTypes) => {
|
|
|
1510
1510
|
context,
|
|
1511
1511
|
};
|
|
1512
1512
|
excecuteMiddleware(params);
|
|
1513
|
-
return type.model.findById(args.id);
|
|
1513
|
+
return await type.model.findById(args.id);
|
|
1514
1514
|
},
|
|
1515
1515
|
};
|
|
1516
1516
|
|
|
1517
1517
|
const argTypes = type.gqltype.getFields();
|
|
1518
1518
|
|
|
1519
|
-
const argsObject =
|
|
1520
|
-
|
|
1521
|
-
for (const [fieldEntryName, fieldEntry] of Object.entries(argTypes)) {
|
|
1522
|
-
argsObject[fieldEntryName] = {};
|
|
1523
|
-
|
|
1524
|
-
if (fieldEntry.type instanceof GraphQLScalarType
|
|
1525
|
-
|| isNonNullOfType(fieldEntry.type, GraphQLScalarType)
|
|
1526
|
-
|| fieldEntry.type instanceof GraphQLEnumType
|
|
1527
|
-
|| isNonNullOfType(fieldEntry.type, GraphQLEnumType)) {
|
|
1528
|
-
argsObject[fieldEntryName].type = QLFilter;
|
|
1529
|
-
} else if (fieldEntry.type instanceof GraphQLObjectType
|
|
1530
|
-
|| isNonNullOfType(fieldEntry.type, GraphQLObjectType)) {
|
|
1531
|
-
argsObject[fieldEntryName].type = QLTypeFilterExpression;
|
|
1532
|
-
} else if (fieldEntry.type instanceof GraphQLList) {
|
|
1533
|
-
const listOfType = fieldEntry.type.ofType;
|
|
1534
|
-
if (listOfType instanceof GraphQLScalarType
|
|
1535
|
-
|| isNonNullOfType(listOfType, GraphQLScalarType)
|
|
1536
|
-
|| listOfType instanceof GraphQLEnumType
|
|
1537
|
-
|| isNonNullOfType(listOfType, GraphQLEnumType)) {
|
|
1538
|
-
argsObject[fieldEntryName].type = QLFilter;
|
|
1539
|
-
} else {
|
|
1540
|
-
argsObject[fieldEntryName].type = QLTypeFilterExpression;
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
argsObject.pagination = {};
|
|
1546
|
-
argsObject.pagination.type = QLPagination;
|
|
1547
|
-
|
|
1548
|
-
argsObject.sort = {};
|
|
1549
|
-
argsObject.sort.type = QLSortExpression;
|
|
1519
|
+
const argsObject = createArgsForQuery(argTypes);
|
|
1550
1520
|
|
|
1551
1521
|
rootQueryArgs.fields[type.listEntitiesEndpointName] = {
|
|
1552
1522
|
type: new GraphQLList(type.gqltype),
|
|
@@ -1568,9 +1538,9 @@ const buildRootQuery = (name, includedTypes) => {
|
|
|
1568
1538
|
|
|
1569
1539
|
let result;
|
|
1570
1540
|
if (aggregateClauses.length === 0) {
|
|
1571
|
-
result = type.model.find({});
|
|
1541
|
+
result = await type.model.find({});
|
|
1572
1542
|
} else {
|
|
1573
|
-
result = type.model.aggregate(aggregateClauses);
|
|
1543
|
+
result = await type.model.aggregate(aggregateClauses);
|
|
1574
1544
|
}
|
|
1575
1545
|
return result;
|
|
1576
1546
|
},
|
|
@@ -1656,18 +1626,39 @@ const autoGenerateResolvers = (gqltype) => {
|
|
|
1656
1626
|
if (!relation.embedded) {
|
|
1657
1627
|
if (fieldEntry.type instanceof GraphQLList) {
|
|
1658
1628
|
// Collection field - generate resolve for one-to-many relationship
|
|
1629
|
+
//This is a one-to-many resolver that will return a list of related objects. Also this one allows to filter the related objects as is in the find endpoint.
|
|
1659
1630
|
const relatedType = fieldEntry.type.ofType;
|
|
1660
1631
|
const connectionField = relation.connectionField || fieldName;
|
|
1632
|
+
const relatedTypeInfo = typesDict.types[relatedType.name];
|
|
1633
|
+
const argsObject = createArgsForQuery(relatedTypeInfo.gqltype.getFields());
|
|
1634
|
+
|
|
1635
|
+
delete argsObject[connectionField];
|
|
1636
|
+
const argsArray = Object.entries(argsObject);
|
|
1637
|
+
|
|
1638
|
+
|
|
1639
|
+
const graphqlArgs = formatArgs(argsArray);
|
|
1640
|
+
|
|
1641
|
+
fieldEntry.args = graphqlArgs;
|
|
1661
1642
|
|
|
1662
|
-
fieldEntry.resolve = (parent) => {
|
|
1643
|
+
fieldEntry.resolve = async (parent, args) => {
|
|
1663
1644
|
// Lazy lookup of the related model
|
|
1664
|
-
|
|
1645
|
+
|
|
1665
1646
|
if (!relatedTypeInfo || !relatedTypeInfo.model) {
|
|
1666
1647
|
throw new Error(`Related type ${relatedType.name} not found or not connected. Make sure it's connected with simfinity.connect() or simfinity.addNoEndpointType().`);
|
|
1667
1648
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1649
|
+
|
|
1650
|
+
args[connectionField] = {
|
|
1651
|
+
terms: [{
|
|
1652
|
+
path: 'id',
|
|
1653
|
+
operator: 'EQ',
|
|
1654
|
+
value: parent.id || parent._id,
|
|
1655
|
+
}],
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
const aggregateClauses = await buildQuery(args, relatedTypeInfo.gqltype);
|
|
1660
|
+
|
|
1661
|
+
return await relatedTypeInfo.model.aggregate(aggregateClauses);
|
|
1671
1662
|
};
|
|
1672
1663
|
} else if (fieldEntry.type instanceof GraphQLObjectType
|
|
1673
1664
|
|| (fieldEntry.type instanceof GraphQLNonNull && fieldEntry.type.ofType instanceof GraphQLObjectType)) {
|
|
@@ -1675,14 +1666,14 @@ const autoGenerateResolvers = (gqltype) => {
|
|
|
1675
1666
|
const relatedType = fieldEntry.type instanceof GraphQLNonNull ? fieldEntry.type.ofType : fieldEntry.type;
|
|
1676
1667
|
const connectionField = relation.connectionField || fieldName;
|
|
1677
1668
|
|
|
1678
|
-
fieldEntry.resolve = (parent) => {
|
|
1669
|
+
fieldEntry.resolve = async (parent) => {
|
|
1679
1670
|
// Lazy lookup of the related model
|
|
1680
1671
|
const relatedTypeInfo = typesDict.types[relatedType.name];
|
|
1681
1672
|
if (!relatedTypeInfo || !relatedTypeInfo.model) {
|
|
1682
1673
|
throw new Error(`Related type ${relatedType.name} not found or not connected. Make sure it's connected with simfinity.connect() or simfinity.addNoEndpointType().`);
|
|
1683
1674
|
}
|
|
1684
1675
|
const relatedId = parent[connectionField] || parent[fieldName];
|
|
1685
|
-
return relatedId ? relatedTypeInfo.model.findById(relatedId) : null;
|
|
1676
|
+
return relatedId ? await relatedTypeInfo.model.findById(relatedId) : null;
|
|
1686
1677
|
};
|
|
1687
1678
|
}
|
|
1688
1679
|
}
|
|
@@ -1741,3 +1732,50 @@ export const addNoEndpointType = (gqltype) => {
|
|
|
1741
1732
|
};
|
|
1742
1733
|
|
|
1743
1734
|
export { createValidatedScalar };
|
|
1735
|
+
|
|
1736
|
+
const createArgsForQuery = (argTypes) => {
|
|
1737
|
+
const argsObject = {};
|
|
1738
|
+
|
|
1739
|
+
for (const [fieldEntryName, fieldEntry] of Object.entries(argTypes)) {
|
|
1740
|
+
argsObject[fieldEntryName] = {};
|
|
1741
|
+
|
|
1742
|
+
if (fieldEntry.type instanceof GraphQLScalarType
|
|
1743
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLScalarType)
|
|
1744
|
+
|| fieldEntry.type instanceof GraphQLEnumType
|
|
1745
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLEnumType)) {
|
|
1746
|
+
argsObject[fieldEntryName].type = QLFilter;
|
|
1747
|
+
} else if (fieldEntry.type instanceof GraphQLObjectType
|
|
1748
|
+
|| isNonNullOfType(fieldEntry.type, GraphQLObjectType)) {
|
|
1749
|
+
argsObject[fieldEntryName].type = QLTypeFilterExpression;
|
|
1750
|
+
} else if (fieldEntry.type instanceof GraphQLList) {
|
|
1751
|
+
const listOfType = fieldEntry.type.ofType;
|
|
1752
|
+
if (listOfType instanceof GraphQLScalarType
|
|
1753
|
+
|| isNonNullOfType(listOfType, GraphQLScalarType)
|
|
1754
|
+
|| listOfType instanceof GraphQLEnumType
|
|
1755
|
+
|| isNonNullOfType(listOfType, GraphQLEnumType)) {
|
|
1756
|
+
argsObject[fieldEntryName].type = QLFilter;
|
|
1757
|
+
} else {
|
|
1758
|
+
argsObject[fieldEntryName].type = QLTypeFilterExpression;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
argsObject.pagination = {};
|
|
1764
|
+
argsObject.pagination.type = QLPagination;
|
|
1765
|
+
|
|
1766
|
+
argsObject.sort = {};
|
|
1767
|
+
argsObject.sort.type = QLSortExpression;
|
|
1768
|
+
return argsObject;
|
|
1769
|
+
};
|
|
1770
|
+
|
|
1771
|
+
function formatArgs(argsArray) {
|
|
1772
|
+
const graphqlArgs = [];
|
|
1773
|
+
for (const [key, value] of argsArray) {
|
|
1774
|
+
const item = {
|
|
1775
|
+
name: key,
|
|
1776
|
+
type: value.type,
|
|
1777
|
+
};
|
|
1778
|
+
graphqlArgs.push(item);
|
|
1779
|
+
}
|
|
1780
|
+
return graphqlArgs;
|
|
1781
|
+
}
|