@simtlix/simfinity-js 1.4.0 → 1.6.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 +1100 -8
- package/eslint.config.mjs +29 -21
- package/package.json +6 -23
- package/src/const/QLOperator.js +2 -4
- package/src/const/QLSort.js +3 -5
- package/src/const/QLValue.js +2 -4
- package/src/errors/internal-server.error.js +2 -2
- package/src/errors/simfinity.error.js +1 -1
- package/src/index.js +62 -61
- package/tests/prevent-collection-creation.test.js +7 -6
- package/tests/validated-scalar.test.js +7 -4
package/README.md
CHANGED
|
@@ -146,13 +146,6 @@ const schema = simfinity.createSchema(
|
|
|
146
146
|
```javascript
|
|
147
147
|
// Prevent automatic MongoDB collection creation (useful for testing)
|
|
148
148
|
simfinity.preventCreatingCollection(true);
|
|
149
|
-
|
|
150
|
-
// Add middleware for all operations
|
|
151
|
-
simfinity.use((params, next) => {
|
|
152
|
-
// params contains: type, args, operation, context
|
|
153
|
-
console.log(`Executing ${params.operation} on ${params.type.name}`);
|
|
154
|
-
next();
|
|
155
|
-
});
|
|
156
149
|
```
|
|
157
150
|
|
|
158
151
|
## 📋 Basic Usage
|
|
@@ -210,6 +203,253 @@ query {
|
|
|
210
203
|
- `NIN` - Not in array
|
|
211
204
|
- `BTW` - Between two values
|
|
212
205
|
|
|
206
|
+
## 🔧 Middlewares
|
|
207
|
+
|
|
208
|
+
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.
|
|
209
|
+
|
|
210
|
+
### Adding Middlewares
|
|
211
|
+
|
|
212
|
+
Register middlewares using `simfinity.use()`. Middlewares execute in the order they're registered:
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
// Basic logging middleware
|
|
216
|
+
simfinity.use((params, next) => {
|
|
217
|
+
console.log(`Executing ${params.operation} on ${params.type?.name || 'custom mutation'}`);
|
|
218
|
+
next();
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Middleware Parameters
|
|
223
|
+
|
|
224
|
+
Each middleware receives a `params` object containing:
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
simfinity.use((params, next) => {
|
|
228
|
+
// params object contains:
|
|
229
|
+
const {
|
|
230
|
+
type, // Type information (model, gqltype, controller, etc.)
|
|
231
|
+
args, // GraphQL arguments passed to the operation
|
|
232
|
+
operation, // Operation type: 'save', 'update', 'delete', 'get_by_id', 'find', 'state_changed', 'custom_mutation'
|
|
233
|
+
context, // GraphQL context object (includes request info, user data, etc.)
|
|
234
|
+
actionName, // For state machine actions (only present for state_changed operations)
|
|
235
|
+
actionField, // State machine action details (only present for state_changed operations)
|
|
236
|
+
entry // Custom mutation name (only present for custom_mutation operations)
|
|
237
|
+
} = params;
|
|
238
|
+
|
|
239
|
+
// Always call next() to continue the middleware chain
|
|
240
|
+
next();
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Common Use Cases
|
|
245
|
+
|
|
246
|
+
#### 1. Authentication & Authorization
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
simfinity.use((params, next) => {
|
|
250
|
+
const { context, operation, type } = params;
|
|
251
|
+
|
|
252
|
+
// Skip authentication for read operations
|
|
253
|
+
if (operation === 'get_by_id' || operation === 'find') {
|
|
254
|
+
return next();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check if user is authenticated
|
|
258
|
+
if (!context.user) {
|
|
259
|
+
throw new simfinity.SimfinityError('Authentication required', 'UNAUTHORIZED', 401);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Check permissions for specific types
|
|
263
|
+
if (type?.name === 'User' && context.user.role !== 'admin') {
|
|
264
|
+
throw new simfinity.SimfinityError('Admin access required', 'FORBIDDEN', 403);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
next();
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### 2. Request Logging & Monitoring
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
simfinity.use((params, next) => {
|
|
275
|
+
const { operation, type, args, context } = params;
|
|
276
|
+
const startTime = Date.now();
|
|
277
|
+
|
|
278
|
+
console.log(`[${new Date().toISOString()}] Starting ${operation}${type ? ` on ${type.name}` : ''}`);
|
|
279
|
+
|
|
280
|
+
// Continue with the operation
|
|
281
|
+
next();
|
|
282
|
+
|
|
283
|
+
const duration = Date.now() - startTime;
|
|
284
|
+
console.log(`[${new Date().toISOString()}] Completed ${operation} in ${duration}ms`);
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
#### 3. Input Validation & Sanitization
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
simfinity.use((params, next) => {
|
|
292
|
+
const { operation, args, type } = params;
|
|
293
|
+
|
|
294
|
+
// Validate input for save operations
|
|
295
|
+
if (operation === 'save' && args.input) {
|
|
296
|
+
// Trim string fields
|
|
297
|
+
Object.keys(args.input).forEach(key => {
|
|
298
|
+
if (typeof args.input[key] === 'string') {
|
|
299
|
+
args.input[key] = args.input[key].trim();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Validate required business rules
|
|
304
|
+
if (type?.name === 'Book' && args.input.title && args.input.title.length < 3) {
|
|
305
|
+
throw new simfinity.SimfinityError('Book title must be at least 3 characters', 'VALIDATION_ERROR', 400);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
next();
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### 4. Rate Limiting
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
const requestCounts = new Map();
|
|
317
|
+
|
|
318
|
+
simfinity.use((params, next) => {
|
|
319
|
+
const { context, operation } = params;
|
|
320
|
+
const userId = context.user?.id || context.ip;
|
|
321
|
+
const now = Date.now();
|
|
322
|
+
const windowMs = 60000; // 1 minute
|
|
323
|
+
const maxRequests = 100;
|
|
324
|
+
|
|
325
|
+
// Only apply rate limiting to mutations
|
|
326
|
+
if (operation === 'save' || operation === 'update' || operation === 'delete') {
|
|
327
|
+
const userRequests = requestCounts.get(userId) || [];
|
|
328
|
+
const recentRequests = userRequests.filter(time => now - time < windowMs);
|
|
329
|
+
|
|
330
|
+
if (recentRequests.length >= maxRequests) {
|
|
331
|
+
throw new simfinity.SimfinityError('Rate limit exceeded', 'TOO_MANY_REQUESTS', 429);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
recentRequests.push(now);
|
|
335
|
+
requestCounts.set(userId, recentRequests);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
next();
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
#### 5. Audit Trail
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
simfinity.use((params, next) => {
|
|
346
|
+
const { operation, type, args, context } = params;
|
|
347
|
+
|
|
348
|
+
// Log all mutations for audit purposes
|
|
349
|
+
if (operation === 'save' || operation === 'update' || operation === 'delete') {
|
|
350
|
+
const auditEntry = {
|
|
351
|
+
timestamp: new Date(),
|
|
352
|
+
user: context.user?.id,
|
|
353
|
+
operation,
|
|
354
|
+
type: type?.name,
|
|
355
|
+
entityId: args.id || 'new',
|
|
356
|
+
data: operation === 'delete' ? null : args.input,
|
|
357
|
+
ip: context.ip,
|
|
358
|
+
userAgent: context.userAgent
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// Save to audit log (could be database, file, or external service)
|
|
362
|
+
console.log('AUDIT:', JSON.stringify(auditEntry));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
next();
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Multiple Middlewares
|
|
370
|
+
|
|
371
|
+
Middlewares execute in registration order. Each middleware must call `next()` to continue the chain:
|
|
372
|
+
|
|
373
|
+
```javascript
|
|
374
|
+
// Middleware 1: Authentication
|
|
375
|
+
simfinity.use((params, next) => {
|
|
376
|
+
console.log('1. Checking authentication...');
|
|
377
|
+
// Authentication logic here
|
|
378
|
+
next(); // Continue to next middleware
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Middleware 2: Authorization
|
|
382
|
+
simfinity.use((params, next) => {
|
|
383
|
+
console.log('2. Checking permissions...');
|
|
384
|
+
// Authorization logic here
|
|
385
|
+
next(); // Continue to next middleware
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Middleware 3: Logging
|
|
389
|
+
simfinity.use((params, next) => {
|
|
390
|
+
console.log('3. Logging request...');
|
|
391
|
+
// Logging logic here
|
|
392
|
+
next(); // Continue to GraphQL operation
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Error Handling in Middlewares
|
|
397
|
+
|
|
398
|
+
Middlewares can throw errors to stop the operation:
|
|
399
|
+
|
|
400
|
+
```javascript
|
|
401
|
+
simfinity.use((params, next) => {
|
|
402
|
+
const { context, operation } = params;
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
// Validation logic
|
|
406
|
+
if (!context.user && operation !== 'find') {
|
|
407
|
+
throw new simfinity.SimfinityError('Authentication required', 'UNAUTHORIZED', 401);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
next(); // Continue only if validation passes
|
|
411
|
+
} catch (error) {
|
|
412
|
+
// Error automatically bubbles up to GraphQL error handling
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Conditional Middleware Execution
|
|
419
|
+
|
|
420
|
+
Execute middleware logic conditionally based on operation type or context:
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
simfinity.use((params, next) => {
|
|
424
|
+
const { operation, type, context } = params;
|
|
425
|
+
|
|
426
|
+
// Only apply to specific types
|
|
427
|
+
if (type?.name === 'SensitiveData') {
|
|
428
|
+
// Special handling for sensitive data
|
|
429
|
+
if (!context.user?.hasHighSecurity) {
|
|
430
|
+
throw new simfinity.SimfinityError('High security clearance required', 'FORBIDDEN', 403);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Only apply to mutation operations
|
|
435
|
+
if (['save', 'update', 'delete', 'state_changed'].includes(operation)) {
|
|
436
|
+
// Mutation-specific logic
|
|
437
|
+
console.log(`Mutation ${operation} executing...`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
next();
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Best Practices
|
|
445
|
+
|
|
446
|
+
1. **Always call `next()`**: Failing to call `next()` will hang the request
|
|
447
|
+
2. **Handle errors gracefully**: Use try-catch blocks for error-prone operations
|
|
448
|
+
3. **Keep middlewares focused**: Each middleware should handle one concern
|
|
449
|
+
4. **Order matters**: Register middlewares in logical order (auth → validation → logging)
|
|
450
|
+
5. **Performance consideration**: Middlewares run on every operation, keep them lightweight
|
|
451
|
+
6. **Use context wisely**: Store request-specific data in the GraphQL context object
|
|
452
|
+
|
|
213
453
|
## 🔗 Relationships
|
|
214
454
|
|
|
215
455
|
### Defining Relationships
|
|
@@ -353,6 +593,104 @@ simfinity.addNoEndpointType(AuthorType);
|
|
|
353
593
|
|
|
354
594
|
That's it! All relationship resolvers are automatically generated when you connect your types.
|
|
355
595
|
|
|
596
|
+
### Adding Types Without Endpoints
|
|
597
|
+
|
|
598
|
+
Use `addNoEndpointType()` for types that should be included in the GraphQL schema but don't need their own CRUD operations:
|
|
599
|
+
|
|
600
|
+
```javascript
|
|
601
|
+
simfinity.addNoEndpointType(TypeName);
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**When to use `addNoEndpointType()` vs `connect()`:**
|
|
605
|
+
|
|
606
|
+
| Method | Use Case | Creates Endpoints | Use Example |
|
|
607
|
+
|--------|----------|-------------------|-------------|
|
|
608
|
+
| `connect()` | Types that need CRUD operations | ✅ Yes | User, Product, Order |
|
|
609
|
+
| `addNoEndpointType()` | Types only used in relationships | ❌ No | Address, Settings, Director |
|
|
610
|
+
|
|
611
|
+
#### Perfect Example: TV Series with Embedded Director
|
|
612
|
+
|
|
613
|
+
From the [series-sample](https://github.com/simtlix/series-sample) project:
|
|
614
|
+
|
|
615
|
+
```javascript
|
|
616
|
+
// Director type - Used only as embedded data, no direct API access needed
|
|
617
|
+
const directorType = new GraphQLObjectType({
|
|
618
|
+
name: 'director',
|
|
619
|
+
fields: () => ({
|
|
620
|
+
id: { type: GraphQLID },
|
|
621
|
+
name: { type: new GraphQLNonNull(GraphQLString) },
|
|
622
|
+
country: { type: GraphQLString }
|
|
623
|
+
})
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Add to schema WITHOUT creating endpoints
|
|
627
|
+
simfinity.addNoEndpointType(directorType);
|
|
628
|
+
|
|
629
|
+
// Serie type - Has its own endpoints and embeds director data
|
|
630
|
+
const serieType = new GraphQLObjectType({
|
|
631
|
+
name: 'serie',
|
|
632
|
+
fields: () => ({
|
|
633
|
+
id: { type: GraphQLID },
|
|
634
|
+
name: { type: new GraphQLNonNull(GraphQLString) },
|
|
635
|
+
categories: { type: new GraphQLList(GraphQLString) },
|
|
636
|
+
director: {
|
|
637
|
+
type: new GraphQLNonNull(directorType),
|
|
638
|
+
extensions: {
|
|
639
|
+
relation: {
|
|
640
|
+
embedded: true, // Director data stored within serie document
|
|
641
|
+
displayField: 'name'
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
})
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// Create full CRUD endpoints for series
|
|
649
|
+
simfinity.connect(null, serieType, 'serie', 'series');
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
**Result:**
|
|
653
|
+
- ✅ `addserie`, `updateserie`, `deleteserie` mutations available
|
|
654
|
+
- ✅ `serie`, `series` queries available
|
|
655
|
+
- ❌ No `adddirector`, `director`, `directors` endpoints (director is embedded)
|
|
656
|
+
|
|
657
|
+
**Usage:**
|
|
658
|
+
```graphql
|
|
659
|
+
mutation {
|
|
660
|
+
addserie(input: {
|
|
661
|
+
name: "Breaking Bad"
|
|
662
|
+
categories: ["crime", "drama", "thriller"]
|
|
663
|
+
director: {
|
|
664
|
+
name: "Vince Gilligan"
|
|
665
|
+
country: "United States"
|
|
666
|
+
}
|
|
667
|
+
}) {
|
|
668
|
+
id
|
|
669
|
+
name
|
|
670
|
+
director {
|
|
671
|
+
name
|
|
672
|
+
country
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
#### When to Use Each Approach
|
|
679
|
+
|
|
680
|
+
**Use `addNoEndpointType()` for:**
|
|
681
|
+
- Simple data objects with few fields
|
|
682
|
+
- Data that doesn't need CRUD operations
|
|
683
|
+
- Objects that belong to a single parent (1:1 relationships)
|
|
684
|
+
- Configuration or settings objects
|
|
685
|
+
- **Examples**: Address, Director info, Product specifications
|
|
686
|
+
|
|
687
|
+
**Use `connect()` for:**
|
|
688
|
+
- Complex entities that need their own endpoints
|
|
689
|
+
- Data that needs CRUD operations
|
|
690
|
+
- Objects shared between multiple parents (many:many relationships)
|
|
691
|
+
- Objects with business logic (controllers, state machines)
|
|
692
|
+
- **Examples**: User, Product, Order, Season, Episode
|
|
693
|
+
|
|
356
694
|
### Embedded vs Referenced Relationships
|
|
357
695
|
|
|
358
696
|
**Referenced Relationships** (default):
|
|
@@ -853,7 +1191,7 @@ simfinity.registerMutation(
|
|
|
853
1191
|
|
|
854
1192
|
### Adding Types Without Endpoints
|
|
855
1193
|
|
|
856
|
-
Include types in the schema without generating endpoints:
|
|
1194
|
+
Include types in the schema without generating endpoints. See the [detailed guide on addNoEndpointType()](#adding-types-without-endpoints) for when and how to use this pattern:
|
|
857
1195
|
|
|
858
1196
|
```javascript
|
|
859
1197
|
// This type can be used in relationships but won't have queries/mutations
|
|
@@ -1031,3 +1369,757 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
1031
1369
|
*Built with ❤️ by [Simtlix](https://github.com/simtlix)*
|
|
1032
1370
|
|
|
1033
1371
|
|
|
1372
|
+
## 📚 Query Examples from Series-Sample
|
|
1373
|
+
|
|
1374
|
+
Here are some practical GraphQL query examples from the series-sample project, showcasing how to use simfinity.js effectively:
|
|
1375
|
+
|
|
1376
|
+
### 1. Series with Directors from a Specific Country
|
|
1377
|
+
|
|
1378
|
+
Find all series that have directors from the United States:
|
|
1379
|
+
|
|
1380
|
+
```graphql
|
|
1381
|
+
query {
|
|
1382
|
+
series(director: {
|
|
1383
|
+
terms: [
|
|
1384
|
+
{
|
|
1385
|
+
path: "country",
|
|
1386
|
+
operator: EQ,
|
|
1387
|
+
value: "United States"
|
|
1388
|
+
}
|
|
1389
|
+
]
|
|
1390
|
+
}) {
|
|
1391
|
+
id
|
|
1392
|
+
name
|
|
1393
|
+
categories
|
|
1394
|
+
director {
|
|
1395
|
+
name
|
|
1396
|
+
country
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
### 2. Series with a Specific Episode Name
|
|
1403
|
+
|
|
1404
|
+
Find series that contain an episode with the name "Pilot":
|
|
1405
|
+
|
|
1406
|
+
```graphql
|
|
1407
|
+
query {
|
|
1408
|
+
series(
|
|
1409
|
+
seasons: {
|
|
1410
|
+
terms: [
|
|
1411
|
+
{
|
|
1412
|
+
path: "episodes.name",
|
|
1413
|
+
operator: EQ,
|
|
1414
|
+
value: "Pilot"
|
|
1415
|
+
}
|
|
1416
|
+
]
|
|
1417
|
+
}
|
|
1418
|
+
) {
|
|
1419
|
+
id
|
|
1420
|
+
name
|
|
1421
|
+
seasons {
|
|
1422
|
+
number
|
|
1423
|
+
episodes {
|
|
1424
|
+
number
|
|
1425
|
+
name
|
|
1426
|
+
date
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
### 3. Series with a Particular Star
|
|
1434
|
+
|
|
1435
|
+
Find series that feature "Bryan Cranston":
|
|
1436
|
+
|
|
1437
|
+
```graphql
|
|
1438
|
+
query {
|
|
1439
|
+
assignedStarsAndSeries(star: {
|
|
1440
|
+
terms: [
|
|
1441
|
+
{
|
|
1442
|
+
path: "name",
|
|
1443
|
+
operator: EQ,
|
|
1444
|
+
value: "Bryan Cranston"
|
|
1445
|
+
}
|
|
1446
|
+
]
|
|
1447
|
+
}) {
|
|
1448
|
+
id
|
|
1449
|
+
star {
|
|
1450
|
+
name
|
|
1451
|
+
}
|
|
1452
|
+
serie {
|
|
1453
|
+
id
|
|
1454
|
+
name
|
|
1455
|
+
categories
|
|
1456
|
+
director {
|
|
1457
|
+
name
|
|
1458
|
+
country
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
```
|
|
1464
|
+
|
|
1465
|
+
### 4. Seasons from Series with Directors from a Given Country
|
|
1466
|
+
|
|
1467
|
+
Find all seasons that belong to series directed by someone from the United States:
|
|
1468
|
+
|
|
1469
|
+
```graphql
|
|
1470
|
+
query {
|
|
1471
|
+
seasons(serie: {
|
|
1472
|
+
terms: [
|
|
1473
|
+
{
|
|
1474
|
+
path: "director.country",
|
|
1475
|
+
operator: EQ,
|
|
1476
|
+
value: "United States"
|
|
1477
|
+
}
|
|
1478
|
+
]
|
|
1479
|
+
}) {
|
|
1480
|
+
id
|
|
1481
|
+
number
|
|
1482
|
+
year
|
|
1483
|
+
state
|
|
1484
|
+
serie {
|
|
1485
|
+
name
|
|
1486
|
+
categories
|
|
1487
|
+
director {
|
|
1488
|
+
name
|
|
1489
|
+
country
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
episodes {
|
|
1493
|
+
number
|
|
1494
|
+
name
|
|
1495
|
+
date
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
```
|
|
1500
|
+
|
|
1501
|
+
### 5. Combining Scalar and ObjectType Filters
|
|
1502
|
+
|
|
1503
|
+
Find series named "Breaking Bad" that have at least one season with number 1:
|
|
1504
|
+
|
|
1505
|
+
```graphql
|
|
1506
|
+
query {
|
|
1507
|
+
series(
|
|
1508
|
+
name: {
|
|
1509
|
+
operator: EQ,
|
|
1510
|
+
value: "Breaking Bad"
|
|
1511
|
+
}
|
|
1512
|
+
seasons: {
|
|
1513
|
+
terms: [
|
|
1514
|
+
{
|
|
1515
|
+
path: "number",
|
|
1516
|
+
operator: EQ,
|
|
1517
|
+
value: 1
|
|
1518
|
+
}
|
|
1519
|
+
]
|
|
1520
|
+
}
|
|
1521
|
+
) {
|
|
1522
|
+
id
|
|
1523
|
+
name
|
|
1524
|
+
director {
|
|
1525
|
+
name
|
|
1526
|
+
country
|
|
1527
|
+
}
|
|
1528
|
+
seasons {
|
|
1529
|
+
number
|
|
1530
|
+
episodes {
|
|
1531
|
+
name
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
```
|
|
1537
|
+
|
|
1538
|
+
### 6. Complex Nested Queries
|
|
1539
|
+
|
|
1540
|
+
Get complete information for a specific series:
|
|
1541
|
+
|
|
1542
|
+
```graphql
|
|
1543
|
+
query {
|
|
1544
|
+
series(name: {
|
|
1545
|
+
operator: EQ,
|
|
1546
|
+
value: "Breaking Bad"
|
|
1547
|
+
}) {
|
|
1548
|
+
id
|
|
1549
|
+
name
|
|
1550
|
+
categories
|
|
1551
|
+
director {
|
|
1552
|
+
name
|
|
1553
|
+
country
|
|
1554
|
+
}
|
|
1555
|
+
seasons {
|
|
1556
|
+
number
|
|
1557
|
+
year
|
|
1558
|
+
state
|
|
1559
|
+
episodes {
|
|
1560
|
+
number
|
|
1561
|
+
name
|
|
1562
|
+
date
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
```
|
|
1568
|
+
|
|
1569
|
+
### 7. Episodes from a Specific Season and Series
|
|
1570
|
+
|
|
1571
|
+
Find all episodes from Season 1 of Breaking Bad:
|
|
1572
|
+
|
|
1573
|
+
```graphql
|
|
1574
|
+
query {
|
|
1575
|
+
episodes(season: {
|
|
1576
|
+
terms: [
|
|
1577
|
+
{
|
|
1578
|
+
path: "number",
|
|
1579
|
+
operator: EQ,
|
|
1580
|
+
value: 1
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
path: "serie.name",
|
|
1584
|
+
operator: EQ,
|
|
1585
|
+
value: "Breaking Bad"
|
|
1586
|
+
}
|
|
1587
|
+
]
|
|
1588
|
+
}) {
|
|
1589
|
+
id
|
|
1590
|
+
number
|
|
1591
|
+
name
|
|
1592
|
+
date
|
|
1593
|
+
season {
|
|
1594
|
+
number
|
|
1595
|
+
serie {
|
|
1596
|
+
name
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
```
|
|
1602
|
+
|
|
1603
|
+
### 8. Series by Category
|
|
1604
|
+
|
|
1605
|
+
Find all crime series:
|
|
1606
|
+
|
|
1607
|
+
```graphql
|
|
1608
|
+
query {
|
|
1609
|
+
series(categories: {
|
|
1610
|
+
operator: EQ,
|
|
1611
|
+
value: "Crime"
|
|
1612
|
+
}) {
|
|
1613
|
+
id
|
|
1614
|
+
name
|
|
1615
|
+
categories
|
|
1616
|
+
director {
|
|
1617
|
+
name
|
|
1618
|
+
country
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
```
|
|
1623
|
+
|
|
1624
|
+
### 9. Search by Partial Episode Name
|
|
1625
|
+
|
|
1626
|
+
Find episodes containing "Fire" in the name:
|
|
1627
|
+
|
|
1628
|
+
```graphql
|
|
1629
|
+
query {
|
|
1630
|
+
episodes(name: {
|
|
1631
|
+
operator: LIKE,
|
|
1632
|
+
value: "Fire"
|
|
1633
|
+
}) {
|
|
1634
|
+
id
|
|
1635
|
+
number
|
|
1636
|
+
name
|
|
1637
|
+
date
|
|
1638
|
+
season {
|
|
1639
|
+
number
|
|
1640
|
+
serie {
|
|
1641
|
+
name
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
```
|
|
1647
|
+
|
|
1648
|
+
### 10. Pagination
|
|
1649
|
+
|
|
1650
|
+
Simfinity.js supports built-in pagination with optional total count:
|
|
1651
|
+
|
|
1652
|
+
```graphql
|
|
1653
|
+
query {
|
|
1654
|
+
series(
|
|
1655
|
+
categories: {
|
|
1656
|
+
operator: EQ,
|
|
1657
|
+
value: "Crime"
|
|
1658
|
+
}
|
|
1659
|
+
pagination: {
|
|
1660
|
+
page: 1,
|
|
1661
|
+
size: 2,
|
|
1662
|
+
count: true
|
|
1663
|
+
}
|
|
1664
|
+
) {
|
|
1665
|
+
id
|
|
1666
|
+
name
|
|
1667
|
+
categories
|
|
1668
|
+
director {
|
|
1669
|
+
name
|
|
1670
|
+
country
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
```
|
|
1675
|
+
|
|
1676
|
+
#### Pagination Parameters:
|
|
1677
|
+
- **page**: Page number (starts at 1, not 0)
|
|
1678
|
+
- **size**: Number of items per page
|
|
1679
|
+
- **count**: Optional boolean - if `true`, returns total count of matching records
|
|
1680
|
+
|
|
1681
|
+
#### Getting Total Count:
|
|
1682
|
+
When `count: true` is specified, the total count is available in the response extensions. You need to configure an Envelop plugin to expose it:
|
|
1683
|
+
|
|
1684
|
+
```javascript
|
|
1685
|
+
// Envelop plugin for count in extensions
|
|
1686
|
+
function useCountPlugin() {
|
|
1687
|
+
return {
|
|
1688
|
+
onExecute() {
|
|
1689
|
+
return {
|
|
1690
|
+
onExecuteDone({ result, args }) {
|
|
1691
|
+
if (args.contextValue?.count) {
|
|
1692
|
+
result.extensions = {
|
|
1693
|
+
...result.extensions,
|
|
1694
|
+
count: args.contextValue.count,
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
```
|
|
1703
|
+
|
|
1704
|
+
#### Example Response:
|
|
1705
|
+
```json
|
|
1706
|
+
{
|
|
1707
|
+
"data": {
|
|
1708
|
+
"series": [
|
|
1709
|
+
{
|
|
1710
|
+
"id": "1",
|
|
1711
|
+
"name": "Breaking Bad",
|
|
1712
|
+
"categories": ["Crime", "Drama"],
|
|
1713
|
+
"director": {
|
|
1714
|
+
"name": "Vince Gilligan",
|
|
1715
|
+
"country": "United States"
|
|
1716
|
+
}
|
|
1717
|
+
},
|
|
1718
|
+
{
|
|
1719
|
+
"id": "2",
|
|
1720
|
+
"name": "Better Call Saul",
|
|
1721
|
+
"categories": ["Crime", "Drama"],
|
|
1722
|
+
"director": {
|
|
1723
|
+
"name": "Vince Gilligan",
|
|
1724
|
+
"country": "United States"
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
]
|
|
1728
|
+
},
|
|
1729
|
+
"extensions": {
|
|
1730
|
+
"count": 15
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
```
|
|
1734
|
+
|
|
1735
|
+
### 11. Sorting
|
|
1736
|
+
|
|
1737
|
+
Simfinity.js supports sorting with multiple fields and sort orders:
|
|
1738
|
+
|
|
1739
|
+
```graphql
|
|
1740
|
+
query {
|
|
1741
|
+
series(
|
|
1742
|
+
categories: { operator: EQ, value: "Crime" }
|
|
1743
|
+
pagination: { page: 1, size: 5, count: true }
|
|
1744
|
+
sort: {
|
|
1745
|
+
terms: [
|
|
1746
|
+
{
|
|
1747
|
+
field: "name",
|
|
1748
|
+
order: DESC
|
|
1749
|
+
}
|
|
1750
|
+
]
|
|
1751
|
+
}
|
|
1752
|
+
) {
|
|
1753
|
+
id
|
|
1754
|
+
name
|
|
1755
|
+
categories
|
|
1756
|
+
director {
|
|
1757
|
+
name
|
|
1758
|
+
country
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
```
|
|
1763
|
+
|
|
1764
|
+
#### Sorting Parameters:
|
|
1765
|
+
- **sort**: Contains sorting configuration
|
|
1766
|
+
- **terms**: Array of sort criteria (allows multiple sort fields)
|
|
1767
|
+
- **field**: The field name to sort by
|
|
1768
|
+
- **order**: Sort order - `ASC` (ascending) or `DESC` (descending)
|
|
1769
|
+
|
|
1770
|
+
#### Sorting by Nested Fields:
|
|
1771
|
+
You can sort by fields from related/nested objects using dot notation:
|
|
1772
|
+
|
|
1773
|
+
```graphql
|
|
1774
|
+
query {
|
|
1775
|
+
series(
|
|
1776
|
+
categories: { operator: EQ, value: "Drama" }
|
|
1777
|
+
pagination: { page: 1, size: 5, count: true }
|
|
1778
|
+
sort: {
|
|
1779
|
+
terms: [
|
|
1780
|
+
{
|
|
1781
|
+
field: "director.name",
|
|
1782
|
+
order: DESC
|
|
1783
|
+
}
|
|
1784
|
+
]
|
|
1785
|
+
}
|
|
1786
|
+
) {
|
|
1787
|
+
id
|
|
1788
|
+
name
|
|
1789
|
+
categories
|
|
1790
|
+
director {
|
|
1791
|
+
name
|
|
1792
|
+
country
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
```
|
|
1797
|
+
|
|
1798
|
+
#### Multiple Sort Fields:
|
|
1799
|
+
You can sort by multiple fields with different orders:
|
|
1800
|
+
|
|
1801
|
+
```graphql
|
|
1802
|
+
query {
|
|
1803
|
+
series(
|
|
1804
|
+
sort: {
|
|
1805
|
+
terms: [
|
|
1806
|
+
{ field: "director.country", order: ASC },
|
|
1807
|
+
{ field: "name", order: DESC }
|
|
1808
|
+
]
|
|
1809
|
+
}
|
|
1810
|
+
) {
|
|
1811
|
+
id
|
|
1812
|
+
name
|
|
1813
|
+
director {
|
|
1814
|
+
name
|
|
1815
|
+
country
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
```
|
|
1820
|
+
|
|
1821
|
+
#### Combining Features:
|
|
1822
|
+
The example above demonstrates combining **filtering**, **pagination**, and **sorting** in a single query - a common pattern for data tables and lists with full functionality.
|
|
1823
|
+
|
|
1824
|
+
### 12. Series Released in a Specific Year Range
|
|
1825
|
+
|
|
1826
|
+
Find series with seasons released between 2010-2015:
|
|
1827
|
+
|
|
1828
|
+
```graphql
|
|
1829
|
+
query {
|
|
1830
|
+
seasons(year: {
|
|
1831
|
+
operator: BETWEEN,
|
|
1832
|
+
value: [2010, 2015]
|
|
1833
|
+
}) {
|
|
1834
|
+
id
|
|
1835
|
+
number
|
|
1836
|
+
year
|
|
1837
|
+
serie {
|
|
1838
|
+
name
|
|
1839
|
+
director {
|
|
1840
|
+
name
|
|
1841
|
+
country
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
```
|
|
1847
|
+
|
|
1848
|
+
|
|
1849
|
+
## 🔄 State Machine Example from Series-Sample
|
|
1850
|
+
|
|
1851
|
+
Simfinity.js provides built-in state machine support for managing entity lifecycles. Here's an example of how a state machine is implemented in the Season entity from the series-sample project.
|
|
1852
|
+
|
|
1853
|
+
### State Machine Configuration
|
|
1854
|
+
|
|
1855
|
+
State machines require **GraphQL Enum Types** to define states and proper state references:
|
|
1856
|
+
|
|
1857
|
+
**Step 1: Define the GraphQL Enum Type**
|
|
1858
|
+
|
|
1859
|
+
```javascript
|
|
1860
|
+
const { GraphQLEnumType } = require('graphql');
|
|
1861
|
+
|
|
1862
|
+
const seasonState = new GraphQLEnumType({
|
|
1863
|
+
name: 'seasonState',
|
|
1864
|
+
values: {
|
|
1865
|
+
SCHEDULED: { value: 'SCHEDULED' },
|
|
1866
|
+
ACTIVE: { value: 'ACTIVE' },
|
|
1867
|
+
FINISHED: { value: 'FINISHED' }
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
```
|
|
1871
|
+
|
|
1872
|
+
**Step 2: Use Enum in GraphQL Object Type**
|
|
1873
|
+
|
|
1874
|
+
```javascript
|
|
1875
|
+
const seasonType = new GraphQLObjectType({
|
|
1876
|
+
name: 'season',
|
|
1877
|
+
fields: () => ({
|
|
1878
|
+
id: { type: GraphQLID },
|
|
1879
|
+
number: { type: GraphQLInt },
|
|
1880
|
+
year: { type: GraphQLInt },
|
|
1881
|
+
state: { type: seasonState }, // ← Use the enum type
|
|
1882
|
+
// ... other fields
|
|
1883
|
+
})
|
|
1884
|
+
});
|
|
1885
|
+
```
|
|
1886
|
+
|
|
1887
|
+
**Step 3: Define State Machine with Enum Values**
|
|
1888
|
+
|
|
1889
|
+
```javascript
|
|
1890
|
+
const stateMachine = {
|
|
1891
|
+
initialState: seasonState.getValue('SCHEDULED'),
|
|
1892
|
+
actions: {
|
|
1893
|
+
activate: {
|
|
1894
|
+
from: seasonState.getValue('SCHEDULED'),
|
|
1895
|
+
to: seasonState.getValue('ACTIVE'),
|
|
1896
|
+
action: async (params) => {
|
|
1897
|
+
console.log('Season activated:', JSON.stringify(params));
|
|
1898
|
+
}
|
|
1899
|
+
},
|
|
1900
|
+
finalize: {
|
|
1901
|
+
from: seasonState.getValue('ACTIVE'),
|
|
1902
|
+
to: seasonState.getValue('FINISHED'),
|
|
1903
|
+
action: async (params) => {
|
|
1904
|
+
console.log('Season finalized:', JSON.stringify(params));
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
|
|
1910
|
+
// Connect type with state machine
|
|
1911
|
+
simfinity.connect(null, seasonType, 'season', 'seasons', null, null, stateMachine);
|
|
1912
|
+
```
|
|
1913
|
+
|
|
1914
|
+
### Season States
|
|
1915
|
+
|
|
1916
|
+
The Season entity has three states:
|
|
1917
|
+
|
|
1918
|
+
1. **SCHEDULED** - Initial state when season is created
|
|
1919
|
+
2. **ACTIVE** - Season is currently airing
|
|
1920
|
+
3. **FINISHED** - Season has completed airing
|
|
1921
|
+
|
|
1922
|
+
### State Transitions
|
|
1923
|
+
|
|
1924
|
+
**Available transitions:**
|
|
1925
|
+
- `activate`: SCHEDULED → ACTIVE
|
|
1926
|
+
- `finalize`: ACTIVE → FINISHED
|
|
1927
|
+
|
|
1928
|
+
### State Machine Mutations
|
|
1929
|
+
|
|
1930
|
+
Simfinity.js automatically generates state transition mutations:
|
|
1931
|
+
|
|
1932
|
+
```graphql
|
|
1933
|
+
# Activate a scheduled season
|
|
1934
|
+
mutation {
|
|
1935
|
+
activateseason(id: "season_id_here") {
|
|
1936
|
+
id
|
|
1937
|
+
number
|
|
1938
|
+
year
|
|
1939
|
+
state
|
|
1940
|
+
serie {
|
|
1941
|
+
name
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
```
|
|
1946
|
+
|
|
1947
|
+
```graphql
|
|
1948
|
+
# Finalize an active season
|
|
1949
|
+
mutation {
|
|
1950
|
+
finalizeseason(id: "season_id_here") {
|
|
1951
|
+
id
|
|
1952
|
+
number
|
|
1953
|
+
year
|
|
1954
|
+
state
|
|
1955
|
+
serie {
|
|
1956
|
+
name
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
```
|
|
1961
|
+
|
|
1962
|
+
### State Machine Features
|
|
1963
|
+
|
|
1964
|
+
**Validation:**
|
|
1965
|
+
- Only valid transitions are allowed
|
|
1966
|
+
- Attempting invalid transitions returns an error
|
|
1967
|
+
- State field is read-only (managed by state machine)
|
|
1968
|
+
|
|
1969
|
+
**Custom Actions:**
|
|
1970
|
+
- Each transition can execute custom business logic
|
|
1971
|
+
- Actions receive parameters including entity data
|
|
1972
|
+
- Actions can perform side effects (logging, notifications, etc.)
|
|
1973
|
+
|
|
1974
|
+
**Query by State:**
|
|
1975
|
+
```graphql
|
|
1976
|
+
query {
|
|
1977
|
+
seasons(state: {
|
|
1978
|
+
operator: EQ,
|
|
1979
|
+
value: ACTIVE
|
|
1980
|
+
}) {
|
|
1981
|
+
id
|
|
1982
|
+
number
|
|
1983
|
+
year
|
|
1984
|
+
state
|
|
1985
|
+
serie {
|
|
1986
|
+
name
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
```
|
|
1991
|
+
|
|
1992
|
+
### State Machine Best Practices
|
|
1993
|
+
|
|
1994
|
+
1. **GraphQL Enum Types**: Always define states as GraphQL enums for type safety
|
|
1995
|
+
2. **getValue() Method**: Use `enumType.getValue('VALUE')` for state machine configuration
|
|
1996
|
+
3. **Initial State**: Define clear initial state using enum values
|
|
1997
|
+
4. **Linear Flows**: Design logical progression (SCHEDULED → ACTIVE → FINISHED)
|
|
1998
|
+
5. **Type Safety**: GraphQL enums provide validation and autocomplete
|
|
1999
|
+
6. **Actions**: Implement side effects in transition actions
|
|
2000
|
+
7. **Error Handling**: Handle transition failures gracefully
|
|
2001
|
+
|
|
2002
|
+
### Key Implementation Points
|
|
2003
|
+
|
|
2004
|
+
- **Enum Definition**: States must be defined as `GraphQLEnumType`
|
|
2005
|
+
- **Type Reference**: Use the enum type in your GraphQL object: `state: { type: seasonState }`
|
|
2006
|
+
- **State Machine Values**: Reference enum values with `seasonState.getValue('STATE_NAME')`
|
|
2007
|
+
- **Automatic Validation**: GraphQL validates state values against the enum
|
|
2008
|
+
- **IDE Support**: Enum values provide autocomplete and type checking
|
|
2009
|
+
|
|
2010
|
+
### Example Workflow
|
|
2011
|
+
|
|
2012
|
+
```graphql
|
|
2013
|
+
# 1. Create season (automatically SCHEDULED)
|
|
2014
|
+
mutation {
|
|
2015
|
+
addseason(input: {
|
|
2016
|
+
number: 6
|
|
2017
|
+
year: 2024
|
|
2018
|
+
serie: "series_id_here"
|
|
2019
|
+
}) {
|
|
2020
|
+
id
|
|
2021
|
+
state # Will be "SCHEDULED"
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
# 2. Activate season when airing begins
|
|
2026
|
+
mutation {
|
|
2027
|
+
activateseason(id: "season_id_here") {
|
|
2028
|
+
id
|
|
2029
|
+
state # Will be "ACTIVE"
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
# 3. Finalize season when completed
|
|
2034
|
+
mutation {
|
|
2035
|
+
finalizeseason(id: "season_id_here") {
|
|
2036
|
+
id
|
|
2037
|
+
state # Will be "FINISHED"
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
```
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
## 📦 Envelop Plugin for Count in Extensions
|
|
2044
|
+
|
|
2045
|
+
To include the total count in the extensions of your GraphQL response, you can use an Envelop plugin. This is particularly useful for pagination and analytics.
|
|
2046
|
+
|
|
2047
|
+
### Envelop Plugin Example
|
|
2048
|
+
|
|
2049
|
+
Here's how you can implement the plugin:
|
|
2050
|
+
|
|
2051
|
+
```javascript
|
|
2052
|
+
// Envelop plugin for count in extensions
|
|
2053
|
+
function useCountPlugin() {
|
|
2054
|
+
return {
|
|
2055
|
+
onExecute() {
|
|
2056
|
+
return {
|
|
2057
|
+
onExecuteDone({ result, args }) {
|
|
2058
|
+
if (args.contextValue?.count) {
|
|
2059
|
+
result.extensions = {
|
|
2060
|
+
...result.extensions,
|
|
2061
|
+
count: args.contextValue.count,
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
```
|
|
2070
|
+
|
|
2071
|
+
### How to Use
|
|
2072
|
+
|
|
2073
|
+
1. **Integrate the Plugin**: Add the plugin to your GraphQL server setup.
|
|
2074
|
+
2. **Configure Context**: Ensure that your context includes the count value when executing queries.
|
|
2075
|
+
3. **Access Count**: The count will be available in the `extensions` field of the GraphQL response.
|
|
2076
|
+
|
|
2077
|
+
### Example Usage
|
|
2078
|
+
|
|
2079
|
+
```javascript
|
|
2080
|
+
const { envelop, useSchema } = require('@envelop/core');
|
|
2081
|
+
const { makeExecutableSchema } = require('@graphql-tools/schema');
|
|
2082
|
+
|
|
2083
|
+
const schema = makeExecutableSchema({
|
|
2084
|
+
typeDefs,
|
|
2085
|
+
resolvers,
|
|
2086
|
+
});
|
|
2087
|
+
|
|
2088
|
+
const getEnveloped = envelop({
|
|
2089
|
+
plugins: [
|
|
2090
|
+
useSchema(schema),
|
|
2091
|
+
useCountPlugin(), // Add the count plugin here
|
|
2092
|
+
],
|
|
2093
|
+
});
|
|
2094
|
+
|
|
2095
|
+
// Use getEnveloped in your server setup
|
|
2096
|
+
```
|
|
2097
|
+
|
|
2098
|
+
### Example Response
|
|
2099
|
+
|
|
2100
|
+
When the plugin is correctly set up, your GraphQL response will include the count in the extensions:
|
|
2101
|
+
|
|
2102
|
+
```json
|
|
2103
|
+
{
|
|
2104
|
+
"data": {
|
|
2105
|
+
"series": [
|
|
2106
|
+
{
|
|
2107
|
+
"id": "1",
|
|
2108
|
+
"name": "Breaking Bad",
|
|
2109
|
+
"categories": ["Crime", "Drama"],
|
|
2110
|
+
"director": {
|
|
2111
|
+
"name": "Vince Gilligan",
|
|
2112
|
+
"country": "United States"
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
]
|
|
2116
|
+
},
|
|
2117
|
+
"extensions": {
|
|
2118
|
+
"count": 15
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
```
|
|
2122
|
+
|
|
2123
|
+
This setup allows you to efficiently manage and display pagination information in your GraphQL applications.
|
|
2124
|
+
|
|
2125
|
+
|