@simtlix/simfinity-js 1.9.1 → 2.0.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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simtlix/simfinity-js",
3
- "version": "1.9.1",
3
+ "version": "2.0.0",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
Binary file
package/src/index.js CHANGED
@@ -1516,37 +1516,7 @@ const buildRootQuery = (name, includedTypes) => {
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),
@@ -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
+
1661
1638
 
1662
- fieldEntry.resolve = (parent) => {
1639
+ const graphqlArgs = formatArgs(argsArray);
1640
+
1641
+ fieldEntry.args = graphqlArgs;
1642
+
1643
+ fieldEntry.resolve = async (parent, args) => {
1663
1644
  // Lazy lookup of the related model
1664
- const relatedTypeInfo = typesDict.types[relatedType.name];
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
- const query = {};
1669
- query[connectionField] = parent.id || parent._id;
1670
- return relatedTypeInfo.model.find(query);
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)) {
@@ -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
+ }