@simtlix/simfinity-js 1.6.0 → 1.8.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 +271 -0
- package/package.json +1 -1
- package/src/index.js +50 -13
- package/tests/prevent-collection-creation.test.js +3 -0
- package/tests/validated-scalar.test.js +2 -0
package/README.md
CHANGED
|
@@ -1232,6 +1232,11 @@ const newBook = await simfinity.saveObject('Book', {
|
|
|
1232
1232
|
const BookModel = simfinity.getModel(BookType);
|
|
1233
1233
|
const books = await BookModel.find({ author: 'Douglas Adams' });
|
|
1234
1234
|
|
|
1235
|
+
// Get the GraphQL type definition by name
|
|
1236
|
+
const UserType = simfinity.getType('User');
|
|
1237
|
+
console.log(UserType.name); // 'User'
|
|
1238
|
+
console.log(UserType.getFields()); // Access GraphQL fields
|
|
1239
|
+
|
|
1235
1240
|
// Get the input type for a GraphQL type
|
|
1236
1241
|
const BookInput = simfinity.getInputType(BookType);
|
|
1237
1242
|
```
|
|
@@ -2122,4 +2127,270 @@ When the plugin is correctly set up, your GraphQL response will include the coun
|
|
|
2122
2127
|
|
|
2123
2128
|
This setup allows you to efficiently manage and display pagination information in your GraphQL applications.
|
|
2124
2129
|
|
|
2130
|
+
## 📖 API Reference
|
|
2131
|
+
|
|
2132
|
+
Simfinity.js provides several utility methods for programmatic access to your GraphQL types and data:
|
|
2133
|
+
|
|
2134
|
+
### `getType(typeName)`
|
|
2135
|
+
|
|
2136
|
+
Retrieves a GraphQL type definition from the internal types registry.
|
|
2137
|
+
|
|
2138
|
+
**Parameters:**
|
|
2139
|
+
- `typeName` (string | GraphQLObjectType): The name of the type or a GraphQL type object
|
|
2140
|
+
|
|
2141
|
+
**Returns:**
|
|
2142
|
+
- `GraphQLObjectType | null`: The GraphQL type definition, or null if not found
|
|
2143
|
+
|
|
2144
|
+
**Examples:**
|
|
2145
|
+
|
|
2146
|
+
```javascript
|
|
2147
|
+
import { getType } from '@simtlix/simfinity-js';
|
|
2148
|
+
|
|
2149
|
+
// Get type by string name
|
|
2150
|
+
const UserType = getType('User');
|
|
2151
|
+
if (UserType) {
|
|
2152
|
+
console.log(UserType.name); // 'User'
|
|
2153
|
+
|
|
2154
|
+
// Access field definitions
|
|
2155
|
+
const fields = UserType.getFields();
|
|
2156
|
+
console.log(Object.keys(fields)); // ['id', 'name', 'email', ...]
|
|
2157
|
+
|
|
2158
|
+
// Check specific field
|
|
2159
|
+
const nameField = fields.name;
|
|
2160
|
+
console.log(nameField.type); // GraphQLString
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// Get type by GraphQL type object
|
|
2164
|
+
const BookType = getType(SomeBookType);
|
|
2165
|
+
|
|
2166
|
+
// Safe access - returns null if not found
|
|
2167
|
+
const nonExistentType = getType('NonExistent');
|
|
2168
|
+
console.log(nonExistentType); // null
|
|
2169
|
+
```
|
|
2170
|
+
|
|
2171
|
+
**Use Cases:**
|
|
2172
|
+
- **Type introspection**: Examine type definitions programmatically
|
|
2173
|
+
- **Dynamic schema analysis**: Build tools that analyze your GraphQL schema
|
|
2174
|
+
- **Runtime type checking**: Validate types exist before operations
|
|
2175
|
+
- **Admin interfaces**: Build dynamic forms based on type definitions
|
|
2176
|
+
- **Circular reference resolution**: Prevent import cycles when types reference each other
|
|
2177
|
+
|
|
2178
|
+
### Preventing Circular References with `getType`
|
|
2179
|
+
|
|
2180
|
+
When you have types that reference each other (like User and Group), using `getType` prevents circular import issues:
|
|
2181
|
+
|
|
2182
|
+
```javascript
|
|
2183
|
+
import { GraphQLObjectType, GraphQLID, GraphQLString, GraphQLList } from 'graphql';
|
|
2184
|
+
import { getType } from '@simtlix/simfinity-js';
|
|
2185
|
+
|
|
2186
|
+
// User type that references Group
|
|
2187
|
+
const UserType = new GraphQLObjectType({
|
|
2188
|
+
name: 'User',
|
|
2189
|
+
fields: () => ({
|
|
2190
|
+
id: { type: GraphQLID },
|
|
2191
|
+
name: { type: GraphQLString },
|
|
2192
|
+
email: { type: GraphQLString },
|
|
2193
|
+
|
|
2194
|
+
// Reference Group type by name to avoid circular imports
|
|
2195
|
+
groups: {
|
|
2196
|
+
type: new GraphQLList(() => getType('Group')), // Use getType instead of direct import
|
|
2197
|
+
extensions: {
|
|
2198
|
+
relation: {
|
|
2199
|
+
connectionField: 'members',
|
|
2200
|
+
displayField: 'name'
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
},
|
|
2204
|
+
|
|
2205
|
+
// Single group reference
|
|
2206
|
+
primaryGroup: {
|
|
2207
|
+
type: () => getType('Group'), // Lazy evaluation with getType
|
|
2208
|
+
extensions: {
|
|
2209
|
+
relation: {
|
|
2210
|
+
connectionField: 'primaryGroupId',
|
|
2211
|
+
displayField: 'name'
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
})
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
// Group type that references User
|
|
2219
|
+
const GroupType = new GraphQLObjectType({
|
|
2220
|
+
name: 'Group',
|
|
2221
|
+
fields: () => ({
|
|
2222
|
+
id: { type: GraphQLID },
|
|
2223
|
+
name: { type: GraphQLString },
|
|
2224
|
+
description: { type: GraphQLString },
|
|
2225
|
+
|
|
2226
|
+
// Reference User type by name to avoid circular imports
|
|
2227
|
+
members: {
|
|
2228
|
+
type: new GraphQLList(() => getType('User')), // Use getType instead of direct import
|
|
2229
|
+
extensions: {
|
|
2230
|
+
relation: {
|
|
2231
|
+
connectionField: 'groups',
|
|
2232
|
+
displayField: 'name'
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
},
|
|
2236
|
+
|
|
2237
|
+
// Single user reference (admin)
|
|
2238
|
+
admin: {
|
|
2239
|
+
type: () => getType('User'), // Lazy evaluation with getType
|
|
2240
|
+
extensions: {
|
|
2241
|
+
relation: {
|
|
2242
|
+
connectionField: 'adminId',
|
|
2243
|
+
displayField: 'name'
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
})
|
|
2248
|
+
});
|
|
2249
|
+
|
|
2250
|
+
// Register types with simfinity
|
|
2251
|
+
simfinity.connect(null, UserType, 'user', 'users');
|
|
2252
|
+
simfinity.connect(null, GroupType, 'group', 'groups');
|
|
2253
|
+
|
|
2254
|
+
// Create schema - resolvers will be auto-generated for all relationships
|
|
2255
|
+
const schema = simfinity.createSchema();
|
|
2256
|
+
```
|
|
2257
|
+
|
|
2258
|
+
**Benefits of this approach:**
|
|
2259
|
+
|
|
2260
|
+
1. **🔄 No Circular Imports**: Each file can import `getType` without importing other type definitions
|
|
2261
|
+
2. **⚡ Lazy Resolution**: Types are resolved at schema creation time when all types are registered
|
|
2262
|
+
3. **🛡️ Type Safety**: Still maintains GraphQL type checking and validation
|
|
2263
|
+
4. **🧹 Clean Architecture**: Separates type definitions from type relationships
|
|
2264
|
+
5. **📦 Better Modularity**: Each type can be in its own file without import dependencies
|
|
2265
|
+
|
|
2266
|
+
**File Structure Example:**
|
|
2267
|
+
|
|
2268
|
+
```
|
|
2269
|
+
types/
|
|
2270
|
+
├── User.js // Defines UserType using getType('Group')
|
|
2271
|
+
├── Group.js // Defines GroupType using getType('User')
|
|
2272
|
+
└── index.js // Registers all types and creates schema
|
|
2273
|
+
```
|
|
2274
|
+
|
|
2275
|
+
```javascript
|
|
2276
|
+
// types/User.js
|
|
2277
|
+
import { GraphQLObjectType, GraphQLID, GraphQLString, GraphQLList } from 'graphql';
|
|
2278
|
+
import { getType } from '@simtlix/simfinity-js';
|
|
2279
|
+
|
|
2280
|
+
export const UserType = new GraphQLObjectType({
|
|
2281
|
+
name: 'User',
|
|
2282
|
+
fields: () => ({
|
|
2283
|
+
id: { type: GraphQLID },
|
|
2284
|
+
name: { type: GraphQLString },
|
|
2285
|
+
groups: {
|
|
2286
|
+
type: new GraphQLList(() => getType('Group')),
|
|
2287
|
+
extensions: { relation: { connectionField: 'members' } }
|
|
2288
|
+
}
|
|
2289
|
+
})
|
|
2290
|
+
});
|
|
2291
|
+
|
|
2292
|
+
// types/Group.js
|
|
2293
|
+
import { GraphQLObjectType, GraphQLID, GraphQLString, GraphQLList } from 'graphql';
|
|
2294
|
+
import { getType } from '@simtlix/simfinity-js';
|
|
2295
|
+
|
|
2296
|
+
export const GroupType = new GraphQLObjectType({
|
|
2297
|
+
name: 'Group',
|
|
2298
|
+
fields: () => ({
|
|
2299
|
+
id: { type: GraphQLID },
|
|
2300
|
+
name: { type: GraphQLString },
|
|
2301
|
+
members: {
|
|
2302
|
+
type: new GraphQLList(() => getType('User')),
|
|
2303
|
+
extensions: { relation: { connectionField: 'groups' } }
|
|
2304
|
+
}
|
|
2305
|
+
})
|
|
2306
|
+
});
|
|
2307
|
+
|
|
2308
|
+
// types/index.js
|
|
2309
|
+
import { UserType } from './User.js';
|
|
2310
|
+
import { GroupType } from './Group.js';
|
|
2311
|
+
import simfinity from '@simtlix/simfinity-js';
|
|
2312
|
+
|
|
2313
|
+
// Register all types
|
|
2314
|
+
simfinity.connect(null, UserType, 'user', 'users');
|
|
2315
|
+
simfinity.connect(null, GroupType, 'group', 'groups');
|
|
2316
|
+
|
|
2317
|
+
// Create schema with auto-generated resolvers
|
|
2318
|
+
export const schema = simfinity.createSchema();
|
|
2319
|
+
```
|
|
2320
|
+
|
|
2321
|
+
### `getModel(gqltype)`
|
|
2322
|
+
|
|
2323
|
+
Retrieves the Mongoose model associated with a GraphQL type.
|
|
2324
|
+
|
|
2325
|
+
**Parameters:**
|
|
2326
|
+
- `gqltype` (GraphQLObjectType): The GraphQL type object
|
|
2327
|
+
|
|
2328
|
+
**Returns:**
|
|
2329
|
+
- `MongooseModel`: The associated Mongoose model
|
|
2330
|
+
|
|
2331
|
+
**Example:**
|
|
2332
|
+
|
|
2333
|
+
```javascript
|
|
2334
|
+
const BookModel = simfinity.getModel(BookType);
|
|
2335
|
+
const books = await BookModel.find({ author: 'Douglas Adams' });
|
|
2336
|
+
```
|
|
2337
|
+
|
|
2338
|
+
### `getInputType(type)`
|
|
2339
|
+
|
|
2340
|
+
Retrieves the input type for mutations associated with a GraphQL type.
|
|
2341
|
+
|
|
2342
|
+
**Parameters:**
|
|
2343
|
+
- `type` (GraphQLObjectType): The GraphQL type object
|
|
2344
|
+
|
|
2345
|
+
**Returns:**
|
|
2346
|
+
- `GraphQLInputObjectType`: The input type for mutations
|
|
2347
|
+
|
|
2348
|
+
**Example:**
|
|
2349
|
+
|
|
2350
|
+
```javascript
|
|
2351
|
+
const BookInput = simfinity.getInputType(BookType);
|
|
2352
|
+
console.log(BookInput.getFields()); // Input fields for mutations
|
|
2353
|
+
```
|
|
2354
|
+
|
|
2355
|
+
### `saveObject(typeName, args, session?)`
|
|
2356
|
+
|
|
2357
|
+
Programmatically save an object outside of GraphQL mutations.
|
|
2358
|
+
|
|
2359
|
+
**Parameters:**
|
|
2360
|
+
- `typeName` (string): The name of the GraphQL type
|
|
2361
|
+
- `args` (object): The data to save
|
|
2362
|
+
- `session` (MongooseSession, optional): Database session for transactions
|
|
2363
|
+
|
|
2364
|
+
**Returns:**
|
|
2365
|
+
- `Promise<object>`: The saved object
|
|
2366
|
+
|
|
2367
|
+
**Example:**
|
|
2368
|
+
|
|
2369
|
+
```javascript
|
|
2370
|
+
const newBook = await simfinity.saveObject('Book', {
|
|
2371
|
+
title: 'New Book',
|
|
2372
|
+
author: 'Author Name'
|
|
2373
|
+
}, session);
|
|
2374
|
+
```
|
|
2375
|
+
|
|
2376
|
+
### `createSchema(includedQueryTypes?, includedMutationTypes?, includedCustomMutations?)`
|
|
2377
|
+
|
|
2378
|
+
Creates the final GraphQL schema with all connected types.
|
|
2379
|
+
|
|
2380
|
+
**Parameters:**
|
|
2381
|
+
- `includedQueryTypes` (array, optional): Limit query types to include
|
|
2382
|
+
- `includedMutationTypes` (array, optional): Limit mutation types to include
|
|
2383
|
+
- `includedCustomMutations` (array, optional): Limit custom mutations to include
|
|
2384
|
+
|
|
2385
|
+
**Returns:**
|
|
2386
|
+
- `GraphQLSchema`: The complete GraphQL schema
|
|
2387
|
+
|
|
2388
|
+
**Example:**
|
|
2389
|
+
|
|
2390
|
+
```javascript
|
|
2391
|
+
const schema = simfinity.createSchema();
|
|
2392
|
+
```
|
|
2393
|
+
|
|
2394
|
+
*Built with ❤️ by [Simtlix](https://github.com/simtlix)*
|
|
2395
|
+
|
|
2125
2396
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1535,13 +1535,54 @@ const buildRootQuery = (name, includedTypes) => {
|
|
|
1535
1535
|
/* Creating a new GraphQL Schema, with options query which defines query
|
|
1536
1536
|
we will allow users to use when they are making request. */
|
|
1537
1537
|
export const createSchema = (includedQueryTypes,
|
|
1538
|
-
includedMutationTypes, includedCustomMutations) =>
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1538
|
+
includedMutationTypes, includedCustomMutations) => {
|
|
1539
|
+
|
|
1540
|
+
// Generate models for all registered types now that all types are available
|
|
1541
|
+
Object.values(typesDict.types).forEach(typeInfo => {
|
|
1542
|
+
if (typeInfo.gqltype && !typeInfo.model) {
|
|
1543
|
+
if (typeInfo.endpoint) {
|
|
1544
|
+
// Generate model with collection for endpoint types (types registered with connect)
|
|
1545
|
+
typeInfo.model = generateModel(typeInfo.gqltype, typeInfo.onModelCreated);
|
|
1546
|
+
} else if (typeInfo.needsModel) {
|
|
1547
|
+
// Generate model without collection for no-endpoint types that need models (addNoEndpointType)
|
|
1548
|
+
typeInfo.model = generateModelWithoutCollection(typeInfo.gqltype, null);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
// Also update the typesDictForUpdate with the generated models
|
|
1554
|
+
Object.keys(typesDict.types).forEach(typeName => {
|
|
1555
|
+
if (typesDictForUpdate.types[typeName]) {
|
|
1556
|
+
typesDictForUpdate.types[typeName].model = typesDict.types[typeName].model;
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
// Auto-generate resolvers for all registered types now that all types are available
|
|
1561
|
+
Object.values(typesDict.types).forEach(typeInfo => {
|
|
1562
|
+
if (typeInfo.gqltype) {
|
|
1563
|
+
autoGenerateResolvers(typeInfo.gqltype);
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
|
|
1567
|
+
return new GraphQLSchema({
|
|
1568
|
+
query: buildRootQuery('RootQueryType', includedQueryTypes),
|
|
1569
|
+
mutation: buildMutation('Mutation', includedMutationTypes, includedCustomMutations),
|
|
1570
|
+
});
|
|
1571
|
+
};
|
|
1542
1572
|
|
|
1543
1573
|
export const getModel = (gqltype) => typesDict.types[gqltype.name].model;
|
|
1544
1574
|
|
|
1575
|
+
export const getType = (typeName) => {
|
|
1576
|
+
if (typeof typeName === 'string') {
|
|
1577
|
+
return typesDict.types[typeName]?.gqltype;
|
|
1578
|
+
}
|
|
1579
|
+
// If it's already a GraphQL type object, get by its name
|
|
1580
|
+
if (typeName && typeName.name) {
|
|
1581
|
+
return typesDict.types[typeName.name]?.gqltype;
|
|
1582
|
+
}
|
|
1583
|
+
return null;
|
|
1584
|
+
};
|
|
1585
|
+
|
|
1545
1586
|
export const registerMutation = (name, description, inputModel, outputModel, callback) => {
|
|
1546
1587
|
registeredMutations[name] = {
|
|
1547
1588
|
description,
|
|
@@ -1607,19 +1648,17 @@ export const connect = (model, gqltype, simpleEntityEndpointName,
|
|
|
1607
1648
|
gqltype,
|
|
1608
1649
|
};
|
|
1609
1650
|
typesDict.types[gqltype.name] = {
|
|
1610
|
-
model: model
|
|
1651
|
+
model: model, // Will be generated later in createSchema if not provided
|
|
1611
1652
|
gqltype,
|
|
1612
1653
|
simpleEntityEndpointName,
|
|
1613
1654
|
listEntitiesEndpointName,
|
|
1614
1655
|
endpoint: true,
|
|
1615
1656
|
controller,
|
|
1616
1657
|
stateMachine,
|
|
1658
|
+
onModelCreated, // Store the callback for later use
|
|
1617
1659
|
};
|
|
1618
1660
|
|
|
1619
1661
|
typesDictForUpdate.types[gqltype.name] = { ...typesDict.types[gqltype.name] };
|
|
1620
|
-
|
|
1621
|
-
// Auto-generate resolve methods for relationship fields if not already defined
|
|
1622
|
-
autoGenerateResolvers(gqltype);
|
|
1623
1662
|
};
|
|
1624
1663
|
|
|
1625
1664
|
export const addNoEndpointType = (gqltype) => {
|
|
@@ -1643,14 +1682,12 @@ export const addNoEndpointType = (gqltype) => {
|
|
|
1643
1682
|
typesDict.types[gqltype.name] = {
|
|
1644
1683
|
gqltype,
|
|
1645
1684
|
endpoint: false,
|
|
1646
|
-
//
|
|
1647
|
-
model:
|
|
1685
|
+
// Model will be generated later in createSchema if needed
|
|
1686
|
+
model: null,
|
|
1687
|
+
needsModel, // Store whether this type needs a model
|
|
1648
1688
|
};
|
|
1649
1689
|
|
|
1650
1690
|
typesDictForUpdate.types[gqltype.name] = { ...typesDict.types[gqltype.name] };
|
|
1651
|
-
|
|
1652
|
-
// Auto-generate resolve methods for relationship fields if not already defined
|
|
1653
|
-
autoGenerateResolvers(gqltype);
|
|
1654
1691
|
};
|
|
1655
1692
|
|
|
1656
1693
|
export { createValidatedScalar };
|
|
@@ -28,6 +28,7 @@ describe('preventCreatingCollection option', () => {
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
simfinity.connect(null, TestType, 'testTypeDefault', 'testTypesDefault');
|
|
31
|
+
simfinity.createSchema(); // Models are now generated during schema creation
|
|
31
32
|
expect(createCollectionSpy).toHaveBeenCalledTimes(1);
|
|
32
33
|
});
|
|
33
34
|
|
|
@@ -43,6 +44,7 @@ describe('preventCreatingCollection option', () => {
|
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
simfinity.connect(null, TestType, 'testTypePrevent', 'testTypesPrevent');
|
|
47
|
+
simfinity.createSchema(); // Models are now generated during schema creation
|
|
46
48
|
expect(createCollectionSpy).not.toHaveBeenCalled();
|
|
47
49
|
});
|
|
48
50
|
|
|
@@ -59,6 +61,7 @@ describe('preventCreatingCollection option', () => {
|
|
|
59
61
|
});
|
|
60
62
|
|
|
61
63
|
simfinity.connect(null, TestType, 'testTypeAllow', 'testTypesAllow');
|
|
64
|
+
simfinity.createSchema(); // Models are now generated during schema creation
|
|
62
65
|
expect(createCollectionSpy).toHaveBeenCalledTimes(1);
|
|
63
66
|
});
|
|
64
67
|
});
|
|
@@ -117,6 +117,7 @@ describe('Custom Validated Scalar Types', () => {
|
|
|
117
117
|
|
|
118
118
|
beforeAll(() => {
|
|
119
119
|
simfinity.connect(null, UserType, 'user', 'users');
|
|
120
|
+
simfinity.createSchema(); // Models are now generated during schema creation
|
|
120
121
|
UserModel = simfinity.getModel(UserType);
|
|
121
122
|
});
|
|
122
123
|
|
|
@@ -150,6 +151,7 @@ describe('Custom Validated Scalar Types', () => {
|
|
|
150
151
|
}),
|
|
151
152
|
});
|
|
152
153
|
simfinity.connect(null, UserWithUniqueType, 'userWithUnique', 'usersWithUnique');
|
|
154
|
+
simfinity.createSchema(); // Models are now generated during schema creation
|
|
153
155
|
const UserWithUniqueModel = simfinity.getModel(UserWithUniqueType);
|
|
154
156
|
const schema = UserWithUniqueModel.schema.obj;
|
|
155
157
|
|